├── .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 | [](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 |
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 |
--------------------------------------------------------------------------------