mirror of
https://github.com/gl-inet/glkvm-cloud.git
synced 2026-09-16 15:45:16 +00:00
c6c57e450c
Signed-off-by: GL.iNet-Yongping.Xie <yongping.xie@gl-inet.com>
282 lines
7.4 KiB
Go
Executable File
282 lines
7.4 KiB
Go
Executable File
package handler
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"rttys/internal/domain/device"
|
|
"rttys/internal/domain/identity"
|
|
"rttys/internal/http/dto"
|
|
"rttys/internal/http/middleware"
|
|
"rttys/internal/store/sqlite"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type DeviceHandler struct {
|
|
devSvc *device.Service
|
|
groupRepo *sqlite.GroupRepo
|
|
relationsRepo *sqlite.RelationsRepo
|
|
}
|
|
|
|
func NewDeviceHandler(
|
|
devSvc *device.Service,
|
|
groupRepo *sqlite.GroupRepo,
|
|
relationsRepo *sqlite.RelationsRepo,
|
|
) *DeviceHandler {
|
|
return &DeviceHandler{
|
|
devSvc: devSvc,
|
|
groupRepo: groupRepo,
|
|
relationsRepo: relationsRepo,
|
|
}
|
|
}
|
|
|
|
// GET /api/devices
|
|
func (h *DeviceHandler) ListDevices(c *gin.Context) {
|
|
traceID := middleware.GetTraceID(c)
|
|
p := middleware.MustPrincipal(c)
|
|
|
|
var filterGroupID *int64
|
|
if raw := strings.TrimSpace(c.Query("groupId")); raw != "" {
|
|
id, err := strconv.ParseInt(raw, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInvalidArgument, "Invalid argument", map[string]any{
|
|
"field": "groupId",
|
|
}))
|
|
return
|
|
}
|
|
filterGroupID = &id
|
|
}
|
|
|
|
isAdmin := p.Role == identity.RoleAdmin
|
|
|
|
emptyResp := func() {
|
|
dto.Write(c, dto.Ok(traceID, dto.ListDevicesResp{
|
|
Items: []dto.Device{}, Page: 1, PageSize: 0, Total: 0,
|
|
}))
|
|
}
|
|
|
|
// Resolve visibility into a group-id restriction. nil = no restriction
|
|
// (admin sees all devices, including ungrouped).
|
|
var restrictGroups []int64
|
|
if isAdmin {
|
|
if filterGroupID != nil {
|
|
restrictGroups = []int64{*filterGroupID}
|
|
}
|
|
} else {
|
|
if h.groupRepo == nil {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInternalError, "Internal error", nil))
|
|
return
|
|
}
|
|
dgIDs, err := h.groupRepo.ListDeviceGroupIDsByUser(c.Request.Context(), p.UserID)
|
|
if err != nil || len(dgIDs) == 0 {
|
|
emptyResp()
|
|
return
|
|
}
|
|
if filterGroupID != nil {
|
|
allowed := false
|
|
for _, gid := range dgIDs {
|
|
if gid == *filterGroupID {
|
|
allowed = true
|
|
break
|
|
}
|
|
}
|
|
if !allowed {
|
|
emptyResp()
|
|
return
|
|
}
|
|
dgIDs = []int64{*filterGroupID}
|
|
}
|
|
restrictGroups = dgIDs
|
|
}
|
|
|
|
// Pagination params: pageSize omitted => return all (backward compatible).
|
|
page := 1
|
|
if v, err := strconv.Atoi(strings.TrimSpace(c.Query("page"))); err == nil && v > 0 {
|
|
page = v
|
|
}
|
|
pageSize := 0
|
|
if v, err := strconv.Atoi(strings.TrimSpace(c.Query("pageSize"))); err == nil && v > 0 {
|
|
pageSize = v
|
|
}
|
|
|
|
// Status filter: only accept known values, ignore anything else.
|
|
status := strings.ToLower(strings.TrimSpace(c.Query("status")))
|
|
switch status {
|
|
case "online", "offline", "disabled":
|
|
default:
|
|
status = ""
|
|
}
|
|
|
|
// All filtering/sorting/pagination is pushed to SQL. The search ':' is
|
|
// stripped to match the colon-less MAC stored in the DB, mirroring the
|
|
// previous client-side search behavior.
|
|
items, total, err := h.devSvc.ListPaged(c.Request.Context(), device.ListQuery{
|
|
RestrictGroups: restrictGroups,
|
|
Search: strings.ToLower(strings.ReplaceAll(strings.TrimSpace(c.Query("q")), ":", "")),
|
|
Unassigned: strings.EqualFold(strings.TrimSpace(c.Query("unassigned")), "true"),
|
|
Status: status,
|
|
SortBy: strings.TrimSpace(c.Query("sortBy")),
|
|
Order: strings.TrimSpace(c.Query("order")),
|
|
Page: page,
|
|
PageSize: pageSize,
|
|
})
|
|
if err != nil {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInternalError, "Internal error", nil))
|
|
return
|
|
}
|
|
|
|
out := make([]dto.Device, 0, len(items))
|
|
for _, d := range items {
|
|
var connectedTime int64
|
|
if d.LastSeenAt != nil {
|
|
connectedTime = *d.LastSeenAt
|
|
}
|
|
out = append(out, dto.Device{
|
|
ID: d.ID,
|
|
Ddns: d.Ddns,
|
|
Status: string(d.Status),
|
|
ConnectedTime: connectedTime,
|
|
IP: d.IP,
|
|
Mac: d.Mac,
|
|
Description: d.Description,
|
|
Client: d.Client,
|
|
DeviceGroupID: d.DeviceGroupID,
|
|
DeviceGroupName: d.GroupName,
|
|
})
|
|
}
|
|
|
|
respPageSize := int(total)
|
|
if pageSize > 0 {
|
|
respPageSize = pageSize
|
|
}
|
|
dto.Write(c, dto.Ok(traceID, dto.ListDevicesResp{
|
|
Items: out,
|
|
Page: page,
|
|
PageSize: respPageSize,
|
|
Total: int(total),
|
|
}))
|
|
}
|
|
|
|
type UpdateDeviceRequest struct {
|
|
Description *string `json:"description"`
|
|
}
|
|
|
|
// PUT /api/devices/:id
|
|
func (h *DeviceHandler) UpdateDevice(c *gin.Context) {
|
|
traceID := middleware.GetTraceID(c)
|
|
idStr := strings.TrimSpace(c.Param("id"))
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInvalidArgument, "Invalid argument", map[string]any{
|
|
"field": "id",
|
|
}))
|
|
return
|
|
}
|
|
|
|
var req UpdateDeviceRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil && !errors.Is(err, io.EOF) {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInvalidArgument, "Invalid argument", map[string]any{
|
|
"field": "description",
|
|
"error": err.Error(),
|
|
}))
|
|
return
|
|
}
|
|
|
|
db := sqlite.MustContainer().Gorm.WithContext(c.Request.Context())
|
|
|
|
var row struct {
|
|
Description string `gorm:"column:description"`
|
|
}
|
|
tx := db.Table("devices").Select("description").Where("id = ?", id).First(&row)
|
|
if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeNotFound, "Device not found", nil))
|
|
return
|
|
}
|
|
if tx.Error != nil {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInternalError, "Internal error", map[string]any{
|
|
"detail": tx.Error.Error(),
|
|
}))
|
|
return
|
|
}
|
|
|
|
newDesc := row.Description
|
|
if req.Description != nil {
|
|
newDesc = *req.Description
|
|
}
|
|
|
|
if req.Description != nil {
|
|
res := db.Exec(
|
|
`UPDATE devices SET description=? WHERE id=?`,
|
|
newDesc, id,
|
|
)
|
|
if res.Error != nil {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInternalError, "Internal error", map[string]any{
|
|
"detail": res.Error.Error(),
|
|
}))
|
|
return
|
|
}
|
|
if res.RowsAffected == 0 {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeNotFound, "Device not found", nil))
|
|
return
|
|
}
|
|
}
|
|
|
|
dto.Write(c, dto.Ok(traceID, struct{}{}))
|
|
}
|
|
|
|
// DELETE /api/devices/:id
|
|
func (h *DeviceHandler) DeleteDevice(c *gin.Context) {
|
|
traceID := middleware.GetTraceID(c)
|
|
idStr := strings.TrimSpace(c.Param("id"))
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil || id <= 0 {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInvalidArgument, "Invalid argument", map[string]any{
|
|
"field": "id",
|
|
}))
|
|
return
|
|
}
|
|
|
|
db := sqlite.MustContainer().Gorm.WithContext(c.Request.Context())
|
|
res := db.Exec(`DELETE FROM devices WHERE id=?`, id)
|
|
if res.Error != nil {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInternalError, "Internal error", map[string]any{
|
|
"detail": res.Error.Error(),
|
|
}))
|
|
return
|
|
}
|
|
if res.RowsAffected == 0 {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeNotFound, "Device not found", nil))
|
|
return
|
|
}
|
|
|
|
dto.Write(c, dto.Ok(traceID, struct{}{}))
|
|
}
|
|
|
|
// POST /api/devices/move-to-device-group
|
|
func (h *DeviceHandler) MoveToDeviceGroup(c *gin.Context) {
|
|
traceID := middleware.GetTraceID(c)
|
|
_ = middleware.MustPrincipal(c)
|
|
|
|
if h.relationsRepo == nil {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInternalError, "Internal error", nil))
|
|
return
|
|
}
|
|
|
|
var req dto.MoveDevicesToGroupReq
|
|
if err := c.ShouldBindJSON(&req); err != nil || req.GroupID <= 0 {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInvalidArgument, "Invalid argument", map[string]any{"field": "groupId"}))
|
|
return
|
|
}
|
|
|
|
if err := h.relationsRepo.AddDevicesToGroup(c.Request.Context(), req.GroupID, req.DeviceIDs); err != nil {
|
|
dto.Write(c, dto.Err(traceID, dto.CodeInternalError, "Internal error", map[string]any{"detail": err.Error()}))
|
|
return
|
|
}
|
|
|
|
dto.Write(c, dto.Ok(traceID, dto.MoveDevicesToGroupResp{}))
|
|
}
|