Compare commits
18 commits
Author | SHA1 | Date | |
---|---|---|---|
28f1bcfce9 | |||
9fe028e1cc | |||
ec4fb68630 | |||
42fe9281f7 | |||
3daa19f2de | |||
25f5a8800d | |||
1414c83cc9 | |||
78dc7baa73 | |||
83d401daa2 | |||
1996b4fb32 | |||
5986875818 | |||
80ba45b83b | |||
f2139a7b01 | |||
42f1187776 | |||
dde679411a | |||
f1e1cbfdd9 | |||
d9c2c6f5f6 | |||
24ed50d9d2 |
31 changed files with 536 additions and 3353 deletions
|
@ -7,8 +7,10 @@ I figured it could be useful for others so I **rewrote** and **updated** it from
|
|||
## What it does
|
||||
|
||||
**MonitoLite** is a very simple monitoring tool developed in PHP powered by Lumen (by Laravel). It supports :
|
||||
* **ping monitoring**: sends a `ping` command to the specified host. Raises an alert if the host is down
|
||||
* **http monitoring**: requests the provided URL and raises an alert if the URL returns an error. Optionally you may specify a string to search on the page using the `param` database field. It raises an alert if the specified text could not be found on the page.
|
||||
* **PING monitoring**: sends a `ping` command to the specified host. Raises an alert if the host is down
|
||||
* **HTTP monitoring**: requests the provided URL and raises an alert if the URL returns an error. Optionally you may specify a string to search on the page using the `param` database field. It raises an alert if the specified text could not be found on the page.
|
||||
* **FTP monitoring**: connects to the provided FTP server as anonymous (authentication not supported yet).
|
||||
* **DNS monitoring**: runs a DNS lookup on a given DNS server for the hostname specified in the params
|
||||
|
||||
In case of an alert, the script sends an email notifications to the specified contacts (one or many).
|
||||
The script also sends a recovery email notification when the alert is over.
|
||||
|
|
|
@ -119,12 +119,20 @@ class RunMonitoring extends Command
|
|||
break;
|
||||
|
||||
case 'http':
|
||||
$result = $this->checkHttp($task);
|
||||
$result = $this->checkRequest($task, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||
break;
|
||||
|
||||
case 'ftp':
|
||||
$result = $this->checkRequest($task, CURLPROTO_FTP | CURLPROTO_FTPS);
|
||||
break;
|
||||
|
||||
case 'dns':
|
||||
$result = $this->checkDns($task);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Nothing to do here
|
||||
continue 2;
|
||||
throw new Exception('Unknown type "'.$task->type.'"');
|
||||
}
|
||||
|
||||
$new_status = 1;
|
||||
|
@ -135,7 +143,8 @@ class RunMonitoring extends Command
|
|||
}
|
||||
catch(Exception $e) {
|
||||
//TODO: handle system exception differently
|
||||
$history = $this->saveHistory($task, false, $e->getMessage());
|
||||
//$history = $this->saveHistory($task, false, $e->getMessage());
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
finally {
|
||||
// Changing task timestamps and status
|
||||
|
@ -250,17 +259,43 @@ class RunMonitoring extends Command
|
|||
return true;
|
||||
}
|
||||
|
||||
final private function checkHttp(Task $task) {
|
||||
final private function checkDns(Task $task) {
|
||||
if (! function_exists('exec') || ! is_callable('exec')) {
|
||||
throw new MonitoringException('The "exec" command is required');
|
||||
}
|
||||
|
||||
if (is_null($task->params) || empty($task->params)) {
|
||||
throw new Exception('Params are required');
|
||||
}
|
||||
|
||||
$cmd = 'nslookup '.trim($task->params).' '.$task->host;
|
||||
|
||||
// If command failed
|
||||
if (false === $exec = exec($cmd.' '.$task->host, $output, $code)) {
|
||||
throw new MonitoringException('Unable to execute DNS lookup');
|
||||
}
|
||||
|
||||
// If command returned a non-zero code
|
||||
if ($code > 0) {
|
||||
throw new MonitoringException('DNS lookup task failed ('.$exec.')');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
final private function checkRequest(Task $task, $protocol = CURLPROTO_HTTP | CURLPROTO_HTTPS) {
|
||||
if (app()->environment() == 'local') {
|
||||
//throw new MonitoringException('Forcing error for testing');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Preparing cURL
|
||||
$opts = [
|
||||
CURLOPT_HEADER => true,
|
||||
CURLOPT_HTTPGET => true,
|
||||
CURLOPT_FRESH_CONNECT => true,
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
|
||||
CURLOPT_PROTOCOLS => $protocol,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
|
|
|
@ -114,7 +114,7 @@ class SyncCustomers extends Command
|
|||
'type' => 'http',
|
||||
'params' => 'restovisio.com',
|
||||
'creation_date' => date('Y-m-d H:i:s'),
|
||||
'frequency' => 600,
|
||||
'frequency' => 3600,
|
||||
'active' => 1,
|
||||
'group_id' => $c->id
|
||||
]);
|
||||
|
|
|
@ -26,14 +26,14 @@ class ApiController extends Controller
|
|||
|
||||
$query = Task
|
||||
::leftJoin('groups', 'groups.id', 'tasks.group_id')
|
||||
->leftJoin('task_history', 'task_id', 'task_history.id')
|
||||
->select(
|
||||
'tasks.id', 'tasks.host', 'tasks.status', 'tasks.type', 'tasks.params', 'tasks.frequency', 'tasks.created_at', 'tasks.executed_at', 'tasks.active', 'tasks.group_id',
|
||||
'task_history.output',
|
||||
'groups.name as group_name')
|
||||
->get()
|
||||
;
|
||||
|
||||
//dd($query->toSql());
|
||||
|
||||
foreach ($query as $t) {
|
||||
if (is_null($t->group_id)) {
|
||||
$group_id = $t->id;
|
||||
|
@ -61,7 +61,7 @@ class ApiController extends Controller
|
|||
$days = ($request->input('days', 15) - 1);
|
||||
|
||||
$task = Task::with(['group'])
|
||||
->find($id)
|
||||
->findOrFail($id)
|
||||
;
|
||||
|
||||
if (! is_null($task)) {
|
||||
|
@ -133,7 +133,7 @@ class ApiController extends Controller
|
|||
// Getting the notifications sent
|
||||
$notifications = $task
|
||||
->notifications()
|
||||
->with('contact')
|
||||
->with(['contact', 'task_history'])
|
||||
->where('notifications.created_at', '>', $first_day->toDateString())
|
||||
->orderBy('notifications.created_at', 'desc')
|
||||
->get()
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateContactTaskTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('contact_task', function (Blueprint $table) {
|
||||
$table->bigInteger('task_id')->unsigned();
|
||||
$table->bigInteger('contact_id')->unsigned();
|
||||
$table->primary(['task_id', 'contact_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('contact_task');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateContactsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('contacts', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('surname', 200);
|
||||
$table->string('firstname', 200);
|
||||
$table->string('email', 250);
|
||||
$table->string('phone', 20);
|
||||
$table->timestamps();
|
||||
$table->tinyInteger('active')->default(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('contacts');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateGroupsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('groups', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('name', 128)->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('groups');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateNotificationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('notifications', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedBigInteger('contact_id');
|
||||
$table->unsignedBigInteger('task_history_id');
|
||||
$table->enum('status', ['pending', 'sent', 'error'])->default('pending');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('notifications');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateTaskHistoryTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('task_history', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedTinyInteger('status');
|
||||
$table->text('output')->nullable();
|
||||
$table->float('duration', 5, 3)->unsigned()->nullable();
|
||||
$table->unsignedBigInteger('task_id');
|
||||
$table->timestamps();
|
||||
$table->index(['status', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('task_history');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateTasksArchivesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tasks_archives', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->date('day');
|
||||
$table->unsignedInteger('uptime')->default('0');
|
||||
$table->unsignedBigInteger('task_id')->default('0');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tasks_archives');
|
||||
}
|
||||
}
|
42
database/migrations/2021_12_28_131705_create_tasks_table.php
Normal file
42
database/migrations/2021_12_28_131705_create_tasks_table.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateTasksTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tasks', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('host');
|
||||
$table->enum('type', ['ping', 'http', 'dns', 'ftp']);
|
||||
$table->string('params')->nullable();
|
||||
$table->unsignedInteger('frequency');
|
||||
$table->unsignedTinyInteger('attempts')->default('0');
|
||||
$table->unsignedTinyInteger('active')->default(1);
|
||||
$table->unsignedTinyInteger('status')->nullable();
|
||||
$table->unsignedBigInteger('group_id')->nullable();
|
||||
$table->timestamps();
|
||||
$table->timestamp('executed_at')->nullable();
|
||||
|
||||
$table->unique(['host', 'type']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tasks');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddForeignKeysToContactTaskTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('contact_task', function (Blueprint $table) {
|
||||
$table->foreign(['task_id'], 'contact_task_ibfk_1')->references(['id'])->on('tasks')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
$table->foreign(['contact_id'], 'contact_task_ibfk_2')->references(['id'])->on('contacts')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('contact_task', function (Blueprint $table) {
|
||||
$table->dropForeign('contact_task_ibfk_1');
|
||||
$table->dropForeign('contact_task_ibfk_2');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddForeignKeysToNotificationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('notifications', function (Blueprint $table) {
|
||||
$table->foreign(['contact_id'], 'contact_id_frgn')->references(['id'])->on('contacts')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
$table->foreign(['task_history_id'], 'task_history_id_frgn')->references(['id'])->on('task_history')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('notifications', function (Blueprint $table) {
|
||||
$table->dropForeign('contact_id_frgn');
|
||||
$table->dropForeign('task_history_id_frgn');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddForeignKeysToTaskHistoryTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('task_history', function (Blueprint $table) {
|
||||
$table->foreign(['task_id'], 'task_history_ibfk_1')->references(['id'])->on('tasks')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('task_history', function (Blueprint $table) {
|
||||
$table->dropForeign('task_history_ibfk_1');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddForeignKeysToTasksTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tasks', function (Blueprint $table) {
|
||||
$table->foreign(['group_id'], 'group_id_frgn')->references(['id'])->on('groups')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('tasks', function (Blueprint $table) {
|
||||
$table->dropForeign('group_id_frgn');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,128 +0,0 @@
|
|||
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
|
||||
/*!40103 SET TIME_ZONE='+00:00' */;
|
||||
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
|
||||
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
|
||||
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
|
||||
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
|
||||
DROP TABLE IF EXISTS `contact_task`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `contact_task` (
|
||||
`task_id` int unsigned NOT NULL,
|
||||
`contact_id` int unsigned NOT NULL,
|
||||
PRIMARY KEY (`task_id`,`contact_id`),
|
||||
KEY `contact_id` (`contact_id`),
|
||||
CONSTRAINT `contact_task_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `contact_task_ibfk_2` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `contacts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `contacts` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`surname` varchar(200) NOT NULL,
|
||||
`firstname` varchar(200) NOT NULL,
|
||||
`email` varchar(250) NOT NULL,
|
||||
`phone` varchar(20) NOT NULL,
|
||||
`creation_date` datetime NOT NULL,
|
||||
`active` int NOT NULL DEFAULT '1',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `groups`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `groups` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(128) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `migrations`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `migrations` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`migration` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`batch` int NOT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `notifications`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `notifications` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`contact_id` int unsigned NOT NULL DEFAULT '0',
|
||||
`task_history_id` int unsigned NOT NULL DEFAULT '0',
|
||||
`status` enum('pending','sent','error') CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL DEFAULT 'pending',
|
||||
`created_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:01',
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `contact_id_frgn` (`contact_id`),
|
||||
KEY `task_history_id_frgn` (`task_history_id`),
|
||||
CONSTRAINT `contact_id_frgn` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `task_history_id_frgn` FOREIGN KEY (`task_history_id`) REFERENCES `task_history` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `task_history`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `task_history` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`status` int unsigned NOT NULL,
|
||||
`output` text CHARACTER SET utf8 COLLATE utf8_general_ci,
|
||||
`duration` float(5,3) unsigned DEFAULT NULL,
|
||||
`task_id` int unsigned NOT NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:01',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `task_id` (`task_id`),
|
||||
KEY `status` (`status`,`created_at`) USING BTREE,
|
||||
CONSTRAINT `task_history_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `tasks`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tasks` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`host` varchar(255) NOT NULL,
|
||||
`type` enum('ping','http') NOT NULL,
|
||||
`params` varchar(255) NOT NULL,
|
||||
`frequency` int unsigned NOT NULL,
|
||||
`attempts` int unsigned NOT NULL DEFAULT '0',
|
||||
`active` int NOT NULL DEFAULT '0',
|
||||
`status` int unsigned DEFAULT NULL,
|
||||
`group_id` int unsigned DEFAULT NULL,
|
||||
`created_at` datetime NOT NULL,
|
||||
`updated_at` datetime DEFAULT NULL,
|
||||
`executed_at` datetime DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `host` (`host`,`type`),
|
||||
KEY `group_id_frgn` (`group_id`),
|
||||
KEY `attempts` (`attempts`) USING BTREE,
|
||||
CONSTRAINT `group_id_frgn` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
DROP TABLE IF EXISTS `tasks_archives`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tasks_archives` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`day` date NOT NULL,
|
||||
`uptime` int unsigned NOT NULL DEFAULT '0',
|
||||
`task_id` int unsigned NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
|
||||
|
||||
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
|
||||
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
|
||||
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
INSERT INTO `migrations` VALUES (1,'2021_12_21_101954_create_jobs_table',1);
|
||||
INSERT INTO `migrations` VALUES (2,'2021_12_21_102355_create_failed_jobs_table',2);
|
350
monitolite.pl
350
monitolite.pl
|
@ -1,350 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
################################
|
||||
# #
|
||||
# M O N I T O L I T E #
|
||||
# #
|
||||
# Lightweight Monitoring Tool #
|
||||
# #
|
||||
# @author: Axel de Vignon #
|
||||
# @copyright: www.vidax.net #
|
||||
# @license: Mozilla Public 1.1 #
|
||||
# #
|
||||
################################
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
use DBI;
|
||||
use Dotenv;
|
||||
use Net::Ping;
|
||||
|
||||
use Email::MIME;
|
||||
use Email::Sender::Simple qw(sendmail);
|
||||
use Email::Sender::Transport::SMTP qw();
|
||||
|
||||
use LWP::Simple;
|
||||
use LWP::UserAgent;
|
||||
use LWP::Protocol::https;
|
||||
|
||||
my $query;
|
||||
my $result;
|
||||
my $tasks;
|
||||
my $update_query;
|
||||
my $emails;
|
||||
my $email;
|
||||
my $message;
|
||||
my $response;
|
||||
my $html;
|
||||
my $numtasks;
|
||||
my $previous_status;
|
||||
my $subject;
|
||||
my $datas;
|
||||
|
||||
|
||||
############################
|
||||
# #
|
||||
# S E T T I N G S #
|
||||
# #
|
||||
############################
|
||||
|
||||
Dotenv->load;
|
||||
|
||||
my $dbtype = $ENV{'DB_CONNECTION'};
|
||||
my $hostname = $ENV{'DB_HOST'};
|
||||
my $database = $ENV{'DB_DATABASE'};
|
||||
my $login = $ENV{'DB_USERNAME'};
|
||||
my $port = $ENV{'DB_PORT'};
|
||||
my $password = $ENV{'DB_PASSWORD'};
|
||||
my $email_from = $ENV{'MAIL_FROM'};
|
||||
my $number_tries = $ENV{'NB_TRIES'};
|
||||
my $days_history_archive = $ENV{'ARCHIVE_DAYS'};
|
||||
my $smtp_host = $ENV{'SMTP_HOST'};
|
||||
my $smtp_user = $ENV{'SMTP_USER'};
|
||||
my $smtp_password = $ENV{'SMTP_PASSWORD'};
|
||||
my $smtp_port = $ENV{'SMTP_PORT'};
|
||||
my $smtp_ssl = $ENV{'SMTP_SSL'};
|
||||
|
||||
|
||||
|
||||
############################
|
||||
|
||||
######
|
||||
# Testing database connection
|
||||
######
|
||||
my $dsn = "DBI:$dbtype:database=$database;host=$hostname;port=$port";
|
||||
my $dbh = DBI->connect($dsn, $login, $password) or output('cannot connect to database', 'ERROR', 1);
|
||||
|
||||
|
||||
######
|
||||
# Getting tasks
|
||||
######
|
||||
my $execution_time = server_time();
|
||||
my $query1 = $dbh->prepare('SELECT id, host, type, params FROM tasks WHERE ( DATE_SUB(now(), INTERVAL frequency SECOND) > last_execution OR last_execution IS NULL ) AND active = 1');
|
||||
$query1->execute() or output('Cannot execute query fetching all pending tasks', 'ERROR', 1);
|
||||
$numtasks = $query1->rows;
|
||||
|
||||
#####
|
||||
# Processing all tasks
|
||||
#####
|
||||
if ($numtasks > 0) {
|
||||
while ($tasks = $query1->fetchrow_hashref()) {
|
||||
print "\n";
|
||||
my $status = -1;
|
||||
$previous_status = -1;
|
||||
$message = 'Host is back up';
|
||||
|
||||
####
|
||||
# Getting last history for this host
|
||||
####
|
||||
my $query2 = $dbh->prepare('SELECT status FROM tasks_history WHERE task_id = ' . $tasks->{'id'} . ' ORDER BY datetime DESC LIMIT 1');
|
||||
$query2->execute() or output('Cannot get history for this task', 'ERROR', 0);
|
||||
|
||||
if ($query2->rows > 0) {
|
||||
my $history = $query2->fetchrow_hashref();
|
||||
$previous_status = $history->{'status'};
|
||||
}
|
||||
|
||||
if ($tasks->{'type'} =~ 'ping') {
|
||||
|
||||
# Ping check returned an error
|
||||
if (! check_ping($tasks->{'host'})) {
|
||||
$status = 0;
|
||||
output('Host "'. $tasks->{'host'} .'" [' . $tasks->{'type'} . '] is down', 'ALERT');
|
||||
$message = 'Host does not reply to ping. Timed out after 5s. Giving up...';
|
||||
|
||||
}
|
||||
# Ping check went fine
|
||||
else {
|
||||
$status = 1;
|
||||
output('Host "'. $tasks->{'host'} .'" [' . $tasks->{'type'} . '] is up', 'SUCCESS');
|
||||
}
|
||||
}
|
||||
elsif ($tasks->{'type'} =~ 'http') {
|
||||
$response = check_http($tasks->{'host'}, $tasks->{'params'});
|
||||
|
||||
# HTTP check went fine
|
||||
if ($response =~ 'OK') {
|
||||
$status = 1;
|
||||
output('Host "'. $tasks->{'host'} .'" [' . $tasks->{'type'} . '] is up', 'SUCCESS');
|
||||
}
|
||||
# HTTP check returned an error
|
||||
else {
|
||||
$status = 0;
|
||||
output('Host "'. $tasks->{'host'} .'" [' . $tasks->{'type'} . '] is down', 'ALERT');
|
||||
$message = 'HTTP response was: ' . $response;
|
||||
}
|
||||
}
|
||||
else {
|
||||
output('dunno how to process this task', 'DEBUG');
|
||||
next;
|
||||
}
|
||||
|
||||
# Notify on status changes only
|
||||
if ($previous_status != -1 && $status != $previous_status) {
|
||||
output('Should send notification', 'DEBUG');
|
||||
&send_notifications($tasks->{'id'}, $tasks->{'host'}, $tasks->{'type'}, $message, $status);
|
||||
}
|
||||
|
||||
# Saving Status into DB
|
||||
if ($status >= 0) {
|
||||
save_history($tasks->{'id'}, $status, $execution_time, $response);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
output('nothing to monitor, sleeping back', 'DEBUG');
|
||||
}
|
||||
|
||||
|
||||
|
||||
#####
|
||||
# Function used for the PING test
|
||||
#####
|
||||
sub check_ping {
|
||||
my ($host, $round) = @_;
|
||||
$round = 1 if (! $round);
|
||||
|
||||
my $ping = Net::Ping->new('icmp');
|
||||
output('ping check n°' . $round . ' on ' . $host, 'DEBUG');
|
||||
|
||||
if (! $ping->ping($host)) {
|
||||
$ping->close();
|
||||
|
||||
if ($number_tries && $round <= $number_tries) {
|
||||
sleep (2);
|
||||
return check_ping($host, $round + 1)
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
|
||||
} else {
|
||||
$ping->close();
|
||||
return 'OK';
|
||||
}
|
||||
}
|
||||
|
||||
#####
|
||||
# Function used to check HTTP service
|
||||
#####
|
||||
sub check_http {
|
||||
my ($host, $find, $round) = @_;
|
||||
$round = 1 if (! $round);
|
||||
|
||||
$host = 'http://'.$host if ($host !~ m/^http/i);
|
||||
|
||||
my $check = LWP::UserAgent->new(
|
||||
ssl_opts => { verify_hostname => 1 },
|
||||
protocols_allowed => ['http', 'https']
|
||||
);
|
||||
$check->timeout(20);
|
||||
$check->env_proxy;
|
||||
|
||||
my $response = $check->get($host, ':content_cb' => \&process_data);
|
||||
output('http check n°' . $round . ' on ' . $host, 'DEBUG');
|
||||
|
||||
if ($response->is_success) {
|
||||
|
||||
if ($find && length($find) > 0) {
|
||||
output('searching "' . $find . '" into html content on ' . $host, 'DEBUG');
|
||||
|
||||
if ($html =~ m/$find/i) {
|
||||
output('html content found, looks fine', 'SUCCESS');
|
||||
return 'OK';
|
||||
}
|
||||
else {
|
||||
output('html content not found', 'ERROR');
|
||||
return 'Could not find "' . $find . '" into the page';
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
output('HTTP response error was: '.$response->status_line, 'DEBUG');
|
||||
if ($number_tries && $round < $number_tries) {
|
||||
sleep (2);
|
||||
return check_http($host, $find, $round + 1);
|
||||
}
|
||||
else {
|
||||
return $response->status_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#####
|
||||
# Save the page HTML content
|
||||
#####
|
||||
sub process_data {
|
||||
my ($content, $handler1, $handler2) = @_;
|
||||
$html .= $content;
|
||||
}
|
||||
|
||||
#####
|
||||
# Function managing DEBUG and OUTPUT
|
||||
#####
|
||||
sub output {
|
||||
my ($output, $level, $fatal) = @_;
|
||||
$output = server_time().' - '.$level.' - '.$output."\n";
|
||||
|
||||
if ($fatal && $fatal == 1) {
|
||||
die ('FATAL '.$output);
|
||||
}
|
||||
else {
|
||||
print ($output);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#####
|
||||
# Function that keeps an history
|
||||
#####
|
||||
sub save_history {
|
||||
my ($task_id, $status, $datetime, $response) = @_;
|
||||
my $query = $dbh->prepare('INSERT INTO `tasks_history` (`status`, `datetime`, `task_id`, `output`) VALUES(' . $status . ', "'.$datetime.'", ' . $task_id . ', "' . $response . '")');
|
||||
if ($query->execute()) {
|
||||
output('saving status to history', 'DEBUG');
|
||||
}
|
||||
else {
|
||||
output('cannot save status to history', 'ERROR');
|
||||
}
|
||||
|
||||
$update_query = $dbh->prepare('UPDATE `tasks` SET `last_execution` = "'.$datetime.'" WHERE id = ' . $task_id);
|
||||
if ($update_query->execute()) {
|
||||
output('saving last execution time for this task', 'DEBUG');
|
||||
}
|
||||
else {
|
||||
output('cannot save last execution time for this task', 'ERROR');
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#####
|
||||
# Function sending notifications
|
||||
#####
|
||||
sub send_notifications {
|
||||
my ($task_id, $host, $type, $message, $status) = @_;
|
||||
|
||||
if ($status == 0) {
|
||||
$subject = 'DOWN: host "' . $host . '" [' . $type . '] is down';
|
||||
$datas = "------ ALERT DETECTED BY MONITORING SERVICE ------ \n\n\nDATETIME: " . server_time() . " (server time)\nHOST: " . $host . "\nSERVICE: " . $type . "\nMESSAGE: " . $message;
|
||||
}
|
||||
else {
|
||||
$subject = 'UP: host "' . $host . '" [' . $type . '] is up';
|
||||
$datas = "------ RECOVERY DETECTED BY MONITORING SERVICE ------ \n\n\nDATETIME: " . server_time() . " (server time)\nHOST: " . $host . "\nSERVICE: " . $type . "\nMESSAGE: " . $message;
|
||||
}
|
||||
|
||||
my $query = $dbh->prepare('SELECT c.email FROM contacts as c JOIN notifications as n ON (n.contact_id = c.id) WHERE c.active = 1 AND n.task_id = '.$task_id);
|
||||
if ($query->execute()) {
|
||||
while ($emails = $query->fetchrow_hashref()) {
|
||||
|
||||
my $email = Email::MIME->create(
|
||||
header_str => [
|
||||
From => $email_from,
|
||||
To => $emails->{'email'},
|
||||
Subject => $subject
|
||||
],
|
||||
parts => [
|
||||
$datas
|
||||
],
|
||||
);
|
||||
eval {
|
||||
sendmail(
|
||||
$email,
|
||||
{
|
||||
from => $email_from,
|
||||
transport => Email::Sender::Transport::SMTP->new({
|
||||
host => $smtp_host,
|
||||
port => $smtp_port,
|
||||
sasl_username => $smtp_user,
|
||||
sasl_password => $smtp_password,
|
||||
ssl => $smtp_ssl,
|
||||
timeout => 10
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
output('Notification email was sent to '.$emails->{'email'}, 'DEBUG');
|
||||
};
|
||||
warn $@ if $@;
|
||||
}
|
||||
return 1
|
||||
}
|
||||
output('failed to send notifications', 'ERROR');
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####
|
||||
# Function getting datetime
|
||||
#####
|
||||
sub server_time {
|
||||
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
my $now = (1900 + $year).'-'.($mon + 1).'-'.$mday.' '.$hour.':'.$min.':00';
|
||||
return $now;
|
||||
}
|
|
@ -1,222 +1,4 @@
|
|||
@import url(https://fonts.googleapis.com/css2?family=Hind:wght@300;400;500;600;700&display=swap);
|
||||
@font-face {
|
||||
font-family: "Digital7";
|
||||
src: url("/fonts/digital.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
/**
|
||||
* SETTINGS
|
||||
**/
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
@font-face{font-family:Digital7;font-style:normal;font-weight:400;src:url(/fonts/digital.ttf) format("truetype")}*{margin:0;padding:0}html{font-size:100%;scroll-behavior:smooth}html body{background-attachment:fixed;background-image:url(../img/bush.png);color:#3d3d3d;font-family:Hind,sans-serif;font-size:1rem;padding:10px}html body a,html body a:visited{color:inherit;text-decoration:inherit}html body a:hover,html body a:visited:hover{text-decoration:underline}html body .container{margin:0 auto;max-width:1000px;padding:0}html body h1,html body h2,html body h3,html body h4{margin-bottom:.8rem;margin-top:.8rem}html body h1{font-size:2.4rem;margin:3rem 0;text-align:center}html body h2{font-size:1.4rem;margin-top:0;padding-top:0}html body h3{background-color:#0a9f9a;color:#f0f0f0;font-size:1.4rem;margin:0;padding:.8rem;position:relative}html body h3 small{font-size:.9rem}html body h3 .context-menu{cursor:pointer;font-size:1rem;position:absolute;right:.7rem;top:.7rem}html body div.round{background-color:#fff;border-radius:5px;box-shadow:3px 3px 6px 0 rgba(0,0,0,.3);margin-bottom:3rem;overflow:hidden;position:relative}html body div.round h3{margin-bottom:1rem}html body img{vertical-align:sub}html body table{border:1px solid #abc;border-collapse:collapse;border-spacing:0;font-size:14px}html body table th{background-color:#e6eeee}html body table td,html body table th{border:1px solid #9ccece;padding:.3rem}html body table td{background-color:#fff;color:#3d3d3d;text-align:center}html body table td,html body table td img{vertical-align:middle}html body table td.right{text-align:right}html body table#contacts_tbl,html body table#tasks_tbl{width:100%}html body .no-data{color:#727272;font-size:.9rem;font-style:italic;margin-bottom:1.3rem;text-align:center}html body .quick-view .new-group{border-radius:.4rem;cursor:pointer;display:inline-block;margin:.2rem;overflow:hidden}html body .quick-view .new-group .square{float:left;height:100%;line-height:1.2rem;margin:0;min-width:1.4rem;padding:.2rem .6rem;text-align:center;vertical-align:middle}html body .quick-view .new-group .square:not(:first-of-type){border-left:1px solid #fff}html body .tasks .task{background-color:#fff;border-radius:5px;box-shadow:3px 3px 6px 0 rgba(0,0,0,.3);margin-top:2rem;overflow:hidden;padding:0;position:relative}html body .spacer{clear:both;line-height:0;margin:0;padding:0}html body .block-content{padding:.8rem}html body .highlight{background-color:#166260;border-radius:.5rem;color:#fff;display:inline-block;font-size:1rem;padding:0 1rem;vertical-align:middle}html body .small{font-size:.8rem}html body .hidden{display:none}html body .up{background-color:#8adf8a}html body .down{background-color:#f79292}html body .unknown{background-color:#f5d69e}html body .inactive{background-color:#dfdfdf!important;opacity:.5}html body .refreshed-time{font-size:.8rem;margin-bottom:2rem;text-align:right}html body .refreshed-time .clock{background-color:#000;border-radius:4px;color:#fff;font-family:Digital7;font-size:1.2rem;padding:.3rem .5rem}@-webkit-keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
html body {
|
||||
padding: 10px;
|
||||
font-family: "Hind", sans-serif;
|
||||
font-size: 1rem;
|
||||
color: #3D3D3D;
|
||||
background-image: url(../img/bush.png);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
html body a, html body a:visited {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
html body a:hover, html body a:visited:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
html body .container {
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
max-width: 1000px;
|
||||
}
|
||||
html body h1, html body h2, html body h3, html body h4 {
|
||||
margin-top: 0.8rem;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
html body h1 {
|
||||
font-size: 2.4rem;
|
||||
text-align: center;
|
||||
margin: 3rem 0;
|
||||
}
|
||||
html body h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
html body h3 {
|
||||
font-size: 1.4rem;
|
||||
background-color: #0a9f9a;
|
||||
margin: 0;
|
||||
padding: 0.8rem;
|
||||
color: #f0f0f0;
|
||||
position: relative;
|
||||
}
|
||||
html body h3 small {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
html body h3 .context-menu {
|
||||
position: absolute;
|
||||
right: 0.7rem;
|
||||
top: 0.7rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
html body div.round {
|
||||
box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
html body div.round h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
html body img {
|
||||
vertical-align: sub;
|
||||
}
|
||||
html body table {
|
||||
border: 1px solid #ABC;
|
||||
font-size: 14px;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
html body table th {
|
||||
background-color: #e6EEEE;
|
||||
border: 1px solid #9ccece;
|
||||
padding: 0.3rem;
|
||||
}
|
||||
html body table td {
|
||||
color: #3D3D3D;
|
||||
padding: 0.3rem;
|
||||
background-color: #FFF;
|
||||
border: 1px solid #9ccece;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
html body table td img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
html body table td.right {
|
||||
text-align: right;
|
||||
}
|
||||
html body table#tasks_tbl, html body table#contacts_tbl {
|
||||
width: 100%;
|
||||
}
|
||||
html body .quick-view .new-group {
|
||||
margin: 0.2rem;
|
||||
display: inline-block;
|
||||
border-radius: 0.4rem;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
html body .quick-view .new-group .square {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
line-height: 1.2rem;
|
||||
min-width: 1.4rem;
|
||||
padding: 0.2rem 0.6rem;
|
||||
}
|
||||
html body .quick-view .new-group .square:not(:first-of-type) {
|
||||
border-left: 1px solid white;
|
||||
}
|
||||
html body .tasks .task {
|
||||
margin-top: 2rem;
|
||||
padding: 0;
|
||||
box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
html body .spacer {
|
||||
clear: both;
|
||||
line-height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
html body .block-content {
|
||||
padding: 0.8rem;
|
||||
}
|
||||
html body .highlight {
|
||||
background-color: #166260;
|
||||
padding: 0px 1rem;
|
||||
display: inline-block;
|
||||
color: #FFF;
|
||||
vertical-align: middle;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
html body .small {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
html body .hidden {
|
||||
display: none;
|
||||
}
|
||||
html body .up {
|
||||
background-color: #8adf8a;
|
||||
}
|
||||
html body .down {
|
||||
background-color: #f79292;
|
||||
}
|
||||
html body .unknown {
|
||||
background-color: #f5d69e;
|
||||
}
|
||||
html body .inactive {
|
||||
background-color: #dfdfdf !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
html body .refreshed-time {
|
||||
text-align: right;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
html body .refreshed-time .clock {
|
||||
font-family: Digital7;
|
||||
font-size: 1.2rem;
|
||||
background-color: #000;
|
||||
border-radius: 4px;
|
||||
color: #FFF;
|
||||
padding: 0.3rem 0.5rem;
|
||||
}
|
||||
|
||||
@-webkit-keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
/*# sourceMappingURL=app.css.map*/
|
1
public/css/app.css.map
Normal file
1
public/css/app.css.map
Normal file
File diff suppressed because one or more lines are too long
1
public/img/dns.svg
Normal file
1
public/img/dns.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg data-name="Layer 1" id="Layer_1" viewBox="0 0 272 272" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#424242;}.cls-2{fill:#f3f4f2;}</style></defs><title/><rect class="cls-1" height="8" width="136" x="68" y="68"/><rect class="cls-1" height="8" width="136" x="68" y="92"/><rect class="cls-1" height="8" width="136" x="68" y="116"/><rect class="cls-1" height="8" width="136" x="68" y="140"/><rect class="cls-1" height="8" width="136" x="68" y="164"/><rect class="cls-1" height="8" width="136" x="68" y="188"/><rect class="cls-1" height="8" width="136" x="68" y="212"/><path class="cls-1" d="M216,40H192V16l-8-8H48V264H224V48Zm0,216H56V16H184V48h32Z"/><rect class="cls-2" height="8" width="136" x="68" y="68"/><rect class="cls-2" height="8" width="136" x="68" y="92"/><rect class="cls-2" height="8" width="136" x="68" y="116"/><rect class="cls-2" height="8" width="136" x="68" y="140"/><rect class="cls-2" height="8" width="136" x="68" y="164"/><rect class="cls-2" height="8" width="136" x="68" y="188"/><rect class="cls-2" height="8" width="136" x="68" y="212"/><path class="cls-1" d="M184,8V48h40Zm8,32V27.31L204.69,40Z"/><rect class="cls-1" height="8" width="136" x="68" y="68"/><rect class="cls-1" height="8" width="136" x="68" y="92"/><rect class="cls-1" height="8" width="136" x="68" y="116"/><rect class="cls-1" height="8" width="136" x="68" y="140"/><rect class="cls-1" height="8" width="136" x="68" y="164"/><rect class="cls-1" height="8" width="136" x="68" y="188"/><rect class="cls-1" height="8" width="136" x="68" y="212"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
public/img/ftp.svg
Normal file
1
public/img/ftp.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg data-name="Layer 1" id="Layer_1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M7,25A7,7,0,0,1,7,11a1,1,0,0,1,0,2A5,5,0,0,0,7,23a1,1,0,0,1,0,2Z"/><path d="M25,25a1,1,0,0,1,0-2,5,5,0,0,0,0-10,1,1,0,0,1,0-2,7,7,0,0,1,0,14Z"/><path d="M25,13a1,1,0,0,1-1-1A8,8,0,0,0,8,12a1,1,0,0,1-2,0,10,10,0,0,1,20,0A1,1,0,0,1,25,13Z"/><path d="M21,22H11a1,1,0,0,1,0-2H21a1,1,0,0,1,0,2Z"/><path d="M21,28H11a1,1,0,0,1,0-2H21a1,1,0,0,1,0,2Z"/><path d="M21,28a1,1,0,0,1-.83-.45l-2-3a1,1,0,1,1,1.66-1.1l2,3a1,1,0,0,1-.28,1.38A.94.94,0,0,1,21,28Z"/><path d="M19,31a.94.94,0,0,1-.55-.17,1,1,0,0,1-.28-1.38l2-3a1,1,0,0,1,1.66,1.1l-2,3A1,1,0,0,1,19,31Z"/><path d="M13,25a1,1,0,0,1-.83-.45l-2-3a1,1,0,0,1,1.66-1.1l2,3a1,1,0,0,1-.28,1.38A.94.94,0,0,1,13,25Z"/><path d="M11,22a.94.94,0,0,1-.55-.17,1,1,0,0,1-.28-1.38l2-3a1,1,0,0,1,1.66,1.1l-2,3A1,1,0,0,1,11,22Z"/><path d="M9,25H7a1,1,0,0,1,0-2H9a1,1,0,0,1,0,2Z"/><path d="M25,25H23a1,1,0,0,1,0-2h2a1,1,0,0,1,0,2Z"/></svg>
|
After Width: | Height: | Size: 989 B |
2588
public/js/app.js
2588
public/js/app.js
File diff suppressed because one or more lines are too long
29
public/js/app.js.LICENSE.txt
Normal file
29
public/js/app.js.LICENSE.txt
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*!
|
||||
* ApexCharts v3.32.0
|
||||
* (c) 2018-2021 ApexCharts
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Vue.js v2.6.14
|
||||
* (c) 2014-2021 Evan You
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* vuex v3.6.2
|
||||
* (c) 2021 Evan You
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*! svg.draggable.js - v2.2.2 - 2019-01-08
|
||||
* https://github.com/svgdotjs/svg.draggable.js
|
||||
* Copyright (c) 2019 Wout Fierens; Licensed MIT */
|
||||
|
||||
/*! svg.filter.js - v2.0.2 - 2016-02-24
|
||||
* https://github.com/wout/svg.filter.js
|
||||
* Copyright (c) 2016 Wout Fierens; Licensed MIT */
|
||||
|
||||
//! moment.js
|
||||
|
||||
//! moment.js locale configuration
|
1
public/js/app.js.map
Normal file
1
public/js/app.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -111,41 +111,48 @@ html {
|
|||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #ABC;
|
||||
font-size: 14px;
|
||||
border-spacing : 0;
|
||||
border-collapse : collapse;
|
||||
border: 1px solid #ABC;
|
||||
font-size: 14px;
|
||||
border-spacing : 0;
|
||||
border-collapse : collapse;
|
||||
|
||||
th {
|
||||
background-color: #e6EEEE;
|
||||
border: 1px solid #9ccece;
|
||||
padding: 0.3rem;
|
||||
th {
|
||||
background-color: #e6EEEE;
|
||||
border: 1px solid #9ccece;
|
||||
padding: 0.3rem;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
color: #3D3D3D;
|
||||
padding: 0.3rem;
|
||||
background-color: #FFF;
|
||||
border: 1px solid #9ccece;
|
||||
text-align: center;
|
||||
td {
|
||||
color: #3D3D3D;
|
||||
padding: 0.3rem;
|
||||
background-color: #FFF;
|
||||
border: 1px solid #9ccece;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&.right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&#tasks_tbl, &#contacts_tbl {
|
||||
width: 100%;
|
||||
&.right {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
&#tasks_tbl, &#contacts_tbl {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
margin-bottom: 1.3rem;
|
||||
font-size: .9rem;
|
||||
color: #727272;
|
||||
}
|
||||
|
||||
.quick-view {
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>MonitoLite - Network monitoring tool</title>
|
||||
<script type="text/javascript" src="/js/app.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="/css/app.css" />
|
||||
<script type="text/javascript" src="{{ url('js/app.js') }}"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{{ url('css/app.css') }}" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -34,16 +34,15 @@
|
|||
getTasks: function() {
|
||||
this.$http.get('/api/getTasks')
|
||||
.then(response => this.$store.commit('setTasks', response.data))
|
||||
.then(() => {
|
||||
this.refreshed_time = this.moment();
|
||||
this.loading.hide()
|
||||
})
|
||||
.catch(error => {
|
||||
this.loading.hide()
|
||||
clearTimeout(this.refresh)
|
||||
window.alert('An error occurred when getting tasks. Automatic refresh has been disabled. You should fix and reload this page.')
|
||||
})
|
||||
this.refreshed_time = this.moment();
|
||||
.then(() => {
|
||||
this.refreshed_time = this.moment();
|
||||
this.loading.hide()
|
||||
})
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
</h3>
|
||||
<div class="block-content">
|
||||
<div
|
||||
v-if="1 == 1"
|
||||
v-if="tasks && Object.keys(tasks).length > 0"
|
||||
>
|
||||
<div
|
||||
v-for="group in tasks"
|
||||
|
@ -28,9 +28,10 @@
|
|||
<p class="spacer"> </p>
|
||||
</div>
|
||||
<div
|
||||
class="no-data"
|
||||
v-else
|
||||
>
|
||||
<center>Sorry, there is no task here.</center>
|
||||
Sorry, there is no task here.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<tr>
|
||||
<th width="5%">Up?</th>
|
||||
<th width="*">Host</th>
|
||||
<th width="5%">Type</th>
|
||||
<th width="10%">Type</th>
|
||||
<th width="20%">Last checked</th>
|
||||
<th width="13%">Frequency (min)</th>
|
||||
<th width="5%">Active</th>
|
||||
|
@ -38,14 +38,14 @@
|
|||
<a :href="task.host" target="_blank">{{ task.host }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<img :src="task.type == 'http' ? '/img/http.svg' : '/img/ping.svg'" width="16" alt="Type of check" :title="'Type: '+task.type" />
|
||||
<img :src="'/img/'+task.type+'.svg'" width="16" alt="Type of check" :title="'Type: '+task.type" />
|
||||
{{ task.type.toUpperCase() }}
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
v-if="task.executed_at"
|
||||
>
|
||||
{{ moment(task.executed_at).fromNow() }}
|
||||
<img src="/img/info.svg" alt="Infos" width="16" :title="'Result: '+task.output" />
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
|
@ -83,7 +83,7 @@ export default {
|
|||
computed: {
|
||||
tasks: function() {
|
||||
return this.$store.state.tasks
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
statusText: function (status) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
v-model="days"
|
||||
@change="refreshTask"
|
||||
>
|
||||
<option value="3">3 days</option>
|
||||
<option value="7">7 days</option>
|
||||
<option value="15">15 days</option>
|
||||
<option value="30">30 days</option>
|
||||
|
@ -23,6 +24,7 @@
|
|||
<h3>Last {{ days }} days uptime</h3>
|
||||
<div class="block-content">
|
||||
<apexchart class="graph" v-if="charts.uptime.render" type="bar" height="350" :options="charts.uptime.options" :series="charts.uptime.series"></apexchart>
|
||||
<p class="no-data" v-else>No chart to display here</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -31,6 +33,7 @@
|
|||
<h3>Last {{ days }} days response time</h3>
|
||||
<div class="block-content">
|
||||
<apexchart class="graph" v-if="charts.response.render" type="line" height="350" :options="charts.response.options" :series="charts.response.series"></apexchart>
|
||||
<p class="no-data" v-else>No chart to display here</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -39,16 +42,16 @@
|
|||
<!-- History backlog -->
|
||||
<div class="round">
|
||||
<h3>Last {{ days }} days history log</h3>
|
||||
<div class="block-content" v-if="history">
|
||||
<div class="block-content" v-if="history && Object.keys(history).length > 0">
|
||||
<p><i>Showing only records where status has changed</i></p>
|
||||
<table id="tasks_tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="10%">Status</th>
|
||||
<th width="10%">Date</th>
|
||||
<th width="10%">Time</th>
|
||||
<th width="*">Output</th>
|
||||
<th width="10%">Duration</th>
|
||||
<th width="10%">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
@ -56,6 +59,9 @@
|
|||
v-for="h in history"
|
||||
v-bind:key="h.id"
|
||||
>
|
||||
<td :class="statusText(h.status)">
|
||||
<img :src="'/img/'+statusText(h.status)+'.svg'" width="16" alt="Status" />
|
||||
</td>
|
||||
<td>{{ moment(h.created_at).format('YYYY-MM-DD') }}</td>
|
||||
<td>{{ moment(h.created_at).format('HH:mm:ss') }}</td>
|
||||
<td>
|
||||
|
@ -70,28 +76,26 @@
|
|||
<span v-if="h.duration != null">{{ h.duration+'s' }}</span>
|
||||
<span v-else><i>No duration</i></span>
|
||||
</td>
|
||||
<td :class="statusText(h.status)">
|
||||
<img :src="'/img/'+statusText(h.status)+'.svg'" width="16" alt="Status" />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p v-else><center>No history to display here</center></p>
|
||||
<p class="no-data" v-else>No history to display here</p>
|
||||
</div>
|
||||
|
||||
<!-- Notifications block -->
|
||||
<div class="round">
|
||||
<h3>Last {{ days }} days notifications log</h3>
|
||||
<div class="block-content" v-if="notifications">
|
||||
<div class="block-content" v-if="notifications && Object.keys(notifications).length > 0">
|
||||
<table id="tasks_tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="20%">Date</th>
|
||||
<th width="20%">Time</th>
|
||||
<th width="*">Firstname</th>
|
||||
<th width="10%">Lastname</th>
|
||||
<th width="10%">Email</th>
|
||||
<th width="10%">Date</th>
|
||||
<th width="10%">Time</th>
|
||||
<th width="15">Firstname</th>
|
||||
<th width="15%">Lastname</th>
|
||||
<th width="30%">Email</th>
|
||||
<th width="10%">Type</th>
|
||||
<th width="10%">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -105,12 +109,13 @@
|
|||
<td>{{ n.contact.firstname }}</td>
|
||||
<td>{{ n.contact.surname }}</td>
|
||||
<td>{{ n.contact.email }}</td>
|
||||
<td>{{ n.status }}</td>
|
||||
<td>{{ n.task_history.status == 1 ? 'UP' : 'DOWN' }}</td>
|
||||
<td>{{ n.status.toUpperCase() }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p v-else><center>No notification to display here</center></p>
|
||||
<p class="no-data" v-else>No notification to display here</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -128,7 +133,7 @@
|
|||
notifications: null,
|
||||
refresh: null,
|
||||
loader: null,
|
||||
days: 7,
|
||||
days: 3,
|
||||
first_day: null,
|
||||
|
||||
charts: {
|
||||
|
@ -179,6 +184,9 @@
|
|||
}, 10000)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
//TODO: do something
|
||||
})
|
||||
.then(() => {
|
||||
this.loader.hide()
|
||||
})
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
|
|
||||
*/
|
||||
|
||||
$router->group(['prefix' => 'api/'], function () use ($router) {
|
||||
$router->get('getTasks/', ['uses' => 'ApiController@getTasks']);
|
||||
$router->post('getTask/{id}', ['uses' => 'ApiController@getTaskDetails']);
|
||||
$router->patch('toggleTaskStatus/{id}', ['uses' => 'ApiController@toggleTaskStatus']);
|
||||
$router->group(['prefix' => '/api'], function () use ($router) {
|
||||
$router->get('/getTasks/', ['uses' => 'ApiController@getTasks']);
|
||||
$router->post('/getTask/{id}', ['uses' => 'ApiController@getTaskDetails']);
|
||||
$router->patch('/toggleTaskStatus/{id}', ['uses' => 'ApiController@toggleTaskStatus']);
|
||||
});
|
||||
|
||||
$router->get('/{route:.*}/', function () {
|
||||
|
|
Loading…
Add table
Reference in a new issue