feat(sandbox): enhance sandbox response structure and filtering
- Introduced new fields in the sandboxResponse struct, including Kind, DisplayName, Mode, and Addr, to provide more detailed information about sandbox entities. - Added hostToResponse function to convert registry.NodeSnapshot to sandboxResponse, improving the handling of host entries. - Implemented filtering logic in handleList to differentiate between box and host entries based on ownership and capabilities. - Enhanced sorting of the response list by LastActive timestamp for better organization of results. Made-with: Cursor
This commit is contained in:
parent
2dc4307175
commit
6407b8531f
1 changed files with 141 additions and 19 deletions
|
|
@ -3,6 +3,7 @@ package sandbox
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -10,6 +11,8 @@ import (
|
||||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||||
"github.com/yaoapp/yao/openapi/response"
|
"github.com/yaoapp/yao/openapi/response"
|
||||||
sandboxv2 "github.com/yaoapp/yao/sandbox/v2"
|
sandboxv2 "github.com/yaoapp/yao/sandbox/v2"
|
||||||
|
"github.com/yaoapp/yao/tai"
|
||||||
|
"github.com/yaoapp/yao/tai/registry"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AttachManage registers sandbox management CRUD routes on the given group.
|
// AttachManage registers sandbox management CRUD routes on the given group.
|
||||||
|
|
@ -82,14 +85,18 @@ type sandboxSystemInfo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type sandboxResponse struct {
|
type sandboxResponse struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
ContainerID string `json:"container_id"`
|
DisplayName string `json:"display_name"`
|
||||||
|
ContainerID string `json:"container_id,omitempty"`
|
||||||
NodeID string `json:"node_id"`
|
NodeID string `json:"node_id"`
|
||||||
Owner string `json:"owner"`
|
Owner string `json:"owner"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Policy string `json:"policy"`
|
Policy string `json:"policy,omitempty"`
|
||||||
Labels map[string]string `json:"labels,omitempty"`
|
Labels map[string]string `json:"labels,omitempty"`
|
||||||
Image string `json:"image"`
|
Image string `json:"image,omitempty"`
|
||||||
|
Mode string `json:"mode,omitempty"`
|
||||||
|
Addr string `json:"addr,omitempty"`
|
||||||
VNC bool `json:"vnc"`
|
VNC bool `json:"vnc"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
LastActive time.Time `json:"last_active"`
|
LastActive time.Time `json:"last_active"`
|
||||||
|
|
@ -101,8 +108,29 @@ type sandboxResponse struct {
|
||||||
func boxToResponse(b *sandboxv2.Box) sandboxResponse {
|
func boxToResponse(b *sandboxv2.Box) sandboxResponse {
|
||||||
snap := b.Snapshot()
|
snap := b.Snapshot()
|
||||||
info := b.ComputerInfo()
|
info := b.ComputerInfo()
|
||||||
|
|
||||||
|
displayName := info.System.Hostname
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = snap.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode, addr string
|
||||||
|
if ns, ok := tai.GetNodeSnapshot(snap.NodeID); ok {
|
||||||
|
mode = ns.Mode
|
||||||
|
addr = ns.Addr
|
||||||
|
}
|
||||||
|
if addr == "" && snap.NodeID != "" {
|
||||||
|
scheme := mode
|
||||||
|
if scheme == "" {
|
||||||
|
scheme = "local"
|
||||||
|
}
|
||||||
|
addr = scheme + "://" + snap.NodeID
|
||||||
|
}
|
||||||
|
|
||||||
return sandboxResponse{
|
return sandboxResponse{
|
||||||
|
Kind: "box",
|
||||||
ID: snap.ID,
|
ID: snap.ID,
|
||||||
|
DisplayName: displayName,
|
||||||
ContainerID: snap.ContainerID,
|
ContainerID: snap.ContainerID,
|
||||||
NodeID: snap.NodeID,
|
NodeID: snap.NodeID,
|
||||||
Owner: snap.Owner,
|
Owner: snap.Owner,
|
||||||
|
|
@ -110,6 +138,8 @@ func boxToResponse(b *sandboxv2.Box) sandboxResponse {
|
||||||
Policy: string(snap.Policy),
|
Policy: string(snap.Policy),
|
||||||
Labels: snap.Labels,
|
Labels: snap.Labels,
|
||||||
Image: snap.Image,
|
Image: snap.Image,
|
||||||
|
Mode: mode,
|
||||||
|
Addr: addr,
|
||||||
VNC: snap.VNC,
|
VNC: snap.VNC,
|
||||||
CreatedAt: snap.CreatedAt,
|
CreatedAt: snap.CreatedAt,
|
||||||
LastActive: snap.LastActive,
|
LastActive: snap.LastActive,
|
||||||
|
|
@ -127,6 +157,71 @@ func boxToResponse(b *sandboxv2.Box) sandboxResponse {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hostToResponse(s registry.NodeSnapshot) sandboxResponse {
|
||||||
|
displayName := s.DisplayName
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = s.System.Hostname
|
||||||
|
}
|
||||||
|
if displayName == "" {
|
||||||
|
displayName = s.TaiID
|
||||||
|
}
|
||||||
|
|
||||||
|
status := "stopped"
|
||||||
|
if s.Status == "online" {
|
||||||
|
status = "running"
|
||||||
|
}
|
||||||
|
|
||||||
|
owner := s.Auth.TeamID
|
||||||
|
if owner == "" {
|
||||||
|
owner = s.Auth.UserID
|
||||||
|
}
|
||||||
|
|
||||||
|
addr := s.Addr
|
||||||
|
if addr == "" {
|
||||||
|
scheme := s.Mode
|
||||||
|
if scheme == "" {
|
||||||
|
scheme = "tai"
|
||||||
|
}
|
||||||
|
addr = scheme + "://" + s.TaiID
|
||||||
|
}
|
||||||
|
|
||||||
|
return sandboxResponse{
|
||||||
|
Kind: "host",
|
||||||
|
ID: s.TaiID,
|
||||||
|
DisplayName: displayName,
|
||||||
|
NodeID: s.TaiID,
|
||||||
|
Owner: owner,
|
||||||
|
Status: status,
|
||||||
|
Policy: "persistent",
|
||||||
|
Mode: s.Mode,
|
||||||
|
Addr: addr,
|
||||||
|
VNC: false,
|
||||||
|
CreatedAt: s.ConnectedAt,
|
||||||
|
LastActive: s.LastPing,
|
||||||
|
System: sandboxSystemInfo{
|
||||||
|
OS: s.System.OS,
|
||||||
|
Arch: s.System.Arch,
|
||||||
|
Hostname: s.System.Hostname,
|
||||||
|
NumCPU: s.System.NumCPU,
|
||||||
|
TotalMem: s.System.TotalMem,
|
||||||
|
Shell: s.System.Shell,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodeOwnedBy(snap *registry.NodeSnapshot, authInfo *types.AuthorizedInfo) bool {
|
||||||
|
if authInfo == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if authInfo.TeamID != "" {
|
||||||
|
return snap.Auth.TeamID == authInfo.TeamID
|
||||||
|
}
|
||||||
|
if authInfo.UserID != "" {
|
||||||
|
return snap.Auth.TeamID == "" && snap.Auth.UserID == authInfo.UserID
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func getManager(c *gin.Context) *sandboxv2.Manager {
|
func getManager(c *gin.Context) *sandboxv2.Manager {
|
||||||
defer func() { recover() }()
|
defer func() { recover() }()
|
||||||
return sandboxv2.M()
|
return sandboxv2.M()
|
||||||
|
|
@ -148,27 +243,54 @@ func checkBoxOwner(c *gin.Context, box *sandboxv2.Box, owner string) bool {
|
||||||
// --- handlers ---
|
// --- handlers ---
|
||||||
|
|
||||||
func handleList(c *gin.Context) {
|
func handleList(c *gin.Context) {
|
||||||
mgr := getManager(c)
|
|
||||||
if mgr == nil {
|
|
||||||
response.RespondWithSuccess(c, http.StatusOK, []sandboxResponse{})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
authInfo := authorized.GetInfo(c)
|
authInfo := authorized.GetInfo(c)
|
||||||
owner := resolveOwner(authInfo)
|
owner := resolveOwner(authInfo)
|
||||||
|
nodeFilter := c.Query("node_id")
|
||||||
|
|
||||||
boxes, err := mgr.List(context.Background(), sandboxv2.ListOptions{
|
var result []sandboxResponse
|
||||||
Owner: owner,
|
|
||||||
NodeID: c.Query("node_id"),
|
// Host entries: list all nodes, filter by ownership + host_exec
|
||||||
})
|
if reg := registry.Global(); reg != nil {
|
||||||
if err != nil {
|
snaps := reg.List()
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
for i := range snaps {
|
||||||
return
|
s := &snaps[i]
|
||||||
|
if !nodeOwnedBy(s, authInfo) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !s.Capabilities["host_exec"] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if nodeFilter != "" && s.TaiID != nodeFilter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, hostToResponse(*s))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]sandboxResponse, 0, len(boxes))
|
// Box entries: list all, then filter by owner
|
||||||
for _, b := range boxes {
|
if mgr := getManager(c); mgr != nil {
|
||||||
result = append(result, boxToResponse(b))
|
boxes, err := mgr.List(context.Background(), sandboxv2.ListOptions{
|
||||||
|
NodeID: nodeFilter,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, b := range boxes {
|
||||||
|
snap := b.Snapshot()
|
||||||
|
if snap.Owner != owner {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
result = append(result, boxToResponse(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(result, func(i, j int) bool {
|
||||||
|
return result[i].LastActive.After(result[j].LastActive)
|
||||||
|
})
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
result = []sandboxResponse{}
|
||||||
}
|
}
|
||||||
response.RespondWithSuccess(c, http.StatusOK, result)
|
response.RespondWithSuccess(c, http.StatusOK, result)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue