From 8ad5b590debfc3bc01e3849e0230711d17c9e587 Mon Sep 17 00:00:00 2001 From: retoor Date: Wed, 27 Nov 2024 15:28:30 +0100 Subject: [PATCH] Initial commit --- .gitignore | 3 + Makefile | 21 ++ dist/Zhurnal-1.3.37-py3-none-any.whl | Bin 0 -> 1327 bytes dist/zhurnal-0.0.0-py3-none-any.whl | Bin 0 -> 1079 bytes dist/zhurnal-0.0.0.tar.gz | Bin 0 -> 795 bytes dist/zhurnal-1.3.37.tar.gz | Bin 0 -> 1123 bytes pyproject.toml | 3 + setup.cfg | 24 ++ src/Zhurnal.egg-info/PKG-INFO | 10 + src/Zhurnal.egg-info/SOURCES.txt | 8 + src/Zhurnal.egg-info/dependency_links.txt | 1 + src/Zhurnal.egg-info/entry_points.txt | 2 + src/Zhurnal.egg-info/requires.txt | 1 + src/Zhurnal.egg-info/top_level.txt | 1 + src/zhurnal.egg-info/PKG-INFO | 3 + src/zhurnal.egg-info/SOURCES.txt | 6 + src/zhurnal.egg-info/dependency_links.txt | 1 + src/zhurnal.egg-info/top_level.txt | 1 + src/zhurnal/__init__.py | 12 + src/zhurnal/__init__py | 0 src/zhurnal/__main__py | 0 src/zhurnal/app.py | 319 ++++++++++++++++++++++ src/zhurnal/tests/__init__.py | 1 + src/zhurnal/tests/app.py | 12 + 24 files changed, 429 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 dist/Zhurnal-1.3.37-py3-none-any.whl create mode 100644 dist/zhurnal-0.0.0-py3-none-any.whl create mode 100644 dist/zhurnal-0.0.0.tar.gz create mode 100644 dist/zhurnal-1.3.37.tar.gz create mode 100644 pyproject.toml create mode 100644 setup.cfg create mode 100644 src/Zhurnal.egg-info/PKG-INFO create mode 100644 src/Zhurnal.egg-info/SOURCES.txt create mode 100644 src/Zhurnal.egg-info/dependency_links.txt create mode 100644 src/Zhurnal.egg-info/entry_points.txt create mode 100644 src/Zhurnal.egg-info/requires.txt create mode 100644 src/Zhurnal.egg-info/top_level.txt create mode 100644 src/zhurnal.egg-info/PKG-INFO create mode 100644 src/zhurnal.egg-info/SOURCES.txt create mode 100644 src/zhurnal.egg-info/dependency_links.txt create mode 100644 src/zhurnal.egg-info/top_level.txt create mode 100644 src/zhurnal/__init__.py create mode 100644 src/zhurnal/__init__py create mode 100644 src/zhurnal/__main__py create mode 100644 src/zhurnal/app.py create mode 100644 src/zhurnal/tests/__init__.py create mode 100644 src/zhurnal/tests/app.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3680851 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.venv +.history +__pycache__ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..26b326c --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +all: ensure_env format build install test + +format: + ./.venv/bin/python -m black . + +ensure_env: + -@python3 -m venv .venv + ./.venv/bin/python -m pip install black + ./.venv/bin/python -m pip install build + +build: + ./.venv/bin/python -m build . + +install: + ./.venv/bin/python -m pip install -e . + +run: + python -m zhurnal "ping -c 1000 google.nl" + +test: + ./.venv/bin/python -m unittest zhurnal.tests diff --git a/dist/Zhurnal-1.3.37-py3-none-any.whl b/dist/Zhurnal-1.3.37-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..fbbf216471a4bf56c24c36932fb5b264a4b8ab0d GIT binary patch literal 1327 zcmWIWW@Zs#U|`^2kcg^|%>0sXv=zvE0mO2Pk|q|5!=0^K`ae7PQC;TZ6J<;JzS=CAf1;n-vGw^O{={7tt& zwnsCcN#BXA@D!!8S%DqbG7Osdvv>B2WH|iXs<~qCd&8$$Hhbn>m5%F9Kl5x$$eO76 z^P_eL%08R-SLbU-C|LixuSg^`!CP`70)+Y@Xk}482Ohm0L9Pq87CFQ z0R0*b#B%uj9PZ)j>ce`_kn6Ak59@{4~oB&fBT@OE7Kg$ES6^+fDesm~mH{RhGKnwV!#|Iz#jRnd6q- zvz)r-xzwY&i=W-_ryyKDBS$taf~s@AcxZ=X!4ld?;+b?97**JBetcYXdC81rYXZ04T{(C0!G~(D7o0Ee2x}@>JS}rePov&m_w}alpKr;{I6L!z z{GX?j*RJT_y1c`i@9kvEBYxiwt7^%)@)ZB}JY}V qFyS#4B@LpRiJk;uW^QTp#%CrZK?ZoUvVk3*2pmc literal 0 HcmV?d00001 diff --git a/dist/zhurnal-0.0.0-py3-none-any.whl b/dist/zhurnal-0.0.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..d203a660feeaf8f20ba43ab33cc01367d8ffc7c1 GIT binary patch literal 1079 zcmWIWW@Zs#U|`^2hz_rg1Tq+yKm-u*0dZAEX;EHcj(%c6fnGr+GeZDebyP%kWbKVz zaXLU%#z3rqP_1jA2ZAY?#U;9#d1?9jzOEsTE{-9NpM7;beLQtMukd>7YMncCeshq) zCF3WbywCV<4O($ZL-&;TnTJi@k}nt)#nEh<&p4?d253Vx5X<7RDcr-=)ra+9MoVQc) zmtfk~k5B7-x0~>NG2^Z@t1NZDYd`a_b%y5sGsi8vXE}AvbE!uSfIpMdo4J6_;Q(S) zJkBb~FNn`cElbVOE2$`14fQD#njhP*UX}O?G$fmmfk6(BVL`6W{y{FKXHFJ1889$h z*nNM}j*aqF0v!sef=o_34MGA0O=b#R`0#bE-|@wt-{tS(vH!j)ddie@n(Tj<#&~~f zUok^tN%+-kg`C|L<=zXQNPHEz7Fa#Grr#| zsCQ?l`Fd0D2@$C)?^N0uyZCkR-({5N^ml)`>bmHhR zN|h!}J84>1t=ev?3Z=xQtRcw~vz7h&8QS##g^D(~>heCl2oNKLn(uu++ac;k-PZ^4 z#Rn57rfh8ma>=SAAza)%p1n>9qpTHPv^+4WY+SmJZ#5Iv3&(aeHhWZaritWgCz4Y2 z6z>Hs@Za<2Y*J=%+98qq>u#_9wU)CZ)soykUe`Pono+40<;>MTQ_|JH^jZI3PX#RgT zHcnz2cfOml8s*t;$RZl-$78b_UR@i(^{M$qf>?n6YxX}=%l_x^zZd_ryZ(0vlz3zP zUrdWK|6v9;viUfz)&FX>|0$>c{4W)A^F%78((u0zj!q_{H0e~+%9`tZIl-LkNN)#o&RI~ zx0(K*M|m_j9o9epU*mrw)WZB<2+iStFa9^fVSkjJ=Es4JhyA(0oz&+~lGqyem;^`y z_-+rAdP<<^8|eRX(A`e`Z?^x-g`n8~MgAlIk^jJN{$IV)t+)O+ z%YR9kK>jx-|FQq~eEGk)|HlRP|B(MK|Kp;FifLW-zjpoaCjX_BE(@q)QeyoN{Xg>m zdHR2W{=ez@ADQ8>Gus&G)>8kQ_5Vsyh5jG?Kl0yq|3Axr^#92JTlxRw9|df7|37v0 zuQkE_e_p`-|IG&ehyUS!_BWQoZJ(|$V2&a2fUZC>*^2h*q000z?x{m+= literal 0 HcmV?d00001 diff --git a/dist/zhurnal-1.3.37.tar.gz b/dist/zhurnal-1.3.37.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..89d6730c6222f7f3ba62578973e9d55977785d10 GIT binary patch literal 1123 zcmV-p1f2UHiwFpw|3+s5|9WV3a&BR4Eio=LE;BbSbYXG;?VDY1+cp@5eOvwt!Phgb z4@)WxcxcgNTd}4Ml5PVQ7y=_PjuuMPD7v-#>q}X-V>`9Gq>kFm=iDfgD3Mb1o`)|< z(=P44IAxRHI7_%{J&NpF+qSmt^=|*JjytyNxGihaYCz*sGi9o89j4spR;JU851i0* zLpyNofcob3_neTlp#Qx-U5{1HM8}~X^}_C>#eW$3)z%4pdv&{vd8Ze;jvt1B7n=BY z&9-IvO~$|CTFM;;__gy-R22RX|A+s>|7-t0{r%U@>$ktW+k5;6z1~*;_xw%&@7aE@ zW!X)}|AG4d3D+!Pnsxr+ky8Aj?zrM?%iWS;o|~Z~etDEk5D# zSW9KGJh6lvjkqE&$NE~TzE!`o`EG}2EES7`FES})!u2Pb3-Tt7d0v_tPhP(#XZ+uB zs(9Hsotn!`tv?Su8j>Sk#wsneX-Mal%r(z-=l!%W7im7xUDKeOM1IU~FF#64-M3gO zueC1VWj`|i-QX_&J@CH~{<~}Z_sxddh?Ya;e=#kT{D;SyYMF_B@;}(lf5&qc{PzRb zKUwXM*PM&?kkl7kviOD@ z;t#BqVsv&c%1oc4jnD%Bk^dwAhn^DuXI@U@tI^)`{~i1fy!5`!^@6^k2w) z6s=cvl?s#QpB=wEIyt6Ua;HcX*W&?^o)*<@)&~U0g`AZthomr_7B-soZm!Zi=`RjM zNm{j-JR4Z0ib*jWc!*Z|LiG?tnwxJ$h}9G1*O~2mkN#-#D-nfd2=`e-(EhCI0U>|Lgg_i}_#VfAD|ge^A5zKTiOt z>0x%N9%gqR@qa)64}u_s{~wJ1WB%{^`M-PD|1tju|6le0Z(S60zx6-H3mij>5aWMM z2~vP7yHR zKll&+LkGnF)nV3$#{W+KI~()=uH*Xn{cj_}{5bdz{)7MEKls0m|KB=^djIi%r)OjR pUn4@OLl6W(5ClOG1VIo4K@bE%5ClOG1VPZ#*JnZCC)NOX0063$SlIvo literal 0 HcmV?d00001 diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..07de284 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" \ No newline at end of file diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8adb6ed --- /dev/null +++ b/setup.cfg @@ -0,0 +1,24 @@ +[metadata] +name = Zhurnal +version = 1.3.37 +description = Web executor and logger +author = retoor +author_email = retoor@molodetz.nl +license = MIT +long_description = file: README.md +long_description_content_type = text/markdown + +[options] +packages = find: +package_dir = + = src +python_requires = >=3.7 +install_requires = + aiohttp + +[options.packages.find] +where = src + +[options.entry_points] +console_scripts = + zhurnal = zhurnal.app:cli diff --git a/src/Zhurnal.egg-info/PKG-INFO b/src/Zhurnal.egg-info/PKG-INFO new file mode 100644 index 0000000..69565e2 --- /dev/null +++ b/src/Zhurnal.egg-info/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 2.1 +Name: Zhurnal +Version: 1.3.37 +Summary: Web executor and logger +Author: retoor +Author-email: retoor@molodetz.nl +License: MIT +Requires-Python: >=3.7 +Description-Content-Type: text/markdown +Requires-Dist: aiohttp diff --git a/src/Zhurnal.egg-info/SOURCES.txt b/src/Zhurnal.egg-info/SOURCES.txt new file mode 100644 index 0000000..a5fab6d --- /dev/null +++ b/src/Zhurnal.egg-info/SOURCES.txt @@ -0,0 +1,8 @@ +pyproject.toml +setup.cfg +src/Zhurnal.egg-info/PKG-INFO +src/Zhurnal.egg-info/SOURCES.txt +src/Zhurnal.egg-info/dependency_links.txt +src/Zhurnal.egg-info/entry_points.txt +src/Zhurnal.egg-info/requires.txt +src/Zhurnal.egg-info/top_level.txt \ No newline at end of file diff --git a/src/Zhurnal.egg-info/dependency_links.txt b/src/Zhurnal.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Zhurnal.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/Zhurnal.egg-info/entry_points.txt b/src/Zhurnal.egg-info/entry_points.txt new file mode 100644 index 0000000..912c411 --- /dev/null +++ b/src/Zhurnal.egg-info/entry_points.txt @@ -0,0 +1,2 @@ +[console_scripts] +zhurnal = zhurnal.app:cli diff --git a/src/Zhurnal.egg-info/requires.txt b/src/Zhurnal.egg-info/requires.txt new file mode 100644 index 0000000..ee4ba4f --- /dev/null +++ b/src/Zhurnal.egg-info/requires.txt @@ -0,0 +1 @@ +aiohttp diff --git a/src/Zhurnal.egg-info/top_level.txt b/src/Zhurnal.egg-info/top_level.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Zhurnal.egg-info/top_level.txt @@ -0,0 +1 @@ + diff --git a/src/zhurnal.egg-info/PKG-INFO b/src/zhurnal.egg-info/PKG-INFO new file mode 100644 index 0000000..7d8530a --- /dev/null +++ b/src/zhurnal.egg-info/PKG-INFO @@ -0,0 +1,3 @@ +Metadata-Version: 2.1 +Name: zhurnal +Version: 0.0.0 diff --git a/src/zhurnal.egg-info/SOURCES.txt b/src/zhurnal.egg-info/SOURCES.txt new file mode 100644 index 0000000..dbc5629 --- /dev/null +++ b/src/zhurnal.egg-info/SOURCES.txt @@ -0,0 +1,6 @@ +pyproject.toml +src/zhurnal/app.py +src/zhurnal.egg-info/PKG-INFO +src/zhurnal.egg-info/SOURCES.txt +src/zhurnal.egg-info/dependency_links.txt +src/zhurnal.egg-info/top_level.txt \ No newline at end of file diff --git a/src/zhurnal.egg-info/dependency_links.txt b/src/zhurnal.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/zhurnal.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/zhurnal.egg-info/top_level.txt b/src/zhurnal.egg-info/top_level.txt new file mode 100644 index 0000000..fb0df89 --- /dev/null +++ b/src/zhurnal.egg-info/top_level.txt @@ -0,0 +1 @@ +zhurnal diff --git a/src/zhurnal/__init__.py b/src/zhurnal/__init__.py new file mode 100644 index 0000000..6c397ad --- /dev/null +++ b/src/zhurnal/__init__.py @@ -0,0 +1,12 @@ +import logging +import sys + +logging.basicConfig( + level=logging.DEBUG, + format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + handlers=[ + logging.StreamHandler(sys.stdout), + ], +) + +log = logging.getLogger(__name__) diff --git a/src/zhurnal/__init__py b/src/zhurnal/__init__py new file mode 100644 index 0000000..e69de29 diff --git a/src/zhurnal/__main__py b/src/zhurnal/__main__py new file mode 100644 index 0000000..e69de29 diff --git a/src/zhurnal/app.py b/src/zhurnal/app.py new file mode 100644 index 0000000..dbe5942 --- /dev/null +++ b/src/zhurnal/app.py @@ -0,0 +1,319 @@ +from aiohttp import web +from typing import List +import shlex +import asyncio +import json +from datetime import datetime +from zhurnal import log +import time + + +index_html = """ + + + + + + Zhurnal + + + +
+ + + +""" + + +class Zhurnal(web.Application): + + def __init__(self, commands: List[str], *args, **kwargs): + self.commands = commands or [] + self.processes = {} + super().__init__(*args, **kwargs) + self.on_startup.append(self.start_processes) + self.clients = [] + self.router.add_get("/ws", self.websocket_handler) + self.router.add_get("/", self.index_handler) + log.info("Application created") + + async def index_handler(self, request): + return web.Response(text=index_html, content_type="text/html") + + async def websocket_handler(self, request): + ws = web.WebSocketResponse() + await ws.prepare(request) + + log.info("WebSocket connection established") + self.clients.append(ws) + + async for msg in ws: + if msg.type == web.WSMsgType.TEXT: + log.info(f"Received message: {msg.data}") + if msg.data == "close": + await ws.close() + else: + await ws.send_str(f"Echo: {msg.data}") + elif msg.type == web.WSMsgType.ERROR: + self.clients.remove(ws) + log.info( + f"WebSocket connection closed with exception: {ws.exception()}" + ) + + log.info("WebSocket connection closed") + if ws in self.clients: + self.clients.remove(ws) + return ws + + async def run_process(self, process_name, command): + process = await asyncio.create_subprocess_exec( + *shlex.split(command), + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE, + ) + log.info("Running process {}: {}".format(process_name, command)) + + async def read_output(app, name, process, f): + time_previous = 0 + async for line in f: + time_current = time.time() + time_elapsed = round( + time_previous and time_current - time_previous or 0, 4 + ) + for client in app.clients: + await client.send_str( + json.dumps( + dict( + elapsed=time_elapsed, + timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"), + line=line.decode().strip(), + name=name, + command=command, + ) + ) + ) + time_previous = time.time() + + await asyncio.gather( + read_output( + self, "{}:stdout".format(process_name), command, process.stdout + ), + read_output( + self, "{}:stderr".format(process_name), process, process.stderr + ), + ) + if process.returncode == 0: + log.info( + "Process {}:{} exited with {}.".format( + process_name, command, process.returncode + ) + ) + else: + log.error( + "Process {}:{} exited with {}.".format( + process_name, command, process.returncode + ) + ) + + async def start_processes(self, app): + print(app) + for x, command in enumerate(self.commands): + self.processes[command] = asyncio.create_task( + self.run_process("process-{}".format(x), command) + ) + # asyncio.create_task(asyncio.gather(*self.processes.values())) + + +def parse_args(): + import argparse + + parser = argparse.ArgumentParser( + description="Executle proccesses and monitor trough web interface." + ) + parser.add_argument( + "commands", nargs="+", help="List of files to commands to execute and monitor." + ) + parser.add_argument( + "--host", + type=str, + default="localhost", + help="Hostname or IP address (default: localhost).", + ) + parser.add_argument( + "--port", type=int, default=8080, help="Port number (default: 8080)." + ) + return parser.parse_args() + + +def cli(): + args = parse_args() + app = Zhurnal(commands=args.commands) + for command in args.commands: + log.info("Preparing execution of {}.".format(command)) + log.info("Host: {} Port: {}".format(args.host, args.port)) + web.run_app(app, host=args.host, port=args.port) diff --git a/src/zhurnal/tests/__init__.py b/src/zhurnal/tests/__init__.py new file mode 100644 index 0000000..ea6df01 --- /dev/null +++ b/src/zhurnal/tests/__init__.py @@ -0,0 +1 @@ +from .app import ZhurnalTestCase diff --git a/src/zhurnal/tests/app.py b/src/zhurnal/tests/app.py new file mode 100644 index 0000000..543b806 --- /dev/null +++ b/src/zhurnal/tests/app.py @@ -0,0 +1,12 @@ +from zhurnal.app import Zhurnal +import unittest + + +class ZhurnalTestCase(unittest.TestCase): + + def setUp(self): + self.commands = ["ls"] + self.app = Zhurnal(commands=self.commands) + + def test_app_available(self): + self.assertTrue(self.app)