Files
configurator/modules/docker/module.gui
Myy Miouyouyou 9341f2f4b5 modules: docker: Small improvements on the UI
Added the ability to check the logs of running instances.

I'm still worried about running background processes with UI,
like "checking the logs continuously" or
"checking the status continuously", since it's the kind of
process that can hog a machine quickly, if they're not handled
correctly when they're not needed.

So, at the moment, I'll just add a few more buttons in order
to check things manually.

Also, docker-compose provide no clear way of checking the
status of an installation. You can use 'ps', and you'll know
the status of each invididual service.
However, it's then up to you to determine if the status of
each service makes sense.

Signed-off-by: Myy Miouyouyou <myy@miouyouyou.fr>
2022-06-27 21:35:15 +02:00

274 lines
9.6 KiB
Python
Executable File

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from inspect import getsourcefile
from pathlib import Path
import os
import subprocess
import sys
import yaml
from PySide2.QtCore import QFile, QIODevice, QTranslator, QCoreApplication, QLocale
from PySide2.QtGui import QStandardItemModel, QStandardItem
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication
from library.docker_compose import DockerComposeModule
# TODO Reset right pane correctly, dissociate reset code from fill code
# TODO Select installed item after installation automatically
# TODO Use a QSS for theming
# TODO Make it look decent
class ModuleGUI:
UI_MAIN_WINDOW = 'ui/mainwindow/designer.ui'
UI_CSS = 'ui/style.css'
UI_TRANSLATIONS_DIRPATH = 'ui/mainwindow/translations'
main_window = None
def ui_refresh_lists(self):
list_data = [
[self.main_window.availableList.model(), self.engine.list_available(self.engine.configuration)],
[self.main_window.installedList.model(), self.engine.list_installed(self.engine.configuration)]
]
for list_data_model_data in list_data:
model, data = list_data_model_data
model.clear()
for element in data:
model.appendRow(QStandardItem(element))
def logic_get_compose_yaml_content(self) -> str:
contents_list = self.engine.docker_compose_get_content([self.current_item['name']])
if contents_list:
return contents_list[0]
else:
return ''
# TODO The engine needs to provide these info correctly
# Or we need to use them in a more appropriate way
# Like display the errors accordingly
def logic_parse_outputs(self, outputs_list:list) -> str:
if outputs_list:
outputs = outputs_list[0]
return str(outputs[0]) + str(outputs[1])
else:
return ''
def logic_get_command_output(self, compose_service:str, engine_method) -> str:
return self.logic_parse_outputs(engine_method([compose_service]))
def logic_get_status_output(self) -> str:
return self.logic_get_command_output(self.current_item['name'], self.engine.compose_status)
def _show_compose_command_output_in(self, text_output, command: str, service_name: str):
text_output.append(
self.logic_get_command_output(service_name, command))
def logic_get_logs(self) -> str:
return self.logic_get_command_output(
self.current_item['name'],
self.engine.compose_logs)
def _xdg_open(self, element_path: str):
subprocess.run(['xdg-open', element_path])
def cb_up_button(self):
self.main_window.statusText.append(
self.logic_get_command_output(
self.current_item['name'],
self.engine.compose_start))
def cb_down_button(self):
self.main_window.statusText.append(
self.logic_get_command_output(
self.current_item['name'],
self.engine.compose_stop))
def cb_logs_button(self):
self.main_window.logsText.clear()
self.main_window.logsText.append(self.logic_get_logs())
def cb_edit_button(self):
# subprocess.run(['xdg-open', str(self.engine.compose_yaml_path(self.current_item['name']))])
self._xdg_open( str(self.engine.compose_yaml_path(self.current_item['name'])) )
def cb_open_folder_button(self):
# subprocess.run(['xdg-open', str(self.engine.installed_path(self.current_item['name']))])
self._xdg_open( str(self.engine.installed_path(self.current_item['name'])) )
def cb_uninstall_button(self):
self.engine.docker_remove([self.current_item['name']])
self.main_window.installedList.clearSelection()
self.ui_refresh_lists()
def cb_install_button(self):
self.engine.docker_install([self.current_item['name']])
self.main_window.availableList.clearSelection()
self.ui_refresh_lists()
def ui_show_current_item(self):
main_window = self.main_window
current_item = self.current_item
main_window.currentComposeNameLabel.setText(current_item['name'])
texts_contents = [
[main_window.runLogsText, None],
[main_window.yamlText, self.logic_get_compose_yaml_content],
[main_window.statusText, self.logic_get_status_output],
[main_window.logsText, self.logic_get_logs]
]
for text_browser_and_content_func in texts_contents:
text_browser, content_func = text_browser_and_content_func
text_browser.clear()
if current_item['installed'] and content_func:
text_browser.append(content_func())
buttons = {
True: [
[main_window.upButton, self.cb_up_button],
[main_window.downButton, self.cb_down_button],
[main_window.editButton, self.cb_edit_button],
[main_window.openFolderButton, self.cb_open_folder_button],
[main_window.uninstallButton, self.cb_uninstall_button],
[main_window.getLogsButton, self.cb_logs_button]
],
False: [
[main_window.installButton, self.cb_install_button]
]
}
for installed_state, buttons_and_cb in buttons.items():
for button_and_cb in buttons_and_cb:
button, cb = button_and_cb
if current_item['installed'] == installed_state:
button.pressed.connect(cb)
else:
try:
button.pressed.disconnect()
except:
pass
# TODO Find a better name
widgets_states = {
True: [main_window.installedButtons],
False: [main_window.notInstalledButtons]
}
for installed_state, widgets in widgets_states.items():
for widget in widgets:
if current_item['installed'] == installed_state:
print(f'Showing {widget}')
widget.show()
else:
print(f'Hiding {widget}')
widget.hide()
def cb_show_item(self, item_info, item_list, item_installed: bool):
# TODO Remove this horrible hack
# TODO Make the two lists synced somehow, through group headers or something
selections_list = [
self.main_window.availableList,
self.main_window.installedList
]
for listview in selections_list:
if listview != item_list:
listview.clearSelection()
self.current_item = {'name': item_info.data(), 'installed': item_installed}
self.ui_show_current_item()
def cb_show_installed_item(self, item_index):
self.cb_show_item(item_index, self.main_window.installedList, True)
def cb_show_available_item(self, item_index):
self.cb_show_item(item_index, self.main_window.availableList, self.engine.is_installed(item_index.data()))
def cb_menu_quit(self):
QCoreApplication.quit()
def cb_menu_open_docker_folder(self):
self._xdg_open(self.engine.install_dirpath())
def setupMainWindow(self):
menu_items = [
[self.main_window.actionQuit, self.cb_menu_quit],
[self.main_window.actionOpenInstallFolder, self.cb_menu_open_docker_folder]
]
for menu_item_and_cb in menu_items:
menu_item, cb = menu_item_and_cb
menu_item.triggered.connect(cb)
lists = [
[self.main_window.installedList, self.cb_show_installed_item],
[self.main_window.availableList, self.cb_show_available_item]
]
for list_view_and_cb in lists:
list_view, cb = list_view_and_cb
list_view.setModel(QStandardItemModel())
list_view.clicked.connect(cb)
self.ui_refresh_lists()
def __init__(self, engine):
self.engine = engine
def start(self):
app = QApplication(sys.argv)
# Handle translations files
translator = QTranslator()
translator.load(QLocale(), 'ui/mainwindow/translations/mainwindow', '.', '', '.qm')
QCoreApplication.installTranslator(translator)
ui_file = QFile(self.UI_MAIN_WINDOW)
loader = QUiLoader()
main_window = loader.load(self.UI_MAIN_WINDOW, None)
if not main_window:
print(loader.errorString())
sys.exit(-1)
self.main_window = main_window
with open(self.UI_CSS, 'r') as qss_file:
app.setStyleSheet(qss_file.read())
self.setupMainWindow()
main_window.show()
sys.exit(app.exec_())
if __name__ == '__main__':
# Set the current working directory as the script one
os.chdir(os.path.dirname(getsourcefile(lambda:0)))
#module_directory = '/usr/share/armbian/configurator/modules/docker'
#if 'ARMBIAN_MODULE_DIRECTORY' in os.environ:
# module_directory = os.environ['ARMBIAN_MODULE_DIRECTORY']
#os.chdir(module_directory)
# FIXME Parse configuration files from a set of directories
# including user writeable ones.
configuration_file_path = Path('configuration/default.yaml')
with open(configuration_file_path, 'r') as conf_file:
try:
default_config = yaml.safe_load(conf_file)
gui = ModuleGUI(DockerComposeModule(default_config))
gui.start()
except Exception as e:
logging.error(f'Could not open configuration file {configuration_file_path}')
logging.error(e)