Compare commits

..
19 Commits
Author SHA1 Message Date
bot fa4d3a80ae Update export statistics 2024-12-10 19:46:24 +00:00
retoor eb4b0b8575 New build.
Build and test Zhurnal / Test (push) Successful in 2m3s
2024-12-10 01:38:31 +01:00
retoor 983969ed9e Update name.
Build and test Zhurnal / Test (push) Waiting to run
2024-12-10 01:35:43 +01:00
bot 86d4f8f1d3 Update export statistics 2024-12-01 07:52:21 +00:00
retoor 0aa8955b4a Formatted.
Build and test Zhurnal / Test (push) Successful in 1m0s
2024-12-01 08:50:26 +01:00
retoor a2354c2ba7 Release new version.
Build and test Zhurnal / Test (push) Successful in 1m4s
2024-12-01 06:18:53 +01:00
retoor ea825f0190 Replace replace with ignore. 2024-12-01 06:18:19 +01:00
retoor a44108901e New release with beter decoding.
Build and test Zhurnal / Test (push) Successful in 1m11s
2024-12-01 06:14:33 +01:00
bot 14782c06c4 Update export statistics 2024-11-27 21:28:23 +00:00
retoor fdcdca8e87 Version bump
Build and test Zhurnal / Test (push) Successful in 55s
2024-11-27 22:25:24 +01:00
bot 66556bf890 Update export statistics 2024-11-27 21:24:01 +00:00
retoor 0511c74f0b Added screen logging
Build and test Zhurnal / Test (push) Successful in 52s
2024-11-27 22:23:03 +01:00
bot 92db21739a Update export statistics 2024-11-27 16:06:54 +00:00
retoor bc9408fc73 Fixed typo.
Build and test Zhurnal / Test (push) Successful in 1m4s
2024-11-27 17:05:42 +01:00
bot ebfcfb9f67 Update export statistics 2024-11-27 14:58:02 +00:00
retoor 239341c032 Typo.
Build and test Zhurnal / Test (push) Successful in 1m8s
2024-11-27 15:56:34 +01:00
retoor b2a88d9a78 Updated readme
Build and test Zhurnal / Test (push) Successful in 1m3s
2024-11-27 15:38:07 +01:00
retoor 7be813c0aa Added build server
Build and test Zhurnal / Test (push) Waiting to run
2024-11-27 15:30:45 +01:00
retoor 8ad5b590de Initial commit 2024-11-27 15:28:30 +01:00
10 changed files with 77 additions and 405 deletions
-8
View File
@@ -7,7 +7,6 @@ ensure_env:
-@python3 -m venv .venv -@python3 -m venv .venv
./.venv/bin/python -m pip install black ./.venv/bin/python -m pip install black
./.venv/bin/python -m pip install build ./.venv/bin/python -m pip install build
./.venv/bin/python -m pip install -e .
build: build:
./.venv/bin/python -m build . ./.venv/bin/python -m build .
@@ -20,10 +19,3 @@ run:
test: test:
./.venv/bin/python -m unittest zhurnal.tests ./.venv/bin/python -m unittest zhurnal.tests
clean:
rm -rf .venv
rm -rf build
rm -rf dist
find . -type d -name "__pycache__" -exec rm -rf {} +
find . -type f -name "*.pyc" -delete
+10 -76
View File
@@ -1,79 +1,13 @@
# Zhurnal: Real-Time Process Monitoring Web Interface 🚀 # Zhurnal
## 🌟 Overview Application for running and monitoring multiple applications.
The monitoring goes trough web.
The web interface will show life updates of stdout and stderr of configured applications.
Zhurnal is a powerful, lightweight, and intuitive process monitoring solution that brings unprecedented visibility to your command-line executions. Transform the way you track, analyze, and interact with running processes through an elegant web interface. ## Installation
1. Run `make`. This will build envionment and will install the application.
2. Run Zhurnal with the commands as parameters followed by the host / port to serve on. `./.venv/bin/zhurnal "ping google.nl" "ping google.nl" --host=127.0.0.1 --port=8081`
## ✨ Key Features ## Todo
1. Optional Basic Auth configurable trough command line.
- **Real-Time Process Tracking**: Monitor multiple processes simultaneously 2. Process filtering in web interface by pressing a digit. Escape to show all processes.
- **Web-Based Dashboard**: Beautiful, responsive interface with live updates
- **Instant Output Streaming**: See command outputs as they happen
- **Process Lifecycle Management**: Automatic process group termination
- **Clipboard-Friendly**: Click-to-copy output lines
- **Minimal Overhead**: Lightweight and efficient design
## đź”§ Installation
### Using pip
```bash
pip install git+https://retoor.molodetz.nl/retoor/zhurnal.git
```
### From Source
```bash
git clone https://retoor.molodetz.nl/retoor/zhurnal.git
cd zhurnal
make install
```
## 🚀 Quick Start
Run multiple processes and monitor them in real-time:
```bash
zhurnal "ping google.com" "docker ps" "top"
```
### Advanced Usage
```bash
# Specify host and port
zhurnal --host 0.0.0.0 --port 8080 "your_command" "another_command"
# With authentication
zhurnal --username admin --password secret "sensitive_process"
```
## đź’ˇ Why Zhurnal?
- **Developer-Friendly**: Perfect for DevOps, system administrators, and developers
- **No More Terminal Switching**: Single web interface for multiple processes
- **Performance Insights**: Elapsed time and detailed process information
- **Cross-Platform**: Works on Linux, macOS, and Windows
## đź”’ Security
- Process isolation
- Optional authentication
- Secure WebSocket connections
## 📦 System Requirements
- Python 3.8+
- Modern web browser
- Minimal system resources
## 🤝 Contributing
Contributions are welcome!
## đź“„ License
MIT License
---
**Zhurnal**: Because monitoring should be as smooth as your workflow. 💻✨
-80
View File
@@ -1,80 +0,0 @@
#!/bin/bash
set -e
# Ensure script is run with sudo or as root
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root or with sudo"
exit 1
fi
# Project details
PROJECT_NAME="zhurnal"
VERSION=$(python3 -c "import importlib.metadata; print(importlib.metadata.version('$PROJECT_NAME'))")
ARCH=$(dpkg --print-architecture)
# Create package directory structure
PACKAGE_DIR="/tmp/${PROJECT_NAME}_${VERSION}_${ARCH}"
mkdir -p "${PACKAGE_DIR}/DEBIAN"
mkdir -p "${PACKAGE_DIR}/usr/local/bin"
mkdir -p "${PACKAGE_DIR}/usr/local/lib/python3/dist-packages/${PROJECT_NAME}"
mkdir -p "${PACKAGE_DIR}/etc/${PROJECT_NAME}"
# Create control file
cat > "${PACKAGE_DIR}/DEBIAN/control" << EOF
Package: ${PROJECT_NAME}
Version: ${VERSION}
Section: utils
Priority: optional
Architecture: ${ARCH}
Depends: python3 (>= 3.8), python3-pip, python3-venv
Maintainer: Your Name <your.email@example.com>
Description: Zhurnal - A command-line utility for running and monitoring commands
EOF
# Create postinst script for additional setup
cat > "${PACKAGE_DIR}/DEBIAN/postinst" << EOF
#!/bin/bash
set -e
# Create virtual environment if not exists
if [ ! -d "/opt/${PROJECT_NAME}/.venv" ]; then
python3 -m venv /opt/${PROJECT_NAME}/.venv
/opt/${PROJECT_NAME}/.venv/bin/pip install --upgrade pip
/opt/${PROJECT_NAME}/.venv/bin/pip install git+https://retoor.molodetz.nl/retoor/zhurnal.git
fi
# Create symlink to binary
ln -sf /opt/${PROJECT_NAME}/.venv/bin/${PROJECT_NAME} /usr/local/bin/${PROJECT_NAME}
exit 0
EOF
# Create prerm script for cleanup
cat > "${PACKAGE_DIR}/DEBIAN/prerm" << EOF
#!/bin/bash
set -e
# Remove symlink
rm -f /usr/local/bin/${PROJECT_NAME}
exit 0
EOF
# Make scripts executable
chmod 755 "${PACKAGE_DIR}/DEBIAN/postinst"
chmod 755 "${PACKAGE_DIR}/DEBIAN/prerm"
# Copy project files
cp -r src/${PROJECT_NAME}/* "${PACKAGE_DIR}/usr/local/lib/python3/dist-packages/${PROJECT_NAME}/"
# Build the package
dpkg-deb --build "${PACKAGE_DIR}"
# Move package to current directory
mv "/tmp/${PROJECT_NAME}_${VERSION}_${ARCH}.deb" .
# Cleanup
rm -rf "${PACKAGE_DIR}"
echo "Debian package created successfully: ${PROJECT_NAME}_${VERSION}_${ARCH}.deb"
Binary file not shown.
BIN
View File
Binary file not shown.
-15
View File
@@ -1,18 +1,3 @@
[build-system] [build-system]
requires = ["setuptools", "wheel"] requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project]
name = "zhurnal"
version = "0.2.0"
description = "Process monitoring and management web application"
requires-python = ">=3.8"
dependencies = [
"aiohttp",
"setuptools",
"app @ git+https://retoor.molodetz.nl/retoor/app.git"
]
readme = { file = "README.md", content-type = "text/markdown" }
[project.scripts]
zhurnal = "zhurnal.app:cli"
+16 -82
View File
@@ -1,91 +1,25 @@
Metadata-Version: 2.4 Metadata-Version: 2.1
Name: zhurnal Name: zhurnal
Version: 0.2.0 Version: 1.4.37
Summary: Process monitoring and management web application Summary: Web executor and logger
Author: retoor Author: retoor
Author-email: retoor@molodetz.nl Author-email: retoor@molodetz.nl
Requires-Python: >=3.8 License: MIT
Requires-Python: >=3.7
Description-Content-Type: text/markdown Description-Content-Type: text/markdown
Requires-Dist: aiohttp Requires-Dist: aiohttp
Requires-Dist: setuptools Requires-Dist: app@ git+https://molodetz.nl/retoor/app.git
Requires-Dist: app @ git+https://retoor.molodetz.nl/retoor/app.git
# Zhurnal: Real-Time Process Monitoring Web Interface 🚀 # Zhurnal
## 🌟 Overview Application for running and monitoring multiple applications.
The monitoring goes trough web.
The web interface will show life updates of stdout and stderr of configured applications.
Zhurnal is a powerful, lightweight, and intuitive process monitoring solution that brings unprecedented visibility to your command-line executions. Transform the way you track, analyze, and interact with running processes through an elegant web interface. ## Installation
1. Run `make`. This will build envionment and will install the application.
2. Run Zhurnal with the commands as parameters followed by the host / port to serve on. `./.venv/bin/zhurnal "ping google.nl" "ping google.nl" --host=127.0.0.1 --port=8081`
## ✨ Key Features ## Todo
1. Optional Basic Auth configurable trough command line.
- **Real-Time Process Tracking**: Monitor multiple processes simultaneously 2. Process filtering in web interface by pressing a digit. Escape to show all processes.
- **Web-Based Dashboard**: Beautiful, responsive interface with live updates
- **Instant Output Streaming**: See command outputs as they happen
- **Process Lifecycle Management**: Automatic process group termination
- **Clipboard-Friendly**: Click-to-copy output lines
- **Minimal Overhead**: Lightweight and efficient design
## đź”§ Installation
### Using pip
```bash
pip install git+https://retoor.molodetz.nl/retoor/zhurnal.git
```
### From Source
```bash
git clone https://retoor.molodetz.nl/retoor/zhurnal.git
cd zhurnal
make install
```
## 🚀 Quick Start
Run multiple processes and monitor them in real-time:
```bash
zhurnal "ping google.com" "docker ps" "top"
```
### Advanced Usage
```bash
# Specify host and port
zhurnal --host 0.0.0.0 --port 8080 "your_command" "another_command"
# With authentication
zhurnal --username admin --password secret "sensitive_process"
```
## đź’ˇ Why Zhurnal?
- **Developer-Friendly**: Perfect for DevOps, system administrators, and developers
- **No More Terminal Switching**: Single web interface for multiple processes
- **Performance Insights**: Elapsed time and detailed process information
- **Cross-Platform**: Works on Linux, macOS, and Windows
## đź”’ Security
- Process isolation
- Optional authentication
- Secure WebSocket connections
## 📦 System Requirements
- Python 3.8+
- Modern web browser
- Minimal system resources
## 🤝 Contributing
Contributions are welcome!
## đź“„ License
MIT License
---
**Zhurnal**: Because monitoring should be as smooth as your workflow. 💻✨
+1 -2
View File
@@ -1,3 +1,2 @@
aiohttp aiohttp
setuptools app@ git+https://molodetz.nl/retoor/app.git
app @ git+https://retoor.molodetz.nl/retoor/app.git
+49 -141
View File
@@ -2,8 +2,6 @@ import asyncio
import json import json
import shlex import shlex
import time import time
import signal
import sys
from datetime import datetime from datetime import datetime
from typing import List from typing import List
from app.app import Application as BaseApplication from app.app import Application as BaseApplication
@@ -206,60 +204,13 @@ class Zhurnal(BaseApplication):
def __init__(self, commands: List[str], *args, **kwargs): def __init__(self, commands: List[str], *args, **kwargs):
self.commands = commands or [] self.commands = commands or []
self.processes = {} self.processes = {}
self.process_tasks = {}
self.shutdown_event = asyncio.Event()
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# Register signal handlers for graceful shutdown
loop = asyncio.get_event_loop()
for sig in (signal.SIGINT, signal.SIGTERM):
loop.add_signal_handler(sig, self.handle_exit)
self.on_startup.append(self.start_processes) self.on_startup.append(self.start_processes)
self.on_cleanup.append(self.cleanup_processes)
self.clients = [] self.clients = []
self.router.add_get("/ws", self.websocket_handler) self.router.add_get("/ws", self.websocket_handler)
self.router.add_get("/", self.index_handler) self.router.add_get("/", self.index_handler)
log.info("Application created") log.info("Application created")
def handle_exit(self):
"""Handle application exit signals."""
log.info("Received exit signal. Initiating graceful shutdown...")
self.shutdown_event.set()
async def cleanup_processes(self, app):
"""Cleanup all running subprocesses."""
log.info("Cleaning up processes...")
# Terminate all running processes
for command, process_info in list(self.processes.items()):
try:
# Get the subprocess
process = process_info.get("process")
if process and process.returncode is None:
log.info(f"Terminating process: {command}")
try:
# First try to terminate gracefully
process.terminate()
# Wait a short time for process to exit
await asyncio.wait_for(process.wait(), timeout=5.0)
except asyncio.TimeoutError:
# If process doesn't exit, force kill
log.warning(f"Force killing process: {command}")
process.kill()
except Exception as e:
log.error(f"Error cleaning up process {command}: {e}")
# Cancel any running tasks
for task in list(self.process_tasks.values()):
if not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
async def index_handler(self, request): async def index_handler(self, request):
return web.Response(text=index_html, content_type="text/html") return web.Response(text=index_html, content_type="text/html")
@@ -289,107 +240,68 @@ class Zhurnal(BaseApplication):
return ws return ws
async def run_process(self, process_name, command): async def run_process(self, process_name, command):
"""Run a single process with enhanced monitoring and error handling.""" process = await asyncio.create_subprocess_exec(
try: *shlex.split(command),
# Create subprocess stdout=asyncio.subprocess.PIPE,
process = await asyncio.create_subprocess_exec( stderr=asyncio.subprocess.PIPE,
*shlex.split(command), )
stdout=asyncio.subprocess.PIPE, log.info(f"Running process {process_name}: {command}")
stderr=asyncio.subprocess.PIPE,
)
# Store process information async def read_output(app, name, process, f):
self.processes[command] = {"process": process, "name": process_name} time_previous = 0
async for line in f:
log.info(f"Running process {process_name}: {command}") time_current = time.time()
time_elapsed = round(
async def read_output(app, name, process, f, is_stderr=False): time_previous and time_current - time_previous or 0, 4
time_previous = 0
try:
async for line in f:
# Check if shutdown is requested
if self.shutdown_event.is_set():
break
time_current = time.time()
time_elapsed = round(
time_previous and time_current - time_previous or 0, 4
)
decoded_line = line.decode("utf-8", "ignore").strip()
print(decoded_line)
decoded_line = "".join(
c for c in decoded_line if c.isprintable()
)
# Broadcast to all WebSocket clients
for client in app.clients:
await client.send_str(
json.dumps(
{
"elapsed": time_elapsed,
"timestamp": datetime.now().strftime(
"%Y-%m-%d %H:%M:%S"
),
"line": decoded_line,
"name": name,
"command": command,
}
)
)
time_previous = time.time()
except Exception as e:
log.error(f"Error reading {name} output: {e}")
finally:
log.info(f"Finished reading {name} output")
# Read stdout and stderr concurrently
await asyncio.gather(
read_output(self, f"{process_name}:stdout", process, process.stdout),
read_output(
self,
f"{process_name}:stderr",
process,
process.stderr,
is_stderr=True,
),
)
# Wait for process to complete
await process.wait()
# Log process exit status
if process.returncode == 0:
log.info(
f"Process {process_name}:{command} exited successfully with {process.returncode}."
)
else:
log.error(
f"Process {process_name}:{command} exited with non-zero status {process.returncode}."
) )
decoded_line = line.decode("utf-8", "ignore").strip()
print(decoded_line)
decoded_line = "".join(c for c in decoded_line if c.isprintable())
for client in app.clients:
await client.send_str(
json.dumps(
{
"elapsed": time_elapsed,
"timestamp": datetime.now().strftime(
"%Y-%m-%d %H:%M:%S"
),
"line": decoded_line,
"name": name,
"command": command,
}
)
)
time_previous = time.time()
except Exception as e: await asyncio.gather(
log.error(f"Error running process {process_name}: {e}") read_output(self, f"{process_name}:stdout", command, process.stdout),
finally: read_output(self, f"{process_name}:stderr", process, process.stderr),
# Remove process from tracking )
if command in self.processes: if process.returncode == 0:
del self.processes[command] log.info(
f"Process {process_name}:{command} exited with {process.returncode}."
)
else:
log.error(
f"Process {process_name}:{command} exited with {process.returncode}."
)
async def start_processes(self, app): async def start_processes(self, app):
"""Start all configured processes."""
for x, command in enumerate(self.commands): for x, command in enumerate(self.commands):
# Create a task for each process self.processes[command] = asyncio.create_task(
task = asyncio.create_task(self.run_process(f"process-{x}", command)) self.run_process(f"process-{x}", command)
self.process_tasks[command] = task )
# asyncio.create_task(asyncio.gather(*self.processes.values()))
def parse_args(): def parse_args():
import argparse import argparse
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="Execute processes and monitor through web interface." description="Executle proccesses and monitor trough web interface."
) )
parser.add_argument( parser.add_argument(
"commands", nargs="+", help="List of commands to execute and monitor." "commands", nargs="+", help="List of files to commands to execute and monitor."
) )
parser.add_argument( parser.add_argument(
"--host", "--host",
@@ -401,7 +313,7 @@ def parse_args():
parser.add_argument( parser.add_argument(
"--port", "--port",
type=int, type=int,
default=8080, default=0,
required=False, required=False,
help="Port number (default: 8080).", help="Port number (default: 8080).",
) )
@@ -426,7 +338,3 @@ def cli():
log.info(f"Host: {args.host} Port: {args.port}") log.info(f"Host: {args.host} Port: {args.port}")
app.run(host=args.host, port=args.port) app.run(host=args.host, port=args.port)
if __name__ == "__main__":
cli()
Binary file not shown.