david
3 years ago
commit
693934ed65
17 changed files with 12399 additions and 0 deletions
@ -0,0 +1,5 @@ |
|||||||
|
node_modules/ |
||||||
|
rubbish/ |
||||||
|
db.sqlite3 |
||||||
|
data.csv |
||||||
|
public/styles/style.css |
@ -0,0 +1,5 @@ |
|||||||
|
node_modules/ |
||||||
|
rubbish/ |
||||||
|
db.sqlite3 |
||||||
|
data.csv |
||||||
|
public/styles/style.css |
@ -0,0 +1,10 @@ |
|||||||
|
FROM nikolaik/python-nodejs:python3.9-nodejs16-alpine |
||||||
|
ENV PYTHONUNBUFFERED=1 NODE_ENV=production |
||||||
|
WORKDIR /app |
||||||
|
COPY ./netmon.py ./ |
||||||
|
CMD [ "python", "./netmon.py" , "monitor" ] |
||||||
|
|
||||||
|
# copy index/views/public/pkg.json/postcss/tailwind configs |
||||||
|
#npm i |
||||||
|
#build css |
||||||
|
#run supervisord or wrapper |
@ -0,0 +1,23 @@ |
|||||||
|
# netmon |
||||||
|
|
||||||
|
bunch of misc cmds |
||||||
|
|
||||||
|
``` |
||||||
|
docker run --rm -it --name pls -w /app -p 3000:3000 -p 5000:5000 -e dstHost=localhost -e dstPort=8000 -e TZ=Australia/Sydney -v /home/blender/projects/netmon:/app nikolaik/python-nodejs:python3.9-nodejs16-alpine sh |
||||||
|
docker exec -it pls sh |
||||||
|
apk add sqlite |
||||||
|
npm install -g nodemon |
||||||
|
npm install express sqlite |
||||||
|
python -m http.server 8000 |
||||||
|
export dstHost=localhost dstPort=8000 |
||||||
|
python netmon.py monitor |
||||||
|
|
||||||
|
sqlite3 db.sqlite3 |
||||||
|
.headers on |
||||||
|
.mode column |
||||||
|
`SELECT * FROM netmonResults;` |
||||||
|
|
||||||
|
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest postcss-cli |
||||||
|
|
||||||
|
npm install tailwindcss postcss autoprefixer postcss-cli |
||||||
|
``` |
@ -0,0 +1,17 @@ |
|||||||
|
var sqlite3 = require('sqlite3').verbose(); |
||||||
|
|
||||||
|
const db = new sqlite3.Database('db.sqlite3', (err) => { |
||||||
|
if (err) { |
||||||
|
console.error(err.message); |
||||||
|
throw err; |
||||||
|
} else { |
||||||
|
console.log('Connected to the SQLite database.'); |
||||||
|
db.run( |
||||||
|
`CREATE TABLE IF NOT EXISTS netmonResults (id integer PRIMARY KEY,
|
||||||
|
timestamp text,
|
||||||
|
status integer)` |
||||||
|
); |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
module.exports = db; |
@ -0,0 +1,36 @@ |
|||||||
|
const express = require('express'); |
||||||
|
const app = express(); |
||||||
|
const port = 3000; |
||||||
|
const path = require('path'); |
||||||
|
|
||||||
|
require('dotenv').config(); |
||||||
|
|
||||||
|
app.set('views', __dirname + '/views'); |
||||||
|
app.set('view engine', 'jsx'); |
||||||
|
app.engine('jsx', require('express-react-views').createEngine()); |
||||||
|
app.use(express.static(path.join(__dirname, '/public'))); |
||||||
|
|
||||||
|
const db = require('./database.js'); |
||||||
|
|
||||||
|
async function db_all(query) { |
||||||
|
return new Promise(function (resolve, reject) { |
||||||
|
db.all(query, function (err, rows) { |
||||||
|
if (err) { |
||||||
|
return reject(err); |
||||||
|
} |
||||||
|
resolve(rows); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
app.get('/', async (req, res) => { |
||||||
|
const sql = `SELECT * FROM netmonResults`; |
||||||
|
const dbdata = await db_all(sql); |
||||||
|
res.render('index', { dbdata }); |
||||||
|
}); |
||||||
|
|
||||||
|
app.listen(port, () => { |
||||||
|
console.log( |
||||||
|
`app listening at http://localhost:${port} in ${app.settings.env} mode. netmon is monitoring ${process.env.dstHost}` |
||||||
|
); |
||||||
|
}); |
@ -0,0 +1,181 @@ |
|||||||
|
#!/usr/bin/python3 |
||||||
|
# -*- coding: utf-8 -*- |
||||||
|
|
||||||
|
import os |
||||||
|
import csv |
||||||
|
import sys |
||||||
|
import time |
||||||
|
import socket |
||||||
|
import datetime |
||||||
|
import sqlite3 |
||||||
|
from pathlib import Path |
||||||
|
|
||||||
|
path = Path(__file__).resolve().parent |
||||||
|
|
||||||
|
def update_db(payload): |
||||||
|
conn = None |
||||||
|
try: |
||||||
|
db = (str(path) + "/db.sqlite3") |
||||||
|
conn = sqlite3.connect(db) |
||||||
|
|
||||||
|
c = conn.cursor() |
||||||
|
c.execute( |
||||||
|
''' |
||||||
|
CREATE TABLE IF NOT EXISTS netmonResults ( |
||||||
|
id integer PRIMARY KEY, |
||||||
|
timestamp text, |
||||||
|
status integer |
||||||
|
) |
||||||
|
''' |
||||||
|
) |
||||||
|
c.execute( |
||||||
|
''' |
||||||
|
INSERT OR REPLACE into netmonResults ( |
||||||
|
timestamp, status |
||||||
|
) |
||||||
|
VALUES (?, ?) |
||||||
|
''', |
||||||
|
(payload) |
||||||
|
) |
||||||
|
conn.commit() |
||||||
|
print (f"db update successful") |
||||||
|
except Exception as ex: |
||||||
|
print(ex) |
||||||
|
print (f"db operation failed") |
||||||
|
finally: |
||||||
|
if conn: |
||||||
|
conn.close() |
||||||
|
|
||||||
|
def internet(dstHost, dstPort, timeout=3): |
||||||
|
""" Generates a new request""" |
||||||
|
try: |
||||||
|
socket.setdefaulttimeout(timeout) |
||||||
|
socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((dstHost, int(dstPort))) |
||||||
|
return True |
||||||
|
except Exception as ex: |
||||||
|
# print(ex) |
||||||
|
return False |
||||||
|
|
||||||
|
|
||||||
|
def current_timestamp(): |
||||||
|
""" Get Current timestamp string """ |
||||||
|
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") |
||||||
|
|
||||||
|
|
||||||
|
def str_to_date(timestamp): |
||||||
|
""" Convert timestamp string to date object """ |
||||||
|
return datetime.datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") |
||||||
|
|
||||||
|
|
||||||
|
def secs_to_HMS(secs): |
||||||
|
""" Convert seconds to second and minutes |
||||||
|
Format : HH:MM:SS |
||||||
|
""" |
||||||
|
return str(datetime.timedelta(seconds=secs)); |
||||||
|
|
||||||
|
|
||||||
|
def record_file_exist(): |
||||||
|
""" Check if records file exist """ |
||||||
|
return os.path.isfile('data.csv') |
||||||
|
|
||||||
|
|
||||||
|
def create_record_file(): |
||||||
|
""" Create a new record file """ |
||||||
|
with open('data.csv', 'a') as csvfile: |
||||||
|
columns = ['timestamp', 'status'] |
||||||
|
writer = csv.DictWriter(csvfile, fieldnames=columns) |
||||||
|
writer.writeheader() |
||||||
|
|
||||||
|
|
||||||
|
def last_record_status(): |
||||||
|
""" Get last record """ |
||||||
|
result = None |
||||||
|
with open('data.csv', 'r') as csvfile: |
||||||
|
reader = csv.DictReader(csvfile) |
||||||
|
for row in reader: |
||||||
|
result = row |
||||||
|
return None if result is None else result['status'] |
||||||
|
|
||||||
|
|
||||||
|
def write_record(status): |
||||||
|
""" Create a new record """ |
||||||
|
with open('data.csv', 'a') as csvfile: |
||||||
|
columns = ['timestamp', 'status'] |
||||||
|
writer = csv.DictWriter(csvfile, fieldnames=columns) |
||||||
|
writer.writerow({'timestamp': str(current_timestamp()), 'status': status}) |
||||||
|
|
||||||
|
|
||||||
|
def get_total_downtime(): |
||||||
|
""" Calculate downtime """ |
||||||
|
seconds = 0 |
||||||
|
down = None |
||||||
|
up = None |
||||||
|
with open('data.csv', 'r') as csvfile: |
||||||
|
reader = csv.DictReader(csvfile) |
||||||
|
for record in reader: |
||||||
|
try: |
||||||
|
if record['status'] == '0': |
||||||
|
print('Went Down at : ', record['timestamp']) |
||||||
|
down = str_to_date(record['timestamp']) |
||||||
|
next_record = next(reader) |
||||||
|
up = str_to_date(next_record['timestamp']) |
||||||
|
seconds += (up - down).total_seconds() |
||||||
|
print('Went up at : ', next_record['timestamp']) |
||||||
|
except Exception as ex: |
||||||
|
print('\nCurrent Status : Still Down') |
||||||
|
seconds += (str_to_date(current_timestamp()) - down).total_seconds() |
||||||
|
return secs_to_HMS(seconds); |
||||||
|
|
||||||
|
|
||||||
|
def monitor_connection(sleep_time): |
||||||
|
try: |
||||||
|
dstHost = os.getenv('dstHost') |
||||||
|
dstPort = os.getenv('dstPort') |
||||||
|
if dstHost is None or dstPort is None: |
||||||
|
raise Exception('vars are fucked') |
||||||
|
except Exception as error: |
||||||
|
print('Caught this error: ' + repr(error)) |
||||||
|
print (""" |
||||||
|
pls set vars |
||||||
|
|
||||||
|
eg; |
||||||
|
|
||||||
|
for host: |
||||||
|
export dstHost=localhost dstPort=8000 |
||||||
|
|
||||||
|
for docker: |
||||||
|
-e dstHost=localhost -e dstPort=8000 |
||||||
|
|
||||||
|
""") |
||||||
|
sys.exit(1) |
||||||
|
|
||||||
|
""" Start monitoring """ |
||||||
|
print('Monitoring your connection for ' + dstHost) |
||||||
|
while True: |
||||||
|
last_record = last_record_status() |
||||||
|
if not internet(dstHost, dstPort): |
||||||
|
if last_record == '1' or last_record is None: |
||||||
|
print('Internet went down') |
||||||
|
write_record(0) |
||||||
|
update_db((str(current_timestamp()), '0')) |
||||||
|
else: |
||||||
|
if last_record == '0' or last_record is None: |
||||||
|
print('Internet is up') |
||||||
|
write_record(1) |
||||||
|
update_db((str(current_timestamp()), '1')) |
||||||
|
time.sleep(sleep_time) |
||||||
|
|
||||||
|
|
||||||
|
def args_error(): |
||||||
|
print('Please provide an argument\nOptions\n./internet.py monitor\n./internet.py downtime') |
||||||
|
|
||||||
|
|
||||||
|
args = sys.argv |
||||||
|
if not len(args) > 1: |
||||||
|
args_error() |
||||||
|
elif args[1] == 'monitor': |
||||||
|
if not record_file_exist(): |
||||||
|
create_record_file() |
||||||
|
monitor_connection(1) |
||||||
|
elif args[1] == 'downtime': |
||||||
|
print('\nRemained down for : ', get_total_downtime(), ' HH:MM:SS ') |
@ -0,0 +1,26 @@ |
|||||||
|
{ |
||||||
|
"dependencies": { |
||||||
|
"dayjs": "^1.10.6", |
||||||
|
"dotenv": "^10.0.0", |
||||||
|
"express": "^4.17.1", |
||||||
|
"express-react-views": "^0.11.0", |
||||||
|
"react": "^16.14.0", |
||||||
|
"react-dom": "^16.14.0", |
||||||
|
"sqlite3": "^5.0.2" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"autoprefixer": "^10.3.1", |
||||||
|
"nodemon": "^2.0.12", |
||||||
|
"postcss": "^8.3.6", |
||||||
|
"postcss-cli": "^8.3.1", |
||||||
|
"tailwindcss": "^2.2.7" |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"start": "NODE_ENV=development node index.js", |
||||||
|
"build:css": "postcss public/styles/tailwind.css -o public/styles/style.css", |
||||||
|
"dev": "npm run build:css && nodemon index.js", |
||||||
|
"testHost": "python -m http.server 8000", |
||||||
|
"netmon": "python netmon.py monitor", |
||||||
|
"prod": "NODE_ENV=production node index.js " |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,6 @@ |
|||||||
|
module.exports = { |
||||||
|
plugins: { |
||||||
|
tailwindcss: {}, |
||||||
|
autoprefixer: {} |
||||||
|
}, |
||||||
|
}; |
@ -0,0 +1,3 @@ |
|||||||
|
@tailwind base; |
||||||
|
@tailwind components; |
||||||
|
@tailwind utilities; |
@ -0,0 +1,36 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# Start the first process |
||||||
|
./my_first_process -D |
||||||
|
status=$? |
||||||
|
if [ $status -ne 0 ]; then |
||||||
|
echo "Failed to start my_first_process: $status" |
||||||
|
exit $status |
||||||
|
fi |
||||||
|
|
||||||
|
# Start the second process |
||||||
|
./my_second_process -D |
||||||
|
status=$? |
||||||
|
if [ $status -ne 0 ]; then |
||||||
|
echo "Failed to start my_second_process: $status" |
||||||
|
exit $status |
||||||
|
fi |
||||||
|
|
||||||
|
# Naive check runs checks once a minute to see if either of the processes exited. |
||||||
|
# This illustrates part of the heavy lifting you need to do if you want to run |
||||||
|
# more than one service in a container. The container exits with an error |
||||||
|
# if it detects that either of the processes has exited. |
||||||
|
# Otherwise it loops forever, waking up every 60 seconds |
||||||
|
|
||||||
|
while sleep 60; do |
||||||
|
ps aux |grep my_first_process |grep -q -v grep |
||||||
|
PROCESS_1_STATUS=$? |
||||||
|
ps aux |grep my_second_process |grep -q -v grep |
||||||
|
PROCESS_2_STATUS=$? |
||||||
|
# If the greps above find anything, they exit with 0 status |
||||||
|
# If they are not both 0, then something is wrong |
||||||
|
if [ $PROCESS_1_STATUS -ne 0 -o $PROCESS_2_STATUS -ne 0 ]; then |
||||||
|
echo "One of the processes has already exited." |
||||||
|
exit 1 |
||||||
|
fi |
||||||
|
done |
@ -0,0 +1,12 @@ |
|||||||
|
module.exports = { |
||||||
|
purge: [], |
||||||
|
darkMode: false, // or 'media' or 'class'
|
||||||
|
purge: ['./public/**/*.html', './views/*.{js,jsx,ts,tsx,vue}', '.index.js'], |
||||||
|
theme: { |
||||||
|
extend: {}, |
||||||
|
}, |
||||||
|
variants: { |
||||||
|
extend: {}, |
||||||
|
}, |
||||||
|
plugins: [require('tailwindcss'), require('autoprefixer')], |
||||||
|
}; |
@ -0,0 +1,27 @@ |
|||||||
|
const React = require('react'); |
||||||
|
import { TimeBetweenRows } from './DiffCard'; |
||||||
|
|
||||||
|
export const Card = ({ dbdata }) => { |
||||||
|
return dbdata.reverse().map((row, index) => { |
||||||
|
const nextRow = dbdata[index + 1]; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="grid grid-cols-1 px-2"> |
||||||
|
<div |
||||||
|
className={`shadow-md p-1 flex items-center justify-between ${ |
||||||
|
row.status === 1 ? 'bg-green-50' : 'bg-red-50' |
||||||
|
} `} |
||||||
|
> |
||||||
|
<div className="flex-grow pl-2">{row.timestamp}</div> |
||||||
|
<div className="p-1 pr-2">{row.status === 1 ? '🟢' : '🔴'}</div> |
||||||
|
</div> |
||||||
|
|
||||||
|
{!!nextRow ? ( |
||||||
|
<div className="flex justify-center items-center py-1 text-gray-400"> |
||||||
|
<TimeBetweenRows row={row} nextRow={nextRow} /> |
||||||
|
</div> |
||||||
|
) : null} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}); |
||||||
|
}; |
@ -0,0 +1,88 @@ |
|||||||
|
const React = require('react'); |
||||||
|
const dayjs = require('dayjs'); |
||||||
|
|
||||||
|
export const TimeBetweenRows = ({ row, nextRow }) => { |
||||||
|
if (nextRow) { |
||||||
|
const firstDate = dayjs(row.timestamp); |
||||||
|
const secondDate = dayjs(nextRow.timestamp); |
||||||
|
const theDiff = firstDate.diff(secondDate); |
||||||
|
const theSeconds = theDiff / 1000; |
||||||
|
const theMinutes = theSeconds / 60; |
||||||
|
const theHours = theMinutes / 60; |
||||||
|
const theDays = theHours / 24; |
||||||
|
|
||||||
|
// console.log(theDiff, theSeconds, theMinutes, theHours, theDays); |
||||||
|
|
||||||
|
const renderSecs = <>{theSeconds === 1 ? 'sec' : 'secs'}</>; |
||||||
|
const renderMins = <>{theMinutes === 1 ? 'min ' : 'mins '}</>; |
||||||
|
const renderHrs = <>{theHours === 1 ? 'hr ' : 'hrs '}</>; |
||||||
|
const renderDays = <>{theDays === 1 ? 'day ' : 'days '}</>; |
||||||
|
|
||||||
|
return ( |
||||||
|
<> |
||||||
|
{/* secs */} |
||||||
|
{theSeconds < 60 ? ( |
||||||
|
<> |
||||||
|
{Math.round(theSeconds)} |
||||||
|
{renderSecs} |
||||||
|
</> |
||||||
|
) : null} |
||||||
|
|
||||||
|
{/* mins secs */} |
||||||
|
{theMinutes > 1 && theMinutes < 60 ? ( |
||||||
|
<> |
||||||
|
<> |
||||||
|
{Math.round(theMinutes)} |
||||||
|
{renderMins} |
||||||
|
</> |
||||||
|
<> |
||||||
|
{Math.round((theMinutes % 1) * 60)} |
||||||
|
{renderSecs} |
||||||
|
</> |
||||||
|
</> |
||||||
|
) : null} |
||||||
|
|
||||||
|
{/* hrs mins secs */} |
||||||
|
{theHours > 1 && theHours < 24 ? ( |
||||||
|
<> |
||||||
|
<> |
||||||
|
{Math.round(theHours)} |
||||||
|
{renderHrs} |
||||||
|
</> |
||||||
|
<> |
||||||
|
{Math.round((theHours % 1) * 60)} |
||||||
|
{renderMins} |
||||||
|
</> |
||||||
|
<> |
||||||
|
{Math.round((theMinutes % 1) * 60)} |
||||||
|
{renderSecs} |
||||||
|
</> |
||||||
|
</> |
||||||
|
) : null} |
||||||
|
|
||||||
|
{/* days hrs mins secs */} |
||||||
|
{theDays > 1 ? ( |
||||||
|
<> |
||||||
|
<> |
||||||
|
{Math.round(theDays)} |
||||||
|
{renderDays} |
||||||
|
</> |
||||||
|
<> |
||||||
|
{Math.round((theDays % 1) * 24)} |
||||||
|
{renderHrs} |
||||||
|
</> |
||||||
|
<> |
||||||
|
{Math.round((theHours % 1) * 60)} |
||||||
|
{renderMins} |
||||||
|
</> |
||||||
|
<> |
||||||
|
{Math.round((theMinutes % 1) * 60)} |
||||||
|
{renderSecs} |
||||||
|
</> |
||||||
|
</> |
||||||
|
) : null} |
||||||
|
</> |
||||||
|
); |
||||||
|
} |
||||||
|
return false; |
||||||
|
}; |
@ -0,0 +1,16 @@ |
|||||||
|
const React = require('react'); |
||||||
|
|
||||||
|
export const Title = ({ dbdata }) => { |
||||||
|
const statusCheck = dbdata?.slice(-1)[0].status === 1; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="text-xl p-2 pb-4"> |
||||||
|
<div> |
||||||
|
Everything looks {statusCheck ? 'AMAZING 🔥🔥🔥' : 'HORRIFIC 💀💀💀'} |
||||||
|
</div> |
||||||
|
<div className="text-xs text-gray-400 pt-1"> |
||||||
|
Monitoring {process.env.dstHost} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,33 @@ |
|||||||
|
const React = require('react'); |
||||||
|
|
||||||
|
import { Title } from './Title'; |
||||||
|
import { Card } from './Card'; |
||||||
|
|
||||||
|
function App({ dbdata }) { |
||||||
|
return ( |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charSet="UTF-8" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<link rel="stylesheet" href="/styles/style.css" /> |
||||||
|
</head> |
||||||
|
<title>netmon</title> |
||||||
|
<body className="bg-indigo-50 max-w-sm antialiased tracking-wide leading-relaxed container mx-auto"> |
||||||
|
<div className="shadow-2xl my-8 rounded p-2"> |
||||||
|
{dbdata.length ? ( |
||||||
|
<div> |
||||||
|
<Title dbdata={dbdata} /> |
||||||
|
<Card dbdata={dbdata} /> |
||||||
|
</div> |
||||||
|
) : ( |
||||||
|
<div className="flex justify-center items-center "> |
||||||
|
no information from the db was loaded, sad |
||||||
|
</div> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
</body> |
||||||
|
</html> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
module.exports = App; |
Loading…
Reference in new issue