Skip to content

Commit

Permalink
ver 0.1.8
Browse files Browse the repository at this point in the history
  • Loading branch information
blademd committed May 19, 2024
1 parent bdf5a3a commit c6adb89
Show file tree
Hide file tree
Showing 64 changed files with 4,981 additions and 3,513 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ dist/
#VENV
.venv/

# VSCode
.vscode/

# Custom AST
thymus_ast/

#conf
conf
*.conf
Expand Down
29 changes: 9 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@

![thymus_default_screen](https://github.com/blademd/thymus/assets/1499024/13dbe00e-8ece-48a7-b203-83742c0f6d8a)
![thymus_default_screen](https://github.com/blademd/thymus/assets/1499024/8c790c6a-7d11-4cd6-8283-52cf29e8472e)


# Thymus

[Thymus](https://en.wikipedia.org/wiki/Thymus_(plant)) — is a config browser. Thymus does not require a connection to any network device (but it can be used itself over SSH from a remote machine). You just need to save a configuration file, open it anytime, and navigate through it. Thymus mimics to CLI of a selected platform.
[Thymus](https://en.wikipedia.org/wiki/Thymus_(plant)) — is a config browser and editor. Thymus understands the context of a network configuration file compared to popular text editors. It mimics the CLI of a selected platform. That allows you to navigate, display, and edit selected configuration parts based on their paths just like in your favorite network operation system. You can save edited versions many times, creating a history log. Thymus supports the diff among them and can roll back to any saved version.

Thymus does not require a connection to any network device but it can fetch a configuration from a remote machine via Telnet or SSH. Also, it can be used over SSH from a remote machine.

Thymus supports:

* Juniper JunOS (and probably other JunOS-like systems, e.g. SR-OS **with** MD-CLI)
* Cisco IOS/IOS-XE/NX-OS (and probably other IOS-like systems)
* Cisco IOS/IOS-XE/NX-OS/XR-OS (and probably other IOS-like systems)
* Arista EOS

*This is the early alpha version! So some glitches can be appearing.*

<details>
<summary>Screenshots</summary>
<hr>
Expand All @@ -35,37 +35,26 @@ Thymus supports:

## Installation

Use `pip` to install the project right from its sources (e.g., `pip install thymus/` or `python -m pip install thymus/`).
Use `pip` or `pipx` to install the package (e.g., `pip install thymus` or `pipx install thymus`). Requires Python **3.9**!

## Modes
## Operations

Thymus operates in two modes:

- **TUI-based**. This mode draws the full-scale user interface in your console with mouse support. From the Textual documentation:
Thymus operates in **TUI-based** mode thanks to the [Textual](https://textual.textualize.io/) library. This mode draws the full-scale user interface in your console with mouse support. From the Textual documentation:

> On modern terminal software (installed by default on most systems), Textual apps can use **16.7 million** colors with mouse support and smooth flicker-free animation. A powerful layout engine and re-usable components makes it possible to build apps that rival the desktop and web experience.

> Textual runs on Linux, macOS, Windows and probably any OS where Python also runs.

- **CLI-based**. This is a hardcore mode for old men who yell at clouds. At least, it works for old terminals without mouse support.

To run the TUI-mode use the command:
To run Thymus use the command:
```
python -m thymus
```
```
python -m thymus tuier
```
The CLI-mode is invoked by:
```
python -m thymus clier
```

## Documentation

Please, refer to [Wiki](https://github.com/blademd/thymus/wiki).

## Feedback

[Twitter](https://twitter.com/blademd)
[Telegram](https://t.me/blademd)
290 changes: 168 additions & 122 deletions poetry.lock

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
[tool.poetry]
name = "thymus"
version = "0.1.7"
description = "A browser for network configuration files"
version = "0.1.8"
description = "Editor & browser for network configuration files"
authors = ["Igor Malyushkin <gmalyushkin@gmail.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.8.1"
textual = "0.44.1"
thymus-ast = "^0.1.6"
python = "^3.9"
textual = "0.61.0"
thymus-ast = "^0.1.8"
asyncssh = "^2.14.1"
telnetlib3 = "^2.0.4"
bcrypt = "^4.1.1"

msgpack = "^1.0.8"

[tool.ruff]
line-length = 119
Expand Down
28 changes: 6 additions & 22 deletions thymus/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
__version__ = '0.1.7-alpha'
__version__ = '0.1.8'

CONFIG_PATH = 'settings/'
CONFIG_NAME = 'thymus.json'
WELCOME_TEXT = """\
LOGO = """\
▄▄▄█████▓ ██░ ██▓██ ██▓ ███▄ ▄███▓ █ ██ ██████
▓ ██▒ ▓▒▓██░ ██▒▒██ ██▒▓██▒▀█▀ ██▒ ██ ▓██▒▒██ ▒
▒ ▓██░ ▒░▒██▀▀██░ ▒██ ██░▓██ ▓██░▓██ ▒██░░ ▓██▄
Expand All @@ -16,21 +14,7 @@
"""
WELCOME_TEXT_LEN = 55
WRAPPER_DIR = '~/thymus_data/'
SAVES_DIR = 'saves/'
SCREENS_DIR = 'screenshots/'
LOGGING_CONF_DIR = 'settings/'
LOGGING_CONF = LOGGING_CONF_DIR + 'logging.conf'
LOGGING_CONF_ENCODING = 'utf-8'
LOGGING_LEVEL = 'INFO'
LOGGING_FILE_DIR = 'log/'
LOGGING_FILE = LOGGING_FILE_DIR + 'thymus.log'
LOGGING_FILE_ENCODING = 'utf-8'
LOGGING_FILE_MAX_SIZE_BYTES = 5000000
LOGGING_FILE_MAX_INSTANCES = 5
LOGGING_BUF_CAP = 65535
LOGGING_FORMAT = '%(asctime)s %(module)-14s %(levelname)-3s %(message)s'
N_VALUE_LIMIT = 65535
NET_TIMEOUT_LIMIT = 300
CONTEXT_HELP = 'templates/context_help.json'
CODE_SAMPLE = """\
if __name__ == '__main__':
print('SCHLOP SCHLOP SCHLOP')
"""
153 changes: 120 additions & 33 deletions thymus/__main__.py
Original file line number Diff line number Diff line change
@@ -1,51 +1,138 @@
from __future__ import annotations

import sys
from uuid import uuid4

from . import __version__ as app_ver
from . import tuier
from . import clier
from textual import on
from textual.app import App, ComposeResult
from textual.widgets import Footer, Label
from textual.containers import Vertical
from textual.events import Resize

from thymus import __version__ as app_ver
from thymus import LOGO

HELP_MESSAGE = """There are two possible options to run the Application:
* -- run the Terminal User Interface version:
** -- python -m thymus
** -- python -m thymus tuier
from thymus.settings import AppSettings
from thymus.modals import OpenScreen, OpenScreenResult, ErrorScreen, SettingsScreen, ContextListScreen
from thymus.working_screen import WorkingScreen

* -- run the Command Line Interface version:
** -- python -m thymus clier
"""

class Thymus(App):
CSS_PATH = 'styles/main.css'
BINDINGS = [
('ctrl+o', 'request_open', 'Open'),
('ctrl+l', 'request_switch', 'List screens'),
('ctrl+s', 'request_settings', 'Settings'),
]

def run_tui() -> None:
app = tuier.TThymus()
app.run()
def __init__(self) -> None:
super().__init__()

self.working_screens: list[WorkingScreen] = []
self.app_settings = AppSettings()

def run_cli() -> None:
try:
clier.main()
except KeyboardInterrupt:
pass
self.install_screen(OpenScreen(self.app_settings), name='open_screen')

# COMPOSE

def help() -> None:
print(HELP_MESSAGE)
def compose(self) -> ComposeResult:
yield Footer()

with Vertical(id='main-screen-logo', classes='logo'):
yield Label(LOGO.format(app_ver))

def main(args: list[str]) -> None:
if len(args) > 1:
if args[1] == 'clier':
run_cli()
elif args[1] == 'tuier':
run_tui()
elif args[1] == 'version' or args[1] == 'ver':
print(f'Thymus ver. {app_ver}')
# ACTIONS

def action_request_open(self) -> None:
self.push_screen('open_screen', self.open_request_cb)

def action_request_switch(self) -> None:
self.push_screen(ContextListScreen(self.working_screens))

def action_request_settings(self) -> None:
try:
self.get_screen('settings_screen')
except KeyError:
self.install_screen(SettingsScreen(self.app_settings), name='settings_screen')

self.push_screen('settings_screen')

# EVENTS

def on_ready(self) -> None:
self.dark = self.app_settings['night_mode'].value

def on_resize(self, event: Resize) -> None:
# TODO: account for the left panel presence
# TODO: change with a simple text

try:
for control in self.query(Vertical):
if 'logo' in control.classes:
if event.virtual_size.width <= 120:
if control.styles.display != 'none':
control.styles.display = 'none'
else:
if control.styles.display != 'block':
control.styles.display = 'block'
except Exception:
...

@on(WorkingScreen.FetchFailed)
def on_fetch_config_failed(self, event: WorkingScreen.FetchFailed) -> None:
self.app_settings.logger.error(event.reason)
self.switch_screen(ErrorScreen(event.reason))
self.uninstall_screen(event.uid)

@on(WorkingScreen.Release)
def on_working_screen_release(self, event: WorkingScreen.Release) -> None:
platform = event.screen.platform_name.upper()
source = event.screen.source
path = event.screen.path
err = bool(event.error)

if source == 'local':
line = f'File "{path}" [{platform}] closed.'
else:
help()
else:
run_tui()
line = f'Remote file "{path}" [{platform}] closed.'

self.app_settings.logger.info(line)

if err:
self.app_settings.logger.error(f'Context was closed with the error: "{event.error}".')

if event.screen in self.working_screens:
self.working_screens.remove(event.screen)

event.screen.on_release()

if not err:
# pop the current working screen which requested the release
# if there is an error, it was switched by the error modal
self.pop_screen()

self.uninstall_screen(event.screen)

def open_request_cb(self, data: OpenScreenResult) -> None:
try:
screen_uid = str(uuid4())

working_screen = WorkingScreen(data=data, name=screen_uid, settings=self.app_settings)
self.install_screen(screen=working_screen, name=screen_uid)
except Exception as err:
err_msg = f'Error has occurred: {err}'
self.app_settings.logger.error(err_msg)

self.push_screen(ErrorScreen(err_msg))
self.uninstall_screen(screen_uid)
else:
self.working_screens.append(working_screen)
self.app_settings.update_last_opened_platform(data.platform)

if len(self.screen_stack) == 1:
self.push_screen(screen_uid)
else:
self.switch_screen(screen_uid)


if __name__ == '__main__':
main(sys.argv)
Thymus().run()
Loading

0 comments on commit c6adb89

Please sign in to comment.