mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-29 23:28:03 +00:00
* fix: fix model deployment style issues, lint problems, and i18n gaps. * fix: adjust the key not to be displayed on the frontend, tested via the backend. * fix: adjust the sidebar configuration logic to use the default configuration items if they are not defined.
811 lines
18 KiB
Go
811 lines
18 KiB
Go
package controller
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/QuantumNous/new-api/common"
|
|
"github.com/QuantumNous/new-api/pkg/ionet"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
func getIoAPIKey(c *gin.Context) (string, bool) {
|
|
common.OptionMapRWMutex.RLock()
|
|
enabled := common.OptionMap["model_deployment.ionet.enabled"] == "true"
|
|
apiKey := common.OptionMap["model_deployment.ionet.api_key"]
|
|
common.OptionMapRWMutex.RUnlock()
|
|
if !enabled || strings.TrimSpace(apiKey) == "" {
|
|
common.ApiErrorMsg(c, "io.net model deployment is not enabled or api key missing")
|
|
return "", false
|
|
}
|
|
return apiKey, true
|
|
}
|
|
|
|
func GetModelDeploymentSettings(c *gin.Context) {
|
|
common.OptionMapRWMutex.RLock()
|
|
enabled := common.OptionMap["model_deployment.ionet.enabled"] == "true"
|
|
hasAPIKey := strings.TrimSpace(common.OptionMap["model_deployment.ionet.api_key"]) != ""
|
|
common.OptionMapRWMutex.RUnlock()
|
|
|
|
common.ApiSuccess(c, gin.H{
|
|
"provider": "io.net",
|
|
"enabled": enabled,
|
|
"configured": hasAPIKey,
|
|
"can_connect": enabled && hasAPIKey,
|
|
})
|
|
}
|
|
|
|
func getIoClient(c *gin.Context) (*ionet.Client, bool) {
|
|
apiKey, ok := getIoAPIKey(c)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return ionet.NewClient(apiKey), true
|
|
}
|
|
|
|
func getIoEnterpriseClient(c *gin.Context) (*ionet.Client, bool) {
|
|
apiKey, ok := getIoAPIKey(c)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return ionet.NewEnterpriseClient(apiKey), true
|
|
}
|
|
|
|
func TestIoNetConnection(c *gin.Context) {
|
|
var req struct {
|
|
APIKey string `json:"api_key"`
|
|
}
|
|
|
|
rawBody, err := c.GetRawData()
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
if len(bytes.TrimSpace(rawBody)) > 0 {
|
|
if err := json.Unmarshal(rawBody, &req); err != nil {
|
|
common.ApiErrorMsg(c, "invalid request payload")
|
|
return
|
|
}
|
|
}
|
|
|
|
apiKey := strings.TrimSpace(req.APIKey)
|
|
if apiKey == "" {
|
|
common.OptionMapRWMutex.RLock()
|
|
storedKey := strings.TrimSpace(common.OptionMap["model_deployment.ionet.api_key"])
|
|
common.OptionMapRWMutex.RUnlock()
|
|
if storedKey == "" {
|
|
common.ApiErrorMsg(c, "api_key is required")
|
|
return
|
|
}
|
|
apiKey = storedKey
|
|
}
|
|
|
|
client := ionet.NewEnterpriseClient(apiKey)
|
|
result, err := client.GetMaxGPUsPerContainer()
|
|
if err != nil {
|
|
if apiErr, ok := err.(*ionet.APIError); ok {
|
|
message := strings.TrimSpace(apiErr.Message)
|
|
if message == "" {
|
|
message = "failed to validate api key"
|
|
}
|
|
common.ApiErrorMsg(c, message)
|
|
return
|
|
}
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
totalHardware := 0
|
|
totalAvailable := 0
|
|
if result != nil {
|
|
totalHardware = len(result.Hardware)
|
|
totalAvailable = result.Total
|
|
if totalAvailable == 0 {
|
|
for _, hw := range result.Hardware {
|
|
totalAvailable += hw.Available
|
|
}
|
|
}
|
|
}
|
|
|
|
common.ApiSuccess(c, gin.H{
|
|
"hardware_count": totalHardware,
|
|
"total_available": totalAvailable,
|
|
})
|
|
}
|
|
|
|
func requireDeploymentID(c *gin.Context) (string, bool) {
|
|
deploymentID := strings.TrimSpace(c.Param("id"))
|
|
if deploymentID == "" {
|
|
common.ApiErrorMsg(c, "deployment ID is required")
|
|
return "", false
|
|
}
|
|
return deploymentID, true
|
|
}
|
|
|
|
func requireContainerID(c *gin.Context) (string, bool) {
|
|
containerID := strings.TrimSpace(c.Param("container_id"))
|
|
if containerID == "" {
|
|
common.ApiErrorMsg(c, "container ID is required")
|
|
return "", false
|
|
}
|
|
return containerID, true
|
|
}
|
|
|
|
func mapIoNetDeployment(d ionet.Deployment) map[string]interface{} {
|
|
var created int64
|
|
if d.CreatedAt.IsZero() {
|
|
created = time.Now().Unix()
|
|
} else {
|
|
created = d.CreatedAt.Unix()
|
|
}
|
|
|
|
timeRemainingHours := d.ComputeMinutesRemaining / 60
|
|
timeRemainingMins := d.ComputeMinutesRemaining % 60
|
|
var timeRemaining string
|
|
if timeRemainingHours > 0 {
|
|
timeRemaining = fmt.Sprintf("%d hour %d minutes", timeRemainingHours, timeRemainingMins)
|
|
} else if timeRemainingMins > 0 {
|
|
timeRemaining = fmt.Sprintf("%d minutes", timeRemainingMins)
|
|
} else {
|
|
timeRemaining = "completed"
|
|
}
|
|
|
|
hardwareInfo := fmt.Sprintf("%s %s x%d", d.BrandName, d.HardwareName, d.HardwareQuantity)
|
|
|
|
return map[string]interface{}{
|
|
"id": d.ID,
|
|
"deployment_name": d.Name,
|
|
"container_name": d.Name,
|
|
"status": strings.ToLower(d.Status),
|
|
"type": "Container",
|
|
"time_remaining": timeRemaining,
|
|
"time_remaining_minutes": d.ComputeMinutesRemaining,
|
|
"hardware_info": hardwareInfo,
|
|
"hardware_name": d.HardwareName,
|
|
"brand_name": d.BrandName,
|
|
"hardware_quantity": d.HardwareQuantity,
|
|
"completed_percent": d.CompletedPercent,
|
|
"compute_minutes_served": d.ComputeMinutesServed,
|
|
"compute_minutes_remaining": d.ComputeMinutesRemaining,
|
|
"created_at": created,
|
|
"updated_at": created,
|
|
"model_name": "",
|
|
"model_version": "",
|
|
"instance_count": d.HardwareQuantity,
|
|
"resource_config": map[string]interface{}{
|
|
"cpu": "",
|
|
"memory": "",
|
|
"gpu": strconv.Itoa(d.HardwareQuantity),
|
|
},
|
|
"description": "",
|
|
"provider": "io.net",
|
|
}
|
|
}
|
|
|
|
func computeStatusCounts(total int, deployments []ionet.Deployment) map[string]int64 {
|
|
counts := map[string]int64{
|
|
"all": int64(total),
|
|
}
|
|
|
|
for _, status := range []string{"running", "completed", "failed", "deployment requested", "termination requested", "destroyed"} {
|
|
counts[status] = 0
|
|
}
|
|
|
|
for _, d := range deployments {
|
|
status := strings.ToLower(strings.TrimSpace(d.Status))
|
|
counts[status] = counts[status] + 1
|
|
}
|
|
|
|
return counts
|
|
}
|
|
|
|
func GetAllDeployments(c *gin.Context) {
|
|
pageInfo := common.GetPageQuery(c)
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
status := c.Query("status")
|
|
opts := &ionet.ListDeploymentsOptions{
|
|
Status: strings.ToLower(strings.TrimSpace(status)),
|
|
Page: pageInfo.GetPage(),
|
|
PageSize: pageInfo.GetPageSize(),
|
|
SortBy: "created_at",
|
|
SortOrder: "desc",
|
|
}
|
|
|
|
dl, err := client.ListDeployments(opts)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
items := make([]map[string]interface{}, 0, len(dl.Deployments))
|
|
for _, d := range dl.Deployments {
|
|
items = append(items, mapIoNetDeployment(d))
|
|
}
|
|
|
|
data := gin.H{
|
|
"page": pageInfo.GetPage(),
|
|
"page_size": pageInfo.GetPageSize(),
|
|
"total": dl.Total,
|
|
"items": items,
|
|
"status_counts": computeStatusCounts(dl.Total, dl.Deployments),
|
|
}
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func SearchDeployments(c *gin.Context) {
|
|
pageInfo := common.GetPageQuery(c)
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
status := strings.ToLower(strings.TrimSpace(c.Query("status")))
|
|
keyword := strings.TrimSpace(c.Query("keyword"))
|
|
|
|
dl, err := client.ListDeployments(&ionet.ListDeploymentsOptions{
|
|
Status: status,
|
|
Page: pageInfo.GetPage(),
|
|
PageSize: pageInfo.GetPageSize(),
|
|
SortBy: "created_at",
|
|
SortOrder: "desc",
|
|
})
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
filtered := make([]ionet.Deployment, 0, len(dl.Deployments))
|
|
if keyword == "" {
|
|
filtered = dl.Deployments
|
|
} else {
|
|
kw := strings.ToLower(keyword)
|
|
for _, d := range dl.Deployments {
|
|
if strings.Contains(strings.ToLower(d.Name), kw) {
|
|
filtered = append(filtered, d)
|
|
}
|
|
}
|
|
}
|
|
|
|
items := make([]map[string]interface{}, 0, len(filtered))
|
|
for _, d := range filtered {
|
|
items = append(items, mapIoNetDeployment(d))
|
|
}
|
|
|
|
total := dl.Total
|
|
if keyword != "" {
|
|
total = len(filtered)
|
|
}
|
|
|
|
data := gin.H{
|
|
"page": pageInfo.GetPage(),
|
|
"page_size": pageInfo.GetPageSize(),
|
|
"total": total,
|
|
"items": items,
|
|
}
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func GetDeployment(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
deploymentID, ok := requireDeploymentID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
details, err := client.GetDeployment(deploymentID)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"id": details.ID,
|
|
"deployment_name": details.ID,
|
|
"model_name": "",
|
|
"model_version": "",
|
|
"status": strings.ToLower(details.Status),
|
|
"instance_count": details.TotalContainers,
|
|
"hardware_id": details.HardwareID,
|
|
"resource_config": map[string]interface{}{
|
|
"cpu": "",
|
|
"memory": "",
|
|
"gpu": strconv.Itoa(details.TotalGPUs),
|
|
},
|
|
"created_at": details.CreatedAt.Unix(),
|
|
"updated_at": details.CreatedAt.Unix(),
|
|
"description": "",
|
|
"amount_paid": details.AmountPaid,
|
|
"completed_percent": details.CompletedPercent,
|
|
"gpus_per_container": details.GPUsPerContainer,
|
|
"total_gpus": details.TotalGPUs,
|
|
"total_containers": details.TotalContainers,
|
|
"hardware_name": details.HardwareName,
|
|
"brand_name": details.BrandName,
|
|
"compute_minutes_served": details.ComputeMinutesServed,
|
|
"compute_minutes_remaining": details.ComputeMinutesRemaining,
|
|
"locations": details.Locations,
|
|
"container_config": details.ContainerConfig,
|
|
}
|
|
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func UpdateDeploymentName(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
deploymentID, ok := requireDeploymentID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Name string `json:"name" binding:"required"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
updateReq := &ionet.UpdateClusterNameRequest{
|
|
Name: strings.TrimSpace(req.Name),
|
|
}
|
|
|
|
if updateReq.Name == "" {
|
|
common.ApiErrorMsg(c, "deployment name cannot be empty")
|
|
return
|
|
}
|
|
|
|
available, err := client.CheckClusterNameAvailability(updateReq.Name)
|
|
if err != nil {
|
|
common.ApiError(c, fmt.Errorf("failed to check name availability: %w", err))
|
|
return
|
|
}
|
|
|
|
if !available {
|
|
common.ApiErrorMsg(c, "deployment name is not available, please choose a different name")
|
|
return
|
|
}
|
|
|
|
resp, err := client.UpdateClusterName(deploymentID, updateReq)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
data := gin.H{
|
|
"status": resp.Status,
|
|
"message": resp.Message,
|
|
"id": deploymentID,
|
|
"name": updateReq.Name,
|
|
}
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func UpdateDeployment(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
deploymentID, ok := requireDeploymentID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req ionet.UpdateDeploymentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
resp, err := client.UpdateDeployment(deploymentID, &req)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
data := gin.H{
|
|
"status": resp.Status,
|
|
"deployment_id": resp.DeploymentID,
|
|
}
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func ExtendDeployment(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
deploymentID, ok := requireDeploymentID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req ionet.ExtendDurationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
details, err := client.ExtendDeployment(deploymentID, &req)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
data := mapIoNetDeployment(ionet.Deployment{
|
|
ID: details.ID,
|
|
Status: details.Status,
|
|
Name: deploymentID,
|
|
CompletedPercent: float64(details.CompletedPercent),
|
|
HardwareQuantity: details.TotalGPUs,
|
|
BrandName: details.BrandName,
|
|
HardwareName: details.HardwareName,
|
|
ComputeMinutesServed: details.ComputeMinutesServed,
|
|
ComputeMinutesRemaining: details.ComputeMinutesRemaining,
|
|
CreatedAt: details.CreatedAt,
|
|
})
|
|
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func DeleteDeployment(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
deploymentID, ok := requireDeploymentID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
resp, err := client.DeleteDeployment(deploymentID)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
data := gin.H{
|
|
"status": resp.Status,
|
|
"deployment_id": resp.DeploymentID,
|
|
"message": "Deployment termination requested successfully",
|
|
}
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func CreateDeployment(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req ionet.DeploymentRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
resp, err := client.DeployContainer(&req)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
data := gin.H{
|
|
"deployment_id": resp.DeploymentID,
|
|
"status": resp.Status,
|
|
"message": "Deployment created successfully",
|
|
}
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func GetHardwareTypes(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
hardwareTypes, totalAvailable, err := client.ListHardwareTypes()
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
data := gin.H{
|
|
"hardware_types": hardwareTypes,
|
|
"total": len(hardwareTypes),
|
|
"total_available": totalAvailable,
|
|
}
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func GetLocations(c *gin.Context) {
|
|
client, ok := getIoClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
locationsResp, err := client.ListLocations()
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
total := locationsResp.Total
|
|
if total == 0 {
|
|
total = len(locationsResp.Locations)
|
|
}
|
|
|
|
data := gin.H{
|
|
"locations": locationsResp.Locations,
|
|
"total": total,
|
|
}
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func GetAvailableReplicas(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
hardwareIDStr := c.Query("hardware_id")
|
|
gpuCountStr := c.Query("gpu_count")
|
|
|
|
if hardwareIDStr == "" {
|
|
common.ApiErrorMsg(c, "hardware_id parameter is required")
|
|
return
|
|
}
|
|
|
|
hardwareID, err := strconv.Atoi(hardwareIDStr)
|
|
if err != nil || hardwareID <= 0 {
|
|
common.ApiErrorMsg(c, "invalid hardware_id parameter")
|
|
return
|
|
}
|
|
|
|
gpuCount := 1
|
|
if gpuCountStr != "" {
|
|
if parsed, err := strconv.Atoi(gpuCountStr); err == nil && parsed > 0 {
|
|
gpuCount = parsed
|
|
}
|
|
}
|
|
|
|
replicas, err := client.GetAvailableReplicas(hardwareID, gpuCount)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
common.ApiSuccess(c, replicas)
|
|
}
|
|
|
|
func GetPriceEstimation(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var req ionet.PriceEstimationRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
priceResp, err := client.GetPriceEstimation(&req)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
common.ApiSuccess(c, priceResp)
|
|
}
|
|
|
|
func CheckClusterNameAvailability(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
clusterName := strings.TrimSpace(c.Query("name"))
|
|
if clusterName == "" {
|
|
common.ApiErrorMsg(c, "name parameter is required")
|
|
return
|
|
}
|
|
|
|
available, err := client.CheckClusterNameAvailability(clusterName)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
data := gin.H{
|
|
"available": available,
|
|
"name": clusterName,
|
|
}
|
|
common.ApiSuccess(c, data)
|
|
}
|
|
|
|
func GetDeploymentLogs(c *gin.Context) {
|
|
client, ok := getIoClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
deploymentID, ok := requireDeploymentID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
containerID := c.Query("container_id")
|
|
if containerID == "" {
|
|
common.ApiErrorMsg(c, "container_id parameter is required")
|
|
return
|
|
}
|
|
level := c.Query("level")
|
|
stream := c.Query("stream")
|
|
cursor := c.Query("cursor")
|
|
limitStr := c.Query("limit")
|
|
follow := c.Query("follow") == "true"
|
|
|
|
var limit int = 100
|
|
if limitStr != "" {
|
|
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
|
|
limit = parsedLimit
|
|
if limit > 1000 {
|
|
limit = 1000
|
|
}
|
|
}
|
|
}
|
|
|
|
opts := &ionet.GetLogsOptions{
|
|
Level: level,
|
|
Stream: stream,
|
|
Limit: limit,
|
|
Cursor: cursor,
|
|
Follow: follow,
|
|
}
|
|
|
|
if startTime := c.Query("start_time"); startTime != "" {
|
|
if t, err := time.Parse(time.RFC3339, startTime); err == nil {
|
|
opts.StartTime = &t
|
|
}
|
|
}
|
|
if endTime := c.Query("end_time"); endTime != "" {
|
|
if t, err := time.Parse(time.RFC3339, endTime); err == nil {
|
|
opts.EndTime = &t
|
|
}
|
|
}
|
|
|
|
rawLogs, err := client.GetContainerLogsRaw(deploymentID, containerID, opts)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
common.ApiSuccess(c, rawLogs)
|
|
}
|
|
|
|
func ListDeploymentContainers(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
deploymentID, ok := requireDeploymentID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
containers, err := client.ListContainers(deploymentID)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
|
|
items := make([]map[string]interface{}, 0)
|
|
if containers != nil {
|
|
items = make([]map[string]interface{}, 0, len(containers.Workers))
|
|
for _, ctr := range containers.Workers {
|
|
events := make([]map[string]interface{}, 0, len(ctr.ContainerEvents))
|
|
for _, event := range ctr.ContainerEvents {
|
|
events = append(events, map[string]interface{}{
|
|
"time": event.Time.Unix(),
|
|
"message": event.Message,
|
|
})
|
|
}
|
|
|
|
items = append(items, map[string]interface{}{
|
|
"container_id": ctr.ContainerID,
|
|
"device_id": ctr.DeviceID,
|
|
"status": strings.ToLower(strings.TrimSpace(ctr.Status)),
|
|
"hardware": ctr.Hardware,
|
|
"brand_name": ctr.BrandName,
|
|
"created_at": ctr.CreatedAt.Unix(),
|
|
"uptime_percent": ctr.UptimePercent,
|
|
"gpus_per_container": ctr.GPUsPerContainer,
|
|
"public_url": ctr.PublicURL,
|
|
"events": events,
|
|
})
|
|
}
|
|
}
|
|
|
|
response := gin.H{
|
|
"total": 0,
|
|
"containers": items,
|
|
}
|
|
if containers != nil {
|
|
response["total"] = containers.Total
|
|
}
|
|
|
|
common.ApiSuccess(c, response)
|
|
}
|
|
|
|
func GetContainerDetails(c *gin.Context) {
|
|
client, ok := getIoEnterpriseClient(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
deploymentID, ok := requireDeploymentID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
containerID, ok := requireContainerID(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
details, err := client.GetContainerDetails(deploymentID, containerID)
|
|
if err != nil {
|
|
common.ApiError(c, err)
|
|
return
|
|
}
|
|
if details == nil {
|
|
common.ApiErrorMsg(c, "container details not found")
|
|
return
|
|
}
|
|
|
|
events := make([]map[string]interface{}, 0, len(details.ContainerEvents))
|
|
for _, event := range details.ContainerEvents {
|
|
events = append(events, map[string]interface{}{
|
|
"time": event.Time.Unix(),
|
|
"message": event.Message,
|
|
})
|
|
}
|
|
|
|
data := gin.H{
|
|
"deployment_id": deploymentID,
|
|
"container_id": details.ContainerID,
|
|
"device_id": details.DeviceID,
|
|
"status": strings.ToLower(strings.TrimSpace(details.Status)),
|
|
"hardware": details.Hardware,
|
|
"brand_name": details.BrandName,
|
|
"created_at": details.CreatedAt.Unix(),
|
|
"uptime_percent": details.UptimePercent,
|
|
"gpus_per_container": details.GPUsPerContainer,
|
|
"public_url": details.PublicURL,
|
|
"events": events,
|
|
}
|
|
|
|
common.ApiSuccess(c, data)
|
|
}
|