- Replaced the previous file extension checks with a unified parsing function for sandbox configuration, improving code clarity and maintainability. - Introduced a new HostExecConfig structure to manage local execution settings, allowing for more granular control over command execution permissions. - Removed deprecated Moapi API files and related functionality, simplifying the codebase and reducing maintenance overhead. - Updated the Tai node registration process to ensure local capabilities are accurately reflected based on the environment, enhancing overall system robustness.
135 lines
3.8 KiB
Go
135 lines
3.8 KiB
Go
package nodes
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/yaoapp/yao/openapi/oauth/authorized"
|
|
"github.com/yaoapp/yao/openapi/oauth/types"
|
|
"github.com/yaoapp/yao/openapi/response"
|
|
"github.com/yaoapp/yao/tai/registry"
|
|
taitypes "github.com/yaoapp/yao/tai/types"
|
|
)
|
|
|
|
// Attach registers Tai node endpoints on the given group.
|
|
// - GET / — list nodes (filtered by team/user from token)
|
|
// - GET /:id — get single node (owner check)
|
|
func Attach(group *gin.RouterGroup, oauth types.OAuth) {
|
|
group.Use(oauth.Guard)
|
|
group.GET("", handleList)
|
|
group.GET("/:id", handleGet)
|
|
}
|
|
|
|
type nodeResponse struct {
|
|
TaiID string `json:"tai_id"`
|
|
MachineID string `json:"machine_id,omitempty"`
|
|
Version string `json:"version,omitempty"`
|
|
DisplayName string `json:"display_name,omitempty"`
|
|
Mode string `json:"mode"`
|
|
Addr string `json:"addr,omitempty"`
|
|
Status string `json:"status"`
|
|
System systemResponse `json:"system"`
|
|
Capabilities map[string]bool `json:"capabilities,omitempty"`
|
|
Ports map[string]int `json:"ports,omitempty"`
|
|
ConnectedAt *time.Time `json:"connected_at,omitempty"`
|
|
LastPing *time.Time `json:"last_ping,omitempty"`
|
|
}
|
|
|
|
type systemResponse struct {
|
|
OS string `json:"os"`
|
|
Arch string `json:"arch"`
|
|
Hostname string `json:"hostname"`
|
|
NumCPU int `json:"num_cpu"`
|
|
TotalMem int64 `json:"total_mem,omitempty"`
|
|
Shell string `json:"shell,omitempty"`
|
|
}
|
|
|
|
func snapToResponse(s taitypes.NodeMeta) nodeResponse {
|
|
r := nodeResponse{
|
|
TaiID: s.TaiID,
|
|
MachineID: s.MachineID,
|
|
Version: s.Version,
|
|
DisplayName: s.DisplayName,
|
|
Mode: s.Mode,
|
|
Addr: s.Addr,
|
|
Status: s.Status,
|
|
Capabilities: map[string]bool{"docker": s.Capabilities.Docker, "k8s": s.Capabilities.K8s, "host_exec": s.Capabilities.HostExec},
|
|
Ports: map[string]int{"grpc": s.Ports.GRPC, "http": s.Ports.HTTP, "vnc": s.Ports.VNC, "docker": s.Ports.Docker, "k8s": s.Ports.K8s},
|
|
System: systemResponse{
|
|
OS: s.System.OS,
|
|
Arch: s.System.Arch,
|
|
Hostname: s.System.Hostname,
|
|
NumCPU: s.System.NumCPU,
|
|
TotalMem: s.System.TotalMem,
|
|
Shell: s.System.Shell,
|
|
},
|
|
}
|
|
if !s.ConnectedAt.IsZero() {
|
|
r.ConnectedAt = &s.ConnectedAt
|
|
}
|
|
if !s.LastPing.IsZero() {
|
|
r.LastPing = &s.LastPing
|
|
}
|
|
return r
|
|
}
|
|
|
|
// nodeOwnedBy checks whether a node belongs to the caller.
|
|
// TeamID match → true; no team and UserID match → true.
|
|
func nodeOwnedBy(snap *taitypes.NodeMeta, 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 handleList(c *gin.Context) {
|
|
reg := registry.Global()
|
|
if reg == nil {
|
|
response.RespondWithSuccess(c, http.StatusOK, []nodeResponse{})
|
|
return
|
|
}
|
|
|
|
authInfo := authorized.GetInfo(c)
|
|
snaps := reg.List()
|
|
|
|
result := make([]nodeResponse, 0, len(snaps))
|
|
for i := range snaps {
|
|
s := &snaps[i]
|
|
if s.Mode != "local" && !nodeOwnedBy(s, authInfo) {
|
|
continue
|
|
}
|
|
result = append(result, snapToResponse(*s))
|
|
}
|
|
response.RespondWithSuccess(c, http.StatusOK, result)
|
|
}
|
|
|
|
func handleGet(c *gin.Context) {
|
|
reg := registry.Global()
|
|
if reg == nil {
|
|
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "node registry not available"})
|
|
return
|
|
}
|
|
|
|
id := c.Param("id")
|
|
snap, ok := reg.Get(id)
|
|
if !ok {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "node not found"})
|
|
return
|
|
}
|
|
|
|
authInfo := authorized.GetInfo(c)
|
|
if snap.Mode != "local" && !nodeOwnedBy(snap, authInfo) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "no permission to access this node"})
|
|
return
|
|
}
|
|
|
|
response.RespondWithSuccess(c, http.StatusOK, snapToResponse(*snap))
|
|
}
|