using System.Text;
using System.Windows;
using AxCopilot.SDK;
using AxCopilot.Services;
using AxCopilot.Themes;
namespace AxCopilot.Handlers;
///
/// L16-2: Docker 컨테이너·이미지 조회 핸들러. "docker" 프리픽스로 사용합니다.
///
/// 예: docker → 실행 중 컨테이너 목록
/// docker all → 모든 컨테이너 (중지 포함)
/// docker images → 로컬 이미지 목록
/// docker ps → 컨테이너 목록 (docker ps 동일)
/// docker stop → 컨테이너 중지
/// docker start → 컨테이너 시작
/// docker logs → 컨테이너 로그 (터미널)
/// docker shell → 컨테이너 shell 접속
/// Enter → 명령 실행 또는 컨테이너 ID 복사.
///
public class DockerHandler : IActionHandler
{
public string? Prefix => "docker";
public PluginMetadata Metadata => new(
"Docker",
"Docker 컨테이너·이미지 조회 — 시작·중지·로그·쉘",
"1.0",
"AX");
public Task> GetItemsAsync(string query, CancellationToken ct)
{
var q = query.Trim();
var items = new List();
if (!IsDockerAvailable())
{
items.Add(new LauncherItem("Docker를 찾을 수 없습니다",
"Docker Desktop이 설치되어 있는지 확인하세요", null, null, Symbol: "\uE756"));
items.Add(new LauncherItem("Docker Desktop 설치",
"https://www.docker.com/products/docker-desktop",
null, ("open_url", "https://www.docker.com/products/docker-desktop"), Symbol: "\uE756"));
return Task.FromResult>(items);
}
if (string.IsNullOrWhiteSpace(q))
{
var containers = GetContainers(running: true);
items.Add(new LauncherItem(
$"실행 중 컨테이너 {containers.Count}개",
"docker ps / docker all / docker images",
null, null, Symbol: "\uE756"));
if (containers.Count == 0)
items.Add(new LauncherItem("실행 중인 컨테이너 없음", "docker all → 전체 목록", null, null, Symbol: "\uE946"));
else
foreach (var c in containers)
items.Add(MakeContainerItem(c));
items.Add(new LauncherItem("docker images", "로컬 이미지 목록", null, ("sub", "images"), Symbol: "\uE756"));
items.Add(new LauncherItem("docker all", "모든 컨테이너 목록", null, ("sub", "all"), Symbol: "\uE756"));
return Task.FromResult>(items);
}
var parts = q.Split(' ', StringSplitOptions.RemoveEmptyEntries);
var sub = parts[0].ToLowerInvariant();
switch (sub)
{
case "all":
case "ps":
{
var all = sub == "all";
var containers = GetContainers(running: !all);
items.Add(new LauncherItem(
$"{(all ? "전체" : "실행 중")} 컨테이너 {containers.Count}개",
"", null, null, Symbol: "\uE756"));
foreach (var c in containers)
items.Add(MakeContainerItem(c));
if (containers.Count == 0)
items.Add(new LauncherItem("컨테이너 없음", "", null, null, Symbol: "\uE946"));
break;
}
case "images":
case "image":
case "img":
{
var images = GetImages();
items.Add(new LauncherItem($"로컬 이미지 {images.Count}개", "", null, null, Symbol: "\uE756"));
foreach (var img in images)
items.Add(MakeImageItem(img));
if (images.Count == 0)
items.Add(new LauncherItem("이미지 없음", "docker pull <이름> 으로 받기", null, null, Symbol: "\uE946"));
break;
}
case "stop":
{
var name = parts.Length > 1 ? parts[1] : "";
if (string.IsNullOrWhiteSpace(name))
{
// 실행 중 컨테이너 목록 표시 → 클릭 시 stop
var running = GetContainers(running: true);
items.Add(new LauncherItem("중지할 컨테이너 선택", "Enter → 중지", null, null, Symbol: "\uE756"));
foreach (var c in running)
items.Add(new LauncherItem($"중지: {c.Name}", c.Image,
null, ("stop", c.Id), Symbol: "\uE756"));
}
else
{
items.Add(new LauncherItem($"컨테이너 중지: {name}",
$"docker stop {name} · Enter 실행",
null, ("stop", name), Symbol: "\uE756"));
}
break;
}
case "start":
{
var name = parts.Length > 1 ? parts[1] : "";
if (string.IsNullOrWhiteSpace(name))
{
var stopped = GetContainers(running: false, stopped: true);
items.Add(new LauncherItem("시작할 컨테이너 선택", "Enter → 시작", null, null, Symbol: "\uE756"));
foreach (var c in stopped)
items.Add(new LauncherItem($"시작: {c.Name}", c.Image,
null, ("start", c.Id), Symbol: "\uE756"));
}
else
{
items.Add(new LauncherItem($"컨테이너 시작: {name}",
$"docker start {name} · Enter 실행",
null, ("start", name), Symbol: "\uE756"));
}
break;
}
case "logs":
case "log":
{
var name = parts.Length > 1 ? parts[1] : "";
if (string.IsNullOrWhiteSpace(name))
{
var running = GetContainers(running: true);
foreach (var c in running)
items.Add(new LauncherItem($"로그: {c.Name}", "Enter → 터미널에서 로그 보기",
null, ("logs", c.Name), Symbol: "\uE756"));
}
else
{
items.Add(new LauncherItem($"로그: {name}", $"docker logs -f {name}",
null, ("logs", name), Symbol: "\uE756"));
}
break;
}
case "shell":
case "exec":
case "sh":
{
var name = parts.Length > 1 ? parts[1] : "";
if (string.IsNullOrWhiteSpace(name))
{
var running = GetContainers(running: true);
foreach (var c in running)
items.Add(new LauncherItem($"쉘: {c.Name}", "Enter → 컨테이너 shell 접속",
null, ("shell", c.Name), Symbol: "\uE756"));
}
else
{
items.Add(new LauncherItem($"쉘 접속: {name}", $"docker exec -it {name} sh",
null, ("shell", name), Symbol: "\uE756"));
}
break;
}
default:
{
// 컨테이너 이름 검색
var all = GetContainers(running: false, stopped: true, all: true);
var found = all.Where(c =>
c.Name.Contains(q, StringComparison.OrdinalIgnoreCase) ||
c.Image.Contains(q, StringComparison.OrdinalIgnoreCase)).ToList();
if (found.Count > 0)
foreach (var c in found)
items.Add(MakeContainerItem(c));
else
items.Add(new LauncherItem($"'{q}' 컨테이너 없음",
"docker all → 전체 목록", null, null, Symbol: "\uE946"));
break;
}
}
return Task.FromResult>(items);
}
public Task ExecuteAsync(LauncherItem item, CancellationToken ct)
{
switch (item.Data)
{
case ("stop", string id):
RunDockerSilent($"stop {id}");
NotificationService.Notify("Docker", $"중지: {id}");
break;
case ("start", string id):
RunDockerSilent($"start {id}");
NotificationService.Notify("Docker", $"시작: {id}");
break;
case ("logs", string name):
RunInTerminal($"docker logs -f {name}");
break;
case ("shell", string name):
RunInTerminal($"docker exec -it {name} sh");
break;
case ("copy", string text):
try
{
System.Windows.Application.Current.Dispatcher.Invoke(() => Clipboard.SetText(text));
NotificationService.Notify("Docker", "복사됨");
}
catch { /* 비핵심 */ }
break;
case ("open_url", string url):
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{ FileName = url, UseShellExecute = true });
}
catch { /* 비핵심 */ }
break;
}
return Task.CompletedTask;
}
// ── Docker 조회 ──────────────────────────────────────────────────────────
private record DockerContainer(string Id, string Name, string Image, string Status, string Ports);
private record DockerImage(string Repository, string Tag, string Id, string Size, string Created);
private static List GetContainers(bool running = true, bool stopped = false, bool all = false)
{
var result = new List();
try
{
var filter = all || (!running && stopped) ? "-a" : (running ? "" : "--filter status=exited");
var output = RunDockerOutput($"ps {filter} --format \"{{{{.ID}}}}\\t{{{{.Names}}}}\\t{{{{.Image}}}}\\t{{{{.Status}}}}\\t{{{{.Ports}}}}\"");
foreach (var line in output.Split('\n'))
{
var trimmed = line.Trim();
if (string.IsNullOrWhiteSpace(trimmed)) continue;
var cols = trimmed.Split('\t');
if (cols.Length < 4) continue;
result.Add(new DockerContainer(
Id: cols[0],
Name: cols[1],
Image: cols[2],
Status: cols[3],
Ports: cols.Length > 4 ? cols[4] : ""));
}
}
catch { /* Docker 없음 */ }
return result;
}
private static List GetImages()
{
var result = new List();
try
{
var output = RunDockerOutput("images --format \"{{.Repository}}\\t{{.Tag}}\\t{{.ID}}\\t{{.Size}}\\t{{.CreatedSince}}\"");
foreach (var line in output.Split('\n'))
{
var trimmed = line.Trim();
if (string.IsNullOrWhiteSpace(trimmed)) continue;
var cols = trimmed.Split('\t');
if (cols.Length < 4) continue;
result.Add(new DockerImage(
Repository: cols[0],
Tag: cols.Length > 1 ? cols[1] : "latest",
Id: cols.Length > 2 ? cols[2] : "",
Size: cols.Length > 3 ? cols[3] : "",
Created: cols.Length > 4 ? cols[4] : ""));
}
}
catch { /* Docker 없음 */ }
return result;
}
private static string RunDockerOutput(string args)
{
var psi = new System.Diagnostics.ProcessStartInfo
{
FileName = "docker",
Arguments = args,
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.UTF8,
};
using var proc = System.Diagnostics.Process.Start(psi);
if (proc == null) return "";
var output = proc.StandardOutput.ReadToEnd();
proc.WaitForExit(5000);
return output;
}
private static void RunDockerSilent(string args)
{
try
{
var psi = new System.Diagnostics.ProcessStartInfo
{
FileName = "docker", Arguments = args,
UseShellExecute = false, CreateNoWindow = true,
};
using var proc = System.Diagnostics.Process.Start(psi);
proc?.WaitForExit(10000);
}
catch { /* 비핵심 */ }
}
private static bool IsDockerAvailable()
{
try
{
var psi = new System.Diagnostics.ProcessStartInfo
{
FileName = "docker", Arguments = "version --format json",
UseShellExecute = false, CreateNoWindow = true,
RedirectStandardOutput = true,
};
using var proc = System.Diagnostics.Process.Start(psi);
proc?.WaitForExit(3000);
return proc?.ExitCode == 0;
}
catch { return false; }
}
private static LauncherItem MakeContainerItem(DockerContainer c)
{
var isRunning = c.Status.StartsWith("Up", StringComparison.OrdinalIgnoreCase);
var icon = isRunning ? "\uE768" : "\uE71A";
var ports = string.IsNullOrWhiteSpace(c.Ports) ? "" : $" · {c.Ports}";
return new LauncherItem(c.Name,
$"{c.Status}{ports} · {c.Image}",
null, ("copy", c.Id), Symbol: icon);
}
private static LauncherItem MakeImageItem(DockerImage img)
{
var name = img.Tag == "" ? img.Repository : $"{img.Repository}:{img.Tag}";
return new LauncherItem(name, $"{img.Size} · {img.Created} · {img.Id[..Math.Min(12, img.Id.Length)]}",
null, ("copy", name), Symbol: "\uE756");
}
private static void RunInTerminal(string cmd)
{
try
{
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = "cmd", Arguments = $"/K {cmd}", UseShellExecute = true,
});
}
catch { /* 비핵심 */ }
}
}