Code cleanup, finish of changes for 0.1.1
Some checks failed
Build & Upload tux-dock / build (push) Has been cancelled
Some checks failed
Build & Upload tux-dock / build (push) Has been cancelled
This commit is contained in:
parent
9e2660e524
commit
665809a21a
6 changed files with 529 additions and 90 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# Tux-Dock Development Log
|
# Tux-Dock Development Log
|
||||||
|
|
||||||
## 0.1-beta
|
## 0.1.1-beta
|
||||||
|
|
||||||
This release establishes the first beta-quality Docker integration and TUI workflow.
|
This release establishes the first beta-quality Docker integration and TUI workflow.
|
||||||
|
|
||||||
|
|
|
||||||
21
README.md
21
README.md
|
|
@ -62,18 +62,17 @@ The web report is generated under `/tmp` and includes exact CTest output, test s
|
||||||
Current TUI actions:
|
Current TUI actions:
|
||||||
|
|
||||||
1. Pull Docker Image
|
1. Pull Docker Image
|
||||||
2. Run/Create Interactive Container
|
2. Create Container
|
||||||
3. List All Containers
|
3. List All Containers
|
||||||
4. List All Images
|
4. List All Images
|
||||||
5. Start Container Interactively (boot new session)
|
5. Start Detached Container Session
|
||||||
6. Start Detached Container Session
|
6. Delete Docker Image
|
||||||
7. Delete Docker Image
|
7. Stop Container
|
||||||
8. Stop Container
|
8. Remove Container
|
||||||
9. Remove Container
|
9. Attach Shell to Running Container
|
||||||
10. Attach Shell to Running Container
|
10. Run Detached Command in Container
|
||||||
11. Run Detached Command in Container
|
11. About Tux-Dock
|
||||||
12. About Tux-Dock
|
12. Exit
|
||||||
13. Exit
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -120,7 +119,7 @@ ctest --test-dir build --output-on-failure
|
||||||
|
|
||||||
## About / Version
|
## About / Version
|
||||||
|
|
||||||
- Version: `0.1-beta`
|
- Version: `0.1.1-beta`
|
||||||
- Created by: `markmental`
|
- Created by: `markmental`
|
||||||
- GitHub: https://github.com/MARKMENTAL/tuxdock
|
- GitHub: https://github.com/MARKMENTAL/tuxdock
|
||||||
- Forgejo: https://mentalnet.xyz/forgejo-v2/markmental/tuxdock
|
- Forgejo: https://mentalnet.xyz/forgejo-v2/markmental/tuxdock
|
||||||
|
|
|
||||||
553
main.cpp
553
main.cpp
|
|
@ -25,17 +25,36 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class ModalMode { None, Input, Confirm, Select, Message, Busy };
|
enum class ModalMode { None, Input, Confirm, Select, Message, Busy };
|
||||||
struct RunContainerContext { std::string image; int port_count = 0; std::vector<std::string> ports; };
|
struct RunContainerContext {
|
||||||
struct MySqlContext { std::string port; std::string password; std::string version; };
|
std::string name;
|
||||||
|
std::string image;
|
||||||
|
int port_count = 0;
|
||||||
|
std::vector<std::string> ports;
|
||||||
|
};
|
||||||
|
|
||||||
DockerManager docker_;
|
DockerManager docker_;
|
||||||
std::vector<DockerManager::ContainerInfo> containers_;
|
std::vector<DockerManager::ContainerInfo> containers_;
|
||||||
std::vector<DockerManager::ImageInfo> images_;
|
std::vector<DockerManager::ImageInfo> images_;
|
||||||
std::vector<std::string> menu_entries_ = {"Pull Docker Image", "Run/Create Interactive Container", "List All Containers", "List All Images", "Start Container Interactively (boot new session)", "Start Detached Container Session", "Delete Docker Image", "Stop Container", "Remove Container", "Attach Shell to Running Container", "Run Detached Command in Container", "About Tux-Dock", "Exit"};
|
std::vector<std::string> menu_entries_ = {
|
||||||
|
"Pull Docker Image",
|
||||||
|
"Create Container",
|
||||||
|
"List All Containers",
|
||||||
|
"List All Images",
|
||||||
|
"Start Detached Container Session",
|
||||||
|
"Delete Docker Image",
|
||||||
|
"Stop Container",
|
||||||
|
"Remove Container",
|
||||||
|
"Attach Shell to Running Container",
|
||||||
|
"Run Detached Command in Container",
|
||||||
|
"About Tux-Dock",
|
||||||
|
"Exit",
|
||||||
|
};
|
||||||
int menu_selected_ = 0;
|
int menu_selected_ = 0;
|
||||||
std::string status_ = "Ready. Select an action and press Enter.";
|
std::string status_ = "Ready. Select an action and press Enter.";
|
||||||
ModalMode modal_mode_ = ModalMode::None;
|
ModalMode modal_mode_ = ModalMode::None;
|
||||||
std::string modal_title_, modal_text_, modal_input_;
|
std::string modal_title_;
|
||||||
|
std::string modal_text_;
|
||||||
|
std::string modal_input_;
|
||||||
ftxui::Element modal_content_;
|
ftxui::Element modal_content_;
|
||||||
std::vector<std::string> modal_select_entries_;
|
std::vector<std::string> modal_select_entries_;
|
||||||
int modal_select_index_ = 0;
|
int modal_select_index_ = 0;
|
||||||
|
|
@ -54,24 +73,43 @@ private:
|
||||||
std::size_t spinner_frame_ = 0;
|
std::size_t spinner_frame_ = 0;
|
||||||
|
|
||||||
static bool IsDigits(const std::string& value);
|
static bool IsDigits(const std::string& value);
|
||||||
|
static bool IsValidContainerName(const std::string& name);
|
||||||
static std::string ShortId(const std::string& id);
|
static std::string ShortId(const std::string& id);
|
||||||
static bool IsValidPortMapping(const std::string& mapping);
|
static bool IsValidPortMapping(const std::string& mapping);
|
||||||
ftxui::Element FormatContainerList(const std::vector<DockerManager::ContainerInfo>& containers) const;
|
ftxui::Element FormatContainerList(const std::vector<DockerManager::ContainerInfo>& containers) const;
|
||||||
ftxui::Element FormatImageList(const std::vector<DockerManager::ImageInfo>& images) const;
|
ftxui::Element FormatImageList(const std::vector<DockerManager::ImageInfo>& images) const;
|
||||||
void SetStatus(const std::string& message) { status_ = message; }
|
void SetStatus(const std::string& message) { status_ = message; }
|
||||||
void OpenInput(const std::string&, const std::string&, std::function<void(bool, const std::string&)>, bool secret = false);
|
void OpenInput(const std::string&, const std::string&,
|
||||||
|
std::function<void(bool, const std::string&)>, bool secret = false);
|
||||||
void OpenConfirm(const std::string&, const std::string&, std::function<void(bool)>);
|
void OpenConfirm(const std::string&, const std::string&, std::function<void(bool)>);
|
||||||
void OpenSelect(const std::string&, const std::string&, std::vector<std::string>, std::function<void(bool, int)>);
|
void OpenSelect(const std::string&, const std::string&, std::vector<std::string>,
|
||||||
|
std::function<void(bool, int)>);
|
||||||
void OpenMessage(const std::string& title, const std::string& text);
|
void OpenMessage(const std::string& title, const std::string& text);
|
||||||
void OpenListMessage(const std::string& title, ftxui::Element content);
|
void OpenListMessage(const std::string& title, ftxui::Element content);
|
||||||
void ResolveInput(bool); void ResolveConfirm(bool); void ResolveSelect(bool); void CloseMessage();
|
void ResolveInput(bool);
|
||||||
|
void ResolveConfirm(bool);
|
||||||
|
void ResolveSelect(bool);
|
||||||
|
void CloseMessage();
|
||||||
void ExecuteSelectedAction();
|
void ExecuteSelectedAction();
|
||||||
void ActionPullImage(); void ActionRunContainer(); void ActionListContainers(); void ActionListImages();
|
void ActionPullImage();
|
||||||
void ActionStartInteractive(); void ActionStartDetached(); void ActionDeleteImage(); void ActionStopContainer();
|
void ActionRunContainer();
|
||||||
void ActionRemoveContainer(); void ActionExecShell(); void ActionExecDetachedCommand(); void ActionAbout();
|
void ActionListContainers();
|
||||||
void PromptPortCountAndRun(const std::shared_ptr<RunContainerContext>&); void PromptNextPort(const std::shared_ptr<RunContainerContext>&, int);
|
void ActionListImages();
|
||||||
void PromptContainerSelection(const std::string&, std::function<void(const std::string&, const std::string&)>);
|
void ActionStartDetached();
|
||||||
void PromptImageSelection(const std::string&, std::function<void(const std::string&, const std::string&)>);
|
void ActionDeleteImage();
|
||||||
|
void ActionStopContainer();
|
||||||
|
void ActionRemoveContainer();
|
||||||
|
void ActionExecShell();
|
||||||
|
void ActionExecDetachedCommand();
|
||||||
|
void ActionAbout();
|
||||||
|
void PromptPortCountAndRun(const std::shared_ptr<RunContainerContext>&);
|
||||||
|
void PromptNextPort(const std::shared_ptr<RunContainerContext>&, int);
|
||||||
|
void PromptContainerSelection(
|
||||||
|
const std::string&,
|
||||||
|
std::function<void(const std::string&, const std::string&)>);
|
||||||
|
void PromptImageSelection(
|
||||||
|
const std::string&,
|
||||||
|
std::function<void(const std::string&, const std::string&)>);
|
||||||
void RunDeferredStatusAction(const std::string&, std::function<std::string()>);
|
void RunDeferredStatusAction(const std::string&, std::function<std::string()>);
|
||||||
void BeginBusyOperation(const std::string&, const std::string&, std::function<std::string()>);
|
void BeginBusyOperation(const std::string&, const std::string&, std::function<std::string()>);
|
||||||
void StartSpinner();
|
void StartSpinner();
|
||||||
|
|
@ -81,13 +119,39 @@ private:
|
||||||
void ApplyRefreshResults(DockerManager::ListResult<DockerManager::ContainerInfo> containers,
|
void ApplyRefreshResults(DockerManager::ListResult<DockerManager::ContainerInfo> containers,
|
||||||
DockerManager::ListResult<DockerManager::ImageInfo> images);
|
DockerManager::ListResult<DockerManager::ImageInfo> images);
|
||||||
static void ClearTerminal();
|
static void ClearTerminal();
|
||||||
void RunWithRestoredIO(const std::function<void()>&, bool clear_before = false, bool clear_after = false);
|
void RunWithRestoredIO(const std::function<void()>&, bool clear_before = false,
|
||||||
bool OnEvent(ftxui::Event); ftxui::Element Render() const; ftxui::Element RenderModal() const;
|
bool clear_after = false);
|
||||||
|
bool OnEvent(ftxui::Event);
|
||||||
|
ftxui::Element Render() const;
|
||||||
|
ftxui::Element RenderModal() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
bool TuxDockApp::IsDigits(const std::string& value) { if (value.empty()) return false; for (unsigned char c : value) if (!std::isdigit(c)) return false; return true; }
|
bool TuxDockApp::IsDigits(const std::string& value) {
|
||||||
std::string TuxDockApp::ShortId(const std::string& id) { return id.size() <= 12 ? id : id.substr(0, 12); }
|
if (value.empty()) return false;
|
||||||
bool TuxDockApp::IsValidPortMapping(const std::string& mapping) { const auto p = mapping.find(':'); return p != std::string::npos && IsDigits(mapping.substr(0, p)) && IsDigits(mapping.substr(p + 1)); }
|
for (unsigned char c : value) {
|
||||||
|
if (!std::isdigit(c)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TuxDockApp::IsValidContainerName(const std::string& name) {
|
||||||
|
if (name.empty()) return false;
|
||||||
|
for (unsigned char c : name) {
|
||||||
|
if (!(std::isalnum(c) || c == '_' || c == '.' || c == '-')) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string TuxDockApp::ShortId(const std::string& id) {
|
||||||
|
return id.size() <= 12 ? id : id.substr(0, 12);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TuxDockApp::IsValidPortMapping(const std::string& mapping) {
|
||||||
|
const auto separator = mapping.find(':');
|
||||||
|
return separator != std::string::npos &&
|
||||||
|
IsDigits(mapping.substr(0, separator)) &&
|
||||||
|
IsDigits(mapping.substr(separator + 1));
|
||||||
|
}
|
||||||
ftxui::Element TuxDockApp::FormatContainerList(const std::vector<DockerManager::ContainerInfo>& items) const {
|
ftxui::Element TuxDockApp::FormatContainerList(const std::vector<DockerManager::ContainerInfo>& items) const {
|
||||||
using namespace ftxui;
|
using namespace ftxui;
|
||||||
Elements rows;
|
Elements rows;
|
||||||
|
|
@ -150,18 +214,139 @@ void TuxDockApp::ApplyRefreshResults(
|
||||||
SetStatus(error.empty() ? "Docker state refreshed." : "Refresh failed; cached state preserved.\n" + error);
|
SetStatus(error.empty() ? "Docker state refreshed." : "Refresh failed; cached state preserved.\n" + error);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TuxDockApp::OpenInput(const std::string& title, const std::string& text, std::function<void(bool, const std::string&)> callback, bool secret) { modal_mode_ = ModalMode::Input; modal_title_ = title; modal_text_ = text; modal_input_.clear(); ftxui::InputOption option; option.password = secret; input_component_ = ftxui::Input(&modal_input_, "Type here", option); input_callback_ = std::move(callback); }
|
void TuxDockApp::OpenInput(const std::string& title,
|
||||||
void TuxDockApp::OpenConfirm(const std::string& title, const std::string& text, std::function<void(bool)> callback) { modal_mode_ = ModalMode::Confirm; modal_title_ = title; modal_text_ = text; confirm_callback_ = std::move(callback); }
|
const std::string& text,
|
||||||
void TuxDockApp::OpenSelect(const std::string& title, const std::string& text, std::vector<std::string> options, std::function<void(bool, int)> callback) { modal_mode_ = ModalMode::Select; modal_title_ = title; modal_text_ = text; modal_select_entries_ = std::move(options); modal_select_index_ = 0; select_component_ = ftxui::Menu(&modal_select_entries_, &modal_select_index_); select_callback_ = std::move(callback); }
|
std::function<void(bool, const std::string&)> callback,
|
||||||
void TuxDockApp::OpenMessage(const std::string& title, const std::string& text) { modal_mode_ = ModalMode::Message; modal_title_ = title; modal_text_ = text; modal_content_ = {}; }
|
bool secret) {
|
||||||
void TuxDockApp::OpenListMessage(const std::string& title, ftxui::Element content) { modal_mode_ = ModalMode::Message; modal_title_ = title; modal_text_.clear(); modal_content_ = std::move(content); }
|
modal_mode_ = ModalMode::Input;
|
||||||
void TuxDockApp::ResolveInput(bool confirmed) { auto callback = std::move(input_callback_); const auto value = modal_input_; modal_mode_ = ModalMode::None; input_callback_ = {}; if (callback) callback(confirmed, value); }
|
modal_title_ = title;
|
||||||
void TuxDockApp::ResolveConfirm(bool confirmed) { auto callback = std::move(confirm_callback_); modal_mode_ = ModalMode::None; confirm_callback_ = {}; if (callback) callback(confirmed); }
|
modal_text_ = text;
|
||||||
void TuxDockApp::ResolveSelect(bool confirmed) { auto callback = std::move(select_callback_); const int selected = modal_select_index_; modal_mode_ = ModalMode::None; select_callback_ = {}; if (callback) callback(confirmed, selected); }
|
modal_input_.clear();
|
||||||
void TuxDockApp::CloseMessage() { modal_mode_ = ModalMode::None; modal_content_ = {}; }
|
|
||||||
|
|
||||||
void TuxDockApp::PromptContainerSelection(const std::string& title, std::function<void(const std::string&, const std::string&)> callback) { if (containers_.empty()) { RefreshState("No cached containers. Refreshing..."); return; } std::vector<std::string> options; for (const auto& c : containers_) options.push_back(c.name + " (" + ShortId(c.id) + ") [" + (c.running ? "running" : "stopped") + "]"); OpenSelect(title, "Select a container with arrows and press Enter.", std::move(options), [this, callback = std::move(callback)](bool ok, int selected) { if (!ok) return SetStatus("Action cancelled."); if (selected < 0 || selected >= static_cast<int>(containers_.size())) return SetStatus("Please choose a valid container."); const auto& c = containers_[static_cast<std::size_t>(selected)]; callback(c.id, c.name); }); }
|
ftxui::InputOption option;
|
||||||
void TuxDockApp::PromptImageSelection(const std::string& title, std::function<void(const std::string&, const std::string&)> callback) { if (images_.empty()) { RefreshState("No cached images. Refreshing..."); return; } std::vector<std::string> options; for (const auto& image : images_) options.push_back(image.second + " (" + ShortId(image.first) + ")"); OpenSelect(title, "Select an image with arrows and press Enter.", std::move(options), [this, callback = std::move(callback)](bool ok, int selected) { if (!ok) return SetStatus("Action cancelled."); if (selected < 0 || selected >= static_cast<int>(images_.size())) return SetStatus("Please choose a valid image."); const auto& image = images_[static_cast<std::size_t>(selected)]; callback(image.first, image.second); }); }
|
option.password = secret;
|
||||||
|
input_component_ = ftxui::Input(&modal_input_, "Type here", option);
|
||||||
|
input_callback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::OpenConfirm(const std::string& title,
|
||||||
|
const std::string& text,
|
||||||
|
std::function<void(bool)> callback) {
|
||||||
|
modal_mode_ = ModalMode::Confirm;
|
||||||
|
modal_title_ = title;
|
||||||
|
modal_text_ = text;
|
||||||
|
confirm_callback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::OpenSelect(const std::string& title,
|
||||||
|
const std::string& text,
|
||||||
|
std::vector<std::string> options,
|
||||||
|
std::function<void(bool, int)> callback) {
|
||||||
|
modal_mode_ = ModalMode::Select;
|
||||||
|
modal_title_ = title;
|
||||||
|
modal_text_ = text;
|
||||||
|
modal_select_entries_ = std::move(options);
|
||||||
|
modal_select_index_ = 0;
|
||||||
|
select_component_ = ftxui::Menu(&modal_select_entries_, &modal_select_index_);
|
||||||
|
select_callback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::OpenMessage(const std::string& title, const std::string& text) {
|
||||||
|
modal_mode_ = ModalMode::Message;
|
||||||
|
modal_title_ = title;
|
||||||
|
modal_text_ = text;
|
||||||
|
modal_content_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::OpenListMessage(const std::string& title, ftxui::Element content) {
|
||||||
|
modal_mode_ = ModalMode::Message;
|
||||||
|
modal_title_ = title;
|
||||||
|
modal_text_.clear();
|
||||||
|
modal_content_ = std::move(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ResolveInput(bool confirmed) {
|
||||||
|
auto callback = std::move(input_callback_);
|
||||||
|
const auto value = modal_input_;
|
||||||
|
modal_mode_ = ModalMode::None;
|
||||||
|
input_callback_ = {};
|
||||||
|
if (callback) callback(confirmed, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ResolveConfirm(bool confirmed) {
|
||||||
|
auto callback = std::move(confirm_callback_);
|
||||||
|
modal_mode_ = ModalMode::None;
|
||||||
|
confirm_callback_ = {};
|
||||||
|
if (callback) callback(confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ResolveSelect(bool confirmed) {
|
||||||
|
auto callback = std::move(select_callback_);
|
||||||
|
const int selected = modal_select_index_;
|
||||||
|
modal_mode_ = ModalMode::None;
|
||||||
|
select_callback_ = {};
|
||||||
|
if (callback) callback(confirmed, selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::CloseMessage() {
|
||||||
|
modal_mode_ = ModalMode::None;
|
||||||
|
modal_content_ = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::PromptContainerSelection(
|
||||||
|
const std::string& title,
|
||||||
|
std::function<void(const std::string&, const std::string&)> callback) {
|
||||||
|
if (containers_.empty()) {
|
||||||
|
RefreshState("No cached containers. Refreshing...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> options;
|
||||||
|
for (const auto& container : containers_) {
|
||||||
|
options.push_back(container.name + " (" + ShortId(container.id) + ") [" +
|
||||||
|
(container.running ? "running" : "stopped") + "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenSelect(
|
||||||
|
title,
|
||||||
|
"Select a container with arrows and press Enter.",
|
||||||
|
std::move(options),
|
||||||
|
[this, callback = std::move(callback)](bool ok, int selected) {
|
||||||
|
if (!ok) return SetStatus("Action cancelled.");
|
||||||
|
if (selected < 0 || selected >= static_cast<int>(containers_.size())) {
|
||||||
|
return SetStatus("Please choose a valid container.");
|
||||||
|
}
|
||||||
|
const auto& container = containers_[static_cast<std::size_t>(selected)];
|
||||||
|
callback(container.id, container.name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::PromptImageSelection(
|
||||||
|
const std::string& title,
|
||||||
|
std::function<void(const std::string&, const std::string&)> callback) {
|
||||||
|
if (images_.empty()) {
|
||||||
|
RefreshState("No cached images. Refreshing...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> options;
|
||||||
|
for (const auto& image : images_) {
|
||||||
|
options.push_back(image.second + " (" + ShortId(image.first) + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenSelect(
|
||||||
|
title,
|
||||||
|
"Select an image with arrows and press Enter.",
|
||||||
|
std::move(options),
|
||||||
|
[this, callback = std::move(callback)](bool ok, int selected) {
|
||||||
|
if (!ok) return SetStatus("Action cancelled.");
|
||||||
|
if (selected < 0 || selected >= static_cast<int>(images_.size())) {
|
||||||
|
return SetStatus("Please choose a valid image.");
|
||||||
|
}
|
||||||
|
const auto& image = images_[static_cast<std::size_t>(selected)];
|
||||||
|
callback(image.first, image.second);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void TuxDockApp::RunDeferredStatusAction(const std::string& wait, std::function<std::string()> action) {
|
void TuxDockApp::RunDeferredStatusAction(const std::string& wait, std::function<std::string()> action) {
|
||||||
BeginBusyOperation("Working", wait, std::move(action));
|
BeginBusyOperation("Working", wait, std::move(action));
|
||||||
|
|
@ -232,7 +417,20 @@ void TuxDockApp::BeginStopOperation(const std::string& id) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void TuxDockApp::ClearTerminal() { std::cout << "\x1b[2J\x1b[H" << std::flush; }
|
void TuxDockApp::ClearTerminal() { std::cout << "\x1b[2J\x1b[H" << std::flush; }
|
||||||
void TuxDockApp::RunWithRestoredIO(const std::function<void()>& action, bool before, bool after) { if (screen_) screen_->WithRestoredIO([&] { if (before) ClearTerminal(); action(); if (after) ClearTerminal(); })(); else action(); }
|
void TuxDockApp::RunWithRestoredIO(const std::function<void()>& action,
|
||||||
|
bool before,
|
||||||
|
bool after) {
|
||||||
|
if (!screen_) {
|
||||||
|
action();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
screen_->WithRestoredIO([&] {
|
||||||
|
if (before) ClearTerminal();
|
||||||
|
action();
|
||||||
|
if (after) ClearTerminal();
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
void TuxDockApp::ActionListContainers() {
|
void TuxDockApp::ActionListContainers() {
|
||||||
if (containers_.empty()) {
|
if (containers_.empty()) {
|
||||||
|
|
@ -249,31 +447,242 @@ void TuxDockApp::ActionListImages() {
|
||||||
}
|
}
|
||||||
OpenListMessage("Images", FormatImageList(images_));
|
OpenListMessage("Images", FormatImageList(images_));
|
||||||
}
|
}
|
||||||
void TuxDockApp::ActionPullImage() { OpenInput("Pull Docker Image", "Enter image name:", [this](bool ok, const std::string& image) { if (!ok) return; BeginBusyOperation("Pulling image", "Please wait...", [this, image] { std::string m; docker_.pullImage(image, m); return m; }); }); }
|
void TuxDockApp::ActionPullImage() {
|
||||||
void TuxDockApp::ActionRunContainer() { PromptImageSelection("Run Interactive Container", [this](const std::string&, const std::string& tag) { auto context = std::make_shared<RunContainerContext>(); context->image = tag; PromptPortCountAndRun(context); }); }
|
OpenInput(
|
||||||
void TuxDockApp::PromptPortCountAndRun(const std::shared_ptr<RunContainerContext>& c) { OpenInput("Port Mappings", "How many port mappings? (0 for none)", [this, c](bool ok, const std::string& value) { if (!ok) return; if (!IsDigits(value)) return SetStatus("Please enter a valid number."); c->port_count = std::stoi(value); PromptNextPort(c, 0); }); }
|
"Pull Docker Image", "Enter image name:",
|
||||||
void TuxDockApp::PromptNextPort(const std::shared_ptr<RunContainerContext>& c, int index) { if (index >= c->port_count) { std::string m; RunWithRestoredIO([this, c, &m] { docker_.runContainerInteractive(c->image, c->ports, m); }, true, true); SetStatus(m); RefreshState(); return; } OpenInput("Port Mapping", "Enter mapping #" + std::to_string(index + 1), [this, c, index](bool ok, const std::string& value) { if (!ok) return; if (!IsValidPortMapping(value)) return SetStatus("Use host:container format."); c->ports.push_back(value); PromptNextPort(c, index + 1); }); }
|
[this](bool ok, const std::string& image) {
|
||||||
void TuxDockApp::ActionStartInteractive() { PromptContainerSelection("Start Interactively", [this](const std::string& id, const std::string&) { std::string m; RunWithRestoredIO([this, &id, &m] { docker_.startInteractive(id, m); }, true, true); SetStatus(m); RefreshState(); }); }
|
if (!ok) return;
|
||||||
void TuxDockApp::ActionStartDetached() { PromptContainerSelection("Start Detached", [this](const std::string& id, const std::string&) { BeginBusyOperation("Starting container", "Please wait...", [this, id] { std::string m; docker_.startDetached(id, m); return m; }); }); }
|
BeginBusyOperation(
|
||||||
void TuxDockApp::ActionDeleteImage() { PromptImageSelection("Delete Image", [this](const std::string& id, const std::string& tag) { OpenConfirm("Delete Image", "Delete image " + tag + "?", [this, id](bool ok) { if (ok) BeginBusyOperation("Deleting image", "Please wait...", [this, id] { std::string m; docker_.deleteImage(id, m); return m; }); }); }); }
|
"Pulling image", "Please wait...",
|
||||||
void TuxDockApp::ActionStopContainer() { PromptContainerSelection("Stop Container", [this](const std::string& id, const std::string&) { BeginStopOperation(id); }); }
|
[this, image] {
|
||||||
void TuxDockApp::ActionRemoveContainer() { PromptContainerSelection("Remove Container", [this](const std::string& id, const std::string& name) { OpenConfirm("Remove Container", "Remove container " + name + "?", [this, id](bool ok) { if (ok) BeginBusyOperation("Removing container", "Please wait...", [this, id] { std::string m; docker_.removeContainer(id, m); return m; }); }); }); }
|
std::string message;
|
||||||
void TuxDockApp::ActionExecShell() { PromptContainerSelection("Open Shell", [this](const std::string& id, const std::string&) { std::string m; RunWithRestoredIO([this, &id, &m] { docker_.execShell(id, m); }, true, true); SetStatus(m); }); }
|
docker_.pullImage(image, message);
|
||||||
void TuxDockApp::ActionExecDetachedCommand() { PromptContainerSelection("Run Detached Command", [this](const std::string& id, const std::string& name) { OpenInput("Detached Command", "Enter command to run in " + name + ":", [this, id](bool ok, const std::string& command) { if (!ok) return; BeginBusyOperation("Running command", "Please wait...", [this, id, command] { std::string m; docker_.execDetachedCommand(id, command, m); return m; }); }); }); }
|
return message;
|
||||||
void TuxDockApp::ActionAbout() { OpenMessage("About Tux-Dock", "Tux-Dock 0.1-beta | Created by markmental"); }
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void TuxDockApp::ExecuteSelectedAction() { switch (menu_selected_) { case 0: ActionPullImage(); break; case 1: ActionRunContainer(); break; case 2: ActionListContainers(); break; case 3: ActionListImages(); break; case 4: ActionStartInteractive(); break; case 5: ActionStartDetached(); break; case 6: ActionDeleteImage(); break; case 7: ActionStopContainer(); break; case 8: ActionRemoveContainer(); break; case 9: ActionExecShell(); break; case 10: ActionExecDetachedCommand(); break; case 11: ActionAbout(); break; case 12: if (screen_) screen_->ExitLoopClosure()(); break; default: break; } }
|
void TuxDockApp::ActionRunContainer() {
|
||||||
|
PromptImageSelection(
|
||||||
|
"Create Container",
|
||||||
|
[this](const std::string&, const std::string& tag) {
|
||||||
|
auto context = std::make_shared<RunContainerContext>();
|
||||||
|
context->image = tag;
|
||||||
|
OpenInput(
|
||||||
|
"Container Name", "Enter a name for the container:",
|
||||||
|
[this, context](bool ok, const std::string& name) {
|
||||||
|
if (!ok) return;
|
||||||
|
if (!IsValidContainerName(name)) {
|
||||||
|
return SetStatus(
|
||||||
|
"Use letters, numbers, '.', '_', or '-' in the container name.");
|
||||||
|
}
|
||||||
|
context->name = name;
|
||||||
|
PromptPortCountAndRun(context);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::PromptPortCountAndRun(
|
||||||
|
const std::shared_ptr<RunContainerContext>& context) {
|
||||||
|
OpenInput(
|
||||||
|
"Port Mappings", "How many port mappings? (0 for none)",
|
||||||
|
[this, context](bool ok, const std::string& value) {
|
||||||
|
if (!ok) return;
|
||||||
|
if (!IsDigits(value)) return SetStatus("Please enter a valid number.");
|
||||||
|
context->port_count = std::stoi(value);
|
||||||
|
PromptNextPort(context, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::PromptNextPort(
|
||||||
|
const std::shared_ptr<RunContainerContext>& context,
|
||||||
|
int index) {
|
||||||
|
if (index >= context->port_count) {
|
||||||
|
BeginBusyOperation(
|
||||||
|
"Creating container", "Please wait...",
|
||||||
|
[this, context] {
|
||||||
|
std::string message;
|
||||||
|
docker_.createContainer(context->name, context->image,
|
||||||
|
context->ports, message);
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenInput(
|
||||||
|
"Port Mapping", "Enter mapping #" + std::to_string(index + 1),
|
||||||
|
[this, context, index](bool ok, const std::string& value) {
|
||||||
|
if (!ok) return;
|
||||||
|
if (!IsValidPortMapping(value)) {
|
||||||
|
return SetStatus("Use host:container format.");
|
||||||
|
}
|
||||||
|
context->ports.push_back(value);
|
||||||
|
PromptNextPort(context, index + 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ActionStartDetached() {
|
||||||
|
PromptContainerSelection(
|
||||||
|
"Start Detached",
|
||||||
|
[this](const std::string& id, const std::string&) {
|
||||||
|
BeginBusyOperation(
|
||||||
|
"Starting container", "Please wait...",
|
||||||
|
[this, id] {
|
||||||
|
std::string message;
|
||||||
|
docker_.startDetached(id, message);
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ActionDeleteImage() {
|
||||||
|
PromptImageSelection(
|
||||||
|
"Delete Image",
|
||||||
|
[this](const std::string& id, const std::string& tag) {
|
||||||
|
OpenConfirm(
|
||||||
|
"Delete Image", "Delete image " + tag + "?",
|
||||||
|
[this, id](bool ok) {
|
||||||
|
if (!ok) return;
|
||||||
|
BeginBusyOperation(
|
||||||
|
"Deleting image", "Please wait...",
|
||||||
|
[this, id] {
|
||||||
|
std::string message;
|
||||||
|
docker_.deleteImage(id, message);
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ActionStopContainer() {
|
||||||
|
PromptContainerSelection(
|
||||||
|
"Stop Container",
|
||||||
|
[this](const std::string& id, const std::string&) {
|
||||||
|
BeginStopOperation(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ActionRemoveContainer() {
|
||||||
|
PromptContainerSelection(
|
||||||
|
"Remove Container",
|
||||||
|
[this](const std::string& id, const std::string& name) {
|
||||||
|
OpenConfirm(
|
||||||
|
"Remove Container", "Remove container " + name + "?",
|
||||||
|
[this, id](bool ok) {
|
||||||
|
if (!ok) return;
|
||||||
|
BeginBusyOperation(
|
||||||
|
"Removing container", "Please wait...",
|
||||||
|
[this, id] {
|
||||||
|
std::string message;
|
||||||
|
docker_.removeContainer(id, message);
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ActionExecShell() {
|
||||||
|
PromptContainerSelection(
|
||||||
|
"Open Shell",
|
||||||
|
[this](const std::string& id, const std::string&) {
|
||||||
|
std::string message;
|
||||||
|
RunWithRestoredIO(
|
||||||
|
[this, &id, &message] { docker_.execShell(id, message); }, true, true);
|
||||||
|
SetStatus(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ActionExecDetachedCommand() {
|
||||||
|
PromptContainerSelection(
|
||||||
|
"Run Detached Command",
|
||||||
|
[this](const std::string& id, const std::string& name) {
|
||||||
|
OpenInput(
|
||||||
|
"Detached Command", "Enter command to run in " + name + ":",
|
||||||
|
[this, id](bool ok, const std::string& command) {
|
||||||
|
if (!ok) return;
|
||||||
|
BeginBusyOperation(
|
||||||
|
"Running command", "Please wait...",
|
||||||
|
[this, id, command] {
|
||||||
|
std::string message;
|
||||||
|
docker_.execDetachedCommand(id, command, message);
|
||||||
|
return message;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ActionAbout() {
|
||||||
|
OpenMessage("About Tux-Dock", "Tux-Dock 0.1.1-beta | Created by markmental");
|
||||||
|
}
|
||||||
|
|
||||||
|
void TuxDockApp::ExecuteSelectedAction() {
|
||||||
|
switch (menu_selected_) {
|
||||||
|
case 0: ActionPullImage(); break;
|
||||||
|
case 1: ActionRunContainer(); break;
|
||||||
|
case 2: ActionListContainers(); break;
|
||||||
|
case 3: ActionListImages(); break;
|
||||||
|
case 4: ActionStartDetached(); break;
|
||||||
|
case 5: ActionDeleteImage(); break;
|
||||||
|
case 6: ActionStopContainer(); break;
|
||||||
|
case 7: ActionRemoveContainer(); break;
|
||||||
|
case 8: ActionExecShell(); break;
|
||||||
|
case 9: ActionExecDetachedCommand(); break;
|
||||||
|
case 10: ActionAbout(); break;
|
||||||
|
case 11:
|
||||||
|
if (screen_) screen_->ExitLoopClosure()();
|
||||||
|
break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool TuxDockApp::OnEvent(ftxui::Event event) {
|
bool TuxDockApp::OnEvent(ftxui::Event event) {
|
||||||
if (modal_mode_ == ModalMode::Busy) {
|
if (modal_mode_ == ModalMode::Busy) {
|
||||||
if (event == ftxui::Event::Custom) ++spinner_frame_;
|
if (event == ftxui::Event::Custom) ++spinner_frame_;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (modal_mode_ == ModalMode::Input) { if (event == ftxui::Event::Return) { ResolveInput(true); return true; } if (event == ftxui::Event::Escape) { ResolveInput(false); return true; } return input_component_->OnEvent(event); }
|
if (modal_mode_ == ModalMode::Input) {
|
||||||
if (modal_mode_ == ModalMode::Confirm) { if (event == ftxui::Event::Return || event == ftxui::Event::Character("y")) { ResolveConfirm(true); return true; } if (event == ftxui::Event::Escape || event == ftxui::Event::Character("n")) { ResolveConfirm(false); return true; } return true; }
|
if (event == ftxui::Event::Return) {
|
||||||
if (modal_mode_ == ModalMode::Select) { if (event == ftxui::Event::Return) { ResolveSelect(true); return true; } if (event == ftxui::Event::Escape) { ResolveSelect(false); return true; } return select_component_->OnEvent(event); }
|
ResolveInput(true);
|
||||||
if (modal_mode_ == ModalMode::Message) { if (event == ftxui::Event::Return || event == ftxui::Event::Escape) { CloseMessage(); return true; } return true; }
|
return true;
|
||||||
if (event == ftxui::Event::Return) { ExecuteSelectedAction(); return true; }
|
}
|
||||||
|
if (event == ftxui::Event::Escape) {
|
||||||
|
ResolveInput(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return input_component_->OnEvent(event);
|
||||||
|
}
|
||||||
|
if (modal_mode_ == ModalMode::Confirm) {
|
||||||
|
if (event == ftxui::Event::Return || event == ftxui::Event::Character("y")) {
|
||||||
|
ResolveConfirm(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (event == ftxui::Event::Escape || event == ftxui::Event::Character("n")) {
|
||||||
|
ResolveConfirm(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (modal_mode_ == ModalMode::Select) {
|
||||||
|
if (event == ftxui::Event::Return) {
|
||||||
|
ResolveSelect(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (event == ftxui::Event::Escape) {
|
||||||
|
ResolveSelect(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return select_component_->OnEvent(event);
|
||||||
|
}
|
||||||
|
if (modal_mode_ == ModalMode::Message) {
|
||||||
|
if (event == ftxui::Event::Return || event == ftxui::Event::Escape) {
|
||||||
|
CloseMessage();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (event == ftxui::Event::Return) {
|
||||||
|
ExecuteSelectedAction();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ftxui::Element TuxDockApp::RenderModal() const {
|
ftxui::Element TuxDockApp::RenderModal() const {
|
||||||
|
|
@ -281,13 +690,21 @@ ftxui::Element TuxDockApp::RenderModal() const {
|
||||||
Element body;
|
Element body;
|
||||||
Element footer;
|
Element footer;
|
||||||
if (modal_mode_ == ModalMode::Input) {
|
if (modal_mode_ == ModalMode::Input) {
|
||||||
body = vbox(Elements{paragraph(modal_text_), separator(), input_component_->Render() | border});
|
body = vbox(Elements{
|
||||||
|
paragraph(modal_text_),
|
||||||
|
separator(),
|
||||||
|
input_component_->Render() | border,
|
||||||
|
});
|
||||||
footer = text("Enter: confirm Esc: cancel") | dim;
|
footer = text("Enter: confirm Esc: cancel") | dim;
|
||||||
} else if (modal_mode_ == ModalMode::Confirm) {
|
} else if (modal_mode_ == ModalMode::Confirm) {
|
||||||
body = paragraph(modal_text_);
|
body = paragraph(modal_text_);
|
||||||
footer = text("Y/Enter: confirm N/Esc: cancel") | dim;
|
footer = text("Y/Enter: confirm N/Esc: cancel") | dim;
|
||||||
} else if (modal_mode_ == ModalMode::Select) {
|
} else if (modal_mode_ == ModalMode::Select) {
|
||||||
body = vbox(Elements{paragraph(modal_text_), separator(), select_component_->Render() | frame | vscroll_indicator});
|
body = vbox(Elements{
|
||||||
|
paragraph(modal_text_),
|
||||||
|
separator(),
|
||||||
|
select_component_->Render() | frame | vscroll_indicator,
|
||||||
|
});
|
||||||
footer = text("Up/Down: choose Enter: confirm Esc: cancel") | dim;
|
footer = text("Up/Down: choose Enter: confirm Esc: cancel") | dim;
|
||||||
} else if (modal_mode_ == ModalMode::Busy) {
|
} else if (modal_mode_ == ModalMode::Busy) {
|
||||||
static const std::string spinner = "|/-\\";
|
static const std::string spinner = "|/-\\";
|
||||||
|
|
@ -303,12 +720,30 @@ ftxui::Element TuxDockApp::RenderModal() const {
|
||||||
size(HEIGHT, LESS_THAN, 18) | size(WIDTH, LESS_THAN, 96);
|
size(HEIGHT, LESS_THAN, 18) | size(WIDTH, LESS_THAN, 96);
|
||||||
footer = text("Enter/Esc: close") | dim;
|
footer = text("Enter/Esc: close") | dim;
|
||||||
}
|
}
|
||||||
const std::string title = modal_mode_ == ModalMode::Busy ? operation_state_.title() : modal_title_;
|
const std::string title = modal_mode_ == ModalMode::Busy
|
||||||
|
? operation_state_.title()
|
||||||
|
: modal_title_;
|
||||||
return window(text(title), vbox(Elements{body, separator(), footer})) |
|
return window(text(title), vbox(Elements{body, separator(), footer})) |
|
||||||
size(WIDTH, LESS_THAN, 96) | size(WIDTH, GREATER_THAN, 42) |
|
size(WIDTH, LESS_THAN, 96) | size(WIDTH, GREATER_THAN, 42) |
|
||||||
size(HEIGHT, LESS_THAN, 24) | size(HEIGHT, GREATER_THAN, 10) | center;
|
size(HEIGHT, LESS_THAN, 24) | size(HEIGHT, GREATER_THAN, 10) | center;
|
||||||
}
|
}
|
||||||
ftxui::Element TuxDockApp::Render() const { using namespace ftxui; auto base = window(text("Actions"), vbox(Elements{menu_component_->Render() | frame | vscroll_indicator, separator(), text("Up/Down: navigate Enter: select") | dim})) | size(WIDTH, GREATER_THAN, 56) | size(WIDTH, LESS_THAN, 90) | center; return modal_mode_ == ModalMode::None ? base : dbox({base, RenderModal() | clear_under | center}); }
|
ftxui::Element TuxDockApp::Render() const {
|
||||||
|
using namespace ftxui;
|
||||||
|
|
||||||
|
auto base = window(
|
||||||
|
text("Actions"),
|
||||||
|
vbox(Elements{
|
||||||
|
menu_component_->Render() | frame | vscroll_indicator,
|
||||||
|
separator(),
|
||||||
|
text("Up/Down: navigate Enter: select") | dim,
|
||||||
|
})) |
|
||||||
|
size(WIDTH, GREATER_THAN, 56) |
|
||||||
|
size(WIDTH, LESS_THAN, 90) | center;
|
||||||
|
|
||||||
|
return modal_mode_ == ModalMode::None
|
||||||
|
? base
|
||||||
|
: dbox({base, RenderModal() | clear_under | center});
|
||||||
|
}
|
||||||
int TuxDockApp::Run() {
|
int TuxDockApp::Run() {
|
||||||
std::string connection_error;
|
std::string connection_error;
|
||||||
if (!docker_.checkConnection(connection_error)) {
|
if (!docker_.checkConnection(connection_error)) {
|
||||||
|
|
@ -332,14 +767,22 @@ int TuxDockApp::Run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
auto root = ftxui::Renderer(menu_component_, [this] { return Render(); });
|
auto root = ftxui::Renderer(menu_component_, [this] { return Render(); });
|
||||||
auto app = ftxui::CatchEvent(root, [this](ftxui::Event e) { return OnEvent(e); });
|
auto app = ftxui::CatchEvent(root, [this](ftxui::Event event) {
|
||||||
|
return OnEvent(event);
|
||||||
|
});
|
||||||
auto screen = ftxui::ScreenInteractive::TerminalOutput();
|
auto screen = ftxui::ScreenInteractive::TerminalOutput();
|
||||||
screen_ = &screen;
|
screen_ = &screen;
|
||||||
screen.Loop(app);
|
screen.Loop(app);
|
||||||
screen_ = nullptr;
|
screen_ = nullptr;
|
||||||
if (refresh_thread_.joinable()) refresh_thread_.join();
|
if (refresh_thread_.joinable()) refresh_thread_.join();
|
||||||
if (spinner_thread_.joinable()) spinner_thread_.join();
|
if (spinner_thread_.joinable()) spinner_thread_.join();
|
||||||
for (auto& thread : action_threads_) if (thread.joinable()) thread.join();
|
for (auto& thread : action_threads_) {
|
||||||
|
if (thread.joinable()) thread.join();
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
int main() { TuxDockApp app; return app.Run(); }
|
|
||||||
|
int main() {
|
||||||
|
TuxDockApp app;
|
||||||
|
return app.Run();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,27 +89,22 @@ bool DockerManager::pullImage(const std::string& image, std::string& message) co
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DockerManager::runContainerInteractive(const std::string& image,
|
bool DockerManager::createContainer(const std::string& name,
|
||||||
const std::vector<std::string>& ports,
|
const std::string& image,
|
||||||
std::string& message) const {
|
const std::vector<std::string>& ports,
|
||||||
if (image.empty()) {
|
std::string& message) const {
|
||||||
message = "Please choose an image first.";
|
if (name.empty() || image.empty()) {
|
||||||
|
message = "Please provide a container name and choose an image first.";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
std::vector<std::string> args{"docker", "run", "-it"};
|
std::vector<std::string> args{"docker", "create", "--name", name};
|
||||||
for (const auto& port : ports) {
|
for (const auto& port : ports) {
|
||||||
args.push_back("-p");
|
args.push_back("-p");
|
||||||
args.push_back(port);
|
args.push_back(port);
|
||||||
}
|
}
|
||||||
args.insert(args.end(), {image, "/bin/sh"});
|
args.insert(args.end(), {image, "sleep", "infinity"});
|
||||||
const bool ok = runProcess(args, message, true);
|
const bool ok = runProcess(args, message);
|
||||||
if (ok) message = "Interactive container session finished.";
|
if (ok) message = "Container created and ready to start when you are ready.";
|
||||||
return ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DockerManager::startInteractive(const std::string& id, std::string& message) const {
|
|
||||||
const bool ok = runProcess({"docker", "start", "-ai", id}, message, true);
|
|
||||||
if (ok) message = "Interactive container session finished.";
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,10 +32,10 @@ public:
|
||||||
ListResult<ImageInfo> getImageList() const;
|
ListResult<ImageInfo> getImageList() const;
|
||||||
|
|
||||||
bool pullImage(const std::string& image, std::string& message) const;
|
bool pullImage(const std::string& image, std::string& message) const;
|
||||||
bool runContainerInteractive(const std::string& image,
|
bool createContainer(const std::string& name,
|
||||||
const std::vector<std::string>& ports,
|
const std::string& image,
|
||||||
std::string& message) const;
|
const std::vector<std::string>& ports,
|
||||||
bool startInteractive(const std::string& containerId, std::string& message) const;
|
std::string& message) const;
|
||||||
bool startDetached(const std::string& containerId, std::string& message) const;
|
bool startDetached(const std::string& containerId, std::string& message) const;
|
||||||
bool deleteImage(const std::string& imageId, std::string& message) const;
|
bool deleteImage(const std::string& imageId, std::string& message) const;
|
||||||
bool stopContainer(const std::string& containerId, std::string& message) const;
|
bool stopContainer(const std::string& containerId, std::string& message) const;
|
||||||
|
|
|
||||||
|
|
@ -33,9 +33,9 @@ cleanup() {
|
||||||
}
|
}
|
||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
printf '%s\n' "[TUI] Actions > Run/Create Interactive Container"
|
printf '%s\n' "[TUI] Actions > Create Container"
|
||||||
printf '%s\n' "[TUI] Busy |"
|
printf '%s\n' "[TUI] Busy |"
|
||||||
printf '%s\n' "[TUI] docker run --name $name $image sh -c '<tests>'"
|
printf '%s\n' "[TUI] docker create --name $name $image sleep infinity"
|
||||||
|
|
||||||
if ! docker image inspect "$image" >/dev/null 2>&1; then
|
if ! docker image inspect "$image" >/dev/null 2>&1; then
|
||||||
printf '%s\n' "[TUI] Pull Docker Image: $image"
|
printf '%s\n' "[TUI] Pull Docker Image: $image"
|
||||||
|
|
@ -53,8 +53,10 @@ run_in_container() {
|
||||||
printf '%s\n' "[TUI] Complete: $command_name succeeded"
|
printf '%s\n' "[TUI] Complete: $command_name succeeded"
|
||||||
}
|
}
|
||||||
|
|
||||||
run_raw "docker run -d --name $name $image sleep 300" docker run -d --name "$name" "$image" sleep 300
|
run_raw "docker create --name $name $image sleep infinity" docker create --name "$name" "$image" sleep infinity
|
||||||
printf '%s\n' "[TUI] Complete: Container created"
|
printf '%s\n' "[TUI] Complete: Container created"
|
||||||
|
run_raw "docker start $name" docker start "$name"
|
||||||
|
printf '%s\n' "[TUI] Complete: Container started"
|
||||||
run_in_container "ls /" ls /
|
run_in_container "ls /" ls /
|
||||||
run_in_container "cat /etc/os-release" cat /etc/os-release
|
run_in_container "cat /etc/os-release" cat /etc/os-release
|
||||||
run_in_container "apt-get update" apt-get update
|
run_in_container "apt-get update" apt-get update
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue