mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 05:20:18 +00:00
* wip ionet integrate * wip ionet integrate * wip ionet integrate * ollama wip * wip * feat: ionet integration & ollama manage * fix merge conflict * wip * fix: test conn cors * wip * fix ionet * fix ionet * wip * fix model select * refactor: Remove `pkg/ionet` test files and update related Go source and web UI model deployment components. * feat: Enhance model deployment UI with styling improvements, updated text, and a new description component. * Revert "feat: Enhance model deployment UI with styling improvements, updated text, and a new description component." This reverts commit 8b75cb5bf0d1a534b339df8c033be9a6c7df7964.
303 lines
8.3 KiB
Go
303 lines
8.3 KiB
Go
package ionet
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
// ListContainers retrieves all containers for a specific deployment
|
|
func (c *Client) ListContainers(deploymentID string) (*ContainerList, error) {
|
|
if deploymentID == "" {
|
|
return nil, fmt.Errorf("deployment ID cannot be empty")
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/deployment/%s/containers", deploymentID)
|
|
|
|
resp, err := c.makeRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list containers: %w", err)
|
|
}
|
|
|
|
var containerList ContainerList
|
|
if err := decodeDataWithFlexibleTimes(resp.Body, &containerList); err != nil {
|
|
return nil, fmt.Errorf("failed to parse containers list: %w", err)
|
|
}
|
|
|
|
return &containerList, nil
|
|
}
|
|
|
|
// GetContainerDetails retrieves detailed information about a specific container
|
|
func (c *Client) GetContainerDetails(deploymentID, containerID string) (*Container, error) {
|
|
if deploymentID == "" {
|
|
return nil, fmt.Errorf("deployment ID cannot be empty")
|
|
}
|
|
if containerID == "" {
|
|
return nil, fmt.Errorf("container ID cannot be empty")
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/deployment/%s/container/%s", deploymentID, containerID)
|
|
|
|
resp, err := c.makeRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get container details: %w", err)
|
|
}
|
|
|
|
// API response format not documented, assuming direct format
|
|
var container Container
|
|
if err := decodeWithFlexibleTimes(resp.Body, &container); err != nil {
|
|
return nil, fmt.Errorf("failed to parse container details: %w", err)
|
|
}
|
|
|
|
return &container, nil
|
|
}
|
|
|
|
// GetContainerJobs retrieves containers jobs for a specific container (similar to containers endpoint)
|
|
func (c *Client) GetContainerJobs(deploymentID, containerID string) (*ContainerList, error) {
|
|
if deploymentID == "" {
|
|
return nil, fmt.Errorf("deployment ID cannot be empty")
|
|
}
|
|
if containerID == "" {
|
|
return nil, fmt.Errorf("container ID cannot be empty")
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/deployment/%s/containers-jobs/%s", deploymentID, containerID)
|
|
|
|
resp, err := c.makeRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get container jobs: %w", err)
|
|
}
|
|
|
|
var containerList ContainerList
|
|
if err := decodeDataWithFlexibleTimes(resp.Body, &containerList); err != nil {
|
|
return nil, fmt.Errorf("failed to parse container jobs: %w", err)
|
|
}
|
|
|
|
return &containerList, nil
|
|
}
|
|
|
|
// buildLogEndpoint constructs the request path for fetching logs
|
|
func buildLogEndpoint(deploymentID, containerID string, opts *GetLogsOptions) (string, error) {
|
|
if deploymentID == "" {
|
|
return "", fmt.Errorf("deployment ID cannot be empty")
|
|
}
|
|
if containerID == "" {
|
|
return "", fmt.Errorf("container ID cannot be empty")
|
|
}
|
|
|
|
params := make(map[string]interface{})
|
|
|
|
if opts != nil {
|
|
if opts.Level != "" {
|
|
params["level"] = opts.Level
|
|
}
|
|
if opts.Stream != "" {
|
|
params["stream"] = opts.Stream
|
|
}
|
|
if opts.Limit > 0 {
|
|
params["limit"] = opts.Limit
|
|
}
|
|
if opts.Cursor != "" {
|
|
params["cursor"] = opts.Cursor
|
|
}
|
|
if opts.Follow {
|
|
params["follow"] = true
|
|
}
|
|
|
|
if opts.StartTime != nil {
|
|
params["start_time"] = opts.StartTime
|
|
}
|
|
if opts.EndTime != nil {
|
|
params["end_time"] = opts.EndTime
|
|
}
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/deployment/%s/log/%s", deploymentID, containerID)
|
|
endpoint += buildQueryParams(params)
|
|
|
|
return endpoint, nil
|
|
}
|
|
|
|
// GetContainerLogs retrieves logs for containers in a deployment and normalizes them
|
|
func (c *Client) GetContainerLogs(deploymentID, containerID string, opts *GetLogsOptions) (*ContainerLogs, error) {
|
|
raw, err := c.GetContainerLogsRaw(deploymentID, containerID, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
logs := &ContainerLogs{
|
|
ContainerID: containerID,
|
|
}
|
|
|
|
if raw == "" {
|
|
return logs, nil
|
|
}
|
|
|
|
normalized := strings.ReplaceAll(raw, "\r\n", "\n")
|
|
lines := strings.Split(normalized, "\n")
|
|
logs.Logs = lo.FilterMap(lines, func(line string, _ int) (LogEntry, bool) {
|
|
if strings.TrimSpace(line) == "" {
|
|
return LogEntry{}, false
|
|
}
|
|
return LogEntry{Message: line}, true
|
|
})
|
|
|
|
return logs, nil
|
|
}
|
|
|
|
// GetContainerLogsRaw retrieves the raw text logs for a specific container
|
|
func (c *Client) GetContainerLogsRaw(deploymentID, containerID string, opts *GetLogsOptions) (string, error) {
|
|
endpoint, err := buildLogEndpoint(deploymentID, containerID, opts)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
resp, err := c.makeRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to get container logs: %w", err)
|
|
}
|
|
|
|
return string(resp.Body), nil
|
|
}
|
|
|
|
// StreamContainerLogs streams real-time logs for a specific container
|
|
// This method uses a callback function to handle incoming log entries
|
|
func (c *Client) StreamContainerLogs(deploymentID, containerID string, opts *GetLogsOptions, callback func(*LogEntry) error) error {
|
|
if deploymentID == "" {
|
|
return fmt.Errorf("deployment ID cannot be empty")
|
|
}
|
|
if containerID == "" {
|
|
return fmt.Errorf("container ID cannot be empty")
|
|
}
|
|
if callback == nil {
|
|
return fmt.Errorf("callback function cannot be nil")
|
|
}
|
|
|
|
// Set follow to true for streaming
|
|
if opts == nil {
|
|
opts = &GetLogsOptions{}
|
|
}
|
|
opts.Follow = true
|
|
|
|
endpoint, err := buildLogEndpoint(deploymentID, containerID, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Note: This is a simplified implementation. In a real scenario, you might want to use
|
|
// Server-Sent Events (SSE) or WebSocket for streaming logs
|
|
for {
|
|
resp, err := c.makeRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to stream container logs: %w", err)
|
|
}
|
|
|
|
var logs ContainerLogs
|
|
if err := decodeWithFlexibleTimes(resp.Body, &logs); err != nil {
|
|
return fmt.Errorf("failed to parse container logs: %w", err)
|
|
}
|
|
|
|
// Call the callback for each log entry
|
|
for _, logEntry := range logs.Logs {
|
|
if err := callback(&logEntry); err != nil {
|
|
return fmt.Errorf("callback error: %w", err)
|
|
}
|
|
}
|
|
|
|
// If there are no more logs or we have a cursor, continue polling
|
|
if !logs.HasMore && logs.NextCursor == "" {
|
|
break
|
|
}
|
|
|
|
// Update cursor for next request
|
|
if logs.NextCursor != "" {
|
|
opts.Cursor = logs.NextCursor
|
|
endpoint, err = buildLogEndpoint(deploymentID, containerID, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Wait a bit before next poll to avoid overwhelming the API
|
|
time.Sleep(2 * time.Second)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RestartContainer restarts a specific container (if supported by the API)
|
|
func (c *Client) RestartContainer(deploymentID, containerID string) error {
|
|
if deploymentID == "" {
|
|
return fmt.Errorf("deployment ID cannot be empty")
|
|
}
|
|
if containerID == "" {
|
|
return fmt.Errorf("container ID cannot be empty")
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/deployment/%s/container/%s/restart", deploymentID, containerID)
|
|
|
|
_, err := c.makeRequest("POST", endpoint, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to restart container: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// StopContainer stops a specific container (if supported by the API)
|
|
func (c *Client) StopContainer(deploymentID, containerID string) error {
|
|
if deploymentID == "" {
|
|
return fmt.Errorf("deployment ID cannot be empty")
|
|
}
|
|
if containerID == "" {
|
|
return fmt.Errorf("container ID cannot be empty")
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/deployment/%s/container/%s/stop", deploymentID, containerID)
|
|
|
|
_, err := c.makeRequest("POST", endpoint, nil)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to stop container: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ExecuteInContainer executes a command in a specific container (if supported by the API)
|
|
func (c *Client) ExecuteInContainer(deploymentID, containerID string, command []string) (string, error) {
|
|
if deploymentID == "" {
|
|
return "", fmt.Errorf("deployment ID cannot be empty")
|
|
}
|
|
if containerID == "" {
|
|
return "", fmt.Errorf("container ID cannot be empty")
|
|
}
|
|
if len(command) == 0 {
|
|
return "", fmt.Errorf("command cannot be empty")
|
|
}
|
|
|
|
reqBody := map[string]interface{}{
|
|
"command": command,
|
|
}
|
|
|
|
endpoint := fmt.Sprintf("/deployment/%s/container/%s/exec", deploymentID, containerID)
|
|
|
|
resp, err := c.makeRequest("POST", endpoint, reqBody)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to execute command in container: %w", err)
|
|
}
|
|
|
|
var result map[string]interface{}
|
|
if err := json.Unmarshal(resp.Body, &result); err != nil {
|
|
return "", fmt.Errorf("failed to parse execution result: %w", err)
|
|
}
|
|
|
|
if output, ok := result["output"].(string); ok {
|
|
return output, nil
|
|
}
|
|
|
|
return string(resp.Body), nil
|
|
}
|