Browse Source

It's not that pretty but it seems to work

master
Zack Marvel 1 year ago
parent
commit
807318bbf5
8 changed files with 150 additions and 22 deletions
  1. +0
    -2
      src/__init__.py
  2. +8
    -3
      src/monitor.py
  3. +52
    -0
      src/package_list.py
  4. +31
    -0
      src/static/packages.js
  5. +16
    -0
      src/static/style.css
  6. +0
    -15
      src/templates/index.html
  7. +39
    -0
      src/templates/index.html.j2
  8. +4
    -2
      src/web.py

+ 0
- 2
src/__init__.py View File

@@ -4,8 +4,6 @@ from .monitor import Monitor


DEFAULT_CONFIG_PATH = 'apt-monitor.ini'


_config = Config(DEFAULT_CONFIG_PATH)

_monitor = Monitor(_config)


+ 8
- 3
src/monitor.py View File

@@ -5,6 +5,8 @@ import asyncssh
import asyncio
from pathlib import Path

from .package_list import PackageList

# 5 minutes
DEFAULT_INTERVAL = 5*60

@@ -28,6 +30,9 @@ class Monitor(threading.Thread):

self._done = False
self._status = {}
for host in self.config.get_hosts():
if host not in ['DEFAULTS', 'apt-monitor']:
self._status[host] = PackageList()

def run(self):
while not self._done:
@@ -48,8 +53,7 @@ class Monitor(threading.Thread):
tasks.append(self.check_host(host))
results = await asyncio.gather(*tasks, return_exceptions=True)
for (host, stdout, stderr) in results:
self._status[host] = list(
filter(lambda line: line != '', stdout))
self._status[host] = PackageList(stdout)
print('HOST {} => {}'.format(host, stderr))

async def check_host(self, host):
@@ -74,7 +78,8 @@ class Monitor(threading.Thread):
upgradable_list = conn.run(cmd)
completed = await upgradable_list
if completed.exit_status == 0:
return (host, completed.stdout.splitlines(),
return (host, list(filter(lambda line: line != '',
completed.stdout.splitlines())),
completed.stderr.splitlines())
else:
raise ConnectionError(host)


+ 52
- 0
src/package_list.py View File

@@ -0,0 +1,52 @@

import re


pattern = re.compile('([A-Za-z0-9\\-_]+)\\/([A-Za-z]+) ([0-9\\.\\-]+) '
'([a-z0-9]+) \\[upgradable from: (.*)\\]')


class PackageList():
def __init__(self, packages=[]):
self.packages = self.parse_packages(packages)

def parse_packages(self, packages):
package_list = []
for line in packages:
match = pattern.match(line)
if match is not None:
# ignore arch for now
package_list.append(Package(match.group(1), match.group(2),
match.group(3), match.group(5)))
return package_list


class Package():
def __init__(self, name, source, new, old):
self.name = name
self.source = source
self.new = new
self.old = old


def test():
case = ['aptitude/stable 0.8.7-1 i386 [upgradable from: 0.6.11-1+b1]']
match = pattern.match(case[0])
assert match is not None
print(match)
assert match.group(1) == 'aptitude'
assert match.group(2) == 'stable'
assert match.group(3) == '0.8.7-1'
assert match.group(4) == 'i386'
assert match.group(5) == '0.6.11-1+b1'
package_list = PackageList(case)
assert len(package_list.packages) == 1
pkg = package_list.packages[0]
assert pkg.name == 'aptitude'
assert pkg.source == 'stable'
assert pkg.new == '0.8.7-1'
assert pkg.old == '0.6.11-1+b1'


if __name__ == '__main__':
test()

+ 31
- 0
src/static/packages.js View File

@@ -0,0 +1,31 @@


document.addEventListener('DOMContentLoaded', registerAnchorCallbacks, false);
document.addEventListener('DOMContentLoaded', hideTables, false);

function registerAnchorCallbacks() {
const anchors = document.getElementsByClassName('host-expand');
Array.prototype.forEach.call(anchors, function (anchor) {
anchor.onclick = expandTable;
});
}

function hideTables() {
const tables = document.getElementsByClassName('host-packages');
Array.prototype.forEach.call(tables, function (table) {
table.style = 'visibility: collapse;';
});
}

function expandTable(e) {
const table = this.nextSibling.nextSibling
if (table) {
console.log('toggle', table);
if (table.visible) {
table.style = 'visibility: collapse;';
} else {
table.style = 'visibility: visible;';
}
table.visible = !table.visible
}
}

+ 16
- 0
src/static/style.css View File

@@ -0,0 +1,16 @@

a.host-expand {
font-weight: bold;
}

table.host-packages {
width: 100%;
}

table.host-packages thead th {
border-bottom: 1px solid black;
}

body {
font-family: sans-serif;
}

+ 0
- 15
src/templates/index.html View File

@@ -1,15 +0,0 @@
<!doctype html>
<html>
<head>
<title>apt-monitor</title>
</head>
<body>
<h1>apt-monitor</h1>
<h2>Hosts</h2>
<ul>
{% for host in config.get_hosts() %}
<li><b>{{ host }}</b>: {{ monitor.get_status(host) }}</li>
{% endfor %}
</ul>
</body>
</html>

+ 39
- 0
src/templates/index.html.j2 View File

@@ -0,0 +1,39 @@
<!doctype html>
<html>
<head>
<title>apt-monitor</title>
<link rel="stylesheet" type="text/css" href="{{ stylesheet }}">
<script src="{{ script }}"></script>
</head>
<body>
<h1>apt-monitor</h1>
<h2>Hosts</h2>
<ul>
{% for host in config.get_hosts() %}
<li>
<a href="#" class="host-expand">{{ host }}</a>: {{ monitor.get_status(host).packages|length }} upgradable
<table class="host-packages">
<thead>
<tr>
<th scope='col'>Package</th>
<th scope='col'>Source</th>
<th scope='col'>Upgradable From</th>
<th scope='col'>Upgradable To</th>
</tr>
</thead>
<tbody>
{% for pkg in monitor.get_status(host).packages %}
<tr>
<th scope='row'>{{ pkg.name }}</th>
<td>{{ pkg.source }}</td>
<td>{{ pkg.old }}</td>
<td>{{ pkg.new }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</li>
{% endfor %}
</ul>
</body>
</html>

+ 4
- 2
src/web.py View File

@@ -1,5 +1,5 @@

from flask import Flask, render_template
from flask import Flask, render_template, url_for

from . import _monitor, _config

@@ -9,4 +9,6 @@ app = Flask(__name__)

@app.route('/')
def index():
return render_template('index.html', monitor=_monitor, config=_config)
return render_template('index.html.j2', monitor=_monitor, config=_config,
stylesheet=url_for('static', filename='style.css'),
script=url_for('static', filename='packages.js'))

Loading…
Cancel
Save