From 295410075f4af8fb47afffde616060b909e83372 Mon Sep 17 00:00:00 2001 From: Jack Date: Sun, 30 Aug 2026 04:48:55 +0000 Subject: [PATCH] initial commit --- __pycache__/database.cpython-312.pyc | Bin 0 -> 6427 bytes app.py | 310 +++++++++++++++++++++++++++ database.py | 132 ++++++++++++ oldapp.py | 255 ++++++++++++++++++++++ requirements.txt | 2 + snmp_monitor.db | Bin 0 -> 258048 bytes templates/base.html | 41 ++++ templates/graph.html | 79 +++++++ templates/index.html | 147 +++++++++++++ 9 files changed, 966 insertions(+) create mode 100644 __pycache__/database.cpython-312.pyc create mode 100644 app.py create mode 100644 database.py create mode 100644 oldapp.py create mode 100644 requirements.txt create mode 100644 snmp_monitor.db create mode 100644 templates/base.html create mode 100644 templates/graph.html create mode 100644 templates/index.html diff --git a/__pycache__/database.cpython-312.pyc b/__pycache__/database.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..23f18baf3166b9ca8500aa7c9892f588a363cff0 GIT binary patch literal 6427 zcmd^DOKcm*8Q$eC$z47~y<)q0I2$XC#DMi9t<$85Ezlxm(TmyC`CEjd{(%ox@|cC4%TTyWiIhlJsRG_TRk}tO zXqpn4f=6U;QUyi~i{6`bffar5-X_a2!oqDSz($)wxwMP$+(}Ulu599QKb%d?aox8OTn1xyZ zNIH|#zD$i!iru`R18G0%g;y!^>Yl4*pM52>)K4#6rLHlv)Kxm_&6a)*dl?YSG?7cj z^GP8eKa)-hsYxL_l^2qiQ`7lrK`D_BB}Ep-?9TX3MHEumeDZ8ECtS>>GV$E3a3MJ> z#AotTsVt0^NoMo0wm0gfnk3|tmpiH}i^Y;C5=oZzMt$b&Ogi2E@?wK1_JoP#WPB!_ z7f!r+qPRZB}I~V4SKSD$XY{`YDHyv(OA1+%{@v!I(dAw zy{}X+%~d65W30ZNt0WTGh*&HsLOzvAPUqv9i}tt^Gr1gWUD0l__b{rLN||Sb4JHvQ z^$)*3ZU+8E7vM;a-4M+!!*&*Bscr++zB@)ck;)%gd*0rTPD55owZ&)ImG)OBr*g^E z*{sgMF?+pHA(xy?!fs9^Z5EGJL}MS3!$xW4wdrI!IguA!P*3KjGB)wksZ0vUwsI<- z&I8>I8?me+o=6B2Q|XyZ*8M)x+=lcBQsqdL)BN&}sue~0w&pK4>UF8CX#T{R;>CFW zoaQSp5xGG$zjRG11247ysYQvDluDysswN46CLgl9qG;Z7wINGUkH+FC8eNvrWeUQR zkz-dHHR+flRU67NaKk0j_v+=jk}MrvT-N**u#ciRNBZFLv=Lkju7{iPmXGrKZl!LdT2v^)J*GllzIE8H;<1W{sza>zz&6Kk@N4|$ z;PYex9H-i?qY2QN7TAXut+&zpJa_X(pDhKcN3==9hO+`J~l^@&gAeEg?=JIGt zQ#Mn_(8tCE$EC&ubeu84c8pNb=Kf+X);fmq{djr?Y-VgcCba#G9#M{XVdVsYAQ*%R zhZ$l)@Xu>}wInN~akU~6{IgMBV{u_xz*(5a;=(lEUYOR`zA!D|m?OamOZxPoZR?hC zKD1&z5!^j30Fk6<^dQee#WA?#ImmkOdz}Dbya6K5g9rz^iY4legJS=ST=N|hR z?|Hh#Ib7}^xEs6^Y^F?7-%M`@hd0^b9hik`lQ_srotyuJ!alg^mIZFk<3x~Ds%Xn1 z5E(sKr;DL3BBetN_2A#C2WA*B=5^;TUV99FzGC{)*M&A0I+aU+=R7kDvLVWMut#HZ zjjLJ^_)?rNl|d^mJABf(c}Xc>DOIZmlNc8j{H6<*ag%`r1_dIZRf^Exq=8Icf!256 zk~5HX^5Bbuhwom$bG@0^VuxKm&*a2r--#{ujqSe3#=_dddbN4hh52AJOJ0Bx%cdfQ zB1It}I^_R?!ag9UT}rSUE(7-8PLeRVP_23k93Fq z(m4pfC>2i$w(QI`z7S@I2RckJJ1f{r_lhHu(Ef^ORVYtWK_ zOMVZs{}t~y9?U*SZ?W<1aNkB@t+4)nbJB(RKr>@tj_b7&8lH~%@1U>`%sG|o8G?9~ z-y;Os0Z8?!e!!J;;mY-52ZSlE8#^3xUe<%n9Xr^}Tuk8cz}$SnuAffk1$6DFAWS&U z9iPy>S`=P)~f z*+IxaWEn!89KtG_TrwWMkW@x8s1;sX#1pN>uGgUpIuaR=)1BVo^Ms5S(uar%wsim&mcvdgJP7Oc?#v^;O|Y z0}k4w4!vw;YQL(_77cU;nr>>vH{T$&l0sgF|7e6BlagM28i#=zXZ;zugNq@QE>gX$m8@LNmH8!)py9Q>vgvWScPL zE$DF#`BH$a(|J7uUqF2Xhd+K1d2$eV66x=8(t(frEPly~(qA2d*`guCLeQZZUjguo zl78&g&s+qBI6=KcFqrCoR_C|A=zEmTJ^i0O;ob%9w^Wr*lXsyf9;oEgkhK_^rvF0q z{gry@%fJgw|08yk9@xpz0s6T|hy3)=qd`ACxHHJp1CI{)AnWI#c{oH5@4OhIhxGCP E3u4L5L;wH) literal 0 HcmV?d00001 diff --git a/app.py b/app.py new file mode 100644 index 0000000..2239350 --- /dev/null +++ b/app.py @@ -0,0 +1,310 @@ +from flask import Flask, render_template, request, jsonify, redirect, url_for +from apscheduler.schedulers.background import BackgroundScheduler +from datetime import datetime +import subprocess +import re +import database as db + +app = Flask(__name__) + +# Modem configuration profiles +MODEM_PROFILES = { + 'rv50': { + 'oid_sent': '.1.3.6.1.4.1.20542.9.1.1.2.284.0', + 'oid_recv': '.1.3.6.1.4.1.20542.9.1.1.2.283.0', + 'type': 'counter64' + }, + 'bullet-lte': { + 'oid_sent': '.1.3.6.1.4.1.21703.6030.9.4.3.0', + 'oid_recv': '.1.3.6.1.4.1.21703.6030.9.4.1.0', + 'type': 'displaystring' + } +} + +# Store last readings for rate calculation +last_readings = {} + +scheduler = BackgroundScheduler() +scheduler.start() + +def format_bytes(bytes_value): + """Convert bytes to human-readable format""" + if bytes_value is None or bytes_value == 0: + return "N/A", 0 + + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if abs(bytes_value) < 1024.0: + return f"{bytes_value:.2f} {unit}", bytes_value + bytes_value /= 1024.0 + return f"{bytes_value:.2f} PB", bytes_value + +def parse_snmp_value(value, data_type): + """Parse SNMP response based on data type""" + if value is None: + return None + + if data_type == 'counter64': + # Counter64: "Counter64: 1234567890" + match = re.search(r'Counter64:\s*(\d+)', value) + if match: + return int(match.group(1)) + + elif data_type == 'displaystring': + # DisplayString: "STRING: 1234567890" or just the number + match = re.search(r'STRING:\s*"?([^"]+)"?', value) + if match: + str_value = match.group(1).strip() + # Remove any non-digit characters (commas, spaces, etc.) + digits = re.sub(r'[^\d]', '', str_value) + if digits: + return int(digits) + # Fallback: try to find any number in the output + digits = re.sub(r'[^\d]', '', value) + if digits: + return int(digits) + + return None + +def snmp_get(ip, port, community, oid): + """Execute snmpget command and return raw output""" + try: + cmd = [ + 'snmpget', '-v', '2c', '-c', community, + '-t', '5', '-r', '2', + f'{ip}:{port}', oid + ] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + return result.stdout.strip() + return None + except Exception as e: + print(f"SNMP error: {e}") + return None + +def poll_target(target_id, ip, port, community, modem_type): + """Poll a target and store the reading""" + global last_readings + + profile = MODEM_PROFILES.get(modem_type, MODEM_PROFILES['rv50']) + data_type = profile['type'] + + # Get raw SNMP responses + raw_sent = snmp_get(ip, port, community, profile['oid_sent']) + raw_recv = snmp_get(ip, port, community, profile['oid_recv']) + + # Parse values based on data type + bytes_sent = parse_snmp_value(raw_sent, data_type) + bytes_recv = parse_snmp_value(raw_recv, data_type) + + if bytes_sent is not None and bytes_recv is not None: + db.add_reading(target_id, bytes_sent, bytes_recv) + + # Calculate rates (bytes per second) + rate_sent = 0 + rate_recv = 0 + + if target_id in last_readings: + last_sent, last_recv, last_time = last_readings[target_id] + time_diff = (datetime.now() - last_time).total_seconds() + if time_diff > 0 and bytes_sent >= last_sent and bytes_recv >= last_recv: + rate_sent = (bytes_sent - last_sent) / time_diff + rate_recv = (bytes_recv - last_recv) / time_diff + + last_readings[target_id] = (bytes_sent, bytes_recv, datetime.now()) + + return { + 'bytes_sent': bytes_sent, + 'bytes_recv': bytes_recv, + 'rate_sent': rate_sent, + 'rate_recv': rate_recv, + 'success': True + } + + return {'success': False, 'error': 'Failed to parse SNMP response'} + +def scheduled_poll(): + """Background job to poll all enabled targets""" + targets = db.get_all_targets() + for target in targets: + if target['enabled']: + poll_target( + target['id'], + target['ip_address'], + target['port'], + target['community'], + target['modem_type'] + ) + +@app.route('/') +def index(): + targets = db.get_all_targets() + target_data = [] + + for target in targets: + latest = db.get_latest_reading(target['id']) + + if latest: + sent_formatted, _ = format_bytes(latest['bytes_sent']) + recv_formatted, _ = format_bytes(latest['bytes_recv']) + + # Calculate rate if we have previous reading + rate_sent = 0 + rate_recv = 0 + if target['id'] in last_readings: + last_sent, last_recv, last_time = last_readings[target['id']] + time_diff = (datetime.now() - last_time).total_seconds() + if time_diff > 0 and latest['bytes_sent'] >= last_sent: + rate_sent = (latest['bytes_sent'] - last_sent) / time_diff + rate_recv = (latest['bytes_recv'] - last_recv) / time_diff + + rate_sent_fmt, _ = format_bytes(rate_sent) + rate_recv_fmt, _ = format_bytes(rate_recv) + else: + sent_formatted = recv_formatted = rate_sent_fmt = rate_recv_fmt = "N/A" + + target_data.append({ + **target, + 'bytes_sent_fmt': sent_formatted, + 'bytes_recv_fmt': recv_formatted, + 'rate_sent_fmt': f"{rate_sent_fmt}/s", + 'rate_recv_fmt': f"{rate_recv_fmt}/s", + 'last_poll': latest['timestamp'] if latest else None + }) + + return render_template('index.html', targets=target_data) + +@app.route('/add', methods=['POST']) +def add_target(): + name = request.form.get('name', 'Unnamed') + ip_address = request.form.get('ip_address') + port = int(request.form.get('port', 161)) + community = request.form.get('community', 'public') + period_minutes = int(request.form.get('period_minutes', 5)) + modem_type = request.form.get('modem_type', 'rv50') + + if ip_address: + target_id = db.add_target(name, ip_address, port, community, period_minutes, modem_type) + + scheduler.add_job( + scheduled_poll, + 'interval', + minutes=period_minutes, + id=f'target_{target_id}', + replace_existing=True + ) + + poll_target(target_id, ip_address, port, community, modem_type) + + return redirect(url_for('index')) + +@app.route('/edit/', methods=['POST']) +def edit_target(target_id): + name = request.form.get('name', 'Unnamed') + ip_address = request.form.get('ip_address') + port = int(request.form.get('port', 161)) + community = request.form.get('community', 'public') + period_minutes = int(request.form.get('period_minutes', 5)) + enabled = 1 if request.form.get('enabled') else 0 + modem_type = request.form.get('modem_type', 'rv50') + + db.update_target(target_id, name, ip_address, port, community, period_minutes, enabled, modem_type) + + if enabled: + scheduler.add_job( + scheduled_poll, + 'interval', + minutes=period_minutes, + id=f'target_{target_id}', + replace_existing=True + ) + else: + try: + scheduler.remove_job(f'target_{target_id}') + except: + pass + + return redirect(url_for('index')) + +@app.route('/delete/', methods=['POST']) +def delete_target(target_id): + try: + scheduler.remove_job(f'target_{target_id}') + except: + pass + db.delete_target(target_id) + return redirect(url_for('index')) + +@app.route('/poll/', methods=['POST']) +def manual_poll(target_id): + target = db.get_target(target_id) + if target: + result = poll_target( + target['id'], + target['ip_address'], + target['port'], + target['community'], + target['modem_type'] + ) + return jsonify(result) + return jsonify({'success': False, 'error': 'Target not found'}), 404 + +@app.route('/graph/') +def graph(target_id): + target = db.get_target(target_id) + hours = request.args.get('hours', 24, type=int) + readings = db.get_readings(target_id, hours) + + labels = [] + sent_data = [] + recv_data = [] + + for r in readings: + labels.append(r['timestamp']) + sent_data.append(r['bytes_sent']) + recv_data.append(r['bytes_recv']) + + return render_template('graph.html', + target=target, + labels=labels, + sent_data=sent_data, + recv_data=recv_data, + hours=hours) + +@app.route('/api/readings/') +def api_readings(target_id): + hours = request.args.get('hours', 24, type=int) + readings = db.get_readings(target_id, hours) + + data = [] + for r in readings: + data.append({ + 'timestamp': r['timestamp'], + 'bytes_sent': r['bytes_sent'], + 'bytes_recv': r['bytes_recv'] + }) + + return jsonify(data) + +if __name__ == '__main__': + db.init_db() + + targets = db.get_all_targets() + for target in targets: + if target['enabled']: + scheduler.add_job( + scheduled_poll, + 'interval', + minutes=target['period_minutes'], + id=f'target_{target["id"]}', + replace_existing=True + ) + poll_target( + target['id'], + target['ip_address'], + target['port'], + target['community'], + target['modem_type'] + ) + + app.run(debug=True, host='0.0.0.0', port=5500) diff --git a/database.py b/database.py new file mode 100644 index 0000000..939c3eb --- /dev/null +++ b/database.py @@ -0,0 +1,132 @@ +import sqlite3 +from datetime import datetime + +DB_PATH = 'snmp_monitor.db' + +def init_db(): + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + + # Targets table (devices to monitor) + c.execute(''' + CREATE TABLE IF NOT EXISTS targets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + ip_address TEXT NOT NULL, + port INTEGER DEFAULT 161, + community TEXT NOT NULL, + period_minutes INTEGER DEFAULT 5, + modem_type TEXT DEFAULT 'rv50', + enabled INTEGER DEFAULT 1, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # Readings table (stored SNMP data) + c.execute(''' + CREATE TABLE IF NOT EXISTS readings ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + target_id INTEGER NOT NULL, + bytes_sent INTEGER, + bytes_recv INTEGER, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (target_id) REFERENCES targets(id) + ) + ''') + + # Check if modem_type column exists, add it if not (for existing databases) + try: + c.execute('SELECT modem_type FROM targets LIMIT 1') + except sqlite3.OperationalError: + c.execute('ALTER TABLE targets ADD COLUMN modem_type TEXT DEFAULT "rv50"') + conn.commit() + + conn.commit() + conn.close() + +def add_target(name, ip_address, port, community, period_minutes, modem_type='rv50'): + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute(''' + INSERT INTO targets (name, ip_address, port, community, period_minutes, modem_type) + VALUES (?, ?, ?, ?, ?, ?) + ''', (name, ip_address, port, community, period_minutes, modem_type)) + conn.commit() + target_id = c.lastrowid + conn.close() + return target_id + +def get_all_targets(): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + c = conn.cursor() + c.execute('SELECT * FROM targets ORDER BY id') + targets = c.fetchall() + conn.close() + return targets + +def get_target(target_id): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + c = conn.cursor() + c.execute('SELECT * FROM targets WHERE id = ?', (target_id,)) + target = c.fetchone() + conn.close() + return target + +def update_target(target_id, name, ip_address, port, community, period_minutes, enabled, modem_type='rv50'): + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute(''' + UPDATE targets + SET name=?, ip_address=?, port=?, community=?, period_minutes=?, enabled=?, modem_type=? + WHERE id=? + ''', (name, ip_address, port, community, period_minutes, enabled, modem_type, target_id)) + conn.commit() + conn.close() + +def delete_target(target_id): + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('DELETE FROM readings WHERE target_id = ?', (target_id,)) + c.execute('DELETE FROM targets WHERE id = ?', (target_id,)) + conn.commit() + conn.close() + +def add_reading(target_id, bytes_sent, bytes_recv): + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute(''' + INSERT INTO readings (target_id, bytes_sent, bytes_recv) + VALUES (?, ?, ?) + ''', (target_id, bytes_sent, bytes_recv)) + conn.commit() + conn.close() + +def get_readings(target_id, hours=24): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + c = conn.cursor() + c.execute(''' + SELECT * FROM readings + WHERE target_id = ? + AND timestamp >= datetime('now', '-{} hours') + ORDER BY timestamp + '''.format(hours), (target_id,)) + readings = c.fetchall() + conn.close() + return readings + +def get_latest_reading(target_id): + conn = sqlite3.connect(DB_PATH) + conn.row_factory = sqlite3.Row + c = conn.cursor() + c.execute(''' + SELECT * FROM readings + WHERE target_id = ? + ORDER BY timestamp DESC + LIMIT 1 + ''', (target_id,)) + reading = c.fetchone() + conn.close() + return reading diff --git a/oldapp.py b/oldapp.py new file mode 100644 index 0000000..949bc7d --- /dev/null +++ b/oldapp.py @@ -0,0 +1,255 @@ +from flask import Flask, render_template, request, jsonify, redirect, url_for +from apscheduler.schedulers.background import BackgroundScheduler +from datetime import datetime +import subprocess +import re +import database as db + +app = Flask(__name__) + +# OIDs for Sierra Wireless RV50 +OID_BYTES_SENT = '.1.3.6.1.4.1.20542.9.1.1.2.284.0' +OID_BYTES_RECV = '.1.3.6.1.4.1.20542.9.1.1.2.283.0' + +# Store last readings for rate calculation +last_readings = {} + +scheduler = BackgroundScheduler() +scheduler.start() + +def format_bytes(bytes_value): + """Convert bytes to human-readable format""" + if bytes_value is None: + return "N/A", 0 + + for unit in ['B', 'KB', 'MB', 'GB', 'TB']: + if abs(bytes_value) < 1024.0: + return f"{bytes_value:.2f} {unit}", bytes_value + bytes_value /= 1024.0 + return f"{bytes_value:.2f} PB", bytes_value + +def snmp_get(ip, port, community, oid): + """Execute snmpget command and return value""" + try: + cmd = [ + 'snmpget', '-v', '2c', '-c', community, + '-t', '5', '-r', '2', + f'{ip}:{port}', oid + ] + result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) + + if result.returncode == 0: + # Parse output: "SNMPv2-SMI::enterprises.20542.9.1.1.2.284.0 = Counter64: 123456" + match = re.search(r'Counter64:\s*(\d+)', result.stdout) + if match: + return int(match.group(1)) + return None + except Exception as e: + print(f"SNMP error: {e}") + return None + +def poll_target(target_id, ip, port, community): + """Poll a target and store the reading""" + global last_readings + + bytes_sent = snmp_get(ip, port, community, OID_BYTES_SENT) + bytes_recv = snmp_get(ip, port, community, OID_BYTES_RECV) + + if bytes_sent is not None and bytes_recv is not None: + db.add_reading(target_id, bytes_sent, bytes_recv) + + # Calculate rates (bytes per second) + rate_sent = 0 + rate_recv = 0 + + if target_id in last_readings: + last_sent, last_recv, last_time = last_readings[target_id] + time_diff = (datetime.now() - last_time).total_seconds() + if time_diff > 0: + rate_sent = (bytes_sent - last_sent) / time_diff + rate_recv = (bytes_recv - last_recv) / time_diff + + last_readings[target_id] = (bytes_sent, bytes_recv, datetime.now()) + + return { + 'bytes_sent': bytes_sent, + 'bytes_recv': bytes_recv, + 'rate_sent': rate_sent, + 'rate_recv': rate_recv, + 'success': True + } + + return {'success': False} + +def scheduled_poll(): + """Background job to poll all enabled targets""" + targets = db.get_all_targets() + for target in targets: + if target['enabled']: + poll_target(target['id'], target['ip_address'], + target['port'], target['community']) + +@app.route('/') +def index(): + targets = db.get_all_targets() + target_data = [] + + for target in targets: + latest = db.get_latest_reading(target['id']) + + if latest: + sent_formatted, _ = format_bytes(latest['bytes_sent']) + recv_formatted, _ = format_bytes(latest['bytes_recv']) + + # Calculate rate if we have previous reading + rate_sent = 0 + rate_recv = 0 + if target['id'] in last_readings: + last_sent, last_recv, last_time = last_readings[target['id']] + time_diff = (datetime.now() - last_time).total_seconds() + if time_diff > 0 and latest['bytes_sent'] >= last_sent: + rate_sent = (latest['bytes_sent'] - last_sent) / time_diff + rate_recv = (latest['bytes_recv'] - last_recv) / time_diff + + rate_sent_fmt, _ = format_bytes(rate_sent) + rate_recv_fmt, _ = format_bytes(rate_recv) + else: + sent_formatted = recv_formatted = rate_sent_fmt = rate_recv_fmt = "N/A" + + target_data.append({ + **target, + 'bytes_sent_fmt': sent_formatted, + 'bytes_recv_fmt': recv_formatted, + 'rate_sent_fmt': f"{rate_sent_fmt}/s", + 'rate_recv_fmt': f"{rate_recv_fmt}/s", + 'last_poll': latest['timestamp'] if latest else None + }) + + return render_template('index.html', targets=target_data) + +@app.route('/add', methods=['POST']) +def add_target(): + name = request.form.get('name', 'Unnamed') + ip_address = request.form.get('ip_address') + port = int(request.form.get('port', 161)) + community = request.form.get('community', 'public') + period_minutes = int(request.form.get('period_minutes', 5)) + + if ip_address: + target_id = db.add_target(name, ip_address, port, community, period_minutes) + + # Schedule polling for this target + scheduler.add_job( + scheduled_poll, + 'interval', + minutes=period_minutes, + id=f'target_{target_id}', + replace_existing=True + ) + + # Do initial poll + poll_target(target_id, ip_address, port, community) + + return redirect(url_for('index')) + +@app.route('/edit/', methods=['POST']) +def edit_target(target_id): + name = request.form.get('name', 'Unnamed') + ip_address = request.form.get('ip_address') + port = int(request.form.get('port', 161)) + community = request.form.get('community', 'public') + period_minutes = int(request.form.get('period_minutes', 5)) + enabled = 1 if request.form.get('enabled') else 0 + + db.update_target(target_id, name, ip_address, port, community, period_minutes, enabled) + + # Update scheduler + if enabled: + scheduler.add_job( + scheduled_poll, + 'interval', + minutes=period_minutes, + id=f'target_{target_id}', + replace_existing=True + ) + else: + try: + scheduler.remove_job(f'target_{target_id}') + except: + pass + + return redirect(url_for('index')) + +@app.route('/delete/', methods=['POST']) +def delete_target(target_id): + try: + scheduler.remove_job(f'target_{target_id}') + except: + pass + db.delete_target(target_id) + return redirect(url_for('index')) + +@app.route('/poll/', methods=['POST']) +def manual_poll(target_id): + target = db.get_target(target_id) + if target: + result = poll_target(target['id'], target['ip_address'], + target['port'], target['community']) + return jsonify(result) + return jsonify({'success': False, 'error': 'Target not found'}), 404 + +@app.route('/graph/') +def graph(target_id): + target = db.get_target(target_id) + hours = request.args.get('hours', 24, type=int) + readings = db.get_readings(target_id, hours) + + labels = [] + sent_data = [] + recv_data = [] + + for r in readings: + labels.append(r['timestamp']) + sent_data.append(r['bytes_sent']) + recv_data.append(r['bytes_recv']) + + return render_template('graph.html', + target=target, + labels=labels, + sent_data=sent_data, + recv_data=recv_data, + hours=hours) + +@app.route('/api/readings/') +def api_readings(target_id): + hours = request.args.get('hours', 24, type=int) + readings = db.get_readings(target_id, hours) + + data = [] + for r in readings: + data.append({ + 'timestamp': r['timestamp'], + 'bytes_sent': r['bytes_sent'], + 'bytes_recv': r['bytes_recv'] + }) + + return jsonify(data) + +if __name__ == '__main__': + db.init_db() + + # Start with initial poll for any existing targets + targets = db.get_all_targets() + for target in targets: + if target['enabled']: + scheduler.add_job( + scheduled_poll, + 'interval', + minutes=target['period_minutes'], + id=f'target_{target["id"]}', + replace_existing=True + ) + poll_target(target['id'], target['ip_address'], + target['port'], target['community']) + + app.run(debug=True, host='0.0.0.0', port=5500) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..38173b6 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +flask==3.0.0 +apscheduler==3.10.4 diff --git a/snmp_monitor.db b/snmp_monitor.db new file mode 100644 index 0000000000000000000000000000000000000000..34b23d2641f7cf39786ed069334ecea1f0c1fe2c GIT binary patch literal 258048 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 M znpr#FRxdXZBM;HTSU!4)V@P9G=N)arES8TR;u!oTtD}DLlq`$qlZU)D0)9s`Yv)mQ zH(JEVLv*lAmX99d=zP-r`)^`RxPG0F9^&Y{K#uTy@{j{FGohxYf3}uH;`!tuI#>?N zM-OpyI&HqWUmFRUpm<2xr0~ED_@U~orhgPkFqW^lh%&u&bjmW{RHwYqr|_rCqOCzSd5Pk%#DEEFV3@k?1mi^s+V* z3_0`=N5Y5ZkJRZdm7T-$$wOYt+@sX=oBDJn4EjVKqKC13^bkk&c=N#|u_j!1%SR7! zRC~*OKt0k8$#C5*pFCvK!!4nvn5aIINIaiBL!peDaV@y_D}RrgBwnP0%Ou5FHH06MBgK$`tb^hZqUHHXl92e#LBlMO@^= zSD5g8@{n~8er_;MAKGh_Bofaj57EJ(Pv{}`%Qo}IE80kapoiEm9W!rSEkSrbdC0m4 zmMJy$Y^24YPvjwb7|TZwv448Vyj)!9qxX~Kqlef(on&67PIuwE!*#cO@{rZbni!0a z?aEVUF(O7D62Uz6#L~mjpb6+9_S4hMrRTK>Jnzs$?58c}(h>>6^T|V2m;9#G^z3da zMjoPr!FWOsu^)cKoI6j9gzIkk=ppvQ)y%n>5`^cIhdejAH`LVIeqD-@hv;C?C-e~e zn}f{H-mi@W7nR~6WzW0kCdDWTxwupxIp+fHR9vKNz5CGX=4W2fMuO)Zd5BIV=o5N~ z{gqATSyP2bcvytxqlegE=`TlkK6%KpL9iA$?dsA;C6RbOd59jy^3g-=8@HKfiZcv+ z`3TEL53z5oZJzNDZ8Er^$U~M5>Za7RMD-9@=?oh(VB{go25v0F6135vPv{}`P!;pY zF(QKJN%GM{?8TeSBg;j>gChClA;qKN{Yle`e{oA9@qF@-;!*c1*w6E|7>p{7tv3&Fp`@(+ao`b|lc%CHx4jjA- ztD1Y3+xtRc&A%NlgThB(4SrhF4pvDdo?pKL81$(gCR|X{+#^LB37&U#5qaV@Il}Yn zP_lloA2+RO`}ehxpii|cfWdgGg$c)3GPik2j70PCnuv_sXl|n}WW(T%AUwYYCi69$ z2YZAk#xhAHo?pEp7|XAQ3CC_Rw=Q=t2q%PARwZPhxwTW94CYT&DA}DbSDAWZ%wt*% z`V>_WjOACxgb(#Lr-`#@I!}^ciIB?XH1+3<&?h`7l5fOhxv{VYKXt;@XT+KayR8Bk zG{Jxgdp%-KEq5;nM}l?S#}Vnd)tpjpphOVvn@7oFV7+_lgx|zjbfk3f|G6sma2Zx^ z+b$dp)=B1I!d4s1rULQPMJC(-lK=m^@BjZ#`~S!P#{Rz_J;dp0Y^feat;tcZBR_gb zY@>x848|ue3%?i?G4v2UEQk5gL!7zIEY%XV2?0S5aX$NvrK-9ZMagowA3Y?t(SmA9 zP5*d88!3zX(L;1G=0^{4&Kk$AZ51O$MnChThd5`&uxleF2=}9h#MVCyXI7q=<5723 zp!7&2?ne*N#h4#G#5psbT~!}(6e*ke(L7{OBRhq0`tmYsEri5lmQVcyr7h`_(5a$p( zJFDJJp-A-h%Uzb=poL|^VkO~ z#YpJ2e)JG$=Oj78{pcaF)mJ`aFwWoMS|^Fb{pcZj7+h57A5aWJwy+K@q`}YY`mE535b!CYBv0gNsd9E&_kR#P1)`t+Jt~89-;__Gy7|{yP*W( ze#Jo)Rf&z7HAPAG;dCtqeL@e>#h4#G#F_XRTmP&W3HNLL=poL;C2V~w3Bvv8A+fjh zf#0=1eton0u!KIL{`%2F^e|`w!zLs=%hvVQCWP^X9^y>6ldY>MLAW11B=)x6@VoZM zufOuG7K1*ahv;E2p3p;_)iPP}0x=SLtsgzaS@m-{!u{kSzgw+P)94{*B$2qEJVXbB zKB0#=t9-?RUA2(_K@V|O@vz`+5`_E7Lw;vpDK!l)Hwwa^4*EnMqK7d*dWhrlCv4H% zlrUR8PvS=paa?|qEfSXq=sby^Jmj|?@D84NTTkC5iNyWnArb6UODzUXKo46k+`2cL2E+l!VWz4>P!BL~cGT|1pfTZXN5Ktc?WgZqq6d z3Hme@BL#Z0=06FM@URHWpF)U1j_~}+nCye!a}37W??jc`)WY*5`I9PuL7ygK!ouFH z*-mXuu<-aOA_cFoW)ma`&!0faZon9y{Z3_Zj7L5ljHmGxz@Sg#FyYLD%vx^YnAY9K z5;Bol)yEGkWYfCa7)7gfM1_(`$_VLR|I4E!!Y5{r&v9Mm@u-a_AmMWfBOCZ+V}sts@xWJCTeF? zFlt&<*C=OH<;q`H-ci|Kd1B=*l^vD-tn^u>?UnK?jjhz7lHK@+@!#?P0`w5q&MMX@ zb_+^HhgU`F8%dQ^{kkPk%#DDIjjIZ#I>V_wel?bE+|qqD?kr%ZGY8T zSv}&WBD{b+WXJ~iZO_tQ52;Ilgk|vp@{l3xUr?}vo3#luSpj;8Yxz;jkLuxmh=kWs z0eXmQ`D2zJ)eRvP;RWO&{U3&N5=;BPr=H6aG4c>Sj1{1VxR%Yb{GjgnQ6y+3dWdV8 z!}9$p@sowi&fx{*A^m&7^J{7UcROe?=o5K}9>xmLLtINHSWZ7LM#A;$0`w5qk~quh zUJ`^CkcafH48MU|vfoo~6yP#<0eOfX#tP6wT#FMer_`G+^eG%Eixr@UxE39?oU&+Z zf(DR>^fsU+&_i64%$CEQBnU4+4~gymA-o%H zNy_!{QVcyr4}-yi9^#s4v%EP>8wsvE^bpsh$1HC~OAuaw9unLA1k7cZq+By;F=!`x zh%Uwo&_i5(<1Jg&rw)8~;nlJxtN=a4)#t6hLKF{CbllZvnq_Mnu^x(|I7k?=ShoBk zL3n}UA7#egWzCxgiz2~zLJ!f!SOI#7D|MvBGh6(0xb9Yf z9^y*9!{SMkAiMxQB(_OQ_#M|%o#zgeV(1}y7&HMr#FY|l$-Pe-DU12hLtM!REYIGM zAl#2065FH&{M75I&U4B=g2JB;d<{KB4+F174{8#4WlhHFG&#YR~)45 z^2}Ha=e40GaTzmG6Z8o^Bvub&e)JHRp{`|=I+uk{M_!8_;{0=`WmKG46D_Erhr}iw zhWEHE{NAA+_fjKqKYEBB22DT@asIK#GE!XhkJJR?2|dL5$5_jVbK07q0q7yINpHe? zTo!(BzekFphv;E2p3p;_7lv7SiHrXD@)71o4{=_oXX#Z>OcqgJ?ne)ab?h1qH9Zll zu1P?j2tyCi#h4#G#Cg7frKh;)ABhC5L=SO(yVug=Gi^<<&x9Tl>)7d5YKkd03L@&u z{pcaO81titI6v5KY1>0cNasoXLnC9{`N42Y+bR--`-k8dw{D03<64;Tg80nCNZdcT zA{g@z!i2|1TiS?MOC%DkCk-T|p{2F>SVV;T2T-!t;RUS=6P{D89oCldJc+-5MKI=n z1QYJ6V@WT!=#TSpe?LTa?y#i&F7^q9)!!GBC4LU?Zd|Y@<$@IJQvnPHOK(iLW49%( z+@gPEvd#Pt6Een<`j$2sjHibv*=OyQnv&I(PUurO5?r3WDuOY8PfWOaz9s$ zO$H6SZ~y!)-=Y!*q9fR zhpbretik9__;r*NBM;HRa#$gHi2KZI*6U7fBtXzZ+-HVbubq}4ypTL(#S<|1@Fx7S zTZ?7!Lh=wjj1{7XxQ{$y{Z^gJLKE;hDnt))AE{>jR-MbL2rpDTq-^d{@gm| zTd}4{Bwnbv$lqbm1jR>`$&UN2TGn&wjwU6{W`&B2lp%*-mm|E8Jfx&AtfS@aNLHs! z6bbr79-@O~vO@F__vTI357oIWL_)7EL=SOq?k`7pA$dsAfV-ileov^&PHH4xNFJht zL7&h=+?xhlPpAjxC=&DuJ;c4KwjAMwy&Rjm8e zxhzD&^CX4nA?}jRa)cL>hxiS!m*u%}Y^^w+@H|N&d59jy3eiK{p;xVYt7{{H|D%Vv zLqp^UFCY)e{}W!Q>ACTqy7Nn)4t$L~L*id<{Ls{p3LF`aiXi;L|A{Qg#J0wNqRwR@S=jtwodi9^J$~z7A&Q5T z)iZvOHKa~=#VCq{gppd-P@Y(FBoQxA{G;r{KT~xBeE3#}YiTi9Cs7>a?=V;ZArE<` z%6KK(p&i<2&~fq*9Sr(}9^&pDWnH4qW#JQ|*A}3MxF6nRU98S!RfHFihb%g~$zaU= zB3a$@6EX4-9Sp`3dWid>ZPrEKi=QrB6I^%bA?}9;TNf>tAiRJ)WYO1?l$zYvv>5b> zJVXy;1?VB})|;)<)VVBty0EmuctQ_xw;u3Uh~gn-gQay1>r{2RD@IWqqzq~Gs&%Tm zAfqC@K=F^VfwJJ^8}P~Ib$wO*WGK-Eii3z)7gY`^8jL6MkOe1UjVpIvXVupTgFcam z=wL9O&_mp*Ev&=TO;3mvu~All9^y{9AV+usdC1(K;VjUzSJ$fMr$*uh)0*3~81#ueL=R&H=ppV#KI?oYbrwIb$U|nl2G8JU`}Y%1gkdCJKpvul!F7io z;%?Z+`am;nLg*8Eh`Yg$)^6%S5vmFLL>@BZRaj4YwtwGt+DOnR@(>*i#uIvo>(79- z}DpO#{6Du6)~T4Ta*w_1}t+DI^-S`jkHnj|jD(0P)AmXxf8 z(kH8}rZy7vsYL}a7*BU&!VjujV_p{{(f#QzM2>H>#*CF9yr4NI8+pCH!T9vE2h{UP zFbX1(ctNuYV9=*KG2#2$tT#u}{q?mN^r>+LFz8bb zCfwQA8uhXe3D1-KhyDMp8qd|3SEFBzJ8LwoezE%D|K{KS|2zBt0rZgMDmQGG+f!>A z)2o1ZfIQ^P0NDQzyuU?sBRaknAP>>Oa##R8BsuC2+a>jY6D7=Ih3Fy4QEP3N25M^p z5hD*dGXVDg1MhDhA;ri;&h&@<|G@j3-q2#;wdf&9msZ%$oD>nfjtbF3k}h?!omnbD zcp-VnsjA;Vvg~;Emjv+X@H#3a57EKEYtciJe(GlXT-|G@NPs9FqRg(6F5Z+QyijqF zvT4()D)1YqK(9b_pyW(^gFEw7M zct}}IC-%d&T^QK?w$M%$Q(WZlFld6}BV|qaG~M>DxG;mKsaT=nB4xY!K`YTil6H5pZ50Pfq^2BRNFMUO=~Jbqw8h#; z&`$CYJ&YBiha|0OZF^o^=);%zutM~Zq?dlMJ)bKkqXjj^L&^rq(F1Unwcv%998yei zk-x*B35t)D4VD)#*`E7{HWJ*Q6c-62rMBn#NDy8~9&&Ua+zSd`_{WD*j66gKgYkqO zl9Yee=Cz8EXx$AxBq`r(%lk-z@IvyCL-|oqQ_Z2`N+-PzsE|BF2ZKJLha~w5Y@UnS zgn*!jB>7s~JkLoGUPvBt$a_esX|TBBLht!0BoEQUSRr~y(wwEXxiMlwT6aSaNt*qo zZH{`b9j-fCP$Li7y9m}N{WEXul|GT=bBn5k2JyFC-7ydH*@3 zCR3@lbm$X#hz~OFyXbb^)?1u9;44g6 zq2eH6WVIaOg^GWa-M(HO_c(mAVV};GB;$pOgZv!^_i^%&SI2&#L_1yX{)FdA3duuW z9owo5OV%cZKB0#sH4oX^i!(7i1;Pr^Lz0@ex3!NIqeRq~7m|l;8W}PeeJ6hnNip(} zO(Wp%%6%t)xva%tJfVjqHM`$-Z;0Sv}d3MQ=G-zNM= z7$l_mcrqc?ZNh(qsR%Dj!elQ^g?$0<_ie=mZH&YV-4(%Dp$ik%s%yOcry$2*JjG+ee^#~L=qJ_`nQZ@u{r~@+|Njf1hoqzi`1xzp znkICq@c?>ATui;{24ku3%Pvw3Jwy)!|3?o=NeS`u>Z%SU1Oz=KC8a(8_MkRd4iBJ* z#KqJ(qSW-oO)Zwi1Lz^T7z?0>q(rykC)I1aiFzFc&_hxhea}BquWc3K0rZf#26w?b z2}+xp3_?wG9R<)s;uNTHjFqVA! ztGeSOV(1}y81xA}B>B&Fe4l#fqDX+Cha~@TiSJX@S4DULJtVGPv%8dBwx6|UvY|&@H|NXJtX-;A>Vjfg75%(NL;PC@JuaPFy)FALl4o#SO7gF z`Ft^d`DbmUY!*NdNj`tC9N_`P{4OkSyt1bspe(ZgUop@$@&Xvvo)h>_51 z1Lz^iAABcAcmO>lu3C?1s3~F021z6yKo8NySO7gF`S?X%azPsj`h*^md_2TURKbAp z6fQfP2hc;}sy+CzQqySlsY4j_2|Yv?V*&J#FmgJp9e90HuNN|5rT%-)y z@e^NiuLR)%^pLp9nOBu$v(%MNN(OyG57EP5JfVjquRhPGR~Hka*9Oo-l2;e-$972& z9zYL?GgJ>iO%E>|F2&G8^f2fXdPwrBAfKi#%)qA$*8~W9Nb;(7eA*IiGI-vhhr}7G zJ*3pMpxh`3M}j_~hv;G~fF6>Z*MbjKXI4-XdTjtbB-wL;58W)*6hU}^Jmlujy$#0V zUlLS@R3q^Kd59jy0_Y*ho;G}lx*|-GU_7CRB|%FD69eUkegTF;R-dW!m38% z0rHTWmp@am_yyWe2;&JoBzZM|dDEa)XY&_-u^97+ke_up|->#8w1jff!79e|7FYs*MD#j7Fs6 zHtwD)L3p4MC3||kQqwARj}ZD4{&di%h84kBpaCY#SS4m{M|dM~MVQ`C5~xc^HQs27HW@Ua4kf#BuToQ? zI&G)Y0jpgB48~I}Oqg(sSN={+NcX3jgskC})dxN#!*#bn4NUg+dN|WrG(N*4iNpid zD}cdZsfG#TH}Fd8%$^d$byt;;K5~Qys!+0ZRh633@7C4?eTu3G#sZZwVb$ktm;Wh7 zirjquzxDqj^pMnt|FnN~hFa5vE;U|69@2O9%Le1BI}SV}#mGbSFjj;flKOBZ`zRd)}mCg>A+hzM}ziq6XJ8e2t6eAj_dX{ha?CuA`iL0 zO>IclvU9ml7m37+$U}56=o5NK>g~7eFO|E{!;xS-p@*d2zQ+ENPg_$qFCq`QzZLw> z@A;OU)H9kep71$eL>{7tu_E-4lv~T~Mb(6acnX9Sp@*d0y5AnyDM5G z*VY8%2|Yv)gYkqOlJaF8`@F4UBwTkZLJvv#=MMY4krIR#p@+n^dgBhLsm@~2jp*1( z5qgL&#){BGQoh)2pF3Zh5L$^IlJdnE``l~^!i&&D;#%!Iq15!0cx#Q+1bspe(ZyI1 zdPvIA;r6lOLLZ$cDMAlPIa*(i@FMh(xaNL154mz~rCVa@ckd66<*j}8G3gS77oms5HTS`psg-k$>$Dj32|Yv)gYkqOlCoutz4t%FNVx7+ zgdUQzrIEdN9|^*X&_m)H=fNJ$if>z)q!@aLF2;(`LsB-!*&m*)jRbu{4@uedrX1l# z=pm5IsMOR_5()Z*9un8s(_Fz?9ML9(KB0%Cl;s*BYkObjH=pk{*GvUnC@>g4lYd4XT zDbOeM5M7KFp@*bQZe_Qs2khX%fnHlwDWWPVlfJiG8;LdHx?7PkB0X^q2K!9Q<0kZw zVz&`yhNUn$27NMM!ihiGE#+=L;e_C8j}ua2XJ2WP!8*x2N|rc7Nj6SggO7YV@U^)W zz`$$gV8VW0d);z3ADoW|W)pI^y{; zzOC(bB$1#`(-GJgPN9Js#b1t6pjYtX&fd@YsoL%D}K7jEV`Wk z`|FOY+p(^-&doZX)Y(?YQ)gtIc6BVZZ`3|f`<2?cwMW$c*Wds5cfbGdSv9k24f_3m zxBtQ4{|C`S(&n^xeB78?(^S2Vg5)9o?QrsJ&9b8hq!@XK4wl1$=pkvd?{}Q+s*RM* zg6JV>vwm}&yiJ1eAbE)Yb-1?IEITqui)Ha3d59jyg6JV>!+&xdoFPVvTqG=r9+Eb^ z#BoraI76Qz2oI8nc=o`Ye@)%b)DwduMjqnX{moxt&?oeev|&Fx4z$zOl*NMRA!)-p zIrd+bAUsGO;(24WlI+t&DMlWmgJrTHdPrKgc8*OG#7L3rhy~F@(z;%9Y_doY9wZNW zdSkx9_|nU7PmyBeAvzfJ2|XmO%P)>snrkBgf*z9AWu;@|j}n9j$wQug`HE80QAz1A zp2$P=Fcw4)NxRGGSoyw?5Kn=yAbLnz^AnDh3nd5-l7}o>|0>kft*u##k%#DD&;<05 zv}PYWR;Y8?rg+8$#uIu-TC;_Y73$+eMR<@rWYPLYN=0OP_Sz;ah#r#0U5?zDVw8yb@*sK0f~Vm?=!-wrR#!JDQez$@ z57EO|5IrQ#mg0CeUz-r_Pv{|O)=wPIs*e*&md%6YAq$qmxzQJYto5um5{xJE5Iu|q z(L>Ve%y!IHXT^{Z&yxhvL(*!WaLhU=CPQHjl84M&Fdb@I`Zkwhurv8JqHNkiy57EO|5IrRIpKeD_b#()33JW4!cjzIhUwrgeh~gp2 z@s89lp7<+7agZ>Q>gcI1$f${UQ1Oql<0jM29X1&Mv8QJf@smY7n-?h#@^@GcFCq__ z_RV4?TKBWsXy9w)Avzd%EqX}m(R&xq%bYtchekNo6lBTkI)6(+ohJY>?bIH>87 z(;1RTyofwR2LoS24@rINXGiPLw2{DT(L++-TH$D&CqZ}-dB~)9Pb)Q@TBOCGPvjwb z7%M^#N!=23q^tG-Uo&|vdPwS)c8;{|Vlwht@{sY<;0)gC>%u!F)JVLDJVXbBKB0%C zZtmbn{Xm-#_65*GQaAnLNPS9z@FMb%@l)adZdPBPqVAAWP0%OuknvM){uKt}2|Xlr zSsRDxcQGM)ZP8e~VoHl0ri~JW7mdM7VRQ%BKUp1^F-wY#t^fvo8iff>tUUNjVur!G3Gt(G9XXb2`7 zcBvR@%6aFs6dPOt44N#04WMK{^-*eidxK4AjgctS2WCQvkdiRiTa+mY}y#KiWud4n;^}fRU|7KL{RV}Mp&8p|C9;&*$s`mZ= zfA{{sPbzJy?e!JUZ~NFz6F{ zNV@L}=bq=pCWLFsX2s|s>Aq*=2rniNIeEmaB>O2%8wuJ;9-@b_V)T&o*{7Ww-lj-R z@vuk}R*W8!K6}w$A&Q46lfv}bStcF)vmeqyiXu$nnK+t{tNF)JB6ok%vUE6ZpU4B4K2i9N|IokV8-H zRBEa&K2zyDNsv552LrD~4@qyD@0_|p{B(GK3ZjRkH*M*hI!J=>AbH5%*3kyz%ZqlG z8{?5kJV+j*hp`}fNP3gD&MD%;Oym>@crAKJdgF`EDJj~Ta(IwDWN*ulm73mAwy;k(;#E|4va zq`QCxVWgCarlkZt$CzV?Q8RYccTG4c>S zj0Mp{(kn4%hdZ@3!F7iol5Tv*dEfUEga^q(wzY)~>kaLq)F};o#o+AJkOiopODmY9Sr(}9+Gyl zq0{=9*o1H-7*C3aC|7XWNBiUm4=N5)c6qMt@uE`G+vUELa3p9adC1!CaLZU<mfmSushcDg843kab4%ka+dI{Z(Ne6VilzJ&2IC3VTEc_nrIn7~#Tf>@pCou6A)Ot+ zH`FGBeS!9rY~T+{vUTE;A)O})-dhoj1>0dl|Gkdy#2p4YPZGQbk^IY!?^=m9;ksL} zEhY=z$cCC4|1egHwW$CGeQJ#feZM*`^wvg#`%^1Io^xENDM5I!B_+G=S8DoxzZ7dh z*tMU_uvjeyW#eSxmmWZK;=wUf5gdUP{^kdfnb)$+RWwQ`^NXF5Ht^?DxHD&V< zdWflk0sg<{wb)Np5u-?1JcJ&ii?I-TNXE8Pu1z<^gt&ekLJ!Hf1U|ilK+-VbCY^kc_R3U9TM0MuK$`^pK1#U%FmV7lEMx;j%%*&_hght*`*{ zs&7l8BoZ%157EQGYtch8mKVF0{~<;quSE~ZSbnc-`6da%i_t?&HOA#bO$|<{%iK_U zBoZ%157EQGYtch8mUVP3JE~0xUW*=*QTnTE*<1<2i_t?&HO5{kt4ZC(pqika=ple9 zUvYyE)R_!n&?oeejKw!y&)y~`B(Fse$yofN9O1?2A*QJO0S4o?+>7cA6q1qt;(mx8 z27N*o$yl_`_4K>inqWNPen`fmhh0yrQ%gz)4L}buMfrA@)ui6OMGQT}6y=3>Zp-~q zeP9v>eL@e(nDmfqhPn?42zqTXdPv43qie=1V%aFH#pofX+a@1`WJx>jkz(i}fKB}? z4Elr~k}GCj5ZSVi9AFPW5wtp8L8u3t;LZ^=ShmuLo!khx>`RVCPS|+CJ*_w zdXd4n<;Kc#E9Zofhv;C?C-jhvFjkBnl3{$> zl~5=~La!}G56Lh#aV2z+AiS77QV|c0Dk~c+#IFnd@lS5ar2St;;cu-npFUUKHZ54e@t-IRBeaSkT@c)#=dk76X8j~8bXGC_{;;w(&db`<

zKOOWbwE`H7rxZ-MFTr{4f*6VBoo9DnWR05+?g+z4u^`@Xx6pDdw&S#)@5- zaL>EWZ?)nO#sV8+sj66gK1FuC7$^5O0`wiIQ ziTn`^Map6!^pMP7SG#wq2h&u9hsZ;E&)HK}Q-UNC50QuTo&($8y9?i)s>L!{2t6e8 zpHH~gsk358h}TgFJtXs=sqVG6#3(4NA@Y!(Jv&0O?8&OGsF8SxJVXbBKB0$Xev#>3 z+en)b)=AJqGS7T2M|g-lq-T#cN==hCYa_ur33*7*?w|h^27N*g$vpD3JJ?!`gkBp$ z56L{z#9j2W1mPj{5Yq#l;2!wKhu^9z;m{}a+7Nn(F2+LWA(?MAcMETLp-9jt^pMQM z=iGrk+L~ZJ2|dL0K*w83P3O95G3XO|h#m%gLJ!H@eBS+(y66fuq1T4cLozoP$Ppew z4>5IOzd=o%|7j6xLj4V)hv;E2p3p-wHwE2Ks@6_5L7&h=GB>r8BRqs20?FV#WN&o- zLVeCtBC@eb=Sdo~5PC@F@~7Rx+ZI%W zhtNYz_a5N}X+Wt=u_n4dp@(En8YV}02tCBq{7}GP+&O8txRQV;Q+NnHL=S@| zpoe5m+~w|aQCky?C-jiai4ELcdP)!;LJu)DKM2>|&Plt@Nip;gJq*SZdPru!M((@q zVkEjhp@(Gl-7iOY2tCBqcx*d^amR^9;&Lzj-%SWTL>FTr^pMOxZ@Qab*46~~C-jia zK9k+eS4j{aLJu)D9#dJVso_8^2A3y#h%UxL=pmWyrn=oOArc-IVIlO8%y!J}`c#7O z5PFCy{R;dxdPmlTa!ZD|JQqR_(ZyH@JtXrU?sop7O$g%&JtVX3`)=op5`>4)Lrm$H z?^J3Uw?K-ahv;E2p3p-wonN@?{UAodb+-_DNT%}{cfA!7gon^WOi6tnG#Iygucb;c z^blQ)h0sGX9eM7$uWKX0rHLMr>A1^XcdP{AA@mSaQtwSlO}~}V5eVD7o;QrLGA{Yxbz=U5Naee%&7zx+iLU#}{&Gqq15`>3t z$7Hb$Zil_pb@$hkV)ZM6u~0os_~lI3$$i>LFrMlXVt1WXjSRlKaM{p+I+X1850siZ zlKf&Ys$BWY$~!9i zD^IN4rLv>apOyY|`~M~AA=!!3leVcpvS_AWM4qJu#b&_l9LO-P#EP#X!x6M9J2sko$BZ%GhdLLRa(tzTJ96Qvk= zNCf*v-B^HjhMNH62|Xn1;LxPet;9%S^J~gW6c14jV4CJNg+s>sTX*cN#hS1Z#YL2( zoLL7(CXG_31CR_2xkT}hFmgxIsNUKrjd_XUAmL<9;PmkR*7x0zVv2|SEyhX|7b#1) ze{9l7bvi&bW%Cm9ka>;QD+$|+D~plR;fp~J$=c8`>7mAAP3W~H=pk9__a*f@FF|+- zdB{v#PlIvaEAw|qG4c={48{|BNY=VTNxjrFF7WBXH9^_vAzAAtCH35@O$HYfdB{v_ z1Er?NM@cdA5FHH06M9HiU~$qt9mGiJwI%2wS%K`Nwl^gRFCh<^Rt0`pw6F6&k4rJ~ z5Iu~Qpoe4?Hce{VKpP33cjzHm1z#t%QIC2-E5l{OKp_vA7Bx|+>5Ip;k)WOAA$k}q zK@Z7#{OhC)uMi0ji?9;(kgUh^DO{5rkh#tmD&_l9Do==MJu8jn( zL=VXtS&$STB|&%zdC0gk@IJ}C3w}}e*I%kwBYNU%b;X#oS@{mzqwSk)AgMFkJd58`MO+XLHdf;+Wb@5&hsR_mtdPvp-%aW>z z_X0$C33Vob2PxBn6HP`!O&xF1He5MO@sPj8SgGP7WeGF>NDk36RgIL* zN)-6t1Zyp%lTy+7|!YFb5xTn-}-(ZOImp@-yDzLq?#RQz;kqowE}Ih9w*5nf6ja`g0J zsA)krha?g&B@fZTpik%_Ih9^Yo=VSD<Q5QHvK$V2onR*D{y z{ll%~!Sqa3`V_7S`h*^m{ll8%!SqZO5MD|i^5)u=Wi^%i?jnZFOUXmtTx(FU+vjRa zhd!Z)WPfsda<{7@5*EZJtQ0*Y`{Uio-RRt?Qd0!srQ{*|#_Ts3kN6tXoL|Kn^HTB< zJ&cv2hh(4JpWKzqPmKh9LJ!G4IU%_#of{PpUP>OaZ}bACrkrx4AY3|(C-M+IjFqB? zWbf;c+(MKwJO#o^(L=KL{vt9-@Q6ctQ`!UR#`;L=O(- z)6xA2JtTYWy~*yc{tt8K9Un#2J^U>xge3G%$^e&^zP+TAgd&I_U78>YO7AKl0wP60 zMFfFllTCJ$Y-o?Dh=>XziqaGi1Voykh#)FRQ+Utaopa}|Gmd$F&+mEPmp}P@Kj)k| zd*{ym-nqNslZgl^WFE44*Hx&ge+%gyjTDI#G7nM1ghKQXldnr+J6^`bnqUe;4>46< zPi(hAUXz&=G7s7OG3?#!?k`Bka9K^zC*~n)m{5owVp^V+*qA*yXrI{pgdSpgbwy&M zulbrpgcLFlc_S8n7x>dHLwFm-NTiT?hzbTxKo2o3TbR`f}37GKFjD(+g850vipT;9H;)jHv`RzhP$eM91S&c$k(|2suBV8Tz zX>4^cVa*s!*lk?GiJ*61Y~O7(Lz*QV|Aub>+jn~clNB$6{mETBdkj>>9LKNXhEcn&O#r((%Oh-3BwPL05{!@za=y!TdA`6UOaHc$?Vse%^_u_hBKLJx^)zUwEN>@|ZtnUNHshp1r& zp$I+1GU$b*6Rl9NXay!q7w1Fz6F{h^1g@ z(!5T5O}KwugdSoka3;-VuTN<*JeO639uiZxc{`2ni>u?BDnueh=pkwt^a(x0lAo0% z{Z(u!A&e*V5KG>VNpn7tCxh{X9uiZx=?}D~F`vpYXeWAzDkc=6hgechB~4&+Agu|{ zlN6zcSW;d{8h@2fhWpn==piwYUU;T{(PQo*MGQSe4TC~Ysw#k!01JFZaB0Ym>O>^cbV(1}i7>pF^B;0o^LJzStzLhjsuYiyu z^pKdaf=4yF16x>Yi;5~UlK+r=hjb2nnND+ESOjv$v zTGI;lV3KNrcA|%KZip1QNo&8+wREmzmU+-`QZlPg2M{M{;Tpf5V^AI&m zC`1o2ADNfb=10CJ=C$Y{<|C;|ZHg2SQph~yYC5mb zN@~4Xo)E?pdWiY(sifAADR50ihdWiXh6G`=g=7K;Z zK+r?XA1p|!7c>`$2q|P9@>4AM|Go{kf@a?U27O{4qJ{~D=pp8+uq5qnt_gSwL?}cL zG5ay>Y57v25Q;(RTiWu_{HB2Z(4>4D6PSPxwM}qN$9%8N>o}}TIGwh&9 zA@h(QEbv}nUytU|3Xw=5^AHsb#uIvo`PGeySJ-YDj7;|XB!vS-RWZLBo_J*qETT2xdc3eJLkg7@xNI-%QXNbv z?2HMATua=}8<1ENjDk)qlDiR2cx9hFA+)n2i_|UgLw@}vMlz7Xf@(y9KILPi&bJab z@GA^FPZH$+|L*VqH;=p)`DJ8P~U-kQFeb88N)X|LHZ;!4D)5$gUw zBV7IO|0Vtx&;Ps7L#)NUlfCr=pMxbi&A@Io;$j{$&}Y@?zFyn+oFc|NLg(=LCCDQ|pO}ZJVS)=i#5yS~dA4{EJJ1C5S{HhVb>gPv zXYTRI&}&`jAu+ugZr13I{&Ff^5kn7A!=MT1A=U|-lb>lKj|BdY9%7v^Tp1xn=piw^ z8cd@#oxCWI1ipqIqK1LjqK8-q?nxdaF2DkPVqS|LVjVa=dCUkt8536YkeKdwv!JGz zBIYV$=pkwt_!@eM^`TA4qaT(>f=v+WA@s0FO!q(T(u6hAedk_rG0WoQN{w1&})m(L#)YPC3h4T^FatHLJx`QI0)8gM|Rc}t^F?;dWaeZ zeL@ehCMPEsh$|~T65K7+L;T~Z<3sS}*&{n6p5)spN~=&rUBo}0lIADpi)T0ZNHCtz zLt<1SL7&h=tc|}+HibdOejJR zu{N5MYz&$U0+C>TLJzSvN=`Ng%>^PtiqJ!1a#z9cBp&J4YJLlAw}pRF}BP~T2oKAye8-qdI(@ODAwbq9E0(M9%4B(JLxvRbI#6_6rqP$ z4kaetewvRWPGO`7JtW334Br1AUOh#8ng=@0F!T^rOejJRu^gP2bn8`lLU`Vxhd?A4 zEQeQ59xunBo#-K!FHa=hyd_6qenJn4F+A*~ktXqP8N?<)pU^`r+YTmu|2`jy%};sa zWN6v;Ow#ug6cAF>0jJd@Eqn*>(483ZRtWTo%}=?1!I}@2M}v7h2NS+GH|fG6IRXvH zW=KlXg$xCR6lGzu#Ct!}nwqiinbNx@P!o)&%xYlJrwmM3-YV(vn|wkxKcyq$J&<(x zF$IJaIWbwAvSnxe zHATrIL7yyt!CbZF81%`E316C)w8O_CcvwUzGBKp3GD3=sn5;$n>F_RfXRDxhg!ua; zMTTl%(5Ey^xb*3y?NjA7!FWnVWXZv#?d=p0Qk24ywL3~{YRT_$iB|`GN|rDi#ajF% zj|P28!i1A9B>DRAh`6ctANhad|Iq)ZhxZ6?A08HVE^K>PQP{LF`u+b1`2PQU|HJS9 zzo~su`?$7)wz=k}<{$nEF7y!F=POf6#H}{}1Du`9axo7n?DM@w_w5&3UJP{bFBtO> zHB4}!huA**Ii>h}c_gq=<{^c>C)0$R`NgwX7i<%thuA*rlu|5ixA8R@h>LlMN+j@q z^bp%y87WKaay7C0$b}wad-I2sCE`|_KN%v##XMx?BeS6F?%Ss;L?SNcAu1SnEqaJ; z(@!Zco|H#|2B3%7Hoczm;>!vMaWM~BIXu%}6Tg$d?jslT5E1)OJnYC{A@m77#I|x> z%2e_B6+p!MNN}Nt*jDyWnJPZNf)L_j9&XMtUy|1ZBa?ZE8YZ~VLu_-J zr40XqkA(ZzF7y!Fv!A347f;FrYQlpeF6JRia&AIR9pT&U{$I=d3&uP|1%oD_huEHt zOBq%uj|AfhJ;XM9TFS5o6cFNK9p= z-2#V2j26L#9%6fZd`kBVd=&q_o6$mC%tIFTZ?4gOv#WcI3^M@6JVXr>T<9UTQJ<#V zFRnlX9S>~unTU&eh<~sw>^FlZyr17D5YM?lJE@EKhux_7ly1++Yl8WSdC0=PCupQ@ zypoHNpik%_wg-ij^bUNaz^0m2aG{6TdhY!@gnEd-o}N#pIK>l;fh%KCsDlI$lHv^d zXhnp$sDJn`e(sQQP_nsrq`V&JHg%AH!py|QJVYfLV9Z0*Fu{c$V#{cn(vCMjJOv`S z&_itLyHeU-;!DSUHy87eSsh_9f1)hasfaNTQNsimdWg;WSxVcWJ2;RKZcXYT{;oTx zrnGrSo(vj59mLJQCbc=pnXNpQP01w=mg35f}52={LWi zHEsJ@Arf&h4^hG3zC#bO)p{>QNBBs%@8&`evDF%xqCKjB5Et{1$y!09J6&9j3?$HYM9_c53$~? zll*ftJ`#GZ3q8boV_P!5?+Z*mD6B5#A)`;lKuwKZLCXh*F%MC}pb6+9*59`$Uk-Y3 zh&C#?&_k@hk4e6~N?tZJfO*L1lW=D0c;litMT~if3I^i|J;ZuuWb!xl_(&IKcX>!Hyd+up3VLL}m9UkwcU)D9D#Zk&AlTX`fv+9GmlXY%n^6cFNS z!;<~-9j&RUPL4r4TUQ4YTm%#Dy_>ve3Kxl;Cvml6NLBKlwh9PwwZvp2YQk@X9ee!y z=M}LQ)xiXpfC)dFn7n(7JQB=L@eGMg-pwyU*m)9H97`7Q2(9UYSs@Z}#a07@@f3pz zH}^{3@&q4=t;d@q^7g&tEwKs+aW%taL$|DenxckxR>YcC1B1cRgeBA`zhjk0G6}B6 zh`hBic{BeYW9La+jaagG`qG*n7T^E-rwsP{B(8?l!30+WOjul*?ByREcnU<-|3}x# zuhk;@cJ!g>e?>2eek?jWIwtB?)WN8=Q46CUjmnDpC;#8`|MC0(V)PLEm0qc@N$Xgn z^mn6*nTH%d3BR@f-TFA`wJwh_4^hK}V)PLE<^HM5rJV#8$s`n`huANNC?lkpdC2i^ zX49Ht*RzqvA{j|B^AHuxAQYpA*pL31I#*hNK_tA7iqS*tM}4VtrCUuxNHOz}Bd@lD zn(RZRH9wCr4^hK}V)PLEk@cx_Btc}6%tA5s5V~%(9~qE3N4nJ{gcMT;@vj??yz&FB zX-E>^r@+-gpO}ZJVL~x_h<(TS)bWz4KtlA|V)PLEj^?T3XYt9Zjh0-JMS?z|huD`lP3^Loj}*{Ni%^UnVt;j)GD3=(hkUvp*8FEb$`DVW!m|RB ziIGS#^AI&mC`J#lzZ#p`d9J)BXaIVMec9C1PCqChq?mcgr~A|VHN`7p%tJoi2VbW+ z`%(HdIRBfycv>G3FsE81xA}#NP96YQ0wS zNN{!3L;T}uYhO1_So32AOem%<;vY{v`=-{Fc7$OR1g;Lo6Z4R*eYO5b&E=7xPv{}` zj0aLR_wxvPZ83U?J^fCqMx%g`V&);6Tfkc7OxBY%6fx!@Dj4($J;a{gE9IW}&AGsR zhhAHZ9%6T{O}Q(+stiI%G4qhk!YNwQ^r7;apySL#HVcg@HZ5q94kUy=p@-O8dr~gv z@sZGLi_t^ut$U>W^t%E=ikXMJ@$}0Y-RUn^NN|}zli}%}qwiD1>Q)C6itAv)=*pC1(ge+Fg6CarhV)MPnpaPo1gRy>6r57*9r(J)?qUDxkyOXbL><_ zOj`{M`lP{xr(G$VevwCleUjOToccXwll1t3D-)+M;(CT9JGGP6G)8(N@Yt+sV6aaz z6BB-ZDa9>K^$g{~`MS#ozxgK@V}pq^B)w$v!Tlbc2+j zhs4I`U()C<9TD~@V(1}im{}-64{P!FLGaA&hiX)j+rF! z==mc`5kn7A!(cq2hd8c;rH#&)M>3HT^pMyFecz)A?>r*Mj6w-|h~x5*v?r?Mh=G)# zhd`v}G}7&@3YbuW9^&}ASK5Ga9D;{Mgc9@+$I*Lf16n8`qy#-AwzjSjB&#{GToFSL zQN@H3^bp6h=aNnVa zINsZtmdg)0{P7W?1UIVs z67&#zbVjglgZ8MGZmh?go+8Jm9 z^a(x0F}+P%_#b>E+;=NM4{=O;CM|rU0zyidhuj?7N2B|GRs3~DjCqI(27N*gaZH_? z7Pea+2^xSN;+UF}7B)ixAtlU1ZjOfW^!=*1GDVDehzbUMLJx5~+$!}NzthRila!!` zIEEfby~gjhAVNx*hy1$uEsgHN?fZl77TlgIVIHD_L7&h=977JJ{`LWXb%98*exe@Y zA1Fg+r~Wok0U;&SLHtAR*TwL6H=rhy9E1CgdC0GeI#aCMVmStVLJx6tX_tED2OhzF zw-WRaN9V(-XVxenq=b3MCX}FuI68fudYWGxiIHI6 z4L!us>ABR?(lI8M%tVTrhg_cWC#|XPN_pvEe#}EuFz{OR5Qp)p)crxXCiB`E_}Lj- zr{a%%`nN6EL6PF8@w5A(4}5Lm{Ew4v^EENAeX1H5_}Y`0(9k}0AHV7m69O_Fk+iRr z5mG#jCF}JOt!ZM=x*$*!^l55!Frjz~CT#v^>U)Rzt7Bd}nIS%9gcMK0WZ%`F3N_8W zD!u=Qj~JKj#SAap6IeoR>eiseQ2>E`lJST%8<4uSm%MCfz&Mty z?rSvJl@}DTvDLs}JdMGGp%123e$Ge2^?31UL_+Q=Bc%8VOm=G0{ZLbjX+iUo7>N`= z{ugX&&{`#cL7zskgnd&hR>*6Dn`$JBG%=bcoN`8vK|3GANcs(_q3$C!txU}nLM9+IAO%rO5?c|w!mMh{8PnXilxH}jCehF@t- zdD3jkYBCZx^AHj1AdNJJ83Z?aNP6NkhAGlvB0%sya-)Z&CngvsALq-)8^p~#WWZDK zd+on0@1u+{4^hJeH+o2VLW*J1Z}NoDO6no>IiKGCxH3ZA)It3FBmFFzGOni5(UZ+~^_c4Q3l2mfk~32yrtH>3wuQ)D(BBK6iC^ zM-n&lklsfve}_R6&_mMe&oc~_Rtzj5j3@Mv^!llWp@s5fCgNru()+7-X|j{jyDSz7 z`oug$1%vT~9^$;)&d@#R>hL^?8$HDN=V8PBpYS!I*SgU|VtXXOe}ili@)TH+o2H*Jt20^VKxp1Bw`Wh#Cgt2|dJl{*1vP zZQ!$-jDj0I#Cd*+!NK29><4_@=pm2{-qBo5t1Oq-1bspeiS0TI{zUQBvgJ!BBq(-+(|Q@HO-h=N6lx{uBj-l%R*icGwE{-Ors)Tjdz|8hVHt2EK+K;w=5n zpuNOL5^Yo{K@V}3zO0Op67-PRtO;v0x+}+$vJ@hb67&#NOejGQak`fqH1EhGf%>9{ zINdo0%@Ya;DM1g3%^DAT%U6yi{3OSqPv{}4m{5Wq;(ReD?Pq?~!_JeGpochL_$lqm z1wI)QR`ifqXASsn*_BbfB+G(f$1wB|RZJ*B4{E#=iK@V|GDM&jWG#3OC0zy5+Kk-cdCGB|7Tp%K(ggS_S3NsH( zr!@_0D8D*rCwfS%8YYyWhd76HOWS>bkAz-Z@(9*6_-5K}=~Wao0EM+=IM$T9IRa{0 z`Y)$KBvLZ08W=R;VV1B*+NaXS3`+>(X(%FtD$_pI%9B9@hOlIB!+5&9bYq4hHke^= zefD=4jHf}Eu#-FO?Ol8%T#uIwWXSz#Z%ePDpln=^mpp{Y5>nbjvItGkvxQ*;s)0eD z`eVY5-n6$~k=F!G=*J=@FM@A*AF%Oh%T&yVRe?7fK&7kM*n$CY1ER zgsHx?($VbdSR`n`0}SbtRvM>(kdp3FGB}g))A%*g{3JypCHFJz)jW!==69;aCP1IM zVZxY~(w2?q6N;0Ms{fDt56}PqlmAcuhu{DEZ~A{JdPv5YHpXYA<*r$}k4l+`lzn?m zqr0`_khGTNG3Fs^m{5uyk}*2LI9qzT&mx(HQuL6FCypp1q?CC`*-6+_zqRCGZ}zFO zNJdi1JVXr>O3_0y`nE8RYr-O#r2D88JtU*gQ^v8+C?KSid5EiIwMKXI+5^&=1St|J zWgen}K@-qJGJ4N4j^Q_E@X(b|iXM{DyS;IYv@y++K?9hFxQb!#=H|8T(k#Ja%tKT# zgHVbdl93%}99WBQf_NVZrRX6US)Usp;+MPZz*Q;pkX6&+_atr(8q81M62?441%p1J zhh${#Hx7^{IJmk%O(vm~dI){aXJkIDjF3|5ApYn4s%aIpra}AVR|oB69-@W`rRX6U z?WP$!_vItubG{TkB%@tRV`pA{@yH1&WgfD;4eUwW*s=YfLL^ekJVXV9KB0$Xv~6ST zBpvjDJ_Tw5|3?qWXmilmQJM=Rgt(c9EN=tP`5QYvly>JC2Ij{+WO?hS{tg3QLl4QQ z*W754-jG8i@#!qM(L*xoPBxnP-WGign=q@y{oKN8E!ZqoNfM;$|K)?|@08yZ&PbURUsB3UM{O(VB7>%Q5H^^AI&maHEH$e?H29zx)obyP)H2-wiz^{qqKfTY5ej+jnCg zGV9I-sHuD1If@wb5H(D2qlct_*2Hi#Mji?JgdUQ<`(tHvX9c{i;teDMfs;`eSu4En@8WLlkkitTt! zUOMy%JtV#G1H&QS{Mg^SaifQ%7d~M)_%k1cZKyF1nb55jB&%cQUmsv3;$|MAh6!%; zkn}a<3lZ-4i!qP50Y`uoOkCOEoYUPn|L0bK4Cc+?7WHq!UBN zC?mw(ktMqU``y1y2nm`q16K!qDyR-7xbrdL_~C~2?f9C|Yu$MasbyGqLIEM}4w&rG zeXuV0_2_7RJB6JmapzV8gC^u)!g2KsYp=-@!bWyBB4a-=tgTQ$h&ziV+gm_udO|u| z2D7iYgGbz%)xcmpWnjYo?-*9c@dw|#AL&wL!qVzGlRys7>T$Y z42x<=u?&8N$PSCR?U=CNNW<$t$`e8>Z49Yzc)dsgA#N*6R`V;GEPb9FgYjgk4kozG zn6N{8!vcO62v33hKlT5A`u+d^rvI0rhh#20WqQ)anrz0wXdz|HLr%wx)#&a$JV1I6 z$z#kzR4}tph8~jnlGikKw>%;E8hS|POB&PEBn5<&G7mZ39RAJ^)KpuJfv+(S5wZT# z1Cx~wUW*=*IrD4NqfhgZ@IER<56PUFW*Tu?0U@Q#Lrx^XVY$1^1`d58)I;|V<^^MUrJ?8o@4 zL$57G56SHQl_|Ta0zyieha4QA05vsRBfYPMn$T-YnTM!hLMeJk=KaS^S<>2rMS@nM zhh*MA-;^n>JtTybG7mX87S8$IZMHgSE(p{F0i=&NGTF2WgfD3#Q{H-Cyxf>2|XmU#cWek$!H)FdTl9s zNM?&fQxkrmAVNx+hwL`N1N_ep*@O9Hc6=G6tVrAR)}-=pmUAY03yG zWgfEAb(_{SL0ZbONYHWSAu1U32|Xm^T6^QK{Dg?7K!j5Ckc{8HGX7f1Ul|@0DP9+L6vG2<_ijKS3f5(0uAlJV<&WrUP65Bac0H(FClP{#w2 zpij(0)G(nGJtX7gLE|ZYLS*MjO3_0yP9_*nz0NlPy|$Ej$krJzXmoduH$SL|F%MC} zpb6+98Q-KBzkNd<3FasCkc<<@jo9+L5|cE)#u z?qKxVQj<6tW^6oceD^v2LlF^DYQ$;vjYnV~_x9z*L1Ua@hU#EKX&NTn@U?M^Bx7)O zqKyirsSJ6}xTS}@CTKtkOE!Ejt!dGxideFQ!FI1Q27OAxgsa{(R`81>JS-xVCNgBG zv3xI|j4iYiFxmQ;^^j~%?oCCk{a>)0J8}%hQ#(xf`Xfef&|DBm2uNFo)HZs9<^mBR zrEOTU=HODd=Vbfj$)KIBtAh!p1QX6{ZCswjM`G*oR*1~~()g-$iU2-d5h0~5FYvswH0dXwZ-m|nO^9n^Qwi+0W zrx;8)=4s=cmV6{}75%^J|G)j4fB)~V@Bjbd`~UiT|BK)MFGCN>dS7pzBrSI>(z&cM z^pLpL?={ir!?RaTVjbi$^bl1{C_@j)+7@n}m@AJ2TLkDKS?|4Ro*+%QESZ^u-F~cGmg);PzEN>0-U}>vOLP!~UNL-6Y_S2e{uHic_ z-jSpXJwy#N2xaIYSubnN-G=dz&}+-kL$Y34Z|>GW0U>4RA#pL&yin5v171N_z@5kn7A#e_2S zkgO*|%ywQ?*m;sN^pLFS|58Rs8G1-uqeB;HO~((*Yl1$ZhXB^mk0r`67*FUSS;M{N zHvEnYo&pid&_l9@_cXWB@KNwNUxpqMSJ&pz=tJ&)C5>_DIK$9G)G!z<=pk9deCF2t z+Cz*4S4Ta>KUnJ8LTJLnzsl=^+mpJ8f7lJvn#pWAVjyMcA#rsrKlvkdQNV;U^pLEs zznkmxTLgGmL?}ZK$?95auJ=111)uX}=pk`64~~Ik_BPT!KlF)V=pkwt^a(vA>%MYx zJ$_?Gj06op56QakL37=Y^DewjpTYR#`=FcK+44^hKlJfVkVneUi24n859 zpU^|H%oV14KPe!j3_T<+bV;U0uP@xtOA$j4QNy55=pk9AwWfQa@<@Q7hh&-hneJ{; zKu8&SNL=U(^Jz`%wQ>yFi5{Yg31#RZS#jM=ms7b&cvwUzLl4P{y=nUCtO7#H&_m)h z=GGd$?r!-iMGQSe6%)$PL$YFfm@e^4a6APfl%a=Y#Z;OuNsk}cpTvVAW#}Ps8WZ$M zch`GWUK8{QJtR(JJnzRk$uSsD=pk9!ZKlH~cm(&|%Fsiyw4+Rid9P&$Mar0m-2Q4R zBpcT5U4=-bjCqI}CX}IvWNEgW4)IhPnssC zL()+gXlLN+V18mAa{I95?;=5;&_gmWMVof;Udzstl%a=Y{`jskLduwjTz`I(Mz8(o zb7>_8HR1ML8S@Y|OejMS$-MZ!X*=(=VkBq)dPwHQ$4%RLuSJBEF%P*uza_2dGyaJn zVa!8RFz6F{Nao=orVYFyizhBDLRoY1L&-c`)3hPzRTS>Kl{LeU_i9cu)YN{Ci~o2T z*0dTJOg>F8;h|cl^{>h!0n!+egIkpmQr3ti%eqW!dRAJt!)J&4Ze5V;nD7c|kRuhw*-}pAXOTBv{n=hSV5!Jw;Pc>LVy=lP}c_cu>5%F$RMo3v0OSWMu zt?Afhc_e6OXmv25ECdt2YBD{^Z>r%bkpETx|M@@k{|ElfzyEha_m=Jz-4nVzouK_w zdqlhO-}w7~9`umxGXpG-NiSb40=q#x%tMA}y{OSg&(uj)+dRfRWO!ybKPK%JGt3}(&_l96 zzia6w?WO`Ej()*|9+Lfel`=v+%tHpPJgU)0?YkgJj}(b`n1`rgf(Jb$`*WS;LEezX znvBH5JY>*H8%=nAF5jy_>CjH}knGPMvh=(mM-0TnJY>*{l{C_K#}qKZgC3Io)&rKD z_jm;NuRZ7?*>B#lrh6x_@knBzOEZGa>kxYUIJtTY6 zT4jWIn1}Q|Ig-{?^|m|`bewsJ3I^i|JtVuR#FG35A4#-P!Gj)>UDQn(As*%-Jqe7b z$jLv%D?}n5<{>H=+;`|9*@ZnUNzzUNjLbky&?oee>@|N_lG5bKOvJ-Hq-QG_D3O!D zpDM?oPs~HqFu{W!lKuQ|mSz=vB;0rNpoe5XUua3y3&htBX;?+Sr z(L>@?Fz6F{NcMmt^EGKL3mbjnDG?&_m*~Tf77{t@2N-&Y8V2JDJtVvFDf9c%W)UP5ZB+1}hh#T? z!TkPfd;?HeJ?J5Ewr0&W`WlC)N_!GgB;r92QN;ugdPsJom(1HX$P+><(L=HurJJ`& z`$;UBnRw7c;%rS$(3&Rms~+*{pik%_s+izG56RZ{G*<=Pcj93Y!K24HQ}d^}D(Jq$ z^CTV}&Y30+Y_8XsuvI$E2cmM1+(*jmc81ouH=3 zc{BOa8TM2)Fp$GfiX3%yc|O&P|X<0jf(Z|IH_zuE)A*|*LfK?Q4&64-4;mD}1A`_^ zz=Q|ynxE?`j|BT};~7$Ao_ALPA!XxOvU3AyO>eJN#Ku+wgYh&56TaWiJZUN)NnDTr zNB-YD@>=AVkyVlNA|H;-jBHf%pZx!ye*gd9_5Y*)f&Y8aLvo`9>riQn+lph>LcGjF z-h64jM&GF0 zE)WsoWgb#|vIniHi?rs4@f5&dJTVVZ!vrsSNRI0btNtx^br$J7i5ER2$2HihAE|&4 zFY}O9f5d3?^^g53-N8~M;$ds7x{c=v^ZWYz5qn(XR)c{0G5hp1pMp3p;b7Hqa$l@38bLN-64hvY06Zn-KQzLXH+WghbC z{`;V&xG&=Qn%MlrJVXr>yyzi0&u_K-{E<8oTpjfg|0c+*`>JTd1Je{R!Ao6)Zl~lt zU(fP0e^gJrIs@@C4|#Q8O&V$cK6xbQ6M9I_g!e2bd9TG&Ac7Y?Bxk}%WrTQ{hb(zN zQKPRn;x%c?fOg{coR@is3I^i|JtSxRXv;Ue*WxJGrqCq8{TUXAs*%-OWuQd zyxxfAK_4;Oo?{-Of`QkfhvW>{Zuy+|TKw@5!Gj)>GhmG6bKYwaAs*%-3%Ha%dPq+H36{^KCnt}fhvf8+QAUV|dB}qO_h?OonN*}ixAj&#P4x= zn1`rf&?oeeoZJhR&C=?SMFMN59^$|6=4^peAa#CuSD|#+PN6R1zwdIhESuYMHuPhi=T$2_Ca*M2U2 zk|OqaH8AMYC`?E;TU=x1kpLOVkl_{=j9BqYt#D<5vSFX(F_vs}7OlytfI-I}tqvx5 zMqt9){VXr8&vO9tn_s3<*(2h^H@0cDWI)so+U@ zBR^JWHzqu*wLEr!k0kDv{YU=a_#ga#PlP98PQ*hIrieN^M)5!V{lEX4 z{$GwB(jm9MZ6LpxXE#VW^N?#7-qGk|`%g|{9kfd4vdWo!Ds7S z#wWx5>vHBHS8k5b=wn>>)mOxrhp1rCC-jgG?bq8nZIed=1U;lf`vJC2;}sB6&OGGG z4MJ<`BE6}D&n{3p^oekdP>vqbq5f@~MVfFSnTU{b<{>{^ zgZ-rD=Nt_bB9U_DAu1R&0X?Kcy}LH^CV3>IP>vqbpPXE$A5r;|V<^_s?=$s}w#V?q8RqhvfeGpsnQ@1%#9{4>|kCU`W>9 z9I1#g4>@}Ww!)gXHw~9#&?oee+&}u-T1YE(C_7LS+;{jsB=--UGD6DP`;fD@lW8(z z9eFb76Y~%i4Elr~l6(G&t@c-ZB;0o^M-Rz8zgig~<;+7)re4(On@zggMj;X@XC9)4 z3FYV^x!<{LwWKu~^eIpi_&<6`?sxauYDsG}2_atQAtzIOw5C4}%WDGjV;-V{fv=&5 zT3SLfCalauzFY`rT$+Xze5w$Mc$tT&VS*PuB)6)a^~}HIkzhPg521pQ z>pN^cBP}giO(x={4&qmpFBkNqHRVs0M}j^v4^hEjJfVl=E`Qc~An3^{+Nj_~56NAg zWIYh{p`fsOnTPC4`b48|^7?C4d`%2v9-@N5U_lSbef2r({&IOFxH{?~{wC~896}Q= z@2`LfUg{$Lb}swIx<5*ez&;7{5S2*KC-jiqS((;tUvQD|u!!JA56PW*Q5hj#<{`Te zJq=}BxAKogj6}T5L)0+Aiyo3Y?f`yk#1+nb)Ozmi4(u~vn2}VVf2d&wp#uFQ@6q?{-KBjYh=lia z1^P$cqxJ3Gr3bf!kP7sV_zXiF)D(Z?Exsnarz=9maiNBpg$neKyb+D<_qUcu0tEdd z?~$GMZqfsZ)nq0W=pXSJso(l*sws~IeM0|G#e@pd zU3!*E2q{PZh_@%z*XYRylO%WKG4u~L4BQd@Bk%q(cBk|#Ws$(K(LeI;Z(?^y&r%5? z<>(*r_QbBVrisJ(J_W80+!6gF-kz|QViVq#W8jYHA9>EN?1_3FVUCUdk>{LaPuQ%0 zkaF~oc+>Cj+#-WQrRO`8E=D5d=pTrMERsiqKB0f)IgZ)eACMz(b<{uT8aCc^{b!m` z|AGQ0lv4+xYuG%8!QS2?M+~GK{R1Ku|6L^L6Z%J9t7Z1axA;iRvC%*BT4mWAuTwxs zIr>L@>iQNMeXHBeepSTKKU6WH9Q`A&o5dIr>L@>bg1p znz|@r=pXT^YtK-uX^9+zKB0f))sC`<59ASa>~iWK{tuK|gl+B9K@pt(Gd5ZXx{#9w;!D0`@+ zb1=pO2#hE6kNCuzH~f(t@`Qj<|L`Yl^HwBa){ms62}DDQE~gIiPnemMqld(|+4K^P zwq4o^W6|I$(L>ZQ_zclQI-I*~JGYT9AHB95J*30ARkm|M6A%h(IeJKZi#RwB+48lw zC2NP87=|7KSeL)U;4?%I>2S8lc6NrmCP2_bI-KodJDZ|_kaF~p_!hAdG}&80&zL|> z&?oc|RZJ*H59zS)d)pT`SVAkFC$W=q>LLEmui0(bIBxOH@BH#o{K*iTP)=Qhj;jv) zvTa`smPfJ(<TKzn&Y=o5NKe6t&!{gJG448{|BNQZY{wr!W*kVBu?oPZwE;oS_|_Ml|!W=9W+ zueSsCxP)87e&>@h3_U~*gR4Ui>F~}wwhvCrBbi7!^$>rb>TUnppYVP;26F;+5r3cF z9&Fq8r5u5q9X%wz-gY~U^sq+(6Uxy;I+S*?tslc9xbIeu9@4>m-L_sjPy?-GQwDlS zeAI!tkSyz$2?~)&IeLg127N*g=}>aZw$3h(1PwqB=}_Xet-YjxkaF~p_^ADGu3gBw z8mEY%hp1sNp3p-&ys+A~TAF;I32c5!#}DO&j<(ee`DAS3apK1tcCLv=A3y(BX$F=e zk#fghuwR14cpw_|$&LvZ-)CDTO+G9VAT~r6{cd|*nxG|wlv`P{Gce@h=l{&Brg(L5 z-&v}I3FT%?I5pe0pg-3HJOv_@n-H0@()K)mbPy3zZp37oLvSb~Zf{p!$tBEC4Gfx) zh6yLHu{}@ZHNkjFWk@I6eCgC2?Bn8wT6qdf_T?^rO^+%>BIU_{!R{L+$6!1qVZ#0c zZR10D1oz#_6B!b28~2U^Ldp}wWO27Y{~l_pw&KU$NsIDO8}3D`N-TAaE3hT{PjMH81oSkyCzL0HoPlA^3g}~Uux*+Bu%;uF$xvvBl$~rC?lkT z`N-VmaP~0i{BL3MnxIe2M^rG_FF_y4pS;MCdYzAi_jU#PNd9DtBUL(62W8_;Tfux} z=3)=jl;D+~Up&TqL-8oqYWxs9?|p^pJc*E&EMrcOI@z+%*#_&_nXmw%Tt>2WljQR4@;DB6}~bX&k>* zfRRWA^N=U9XZ{@q;|V<^zxhV{1%8(!aGu1NCR9)l@%O3uK>LL(z5)J?HDem7pbp~i z(};1GAX)nRX^I&05H(DwKo7}pHq?GTXf6os#%JJwy$IKB0%?ZQpHQ9dzHJYgeF$ z(gnOxAfMtbN-Qe6ofS=u;C+_-Y&bl(GEPvGsUkM3x;?Mo2{?OqTE52=5BNbH*w}A{7m* zg9#N4FySjl?307G>jO1GpXxJYu6+{!&|trrQc;g3bBw1o+4sn6f|;&vbugi#4knzL zY9ICpABnBUYa=q_gnd{e1%y=8!elwE2SH79?<`luqN{f&XRyAFa>Vx6s{Y{@>|;@cVz2=phCBpLF)fWv#JGH%KM(kk4K((df;?+w@Sx zn1_6}av{Z9SI9AgP>CK=uw{ueYYUIyeN>4aQn1D0%#ySKYC^BAWFE3>JWS*S!0DR}1GYXZ|Md)+BVDm+1#*cEu zKq{Gs?0PJVM*3x?0wz?VhZMM1I1@MU2;N7P=ph9qSDcBG7C@gELOsM^Pf15-!lw#R zNF{TS9XU;*p4Dfi9Tz?lI!HnBug-*I#Yoga{5?{MWG0o&Lv~~xqY2Mc$)g!bCG!v! z48{|BNWtR$&L$o{5_)YVdPu?Ir=3lt^%-29h>%L=A@9e9Lrs=C(pfPcV;-V{!FWOs zDOmK3v$6CRgGDk4mFOV_ixQlTTgl6YA;&!A{g_W_O|_+ceijM(#5_a|6DrX|3Z}Gj zhDu9th=g8Si5^lg`G7N2TAoMD* zK(DPt4=I>5+ZobS0U?#lL*5NrL2Ftqt%_Mq&?n|0Dj19>^pJwVdmO(?h76JLTuCK* zNWtLgj$frM0tq3N%tPM#4Bm$rpLj!hH_BtoLsT&66M9I&pc#%|g64uiO)#FQhxl*w zL2Vts1kD8^LMo|)_;1g*cE3(*`d1|1&Hx5|VjiM~36)RK(DPt4=Kp+ z=J+;fE)Wq?$vk9ZAJ~&H>Q6l^zdGm>^AHsb#uIu-L27|x?|wcKdTk|oNI}Xkj=hqq zKm$aCR5A})+q|ttZz$O#c?gd&4^hFOPv{{9$=4lwg64uiLKsifL;T|@+3na9G#7{n zsiY3#A5UwWEub}h{}_Fgk)JKdnsbfLsT&66M9HN<8h8H(k3mG9Y_d$LJuiu+|03MlRO#B z~wggcNNel(QAcD^pFD0c!yVdS0N#!l6iD$zsoFFod1E*&_47h7z8LJ!HmR8JWpmCQrdv0j;UdHAO6fVJ$xXI}FBC z4NQ3W9mkx8d?YqMg)`(4$FsW?5KR>{p1`~cz+c8dhnnNU< zpDJb}^8R~{adr5zaek_J29v#%0>2TK^2nE`6tP(n27e|%83V7Ki3zuT=ol-_1*|66 zD4M~L(T*|FVogFw#nUWVay^>t3w|eo{j^HOQ`Nv=pX5nQSkb^SP?~)qAeu+C#^*K4YRsuIw1&M#-S8{nd%`{8 zbHX1Aw}saUyBPLKSXtQ2umNGlu-c(NgzgA+h0X}=9cl=z8S;I|_K;N}(?WWMB!z_P z&+0$azpkICzfa#*AEG;>+p1fso1p8WYoooV{YLw)cA0j(wzIZ{<}O^4;6oeHrvIP~ zUmNJ7js8t~Iun|mmNdbKHlj^mp$#9RfDj+rNcNqZ^EKhE4;71c%SAFfh!1T<6%%}D zBic4gG}jmL31Jd|nvBFpZGKtWZR#jRcSpnloMG2(%JS zL>paC86iGuBL7s6f%woyvaiNpqGj(EpFRB5r4b+6h$<%d&_*;jMrrmR=Szn>oc2lZ zp^a#6G}7!}qJR(|+DP`rjpsGtt=0~A%P}Y&ZA29ld}t$@-v!OSa(N`U@6bjx*Y|7o z4NyRc4{ap-;s!6RY1jiY%xotxB>}8f-T5EpQw%a`*iZn zdXUh5UVLl@VSM6)zp z0U-=U4DV(~t|jp*QRS$=LNe*@FwDYQ-TKEI6kiC1Ro_*5c6 zeCQ(CUzRV^gbSZM$v+UqK+q+05mhYS=XVjB@6|Ok#IJM(J~)3qvm@T;R}nwa(&@ZQJ%pAN|KlL|a=$ILqjbWMVK%`>U4)MAj`rQoO6klmL#)KdJmklL z$7!Sj@#EzangyQ*BehF+u1Vr+VqS|L(y{IL&ef7&Kr#^_mCQrVZ%ESUt;KD{dpD4b zVa!8BtPQ`5Cq@HbLl5cL=8|*OO?ge=wdf%o+pKi15*ML-G7vH5A?Me@J7jBdYso`c z>3}g0QNh4#(L*{mXzpCRl&^_-EqX}D`ky)%=PDqil6lCf*Y0TamUACWQN);ss9{1S zdPv7QOPmW1$|Hf-PR5V7j>EZte+hwqriWBc!XbBRIUEtNoO{2eJQDP2Vs$W~a)Ow! z;7`FhoqzX*hed?S@reAf*E!wBCqu8T9EZt{9fa>pS**`ARm8?t1B3B2h9xAOn82e^O4u(kzf=&!XkZ}2a!I$C;2uj z9ojjZMQY_7epem|-dhfLVNQaJ>ZeYPrx zzD#a&OA#Af4Gj7;2ovt#>+HeL1$YW1;Q!Y(wU5=_!!IBL_t~#NYq-)_kTWeg9uK;z$4a z|5*`zBT^%xYFw=Gk^lGq`qW6N5gGnN_>OQ_`1J7J;mP4)VduiOhph^m6h?plFEsSq z(Dy?NL#Kvz4^0k@3OOJ0K}b=^%#c1IDIwANpY*%*F8wV1AiYIjOLtkfSLf9|qwA-$ z>S}ARX!mK?Y3FN)Xfw4jn(Ke_0|4~DNfnArPG(uYCOpYkByOt1V-SAaQ&jA@Dk?HM zc%F_vHAtLl{sK^q@dHZ~0aAq$lapB*LK77(4}6?l99Sq+p~R?SaaB}eXu7`kF48&y z3Km^Au8JxQ1^RU8GLlck04gsO=@j#!UKd36ISX>oV6@NNiUGWGX7su>~tD>etfqDwWES-)Kui_+;qCRn$`aW2m)j zAJo)%Wj#3tJwjEfW5X|jzOPLRkSBP+Lwm&2yh&u?Kh#?r75zS=;i>biuS}N?GkD4C-7&%%uZ;4 z4^<_nMd&tK_TfDWk%$jfMHLf#s4Ch|`f6uv>OyPUF+d&-bR1Ph6%%}@D%y>kw4>hSBf;SnI&%v?R2A*UXzeKe;YUyJ z_$M77s!C3i7Iie??LJBPP#y`!6RL_TCiqZQv>O^}M~Z9yzy~8H6nv;E+Vwk?5#mEt z$!QY*1+A(5SXoVU*rBSZVuBAE9S@%6(UReKFD#W3?&Q_?iL; z43<6&X|7GFP(*qo^7v=U2=Vp$OFcVVD?}o`2O0KMJzDmTpb0Th6O5;x|BTTGM-Qy4 z_YQ50olhvPyanF_42jh?4_Z2i2=R5tWFccFX~NrlDAbZif;;B^>R^Je8z$`4R@*#i zAreRkw?S7#9{ft%>>XJ$I#BLo$zU>rn&QPR%)lt{N1|AlzhH6OjwK3|&sK=u^MzxCkBl4@r zO_47}j*85UjH`L4=HZ&_Yc8%ivSv=rrV+nK9Ew;MF+XB>M0P|Ic>lk@#@ZUs)fimk z|Fn1BVOA8`-|uwi&I}AmB|DVR2&ljWh9L-YX8-{Kk(@;ll%zymU6ECE$-0P$WHEbh z_r0hHD4UOtD zuMz!kbZ7Lr=*;NQXhF1E+V^QY($=Kimo_A=MOs$myU2Fn|GOjoBl(>Fw;8LAzZf?F z|5wnD>09(=`V_sF-b7E+j%u5=#oA=;I?dEn^-Fbw`VV!2+D&bsoDZRY@n2>_V9J~Z ztCeWMf9#h;CE*o`?wx5S5L5W%8zy53gcbfkc(Rr{+}p_#k#bl9afM4(za1BRKvZ#Z3X!n{0t+{sJ6`Rc?Y}zbE+}LPL>4ZYQ(x`oooxU!x-%03 zS7uIo26Afu@9uJ>JTrl~!gC6d<+21q3pbqgvD(e;V8Z<*40Ftcz?PX);VE6v{)e-Y zAz)oXU`v>cB?PuiT%xw}FMZu}AO$QTuw`o+cR5vLKGw!mn=d6RO$PZ{|wOI3f$by1SMbN;H3O#w=L|cqg#s zuE*g|oWD2y%K#aR3v3zml-i(gphRHHpk8W&Xn@3w3v9V-v598U`H~Fk6c^YMCS!4d zEk#kaYMwumGi4O8xWJa;HEI=q?+1^S@g+eVv4xki_6AkN!ve# zv}=B_Nj8I0BDRz%V{t?mZdf!(t?bQo@aLuHeOzG6q-{&laL*XY5bGqcB}~TR0$Z+H ztNdP~;?B&{g2e^4Tvbsy|C8U$oqSjvv4y97Zoj+#8Yv3m~v%+`Bhpq^|}9$^^EA$yi)q%O!=%ckVU? z9|?3eg~btDco8mMq?{PiqgXVH1s1sp}@=K#& zi5VBzGD3%WJm;rg=cGtjCxI)=6MXhz#0vge}U>OO<$u4HfFY#R*%KcivOt zxBAnjPF`GKOW*1lO0@Z3AM$r%#p#r|z?Lu>ixaje|2m+&_&3=Q>O|P0{A;H2Vu>5d z$qDKtu%++CYcQt=mq?MYEiSMnOvd7bEy_!`C`*R>BRM@dn8yiQl$WZNCP8fBf8MqK zRF+f?jDpDGNN!gi-w_}&;~iac6+Sj2BQcMF%G+(WXNl zFLCv5kFH+4g$UIAjwW&-nBnrv6UdC`3E3A-(QmQ8DT+si;O_>vW^Q?8+3_5Z;m*#4 zM#K91vvQZ&+41JCVEln$836(_-i(aG_PF`}zpCQ-|JN%osyrU~pYQ*_TItbB<0^Hm zbXnFPSs!Pu%UX~%Hmex+|IfJmKdNF;#X9i)zx}}f^D5j@p}0bQ&i}7uKAJf$vtwq1 zOfBQ{jCC1{GA3tSmvKdgo_-{KQ~G1+Q__2T^^kp?Gw$5R!BRRwmt2s zw7;hHg*FESvK6Uj2ZGqxKmjJu8gMsp)w|61RsFW0B)H|WjusCG!( zqAk;=Xx+6YTAKQmx=~%MPFAl|O}M1;{bLE_6@J%#eMZ^t5eB@x(!FcVgkY7OXR?&& z)kiu!FDfo%xn=@cg+B^+{ws~6`A`Lhq2OvVz#E6T3hl;k~fB&ZYe3a`_y>PoU{fW%A)R@u>J9p?18w*yL7 zhphx;6<((>8A}kaC>u5?E4-yTL?T2^5U(g3E>f1i=Ff=`IU#uEjd@j-Xxn?TyvL4+ z1a%U;5+-8_;uU56rOGmIsV*Wxosd^}onC!Up|8R4mWz`%4iv#EZ_L?;IaO>erH(RW z6<((>8A}kaC{J!xX0P=}BK}Pvukf6nyi}R(E!81yS7Igvt8D!4Q6<`D=LL_X{W4?~ zp3}x}Uq;#Q{w|gq4f-j>E6VCd%B*|koS;s~D?F!F2b5XfYF?N@%SEtC>1J3bWR)^< zf}_R>&MWvRob%E&k0HU&#ou}f#1;OqoRc-x+m!YcWXx3W+3cPs1DeJ0^!i>(=lLFyAhn(VM6e7z` zAgkb`aB{&-@B8A2Q!5E16%GAtwViIY!=EIWaWf_6iW z_k7DAhln>J2qk_TFTdaP3jwn11QH65Q{3~)0D+l6HsSe%#L3N0AfI5I!998`l;c2s zkWNrA=v0{&h^x(Or`9_P8f zKPb^wUmRZGw{sCTJArh9af;8k@iret92h@>O~T_KoA5Yktvyr}aiBg(B`7Fb*LsDV z35*}XBhU4*FyTX9Czj&i{e?W@Tvv!JJAq7sc1@Fw{nIeMBL+JG*$E^PE*RQqr#~M; z;Dlh2H4ih$XTU=#SqAk%7UB7X$+8p3BWQQkn9GmIaiBg(BV14;K!D>%aLAhZhcKT9 zJ<5Rk@U&5e9OC2?BFj!7jG$f3^VL81=i{DH$xa}Pa6zKFw@C~}uE0zP6j||ReBoQvSHBewC1cR)2qdMkuvv>TJ(#G+F z4C3SyB4Y_85qxN>$Cs+hUiDv@SU?LFS@ISfi!1D(S2IAy5=|-6H>cEPbpj-2;ws81 zBoaL9ucSy{pH-LMFGqrB{S`v;yt=f7B*6jFM40V>({qLWbKVY=H7<{gB^r_82i??J zURw?-E*8+2Q=~;r9#o<&4|cWvhOUey8d9V^&#Hf)5+E@Xrij$&8H{x8%d!mW)PN%G zPO1-iZ8?z>)TzFZ3{fBQ+H#)6Ok7sB8J3fkN5&HM$ndp3>J+an2Zqi98dxNoBpZ|J z6z`W#LehdIE+xr^_3C7=ktQUtqgR*mS#b6w)M(txmHjnx%;2`HQyv*h)E0(S)k#a` zNHA~L5|PflgobPV3p(fO@V={A9vMs2Aj35y)f>HT8C;=QV^#H7lMBIR8Jh!U#D3UoG?d%pI9)`1$`9nOSiDe|yG?jJx6de?FZ5 z{~FH!FHfJEegi-Me=NEsx(v?$)Z>-hKozKm>&JOlSGgQQ-T3 z>y1Umc=-Nbef@&|34H(WVIAN9cPW4W-&$>fHd-sz>caQ`_Ngg#HhllDy;?^(9R~lV zh3J73Tdelg`|tSSqLE9P~EDgyyLD=1R`FG;3HP0)n#BfV7#}7<;j@E;R7qK z)=`t*aaSRMIw5`VoI)byG7Axe=cLqElir@chy-;){6L9%VUKDb_Lq{UjRipTaLigB z#WU$LL%LxtL=ZQ=%RB zSN0Y+LI!n02=U7*jgSqYPKYAh@Wegp@3n1x8fbJ};MI?JO>5edc@OdIksM=~-*q3@LS#A)u5gV-{ix zH=KK)+B3%=$(ej$uppvv$^1HMkE60Ak6DN(Tr%$+wa4QD64OFJ;rX1JH(!ayep}F9 zmce)um=Y#q7GjE%_FlESpFG9677G!@F`V-b8qWKtWQcVVm~tva#w>v;Q}Wd|ZT*p) zSq18Zn8I_Kx=U^SZ&?C$5{NRjiQ3w~?||=%;#=?*A_`CY$CVnSUG$S5zg-#BNnpy4 zD_$;D#w^4Xp3~&RYO4uSPFMg$6vq%7WwD}@{tlpHh%$jGKZeMdB`{_9NVSRTGjtEF zz_SH0h37QvO|`MNzYgt9r+L?cSprdpO;8*A*ZL$eEkqQa_VFhpO0>i5F}-9nn8pOA zgvg+r0#lB!g7dWeuN0ODVc?+!v8QG?oumtmeR&i zATZ_drrR*5wf~W2@X$w0;dKg=F-xGz6*GR}Rz)&9a1hqx z?FUxV%*rIgDrc2%{EupS6$-O5gyaR~n~{>+=b)X53TnV3-?nZy6TA^)%E`~RI_{~zD~ z_Y%DS&)NS!3;X}b|D*Z-|IZco!TEpu{eOGD|G%q3yuvKb|J5^3Wxk*JV&(&x`2N4D z@cqB{GM>qpo-sJ1Fry0m{{L-w|KGjo{nGQ&E4#n{|11Cfe`eZo{`>zs)B5n={~w9G z8F?Zy34Z^7WyCNJ8m}8mjEP29<8njSztCURAJy^uf0x1c|325&0soKH@csX1)Q{9x z)cNWtwMea}{1(Rl_Vi^;n>6?`ZdS3*FN@3+#%y8{z5Q}+GN===NUZzcZYovAY+@0;$_j1aX*m*%Ct?x3N>^>6 zx9b1}aJyxuO)L`YKI1Ypo8mphg$(LMED|bXHnE6yE?b-79cq9`)O)juMYOYj(Plj4 zPurE4HnIpGPo3kfAg4B@+R^>m_`Cg)h;40R5$$MGZQNG@64OQ&;dSb; z?}QTVHhOkTSq9^YcqGO?G}9X_m${Y{eK#6v6+ z>#!Hj)pi^GcaOB-Z|Ah=piaahp)zI@i)g#w)P@Z98&dDhCKl0lkJpA|1xQRAS%lZA ztq!l_=(_e5f8&@=nwU26NGwdoY~&GMr(ILD!I#U1P$y&&N5Zs0-u?^}z)3sDw24Dv zZM6cPQ@#`l#}jghle!~&xx}BCN5khGv52;zlGerWN21=FjV!`*TE9uVc0+)~w24Dv zEn8m$IpsX&^-3Uh>b=>>Av~ur8MBE+v{$!joj;Hbp-#vmJf~Oh&^phQBsiXkLt-rp zFJMlKyj}_Agfiq1C#TY7P$yy$?Wv!&!Wuq9I(5QqViE1B>$R5C10<$REE3Co9nSi8 z+1f~#WpLjSi^Os_Kr6Ep8MBE+v^D*;f}?UIs1vdXuhSY`D|kFWV%o$ZWY(~%B*Qu( zhwwVNIR(m?O)R3#@1dDh{E>)lZDNsF(~j^XkuE)wj|Rw?jV!`bpZC1h;7(ZrbwUo| zIfX=md7M~8o14_?d*A~dSe_HE3y?*)+1w#o{YNA-EC6vxtZ6aqX?N*icaUXp-yw%M zIfclWO)R2K{X?sAn?I66q#p$RRwZ zFd4InMYJh5YL)#9HuvZZj3;Cfp3{_yTBYN%8Q?E*NUZU^S1_llk4Z8pr*)smDMZGs z>0~%uQ-81Kk3{Qn>t2!!8>*i7PDH_(MuYN;}2 z-A#r=N2=$(lyd^C`-_lNQO|j&q9`XUz+J-ZlZt5eY>9mxo)gOMEQhQWv!;>ZwNdrN zYyO;Qv29JINc9@u3cH}YmrV?iF>4A%>U@)WqMj^)I!!J|B-m@3M3Fk(ruu#`21VdG z;a<~3A-P2T*1ymuiD}*8nbp9wmwHV#FOq59E@YRV9g8I=&zl7G~ z)_9UMAEka&JwRew8E;%m1$d z|BnOyZwUPVG4TIF;Q!+PhwuL-kwb9iGNyLbUw=sy8NSO>&?X=HjrnF08H7)htIkeP zqJ3wl{vpfq%p`J%BRkUpWyyGej3tppxZ&_0^%uOQ92mCXhAmkVImD4H(_fhHkK#(q zBr*s$Tef5fm|ZonzAP&+lgJ^itU{?WmP8iehQrqA&p#_i%4bRB5J%Eee|}(q#7rWC zIA*v#*!Qab*96LtL&}onu_UqxH@x;)eeq0xqzL&}t~B(exM>^w++?0Pv8)CoDnk)-L5dHbUxCm2u2AdXqPIM1n1 zKqNDX9OB5X-i5NG-d>G}mdldJBHS?Ncm07Y{E_mUI+;nqA&+KU3XvxF^d7ws$(6Ar zafp#KK)=7l7<_i`kV(Vj@`(da=gE0Sd}o{&X&PPO*wx1aP&aI(2bE-K;pYWtCft&<~ zJX~otMr!FTEQBFk9dU?paaA|{bTcsUo0hM!FK$-2`!qsb$28A~FI@SH9h zu8+MaAQDR=hd7c7`k1$635-f&5Tn{geT>(S5JtIX5*ehdd_X2RWWgx-ZDyaWY2MRW zM9VRgf}^B2H~mC>9k6T_J&`{ z%aI^;!69KXmLv|*&o|Zkd+ld3g!NF8I7C19ncnZek{K)rlY&F$bR3R3-MlVPCO9Na z#*)M#`Zv?{VsE<{K2bzXwIp%~FT#WSVLbJYHS~U<>d9CVS%jDK>yPzf|0>3f1a%S| z5)ui<6mf`t>`T39vA8;62Dc`0h<-xqUa&Mh@Y1nsMkeC3?f_wU-3Qn2jvL>$Io2p5yNbi!&v* z;E<3=z_r97`tFvx?{#HRC#PEmTuU6H?>eeC_x6xIiD?TCneo*!OuLr9gXcy9t`!^- zCSx{nh`w>Oeraug0o=ZuX$uayX9ujGZfG{Ge}IhH#3A~If9iGrCQE>81&7?T{Yi{8 z)teCE3LQhJ6LE;X{$F|>KOxhB5oQyI=H z;E*sGvx!6WHFfk%Z}x?>X@0VaL-Z%#(KEc+*OQpG;E;)LY*(Va%$!~R)J5M-aLB|r z8gQ8zAY(Rhh`xG{?t5LCFa-W54$)WN8!Rzx!66ejA4Rif{%*c|rUb^*Xu4a%WXv8# zcgvg{?U!-BwCP1C%)W&rvkz*&_^FR1rah9(#vWawM6bX7#|3gE7*98sN5<@%$Z*y% z?dL){64YsgkSx?rmuUDq=UiYs-6+hC{E9jKa7>DX^V4uo249r{S&6ndkA{8RVPrTy zQ#<&cKa%LX4W&q*m2qy z{>e}rJFy1}NlopGZju=mV1O{ogwSdIeY>3U&S`UZDv!T$$g|NoH^zyB}&`+xrZ|GlvPkLUl}x$pm{{r^kc z{eR^DeAxg05%&LA!~Q?<|6A_+|8t|6{Qdtc)9&Q&|407c1pEKU|BXuWKb-&nLJ#AA z-2Wc~`~P*y-v4JQ;*iKH#aQRvwD4_+Rve%5&6MDfL!FD2=->-Ww~IpiWp%00A+lVS zA`XenJ!(ATJu4tmq04J2!6Ba|7l7gD-QEU_Cu1q%kjUJ2#?#(vQw1eYg~*(Z&sZVHf?DP$17(D}@U?TNvocX_)#BBwkvB{<|W zy9dhNe_4(Obs`RlOf57Pdg~#G)Y7RFOA&`erYto6@wq>WD=|~ZAv~uKzl1|FgWhf7 ztztwZsFPrkFd0iBi|`^$e!_U|Z&KTgQQ8;mdqX7Do$RRwZW;Koeei|i-nM4NRIcq)Zu0B8zat>XVJ460?dE35p;%WXlt9jA=lJ^!9RIP)_2I$R+O^MYUuJ)CoDn z$?1uXrA2}|A&c;wlv+lPzatDs7u@lL9D)+#!rMmkpZsQ`p@s~?%{DfN-g5sJQio+3 zj3?xfGG#1@EW!={*aiJZIT92AImD6NV>ByqLpeD?DUm@Ovu5!6mi{j!d&`laPRJo; z%2*Ovgd3h{Xw>y+6vlXtQztViIAmRw(_q-=jCY#Ald&Xmi1FXdj)JCUFWfCoz-AAiMynAE4RL?`-qG z`^?-(P$%RNNA`U$lr=jTAY(~n5pMXwTqAv`EP;MEa)=`-G}0>vNX#TMh+}qQKbke| zDa)Wv$RTCQSQ1%;8*XZ*|KeZR!s!OQ?+VSN;E=>y@H3BoE9d%$ohyUso;bvKy+HqY zr9USp5=e+cjMt9nKlcidm`TAQi5>7VkA5rX_-9|&3@YA}t}aZ*l0E3^Ub|X9Jw`T! zNZm!G?eJ@mek*4uWf_dj>nPIGU+G8uvkIJUaObCPLNZ@JQX*|yvM0Ne+4Bp>f?4e1 zDSk6Y203*pkBlX+CBvr{>4&}M6%1e}5{#$LLefD$JVG{ut%FX&>=9UxLrz{3L_~t| z)bUTUYF@*^ld)tm87?T)_ju>sKtk*BWD!Z`FVy$6_2)$E@nnq5o*vc@_6U#tQeBoo zojR09#*){N;k+mG-F@Xq@aJtWB%So#(Ey2=Y$wcy!rHg*vD1E(aIX&PbhVHT`J_}C zOSUD$JKO4;^ZbUi9#6I*$+X4#rY8anRzl7v;MgSwhBA zv>(E*$gjQ9T@U#sEzJ~h$iNvrX7ETCIg@S)8A}m|uqN|ruhb<;9yE(+KV;yH?u#%| zp8F8@Mas)#DcTQVjgQt|;jV}Ll3X)I95Qgmb*C^=?lj3Tw>3)eps_L-zaZ(MN7Yhj8=&?g}d36-%FaY)**yCQwB zmJOkwgdD^eNxSx)NbCby0tFxrDT;mz&~sz6Tf9>skdqS$WW*sP8~JoVG?pR`N$b2P5^E_- zTCfyxNLuH6gC%B)IHV|gybyD``35->)QLDGRK`-oA!#|ONHcGT9InpgT9!f%;eWfF zp}`U}g$%;~cG@WT$;t4QPn_`QL|mIf4k=T{Qph6Qu=&VH(}(3qP$%RNM^YuyG!`H+ zQ^+8W*)4FmZurV2-UhL_I=F+0LyEN0WpD=*hosedB~r)RA#a5zE;xsnDdZ3|jGg~% z6d0zRc~j)&$yf?mgr{CBD^lCrA@?L^N^nR>B-j@q4oRzdaio?X4&9ufPQ)Q;HMT}- zc?A$=EzFeQkn^8H-wo3Cule0bP$$75=Ra+Tvfn1iR|j<>4oTB1MAF@972aVBhSYaU z5r?E{qax9_{ZU+rnGziGYx}-Rbl9YA&&e{l?*xa0$ykawBuyP3N&8BU1oIPdNSazJ zSYoCGhy2>^V$5l)pJ?2xgZoZ!NSKVJh(jXZ4>W$R=+8;aPsAaS?<2sFX}b3k1ZWpJk)WK7%Ohi{Mr63D zm+}5*vLTG8%SrP3@4*r?)lirnew^pjK#l};GKK8WFDQ%NBg%yJu+O?CwP& literal 0 HcmV?d00001 diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..4d25759 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,41 @@ + + + + + + {% block title %}SNMP Monitor{% endblock %} + + {% block extra_css %}{% endblock %} + + +

+ {% block content %}{% endblock %} +
+ {% block extra_js %}{% endblock %} + + diff --git a/templates/graph.html b/templates/graph.html new file mode 100644 index 0000000..ffdde15 --- /dev/null +++ b/templates/graph.html @@ -0,0 +1,79 @@ +{% extends 'base.html' %} + +{% block title %}Graph - {{ target.name }}{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +

📊 {{ target.name }} - Data Usage

+ +
+
+ 6h + 24h + 7d + ← Back +
+ + +
+ +
+

Device Info

+

Name: {{ target.name }}

+

Modem Type: {{ target.modem_type or 'rv50' }}

+

IP: {{ target.ip_address }}:{{ target.port }}

+

Community: {{ target.community }}

+

Poll Period: {{ target.period_minutes }} minutes

+
+{% endblock %} + +{% block extra_js %} + +{% endblock %} diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..389e73f --- /dev/null +++ b/templates/index.html @@ -0,0 +1,147 @@ +{% extends 'base.html' %} + +{% block title %}SNMP Monitor - Dashboard{% endblock %} + +{% block content %} +

📡 Cellular Data Usage Monitor

+ + +
+

Add New Target

+
+
+ + + + + + + +
+
+
+ + +
+

Monitored Devices

+ {% if targets %} + + + + + + + + + + + + + + + + + + {% for target in targets %} + + + + + + + + + + + + + + {% endfor %} + +
NameModemIP AddressBytes SentBytes ReceivedRate SentRate RecvPeriodLast PollStatusActions
{{ target.name }}{{ target.modem_type or 'rv50' }}{{ target.ip_address }}:{{ target.port }}{{ target.bytes_sent_fmt }}{{ target.bytes_recv_fmt }}{{ target.rate_sent_fmt }}{{ target.rate_recv_fmt }}{{ target.period_minutes }} min{{ target.last_poll or 'Never' }} + {{ 'Active' if target.enabled else 'Paused' }} + + 📊 Graph + + +
+ +
+
+ {% else %} +

No targets configured. Add one above!

+ {% endif %} +
+ + + +{% endblock %} + +{% block extra_js %} + +{% endblock %}