You've already forked configurator
mirror of
https://github.com/armbian/configurator.git
synced 2026-01-06 10:36:02 -08:00
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>
274 lines
9.6 KiB
Python
Executable File
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)
|
|
|
|
|