diff --git a/README.md b/README.md index c14e584..5b9b9fc 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,7 @@ Micromamba environments are particularly useful where several software packages | -------------- | ----------------------------------------- | | `base` | micromamba's base environment | | `system` | `supervisord`, `openssh`, `rclone` | +| `fastapi` | `logtail web UI`, `port redirector web UI` | If you are extending this image or running an interactive session where additional software is required, you should almost certainly create a new environment first. See below for guidance. @@ -182,6 +183,25 @@ All processes are managed by [supervisord](https://supervisord.readthedocs.io/en >[!NOTE] >*Some of the included services would not normally be found **inside** of a container. They are, however, necessary here as some cloud providers give no access to the host; Containers are deployed as if they were a virtual machine.* +### Port Redirector + +This is a simple list of links to the web services available inside the container. + +The service will bind to port `1111`. + +For each service, you will find a direct link and, if you have set `CF_QUICK_TUNNELS=true`, a link to the service via a fast and secure Cloudflare tunnel. + +>[!NOTE] +>*This service will not show links to any pre-configured Cloudflare tunnels as the domains are static and already known to the user.* + +### Log Viewer + +The web based log viewer will start on port `1122`. + +It's a very lightweight websocket based stream of the latest updates in `/var/log/logtail.log`. + +This service will also be accessible on any other exposed ports until the program designated to that port is ready to use. + ### Cloudflared The Cloudflare tunnel daemon will start if you have provided a token with the `CF_TUNNEL_TOKEN` environment variable. @@ -260,9 +280,11 @@ If you are logged into the container you can follow the logs by running `logtail Some ports need to be open for the services to run or for certain features of the provided software to function -| Open Port | Service / Description | -| --------------------- | --------------------- | -| `22` | SSH server | +| Open Port | Service / Description | +| --------------------- | ------------------------- | +| `22` | SSH server | +| `1111` | Port redirector web UI | +| `1122` | Log viewer web UI | | `53682` | Rclone interactive config | ## Pre-Configured Templates diff --git a/build/COPY_ROOT/opt/ai-dock/fastapi/logviewer/main.py b/build/COPY_ROOT/opt/ai-dock/fastapi/logviewer/main.py index da459c8..8cd4470 100644 --- a/build/COPY_ROOT/opt/ai-dock/fastapi/logviewer/main.py +++ b/build/COPY_ROOT/opt/ai-dock/fastapi/logviewer/main.py @@ -4,6 +4,7 @@ # import libraries import os +import hashlib from pathlib import Path from fastapi import FastAPI, WebSocket, Request from fastapi.staticfiles import StaticFiles @@ -18,7 +19,7 @@ parser.add_argument("-f", "--file", action="store", help="file to read", type=str, default="/var/log/logtail.log") parser.add_argument("-n", "--numlines", action="store", help="number of lines to stream", type=int, default=250) parser.add_argument("-p", "--port", action="store", help="listen port", required="True", type=int) -parser.add_argument("-r", "--refresh", action="store", help="time to wait in seconds before refreshing", type=int, default=5) +parser.add_argument("-r", "--refresh", action="store", help="time to wait in seconds before refreshing", type=int, default=0) parser.add_argument("-s", "--service", action="store", help="service name", type=str, default="service") parser.add_argument("-t", "--title", action="store", help="page title", type=str, default="Preparing your container...") parser.add_argument("-u", "--urlslug", action="store", help="image slug", type=str, default=os.environ.get('IMAGE_SLUG')) @@ -54,12 +55,17 @@ async def log_reader(n=args.numlines) -> list: @app.websocket("/ai-dock/logtail.sh") async def websocket_endpoint_log(websocket: WebSocket) -> None: + last_logs = [] await websocket.accept() try: while True: await asyncio.sleep(1) logs = await log_reader(args.numlines) - await websocket.send_text(logs) + if not logs == last_logs: + await websocket.send_text(logs) + last_logs = logs + else: + await websocket.send_text("") except Exception as e: print(e) finally: diff --git a/build/COPY_ROOT/opt/ai-dock/fastapi/logviewer/templates/index.html b/build/COPY_ROOT/opt/ai-dock/fastapi/logviewer/templates/index.html index 58e708a..8f2a5ab 100644 --- a/build/COPY_ROOT/opt/ai-dock/fastapi/logviewer/templates/index.html +++ b/build/COPY_ROOT/opt/ai-dock/fastapi/logviewer/templates/index.html @@ -179,6 +179,7 @@

Information

const terminal = document.getElementById("terminal"); const anchor = document.getElementById("anchor"); let has_scrolled = false; + let indicator = "."; let refresh_page_in = function(secs) { setTimeout(function(){ @@ -186,10 +187,18 @@

Information

}, secs * 1000); } + let count = -1; ws_log.onmessage = function (event) { + count++; + if (count >= 4) { + count = 0 + } const logs = document.getElementById("logs"); let log_data = event.data; - logs.innerHTML = log_data; + status.innerHTML = "{{context.title}}" + indicator.repeat(count); + if (log_data !== "") { + logs.innerHTML = log_data; + } if ((logs.offsetHeight > terminal.offsetHeight) && has_scrolled == false) { has_scrolled = true; anchor.scrollIntoView( @@ -200,12 +209,16 @@

Information

} }; ws_log.onclose = function (event) { - status.innerHTML = "Checking container state..." - refresh_page_in({{ context.refresh }}); + status.innerHTML = "Checking container state..."; + if ({{ context.refresh }} > 0) { + refresh_page_in({{ context.refresh }}); + } } ws_log.onerror = function (event) { - status.innerHTML = "Checking container state..." - refresh_page_in({{ context.refresh }}); + status.innerHTML = "Checking container state..."; + if ({{ context.refresh }} > 0) { + refresh_page_in({{ context.refresh }}); + } }