From e44fd8a7a5850ad159ce451b5b6cab930a615188 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Fri, 5 Jul 2024 11:15:07 +0200
Subject: [PATCH 01/21] Refactor, typing improvements, ci improvements

---
 .forgejo/workflows/build-release.yaml         |  11 +-
 .forgejo/workflows/{lint.yaml => check.yaml}  |  21 ++-
 dockge_cli/client/commandprovider/bindings.py |   4 +-
 .../client/commandprovider/descriptors.py     | 172 +++++++++---------
 dockge_cli/client/commandprovider/factory.py  |  18 +-
 dockge_cli/client/parser.py                   |   2 +-
 dockge_cli/models/__init__.py                 |   2 +
 dockge_cli/models/commands.py                 |   9 +
 pyproject.toml                                |   9 +
 9 files changed, 141 insertions(+), 107 deletions(-)
 rename .forgejo/workflows/{lint.yaml => check.yaml} (51%)
 create mode 100644 dockge_cli/models/commands.py

diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml
index 1170c10..16bebd7 100644
--- a/.forgejo/workflows/build-release.yaml
+++ b/.forgejo/workflows/build-release.yaml
@@ -6,7 +6,7 @@ on:
       - '[0-9]+\.[0-9]+\.[0-9]'
 
 jobs:
-  backend-pylint:
+  lint-and-typing:
     runs-on: docker
     container: nikolaik/python-nodejs:python3.11-nodejs21
     steps:
@@ -15,15 +15,20 @@ jobs:
       - name: Install packages
         run: |
           pip install -e . -q
+          pip install pylint~=2.17.7 mypy~=1.10.1 --disable-pip-version-check -q
+          mypy --install-types
           python -m pip list --format=columns --disable-pip-version-check
-          pip install pylint~=2.17.7 --disable-pip-version-check -q
       - name: Run pylint
         run: |
           pylint --version
           pylint **/*.py --exit-zero --rc-file pyproject.toml
+      - name: Run mypy
+        run: |
+          mypy --version
+          mypy .
 
   build-artifacts:
-    needs: ["backend-pylint"]
+    needs: ["lint-and-typing"]
     runs-on: docker
     container: nikolaik/python-nodejs:python3.11-nodejs21
     steps:
diff --git a/.forgejo/workflows/lint.yaml b/.forgejo/workflows/check.yaml
similarity index 51%
rename from .forgejo/workflows/lint.yaml
rename to .forgejo/workflows/check.yaml
index a88449c..346a154 100644
--- a/.forgejo/workflows/lint.yaml
+++ b/.forgejo/workflows/check.yaml
@@ -3,7 +3,7 @@ on:
     branches: "**"
 
 jobs:
-  backend-pylint:
+  pylint:
     runs-on: docker
     container: nikolaik/python-nodejs:python3.11-nodejs21
     steps:
@@ -12,9 +12,26 @@ jobs:
       - name: Install packages
         run: |
           pip install -e . -q
-          python -m pip list --format=columns --disable-pip-version-check
           pip install pylint~=2.17.7 --disable-pip-version-check -q
+          python -m pip list --format=columns --disable-pip-version-check
       - name: Run pylint
         run: |
           pylint --version
           pylint **/*.py --exit-zero --rc-file pyproject.toml
+
+  typing-mypy:
+    runs-on: docker
+    container: nikolaik/python-nodejs:python3.11-nodejs21
+    steps:
+      - name: Checkout source code
+        uses: https://code.forgejo.org/actions/checkout@v3
+      - name: Install packages
+        run: |
+          pip install -e . -q
+          pip install mypy~=1.10.1 --disable-pip-version-check -q
+          mypy --install-types
+          python -m pip list --format=columns --disable-pip-version-check
+      - name: Run mypy
+        run: |
+          mypy --version
+          mypy .
diff --git a/dockge_cli/client/commandprovider/bindings.py b/dockge_cli/client/commandprovider/bindings.py
index 6e813de..d6c7685 100644
--- a/dockge_cli/client/commandprovider/bindings.py
+++ b/dockge_cli/client/commandprovider/bindings.py
@@ -1,10 +1,10 @@
 from urllib.parse import urlparse
 from getpass import getpass
 
-from ...models.parser import Credentials
+from ...models import Credentials
 from ...service import storage
-from ..utils import stack_formatter, status_formatter, generic_formatter, get_credential_parser
 from ...service.communicate import DockgeConnection
+from ..utils import stack_formatter, status_formatter, generic_formatter, get_credential_parser
 
 class ExecutionCommands():
     @staticmethod
diff --git a/dockge_cli/client/commandprovider/descriptors.py b/dockge_cli/client/commandprovider/descriptors.py
index cd346fd..10e60f3 100644
--- a/dockge_cli/client/commandprovider/descriptors.py
+++ b/dockge_cli/client/commandprovider/descriptors.py
@@ -1,88 +1,90 @@
+from typing import List
+from ...models import Command
 from .bindings import ExecutionCommands
 
-command_mappings = [
-    {
-        "command": "host",
-        "description": "Sets and gets the URI of the dockge instance. Remove any unnecessary subdomains/protocols from the URI",
-        "args": 1,
-        "optional": True,
-        "binding":    ExecutionCommands.host
-    },
-    {
-        "command": "login",
-        "description": "Logs into a given dockge account, either with an interactive dialogue or by passing --user and --password",
-        "args": 2,
-        "optional": True,
-        "binding":   ExecutionCommands.login
-    },
-    {
-        "command": "logout",
-        "description": "Removes the credentials from the local storage.",
-        "args": 0,
-        "optional": False,
-        "binding": ExecutionCommands.logout
-    },
-    {
-        "command": "list",
-        "description": "Lists all available stacks with their status",
-        "args": 0,
-        "optional": False,
-        "binding": ExecutionCommands.list
-    },
-    {
-        "command": "status",
-        "description": "Returns the status of one stack",
-        "args": 1,
-        "optional": False,
-        "binding": ExecutionCommands.status
-    },
-    {
-        "command": "restart",
-        "description": "Restarts a given stack",
-        "args": 1,
-        "optional": False,
-        "binding": ExecutionCommands.restart
-    },
-    {
-        "command": "start",
-        "description": "Starts a given stack",
-        "args": 1,
-        "optional": False,
-        "binding": ExecutionCommands.start
-    },
-    {
-        "command": "stop",
-        "description": "Stops a given stack",
-        "args": 1,
-        "optional": False,
-        "binding": ExecutionCommands.stop
-    },
-    {
-        "command": "down",
-        "description": "Stop & Downs a given stack",
-        "args": 1,
-        "optional": False,
-        "binding": ExecutionCommands.down
-    },
-    {
-        "command": "update",
-        "description": "Updates a stack",
-        "args": 1,
-        "optional": False,
-        "binding": ExecutionCommands.update
-    },
-    {
-        "command": "exit",
-        "description": "Exits the CLI - this will reset all settings, including credentials and host",
-        "args": 0,
-        "optional": False,
-        "binding": ExecutionCommands.exit
-    },
-    {
-        "command": "help",
-        "description": "Displays helping hints for commands",
-        "args": 1,
-        "optional": True,
-        "binding": ExecutionCommands.help
-    }
+mapping: List[Command] = [
+    Command(
+        cmd="host",
+        description="Sets and gets the URI of the dockge instance. Remove any unnecessary subdomains/protocols from the URI",
+        args=1,
+        optional=True,
+        bind=ExecutionCommands.host
+    ),
+    Command(
+        cmd="login",
+        description="Logs into a given dockge account, either with an interactive dialogue or by passing --user and --password",
+        args=2,
+        optional=True,
+        bind=ExecutionCommands.login
+    ),
+    Command(
+        cmd="logout",
+        description="Removes the credentials from the local storage.",
+        args=0,
+        optional=False,
+        bind=ExecutionCommands.logout
+    ),
+    Command(
+        cmd="list",
+        description="Lists all available stacks with their status",
+        args=0,
+        optional=False,
+        bind=ExecutionCommands.list
+    ),
+    Command(
+        cmd="status",
+        description="Returns the status of one stack",
+        args=1,
+        optional=False,
+        bind=ExecutionCommands.status
+    ),
+    Command(
+        cmd="restart",
+        description="Restarts a given stack",
+        args=1,
+        optional=False,
+        bind=ExecutionCommands.restart
+    ),
+    Command(
+        cmd="start",
+        description="Starts a given stack",
+        args=1,
+        optional=False,
+        bind=ExecutionCommands.start
+    ),
+    Command(
+        cmd="stop",
+        description="Stops a given stack",
+        args=1,
+        optional=False,
+        bind=ExecutionCommands.stop
+    ),
+    Command(
+        cmd="down",
+        description="Stop & Downs a given stack",
+        args=1,
+        optional=False,
+        bind=ExecutionCommands.down
+    ),
+    Command(
+        cmd="update",
+        description="Updates a stack",
+        args=1,
+        optional=False,
+        bind=ExecutionCommands.update
+    ),
+    Command(
+        cmd="exit",
+        description="Exits the CLI - this will reset all settings, including credentials and host",
+        args=0,
+        optional=False,
+        bind=ExecutionCommands.exit
+    ),
+    Command(
+        cmd="help",
+        description="Displays helping hints for commands",
+        args=1,
+        optional=True,
+        bind=ExecutionCommands.help
+    )
 ]
diff --git a/dockge_cli/client/commandprovider/factory.py b/dockge_cli/client/commandprovider/factory.py
index 4bfe95a..6bb2fbe 100644
--- a/dockge_cli/client/commandprovider/factory.py
+++ b/dockge_cli/client/commandprovider/factory.py
@@ -1,17 +1,7 @@
-from typing import List, Callable
-from pydantic import BaseModel
-from .descriptors import command_mappings
-
-class Command(BaseModel):
-    command: str
-    description: str
-    args: int
-    optional: bool
-    binding: Callable
+from ...models import Command
+from .descriptors import mapping
 
 commands: dict[str, Command] = {}
-descriptors: List[dict[str, object]] = command_mappings
 
-for descriptor in descriptors:
-    c = Command(**descriptor) # type: ignore
-    commands.update({ c.command: c  })
+for descriptor in mapping:
+    commands.update({ descriptor.cmd: descriptor  })
diff --git a/dockge_cli/client/parser.py b/dockge_cli/client/parser.py
index 2efbc30..f76bc36 100644
--- a/dockge_cli/client/parser.py
+++ b/dockge_cli/client/parser.py
@@ -2,7 +2,7 @@ import argparse
 import sys
 
 from .. import __version__
-from ..models.parser import Arguments
+from ..models import Arguments
 from .commandprovider.factory import commands
 
 def parse_arguments():
diff --git a/dockge_cli/models/__init__.py b/dockge_cli/models/__init__.py
index ef15c9b..e412edf 100644
--- a/dockge_cli/models/__init__.py
+++ b/dockge_cli/models/__init__.py
@@ -1 +1,3 @@
 from .codes import StackStatus
+from .commands import Command
+from .parser import Arguments, Credentials
diff --git a/dockge_cli/models/commands.py b/dockge_cli/models/commands.py
new file mode 100644
index 0000000..5bc7fb6
--- /dev/null
+++ b/dockge_cli/models/commands.py
@@ -0,0 +1,9 @@
+from typing import Callable
+from pydantic import BaseModel
+
+class Command(BaseModel):
+    cmd: str
+    bind: Callable
+    args: int
+    optional: bool
+    description: str
diff --git a/pyproject.toml b/pyproject.toml
index 50b898b..9d64f03 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,6 +34,15 @@ include = ["dockge_cli*"]
 [tool.pylint."MAIN"]
 disable = [ "line-too-long", "missing-module-docstring", "missing-function-docstring", "missing-class-docstring" ]
 
+[tool.mypy]
+python_version = "3.11"
+warn_return_any = true
+warn_unused_configs = true
+
+[[tool.mypy.overrides]]
+module = 'socketio.*'
+ignore_missing_imports = true
+
 [build-system]
 requires = ["setuptools >= 61.0"]
 build-backend = "setuptools.build_meta"

From f309a19d5e477860f636255803e312c534eae049 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Fri, 5 Jul 2024 11:22:56 +0200
Subject: [PATCH 02/21] Added dev dependencies

---
 .forgejo/workflows/build-release.yaml | 4 +---
 .forgejo/workflows/check.yaml         | 9 +++------
 pyproject.toml                        | 8 ++++++++
 3 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml
index 16bebd7..75a71ac 100644
--- a/.forgejo/workflows/build-release.yaml
+++ b/.forgejo/workflows/build-release.yaml
@@ -14,9 +14,7 @@ jobs:
         uses: https://code.forgejo.org/actions/checkout@v3
       - name: Install packages
         run: |
-          pip install -e . -q
-          pip install pylint~=2.17.7 mypy~=1.10.1 --disable-pip-version-check -q
-          mypy --install-types
+          pip install -e .[dev] -q --disable-pip-version-check -q
           python -m pip list --format=columns --disable-pip-version-check
       - name: Run pylint
         run: |
diff --git a/.forgejo/workflows/check.yaml b/.forgejo/workflows/check.yaml
index 346a154..d134355 100644
--- a/.forgejo/workflows/check.yaml
+++ b/.forgejo/workflows/check.yaml
@@ -11,15 +11,14 @@ jobs:
         uses: https://code.forgejo.org/actions/checkout@v3
       - name: Install packages
         run: |
-          pip install -e . -q
-          pip install pylint~=2.17.7 --disable-pip-version-check -q
+          pip install -e .[dev] -q --disable-pip-version-check -q
           python -m pip list --format=columns --disable-pip-version-check
       - name: Run pylint
         run: |
           pylint --version
           pylint **/*.py --exit-zero --rc-file pyproject.toml
 
-  typing-mypy:
+  mypy:
     runs-on: docker
     container: nikolaik/python-nodejs:python3.11-nodejs21
     steps:
@@ -27,9 +26,7 @@ jobs:
         uses: https://code.forgejo.org/actions/checkout@v3
       - name: Install packages
         run: |
-          pip install -e . -q
-          pip install mypy~=1.10.1 --disable-pip-version-check -q
-          mypy --install-types
+          pip install -e .[dev] -q --disable-pip-version-check -q
           python -m pip list --format=columns --disable-pip-version-check
       - name: Run mypy
         run: |
diff --git a/pyproject.toml b/pyproject.toml
index 9d64f03..1c52ac8 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -31,6 +31,14 @@ include = ["dockge_cli*"]
 [tool.setuptools.package-data]
 "*" = ["py.typed"]
 
+[project.optional-dependencies]
+dev = [
+    "pylint~=3.2.5",
+    "mypy",
+    "types-PyYAML~=6.0.12.20240311",
+    "types-tabulate~=0.9.0.20240106",
+]
+
 [tool.pylint."MAIN"]
 disable = [ "line-too-long", "missing-module-docstring", "missing-function-docstring", "missing-class-docstring" ]
 

From be932ea9c40d0e0ae3d50f4f66cb3cc4dbbfc3ad Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Fri, 5 Jul 2024 11:25:00 +0200
Subject: [PATCH 03/21] Split optionals

---
 .forgejo/workflows/build-release.yaml | 2 +-
 .forgejo/workflows/check.yaml         | 4 ++--
 pyproject.toml                        | 6 ++++--
 3 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml
index 75a71ac..600b9b0 100644
--- a/.forgejo/workflows/build-release.yaml
+++ b/.forgejo/workflows/build-release.yaml
@@ -14,7 +14,7 @@ jobs:
         uses: https://code.forgejo.org/actions/checkout@v3
       - name: Install packages
         run: |
-          pip install -e .[dev] -q --disable-pip-version-check -q
+          pip install -e .[lint,typing] -q --disable-pip-version-check -q
           python -m pip list --format=columns --disable-pip-version-check
       - name: Run pylint
         run: |
diff --git a/.forgejo/workflows/check.yaml b/.forgejo/workflows/check.yaml
index d134355..55de02a 100644
--- a/.forgejo/workflows/check.yaml
+++ b/.forgejo/workflows/check.yaml
@@ -11,7 +11,7 @@ jobs:
         uses: https://code.forgejo.org/actions/checkout@v3
       - name: Install packages
         run: |
-          pip install -e .[dev] -q --disable-pip-version-check -q
+          pip install -e .[lint] -q --disable-pip-version-check -q
           python -m pip list --format=columns --disable-pip-version-check
       - name: Run pylint
         run: |
@@ -26,7 +26,7 @@ jobs:
         uses: https://code.forgejo.org/actions/checkout@v3
       - name: Install packages
         run: |
-          pip install -e .[dev] -q --disable-pip-version-check -q
+          pip install -e .[typing] -q --disable-pip-version-check -q
           python -m pip list --format=columns --disable-pip-version-check
       - name: Run mypy
         run: |
diff --git a/pyproject.toml b/pyproject.toml
index 1c52ac8..830b0f4 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -32,9 +32,11 @@ include = ["dockge_cli*"]
 "*" = ["py.typed"]
 
 [project.optional-dependencies]
-dev = [
+lint = [
     "pylint~=3.2.5",
-    "mypy",
+]
+typing = [
+    "mypy~=1.10.1",
     "types-PyYAML~=6.0.12.20240311",
     "types-tabulate~=0.9.0.20240106",
 ]

From a99b81b0c7b009aa21ee5b33757e8e04999554f6 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Fri, 5 Jul 2024 16:05:35 +0200
Subject: [PATCH 04/21] Pylint settings

---
 dockge_cli/__main__.py                        |  2 +
 dockge_cli/client/commandprovider/bindings.py | 40 +++++++++++++++++++
 dockge_cli/client/parser.py                   |  4 ++
 dockge_cli/client/run.py                      |  9 ++++-
 dockge_cli/client/utils.py                    | 12 ++++++
 dockge_cli/dockge_cli.py                      |  3 ++
 dockge_cli/models/codes.py                    |  3 ++
 dockge_cli/models/commands.py                 |  3 ++
 dockge_cli/models/parser.py                   |  6 +++
 dockge_cli/service/communicate.py             | 39 +++++++++++++++++-
 dockge_cli/service/storage.py                 | 17 ++++++++
 pyproject.toml                                |  5 ++-
 12 files changed, 140 insertions(+), 3 deletions(-)
 create mode 100644 dockge_cli/__main__.py

diff --git a/dockge_cli/__main__.py b/dockge_cli/__main__.py
new file mode 100644
index 0000000..660e368
--- /dev/null
+++ b/dockge_cli/__main__.py
@@ -0,0 +1,2 @@
+from .dockge_cli import cli
+cli()
diff --git a/dockge_cli/client/commandprovider/bindings.py b/dockge_cli/client/commandprovider/bindings.py
index d6c7685..c29c3ab 100644
--- a/dockge_cli/client/commandprovider/bindings.py
+++ b/dockge_cli/client/commandprovider/bindings.py
@@ -7,6 +7,10 @@ from ...service.communicate import DockgeConnection
 from ..utils import stack_formatter, status_formatter, generic_formatter, get_credential_parser
 
 class ExecutionCommands():
+    """
+    Helper class that provides all the static methods in an organized way
+    This is an abstraction layer of the CLI, as those functions only do little preprocessing before calling the actural DockgeConnection
+    """
     @staticmethod
     def __setup():
         con = DockgeConnection()
@@ -15,6 +19,9 @@ class ExecutionCommands():
 
     @staticmethod
     def host(extra_args):
+        """
+        host command binding
+        """
         if len(extra_args) > 0:
             res = urlparse(extra_args[0])
             if all([res.scheme, res.netloc]):
@@ -26,6 +33,9 @@ class ExecutionCommands():
 
     @staticmethod
     def login(extra_args):
+        """
+        login command binding
+        """
         if len(extra_args) > 0:
             credentials = get_credential_parser().parse_args(extra_args, namespace=Credentials)
             storage.put("username", credentials.username, encoded=True)
@@ -36,21 +46,33 @@ class ExecutionCommands():
 
     @staticmethod
     def logout(_):
+        """
+        logout command binding
+        """
         storage.remove("username")
         storage.remove("password")
 
     @staticmethod
     def exit(_):
+        """
+        exit command binding
+        """
         storage.clear()
 
     @staticmethod
     def list(_):
+        """
+        list command binding
+        """
         con = ExecutionCommands.__setup()
         stack_formatter(con.list_stacks())
         con.disconnect()
 
     @staticmethod
     def status(extra_args):
+        """
+        status command binding
+        """
         if extra_args is None:
             raise ValueError
         con = ExecutionCommands.__setup()
@@ -59,6 +81,9 @@ class ExecutionCommands():
 
     @staticmethod
     def restart(extra_args):
+        """
+        restart command binding
+        """
         if extra_args is None:
             raise ValueError
         con = ExecutionCommands.__setup()
@@ -67,6 +92,9 @@ class ExecutionCommands():
 
     @staticmethod
     def update(extra_args):
+        """
+        update command binding
+        """
         if extra_args is None:
             raise ValueError
         con = ExecutionCommands.__setup()
@@ -75,6 +103,9 @@ class ExecutionCommands():
 
     @staticmethod
     def stop(extra_args):
+        """
+        stop command binding
+        """
         if extra_args is None:
             raise ValueError
         con = ExecutionCommands.__setup()
@@ -83,6 +114,9 @@ class ExecutionCommands():
 
     @staticmethod
     def start(extra_args):
+        """
+        start command binding
+        """
         if extra_args is None:
             raise ValueError
         con = ExecutionCommands.__setup()
@@ -91,6 +125,9 @@ class ExecutionCommands():
 
     @staticmethod
     def down(extra_args):
+        """
+        down command binding
+        """
         if extra_args is None:
             raise ValueError
         con = ExecutionCommands.__setup()
@@ -99,4 +136,7 @@ class ExecutionCommands():
 
     @staticmethod
     def help():
+        """
+        exit command binding - This should never be invoked
+        """
         print("WTF")
diff --git a/dockge_cli/client/parser.py b/dockge_cli/client/parser.py
index f76bc36..9e68f3e 100644
--- a/dockge_cli/client/parser.py
+++ b/dockge_cli/client/parser.py
@@ -6,6 +6,10 @@ from ..models import Arguments
 from .commandprovider.factory import commands
 
 def parse_arguments():
+    """
+    Create a parser and parse the arguments of sys.argv based on the Arguments Namespace
+    Returns arguments and extra arguments separately
+    """
     parser = argparse.ArgumentParser(
                         prog="dockge_cli",
                         description="CLI interface for interacting with Dockge",)
diff --git a/dockge_cli/client/run.py b/dockge_cli/client/run.py
index 105f380..bd5e7d8 100644
--- a/dockge_cli/client/run.py
+++ b/dockge_cli/client/run.py
@@ -1,6 +1,9 @@
 from .commandprovider.factory import commands
 
 def display_help(extra_args):
+    """
+    Display help dialogues for each command
+    """
     if not extra_args:
         print(f"{commands['help'].description}")
         return
@@ -11,6 +14,10 @@ def display_help(extra_args):
     print(f"{commands[extra_args[0]].description}")
 
 def run(command, args):
+    """
+    Runs a given command with the provided args
+    Alsso automatically maps the given command string to the correct Command class
+    """
     if command not in commands:
         raise ValueError("Invalid Command")
 
@@ -25,4 +32,4 @@ def run(command, args):
         display_help(args)
         return
 
-    c.binding(args)
+    c.bind(args)
diff --git a/dockge_cli/client/utils.py b/dockge_cli/client/utils.py
index 6e9fa87..9ac97bc 100644
--- a/dockge_cli/client/utils.py
+++ b/dockge_cli/client/utils.py
@@ -3,6 +3,9 @@ from tabulate import tabulate
 from ..models import StackStatus
 
 def get_credential_parser():
+    """
+    Creates a new parser for login credentials
+    """
     credentialparser = argparse.ArgumentParser(
         prog="login",
         description="Subparser for login credentials provided by CI"
@@ -12,6 +15,9 @@ def get_credential_parser():
     return credentialparser
 
 def stack_formatter(stacks):
+    """
+    Prints a given stack list formatted as a table
+    """
     if not stacks["ok"]:
         raise RuntimeError("Stack GET didn't work")
 
@@ -22,11 +28,17 @@ def stack_formatter(stacks):
     print(tabulate(table, headers=headers, tablefmt="github"), "\n")
 
 def status_formatter(status):
+    """
+    Prints the status for a given stack
+    """
     print(f"Is Stack Ok? {'Yes' if status['ok'] else 'No'}")
     headers = ["Container", "Status"]
     table = [[k, v] for k, v in status["serviceStatusList"].items()]
     print(tabulate(table, headers=headers, tablefmt="github"), "\n")
 
 def generic_formatter(status):
+    """
+    Prints a generic dockge message
+    """
     print(f"Is Ok? {'Yes' if status['ok'] else 'No'}")
     print(f"Stack status: {status['msg']}")
diff --git a/dockge_cli/dockge_cli.py b/dockge_cli/dockge_cli.py
index 25c437f..af77054 100644
--- a/dockge_cli/dockge_cli.py
+++ b/dockge_cli/dockge_cli.py
@@ -2,5 +2,8 @@ from .client.parser import parse_arguments
 from .client.run import run
 
 def cli():
+    """
+    main function for cli invocation
+    """
     command, args= parse_arguments()
     run(command.command, args)
diff --git a/dockge_cli/models/codes.py b/dockge_cli/models/codes.py
index b8f9a07..4c3799b 100644
--- a/dockge_cli/models/codes.py
+++ b/dockge_cli/models/codes.py
@@ -1,6 +1,9 @@
 from enum import Enum
 
 class StackStatus(Enum):
+    """
+    mapping codes for status vs text
+    """
     # pylint: disable=invalid-name
     inactive = 1
     running = 3
diff --git a/dockge_cli/models/commands.py b/dockge_cli/models/commands.py
index 5bc7fb6..d7a8123 100644
--- a/dockge_cli/models/commands.py
+++ b/dockge_cli/models/commands.py
@@ -2,6 +2,9 @@ from typing import Callable
 from pydantic import BaseModel
 
 class Command(BaseModel):
+    """
+    Basic command structure for the CLI to automatically generate valid commands
+    """
     cmd: str
     bind: Callable
     args: int
diff --git a/dockge_cli/models/parser.py b/dockge_cli/models/parser.py
index c836e62..e590ce9 100644
--- a/dockge_cli/models/parser.py
+++ b/dockge_cli/models/parser.py
@@ -2,9 +2,15 @@ import argparse
 
 # pylint: disable=too-few-public-methods
 class Arguments(argparse.Namespace):
+    """
+    Default Arguments when calling the CLI
+    """
     command: str
 
 # pylint: disable=too-few-public-methods
 class Credentials(argparse.Namespace):
+    """
+    Special Argument Namespace for login credentials of the login commands
+    """
     username: str
     password: str
diff --git a/dockge_cli/service/communicate.py b/dockge_cli/service/communicate.py
index 4eb645d..b534174 100644
--- a/dockge_cli/service/communicate.py
+++ b/dockge_cli/service/communicate.py
@@ -5,8 +5,14 @@ import socketio.exceptions
 from . import storage
 
 class DockgeConnection:
+    """
+    Provider class for Dockge
+    Provides all the functionality for connecting, logging in and executing commands
+    """
     class LoginException(BaseException):
-        pass
+        """
+        Special exception when login fails too often
+        """
 
     _sio: socketio.Client
     _host: str
@@ -61,10 +67,17 @@ class DockgeConnection:
 
     # Functions
     def connect_and_login(self):
+        """
+        Connect to the websocket
+        """
         self._sio.connect(f"https://{self._host}/socket.io/", transports=['websocket'])
         self.connect()
 
     def connect(self):
+        """
+        Log into dockge using basicauth
+        Retries 5 times when timeouts occur
+        """
         if self._logged_in:
             return
 
@@ -92,6 +105,9 @@ class DockgeConnection:
         self._logged_in = True
 
     def list_stacks(self):
+        """
+        Requests stack list from dockge, returns list when event was sent
+        """
         self._sio.emit("agent", ("", "requestStackList"))
         while self._stacklist is None:
             time.sleep(0.5)
@@ -100,29 +116,50 @@ class DockgeConnection:
         return retval
 
     def list_stack(self, name: str):
+        """
+        Lists status for a stack
+        """
         ret = self._sio.call("agent", ("", "serviceStatusList", name), timeout=5)
         return ret
 
     def restart(self, name):
+        """
+        Restarts a given stack
+        """
         ret = self._sio.call("agent", ("", "restartStack", name), timeout=10)
         return ret
 
     def update(self, name):
+        """
+        Updates a given stack
+        """
         ret = self._sio.call("agent", ("", "updateStack", name), timeout=10)
         return ret
 
     def stop(self, name):
+        """
+        Stops a given stack
+        """
         ret = self._sio.call("agent", ("", "stopStack", name), timeout=10)
         return ret
 
     def start(self, name):
+        """
+        Starts a given stack
+        """
         ret = self._sio.call("agent", ("", "startStack", name), timeout=10)
         return ret
 
     def down(self, name):
+        """
+        Stops and downs a given stack
+        """
         ret = self._sio.call("agent", ("", "downStack", name), timeout=10)
         return ret
 
     def disconnect(self):
+        """
+        Logs out of dockge
+        """
         self._sio.emit("logout")
         self._sio.disconnect()
diff --git a/dockge_cli/service/storage.py b/dockge_cli/service/storage.py
index 0a3a0f8..e4276a2 100644
--- a/dockge_cli/service/storage.py
+++ b/dockge_cli/service/storage.py
@@ -9,11 +9,18 @@ _file = _storagepath / "storage.yaml"
 _storagepath.mkdir(exist_ok=True, parents=True)
 
 def fileexists():
+    """
+    Checks if storage file does exist, creates it when necessary
+    """
     if not _file.exists():
         with open(_file, 'a', encoding="utf-8"):
             os.utime(_file, None)
 
 def put(key: str, value: str, encoded=False):
+    """
+    Puts a given value with a given key into the storage file
+    Encodes the data as base64 when encoded is set to true
+    """
     fileexists()
     with open(_file, "r+", encoding="utf-8") as file:
         content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) or {}
@@ -22,6 +29,9 @@ def put(key: str, value: str, encoded=False):
         yaml.dump(content, file, Dumper=yaml.SafeDumper)
 
 def remove(key: str):
+    """
+    Removed a given key from the storage file
+    """
     fileexists()
     with open(_file, "r", encoding="utf-8") as file:
         content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) or {}
@@ -30,6 +40,10 @@ def remove(key: str):
         yaml.dump(content, file, Dumper=yaml.SafeDumper)
 
 def get(key: str, encoded=False):
+    """
+    Retrieves a value for a given key from the storage file
+    If the value was encoded, encoded needs to be set True to decode it again
+    """
     value: str | None = None
     if not _file.exists():
         return None
@@ -41,4 +55,7 @@ def get(key: str, encoded=False):
     return base64.b64decode(value).decode() if encoded else value
 
 def clear():
+    """
+    Deletes the storage file
+    """
     _file.unlink()
diff --git a/pyproject.toml b/pyproject.toml
index 830b0f4..7b15966 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -42,7 +42,10 @@ typing = [
 ]
 
 [tool.pylint."MAIN"]
-disable = [ "line-too-long", "missing-module-docstring", "missing-function-docstring", "missing-class-docstring" ]
+disable = [
+    "line-too-long",
+    "missing-module-docstring",
+]
 
 [tool.mypy]
 python_version = "3.11"

From 68f05a0c0c92f8918796a3ca8fd2ca952690f91f Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Fri, 5 Jul 2024 16:05:55 +0200
Subject: [PATCH 05/21] Bumped version

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 7b15966..75938fa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.0.1-c.1"
+version = "0.1.0-c.1"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",

From d79a01cfb14494cf8c90d9c50f7918e19205544f Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Fri, 5 Jul 2024 16:36:51 +0200
Subject: [PATCH 06/21] Fixed issue with bytes when saving credentials

---
 dockge_cli/service/storage.py | 4 ++--
 pyproject.toml                | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/dockge_cli/service/storage.py b/dockge_cli/service/storage.py
index e4276a2..20ffb85 100644
--- a/dockge_cli/service/storage.py
+++ b/dockge_cli/service/storage.py
@@ -24,7 +24,7 @@ def put(key: str, value: str, encoded=False):
     fileexists()
     with open(_file, "r+", encoding="utf-8") as file:
         content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) or {}
-        content.update({ key: str(base64.b64encode(value.encode())) if encoded else value })
+        content.update({ key: str(base64.b64encode(value.encode()), "utf-8") if encoded else value })
     with open(_file, "w+", encoding="utf-8") as file:
         yaml.dump(content, file, Dumper=yaml.SafeDumper)
 
@@ -52,7 +52,7 @@ def get(key: str, encoded=False):
         value = content.get(key, None)
     if value is None:
         return None
-    return base64.b64decode(value).decode() if encoded else value
+    return base64.b64decode(value.encode()).decode() if encoded else value
 
 def clear():
     """
diff --git a/pyproject.toml b/pyproject.toml
index 75938fa..457eb3e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.0-c.1"
+version = "0.1.0-c.2"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",

From 58aea47921d8f5f7a156ec8f4c2125bd9b93d9fe Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 6 Jul 2024 00:16:41 +0200
Subject: [PATCH 07/21] Refactored code to be more concise

---
 dockge_cli/client/commandprovider/__init__.py |  0
 dockge_cli/client/commandprovider/factory.py  |  7 -----
 dockge_cli/client/commands/__init__.py        |  4 +++
 .../bindings.py => commands/functions.py}     | 23 +++++++++-------
 .../descriptors.py => commands/mappings.py}   | 26 +++++++++----------
 dockge_cli/client/parser.py                   |  2 +-
 dockge_cli/client/run.py                      |  4 +--
 dockge_cli/client/utils.py                    |  4 +--
 dockge_cli/models/__init__.py                 |  2 +-
 dockge_cli/models/codes.py                    |  9 +++----
 dockge_cli/models/{commands.py => command.py} |  2 +-
 dockge_cli/service/communicate.py             | 14 +++++++---
 dockge_cli/service/storage.py                 | 13 ++++++++++
 13 files changed, 64 insertions(+), 46 deletions(-)
 delete mode 100644 dockge_cli/client/commandprovider/__init__.py
 delete mode 100644 dockge_cli/client/commandprovider/factory.py
 create mode 100644 dockge_cli/client/commands/__init__.py
 rename dockge_cli/client/{commandprovider/bindings.py => commands/functions.py} (87%)
 rename dockge_cli/client/{commandprovider/descriptors.py => commands/mappings.py} (79%)
 rename dockge_cli/models/{commands.py => command.py} (92%)

diff --git a/dockge_cli/client/commandprovider/__init__.py b/dockge_cli/client/commandprovider/__init__.py
deleted file mode 100644
index e69de29..0000000
diff --git a/dockge_cli/client/commandprovider/factory.py b/dockge_cli/client/commandprovider/factory.py
deleted file mode 100644
index 6bb2fbe..0000000
--- a/dockge_cli/client/commandprovider/factory.py
+++ /dev/null
@@ -1,7 +0,0 @@
-from ...models import Command
-from .descriptors import mapping
-
-commands: dict[str, Command] = {}
-
-for descriptor in mapping:
-    commands.update({ descriptor.cmd: descriptor  })
diff --git a/dockge_cli/client/commands/__init__.py b/dockge_cli/client/commands/__init__.py
new file mode 100644
index 0000000..61eb6fc
--- /dev/null
+++ b/dockge_cli/client/commands/__init__.py
@@ -0,0 +1,4 @@
+from ...models import Command
+from .mappings import mapping
+
+commands: dict[str, Command] = { c.cmd: c for c in mapping }
diff --git a/dockge_cli/client/commandprovider/bindings.py b/dockge_cli/client/commands/functions.py
similarity index 87%
rename from dockge_cli/client/commandprovider/bindings.py
rename to dockge_cli/client/commands/functions.py
index c29c3ab..daa0ed2 100644
--- a/dockge_cli/client/commandprovider/bindings.py
+++ b/dockge_cli/client/commands/functions.py
@@ -4,15 +4,18 @@ from getpass import getpass
 from ...models import Credentials
 from ...service import storage
 from ...service.communicate import DockgeConnection
-from ..utils import stack_formatter, status_formatter, generic_formatter, get_credential_parser
+from ..utils import stack_formatter, status_formatter, generic_formatter, credential_parser_factory
 
-class ExecutionCommands():
+class FunctionBindings():
     """
     Helper class that provides all the static methods in an organized way
     This is an abstraction layer of the CLI, as those functions only do little preprocessing before calling the actural DockgeConnection
     """
     @staticmethod
     def __setup():
+        """
+        Creates a connection and logs into Dockge
+        """
         con = DockgeConnection()
         con.connect_and_login()
         return con
@@ -37,7 +40,7 @@ class ExecutionCommands():
         login command binding
         """
         if len(extra_args) > 0:
-            credentials = get_credential_parser().parse_args(extra_args, namespace=Credentials)
+            credentials = credential_parser_factory().parse_args(extra_args, namespace=Credentials)
             storage.put("username", credentials.username, encoded=True)
             storage.put("password", credentials.password, encoded=True)
             return
@@ -64,7 +67,7 @@ class ExecutionCommands():
         """
         list command binding
         """
-        con = ExecutionCommands.__setup()
+        con = FunctionBindings.__setup()
         stack_formatter(con.list_stacks())
         con.disconnect()
 
@@ -75,7 +78,7 @@ class ExecutionCommands():
         """
         if extra_args is None:
             raise ValueError
-        con = ExecutionCommands.__setup()
+        con = FunctionBindings.__setup()
         status_formatter(con.list_stack(extra_args[0]))
         con.disconnect()
 
@@ -86,7 +89,7 @@ class ExecutionCommands():
         """
         if extra_args is None:
             raise ValueError
-        con = ExecutionCommands.__setup()
+        con = FunctionBindings.__setup()
         generic_formatter(con.restart(extra_args[0]))
         con.disconnect()
 
@@ -97,7 +100,7 @@ class ExecutionCommands():
         """
         if extra_args is None:
             raise ValueError
-        con = ExecutionCommands.__setup()
+        con = FunctionBindings.__setup()
         generic_formatter(con.update(extra_args[0]))
         con.disconnect()
 
@@ -108,7 +111,7 @@ class ExecutionCommands():
         """
         if extra_args is None:
             raise ValueError
-        con = ExecutionCommands.__setup()
+        con = FunctionBindings.__setup()
         generic_formatter(con.stop(extra_args[0]))
         con.disconnect()
 
@@ -119,7 +122,7 @@ class ExecutionCommands():
         """
         if extra_args is None:
             raise ValueError
-        con = ExecutionCommands.__setup()
+        con = FunctionBindings.__setup()
         generic_formatter(con.start(extra_args[0]))
         con.disconnect()
 
@@ -130,7 +133,7 @@ class ExecutionCommands():
         """
         if extra_args is None:
             raise ValueError
-        con = ExecutionCommands.__setup()
+        con = FunctionBindings.__setup()
         generic_formatter(con.down(extra_args[0]))
         con.disconnect()
 
diff --git a/dockge_cli/client/commandprovider/descriptors.py b/dockge_cli/client/commands/mappings.py
similarity index 79%
rename from dockge_cli/client/commandprovider/descriptors.py
rename to dockge_cli/client/commands/mappings.py
index 10e60f3..a6ca52f 100644
--- a/dockge_cli/client/commandprovider/descriptors.py
+++ b/dockge_cli/client/commands/mappings.py
@@ -1,6 +1,6 @@
 from typing import List
 from ...models import Command
-from .bindings import ExecutionCommands
+from .functions import FunctionBindings
 
 mapping: List[Command] = [
     Command(
@@ -8,83 +8,83 @@ mapping: List[Command] = [
         description="Sets and gets the URI of the dockge instance. Remove any unnecessary subdomains/protocols from the URI",
         args=1,
         optional=True,
-        bind=ExecutionCommands.host
+        func=FunctionBindings.host
     ),
     Command(
         cmd="login",
         description="Logs into a given dockge account, either with an interactive dialogue or by passing --user and --password",
         args=2,
         optional=True,
-        bind=ExecutionCommands.login
+        func=FunctionBindings.login
     ),
     Command(
         cmd="logout",
         description="Removes the credentials from the local storage.",
         args=0,
         optional=False,
-        bind=ExecutionCommands.logout
+        func=FunctionBindings.logout
     ),
     Command(
         cmd="list",
         description="Lists all available stacks with their status",
         args=0,
         optional=False,
-        bind=ExecutionCommands.list
+        func=FunctionBindings.list
     ),
     Command(
         cmd="status",
         description="Returns the status of one stack",
         args=1,
         optional=False,
-        bind=ExecutionCommands.status
+        func=FunctionBindings.status
     ),
     Command(
         cmd="restart",
         description="Restarts a given stack",
         args=1,
         optional=False,
-        bind=ExecutionCommands.restart
+        func=FunctionBindings.restart
     ),
     Command(
         cmd="start",
         description="Starts a given stack",
         args=1,
         optional=False,
-        bind=ExecutionCommands.start
+        func=FunctionBindings.start
     ),
     Command(
         cmd="stop",
         description="Stops a given stack",
         args=1,
         optional=False,
-        bind=ExecutionCommands.stop
+        func=FunctionBindings.stop
     ),
     Command(
         cmd="down",
         description="Stop & Downs a given stack",
         args=1,
         optional=False,
-        bind=ExecutionCommands.down
+        func=FunctionBindings.down
     ),
     Command(
         cmd="update",
         description="Updates a stack",
         args=1,
         optional=False,
-        bind=ExecutionCommands.update
+        func=FunctionBindings.update
     ),
     Command(
         cmd="exit",
         description="Exits the CLI - this will reset all settings, including credentials and host",
         args=0,
         optional=False,
-        bind=ExecutionCommands.exit
+        func=FunctionBindings.exit
     ),
     Command(
         cmd="help",
         description="Displays helping hints for commands",
         args=1,
         optional=True,
-        bind=ExecutionCommands.help
+        func=FunctionBindings.help
     )
 ]
diff --git a/dockge_cli/client/parser.py b/dockge_cli/client/parser.py
index 9e68f3e..f84628b 100644
--- a/dockge_cli/client/parser.py
+++ b/dockge_cli/client/parser.py
@@ -3,7 +3,7 @@ import sys
 
 from .. import __version__
 from ..models import Arguments
-from .commandprovider.factory import commands
+from .commands import commands
 
 def parse_arguments():
     """
diff --git a/dockge_cli/client/run.py b/dockge_cli/client/run.py
index bd5e7d8..22d0a2a 100644
--- a/dockge_cli/client/run.py
+++ b/dockge_cli/client/run.py
@@ -1,4 +1,4 @@
-from .commandprovider.factory import commands
+from .commands import commands
 
 def display_help(extra_args):
     """
@@ -32,4 +32,4 @@ def run(command, args):
         display_help(args)
         return
 
-    c.bind(args)
+    c.func(args)
diff --git a/dockge_cli/client/utils.py b/dockge_cli/client/utils.py
index 9ac97bc..ebddf26 100644
--- a/dockge_cli/client/utils.py
+++ b/dockge_cli/client/utils.py
@@ -2,7 +2,7 @@ import argparse
 from tabulate import tabulate
 from ..models import StackStatus
 
-def get_credential_parser():
+def credential_parser_factory():
     """
     Creates a new parser for login credentials
     """
@@ -23,7 +23,7 @@ def stack_formatter(stacks):
 
     table, headers = [], ["Stackname", "Status"]
     for key, val in stacks["stackList"].items():
-        table.append([key, StackStatus(val['status']).name])
+        table.append([key, StackStatus(val['status']).name.lower()])
 
     print(tabulate(table, headers=headers, tablefmt="github"), "\n")
 
diff --git a/dockge_cli/models/__init__.py b/dockge_cli/models/__init__.py
index e412edf..ac7af80 100644
--- a/dockge_cli/models/__init__.py
+++ b/dockge_cli/models/__init__.py
@@ -1,3 +1,3 @@
 from .codes import StackStatus
-from .commands import Command
+from .command import Command
 from .parser import Arguments, Credentials
diff --git a/dockge_cli/models/codes.py b/dockge_cli/models/codes.py
index 4c3799b..569ce1d 100644
--- a/dockge_cli/models/codes.py
+++ b/dockge_cli/models/codes.py
@@ -2,9 +2,8 @@ from enum import Enum
 
 class StackStatus(Enum):
     """
-    mapping codes for status vs text
+    mapping for plaintext vs statuscode
     """
-    # pylint: disable=invalid-name
-    inactive = 1
-    running = 3
-    exited = 4
+    INACTIVE = 1
+    RUNNING = 3
+    EXITED = 4
diff --git a/dockge_cli/models/commands.py b/dockge_cli/models/command.py
similarity index 92%
rename from dockge_cli/models/commands.py
rename to dockge_cli/models/command.py
index d7a8123..61a13b3 100644
--- a/dockge_cli/models/commands.py
+++ b/dockge_cli/models/command.py
@@ -6,7 +6,7 @@ class Command(BaseModel):
     Basic command structure for the CLI to automatically generate valid commands
     """
     cmd: str
-    bind: Callable
+    func: Callable
     args: int
     optional: bool
     description: str
diff --git a/dockge_cli/service/communicate.py b/dockge_cli/service/communicate.py
index b534174..9fb063b 100644
--- a/dockge_cli/service/communicate.py
+++ b/dockge_cli/service/communicate.py
@@ -32,7 +32,7 @@ class DockgeConnection:
     def _init_events(self):
         @self._sio.event
         def connect():
-            self.connect()
+            self.login()
             print("Connected!")
 
         @self._sio.event
@@ -70,10 +70,11 @@ class DockgeConnection:
         """
         Connect to the websocket
         """
+        # Dockge uses Socket.io for the websockets, so this URI and params are always the same
         self._sio.connect(f"https://{self._host}/socket.io/", transports=['websocket'])
-        self.connect()
+        self.login()
 
-    def connect(self):
+    def login(self):
         """
         Log into dockge using basicauth
         Retries 5 times when timeouts occur
@@ -84,6 +85,11 @@ class DockgeConnection:
         data = None
         retry, count = True, 0
 
+        if not storage.exists("username"):
+            raise ValueError("Missing username")
+        if not storage.exists("password"):
+            raise ValueError("Missing password")
+
         while retry and count < 5:
             try:
                 data = self._sio.call(
@@ -91,7 +97,7 @@ class DockgeConnection:
                     {
                         "username": storage.get("username", encoded=True),
                         "password": storage.get("password", encoded=True),
-                        "token":""
+                        "token": ""
                     },
                     timeout=5
                 )
diff --git a/dockge_cli/service/storage.py b/dockge_cli/service/storage.py
index 20ffb85..f9230fa 100644
--- a/dockge_cli/service/storage.py
+++ b/dockge_cli/service/storage.py
@@ -16,6 +16,19 @@ def fileexists():
         with open(_file, 'a', encoding="utf-8"):
             os.utime(_file, None)
 
+def exists(key: str) -> bool:
+    """
+    Checks if a given key exists in the storage file
+    """
+    if not _file.exists():
+        return False
+
+    with open(_file, "r", encoding="utf-8") as file:
+        content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader)
+    
+    return key in content
+
+
 def put(key: str, value: str, encoded=False):
     """
     Puts a given value with a given key into the storage file

From c00aa232fc68ebcc3745ed6a268ab0ae95a78d6e Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 6 Jul 2024 00:28:37 +0200
Subject: [PATCH 08/21] Moved files, refactored calls, added credentials
 warning

---
 dockge_cli/.gitignore                   |  1 -
 dockge_cli/client/commands/functions.py | 13 +----------
 dockge_cli/service/.gitignore           |  1 +
 dockge_cli/service/storage.py           | 30 ++++++++++++++++---------
 pyproject.toml                          |  2 +-
 5 files changed, 23 insertions(+), 24 deletions(-)
 delete mode 100644 dockge_cli/.gitignore
 create mode 100644 dockge_cli/service/.gitignore

diff --git a/dockge_cli/.gitignore b/dockge_cli/.gitignore
deleted file mode 100644
index d36a0d4..0000000
--- a/dockge_cli/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-.temp/*
\ No newline at end of file
diff --git a/dockge_cli/client/commands/functions.py b/dockge_cli/client/commands/functions.py
index daa0ed2..f62ed9c 100644
--- a/dockge_cli/client/commands/functions.py
+++ b/dockge_cli/client/commands/functions.py
@@ -39,6 +39,7 @@ class FunctionBindings():
         """
         login command binding
         """
+        print(f"WARNING! These credentials will be saved unencrypted in {storage._file.absolute()}")
         if len(extra_args) > 0:
             credentials = credential_parser_factory().parse_args(extra_args, namespace=Credentials)
             storage.put("username", credentials.username, encoded=True)
@@ -76,8 +77,6 @@ class FunctionBindings():
         """
         status command binding
         """
-        if extra_args is None:
-            raise ValueError
         con = FunctionBindings.__setup()
         status_formatter(con.list_stack(extra_args[0]))
         con.disconnect()
@@ -87,8 +86,6 @@ class FunctionBindings():
         """
         restart command binding
         """
-        if extra_args is None:
-            raise ValueError
         con = FunctionBindings.__setup()
         generic_formatter(con.restart(extra_args[0]))
         con.disconnect()
@@ -98,8 +95,6 @@ class FunctionBindings():
         """
         update command binding
         """
-        if extra_args is None:
-            raise ValueError
         con = FunctionBindings.__setup()
         generic_formatter(con.update(extra_args[0]))
         con.disconnect()
@@ -109,8 +104,6 @@ class FunctionBindings():
         """
         stop command binding
         """
-        if extra_args is None:
-            raise ValueError
         con = FunctionBindings.__setup()
         generic_formatter(con.stop(extra_args[0]))
         con.disconnect()
@@ -120,8 +113,6 @@ class FunctionBindings():
         """
         start command binding
         """
-        if extra_args is None:
-            raise ValueError
         con = FunctionBindings.__setup()
         generic_formatter(con.start(extra_args[0]))
         con.disconnect()
@@ -131,8 +122,6 @@ class FunctionBindings():
         """
         down command binding
         """
-        if extra_args is None:
-            raise ValueError
         con = FunctionBindings.__setup()
         generic_formatter(con.down(extra_args[0]))
         con.disconnect()
diff --git a/dockge_cli/service/.gitignore b/dockge_cli/service/.gitignore
new file mode 100644
index 0000000..9e62042
--- /dev/null
+++ b/dockge_cli/service/.gitignore
@@ -0,0 +1 @@
+.storage/*
\ No newline at end of file
diff --git a/dockge_cli/service/storage.py b/dockge_cli/service/storage.py
index f9230fa..3edf415 100644
--- a/dockge_cli/service/storage.py
+++ b/dockge_cli/service/storage.py
@@ -3,18 +3,18 @@ import pathlib
 import base64
 import yaml
 
-_storagepath = pathlib.Path(__file__).parents[1] / ".temp"
+_storagepath = pathlib.Path(__file__).parent / ".storage"
+_storagepath.mkdir(exist_ok=True, parents=True)
 _file = _storagepath / "storage.yaml"
 
-_storagepath.mkdir(exist_ok=True, parents=True)
-
-def fileexists():
+def create_file_when_missing():
     """
     Checks if storage file does exist, creates it when necessary
     """
-    if not _file.exists():
-        with open(_file, 'a', encoding="utf-8"):
-            os.utime(_file, None)
+    if _file.exists():
+        return
+    with open(_file, 'a', encoding="utf-8"):
+        os.utime(_file, None)
 
 def exists(key: str) -> bool:
     """
@@ -34,10 +34,13 @@ def put(key: str, value: str, encoded=False):
     Puts a given value with a given key into the storage file
     Encodes the data as base64 when encoded is set to true
     """
-    fileexists()
-    with open(_file, "r+", encoding="utf-8") as file:
+    if not _file.exists():
+        create_file_when_missing()
+
+    with open(_file, "r", encoding="utf-8") as file:
         content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) or {}
         content.update({ key: str(base64.b64encode(value.encode()), "utf-8") if encoded else value })
+
     with open(_file, "w+", encoding="utf-8") as file:
         yaml.dump(content, file, Dumper=yaml.SafeDumper)
 
@@ -45,10 +48,13 @@ def remove(key: str):
     """
     Removed a given key from the storage file
     """
-    fileexists()
+    if not _file.exists():
+        create_file_when_missing()
+
     with open(_file, "r", encoding="utf-8") as file:
         content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader) or {}
         content.pop(key, None)
+
     with open(_file, "w+", encoding="utf-8") as file:
         yaml.dump(content, file, Dumper=yaml.SafeDumper)
 
@@ -60,9 +66,11 @@ def get(key: str, encoded=False):
     value: str | None = None
     if not _file.exists():
         return None
+
     with open(_file, "r", encoding="utf-8") as file:
         content: dict[str, str] = yaml.load(file, Loader=yaml.SafeLoader)
         value = content.get(key, None)
+
     if value is None:
         return None
     return base64.b64decode(value.encode()).decode() if encoded else value
@@ -71,4 +79,6 @@ def clear():
     """
     Deletes the storage file
     """
+    if not _file.exists():
+        return
     _file.unlink()
diff --git a/pyproject.toml b/pyproject.toml
index 457eb3e..d1abd6c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.0-c.2"
+version = "0.1.0-c.3"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",

From 5628e4f0624a10c37d1d4f8ce7faef8dcc2e8689 Mon Sep 17 00:00:00 2001
From: Firq-ow <firelp42@gmail.com>
Date: Tue, 9 Jul 2024 14:34:07 +0200
Subject: [PATCH 09/21] Added better uri parsing, updated README

---
 README.md                               | 30 +++++++++++++++++++++++++
 dockge_cli/client/commands/functions.py |  9 +++++---
 2 files changed, 36 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index 4c51468..3ce3770 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,33 @@
 # dockge-cli
 
 A simple CLI application written in Python for communicating with Dockge using websockets
+
+## Background
+
+Dockge (spoken dock-ge or dockage) is a tool to manage docker-compose stacks from a web ui. It is developed by louislam, who also develops UptimeKuma.
+
+Dockge itself doesn't offer any kind of API or programmatic access, as it is just intended for managing stacks via UI.
+
+My current deployment solution for firq.dev and fgo-ta.com is based on Dockge, and I was over it always having to reload the stack whenever I pushed an update. Instead, I wanted to have this as a separate CI step, automatically redeploying a givens stack.
+
+As Dockge is using a websocket-based system under the hood, it was easy to take a look at how communication occurs. In general, communication is achieved by leveraging socket.io for the data. Since Python already offers a solution for socket.io, it is just a matter of emulating the calls the webui sends and receives.
+
+In the end, this is the current result that works pretty well for my understanding. I am still trying to improve upon some issues (login times out, stability, features), but in general this works as a fine solution for automatic stack updating.
+
+## Installation
+
+Install it from the custom package index using
+
+```
+pip install --extra-index-url https://forgejo.neshweb.net/api/packages/Firq/pypi/simple/ dockge-cli
+```
+
+Alternativly, install it using this repository. When installing for development, make sure to install with the additional dependencies
+
+```
+pip install -e .[lint,typing]
+```
+
+## Usage
+
+Call the CLI using `dockge-cli` or `dockge`.
diff --git a/dockge_cli/client/commands/functions.py b/dockge_cli/client/commands/functions.py
index f62ed9c..1cca01d 100644
--- a/dockge_cli/client/commands/functions.py
+++ b/dockge_cli/client/commands/functions.py
@@ -1,5 +1,6 @@
 from urllib.parse import urlparse
 from getpass import getpass
+import re
 
 from ...models import Credentials
 from ...service import storage
@@ -26,10 +27,12 @@ class FunctionBindings():
         host command binding
         """
         if len(extra_args) > 0:
-            res = urlparse(extra_args[0])
+            mat = re.search(r"((\w+\.)?\w+\.\w+(\/.+)?)", extra_args[0], re.IGNORECASE)
+            if mat is None:
+                raise ValueError("Given host did not match regex")
+            res = urlparse(f"https://{mat[0]}")
             if all([res.scheme, res.netloc]):
-                host = extra_args[0].rstrip("/").replace("https://", "").replace("wss://", "")
-                storage.put("host", host)
+                storage.put("host", mat[0])
             else:
                 raise ValueError(f"Malformed URL {extra_args[0]}")
         print(storage.get("host"))

From f5ffe07e77bceffe883e3489a0e7c5365147696a Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Fri, 12 Jul 2024 23:25:52 +0200
Subject: [PATCH 10/21] Renamed communication to connection

---
 dockge_cli/client/commands/functions.py              | 2 +-
 dockge_cli/service/{communicate.py => connection.py} | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename dockge_cli/service/{communicate.py => connection.py} (100%)

diff --git a/dockge_cli/client/commands/functions.py b/dockge_cli/client/commands/functions.py
index 1cca01d..3ee695d 100644
--- a/dockge_cli/client/commands/functions.py
+++ b/dockge_cli/client/commands/functions.py
@@ -4,7 +4,7 @@ import re
 
 from ...models import Credentials
 from ...service import storage
-from ...service.communicate import DockgeConnection
+from ...service.connection import DockgeConnection
 from ..utils import stack_formatter, status_formatter, generic_formatter, credential_parser_factory
 
 class FunctionBindings():
diff --git a/dockge_cli/service/communicate.py b/dockge_cli/service/connection.py
similarity index 100%
rename from dockge_cli/service/communicate.py
rename to dockge_cli/service/connection.py

From 360aca36c5aae1c9042b838bc620d94fa764a27a Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 20 Jul 2024 16:37:41 +0200
Subject: [PATCH 11/21] First official release

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index d1abd6c..1412bbb 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.0-c.3"
+version = "0.1.0"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",

From 34cb6e5dc7e1e12fdfef245184052c58b6bf07d3 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 20 Jul 2024 17:25:34 +0200
Subject: [PATCH 12/21] Increased timeouts

---
 dockge_cli/service/connection.py | 15 ++++++++-------
 pyproject.toml                   |  4 ++--
 2 files changed, 10 insertions(+), 9 deletions(-)

diff --git a/dockge_cli/service/connection.py b/dockge_cli/service/connection.py
index 9fb063b..bcb3802 100644
--- a/dockge_cli/service/connection.py
+++ b/dockge_cli/service/connection.py
@@ -99,10 +99,11 @@ class DockgeConnection:
                         "password": storage.get("password", encoded=True),
                         "token": ""
                     },
-                    timeout=5
+                    timeout=10
                 )
                 retry = False
             except socketio.exceptions.TimeoutError:
+                print("Reached timeout for login, retrying ...")
                 retry = True
                 count += 1
 
@@ -125,42 +126,42 @@ class DockgeConnection:
         """
         Lists status for a stack
         """
-        ret = self._sio.call("agent", ("", "serviceStatusList", name), timeout=5)
+        ret = self._sio.call("agent", ("", "serviceStatusList", name), timeout=10)
         return ret
 
     def restart(self, name):
         """
         Restarts a given stack
         """
-        ret = self._sio.call("agent", ("", "restartStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "restartStack", name), timeout=30)
         return ret
 
     def update(self, name):
         """
         Updates a given stack
         """
-        ret = self._sio.call("agent", ("", "updateStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "updateStack", name), timeout=30)
         return ret
 
     def stop(self, name):
         """
         Stops a given stack
         """
-        ret = self._sio.call("agent", ("", "stopStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "stopStack", name), timeout=30)
         return ret
 
     def start(self, name):
         """
         Starts a given stack
         """
-        ret = self._sio.call("agent", ("", "startStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "startStack", name), timeout=30)
         return ret
 
     def down(self, name):
         """
         Stops and downs a given stack
         """
-        ret = self._sio.call("agent", ("", "downStack", name), timeout=10)
+        ret = self._sio.call("agent", ("", "downStack", name), timeout=30)
         return ret
 
     def disconnect(self):
diff --git a/pyproject.toml b/pyproject.toml
index 1412bbb..6371330 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.0"
+version = "0.1.1-c.1"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",
@@ -11,7 +11,7 @@ dependencies = [
 requires-python = ">= 3.10"
 authors = [{name = "Firq", email = "firelp42@gmail.com"}]
 maintainers = [{name = "Firq", email = "firelp42@gmail.com"}]
-description = "CLi for interacting with dockge"
+description = "CLI for interacting with dockge"
 classifiers = [
     "Development Status :: 3 - Alpha",
     "Programming Language :: Python :: 3",

From 44a04097985ebb68c4ec64a6f5e4b56adb931b9d Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 20 Jul 2024 17:49:25 +0200
Subject: [PATCH 13/21] More logging

---
 dockge_cli/service/connection.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/dockge_cli/service/connection.py b/dockge_cli/service/connection.py
index bcb3802..35ba9a6 100644
--- a/dockge_cli/service/connection.py
+++ b/dockge_cli/service/connection.py
@@ -63,6 +63,7 @@ class DockgeConnection:
             success = True
         else:
             print("Issue with login procedure")
+            print(data)
         return success
 
     # Functions

From 003204afb137fe7c0678b20a47b68ab4b96bca14 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Mon, 22 Jul 2024 17:40:57 +0200
Subject: [PATCH 14/21] Add docker build step to provide ready-to-use dockge
 cli container

---
 .forgejo/workflows/build-release.yaml | 35 +++++++++++++++++++++++++++
 Dockerfile                            |  4 +++
 2 files changed, 39 insertions(+)
 create mode 100644 Dockerfile

diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml
index 600b9b0..9bbcfd2 100644
--- a/.forgejo/workflows/build-release.yaml
+++ b/.forgejo/workflows/build-release.yaml
@@ -56,3 +56,38 @@ jobs:
         run: pip install twine
       - name: Upload package to registry
         run: python -m twine upload --repository-url ${{ secrets.REPOSITORY_URL }} -u ${{ secrets.TWINE_DEPLOY_USER }} -p ${{ secrets.TWINE_DEPLOY_PASSWORD }} dist/*
+
+  build-and-push-container:
+    needs: [ "publish-artifacts" ]
+    runs-on: dind
+    steps:
+      - name: Set up Docker Buildx
+        uses: docker/setup-buildx-action@v3
+      - name: Log into Docker Package Registry
+        uses: docker/login-action@v3
+        with:
+          registry: forgejo.neshweb.net
+          username: ${{ secrets.FORGEJO_USERNAME }}
+          password: ${{ secrets.FORGEJO_TOKEN }}
+      - name: Build and push to Docker Package Registry
+        uses: docker/build-push-action@v5
+        with:
+          context: .
+          build-args: |
+            PACKAGE_VERSION=${{ github.ref_name }}
+            push: true
+          tags: forgejo.neshweb.net/firq/dockge-cli:${{ github.ref_name }}
+
+  release:
+    needs: [ build-and-push-container, publish-artifacts ]
+    if: success()
+    runs-on: docker
+    steps:
+      - name: Release New Version
+        uses: actions/forgejo-release@v1
+        with:
+          direction: upload
+          url: https://forgejo.neshweb.net
+          release-dir: release
+          token: ${{ secrets.FORGEJO_TOKEN }}
+          tag: ${{  github.ref_name }}
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..88e754d
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,4 @@
+FROM forgejo.neshweb.net/ci-docker-images/python-neshweb:3.11
+
+ARG PACKAGE_VERSION=0.1.0
+RUN pip install dockge-cli==${PACKAGE_VERSION}

From ca5803bcccfa5a9575572c28c229f0ea60bda930 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Mon, 22 Jul 2024 17:42:48 +0200
Subject: [PATCH 15/21] Fixed version

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 6371330..7e3f81e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.1-c.1"
+version = "0.1.1-c.2"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",

From de234133f9fac7e516691288030af9844cec565b Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Mon, 22 Jul 2024 17:44:54 +0200
Subject: [PATCH 16/21] Fixed workflow

---
 .forgejo/workflows/build-release.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml
index 9bbcfd2..6932d93 100644
--- a/.forgejo/workflows/build-release.yaml
+++ b/.forgejo/workflows/build-release.yaml
@@ -75,7 +75,7 @@ jobs:
           context: .
           build-args: |
             PACKAGE_VERSION=${{ github.ref_name }}
-            push: true
+          push: true
           tags: forgejo.neshweb.net/firq/dockge-cli:${{ github.ref_name }}
 
   release:

From 09da354b0ec9f56eb9eb5d95d9f16c8c414fe1e1 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Mon, 22 Jul 2024 17:47:06 +0200
Subject: [PATCH 17/21] Fixed workflow 2

---
 .forgejo/workflows/build-release.yaml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.forgejo/workflows/build-release.yaml b/.forgejo/workflows/build-release.yaml
index 6932d93..48e5e92 100644
--- a/.forgejo/workflows/build-release.yaml
+++ b/.forgejo/workflows/build-release.yaml
@@ -72,7 +72,6 @@ jobs:
       - name: Build and push to Docker Package Registry
         uses: docker/build-push-action@v5
         with:
-          context: .
           build-args: |
             PACKAGE_VERSION=${{ github.ref_name }}
           push: true

From fc7ecdcd774e9fddd4c0b1204521409c5d4645bc Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Thu, 15 Aug 2024 23:09:02 +0200
Subject: [PATCH 18/21] Readme

---
 README.md | 38 ++++++++++++++++++++++++++++++++++++--
 1 file changed, 36 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 3ce3770..2f946e3 100644
--- a/README.md
+++ b/README.md
@@ -18,16 +18,50 @@ In the end, this is the current result that works pretty well for my understandi
 
 Install it from the custom package index using
 
-```
+```shell
 pip install --extra-index-url https://forgejo.neshweb.net/api/packages/Firq/pypi/simple/ dockge-cli
 ```
 
 Alternativly, install it using this repository. When installing for development, make sure to install with the additional dependencies
 
-```
+```shell
 pip install -e .[lint,typing]
 ```
 
 ## Usage
 
 Call the CLI using `dockge-cli` or `dockge`.
+
+```shell
+usage: dockge_cli [-h] [--version] {host,login,logout,list,status,restart,start,stop,down,update,exit,help}
+
+CLI interface for interacting with Dockge
+
+positional arguments:
+  {host,login,logout,list,status,restart,start,stop,down,update,exit,help}
+
+options:
+  -h, --help            show this help message and exit
+  --version             show program's version number and exit
+```
+
+Help for each individual command can be invoked by calling `dockge-cli help <command>`
+
+## The magic behind this
+
+Generally, this makes use of the underlying Websockets API that the Dockge frontend uses to communicate with the server. By analyzing the traffic and looking into the codebase, I was able to reverse most of the packets that are being sent. This allows me to then contruct, send and receive my own packets, making the whole thing work.
+
+There are some things that need to be taken into account for this: For one, dockge uses socket.io for the websocket communication. This meant I had to find the corresponding socket.io version to get the correct version of python-socketio. In addition, I had to find out how the authorization mechanism behind this works.
+
+After finishing up the first prototype, the workings are as follows:
+
+1. A websocket session is established using socket.io - this happens automatically
+2. After the session is ready, the `login` command is sent together with a provided username and password
+3. Once the CLI is authorized, the selected command is sent
+4. The CLI waits for any response values and exits once the command has executed successfully
+
+To provide a smooth experience, both the credentials and the remote host URI are stored on disk. just like the `docker` cli, the credentials are not encrypted, meaning it is advised to either clear the credentials after use OR to use the `--username` and `--password` parameters. This is especially recommended for CI applications.
+
+## Known issues
+
+This CLI does not work when Mullvad is used, as Mullvad actively blocks port forwarding (which python-socketio uses)

From 401e446059320a325ed5fc0e6de4df0fc8d498ef Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Sat, 28 Sep 2024 15:39:22 +0200
Subject: [PATCH 19/21] Release 0.1.1

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 7e3f81e..43d9c5d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.1-c.2"
+version = "0.1.1"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",

From ed24d59a9203631d1066036f04f7fcde8054a510 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Tue, 1 Oct 2024 23:24:31 +0200
Subject: [PATCH 20/21] HTTP long polling before upgrade

---
 dockge_cli/service/connection.py | 2 +-
 pyproject.toml                   | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/dockge_cli/service/connection.py b/dockge_cli/service/connection.py
index 35ba9a6..78e7582 100644
--- a/dockge_cli/service/connection.py
+++ b/dockge_cli/service/connection.py
@@ -72,7 +72,7 @@ class DockgeConnection:
         Connect to the websocket
         """
         # Dockge uses Socket.io for the websockets, so this URI and params are always the same
-        self._sio.connect(f"https://{self._host}/socket.io/", transports=['websocket'])
+        self._sio.connect(f"https://{self._host}/socket.io/")
         self.login()
 
     def login(self):
diff --git a/pyproject.toml b/pyproject.toml
index 43d9c5d..39793ce 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,6 +4,7 @@ version = "0.1.1"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",
+    "requests~=2.32.3",
     "python-socketio~=5.11.3",
     "websocket-client~=1.8.0",
     "tabulate ~=0.9.0",

From ac982668c73b7e6a296e755c3ffff3174b4caa17 Mon Sep 17 00:00:00 2001
From: Firq <firelp42@gmail.com>
Date: Tue, 1 Oct 2024 23:25:30 +0200
Subject: [PATCH 21/21] HTTP long polling before upgrade

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 39793ce..b706b37 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "dockge_cli"
-version = "0.1.1"
+version = "0.1.2"
 dependencies = [
     "pyyaml~=6.0.1",
     "pydantic~=2.8.0",