├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── dmg.json ├── kuberider ├── __init__.py ├── application.py ├── core │ ├── __init__.py │ ├── console_manager.py │ ├── kube_command_builder.py │ ├── mock_console_manager.py │ ├── network_manager.py │ ├── terminal.py │ └── worker_pool.py ├── domain │ ├── __init__.py │ ├── container_interactor.py │ ├── contexts_interactor.py │ ├── interactor.py │ ├── kube_resource_interactor.py │ ├── namespaces_interactor.py │ └── pods_interactor.py ├── entities │ ├── __init__.py │ ├── data_manager.py │ └── model.py ├── events │ ├── __init__.py │ └── signals.py ├── generated │ ├── __init__.py │ ├── configuration_dialog.py │ ├── kube_resource_dialog.py │ ├── kube_rider_main.py │ ├── pod_container_widget.py │ ├── pod_item_widget.py │ ├── pod_logs_dialog.py │ ├── pod_volume_widget.py │ └── progress_dialog.py ├── images │ ├── __init__.py │ ├── configure-48.png │ ├── download-48.png │ ├── download-disabled-48.png │ ├── kuberider.png │ ├── kuberider.svg │ ├── load-contexts-48.png │ └── plus-48.png ├── main.py ├── mock_responses │ ├── __init__.py │ ├── k_exec_shell.txt │ ├── k_get_contexts.txt │ ├── k_get_current_context.txt │ ├── k_get_pod_events.json │ ├── k_get_pod_logs.txt │ ├── k_get_qa_multiple_pods.json │ ├── k_get_qa_namespaces.json │ ├── k_get_qa_single_pod.json │ ├── k_get_test_namespaces.json │ └── k_pod_deleted.txt ├── presenters │ ├── __init__.py │ ├── configuration_presenter.py │ ├── console_presenter.py │ ├── container_exec_presenter.py │ ├── file_menu_presenter.py │ ├── kube_resource_presenter.py │ ├── kube_rider_main_presenter.py │ ├── pod_containers_presenter.py │ ├── pod_events_presenter.py │ ├── pod_list_presenter.py │ ├── pod_logs_presenter.py │ ├── pods_filter_presenter.py │ ├── toolbar_presenter.py │ └── watch_presenter.py ├── resources.qrc ├── resources_rc.py ├── settings │ ├── __init__.py │ └── app_settings.py ├── themes │ ├── __init__.py │ ├── light.qss │ └── light_theme.py ├── ui │ ├── __init__.py │ ├── configuration_dialog.py │ ├── kube_resource_dialog.py │ ├── kube_rider_main_window.py │ ├── menus.py │ ├── pod_logs_dialog.py │ ├── progress_dialog.py │ ├── shortcuts.py │ ├── toolbar.py │ └── updater_dialog.py └── widgets │ ├── __init__.py │ ├── pod_container_widget.py │ └── pod_item_widget.py ├── mk-icns.sh ├── packaging └── data │ └── icons │ └── kuberider.icns ├── pre-build.sh ├── requirements.txt ├── resources └── ui │ ├── configuration_dialog.ui │ ├── kube_resource_dialog.ui │ ├── kube_rider_main.ui │ ├── pod_container_widget.ui │ ├── pod_item_widget.ui │ ├── pod_logs_dialog.ui │ ├── pod_volume_widget.ui │ └── progress_dialog.ui └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | .idea/workspace.xml 106 | .DS_Store 107 | 108 | # IntelliJ 109 | .idea/dictionaries/jk.xml 110 | .idea/workspace.xml 111 | .idea 112 | 113 | .vscode/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | os: linux 3 | dist: trusty 4 | sudo: required 5 | python: '3.6' 6 | git: 7 | depth: 1 8 | branches: 9 | only: 10 | - master 11 | env: 12 | global: 13 | - BUILD_VERSION="0.0.7" 14 | script: 15 | - git config --local user.name "namuan" 16 | - git remote set-url origin https://namuan:${GITHUB_TOKEN}@github.com/namuan/kube-rider.git 17 | - git push origin :refs/tags/${BUILD_VERSION} 18 | - git tag -f -am v${BUILD_VERSION} ${BUILD_VERSION} 19 | - git push origin ${BUILD_VERSION} 20 | notifications: 21 | email: false 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 namuan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export PROJECTNAME=$(shell basename "$(PWD)") 2 | 3 | .SILENT: ; # no need for @ 4 | 5 | setup: ## Setup virtual environment and install dependencies 6 | echo "Run the following commands to install required dependencies" 7 | echo "python3 -m venv venv" 8 | echo "source venv/bin/activate" 9 | echo "pip install -r requirements.txt" 10 | echo "Once everything is installed, 'make run' to run the application" 11 | 12 | venv: ## Activates local venv 13 | source venv/bin/activate 14 | 15 | uic: res ## Converts ui files to python 16 | for i in `ls resources/ui/*.ui`; do FNAME=`basename $${i} ".ui"`; ./venv/bin/pyuic5 $${i} > "kuberider/generated/$${FNAME}.py"; done 17 | 18 | res: venv ## Generates and compresses resource file 19 | ./venv/bin/pyrcc5 -compress 9 -o kuberider/resources_rc.py kuberider/resources.qrc 20 | 21 | icns: ## Generates icon files from svg 22 | echo "Run ./mk-icns.sh kuberider/images/kuberider.svg kuberider" 23 | 24 | run: ## Runs the application 25 | export PYTHONPATH=`pwd`:$${PYTHONPATH} && \ 26 | python3 kuberider/main.py 27 | 28 | .PHONY: help 29 | .DEFAULT_GOAL := setup 30 | 31 | help: Makefile 32 | echo 33 | echo " Choose a command run in "$(PROJECTNAME)":" 34 | echo 35 | @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' 36 | echo -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kube-Rider 2 | 3 | A simple desktop client for Kubernetes. 4 | See https://deskriders.dev/tags/kuberider/ for development updates. 5 | 6 | ### Features 7 | 8 | [✓] Uses kubectl 9 | [✓] Display kubectl commands for learning 10 | [✓] Context and Namespace switching 11 | [✓] Pod list and watching 12 | [✓] Create/Delete Pods 13 | [✓] List Pod containers and Events 14 | [✓] Open container logs 15 | [✓] Running commands in container 16 | [✓] Container Port forwarding 17 | [✓] Follow container logs 18 | 19 | ### Demo 20 | 21 | [![KubeRider](https://img.youtube.com/vi/fhQuB4BNryo/0.jpg)](https://www.youtube.com/watch?v=fhQuB4BNryo) 22 | 23 | ### Setup 24 | 25 | Run `make` to display list of commands to install required dependencies in a virtual environment. 26 | 27 | ``` 28 | $ make 29 | Run the following commands to install required dependencies 30 | python3 -m venv venv 31 | source venv/bin/activate 32 | pip install -r requirements.txt 33 | Once everything is installed, 'make run' to run the application 34 | ``` 35 | 36 | Then `make run` should startup the application. 37 | 38 | ``` 39 | $ make run 40 | ``` 41 | -------------------------------------------------------------------------------- /dmg.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "KubeRider", 3 | "icon": "packaging/data/icons/kuberider.icns", 4 | "background": "kuberider/images/kuberider.png", 5 | "icon-size": 80, 6 | "window": { 7 | "position": { 8 | "x": 300, 9 | "y": 300 10 | }, 11 | "size": { 12 | "width": 500, 13 | "height": 250 14 | } 15 | }, 16 | "contents": [ 17 | { 18 | "x": 50, 19 | "y": 50, 20 | "type": "file", 21 | "path": "dist/KubeRider.app" 22 | }, 23 | { 24 | "x": 300, 25 | "y": 50, 26 | "type": "link", 27 | "path": "/Applications" 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /kuberider/__init__.py: -------------------------------------------------------------------------------- 1 | __version__ = '0.0.7' 2 | __appname__ = 'kuberider' 3 | __description__ = 'Kubernetes Desktop Application' 4 | __desktopid__ = 'com.deskriders.KubeRider' 5 | 6 | import sqlalchemy.dialects.sqlite 7 | import sqlalchemy.sql.default_comparator 8 | -------------------------------------------------------------------------------- /kuberider/application.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | from PyQt5.QtWidgets import * 4 | 5 | from . import __version__, __appname__, __desktopid__ 6 | from .themes.light_theme import LightTheme 7 | from .ui.kube_rider_main_window import KubeRiderMainWindow 8 | 9 | 10 | def configure_theme(application): 11 | application.setStyle(LightTheme()) 12 | application.style().load_stylesheet() 13 | 14 | 15 | def main(): 16 | application = QApplication(sys.argv) 17 | application.setApplicationVersion(__version__) 18 | application.setApplicationName(__appname__) 19 | application.setDesktopFileName(__desktopid__) 20 | 21 | window = KubeRiderMainWindow() 22 | configure_theme(application) 23 | 24 | window.show() 25 | sys.exit(application.exec_()) 26 | -------------------------------------------------------------------------------- /kuberider/core/__init__.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QFile, QFileInfo, QTextStream 2 | 3 | 4 | def styles_from_file(filename): 5 | if QFileInfo(filename).exists(): 6 | qss_file = QFile(filename) 7 | qss_file.open(QFile.ReadOnly | QFile.Text) 8 | content = QTextStream(qss_file).readAll() 9 | return content 10 | else: 11 | return None 12 | 13 | def str_to_bool(bool_str): 14 | if type(bool_str) is bool: 15 | return bool_str 16 | return bool_str.lower() in ("yes", "true", "t", "1") 17 | -------------------------------------------------------------------------------- /kuberider/core/console_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | import time 4 | 5 | from kuberider.core.terminal import Terminal 6 | 7 | 8 | class ConsoleManager: 9 | abort_long_running_command = False 10 | terminal = Terminal() 11 | 12 | def run_command(self, command): 13 | logging.debug(f"Running command: {command}") 14 | return subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True).decode('utf-8') 15 | 16 | def run_long_running_command(self, command): 17 | p = subprocess.Popen( 18 | command, 19 | stdout=subprocess.PIPE, 20 | stderr=subprocess.STDOUT, 21 | shell=True 22 | ) 23 | 24 | while not self.abort_long_running_command: 25 | ret_code = p.poll() 26 | line = p.stdout.readline() 27 | yield line 28 | time.sleep(0.2) 29 | if ret_code is not None or ret_code is not 0: 30 | break 31 | 32 | def run_osx_terminal(self, command): 33 | return self.terminal.open_terminal(script=command) 34 | -------------------------------------------------------------------------------- /kuberider/core/kube_command_builder.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QObject 2 | 3 | from kuberider.core.worker_pool import CommandThread 4 | from kuberider.settings.app_settings import app 5 | 6 | 7 | class Kcb(QObject): 8 | kubectl = None 9 | command_thread = None 10 | _command = None 11 | ctx_param = None 12 | ns_param = None 13 | 14 | @staticmethod 15 | def init(command_thread: CommandThread): 16 | c = Kcb() 17 | c.command_thread = command_thread 18 | 19 | c.command_thread.signals.started.connect( 20 | lambda cmd: app.data.update_command_status(cmd, started=True) 21 | ) 22 | c.command_thread.signals.finished.connect( 23 | lambda cmd: app.data.update_command_status(cmd, started=False) 24 | ) 25 | 26 | return c 27 | 28 | def command(self, command): 29 | self._command = command 30 | return self 31 | 32 | def ctx(self): 33 | self.ctx_param = f"--context {app.data.get_current_context()}" 34 | return self 35 | 36 | def ns(self): 37 | self.ns_param = f"--namespace {app.data.get_current_namespace()}" 38 | return self 39 | 40 | def complete_command(self): 41 | kubectl = app.load_kubectl_path() 42 | c = f"{kubectl}" 43 | if self.ctx_param: 44 | c += f" {self.ctx_param}" 45 | if self.ns_param: 46 | c += f" {self.ns_param}" 47 | if self._command: 48 | c += f" {self._command}" 49 | 50 | return c 51 | 52 | def start(self): 53 | self.command_thread.command = self.complete_command() 54 | self.command_thread.start() 55 | 56 | def start_command(self, command): 57 | self.command_thread.command = command 58 | self.command_thread.start() 59 | -------------------------------------------------------------------------------- /kuberider/core/mock_console_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import subprocess 3 | import time 4 | from pathlib import Path 5 | 6 | from kuberider.core.terminal import Terminal 7 | 8 | command_file_mapping = { 9 | "kubectl config get-contexts --output='name'": "k_get_contexts.txt", 10 | "kubectl config current-context": "k_get_current_context.txt", 11 | "kubectl --context qa get namespaces -o json": "k_get_qa_namespaces.json", 12 | "kubectl --context development get namespaces -o json": "k_get_test_namespaces.json", 13 | "kubectl --context qa --namespace default get pods -o json": "k_get_qa_multiple_pods.json", 14 | "kubectl --context qa --namespace kube-public get pods -o json": "k_get_qa_single_pod.json", 15 | "kubectl --context qa --namespace default get event --field-selector='involvedObject.name=hello-node-2-7c99ff6cd7-gtpxr' -o json": "k_get_pod_events.json", 16 | "kubectl --context qa --namespace default exec hello-node-2-7c99ff6cd7-gtpxr -c hello-node-1 sql": "k_exec_shell.txt", 17 | "kubectl --context qa --namespace default logs hello-node-2-7c99ff6cd7-gtpxr -c hello-node-1": "k_get_pod_logs.txt", 18 | "kubectl --context qa --namespace default delete pod hello-node-2-7c99ff6cd7-gtpxr": "k_pod_deleted.txt" 19 | } 20 | 21 | 22 | class MockConsoleManager: 23 | abort_long_running_command = False 24 | 25 | def __init__(self): 26 | self.mock_responses_dir = Path(".").joinpath("mock_responses") 27 | self.terminal = Terminal() 28 | 29 | def run_command(self, command): 30 | logging.debug(f"Running command: {command}") 31 | mock_response = command_file_mapping.get(command, None) 32 | if mock_response: 33 | time.sleep(0.1) 34 | return self.mock_responses_dir.joinpath(mock_response).read_text() 35 | else: 36 | raise LookupError(f"No Mock found for command: {command}") 37 | 38 | def run_long_running_command(self, command): 39 | p = subprocess.Popen( 40 | command, 41 | stdout=subprocess.PIPE, 42 | stderr=subprocess.STDOUT, 43 | shell=True 44 | ) 45 | 46 | while not self.abort_long_running_command: 47 | ret_code = p.poll() 48 | line = p.stdout.readline() 49 | yield line 50 | time.sleep(0.2) 51 | if ret_code is not None or ret_code is not 0: 52 | break 53 | 54 | def run_osx_terminal(self, command): 55 | return self.terminal.open_terminal(script=command) 56 | -------------------------------------------------------------------------------- /kuberider/core/network_manager.py: -------------------------------------------------------------------------------- 1 | class NetworkManager: 2 | pass -------------------------------------------------------------------------------- /kuberider/core/worker_pool.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | from PyQt5.QtCore import QThread, pyqtSignal, QObject 5 | 6 | from kuberider.core.console_manager import ConsoleManager 7 | from kuberider.core.mock_console_manager import MockConsoleManager 8 | from kuberider.settings.app_settings import app 9 | 10 | is_offline = os.getenv("MOCKED", "false").lower() == "true" 11 | 12 | 13 | class CommandSignals(QObject): 14 | started = pyqtSignal(str) 15 | finished = pyqtSignal(str, dict) 16 | success = pyqtSignal(dict) 17 | failure = pyqtSignal(dict) 18 | partial_output = pyqtSignal(str) 19 | 20 | 21 | class BaseCommand(QThread): 22 | def __init__(self): 23 | self.signals = CommandSignals() 24 | self._command = None 25 | self.console_manager = MockConsoleManager() if is_offline else ConsoleManager() 26 | QThread.__init__(self) 27 | 28 | @property 29 | def command(self): 30 | return self._command 31 | 32 | @command.setter 33 | def command(self, value): 34 | self._command = value 35 | 36 | def run(self) -> None: 37 | raise SyntaxError("Implement run") 38 | 39 | 40 | class CommandThread(BaseCommand): 41 | def __init__(self): 42 | super().__init__() 43 | 44 | def run(self): 45 | if not self._command: 46 | logging.warning("No Commands to run") 47 | return 48 | 49 | result = {} 50 | try: 51 | app.data.save_command(self.command) 52 | self.signals.started.emit(self.command) 53 | output = self.console_manager.run_command(self.command) 54 | result = { 55 | 'command': self.command, 56 | 'status': True, 57 | 'output': output 58 | } 59 | self.signals.success.emit(result) 60 | except Exception as e: 61 | logging.error(e) 62 | result = { 63 | 'command': self.command, 64 | 'status': False, 65 | 'output': e 66 | } 67 | self.signals.failure.emit(result) 68 | finally: 69 | self.signals.finished.emit(self.command, result) 70 | 71 | 72 | class TailCommandThread(BaseCommand): 73 | def __init__(self): 74 | super().__init__() 75 | 76 | def run(self): 77 | self.console_manager.abort_long_running_command = False 78 | app.data.save_command(self.command) 79 | self.signals.started.emit(self.command) 80 | for line in self.console_manager.run_long_running_command(self.command): 81 | self.signals.partial_output.emit(line.strip().decode('utf-8')) 82 | self.signals.finished.emit(self.command, {}) 83 | 84 | def stop_process(self): 85 | self.console_manager.abort_long_running_command = True 86 | 87 | 88 | class ExternalAppCommandThread(BaseCommand): 89 | def __init__(self): 90 | super().__init__() 91 | 92 | def run(self): 93 | self.console_manager.run_osx_terminal(self.command) 94 | -------------------------------------------------------------------------------- /kuberider/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/domain/__init__.py -------------------------------------------------------------------------------- /kuberider/domain/container_interactor.py: -------------------------------------------------------------------------------- 1 | from kuberider.core.kube_command_builder import Kcb 2 | from kuberider.core.worker_pool import ExternalAppCommandThread 3 | from kuberider.settings.app_settings import app 4 | 5 | 6 | class ExecShellInteractor: 7 | def __init__(self): 8 | self.ct = ExternalAppCommandThread() 9 | self.kcb = Kcb.init(self.ct) 10 | 11 | def run(self, pod_name, container_name, shell_cmd): 12 | cmd = self.kcb.ctx().ns().command( 13 | f"exec -it {pod_name} -c {container_name} {shell_cmd}" 14 | ).complete_command() 15 | app.data.save_command(cmd) 16 | self.kcb.start_command(cmd) 17 | 18 | def port_forward(self, pod_name, container_name, ports): 19 | cmd = self.kcb.ctx().ns().command( 20 | f"port-forward {pod_name} {ports}" 21 | ).complete_command() 22 | app.data.save_command(cmd) 23 | self.kcb.start_command(cmd) 24 | 25 | def follow_logs(self, pod_name, container_name): 26 | cmd = self.kcb.ctx().ns().command( 27 | f'logs {pod_name} -c {container_name} -f' 28 | ).complete_command() 29 | app.data.save_command(cmd) 30 | self.kcb.start_command(cmd) 31 | -------------------------------------------------------------------------------- /kuberider/domain/contexts_interactor.py: -------------------------------------------------------------------------------- 1 | from kuberider.domain.interactor import Interactor 2 | from kuberider.settings.app_settings import app 3 | 4 | 5 | class ContextsLoaderInteractor(Interactor): 6 | def __init__(self): 7 | super().__init__( 8 | on_success=self.on_result 9 | ) 10 | 11 | def load_contexts(self): 12 | self.kcb.command("config get-contexts --output='name'").start() 13 | 14 | def on_result(self, result): 15 | output = result['output'] 16 | contexts = output.splitlines() 17 | app.data.save_contexts(contexts) 18 | app.data.signals.contexts_loaded.emit() 19 | 20 | 21 | class CurrentContextInteractor(Interactor): 22 | def __init__(self): 23 | super().__init__( 24 | on_success=self.on_result, on_failure=self.on_result 25 | ) 26 | 27 | def current_context(self): 28 | self.kcb.command("config current-context").start() 29 | 30 | def on_result(self, result): 31 | output = result['output'].strip() 32 | app.data.update_current_context(output) 33 | app.data.signals.context_changed.emit(output) 34 | 35 | 36 | class ChangeContextInteractor(Interactor): 37 | def update_context(self, new_context): 38 | app.data.update_current_context(new_context) 39 | app.data.signals.context_changed.emit(new_context) 40 | -------------------------------------------------------------------------------- /kuberider/domain/interactor.py: -------------------------------------------------------------------------------- 1 | from kuberider.core.kube_command_builder import Kcb 2 | from kuberider.core.worker_pool import CommandThread 3 | from kuberider.settings.app_settings import app 4 | 5 | 6 | class Interactor: 7 | def __init__(self, on_success=None, on_failure=None): 8 | self.ct = CommandThread() 9 | 10 | self.ct.signals.success.connect(on_success or self.default_on_success) 11 | self.ct.signals.failure.connect(on_failure or self.default_on_failure) 12 | 13 | self.kcb = Kcb.init(self.ct) 14 | 15 | def default_on_success(self, result): 16 | raise SyntaxError(f"Should at least implement on success handler: {result}") 17 | 18 | def default_on_failure(self, result): 19 | app.data.save_command_error(result['output']) 20 | -------------------------------------------------------------------------------- /kuberider/domain/kube_resource_interactor.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | from tempfile import mktemp 4 | 5 | from kuberider.domain.interactor import Interactor 6 | from kuberider.settings.app_settings import app 7 | 8 | 9 | class KubeResourceInteractor(Interactor): 10 | def __init__(self): 11 | super().__init__(on_success=self.on_result, on_failure=self.on_result) 12 | 13 | def write_temp_file(self, file_contents): 14 | tmp_file_name = mktemp(prefix="kuberider", suffix=".yaml") 15 | output = Path(tmp_file_name).write_text(file_contents) 16 | logging.info(f"Written {output} chars to {tmp_file_name}") 17 | return tmp_file_name 18 | 19 | def run(self, resource_definition): 20 | app.data.signals.command_started.emit("Applying Resource", True) 21 | temp_resource_file = self.write_temp_file(resource_definition) 22 | self.kcb.ctx().ns().command(f"apply -f {temp_resource_file}").start() 23 | 24 | def on_result(self, result): 25 | output = result['output'] 26 | app.data.save_kube_resource(output) 27 | -------------------------------------------------------------------------------- /kuberider/domain/namespaces_interactor.py: -------------------------------------------------------------------------------- 1 | from kuberider.domain.interactor import Interactor 2 | from kuberider.entities.model import KubeNamespaces 3 | from kuberider.settings.app_settings import app 4 | 5 | 6 | class NamespacesLoaderInteractor(Interactor): 7 | def __init__(self): 8 | super().__init__( 9 | on_success=self.on_result, on_failure=self.on_failure 10 | ) 11 | 12 | def load_namespaces(self): 13 | self.kcb.ctx().command(f"get namespaces -o json").start() 14 | 15 | def on_result(self, result): 16 | output = result['output'] 17 | kube_namespaces = KubeNamespaces.from_json_str(output) 18 | namespaces = [i.metadata.get('name') for i in kube_namespaces.items] 19 | app.data.save_namespaces(namespaces) 20 | app.data.signals.namespaces_loaded.emit() 21 | 22 | def on_failure(self, result): 23 | app.data.save_namespaces([]) 24 | app.data.signals.namespaces_loaded.emit() 25 | 26 | 27 | class ChangeNamespaceInteractor(Interactor): 28 | def update_namespace(self, namespace): 29 | app.data.update_current_namespace(namespace) 30 | app.data.signals.namespace_changed.emit() 31 | -------------------------------------------------------------------------------- /kuberider/domain/pods_interactor.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from kuberider.core.kube_command_builder import Kcb 4 | from kuberider.core.worker_pool import TailCommandThread 5 | from kuberider.domain.interactor import Interactor 6 | from kuberider.entities.model import KubePods, KubePodEvents 7 | from kuberider.settings.app_settings import app 8 | 9 | 10 | class GetPodsInteractor(Interactor): 11 | def __init__(self): 12 | super().__init__(on_success=self.on_result, on_failure=self.on_result) 13 | 14 | def run(self): 15 | self.kcb.ctx().ns().command("get pods -o json").start() 16 | 17 | def on_result(self, result): 18 | output = result['output'] 19 | kube_pods: KubePods = KubePods.from_json_str(output) 20 | app.data.save_pods(kube_pods.items) 21 | 22 | 23 | class FilterPodsInteractor: 24 | def apply_filter(self, filter): 25 | app.data.save_filter(filter) 26 | app.data.signals.filter_enabled.emit() 27 | 28 | def clear_filter(self): 29 | app.data.save_filter(None) 30 | app.data.signals.filter_cleared.emit() 31 | 32 | 33 | class PodLogsInteractor(Interactor): 34 | 35 | def __init__(self): 36 | super().__init__(on_success=self.on_full_logs, on_failure=self.on_full_logs) 37 | 38 | def on_full_logs(self, result): 39 | output = result['output'] 40 | app.data.save_partial_output(output) 41 | 42 | def fetch_all(self, pod_name, container_name): 43 | logging.info(f"Fetching all logs for {pod_name} -> {container_name}") 44 | command = f'logs {pod_name} -c {container_name}' 45 | self.kcb.ctx().ns().command(command).start() 46 | 47 | 48 | # @todo: Remove if not used in future 49 | class PodFollowLogsInteractor: 50 | 51 | def __init__(self): 52 | self.tct = TailCommandThread() 53 | self.tct.signals.partial_output.connect(self.on_partial_output) 54 | self.kcb = Kcb.init(self.tct) 55 | 56 | def on_partial_output(self, output): 57 | logging.debug(f"Read: {output}") 58 | app.data.save_partial_output(output) 59 | 60 | def tail(self, pod_name, container_name): 61 | logging.info(f"Following logs for {pod_name} -> {container_name}") 62 | command = f'logs {pod_name} -c {container_name} -f' 63 | self.kcb.ctx().ns().command(command).start() 64 | 65 | def stop_tail(self): 66 | self.tct.stop_process() 67 | 68 | 69 | class GetPodEventsInteractor(Interactor): 70 | def __init__(self): 71 | super().__init__(on_success=self.on_result, on_failure=self.on_result) 72 | 73 | def run(self, pod_name): 74 | event_query = f"--field-selector='involvedObject.name={pod_name}'" 75 | self.kcb.ctx().ns().command(f"get event {event_query} -o json").start() 76 | 77 | def on_result(self, result): 78 | output = result['output'] 79 | kube_pod_events = KubePodEvents.from_json_str(output) 80 | app.data.save_pod_events(kube_pod_events.items) 81 | 82 | 83 | class DeletePodInteractor(Interactor): 84 | 85 | def __init__(self): 86 | super().__init__(on_success=self.on_result, on_failure=self.on_result) 87 | 88 | def delete(self, pod_name): 89 | delete_command = f"delete pod {pod_name}" 90 | self.kcb.ctx().ns().command(delete_command).start() 91 | 92 | def on_result(self, result): 93 | app.data.pod_deleted() 94 | -------------------------------------------------------------------------------- /kuberider/entities/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/entities/__init__.py -------------------------------------------------------------------------------- /kuberider/entities/data_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import List 3 | 4 | import dataset 5 | 6 | from kuberider.entities.model import AppState 7 | from kuberider.events.signals import AppSignals 8 | 9 | 10 | class DataManager: 11 | signals = AppSignals() 12 | _app_state = AppState() 13 | 14 | def __init__(self, app_dir): 15 | db_path = f"sqlite:///{app_dir}/kuberider.db" 16 | self.db = dataset.connect(db_path) 17 | 18 | @property 19 | def app_state(self): 20 | return self._app_state 21 | 22 | def save_command(self, new_command): 23 | logging.info(f"Command added: {new_command}") 24 | self.app_state.commands.append(new_command) 25 | self.signals.command_added.emit(new_command) 26 | 27 | def save_command_error(self, error): 28 | self.signals.command_failed.emit(error) 29 | 30 | def update_command_status(self, command, started=False): 31 | if started: 32 | self.signals.command_started.emit(command, False) 33 | else: 34 | self.signals.command_finished.emit() 35 | 36 | def update_app_state_in_db(self, app_state_entity: AppState): 37 | table = self.db[app_state_entity.record_type] 38 | table.upsert( 39 | dict( 40 | name=app_state_entity.record_type, 41 | object=app_state_entity.to_json_str() 42 | ), 43 | ['name'] 44 | ) 45 | 46 | def save_partial_output(self, partial_output): 47 | self.signals.output_generated.emit(partial_output) 48 | 49 | def save_filter(self, filter): 50 | logging.info(f"Saving filter {filter} in AppState") 51 | self.app_state.pods_filter = filter 52 | self.update_app_state_in_db(self.app_state) 53 | 54 | def save_contexts(self, contexts): 55 | logging.info(f"Contexts added: {len(contexts)}") 56 | self.app_state.contexts = contexts 57 | self.update_app_state_in_db(self.app_state) 58 | 59 | def load_contexts(self) -> List[str]: 60 | return self.app_state.contexts 61 | 62 | def update_current_context(self, current_context): 63 | logging.info(f"Context changed: {current_context}") 64 | self.app_state.current_context = current_context 65 | self.update_app_state_in_db(self.app_state) 66 | 67 | def get_current_context(self) -> str: 68 | return self.app_state.current_context 69 | 70 | def save_namespaces(self, namespaces): 71 | logging.info(f"Namespaces added: {len(namespaces)}") 72 | self.app_state.namespaces = namespaces 73 | self.update_app_state_in_db(self.app_state) 74 | 75 | def update_current_namespace(self, current_namespace): 76 | logging.info(f"Namespace changed: {current_namespace}") 77 | self.app_state.current_namespace = current_namespace 78 | self.update_app_state_in_db(self.app_state) 79 | 80 | def load_namespaces(self): 81 | return self.app_state.namespaces 82 | 83 | def get_current_namespace(self): 84 | return self.app_state.current_namespace 85 | 86 | def save_pods(self, pods): 87 | self.signals.pods_loaded.emit(pods) 88 | 89 | def save_pod_events(self, pod_events: List): 90 | self.signals.pod_events_loaded.emit(pod_events) 91 | 92 | def save_kube_resource(self, kube_resource_output): 93 | self.signals.kube_resource_applied.emit(kube_resource_output) 94 | 95 | def pod_deleted(self): 96 | self.signals.pod_deleted.emit() 97 | -------------------------------------------------------------------------------- /kuberider/entities/model.py: -------------------------------------------------------------------------------- 1 | import json 2 | import warnings 3 | from typing import List, Dict, Any, Optional 4 | 5 | import arrow 6 | import attr 7 | import cattr 8 | from toolz import itertoolz 9 | 10 | APP_STATE_RECORD_TYPE = "app_state" 11 | 12 | 13 | @attr.s(auto_attribs=True) 14 | class AppState: 15 | record_type: str = APP_STATE_RECORD_TYPE 16 | contexts: List[str] = [] 17 | commands: List[str] = [] 18 | current_context: str = None 19 | namespaces: List[str] = [] 20 | current_namespace: str = None 21 | pods_filter: str = None 22 | 23 | @classmethod 24 | def from_json(cls, json_obj): 25 | if not json_obj: 26 | return cls() 27 | return cattr.structure(json_obj, cls) 28 | 29 | def to_json(self): 30 | return cattr.unstructure(self) 31 | 32 | def to_json_str(self): 33 | return json.dumps(self.to_json()) 34 | 35 | 36 | @attr.s(auto_attribs=True) 37 | class KubeNamespace(object): 38 | apiVersion: str 39 | kind: str 40 | metadata: Dict = {} 41 | spec: Dict = {} 42 | status: Dict = {} 43 | 44 | 45 | @attr.s(auto_attribs=True) 46 | class KubeNamespaces(object): 47 | apiVersion: str 48 | kind: str 49 | metadata: Dict 50 | items: List[KubeNamespace] 51 | 52 | @classmethod 53 | def from_json_str(cls, json_str): 54 | return cattr.structure(json.loads(json_str), cls) 55 | 56 | 57 | @attr.s(auto_attribs=True) 58 | class KubePodContainer(object): 59 | name: str 60 | image: str 61 | volumeMounts: Dict 62 | ready: bool 63 | container_id: str 64 | restart_count: Optional[int] 65 | state: str 66 | state_details: str 67 | 68 | @staticmethod 69 | def extract_container_state(state_json): 70 | if not state_json: 71 | return "N/A" 72 | 73 | current_state = itertoolz.first(state_json) 74 | current_state_details = state_json.get(current_state) 75 | state_at = itertoolz.first(current_state_details) 76 | state_at_details = current_state_details.get(state_at) 77 | if state_at == "startedAt": 78 | state_at_details = arrow.get(state_at_details).humanize() 79 | 80 | return current_state, f"{current_state.strip()} ({state_at.strip()}: {state_at_details.strip()})" 81 | 82 | @classmethod 83 | def from_spec(cls, json_spec, json_container_status): 84 | container_name = json_spec.get('name', None) 85 | container_status = next(cs 86 | for cs in json_container_status.get('containerStatuses') 87 | if cs.get('name') == container_name 88 | ) 89 | state, details = cls.extract_container_state(container_status.get('state')) 90 | 91 | return cls( 92 | name=json_spec.get('name', None), 93 | image=json_spec.get('image', None), 94 | ready=container_status.get('ready', False), 95 | container_id=container_status.get('containerID', None), 96 | restart_count=container_status.get('restartCount', None), 97 | volumeMounts={ 98 | vol.get('name'): vol.get('mountPath') 99 | for vol in json_spec.get('volumeMounts', []) 100 | }, 101 | state=state, 102 | state_details=details 103 | ) 104 | 105 | 106 | @attr.s(auto_attribs=True) 107 | class KubePodItem(object): 108 | apiVersion: str 109 | kind: Any 110 | metadata: Dict 111 | spec: Dict 112 | status: Dict 113 | 114 | @property 115 | def name(self): 116 | return self.metadata.get('name') 117 | 118 | @property 119 | def pod_state(self): 120 | ready, total = self.count 121 | return "running" if ready == total else "waiting" 122 | 123 | @property 124 | def count(self): 125 | total = len([c for c in self.status.get('containerStatuses', [])]) 126 | ready = len([ 127 | c 128 | for c in self.status.get('containerStatuses', []) 129 | if c.get('ready') 130 | ]) 131 | return ready, total 132 | 133 | @property 134 | def pod_status(self): 135 | return self.status.get('phase') 136 | 137 | @property 138 | def containers(self): 139 | return [ 140 | KubePodContainer.from_spec(container_spec, self.status) 141 | for container_spec in self.spec.get('containers', []) 142 | ] 143 | 144 | @property 145 | def volumes(self): 146 | return [] 147 | 148 | 149 | @attr.s(auto_attribs=True) 150 | class KubePodEvents(object): 151 | apiVersion: str 152 | kind: Any 153 | metadata: Any 154 | items: List[Dict] 155 | 156 | @classmethod 157 | def from_json_str(cls, json_str): 158 | return cattr.structure(json.loads(json_str), cls) 159 | 160 | 161 | @attr.s(auto_attribs=True) 162 | class KubePods(object): 163 | apiVersion: str 164 | items: List[KubePodItem] 165 | kind: Any 166 | metadata: Any 167 | 168 | @classmethod 169 | def from_json_str(cls, json_str): 170 | return cattr.structure(json.loads(json_str), cls) 171 | 172 | # if __name__ == '__main__': 173 | # mock_file = Path("..").joinpath("mock_responses").joinpath("k_get_qa_multiple_pods.json").read_text( 174 | # encoding='utf-8') 175 | # pods = KubePods.from_json_str(mock_file) 176 | # for p in pods.items: 177 | # for c in p.containers: 178 | # print(c.name, c.state) 179 | -------------------------------------------------------------------------------- /kuberider/events/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/events/__init__.py -------------------------------------------------------------------------------- /kuberider/events/signals.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QObject, pyqtSignal 2 | 3 | from kuberider.entities.model import KubePodItem 4 | 5 | 6 | class AppSignals(QObject): 7 | contexts_loaded = pyqtSignal() 8 | command_added = pyqtSignal(str) 9 | context_changed = pyqtSignal(str) 10 | command_started = pyqtSignal(str, bool) 11 | command_finished = pyqtSignal() 12 | command_failed = pyqtSignal(str) 13 | namespaces_loaded = pyqtSignal() 14 | namespace_changed = pyqtSignal() 15 | pods_loaded = pyqtSignal(list) 16 | pod_events_loaded = pyqtSignal(list) 17 | pod_selected = pyqtSignal(KubePodItem) 18 | filter_enabled = pyqtSignal() 19 | filter_cleared = pyqtSignal() 20 | output_generated = pyqtSignal(str) 21 | kube_resource_applied = pyqtSignal(str) 22 | pod_deleted = pyqtSignal() 23 | 24 | 25 | class AppCommands(QObject): 26 | reload_pods = pyqtSignal() 27 | open_pod_logs = pyqtSignal(str, str) 28 | on_exec_shell = pyqtSignal(str, str) 29 | on_port_forward = pyqtSignal(str, str) 30 | on_follow_logs = pyqtSignal(str, str) 31 | -------------------------------------------------------------------------------- /kuberider/generated/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/generated/__init__.py -------------------------------------------------------------------------------- /kuberider/generated/configuration_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/ui/configuration_dialog.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_Configuration(object): 13 | def setupUi(self, Configuration): 14 | Configuration.setObjectName("Configuration") 15 | Configuration.setWindowModality(QtCore.Qt.WindowModal) 16 | Configuration.resize(486, 255) 17 | Configuration.setModal(True) 18 | self.tabWidget = QtWidgets.QTabWidget(Configuration) 19 | self.tabWidget.setGeometry(QtCore.QRect(12, 6, 461, 201)) 20 | self.tabWidget.setObjectName("tabWidget") 21 | self.tab = QtWidgets.QWidget() 22 | self.tab.setObjectName("tab") 23 | self.txt_kubectl = QtWidgets.QLineEdit(self.tab) 24 | self.txt_kubectl.setGeometry(QtCore.QRect(10, 10, 441, 21)) 25 | self.txt_kubectl.setObjectName("txt_kubectl") 26 | self.tabWidget.addTab(self.tab, "") 27 | self.update = QtWidgets.QWidget() 28 | self.update.setObjectName("update") 29 | self.chk_updates_startup = QtWidgets.QCheckBox(self.update) 30 | self.chk_updates_startup.setEnabled(False) 31 | self.chk_updates_startup.setGeometry(QtCore.QRect(20, 20, 221, 20)) 32 | self.chk_updates_startup.setCheckable(True) 33 | self.chk_updates_startup.setChecked(False) 34 | self.chk_updates_startup.setObjectName("chk_updates_startup") 35 | self.tabWidget.addTab(self.update, "") 36 | self.btn_save_configuration = QtWidgets.QPushButton(Configuration) 37 | self.btn_save_configuration.setGeometry(QtCore.QRect(360, 210, 113, 32)) 38 | self.btn_save_configuration.setObjectName("btn_save_configuration") 39 | self.btn_cancel_configuration = QtWidgets.QPushButton(Configuration) 40 | self.btn_cancel_configuration.setGeometry(QtCore.QRect(250, 210, 113, 32)) 41 | self.btn_cancel_configuration.setObjectName("btn_cancel_configuration") 42 | 43 | self.retranslateUi(Configuration) 44 | self.tabWidget.setCurrentIndex(0) 45 | QtCore.QMetaObject.connectSlotsByName(Configuration) 46 | 47 | def retranslateUi(self, Configuration): 48 | _translate = QtCore.QCoreApplication.translate 49 | Configuration.setWindowTitle(_translate("Configuration", "Settings")) 50 | self.txt_kubectl.setText(_translate("Configuration", "kubectl")) 51 | self.txt_kubectl.setPlaceholderText(_translate("Configuration", "path to kubectl ...")) 52 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Configuration", "Settings")) 53 | self.chk_updates_startup.setText(_translate("Configuration", "Check for updates on start up")) 54 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.update), _translate("Configuration", "Updates")) 55 | self.btn_save_configuration.setText(_translate("Configuration", "OK")) 56 | self.btn_cancel_configuration.setText(_translate("Configuration", "Cancel")) 57 | 58 | 59 | -------------------------------------------------------------------------------- /kuberider/generated/kube_resource_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/ui/kube_resource_dialog.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_KubeResourceDialog(object): 13 | def setupUi(self, KubeResourceDialog): 14 | KubeResourceDialog.setObjectName("KubeResourceDialog") 15 | KubeResourceDialog.setWindowModality(QtCore.Qt.WindowModal) 16 | KubeResourceDialog.setEnabled(True) 17 | KubeResourceDialog.resize(746, 640) 18 | KubeResourceDialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) 19 | KubeResourceDialog.setModal(True) 20 | self.verticalLayout_2 = QtWidgets.QVBoxLayout(KubeResourceDialog) 21 | self.verticalLayout_2.setContentsMargins(12, 12, 12, 12) 22 | self.verticalLayout_2.setObjectName("verticalLayout_2") 23 | self.txt_resource_definition = QtWidgets.QPlainTextEdit(KubeResourceDialog) 24 | self.txt_resource_definition.setObjectName("txt_resource_definition") 25 | self.verticalLayout_2.addWidget(self.txt_resource_definition) 26 | self.horizontalLayout = QtWidgets.QHBoxLayout() 27 | self.horizontalLayout.setObjectName("horizontalLayout") 28 | self.lbl_command_output = QtWidgets.QLabel(KubeResourceDialog) 29 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 30 | sizePolicy.setHorizontalStretch(1) 31 | sizePolicy.setVerticalStretch(0) 32 | sizePolicy.setHeightForWidth(self.lbl_command_output.sizePolicy().hasHeightForWidth()) 33 | self.lbl_command_output.setSizePolicy(sizePolicy) 34 | self.lbl_command_output.setObjectName("lbl_command_output") 35 | self.horizontalLayout.addWidget(self.lbl_command_output) 36 | self.verticalLayout = QtWidgets.QVBoxLayout() 37 | self.verticalLayout.setObjectName("verticalLayout") 38 | self.btn_apply_resource = QtWidgets.QPushButton(KubeResourceDialog) 39 | self.btn_apply_resource.setObjectName("btn_apply_resource") 40 | self.verticalLayout.addWidget(self.btn_apply_resource) 41 | self.btn_close_dialog = QtWidgets.QPushButton(KubeResourceDialog) 42 | self.btn_close_dialog.setObjectName("btn_close_dialog") 43 | self.verticalLayout.addWidget(self.btn_close_dialog) 44 | self.horizontalLayout.addLayout(self.verticalLayout) 45 | self.verticalLayout_2.addLayout(self.horizontalLayout) 46 | 47 | self.retranslateUi(KubeResourceDialog) 48 | QtCore.QMetaObject.connectSlotsByName(KubeResourceDialog) 49 | 50 | def retranslateUi(self, KubeResourceDialog): 51 | _translate = QtCore.QCoreApplication.translate 52 | KubeResourceDialog.setWindowTitle(_translate("KubeResourceDialog", "Progress ...")) 53 | self.txt_resource_definition.setPlainText(_translate("KubeResourceDialog", "<< Kube definition >>")) 54 | self.lbl_command_output.setText(_translate("KubeResourceDialog", "<< Status >>")) 55 | self.btn_apply_resource.setText(_translate("KubeResourceDialog", "Apply")) 56 | self.btn_close_dialog.setText(_translate("KubeResourceDialog", "Close")) 57 | 58 | 59 | -------------------------------------------------------------------------------- /kuberider/generated/kube_rider_main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/ui/kube_rider_main.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_MainWindow(object): 13 | def setupUi(self, MainWindow): 14 | MainWindow.setObjectName("MainWindow") 15 | MainWindow.resize(871, 600) 16 | MainWindow.setUnifiedTitleAndToolBarOnMac(False) 17 | self.centralwidget = QtWidgets.QWidget(MainWindow) 18 | self.centralwidget.setObjectName("centralwidget") 19 | self.horizontalLayout_5 = QtWidgets.QHBoxLayout(self.centralwidget) 20 | self.horizontalLayout_5.setObjectName("horizontalLayout_5") 21 | self.splitter = QtWidgets.QSplitter(self.centralwidget) 22 | self.splitter.setOrientation(QtCore.Qt.Horizontal) 23 | self.splitter.setObjectName("splitter") 24 | self.frame = QtWidgets.QFrame(self.splitter) 25 | self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel) 26 | self.frame.setFrameShadow(QtWidgets.QFrame.Raised) 27 | self.frame.setObjectName("frame") 28 | self.verticalLayout = QtWidgets.QVBoxLayout(self.frame) 29 | self.verticalLayout.setSpacing(3) 30 | self.verticalLayout.setObjectName("verticalLayout") 31 | self.horizontalLayout_4 = QtWidgets.QHBoxLayout() 32 | self.horizontalLayout_4.setObjectName("horizontalLayout_4") 33 | self.chk_watch = QtWidgets.QCheckBox(self.frame) 34 | self.chk_watch.setObjectName("chk_watch") 35 | self.horizontalLayout_4.addWidget(self.chk_watch) 36 | self.txt_watch_interval = QtWidgets.QSpinBox(self.frame) 37 | self.txt_watch_interval.setProperty("value", 5) 38 | self.txt_watch_interval.setObjectName("txt_watch_interval") 39 | self.horizontalLayout_4.addWidget(self.txt_watch_interval) 40 | self.label = QtWidgets.QLabel(self.frame) 41 | self.label.setObjectName("label") 42 | self.horizontalLayout_4.addWidget(self.label) 43 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 44 | self.horizontalLayout_4.addItem(spacerItem) 45 | self.btn_reload_pods = QtWidgets.QPushButton(self.frame) 46 | self.btn_reload_pods.setObjectName("btn_reload_pods") 47 | self.horizontalLayout_4.addWidget(self.btn_reload_pods) 48 | self.verticalLayout.addLayout(self.horizontalLayout_4) 49 | self.horizontalLayout_7 = QtWidgets.QHBoxLayout() 50 | self.horizontalLayout_7.setObjectName("horizontalLayout_7") 51 | self.txt_filter = QtWidgets.QLineEdit(self.frame) 52 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed) 53 | sizePolicy.setHorizontalStretch(0) 54 | sizePolicy.setVerticalStretch(0) 55 | sizePolicy.setHeightForWidth(self.txt_filter.sizePolicy().hasHeightForWidth()) 56 | self.txt_filter.setSizePolicy(sizePolicy) 57 | self.txt_filter.setObjectName("txt_filter") 58 | self.horizontalLayout_7.addWidget(self.txt_filter) 59 | self.btn_enable_filter = QtWidgets.QPushButton(self.frame) 60 | self.btn_enable_filter.setObjectName("btn_enable_filter") 61 | self.horizontalLayout_7.addWidget(self.btn_enable_filter) 62 | self.btn_clear_filter = QtWidgets.QPushButton(self.frame) 63 | self.btn_clear_filter.setObjectName("btn_clear_filter") 64 | self.horizontalLayout_7.addWidget(self.btn_clear_filter) 65 | self.verticalLayout.addLayout(self.horizontalLayout_7) 66 | self.lst_pods = QtWidgets.QListWidget(self.frame) 67 | self.lst_pods.setObjectName("lst_pods") 68 | self.verticalLayout.addWidget(self.lst_pods) 69 | self.frame_2 = QtWidgets.QFrame(self.splitter) 70 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 71 | sizePolicy.setHorizontalStretch(1) 72 | sizePolicy.setVerticalStretch(0) 73 | sizePolicy.setHeightForWidth(self.frame_2.sizePolicy().hasHeightForWidth()) 74 | self.frame_2.setSizePolicy(sizePolicy) 75 | self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel) 76 | self.frame_2.setFrameShadow(QtWidgets.QFrame.Raised) 77 | self.frame_2.setObjectName("frame_2") 78 | self.horizontalLayout_3 = QtWidgets.QHBoxLayout(self.frame_2) 79 | self.horizontalLayout_3.setContentsMargins(5, 5, 5, 5) 80 | self.horizontalLayout_3.setObjectName("horizontalLayout_3") 81 | self.splitter_2 = QtWidgets.QSplitter(self.frame_2) 82 | self.splitter_2.setOrientation(QtCore.Qt.Vertical) 83 | self.splitter_2.setObjectName("splitter_2") 84 | self.frame_3 = QtWidgets.QFrame(self.splitter_2) 85 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 86 | sizePolicy.setHorizontalStretch(0) 87 | sizePolicy.setVerticalStretch(1) 88 | sizePolicy.setHeightForWidth(self.frame_3.sizePolicy().hasHeightForWidth()) 89 | self.frame_3.setSizePolicy(sizePolicy) 90 | self.frame_3.setFrameShape(QtWidgets.QFrame.StyledPanel) 91 | self.frame_3.setFrameShadow(QtWidgets.QFrame.Raised) 92 | self.frame_3.setObjectName("frame_3") 93 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout(self.frame_3) 94 | self.horizontalLayout_2.setContentsMargins(5, 5, 5, 5) 95 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 96 | self.tabWidget = QtWidgets.QTabWidget(self.frame_3) 97 | self.tabWidget.setObjectName("tabWidget") 98 | self.tab_3 = QtWidgets.QWidget() 99 | self.tab_3.setObjectName("tab_3") 100 | self.horizontalLayout_6 = QtWidgets.QHBoxLayout(self.tab_3) 101 | self.horizontalLayout_6.setObjectName("horizontalLayout_6") 102 | self.lst_pod_containers = QtWidgets.QListWidget(self.tab_3) 103 | self.lst_pod_containers.setObjectName("lst_pod_containers") 104 | self.horizontalLayout_6.addWidget(self.lst_pod_containers) 105 | self.tabWidget.addTab(self.tab_3, "") 106 | self.tab = QtWidgets.QWidget() 107 | self.tab.setObjectName("tab") 108 | self.horizontalLayout_8 = QtWidgets.QHBoxLayout(self.tab) 109 | self.horizontalLayout_8.setObjectName("horizontalLayout_8") 110 | self.txt_pod_events = QtWidgets.QPlainTextEdit(self.tab) 111 | self.txt_pod_events.setObjectName("txt_pod_events") 112 | self.horizontalLayout_8.addWidget(self.txt_pod_events) 113 | self.tabWidget.addTab(self.tab, "") 114 | self.horizontalLayout_2.addWidget(self.tabWidget) 115 | self.frame_4 = QtWidgets.QFrame(self.splitter_2) 116 | self.frame_4.setFrameShape(QtWidgets.QFrame.StyledPanel) 117 | self.frame_4.setFrameShadow(QtWidgets.QFrame.Raised) 118 | self.frame_4.setObjectName("frame_4") 119 | self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame_4) 120 | self.horizontalLayout.setContentsMargins(5, 5, 5, 5) 121 | self.horizontalLayout.setObjectName("horizontalLayout") 122 | self.console_text_edit = QtWidgets.QPlainTextEdit(self.frame_4) 123 | self.console_text_edit.setReadOnly(True) 124 | self.console_text_edit.setObjectName("console_text_edit") 125 | self.horizontalLayout.addWidget(self.console_text_edit) 126 | self.horizontalLayout_3.addWidget(self.splitter_2) 127 | self.horizontalLayout_5.addWidget(self.splitter) 128 | MainWindow.setCentralWidget(self.centralwidget) 129 | 130 | self.retranslateUi(MainWindow) 131 | self.tabWidget.setCurrentIndex(0) 132 | QtCore.QMetaObject.connectSlotsByName(MainWindow) 133 | 134 | def retranslateUi(self, MainWindow): 135 | _translate = QtCore.QCoreApplication.translate 136 | MainWindow.setWindowTitle(_translate("MainWindow", ":::: KubeRider ::::")) 137 | self.chk_watch.setText(_translate("MainWindow", "Refresh every ")) 138 | self.label.setText(_translate("MainWindow", "seconds")) 139 | self.btn_reload_pods.setText(_translate("MainWindow", "Reload")) 140 | self.txt_filter.setPlaceholderText(_translate("MainWindow", "Filter pods ...")) 141 | self.btn_enable_filter.setText(_translate("MainWindow", "Filter")) 142 | self.btn_clear_filter.setText(_translate("MainWindow", "Clear")) 143 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "Containers")) 144 | self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Events")) 145 | 146 | 147 | import resources_rc 148 | -------------------------------------------------------------------------------- /kuberider/generated/pod_container_widget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/ui/pod_container_widget.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_PodContainerWidget(object): 13 | def setupUi(self, PodContainerWidget): 14 | PodContainerWidget.setObjectName("PodContainerWidget") 15 | PodContainerWidget.resize(433, 96) 16 | PodContainerWidget.setStyleSheet("") 17 | self.verticalLayout = QtWidgets.QVBoxLayout(PodContainerWidget) 18 | self.verticalLayout.setObjectName("verticalLayout") 19 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 20 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 21 | self.lbl_container_name = QtWidgets.QLabel(PodContainerWidget) 22 | font = QtGui.QFont() 23 | font.setPointSize(12) 24 | font.setBold(True) 25 | font.setWeight(75) 26 | self.lbl_container_name.setFont(font) 27 | self.lbl_container_name.setObjectName("lbl_container_name") 28 | self.horizontalLayout_2.addWidget(self.lbl_container_name) 29 | self.lbl_container_status = QtWidgets.QLabel(PodContainerWidget) 30 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Preferred) 31 | sizePolicy.setHorizontalStretch(0) 32 | sizePolicy.setVerticalStretch(0) 33 | sizePolicy.setHeightForWidth(self.lbl_container_status.sizePolicy().hasHeightForWidth()) 34 | self.lbl_container_status.setSizePolicy(sizePolicy) 35 | font = QtGui.QFont() 36 | font.setPointSize(10) 37 | self.lbl_container_status.setFont(font) 38 | self.lbl_container_status.setTextFormat(QtCore.Qt.PlainText) 39 | self.lbl_container_status.setAlignment(QtCore.Qt.AlignCenter) 40 | self.lbl_container_status.setObjectName("lbl_container_status") 41 | self.horizontalLayout_2.addWidget(self.lbl_container_status) 42 | self.btn_port_forward = QtWidgets.QToolButton(PodContainerWidget) 43 | self.btn_port_forward.setObjectName("btn_port_forward") 44 | self.horizontalLayout_2.addWidget(self.btn_port_forward) 45 | self.btn_exec_shell = QtWidgets.QToolButton(PodContainerWidget) 46 | self.btn_exec_shell.setObjectName("btn_exec_shell") 47 | self.horizontalLayout_2.addWidget(self.btn_exec_shell) 48 | self.btn_open_logs = QtWidgets.QToolButton(PodContainerWidget) 49 | self.btn_open_logs.setToolButtonStyle(QtCore.Qt.ToolButtonTextOnly) 50 | self.btn_open_logs.setAutoRaise(True) 51 | self.btn_open_logs.setObjectName("btn_open_logs") 52 | self.horizontalLayout_2.addWidget(self.btn_open_logs) 53 | self.btn_follow_logs = QtWidgets.QToolButton(PodContainerWidget) 54 | self.btn_follow_logs.setObjectName("btn_follow_logs") 55 | self.horizontalLayout_2.addWidget(self.btn_follow_logs) 56 | self.verticalLayout.addLayout(self.horizontalLayout_2) 57 | self.lbl_container_image = QtWidgets.QLabel(PodContainerWidget) 58 | font = QtGui.QFont() 59 | font.setPointSize(10) 60 | self.lbl_container_image.setFont(font) 61 | self.lbl_container_image.setObjectName("lbl_container_image") 62 | self.verticalLayout.addWidget(self.lbl_container_image) 63 | self.horizontalLayout = QtWidgets.QHBoxLayout() 64 | self.horizontalLayout.setObjectName("horizontalLayout") 65 | self.label_2 = QtWidgets.QLabel(PodContainerWidget) 66 | font = QtGui.QFont() 67 | font.setPointSize(10) 68 | self.label_2.setFont(font) 69 | self.label_2.setObjectName("label_2") 70 | self.horizontalLayout.addWidget(self.label_2) 71 | self.lbl_volumes = QtWidgets.QLabel(PodContainerWidget) 72 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 73 | sizePolicy.setHorizontalStretch(1) 74 | sizePolicy.setVerticalStretch(0) 75 | sizePolicy.setHeightForWidth(self.lbl_volumes.sizePolicy().hasHeightForWidth()) 76 | self.lbl_volumes.setSizePolicy(sizePolicy) 77 | font = QtGui.QFont() 78 | font.setPointSize(10) 79 | self.lbl_volumes.setFont(font) 80 | self.lbl_volumes.setObjectName("lbl_volumes") 81 | self.horizontalLayout.addWidget(self.lbl_volumes) 82 | self.verticalLayout.addLayout(self.horizontalLayout) 83 | 84 | self.retranslateUi(PodContainerWidget) 85 | QtCore.QMetaObject.connectSlotsByName(PodContainerWidget) 86 | 87 | def retranslateUi(self, PodContainerWidget): 88 | _translate = QtCore.QCoreApplication.translate 89 | PodContainerWidget.setWindowTitle(_translate("PodContainerWidget", "Form")) 90 | self.lbl_container_name.setText(_translate("PodContainerWidget", "TextLabel")) 91 | self.lbl_container_status.setText(_translate("PodContainerWidget", "Status")) 92 | self.btn_port_forward.setText(_translate("PodContainerWidget", "Port Fwd")) 93 | self.btn_exec_shell.setText(_translate("PodContainerWidget", "Exec")) 94 | self.btn_open_logs.setText(_translate("PodContainerWidget", "Logs")) 95 | self.btn_follow_logs.setText(_translate("PodContainerWidget", "Follow Logs")) 96 | self.lbl_container_image.setText(_translate("PodContainerWidget", "image path")) 97 | self.label_2.setText(_translate("PodContainerWidget", "Volumes:")) 98 | self.lbl_volumes.setText(_translate("PodContainerWidget", "TextLabel")) 99 | 100 | 101 | -------------------------------------------------------------------------------- /kuberider/generated/pod_item_widget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/ui/pod_item_widget.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_PodItemWidget(object): 13 | def setupUi(self, PodItemWidget): 14 | PodItemWidget.setObjectName("PodItemWidget") 15 | PodItemWidget.resize(431, 74) 16 | PodItemWidget.setStyleSheet("") 17 | self.verticalLayout = QtWidgets.QVBoxLayout(PodItemWidget) 18 | self.verticalLayout.setContentsMargins(5, 5, 5, 5) 19 | self.verticalLayout.setObjectName("verticalLayout") 20 | self.horizontalLayout = QtWidgets.QHBoxLayout() 21 | self.horizontalLayout.setObjectName("horizontalLayout") 22 | self.lbl_pod_name = QtWidgets.QLabel(PodItemWidget) 23 | font = QtGui.QFont() 24 | font.setPointSize(12) 25 | font.setBold(True) 26 | font.setWeight(75) 27 | self.lbl_pod_name.setFont(font) 28 | self.lbl_pod_name.setObjectName("lbl_pod_name") 29 | self.horizontalLayout.addWidget(self.lbl_pod_name) 30 | self.verticalLayout.addLayout(self.horizontalLayout) 31 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 32 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 33 | self.lbl_pod_status = QtWidgets.QLabel(PodItemWidget) 34 | font = QtGui.QFont() 35 | font.setPointSize(10) 36 | self.lbl_pod_status.setFont(font) 37 | self.lbl_pod_status.setObjectName("lbl_pod_status") 38 | self.horizontalLayout_2.addWidget(self.lbl_pod_status) 39 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 40 | self.horizontalLayout_2.addItem(spacerItem) 41 | self.lbl_pod_count = QtWidgets.QLabel(PodItemWidget) 42 | font = QtGui.QFont() 43 | font.setPointSize(10) 44 | font.setItalic(False) 45 | self.lbl_pod_count.setFont(font) 46 | self.lbl_pod_count.setObjectName("lbl_pod_count") 47 | self.horizontalLayout_2.addWidget(self.lbl_pod_count) 48 | self.verticalLayout.addLayout(self.horizontalLayout_2) 49 | 50 | self.retranslateUi(PodItemWidget) 51 | QtCore.QMetaObject.connectSlotsByName(PodItemWidget) 52 | 53 | def retranslateUi(self, PodItemWidget): 54 | _translate = QtCore.QCoreApplication.translate 55 | PodItemWidget.setWindowTitle(_translate("PodItemWidget", "Form")) 56 | self.lbl_pod_name.setText(_translate("PodItemWidget", "TextLabel")) 57 | self.lbl_pod_status.setText(_translate("PodItemWidget", "ContainerCreating")) 58 | self.lbl_pod_count.setText(_translate("PodItemWidget", "1/1")) 59 | 60 | 61 | -------------------------------------------------------------------------------- /kuberider/generated/pod_logs_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/ui/pod_logs_dialog.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_PodLogsDialog(object): 13 | def setupUi(self, PodLogsDialog): 14 | PodLogsDialog.setObjectName("PodLogsDialog") 15 | PodLogsDialog.setWindowModality(QtCore.Qt.WindowModal) 16 | PodLogsDialog.setEnabled(True) 17 | PodLogsDialog.resize(622, 345) 18 | PodLogsDialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) 19 | PodLogsDialog.setModal(True) 20 | self.verticalLayout = QtWidgets.QVBoxLayout(PodLogsDialog) 21 | self.verticalLayout.setObjectName("verticalLayout") 22 | self.txt_pod_logs = QtWidgets.QPlainTextEdit(PodLogsDialog) 23 | self.txt_pod_logs.setObjectName("txt_pod_logs") 24 | self.verticalLayout.addWidget(self.txt_pod_logs) 25 | self.horizontalLayout = QtWidgets.QHBoxLayout() 26 | self.horizontalLayout.setObjectName("horizontalLayout") 27 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) 28 | self.horizontalLayout.addItem(spacerItem) 29 | self.btn_close_logs = QtWidgets.QPushButton(PodLogsDialog) 30 | self.btn_close_logs.setObjectName("btn_close_logs") 31 | self.horizontalLayout.addWidget(self.btn_close_logs) 32 | self.verticalLayout.addLayout(self.horizontalLayout) 33 | 34 | self.retranslateUi(PodLogsDialog) 35 | QtCore.QMetaObject.connectSlotsByName(PodLogsDialog) 36 | 37 | def retranslateUi(self, PodLogsDialog): 38 | _translate = QtCore.QCoreApplication.translate 39 | PodLogsDialog.setWindowTitle(_translate("PodLogsDialog", "Logs ...")) 40 | self.btn_close_logs.setText(_translate("PodLogsDialog", "Close")) 41 | 42 | 43 | -------------------------------------------------------------------------------- /kuberider/generated/pod_volume_widget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/ui/pod_volume_widget.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_PodVolumeWidget(object): 13 | def setupUi(self, PodVolumeWidget): 14 | PodVolumeWidget.setObjectName("PodVolumeWidget") 15 | PodVolumeWidget.resize(423, 85) 16 | PodVolumeWidget.setStyleSheet("") 17 | self.verticalLayout = QtWidgets.QVBoxLayout(PodVolumeWidget) 18 | self.verticalLayout.setObjectName("verticalLayout") 19 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout() 20 | self.horizontalLayout_2.setObjectName("horizontalLayout_2") 21 | self.lbl_volume_name = QtWidgets.QLabel(PodVolumeWidget) 22 | font = QtGui.QFont() 23 | font.setPointSize(12) 24 | font.setBold(True) 25 | font.setWeight(75) 26 | self.lbl_volume_name.setFont(font) 27 | self.lbl_volume_name.setObjectName("lbl_volume_name") 28 | self.horizontalLayout_2.addWidget(self.lbl_volume_name) 29 | self.verticalLayout.addLayout(self.horizontalLayout_2) 30 | self.lbl_mount_path = QtWidgets.QLabel(PodVolumeWidget) 31 | font = QtGui.QFont() 32 | font.setPointSize(10) 33 | self.lbl_mount_path.setFont(font) 34 | self.lbl_mount_path.setObjectName("lbl_mount_path") 35 | self.verticalLayout.addWidget(self.lbl_mount_path) 36 | self.horizontalLayout = QtWidgets.QHBoxLayout() 37 | self.horizontalLayout.setObjectName("horizontalLayout") 38 | self.label_2 = QtWidgets.QLabel(PodVolumeWidget) 39 | font = QtGui.QFont() 40 | font.setPointSize(10) 41 | self.label_2.setFont(font) 42 | self.label_2.setObjectName("label_2") 43 | self.horizontalLayout.addWidget(self.label_2) 44 | self.lbl_volumes = QtWidgets.QLabel(PodVolumeWidget) 45 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) 46 | sizePolicy.setHorizontalStretch(1) 47 | sizePolicy.setVerticalStretch(0) 48 | sizePolicy.setHeightForWidth(self.lbl_volumes.sizePolicy().hasHeightForWidth()) 49 | self.lbl_volumes.setSizePolicy(sizePolicy) 50 | font = QtGui.QFont() 51 | font.setPointSize(10) 52 | self.lbl_volumes.setFont(font) 53 | self.lbl_volumes.setObjectName("lbl_volumes") 54 | self.horizontalLayout.addWidget(self.lbl_volumes) 55 | self.verticalLayout.addLayout(self.horizontalLayout) 56 | 57 | self.retranslateUi(PodVolumeWidget) 58 | QtCore.QMetaObject.connectSlotsByName(PodVolumeWidget) 59 | 60 | def retranslateUi(self, PodVolumeWidget): 61 | _translate = QtCore.QCoreApplication.translate 62 | PodVolumeWidget.setWindowTitle(_translate("PodVolumeWidget", "Form")) 63 | self.lbl_volume_name.setText(_translate("PodVolumeWidget", "TextLabel")) 64 | self.lbl_mount_path.setText(_translate("PodVolumeWidget", "image path")) 65 | self.label_2.setText(_translate("PodVolumeWidget", "Volumes:")) 66 | self.lbl_volumes.setText(_translate("PodVolumeWidget", "TextLabel")) 67 | 68 | 69 | -------------------------------------------------------------------------------- /kuberider/generated/progress_dialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'resources/ui/progress_dialog.ui' 4 | # 5 | # Created by: PyQt5 UI code generator 5.12.1 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore, QtGui, QtWidgets 10 | 11 | 12 | class Ui_ProgressDialog(object): 13 | def setupUi(self, ProgressDialog): 14 | ProgressDialog.setObjectName("ProgressDialog") 15 | ProgressDialog.setWindowModality(QtCore.Qt.WindowModal) 16 | ProgressDialog.setEnabled(True) 17 | ProgressDialog.resize(635, 109) 18 | ProgressDialog.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) 19 | ProgressDialog.setModal(True) 20 | self.lbl_progress_status = QtWidgets.QLabel(ProgressDialog) 21 | self.lbl_progress_status.setGeometry(QtCore.QRect(30, 30, 581, 16)) 22 | self.lbl_progress_status.setText("") 23 | self.lbl_progress_status.setAlignment(QtCore.Qt.AlignCenter) 24 | self.lbl_progress_status.setObjectName("lbl_progress_status") 25 | self.btn_cancel_progress = QtWidgets.QPushButton(ProgressDialog) 26 | self.btn_cancel_progress.setGeometry(QtCore.QRect(270, 70, 113, 32)) 27 | self.btn_cancel_progress.setObjectName("btn_cancel_progress") 28 | 29 | self.retranslateUi(ProgressDialog) 30 | QtCore.QMetaObject.connectSlotsByName(ProgressDialog) 31 | 32 | def retranslateUi(self, ProgressDialog): 33 | _translate = QtCore.QCoreApplication.translate 34 | ProgressDialog.setWindowTitle(_translate("ProgressDialog", "Progress ...")) 35 | self.btn_cancel_progress.setText(_translate("ProgressDialog", "Cancel")) 36 | 37 | 38 | -------------------------------------------------------------------------------- /kuberider/images/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/images/__init__.py -------------------------------------------------------------------------------- /kuberider/images/configure-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/images/configure-48.png -------------------------------------------------------------------------------- /kuberider/images/download-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/images/download-48.png -------------------------------------------------------------------------------- /kuberider/images/download-disabled-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/images/download-disabled-48.png -------------------------------------------------------------------------------- /kuberider/images/kuberider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/images/kuberider.png -------------------------------------------------------------------------------- /kuberider/images/kuberider.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 62 | 65 | 70 | 76 | 82 | 88 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /kuberider/images/load-contexts-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/images/load-contexts-48.png -------------------------------------------------------------------------------- /kuberider/images/plus-48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/images/plus-48.png -------------------------------------------------------------------------------- /kuberider/main.py: -------------------------------------------------------------------------------- 1 | from kuberider.application import main 2 | 3 | if __name__ == '__main__': 4 | main() 5 | -------------------------------------------------------------------------------- /kuberider/mock_responses/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/mock_responses/__init__.py -------------------------------------------------------------------------------- /kuberider/mock_responses/k_exec_shell.txt: -------------------------------------------------------------------------------- 1 | Done -------------------------------------------------------------------------------- /kuberider/mock_responses/k_get_contexts.txt: -------------------------------------------------------------------------------- 1 | development 2 | qa 3 | test 4 | -------------------------------------------------------------------------------- /kuberider/mock_responses/k_get_current_context.txt: -------------------------------------------------------------------------------- 1 | qa -------------------------------------------------------------------------------- /kuberider/mock_responses/k_get_pod_events.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "v1", 6 | "count": 1, 7 | "eventTime": null, 8 | "firstTimestamp": "2019-08-04T20:12:45Z", 9 | "involvedObject": { 10 | "apiVersion": "v1", 11 | "kind": "Pod", 12 | "name": "k8s-bootcamp-59b5b9478-n5nzs", 13 | "namespace": "default", 14 | "resourceVersion": "2404", 15 | "uid": "ea509c58-6fdf-49d7-a7b1-fd78ebc3c9bb" 16 | }, 17 | "kind": "Event", 18 | "lastTimestamp": "2019-08-04T20:12:45Z", 19 | "message": "Successfully assigned default/k8s-bootcamp-59b5b9478-n5nzs to minikube", 20 | "metadata": { 21 | "creationTimestamp": "2019-08-04T20:12:45Z", 22 | "name": "k8s-bootcamp-59b5b9478-n5nzs.15b7d15692726a24", 23 | "namespace": "default", 24 | "resourceVersion": "2409", 25 | "selfLink": "/api/v1/namespaces/default/events/k8s-bootcamp-59b5b9478-n5nzs.15b7d15692726a24", 26 | "uid": "48be6016-c677-4f2f-89c3-bbf9aac2d192" 27 | }, 28 | "reason": "Scheduled", 29 | "reportingComponent": "", 30 | "reportingInstance": "", 31 | "source": { 32 | "component": "default-scheduler" 33 | }, 34 | "type": "Normal" 35 | }, 36 | { 37 | "apiVersion": "v1", 38 | "count": 1, 39 | "eventTime": null, 40 | "firstTimestamp": "2019-08-04T20:12:46Z", 41 | "involvedObject": { 42 | "apiVersion": "v1", 43 | "fieldPath": "spec.containers{k8s-bootcamp}", 44 | "kind": "Pod", 45 | "name": "k8s-bootcamp-59b5b9478-n5nzs", 46 | "namespace": "default", 47 | "resourceVersion": "2405", 48 | "uid": "ea509c58-6fdf-49d7-a7b1-fd78ebc3c9bb" 49 | }, 50 | "kind": "Event", 51 | "lastTimestamp": "2019-08-04T20:12:46Z", 52 | "message": "Pulling image \"gcr.io/google-samples/kubernetes-bootcamp:v1\"", 53 | "metadata": { 54 | "creationTimestamp": "2019-08-04T20:12:46Z", 55 | "name": "k8s-bootcamp-59b5b9478-n5nzs.15b7d156bf7bc3f1", 56 | "namespace": "default", 57 | "resourceVersion": "2415", 58 | "selfLink": "/api/v1/namespaces/default/events/k8s-bootcamp-59b5b9478-n5nzs.15b7d156bf7bc3f1", 59 | "uid": "c94532fe-28cf-471c-8d85-1074b5398daa" 60 | }, 61 | "reason": "Pulling", 62 | "reportingComponent": "", 63 | "reportingInstance": "", 64 | "source": { 65 | "component": "kubelet", 66 | "host": "minikube" 67 | }, 68 | "type": "Normal" 69 | }, 70 | { 71 | "apiVersion": "v1", 72 | "count": 1, 73 | "eventTime": null, 74 | "firstTimestamp": "2019-08-04T20:13:05Z", 75 | "involvedObject": { 76 | "apiVersion": "v1", 77 | "fieldPath": "spec.containers{k8s-bootcamp}", 78 | "kind": "Pod", 79 | "name": "k8s-bootcamp-59b5b9478-n5nzs", 80 | "namespace": "default", 81 | "resourceVersion": "2405", 82 | "uid": "ea509c58-6fdf-49d7-a7b1-fd78ebc3c9bb" 83 | }, 84 | "kind": "Event", 85 | "lastTimestamp": "2019-08-04T20:13:05Z", 86 | "message": "Successfully pulled image \"gcr.io/google-samples/kubernetes-bootcamp:v1\"", 87 | "metadata": { 88 | "creationTimestamp": "2019-08-04T20:13:05Z", 89 | "name": "k8s-bootcamp-59b5b9478-n5nzs.15b7d15b431098fc", 90 | "namespace": "default", 91 | "resourceVersion": "2438", 92 | "selfLink": "/api/v1/namespaces/default/events/k8s-bootcamp-59b5b9478-n5nzs.15b7d15b431098fc", 93 | "uid": "a178097d-8614-4837-a43a-0b9a68266c3f" 94 | }, 95 | "reason": "Pulled", 96 | "reportingComponent": "", 97 | "reportingInstance": "", 98 | "source": { 99 | "component": "kubelet", 100 | "host": "minikube" 101 | }, 102 | "type": "Normal" 103 | }, 104 | { 105 | "apiVersion": "v1", 106 | "count": 1, 107 | "eventTime": null, 108 | "firstTimestamp": "2019-08-04T20:13:05Z", 109 | "involvedObject": { 110 | "apiVersion": "v1", 111 | "fieldPath": "spec.containers{k8s-bootcamp}", 112 | "kind": "Pod", 113 | "name": "k8s-bootcamp-59b5b9478-n5nzs", 114 | "namespace": "default", 115 | "resourceVersion": "2405", 116 | "uid": "ea509c58-6fdf-49d7-a7b1-fd78ebc3c9bb" 117 | }, 118 | "kind": "Event", 119 | "lastTimestamp": "2019-08-04T20:13:05Z", 120 | "message": "Created container k8s-bootcamp", 121 | "metadata": { 122 | "creationTimestamp": "2019-08-04T20:13:05Z", 123 | "name": "k8s-bootcamp-59b5b9478-n5nzs.15b7d15b55226cef", 124 | "namespace": "default", 125 | "resourceVersion": "2440", 126 | "selfLink": "/api/v1/namespaces/default/events/k8s-bootcamp-59b5b9478-n5nzs.15b7d15b55226cef", 127 | "uid": "989df962-3efc-4ef9-903e-e46310cad786" 128 | }, 129 | "reason": "Created", 130 | "reportingComponent": "", 131 | "reportingInstance": "", 132 | "source": { 133 | "component": "kubelet", 134 | "host": "minikube" 135 | }, 136 | "type": "Normal" 137 | }, 138 | { 139 | "apiVersion": "v1", 140 | "count": 1, 141 | "eventTime": null, 142 | "firstTimestamp": "2019-08-04T20:13:05Z", 143 | "involvedObject": { 144 | "apiVersion": "v1", 145 | "fieldPath": "spec.containers{k8s-bootcamp}", 146 | "kind": "Pod", 147 | "name": "k8s-bootcamp-59b5b9478-n5nzs", 148 | "namespace": "default", 149 | "resourceVersion": "2405", 150 | "uid": "ea509c58-6fdf-49d7-a7b1-fd78ebc3c9bb" 151 | }, 152 | "kind": "Event", 153 | "lastTimestamp": "2019-08-04T20:13:05Z", 154 | "message": "Started container k8s-bootcamp", 155 | "metadata": { 156 | "creationTimestamp": "2019-08-04T20:13:05Z", 157 | "name": "k8s-bootcamp-59b5b9478-n5nzs.15b7d15b5eb8cd6e", 158 | "namespace": "default", 159 | "resourceVersion": "2441", 160 | "selfLink": "/api/v1/namespaces/default/events/k8s-bootcamp-59b5b9478-n5nzs.15b7d15b5eb8cd6e", 161 | "uid": "ad668e29-ddba-44ba-8893-494eb593e069" 162 | }, 163 | "reason": "Started", 164 | "reportingComponent": "", 165 | "reportingInstance": "", 166 | "source": { 167 | "component": "kubelet", 168 | "host": "minikube" 169 | }, 170 | "type": "Normal" 171 | } 172 | ], 173 | "kind": "List", 174 | "metadata": { 175 | "resourceVersion": "", 176 | "selfLink": "" 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /kuberider/mock_responses/k_get_qa_multiple_pods.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "v1", 6 | "kind": "Pod", 7 | "metadata": { 8 | "creationTimestamp": "2019-07-27T19:14:56Z", 9 | "generateName": "hello-node-2-7c99ff6cd7-", 10 | "labels": { 11 | "app": "hello-node-2", 12 | "pod-template-hash": "7c99ff6cd7" 13 | }, 14 | "name": "hello-node-2-7c99ff6cd7-gtpxr", 15 | "namespace": "default", 16 | "ownerReferences": [ 17 | { 18 | "apiVersion": "apps/v1", 19 | "blockOwnerDeletion": true, 20 | "controller": true, 21 | "kind": "ReplicaSet", 22 | "name": "hello-node-2-7c99ff6cd7", 23 | "uid": "d204dc8b-b0a2-11e9-afeb-080027f575a9" 24 | } 25 | ], 26 | "resourceVersion": "14859", 27 | "selfLink": "/api/v1/namespaces/default/pods/hello-node-2-7c99ff6cd7-gtpxr", 28 | "uid": "d2092e65-b0a2-11e9-afeb-080027f575a9" 29 | }, 30 | "spec": { 31 | "containers": [ 32 | { 33 | "image": "gcr.io/hello-minikube-zero-install/hello-node", 34 | "imagePullPolicy": "Always", 35 | "name": "hello-node-1", 36 | "resources": {}, 37 | "terminationMessagePath": "/dev/termination-log", 38 | "terminationMessagePolicy": "File", 39 | "volumeMounts": [ 40 | { 41 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", 42 | "name": "default-token-zr92w", 43 | "readOnly": true 44 | } 45 | ] 46 | }, 47 | { 48 | "image": "gcr.io/hello-minikube-zero-install/hello-node", 49 | "imagePullPolicy": "Always", 50 | "name": "hello-node-2", 51 | "resources": {}, 52 | "terminationMessagePath": "/dev/termination-log", 53 | "terminationMessagePolicy": "File", 54 | "volumeMounts": [ 55 | { 56 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", 57 | "name": "default-token-zr92w", 58 | "readOnly": true 59 | } 60 | ] 61 | } 62 | ], 63 | "dnsPolicy": "ClusterFirst", 64 | "enableServiceLinks": true, 65 | "nodeName": "minikube", 66 | "priority": 0, 67 | "restartPolicy": "Always", 68 | "schedulerName": "default-scheduler", 69 | "securityContext": {}, 70 | "serviceAccount": "default", 71 | "serviceAccountName": "default", 72 | "terminationGracePeriodSeconds": 30, 73 | "tolerations": [ 74 | { 75 | "effect": "NoExecute", 76 | "key": "node.kubernetes.io/not-ready", 77 | "operator": "Exists", 78 | "tolerationSeconds": 300 79 | }, 80 | { 81 | "effect": "NoExecute", 82 | "key": "node.kubernetes.io/unreachable", 83 | "operator": "Exists", 84 | "tolerationSeconds": 300 85 | } 86 | ], 87 | "volumes": [ 88 | { 89 | "name": "default-token-zr92w", 90 | "secret": { 91 | "defaultMode": 420, 92 | "secretName": "default-token-zr92w" 93 | } 94 | } 95 | ] 96 | }, 97 | "status": { 98 | "conditions": [ 99 | { 100 | "lastProbeTime": null, 101 | "lastTransitionTime": "2019-07-27T19:14:56Z", 102 | "status": "True", 103 | "type": "Initialized" 104 | }, 105 | { 106 | "lastProbeTime": null, 107 | "lastTransitionTime": "2019-07-27T19:14:58Z", 108 | "status": "True", 109 | "type": "Ready" 110 | }, 111 | { 112 | "lastProbeTime": null, 113 | "lastTransitionTime": "2019-07-27T19:14:58Z", 114 | "status": "True", 115 | "type": "ContainersReady" 116 | }, 117 | { 118 | "lastProbeTime": null, 119 | "lastTransitionTime": "2019-07-27T19:14:56Z", 120 | "status": "True", 121 | "type": "PodScheduled" 122 | } 123 | ], 124 | "containerStatuses": [ 125 | { 126 | "containerID": "docker://8a5efa42daa5ff5a6abcd6f84fc50997f4d0d9d8ad3b9de7c3266d475a39450f", 127 | "image": "gcr.io/hello-minikube-zero-install/hello-node:latest", 128 | "imageID": "docker-pullable://gcr.io/hello-minikube-zero-install/hello-node@sha256:9cf82733f7278ae7ae899d432f8d3b3bb0fcb54e673c67496a9f76bb58f30a1c", 129 | "lastState": {}, 130 | "name": "hello-node-1", 131 | "ready": true, 132 | "restartCount": 0, 133 | "state": { 134 | "running": { 135 | "startedAt": "2019-07-27T19:10:58Z" 136 | } 137 | } 138 | }, 139 | { 140 | "containerID": "docker://665efa42daa5ff5a6abcd6f84fc50997f4d0d9d8ad3b9de7c3266d475a39450f", 141 | "image": "gcr.io/hello-minikube-zero-install/hello-node:latest", 142 | "imageID": "docker-pullable://gcr.io/hello-minikube-zero-install/hello-node@sha256:9cf82733f7278ae7ae899d432f8d3b3bb0fcb54e673c67496a9f76bb58f30a1c", 143 | "lastState": {}, 144 | "name": "hello-node-2", 145 | "ready": true, 146 | "restartCount": 0, 147 | "state": { 148 | "running": { 149 | "startedAt": "2019-07-27T19:20:58Z" 150 | } 151 | } 152 | } 153 | ], 154 | "hostIP": "10.0.2.15", 155 | "phase": "Running", 156 | "podIP": "172.17.0.6", 157 | "qosClass": "BestEffort", 158 | "startTime": "2019-07-27T19:14:56Z" 159 | } 160 | }, 161 | { 162 | "apiVersion": "v1", 163 | "kind": "Pod", 164 | "metadata": { 165 | "creationTimestamp": "2019-07-27T19:15:01Z", 166 | "generateName": "hello-node-3-cbbccdcb6-", 167 | "labels": { 168 | "app": "hello-node-3", 169 | "pod-template-hash": "cbbccdcb6" 170 | }, 171 | "name": "hello-node-3-cbbccdcb6-pbxl4", 172 | "namespace": "default", 173 | "ownerReferences": [ 174 | { 175 | "apiVersion": "apps/v1", 176 | "blockOwnerDeletion": true, 177 | "controller": true, 178 | "kind": "ReplicaSet", 179 | "name": "hello-node-3-cbbccdcb6", 180 | "uid": "d4badcce-b0a2-11e9-afeb-080027f575a9" 181 | } 182 | ], 183 | "resourceVersion": "14886", 184 | "selfLink": "/api/v1/namespaces/default/pods/hello-node-3-cbbccdcb6-pbxl4", 185 | "uid": "d4bb916c-b0a2-11e9-afeb-080027f575a9" 186 | }, 187 | "spec": { 188 | "containers": [ 189 | { 190 | "image": "gcr.io/hello-minikube-zero-install/hello-node", 191 | "imagePullPolicy": "Always", 192 | "name": "hello-node", 193 | "resources": {}, 194 | "terminationMessagePath": "/dev/termination-log", 195 | "terminationMessagePolicy": "File", 196 | "volumeMounts": [ 197 | { 198 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", 199 | "name": "default-token-zr92w", 200 | "readOnly": true 201 | } 202 | ] 203 | } 204 | ], 205 | "dnsPolicy": "ClusterFirst", 206 | "enableServiceLinks": true, 207 | "nodeName": "minikube", 208 | "priority": 0, 209 | "restartPolicy": "Always", 210 | "schedulerName": "default-scheduler", 211 | "securityContext": {}, 212 | "serviceAccount": "default", 213 | "serviceAccountName": "default", 214 | "terminationGracePeriodSeconds": 30, 215 | "tolerations": [ 216 | { 217 | "effect": "NoExecute", 218 | "key": "node.kubernetes.io/not-ready", 219 | "operator": "Exists", 220 | "tolerationSeconds": 300 221 | }, 222 | { 223 | "effect": "NoExecute", 224 | "key": "node.kubernetes.io/unreachable", 225 | "operator": "Exists", 226 | "tolerationSeconds": 300 227 | } 228 | ], 229 | "volumes": [ 230 | { 231 | "name": "default-token-zr92w", 232 | "secret": { 233 | "defaultMode": 420, 234 | "secretName": "default-token-zr92w" 235 | } 236 | } 237 | ] 238 | }, 239 | "status": { 240 | "conditions": [ 241 | { 242 | "lastProbeTime": null, 243 | "lastTransitionTime": "2019-07-27T19:15:01Z", 244 | "status": "True", 245 | "type": "Initialized" 246 | }, 247 | { 248 | "lastProbeTime": null, 249 | "lastTransitionTime": "2019-07-27T19:15:04Z", 250 | "status": "True", 251 | "type": "Ready" 252 | }, 253 | { 254 | "lastProbeTime": null, 255 | "lastTransitionTime": "2019-07-27T19:15:04Z", 256 | "status": "True", 257 | "type": "ContainersReady" 258 | }, 259 | { 260 | "lastProbeTime": null, 261 | "lastTransitionTime": "2019-07-27T19:15:01Z", 262 | "status": "True", 263 | "type": "PodScheduled" 264 | } 265 | ], 266 | "containerStatuses": [ 267 | { 268 | "containerID": "docker://a3929b89f0909fc522e97fc7b1052f19f95e5ab11a0c5a1123728f0472017319", 269 | "image": "gcr.io/hello-minikube-zero-install/hello-node:latest", 270 | "imageID": "docker-pullable://gcr.io/hello-minikube-zero-install/hello-node@sha256:9cf82733f7278ae7ae899d432f8d3b3bb0fcb54e673c67496a9f76bb58f30a1c", 271 | "lastState": {}, 272 | "name": "hello-node", 273 | "ready": true, 274 | "restartCount": 0, 275 | "state": { 276 | "running": { 277 | "startedAt": "2019-07-27T19:15:02Z" 278 | } 279 | } 280 | } 281 | ], 282 | "hostIP": "10.0.2.15", 283 | "phase": "Running", 284 | "podIP": "172.17.0.7", 285 | "qosClass": "BestEffort", 286 | "startTime": "2019-07-27T19:15:01Z" 287 | } 288 | }, 289 | { 290 | "apiVersion": "v1", 291 | "kind": "Pod", 292 | "metadata": { 293 | "creationTimestamp": "2019-07-27T10:43:24Z", 294 | "generateName": "hello-node-64c578bdf8-", 295 | "labels": { 296 | "app": "hello-node", 297 | "pod-template-hash": "64c578bdf8" 298 | }, 299 | "name": "hello-node-64c578bdf8-mwpff", 300 | "namespace": "default", 301 | "ownerReferences": [ 302 | { 303 | "apiVersion": "apps/v1", 304 | "blockOwnerDeletion": true, 305 | "controller": true, 306 | "kind": "ReplicaSet", 307 | "name": "hello-node-64c578bdf8", 308 | "uid": "5bcd3208-b05b-11e9-afeb-080027f575a9" 309 | } 310 | ], 311 | "resourceVersion": "3473", 312 | "selfLink": "/api/v1/namespaces/default/pods/hello-node-64c578bdf8-mwpff", 313 | "uid": "5bcf9b2d-b05b-11e9-afeb-080027f575a9" 314 | }, 315 | "spec": { 316 | "containers": [ 317 | { 318 | "image": "gcr.io/hello-minikube-zero-install/hello-node", 319 | "imagePullPolicy": "Always", 320 | "name": "hello-node", 321 | "resources": {}, 322 | "terminationMessagePath": "/dev/termination-log", 323 | "terminationMessagePolicy": "File", 324 | "volumeMounts": [ 325 | { 326 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", 327 | "name": "default-token-zr92w", 328 | "readOnly": true 329 | } 330 | ] 331 | } 332 | ], 333 | "dnsPolicy": "ClusterFirst", 334 | "enableServiceLinks": true, 335 | "nodeName": "minikube", 336 | "priority": 0, 337 | "restartPolicy": "Always", 338 | "schedulerName": "default-scheduler", 339 | "securityContext": {}, 340 | "serviceAccount": "default", 341 | "serviceAccountName": "default", 342 | "terminationGracePeriodSeconds": 30, 343 | "tolerations": [ 344 | { 345 | "effect": "NoExecute", 346 | "key": "node.kubernetes.io/not-ready", 347 | "operator": "Exists", 348 | "tolerationSeconds": 300 349 | }, 350 | { 351 | "effect": "NoExecute", 352 | "key": "node.kubernetes.io/unreachable", 353 | "operator": "Exists", 354 | "tolerationSeconds": 300 355 | } 356 | ], 357 | "volumes": [ 358 | { 359 | "name": "default-token-zr92w", 360 | "secret": { 361 | "defaultMode": 420, 362 | "secretName": "default-token-zr92w" 363 | } 364 | } 365 | ] 366 | }, 367 | "status": { 368 | "conditions": [ 369 | { 370 | "lastProbeTime": null, 371 | "lastTransitionTime": "2019-07-27T10:43:24Z", 372 | "status": "True", 373 | "type": "Initialized" 374 | }, 375 | { 376 | "lastProbeTime": null, 377 | "lastTransitionTime": "2019-07-27T10:43:24Z", 378 | "message": "containers with unready status: [hello-node]", 379 | "reason": "ContainersNotReady", 380 | "status": "False", 381 | "type": "Ready" 382 | }, 383 | { 384 | "lastProbeTime": null, 385 | "lastTransitionTime": "2019-07-27T10:43:24Z", 386 | "message": "containers with unready status: [hello-node]", 387 | "reason": "ContainersNotReady", 388 | "status": "False", 389 | "type": "ContainersReady" 390 | }, 391 | { 392 | "lastProbeTime": null, 393 | "lastTransitionTime": "2019-07-27T10:43:24Z", 394 | "status": "True", 395 | "type": "PodScheduled" 396 | } 397 | ], 398 | "containerStatuses": [ 399 | { 400 | "image": "gcr.io/hello-minikube-zero-install/hello-node", 401 | "imageID": "", 402 | "lastState": {}, 403 | "name": "hello-node", 404 | "ready": false, 405 | "restartCount": 0, 406 | "state": { 407 | "waiting": { 408 | "reason": "ContainerCreating" 409 | } 410 | } 411 | } 412 | ], 413 | "hostIP": "10.0.2.15", 414 | "phase": "Pending", 415 | "qosClass": "BestEffort", 416 | "startTime": "2019-07-27T10:43:24Z" 417 | } 418 | } 419 | ], 420 | "kind": "List", 421 | "metadata": { 422 | "resourceVersion": "", 423 | "selfLink": "" 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /kuberider/mock_responses/k_get_qa_namespaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "v1", 6 | "kind": "Namespace", 7 | "metadata": { 8 | "creationTimestamp": "2019-07-22T06:54:45Z", 9 | "name": "default", 10 | "namespace": "", 11 | "resourceVersion": "4", 12 | "selfLink": "/api/v1/namespaces/default", 13 | "uid": "9712aeed-ac4d-11e9-872d-080027f575a9" 14 | }, 15 | "spec": { 16 | "finalizers": [ 17 | "kubernetes" 18 | ] 19 | }, 20 | "status": { 21 | "phase": "Active" 22 | } 23 | }, 24 | { 25 | "apiVersion": "v1", 26 | "kind": "Namespace", 27 | "metadata": { 28 | "creationTimestamp": "2019-07-22T06:54:49Z", 29 | "name": "kube-public", 30 | "namespace": "", 31 | "resourceVersion": "21", 32 | "selfLink": "/api/v1/namespaces/kube-public", 33 | "uid": "997ac58a-ac4d-11e9-872d-080027f575a9" 34 | }, 35 | "spec": { 36 | "finalizers": [ 37 | "kubernetes" 38 | ] 39 | }, 40 | "status": { 41 | "phase": "Active" 42 | } 43 | } 44 | ], 45 | "kind": "List", 46 | "metadata": { 47 | "resourceVersion": "", 48 | "selfLink": "" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /kuberider/mock_responses/k_get_qa_single_pod.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "v1", 6 | "kind": "Pod", 7 | "metadata": { 8 | "creationTimestamp": "2019-07-27T10:43:24Z", 9 | "generateName": "hello-java-64c578bdf8-", 10 | "labels": { 11 | "app": "hello-java", 12 | "pod-template-hash": "64c578bdf8" 13 | }, 14 | "name": "hello-java-64c578bdf8-mwpff", 15 | "namespace": "default", 16 | "ownerReferences": [ 17 | { 18 | "apiVersion": "apps/v1", 19 | "blockOwnerDeletion": true, 20 | "controller": true, 21 | "kind": "ReplicaSet", 22 | "name": "hello-java-64c578bdf8", 23 | "uid": "5bcd3208-b05b-11e9-afeb-080027f575a9" 24 | } 25 | ], 26 | "resourceVersion": "3473", 27 | "selfLink": "/api/v1/namespaces/default/pods/hello-java-64c578bdf8-mwpff", 28 | "uid": "5bcf9b2d-b05b-11e9-afeb-080027f575a9" 29 | }, 30 | "spec": { 31 | "containers": [ 32 | { 33 | "image": "gcr.io/hello-minikube-zero-install/hello-java", 34 | "imagePullPolicy": "Always", 35 | "name": "hello-java", 36 | "resources": {}, 37 | "terminationMessagePath": "/dev/termination-log", 38 | "terminationMessagePolicy": "File", 39 | "volumeMounts": [ 40 | { 41 | "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount", 42 | "name": "default-token-zr92w", 43 | "readOnly": true 44 | } 45 | ] 46 | } 47 | ], 48 | "dnsPolicy": "ClusterFirst", 49 | "enableServiceLinks": true, 50 | "nodeName": "minikube", 51 | "priority": 0, 52 | "restartPolicy": "Always", 53 | "schedulerName": "default-scheduler", 54 | "securityContext": {}, 55 | "serviceAccount": "default", 56 | "serviceAccountName": "default", 57 | "terminationGracePeriodSeconds": 30, 58 | "tolerations": [ 59 | { 60 | "effect": "NoExecute", 61 | "key": "node.kubernetes.io/not-ready", 62 | "operator": "Exists", 63 | "tolerationSeconds": 300 64 | }, 65 | { 66 | "effect": "NoExecute", 67 | "key": "node.kubernetes.io/unreachable", 68 | "operator": "Exists", 69 | "tolerationSeconds": 300 70 | } 71 | ], 72 | "volumes": [ 73 | { 74 | "name": "default-token-zr92w", 75 | "secret": { 76 | "defaultMode": 420, 77 | "secretName": "default-token-zr92w" 78 | } 79 | } 80 | ] 81 | }, 82 | "status": { 83 | "conditions": [ 84 | { 85 | "lastProbeTime": null, 86 | "lastTransitionTime": "2019-07-27T10:43:24Z", 87 | "status": "True", 88 | "type": "Initialized" 89 | }, 90 | { 91 | "lastProbeTime": null, 92 | "lastTransitionTime": "2019-07-27T10:43:24Z", 93 | "message": "containers with unready status: [hello-java]", 94 | "reason": "ContainersNotReady", 95 | "status": "False", 96 | "type": "Ready" 97 | }, 98 | { 99 | "lastProbeTime": null, 100 | "lastTransitionTime": "2019-07-27T10:43:24Z", 101 | "message": "containers with unready status: [hello-java]", 102 | "reason": "ContainersNotReady", 103 | "status": "False", 104 | "type": "ContainersReady" 105 | }, 106 | { 107 | "lastProbeTime": null, 108 | "lastTransitionTime": "2019-07-27T10:43:24Z", 109 | "status": "True", 110 | "type": "PodScheduled" 111 | } 112 | ], 113 | "containerStatuses": [ 114 | { 115 | "image": "gcr.io/hello-minikube-zero-install/hello-java", 116 | "imageID": "", 117 | "lastState": {}, 118 | "name": "hello-java", 119 | "ready": false, 120 | "restartCount": 0, 121 | "state": { 122 | "waiting": { 123 | "reason": "ContainerCreating" 124 | } 125 | } 126 | } 127 | ], 128 | "hostIP": "10.0.2.15", 129 | "phase": "Pending", 130 | "qosClass": "BestEffort", 131 | "startTime": "2019-07-27T10:43:24Z" 132 | } 133 | } 134 | ], 135 | "kind": "List", 136 | "metadata": { 137 | "resourceVersion": "", 138 | "selfLink": "" 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /kuberider/mock_responses/k_get_test_namespaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "apiVersion": "v1", 3 | "items": [ 4 | { 5 | "apiVersion": "v1", 6 | "kind": "Namespace", 7 | "metadata": { 8 | "creationTimestamp": "2019-07-22T06:54:45Z", 9 | "name": "default", 10 | "namespace": "", 11 | "resourceVersion": "4", 12 | "selfLink": "/api/v1/namespaces/default", 13 | "uid": "9712aeed-ac4d-11e9-872d-080027f575a9" 14 | }, 15 | "spec": { 16 | "finalizers": [ 17 | "kubernetes" 18 | ] 19 | }, 20 | "status": { 21 | "phase": "Active" 22 | } 23 | } 24 | ], 25 | "kind": "List", 26 | "metadata": { 27 | "resourceVersion": "", 28 | "selfLink": "" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /kuberider/mock_responses/k_pod_deleted.txt: -------------------------------------------------------------------------------- 1 | Get Response from delete pod command -------------------------------------------------------------------------------- /kuberider/presenters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/presenters/__init__.py -------------------------------------------------------------------------------- /kuberider/presenters/configuration_presenter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from kuberider.settings.app_settings import app 4 | 5 | 6 | class ConfigurationPresenter: 7 | def __init__(self, view, parent_view): 8 | self.view = view 9 | self.parent_view = parent_view 10 | self.view.btn_save_configuration.pressed.connect(self.on_success) 11 | self.view.btn_cancel_configuration.pressed.connect(self.ignore_changes) 12 | 13 | def ignore_changes(self): 14 | self.view.reject() 15 | 16 | def on_success(self): 17 | logging.info("Saving configuration") 18 | updates_check = self.view.chk_updates_startup.isChecked() 19 | kubectl_path = self.view.txt_kubectl.text() 20 | app.save_configuration(updates_check, kubectl_path) 21 | self.parent_view.status_bar.showMessage("Ready", 5000) 22 | self.view.accept() 23 | 24 | def load_configuration_dialog(self): 25 | check_updates = app.load_updates_configuration() 26 | self.view.chk_updates_startup.setChecked(check_updates) 27 | 28 | kubectl_path = app.load_kubectl_path() 29 | self.view.txt_kubectl.setText(kubectl_path) 30 | 31 | self.view.show() 32 | -------------------------------------------------------------------------------- /kuberider/presenters/console_presenter.py: -------------------------------------------------------------------------------- 1 | from kuberider.settings.app_settings import app 2 | 3 | 4 | class ConsolePresenter: 5 | def __init__(self, console_text_edit): 6 | self.console_text_edit = console_text_edit 7 | 8 | # domain events 9 | app.data.signals.command_added.connect(self.on_command_added) 10 | app.data.signals.command_failed.connect(self.on_command_failed) 11 | 12 | def on_command_added(self, new_command): 13 | self.console_text_edit.appendPlainText(new_command.replace(" -o json", "")) 14 | 15 | def on_command_failed(self, failure_message): 16 | message = f"[ERROR] {failure_message}" 17 | self.console_text_edit.appendPlainText(message) 18 | -------------------------------------------------------------------------------- /kuberider/presenters/container_exec_presenter.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import QInputDialog, QLineEdit 2 | 3 | from kuberider.domain.container_interactor import ExecShellInteractor 4 | from kuberider.settings.app_settings import app 5 | 6 | 7 | class ContainerExecPresenter: 8 | def __init__(self, parent): 9 | self.parent = parent 10 | self.exec_shell = ExecShellInteractor() 11 | 12 | # commands 13 | app.commands.on_exec_shell.connect(self.on_exec_shell) 14 | app.commands.on_port_forward.connect(self.on_port_forward) 15 | app.commands.on_follow_logs.connect(self.on_follow_logs) 16 | 17 | def on_exec_shell(self, pod_name, container_name): 18 | cmd, ret = QInputDialog.getText(self.parent, "Execute command", "Command", 19 | QLineEdit.Normal) 20 | if ret and cmd: 21 | self.exec_shell.run(pod_name, container_name, cmd) 22 | 23 | def on_port_forward(self, pod_name, container_name): 24 | cmd, ret = QInputDialog.getText(self.parent, "Port forwarding", "Enter Ports (Local:Remote)", QLineEdit.Normal) 25 | if ret and cmd: 26 | self.exec_shell.port_forward(pod_name, container_name, cmd) 27 | 28 | def on_follow_logs(self, pod_name, container_name): 29 | self.exec_shell.follow_logs(pod_name, container_name) 30 | -------------------------------------------------------------------------------- /kuberider/presenters/file_menu_presenter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | 4 | class FileMenuPresenter: 5 | def __init__(self, parent): 6 | self.main_window = parent 7 | 8 | def on_file_new(self): 9 | logging.info("File New Menu triggered") 10 | -------------------------------------------------------------------------------- /kuberider/presenters/kube_resource_presenter.py: -------------------------------------------------------------------------------- 1 | from kuberider.domain.kube_resource_interactor import KubeResourceInteractor 2 | from kuberider.settings.app_settings import app 3 | from kuberider.ui.kube_resource_dialog import KubeResourceDialog 4 | 5 | 6 | class KubeResourcePresenter: 7 | def __init__(self, parent): 8 | self.parent = parent 9 | self.dialog = KubeResourceDialog(self.parent) 10 | self.kube_apply = KubeResourceInteractor() 11 | 12 | # domain events 13 | app.data.signals.kube_resource_applied.connect(self.on_kube_resource_applied) 14 | 15 | # ui events 16 | self.dialog.btn_apply_resource.clicked.connect(self.apply_resource) 17 | self.dialog.btn_close_dialog.clicked.connect(self.hide_dialog) 18 | 19 | def show_dialog(self): 20 | self.dialog.txt_resource_definition.clear() 21 | self.dialog.lbl_command_output.clear() 22 | self.dialog.show() 23 | 24 | def apply_resource(self): 25 | resource_definition = self.dialog.txt_resource_definition.toPlainText() 26 | self.kube_apply.run(resource_definition) 27 | 28 | def on_kube_resource_applied(self, output): 29 | self.dialog.lbl_command_output.setText(output) 30 | 31 | def hide_dialog(self): 32 | self.dialog.hide() 33 | -------------------------------------------------------------------------------- /kuberider/presenters/kube_rider_main_presenter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from kuberider.domain.contexts_interactor import ContextsLoaderInteractor 4 | from kuberider.settings.app_settings import app 5 | 6 | 7 | class KubeRiderMainPresenter: 8 | def __init__(self, view): 9 | self.view = view 10 | self.initial_load = True 11 | app.init() 12 | app.init_logger() 13 | if app.geometry(): 14 | self.view.restoreGeometry(app.geometry()) 15 | if app.window_state(): 16 | self.view.restoreState(app.window_state()) 17 | 18 | 19 | 20 | def after_window_loaded(self): 21 | if not self.initial_load: 22 | return 23 | 24 | self.initial_load = False 25 | self.check_updates() 26 | 27 | def check_updates(self): 28 | if app.load_updates_configuration(): 29 | self.view.updater.check() 30 | 31 | def save_settings(self): 32 | logging.info("Saving settings for Main Window") 33 | app.save_window_state( 34 | geometry=self.view.saveGeometry(), 35 | window_state=self.view.saveState() 36 | ) 37 | 38 | def shutdown(self): 39 | self.save_settings() 40 | -------------------------------------------------------------------------------- /kuberider/presenters/pod_containers_presenter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from PyQt5 import QtWidgets 4 | from PyQt5.QtCore import Qt 5 | 6 | from kuberider.entities.model import KubePodItem 7 | from kuberider.settings.app_settings import app 8 | from kuberider.widgets.pod_container_widget import PodContainerWidget 9 | 10 | 11 | class PodContainersPresenter: 12 | def __init__(self, view): 13 | self.view = view 14 | 15 | # domain events 16 | app.data.signals.pod_selected.connect(self.on_pod_selected) 17 | 18 | def on_pod_selected(self, pod_info: KubePodItem): 19 | self.view.clear() 20 | for container in pod_info.containers: 21 | pod_container_widget = PodContainerWidget(pod_info, container, self.view) 22 | pod_container_widget_item = QtWidgets.QListWidgetItem(self.view) 23 | pod_container_widget_item.setData(Qt.UserRole, container.name) 24 | pod_container_widget_item.setSizeHint(pod_container_widget.sizeHint()) 25 | 26 | self.view.addItem(pod_container_widget_item) 27 | self.view.setItemWidget(pod_container_widget_item, pod_container_widget) 28 | -------------------------------------------------------------------------------- /kuberider/presenters/pod_events_presenter.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | from kuberider.domain.pods_interactor import GetPodEventsInteractor 4 | from kuberider.entities.model import KubePodItem 5 | from kuberider.settings.app_settings import app 6 | 7 | 8 | class PodEventsPresenter: 9 | def __init__(self, view): 10 | self.view = view 11 | self.pod_events = GetPodEventsInteractor() 12 | 13 | # domain events 14 | app.data.signals.pod_selected.connect(self.on_pod_selected) 15 | app.data.signals.pod_events_loaded.connect(self.on_pod_events_loaded) 16 | 17 | def on_pod_selected(self, pod_info: KubePodItem): 18 | self.pod_events.run(pod_info.name) 19 | pass 20 | 21 | def on_pod_events_loaded(self, pod_events: List): 22 | self.view.clear() 23 | for event in pod_events: 24 | self.view.appendPlainText(event.get("message")) 25 | -------------------------------------------------------------------------------- /kuberider/presenters/pod_list_presenter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Optional, List 3 | 4 | from PyQt5 import QtWidgets 5 | from PyQt5.QtCore import Qt, QAbstractItemModel, QModelIndex 6 | from PyQt5.QtWidgets import QAction, QMenu, QMessageBox 7 | 8 | from kuberider.domain.pods_interactor import GetPodsInteractor, DeletePodInteractor 9 | from kuberider.entities.model import KubePodItem 10 | from kuberider.settings.app_settings import app 11 | from kuberider.widgets.pod_item_widget import PodItemWidget 12 | 13 | 14 | class PodListPresenter: 15 | def __init__(self, parent_view=None): 16 | self.view = parent_view 17 | self.get_pods = GetPodsInteractor() 18 | self.delete_pod = DeletePodInteractor() 19 | 20 | # context menu 21 | self.view.setContextMenuPolicy(Qt.CustomContextMenu) 22 | self.view.customContextMenuRequested.connect(self.show_context_menu) 23 | 24 | # Context Menu setup 25 | remove_action = QAction("Delete", self.view) 26 | remove_action.triggered.connect(self.on_remove_selected_item) 27 | 28 | self.menu = QMenu() 29 | self.menu.addAction(remove_action) 30 | 31 | # ui events 32 | self.view.clicked.connect(self.ui_pod_selected) 33 | 34 | # domain events 35 | app.data.signals.namespace_changed.connect(self.on_namespace_changed) 36 | app.data.signals.pods_loaded.connect(self.on_pods_loaded) 37 | app.data.signals.pod_deleted.connect(self.get_all_pods) 38 | app.data.signals.filter_enabled.connect(self.on_namespace_changed) 39 | app.data.signals.filter_cleared.connect(self.on_namespace_changed) 40 | app.commands.reload_pods.connect(self.get_all_pods) 41 | 42 | def ui_pod_selected(self): 43 | pod_item: KubePodItem = self.currently_selected_pod() 44 | logging.info(f"Selected {pod_item}") 45 | app.data.signals.pod_selected.emit(pod_item) 46 | 47 | def on_namespace_changed(self): 48 | self.get_all_pods() 49 | 50 | def get_all_pods(self): 51 | self.view.clear() 52 | self.get_pods.run() 53 | 54 | def currently_selected_pod(self) -> Optional[KubePodItem]: 55 | selected_items = self.view.selectedItems() 56 | if selected_items: 57 | item = self.view.itemWidget(self.view.selectedItems()[0]) 58 | return item.get_data() 59 | 60 | def on_pods_loaded(self, pods: List[KubePodItem]): 61 | logging.info(f"on_pods_loaded: Displaying {len(pods)}") 62 | pod_item = self.currently_selected_pod() 63 | 64 | for pod in pods: 65 | self.add_or_update_pod_item(pod) 66 | 67 | if pod_item: 68 | existing_pod_index = self.find_pod_index_by_name(pod_item.name) 69 | if existing_pod_index: 70 | item_widget = self.view.item(existing_pod_index.row()) 71 | self.view.setCurrentItem(item_widget) 72 | 73 | def find_pod_index_by_name(self, pod_name): 74 | m: QAbstractItemModel = self.view.model() 75 | items = m.match(m.index(0, 0), Qt.UserRole, pod_name, 1, Qt.MatchExactly) 76 | return items[0] if items else None 77 | 78 | def add_or_update_pod_item(self, pod_info: KubePodItem): 79 | current_filter = app.data.app_state.pods_filter 80 | if current_filter and current_filter not in pod_info.name: 81 | return 82 | 83 | existing_pod_index = self.find_pod_index_by_name(pod_info.name) 84 | 85 | if existing_pod_index: 86 | item_row = existing_pod_index.row() 87 | existing_pod_info = self.pod_at_row(item_row) 88 | if self.pod_changed(existing_pod_info, pod_info): 89 | self.view.takeItem(item_row) 90 | pod_widget = PodItemWidget(pod_info, self.view) 91 | pod_widget_item = QtWidgets.QListWidgetItem(self.view) 92 | pod_widget_item.setData(Qt.UserRole, pod_info.name) 93 | pod_widget_item.setSizeHint(pod_widget.sizeHint()) 94 | self.view.insertItem(item_row, pod_widget_item) 95 | self.view.setItemWidget(pod_widget_item, pod_widget) 96 | else: 97 | pod_widget = PodItemWidget(pod_info, self.view) 98 | pod_widget_item = QtWidgets.QListWidgetItem(self.view) 99 | pod_widget_item.setData(Qt.UserRole, pod_info.name) 100 | pod_widget_item.setSizeHint(pod_widget.sizeHint()) 101 | self.view.addItem(pod_widget_item) 102 | self.view.setItemWidget(pod_widget_item, pod_widget) 103 | 104 | def pod_changed(self, old, new): 105 | is_changed = old.name != new.name or old.pod_status != new.pod_status 106 | return is_changed 107 | 108 | def pod_at_row(self, item_row): 109 | item_widget = self.view.item(item_row) 110 | pod_widget: PodItemWidget = self.view.itemWidget(item_widget) 111 | return pod_widget.get_data() 112 | 113 | def show_context_menu(self, position): 114 | index: QModelIndex = self.view.indexAt(position) 115 | if not index.isValid(): 116 | return 117 | 118 | global_position = self.view.viewport().mapToGlobal(position) 119 | self.menu.exec_(global_position) 120 | 121 | def on_remove_selected_item(self): 122 | pod_item = self.currently_selected_pod() 123 | app_state = app.data.app_state 124 | msg_box = QMessageBox( 125 | QMessageBox.Warning, 126 | "Confirmation", 127 | f"Selected context {app_state.current_context} and namespace {app_state.current_namespace}\nDelete {pod_item.name}?", 128 | QMessageBox.Ok | QMessageBox.Cancel 129 | ) 130 | msg_box.setDefaultButton(QMessageBox.Ok) 131 | if msg_box.exec_() == QMessageBox.Ok: 132 | self.delete_pod.delete(pod_item.name) 133 | -------------------------------------------------------------------------------- /kuberider/presenters/pod_logs_presenter.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from ..domain.pods_interactor import PodLogsInteractor 4 | from ..settings.app_settings import app 5 | from ..ui.pod_logs_dialog import PodLogsDialog 6 | 7 | 8 | class PodLogsPresenter: 9 | pod_name: Optional[str] = None 10 | container_name: Optional[str] = None 11 | 12 | def __init__(self, parent): 13 | self.parent = parent 14 | self.pod_logs_dialog = PodLogsDialog(self.parent) 15 | self.pods_logs = PodLogsInteractor() 16 | 17 | # ui events 18 | self.pod_logs_dialog.btn_close_logs.pressed.connect(self.on_close_logs_dialog) 19 | 20 | # domain event 21 | app.commands.open_pod_logs.connect(self.on_open_logs_dialog) 22 | app.data.signals.output_generated.connect(self.on_output_generated) 23 | 24 | def on_open_logs_dialog(self, pod_name, container_name): 25 | self.pod_name = pod_name 26 | self.container_name = container_name 27 | 28 | self.pod_logs_dialog.show_dialog() 29 | self.pods_logs.fetch_all(self.pod_name, self.container_name) 30 | 31 | def on_close_logs_dialog(self): 32 | self.pod_name = None 33 | self.container_name = None 34 | self.pod_logs_dialog.hide_dialog() 35 | 36 | def on_output_generated(self, output): 37 | self.pod_logs_dialog.txt_pod_logs.appendPlainText(output) 38 | -------------------------------------------------------------------------------- /kuberider/presenters/pods_filter_presenter.py: -------------------------------------------------------------------------------- 1 | from kuberider.domain.pods_interactor import FilterPodsInteractor 2 | 3 | 4 | class PodsFilterPresenter: 5 | def __init__(self, view): 6 | self.view = view 7 | self.filter_pods = FilterPodsInteractor() 8 | 9 | # ui events 10 | self.view.btn_enable_filter.clicked.connect(self.enable_filter) 11 | self.view.btn_clear_filter.clicked.connect(self.clear_filter) 12 | 13 | def enable_filter(self): 14 | filter = self.view.txt_filter.text() 15 | self.filter_pods.apply_filter(filter) 16 | 17 | def clear_filter(self): 18 | self.view.txt_filter.setText("") 19 | self.filter_pods.clear_filter() 20 | -------------------------------------------------------------------------------- /kuberider/presenters/toolbar_presenter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from kuberider.domain.contexts_interactor import ChangeContextInteractor, CurrentContextInteractor, \ 4 | ContextsLoaderInteractor 5 | from kuberider.domain.namespaces_interactor import NamespacesLoaderInteractor, ChangeNamespaceInteractor 6 | from kuberider.settings.app_settings import app 7 | 8 | 9 | class ToolbarPresenter: 10 | def __init__(self, toolbar): 11 | self.toolbar = toolbar 12 | self.contexts_loader = ContextsLoaderInteractor() 13 | self.namespaces = NamespacesLoaderInteractor() 14 | self.change_context = ChangeContextInteractor() 15 | self.current_context = CurrentContextInteractor() 16 | self.change_namespace = ChangeNamespaceInteractor() 17 | self.contexts_loaded = False 18 | self.namespaces_loaded = False 19 | 20 | # events 21 | app.data.signals.contexts_loaded.connect(self.on_contexts_loaded) 22 | app.data.signals.context_changed.connect(self.on_current_context_changed) 23 | app.data.signals.namespaces_loaded.connect(self.on_namespaces_loaded) 24 | 25 | def on_toolbar_load_contexts(self): 26 | self.contexts_loader.load_contexts() 27 | 28 | def on_toolbar_context_changed(self, new_context_name): 29 | self.change_context.update_context(new_context_name) 30 | 31 | def __get_combox_box(self, action_name): 32 | toolbar_actions = self.toolbar.actions() 33 | tags_list_action = next(act for act in toolbar_actions if act.text() == action_name) 34 | return tags_list_action.defaultWidget() 35 | 36 | def on_namespaces_loaded(self): 37 | namespaces_ui = self.__get_combox_box("Namespaces") 38 | namespaces_ui.clear() 39 | namespaces = app.data.load_namespaces() 40 | for ns in namespaces: 41 | namespaces_ui.addItem(ns) 42 | 43 | self.namespaces_loaded = True 44 | if namespaces: 45 | self.on_current_namespace_changed(namespaces[0]) 46 | 47 | def on_contexts_loaded(self): 48 | contexts_ui = self.__get_combox_box("Contexts") 49 | contexts_ui.clear() 50 | contexts = app.data.load_contexts() 51 | for ctx in contexts: 52 | contexts_ui.addItem(ctx) 53 | 54 | self.contexts_loaded = True 55 | self.current_context.current_context() 56 | 57 | def on_current_context_changed(self, selected_context): 58 | if self.contexts_loaded: 59 | self.namespaces_loaded = False 60 | self.namespaces.load_namespaces() 61 | 62 | def on_current_namespace_changed(self, new_namespace_name): 63 | if self.namespaces_loaded: 64 | logging.info(f"Current namespace changed to {new_namespace_name}. Should get pods") 65 | self.change_namespace.update_namespace(new_namespace_name) 66 | -------------------------------------------------------------------------------- /kuberider/presenters/watch_presenter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from PyQt5.QtCore import QTimer 4 | 5 | from kuberider.settings.app_settings import app 6 | 7 | 8 | class WatchPresenter: 9 | def __init__(self, view): 10 | self.view = view 11 | self.timer = QTimer() 12 | 13 | # ui events 14 | self.view.btn_reload_pods.clicked.connect(lambda _: app.commands.reload_pods.emit()) 15 | self.view.chk_watch.stateChanged.connect(self.update_timer) 16 | self.timer.timeout.connect(self.on_timer_timeout) 17 | 18 | def update_timer(self): 19 | currently_watching = self.view.chk_watch.isChecked() 20 | watch_interval_secs = self.view.txt_watch_interval.value() 21 | if currently_watching: 22 | logging.info(f"Watching Pods every {watch_interval_secs} secs") 23 | QTimer.singleShot(watch_interval_secs * 1000, self.on_timer_timeout) 24 | else: 25 | logging.info(f"Stopped watching Pods") 26 | 27 | def on_timer_timeout(self): 28 | try: 29 | app.commands.reload_pods.emit() 30 | finally: 31 | self.update_timer() 32 | -------------------------------------------------------------------------------- /kuberider/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | images/configure-48.png 4 | images/plus-48.png 5 | images/download-48.png 6 | images/download-disabled-48.png 7 | images/load-contexts-48.png 8 | themes/light.qss 9 | 10 | 11 | -------------------------------------------------------------------------------- /kuberider/resources_rc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Resource object code 4 | # 5 | # Created by: The Resource Compiler for PyQt5 (Qt v5.12.2) 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from PyQt5 import QtCore 10 | 11 | qt_resource_data = b"\ 12 | \x00\x00\x01\xb9\ 13 | \x51\ 14 | \x54\x6f\x6f\x6c\x42\x61\x72\x20\x7b\x0a\x20\x20\x20\x20\x62\x6f\ 15 | \x72\x64\x65\x72\x2d\x62\x6f\x74\x74\x6f\x6d\x3a\x20\x31\x70\x78\ 16 | \x20\x73\x6f\x6c\x69\x64\x20\x23\x34\x44\x35\x34\x35\x42\x3b\x0a\ 17 | \x7d\x0a\x0a\x51\x4c\x69\x73\x74\x57\x69\x64\x67\x65\x74\x3a\x3a\ 18 | \x69\x74\x65\x6d\x3a\x73\x65\x6c\x65\x63\x74\x65\x64\x20\x7b\x0a\ 19 | \x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x3a\x20\x23\ 20 | \x43\x42\x44\x38\x45\x31\x3b\x0a\x20\x20\x20\x63\x6f\x6c\x6f\x72\ 21 | \x3a\x20\x62\x6c\x61\x63\x6b\x3b\x0a\x7d\x0a\x0a\x51\x4c\x61\x62\ 22 | \x65\x6c\x5b\x61\x63\x63\x65\x73\x73\x69\x62\x6c\x65\x4e\x61\x6d\ 23 | \x65\x3d\x22\x72\x75\x6e\x6e\x69\x6e\x67\x22\x5d\x20\x7b\x0a\x20\ 24 | \x20\x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x63\x6f\ 25 | \x6c\x6f\x72\x3a\x20\x23\x30\x31\x37\x32\x31\x64\x3b\x0a\x20\x20\ 26 | \x20\x20\x63\x6f\x6c\x6f\x72\x20\x3a\x20\x77\x68\x69\x74\x65\x3b\ 27 | \x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x6c\x65\x66\ 28 | \x74\x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\ 29 | \x69\x6e\x67\x2d\x72\x69\x67\x68\x74\x3a\x20\x35\x70\x78\x3b\x0a\ 30 | \x20\x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x72\x61\x64\x69\x75\ 31 | \x73\x3a\x20\x34\x70\x78\x3b\x0a\x7d\x0a\x0a\x51\x4c\x61\x62\x65\ 32 | \x6c\x5b\x61\x63\x63\x65\x73\x73\x69\x62\x6c\x65\x4e\x61\x6d\x65\ 33 | \x3d\x22\x77\x61\x69\x74\x69\x6e\x67\x22\x5d\x20\x7b\x0a\x20\x20\ 34 | \x20\x20\x62\x61\x63\x6b\x67\x72\x6f\x75\x6e\x64\x2d\x63\x6f\x6c\ 35 | \x6f\x72\x3a\x20\x23\x64\x38\x61\x34\x31\x33\x3b\x0a\x20\x20\x20\ 36 | \x20\x63\x6f\x6c\x6f\x72\x20\x3a\x20\x77\x68\x69\x74\x65\x3b\x0a\ 37 | \x20\x20\x20\x20\x70\x61\x64\x64\x69\x6e\x67\x2d\x6c\x65\x66\x74\ 38 | \x3a\x20\x35\x70\x78\x3b\x0a\x20\x20\x20\x20\x70\x61\x64\x64\x69\ 39 | \x6e\x67\x2d\x72\x69\x67\x68\x74\x3a\x20\x35\x70\x78\x3b\x0a\x20\ 40 | \x20\x20\x20\x62\x6f\x72\x64\x65\x72\x2d\x72\x61\x64\x69\x75\x73\ 41 | \x3a\x20\x34\x70\x78\x3b\x0a\x7d\ 42 | \x00\x00\x01\x9c\ 43 | \x89\ 44 | \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ 45 | \x00\x00\x1e\x00\x00\x00\x1e\x08\x04\x00\x00\x00\x91\x39\x66\x29\ 46 | \x00\x00\x00\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\ 47 | \x01\x55\x49\x44\x41\x54\x48\x0d\xdd\xc1\xb1\x4e\x53\x61\x18\x00\ 48 | \xd0\x33\x41\x42\xab\x93\xfe\x09\xa6\x4d\x6c\x24\x81\x87\x51\x76\ 49 | \xd3\x4d\x37\x54\x58\x94\x77\x31\x6e\x4e\x96\x42\x64\x69\x22\x8c\ 50 | \x96\x4e\x65\xbb\x03\xbc\x46\x71\x29\xd0\x9b\xcf\x34\xa4\x29\xf7\ 51 | \xd2\x96\xce\x9c\xe3\x29\x79\x69\xd5\x58\xcf\xc8\xa5\x3f\x3e\xab\ 52 | \x5b\x52\xdd\x50\xcb\x58\x57\x08\x21\xe4\x0e\xbc\xb6\x84\xaa\x13\ 53 | \xbb\xee\x54\x6c\x7a\xaf\xe5\x5a\xb8\xb2\x6d\x81\x8f\x0e\x55\xcc\ 54 | \xf2\xc6\x91\x90\xdb\x35\xd7\xa1\x5b\x0d\xf3\x7c\x95\xcb\x6d\x9b\ 55 | \x63\x4d\xc3\x22\xdf\x84\x2b\xaf\x3c\x50\x57\x55\xd6\x73\xa6\xe8\ 56 | \xb7\xf0\x43\x49\x32\x74\xaa\x2c\x84\xa2\x0d\xd7\x46\x6a\x0a\x56\ 57 | \xb4\xec\x2a\x0b\xa1\xec\x40\xf8\x64\x09\x21\x94\x35\x85\x8e\x25\ 58 | \x84\x50\xb6\x29\x5c\x2a\xe8\xe9\x7a\x28\x84\xb2\xaa\xf0\x4f\x41\ 59 | \xd7\x5f\x53\x67\xfa\x12\x42\x20\x39\xd7\x35\xf1\x5c\x18\x58\xe0\ 60 | \x5c\xc8\x24\x21\x24\x99\xd0\x37\xb1\x25\x5c\x58\x20\xc9\x84\x4c\ 61 | \x08\x99\x90\x49\x26\x9a\x42\x47\xc1\xaa\xb6\x3d\x53\x49\x26\x84\ 62 | \x10\x32\xc9\x54\x5b\xd8\x51\x90\x0c\x9d\xb8\x2f\xc9\x84\x90\x49\ 63 | \xa6\x36\xdc\xb8\x55\x53\x52\x57\x51\x94\xf4\xf5\x25\xf7\x1d\x0b\ 64 | \xdf\xcd\x54\xd1\xb0\xc8\xbe\x30\xb0\x6e\xa6\x23\x23\x0d\xf3\xec\ 65 | \xcb\xe5\xde\x99\xe3\x83\xb6\x35\xb3\x6c\x38\x16\x72\x5f\x3c\xe2\ 66 | \x99\x53\x7b\xee\x54\x6d\x69\x6a\xbb\x11\x06\xde\x7a\x54\xdd\xd0\ 67 | \x2f\x63\x3d\x21\x84\x90\xfb\x69\xdd\x52\x5e\x58\x31\xd6\x35\x72\ 68 | \xa1\x63\x47\xcd\xd3\xf1\x1f\xe6\xee\x88\x0e\x06\x6d\xb7\x94\x00\ 69 | \x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ 70 | \x00\x00\x02\xff\ 71 | \x89\ 72 | \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ 73 | \x00\x00\x1e\x00\x00\x00\x1e\x08\x06\x00\x00\x00\x3b\x30\xae\xa2\ 74 | \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ 75 | \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ 76 | \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ 77 | \xa8\x64\x00\x00\x02\x94\x49\x44\x41\x54\x48\x4b\xed\x96\xb9\xaa\ 78 | \x14\x41\x14\x40\x5b\x05\x0d\xdc\xf7\x05\x71\xf9\x04\x15\x31\x72\ 79 | \x01\x45\x34\x11\x31\x12\x14\x03\x11\x4d\x5c\x10\x0c\x8c\x14\xf4\ 80 | \x21\x08\x62\x20\x2e\xa1\xa2\xb8\x06\x1a\x18\xfb\x05\x8a\xbb\xb8\ 81 | \x04\xa2\x81\xb8\x04\xee\xe2\xbe\x9c\x33\x6f\x2e\x94\x3d\x5d\x3d\ 82 | \xf3\x74\xc2\x77\xe0\xc0\x74\x4d\xd5\xad\xee\xaa\xdb\xb7\xba\xe8\ 83 | \x27\xc3\x40\x1c\xdd\xfb\xb3\xc1\x18\x7c\x89\x87\x1b\x57\xbd\x0c\ 84 | \x40\xdb\xbb\x8a\x13\x7c\xc1\x39\x8d\xab\xa2\x58\x83\xbf\xf1\x39\ 85 | \x0e\xb3\x01\xec\xf3\x1d\x67\x35\xae\xba\xc4\x31\x74\xa2\x77\x78\ 86 | \x1b\x7f\x36\xaf\xf5\x35\xde\x49\xae\xe3\xe6\xfe\x89\x19\xb8\x1b\ 87 | \x27\xe0\x64\xbc\x87\x11\xb8\x9d\xfb\xd0\xad\x59\x86\xdb\xd0\x2d\ 88 | \xe8\x98\x5d\x68\x90\x4f\xe8\xf2\x95\x83\xab\x4f\xff\xb9\xd4\x16\ 89 | \xbe\x4d\x7e\x4f\xc1\x8e\x99\x88\x4e\x9a\x06\x53\x97\x75\x39\x0e\ 90 | \xc5\x60\x2a\x6e\xc2\x07\x58\xee\x7f\x1a\xfb\x84\xcb\xfb\x0d\x23\ 91 | \xc0\x33\x7c\xd5\xfc\xbd\x02\xab\x18\x84\xae\xd4\x0f\x8c\x71\x97\ 92 | \xb0\x2d\xee\x8b\x99\x79\x04\xef\x62\x0c\x7e\x8a\x33\x71\x43\xf3\ 93 | \xfa\x11\x0e\xc1\x1c\x1b\x31\xc6\xea\x19\x3c\x87\xb9\x1b\x2e\x46\ 94 | \xe1\x57\x4c\x07\xfd\xc2\x85\x28\xde\xd8\x0d\xb4\x7d\x87\x0d\x35\ 95 | \x1c\xc7\x34\x8e\x9e\xc4\x2c\x73\xd1\xa4\x89\xce\xe7\x31\x65\x01\ 96 | \xda\x6e\x9f\x49\x36\x64\x70\xab\xd2\x87\xb8\x8c\xf1\xbe\x67\xf1\ 97 | \x3d\x8d\x01\x26\x52\x99\x0b\xe8\x7f\x27\x70\xb0\x0d\x19\x2e\x62\ 98 | \xc4\xd9\x6a\x43\x0e\xcb\xdd\x5a\x4c\x8b\xc3\x48\x2c\x33\x1d\x23\ 99 | \xf1\xde\xe3\x2a\xb4\x68\x94\x3d\x84\x11\xe7\x2a\xce\x43\x13\xb0\ 100 | \x05\x6b\x6f\x74\x8c\xa0\x55\x8c\x47\xf7\x3e\xed\xdb\xa9\x3d\xd8\ 101 | \x82\x19\xfd\x02\xa3\x93\x4f\x65\x42\x95\x31\xa3\xd3\xfd\x33\xeb\ 102 | \xaf\x55\xf8\x04\xa3\x8f\x37\x7a\x0b\xad\x66\x95\x8c\xc0\x37\x18\ 103 | \x03\x5c\xd6\x32\x2b\xd1\xff\x2c\x26\x2e\x67\x5a\x4c\x52\xf6\x63\ 104 | \xc4\xb1\xd6\xb7\x25\x4d\xae\x72\x52\xf8\xb4\x8f\xd1\xff\xd6\xdb\ 105 | \x50\xc3\x4d\x8c\x38\xd6\xec\x2c\x16\x73\x97\x3b\x3a\xeb\x7d\x4c\ 106 | \x33\x77\x27\xda\x6e\xb2\x54\x6d\x43\xb0\x04\xd3\x38\x56\xbd\xa5\ 107 | \x58\x89\x07\x7d\xd5\x81\xb0\x07\xc5\xf7\xf6\x03\xba\x5f\xf3\x6d\ 108 | \xc8\x30\x1c\xad\x6e\xe5\x38\x67\x31\xcb\x6c\xf4\x55\xf0\x68\x8b\ 109 | \x01\x4e\xb4\x19\x7d\x6f\xdb\x05\xb0\x48\x5c\xc1\x18\x6b\x1e\x2c\ 110 | \x6a\x3a\x16\xdb\xe2\x32\xa6\x47\x9b\x7a\x03\x9e\x58\xd3\xb0\x8a\ 111 | \xc5\xf8\x10\xd3\x31\x07\xb1\x4f\xb8\x1f\x69\x80\xd0\xcf\x1f\x2b\ 112 | \xd2\x76\x5c\x8d\xeb\x70\x2f\x5e\xc7\xaa\xfe\x1e\x95\x75\x07\x4a\ 113 | \x0b\x66\xb3\x03\x3d\x59\x3c\xda\xca\x01\xeb\x74\x79\x0f\xa0\x93\ 114 | \x7e\xc4\x71\xd8\x31\x66\x78\xfa\xe5\xe0\x0d\x54\x4d\x52\xa5\x19\ 115 | \x2d\x3e\x69\x47\xfb\x5a\x47\x4c\xec\x29\xb3\x05\xad\x4c\x5e\xbb\ 116 | \xef\x47\x9b\x6d\xf1\xa1\x60\x22\x75\x0d\x0f\xf1\x53\xe8\xab\x22\ 117 | \x1e\x9f\x4e\xe2\xde\x06\x26\x97\x19\xff\xdf\x4f\x59\x87\xa7\x8c\ 118 | \x49\x95\xad\xbd\xfd\xfc\x4d\x51\xfc\x01\x31\xd8\x1b\xd7\xca\x0d\ 119 | \xd7\x3d\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ 120 | \x00\x00\x03\xba\ 121 | \x89\ 122 | \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ 123 | \x00\x00\x32\x00\x00\x00\x32\x08\x06\x00\x00\x00\x1e\x3f\x88\xb1\ 124 | \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ 125 | \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ 126 | \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ 127 | \xa8\x64\x00\x00\x03\x4f\x49\x44\x41\x54\x68\x43\xed\x98\x59\x88\ 128 | \x4e\x61\x18\x80\xc7\xbe\x64\x8f\x42\x96\x92\x28\x45\xa1\x2c\x17\ 129 | \x12\x17\x4a\x96\x0b\xcb\x95\x44\xb6\x1b\x94\x2c\x21\x4b\xc9\x5a\ 130 | \x6e\x44\x28\xcb\x85\x70\x23\x14\x8a\x2c\x17\x44\x09\x49\x49\xc2\ 131 | \x05\x21\x4b\x91\x25\x4b\x76\x9e\xe7\xcc\x7f\xa6\x63\x32\x33\xe6\ 132 | \x9c\xcf\xcc\x8f\xf3\xd4\xd3\xfc\xef\x37\xff\xff\x9d\xef\x3d\xe7\ 133 | \x3b\xdf\x56\x92\x93\x93\x93\x93\xf3\x2f\xd0\x18\x1b\x94\x7e\xfc\ 134 | \xbb\x18\x84\x1b\xf0\x1a\xbe\xc0\xef\x05\x5f\xe2\x19\x5c\x80\x1d\ 135 | \xb0\x68\x19\x80\x36\x34\x6e\x78\xec\x5b\x7c\x5f\xae\xec\x03\x6e\ 136 | \xc2\x16\x58\x54\xcc\xc7\x2f\x68\x23\x1f\xe3\x3a\x1c\x8c\xcd\x30\ 137 | \xa6\x2d\x8e\xc5\xfd\xf8\x19\xfd\xee\x03\xec\x8f\x45\xc1\x16\xb4\ 138 | \x51\x9f\x70\x39\x36\xc1\xaa\xe8\x8e\xa7\xd1\xdf\xf9\xc4\x86\x62\ 139 | \xad\x32\x07\x6d\x8c\xef\xc2\x30\x0b\xaa\x41\x1d\xdc\x88\xfe\xfe\ 140 | \x15\xf6\xc4\x98\x56\xd8\xa8\xf4\xe3\x9f\xc7\x0b\xfb\x14\x3e\xe2\ 141 | \x70\x0b\x52\xb2\x0d\x4d\xe6\x16\x9e\xc3\xe4\xfb\xf4\x1c\x8f\xe2\ 142 | \x34\xfc\x9d\x27\x9d\x0a\x2f\xe0\xc5\xec\x4e\x59\x70\x68\xbe\x8a\ 143 | \x71\xe3\xbd\x39\x4f\xf1\x5d\xa2\x4c\x7d\xf7\x4c\x28\x28\xdd\xf0\ 144 | \x1b\x3e\xc2\xa6\x16\x64\xa4\x1f\x2e\xc3\xbe\x58\xd7\x82\x02\xed\ 145 | \x70\x32\x9e\xc5\x38\xa1\xbd\xe8\xdc\x14\x84\x25\x68\xa5\x8e\x4e\ 146 | \x35\xc5\x28\x7c\x86\x5e\xf7\x08\xd6\xc3\xcc\x1c\x47\x2b\x74\xf2\ 147 | \xab\x49\xec\x09\xf7\xd0\x6b\xaf\xb6\x20\x2b\x77\xd0\xca\x9a\x47\ 148 | \x51\xcd\xd2\x1b\xdf\xa0\xef\x52\x0f\x0b\xd2\xd0\x05\x17\xa3\x95\ 149 | \xf8\x32\xa6\xa1\x21\x7a\x67\x3b\x46\x51\x3a\x56\xa2\x37\x72\x5f\ 150 | \x14\x55\x83\x36\xb8\x15\xe3\x19\x59\x1f\x62\x1a\xbc\xa3\xfe\xfe\ 151 | \x7c\x14\xa5\xc3\x15\x83\x4f\xc5\xc9\xf4\xb7\x07\x1b\x47\x92\xbb\ 152 | \xe8\xc5\x9d\xb8\xd6\x14\xca\x9c\xd0\xd2\x10\x22\x11\x39\x84\xd6\ 153 | \x33\x32\x8a\xaa\xc0\x8b\xda\x78\x7f\x70\x10\x5b\x63\x56\x42\x25\ 154 | \x62\x17\xb7\x9e\xb9\x51\x54\x09\x2d\xf1\x3e\xfa\x65\x57\xaa\xa1\ 155 | \x08\x95\xc8\x14\xb4\x1e\xe7\x98\x5e\x16\x54\xc4\x76\xf4\x8b\x27\ 156 | \x30\x6d\x37\xfa\x15\xa1\x12\x99\x80\xd6\xa3\x4e\xce\x3b\x30\xb9\ 157 | \xda\x8e\x70\xe3\xe3\x1a\x4a\x3b\x59\x10\x90\x50\x89\x48\x67\x74\ 158 | \xa3\x16\x4f\x94\x6e\xe6\x5c\x0d\x94\xb1\x14\xfd\xc7\xae\x28\x0a\ 159 | \x4b\xc8\x44\x62\x6c\xfc\x05\xb4\xde\x8b\x58\x1f\x23\x4e\xa1\x85\ 160 | \x63\xa2\x28\x3d\x0e\x0e\x87\xb1\x6b\x14\x95\x52\x51\x22\xb3\xd0\ 161 | \x1b\x98\x16\xdf\xe9\x9b\x68\xdd\x8b\x2c\x10\x17\x83\x16\xb4\x8f\ 162 | \xa2\xf4\xec\x46\xeb\x71\x59\x11\x27\xf3\xab\x44\x4c\xc2\x7e\x6e\ 163 | \x79\x96\x65\xcf\x10\xb4\x0e\x57\xcf\x4e\xbc\xd1\xbb\x61\x41\xd9\ 164 | \x23\x4a\x89\x77\xe9\x0a\x26\x93\x29\x9f\x48\x32\x89\xb2\x3b\x99\ 165 | \x81\x4b\x68\x5d\x23\x0c\xdc\xd0\x18\xd8\x90\xac\x94\x4f\x66\x74\ 166 | \xe1\xb3\x89\x84\x4e\x42\x5c\x91\x5b\xdf\x0a\x83\xeb\x85\xa0\x8f\ 167 | \x41\x00\x92\xc9\xbc\x4e\xfc\x0d\x9d\x84\xcc\x46\xeb\xdc\x6c\xb0\ 168 | \xb3\x10\x84\xbc\x80\xc9\x5c\x46\xeb\x4d\x1a\xf2\x1a\x62\x7d\xd6\ 169 | \xbb\xd6\x20\x7e\xfc\x37\x30\xc8\x06\xa6\x40\xf9\x64\x42\x27\x21\ 170 | \xae\x88\xad\x7b\xa6\x81\x2f\xf9\xed\x42\xc1\x74\x0b\x02\x62\x32\ 171 | \x8e\xf5\x0b\xa3\x28\x2c\x1e\xf8\x79\xa2\x63\x97\x75\xcb\x11\x31\ 172 | \x0e\x4d\xc4\x7d\x87\x23\x4d\x48\xb2\x8e\x86\x15\xe1\x51\xad\x6d\ 173 | \x3e\x19\x45\x09\xe2\xf5\xd6\x13\xf4\xe4\xb0\x98\x19\x8f\x3e\x09\ 174 | \x4f\x3c\xdd\x66\xfc\x84\x93\x8a\x1b\x7d\x93\xf1\xbc\xd6\xa1\xcd\ 175 | \xc3\xb3\x62\xc2\xf3\xae\x55\xf8\x15\x6d\x67\x85\xcb\x7a\x8f\x67\ 176 | \xd6\x63\xfc\x45\xbb\x9a\xcb\x0e\xfb\xf8\x24\x9c\x58\x4b\xce\x40\ 177 | \x0f\xf7\xec\x2d\xb6\xcb\x27\x31\x0f\xab\xc4\x39\xe5\x18\xc6\x87\ 178 | \xd5\xc5\xa4\x93\xeb\x40\xac\x16\x9e\xaa\x4f\x45\xbb\xd9\x1e\x3c\ 179 | \x50\x4b\x7a\x86\xe0\xf0\x5d\xe9\xa6\x2a\x27\x27\x27\x27\x27\xe7\ 180 | \x3f\xa7\xa4\xe4\x07\x3e\x39\x36\x55\xdc\x11\x33\x1e\x00\x00\x00\ 181 | \x00\x49\x45\x4e\x44\xae\x42\x60\x82\ 182 | \x00\x00\x01\xfe\ 183 | \x89\ 184 | \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ 185 | \x00\x00\x1e\x00\x00\x00\x1e\x08\x06\x00\x00\x00\x3b\x30\xae\xa2\ 186 | \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ 187 | \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ 188 | \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ 189 | \xa8\x64\x00\x00\x01\x93\x49\x44\x41\x54\x48\x4b\xed\xd6\xbf\x2b\ 190 | \x46\x51\x1c\xc7\xf1\x8b\x49\x61\x62\xb5\x30\x29\x25\x64\x52\xb2\ 191 | \x5b\x59\x64\x42\xfc\x05\x58\x15\x7f\x80\x95\x2c\x32\x98\x29\x13\ 192 | \xb3\x42\x29\x66\x56\x3f\x32\xf9\x91\xc8\xaf\xf7\xa7\xa8\xd3\xe9\ 193 | \x7b\xee\x39\xe7\x22\xcb\xf3\xa9\x57\x4f\x3d\xf7\x39\xdf\x6f\xcf\ 194 | \xbd\xe7\xc7\x2d\x6a\xc9\x48\xfd\xd7\xe7\x9f\xa7\x1f\xcb\x38\xc2\ 195 | \x25\xde\xf1\x84\x0b\x6c\x63\x1a\xad\xf8\xb5\xf4\x62\x1f\x1f\x09\ 196 | \x1e\xb1\x84\x16\xfc\x28\x73\x78\x85\xd5\xa4\x8c\xee\x42\x37\x2a\ 197 | \x65\x15\x56\xd1\x54\xf7\x18\x46\x56\xf4\x4f\xad\x62\xae\x07\xc4\ 198 | \xee\xc6\x0d\x3a\x91\x94\x3e\xc4\x0a\x5e\xa3\x09\x13\xce\x77\x21\ 199 | \x07\xa8\x43\x34\x7b\xb0\x0a\xb8\x4e\xa1\x0c\xc2\xba\xee\x1b\x45\ 200 | \x69\x06\x60\x0d\xf4\xe5\x36\x3e\x46\x69\xb4\x14\xac\x81\xbe\xdc\ 201 | \xc6\xd2\x8e\x60\x0e\x61\x0d\xf2\x55\x69\xac\x0d\x26\x98\x2b\xf8\ 202 | \x03\x34\x91\x74\xab\x5c\x5b\x50\x7a\xe0\x5f\x3b\xc1\x33\xfc\x3a\ 203 | \x8b\x30\xa3\xbd\xf7\x0d\xee\x8f\xb5\x64\x9a\x91\x9b\x59\xb8\x75\ 204 | \x64\x1d\xc1\x68\xef\x75\x7f\xac\x65\x35\x0e\x2d\x31\x57\x17\x14\ 205 | \x2d\x29\xff\x9a\x26\xa8\xee\x88\x5b\x47\x56\x10\xcc\x39\xfc\x01\ 206 | \x96\x2a\xcf\x78\x01\xc1\xec\xc0\x1a\xe4\xab\xd2\x78\x04\xc1\x4c\ 207 | \xc1\x1a\xe4\xcb\x6d\xac\xb9\xd2\x88\x60\xda\xa0\xa3\xcd\x1a\xec\ 208 | \xca\x6d\xbc\x89\x68\x52\x36\x91\x33\x28\x29\x8d\xb5\xb4\x3a\x10\ 209 | \x8d\x0e\xf1\xd8\x24\x7b\xc1\x0c\xac\xd9\xeb\xd3\x5b\x4b\x72\x74\ 210 | \x88\xdf\xc1\x2a\x94\x63\x17\x0d\xc8\xca\x10\x6e\x61\x15\x4c\xa1\ 211 | \xa6\x95\x5f\x81\xf4\x6c\x74\x9e\x5a\x85\x43\xf4\x4c\x35\x4f\xb2\ 212 | \xff\xa9\x1f\x1d\xe2\x63\xd0\x3e\x6c\x35\xfa\xa6\xd5\xb0\x81\xe4\ 213 | \x37\x8e\x9c\xe8\x68\x9b\x84\x36\xfc\x35\x68\x1b\x9c\x87\x36\x87\ 214 | \xd2\x75\x5a\xcb\x3f\xa5\x28\x3e\x01\xac\x7a\x83\x8a\x2e\x2a\xbf\ 215 | \x04\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ 216 | \x00\x00\x0c\x3d\ 217 | \x89\ 218 | \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\ 219 | \x00\x00\x64\x00\x00\x00\x64\x08\x06\x00\x00\x00\x70\xe2\x95\x54\ 220 | \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\ 221 | \x04\x67\x41\x4d\x41\x00\x00\xb1\x8f\x0b\xfc\x61\x05\x00\x00\x00\ 222 | \x09\x70\x48\x59\x73\x00\x00\x0e\xc3\x00\x00\x0e\xc3\x01\xc7\x6f\ 223 | \xa8\x64\x00\x00\x0b\xd2\x49\x44\x41\x54\x78\x5e\xed\x5d\x09\x74\ 224 | \x13\xc7\x19\x86\xf0\x7a\x5f\xef\xb5\x4d\x29\x3c\xac\xdd\x95\x64\ 225 | \x4b\x9a\x95\x25\xdb\xb2\x2c\x9b\x84\x18\x07\x5c\x82\x83\xc3\x11\ 226 | \xec\x00\x01\x13\x6e\x9c\x94\x00\xe1\x08\x79\x24\x84\xab\x24\x01\ 227 | \x17\x03\x01\x73\x84\x52\x8e\x42\x0c\x25\x34\x21\x05\xca\x19\x73\ 228 | \x25\x81\x50\xee\x26\x90\xe6\xe0\xa5\x09\x04\xd2\xd2\x14\x53\x1a\ 229 | \x8e\x32\xfd\xff\xd1\xc8\x01\x3c\x16\x92\x2c\xc9\x2b\x77\xbe\xf7\ 230 | \xbe\x67\xa4\x9d\x9d\xdd\xf9\xbf\xfd\xff\x99\x7f\x66\xb4\x34\x91\ 231 | \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\ 232 | \x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\ 233 | \x90\x90\x90\x90\x90\x90\x90\x90\x90\x48\x2c\xdc\x61\x32\x11\x92\ 234 | \xa4\xea\x25\x8a\x46\xa6\x98\x54\x52\xa1\xa8\xfa\x5c\x45\xd3\x27\ 235 | \x29\x66\xd2\x2f\xc9\xe2\xd0\xa1\x4c\x53\x7f\x51\x89\x98\x41\x55\ 236 | \x53\x6d\x26\x55\x9f\x09\x02\x9c\x05\xe3\xd3\xa0\x54\xc9\xdf\x40\ 237 | \xac\xe9\x66\x33\x31\xf1\xd3\x25\xa2\x85\x16\x29\x29\x3f\x05\x23\ 238 | \x2f\x04\xfe\x37\x60\x70\xb3\x37\x8f\xda\x7a\x0c\xa6\xce\xd1\x53\ 239 | \xa9\x73\xec\x73\x8c\xfa\x2f\x9f\xa1\xb6\x2e\x25\xd4\x9c\xd1\xa6\ 240 | \x46\x18\x10\xef\x0a\x08\x33\xcb\x6c\xf6\xfc\x88\x57\x27\x51\x1f\ 241 | \x98\xcc\xe4\x5e\x08\x47\x5f\x30\x03\x5b\xdd\xd4\x31\xe0\x09\x9a\ 242 | \xb9\x7c\x23\xcd\xd9\x75\xa2\x6e\xee\x7c\x8f\x66\xcc\x5f\x43\xed\ 243 | \xc5\x03\xa9\x6a\x49\xf5\x0b\xa3\xe9\x1f\xab\xaa\xc3\xc7\xab\x95\ 244 | \x88\x04\x60\xc4\x01\x10\x7a\xae\xa2\x41\x53\xba\xf4\xa1\xde\xb5\ 245 | \x55\x62\x01\x82\x30\x73\xe9\xeb\xd4\x7a\xf7\x7d\xdc\x63\xc8\xa5\ 246 | \x24\xcd\x99\xcf\xab\x97\x08\x07\x26\x8d\x3c\x0c\x46\xbc\xae\x98\ 247 | \x9d\xd4\x39\x72\x12\x7b\xea\x45\x06\x0f\x85\xd9\x3b\x8e\x51\xd2\ 248 | \x6f\x44\xc0\x53\xfe\x63\xd2\x1c\xed\xf9\x65\x24\x42\x81\xa6\x91\ 249 | \x2c\xe8\xbc\x2f\xa3\x01\x5d\xe3\xcb\x84\x46\x0e\x9b\x20\x68\x40\ 250 | \x14\x0c\x81\x49\x49\xb6\x96\xfc\x72\x12\xc1\x60\xb5\x5a\xbf\x05\ 251 | \x06\x3b\x8e\x86\x23\xa5\x4f\x89\x8d\x1b\x29\x41\x14\x5b\xd7\x92\ 252 | \x80\x28\xdb\xf9\x25\x25\x82\x01\xe2\xfc\x28\x34\x18\xc6\x7d\x0c\ 253 | \x35\x42\xc3\xd6\x83\x59\xaf\xbd\x49\x35\x67\x96\x5f\x14\xc5\xd1\ 254 | \x91\x5f\xb6\x16\x5a\xb5\x22\x3f\x86\xb0\xd9\x1d\x3c\x75\x2c\xfc\ 255 | \x2d\x87\x50\xb7\x00\xce\x99\x04\x7c\x94\x0f\x0e\xee\xf0\x97\x6c\ 256 | \xc4\x20\x84\x7c\x13\x1a\xfc\x19\x1a\x2b\x63\x5e\xa5\xd0\xa0\xd1\ 257 | \xa0\xeb\xd9\x59\xdc\x4b\xc8\x3b\xfc\xd2\x01\x34\x05\xc3\x3f\x08\ 258 | \x02\x54\x05\x06\x13\x41\x78\x1a\x13\xd2\x46\x9d\xe7\x24\xa9\xce\ 259 | \x62\x6c\xac\xb5\x6d\xa1\xd0\x90\xd1\xa2\x6f\xeb\x61\xaa\x3a\xbc\ 260 | \xcc\xb0\x9a\xe6\x74\xe1\xb5\x15\xc5\x9e\x0d\x22\x1c\x08\x18\x5c\ 261 | \xb5\x67\xb0\xf0\xe6\x1c\xfe\x2c\x75\x4d\x7a\x91\xba\x26\xbf\x48\ 262 | \x9d\xa3\xa6\x50\x47\xff\x91\xd4\xe2\x6b\x57\x23\x0c\x1b\x24\xa8\ 263 | \x7a\xd9\x9d\x84\x7c\x9f\x35\xa2\x31\x01\x1a\xb6\x04\x1b\xe9\x1a\ 264 | \x3f\x43\x68\xc8\x68\x12\xf3\x19\x66\x50\x95\x4c\x00\x0e\x0e\x0c\ 265 | \x22\xd0\xd8\xee\xa9\x15\x34\x7b\xdb\x51\xe1\x79\x01\x66\xbe\xbc\ 266 | \x85\x3a\x4a\x86\x51\xc5\xcc\xf3\x1c\x95\x1c\x81\x50\xa6\xf0\xa6\ 267 | \x34\x0e\xc0\xd3\x76\x0a\x1b\x87\x8d\x15\x19\x21\x9a\x4c\x9f\xb9\ 268 | \x8c\x3f\xe5\xe4\x12\xfb\x0b\x86\x45\x6f\xc8\xd9\x71\x5c\x58\xbe\ 269 | \x2e\x7a\x57\x6e\x86\xfe\xae\x63\x40\x94\xb3\xad\xb4\xd4\x54\xde\ 270 | \x9c\xc4\x86\xcd\x66\xfb\x01\x33\x8c\xd5\x55\xaf\x9c\x23\x54\x66\ 271 | \xbd\xb2\x8b\x0b\x82\xd7\x74\xd3\xf4\xf2\x65\xc2\x72\xa1\x30\x7b\ 272 | \xcb\xa1\xaf\x47\x6f\x1a\xf9\xd0\x6a\x4d\xbb\x93\x37\xcb\xd0\x68\ 273 | \xaa\xaa\xba\x5b\x31\x93\x9e\x8a\xe2\x1c\xaa\x28\xe4\x11\x93\x45\ 274 | \x6f\xad\x28\xca\xb7\xf1\xa0\xa2\xe8\x0e\x6c\x90\xd9\xd3\x46\xd8\ 275 | \xe8\xa8\x13\x44\xd7\x52\xb3\xc1\x33\x9c\x34\x6d\xfa\x62\x71\x99\ 276 | \x70\x08\x9e\x95\x9c\xdf\xcd\x2f\x8a\x4a\xb6\xb0\x16\x1b\x11\x2d\ 277 | \x5b\xda\x7f\x02\x37\x39\x19\xc2\xd1\x19\xff\x13\x74\x33\xc1\xcd\ 278 | \xbf\x84\x06\x2c\x4e\xd2\xf4\x42\xfc\x6c\xb9\xa7\x40\xdc\xe0\x18\ 279 | \xd0\xb3\xe4\x35\x36\xe7\x25\x3a\x16\x09\x7d\x1b\xf6\x53\xcd\xdd\ 280 | \xda\xdf\x36\xc5\xd1\x85\x9b\xc0\x38\xc0\x71\x3e\xc6\xd5\x80\xf1\ 281 | \x03\x33\xb4\xd8\xa1\xda\x7b\x95\x32\xe3\x07\x8e\x01\xaf\xfb\xcb\ 282 | \xb4\x15\x36\x36\x51\xe8\x9e\x38\x87\xb5\x07\x86\xce\xef\x36\x69\ 283 | \x52\xd4\x8c\x9b\xa2\xe1\xa1\xaa\xa4\x2f\xc4\xd3\x6b\x78\x73\x29\ 284 | \x05\x3d\xe0\x69\x5c\x2f\x6c\x80\xf7\xf7\x6f\x50\x7b\xcf\x21\x2c\ 285 | \x74\x30\x61\x92\xdd\x71\xe9\x43\x62\xc5\xec\xaa\x77\x61\xa4\x76\ 286 | \x2f\x6b\x0b\x8c\xba\xee\xe1\xe6\x68\x58\xa8\xaa\x33\x37\x20\x06\ 287 | \x8e\xdd\x43\x31\x70\x5a\xd9\x12\x10\x23\x8d\x75\xb0\xe1\x8e\x74\ 288 | \x8c\x46\x5c\x97\x61\x5e\xa2\xea\xbf\xe6\x26\x69\x38\x60\x27\x0d\ 289 | \xfd\xc5\xc7\x4c\x8c\x61\x4f\x0b\x6f\xb8\x2e\x7a\x5f\xde\x4a\x3d\ 290 | \xcb\xfe\x28\x3c\x96\x48\xc4\x68\xc0\xbc\x5d\xd5\x8f\x71\xb3\x34\ 291 | \x1c\xd8\x08\x0a\x6e\xc6\xda\xb6\x13\x73\x5f\xd1\x0d\x47\x4c\xf0\ 292 | \x34\xcc\x51\xdc\x53\xe7\xb1\x15\x42\xfc\x2b\x2c\xd7\xc0\xc4\x61\ 293 | \x30\xf7\x90\x0b\xdc\x2c\x0d\x07\x18\x31\xed\xc5\x9b\x49\x9f\xf1\ 294 | \x1b\xe1\xcd\x46\x44\x10\x16\xa7\x2f\xcc\x39\xf9\xd4\xd2\x3a\x9f\ 295 | \x65\xc8\xe8\x7d\xae\x67\x66\x8a\xcb\x1b\x80\x9a\xdd\xc3\x44\x89\ 296 | \xe3\x94\x4a\x51\x33\xc8\x25\xd2\x70\x21\x29\x49\xd5\x87\xe3\x2c\ 297 | \x2d\x84\xaa\x01\xac\xef\x80\xe4\x2e\x7b\x7b\xf0\xa9\x87\x50\x89\ 298 | \x43\xc9\xe4\xfb\x8a\x69\x72\x41\x71\x9d\x03\x03\x23\xd2\x9c\x7e\ 299 | \x37\x13\x24\xe6\x93\x8f\xb8\xfd\x06\x2e\xb4\xb0\x66\x9d\x5b\x40\ 300 | \x9c\x3a\x17\xdd\x64\xb8\xf4\x6d\xfa\x33\xb5\x40\x5d\xf5\x5d\x31\ 301 | \x8c\x1b\xe1\x1e\x7d\xaf\xee\xa1\xde\xb5\x3b\xa9\xca\x3d\x04\x22\ 302 | \xc6\x51\x78\x48\x47\x62\x3e\xc6\x4d\x18\x1d\x98\xcd\xae\x9f\x41\ 303 | \x4c\x5c\x05\x17\x61\xf9\x02\x12\x27\xe2\x70\x23\x81\x5e\xfa\x14\ 304 | \x0b\x21\x98\x5b\x24\xe7\x77\xa5\xee\x49\x73\xc4\x37\x1c\x26\x1d\ 305 | \x0f\x97\x52\x7d\xc8\x18\xe1\x31\x23\x11\x97\x0a\x6c\x45\xfd\xa9\ 306 | \x4a\xbc\x6c\xc7\x0b\x0e\x7b\x71\xb6\xb8\x66\x28\x0f\x84\x48\xf2\ 307 | \x4f\x93\xea\x7c\x3c\x2a\xb9\x89\xa2\xe8\x6d\x21\xc1\x3b\x87\x15\ 308 | \xe3\x85\xc8\xe0\x31\x6c\x44\x24\xba\xb9\x68\x31\x13\xea\xd7\x3c\ 309 | \xb9\x51\x0b\x7d\xb1\xa0\x6f\xe3\x3b\xd4\xd6\xbd\x3f\x8b\x08\x38\ 310 | \xc0\xc0\xcf\x37\x1e\xcf\x02\x6f\xd1\x07\x8e\xae\x09\x5f\x8c\x2a\ 311 | \xd9\x82\x8b\x5f\xdc\xb4\xe1\x03\x94\xc5\x55\xb3\xaf\xb0\xb2\x94\ 312 | \xc2\x5e\x34\x6b\xdd\xee\x9b\x2e\x1a\x2b\x32\xaf\x7b\x62\xb2\xf0\ 313 | \x98\x11\x88\xe1\x14\x47\x92\x6c\x99\x39\x84\xbc\x29\x7d\xee\x4a\ 314 | \xaa\xb9\x72\xfc\xde\xa2\xea\x27\x23\x9a\x80\xc4\xa5\x4a\x5c\x80\ 315 | \xc1\x4a\xc8\xd0\x71\x6c\xb4\x23\xba\x58\x7d\xe8\x79\xe9\x15\x48\ 316 | \xa4\xc6\x53\xfb\x43\x83\x58\x78\x4a\x9b\xfe\x12\x6b\x20\x66\xf7\ 317 | \xd1\x9c\x67\x8a\x36\x1d\xbd\x1f\xa5\x64\xd0\x68\xe1\xb1\xba\xe8\ 318 | \x7b\xfd\x6d\x6a\x6d\xc3\xa7\xea\x35\xfd\xed\x16\x2d\x3c\xdf\xe5\ 319 | \xa6\xbe\x3d\x70\xa7\x1f\x74\x46\x9f\x32\x31\xc2\xbc\x70\x28\xc4\ 320 | \x9b\x4b\xb9\xbf\x27\xbb\xc1\xd4\x31\xd3\x58\xa6\x8e\x4b\xab\xb6\ 321 | \x07\xfb\xb1\x39\x2f\x2d\xd5\x47\x33\x57\xc5\x7e\x5d\x24\x12\x66\ 322 | \xfe\xee\x4f\xfe\x70\xba\xf5\x88\xf0\x78\x30\xe2\xa8\x31\xb0\xe2\ 323 | \x08\x9e\x52\xc6\xcd\x7d\x7b\xc0\x28\x0a\x37\x2f\xd3\x94\x0e\x45\ 324 | \x51\x4f\xf0\x70\x09\x15\x85\xc0\x6d\xa0\x22\xaf\xcb\x58\xbc\x8e\ 325 | \x4d\x8d\x67\xae\xb8\xcd\x0e\xc5\x06\x22\x4e\x8d\xe8\xc3\x27\x08\ 326 | \x8f\x85\x42\xcf\xb2\x0d\x6c\xdd\x87\xa5\x08\x8a\x23\x9d\x9b\xbc\ 327 | \x6e\x98\x4c\xf6\x16\xa0\xde\x65\xdc\x86\xe9\xad\xdc\x26\xac\xb4\ 328 | \x3e\xc4\x61\xac\xa3\xcf\x63\xc2\x63\x01\x66\x6f\x35\x6e\x67\x8e\ 329 | \x0f\x0a\x3e\xe9\xa2\x63\xa1\x12\xbb\x00\x7c\xe0\x41\x94\xd5\xdc\ 330 | \xec\x75\x03\x0a\x4e\xc4\xc2\x18\xd7\x45\x95\x85\xc3\x8c\x85\x6b\ 331 | \xa1\x6f\x18\x4b\x93\x3b\x74\xa7\xd6\xbc\x42\x96\xe0\x69\xa9\x39\ 332 | \xd0\xa8\x4d\xc2\xf2\xff\x2f\xc4\xed\x48\x38\x91\x0a\xa3\xae\xab\ 333 | \x90\x60\xff\x9c\x9b\x5e\x0c\xe8\x70\x0e\xa2\x20\x19\xf3\x57\x0b\ 334 | \x2b\x0b\x85\x38\x6c\xb5\xb6\xeb\xe2\xef\x23\xc6\xbd\x40\x3d\x8b\ 335 | \xd6\xb1\x4d\xd2\x19\x15\xab\xa9\x7b\xda\x82\xe8\x26\x7a\x38\xc7\ 336 | \x85\x4f\xed\xc6\xfa\x3d\xb5\xf1\x26\xee\xd4\x47\x3b\x9b\x34\x67\ 337 | \x6f\x6e\xfa\xda\xf0\x77\xe6\xfa\x75\xcc\x37\x22\xdd\xa8\xc6\xfa\ 338 | \x00\x77\x6b\xff\xe4\x5f\xac\x33\x6c\xe8\x83\x52\x3a\xf7\xa6\xb6\ 339 | \xbb\xda\x53\x73\xda\x5d\x6c\x73\xb5\xb0\x9c\x01\x89\xdb\x64\x51\ 340 | \x10\xe0\x22\x6e\xfe\xda\xc0\xf9\x29\x2c\x14\xe9\x52\x2a\x8e\x9e\ 341 | \x34\x30\x4c\x7d\xbc\x2b\x1c\xa2\xc7\xe9\x79\x05\xf4\x4a\xf5\x1a\ 342 | \x3a\xe4\xf9\xa7\x29\xe9\x3f\x42\x58\xce\x88\xc4\x61\x3d\x13\x44\ 343 | \xd5\xb7\x72\xf3\xd7\x06\x6e\xd7\xc7\x42\x29\x9d\x7a\x0a\x2b\xb9\ 344 | \x1d\x09\x64\xa6\x98\x57\x88\x8e\xc5\x82\x98\xb7\xe4\x3d\x32\x80\ 345 | \x5e\xbf\xb4\x86\x4e\xfe\xed\x73\xd4\xde\x73\xa8\xb0\x9c\x11\x89\ 346 | \xfd\x28\xda\x1a\x92\xef\xc3\xdc\xfc\xb5\xa1\x28\xce\x1c\x2c\x84\ 347 | \x3b\x28\x44\x95\x04\x23\x0e\x8f\x71\x3e\x27\x6b\xfd\x5b\xc2\xe3\ 348 | \xb1\x60\x22\x0b\xc2\x86\xbf\xcc\x43\x6a\x6d\x69\xfd\x1a\x7c\x36\ 349 | \x97\xcd\xb2\x8a\x2a\x09\x46\x5c\x1f\xc7\xa4\x47\x74\x2c\x56\x4c\ 350 | \x64\x41\xd2\x67\x2d\xe7\x1e\xa2\xbf\xca\xcd\x5f\x1b\xcd\x9b\xbb\ 351 | \xbe\xe7\xcf\x41\x9c\x34\x7b\xf3\x41\x61\x45\x75\x72\xc7\xf1\xb8\ 352 | \x2f\xc1\x26\xb2\x20\xa9\x4f\x4e\xf3\x7b\x88\xa6\xcf\xe1\xe6\x17\ 353 | \x03\x62\x5a\x15\x16\x4c\x9f\xbd\x42\x58\x91\x91\x98\xc8\x82\x58\ 354 | \xdb\x77\x65\x82\xe0\x9e\x34\x6e\x7a\x31\x92\x54\xf2\x24\x16\xc4\ 355 | \xed\x91\xa2\x8a\x8c\xc4\x44\x15\xc4\xbb\x7a\x87\x7f\xa3\xb6\xaa\ 356 | \x57\xb7\x6a\x95\xfd\x1d\x6e\x7a\x31\xd8\x8f\x55\x54\xfd\x02\x2e\ 357 | \xb0\x60\xc7\x23\xaa\xd0\x28\x4c\x54\x41\xec\x45\x03\x78\xff\x41\ 358 | \xe6\x73\xb3\x07\x87\x49\x25\xbf\xc2\x13\xd8\xee\x11\x03\xcf\x2b\ 359 | \x25\xa2\x20\xe9\x73\x56\xfa\x57\x13\xc1\x3b\x34\xcd\xd9\x9c\x9b\ 360 | \x3c\x38\xac\x56\xeb\x0f\x41\xbd\xf7\x50\x14\x5c\x4a\x35\xea\x9a\ 361 | \x76\xa2\x09\x82\x93\xb5\x2a\xc9\xe4\x9d\x39\x19\xc9\xcd\x1d\x1a\ 362 | \x70\x47\x3a\x0b\x5d\x70\x32\x6e\xf7\x34\xe2\x72\x6a\x22\x09\x82\ 363 | \x9b\xbb\x03\x9b\xb1\xc1\xae\x2b\xc1\xc4\xe1\xbf\x8f\x85\xbd\x49\ 364 | \x41\x23\xe7\x59\xf8\xca\x2b\x34\xdc\x56\x9c\x44\x10\x04\x7f\x8d\ 365 | \xc5\x86\xb8\xb8\x55\x16\xc5\xd0\xf4\x4d\x61\xad\x16\xde\x0a\xc8\ 366 | \xde\xed\x90\x4d\x7e\xc0\xdc\x0c\x62\x1f\x2e\xec\xa7\xcd\x5c\x1a\ 367 | \xd1\x8a\x59\xb4\x69\x64\x41\xbc\xab\xb7\x33\x21\xcc\x99\xb9\x3c\ 368 | \x44\x31\xcf\x28\x8b\xca\xce\x13\xdc\x81\x07\x15\x4e\xc6\x8e\x28\ 369 | \x50\x39\xce\xe7\x5b\x5a\xff\x82\x6d\x64\xc3\xb5\x93\x78\x13\x67\ 370 | \x95\x45\x82\xe0\xee\x0f\x7b\x9f\xc7\x84\xe7\xc4\x9a\xb6\x6e\x7d\ 371 | \x59\x24\xc1\x09\xd6\x1a\x3b\x21\x55\xb2\x1f\x77\xef\x70\x73\x46\ 372 | \x0f\xb8\x98\x02\x21\x6c\x14\x08\xb3\x8b\x2d\x41\xde\x78\xd1\x38\ 373 | \xd3\x9a\xf7\xc0\x46\x7b\xc9\xb0\xd9\xf7\x76\x2e\x3c\x81\x82\xcc\ 374 | \x5a\x38\xee\x84\x25\x27\x7f\x37\x19\x36\x61\x8a\xa8\x7c\xbc\xc9\ 375 | \x7e\x1b\xa3\x92\xc5\x8a\xd9\x51\x00\xa6\x8b\xfd\xfb\xbb\x70\x9a\ 376 | \x05\xdf\x61\x85\xbf\x83\x80\x11\x59\x51\x3c\x89\xbf\x50\xc2\xeb\ 377 | \xc3\x43\xd1\xab\x5d\xbb\xdc\xbd\x28\xc8\xfc\xb9\xc3\xf7\xf2\xce\ 378 | \x92\xf5\x7d\xa2\xf3\x62\x4d\xcc\xba\x4d\x96\x54\x0f\x2e\x83\xc3\ 379 | \x6d\x34\xfe\x97\x0b\xdc\x8a\xba\x04\x91\x68\x20\x48\x41\x0c\x06\ 380 | \x29\x88\xc1\x20\x05\x31\x18\xa4\x20\x06\x83\x14\xc4\x60\x90\x82\ 381 | \x34\x00\xf0\x35\x1c\x60\xf8\x8f\x70\x0e\xe8\xd6\x57\xf3\x85\x22\ 382 | \x08\x7c\x37\x06\xca\x5d\x84\xf3\x0f\x35\xba\x37\xf6\xc4\x1b\x6c\ 383 | \xe3\x9e\xaa\x57\x1f\xdc\x5f\x7e\xb2\x74\x68\xd1\x1b\xf0\xef\xe3\ 384 | \x37\xfe\xb6\xe2\x76\x82\xc0\xe7\xd1\x29\x36\xf7\x5f\x3f\xfb\x68\ 385 | \xf1\xe7\x7d\x4b\x3a\x57\x41\xf6\x5c\xc1\x0f\x49\x44\x02\x34\xbe\ 386 | \xa2\xe9\xd5\x7f\x3f\xbd\xf4\x3c\x1a\x7d\x60\xff\x6e\x20\x0a\x39\ 387 | \x10\x78\x31\x72\x30\x41\xf0\x6d\x12\x9a\xc5\xf9\xe9\x27\x1f\x2c\ 388 | \x3a\x8d\xc7\x7b\xf4\xb8\x7f\x27\x64\xd3\xe5\xfc\xb0\x44\xa4\x00\ 389 | \x23\x4f\xd3\x53\x33\x8e\x5d\x38\xb7\xbc\x1a\x0d\x5b\x5c\xdc\xb1\ 390 | \x0a\x3c\x65\x37\x4e\x61\xd7\x25\x48\x92\x4a\x1e\x50\x2d\xfa\xd9\ 391 | \x93\xc7\xe6\x9d\xc2\x63\x15\x73\x1f\xdf\x03\xde\xf1\x09\x9f\xd2\ 392 | \x90\xa8\x27\x9a\x82\x97\x2c\x74\xa7\x7b\x0e\x5f\xfc\xc7\xca\x7f\ 393 | \x5f\xbb\x58\x79\xbd\x63\xc7\x76\xbb\xe1\x69\xdf\x8c\xfd\xcb\xad\ 394 | \x82\xf8\xd7\x70\xf4\xb3\xfb\xf6\x94\xfd\x05\xbf\x5f\xb5\x62\xdc\ 395 | \x3e\xd5\xac\x9f\xc6\x3d\x67\xbc\x3e\x89\x28\x00\x5f\x50\xb9\xd4\ 396 | \xe3\xf5\x1d\xf8\xea\xcb\x55\x97\xaf\x5c\xa8\xbc\x96\x9b\xdb\xe6\ 397 | \x4d\xf8\xee\xcc\x4d\x82\x40\xc7\xad\x98\xc9\xe7\x1b\xd6\x4f\x3c\ 398 | \x88\xdf\x6d\xdb\x38\xe5\x28\x88\x73\x0e\x7f\x9e\xc7\xeb\x91\x88\ 399 | \x1e\x8a\x9a\x81\x57\x54\xe6\xe4\xe4\xec\x43\x51\x90\x3e\x5f\xf6\ 400 | \xfe\x1b\x05\x81\x10\x76\x0d\x3d\x02\x3f\x1f\xda\x5f\xfe\x3e\x88\ 401 | \xf1\x05\x7a\x0c\xaf\x40\x22\xda\xf0\x78\x3c\xdf\x00\x2f\xf8\x03\ 402 | \x7a\x07\x7a\x09\x86\xb0\x19\xcf\x97\xee\x42\x01\x16\x54\x8c\xd8\ 403 | \x3b\xbb\x7c\x18\xfb\x37\xf6\x1d\xd8\x87\x28\x66\xbd\x1b\x3f\x55\ 404 | \x22\x56\xc0\xf7\xfb\x82\x28\x9b\x3a\x74\xc8\xdb\x73\xb5\xba\xf2\ 405 | \x1a\x0a\x80\x44\x71\xf0\x2f\x8e\xaa\xac\xc9\xa9\xa7\xa0\x13\x1f\ 406 | \xc4\x4f\x91\x88\x35\x70\x84\x05\xe1\x6b\x47\x61\xa7\xfc\x5d\xd8\ 407 | \xc9\x07\x44\x39\x7f\x66\xe9\xbf\x6c\x76\xd7\xfb\xd0\x8f\x8c\xe3\ 408 | \x45\x25\xe2\x05\xb6\x62\xa8\x92\xbd\x0f\x15\x17\x54\x05\x3c\x44\ 409 | \x77\x66\x1c\x87\x7e\xe4\x05\x5e\x44\x22\xde\xc0\x04\x11\xc2\xd7\ 410 | \x5b\x6e\x77\xe6\x11\x6b\x8a\xeb\x43\x1c\x1e\xc3\xd7\xf2\xff\x9f\ 411 | \x6a\x48\xb0\x77\xc6\x9b\x1d\x05\xf8\xba\x70\xfe\x95\x84\x84\x84\ 412 | \x84\x44\xac\xd1\xa4\xc9\xff\x00\x23\x9c\xd9\xdc\x50\x41\x8f\x29\ 413 | \x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82\ 414 | " 415 | 416 | qt_resource_name = b"\ 417 | \x00\x06\ 418 | \x07\x03\x7d\xc3\ 419 | \x00\x69\ 420 | \x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\ 421 | \x00\x06\ 422 | \x07\xae\xc3\xc3\ 423 | \x00\x74\ 424 | \x00\x68\x00\x65\x00\x6d\x00\x65\x00\x73\ 425 | \x00\x09\ 426 | \x0d\xf7\xbd\x43\ 427 | \x00\x6c\ 428 | \x00\x69\x00\x67\x00\x68\x00\x74\x00\x2e\x00\x71\x00\x73\x00\x73\ 429 | \x00\x14\ 430 | \x0e\xfe\x40\xa7\ 431 | \x00\x6c\ 432 | \x00\x6f\x00\x61\x00\x64\x00\x2d\x00\x63\x00\x6f\x00\x6e\x00\x74\x00\x65\x00\x78\x00\x74\x00\x73\x00\x2d\x00\x34\x00\x38\x00\x2e\ 433 | \x00\x70\x00\x6e\x00\x67\ 434 | \x00\x10\ 435 | \x09\x9c\x5d\x67\ 436 | \x00\x63\ 437 | \x00\x6f\x00\x6e\x00\x66\x00\x69\x00\x67\x00\x75\x00\x72\x00\x65\x00\x2d\x00\x34\x00\x38\x00\x2e\x00\x70\x00\x6e\x00\x67\ 438 | \x00\x18\ 439 | \x02\xe2\xc4\x47\ 440 | \x00\x64\ 441 | \x00\x6f\x00\x77\x00\x6e\x00\x6c\x00\x6f\x00\x61\x00\x64\x00\x2d\x00\x64\x00\x69\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\ 442 | \x00\x2d\x00\x34\x00\x38\x00\x2e\x00\x70\x00\x6e\x00\x67\ 443 | \x00\x0b\ 444 | \x00\x95\x2f\x87\ 445 | \x00\x70\ 446 | \x00\x6c\x00\x75\x00\x73\x00\x2d\x00\x34\x00\x38\x00\x2e\x00\x70\x00\x6e\x00\x67\ 447 | \x00\x0f\ 448 | \x0c\xdc\x54\x07\ 449 | \x00\x64\ 450 | \x00\x6f\x00\x77\x00\x6e\x00\x6c\x00\x6f\x00\x61\x00\x64\x00\x2d\x00\x34\x00\x38\x00\x2e\x00\x70\x00\x6e\x00\x67\ 451 | " 452 | 453 | qt_resource_struct_v1 = b"\ 454 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ 455 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x04\ 456 | \x00\x00\x00\x12\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ 457 | \x00\x00\x00\x24\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ 458 | \x00\x00\x00\xc6\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x1e\ 459 | \x00\x00\x00\x90\x00\x00\x00\x00\x00\x01\x00\x00\x06\x60\ 460 | \x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x5d\ 461 | \x00\x00\x00\xe2\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x20\ 462 | \x00\x00\x00\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x01\xbd\ 463 | " 464 | 465 | qt_resource_struct_v2 = b"\ 466 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x01\ 467 | \x00\x00\x00\x00\x00\x00\x00\x00\ 468 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x05\x00\x00\x00\x04\ 469 | \x00\x00\x00\x00\x00\x00\x00\x00\ 470 | \x00\x00\x00\x12\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\ 471 | \x00\x00\x00\x00\x00\x00\x00\x00\ 472 | \x00\x00\x00\x24\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ 473 | \x00\x00\x01\x6c\x75\x74\x6a\xc4\ 474 | \x00\x00\x00\xc6\x00\x00\x00\x00\x00\x01\x00\x00\x0a\x1e\ 475 | \x00\x00\x01\x6c\x75\x80\xe1\x33\ 476 | \x00\x00\x00\x90\x00\x00\x00\x00\x00\x01\x00\x00\x06\x60\ 477 | \x00\x00\x01\x69\xbe\x37\xba\x08\ 478 | \x00\x00\x00\x6a\x00\x00\x00\x00\x00\x01\x00\x00\x03\x5d\ 479 | \x00\x00\x01\x69\xbe\x37\xba\x08\ 480 | \x00\x00\x00\xe2\x00\x00\x00\x00\x00\x01\x00\x00\x0c\x20\ 481 | \x00\x00\x01\x69\xbe\x37\xba\x08\ 482 | \x00\x00\x00\x3c\x00\x00\x00\x00\x00\x01\x00\x00\x01\xbd\ 483 | \x00\x00\x01\x6c\xd7\x28\x19\xa5\ 484 | " 485 | 486 | qt_version = [int(v) for v in QtCore.qVersion().split('.')] 487 | if qt_version < [5, 8, 0]: 488 | rcc_version = 1 489 | qt_resource_struct = qt_resource_struct_v1 490 | else: 491 | rcc_version = 2 492 | qt_resource_struct = qt_resource_struct_v2 493 | 494 | def qInitResources(): 495 | QtCore.qRegisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) 496 | 497 | def qCleanupResources(): 498 | QtCore.qUnregisterResourceData(rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data) 499 | 500 | qInitResources() 501 | -------------------------------------------------------------------------------- /kuberider/settings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/settings/__init__.py -------------------------------------------------------------------------------- /kuberider/settings/app_settings.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import logging.handlers 3 | from pathlib import Path 4 | from typing import Any, Union 5 | 6 | from PyQt5.QtCore import QSettings, QStandardPaths 7 | from PyQt5.QtWidgets import qApp 8 | 9 | from kuberider.entities.data_manager import DataManager 10 | from kuberider.events.signals import AppCommands 11 | from ..core import str_to_bool 12 | 13 | 14 | class AppSettings: 15 | 16 | def __init__(self): 17 | self.settings: QSettings = None 18 | self.app_name: str = None 19 | self.app_dir: Union[Path, Any] = None 20 | self.docs_location: Path = Path(QStandardPaths.writableLocation(QStandardPaths.DocumentsLocation)) 21 | self.data: DataManager = None 22 | self.commands: AppCommands = AppCommands() 23 | 24 | def init(self): 25 | self.app_name = qApp.applicationName().lower() 26 | self.app_dir = Path(QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation)) 27 | self.app_dir.mkdir(exist_ok=True) 28 | settings_file = f"{self.app_name}.ini" 29 | self.settings = QSettings(self.app_dir.joinpath(settings_file).as_posix(), QSettings.IniFormat) 30 | self.settings.sync() 31 | self.data = DataManager(self.app_dir) 32 | 33 | def init_logger(self): 34 | log_file = f"{self.app_name}.log" 35 | handlers = [ 36 | logging.handlers.RotatingFileHandler( 37 | self.app_dir.joinpath(log_file), 38 | maxBytes=1000000, backupCount=1 39 | ), 40 | logging.StreamHandler() 41 | ] 42 | 43 | logging.basicConfig( 44 | handlers=handlers, 45 | format='%(asctime)s - %(filename)s:%(lineno)d - %(message)s', 46 | datefmt='%Y-%m-%d %H:%M:%S', 47 | level=logging.DEBUG 48 | ) 49 | logging.captureWarnings(capture=True) 50 | 51 | def save_window_state(self, geometry, window_state): 52 | self.settings.setValue('geometry', geometry) 53 | self.settings.setValue('windowState', window_state) 54 | self.settings.sync() 55 | 56 | def save_configuration(self, updates_check, kubectl_path): 57 | self.settings.setValue('kubectl', kubectl_path) 58 | self.settings.setValue('startupCheck', updates_check) 59 | self.settings.sync() 60 | 61 | def load_updates_configuration(self): 62 | return str_to_bool(self.settings.value("startupCheck", True)) 63 | 64 | def load_kubectl_path(self): 65 | return self.settings.value('kubectl') or "kubectl" 66 | 67 | def geometry(self): 68 | return self.settings.value("geometry", None) 69 | 70 | def window_state(self): 71 | return self.settings.value("windowState", None) 72 | 73 | 74 | app = AppSettings() 75 | -------------------------------------------------------------------------------- /kuberider/themes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/themes/__init__.py -------------------------------------------------------------------------------- /kuberider/themes/light.qss: -------------------------------------------------------------------------------- 1 | QToolBar { 2 | border-bottom: 1px solid #4D545B; 3 | } 4 | 5 | QListWidget::item:selected { 6 | background: #CBD8E1; 7 | color: black; 8 | } 9 | 10 | QLabel[accessibleName="running"] { 11 | background-color: #01721d; 12 | color : white; 13 | padding-left: 5px; 14 | padding-right: 5px; 15 | border-radius: 4px; 16 | } 17 | 18 | QLabel[accessibleName="waiting"] { 19 | background-color: #d8a413; 20 | color : white; 21 | padding-left: 5px; 22 | padding-right: 5px; 23 | border-radius: 4px; 24 | } -------------------------------------------------------------------------------- /kuberider/themes/light_theme.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from PyQt5.QtCore import Qt 4 | from PyQt5.QtGui import QColor, QPalette 5 | from PyQt5.QtWidgets import QProxyStyle, qApp 6 | 7 | from ..core import styles_from_file 8 | 9 | 10 | class LightTheme(QProxyStyle): 11 | def __init__(self): 12 | super(LightTheme, self).__init__() 13 | palette = qApp.palette() 14 | palette.setColor(QPalette.Window, QColor(239, 240, 241)) 15 | palette.setColor(QPalette.WindowText, QColor(49, 54, 59)) 16 | palette.setColor(QPalette.Base, QColor(252, 252, 252)) 17 | palette.setColor(QPalette.AlternateBase, QColor(239, 240, 241)) 18 | palette.setColor(QPalette.ToolTipBase, QColor(239, 240, 241)) 19 | palette.setColor(QPalette.ToolTipText, QColor(49, 54, 59)) 20 | palette.setColor(QPalette.Text, QColor(49, 54, 59)) 21 | palette.setColor(QPalette.Button, QColor(239, 240, 241)) 22 | palette.setColor(QPalette.ButtonText, QColor(49, 54, 59)) 23 | palette.setColor(QPalette.BrightText, QColor(255, 255, 255)) 24 | palette.setColor(QPalette.Link, QColor(41, 128, 185)) 25 | palette.setColor(QPalette.Highlight, QColor(126, 71, 130)) 26 | palette.setColor(QPalette.HighlightedText, Qt.white) 27 | palette.setColor(QPalette.Disabled, QPalette.Light, Qt.white) 28 | palette.setColor(QPalette.Disabled, QPalette.Shadow, QColor(234, 234, 234)) 29 | qApp.setPalette(palette) 30 | 31 | def load_stylesheet(self): 32 | filename = ":/themes/light.qss" 33 | 34 | styles = styles_from_file(filename) 35 | qApp.setStyleSheet(styles) if styles else self.log_error(filename) 36 | 37 | def log_error(self, styles_file): 38 | logging.error(f"Unable to read file from {styles_file}") 39 | -------------------------------------------------------------------------------- /kuberider/ui/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/ui/__init__.py -------------------------------------------------------------------------------- /kuberider/ui/configuration_dialog.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import (QDialog) 2 | 3 | from ..generated.configuration_dialog import Ui_Configuration 4 | from ..presenters.configuration_presenter import ConfigurationPresenter 5 | 6 | 7 | class ConfigurationDialog(QDialog, Ui_Configuration): 8 | 9 | def __init__(self, parent=None): 10 | super(ConfigurationDialog, self).__init__(parent) 11 | self.setupUi(self) 12 | self.setFixedSize(self.size()) 13 | self.presenter = ConfigurationPresenter(self, parent) 14 | 15 | def show_dialog(self): 16 | self.presenter.load_configuration_dialog() 17 | -------------------------------------------------------------------------------- /kuberider/ui/kube_resource_dialog.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import (QDialog) 2 | 3 | from ..generated.kube_resource_dialog import Ui_KubeResourceDialog 4 | 5 | 6 | class KubeResourceDialog(QDialog, Ui_KubeResourceDialog): 7 | 8 | def __init__(self, parent=None): 9 | super(KubeResourceDialog, self).__init__(parent) 10 | self.setupUi(self) 11 | -------------------------------------------------------------------------------- /kuberider/ui/kube_rider_main_window.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | import traceback 4 | 5 | from PyQt5.QtGui import QDesktopServices, QCloseEvent, QIcon 6 | from PyQt5.QtWidgets import QMainWindow, QToolBar, qApp 7 | 8 | from kuberider.presenters.kube_resource_presenter import KubeResourcePresenter 9 | from ..presenters.container_exec_presenter import ContainerExecPresenter 10 | from ..presenters.pod_containers_presenter import PodContainersPresenter 11 | from ..presenters.pod_events_presenter import PodEventsPresenter 12 | from ..presenters.pod_list_presenter import PodListPresenter 13 | from ..presenters.pod_logs_presenter import PodLogsPresenter 14 | from ..presenters.pods_filter_presenter import PodsFilterPresenter 15 | from ..presenters.watch_presenter import WatchPresenter 16 | from ..generated.kube_rider_main import Ui_MainWindow 17 | from ..presenters.console_presenter import ConsolePresenter 18 | from ..presenters.file_menu_presenter import FileMenuPresenter 19 | from ..presenters.kube_rider_main_presenter import KubeRiderMainPresenter 20 | from ..presenters.toolbar_presenter import ToolbarPresenter 21 | from ..ui.configuration_dialog import ConfigurationDialog 22 | from ..ui.menus import menu_items 23 | from ..ui.progress_dialog import ProgressDialog 24 | from ..ui.shortcuts import shortcut_items 25 | from ..ui.toolbar import toolbar_items 26 | from ..ui.updater_dialog import Updater 27 | 28 | 29 | class KubeRiderMainWindow(QMainWindow, Ui_MainWindow): 30 | def __init__(self, parent=None): 31 | super(KubeRiderMainWindow, self).__init__(parent) 32 | self.setupUi(self) 33 | 34 | # Add Components on Main Window 35 | self.updater = Updater(self) 36 | self.menu_bar = self.menuBar() 37 | self.toolbar = QToolBar() 38 | self.status_bar = self.statusBar() 39 | self.status_bar.showMessage('Ready', 5000) 40 | 41 | # Initialise Presenters 42 | self.presenter = KubeRiderMainPresenter(self) 43 | self.file_menu_presenter = FileMenuPresenter(self) 44 | self.toolbar_presenter = ToolbarPresenter(self.toolbar) 45 | self.pod_list_presenter = PodListPresenter(self.lst_pods) 46 | self.console_presenter = ConsolePresenter(self.console_text_edit) 47 | self.watch_presenter = WatchPresenter(self) 48 | self.pod_containers_presenter = PodContainersPresenter(self.lst_pod_containers) 49 | self.pod_events_presenter = PodEventsPresenter(self.txt_pod_events) 50 | self.pods_filter_presenter = PodsFilterPresenter(self) 51 | self.pod_logs_presenter = PodLogsPresenter(self) 52 | self.container_exec_presenter = ContainerExecPresenter(self) 53 | self.kube_resource_presenter = KubeResourcePresenter(self) 54 | 55 | # Custom Dialogs 56 | self.progress_dialog = ProgressDialog(self) 57 | self.configuration_dialog = ConfigurationDialog(self) 58 | 59 | # Initialise Components 60 | menu_items(self) 61 | toolbar_items(self) 62 | shortcut_items(self) 63 | 64 | # Initialise Sub-Systems 65 | sys.excepthook = KubeRiderMainWindow.log_uncaught_exceptions 66 | 67 | @staticmethod 68 | def log_uncaught_exceptions(cls, exc, tb) -> None: 69 | logging.critical(''.join(traceback.format_tb(tb))) 70 | logging.critical('{0}: {1}'.format(cls, exc)) 71 | 72 | # Main Window events 73 | def resizeEvent(self, event): 74 | self.presenter.after_window_loaded() 75 | 76 | def closeEvent(self, event: QCloseEvent): 77 | logging.info("Received close event") 78 | event.accept() 79 | self.presenter.shutdown() 80 | try: 81 | qApp.exit(0) 82 | except: 83 | pass 84 | 85 | # End Main Window events 86 | def check_updates(self): 87 | self.updater.check() 88 | 89 | def update_available(self, latest, current): 90 | update_available = True if latest > current else False 91 | logging.info(f"Update Available ({latest} > {current}) ? ({update_available}) Enable Toolbar Icon") 92 | if update_available: 93 | toolbar_actions = self.toolbar.actions() 94 | updates_action = next(act for act in toolbar_actions if act.text() == 'Update Available') 95 | if updates_action: 96 | updates_action.setIcon(QIcon(":/images/download-48.png")) 97 | updates_action.setEnabled(True) 98 | 99 | def open_releases_page(self) -> None: 100 | QDesktopServices.openUrl(self.releases_page) 101 | -------------------------------------------------------------------------------- /kuberider/ui/menus.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtWidgets import (QAction, QMenu) 2 | 3 | 4 | def menu_items(self): 5 | # File Menu 6 | new_action = QAction('&New', self) 7 | new_action.setShortcut('Ctrl+N') 8 | new_action.triggered.connect(self.file_menu_presenter.on_file_new) 9 | 10 | f: QMenu = self.menu_bar.addMenu("&File") 11 | f.addAction(new_action) 12 | -------------------------------------------------------------------------------- /kuberider/ui/pod_logs_dialog.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import Qt 2 | from PyQt5.QtGui import QKeyEvent 3 | from PyQt5.QtWidgets import (QDialog) 4 | 5 | from ..generated.pod_logs_dialog import Ui_PodLogsDialog 6 | 7 | 8 | class PodLogsDialog(QDialog, Ui_PodLogsDialog): 9 | 10 | def __init__(self, parent=None): 11 | super(PodLogsDialog, self).__init__(parent) 12 | self.initialize() 13 | 14 | def initialize(self): 15 | self.setupUi(self) 16 | 17 | def hide_dialog(self): 18 | self.hide() 19 | 20 | def show_dialog(self): 21 | self.show() 22 | 23 | def keyPressEvent(self, e: QKeyEvent): 24 | if e.key() != Qt.Key_Escape: 25 | super(QDialog, self).keyPressEvent(e) 26 | -------------------------------------------------------------------------------- /kuberider/ui/progress_dialog.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import Qt 2 | from PyQt5.QtGui import QKeyEvent 3 | from PyQt5.QtWidgets import (QDialog) 4 | 5 | from kuberider.settings.app_settings import app 6 | from ..generated.progress_dialog import Ui_ProgressDialog 7 | 8 | 9 | class ProgressDialog(QDialog, Ui_ProgressDialog): 10 | 11 | def __init__(self, parent=None): 12 | super(ProgressDialog, self).__init__(parent) 13 | self.initialize() 14 | self.btn_cancel_progress.pressed.connect(self.cancel_processing) 15 | app.data.signals.command_started.connect(self.show_dialog) 16 | app.data.signals.command_finished.connect(self.hide_dialog) 17 | 18 | def initialize(self): 19 | self.setupUi(self) 20 | self.setFixedSize(self.size()) 21 | 22 | def cancel_processing(self): 23 | self.update_status("Cancelling....") 24 | 25 | def show_dialog(self, message="", suppress_dialog=True): 26 | if not suppress_dialog: 27 | self.lbl_progress_status.setText(message) 28 | self.show() 29 | 30 | def hide_dialog(self): 31 | self.lbl_progress_status.setText("") 32 | self.hide() 33 | 34 | def update_status(self, message): 35 | self.lbl_progress_status.setText(message) 36 | 37 | def keyPressEvent(self, e: QKeyEvent): 38 | if e.key() != Qt.Key_Escape: 39 | super(QDialog, self).keyPressEvent(e) 40 | -------------------------------------------------------------------------------- /kuberider/ui/shortcuts.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import * 2 | from PyQt5.QtWidgets import QShortcut 3 | 4 | 5 | def shortcut_items(self): 6 | short = QShortcut(QKeySequence("Ctrl+L"), self) 7 | # short.activated.connect(self.progress_dialog.show_dialog) 8 | pass 9 | -------------------------------------------------------------------------------- /kuberider/ui/toolbar.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import Qt 2 | from PyQt5.QtGui import QIcon 3 | from PyQt5.QtWidgets import * 4 | 5 | 6 | def toolbar_items(self): 7 | """Create a tool bar for the main window.""" 8 | self.toolbar.setObjectName("maintoolbar") 9 | self.addToolBar(Qt.TopToolBarArea, self.toolbar) 10 | self.toolbar.setMovable(False) 11 | 12 | toolbar_configure_action = QAction(QIcon(":/images/configure-48.png"), 'Settings', self) 13 | toolbar_configure_action.triggered.connect(self.configuration_dialog.show_dialog) 14 | self.toolbar.addAction(toolbar_configure_action) 15 | 16 | toolbar_add_resource_action = QAction(QIcon(":/images/plus-48.png"), 'Add Resource', self) 17 | toolbar_add_resource_action.triggered.connect(self.kube_resource_presenter.show_dialog) 18 | self.toolbar.addAction(toolbar_add_resource_action) 19 | 20 | self.toolbar.addSeparator() 21 | 22 | toolbar_load_contexts_action = QAction(QIcon(":/images/load-contexts-48.png"), 'Load Contexts', self) 23 | toolbar_load_contexts_action.triggered.connect(self.toolbar_presenter.on_toolbar_load_contexts) 24 | self.toolbar.addAction(toolbar_load_contexts_action) 25 | 26 | toolbar_ctx_list = QComboBox(self) 27 | toolbar_ctx_list.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) 28 | toolbar_ctx_list.setDuplicatesEnabled(False) 29 | toolbar_ctx_list.currentIndexChanged[str].connect( 30 | lambda new_ctx: self.toolbar_presenter.on_toolbar_context_changed(new_ctx) 31 | ) 32 | toolbar_ctx_list_action = QWidgetAction(self) 33 | toolbar_ctx_list_action.setText("Contexts") 34 | toolbar_ctx_list_action.setDefaultWidget(toolbar_ctx_list) 35 | self.toolbar.addAction(toolbar_ctx_list_action) 36 | 37 | toolbar_ns_list = QComboBox(self) 38 | toolbar_ns_list.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) 39 | toolbar_ns_list.setDuplicatesEnabled(False) 40 | toolbar_ns_list.currentIndexChanged[str].connect( 41 | lambda new_ns: self.toolbar_presenter.on_current_namespace_changed(new_ns) 42 | ) 43 | toolbar_ns_list.setEditable(True) 44 | toolbar_ns_list_action = QWidgetAction(self) 45 | toolbar_ns_list_action.setText("Namespaces") 46 | toolbar_ns_list_action.setDefaultWidget(toolbar_ns_list) 47 | self.toolbar.addAction(toolbar_ns_list_action) 48 | 49 | spacer = QWidget(self) 50 | spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 51 | self.toolbar.addWidget(spacer) 52 | 53 | toolbar_update_available = QAction(QIcon(":/images/download-disabled-48.png"), 'Update Available', self) 54 | toolbar_update_available.setEnabled(False) 55 | toolbar_update_available.triggered.connect(self.open_releases_page) 56 | 57 | self.toolbar.addAction(toolbar_update_available) 58 | -------------------------------------------------------------------------------- /kuberider/ui/updater_dialog.py: -------------------------------------------------------------------------------- 1 | import json 2 | import sys 3 | 4 | from PyQt5.QtCore import Qt, QUrl 5 | from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkReply, QNetworkRequest 6 | from PyQt5.QtWidgets import qApp, QDialog 7 | 8 | 9 | class Updater(QDialog): 10 | api_github_latest: QUrl = QUrl('https://api.github.com/repos/namuan/kube-rider-osx/releases/latest') 11 | 12 | def __init__(self, parent=None, flags=Qt.Dialog | Qt.WindowCloseButtonHint): 13 | super(Updater, self).__init__(parent, flags) 14 | self.parent = parent 15 | self.manager = QNetworkAccessManager(self) 16 | self.manager.finished.connect(self.done) 17 | 18 | def done(self, reply: QNetworkReply): 19 | if reply.error() != QNetworkReply.NoError: 20 | sys.stderr.write(reply.errorString()) 21 | return 22 | try: 23 | json_data = json.loads(str(reply.readAll(), 'utf-8')) 24 | reply.deleteLater() 25 | latest = json_data.get('tag_name') 26 | current = qApp.applicationVersion() 27 | self.parent.update_available(latest, current) 28 | except json.JSONDecodeError: 29 | self.logger.exception('Error retrieving data', exc_info=True) 30 | raise 31 | 32 | def check(self) -> None: 33 | if self.api_github_latest.isValid(): 34 | self.manager.get(QNetworkRequest(self.api_github_latest)) 35 | -------------------------------------------------------------------------------- /kuberider/widgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/kuberider/widgets/__init__.py -------------------------------------------------------------------------------- /kuberider/widgets/pod_container_widget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtWidgets 2 | 3 | from ..entities.model import KubePodContainer, KubePodItem 4 | from ..generated.pod_container_widget import Ui_PodContainerWidget 5 | from ..settings.app_settings import app 6 | 7 | 8 | class PodContainerWidget(QtWidgets.QWidget, Ui_PodContainerWidget): 9 | pod_info: KubePodItem 10 | pod_container: KubePodContainer 11 | 12 | def __init__(self, pod_info: KubePodItem, pod_container: KubePodContainer, parent=None): 13 | super(PodContainerWidget, self).__init__(parent) 14 | self.setupUi(self) 15 | self.pod_info = pod_info 16 | self.set_data(pod_container) 17 | 18 | # ui events 19 | self.btn_open_logs.clicked.connect(self.on_open_logs) 20 | self.btn_exec_shell.clicked.connect(self.on_exec_shell) 21 | self.btn_port_forward.clicked.connect(self.on_port_forward) 22 | self.btn_follow_logs.clicked.connect(self.on_follow_logs) 23 | 24 | def set_data(self, pod_container: KubePodContainer): 25 | self.pod_container = pod_container 26 | self.lbl_container_name.setText(pod_container.name) 27 | self.lbl_container_status.setAccessibleName(pod_container.state) 28 | self.lbl_container_status.setText(pod_container.state_details) 29 | self.lbl_container_image.setText(pod_container.image) 30 | self.lbl_volumes.setText(",".join(pod_container.volumeMounts.keys())) 31 | self.lbl_volumes.setToolTip(",".join(pod_container.volumeMounts.values())) 32 | 33 | def get_data(self): 34 | return self.pod_info 35 | 36 | def on_open_logs(self): 37 | app.commands.open_pod_logs.emit(self.pod_info.name, self.pod_container.name) 38 | 39 | def on_exec_shell(self): 40 | app.commands.on_exec_shell.emit(self.pod_info.name, self.pod_container.name) 41 | 42 | def on_port_forward(self): 43 | app.commands.on_port_forward.emit(self.pod_info.name, self.pod_container.name) 44 | 45 | def on_follow_logs(self): 46 | app.commands.on_follow_logs.emit(self.pod_info.name, self.pod_container.name) -------------------------------------------------------------------------------- /kuberider/widgets/pod_item_widget.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtWidgets 2 | 3 | from ..entities.model import KubePodItem 4 | from ..generated.pod_item_widget import Ui_PodItemWidget 5 | 6 | 7 | class PodItemWidget(QtWidgets.QWidget, Ui_PodItemWidget): 8 | pod_info: KubePodItem 9 | 10 | def __init__(self, pod_info: KubePodItem, parent=None): 11 | super(PodItemWidget, self).__init__(parent) 12 | self.setupUi(self) 13 | self.set_data(pod_info) 14 | 15 | def set_data(self, pod_info: KubePodItem): 16 | self.pod_info = pod_info 17 | self.lbl_pod_name.setText(pod_info.name) 18 | self.lbl_pod_count.setAccessibleName(pod_info.pod_state) 19 | self.lbl_pod_count.setText("{0}/{1}".format(*pod_info.count)) 20 | self.lbl_pod_status.setText(pod_info.pod_status) 21 | 22 | def get_data(self): 23 | return self.pod_info 24 | -------------------------------------------------------------------------------- /mk-icns.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | inkscape="/Applications/Inkscape.app/Contents/Resources/bin/inkscape" 5 | svg_file=$1 6 | output_name=$2 7 | 8 | set -e 9 | test -d "$output_name.iconset" && rm -R "$output_name.iconset" 10 | mkdir "$output_name.iconset" 11 | $inkscape -z -e "$PWD/$output_name.iconset/icon_16x16.png" -w 16 -h 16 "$svg_file" 12 | $inkscape -z -e "$PWD/$output_name.iconset/icon_16x16@2x.png" -w 32 -h 32 "$svg_file" 13 | $inkscape -z -e "$PWD/$output_name.iconset/icon_32x32.png" -w 32 -h 32 "$svg_file" 14 | $inkscape -z -e "$PWD/$output_name.iconset/icon_32x32@2x.png" -w 64 -h 64 "$svg_file" 15 | $inkscape -z -e "$PWD/$output_name.iconset/icon_128x128.png" -w 128 -h 128 "$svg_file" 16 | $inkscape -z -e "$PWD/$output_name.iconset/icon_128x128@2x.png" -w 256 -h 256 "$svg_file" 17 | $inkscape -z -e "$PWD/$output_name.iconset/icon_256x256.png" -w 256 -h 256 "$svg_file" 18 | $inkscape -z -e "$PWD/$output_name.iconset/icon_256x256@2x.png" -w 512 -h 512 "$svg_file" 19 | $inkscape -z -e "$PWD/$output_name.iconset/icon_512x512.png" -w 512 -h 512 "$svg_file" 20 | $inkscape -z -e "$PWD/$output_name.iconset/icon_512x512@2x.png" -w 1024 -h 1024 "$svg_file" 21 | iconutil -c icns "$output_name.iconset" 22 | rm -R "$output_name.iconset" 23 | mv $output_name.icns packaging/data/icons/ 24 | 25 | $inkscape -z -e "$PWD/${output_name}/images/${output_name}.png" -w 512 -h 512 "$svg_file" -------------------------------------------------------------------------------- /packaging/data/icons/kuberider.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/namuan/kube-rider/9bac49fac546213a7a462a4140289107c36e73c0/packaging/data/icons/kuberider.icns -------------------------------------------------------------------------------- /pre-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for i in `ls resources/ui/*.ui`; do FNAME=`basename $i ".ui"`; ./venv/bin/pyuic5 $i > "kuberider/generated/$FNAME.py"; done 4 | 5 | ./venv/bin/pyrcc5 -compress 9 -o kuberider/resources_rc.py kuberider/resources.qrc -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | sip==4.19.8 2 | tinydb 3 | requests 4 | attrs 5 | cattrs 6 | dataset 7 | toolz 8 | arrow 9 | PyQt5 == 5.12.1 -------------------------------------------------------------------------------- /resources/ui/configuration_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Configuration 4 | 5 | 6 | Qt::WindowModal 7 | 8 | 9 | 10 | 0 11 | 0 12 | 486 13 | 255 14 | 15 | 16 | 17 | Settings 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 25 | 12 26 | 6 27 | 461 28 | 201 29 | 30 | 31 | 32 | 0 33 | 34 | 35 | 36 | Settings 37 | 38 | 39 | 40 | 41 | 10 42 | 10 43 | 441 44 | 21 45 | 46 | 47 | 48 | kubectl 49 | 50 | 51 | path to kubectl ... 52 | 53 | 54 | 55 | 56 | 57 | Updates 58 | 59 | 60 | 61 | false 62 | 63 | 64 | 65 | 20 66 | 20 67 | 221 68 | 20 69 | 70 | 71 | 72 | Check for updates on start up 73 | 74 | 75 | true 76 | 77 | 78 | false 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 360 87 | 210 88 | 113 89 | 32 90 | 91 | 92 | 93 | OK 94 | 95 | 96 | 97 | 98 | 99 | 250 100 | 210 101 | 113 102 | 32 103 | 104 | 105 | 106 | Cancel 107 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /resources/ui/kube_resource_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | KubeResourceDialog 4 | 5 | 6 | Qt::WindowModal 7 | 8 | 9 | true 10 | 11 | 12 | 13 | 0 14 | 0 15 | 746 16 | 640 17 | 18 | 19 | 20 | Qt::DefaultContextMenu 21 | 22 | 23 | Progress ... 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 12 31 | 32 | 33 | 12 34 | 35 | 36 | 12 37 | 38 | 39 | 12 40 | 41 | 42 | 43 | 44 | << Kube definition >> 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 1 55 | 0 56 | 57 | 58 | 59 | << Status >> 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | Apply 69 | 70 | 71 | 72 | 73 | 74 | 75 | Close 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /resources/ui/kube_rider_main.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 871 10 | 600 11 | 12 | 13 | 14 | :::: KubeRider :::: 15 | 16 | 17 | false 18 | 19 | 20 | 21 | 22 | 23 | 24 | Qt::Horizontal 25 | 26 | 27 | 28 | QFrame::StyledPanel 29 | 30 | 31 | QFrame::Raised 32 | 33 | 34 | 35 | 3 36 | 37 | 38 | 39 | 40 | 41 | 42 | Refresh every 43 | 44 | 45 | 46 | 47 | 48 | 49 | 5 50 | 51 | 52 | 53 | 54 | 55 | 56 | seconds 57 | 58 | 59 | 60 | 61 | 62 | 63 | Qt::Horizontal 64 | 65 | 66 | 67 | 40 68 | 20 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | Reload 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 0 89 | 0 90 | 91 | 92 | 93 | Filter pods ... 94 | 95 | 96 | 97 | 98 | 99 | 100 | Filter 101 | 102 | 103 | 104 | 105 | 106 | 107 | Clear 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 1 122 | 0 123 | 124 | 125 | 126 | QFrame::StyledPanel 127 | 128 | 129 | QFrame::Raised 130 | 131 | 132 | 133 | 5 134 | 135 | 136 | 5 137 | 138 | 139 | 5 140 | 141 | 142 | 5 143 | 144 | 145 | 146 | 147 | Qt::Vertical 148 | 149 | 150 | 151 | 152 | 0 153 | 1 154 | 155 | 156 | 157 | QFrame::StyledPanel 158 | 159 | 160 | QFrame::Raised 161 | 162 | 163 | 164 | 5 165 | 166 | 167 | 5 168 | 169 | 170 | 5 171 | 172 | 173 | 5 174 | 175 | 176 | 177 | 178 | 0 179 | 180 | 181 | 182 | Containers 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | Events 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | QFrame::StyledPanel 207 | 208 | 209 | QFrame::Raised 210 | 211 | 212 | 213 | 5 214 | 215 | 216 | 5 217 | 218 | 219 | 5 220 | 221 | 222 | 5 223 | 224 | 225 | 226 | 227 | true 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /resources/ui/pod_container_widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PodContainerWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 433 10 | 96 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 12 27 | 75 28 | true 29 | 30 | 31 | 32 | TextLabel 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 0 41 | 0 42 | 43 | 44 | 45 | 46 | 10 47 | 48 | 49 | 50 | Status 51 | 52 | 53 | Qt::PlainText 54 | 55 | 56 | Qt::AlignCenter 57 | 58 | 59 | 60 | 61 | 62 | 63 | Port Fwd 64 | 65 | 66 | 67 | 68 | 69 | 70 | Exec 71 | 72 | 73 | 74 | 75 | 76 | 77 | Logs 78 | 79 | 80 | Qt::ToolButtonTextOnly 81 | 82 | 83 | true 84 | 85 | 86 | 87 | 88 | 89 | 90 | Follow Logs 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 10 101 | 102 | 103 | 104 | image path 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 10 115 | 116 | 117 | 118 | Volumes: 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 1 127 | 0 128 | 129 | 130 | 131 | 132 | 10 133 | 134 | 135 | 136 | TextLabel 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /resources/ui/pod_item_widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PodItemWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 431 10 | 74 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 5 22 | 23 | 24 | 5 25 | 26 | 27 | 5 28 | 29 | 30 | 5 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 12 39 | 75 40 | true 41 | 42 | 43 | 44 | TextLabel 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 10 57 | 58 | 59 | 60 | ContainerCreating 61 | 62 | 63 | 64 | 65 | 66 | 67 | Qt::Horizontal 68 | 69 | 70 | 71 | 40 72 | 20 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 10 82 | false 83 | 84 | 85 | 86 | 1/1 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /resources/ui/pod_logs_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PodLogsDialog 4 | 5 | 6 | Qt::WindowModal 7 | 8 | 9 | true 10 | 11 | 12 | 13 | 0 14 | 0 15 | 622 16 | 345 17 | 18 | 19 | 20 | Qt::DefaultContextMenu 21 | 22 | 23 | Logs ... 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Qt::Horizontal 38 | 39 | 40 | 41 | 40 42 | 20 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | Close 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /resources/ui/pod_volume_widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | PodVolumeWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 423 10 | 85 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 12 27 | 75 28 | true 29 | 30 | 31 | 32 | TextLabel 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 10 43 | 44 | 45 | 46 | image path 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 10 57 | 58 | 59 | 60 | Volumes: 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 1 69 | 0 70 | 71 | 72 | 73 | 74 | 10 75 | 76 | 77 | 78 | TextLabel 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /resources/ui/progress_dialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ProgressDialog 4 | 5 | 6 | Qt::WindowModal 7 | 8 | 9 | true 10 | 11 | 12 | 13 | 0 14 | 0 15 | 635 16 | 109 17 | 18 | 19 | 20 | Qt::DefaultContextMenu 21 | 22 | 23 | Progress ... 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | 30 32 | 30 33 | 581 34 | 16 35 | 36 | 37 | 38 | 39 | 40 | 41 | Qt::AlignCenter 42 | 43 | 44 | 45 | 46 | 47 | 270 48 | 70 49 | 113 50 | 32 51 | 52 | 53 | 54 | Cancel 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | import os 4 | from pathlib import Path 5 | from setuptools import setup 6 | 7 | py2exe_build = False 8 | py2app_build = False 9 | 10 | if "py2exe" in sys.argv: 11 | try: 12 | import py2exe 13 | 14 | py2exe_build = True 15 | except ImportError: 16 | print("Cannot find py2exe") 17 | elif "py2app" in sys.argv: 18 | py2app_build = True 19 | 20 | with open('requirements.txt') as f: 21 | requirements = f.read().splitlines() 22 | 23 | test_requirements = [ 24 | 'pytest', 25 | 'pytest-cov', 26 | 'pytest-faulthandler', 27 | 'pytest-mock' 28 | ] 29 | 30 | import kuberider 31 | 32 | app_name = kuberider.__appname__ 33 | version = kuberider.__version__ 34 | description = kuberider.__description__ 35 | 36 | dist_dir = Path(os.getcwd()).joinpath('dist').as_posix() 37 | 38 | APP = ['kuberider/main.py'] 39 | 40 | if py2app_build: 41 | py2app_options = { 42 | 'iconfile': 'packaging/data/icons/kuberider.icns' 43 | } 44 | 45 | extra_options = dict( 46 | app=APP, 47 | options={'py2app': py2app_options}, 48 | ) 49 | else: 50 | extra_options = dict() 51 | 52 | setup( 53 | name=app_name, 54 | version=version, 55 | description=description, 56 | author="nmn", 57 | author_email='info@deskriders.dev', 58 | url='https://github.com/namuan/kube-rider', 59 | packages=[ 60 | 'kuberider', 61 | 'kuberider.core', 62 | 'kuberider.domain', 63 | 'kuberider.entities', 64 | 'kuberider.events', 65 | 'kuberider.generated', 66 | 'kuberider.images', 67 | 'kuberider.presenters', 68 | 'kuberider.settings', 69 | 'kuberider.themes', 70 | 'kuberider.ui', 71 | 'kuberider.widgets' 72 | ], 73 | package_data={ 74 | 'kuberider.images': ['*.png'], 75 | 'kuberider.themes': ['*.qss', '*.css'] 76 | }, 77 | entry_points={ 78 | 'gui_scripts': [ 79 | 'context=kuberider.main:main' 80 | ] 81 | }, 82 | install_requires=requirements, 83 | zip_safe=False, 84 | keywords='Desktop Kubernetes Client', 85 | test_suite='tests', 86 | tests_require=test_requirements, 87 | **extra_options 88 | ) 89 | 90 | if py2app_build: 91 | print('*** Removing unused Qt frameworks ***') 92 | framework_dir = os.path.join(dist_dir, "kuberider.app/Contents/Resources/lib/python{0}.{1}/PyQt5/Qt/lib".format( 93 | sys.version_info.major, sys.version_info.minor)) 94 | frameworks = [ 95 | 'QtDeclarative.framework', 96 | 'QtHelp.framework', 97 | 'QtMultimedia.framework', 98 | 'QtScript.framework', 99 | 'QtScriptTools.framework', 100 | 'QtSql.framework', 101 | 'QtDesigner.framework', 102 | 'QtTest.framework', 103 | 'QtWebKit.framework', 104 | 'QtXMLPatterns.framework', 105 | 'QtCLucene.framework', 106 | 'QtBluetooth.framework', 107 | 'QtConcurrent.framework', 108 | 'QtMultimediaWidgets.framework', 109 | 'QtPositioning.framework', 110 | 'QtQml.framework', 111 | 'QtQuick.framework', 112 | 'QtQuickWidgets.framework', 113 | 'QtSensors.framework', 114 | 'QtSerialPort.framework', 115 | 'QtWebChannel.framework', 116 | 'QtWebKitWidgets.framework', 117 | 'QtWebSockets.framework'] 118 | 119 | for framework in frameworks: 120 | for root, dirs, files in os.walk(os.path.join(framework_dir, framework)): 121 | for file in files: 122 | os.remove(os.path.join(root, file)) 123 | --------------------------------------------------------------------------------