added browsers support

This commit is contained in:
ACoolName 2024-05-17 17:32:11 +03:00
parent 3c08208a60
commit 7d3051d4cb
2 changed files with 184 additions and 53 deletions

View File

@ -28,12 +28,12 @@ type FileBrowserInfo struct {
} }
type ServerData struct { type ServerData struct {
Id string Id string `bson:"Id"`
OwnerId string OwnerId string `bson:"OwnerId"`
Image string Image string `bson:"Image"`
VolumeId string VolumeId string `bson:"VolumeId"`
Nickname string Nickname string `bson:"Nickname"`
UserPermissions map[string]Permission UserPermissions map[string]Permission `bson:"UserPermissions"`
DefaultCommand string DefaultCommand string `bson:"DefaultCommand"`
DefaultPorts []Port DefaultPorts []Port `bson:"DefaultPorts"`
} }

View File

@ -29,9 +29,13 @@ import (
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
) )
const FILE_BROWSER_IMAGE = "filebrowser/filebrowser:latest"
var DOMAIN = os.Getenv("SERVER_DOMAIN")
type Connection struct { type Connection struct {
connection *mongo.Client databaseConnection *mongo.Client
apiClient *client.Client dockerClient *client.Client
} }
type ContainerLabels struct { type ContainerLabels struct {
@ -63,7 +67,7 @@ func (con Connection) getServerInfo(volume volume.Volume) (*models.ServerInfo, e
var volumeLabels VolumeLabels var volumeLabels VolumeLabels
var serverData models.ServerData var serverData models.ServerData
con.connection.Database("Backend").Collection("Servers").FindOne(context.TODO(), bson.D{{Key: "id", Value: volume.Name}}).Decode(&serverData) con.databaseConnection.Database("Backend").Collection("Servers").FindOne(context.TODO(), bson.D{{Key: "Id", Value: volume.Name}}).Decode(&serverData)
jsonData, err := json.Marshal(volume.Labels) jsonData, err := json.Marshal(volume.Labels)
if err != nil { if err != nil {
@ -78,7 +82,7 @@ func (con Connection) getServerInfo(volume volume.Volume) (*models.ServerInfo, e
if len(serverData.DefaultPorts) == 0 { if len(serverData.DefaultPorts) == 0 {
imageList, err := con.apiClient.ImageList(context.TODO(), image.ListOptions{Filters: filters.NewArgs(filters.Arg("reference", volumeLabels.ImageId))}) imageList, err := con.dockerClient.ImageList(context.TODO(), image.ListOptions{Filters: filters.NewArgs(filters.Arg("reference", volumeLabels.ImageId), filters.Arg("label", "type=GAME"))})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -86,7 +90,7 @@ func (con Connection) getServerInfo(volume volume.Volume) (*models.ServerInfo, e
return nil, fmt.Errorf("ImageId %s does not exist", volumeLabels.ImageId) return nil, fmt.Errorf("ImageId %s does not exist", volumeLabels.ImageId)
} }
imageSummery := imageList[0] imageSummery := imageList[0]
imageInspect, _, err := con.apiClient.ImageInspectWithRaw(context.TODO(), imageSummery.ID) imageInspect, _, err := con.dockerClient.ImageInspectWithRaw(context.TODO(), imageSummery.ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -105,7 +109,7 @@ func (con Connection) getServerInfo(volume volume.Volume) (*models.ServerInfo, e
imageName := imageNameAndVersion[0] imageName := imageNameAndVersion[0]
imageVersion := imageNameAndVersion[1] imageVersion := imageNameAndVersion[1]
containers, err := con.apiClient.ContainerList(context.TODO(), container.ListOptions{ containers, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{
All: true, All: true,
Filters: filters.NewArgs(filters.Arg("label", "type=GAME"), filters.Arg("label", fmt.Sprintf("volume_id=%s", volume.Name))), Filters: filters.NewArgs(filters.Arg("label", "type=GAME"), filters.Arg("label", fmt.Sprintf("volume_id=%s", volume.Name))),
}) })
@ -138,7 +142,7 @@ func (con Connection) getServerInfo(volume volume.Volume) (*models.ServerInfo, e
} }
func (con Connection) getServerInfoFromId(ServerId string) (*models.ServerInfo, error) { func (con Connection) getServerInfoFromId(ServerId string) (*models.ServerInfo, error) {
volume, err := con.apiClient.VolumeInspect(context.TODO(), ServerId) volume, err := con.dockerClient.VolumeInspect(context.TODO(), ServerId)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -168,9 +172,10 @@ func convertLabelsToMap(v any) (map[string]string, error) {
} }
type CreateServerRequest struct { type CreateServerRequest struct {
ImageId string `json:"image_id"` ImageId string `json:"ImageId"`
DefaultPorts []models.Port `json:"default_ports"` DefaultPorts []models.Port `json:"DefaultPorts"`
DefaultCommand string `json:"default_command"` DefaultCommand string `json:"DefaultCommand"`
Nickname string `json:"Nickname"`
} }
func (con Connection) CreateServer(ctx *gin.Context) { func (con Connection) CreateServer(ctx *gin.Context) {
@ -186,7 +191,7 @@ func (con Connection) CreateServer(ctx *gin.Context) {
return return
} }
imageList, err := con.apiClient.ImageList(context.TODO(), image.ListOptions{All: true, Filters: filters.NewArgs(filters.Arg("reference", request.ImageId))}) imageList, err := con.dockerClient.ImageList(context.TODO(), image.ListOptions{All: true, Filters: filters.NewArgs(filters.Arg("reference", request.ImageId), filters.Arg("label", "type=GAME"))})
if err != nil { if err != nil {
ctx.AbortWithError(400, err) ctx.AbortWithError(400, err)
return return
@ -201,14 +206,14 @@ func (con Connection) CreateServer(ctx *gin.Context) {
ctx.AbortWithError(400, err) ctx.AbortWithError(400, err)
return return
} }
volumeResponse, err := con.apiClient.VolumeCreate(context.TODO(), volume.CreateOptions{ volumeResponse, err := con.dockerClient.VolumeCreate(context.TODO(), volume.CreateOptions{
Labels: labels, Labels: labels,
}) })
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
} }
con.connection.Database("Backend").Collection("Servers").InsertOne(context.TODO(), models.ServerData{ con.databaseConnection.Database("Backend").Collection("Servers").InsertOne(context.TODO(), models.ServerData{
Id: volumeResponse.Name, Id: volumeResponse.Name,
OwnerId: claims.(*auth.AuthClaims).Username, OwnerId: claims.(*auth.AuthClaims).Username,
Image: imageSummary.RepoTags[0], Image: imageSummary.RepoTags[0],
@ -226,8 +231,8 @@ type PortMappingRequest struct {
} }
type StartServerRequest struct { type StartServerRequest struct {
Command string `json:"command"` Command string `json:"Command"`
Ports []PortMappingRequest `json:"ports"` Ports []PortMappingRequest `json:"Ports"`
} }
func (con Connection) StartServer(ctx *gin.Context) { func (con Connection) StartServer(ctx *gin.Context) {
@ -271,7 +276,7 @@ func (con Connection) StartServer(ctx *gin.Context) {
volumes := make(map[string]struct{}) volumes := make(map[string]struct{})
image, _, err := con.apiClient.ImageInspectWithRaw(context.TODO(), imageId) image, _, err := con.dockerClient.ImageInspectWithRaw(context.TODO(), imageId)
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
@ -302,11 +307,13 @@ func (con Connection) StartServer(ctx *gin.Context) {
if len(words) == 0 { if len(words) == 0 {
words = nil words = nil
} }
portSet := make(nat.PortSet) portSet := make(nat.PortSet)
for port, _ := range portMapping { for port := range portMapping {
portSet[port] = struct{}{} portSet[port] = struct{}{}
} }
response, err := con.apiClient.ContainerCreate(
response, err := con.dockerClient.ContainerCreate(
context.TODO(), context.TODO(),
&container.Config{ &container.Config{
AttachStdin: true, AttachStdin: true,
@ -334,22 +341,25 @@ func (con Connection) StartServer(ctx *gin.Context) {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
} }
if err := con.apiClient.ContainerStart(ctx, response.ID, container.StartOptions{}); err != nil { if err := con.dockerClient.ContainerStart(ctx, response.ID, container.StartOptions{}); err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
} }
UPNPPath, exists := os.LookupEnv("UPNP_PATH") UPNPPath, exists := os.LookupEnv("UPNP_PATH")
HostIP, hostIPexists := os.LookupEnv("HOST_IP") HostIP, hostIPexists := os.LookupEnv("HOST_IP")
if exists && hostIPexists { if exists && hostIPexists {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
containerData, err := con.apiClient.ContainerInspect(context.TODO(), response.ID) containerData, err := con.dockerClient.ContainerInspect(context.TODO(), response.ID)
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
} }
fo, err := os.OpenFile(UPNPPath, os.O_APPEND, os.ModeAppend) fo, err := os.OpenFile(UPNPPath, os.O_APPEND, os.ModeAppend)
if err != nil { if err != nil {
panic(err) ctx.AbortWithError(503, err)
con.dockerClient.ContainerStop(context.TODO(), containerData.ID, container.StopOptions{})
return
} }
defer func() { defer func() {
if err := fo.Close(); err != nil { if err := fo.Close(); err != nil {
@ -372,7 +382,7 @@ func (con Connection) StartServer(ctx *gin.Context) {
} }
func (con Connection) GetServers(ctx *gin.Context) { func (con Connection) GetServers(ctx *gin.Context) {
volumes, err := con.apiClient.VolumeList( volumes, err := con.dockerClient.VolumeList(
context.TODO(), context.TODO(),
volume.ListOptions{ volume.ListOptions{
Filters: filters.NewArgs(filters.Arg("label", "type=GAME")), Filters: filters.NewArgs(filters.Arg("label", "type=GAME")),
@ -398,7 +408,7 @@ func (con Connection) GetServers(ctx *gin.Context) {
func (con Connection) StopServer(ctx *gin.Context) { func (con Connection) StopServer(ctx *gin.Context) {
serverId := ctx.Param("server_id") serverId := ctx.Param("server_id")
containersList, err := con.apiClient.ContainerList(context.TODO(), container.ListOptions{ containersList, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{
Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId), filters.Arg("label", "type=GAME")), Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId), filters.Arg("label", "type=GAME")),
}) })
if err != nil { if err != nil {
@ -411,36 +421,36 @@ func (con Connection) StopServer(ctx *gin.Context) {
} }
for _, containerData := range containersList { for _, containerData := range containersList {
con.apiClient.ContainerStop(context.TODO(), containerData.ID, container.StopOptions{}) con.dockerClient.ContainerStop(context.TODO(), containerData.ID, container.StopOptions{})
} }
ctx.Status(200) ctx.Status(200)
} }
func (con Connection) DeleteServer(ctx *gin.Context) { func (con Connection) DeleteServer(ctx *gin.Context) {
serverId := ctx.Param("server_id") serverId := ctx.Param("server_id")
containers, err := con.apiClient.ContainerList(context.TODO(), container.ListOptions{All: true, Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId))}) containers, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{All: true, Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId))})
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
} }
for _, containerInstance := range containers { for _, containerInstance := range containers {
con.apiClient.ContainerStop(context.TODO(), containerInstance.ID, container.StopOptions{}) con.dockerClient.ContainerStop(context.TODO(), containerInstance.ID, container.StopOptions{})
err := con.apiClient.ContainerRemove(context.TODO(), containerInstance.ID, container.RemoveOptions{Force: true, RemoveLinks: true}) err := con.dockerClient.ContainerRemove(context.TODO(), containerInstance.ID, container.RemoveOptions{Force: true, RemoveLinks: true})
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
} }
} }
con.apiClient.VolumeRemove(context.TODO(), serverId, false) con.dockerClient.VolumeRemove(context.TODO(), serverId, false)
con.connection.Database("Backend").Collection("Servers").FindOneAndDelete(context.TODO(), bson.D{{Key: "id", Value: serverId}}) con.databaseConnection.Database("Backend").Collection("Servers").FindOneAndDelete(context.TODO(), bson.D{{Key: "Id", Value: serverId}})
ctx.JSON(200, "ok") ctx.JSON(200, "ok")
} }
type RunCommandRequest struct { type RunCommandRequest struct {
Command string `json:"command"` Command string `json:"Command"`
} }
func (con Connection) RunCommand(ctx *gin.Context) { func (con Connection) RunCommand(ctx *gin.Context) {
@ -452,14 +462,14 @@ func (con Connection) RunCommand(ctx *gin.Context) {
serverId := ctx.Param("server_id") serverId := ctx.Param("server_id")
log.Print("Writing command \"", request.Command, "\"") log.Print("Writing command \"", request.Command, "\"")
containers, err := con.apiClient.ContainerList(context.TODO(), container.ListOptions{Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId))}) containers, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId))})
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
} }
for _, containerData := range containers { for _, containerData := range containers {
hijacked, err := con.apiClient.ContainerAttach(context.TODO(), containerData.ID, container.AttachOptions{Stream: true, Stdin: true}) hijacked, err := con.dockerClient.ContainerAttach(context.TODO(), containerData.ID, container.AttachOptions{Stream: true, Stdin: true})
defer func() { hijacked.Close(); hijacked.CloseWrite() }() defer func() { hijacked.Close(); hijacked.CloseWrite() }()
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
@ -477,8 +487,8 @@ func (con Connection) RunCommand(ctx *gin.Context) {
} }
type Commands struct { type Commands struct {
CommandType string `json:"command_type"` CommandType string `json:"CommandType"`
Arguments []string `json:"arguments"` Arguments []string `json:"Arguments"`
} }
func (con Connection) AttachServer(ctx *gin.Context) { func (con Connection) AttachServer(ctx *gin.Context) {
@ -493,7 +503,7 @@ func (con Connection) AttachServer(ctx *gin.Context) {
close(containerRead) close(containerRead)
}() }()
containers, err := con.apiClient.ContainerList(context.TODO(), container.ListOptions{Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId))}) containers, err := con.dockerClient.ContainerList(context.TODO(), container.ListOptions{Filters: filters.NewArgs(filters.Arg("label", "volume_id="+serverId))})
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
@ -502,7 +512,7 @@ func (con Connection) AttachServer(ctx *gin.Context) {
ctx.AbortWithStatus(404) ctx.AbortWithStatus(404)
return return
} }
hijacked, err := con.apiClient.ContainerAttach(context.TODO(), containers[0].ID, container.AttachOptions{Stream: true, Stdin: true, Stdout: true, Stderr: true, Logs: true}) hijacked, err := con.dockerClient.ContainerAttach(context.TODO(), containers[0].ID, container.AttachOptions{Stream: true, Stdin: true, Stdout: true, Stderr: true, Logs: true})
if err != nil { if err != nil {
ctx.AbortWithError(500, err) ctx.AbortWithError(500, err)
return return
@ -511,6 +521,7 @@ func (con Connection) AttachServer(ctx *gin.Context) {
ws, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil) ws, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
if err != nil { if err != nil {
ctx.AbortWithError(500, err)
return return
} }
defer ws.Close() defer ws.Close()
@ -552,11 +563,6 @@ func (con Connection) AttachServer(ctx *gin.Context) {
} }
}() }()
if err != nil {
ctx.AbortWithError(500, err)
return
}
for { for {
if stop { if stop {
break break
@ -583,7 +589,6 @@ func (con Connection) AttachServer(ctx *gin.Context) {
} }
} }
} }
} }
func (con Connection) serverAuthorized(permissions models.Permission) func(*gin.Context) bool { func (con Connection) serverAuthorized(permissions models.Permission) func(*gin.Context) bool {
@ -600,7 +605,7 @@ func (con Connection) serverAuthorized(permissions models.Permission) func(*gin.
var serverData models.ServerData var serverData models.ServerData
con.connection.Database("Backend").Collection("Servers").FindOne(context.TODO(), bson.D{{Key: "id", Value: server_id}}).Decode(&serverData) con.databaseConnection.Database("Backend").Collection("Servers").FindOne(context.TODO(), bson.D{{Key: "Id", Value: server_id}}).Decode(&serverData)
if serverData.OwnerId == claims.(*auth.AuthClaims).Username { if serverData.OwnerId == claims.(*auth.AuthClaims).Username {
return true return true
@ -616,6 +621,130 @@ func (con Connection) serverAuthorized(permissions models.Permission) func(*gin.
} }
} }
type UpdateServerRequest struct {
DefaultPorts []models.Port `json:"DefaultPorts"`
DefaultCommand string `json:"DefaultCommand"`
Nickname string `json:"Nickname"`
UserPermissions map[string][]models.Permission `json:"UserPermissions"`
}
func (con Connection) UpdateServer(ctx *gin.Context) {
serverId := ctx.Param("server_id")
var Request UpdateServerRequest
err := json.NewDecoder(ctx.Request.Body).Decode(&Request)
if err != nil {
ctx.AbortWithError(500, err)
}
updateOperation := bson.M{}
if Request.DefaultCommand != "" {
updateOperation["DefaultCommand"] = Request.DefaultCommand
}
if Request.DefaultPorts != nil {
updateOperation["DefaultPorts"] = Request.DefaultPorts
}
if Request.Nickname != "" {
updateOperation["Nickname"] = Request.Nickname
}
_, err = con.databaseConnection.Database("Backend").Collection("Servers").UpdateOne(context.TODO(), bson.D{{Key: "Id", Value: serverId}}, bson.D{{Key: "$set", Value: updateOperation}})
if err != nil {
ctx.AbortWithError(500, err)
}
ctx.JSON(200, "OK")
}
func (con Connection) BrowseServer(ctx *gin.Context) {
serverID := ctx.Param("server_id")
claims, exists := ctx.Get("claims")
if !exists {
ctx.AbortWithStatus(403)
return
}
serverInfo, err := con.getServerInfoFromId(serverID)
if err != nil {
ctx.AbortWithError(500, err)
}
browserLabels := make(map[string]string)
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].main", serverInfo.Id)] = fmt.Sprintf("%s.%s", "browsers", DOMAIN)
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.domains[0].sans", serverInfo.Id)] = fmt.Sprintf("*.%s.%s", "browsers", DOMAIN)
browserLabels[fmt.Sprintf("traefik.http.routers.%s.tls.certresolver", serverInfo.Id)] = "myresolver"
containerLabels := ContainerLabels{
OwnerId: claims.(*auth.AuthClaims).Username,
ImageId: FILE_BROWSER_IMAGE,
VolumeId: serverInfo.Id,
Type: "FILE-BROWSER",
}
jsonLabels, err := json.Marshal(containerLabels)
if err != nil {
ctx.AbortWithError(500, err)
return
}
err = json.Unmarshal(jsonLabels, &browserLabels)
if err != nil {
ctx.AbortWithError(500, err)
return
}
command, err := shellwords.Split("-r /tmp/data --noauth")
if err != nil {
ctx.AbortWithError(500, err)
return
}
imageRef := fmt.Sprintf("%s:%s", serverInfo.Image.Name, serverInfo.Image.Version)
images, err := con.dockerClient.ImageList(context.TODO(), image.ListOptions{Filters: filters.NewArgs(filters.Arg("label", "type=GAME"), filters.Arg("reference", imageRef))})
if err != nil {
ctx.AbortWithError(500, err)
return
}
if len(images) == 0 {
ctx.AbortWithError(500, fmt.Errorf("Image %s no longer exists", imageRef))
return
}
ContainerResponse, err := con.dockerClient.ContainerCreate(
context.TODO(),
&container.Config{
Cmd: command,
Image: FILE_BROWSER_IMAGE,
Labels: browserLabels,
ExposedPorts: nat.PortSet{"80/tcp": struct{}{}},
},
&container.HostConfig{
Mounts: []mount.Mount{{Source: serverInfo.Id, Target: "/tmp/data", Type: "volume"}},
PublishAllPorts: true,
AutoRemove: true,
},
&network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{"test": {
NetworkID: "exposed",
}},
},
&v1.Platform{},
"",
)
if err != nil {
ctx.AbortWithError(500, err)
return
}
err = con.dockerClient.ContainerStart(context.TODO(), ContainerResponse.ID, container.StartOptions{})
if err != nil {
ctx.AbortWithError(500, err)
return
}
ctx.JSON(200, "OK")
}
func LoadGroup(group *gin.RouterGroup, mongo_client *mongo.Client) { func LoadGroup(group *gin.RouterGroup, mongo_client *mongo.Client) {
apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) apiClient, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
if err != nil { if err != nil {
@ -623,12 +752,14 @@ func LoadGroup(group *gin.RouterGroup, mongo_client *mongo.Client) {
} }
defer apiClient.Close() defer apiClient.Close()
connection := Connection{connection: mongo_client, apiClient: apiClient} connection := Connection{databaseConnection: mongo_client, dockerClient: apiClient}
group.POST("/:server_id/start", auth.AuthorizedTo(models.Start, connection.serverAuthorized(models.Start)), connection.StartServer) group.POST("/:server_id/start", auth.AuthorizedTo(models.Start, connection.serverAuthorized(models.Start)), connection.StartServer)
group.POST("/", auth.AuthorizedTo(models.Create), connection.CreateServer) group.POST("/", auth.AuthorizedTo(models.Create), connection.CreateServer)
group.GET("/", auth.AuthorizedTo(0), connection.GetServers) group.GET("/", auth.AuthorizedTo(0), connection.GetServers)
group.POST("/:server_id/stop", auth.AuthorizedTo(models.Stop, connection.serverAuthorized(models.Stop)), connection.StopServer) group.POST("/:server_id/stop", auth.AuthorizedTo(models.Stop, connection.serverAuthorized(models.Stop)), connection.StopServer)
group.POST("/:server_id/delete", auth.AuthorizedTo(models.Delete, connection.serverAuthorized(models.Delete)), connection.DeleteServer) group.DELETE("/:server_id", auth.AuthorizedTo(models.Delete, connection.serverAuthorized(models.Delete)), connection.DeleteServer)
group.POST("/:server_id/run_command", auth.AuthorizedTo(models.RunCommand, connection.serverAuthorized(models.RunCommand)), connection.RunCommand) group.POST("/:server_id/run_command", auth.AuthorizedTo(models.RunCommand, connection.serverAuthorized(models.RunCommand)), connection.RunCommand)
group.GET("/:server_id/attach", auth.AuthorizedTo(models.RunCommand, connection.serverAuthorized(models.RunCommand)), connection.AttachServer) group.GET("/:server_id/attach", auth.AuthorizedTo(models.RunCommand, connection.serverAuthorized(models.RunCommand)), connection.AttachServer)
group.PATCH("/:server_id", auth.AuthorizedTo(models.Admin, connection.serverAuthorized(models.Admin)), connection.UpdateServer)
group.POST("/:server_id/browse", auth.AuthorizedTo(models.Browse, connection.serverAuthorized(models.Admin)), connection.BrowseServer)
} }