This commit is contained in:
william 2025-03-18 21:43:21 +01:00
commit 81befa917b
13 changed files with 2026 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
pinglerr_web/config.php
pinglerr_daemon/config.ini

34
dump.sql Normal file
View File

@ -0,0 +1,34 @@
CREATE TABLE `pinglerr_pings` (
`id` int(11) NOT NULL,
`ping_avg` float DEFAULT NULL,
`pkt_transmitted` int(11) DEFAULT NULL,
`pkt_received` int(11) DEFAULT NULL,
`scan_id` int(11) NOT NULL,
`date_added` timestamp NOT NULL DEFAULT current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
CREATE TABLE `pinglerr_scans` (
`id` int(11) NOT NULL,
`start_date` timestamp NOT NULL DEFAULT current_timestamp(),
`end_date` timestamp NOT NULL,
`status_code` varchar(255) NOT NULL,
`host` varchar(255) NOT NULL,
`error_message` mediumtext DEFAULT NULL,
`ref_uuid` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
ALTER TABLE `pinglerr_pings`
ADD PRIMARY KEY (`id`),
ADD KEY `scan_id` (`scan_id`);
ALTER TABLE `pinglerr_scans`
ADD PRIMARY KEY (`id`),
ADD KEY `ref_uuid` (`ref_uuid`);
ALTER TABLE `pinglerr_pings`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
ALTER TABLE `pinglerr_scans`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT;
COMMIT;

156
pinglerr_daemon/Pinglerr.py Normal file
View File

@ -0,0 +1,156 @@
import configparser
config = configparser.ConfigParser()
config.read('config.ini')
config = {
'endpoint' : config.get('General', 'endpoint')
}
import logging
import subprocess
import threading
import time
import requests
# index being the scan_id
ping_is_in_progress = {
0: False
}
# use persistent connection, this is MUCH faster
session = requests.Session()
# holds the result of all pings that are waiting to be sent to the server
completed_pings = []
# used to prevent race conditions
lock = threading.Lock()
def main():
global completed_pings
global session
global lock
logging.basicConfig(level=logging.INFO, format='[%(asctime)s] %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
logging.info("Started")
last_loop_time = 0
time_since_last_status_update = 0.0
scans_in_progress = []
while True:
last_loop_time = time.time()
try:
response = session.get(config['endpoint'] + '?p=daemon_poll')
if scans_in_progress != response.json():
prev_scan_ids = []
for scan in scans_in_progress:
prev_scan_ids.append(scan['id'])
scans_in_progress = response.json()
for scan in scans_in_progress:
if scan['id'] not in prev_scan_ids:
logging.info(f"Found job for host: {scan['host']}, ref_uuid: {scan['ref_uuid']}")
except:
logging.error("Failed to retrieve jobs!")
time.sleep(5)
continue
for scan in scans_in_progress:
ping_thread = threading.Thread(
args=[scan],
target=ping
)
ping_thread.start()
if completed_pings:
lock.acquire()
session.post(config['endpoint'] + '?p=daemon_bulk_update', json=completed_pings)
completed_pings = []
lock.release()
if time.time() > time_since_last_status_update + 10:
time_since_last_status_update = time.time()
logging.info(f"Summary: [Active scans: {len(scans_in_progress)}] [Active threads: {threading.active_count()}]")
# make sure it stays around 5 seconds each loop
time_to_sleep = last_loop_time - time.time() + 5
if time_to_sleep < 0:
continue
time.sleep(time_to_sleep)
def report_ping_error(scan_id, error_message, pkt_transmitted = '0', pkt_received = '0'):
global completed_pings
global lock
logging.debug('Ping error; scan_id: ' + str(scan_id) + ' Error message: ' + error_message)
lock.acquire()
completed_pings.append({
'scan_id' : str(scan_id),
'pkt_transmitted' : pkt_transmitted,
'pkt_received' : pkt_received,
})
lock.release()
def ping(scan):
global ping_is_in_progress
global completed_pings
global lock
if scan['id'] in ping_is_in_progress and ping_is_in_progress[scan['id']] == True:
return logging.debug('Ping command is already in progress, exiting thread for host: ' + scan['host'])
lock.acquire()
ping_is_in_progress[scan['id']] = True
lock.release()
pkts_to_send = '10'
ping_cmd_output = subprocess.run(
['ping', '-c', pkts_to_send, '-i', '0.5', scan['host'], '-w', '5'],
capture_output=True
)
lock.acquire()
ping_is_in_progress[scan['id']] = False
lock.release()
if ping_cmd_output.returncode == 2:
return report_ping_error(scan['id'],
'Ping command exited with status code 2; most likely failed to resolve host',
pkts_to_send
)
cmd_lines = (
ping_cmd_output.stdout.decode()
).split('\n')
ping_avg = '0'
pkt_transmitted = '0'
pkt_received = '0'
try:
ping_stats = cmd_lines[-3].split()
ping_avgs = cmd_lines[-2].split()[3].split('/')
ping_avg = ping_avgs[1]
pkt_transmitted = ping_stats[0]
pkt_received = ping_stats[3]
except:
return report_ping_error(scan['id'],
'Failed to parse ping output: \n' + ping_cmd_output.stdout.decode(),
pkts_to_send
)
lock.acquire()
completed_pings.append({
'scan_id' : str(scan['id']),
'pkt_transmitted' : pkt_transmitted,
'pkt_received' : pkt_received,
'ping_avg' : ping_avg,
})
lock.release()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,2 @@
[General]
endpoint = https://whatever.com/

View File

@ -0,0 +1,10 @@
<?php
/**
* Copy this file and rename it to config.php
*/
return [
'db_host' => 'hostname.invalid',
'db_user' => 'admin',
'db_pass' => 'pass',
'db_database' => 'poop',
];

View File

@ -0,0 +1,147 @@
<?php
/**
* Since we are gathering ping statistics at a high resolution interval this builds up really
* quickly for longer running ping jobs causing slowdowns and using lots of storage.
*
* This script is meant to be ran periodically; it merges the high resolution dataset into a lower
* resolution dataset without losing too much important information.
*/
$config = require 'config.php';
function get_mysqli(): MySQLi
{
static $handle;
if (!isset($handle)) {
$handle = new MySQLi(
hostname: $config['db_host'],
username: $config['db_name'],
password: $config['db_pass'],
database: $config['db_database'],
);
// make sure that the timezone is set to UTC!!!
$handle->query("SET time_zone = '+00:00'");
}
return $handle;
}
$scans = (function(): array
{
$stmt = get_mysqli()->prepare(
"SELECT * FROM pinglerr_scans"
);
$stmt->execute();
$result = $stmt->get_result();
$assoc_array = [];
while ($row = $result->fetch_assoc()) {
$assoc_array[] = $row;
}
return $assoc_array;
})();
foreach ($scans as $scan) {
$pings = (function() use($scan): array
{
$stmt = get_mysqli()->prepare(
"SELECT ping_avg, pkt_transmitted, pkt_received, date_added FROM pinglerr_pings WHERE scan_id = ? ORDER BY date_added ASC"
);
$stmt->bind_param("i", $scan['id']);
$stmt->execute();
$result = $stmt->get_result();
$assoc_array = [];
while ($row = $result->fetch_assoc()) {
$assoc_array[] = $row;
}
return $assoc_array;
})();
if (empty($pings)) {
continue;
}
echo 'Processing scan_id: ' . $scan['id'] . ' rows: ' . count($pings) . "\n";
$limit_length = 1000;
if (count($pings) > $limit_length) {
$merged_pings = [];
$merge_gap = count($pings) / $limit_length;
$merge_iteration = 0;
$merge_remainder = 0;
$ping_avg_sum_storage = [];
$ping_pkt_transmitted_storage = [];
$ping_pkt_received_storage = [];
foreach ($pings as $ping) {
if ($merge_iteration >= $merge_gap - $merge_remainder) {
$merge_remainder+= $merge_iteration - $merge_gap;
$how_many_nulls = 0;
for ($i=0; $i < count($ping_avg_sum_storage); $i++) {
if ($ping_avg_sum_storage[$i] === null) {
$how_many_nulls++;
}
}
if ($how_many_nulls > count($ping_avg_sum_storage) / 2) {
$ping_avg = null;
} else {
for ($i=0; $i < count($ping_avg_sum_storage); $i++) {
if ($ping_avg_sum_storage[$i] === null) {
unset($ping_avg_sum_storage[$i]);
}
}
$ping_avg = round(array_sum($ping_avg_sum_storage) / count($ping_avg_sum_storage), 3);
}
$merged_pings[] = [
'ping_avg' => $ping_avg,
'date_added' => $ping['date_added'],
'pkt_transmitted' => round(array_sum($ping_pkt_transmitted_storage) / count($ping_pkt_transmitted_storage), 3),
'pkt_received' => round(array_sum($ping_pkt_received_storage) / count($ping_pkt_received_storage), 3),
];
$ping_pkt_transmitted_storage = [];
$ping_pkt_received_storage = [];
$ping_avg_sum_storage = [];
$merge_iteration = 0;
}
$ping_pkt_transmitted_storage[] = $ping['pkt_transmitted'];
$ping_pkt_received_storage[] = $ping['pkt_received'];
$ping_avg_sum_storage[] = $ping['ping_avg'];
$merge_iteration++;
}
$pings = $merged_pings;
} else {
echo "Skipped\n";
continue;
}
(function() use($scan) {
$stmt = get_mysqli()->prepare(
"DELETE FROM pinglerr_pings WHERE scan_id = ?"
);
$status_code = 'DONE';
$error_message = 'Stopped because end_date was reached';
$stmt->bind_param("s",
$scan['id']
);
$stmt->execute();
})();
foreach ($pings as $ping) {
$stmt = get_mysqli()->prepare(
"INSERT INTO pinglerr_pings (scan_id, ping_avg, pkt_transmitted, pkt_received, date_added) VALUES (?,?,?,?,?)"
);
$stmt->bind_param("idiis",
$scan['id'],
$ping['ping_avg'],
$ping['pkt_transmitted'],
$ping['pkt_received'],
$ping['date_added']
);
$stmt->execute();
}
echo 'Done: ' . count($pings) . "\n";
}
exit();

View File

@ -0,0 +1,955 @@
<?php
$config = require '../config.php';
function get_mysqli(): MySQLi
{
global $config;
static $handle;
if (!isset($handle)) {
$handle = new MySQLi(
hostname: $config['db_host'],
username: $config['db_user'],
password: $config['db_pass'],
database: $config['db_database'],
);
// make sure that the timezone is set to UTC!!!
$handle->query("SET time_zone = '+00:00'");
}
return $handle;
}
function flash_message(string $msg, string $type = 'info', bool $unsafe = false): void
{
if (session_status() === PHP_SESSION_NONE)
session_start();
$types = [
"info",
"success",
"danger",
"warning"
];
if (!in_array($type, $types)) {
throw new InvalidArgumentException("Flash type: \"$type\" does not exist");
}
$key = 'flash_messages';
if (!isset($_SESSION[$key]))
{
$_SESSION[$key] = [];
}
if (count($_SESSION[$key]) >= 100)
{
$_SESSION[$key] = [];
throw new Exception('Too many flashed messages!');
}
$_SESSION[$key][] = [
"message" => ($unsafe) ? $msg : htmlspecialchars($msg),
"type" => $type
];
}
function get_flash_messages(): ?array
{
if (session_status() === PHP_SESSION_NONE)
session_start();
$key = 'flash_messages';
if (empty($_SESSION[$key])) {
return null;
}
$msgs = $_SESSION[$key];
$_SESSION[$key] = [];
return $msgs;
}
function page_header(string $title, array $toolbar_links = [])
{
$ref_uuid = filter_input(INPUT_GET, 'ref_uuid');
$prefer_ref_uuid = !empty($ref_uuid);
if ($prefer_ref_uuid) $ref_uuid = htmlspecialchars($ref_uuid);
$host = strtolower(filter_input(INPUT_GET, 'host'));
if (!empty($host) && !is_valid_host($host)) {
$host = NULL;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?=$title?></title>
<link rel="stylesheet" href="static/css/style.css">
<link rel="icon" type="image/svg+xml" href="static/img/logo.svg">
</head>
<body>
<header>
<nav>
<a href="?p=default<?=($prefer_ref_uuid ? '&ref_uuid=' . $ref_uuid : '') . (!empty($host) ? '&host=' . $host : '')?>">Pinglerr</a>
</nav>
</header>
<?php if (!empty($toolbar_links)): ?>
<div class="toolbar">
<nav>
Handling:
<?php foreach ($toolbar_links as $link): ?>
<span>[&nbsp;<a
<?php if(isset($link['type']) && $link['type'] === 'danger'): ?>
class="link-btn-danger"
<?php else: ?>
class="link-btn-normal"
<?php endif; ?>
href="<?=$link['href']?>"><?=$link['name']?></a>&nbsp;]</span>
<?php endforeach; ?>
</nav>
</div>
<?php endif; ?>
<main>
<?php
$flash_messages = get_flash_messages();
?>
<?php if(!empty($flash_messages)): ?>
<?php foreach ($flash_messages as $message): ?>
<?php if($message['type'] === 'info'): ?>
<div class="alert alert-info"><?=$message['message']?></div>
<?php endif; ?>
<?php if($message['type'] === 'success'): ?>
<div class="alert alert-success"><?=$message['message']?></div>
<?php endif; ?>
<?php if($message['type'] === 'danger'): ?>
<div class="alert alert-danger"><?=$message['message']?></div>
<?php endif; ?>
<?php if($message['type'] === 'warning'): ?>
<div class="alert alert-warning"><?=$message['message']?></div>
<?php endif; ?>
<?php endforeach ?>
<hr>
<?php endif; ?>
<?php
}
function page_footer()
{
?>
</main>
<script src="static/js/apexcharts.min.js"></script>
<script src="static/js/global.js"></script>
<footer>
<small>Laget av William for Altidata AS</small>
</footer>
</body>
</html>
<?php
}
function get_referer(): string
{
if (isset($_SERVER['HTTP_REFERER'])) {
return htmlspecialchars($_SERVER['HTTP_REFERER']);
}
return '?p=default';
}
function is_valid_host(string $host): bool
{
if (empty($host)) {
return false;
}
if (!filter_var(
$host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)
) {
return false;
}
return true;
}
function suicide_user_bad_request(string $errormsg)
{
http_response_code(400);
page_header('Oops, dette var en feil');
?>
<h1>Kunne ikke fortsette</h1>
<p>Feilmelding: <?=htmlspecialchars($errormsg)?></p>
<a href="<?=get_referer()?>">Klikk her for å tilbake</a>
<?php
page_footer();
die();
}
function pinglerr_get_scan(?string $host = null, ?string $ref_uuid = null, ?string $scan_id = null, bool $must_be_active_or_waiting = true): ?array
{
if ($host !== null) {
$sql = "SELECT * FROM pinglerr_scans WHERE host = ? AND ref_uuid IS NULL";
$param = $host;
}
if ($ref_uuid !== null) {
$sql = "SELECT * FROM pinglerr_scans WHERE ref_uuid = ?";
$param = $ref_uuid;
}
if ($scan_id !== null) {
$sql = "SELECT * FROM pinglerr_scans WHERE id = ?";
$param = $scan_id;
}
if ($must_be_active_or_waiting) {
$sql = $sql . " AND status_code IN ('ACTIVE', 'WAITING')";
}
$stmt = get_mysqli()->prepare($sql);
$stmt->bind_param("s", $param);
$stmt->execute();
$result = $stmt->get_result();
$assoc = $result->fetch_assoc();
return $assoc;
}
function pinglerr_get_scan_history(?string $host = null, ?string $ref_uuid = null, ?int $max_rows = null): ?array
{
if ($host !== null) {
$sql = "SELECT * FROM pinglerr_scans WHERE host = ? AND ref_uuid IS NULL ORDER BY start_date DESC";
$param = $host;
}
if ($ref_uuid !== null) {
$sql = "SELECT * FROM pinglerr_scans WHERE ref_uuid= ? ORDER BY start_date DESC";
$param = $ref_uuid;
}
if ($max_rows !== null) {
$sql = $sql . ' LIMIT ' . $max_rows;
}
$scans = (function() use ($sql, $param): ?array
{
$stmt = get_mysqli()->prepare($sql);
$stmt->bind_param("s", $param);
$stmt->execute();
$result = $stmt->get_result();
$assoc_array = [];
while ($row = $result->fetch_assoc()) {
$assoc_array[] = $row;
}
if (empty($assoc_array)) {
return null;
}
return $assoc_array;
})();
return $scans;
}
$route['default'] = function()
{
$ref_uuid = filter_input(INPUT_GET, 'ref_uuid');
$prefer_ref_uuid = !empty($ref_uuid);
if ($prefer_ref_uuid) $ref_uuid = htmlspecialchars($ref_uuid);
$host = strtolower(filter_input(INPUT_GET, 'host'));
if (!empty($host) && !is_valid_host($host)) {
suicide_user_bad_request('Ugyldig vert adresse');
}
if ($prefer_ref_uuid) {
$scan = pinglerr_get_scan(ref_uuid: $ref_uuid);
} else {
$scan = pinglerr_get_scan(host: $host);
}
$function['show_form'] = function() use($ref_uuid, $prefer_ref_uuid, $host) {
?>
<h1>Pinglerr</h1>
<p>Sjekk tilkobling status til IP-adresse eller annen tjeneste</p>
<form method="POST" action="?p=start_scan">
<input style="width: 100%; box-sizing: border-box; padding: .5rem;" value="<?=$host?>" autocomplete="off" placeholder="Vertsnavn eller adresse som f.eks nrk.no, 192.168.1.1" type="text" name="host">
<?php if($prefer_ref_uuid): ?>
<br>
<br>
<label for="ref_uuid">Referanse</label>
<input style="width: 100%; box-sizing: border-box; padding: .5rem;" value="<?=$ref_uuid?>" autocomplete="off" placeholder="Referanse" type="text" name="ref_uuid">
<?php endif; ?>
<br>
<br>
<label for="time_limit">Hvor lenge skal denne kjøre?</label>
<select name="time_limit" id="time_limit">
<option value="600">10 minutter</option>
<option value="3600">1 time</option>
<option value="14400">4 timer</option>
<option value="28800">8 timer</option>
<option value="43200">12 timer</option>
<option value="86400">24 timer</option>
<option value="172800">2 døgn</option>
<option value="345600">4 døgn</option>
<option value="604800">1 uke</option>
</select>
<br>
<br>
<input style="width: 5rem; padding: .5rem;" type="submit" value="Sjekk" class="btn btn-primary btn-lg">
</form>
<?php
};
$function['show_scan_history'] = function() use($prefer_ref_uuid, $ref_uuid, $host) {
$show_full_history = boolval(filter_input(INPUT_GET, 'show_full_history'));
$max_rows = 10;
if ($show_full_history) {
$max_rows = 10000;
}
if ($prefer_ref_uuid) {
$scans = pinglerr_get_scan_history(ref_uuid: $ref_uuid, max_rows: $max_rows);
$current_scan = pinglerr_get_scan(ref_uuid: $ref_uuid);
} else {
$scans = pinglerr_get_scan_history($host, max_rows: $max_rows);
$current_scan = pinglerr_get_scan($host);
}
if (empty($scans)) {
return;
}
?>
<h1>Historikk</h1>
<p>Tidligere skanninger som er gjort denne verten eller referanse.</p>
<table>
<tr>
<th>Start Dato</th>
<th>Handling</th>
</tr>
<?php foreach($scans as $scan): ?>
<tr>
<td class="dateConverter"><?=$scan['start_date']?></td>
<td>[&nbsp;<a class="link-btn-normal" href="?p=scan_details&scan_id=<?=$scan['id']?><?=($prefer_ref_uuid ? '&ref_uuid=' . $ref_uuid : '&host=' . $host)?>">Detaljer</a>&nbsp;]</td>
</tr>
<?php endforeach; ?>
</table>
<?php
};
$function['show_live_stats'] = function() use($scan, $ref_uuid, $prefer_ref_uuid)
{
?>
<form>
<label for="timeRangeSelector">Hent statistikk fra siste:</label>
<select name="timeRangeSelector" id="timeRangeSelector">
<option value="900">15 minutter</option>
<option value="1800">30 minutter</option>
<option value="3600">1 time</option>
<option value="14400">4 timer</option>
<option value="28800">8 timer</option>
<option value="43200">12 timer</option>
<option value="86400">24 timer</option>
<option value="Details">Fullstendig</option>
</select>
</form>
<hr>
<h1>Sanntid statistikk</h1>
<div class="sign-container">
<div class="sign">
<span class="sign-label" style="">Tilkobling</span><span class="sign-text" id="connection_status">Ukjent</span>
</div>
<div class="sign">
<span class="sign-label">Vert</span><span class="sign-text"><?=$scan['host']?></span>
</div>
<?php if($prefer_ref_uuid): ?>
<div class="sign">
<span class="sign-label">Referanse</span><span class="sign-text"><?=$scan['ref_uuid']?></span>
</div>
<?php endif; ?>
</div>
<br>
<div style="
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: .5rem;
">
<div style="padding: 0.5rem; background: #eee; border: 1px solid #ccc; text-align: center; border-radius: 0.25rem;">
<div>Ping (siste)</div>
<div style="font-size: 2rem;"><span id="ping">Vent</span></div>
</div>
<div style="padding: 0.5rem; background: #eee; border: 1px solid #ccc; text-align: center; border-radius: 0.25rem;">
<div style="">Pakke tap</div>
<div style="font-size: 2rem;"><span id="packet_loss">Vent</span></div>
</div>
<div style="padding: 0.5rem; background: #eee; border: 1px solid #ccc; text-align: center; border-radius: 0.25rem;">
<div style="">Suksess-rate</div>
<div style="font-size: 2rem;"><span id="uptime">Vent</span></div>
</div>
</div>
<br>
<div style="height: 15rem">
<div id="liveChart"></div>
</div>
<script src="static/js/live_stats.js"></script>
<?php
};
if (!empty($scan)) {
page_header('Pinglerr', toolbar_links: [
[
'href' => '?p=stop_scanning&scan_id=' . $scan['id'] . ($prefer_ref_uuid ? '&ref_uuid=' . $ref_uuid : '') . (!empty($host) ? '&host=' . $host : ''),
'name' => 'Stop skanning',
'type' => 'danger'
]
]);
} else {
page_header('Pinglerr');
}
?>
<?php if(!empty($scan)): ?>
<?=$function['show_live_stats']()?>
<?php endif;?>
<?php if(empty($scan)): ?>
<?=$function['show_form']()?>
<?php endif;?>
<?php if(!empty($host) || $prefer_ref_uuid): ?>
<?=$function['show_scan_history']();?>
<?php endif; ?>
<?php
page_footer();
};
$route['scan_details'] = function()
{
$ref_uuid = filter_input(INPUT_GET, 'ref_uuid');
$prefer_ref_uuid = !empty($ref_uuid);
if ($prefer_ref_uuid) $ref_uuid = htmlspecialchars($ref_uuid);
$scan_id = filter_input(INPUT_GET, 'scan_id');
if (empty($scan_id)) {
suicide_user_bad_request('scan_id kan ikke være tom!');
}
$scan = pinglerr_get_scan(
scan_id: $scan_id,
must_be_active_or_waiting: false
);
if (!$scan) {
suicide_user_bad_request('Fant ikke scan i databasen!');
}
if ($scan['status_code'] === 'WAITING') {
suicide_user_bad_request('Venter på at denne skanningen skal starte');
}
page_header('Detaljer - Pinglerr', toolbar_links: [
[
'href' => get_referer(),
'name' => 'Gå tilbake'
]
]);
?>
<h1>Fullstendig statistikk</h1>
<div class="sign-container">
<?php if($scan['status_code'] === 'ACTIVE'): ?>
<span class="sign"><span class="sign-label">Status</span><span class="sign-text alert-success">Aktiv</span></span>
<?php elseif($scan['status_code'] === 'DONE'): ?>
<span class="sign"><span class="sign-label">Status</span><span class="sign-text alert-info">Ferdig</span></span>
<?php endif; ?>
<span class="sign"><span class="sign-label">Vert</span><span class="sign-text"><?=$scan['host']?></span></span>
<span class="sign"><span class="sign-label">ID</span><span class="sign-text"><?=$scan_id?></span></span>
</div>
<div id="chartElement"></div>
<div id="brushChartElement"></div>
<script src="static/js/scan_history_details.js"></script>
<?php
page_footer();
};
$route['start_scan'] = function()
{
$ref_uuid = filter_input(INPUT_POST, 'ref_uuid');
$prefer_ref_uuid = !empty($ref_uuid);
if ($prefer_ref_uuid) $ref_uuid = htmlspecialchars($ref_uuid);
$host = strtolower(filter_input(INPUT_POST, 'host'));
if (!is_valid_host($host)) {
if ($prefer_ref_uuid) {
header('Location: ?p=default&host=' . $host . '&ref_uuid=' . $ref_uuid);
} else {
header('Location: ?p=default&host=' . $host);
}
flash_message('Kunne ikke starte ny skanning: ugyldig vert adresse!', 'danger');
die();
}
if ($prefer_ref_uuid) {
$scan = pinglerr_get_scan(ref_uuid: $ref_uuid);
} else {
$scan = pinglerr_get_scan($host);
}
// if scan is not active or waiting
if ($scan === null) {
$stmt = get_mysqli()->prepare(
"INSERT INTO pinglerr_scans (host, ref_uuid, status_code, end_date) VALUES (?, ?, ?, NOW() + INTERVAL ? SECOND)"
);
$status_code = 'WAITING';
$time_limit_in_seconds = filter_input(INPUT_POST, 'time_limit');
if (!is_numeric($time_limit_in_seconds))
suicide_user_bad_request('time_limit is not numeric');
if ($time_limit_in_seconds > 31536000)
suicide_user_bad_request('time_limit should not be above 1 year');
if ($time_limit_in_seconds < 0)
suicide_user_bad_request('time_limit should not be a negative value');
// run forever
if ($time_limit_in_seconds == 0)
$time_limit_in_seconds = 315569260;
$stmt->bind_param("sssi", $host, $ref_uuid, $status_code, $time_limit_in_seconds);
$stmt->execute();
} else {
flash_message('Det eksisterer allerede en aktiv skanning på denne verten eller referansen');
if ($prefer_ref_uuid) {
header('Location: ?p=default&ref_uuid=' . $ref_uuid);
} else {
header('Location: ?p=default&host=' . $host);
}
die();
}
if ($prefer_ref_uuid) {
header('Location: ?p=default&ref_uuid=' . $ref_uuid);
} else {
header('Location: ?p=default&host=' . $host);
}
};
$route['stop_scanning'] = function()
{
$confirm = filter_input(INPUT_GET, 'confirm');
$scan_id = filter_input(INPUT_GET, 'scan_id');
$ref_uuid = filter_input(INPUT_GET, 'ref_uuid');
$prefer_ref_uuid = !empty($ref_uuid);
if ($prefer_ref_uuid) $ref_uuid = htmlspecialchars($ref_uuid);
$host = strtolower(filter_input(INPUT_GET, 'host'));
if (!$prefer_ref_uuid && !empty($host) && !is_valid_host($host)) {
suicide_user_bad_request('Ugyldig vertsnavn');
}
if (boolval($confirm)) {
$scan = pinglerr_get_scan(scan_id: $scan_id);
if (empty($scan)) {
suicide_user_bad_request('Fant ikke noen aktiv skanning');
}
$stmt = get_mysqli()->prepare(
"UPDATE pinglerr_scans SET status_code = ?, end_date = NOW(), error_message = ? WHERE status_code IN ('ACTIVE', 'WAITING') AND id = ?"
);
$status_code = 'DONE';
$error_message = 'Manually stopped by user';
$stmt->bind_param("sss",
$status_code,
$error_message,
$scan['id']
);
$stmt->execute();
if ($prefer_ref_uuid) {
header('Location: ?p=default&host=' . $host . '&ref_uuid=' . $ref_uuid);
} else {
header('Location: ?p=default&host=' . $host);
}
flash_message('Du har nå stanset skanningen', 'success');
die();
} else {
page_header('Er du sikker? - Pingler');
?>
<h1>Er du sikker?</h1>
<p>Vil du virkelig stanse denne skanningen?</p>
<span>[&nbsp;<a class="link-btn-success" href="?p=stop_scanning&confirm=true&scan_id=<?=htmlspecialchars($scan_id)?><?=($prefer_ref_uuid ? '&ref_uuid=' . $ref_uuid : '') . (!empty($host) ? '&host=' . $host : '')?>">Ja det vil jeg!</a>&nbsp;]</span>
<span>[&nbsp;<a class="link-btn-danger" href="<?=get_referer()?>">Nei ta meg tilbake igjen</a>&nbsp;]</span>
<?php
page_footer();
}
};
$route['daemon_poll'] = function()
{
$scans = (function(): ?array {
$stmt = get_mysqli()->prepare(
"SELECT * FROM pinglerr_scans WHERE status_code IN ('ACTIVE', 'WAITING')"
);
$stmt->execute();
$result = $stmt->get_result();
$assoc_array = [];
while ($row = $result->fetch_assoc()) {
$assoc_array[] = $row;
}
return $assoc_array;
})();
header('Content-type: application/json');
echo json_encode($scans);
};
// not in use anymore, replaced with daemon_bulk_update
$route['daemon_update'] = function()
{
$scan_id = filter_input(INPUT_GET, 'scan_id');
if (empty($scan_id)) {
suicide_user_bad_request('Mangler scan_id query parameter');
}
$scan = pinglerr_get_scan(scan_id: $scan_id);
if ($scan === null) {
suicide_user_bad_request('Kunne ikke finne scan fra scan_id');
}
// if time is up change the status to DONE and also exit the script
// TODO: this would probably be more suitable in the route daemon_jobs or some other
// endpoint that the daemon would periodically call
(function() use($scan)
{
$current_epoch = time();
$end_epoch = strtotime($scan['end_date']);
if ($current_epoch > $end_epoch) {
$stmt = get_mysqli()->prepare(
"UPDATE pinglerr_scans SET status_code = ?, error_message = ? WHERE status_code IN ('ACTIVE', 'WAITING') AND id = ?"
);
$status_code = 'DONE';
$error_message = 'Stopped because end_date was reached';
$stmt->bind_param("sss",
$status_code,
$error_message,
$scan['id']
);
$stmt->execute();
die();
}
})();
// set scan status from WAITING to ACTIVE
(function() use($scan)
{
$stmt = get_mysqli()->prepare(
"UPDATE pinglerr_scans SET status_code = ? WHERE id = ?"
);
$status_code = 'ACTIVE';
$stmt->bind_param("si",
$status_code,
$scan['id'],
);
$stmt->execute();
})();
(function() use($scan)
{
$pkt_transmitted = filter_input(INPUT_GET, 'pkt_transmitted');
$pkt_received = filter_input(INPUT_GET, 'pkt_received');
$ping_avg = filter_input(INPUT_GET, 'ping_avg');
$stmt = get_mysqli()->prepare(
"INSERT INTO pinglerr_pings (scan_id, ping_avg, pkt_transmitted, pkt_received) VALUES (?,?,?,?)"
);
$stmt->bind_param("idii",
$scan['id'],
$ping_avg,
$pkt_transmitted,
$pkt_received,
);
$stmt->execute();
})();
};
$route['daemon_bulk_update'] = function()
{
$bulk_pings = json_decode(file_get_contents('php://input'), true);
foreach ($bulk_pings as $ping) {
$scan_id = $ping['scan_id'];
$pkt_transmitted = $ping['pkt_transmitted'];
$pkt_received = $ping['pkt_received'];
if (!isset($ping['ping_avg'])) {
$ping_avg = null;
} else {
$ping_avg = $ping['ping_avg'];
}
$scan = pinglerr_get_scan(scan_id: $scan_id);
if ($scan === null) {
suicide_user_bad_request('Kunne ikke finne scan for scan_id');
}
$time_is_up = (function() use($scan)
{
$current_epoch = time();
$end_epoch = strtotime($scan['end_date']);
if ($current_epoch > $end_epoch) {
$stmt = get_mysqli()->prepare(
"UPDATE pinglerr_scans SET status_code = ?, error_message = ? WHERE status_code IN ('ACTIVE', 'WAITING') AND id = ?"
);
$status_code = 'DONE';
$error_message = 'Stopped because end_date was reached';
$stmt->bind_param("sss",
$status_code,
$error_message,
$scan['id']
);
$stmt->execute();
return true;
}
return false;
})();
if ($time_is_up) {
continue;
}
// set scan status from WAITING to ACTIVE
(function() use($scan)
{
$stmt = get_mysqli()->prepare(
"UPDATE pinglerr_scans SET status_code = ? WHERE id = ?"
);
$status_code = 'ACTIVE';
$stmt->bind_param("si",
$status_code,
$scan['id'],
);
$stmt->execute();
})();
(function() use($scan, $pkt_transmitted, $pkt_received, $ping_avg)
{
$stmt = get_mysqli()->prepare(
"INSERT INTO pinglerr_pings (scan_id, ping_avg, pkt_transmitted, pkt_received) VALUES (?,?,?,?)"
);
$stmt->bind_param("idii",
$scan['id'],
$ping_avg,
$pkt_transmitted,
$pkt_received,
);
$stmt->execute();
})();
}
};
$route['api_chart'] = function()
{
$scan_id = filter_input(INPUT_GET, 'scan_id');
$since = intval(filter_input(INPUT_GET, 'since'));
$limit_length = intval(filter_input(INPUT_GET, 'limit_length'));
$pings = (function() use($scan_id, $since): array
{
$stmt = get_mysqli()->prepare(
"SELECT ping_avg, pkt_transmitted, pkt_received, date_added FROM pinglerr_pings WHERE scan_id = ? AND date_added > FROM_UNIXTIME(?) ORDER BY date_added ASC"
);
$stmt->bind_param("ii", $scan_id, $since);
$stmt->execute();
$result = $stmt->get_result();
$assoc_array = [];
while ($row = $result->fetch_assoc()) {
$assoc_array[] = $row;
}
return $assoc_array;
})();
if (empty($pings)) {
http_response_code(204);
die();
}
if ($limit_length > 0 && $limit_length < count($pings)) {
$merged_pings = [];
$merge_gap = count($pings) / $limit_length;
$merge_iteration = 0;
$merge_remainder = 0;
$ping_avg_sum_storage = [];
$ping_pkt_transmitted_storage = [];
$ping_pkt_received_storage = [];
foreach ($pings as $ping) {
if ($merge_iteration >= $merge_gap - $merge_remainder) {
$merge_remainder+= $merge_iteration - $merge_gap;
$how_many_nulls = 0;
for ($i=0; $i < count($ping_avg_sum_storage); $i++) {
if ($ping_avg_sum_storage[$i] === null) {
$how_many_nulls++;
}
}
if ($how_many_nulls > count($ping_avg_sum_storage) / 2) {
$ping_avg = null;
} else {
for ($i=0; $i < count($ping_avg_sum_storage); $i++) {
if ($ping_avg_sum_storage[$i] === null) {
unset($ping_avg_sum_storage[$i]);
}
}
$ping_avg = round(array_sum($ping_avg_sum_storage) / count($ping_avg_sum_storage), 3);
}
$merged_pings[] = [
'ping_avg' => $ping_avg,
'date_added' => $ping['date_added'],
'pkt_transmitted' => round(array_sum($ping_pkt_transmitted_storage) / count($ping_pkt_transmitted_storage), 3),
'pkt_received' => round(array_sum($ping_pkt_received_storage) / count($ping_pkt_received_storage), 3),
];
$ping_pkt_transmitted_storage = [];
$ping_pkt_received_storage = [];
$ping_avg_sum_storage = [];
$merge_iteration = 0;
}
$ping_pkt_transmitted_storage[] = $ping['pkt_transmitted'];
$ping_pkt_received_storage[] = $ping['pkt_received'];
$ping_avg_sum_storage[] = $ping['ping_avg'];
$merge_iteration++;
}
$pings = $merged_pings;
}
//echo 'Gap: ' . htmlspecialchars($merge_gap) . ' Antall: ' . count($pings) . ' Remains: ' . $merge_remainder;
//die();
header('Content-type: application/json');
echo json_encode([
'latest' => strtotime($pings[count($pings) - 1]['date_added']),
'pings' => $pings,
]);
};
$route['api_stats'] = function()
{
$ref_uuid = filter_input(INPUT_GET, 'ref_uuid');
$prefer_ref_uuid = !empty($ref_uuid);
if ($prefer_ref_uuid) $ref_uuid = htmlspecialchars($ref_uuid);
$host = strtolower(filter_input(INPUT_GET, 'host'));
if (!$prefer_ref_uuid && !empty($host) && !is_valid_host($host)) {
suicide_user_bad_request('Ugyldig vertsnavn');
}
$scan_id = filter_input(INPUT_GET, 'scan_id');
if ($prefer_ref_uuid) {
$scan = pinglerr_get_scan(ref_uuid: $ref_uuid);
} elseif(!empty($host)) {
$scan = pinglerr_get_scan($host);
} elseif(!empty($scan_id)) {
$scan = pinglerr_get_scan(
scan_id: $scan_id,
must_be_active_or_waiting: false
);
} else {
suicide_user_bad_request('Mangler ref_uuid, host eller scan_id parameter');
}
if ($scan === null) {
suicide_user_bad_request('Kunne ikke finne noen aktive skanninger for denne verten eller referanse');
}
$since = intval(filter_input(INPUT_GET, 'since'));
$pings = (function() use($scan, $since): array
{
$stmt = get_mysqli()->prepare(
"SELECT * FROM pinglerr_pings WHERE scan_id = ? AND date_added > FROM_UNIXTIME(?) ORDER BY date_added DESC"
);
$stmt->bind_param("is", $scan['id'], $since);
$stmt->execute();
$result = $stmt->get_result();
$assoc_array = [];
while ($row = $result->fetch_assoc()) {
$assoc_array[] = $row;
}
return $assoc_array;
})();
if (empty($pings)) {
http_response_code(204);
die();
}
$pkt_transmitted = (function() use($pings): int {
$pkts = 0;
foreach ($pings as $ping_row) {
if ($ping_row['pkt_transmitted'] === null) {
continue;
}
$pkts = $pkts + $ping_row['pkt_transmitted'];
}
return $pkts;
})();
$pkt_received = (function() use($pings): int {
$pkts = 0;
foreach ($pings as $ping_row) {
if ($ping_row['pkt_received'] === null) {
continue;
}
$pkts = $pkts + $ping_row['pkt_received'];
}
return $pkts;
})();
$packet_loss_percentage = (function() use($pkt_transmitted, $pkt_received): float {
try {
$packet_loss_percentage = $pkt_received / $pkt_transmitted * 100;
} catch (\DivisionByZeroError $th) {
return 100;
}
if ($packet_loss_percentage > 100) {
$packet_loss_percentage = 100;
} else {
$packet_loss_percentage = 100 - $packet_loss_percentage;
}
return $packet_loss_percentage;
})();
// TODO: this is just packet_loss_percentage reversed! make a more proper uptime calculation
$uptime_percentage = (function() use($packet_loss_percentage): float {
return 100 - $packet_loss_percentage;
})();
// TODO: this is too simple, but it works for now. make a more proper calculation using jitter
$connection_status = (function() use($pings, $packet_loss_percentage) {
if ($pings[0]['ping_avg'] === null) {
return 'OFFLINE';
}
if ($packet_loss_percentage >= 2) {
return 'UNSTABLE';
}
return 'ONLINE';
})();
header('Content-type: application/json');
return json_encode([
'latest' => strtotime($pings[0]['date_added']),
'scan_id' => $scan['id'],
'host' => $scan['host'],
'ref_uuid' => $scan['ref_uuid'],
'status_code' => $scan['status_code'],
'connection_status' => $connection_status,
'pkt_transmitted' => $pkt_transmitted,
'pkt_received' => $pkt_received,
'ping_latest' => round($pings[0]['ping_avg'], 0, PHP_ROUND_HALF_EVEN),
'packet_loss_percentage' => round($packet_loss_percentage, 0, PHP_ROUND_HALF_UP),
'uptime_percentage' => round($uptime_percentage, 0, PHP_ROUND_HALF_DOWN),
]);
};
$page = filter_input(INPUT_GET, 'p');
if (empty($page)) {
$page = 'default';
}
if (isset($route[$page])) {
die($route[$page]());
}
http_response_code(404);
page_header('Kunne ikke finne siden')
?>
<h1>Siden ble ikke funnet</h1>
<a href="<?=get_referer()?>">Klikk her for å tilbake</a>
<?php
page_footer()
?>

View File

@ -0,0 +1,149 @@
@media (prefers-color-scheme: dark) {
html {
filter: invert();
background: #ddd !important;
}
header {
filter: invert();
}
}
html {
background: #def0fe;
}
body {
background: #fff;
max-width: 50rem;
margin: 0 auto;
color: #111;
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, Cantarell, Ubuntu, roboto, noto, helvetica, arial, sans-serif;
}
header, main {
padding: .5rem;
}
header {
background: #1280c3;
}
header>nav>a {
color: #000 !important;
text-decoration: none;
background: url(../img/logo.svg) no-repeat left center;
background-size: 1.25rem;
filter: invert(100%);
padding-left: 1.5rem;
}
.toolbar {
padding: .5rem;
background: #eee;
border-bottom: 1px solid #ccc;
}
.toolbar>nav>span>a {
white-space: pre;
}
footer {
padding: .5rem;
color: #333;
background: #eee;
border-top: 1px solid #ccc;
}
table {
display: block;
border-collapse: collapse;
width: 100%;
overflow: auto;
}
table td, table th {
padding: 0.5rem;
}
table tr:nth-child(even) {
background-color: #eee;
}
table th {
font-weight: bold;
text-align: left;
}
table tr {
border-bottom: 1px solid #ccc;
}
tbody {
display: table;
width: 100%
}
a, a:visited, a:hover, a:active {
color: #1280c3;
}
hr {
border: 0;
border-bottom: 1px solid #ccc;
}
.sign * {
padding: .25rem;
border: 1px solid #ccc;
}
.sign-label {
border-right: 0;
border-radius: .25rem 0 0 .25rem;
}
.sign-container {
display: flex;
flex-wrap: wrap;
gap: .5rem;
row-gap: 1rem;
}
.sign-text {
border-radius: 0 .25rem .25rem 0;
color: #222;
background-color: #e2e3e5;
}
.sign>.sign-label {
background: #eee;
}
.alert {
padding: .5rem;
border-radius: .25rem;
border-width: 1px;
border-style: solid;
}
.alert-info {
color: #0c5460;
background-color: #d1ecf1;
border-color: #bee5eb;
}
.alert-success {
color: #155724;
background-color: #d4edda;
border-color: #c3e6cb;
}
.alert-danger {
color: #721c24;
background-color: #f8d7da;
border-color: #f5c6cb;
}
.alert-warning {
color: #856404;
background-color: #fff3cd;
border-color: #ffeeba;
}
.pill {
padding-right: 0.5rem;
padding-left: 0.5rem;
border-radius: 1rem;
border-style: solid;
border-width: .15rem;
}
.link-btn {
font-weight: bold;
}
.link-btn-normal {
color: #1280c3 !important;
}
.link-btn-success {
color: #16b93b !important;
}
.link-btn-danger {
color: #c61910 !important
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 55 55" xml:space="preserve">
<path d="M49,0c-3.309,0-6,2.691-6,6c0,1.035,0.263,2.009,0.726,2.86l-9.829,9.829C32.542,17.634,30.846,17,29,17
s-3.542,0.634-4.898,1.688l-7.669-7.669C16.785,10.424,17,9.74,17,9c0-2.206-1.794-4-4-4S9,6.794,9,9s1.794,4,4,4
c0.74,0,1.424-0.215,2.019-0.567l7.669,7.669C21.634,21.458,21,23.154,21,25s0.634,3.542,1.688,4.897L10.024,42.562
C8.958,41.595,7.549,41,6,41c-3.309,0-6,2.691-6,6s2.691,6,6,6s6-2.691,6-6c0-1.035-0.263-2.009-0.726-2.86l12.829-12.829
c1.106,0.86,2.44,1.436,3.898,1.619v10.16c-2.833,0.478-5,2.942-5,5.91c0,3.309,2.691,6,6,6s6-2.691,6-6c0-2.967-2.167-5.431-5-5.91
v-10.16c1.458-0.183,2.792-0.759,3.898-1.619l7.669,7.669C41.215,39.576,41,40.26,41,41c0,2.206,1.794,4,4,4s4-1.794,4-4
s-1.794-4-4-4c-0.74,0-1.424,0.215-2.019,0.567l-7.669-7.669C36.366,28.542,37,26.846,37,25s-0.634-3.542-1.688-4.897l9.665-9.665
C46.042,11.405,47.451,12,49,12c3.309,0,6-2.691,6-6S52.309,0,49,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,58 @@
window.addEventListener('DOMContentLoaded', function() {
updateDates();
}, false);
function updateDates() {
dateElements = document.querySelectorAll(".dateConverter");
const options = {
weekday: "long",
year: "numeric",
month: "long",
day: "numeric",
hour: "2-digit",
minute: "2-digit"
};
dateElements.forEach(element => {
dateToConvert = element.innerHTML + ' UTC';
newDate = new Date(dateToConvert).toLocaleDateString('no-NB', options);
timeAgo(dateToConvert);
element.innerHTML = 'Startet ' + timeAgo(dateToConvert);
});
}
function timeAgo(dateString)
{
// Assume we have a date string being received from some object.
const data = {
formattedDate: dateString // Example date string in ISO 8601 format
};
// Extract the formatted date string from the object
const { formattedDate } = data;
// Convert the date string to a Date object
const dateObject = new Date(formattedDate);
// Calculate the difference in seconds between the given date and the current date
const secondsDiff = Math.round((dateObject - Date.now()) / 1000);
// Array representing one minute, hour, day, week, month, etc. in seconds
const unitsInSec = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
// Array equivalent to the above but in the string representation of the units
const unitStrings = ["second", "minute", "hour", "day", "week", "month", "year"];
// Find the appropriate unit based on the seconds difference
const unitIndex = unitsInSec.findIndex((cutoff) => cutoff > Math.abs(secondsDiff));
// Get the divisor to convert seconds to the appropriate unit
const divisor = unitIndex ? unitsInSec[unitIndex - 1] : 1;
// Initialize Intl.RelativeTimeFormat
const rtf = new Intl.RelativeTimeFormat("no", { numeric: "auto" });
// Format the relative time based on the calculated unit
const relativeTime = rtf.format(Math.floor(secondsDiff / divisor), unitStrings[unitIndex]);
return(relativeTime);
}

View File

@ -0,0 +1,240 @@
var chartOptions = {
colors:['#008ffb', '#f08681'],
series: [
{
name: 'Ping',
data: [null]
},
{
name: 'Pakke Tap',
type: 'area',
data: [null]
}
],
chart: {
height: '100%',
id: 'realtime',
type: 'area',
animations: {
enabled: false,
},
toolbar: {
show: false
},
zoom: {
enabled: false
},
},
dataLabels: {
enabled: false,
},
stroke: {
curve: 'straight'
},
markers: {
size: 0
},
xaxis: {
type: 'datetime',
labels: {
datetimeUTC: false,
},
},
yaxis: [
{
seriesName: 'Ping',
labels: {
formatter: function (val) {
if (val == null) {
return 0;
}
return val.toFixed(1);
},
}
},
{
seriesName: 'Pakke Tap',
min:0,
max:100,
opposite: true,
labels: {
formatter: function (val) {
if (val == null) {
return 0 + '%';
}
return val.toFixed(0) + "%";
},
}
}
],
legend: {
show: false
},
};
var timeRangeInSeconds = 0;
var chartLatest;
var chartHasFetchedTimeRange = false;
var chartElement;
var chartPingSeries = [];
var chartPacketLossSeries = [];
var scanId;
const queryParam = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
document.body.onload = async function() {
chartElement = new ApexCharts(document.querySelector("#liveChart"), chartOptions);
chartElement.render();
timeRangeInSeconds = timeRangeSelector[timeRangeSelector.selectedIndex].attributes.value.nodeValue;
await update_summary();
update_chart();
window.setInterval(async function() {
update_summary();
update_chart();
}, 5000)
};
timeRangeSelector.onchange = function(event) {
timeRangeInSeconds = event.srcElement[event.srcElement.selectedIndex].attributes.value.nodeValue;
if (timeRangeInSeconds == 'Details') {
if (queryParam.ref_uuid !== null) {
window.location.href = './?p=scan_details&ref_uuid=' + queryParam.ref_uuid + '&scan_id=' + scanId;
} else {
window.location.href = './?p=scan_details&host=' + queryParam.host + '&scan_id=' + scanId;
}
}
// TODO: a very rare race condition may occur when switching too fast between time ranges can cause the chart to show wrong data
// cancel the update interval and start it again
// reset chart
chartHasFetchedTimeRange = false;
chartPingSeries = [];
chartPacketLossSeries = [];
chartElement.updateSeries([
{
data: chartPingSeries
},
{
data: chartPacketLossSeries
}
]);
update_chart();
update_summary();
};
async function update_summary() {
if (queryParam.ref_uuid !== null) {
var response = await fetch('./?p=api_stats&ref_uuid=' + queryParam.ref_uuid + '&since=' + (Math.floor(Date.now() / 1000) - timeRangeInSeconds));
} else {
var response = await fetch('./?p=api_stats&host=' + queryParam.host + '&since=' + (Math.floor(Date.now() / 1000) - timeRangeInSeconds));
}
if (response.status == 400) {
setTimeout(() => {
window.location.reload();
}, 1000);
return;
}
if (response.status !== 200) {
return;
}
let jsonResponse = await response.json();
scanId = jsonResponse.scan_id;
let resp_ping = jsonResponse.ping_latest;
let resp_packet_loss = jsonResponse.packet_loss_percentage;
let resp_uptime = jsonResponse.uptime_percentage;
let resp_connection_status = jsonResponse.connection_status;
if (resp_ping == null) {
ping.innerHTML = 'Feilet';
} else {
ping.innerHTML = resp_ping + ' ms';
}
packet_loss.innerHTML = resp_packet_loss + ' %';
uptime.innerHTML = resp_uptime + ' %';
if (resp_connection_status == 'ONLINE') {
connection_status.className = 'sign-text alert-success'
connection_status.innerHTML = 'Online';
}
if (resp_connection_status == 'UNSTABLE') {
connection_status.className = 'sign-text alert-warning'
connection_status.innerHTML = 'Ustabilt';
}
if (resp_connection_status == 'OFFLINE') {
connection_status.className = 'sign-text alert-danger'
connection_status.innerHTML = 'Offline';
}
}
async function update_chart()
{
if (!chartHasFetchedTimeRange) {
var response = await fetch('./?p=api_chart&limit_length=1000&scan_id=' + scanId + '&since=' + (Math.floor(Date.now() / 1000) - timeRangeInSeconds));
chartHasFetchedTimeRange = true;
} else {
var response = await fetch('./?p=api_chart&scan_id=' + scanId + '&since=' + chartLatest);
}
if (response.status === 204) {
return;
}
let jsonResponse = await response.json();
chartLatest = jsonResponse.latest;
jsonResponse.pings.forEach(ping => {
let newDate = new Date(ping.date_added + ' UTC');
chartPingSeries.push({ x: newDate.toString(), y: ping.ping_avg});
});
// remove old pings
for (let i = 0; i < chartPingSeries.length; i++) {
let pingEpoch = Math.floor((new Date(chartPingSeries[i].x)).getTime() / 1000);
if (pingEpoch < chartLatest - timeRangeInSeconds) {
chartPingSeries.splice(i, 1);
}
}
jsonResponse.pings.forEach(ping => {
let newDate = new Date(ping.date_added + ' UTC');
if (ping.pkt_transmitted != ping.pkt_received) {
let pktLossPercentage = ping.pkt_received / ping.pkt_transmitted * 100;
pktLossPercentage = 100 - pktLossPercentage;
chartPacketLossSeries.push({ x: newDate.toString(), y: pktLossPercentage});
} else {
chartPacketLossSeries.push({ x: newDate.toString(), y: 0});
}
});
for (let i = 0; i < chartPacketLossSeries.length; i++) {
let pingEpoch = Math.floor((new Date(chartPacketLossSeries[i].x)).getTime() / 1000);
if (pingEpoch < chartLatest - timeRangeInSeconds) {
chartPacketLossSeries.splice(i, 1);
}
}
chartElement.updateSeries([
{
name: 'Ping',
data: chartPingSeries
},
{
name: 'Pakke Tap',
data: chartPacketLossSeries
}
]);
}

View File

@ -0,0 +1,222 @@
var chartOptions = {
colors:['#008ffb', '#f08681'],
series: [
{
name: 'Ping',
data: [null]
},
{
name: 'Pakke Tap',
type: 'area',
data: [null]
}
],
chart: {
height: '300',
id: 'chart',
type: 'area',
animations: {
enabled: false,
},
toolbar: {
show: true
},
zoom: {
enabled: true
},
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'straight'
},
markers: {
size: 0
},
xaxis: {
type: 'datetime',
labels: {
datetimeUTC: false,
},
},
yaxis: [
{
seriesName: 'Ping',
labels: {
formatter: function (val) {
if (val == null) {
return 0;
}
return val.toFixed(1);
},
}
},
{
seriesName: 'Pakke Tap',
min:0,
max:100,
opposite: true,
labels: {
formatter: function (val) {
if (val == null) {
return 0 + '%';
}
return val.toFixed(0) + "%";
},
}
}
],
legend: {
show: false
},
};
var brushChartOptions = {
colors:['#008ffb', '#f08681'],
series: [
{
name: 'Ping',
data: [1]
},
{
name: 'Pakke Tap',
type: 'area',
data: [1]
}
],
chart: {
height: '100',
id: 'brushChart',
type: 'area',
brush:{
target: 'chart',
enabled: true
},
animations: {
enabled: false,
},
toolbar: {
show: false,
},
zoom: {
enabled: false
},
selection: {
enabled: true,
},
},
dataLabels: {
enabled: false
},
stroke: {
curve: 'straight'
},
markers: {
size: 0
},
xaxis: {
type: 'datetime',
labels: {
datetimeUTC: false,
},
},
yaxis: [
{
seriesName: 'Ping',
labels: {
formatter: function (val) {
if (val == null) {
return 0;
}
return val.toFixed(1);
},
}
},
{
seriesName: 'Pakke Tap',
min:0,
max:100,
opposite: true,
labels: {
formatter: function (val) {
if (val == null) {
return 0 + '%';
}
return val.toFixed(0) + "%"
},
}
}
],
legend: {
show: false
},
};
const queryParam = new Proxy(new URLSearchParams(window.location.search), {
get: (searchParams, prop) => searchParams.get(prop),
});
document.body.onload = async function() {
chartElement = new ApexCharts(document.querySelector("#chartElement"), chartOptions);
chartElement.render();
brushChartElement = new ApexCharts(document.querySelector("#brushChartElement"), brushChartOptions);
brushChartElement.render();
var statsResponse = await fetch('./?p=api_stats&scan_id=' + queryParam.scan_id);
if (statsResponse.status != 200) {
alert('Feil under henting av statistikk');
}
let chartResponse = await fetch('./?p=api_chart&limit_length=1000&scan_id=' + queryParam.scan_id);
if (chartResponse.status != 200) {
alert('Feil under henting av graf data');
}
let jsonResponse = await chartResponse.json();
let newPingSeries = [];
jsonResponse.pings.forEach(ping => {
let newDate = new Date(ping.date_added + ' UTC');
newPingSeries.push({ x: newDate.toString(), y: ping.ping_avg});
});
let chartPacketLossSeries = [];
jsonResponse.pings.forEach(ping => {
let newDate = new Date(ping.date_added + ' UTC');
if (ping.pkt_transmitted != ping.pkt_received) {
let pktLossPercentage = ping.pkt_received / ping.pkt_transmitted * 100;
pktLossPercentage = 100 - pktLossPercentage;
chartPacketLossSeries.push({ x: newDate.toString(), y: pktLossPercentage});
} else {
chartPacketLossSeries.push({ x: newDate.toString(), y: 0});
}
});
chartElement.updateSeries([
{
name: 'Ping',
data: newPingSeries
},
{
name: 'Pakke Tap',
data: chartPacketLossSeries
}
]);
brushChartElement.updateSeries([
{
name: 'Ping',
data: newPingSeries
},
{
name: 'Pakke Tap',
data: chartPacketLossSeries
}
]);
}