2025-01-04 19:23:08 +13:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"runtime"
|
|
|
|
"strings"
|
2025-01-06 18:33:14 +13:00
|
|
|
"time"
|
2025-01-04 19:23:08 +13:00
|
|
|
|
|
|
|
qt "github.com/mappu/miqt/qt6"
|
2025-01-06 18:33:14 +13:00
|
|
|
"github.com/mappu/miqt/qt6/mainthread"
|
2025-01-04 19:23:08 +13:00
|
|
|
)
|
|
|
|
|
2025-01-06 18:33:14 +13:00
|
|
|
const AutoRefreshInterval = 1 * time.Second
|
|
|
|
|
2025-01-06 19:05:40 +13:00
|
|
|
const (
|
|
|
|
ContainersTab int = 0
|
|
|
|
ImagesTab int = 1
|
|
|
|
NetworksTab int = 2
|
|
|
|
VolumesTab int = 3
|
|
|
|
)
|
2025-01-06 19:07:03 +13:00
|
|
|
|
|
|
|
var (
|
|
|
|
dockerSudo bool
|
|
|
|
)
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
func NewStatusDelegate(status string) *qt.QWidget {
|
|
|
|
mw := qt.NewQWidget2()
|
|
|
|
|
|
|
|
layout := qt.NewQHBoxLayout(mw)
|
|
|
|
layout.SetContentsMargins(4, 4, 4, 4)
|
|
|
|
layout.SetSpacing(8)
|
|
|
|
|
|
|
|
statusCircle := qt.NewQWidget2()
|
|
|
|
statusCircle.SetFixedSize2(12, 12)
|
|
|
|
|
|
|
|
var color string = "red"
|
|
|
|
if strings.Contains(status, "Up") {
|
|
|
|
color = "green"
|
|
|
|
}
|
|
|
|
statusCircle.SetStyleSheet("background-color: " + color + "; border-radius: 6px;")
|
|
|
|
|
|
|
|
statusLabel := qt.NewQLabel3(status)
|
|
|
|
|
|
|
|
layout.AddWidget(statusCircle)
|
|
|
|
layout.AddWidget(statusLabel.QWidget)
|
|
|
|
layout.AddStretch()
|
|
|
|
|
|
|
|
return mw
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
func dockerCommand(args ...string) *exec.Cmd {
|
|
|
|
if dockerSudo {
|
|
|
|
newargs := []string{"docker"}
|
|
|
|
newargs = append(newargs, args...)
|
|
|
|
return exec.Command("sudo", newargs...)
|
|
|
|
}
|
|
|
|
|
|
|
|
return dockerCommand(args...)
|
|
|
|
}
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
func openTerminal(container_id string) {
|
2025-01-06 19:07:03 +13:00
|
|
|
command := "docker exec -it " + container_id + " sh -c '[ -x /bin/bash ] && exec /bin/bash || exec /bin/sh'"
|
|
|
|
if dockerSudo {
|
|
|
|
command = "sudo " + command
|
|
|
|
}
|
|
|
|
|
|
|
|
popupCommand(command)
|
|
|
|
}
|
|
|
|
|
|
|
|
func openLogs(container_id string) {
|
|
|
|
command := "docker logs -f " + container_id
|
|
|
|
if dockerSudo {
|
|
|
|
command = "sudo " + command
|
|
|
|
}
|
|
|
|
|
|
|
|
popupCommand(command)
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func popupCommand(command string) {
|
|
|
|
switch runtime.GOOS {
|
|
|
|
case "darwin":
|
|
|
|
exec.Command("open", "-a", "Terminal", "--", "sh", "-c", command).Start()
|
|
|
|
|
|
|
|
case "linux":
|
|
|
|
exec.Command("x-terminal-emulator", "-e", "sh -c \""+command+"\"").Start()
|
|
|
|
|
|
|
|
case "windows":
|
|
|
|
exec.Command("start", "cmd", "/k", command).Start()
|
|
|
|
|
|
|
|
default:
|
|
|
|
panic("Opening a terminal is not supported on " + runtime.GOOS)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type DockerGUITab struct {
|
|
|
|
*qt.QWidget
|
|
|
|
|
|
|
|
tree *qt.QTreeWidget
|
|
|
|
search_bar *qt.QLineEdit
|
|
|
|
}
|
|
|
|
|
|
|
|
type DockerGUI struct {
|
|
|
|
*qt.QMainWindow
|
|
|
|
|
|
|
|
toolbar *qt.QToolBar
|
|
|
|
|
|
|
|
start_action *qt.QAction
|
|
|
|
stop_action *qt.QAction
|
|
|
|
remove_action *qt.QAction
|
|
|
|
create_network_action *qt.QAction
|
|
|
|
remove_network_action *qt.QAction
|
|
|
|
create_volume_action *qt.QAction
|
|
|
|
remove_volume_action *qt.QAction
|
|
|
|
terminal_action *qt.QAction
|
|
|
|
pull_image_action *qt.QAction
|
|
|
|
remove_image_action *qt.QAction
|
|
|
|
logs_action *qt.QAction
|
|
|
|
|
|
|
|
auto_refresh_checkbox *qt.QCheckBox
|
|
|
|
|
|
|
|
tab_widget *qt.QTabWidget
|
|
|
|
|
|
|
|
containers_tab *qt.QWidget
|
|
|
|
images_tab *qt.QWidget
|
|
|
|
networks_tab *qt.QWidget
|
|
|
|
volumes_tab *qt.QWidget
|
|
|
|
|
|
|
|
containers_tree *qt.QTreeWidget
|
|
|
|
images_tree *qt.QTreeWidget
|
|
|
|
networks_tree *qt.QTreeWidget
|
|
|
|
volumes_tree *qt.QTreeWidget
|
|
|
|
|
|
|
|
containers_searchbar *qt.QLineEdit
|
|
|
|
images_searchbar *qt.QLineEdit
|
|
|
|
networks_searchbar *qt.QLineEdit
|
|
|
|
volumes_searchbar *qt.QLineEdit
|
|
|
|
|
2025-01-06 18:33:14 +13:00
|
|
|
refresh_timer *time.Ticker
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewDockerGUI() *DockerGUI {
|
|
|
|
var self DockerGUI
|
|
|
|
|
|
|
|
self.QMainWindow = qt.NewQMainWindow2()
|
|
|
|
|
|
|
|
self.SetWindowTitle("Qocker - Docker Graphical User Interface")
|
|
|
|
self.SetGeometry(100, 100, 1000, 600)
|
|
|
|
|
|
|
|
// Create central widget and layout
|
|
|
|
central_widget := qt.NewQWidget2()
|
|
|
|
self.SetCentralWidget(central_widget)
|
|
|
|
mainLayout := qt.NewQVBoxLayout(central_widget)
|
|
|
|
|
|
|
|
// Create toolbar
|
|
|
|
self.create_toolbar()
|
|
|
|
|
|
|
|
// Create tab widget
|
|
|
|
self.tab_widget = qt.NewQTabWidget2()
|
|
|
|
mainLayout.AddWidget(self.tab_widget.QWidget)
|
|
|
|
|
|
|
|
// Create tabs
|
|
|
|
self.containers_tab = qt.NewQWidget2()
|
|
|
|
self.images_tab = qt.NewQWidget2()
|
|
|
|
self.networks_tab = qt.NewQWidget2()
|
|
|
|
self.volumes_tab = qt.NewQWidget2()
|
|
|
|
|
|
|
|
self.tab_widget.AddTab(self.containers_tab, "Containers")
|
|
|
|
self.tab_widget.AddTab(self.images_tab, "Images")
|
|
|
|
self.tab_widget.AddTab(self.networks_tab, "Networks")
|
|
|
|
self.tab_widget.AddTab(self.volumes_tab, "Volumes")
|
|
|
|
|
|
|
|
// Connect tab change to toolbar update
|
|
|
|
self.tab_widget.OnCurrentChanged(self.update_toolbar_buttons)
|
|
|
|
|
|
|
|
// Create tree widgets for each tab
|
|
|
|
self.containers_tree = self.create_tree_widget("ID", "Name", "Image", "Status", "Ports")
|
|
|
|
self.images_tree = self.create_tree_widget("ID", "Repository", "Tag", "Size")
|
|
|
|
self.networks_tree = self.create_tree_widget("ID", "Name", "Driver")
|
|
|
|
self.volumes_tree = self.create_tree_widget("Name", "Driver", "Mountpoint")
|
|
|
|
|
|
|
|
self.containers_tree.OnItemDoubleClicked(func(item *qt.QTreeWidgetItem, column int) {
|
|
|
|
self.open_terminal()
|
|
|
|
})
|
|
|
|
|
|
|
|
// Add search bars
|
|
|
|
self.containers_searchbar = self.create_searchbar_widget(self.containers_tree, "Search containers...")
|
|
|
|
self.images_searchbar = self.create_searchbar_widget(self.images_tree, "Search images...")
|
|
|
|
self.networks_searchbar = self.create_searchbar_widget(self.networks_tree, "Search networks...")
|
|
|
|
self.volumes_searchbar = self.create_searchbar_widget(self.volumes_tree, "Search volumes...")
|
|
|
|
|
|
|
|
// Add tree widgets to tabs
|
|
|
|
self.setup_tab(self.containers_tab, self.containers_tree, self.containers_searchbar)
|
|
|
|
self.setup_tab(self.images_tab, self.images_tree, self.images_searchbar)
|
|
|
|
self.setup_tab(self.networks_tab, self.networks_tree, self.networks_searchbar)
|
|
|
|
self.setup_tab(self.volumes_tab, self.volumes_tree, self.volumes_searchbar)
|
|
|
|
|
|
|
|
// Create menu bar
|
|
|
|
self.create_menu_bar()
|
|
|
|
|
|
|
|
//.Setup auto-refresh
|
|
|
|
self.setup_auto_refresh()
|
|
|
|
|
|
|
|
// Populate data
|
|
|
|
self.refresh_data()
|
|
|
|
|
|
|
|
// Update toolbar buttons for initial state
|
|
|
|
self.update_toolbar_buttons(0)
|
|
|
|
|
|
|
|
// override QMainWindow.createPopupMenu
|
|
|
|
self.OnCreatePopupMenu(func(super func() *qt.QMenu) *qt.QMenu {
|
|
|
|
filtered_menu := super()
|
|
|
|
filtered_menu.RemoveAction(self.toolbar.ToggleViewAction())
|
|
|
|
|
|
|
|
return filtered_menu
|
|
|
|
})
|
|
|
|
|
|
|
|
return &self
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) create_tree_widget(headers ...string) *qt.QTreeWidget {
|
|
|
|
tree := qt.NewQTreeWidget2()
|
|
|
|
tree.SetHeaderLabels(headers)
|
|
|
|
tree.SetContextMenuPolicy(qt.CustomContextMenu)
|
|
|
|
tree.OnCustomContextMenuRequested(self.show_context_menu)
|
2025-01-06 19:23:40 +13:00
|
|
|
tree.SetIndentation(0)
|
|
|
|
tree.SetUniformRowHeights(true) // Speedup
|
2025-01-04 19:23:08 +13:00
|
|
|
tree.SetSortingEnabled(true)
|
|
|
|
|
|
|
|
return tree
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add search bar
|
|
|
|
func (self *DockerGUI) create_searchbar_widget(tree *qt.QTreeWidget, search_placeholder string) *qt.QLineEdit {
|
|
|
|
|
|
|
|
search_bar := qt.NewQLineEdit2()
|
|
|
|
search_bar.SetPlaceholderText(search_placeholder)
|
|
|
|
search_bar.OnTextChanged(func(text string) {
|
|
|
|
self.filter_tree(tree, text)
|
|
|
|
})
|
|
|
|
|
|
|
|
return search_bar
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) setup_tab(tab *qt.QWidget, tree *qt.QTreeWidget, search_bar *qt.QLineEdit) {
|
|
|
|
layout := qt.NewQVBoxLayout(tab)
|
|
|
|
layout.AddWidget(search_bar.QWidget)
|
|
|
|
layout.AddWidget(tree.QWidget)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) filter_tree(tree *qt.QTreeWidget, text string) {
|
|
|
|
text = strings.ToLower(text)
|
|
|
|
|
|
|
|
for i := 0; i < tree.TopLevelItemCount(); i++ {
|
|
|
|
item := tree.TopLevelItem(i)
|
|
|
|
|
|
|
|
match := false
|
|
|
|
for j := 0; j < item.ColumnCount(); j++ {
|
|
|
|
if strings.Contains(strings.ToLower(item.Text(j)), text) {
|
|
|
|
match = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
item.SetHidden(!match)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) create_toolbar() {
|
|
|
|
self.toolbar = qt.NewQToolBar3()
|
|
|
|
self.toolbar.SetMovable(false) // Make toolbar fixed
|
|
|
|
self.AddToolBar(qt.TopToolBarArea, self.toolbar)
|
|
|
|
|
|
|
|
// Common actions
|
|
|
|
refresh_action := qt.NewQAction6(qt.QIcon_FromTheme("view-refresh"), "Refresh", self.QObject)
|
|
|
|
refresh_action.OnTriggered(self.refresh_data)
|
|
|
|
self.toolbar.AddAction(refresh_action)
|
|
|
|
|
|
|
|
// Add auto-refresh checkbox
|
|
|
|
self.auto_refresh_checkbox = qt.NewQCheckBox3("Auto-refresh")
|
|
|
|
self.auto_refresh_checkbox.SetChecked(true)
|
|
|
|
self.toolbar.AddWidget(self.auto_refresh_checkbox.QWidget)
|
|
|
|
|
|
|
|
// Add separator
|
|
|
|
self.toolbar.AddSeparator()
|
|
|
|
|
|
|
|
// Container-specific actions
|
|
|
|
self.start_action = qt.NewQAction6(qt.QIcon_FromTheme("media-playback-start"), "Start", self.QObject)
|
|
|
|
self.start_action.OnTriggered(self.start_container)
|
|
|
|
self.toolbar.AddAction(self.start_action)
|
|
|
|
|
|
|
|
self.stop_action = qt.NewQAction6(qt.QIcon_FromTheme("media-playback-stop"), "Stop", self.QObject)
|
|
|
|
self.stop_action.OnTriggered(self.stop_container)
|
|
|
|
self.toolbar.AddAction(self.stop_action)
|
|
|
|
|
|
|
|
self.remove_action = qt.NewQAction6(qt.QIcon_FromTheme("edit-delete"), "Remove", self.QObject)
|
|
|
|
self.remove_action.OnTriggered(self.remove_container)
|
|
|
|
self.toolbar.AddAction(self.remove_action)
|
|
|
|
|
|
|
|
// Image-specific actions
|
|
|
|
self.pull_image_action = qt.NewQAction6(qt.QIcon_FromTheme("download"), "Pull Image", self.QObject)
|
|
|
|
self.pull_image_action.OnTriggered(self.pull_image)
|
|
|
|
self.toolbar.AddAction(self.pull_image_action)
|
|
|
|
|
|
|
|
self.remove_image_action = qt.NewQAction6(qt.QIcon_FromTheme("edit-delete"), "Remove Image", self.QObject)
|
|
|
|
self.remove_image_action.OnTriggered(self.remove_image)
|
|
|
|
self.toolbar.AddAction(self.remove_image_action)
|
|
|
|
|
|
|
|
// Network-specific actions
|
|
|
|
self.create_network_action = qt.NewQAction6(qt.QIcon_FromTheme("list-add"), "Create Network", self.QObject)
|
|
|
|
self.create_network_action.OnTriggered(self.create_network)
|
|
|
|
self.toolbar.AddAction(self.create_network_action)
|
|
|
|
|
|
|
|
self.remove_network_action = qt.NewQAction6(qt.QIcon_FromTheme("edit-delete"), "Remove Network", self.QObject)
|
|
|
|
self.remove_network_action.OnTriggered(self.remove_network)
|
|
|
|
self.toolbar.AddAction(self.remove_network_action)
|
|
|
|
|
|
|
|
// Volume-specific actions
|
|
|
|
self.create_volume_action = qt.NewQAction6(qt.QIcon_FromTheme("list-add"), "Create Volume", self.QObject)
|
|
|
|
self.create_volume_action.OnTriggered(self.create_volume)
|
|
|
|
self.toolbar.AddAction(self.create_volume_action)
|
|
|
|
|
|
|
|
self.remove_volume_action = qt.NewQAction6(qt.QIcon_FromTheme("edit-delete"), "Remove Volume", self.QObject)
|
|
|
|
self.remove_volume_action.OnTriggered(self.remove_volume)
|
|
|
|
self.toolbar.AddAction(self.remove_volume_action)
|
|
|
|
|
|
|
|
// Add terminal action
|
|
|
|
self.terminal_action = qt.NewQAction6(qt.QIcon_FromTheme("utilities-terminal"), "Open Terminal", self.QObject)
|
|
|
|
self.terminal_action.OnTriggered(self.open_terminal)
|
|
|
|
self.toolbar.AddAction(self.terminal_action)
|
|
|
|
|
|
|
|
// Add logs action
|
|
|
|
self.logs_action = qt.NewQAction6(qt.QIcon_FromTheme("document-open"), "Open Logs", self.QObject)
|
|
|
|
self.logs_action.OnTriggered(self.open_logs)
|
|
|
|
self.toolbar.AddAction(self.logs_action)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) update_toolbar_buttons(index int) {
|
|
|
|
// Show actions based on the current tab
|
|
|
|
|
|
|
|
// Containers tab
|
2025-01-06 19:05:40 +13:00
|
|
|
self.start_action.SetVisible(index == ContainersTab)
|
|
|
|
self.stop_action.SetVisible(index == ContainersTab)
|
|
|
|
self.remove_action.SetVisible(index == ContainersTab)
|
|
|
|
self.terminal_action.SetVisible(index == ContainersTab)
|
|
|
|
self.logs_action.SetVisible(index == ContainersTab)
|
2025-01-04 19:23:08 +13:00
|
|
|
|
|
|
|
// Images tab
|
2025-01-06 19:05:40 +13:00
|
|
|
self.pull_image_action.SetVisible(index == ImagesTab)
|
|
|
|
self.remove_image_action.SetVisible(index == ImagesTab)
|
2025-01-04 19:23:08 +13:00
|
|
|
|
|
|
|
// Networks tab
|
2025-01-06 19:05:40 +13:00
|
|
|
self.create_network_action.SetVisible(index == NetworksTab)
|
|
|
|
self.remove_network_action.SetVisible(index == NetworksTab)
|
2025-01-04 19:23:08 +13:00
|
|
|
|
|
|
|
// Volumes tab
|
2025-01-06 19:05:40 +13:00
|
|
|
self.create_volume_action.SetVisible(index == VolumesTab)
|
|
|
|
self.remove_volume_action.SetVisible(index == VolumesTab)
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) create_menu_bar() {
|
|
|
|
menubar := self.MenuBar()
|
|
|
|
|
2025-01-06 18:33:53 +13:00
|
|
|
file_menu := menubar.AddMenuWithTitle("&File")
|
|
|
|
exit_action := qt.NewQAction5("E&xit", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
exit_action.OnTriggered(func() {
|
|
|
|
self.Close()
|
|
|
|
})
|
|
|
|
file_menu.AddAction(exit_action)
|
|
|
|
|
2025-01-06 18:33:53 +13:00
|
|
|
docker_menu := menubar.AddMenuWithTitle("&Docker")
|
|
|
|
refresh_action := qt.NewQAction5("&Refresh", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
refresh_action.OnTriggered(self.refresh_data)
|
|
|
|
docker_menu.AddAction(refresh_action)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) show_context_menu(position *qt.QPoint) {
|
|
|
|
context_menu := qt.NewQMenu2()
|
2025-01-06 19:06:44 +13:00
|
|
|
current_tab := self.tab_widget.CurrentIndex()
|
2025-01-04 19:23:08 +13:00
|
|
|
|
|
|
|
// Add refresh action to context menu
|
2025-01-06 18:33:53 +13:00
|
|
|
refresh_action := qt.NewQAction5("&Refresh", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
refresh_action.OnTriggered(self.refresh_data)
|
|
|
|
context_menu.AddAction(refresh_action)
|
|
|
|
|
|
|
|
context_menu.AddSeparator()
|
|
|
|
|
2025-01-06 19:06:44 +13:00
|
|
|
if current_tab == ContainersTab {
|
2025-01-06 18:33:53 +13:00
|
|
|
terminal_action := qt.NewQAction5("&Terminal", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
terminal_action.OnTriggered(func() { self.handle_action("Terminal") })
|
|
|
|
context_menu.AddAction(terminal_action)
|
|
|
|
|
2025-01-06 18:33:53 +13:00
|
|
|
logs_action := qt.NewQAction5("&Logs", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
logs_action.OnTriggered(func() { self.open_logs() })
|
|
|
|
context_menu.AddAction(logs_action)
|
|
|
|
|
|
|
|
context_menu.AddSeparator()
|
|
|
|
|
2025-01-06 18:33:53 +13:00
|
|
|
start_action := qt.NewQAction5("&Start", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
start_action.OnTriggered(func() { self.handle_action("Start") })
|
|
|
|
context_menu.AddAction(start_action)
|
|
|
|
|
2025-01-06 18:33:53 +13:00
|
|
|
stop_action := qt.NewQAction5("S&top", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
stop_action.OnTriggered(func() { self.handle_action("Stop") })
|
|
|
|
context_menu.AddAction(stop_action)
|
|
|
|
|
2025-01-06 18:33:53 +13:00
|
|
|
remove_action := qt.NewQAction5("Remo&ve", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
remove_action.OnTriggered(func() { self.handle_action("Remove") })
|
|
|
|
context_menu.AddAction(remove_action)
|
|
|
|
|
2025-01-06 19:06:44 +13:00
|
|
|
context_menu.ExecWithPos(self.containers_tree.Viewport().MapToGlobalWithQPoint(position))
|
|
|
|
|
|
|
|
} else if current_tab == ImagesTab {
|
2025-01-06 18:33:53 +13:00
|
|
|
pull_action := qt.NewQAction5("&Pull", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
pull_action.OnTriggered(self.pull_image)
|
|
|
|
context_menu.AddAction(pull_action)
|
|
|
|
|
|
|
|
context_menu.AddSeparator()
|
|
|
|
|
2025-01-06 18:33:53 +13:00
|
|
|
remove_action := qt.NewQAction5("Remo&ve", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
remove_action.OnTriggered(self.remove_image)
|
|
|
|
context_menu.AddAction(remove_action)
|
|
|
|
|
2025-01-06 19:06:44 +13:00
|
|
|
context_menu.ExecWithPos(self.images_tree.Viewport().MapToGlobalWithQPoint(position))
|
|
|
|
|
|
|
|
} else if current_tab == NetworksTab {
|
2025-01-06 18:33:53 +13:00
|
|
|
remove_action := qt.NewQAction5("Remo&ve", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
remove_action.OnTriggered(func() { self.handle_action("Remove") })
|
|
|
|
context_menu.AddAction(remove_action)
|
|
|
|
|
2025-01-06 19:06:44 +13:00
|
|
|
context_menu.ExecWithPos(self.networks_tree.Viewport().MapToGlobalWithQPoint(position))
|
|
|
|
|
|
|
|
} else if current_tab == VolumesTab {
|
2025-01-06 18:33:53 +13:00
|
|
|
remove_action := qt.NewQAction5("Remo&ve", self.QObject)
|
2025-01-04 19:23:08 +13:00
|
|
|
remove_action.OnTriggered(func() { self.handle_action("Remove") })
|
|
|
|
context_menu.AddAction(remove_action)
|
|
|
|
|
2025-01-06 19:06:44 +13:00
|
|
|
context_menu.ExecWithPos(self.volumes_tree.Viewport().MapToGlobalWithQPoint(position))
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) setup_auto_refresh() {
|
2025-01-06 18:33:14 +13:00
|
|
|
self.refresh_timer = time.NewTicker(AutoRefreshInterval)
|
2025-01-04 19:23:08 +13:00
|
|
|
|
2025-01-06 18:33:14 +13:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
_, ok := <-self.refresh_timer.C
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
mainthread.Wait(func() {
|
|
|
|
if !self.auto_refresh_checkbox.IsChecked() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_data()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}()
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) handle_action(action string) {
|
2025-01-06 19:06:44 +13:00
|
|
|
current_tab := self.tab_widget.CurrentIndex()
|
2025-01-04 19:23:08 +13:00
|
|
|
|
2025-01-06 19:06:44 +13:00
|
|
|
if current_tab == ContainersTab {
|
2025-01-04 19:23:08 +13:00
|
|
|
selected_items := self.containers_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
container_id := selected_items[0].Text(0)
|
|
|
|
if action == "Terminal" {
|
|
|
|
self.open_terminal()
|
|
|
|
} else if action == "Start" {
|
2025-01-06 19:07:03 +13:00
|
|
|
dockerCommand("start", container_id).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
} else if action == "Stop" {
|
2025-01-06 19:07:03 +13:00
|
|
|
dockerCommand("stop", container_id).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
} else if action == "Remove" {
|
2025-01-06 19:07:03 +13:00
|
|
|
dockerCommand("rm", "-f", container_id).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
2025-01-06 19:06:44 +13:00
|
|
|
} else if current_tab == NetworksTab {
|
2025-01-04 19:23:08 +13:00
|
|
|
selected_items := self.networks_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
network_id := selected_items[0].Text(0)
|
|
|
|
if action == "Remove" {
|
2025-01-06 19:07:03 +13:00
|
|
|
dockerCommand("network", "rm", network_id).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
2025-01-06 19:06:44 +13:00
|
|
|
} else if current_tab == VolumesTab {
|
2025-01-04 19:23:08 +13:00
|
|
|
selected_items := self.volumes_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
volume_name := selected_items[0].Text(0)
|
|
|
|
if action == "Remove" {
|
2025-01-06 19:07:03 +13:00
|
|
|
dockerCommand("volume", "rm", volume_name).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_data()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) refresh_data() {
|
|
|
|
self.refresh_containers()
|
|
|
|
self.refresh_images()
|
|
|
|
self.refresh_networks()
|
|
|
|
self.refresh_volumes()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) refresh_containers() {
|
2025-01-06 19:07:03 +13:00
|
|
|
output, err := dockerCommand("ps", "-a", "--format", "{{.ID}}\\t{{.Names}}\\t{{.Image}}\\t{{.Status}}\\t{{.Ports}}").Output()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:08:36 +13:00
|
|
|
scroll_position := self.containers_tree.VerticalScrollBar().Value()
|
|
|
|
selected_items := self.get_selected_items(self.containers_tree)
|
|
|
|
self.containers_tree.Clear()
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
containers := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
|
|
for _, container := range containers {
|
|
|
|
parts := strings.Split(container, "\t")
|
2025-01-06 18:35:12 +13:00
|
|
|
if len(parts) == 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
id := parts[0]
|
|
|
|
name := parts[1]
|
|
|
|
image := parts[2]
|
|
|
|
status := parts[3]
|
|
|
|
ports := ""
|
|
|
|
if len(parts) > 4 {
|
|
|
|
ports = parts[4]
|
|
|
|
}
|
|
|
|
|
|
|
|
item := qt.NewQTreeWidgetItem2([]string{id, name, image, "", ports}) // Empty string for status column
|
|
|
|
status_widget := NewStatusDelegate(status)
|
|
|
|
self.containers_tree.AddTopLevelItem(item)
|
|
|
|
self.containers_tree.SetItemWidget(item, 3, status_widget)
|
|
|
|
}
|
|
|
|
|
|
|
|
self.filter_tree(self.containers_tree, self.containers_searchbar.Text())
|
|
|
|
self.restore_selection(self.containers_tree, selected_items)
|
|
|
|
|
2025-01-06 18:34:52 +13:00
|
|
|
self.containers_tree.VerticalScrollBar().SetValue(scroll_position)
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) refresh_images() {
|
2025-01-06 19:07:03 +13:00
|
|
|
output, err := dockerCommand("images", "--format", "{{.ID}}\\t{{.Repository}}\\t{{.Tag}}\\t{{.Size}}").Output()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:08:36 +13:00
|
|
|
scroll_position := self.images_tree.VerticalScrollBar().Value()
|
|
|
|
selected_items := self.get_selected_items(self.images_tree)
|
|
|
|
self.images_tree.Clear()
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
images := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
|
|
for _, image := range images {
|
|
|
|
parts := strings.Split(image, "\t")
|
2025-01-06 18:35:12 +13:00
|
|
|
if len(parts) == 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
id := parts[0]
|
|
|
|
repository := parts[1]
|
|
|
|
tag := parts[2]
|
|
|
|
size := parts[3]
|
|
|
|
|
|
|
|
item := qt.NewQTreeWidgetItem2([]string{id, repository, tag, size})
|
|
|
|
self.images_tree.AddTopLevelItem(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
self.filter_tree(self.images_tree, self.images_searchbar.Text())
|
|
|
|
self.restore_selection(self.images_tree, selected_items)
|
|
|
|
|
2025-01-06 18:34:52 +13:00
|
|
|
self.images_tree.VerticalScrollBar().SetValue(scroll_position)
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) refresh_networks() {
|
2025-01-06 19:07:03 +13:00
|
|
|
output, err := dockerCommand("network", "ls", "--format", "{{.ID}}\\t{{.Name}}\\t{{.Driver}}").Output()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:08:36 +13:00
|
|
|
scroll_position := self.networks_tree.VerticalScrollBar().Value()
|
|
|
|
selected_items := self.get_selected_items(self.networks_tree)
|
|
|
|
self.networks_tree.Clear()
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
networks := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
|
|
for _, network := range networks {
|
|
|
|
parts := strings.Split(network, "\t")
|
2025-01-06 18:35:12 +13:00
|
|
|
if len(parts) == 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
id := parts[0]
|
|
|
|
name := parts[1]
|
|
|
|
driver := parts[2]
|
|
|
|
|
|
|
|
item := qt.NewQTreeWidgetItem2([]string{id, name, driver})
|
|
|
|
self.networks_tree.AddTopLevelItem(item)
|
|
|
|
}
|
|
|
|
self.filter_tree(self.networks_tree, self.networks_searchbar.Text())
|
|
|
|
self.restore_selection(self.networks_tree, selected_items)
|
|
|
|
|
2025-01-06 18:34:52 +13:00
|
|
|
self.networks_tree.VerticalScrollBar().SetValue(scroll_position)
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) refresh_volumes() {
|
2025-01-06 19:07:03 +13:00
|
|
|
output, err := dockerCommand("volume", "ls", "--format", "{{.Name}}\\t{{.Driver}}\\t{{.Mountpoint}}").Output()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:08:36 +13:00
|
|
|
scroll_position := self.volumes_tree.VerticalScrollBar().Value()
|
|
|
|
selected_items := self.get_selected_items(self.volumes_tree)
|
|
|
|
self.volumes_tree.Clear()
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
volumes := strings.Split(strings.TrimSpace(string(output)), "\n")
|
|
|
|
for _, volume := range volumes {
|
|
|
|
parts := strings.Split(volume, "\t")
|
2025-01-06 18:35:12 +13:00
|
|
|
if len(parts) == 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
name := parts[0]
|
|
|
|
driver := parts[1]
|
|
|
|
mountpoint := parts[2]
|
|
|
|
|
|
|
|
item := qt.NewQTreeWidgetItem2([]string{name, driver, mountpoint})
|
|
|
|
self.volumes_tree.AddTopLevelItem(item)
|
|
|
|
}
|
|
|
|
self.filter_tree(self.volumes_tree, self.volumes_searchbar.Text())
|
|
|
|
self.restore_selection(self.volumes_tree, selected_items)
|
|
|
|
|
2025-01-06 18:34:52 +13:00
|
|
|
self.volumes_tree.VerticalScrollBar().SetValue(scroll_position)
|
2025-01-04 19:23:08 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) get_selected_items(tree *qt.QTreeWidget) []string {
|
|
|
|
selItems := tree.SelectedItems()
|
|
|
|
|
|
|
|
ret := make([]string, 0, len(selItems))
|
|
|
|
for _, itm := range selItems {
|
|
|
|
ret = append(ret, itm.Text(0))
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) restore_selection(tree *qt.QTreeWidget, selected_items []string) {
|
|
|
|
|
|
|
|
rmatch := map[string]struct{}{}
|
|
|
|
for _, itm := range selected_items {
|
|
|
|
rmatch[itm] = struct{}{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < tree.TopLevelItemCount(); i++ {
|
|
|
|
item := tree.TopLevelItem(i)
|
|
|
|
|
|
|
|
if _, ok := rmatch[item.Text(0)]; ok {
|
|
|
|
item.SetSelected(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) start_container() {
|
|
|
|
selected_items := self.containers_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
qt.QMessageBox_Warning(self.QWidget, "No Selection", "Please select a container to start.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range selected_items {
|
|
|
|
container_id := item.Text(0)
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
err := dockerCommand("start", container_id).Wait()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err != nil {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", "Failed to start container "+container_id+": "+err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_containers()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) stop_container() {
|
|
|
|
selected_items := self.containers_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
qt.QMessageBox_Warning(self.QWidget, "No Selection", "Please select a container to stop.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range selected_items {
|
|
|
|
container_id := item.Text(0)
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
err := dockerCommand("stop", container_id).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err != nil {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", "Failed to stop container "+container_id+": "+err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_containers()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) remove_container() {
|
|
|
|
selected_items := self.containers_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
qt.QMessageBox_Warning(self.QWidget, "No Selection", "Please select a container to remove.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range selected_items {
|
|
|
|
container_id := item.Text(0)
|
|
|
|
reply := qt.QMessageBox_Question5(self.QWidget,
|
|
|
|
"Confirm Removal",
|
|
|
|
"Are you sure you want to remove container "+container_id+"?",
|
|
|
|
qt.QMessageBox__Yes|qt.QMessageBox__No,
|
|
|
|
qt.QMessageBox__No,
|
|
|
|
)
|
|
|
|
|
|
|
|
if reply != qt.QMessageBox__Yes {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
err := dockerCommand("rm", "-f", container_id).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err != nil {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", "Failed to remove container "+container_id+": "+err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_containers()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) pull_image() {
|
|
|
|
image_name := qt.QInputDialog_GetText(self.QWidget, "Pull Image", "Enter image name (e.g., ubuntu:latest):")
|
|
|
|
if image_name == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
err := dockerCommand("pull", image_name).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err == nil {
|
|
|
|
qt.QMessageBox_Information(self.QWidget, "Success", "Image \""+image_name+"\" pulled successfully.")
|
|
|
|
} else {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", "Failed to pull image: "+err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_images()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) remove_image() {
|
|
|
|
selected_items := self.images_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
qt.QMessageBox_Warning(self.QWidget, "No Selection", "Please select an image to remove.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range selected_items {
|
|
|
|
image_id := item.Text(0)
|
|
|
|
reply := qt.QMessageBox_Question5(self.QWidget, "Confirm Removal",
|
|
|
|
"Are you sure you want to remove image "+image_id+"?",
|
|
|
|
qt.QMessageBox__Yes|qt.QMessageBox__No,
|
|
|
|
qt.QMessageBox__No,
|
|
|
|
)
|
|
|
|
if reply != qt.QMessageBox__Yes {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
err := dockerCommand("rmi", image_id).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err == nil {
|
|
|
|
qt.QMessageBox_Information(self.QWidget, "Success", "Image \""+image_id+"\" removed successfully.")
|
|
|
|
} else {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", "Failed to remove image "+image_id+": "+err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_images()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) create_network() {
|
|
|
|
|
|
|
|
network_name := qt.QInputDialog_GetText(self.QWidget, "Create Network", "Enter network name:")
|
|
|
|
if network_name == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
err := dockerCommand("network", "create", network_name).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err == nil {
|
|
|
|
qt.QMessageBox_Information(self.QWidget, "Success", "Network \""+network_name+"\" created successfully.")
|
|
|
|
} else {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", "Failed to create network: "+err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_networks()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) remove_network() {
|
|
|
|
selected_items := self.networks_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
qt.QMessageBox_Warning(self.QWidget, "No Selection", "Please select a network to remove.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range selected_items {
|
|
|
|
network_name := item.Text(1) // Assuming the network name is in the second column
|
|
|
|
reply := qt.QMessageBox_Question5(self.QWidget, "Confirm Removal",
|
|
|
|
"Are you sure you want to remove network "+network_name+"?",
|
|
|
|
qt.QMessageBox__Yes|qt.QMessageBox__No,
|
|
|
|
qt.QMessageBox__No,
|
|
|
|
)
|
|
|
|
if reply != qt.QMessageBox__Yes {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
err := dockerCommand("network", "rm", network_name).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err == nil {
|
|
|
|
qt.QMessageBox_Information(self.QWidget, "Success", "Network \""+network_name+"\" removed successfully.")
|
|
|
|
} else {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", "Failed to remove network "+network_name+": "+err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_networks()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) create_volume() {
|
|
|
|
volume_name := qt.QInputDialog_GetText(self.QWidget, "Create Volume", "Enter volume name:")
|
|
|
|
if volume_name == "" {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
err := dockerCommand("volume", "create", volume_name).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err == nil {
|
|
|
|
qt.QMessageBox_Information(self.QWidget, "Success", "Volume \""+volume_name+"\" created successfully.")
|
|
|
|
} else {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", "Failed to create volume "+volume_name+": "+err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_volumes()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) remove_volume() {
|
|
|
|
selected_items := self.volumes_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
qt.QMessageBox_Warning(self.QWidget, "No Selection", "Please select a volume to remove.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range selected_items {
|
|
|
|
volume_name := item.Text(0) // Assuming the volume name is in the first column
|
|
|
|
reply := qt.QMessageBox_Question5(self.QWidget, "Confirm Removal",
|
|
|
|
"Are you sure you want to remove volume "+volume_name+"?",
|
|
|
|
qt.QMessageBox__Yes|qt.QMessageBox__No,
|
|
|
|
qt.QMessageBox__No,
|
|
|
|
)
|
|
|
|
if reply != qt.QMessageBox__Yes {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2025-01-06 19:07:03 +13:00
|
|
|
err := dockerCommand("volume", "rm", volume_name).Run()
|
2025-01-04 19:23:08 +13:00
|
|
|
if err == nil {
|
|
|
|
qt.QMessageBox_Information(self.QWidget, "Success", "Volume \""+volume_name+"\" removed successfully.")
|
|
|
|
} else {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", "Failed to remove volume "+volume_name+": "+err.Error())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.refresh_volumes()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) open_terminal() {
|
|
|
|
selected_items := self.containers_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
qt.QMessageBox_Warning(self.QWidget, "No Selection", "Please select a container to open terminal.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
container_id := selected_items[0].Text(0)
|
|
|
|
|
|
|
|
openTerminal(container_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) show_terminal_error(error_message string) {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", error_message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) open_logs() {
|
|
|
|
selected_items := self.containers_tree.SelectedItems()
|
|
|
|
if len(selected_items) == 0 {
|
|
|
|
qt.QMessageBox_Warning(self.QWidget, "No Selection", "Please select a container to open logs.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
container_id := selected_items[0].Text(0)
|
|
|
|
openLogs(container_id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *DockerGUI) show_logs_error(error_message string) {
|
|
|
|
qt.QMessageBox_Critical(self.QWidget, "Error", error_message)
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
qt.NewQApplication(os.Args)
|
2025-01-06 19:07:03 +13:00
|
|
|
|
|
|
|
for _, arg := range os.Args {
|
|
|
|
if arg == `--sudo` {
|
|
|
|
dockerSudo = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-04 19:23:08 +13:00
|
|
|
window := NewDockerGUI()
|
|
|
|
window.Show()
|
|
|
|
os.Exit(qt.QApplication_Exec())
|
|
|
|
}
|