功能:扩展云管资源运维、账号授权及监控管理
This commit is contained in:
1 parent
eef485306f
commit
6134d49c16
87 files changed
+29498
-126
No files matched your search
@@ -12,6 +12,8 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"vdi-cloud/platform/internal/config"
|
||||
"vdi-cloud/platform/internal/model"
|
||||
@@ -20,10 +22,13 @@ import (
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Config config.Config
|
||||
DB *sqlx.DB
|
||||
PVE *proxmox.Client
|
||||
Router *gin.Engine
|
||||
Config config.Config
|
||||
DB *sqlx.DB
|
||||
PVE *proxmox.Client
|
||||
Router *gin.Engine
|
||||
computeMu sync.Mutex
|
||||
computeRevision string
|
||||
computeRuntime atomic.Pointer[proxmox.Client]
|
||||
}
|
||||
type claims struct {
|
||||
AdminID string `json:"adminId"`
|
||||
@@ -34,6 +39,7 @@ func New(cfg config.Config, db *sqlx.DB, pve *proxmox.Client) *Server {
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
s := &Server{Config: cfg, DB: db, PVE: pve}
|
||||
r := gin.New()
|
||||
_ = r.SetTrustedProxies(cfg.TrustedProxies)
|
||||
r.Use(gin.Recovery(), func(c *gin.Context) {
|
||||
c.Set("requestId", store.ID("req_"))
|
||||
c.Header("X-Request-ID", c.GetString("requestId"))
|
||||
@@ -56,32 +62,90 @@ func New(cfg config.Config, db *sqlx.DB, pve *proxmox.Client) *Server {
|
||||
t := r.Group("/api/v1/terminal", s.deviceAuth)
|
||||
t.POST("/heartbeat", s.heartbeat)
|
||||
t.GET("/config", s.deviceConfig)
|
||||
t.POST("/config/ack", s.policyAck)
|
||||
t.GET("/desktops", s.deviceDesktops)
|
||||
t.GET("/commands", s.deviceCommands)
|
||||
t.POST("/commands/:id/ack", s.deviceAck)
|
||||
t.POST("/connect-ticket", s.createTicket)
|
||||
t.POST("/connect-ticket/consume", s.consumeTicket)
|
||||
t.POST("/session", s.deviceSession)
|
||||
a := r.Group("/api/v1", s.adminAuth)
|
||||
a := r.Group("/api/v1", s.adminAuth, s.refreshComputeRequest)
|
||||
a.GET("/auth/me", func(c *gin.Context) { c.JSON(200, c.MustGet("admin")) })
|
||||
a.POST("/auth/logout", s.logout)
|
||||
a.POST("/auth/password", s.changeOwnPassword)
|
||||
a.GET("/administrators", s.allow("super_admin"), s.administratorList)
|
||||
a.POST("/administrators", s.allow("super_admin"), s.createAdministrator)
|
||||
a.PUT("/administrators/:id", s.allow("super_admin"), s.updateAdministrator)
|
||||
a.POST("/administrators/:id/password", s.allow("super_admin"), s.resetAdministratorPassword)
|
||||
a.GET("/overview", s.overview)
|
||||
a.GET("/sessions/history", s.sessionHistory)
|
||||
a.GET("/monitor/series", s.resourceSeries)
|
||||
a.GET("/monitor/rules", s.alertRules)
|
||||
a.POST("/monitor/rules", s.allow("super_admin"), s.saveAlertRule)
|
||||
a.PUT("/monitor/rules/:id", s.allow("super_admin"), s.saveAlertRule)
|
||||
a.GET("/monitor/alerts", s.alerts)
|
||||
a.POST("/monitor/alerts/:id/acknowledge", s.allow("super_admin", "operator"), s.acknowledgeAlert)
|
||||
a.POST("/imports/preview", s.allow("super_admin"), s.previewImport)
|
||||
a.POST("/imports/commit", s.allow("super_admin"), s.commitImport)
|
||||
a.GET("/settings", s.settings)
|
||||
a.GET("/settings/pve", s.allow("super_admin"), s.getPVEConnection)
|
||||
a.POST("/settings/pve/check", s.allow("super_admin"), s.checkPVEConnection)
|
||||
a.PUT("/settings/pve", s.allow("super_admin"), s.savePVEConnection)
|
||||
a.GET("/desktops/:id/hardware", s.desktopHardware)
|
||||
a.GET("/desktops/:id/snapshots", s.snapshotView)
|
||||
a.POST("/desktops/:id/snapshots/check", s.allow("super_admin"), s.checkSnapshot)
|
||||
a.POST("/desktops/:id/snapshots/actions", s.allow("super_admin"), s.queueSnapshot)
|
||||
a.POST("/tasks/:id/snapshot-reconcile", s.allow("super_admin"), s.reconcileSnapshot)
|
||||
a.POST("/desktops/:id/hardware/check", s.allow("super_admin"), s.checkHardware)
|
||||
a.POST("/desktops/:id/hardware", s.allow("super_admin"), s.queueHardware)
|
||||
a.GET("/resources", s.resources)
|
||||
a.GET("/resources/gpus", s.gpuInventory)
|
||||
a.GET("/resources/vms", s.discoverVMs)
|
||||
a.GET("/resources/templates", s.discoverTemplates)
|
||||
a.GET("/resources/storage", s.storageList)
|
||||
a.GET("/templates", s.templateList)
|
||||
a.POST("/templates", s.allow("super_admin"), s.registerTemplate)
|
||||
a.PATCH("/templates/:id", s.allow("super_admin"), s.updateTemplate)
|
||||
a.POST("/templates/:id/validate", s.allow("super_admin"), s.validateTemplate)
|
||||
a.GET("/pools", s.poolList)
|
||||
a.POST("/pools", s.allow("super_admin"), s.savePool)
|
||||
a.PUT("/pools/:id", s.allow("super_admin"), s.savePool)
|
||||
a.PUT("/pools/:id/members", s.allow("super_admin"), s.poolMembers)
|
||||
a.POST("/desktops/clone/check", s.allow("super_admin"), s.checkClone)
|
||||
a.POST("/desktops/clone", s.allow("super_admin"), s.cloneDesktop)
|
||||
a.POST("/resources/sync", s.allow("super_admin"), s.syncResources)
|
||||
a.GET("/desktop-batches", s.powerBatches)
|
||||
a.GET("/desktop-batches/:id", s.getPowerBatch)
|
||||
a.POST("/desktop-batches/check", s.allow("super_admin", "operator"), s.checkPowerBatch)
|
||||
a.POST("/desktop-batches", s.allow("super_admin", "operator"), s.queuePowerBatch)
|
||||
a.POST("/tasks/:id/batch-power-reconcile", s.allow("super_admin", "operator"), s.reconcileBatchPower)
|
||||
a.GET("/desktops", s.desktops)
|
||||
a.POST("/desktops/import", s.allow("super_admin"), s.importDesktop)
|
||||
a.GET("/desktops/:id", s.desktop)
|
||||
a.PUT("/desktops/:id/connection", s.allow("super_admin"), s.configureDesktop)
|
||||
a.POST("/desktops/:id/actions", s.allow("super_admin", "operator"), s.desktopAction)
|
||||
a.GET("/terminals", s.terminals)
|
||||
a.GET("/terminals/:id/metrics", s.terminalMetrics)
|
||||
a.GET("/terminal-policy-templates", s.policyTemplates)
|
||||
a.POST("/terminal-policy-templates", s.allow("super_admin"), s.createPolicyTemplate)
|
||||
a.GET("/terminal-policy-states", s.policyStates)
|
||||
a.GET("/terminal-policy-deployments", s.policyDeployments)
|
||||
a.POST("/terminal-policy-deployments", s.allow("super_admin"), s.deployPolicy)
|
||||
a.GET("/terminal-policy-deployments/:id/results", s.policyResults)
|
||||
a.POST("/terminals", s.allow("super_admin"), s.enroll)
|
||||
a.PUT("/terminals/:id", s.allow("super_admin"), s.updateTerminal)
|
||||
a.POST("/terminals/:id/disable", s.allow("super_admin"), s.disableTerminal)
|
||||
a.POST("/terminals/:id/reactivate", s.allow("super_admin"), s.reactivateTerminal)
|
||||
a.POST("/terminals/:id/commands", s.allow("super_admin", "operator"), s.terminalCommand)
|
||||
a.GET("/users", s.users)
|
||||
a.POST("/users", s.allow("super_admin"), s.createUser)
|
||||
a.PUT("/users/:id", s.allow("super_admin"), s.updateUser)
|
||||
a.POST("/users/batch-bindings", s.allow("super_admin"), s.batchBindings)
|
||||
a.PUT("/users/:id/bindings", s.allow("super_admin"), s.bindings)
|
||||
a.GET("/tasks", s.tasks)
|
||||
a.GET("/tasks/:id", s.task)
|
||||
a.POST("/tasks/:id/hardware-reconcile", s.allow("super_admin"), s.reconcileHardware)
|
||||
a.POST("/tasks/:id/reconcile", s.allow("super_admin"), s.reconcileProvision)
|
||||
a.GET("/tasks/:id/diagnostics", s.allow("super_admin", "operator"), s.taskDiagnostics)
|
||||
a.GET("/audit-logs", s.audits)
|
||||
r.NoRoute(func(c *gin.Context) { s.fail(c, 404, "not_found", "接口不存在") })
|
||||
@@ -139,7 +203,7 @@ func (s *Server) adminAuth(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
var a model.Administrator
|
||||
err = s.DB.GetContext(c.Request.Context(), &a, `SELECT a.* FROM administrators a JOIN admin_sessions x ON x.admin_id=a.id WHERE x.id=$1 AND a.id=$2 AND x.expires_at>now()`, cl.ID, cl.AdminID)
|
||||
err = s.DB.GetContext(c.Request.Context(), &a, `SELECT a.* FROM administrators a JOIN admin_sessions x ON x.admin_id=a.id WHERE x.id=$1 AND a.id=$2 AND a.enabled AND x.expires_at>now()`, cl.ID, cl.AdminID)
|
||||
if err != nil {
|
||||
s.fail(c, 401, "unauthorized", "登录已失效,请重新登录")
|
||||
return
|
||||
@@ -156,8 +220,18 @@ func (s *Server) login(c *gin.Context) {
|
||||
if !s.decode(c, &input) {
|
||||
return
|
||||
}
|
||||
if !s.allowLoginAttempt(c, input.Username) {
|
||||
return
|
||||
}
|
||||
var a model.Administrator
|
||||
err := s.DB.GetContext(c.Request.Context(), &a, "SELECT * FROM administrators WHERE username=$1", input.Username)
|
||||
tx, err := s.DB.BeginTxx(c.Request.Context(), nil)
|
||||
if err != nil {
|
||||
s.dbError(c, err)
|
||||
return
|
||||
}
|
||||
defer tx.Rollback()
|
||||
// Serialize session creation against account disable, role changes and password reset.
|
||||
err = tx.GetContext(c.Request.Context(), &a, "SELECT * FROM administrators WHERE username=$1 AND enabled FOR SHARE", input.Username)
|
||||
if err != nil || bcrypt.CompareHashAndPassword([]byte(a.PasswordHash), []byte(input.Password)) != nil {
|
||||
s.fail(c, 401, "invalid_credentials", "用户名或密码错误")
|
||||
return
|
||||
@@ -170,7 +244,11 @@ func (s *Server) login(c *gin.Context) {
|
||||
s.dbError(c, err)
|
||||
return
|
||||
}
|
||||
if _, err = s.DB.ExecContext(c.Request.Context(), "INSERT INTO admin_sessions VALUES($1,$2,$3)", id, a.ID, expires); err != nil {
|
||||
if _, err = tx.ExecContext(c.Request.Context(), "INSERT INTO admin_sessions VALUES($1,$2,$3)", id, a.ID, expires); err != nil {
|
||||
s.dbError(c, err)
|
||||
return
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
s.dbError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user