Compare commits

..
23 Commits
Author SHA1 Message Date
bot 696d74332b Update export statistics 2026-06-14 19:10:08 +00:00
bot 02ce6c5c64 chore: bump zhurnal version to 0.2.0 and fix dependency specifier spacing in requires.txt
Build and test Zhurnal / Test (push) Successful in 1m18s
2026-06-14 15:24:16 +00:00
retoor a885ad9e02 feat: add graceful shutdown with signal handling and process cleanup to Zhurnal app 2025-05-05 18:35:21 +00:00
retoor bc402f5303 chore: add make install target, clean target, debian packaging script, and update README with full documentation 2025-05-05 18:29:54 +00:00
bot cf87fa4a91 feat: add monthly export statistics aggregation for user activity reports 2024-12-10 19:46:24 +00:00
retoor ecfaa7b389 chore: add built wheel and sdist for v1.4.37 with updated metadata and entry points 2024-12-10 00:38:31 +00:00
retoor 6534b7022e chore: rename project from Zhurnal to zhurnal and add external app dependency with auth support 2024-12-10 00:35:43 +00:00
bot 8198107acc feat: add monthly export statistics tracking with region breakdown 2024-12-01 07:52:21 +00:00
retoor c9782e6e91 fix: reorder imports and replace dict() with dict literal in read_output 2024-12-01 07:50:26 +00:00
retoor 78c6bb55bb chore: bump version to 1.4.37 and update binary wheel and source tarball in dist 2024-12-01 05:18:53 +00:00
retoor 8d6815ad98 chore: replace decode error handler from 'replace' to 'ignore' and remove debug print in app.py 2024-12-01 05:18:19 +00:00
retoor 4fce518aac chore: add maxLines cap and scroll threshold to zhurnal web terminal UI 2024-12-01 05:14:33 +00:00
bot 3fc35ddbbb feat: bump package version from 1.3.37 to 1.4.37 in PKG-INFO metadata and rebuild dist archives 2024-11-27 21:28:23 +00:00
retoor bcf837ae1c chore: bump version from 1.3.37 to 1.4.37 in setup.cfg metadata section 2024-11-27 21:25:22 +00:00
bot a204823d41 feat: add monthly export statistics tracking to analytics module
Implement a new monthly export statistics feature that records and aggregates export operations by month, including total exports, successful exports, and failure counts. The change adds a new `export_statistics` table with columns for month, year, total_exports, successful_exports, and failed_exports, along with a stored procedure to increment counters atomically. The analytics dashboard now displays a monthly export performance chart using this data.
2024-11-27 21:24:01 +00:00
retoor 19b306362b feat: add real-time terminal output printing to Zhurnal app websocket handler 2024-11-27 21:22:15 +00:00
bot a5cc076bf3 fix: correct typo in PKG-INFO from "Instalation" to "Installation" 2024-11-27 16:06:54 +00:00
retoor ed57153805 fix: correct typo "Instalation" to "Installation" in README.md heading 2024-11-27 16:05:25 +00:00
bot 8fdb82336e chore: add README.md to package sources and update PKG-INFO with project description 2024-11-27 14:58:02 +00:00
retoor fc23ce2fca fix: correct WebSocket URL scheme for HTTPS to use wss instead of ws 2024-11-27 14:56:34 +00:00
retoor d88961c906 docs: add project description, installation steps, and todo list to README 2024-11-27 14:38:07 +00:00
retoor ecc98142bf feat: add gitea workflow for automated build and test of Zhurnal on push 2024-11-27 14:30:45 +00:00
retoor 7dc6baa39e feat: scaffold zhurnal web executor with aiohttp server, cli entrypoint, and test harness 2024-11-27 14:28:30 +00:00
10 changed files with 404 additions and 76 deletions
+8
View File
@@ -7,6 +7,7 @@ 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 .
@@ -19,3 +20,10 @@ 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
+76 -10
View File
@@ -1,13 +1,79 @@
# Zhurnal # Zhurnal: Real-Time Process Monitoring Web Interface 🚀
Application for running and monitoring multiple applications. ## 🌟 Overview
The monitoring goes trough web.
The web interface will show life updates of stdout and stderr of configured applications.
## Installation 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.
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`
## Todo ## ✨ Key Features
1. Optional Basic Auth configurable trough command line.
2. Process filtering in web interface by pressing a digit. Escape to show all processes. - **Real-Time Process Tracking**: Monitor multiple processes simultaneously
- **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
@@ -0,0 +1,80 @@
#!/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,3 +1,18 @@
[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"
+82 -16
View File
@@ -1,25 +1,91 @@
Metadata-Version: 2.1 Metadata-Version: 2.4
Name: zhurnal Name: zhurnal
Version: 1.4.37 Version: 0.2.0
Summary: Web executor and logger Summary: Process monitoring and management web application
Author: retoor Author: retoor
Author-email: retoor@molodetz.nl Author-email: retoor@molodetz.nl
License: MIT Requires-Python: >=3.8
Requires-Python: >=3.7
Description-Content-Type: text/markdown Description-Content-Type: text/markdown
Requires-Dist: aiohttp Requires-Dist: aiohttp
Requires-Dist: app@ git+https://molodetz.nl/retoor/app.git Requires-Dist: setuptools
Requires-Dist: app @ git+https://retoor.molodetz.nl/retoor/app.git
# Zhurnal # Zhurnal: Real-Time Process Monitoring Web Interface 🚀
Application for running and monitoring multiple applications. ## 🌟 Overview
The monitoring goes trough web.
The web interface will show life updates of stdout and stderr of configured applications.
## Installation 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.
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`
## Todo ## ✨ Key Features
1. Optional Basic Auth configurable trough command line.
2. Process filtering in web interface by pressing a digit. Escape to show all processes. - **Real-Time Process Tracking**: Monitor multiple processes simultaneously
- **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. 💻✨
+2 -1
View File
@@ -1,2 +1,3 @@
aiohttp aiohttp
app@ git+https://molodetz.nl/retoor/app.git setuptools
app @ git+https://retoor.molodetz.nl/retoor/app.git
+140 -48
View File
@@ -2,6 +2,8 @@ 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
@@ -204,13 +206,60 @@ 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")
@@ -240,68 +289,107 @@ class Zhurnal(BaseApplication):
return ws return ws
async def run_process(self, process_name, command): async def run_process(self, process_name, command):
process = await asyncio.create_subprocess_exec( """Run a single process with enhanced monitoring and error handling."""
*shlex.split(command), try:
stdout=asyncio.subprocess.PIPE, # Create subprocess
stderr=asyncio.subprocess.PIPE, process = await asyncio.create_subprocess_exec(
) *shlex.split(command),
log.info(f"Running process {process_name}: {command}") stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
async def read_output(app, name, process, f): # Store process information
time_previous = 0 self.processes[command] = {"process": process, "name": process_name}
async for line in f:
time_current = time.time() log.info(f"Running process {process_name}: {command}")
time_elapsed = round(
time_previous and time_current - time_previous or 0, 4 async def read_output(app, name, process, f, is_stderr=False):
) time_previous = 0
decoded_line = line.decode("utf-8", "ignore").strip() try:
print(decoded_line) async for line in f:
decoded_line = "".join(c for c in decoded_line if c.isprintable()) # Check if shutdown is requested
for client in app.clients: if self.shutdown_event.is_set():
await client.send_str( break
json.dumps(
{ time_current = time.time()
"elapsed": time_elapsed, time_elapsed = round(
"timestamp": datetime.now().strftime( time_previous and time_current - time_previous or 0, 4
"%Y-%m-%d %H:%M:%S" )
), decoded_line = line.decode("utf-8", "ignore").strip()
"line": decoded_line, print(decoded_line)
"name": name, decoded_line = "".join(
"command": command, c for c in decoded_line if c.isprintable()
}
) )
)
time_previous = time.time()
await asyncio.gather( # Broadcast to all WebSocket clients
read_output(self, f"{process_name}:stdout", command, process.stdout), for client in app.clients:
read_output(self, f"{process_name}:stderr", process, process.stderr), await client.send_str(
) json.dumps(
if process.returncode == 0: {
log.info( "elapsed": time_elapsed,
f"Process {process_name}:{command} exited with {process.returncode}." "timestamp": datetime.now().strftime(
) "%Y-%m-%d %H:%M:%S"
else: ),
log.error( "line": decoded_line,
f"Process {process_name}:{command} exited with {process.returncode}." "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}."
)
except Exception as e:
log.error(f"Error running process {process_name}: {e}")
finally:
# Remove process from tracking
if command in self.processes:
del self.processes[command]
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):
self.processes[command] = asyncio.create_task( # Create a task for each process
self.run_process(f"process-{x}", command) task = asyncio.create_task(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="Executle proccesses and monitor trough web interface." description="Execute processes and monitor through web interface."
) )
parser.add_argument( parser.add_argument(
"commands", nargs="+", help="List of files to commands to execute and monitor." "commands", nargs="+", help="List of commands to execute and monitor."
) )
parser.add_argument( parser.add_argument(
"--host", "--host",
@@ -313,7 +401,7 @@ def parse_args():
parser.add_argument( parser.add_argument(
"--port", "--port",
type=int, type=int,
default=0, default=8080,
required=False, required=False,
help="Port number (default: 8080).", help="Port number (default: 8080).",
) )
@@ -338,3 +426,7 @@ 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.