package views import ( "encoding/json" "fmt" "strings" "time" "github.com/lucasdataproyects/devfactory/shell" ) const dockerTimeout = 15 * time.Second // --- Containers --- func ListContainers() ([]DockerContainer, error) { result := shell.RunWithTimeout("docker", dockerTimeout, "ps", "-a", "--format", "{{json .}}") stdout, err := result.Both() if err != nil { return nil, err } return parseJSONLines[DockerContainer](stdout.Stdout) } func StartContainer(id string) error { _, err := shell.RunWithTimeout("docker", dockerTimeout, "start", id).Both() return err } func StopContainer(id string) error { _, err := shell.RunWithTimeout("docker", dockerTimeout, "stop", id).Both() return err } func RestartContainer(id string) error { _, err := shell.RunWithTimeout("docker", dockerTimeout, "restart", id).Both() return err } func ContainerLogs(id string, lines int) (string, error) { result := shell.RunWithTimeout("docker", dockerTimeout, "logs", "--tail", itoa(lines), id) out, err := result.Both() if err != nil { return "", err } // docker logs writes to both stdout and stderr output := out.Stdout if out.Stderr != "" { if output != "" { output += "\n" } output += out.Stderr } return output, nil } // --- Images --- func ListImages() ([]DockerImage, error) { result := shell.RunWithTimeout("docker", dockerTimeout, "image", "ls", "--format", "{{json .}}") stdout, err := result.Both() if err != nil { return nil, err } return parseJSONLines[DockerImage](stdout.Stdout) } func PullImage(name string) (string, error) { result := shell.RunWithTimeout("docker", 120*time.Second, "pull", name) out, err := result.Both() if err != nil { return "", err } return out.Stdout, nil } func RemoveImage(id string) error { _, err := shell.RunWithTimeout("docker", dockerTimeout, "rmi", id).Both() return err } // --- Volumes --- func ListVolumes() ([]DockerVolume, error) { result := shell.RunWithTimeout("docker", dockerTimeout, "volume", "ls", "--format", "{{json .}}") stdout, err := result.Both() if err != nil { return nil, err } return parseJSONLines[DockerVolume](stdout.Stdout) } func CreateVolume(name string) error { args := []string{"volume", "create"} if name != "" { args = append(args, name) } _, err := shell.RunWithTimeout("docker", dockerTimeout, args...).Both() return err } func RemoveVolume(name string) error { _, err := shell.RunWithTimeout("docker", dockerTimeout, "volume", "rm", name).Both() return err } // --- Networks --- func ListNetworks() ([]DockerNetwork, error) { result := shell.RunWithTimeout("docker", dockerTimeout, "network", "ls", "--format", "{{json .}}") stdout, err := result.Both() if err != nil { return nil, err } return parseJSONLines[DockerNetwork](stdout.Stdout) } func CreateNetwork(name string) error { _, err := shell.RunWithTimeout("docker", dockerTimeout, "network", "create", name).Both() return err } func RemoveNetwork(name string) error { _, err := shell.RunWithTimeout("docker", dockerTimeout, "network", "rm", name).Both() return err } // --- Compose --- func ComposePS() ([]ComposeService, error) { result := shell.RunWithTimeout("docker", dockerTimeout, "compose", "ps", "--format", "json") stdout, err := result.Both() if err != nil { return nil, err } // docker compose ps --format json returns a JSON array var services []ComposeService if err := json.Unmarshal([]byte(stdout.Stdout), &services); err != nil { // Try line-by-line as fallback return parseJSONLines[ComposeService](stdout.Stdout) } return services, nil } func ComposeUp() (string, error) { result := shell.RunWithTimeout("docker", 120*time.Second, "compose", "up", "-d") out, err := result.Both() if err != nil { return "", err } return out.Stdout + out.Stderr, nil } func ComposeDown() (string, error) { result := shell.RunWithTimeout("docker", 60*time.Second, "compose", "down") out, err := result.Both() if err != nil { return "", err } return out.Stdout + out.Stderr, nil } func ComposeLogs(lines int) (string, error) { result := shell.RunWithTimeout("docker", dockerTimeout, "compose", "logs", "--tail", itoa(lines)) out, err := result.Both() if err != nil { return "", err } return out.Stdout + out.Stderr, nil } // --- Helpers --- func parseJSONLines[T any](s string) ([]T, error) { var result []T for _, line := range strings.Split(strings.TrimSpace(s), "\n") { line = strings.TrimSpace(line) if line == "" { continue } var item T if err := json.Unmarshal([]byte(line), &item); err != nil { continue } result = append(result, item) } return result, nil } func itoa(n int) string { return fmt.Sprintf("%d", n) }