wip: Windows desktop app scaffold (WinUI 3/.NET 8) + NSIS installer + docs
Owner decision pending (STATE.md DC-100 tick): adopt/ship/park. Preserved from fragile git stash to named branch 2026-08-23. NOTE: requires a Windows build machine (WinUI XAML compiler + MSIX do not cross-build on Linux) — see WINDOWS_APP_BUILD.md in this tree. Secret-scanned clean 2026-08-23 (no keys/tokens/PEM in tree).
This commit is contained in:
@@ -0,0 +1,284 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Nodes;
|
||||
using DashCaddy.Desktop.Models;
|
||||
|
||||
namespace DashCaddy.Desktop.Services;
|
||||
|
||||
public static class ComposeParser
|
||||
{
|
||||
public static List<ServiceModel> Parse(string yamlContent)
|
||||
{
|
||||
var services = new List<ServiceModel>();
|
||||
|
||||
// Simple YAML parsing - in production use YamlDotNet
|
||||
// For now, we'll do a basic JSON-based approach assuming compose is converted
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Deserialize<JsonNode>(yamlContent); // This won't work for YAML directly
|
||||
// Real implementation would use YamlDotNet
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Fallback
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static List<ServiceModel> ParseJson(string jsonContent)
|
||||
{
|
||||
var compose = JsonSerializer.Deserialize<ComposeFile>(jsonContent, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
|
||||
var services = new List<ServiceModel>();
|
||||
|
||||
if (compose?.Services != null)
|
||||
{
|
||||
foreach (var (name, svc) in compose.Services)
|
||||
{
|
||||
var port = ExtractPort(svc);
|
||||
var env = svc.Environment ?? new Dictionary<string, string>();
|
||||
|
||||
services.Add(new ServiceModel
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N")[..8],
|
||||
Name = name,
|
||||
Type = "docker-compose",
|
||||
Image = svc.Image ?? "",
|
||||
Host = "localhost",
|
||||
Port = port,
|
||||
Environment = env,
|
||||
Volumes = svc.Volumes ?? new List<string>(),
|
||||
Labels = svc.Labels ?? new Dictionary<string, string>(),
|
||||
ComposeFile = jsonContent
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static int ExtractPort(ComposeService svc)
|
||||
{
|
||||
if (svc.Ports != null)
|
||||
{
|
||||
foreach (var (key, value) in svc.Ports)
|
||||
{
|
||||
if (int.TryParse(key.Split(':')[0], out var hostPort))
|
||||
return hostPort;
|
||||
if (int.TryParse(key, out var containerPort))
|
||||
return containerPort;
|
||||
}
|
||||
}
|
||||
return 80;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TemplateRegistry
|
||||
{
|
||||
private static readonly Dictionary<string, ServiceTemplate> Templates = new()
|
||||
{
|
||||
["plex"] = new ServiceTemplate
|
||||
{
|
||||
Id = "plex",
|
||||
Name = "Plex Media Server",
|
||||
Description = "Media server for movies, TV shows, and music",
|
||||
Icon = "📺",
|
||||
DefaultPort = 32400,
|
||||
Image = "plexinc/pms-docker:latest",
|
||||
Environment = new Dictionary<string, string>
|
||||
{
|
||||
["PLEX_CLAIM"] = "",
|
||||
["TZ"] = "America/Los_Angeles"
|
||||
},
|
||||
Volumes = new List<string>
|
||||
{
|
||||
"E:/dockerdata/plex/config:/config",
|
||||
"E:/dockerdata/plex/transcode:/transcode",
|
||||
"E:/media:/data"
|
||||
},
|
||||
RequiredVariables = new[] { "PLEX_CLAIM" }
|
||||
},
|
||||
["homeassistant"] = new ServiceTemplate
|
||||
{
|
||||
Id = "homeassistant",
|
||||
Name = "Home Assistant",
|
||||
Description = "Open source home automation platform",
|
||||
Icon = "🏠",
|
||||
DefaultPort = 8123,
|
||||
Image = "ghcr.io/home-assistant/home-assistant:stable",
|
||||
Environment = new Dictionary<string, string>
|
||||
{
|
||||
["TZ"] = "America/Los_Angeles"
|
||||
},
|
||||
Volumes = new List<string>
|
||||
{
|
||||
"E:/dockerdata/homeassistant:/config"
|
||||
},
|
||||
NetworkMode = "host"
|
||||
},
|
||||
["jellyfin"] = new ServiceTemplate
|
||||
{
|
||||
Id = "jellyfin",
|
||||
Name = "Jellyfin",
|
||||
Description = "Free software media system",
|
||||
Icon = "🎬",
|
||||
DefaultPort = 8096,
|
||||
Image = "jellyfin/jellyfin:latest",
|
||||
Environment = new Dictionary<string, string>
|
||||
{
|
||||
["TZ"] = "America/Los_Angeles"
|
||||
},
|
||||
Volumes = new List<string>
|
||||
{
|
||||
"E:/dockerdata/jellyfin/config:/config",
|
||||
"E:/dockerdata/jellyfin/cache:/cache",
|
||||
"E:/media:/media"
|
||||
}
|
||||
},
|
||||
["portainer"] = new ServiceTemplate
|
||||
{
|
||||
Id = "portainer",
|
||||
Name = "Portainer",
|
||||
Description = "Docker container management UI",
|
||||
Icon = "🐳",
|
||||
DefaultPort = 9443,
|
||||
Image = "portainer/portainer-ce:latest",
|
||||
Volumes = new List<string>
|
||||
{
|
||||
"E:/dockerdata/portainer:/data",
|
||||
"//./pipe/docker_engine:/var/run/docker.sock"
|
||||
}
|
||||
},
|
||||
["adguard"] = new ServiceTemplate
|
||||
{
|
||||
Id = "adguard",
|
||||
Name = "AdGuard Home",
|
||||
Description = "Network-wide ad blocking & DNS",
|
||||
Icon = "🛡️",
|
||||
DefaultPort = 3000,
|
||||
Image = "adguard/adguardhome:latest",
|
||||
Volumes = new List<string>
|
||||
{
|
||||
"E:/dockerdata/adguard/work:/opt/adguardhome/work",
|
||||
"E:/dockerdata/adguard/conf:/opt/adguardhome/conf"
|
||||
},
|
||||
Ports = new Dictionary<int, int> { [53] = 53, [3000] = 3000 }
|
||||
},
|
||||
["uptime-kuma"] = new ServiceTemplate
|
||||
{
|
||||
Id = "uptime-kuma",
|
||||
Name = "Uptime Kuma",
|
||||
Description = "Self-hosted monitoring tool",
|
||||
Icon = "📊",
|
||||
DefaultPort = 3001,
|
||||
Image = "louislam/uptime-kuma:1",
|
||||
Volumes = new List<string>
|
||||
{
|
||||
"E:/dockerdata/uptime-kuma:/app/data"
|
||||
}
|
||||
},
|
||||
["vaultwarden"] = new ServiceTemplate
|
||||
{
|
||||
Id = "vaultwarden",
|
||||
Name = "Vaultwarden (Bitwarden)",
|
||||
Description = "Self-hosted password manager",
|
||||
Icon = "🔐",
|
||||
DefaultPort = 8080,
|
||||
Image = "vaultwarden/server:latest",
|
||||
Environment = new Dictionary<string, string>
|
||||
{
|
||||
["SIGNUPS_ALLOWED"] = "true",
|
||||
["INVITATIONS_ALLOWED"] = "true"
|
||||
},
|
||||
Volumes = new List<string>
|
||||
{
|
||||
"E:/dockerdata/vaultwarden:/data"
|
||||
}
|
||||
},
|
||||
["paperless"] = new ServiceTemplate
|
||||
{
|
||||
Id = "paperless",
|
||||
Name = "Paperless-ngx",
|
||||
Description = "Document management system",
|
||||
Icon = "📄",
|
||||
DefaultPort = 8000,
|
||||
Image = "ghcr.io/paperless-ngx/paperless-ngx:latest",
|
||||
Environment = new Dictionary<string, string>
|
||||
{
|
||||
["PAPERLESS_REDIS"] = "redis://localhost:6379",
|
||||
["PAPERLESS_DBHOST"] = "localhost",
|
||||
["PAPERLESS_DBNAME"] = "paperless",
|
||||
["PAPERLESS_DBUSER"] = "paperless",
|
||||
["PAPERLESS_DBPASS"] = "paperless"
|
||||
},
|
||||
Volumes = new List<string>
|
||||
{
|
||||
"E:/dockerdata/paperless/data:/usr/src/paperless/data",
|
||||
"E:/dockerdata/paperless/media:/usr/src/paperless/media",
|
||||
"E:/dockerdata/paperless/export:/usr/src/paperless/export",
|
||||
"E:/dockerdata/paperless/consume:/usr/src/paperless/consume"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static ServiceTemplate Get(string id)
|
||||
{
|
||||
return Templates.TryGetValue(id, out var template) ? template : null;
|
||||
}
|
||||
|
||||
public static IEnumerable<ServiceTemplate> GetAll()
|
||||
{
|
||||
return Templates.Values;
|
||||
}
|
||||
}
|
||||
|
||||
public class ServiceTemplate
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string Description { get; set; }
|
||||
public string Icon { get; set; }
|
||||
public int DefaultPort { get; set; }
|
||||
public string Image { get; set; }
|
||||
public Dictionary<string, string> Environment { get; set; } = new();
|
||||
public List<string> Volumes { get; set; } = new();
|
||||
public Dictionary<int, int> Ports { get; set; } = new();
|
||||
public string NetworkMode { get; set; }
|
||||
public string[] RequiredVariables { get; set; } = Array.Empty<string>();
|
||||
|
||||
public ServiceModel Instantiate(Dictionary<string, string> variables)
|
||||
{
|
||||
var env = new Dictionary<string, string>(Environment);
|
||||
foreach (var (key, value) in variables)
|
||||
{
|
||||
env[key] = value;
|
||||
}
|
||||
|
||||
var port = DefaultPort;
|
||||
if (Ports.Count > 0)
|
||||
{
|
||||
port = Ports.Keys.First();
|
||||
}
|
||||
|
||||
return new ServiceModel
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N")[..8],
|
||||
Name = Name,
|
||||
Type = "template",
|
||||
Image = Image,
|
||||
Host = "localhost",
|
||||
Port = port,
|
||||
Environment = env,
|
||||
Volumes = Volumes,
|
||||
Labels = new Dictionary<string, string>
|
||||
{
|
||||
["dashcaddy.template"] = Id,
|
||||
["dashcaddy.managed"] = "true"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user