- Go 98.8%
- Makefile 1.2%
| internal | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| Makefile | ||
| README.md | ||
uncloud-gui
A desktop GUI application built with Gio UI that serves as a graphical replacement for the uc CLI — the command-line tool for Uncloud, a lightweight Docker container orchestration platform.
Features
Every major uc command is surfaced through a dedicated UI panel:
| Page | CLI Commands Covered |
|---|---|
| Dashboard | uc machine ls, uc service ls, uc ps, uc wg show |
| Machines | uc machine ls/init/add/rm/rename/token |
| Services | uc service ls/run/rm/logs/inspect/scale/start/stop/exec, uc deploy |
| Volumes | uc volume ls/create/inspect/rm |
| Images | uc image ls/push |
| DNS | uc dns show/reserve/release |
| Caddy | uc caddy config/deploy |
| Contexts | uc ctx ls/use/connection |
| Terminal | Free-form uc <any command> + full command history log |
| Settings | Configure binary path, cluster context, --connect, --uncloud-config |
Prerequisites
- Go 1.21+
- Gio UI dependencies (platform libraries for GPU rendering):
- macOS: Xcode command-line tools
- Linux:
libwayland-dev libx11-dev libxkbcommon-x11-dev libgles2-mesa-dev - Windows: No extra deps
- The
ucbinary installed and available in$PATH(or configure a custom path in Settings)
Installation
# Install uc CLI first
brew install psviderski/tap/uncloud # macOS/Linux via Homebrew
# Clone and run uncloud-gui
git clone https://github.com/yourname/uncloud-gui
cd uncloud-gui
go mod tidy
go run .
Or build a standalone binary:
go build -o uncloud-gui .
./uncloud-gui
Project Structure
uncloud-gui/
├── main.go # Entry point, window setup
├── go.mod
└── internal/
├── runner/
│ └── runner.go # Wraps uc CLI execution (exec.Command)
├── theme/
│ └── colors.go # Dark theme color palette
└── ui/
├── app.go # App state, main event loop, page routing
├── sidebar.go # Left navigation panel
├── widgets.go # Shared helpers: Card, StatusBadge, TerminalLog, OutputBox
├── page_dashboard.go # Dashboard overview page
├── page_machines.go # Machines management page
├── page_services.go # Services management page (most complex)
├── page_generic.go # Generic command page + Volumes/Images/DNS/Caddy/Contexts
├── page_terminal.go # Terminal log + free-form command runner
└── page_settings.go # Settings / configuration page
Architecture
┌─────────────────────────────────────────────────────────┐
│ Gio Window │
│ ┌──────────────┐ ┌──────────────────────────────────┐ │
│ │ Sidebar │ │ Page Content │ │
│ │ │ │ │ │
│ │ • Dashboard │ │ Each page renders UI for a │ │
│ │ • Machines │ │ group of uc commands. │ │
│ │ • Services │ │ │ │
│ │ • Volumes │ │ User clicks a button → page │ │
│ │ • Images │ │ calls runner.RunAsync(args…) │ │
│ │ • DNS │ │ → goroutine runs exec.Command │ │
│ │ • Caddy │ │ → result sent back via channel │ │
│ │ • Contexts │ │ → output displayed in panel │ │
│ │ • Terminal │ │ │ │
│ │ • Settings │ │ All command output is also │ │
│ │ │ │ logged to shared TerminalLog │ │
│ └──────────────┘ └──────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
runner.Runner
│
exec.Command("uc", args…)
│
uc binary (SSH → cluster)
Key Design Decisions
Gio immediate mode UI: Every frame the entire UI is re-rendered from state. No retained widget tree. Widgets are laid out and painted in a single pass.
Async command execution: Commands run in goroutines. The page polls a result channel on each frame (select { case res := <-p.resultCh: ... default: }) and calls gtx.Execute(op.InvalidateCmd{}) to request repaints while loading.
TerminalLog: A thread-safe append-only log shared across all pages. The Terminal page displays it. Every command run anywhere in the app is recorded here.
PageView interface: Simple Layout(gtx, th) layout.Dimensions + Title() string. New pages can be added trivially.
genericPage: A reusable template for simple command pages (Volumes, Images, DNS, Caddy, Contexts). You declare CommandDef (label, mode, arg builder) and FieldDef (input fields per mode) and get a full page automatically.
Extending
To add a new command page:
- Implement the
PageViewinterface or usenewGenericPage() - Add a new
Pageconstant inapp.go - Register it in
AppState.pages - Add a nav item to
NewSidebar()insidebar.go
Example — adding a uc ps quick-view page:
// In page_generic.go or a new file:
func NewPsPage(r *runner.Runner, log *TerminalLog) PageView {
cmds := []CommandDef{
{Label: "List Containers", Mode: "ps", ArgsFunc: func(_ map[string]string) []string {
return []string{"ps"}
}},
}
return newGenericPage("Containers (ps)", r, log, cmds, nil)
}
License
MIT