update
This commit is contained in:
parent
4e72174670
commit
e5fd11edbd
11 changed files with 3171 additions and 0 deletions
2017
Parsedown.php
Normal file
2017
Parsedown.php
Normal file
File diff suppressed because it is too large
Load diff
3
configuration.php
Normal file
3
configuration.php
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
define('PATH', substr(__FILE__, 0, strrpos(__FILE__, '/')));
|
1
incidents/message.md
Normal file
1
incidents/message.md
Normal file
|
@ -0,0 +1 @@
|
|||
There are no active incidents.
|
222
index.php
Normal file
222
index.php
Normal file
|
@ -0,0 +1,222 @@
|
|||
<?php
|
||||
// Start session for CSRF token management
|
||||
session_start();
|
||||
|
||||
// CSRF Protection Functions
|
||||
function generate_csrf_token() {
|
||||
if (empty($_SESSION['csrf_token'])) {
|
||||
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
|
||||
}
|
||||
return $_SESSION['csrf_token'];
|
||||
}
|
||||
|
||||
function verify_csrf_token($token) {
|
||||
// Check if token exists in session and matches submitted token
|
||||
if (!isset($_SESSION['csrf_token']) ||
|
||||
!isset($token) ||
|
||||
!hash_equals($_SESSION['csrf_token'], $token)) {
|
||||
|
||||
// Log potential CSRF attempt
|
||||
error_log('Potential CSRF attack detected');
|
||||
|
||||
// Destroy session and prevent further execution
|
||||
session_destroy();
|
||||
die('CSRF token validation failed');
|
||||
}
|
||||
|
||||
// Regenerate token after successful validation for additional security
|
||||
unset($_SESSION['csrf_token']);
|
||||
generate_csrf_token();
|
||||
}
|
||||
|
||||
// Configuration and include path handling
|
||||
include('configuration.php');
|
||||
include(PATH.'/Parsedown.php');
|
||||
|
||||
// Mandatory file and directory checks with CSRF protection
|
||||
$requiredChecks = [
|
||||
PATH.'/monitors.json' => '<h1>Missing monitors.json</h1>',
|
||||
PATH.'/monitors' => '<h1>Missing monitors directory</h1>',
|
||||
PATH.'/incidents' => '<h1>Missing incidents directory</h1>'
|
||||
];
|
||||
|
||||
foreach ($requiredChecks as $path => $errorMessage) {
|
||||
if (!file_exists($path)) {
|
||||
die($errorMessage . '<p>See project documentation for more information.</p>');
|
||||
}
|
||||
}
|
||||
|
||||
// Check monitors directory writability
|
||||
if (!is_writable(PATH.'/monitors')) {
|
||||
die('<h1>Monitors directory is not writable</h1>');
|
||||
}
|
||||
?><!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>HHF Website Monitor</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="theme-color" content="#212529">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="style.css" rel="stylesheet">
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.8.2/dist/chart.min.js" crossorigin="anonymous"></script>
|
||||
<meta name="csrf-token" content="<?php echo generate_csrf_token(); ?>">
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>HHF Website Monitor</h1>
|
||||
|
||||
<?php
|
||||
|
||||
$incidents = array();
|
||||
foreach(glob(PATH.'/incidents/*.md') as $incident_file) {
|
||||
$incident_filename = str_replace(PATH.'/incidents/', '', $incident_file);
|
||||
if(substr($incident_filename, 0, 6) == 'alert_') $incidents[$incident_filename] = 'alert';
|
||||
else if(substr($incident_filename, 0, 7) == 'notice_') $incidents[$incident_filename] = 'notice';
|
||||
else $incidents['post_'.$incident_filename] = 'post';
|
||||
}
|
||||
|
||||
ksort($incidents);
|
||||
foreach($incidents as $incident_filename => $class) {
|
||||
if(substr($incident_filename, 0, 5) == 'post_') $incident_filename = substr($incident_filename, 5);
|
||||
$Parsedown = new Parsedown();
|
||||
echo '<div class="incident '.$class.'">';
|
||||
echo $Parsedown->text(file_get_contents(PATH.'/incidents/'.$incident_filename));
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
$monitors = json_decode(file_get_contents(PATH.'/monitors.json'));
|
||||
|
||||
$i = 0;
|
||||
|
||||
foreach($monitors as $monitor => $url) {
|
||||
|
||||
$log = json_decode(file_get_contents(PATH.'/monitors/'.$monitor), TRUE);
|
||||
$last = $log[array_key_last($log)];
|
||||
|
||||
if(is_numeric($last['response'])) {
|
||||
$last = ' <span class="status">HTTP/1.1 '.$last['response'].'</span>';
|
||||
$class = 'good';
|
||||
$graph_color = '#51cf66';
|
||||
}
|
||||
else {
|
||||
$last = ' <span class="status">HTTP/1.1 '.$last['response'].'</span>';
|
||||
$class = 'bad';
|
||||
$graph_color = '#fa5252';
|
||||
}
|
||||
|
||||
echo '<div class="item"><h2><span class="'.$class.'">⬤</span> '.$monitor.$last.'</h2>';
|
||||
|
||||
if(file_exists(PATH.'/updates/'.$monitor.'.md')) {
|
||||
$Parsedown = new Parsedown();
|
||||
echo $Parsedown->text(file_get_contents(PATH.'/updates/'.$monitor.'.md'));
|
||||
}
|
||||
|
||||
$labels = array();
|
||||
$data = array();
|
||||
|
||||
$const = 'ctx'.$i;
|
||||
$const_chart = $const.'_'.$const;
|
||||
|
||||
echo '<div class="bloops">';
|
||||
|
||||
// do we need bloop padding?
|
||||
if(count($log) < 60) {
|
||||
$diff = 59 - count($log);
|
||||
|
||||
for ($x = 0; $x <= $diff; $x++) {
|
||||
echo '<span class="bloop" title="Not monitored"></span>';
|
||||
}
|
||||
}
|
||||
|
||||
foreach($log as $arr) {
|
||||
|
||||
$labels[] = "'".date("H:i", $arr['timestamp'])."'";
|
||||
|
||||
$data[] = @$arr['time'];
|
||||
|
||||
if(@$arr['time'] > 0) {
|
||||
echo '<span class="bloop good" data-status="Up" data-time="'.date("H:i", $arr['timestamp']).'" data-response="'.$arr['response'].'" data-ms="'.$arr['time'].'" data-status="Up at '.date("H:i", $arr['timestamp']).'" title="Up at '.date("H:i", $arr['timestamp']).' ('.$arr['time'].' ms)"></span>';
|
||||
}
|
||||
else {
|
||||
echo '<span class="bloop bad"></span>';
|
||||
}
|
||||
}
|
||||
|
||||
$min = min($data);
|
||||
$max = max($data);
|
||||
|
||||
$diff = $max - $min;
|
||||
|
||||
$max += ($diff / 3);
|
||||
$min -= ($diff / 3);
|
||||
|
||||
if($min < 0) $min = 0;
|
||||
|
||||
echo '</div>';
|
||||
|
||||
$chart_id = str_replace('.', '-', $monitor);
|
||||
|
||||
$labels = implode(', ', $labels);
|
||||
$data = implode(', ', $data);
|
||||
|
||||
$out = <<<EOD
|
||||
<canvas id="$chart_id" width="300" height="100"></canvas>
|
||||
<script>
|
||||
const $const = document.getElementById('$chart_id').getContext('2d');
|
||||
const $const_chart = new Chart($const, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: [$labels],
|
||||
datasets: [{
|
||||
label: 'response time',
|
||||
data: [$data],
|
||||
backgroundColor: '$graph_color',
|
||||
borderColor: '$graph_color',
|
||||
borderWidth: 2,
|
||||
tension: .4
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false,
|
||||
min: $min,
|
||||
max: $max,
|
||||
grid: { display: false, drawBorder: false }
|
||||
},
|
||||
x: {
|
||||
grid: { display: false, drawBorder: false }
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function(context) {
|
||||
return context.parsed.y + ' ms';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
EOD;
|
||||
echo $out;
|
||||
echo '</div>';
|
||||
$i++;
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
<footer>
|
||||
|
||||
<p>Website Monitor is an open source project. <a href="https://git.hhf.technology/hhf/HHFWebsiteMonitor.git">Download it on Forgejo</a>.</p>
|
||||
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
171
monitor.php
Normal file
171
monitor.php
Normal file
|
@ -0,0 +1,171 @@
|
|||
<?php
|
||||
/**
|
||||
* Secure Website Monitoring Script
|
||||
*
|
||||
* Monitors websites and logs their response times and status codes
|
||||
* Recommended to run via cron job
|
||||
*/
|
||||
|
||||
// Strict error reporting
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 0);
|
||||
ini_set('log_errors', 1);
|
||||
|
||||
// Validate and include configuration
|
||||
$configPath = __DIR__ . '/configuration.php';
|
||||
if (!file_exists($configPath)) {
|
||||
error_log('Configuration file not found');
|
||||
exit(1);
|
||||
}
|
||||
require_once $configPath;
|
||||
|
||||
// Validate PATH constant
|
||||
if (!defined('PATH') || !is_dir(PATH)) {
|
||||
error_log('Invalid monitoring directory');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Secure monitors file path
|
||||
$monitorsFile = PATH . '/monitors.json';
|
||||
if (!file_exists($monitorsFile)) {
|
||||
error_log('Monitors configuration file not found');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Read and validate monitors
|
||||
$monitorsJson = file_get_contents($monitorsFile);
|
||||
$monitors = json_decode($monitorsJson, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE || !is_array($monitors)) {
|
||||
error_log('Invalid monitors JSON');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
// Maximum number of historical records to keep
|
||||
const MAX_HISTORY_RECORDS = 60;
|
||||
|
||||
// Maximum execution time
|
||||
set_time_limit(30);
|
||||
|
||||
/**
|
||||
* Validate and sanitize URL
|
||||
*
|
||||
* @param string $url URL to validate
|
||||
* @return string|false Sanitized URL or false if invalid
|
||||
*/
|
||||
function validateUrl($url) {
|
||||
// Trim and validate URL
|
||||
$url = trim($url);
|
||||
|
||||
// Check if URL is valid
|
||||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Additional URL schemes validation (optional)
|
||||
$allowedSchemes = ['http', 'https'];
|
||||
$urlParts = parse_url($url);
|
||||
|
||||
if (!isset($urlParts['scheme']) || !in_array($urlParts['scheme'], $allowedSchemes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely fetch website response
|
||||
*
|
||||
* @param string $url URL to monitor
|
||||
* @return array Response data
|
||||
*/
|
||||
function fetchWebsiteResponse($url) {
|
||||
$response_data = [
|
||||
'timestamp' => time(),
|
||||
'error' => null,
|
||||
'time' => null,
|
||||
'response' => null
|
||||
];
|
||||
|
||||
// Validate URL
|
||||
$sanitizedUrl = validateUrl($url);
|
||||
if (!$sanitizedUrl) {
|
||||
$response_data['error'] = 'Invalid URL';
|
||||
return $response_data;
|
||||
}
|
||||
|
||||
// Initialize cURL with safe options
|
||||
$curl = curl_init();
|
||||
curl_setopt_array($curl, [
|
||||
CURLOPT_URL => $sanitizedUrl,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HEADER => false,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_SSL_VERIFYPEER => true,
|
||||
CURLOPT_SSL_VERIFYHOST => 2
|
||||
]);
|
||||
|
||||
// Execute request
|
||||
$response = curl_exec($curl);
|
||||
|
||||
// Check for cURL errors
|
||||
if ($response === false) {
|
||||
$response_data['error'] = curl_error($curl);
|
||||
} else {
|
||||
// Get connection info
|
||||
$info = curl_getinfo($curl);
|
||||
$response_data['time'] = round($info['total_time_us'] / 1000, 2);
|
||||
$response_data['response'] = $info['http_code'];
|
||||
}
|
||||
|
||||
// Close cURL resource
|
||||
curl_close($curl);
|
||||
|
||||
return $response_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely write monitor data
|
||||
*
|
||||
* @param string $name Monitor name
|
||||
* @param array $newData New response data
|
||||
*/
|
||||
function writeMonitorData($name, $newData) {
|
||||
// Validate monitor name (prevent path traversal)
|
||||
if (!preg_match('/^[a-zA-Z0-9_-]+$/', $name)) {
|
||||
error_log("Invalid monitor name: $name");
|
||||
return;
|
||||
}
|
||||
|
||||
$monitorFile = PATH . '/monitors/' . $name;
|
||||
|
||||
// Read existing data
|
||||
$data = [];
|
||||
if (file_exists($monitorFile)) {
|
||||
$fileContent = file_get_contents($monitorFile);
|
||||
$data = json_decode($fileContent, true) ?: [];
|
||||
}
|
||||
|
||||
// Merge and limit historical data
|
||||
$data[] = $newData;
|
||||
$data = array_slice($data, -MAX_HISTORY_RECORDS);
|
||||
|
||||
// Securely write file
|
||||
$jsonData = json_encode($data, JSON_PRETTY_PRINT);
|
||||
if (file_put_contents($monitorFile, $jsonData) === false) {
|
||||
error_log("Failed to write monitor data for: $name");
|
||||
}
|
||||
}
|
||||
|
||||
// Main monitoring loop
|
||||
foreach ($monitors as $name => $url) {
|
||||
try {
|
||||
$responseData = fetchWebsiteResponse($url);
|
||||
writeMonitorData($name, $responseData);
|
||||
} catch (Exception $e) {
|
||||
error_log("Monitoring error for $name: " . $e->getMessage());
|
||||
}
|
||||
}
|
4
monitors.json
Normal file
4
monitors.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"forum.hhf.technology": "https:\/\/forum.hhf.technology",
|
||||
"git.hhf.technology": "https:\/\/git.hhf.technology"
|
||||
}
|
302
monitors/forum.hhf.technology
Normal file
302
monitors/forum.hhf.technology
Normal file
|
@ -0,0 +1,302 @@
|
|||
[
|
||||
{
|
||||
"timestamp": 1671749821,
|
||||
"time": 4.192,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671749881,
|
||||
"time": 6.308,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671749941,
|
||||
"time": 4.439,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750002,
|
||||
"time": 4.309,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750061,
|
||||
"time": 4.195,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750121,
|
||||
"time": 4.228,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750181,
|
||||
"time": 4.366,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750241,
|
||||
"time": 4.25,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750301,
|
||||
"time": 4.322,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750361,
|
||||
"time": 4.349,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750421,
|
||||
"time": 4.231,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750481,
|
||||
"time": 5.795,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750541,
|
||||
"time": 4.355,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750601,
|
||||
"time": 4.224,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750661,
|
||||
"time": 4.213,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750721,
|
||||
"time": 4.274,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750781,
|
||||
"time": 4.258,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750841,
|
||||
"time": 4.24,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750901,
|
||||
"time": 4.22,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750961,
|
||||
"time": 4.23,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751021,
|
||||
"time": 4.24,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751081,
|
||||
"time": 4.225,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751141,
|
||||
"time": 4.199,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751201,
|
||||
"time": 4.263,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751261,
|
||||
"time": 4.195,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751321,
|
||||
"time": 4.291,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751381,
|
||||
"time": 4.14,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751441,
|
||||
"time": 4.214,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751501,
|
||||
"time": 4.306,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751561,
|
||||
"time": 4.233,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751621,
|
||||
"time": 4.39,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751681,
|
||||
"time": 4.415,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751741,
|
||||
"time": 4.291,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751801,
|
||||
"time": 4.284,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751861,
|
||||
"time": 4.364,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751921,
|
||||
"time": 4.24,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751981,
|
||||
"time": 4.269,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752041,
|
||||
"time": 4.326,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752101,
|
||||
"time": 4.256,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752161,
|
||||
"time": 4.227,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752221,
|
||||
"time": 4.248,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752281,
|
||||
"time": 4.349,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752341,
|
||||
"time": 4.274,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752401,
|
||||
"time": 4.27,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752461,
|
||||
"time": 4.497,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752521,
|
||||
"time": 4.136,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752581,
|
||||
"time": 4.397,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752641,
|
||||
"time": 4.224,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752701,
|
||||
"time": 4.241,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752761,
|
||||
"time": 4.248,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752821,
|
||||
"time": 4.517,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752881,
|
||||
"time": 4.381,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752941,
|
||||
"time": 4.371,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753001,
|
||||
"time": 4.626,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753061,
|
||||
"time": 6.07,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753121,
|
||||
"time": 4.455,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753181,
|
||||
"time": 4.373,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753241,
|
||||
"time": 4.184,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753301,
|
||||
"time": 4.685,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753361,
|
||||
"time": 7.862,
|
||||
"response": 200
|
||||
}
|
||||
]
|
302
monitors/git.hhf.technology
Normal file
302
monitors/git.hhf.technology
Normal file
|
@ -0,0 +1,302 @@
|
|||
[
|
||||
{
|
||||
"timestamp": 1671749821,
|
||||
"time": 225.4,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671749881,
|
||||
"time": 215.243,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671749941,
|
||||
"time": 232.023,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750002,
|
||||
"time": 219.797,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750061,
|
||||
"time": 228.624,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750121,
|
||||
"time": 226.033,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750181,
|
||||
"time": 224.71,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750241,
|
||||
"time": 239.184,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750301,
|
||||
"time": 223.836,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750361,
|
||||
"time": 224.607,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750421,
|
||||
"time": 220.908,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750481,
|
||||
"time": 229.849,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750541,
|
||||
"time": 238.873,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750601,
|
||||
"time": 224.467,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750661,
|
||||
"time": 215.798,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750721,
|
||||
"time": 225.466,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750781,
|
||||
"time": 209.352,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750841,
|
||||
"time": 225.638,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750901,
|
||||
"time": 228.026,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671750961,
|
||||
"time": 215.496,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751021,
|
||||
"time": 238.666,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751081,
|
||||
"time": 218.908,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751141,
|
||||
"time": 216.086,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751201,
|
||||
"time": 225.955,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751261,
|
||||
"time": 224.078,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751321,
|
||||
"time": 223.235,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751381,
|
||||
"time": 226.129,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751441,
|
||||
"time": 228.306,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751501,
|
||||
"time": 212.357,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751561,
|
||||
"time": 239.809,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751621,
|
||||
"time": 237.583,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751681,
|
||||
"time": 224.485,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751741,
|
||||
"time": 235.199,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751801,
|
||||
"time": 239.203,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751861,
|
||||
"time": 239.08,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751921,
|
||||
"time": 221.216,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671751981,
|
||||
"time": 220.381,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752041,
|
||||
"time": 219.535,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752101,
|
||||
"time": 218.568,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752161,
|
||||
"time": 225.099,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752221,
|
||||
"time": 233.544,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752281,
|
||||
"time": 231.748,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752341,
|
||||
"time": 219.454,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752401,
|
||||
"time": 236.159,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752462,
|
||||
"time": 220.944,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752521,
|
||||
"time": 209.714,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752581,
|
||||
"time": 240.137,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752641,
|
||||
"time": 239.22,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752701,
|
||||
"time": 238.772,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752761,
|
||||
"time": 223.05,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752821,
|
||||
"time": 223.467,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752881,
|
||||
"time": 230.855,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671752941,
|
||||
"time": 223.755,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753001,
|
||||
"time": 218.684,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753061,
|
||||
"time": 223.834,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753121,
|
||||
"time": 219.454,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753181,
|
||||
"time": 215.982,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753241,
|
||||
"time": 225.577,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753301,
|
||||
"time": 227.389,
|
||||
"response": 200
|
||||
},
|
||||
{
|
||||
"timestamp": 1671753361,
|
||||
"time": 225.435,
|
||||
"response": 200
|
||||
}
|
||||
]
|
147
style.css
Normal file
147
style.css
Normal file
|
@ -0,0 +1,147 @@
|
|||
@import url('https://rsms.me/inter/inter.css');
|
||||
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css');
|
||||
|
||||
* {
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background:#212529;
|
||||
color: #f8f9fa;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.incident {
|
||||
background: #000;
|
||||
padding: .1em 1em;
|
||||
margin: 0 0 1em 0;
|
||||
border-radius: .25em;
|
||||
}
|
||||
|
||||
.post {
|
||||
background: #495057;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.notice {
|
||||
background: #ffd43b;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.alert {
|
||||
background: #c92a2a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.item p, .item p a {
|
||||
margin: 0;
|
||||
color: #aaa;
|
||||
font-size: 95%;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
canvas:hover {
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 40em;
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin: 0 0 3em 0;
|
||||
color: #888;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
a:link, a:visited, a:hover, a:active {
|
||||
color: #f8f9fa;
|
||||
}
|
||||
|
||||
#status {
|
||||
background: #343a40;
|
||||
border-radius: .25em;
|
||||
padding: .1em 1em;
|
||||
}
|
||||
|
||||
#notice {
|
||||
background: #ffec99;
|
||||
color: #000;
|
||||
border-radius: .25em;
|
||||
padding: .1em 1em;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #868e96;
|
||||
font-weight: normal;
|
||||
font-size: 65%;
|
||||
font-family: monospace;
|
||||
padding-left: .7em;
|
||||
}
|
||||
|
||||
.bloops {
|
||||
margin: .5em 0;
|
||||
}
|
||||
|
||||
.bloop {
|
||||
background: #555;
|
||||
width: calc((100% / 60) - 2px);
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
margin-right: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.bloop:hover {
|
||||
opacity: 0.5;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.bloops p {
|
||||
background: #495057;
|
||||
padding: .5em;
|
||||
border-radius: .25em;
|
||||
}
|
||||
|
||||
.bloop.good { background: #51cf66; }
|
||||
.bloop.bad { background: #fa5252; }
|
||||
.bloop.meh { background: #ffd43b; }
|
||||
|
||||
.item {
|
||||
margin: 3em 0;
|
||||
}
|
||||
|
||||
.good { color: #51cf66; }
|
||||
.bad { color: #fa5252; }
|
||||
.meh { color: #ffd43b; }
|
||||
|
||||
.bloops {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bloop_info {
|
||||
position: absolute;
|
||||
top: 2em; left: 0em;
|
||||
background: #fff;
|
||||
padding: 1em;
|
||||
opacity: 1;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
@media (max-width: 500px) {
|
||||
body {
|
||||
font-size: 1em;
|
||||
}
|
||||
}
|
1
updates/forum.hhf.technology.md
Normal file
1
updates/forum.hhf.technology.md
Normal file
|
@ -0,0 +1 @@
|
|||
hhf.technology, your go-to community for all things technology!.
|
1
updates/git.hhf.technology.md
Normal file
1
updates/git.hhf.technology.md
Normal file
|
@ -0,0 +1 @@
|
|||
HHF Technology Repository: A painless, self-hosted Git service.
|
Loading…
Reference in a new issue