A app/Console/Commands/MonitorList.php => app/Console/Commands/MonitorList.php +49 -0
@@ 0,0 1,49 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\ProbeInstance;
+use Illuminate\Console\Command;
+
+class MonitorList extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'monitor:list';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'List registerd probes and monitored objects';
+
+ /**
+ * Create a new command instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $headers = ['ID', 'Description'];
+ $rows = [];
+ foreach (ProbeInstance::all() as $probe_instance) {
+ $probe = unserialize($probe_instance->probe);
+ $rows[] = [$probe_instance->id, $probe->describe()];
+ }
+ $this->table($headers, $rows);
+ }
+}
A app/Console/Commands/MonitorProbe.php => app/Console/Commands/MonitorProbe.php +60 -0
@@ 0,0 1,60 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\ProbeInstance;
+use App\ProbeLog;
+use Exception;
+use Illuminate\Console\Command;
+
+class MonitorProbe extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'monitor:probe';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Perform probes';
+
+ /**
+ * Create a new command instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ foreach (ProbeInstance::all() as $probe_instance) {
+ $probe = unserialize($probe_instance->probe);
+ try {
+ $probe->execute();
+ $log = new ProbeLog();
+ $log->success = true;
+ $probe_instance->logs()->save($log);
+ } catch (Exception $e) {
+ $this->error($probe->describe() . ' failed');
+ $this->info($e->getMessage());
+ $log = new ProbeLog();
+ $log->success = false;
+ $log->outputs = $e->getMessage();
+ $probe_instance->logs()->save($log);
+ }
+ }
+ }
+}
A app/Console/Commands/MonitorRegister.php => app/Console/Commands/MonitorRegister.php +53 -0
@@ 0,0 1,53 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\ProbeInstance;
+use Illuminate\Console\Command;
+
+class MonitorRegister extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'monitor:register
+ {class : Class of the probe}
+ {args?* : Extra arguments of <class> constructor}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Register a probe';
+
+ /**
+ * Create a new command instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $class = $this->argument('class');
+ $args = $this->argument('args');
+ $probe = new $class(...$args);
+ // test round
+ $probe->execute();
+
+ $record = new ProbeInstance();
+ $record->probe = serialize($probe);
+ $record->save();
+ }
+}
A app/Exceptions/StreamSocketException.php => app/Exceptions/StreamSocketException.php +21 -0
@@ 0,0 1,21 @@
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+
+class StreamSocketException extends Exception
+{
+ public function __construct($url, $errno, $errstr)
+ {
+ if (!$errstr) {
+ if (!$errno) {
+ $this->message = "Connection to $url failed: Unknown error";
+ } else {
+ $this->message = "Connection to $url failed: $errno";
+ }
+ } else {
+ $this->message = "Connection to $url failed: $errstr";
+ }
+ }
+}
A app/Exceptions/TcpException.php => app/Exceptions/TcpException.php +13 -0
@@ 0,0 1,13 @@
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+
+class TcpException extends Exception
+{
+ public function __construct($address, $port, $socket_errno)
+ {
+ $this->message = "TCP connection to $address:$port failed: " . socket_strerror($socket_errno);
+ }
+}
A app/Exceptions/UnexpectedHttpStatusCodeException.php => app/Exceptions/UnexpectedHttpStatusCodeException.php +13 -0
@@ 0,0 1,13 @@
+<?php
+
+namespace App\Exceptions;
+
+use Exception;
+
+class UnexpectedHttpStatusCodeException extends Exception
+{
+ public function __construct($url, $expected_code, $result_code)
+ {
+ $this->message = "Unexpected status code at $url, expected: $expected_code, got: $result_code";
+ }
+}
A app/Http/Controllers/UptimeController.php => app/Http/Controllers/UptimeController.php +58 -0
@@ 0,0 1,58 @@
+<?php
+
+namespace App\Http\Controllers;
+
+use App\ProbeInstance;
+use Carbon\Carbon;
+use Carbon\CarbonInterval;
+use Illuminate\Http\Request;
+
+class UptimeController extends Controller
+{
+ public function show()
+ {
+ $to = Carbon::now();
+ $from = Carbon::now()->subDays(7);
+ $timeslot = CarbonInterval::minutes(90);
+ $instances = ProbeInstance::all();
+ $data = [];
+ foreach ($instances as $instance) {
+ $probe = unserialize($instance->probe);
+
+ $raw_logs = $instance->logs()->where('created_at', '>=', $from)->orderBy('created_at')->get();
+ $raw_index = 0;
+ $raw_length = count($raw_logs);
+ $logs = [];
+ for ($i = $from->copy(); $i < $to; $i->add($timeslot)) {
+ $localto = $i->copy()->add($timeslot);
+
+ $known = false;
+ $up = true;
+ $info = 'Succeeded';
+ while ($raw_index < $raw_length &&
+ $raw_logs[$raw_index]->created_at < $localto) {
+ $known = true;
+ if ($raw_logs[$raw_index]->success == false) {
+ $up = false;
+ $info = 'Failed';
+ }
+ $raw_index++;
+ }
+
+ $logs[] = [
+ 'from' => $i->copy(),
+ 'to' => $localto,
+ 'known' => $known,
+ 'up' => $up,
+ 'info' =>'',
+ ];
+ }
+
+ $data[] = [
+ 'description' => $probe->describe(),
+ 'logs' => $logs,
+ ];
+ }
+ return view('uptime', [ 'data' => $data, 'from' => $from, 'to' => $to ]);
+ }
+}
M app/Http/Kernel.php => app/Http/Kernel.php +0 -1
@@ 14,7 14,6 @@ class Kernel extends HttpKernel
* @var array
*/
protected $middleware = [
- \App\Http\Middleware\TrustProxies::class,
\App\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
D app/Http/Middleware/TrustProxies.php => app/Http/Middleware/TrustProxies.php +0 -23
@@ 1,23 0,0 @@
-<?php
-
-namespace App\Http\Middleware;
-
-use Fideloper\Proxy\TrustProxies as Middleware;
-use Illuminate\Http\Request;
-
-class TrustProxies extends Middleware
-{
- /**
- * The trusted proxies for this application.
- *
- * @var array|string
- */
- protected $proxies;
-
- /**
- * The headers that should be used to detect proxies.
- *
- * @var int
- */
- protected $headers = Request::HEADER_X_FORWARDED_ALL;
-}
A app/Interfaces/Probe.php => app/Interfaces/Probe.php +9 -0
@@ 0,0 1,9 @@
+<?php
+
+namespace App\Interfaces;
+
+interface Probe
+{
+ public function execute();
+ public function describe();
+}
A app/ProbeInstance.php => app/ProbeInstance.php +13 -0
@@ 0,0 1,13 @@
+<?php
+
+namespace App;
+
+use Illuminate\Database\Eloquent\Model;
+
+class ProbeInstance extends Model
+{
+ public function logs()
+ {
+ return $this->hasMany('App\ProbeLog');
+ }
+}
A app/ProbeLog.php => app/ProbeLog.php +13 -0
@@ 0,0 1,13 @@
+<?php
+
+namespace App;
+
+use Illuminate\Database\Eloquent\Model;
+
+class ProbeLog extends Model
+{
+ public function instance()
+ {
+ return $this->belongsTo('App\ProbeInstance');
+ }
+}
A app/Probes/CommandProbe.php => app/Probes/CommandProbe.php +32 -0
@@ 0,0 1,32 @@
+<?php
+
+namespace App\Probes;
+
+use App\Interfaces\Probe;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Process;
+
+class CommandProbe implements Probe
+{
+ public function __construct($command)
+ {
+ $this->command = $command;
+ }
+
+ public function execute()
+ {
+ $process = new Process($this->command);
+ $process->run();
+
+ if (!$process->isSuccessful()) {
+ throw new ProcessFailedException($process);
+ } else {
+ return true;
+ }
+ }
+
+ public function describe()
+ {
+ return "Execute command $this->command";
+ }
+}
A app/Probes/HttpProbe.php => app/Probes/HttpProbe.php +34 -0
@@ 0,0 1,34 @@
+<?php
+
+namespace App\Probes;
+
+use App\Exceptions\UnexpectedHttpStatusCodeException;
+use App\Interfaces\Probe;
+use GuzzleHttp\Client;
+
+class HttpProbe implements Probe
+{
+ public function __construct($url, int $code)
+ {
+ $this->url = $url;
+ $this->code = $code;
+ }
+
+ public function execute()
+ {
+ $client = new Client();
+ $res = $client->request(
+ 'GET',
+ $this->url,
+ ['allow_redirects' => false, 'timeout' => 1]
+ );
+ if ($res->getStatusCode() !== $this->code) {
+ throw new UnexpectedHttpStatusCodeException($this->url, $this->code, $res->getStatusCode());
+ }
+ }
+
+ public function describe()
+ {
+ return "Access $this->url";
+ }
+}
A app/Probes/PingProbe.php => app/Probes/PingProbe.php +22 -0
@@ 0,0 1,22 @@
+<?php
+
+namespace App\Probes;
+
+class PingProbe extends CommandProbe
+{
+ public function __construct($host)
+ {
+ $this->host = $host;
+ parent::__construct([
+ 'ping',
+ '-c1',
+ '-W1',
+ $host
+ ]);
+ }
+
+ public function describe()
+ {
+ return "Ping $this->host";
+ }
+}
A app/Probes/StreamSocketProbe.php => app/Probes/StreamSocketProbe.php +42 -0
@@ 0,0 1,42 @@
+<?php
+
+namespace App\Probes;
+
+use App\Exceptions\StreamSocketException;
+use App\Interfaces\Probe;
+
+class StreamSocketProbe implements Probe
+{
+ public function __construct($url)
+ {
+ $this->url = $url;
+ }
+
+ protected function connect($context = null)
+ {
+ $fp = false;
+ try {
+ $fp = stream_socket_client($this->url, $errno, $errstr, 1, STREAM_CLIENT_CONNECT, $context);
+ } catch (\ErrorException $e) {
+ if (!$errstr && $errno === 0) {
+ // Message from exceptions may be more useful
+ $errstr = $e->getMessage();
+ }
+ $fp = false;
+ }
+ if ($fp === false) {
+ throw new StreamSocketException($this->url, $errno, $errstr);
+ }
+ fclose($fp);
+ }
+
+ public function execute()
+ {
+ $this->connect();
+ }
+
+ public function describe()
+ {
+ return "Connect to $this->url";
+ }
+}
A app/Probes/TcpProbe.php => app/Probes/TcpProbe.php +43 -0
@@ 0,0 1,43 @@
+<?php
+
+namespace App\Probes;
+
+use App\Exceptions\TcpException;
+use App\Interfaces\Probe;
+
+class TcpProbe implements Probe
+{
+ public function __construct($address, $port)
+ {
+ $this->address = $address;
+ $this->port = $port;
+ }
+
+ public function execute()
+ {
+ $socket = socket_create(
+ (strpos($this->address, ':') === false) ? AF_INET : AF_INET6,
+ SOCK_STREAM,
+ SOL_TCP
+ );
+ socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 1, 'usec' => 0]);
+ socket_set_option($socket, SOL_SOCKET, SO_SNDTIMEO, ['sec' => 1, 'usec' => 0]);
+ $res = false;
+ try {
+ $res = socket_connect($socket, $this->address, $this->port);
+ } catch (\ErrorException $e) {
+ $res = false;
+ }
+ if ($res === false) {
+ $errno = socket_last_error($socket);
+ socket_close($socket);
+ throw new TcpException($this->address, $this->port, $errno);
+ }
+ socket_close($socket);
+ }
+
+ public function describe()
+ {
+ return "Establish TCP connection with $this->address:$this->port";
+ }
+}
A app/Probes/TlsProbe.php => app/Probes/TlsProbe.php +32 -0
@@ 0,0 1,32 @@
+<?php
+
+namespace App\Probes;
+
+class TlsProbe extends StreamSocketProbe
+{
+ public function __construct($address, $port, $fingerprint = null)
+ {
+ $this->address = $address;
+ $this->port = $port;
+ $this->fingerprint = $fingerprint;
+ }
+
+ public function execute()
+ {
+ $this->url = "tls://$this->address:$this->port";
+ $context = null;
+ if ($this->fingerprint) {
+ $context = stream_context_create();
+ // When we know the expected fingerprint, ignore self-signed
+ // This allows usage like in Gemini protocol
+ stream_context_set_option($context, 'ssl', 'allow_self_signed', true);
+ stream_context_set_option($context, 'ssl', 'peer_fingerprint', $this->fingerprint);
+ }
+ $this->connect($context);
+ }
+
+ public function describe()
+ {
+ return "Establish TLS connection with $this->address:$this->port";
+ }
+}
M composer.json => composer.json +1 -0
@@ 9,6 9,7 @@
"license": "MIT",
"require": {
"php": "^7.2.5|^8.0",
+ "guzzlehttp/guzzle": "^7.3",
"laravel/framework": "^6.20"
},
"require-dev": {
M composer.lock => composer.lock +361 -1
@@ 4,7 4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "c4e7cceb2aa49838798e5767909b7311",
+ "content-hash": "cccf05d5f03d2c18794c94e8107895ae",
"packages": [
{
"name": "doctrine/inflector",
@@ 298,6 298,227 @@
"time": "2020-12-29T14:50:06+00:00"
},
{
+ "name": "guzzlehttp/guzzle",
+ "version": "7.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/guzzle.git",
+ "reference": "7008573787b430c1c1f650e3722d9bba59967628"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628",
+ "reference": "7008573787b430c1c1f650e3722d9bba59967628",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "guzzlehttp/promises": "^1.4",
+ "guzzlehttp/psr7": "^1.7 || ^2.0",
+ "php": "^7.2.5 || ^8.0",
+ "psr/http-client": "^1.0"
+ },
+ "provide": {
+ "psr/http-client-implementation": "1.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.4.1",
+ "ext-curl": "*",
+ "php-http/client-integration-tests": "^3.0",
+ "phpunit/phpunit": "^8.5.5 || ^9.3.5",
+ "psr/log": "^1.1"
+ },
+ "suggest": {
+ "ext-curl": "Required for CURL handler support",
+ "ext-intl": "Required for Internationalized Domain Name (IDN) support",
+ "psr/log": "Required for using the Log middleware"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "7.3-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Márk Sági-Kazár",
+ "email": "mark.sagikazar@gmail.com",
+ "homepage": "https://sagikazarmark.hu"
+ }
+ ],
+ "description": "Guzzle is a PHP HTTP client library",
+ "homepage": "http://guzzlephp.org/",
+ "keywords": [
+ "client",
+ "curl",
+ "framework",
+ "http",
+ "http client",
+ "psr-18",
+ "psr-7",
+ "rest",
+ "web service"
+ ],
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/Nyholm",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/alexeyshockov",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/gmponos",
+ "type": "github"
+ }
+ ],
+ "time": "2021-03-23T11:33:13+00:00"
+ },
+ {
+ "name": "guzzlehttp/promises",
+ "version": "1.4.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/promises.git",
+ "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d",
+ "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.5"
+ },
+ "require-dev": {
+ "symfony/phpunit-bridge": "^4.4 || ^5.1"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Promise\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ }
+ ],
+ "description": "Guzzle promises library",
+ "keywords": [
+ "promise"
+ ],
+ "time": "2021-03-07T09:25:29+00:00"
+ },
+ {
+ "name": "guzzlehttp/psr7",
+ "version": "1.8.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/guzzle/psr7.git",
+ "reference": "dc960a912984efb74d0a90222870c72c87f10c91"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91",
+ "reference": "dc960a912984efb74d0a90222870c72c87f10c91",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.4.0",
+ "psr/http-message": "~1.0",
+ "ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
+ },
+ "provide": {
+ "psr/http-message-implementation": "1.0"
+ },
+ "require-dev": {
+ "ext-zlib": "*",
+ "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
+ },
+ "suggest": {
+ "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.7-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "GuzzleHttp\\Psr7\\": "src/"
+ },
+ "files": [
+ "src/functions_include.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Michael Dowling",
+ "email": "mtdowling@gmail.com",
+ "homepage": "https://github.com/mtdowling"
+ },
+ {
+ "name": "Tobias Schultze",
+ "homepage": "https://github.com/Tobion"
+ }
+ ],
+ "description": "PSR-7 message implementation that also provides common utility methods",
+ "keywords": [
+ "http",
+ "message",
+ "psr-7",
+ "request",
+ "response",
+ "stream",
+ "uri",
+ "url"
+ ],
+ "time": "2021-04-26T09:17:50+00:00"
+ },
+ {
"name": "laravel/framework",
"version": "v6.20.26",
"source": {
@@ 1085,6 1306,105 @@
"time": "2021-03-05T17:36:06+00:00"
},
{
+ "name": "psr/http-client",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-client.git",
+ "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+ "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.0 || ^8.0",
+ "psr/http-message": "^1.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Client\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP clients",
+ "homepage": "https://github.com/php-fig/http-client",
+ "keywords": [
+ "http",
+ "http-client",
+ "psr",
+ "psr-18"
+ ],
+ "time": "2020-06-29T06:28:15+00:00"
+ },
+ {
+ "name": "psr/http-message",
+ "version": "1.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/http-message.git",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.0.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Psr\\Http\\Message\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for HTTP messages",
+ "homepage": "https://github.com/php-fig/http-message",
+ "keywords": [
+ "http",
+ "http-message",
+ "psr",
+ "psr-7",
+ "request",
+ "response"
+ ],
+ "time": "2016-08-06T14:39:51+00:00"
+ },
+ {
"name": "psr/log",
"version": "1.1.3",
"source": {
@@ 1180,6 1500,46 @@
"time": "2017-10-23T01:57:42+00:00"
},
{
+ "name": "ralouphie/getallheaders",
+ "version": "3.0.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/ralouphie/getallheaders.git",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
+ "reference": "120b605dfeb996808c31b6477290a714d356e822",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.6"
+ },
+ "require-dev": {
+ "php-coveralls/php-coveralls": "^2.1",
+ "phpunit/phpunit": "^5 || ^6.5"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/getallheaders.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ralph Khattar",
+ "email": "ralph.khattar@gmail.com"
+ }
+ ],
+ "description": "A polyfill for getallheaders.",
+ "time": "2019-03-08T08:55:37+00:00"
+ },
+ {
"name": "ramsey/uuid",
"version": "3.9.3",
"source": {
A database/migrations/2021_07_24_064634_create_probe_instances.php => database/migrations/2021_07_24_064634_create_probe_instances.php +32 -0
@@ 0,0 1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateProbeInstances extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create('probe_instances', function (Blueprint $table) {
+ $table->bigIncrements('id');
+ $table->string('probe');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('probe_instances');
+ }
+}
A database/migrations/2021_07_24_073612_create_probe_logs.php => database/migrations/2021_07_24_073612_create_probe_logs.php +36 -0
@@ 0,0 1,36 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+class CreateProbeLogs extends Migration
+{
+ /**
+ * Run the migrations.
+ *
+ * @return void
+ */
+ public function up()
+ {
+ Schema::create('probe_logs', function (Blueprint $table) {
+ $table->bigIncrements('id');
+ $table->unsignedBigInteger('probe_instance_id');
+ $table->foreign('probe_instance_id')->references('id')
+ ->on('probe_instances')->onDelete('cascade');
+ $table->boolean('success');
+ $table->string('outputs')->nullable();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('probe_logs');
+ }
+}
A resources/views/uptime.blade.php => resources/views/uptime.blade.php +82 -0
@@ 0,0 1,82 @@
+<!DOCTYPE html>
+<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>Laravel</title>
+ <style>
+ body {
+ background-color: #fafafa;
+ font-family: sans-serif;
+ }
+
+ .card {
+ display: inline-block;
+ border-radius: 2px;
+ background-color: white;
+ box-shadow: 0 1px 1px 0 rgba(60,64,67,.08),0 1px 3px 1px rgba(60,64,67,.16);
+ padding: 16px;
+ margin: 16px;
+ //overflow: auto;
+ }
+
+ .uptime-view {
+ width: 320px;
+ margin-top: 8px;
+ }
+
+ .uptime-from, .uptime-to {
+ font-size: 10px;
+ color: #aaa;
+ }
+
+ .uptime-to {
+ text-align: right;
+ }
+
+ .uptime-grid {
+ display: grid;
+ column-gap: 1%;
+ row-gap: 2px;
+ grid-template-rows: repeat(4, 1fr);
+ grid-auto-flow: column;
+ margin: 4px 0;
+ }
+
+ .uptime-item {
+ width: 8px;
+ height: 8px;
+ }
+
+ .up {
+ background: #0f0;
+ }
+
+ .down {
+ background: #f00;
+ }
+
+ .unknown {
+ background: #ccc;
+ }
+ </style>
+ </head>
+ <body>
+ @foreach ($data as $instance)
+ <div class="card">
+ {{ $instance['description'] }}
+ <div class="uptime-view">
+ <div class="uptime-from">{{ $instance['logs'][0]['from'] }}</div>
+ <div class="uptime-grid">
+ @foreach ($instance['logs'] as $log)
+ <div class="uptime-item {{ $log['known'] ? $log['up'] ? 'up' : 'down' : 'unknown'}}"
+ title="{{ "{$log['from']} to {$log['to']}" }}">
+ </div>
+ @endforeach
+ </div>
+ <div class="uptime-to">{{ $instance['logs'][count($instance['logs']) - 1]['to'] }}</div>
+ </div>
+ </div>
+ @endforeach
+ </body>
+</html>
M routes/web.php => routes/web.php +1 -3
@@ 11,6 11,4 @@
|
*/
-Route::get('/', function () {
- return view('welcome');
-});
+Route::get('/', 'UptimeController@show');