From 6760bc5c2e98ebdc89b74b84e8f8e93e4881e2dd Mon Sep 17 00:00:00 2001
From: jwansek <eddie.atten.ea29@gmail.com>
Date: Tue, 8 Apr 2025 23:08:19 +0100
Subject: Re-added the services page

---
 .gitignore                 |   2 +
 app.py                     |  13 +++++-
 requirements.txt           |   8 ++--
 services.py                |  74 +++++++++++++++++++++++++++++--
 static/index.md            |   2 +
 templates/services.html.j2 | 106 ++++++++++++++-------------------------------
 6 files changed, 123 insertions(+), 82 deletions(-)

diff --git a/.gitignore b/.gitignore
index 2c678d3..087b0b3 100755
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+*.pem
+*.pub
 homelab-wiki/wiki.env
 homelab-wiki/images/*
 edaweb.conf
diff --git a/app.py b/app.py
index 50d5b5b..985bcac 100755
--- a/app.py
+++ b/app.py
@@ -2,7 +2,7 @@ from paste.translogger import TransLogger
 from waitress import serve
 from PIL import Image
 import configparser
-import webbrowser
+import transmission_rpc
 import downloader
 import datetime
 import database
@@ -78,6 +78,17 @@ def index():
 def robots():
     return flask.send_from_directory("static", "robots.txt")
 
+@app.route("/services")
+def serve_services():
+    with database.Database() as db:
+        return flask.render_template(
+            "services.html.j2",
+            **get_template_items("services", db),
+            docker = services.get_all_docker_containers(CONFIG.get("ssh", "docker_key_path")),
+            trans = services.get_torrent_stats(),
+            pihole = services.get_pihole_stats()
+        )
+
 @app.route("/discord")
 def discord():
     with database.Database() as db:
diff --git a/requirements.txt b/requirements.txt
index 0ce280b..581cd7c 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,15 +1,13 @@
 itsdangerous==2.0.1
 PyMySQL==1.0.2
-python_qbittorrent==0.4.2
+transmission_rpc==4.1.0
 Werkzeug==2.3.7
 Flask==2.2.2
 PiHole_api
-transmission-clutch
 dataclasses
 docker
-PiHole-api
+APiHole
 Pillow==9.4.0
-python-qbittorrent==0.4.2
 PyGithub
 lxml
 requests
@@ -19,3 +17,5 @@ houdini.py
 Pygments
 mistune==2.0.3
 twython==3.8.2
+fabric
+paramiko
\ No newline at end of file
diff --git a/services.py b/services.py
index b56a07d..5fb7d81 100755
--- a/services.py
+++ b/services.py
@@ -3,8 +3,9 @@ from io import StringIO
 from lxml import html, etree
 from github import Github
 import multiprocessing
-import pihole as ph
-import qbittorrent
+import paramiko.client
+from APiHole import PiHole
+import transmission_rpc
 import configparser
 import math as maths
 import requests
@@ -12,6 +13,8 @@ import datetime
 import urllib
 import docker
 import random
+import subprocess
+import fabric
 import queue
 import json
 import time
@@ -266,6 +269,68 @@ def scrape_whispa(whispa_url, since):
                 })
     return qnas
 
+def get_docker_containers(host, ssh_key_path):
+    result = fabric.Connection(
+        host = host,
+        user = "root",
+        connect_kwargs = {
+            "key_filename": ssh_key_path,
+            "look_for_keys": False
+        }
+    ).run('docker ps -a -s --format "table {{.Names}};{{.Status}};{{.Image}}"', hide = True)
+    return [line.split(";") for line in result.stdout.split("\n")[1:-1]]
+
+def get_all_docker_containers(ssh_key_path):
+    containers = {}
+    for host, name in CONFIG["docker_hosts"].items():
+        print(host)
+        containers[(host, name)] = get_docker_containers(host, ssh_key_path)
+    return containers
+
+def timeout(func):
+    # cant get this to work with queue.Queue() for some reason?
+    # this works but Manager() uses an extra thread than Queue()
+    manager = multiprocessing.Manager()
+    returnVan = manager.list()
+    # ti = time.time()
+   
+    def runFunc(q, func):
+        q.append(func())
+
+    def beginTimeout():
+        t = multiprocessing.Process(target = runFunc, args = (returnVan, func))
+        t.start()
+
+        t.join(timeout = CONFIG["servicetimeout"].getint("seconds"))
+
+        # print("Request took:", time.time() - ti)
+        try:
+            return returnVan[0]
+        except IndexError:
+            if t.is_alive():
+                t.terminate()
+
+    return beginTimeout
+
+@timeout
+def get_torrent_stats():
+    client = transmission_rpc.client.Client(
+        host = CONFIG.get("transmission", "host")
+    )
+    s = vars(client.session_stats())["fields"]
+    return {
+        "Active torrents:": s["activeTorrentCount"],
+        "Downloaded:": humanbytes(s["cumulative-stats"]["downloadedBytes"]),
+        "Uploaded:": humanbytes(s["cumulative-stats"]["uploadedBytes"]),
+        "Active time:": str(datetime.timedelta(seconds = s["cumulative-stats"]["secondsActive"])),
+        "Files added:": s["cumulative-stats"]["filesAdded"],
+        "Current upload speed": humanbytes(s["uploadSpeed"]) + "s/S",
+        "Current download speed:": humanbytes(s["downloadSpeed"]) + "s/S"
+    }
+
+@timeout
+def get_pihole_stats():
+    return PiHole.GetSummary(CONFIG.get("pihole", "url"), CONFIG.get("pihole", "key"), True)
 
 if __name__ == "__main__":
     # print(get_trans_stats())
@@ -277,4 +342,7 @@ if __name__ == "__main__":
 
     # print(request_recent_commits(since = datetime.datetime.now() - datetime.timedelta(days=30)))
 
-    print(scrape_whispa(CONFIG.get("qnas", "url"), datetime.datetime.fromtimestamp(0.0)))
+    # print(scrape_whispa(CONFIG.get("qnas", "url"), datetime.datetime.fromtimestamp(0.0)))
+    # print(get_all_docker_containers(os.path.join(os.path.dirname(__file__), "edaweb-docker.pem")))
+
+    print(get_torrent_stats())
diff --git a/static/index.md b/static/index.md
index 99bfa0c..39bbc7c 100755
--- a/static/index.md
+++ b/static/index.md
@@ -16,6 +16,8 @@ i'll post my thoughts on here sometimes, and use this site to link to other stuf
 - [git server - github alternative](https://git.eda.gay/)
 - [jellyfin - web player for ~~legally downloaded~~ TV and films](https://jellyfin.eda.gay) - RIP emby!
 
+[see the services im running right now](/services) (takes a couple seconds to load)
+
 these sites are hosted on my [homelab system](https://wiki.eda.gay)
 
 ![startech 8u rack cropped](/img/GcyexeCW0AAYssz.jpg?w=300&h=5000)
diff --git a/templates/services.html.j2 b/templates/services.html.j2
index ebf8ebd..9c9e5ec 100755
--- a/templates/services.html.j2
+++ b/templates/services.html.j2
@@ -3,96 +3,54 @@
 <article id=statusTables>
     <section id=docker>
         <h2>docker</h2>
-        {% if docker == None %}
-            <p>Couldn't access the docker API. Is sherpa running?</p>
-        {% else %}
-            <table>
-                {% for name, status in docker.items() %}
-                    <tr>
-                        <td>{{name}}</td>
-                        {% if status == "running" %}
-                            <td class=running>{{status}}</td>
-                        {% else %}
-                            <td class=notRunning>{{status}}</td>
-                        {% endif %}
-                    </tr>
-                {% endfor %}
-            </table>
-        {% endif %}
+        <ul>
+            {% for host, containers in docker.items() %}
+                <h4>{{ "%s - %s" % (host[0], host[1]) }}</h4>
+                <table>
+                    {% for name, status, image in containers %}
+                        <tr>
+                            <td>{{ name }}</td>
+                            {% if "Up" in status %}
+                                <td class=running>{{ status }}</td>
+                            {% else %}
+                                <td class=notRunning>{{ status }}</td>
+                            {% endif %}
+                            <td>{{ image }}</td>
+                        </tr>
+                    {% endfor %}
+                </table>
+            {% endfor %}
+        </ul>
     </section>
+
     <section id="torrents">
         <h2>transmission</h2>
         {% if trans == None %}
             <p>Couldn't access the transmission API. Is docker container running?</p>
         {% else %}
             <table>
-                <tr>
-                    <td>downloaded</td>
-                    <td>{{trans["bytes_dl"]}}</td>
-                </tr>
-                <tr>
-                    <td>uploaded</td>
-                    <td>{{trans["bytes_up"]}}</td>
-                </tr>
-                <tr>
-                    <td>torrents</td>
-                    <td>{{trans["num"]}}</td>
-                </tr>
-                <tr>
-                    <td>ratio</td>
-                    <td>{{trans["ratio"]}}</td>
-                </tr>
-                <tr>
-                    <td>active for</td>
-                    <td>{{trans["active_for"]}}</td>
-                </tr>
+                {% for k, v in trans.items() %}
+                    <tr>
+                        <td>{{ k }}</td>
+                        <td>{{ v }}</td>
+                    </tr>
+                {% endfor %}
             </table>
         {% endif %}
-    <br>
-    <a>statistics of some old torrent clients which were shut down recently ;_;</a>
-    <br>
-    <img src="https://nitter.eda.gay/pic/media%2FFRnF3kcXwAAHE6v.png%3Fname%3Dorig">
-    <img src="https://nitter.eda.gay/pic/media%2FFRnF6I0WUAEq4FD.png%3Fname%3Dorig">
-    <img src="/img/photo_2022-12-07_14-06-03.jpg">
     </section>
+    
     <section id=pihole>
         <h2>pihole</h2>
         {% if pihole == None %}
             <p>Couldn't access the pihole API. Is docker container running?</p>
         {% else %}
             <table>
-                <tr>
-                    <td>status</td>
-                    {% if pihole["status"] == "enabled" %}
-                        <td class=running>{{pihole["status"]}}</td>
-                    {% else %}
-                        <td class=notRunning>{{pihole["status"]}}</td>
-                    {% endif %}
-                </tr>
-                <tr>
-                    <td>queries</td>
-                    <td>{{pihole["queries"]}}</td>
-                </tr>
-                <tr>
-                    <td>clients</td>
-                    <td>{{pihole["clients"]}}</td>
-                </tr>
-                <tr>
-                    <td>percentage blocked</td>
-                    <td>{{pihole["percentage"]}}%</td>
-                </tr>
-                <tr>
-                    <td>blocked requests</td>
-                    <td>{{pihole["blocked"]}}</td>
-                </tr>
-                <tr>
-                    <td>domains in blocklist</td>
-                    <td>{{pihole["domains"]}}</td>
-                </tr>
-                <tr>
-                    <td>last updated</td>
-                    <td>{{pihole["last_updated"]}}</td>
-                </tr>
+                {% for k, v in pihole.items() %}
+                    <tr>
+                        <td>{{ k }}</td>
+                        <td>{{ v }}</td>
+                    </tr>
+                {% endfor %}
             </table>
         {% endif %}
     </section>
-- 
cgit v1.2.3