Compare commits
130 commits
Author | SHA1 | Date | |
---|---|---|---|
28f1bcfce9 | |||
9fe028e1cc | |||
ec4fb68630 | |||
42fe9281f7 | |||
3daa19f2de | |||
25f5a8800d | |||
1414c83cc9 | |||
78dc7baa73 | |||
83d401daa2 | |||
1996b4fb32 | |||
5986875818 | |||
80ba45b83b | |||
f2139a7b01 | |||
42f1187776 | |||
dde679411a | |||
f1e1cbfdd9 | |||
d9c2c6f5f6 | |||
24ed50d9d2 | |||
fc6f36ce3a | |||
fccf7bf165 | |||
e89e4ca451 | |||
6ba0d76dda | |||
230e1c5668 | |||
5b73ad72e4 | |||
d292b0342e | |||
d3f05d6ee1 | |||
b0e28950bc | |||
6fe20c0102 | |||
ea63595795 | |||
a704c5bea3 | |||
d57cee575b | |||
a71505def5 | |||
a9ee94b8fd | |||
234547a903 | |||
84ec4847d9 | |||
f20adb7f28 | |||
3852ef2945 | |||
bae507328b | |||
2c415cd08b | |||
441353fcb0 | |||
9ead771d6b | |||
1e0d78f67b | |||
73dd738970 | |||
6ca118f717 | |||
e344d5ca38 | |||
0e7221da07 | |||
c1817425cd | |||
8d7bd04424 | |||
afd273081b | |||
9f4de0dee6 | |||
bbc1afbddc | |||
387d910e44 | |||
3981724c86 | |||
4067a8448b | |||
a78320344f | |||
646e7de68f | |||
c832ca94fa | |||
a997dec6a5 | |||
6a9b886796 | |||
327b2d8f63 | |||
30dba8447f | |||
22b6b9de57 | |||
e48daa0008 | |||
630ac562ee | |||
c2e2532103 | |||
a518468434 | |||
a1ee1d0335 | |||
9c170504cd | |||
416f5294e6 | |||
cc03a0cf03 | |||
b163ce6c5c | |||
4381db999c | |||
81bcaed76d | |||
217428296a | |||
7b999e994b | |||
4a36d03729 | |||
68d37f6db8 | |||
82f543a587 | |||
1cc4ac9a95 | |||
696d7fd299 | |||
746e5afa8f | |||
caa8ca8ce5 | |||
3a288ff15f | |||
3cdeded7a2 | |||
797dade52b | |||
6e5f4e9736 | |||
8972e44e6b | |||
6b5b7455b5 | |||
a5b047887d | |||
972914691b | |||
ca765a1328 | |||
bece9b724e | |||
fb1f63d4bc | |||
dfe833d670 | |||
74a7353b85 | |||
10ef2dd4e8 | |||
ee16a6f8f7 | |||
a6f26e0da1 | |||
7ed25704e1 | |||
6483c516e3 | |||
37065e3a3e | |||
209bf87c0a | |||
374a6d34ea | |||
cccaa10c23 | |||
df690a63be | |||
b32847e827 | |||
5079e836ca | |||
6595d09bf2 | |||
192aeefc9b | |||
0eb5269b9d | |||
45d728410f | |||
d4e25c1033 | |||
d2e302dcc7 | |||
fda3a74a29 | |||
92e6921cf9 | |||
6074009936 | |||
a3fabb1609 | |||
ea673e0083 | |||
d34a4ff21f | |||
d3320821c0 | |||
3bba0930f3 | |||
72fecab8a6 | |||
3569ccabb2 | |||
9a98c8076b | |||
187eae16e1 | |||
a0fd4aea60 | |||
a49f577dae | |||
5b87f3c580 | |||
c9bf661626 | |||
68c88c7fcf |
15
.editorconfig
Normal file
|
@ -0,0 +1,15 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
38
.env.example
Executable file → Normal file
|
@ -1,14 +1,32 @@
|
|||
DB_TYPE=mysql
|
||||
APP_NAME=Monitolite
|
||||
APP_ENV=production
|
||||
APP_KEY=
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
APP_TIMEZONE=UTC
|
||||
DB_TIMEZONE="+1:00"
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_SLACK_WEBHOOK_URL=
|
||||
|
||||
DB_CONNECTION=mysql
|
||||
DB_HOST=127.0.0.1
|
||||
DB_USER=vagrant
|
||||
DB_PASSWORD=vagrant
|
||||
DB_NAME=monitoring
|
||||
DB_PORT=3306
|
||||
SMTP_HOST=localhost
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_PORT=80
|
||||
SMTP_SSL=1
|
||||
MAIL_FROM=axel@monitolite.fr
|
||||
DB_DATABASE=homestead
|
||||
DB_USERNAME=homestead
|
||||
DB_PASSWORD=secret
|
||||
|
||||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
|
||||
NB_TRIES=3
|
||||
ARCHIVE_DAYS=10
|
||||
|
||||
MAIL_MAILER=smtp
|
||||
MAIL_HOST=smtp.mailtrap.io
|
||||
MAIL_PORT=2525
|
||||
MAIL_USERNAME=
|
||||
MAIL_PASSWORD=
|
||||
MAIL_ENCRYPTION=tls
|
||||
MAIL_FROM_ADDRESS=noreply@monitolite.fr
|
||||
MAIL_FROM_NAME="Monitolite"
|
7
.gitignore
vendored
|
@ -1,2 +1,7 @@
|
|||
web/vendor/**/*
|
||||
/vendor
|
||||
/.idea
|
||||
Homestead.json
|
||||
Homestead.yaml
|
||||
.env
|
||||
.phpunit.result.cache
|
||||
/node_modules
|
||||
|
|
6
.styleci.yml
Normal file
|
@ -0,0 +1,6 @@
|
|||
php:
|
||||
preset: laravel
|
||||
disabled:
|
||||
- unused_use
|
||||
js: true
|
||||
css: true
|
184
README.md
|
@ -1,86 +1,98 @@
|
|||
# MONITOLITE
|
||||
|
||||
**MonitoLite** is an old project I recently dug up from my archives. I developed this script years ago for my personal needs.
|
||||
I figured it could be useful for others so here we are.
|
||||
|
||||
|
||||
## What it does
|
||||
|
||||
**MonitoLite** is a very simple monitoring tool developed in Perl. 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.
|
||||
|
||||
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.
|
||||
|
||||
It uses a SQL backend for handling the tasks and the status of the tasks.
|
||||
Tested on MySQL only but should support other SQL-based DBMS.
|
||||
|
||||
It comes with a very straightforward dashboard written in PHP. This is **optional**, the `monitolite.pl` script runs as standalone.
|
||||
**Caution**: the backend is not password-protected. You should make sure you add your own security layer via IP filtering or basic authentication.
|
||||
|
||||
|
||||
I rewrote a couple of things today to make sure the script still works.
|
||||
|
||||
## Screenshot
|
||||
|
||||

|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
* Perl : with DBI, Dotenv, Net::Ping, Email::MIME, Email::Sender::Simple, Email::Sender::Transport::SMTP, LWP::Simple, LWP::UserAgent, LWP::Protocol::https
|
||||
* a MTA: Postfix, ...
|
||||
* PHP 7+ (optional): with PDO
|
||||
* a webserver (optional): Apache, Nginx, ...
|
||||
* a Database server: MySQL, other? (untested)
|
||||
* access to CRON tasks
|
||||
* possibly `root` access for the `ping` command to run (needs confirmation)
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
* clone this repo
|
||||
* install Perl dependencies
|
||||
* install PHP composer dependencies: `cd ./web && composer install`
|
||||
* create a Database and import the schema from `sql/create.sql`
|
||||
* create your own `.env` file: `cp .env.example .env` and adapt it to your needs
|
||||
* create a webserver vhost with document root to the `web` directory
|
||||
* add tasks and contacts into the database (no backend yet)
|
||||
* run the script: `perl monitolite.pl`
|
||||
* check the web dashboard for results.
|
||||
* when everything works, you may create a CRON `* * * * * cd <change/this/to/the/correct/path> && /usr/bin/perl monitolite.pl > /dev/null`
|
||||
|
||||
|
||||
## Settings
|
||||
|
||||
* DB_TYPE=mysql
|
||||
* DB_HOST=127.0.0.1
|
||||
* DB_USER=vagrant
|
||||
* DB_PASSWORD=vagrant
|
||||
* DB_NAME=monitoring
|
||||
* DB_PORT=3306
|
||||
* SMTP_HOST=localhost
|
||||
* SMTP_USER=
|
||||
* SMTP_PASSWORD=
|
||||
* SMTP_PORT=80
|
||||
* SMTP_SSL=1
|
||||
* MAIL_FROM=axel@monitolite.fr
|
||||
* NB_TRIES=3
|
||||
* ARCHIVE_DAYS=10
|
||||
|
||||
## MORE INFORMATION COMING SOON.
|
||||
|
||||
## TODO
|
||||
|
||||
* Make CRUD possible from the backend for adding tasks and contacts
|
||||
* Multithreading
|
||||
* SMS Notifications
|
||||
* Better dashboard
|
||||
* Protected backend with authentication
|
||||
* Create an installation script
|
||||
* Raise alert when tasks are not run at the correct frequency (CRON down or other reason)
|
||||
* Set a notification capping limit to prevent many notifications to be sent in case of an up-and-down host
|
||||
* Add a notification history log
|
||||
* Keep track of tasks response time
|
||||
* Daemonize the script (instead of CRONs)
|
||||
# MONITOLITE
|
||||
|
||||
**MonitoLite** is an old project I recently dug up from my archives. I developed this script years ago for my personal needs.
|
||||
I figured it could be useful for others so I **rewrote** and **updated** it from scratch in a modern way.
|
||||
|
||||
|
||||
## 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.
|
||||
* **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.
|
||||
|
||||
It uses a SQL backend for handling the tasks and the status of the tasks.
|
||||
Tested on MySQL only but should support other SQL-based DBMS.
|
||||
|
||||
It comes with a very straightforward dashboard written in PHP. This is **optional**, the monitoring script runs as standalone.
|
||||
**Caution**: the backend is not password-protected. You should make sure you add your own security layer via IP filtering or basic authentication.
|
||||
|
||||
## Demo
|
||||
|
||||
[DEMO](https://monitolite.mabox.eu)
|
||||
|
||||
## Screenshot
|
||||
|
||||
### Tasks list with quick preview
|
||||
|
||||

|
||||
|
||||
### Task details with graph and history
|
||||
|
||||

|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
* PHP 7+ with cURL, `exec` command allowed, MySQL extension via PDO
|
||||
* a MTA: Postfix, or an external SMTP ...
|
||||
* a webserver (optional): Apache, Nginx, ...
|
||||
* a Database server: MySQL, other? (untested)
|
||||
* access to CRON tasks
|
||||
|
||||
## Installation
|
||||
|
||||
* clone this repo
|
||||
* install PHP composer dependencies: `cd ./web && composer install`
|
||||
* create a Database and import the initial schema using `php artisan migrate`
|
||||
* create your own `.env` file: `cp .env.example .env` and adapt it to your needs
|
||||
* create a webserver vhost with document root to the `public` directory
|
||||
* add tasks and contacts into the database (no GUI for CRUD yet)
|
||||
* run the script: `cd /var/www/<your-path> && php artisan monitolite:run`
|
||||
* check the output of the command for results.
|
||||
* if everything works, you may create a CRON `* * * * * cd /var/www/<your-path> && php artisan monitolite:run > /dev/null`
|
||||
|
||||
|
||||
## Settings
|
||||
|
||||
* APP_NAME=Monitolite
|
||||
* APP_ENV=production
|
||||
* APP_KEY=<GENERATE KEY HERE>
|
||||
* APP_DEBUG=false
|
||||
* APP_URL=http://localhost
|
||||
* APP_TIMEZONE=UTC
|
||||
* DB_TIMEZONE="+1:00"
|
||||
* DB_CONNECTION=mysql
|
||||
* DB_HOST=127.0.0.1
|
||||
* DB_PORT=3306
|
||||
* DB_DATABASE=homestead
|
||||
* DB_USERNAME=homestead
|
||||
* DB_PASSWORD=secret
|
||||
* MAIL_MAILER=smtp
|
||||
* MAIL_HOST=localhost
|
||||
* MAIL_PORT=25
|
||||
* MAIL_USERNAME=
|
||||
* MAIL_PASSWORD=
|
||||
* MAIL_ENCRYPTION=
|
||||
* MAIL_FROM_ADDRESS=noreply@monitolite.fr
|
||||
* MAIL_FROM_NAME="Monitolite"
|
||||
* NB_TRIES=3
|
||||
* ARCHIVE_DAYS=10
|
||||
|
||||
|
||||
## TODO
|
||||
|
||||
[ ] Make CRUD possible from the backend for adding tasks and contacts
|
||||
[ ] Multithreading
|
||||
[ ] SMS Notifications
|
||||
[ ] Protected backend with authentication
|
||||
[ ] Create an installation script
|
||||
[ ] Raise alert when tasks are not run at the correct frequency (CRON down or other reason)
|
||||
[x] Set a notification capping limit to prevent many notifications to be sent in case of an up-and-down host
|
||||
[x] Add a notification history log
|
||||
[x] Keep track of tasks response time
|
||||
[ ] Daemonize the script (instead of CRONs)
|
||||
|
|
0
app/Console/Commands/.gitkeep
Normal file
51
app/Console/Commands/CleanHistory.php
Normal file
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class CleanHistory extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monitolite:purge';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Aggregates and cleans tasks history';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$lastweek = \Carbon\Carbon::now()->subWeek();
|
||||
$history = app('db')->select('
|
||||
SELECT * FROM task_history as h
|
||||
WHERE created_at < :lastweek
|
||||
', [
|
||||
'lastweek' => $lastweek
|
||||
]);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
341
app/Console/Commands/RunMonitoring.php
Normal file
|
@ -0,0 +1,341 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use \Exception;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskHistory;
|
||||
use App\Models\Notification;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RunMonitoring extends Command
|
||||
{
|
||||
private $limit = 50;
|
||||
private $max_tries = 3;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monitolite:run
|
||||
{--limit=50 : the number of tasks to handle in one run}
|
||||
{--task= : the ID of an individual task to handle}
|
||||
{--force : handles tasks even if they are pending}
|
||||
';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Executes all the monitoring tasks';
|
||||
|
||||
/**
|
||||
* Storing all the results for output
|
||||
*/
|
||||
private $results;
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$count = 0;
|
||||
$limit = $this->option('limit') ?? $this->limit;
|
||||
$this->max_tries = env('NB_TRIES', $this->max_tries);
|
||||
|
||||
// If a force has been asked via command line
|
||||
$force = false;
|
||||
if (! empty($this->option('force'))) {
|
||||
if (empty($this->option('task'))) {
|
||||
if ($this->confirm('You asked me to force the execution (--force) but you did not specify a particular task ID (--task). I might have to handle a large amount of tasks. Are you sure?')) {
|
||||
$force = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$force = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Getting pending tasks
|
||||
$tasks = Task::where(function($query) use ($force) {
|
||||
$query->whereRaw('DATE_SUB(NOW(), INTERVAL frequency SECOND) > executed_at');
|
||||
$query->orWhereBetween('attempts', [1, ($this->max_tries - 1)]);
|
||||
$query->orWhereNull('executed_at');
|
||||
|
||||
if ($force === true) {
|
||||
$query->orWhere('id', '>', 0);
|
||||
}
|
||||
})
|
||||
->where('active', 1)
|
||||
->orderBy('attempts', 'DESC')
|
||||
->orderBy('executed_at', 'ASC')
|
||||
->take($limit)
|
||||
;
|
||||
|
||||
|
||||
// If a particular task has been set via the command line
|
||||
if (! empty($this->option('task'))) {
|
||||
$tasks = $tasks->where('id', '=', $this->option('task'));
|
||||
}
|
||||
|
||||
// Now getting tasks
|
||||
$tasks = $tasks->get();
|
||||
|
||||
if (is_null($tasks) || count($tasks) == 0) {
|
||||
$this->info('No task to process, going back to sleep');
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->info('I have '.count($tasks).' tasks to process. Better get started ...');
|
||||
|
||||
$this->newLine();
|
||||
$bar = $this->output->createProgressBar(count($tasks));
|
||||
$bar->start();
|
||||
|
||||
foreach ($tasks as $task) {
|
||||
$bar->advance();
|
||||
|
||||
// Getting current task last status
|
||||
$previous_status = $task->status;
|
||||
|
||||
try {
|
||||
switch ($task->type) {
|
||||
case 'ping':
|
||||
$result = $this->checkPing($task);
|
||||
break;
|
||||
|
||||
case 'http':
|
||||
$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
|
||||
throw new Exception('Unknown type "'.$task->type.'"');
|
||||
}
|
||||
|
||||
$new_status = 1;
|
||||
$history = $this->saveHistory($task, true, 'success', $result['duration'] ?? null);
|
||||
}
|
||||
catch(MonitoringException $e) {
|
||||
$history = $this->saveHistory($task, false, $e->getMessage());
|
||||
}
|
||||
catch(Exception $e) {
|
||||
//TODO: handle system exception differently
|
||||
//$history = $this->saveHistory($task, false, $e->getMessage());
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
finally {
|
||||
// Changing task timestamps and status
|
||||
$task->executed_at = $history->created_at; # Using the same timestamp as the task history
|
||||
$task->attempts = $history->status == 1 ? 0 : $task->attempts + 1; # when success, resetting counter
|
||||
/**
|
||||
* We don't want to change the primary status in the task table
|
||||
* as long as failed tasks have reached the max tries limit
|
||||
* In the cast of a success, we can change the status straight away
|
||||
*/
|
||||
if ($history->status == 0 && $task->attempts >= $this->max_tries) {
|
||||
$task->status = 0;
|
||||
}
|
||||
else if ($history->status === 1) {
|
||||
$task->status = 1;
|
||||
}
|
||||
|
||||
if (! $task->save()) {
|
||||
throw new Exception('Cannot save task details');
|
||||
}
|
||||
|
||||
|
||||
// Task status has changed
|
||||
// But not from null (new task)
|
||||
if (! is_null($previous_status) && $task->status != $previous_status) {
|
||||
// If host is up, no double-check
|
||||
if ($task->status == 1 || ($task->status == 0 && $task->attempts == $this->max_tries)) {
|
||||
Notification::addNotificationTask($history);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$bar->finish();
|
||||
$this->newLine(2);
|
||||
|
||||
if (!empty($this->results)) {
|
||||
$this->table(
|
||||
['ID', 'Host', 'Type', 'Result', 'Attempts', 'Message'],
|
||||
$this->results
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final private function saveHistory(Task $task, $status, $output = null, $duration = null) {
|
||||
$date = date('Y-m-d H:i:s');
|
||||
|
||||
// Inserting new history
|
||||
$insert = new TaskHistory;
|
||||
$insert->status = $status === true ? 1 : 0;
|
||||
$insert->created_at = $date;
|
||||
$insert->output = $output ?? '';
|
||||
$insert->duration = $duration;
|
||||
$insert->task_id = $task->id;
|
||||
if (! $insert->save()) {
|
||||
throw new Exception('Cannot insert history for task #'.$task->id);
|
||||
}
|
||||
|
||||
$this->results[] = [
|
||||
'id' => $task->id,
|
||||
'host' => $task->host,
|
||||
'type' => $task->type,
|
||||
'result' => $status === true ? 'OK' : 'FAILED',
|
||||
'attempts' => $task->attempts,
|
||||
'message' => $output
|
||||
];
|
||||
|
||||
|
||||
return $insert;
|
||||
}
|
||||
|
||||
final private function checkPing(Task $task) {
|
||||
if (! function_exists('exec') || ! is_callable('exec')) {
|
||||
throw new MonitoringException('The "exec" command is required');
|
||||
}
|
||||
|
||||
// Different command line for different OS
|
||||
switch (strtolower(php_uname('s'))) {
|
||||
case 'darmin':
|
||||
$cmd = 'ping -n 1 -t 5';
|
||||
break;
|
||||
case 'windows':
|
||||
$cmd = 'ping /n 1 /w 5';
|
||||
break;
|
||||
case 'linux':
|
||||
case 'freebsd':
|
||||
default:
|
||||
$cmd = 'ping -c 1 -W 5';
|
||||
break;
|
||||
}
|
||||
|
||||
// If command failed
|
||||
if (false === $exec = exec($cmd.' '.$task->host, $output, $code)) {
|
||||
throw new MonitoringException('Unable to execute ping command');
|
||||
}
|
||||
|
||||
// If command returned a non-zero code
|
||||
if ($code > 0) {
|
||||
throw new MonitoringException('Ping task failed ('.$exec.')');
|
||||
}
|
||||
|
||||
// Double check
|
||||
$output = implode(' ', $output);
|
||||
// Looking for the 100% package loss output
|
||||
if (preg_match('~([0-9]{1,3})\.[0-9]{0,2}% +(packet)? +loss~', $output, $matches)) {
|
||||
if (! empty($matches[1])) {
|
||||
if (floatval($matches[1]) == 100) {
|
||||
throw new MonitoringException('Packet loss detected ('.($matches[0] ?? 'n/a').')');
|
||||
}
|
||||
}
|
||||
}
|
||||
// Else everything is fine
|
||||
return true;
|
||||
}
|
||||
|
||||
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 => $protocol,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 3,
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
CURLOPT_URL => trim($task->host)
|
||||
];
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, $opts);
|
||||
if ($result = curl_exec($ch)) {
|
||||
$duration = curl_getinfo($ch, CURLINFO_TOTAL_TIME);
|
||||
|
||||
// We have nothing to check into the page
|
||||
// So for me, this is a big YES
|
||||
if (empty($task->params)) {
|
||||
return [
|
||||
'result' => true,
|
||||
'duration' => $duration
|
||||
];
|
||||
}
|
||||
// We are looking for a string in the page
|
||||
else {
|
||||
if (strpos($result, $task->params) !== false) {
|
||||
return [
|
||||
'result' => true,
|
||||
'output' => 'String was found in the page',
|
||||
'duration' => $duration
|
||||
];
|
||||
}
|
||||
else {
|
||||
throw new MonitoringException('Cannot find the required string into the page');
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new MonitoringException(curl_error($ch), curl_errno($ch));
|
||||
}
|
||||
}
|
||||
|
||||
class MonitoringException extends Exception {}
|
||||
|
94
app/Console/Commands/SendNotifications.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use \Exception;
|
||||
use App\Models\Notification;
|
||||
use App\Mail\TaskNotification;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
|
||||
class SendNotifications extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monitolite:notify
|
||||
{--limit=1000 : maximum notifications to process at once }';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Sends the notifications alerts';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$notifications = Notification::with(['contact', 'task_history', 'task_history.task'])
|
||||
->where('status', '=', 'pending')
|
||||
->orderBy('created_at', 'ASC')
|
||||
->limit($this->option('limit'), 1000)
|
||||
->get()
|
||||
;
|
||||
|
||||
$results = [];
|
||||
if (! empty($notifications)) {
|
||||
foreach ($notifications as $n) {
|
||||
if (! isset($results[$n->contact_id])) {
|
||||
$results[$n->contact_id] = [
|
||||
'contact' => $n->contact->toArray(),
|
||||
'tasks' => []
|
||||
];
|
||||
}
|
||||
//else {
|
||||
$history = $n->task_history;
|
||||
$task = $history->task;
|
||||
|
||||
if (! isset($results[$n->contact_id]['tasks'][$task->id])) {
|
||||
$results[$n->contact_id]['tasks'][$task->id] = [
|
||||
'history' => []
|
||||
];
|
||||
}
|
||||
array_push($results[$n->contact_id]['tasks'][$task->id]['history'], $history->toArray());
|
||||
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($results) > 0) {
|
||||
foreach ($results as $r) {
|
||||
$this->info('Sending notifications to '.$r['contact']['email']);
|
||||
try {
|
||||
Mail::to($r['contact']['email'])->send(new TaskNotification($r));
|
||||
Notification::where('contact_id', '=', $r['contact']['id'])->update(
|
||||
['status' => 'sent']
|
||||
);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
Notification::where('contact_id', '=', $r['contact']['id'])->update(
|
||||
['status' => 'error']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
156
app/Console/Commands/SyncCustomers.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
/**
|
||||
* R E A D T H I S :
|
||||
* THIS COMMAND IS FOR MY OWN NEEDS ONLY
|
||||
* IT SYNCS ALL THE TASKS FROM A DISTANT API
|
||||
* IT IS PROBABLY WORTHLESS FOR YOU
|
||||
*/
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncCustomers extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monitolite:sync';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Synchronizes all customers\' websites with Monitolite';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
if (env('CMS_ENABLE_SYNC') != true) {
|
||||
$this->error('Customers synchronisation is globally disabled.');
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
$this->line('Starting synchronisation');
|
||||
$customers = $tasks = $contacts = [];
|
||||
|
||||
// Getting active customers
|
||||
$opts = [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => false,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_POSTFIELDS => [
|
||||
'access' => env('CMS_API_ACCESS'),
|
||||
'token' => env('CMS_API_TOKEN')
|
||||
],
|
||||
CURLOPT_URL => env('CMS_API_URL')
|
||||
];
|
||||
|
||||
$ch = curl_init();
|
||||
curl_setopt_array($ch, $opts);
|
||||
if ($result = curl_exec($ch)) {
|
||||
$hosts = [];
|
||||
|
||||
$customers = json_decode($result);
|
||||
|
||||
$bar = $this->output->createProgressBar(count($customers));
|
||||
$bar->start();
|
||||
|
||||
// Getting existing tasks
|
||||
$tasks_flat = [];
|
||||
$tasks = app('db')->select('SELECT * FROM tasks');
|
||||
foreach ($tasks as $t) {
|
||||
$tasks_flat[$t->id] = preg_replace('~^https?://~', '', trim($t->host));
|
||||
}
|
||||
|
||||
// Getting existing contacts
|
||||
$contacts = app('db')->select('SELECT * FROM contacts');
|
||||
|
||||
// Getting existing groups
|
||||
$groups_flat = [];
|
||||
$groups = app('db')->select('SELECT * FROM `groups`');
|
||||
foreach ($groups as $g) {
|
||||
$groups_flat[$g->id] = $g->name;
|
||||
}
|
||||
|
||||
// First we insert new customers
|
||||
foreach($customers as $c) {
|
||||
$bar->advance();
|
||||
|
||||
$hosts[] = 'https://'.trim($c->domain);
|
||||
|
||||
// Checking group existence
|
||||
if (empty($groups_flat[$c->id])) {
|
||||
app('db')->insert('INSERT INTO `groups` (`id`, `name`) VALUE (?, ?)', [ $c->id, $c->name ]);
|
||||
$groups_flat[$c->id] = $c->name;
|
||||
}
|
||||
|
||||
if (false === array_search(trim($c->domain), $tasks_flat)) {
|
||||
$ret = app('db')->insert('
|
||||
INSERT INTO tasks (`host`, `type`, `params`, `created_at`, `frequency`, `active`, `group_id`)
|
||||
VALUES(:host, :type, :params, :creation_date, :frequency, :active, :group_id)
|
||||
', [
|
||||
'host' => 'https://'.trim($c->domain),
|
||||
'type' => 'http',
|
||||
'params' => 'restovisio.com',
|
||||
'creation_date' => date('Y-m-d H:i:s'),
|
||||
'frequency' => 3600,
|
||||
'active' => 1,
|
||||
'group_id' => $c->id
|
||||
]);
|
||||
|
||||
if ($ret === true) {
|
||||
$task_id = app('db')->getPdo()->lastInsertId();
|
||||
|
||||
// Inserting contacts
|
||||
foreach ($contacts as $c) {
|
||||
app('db')->insert('INSERT INTO contact_task (`task_id`, `contact_id`) VALUES (:task_id, :contact_id)', [
|
||||
'task_id' => $task_id,
|
||||
'contact_id' => $c->id
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$bar->finish();
|
||||
|
||||
$this->newLine(2);
|
||||
$this->line('Checking tasks to delete');
|
||||
$bar = $this->output->createProgressBar(count($tasks));
|
||||
$bar->start();
|
||||
|
||||
// Then we delete old customers
|
||||
foreach ($tasks as $t) {
|
||||
$bar->advance();
|
||||
|
||||
if (false === array_search($t->host, $hosts)) {
|
||||
// Must delete task
|
||||
//$this->line('must delete '.$t->host);
|
||||
//app('db')->delete('DELETE FROM `tasks` WHERE host = ?', [$t->host]);
|
||||
}
|
||||
}
|
||||
$bar->finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
50
app/Console/Kernel.php
Normal file
|
@ -0,0 +1,50 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Laravel\Lumen\Console\Kernel as ConsoleKernel;
|
||||
use App\Console\Commands\SyncCustomers;
|
||||
use App\Console\Commands\RunMonitoring;
|
||||
use App\Console\Commands\SendNotifications;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
/**
|
||||
* The Artisan commands provided by your application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commands = [
|
||||
SyncCustomers::class,
|
||||
RunMonitoring::class,
|
||||
SendNotifications::class
|
||||
];
|
||||
|
||||
/**
|
||||
* Define the application's command schedule.
|
||||
*
|
||||
* @param \Illuminate\Console\Scheduling\Schedule $schedule
|
||||
* @return void
|
||||
*/
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
/**
|
||||
* This is for my own needs
|
||||
* You may safely remove this scheduled task
|
||||
*/
|
||||
if (env('CMS_ENABLE_SYNC') == true) {
|
||||
$schedule->command('monitolite:sync')->hourly();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main monitoring task
|
||||
*/
|
||||
$schedule->command('monitolite:run')->everyMinute();
|
||||
|
||||
/**
|
||||
* Send all the notifications
|
||||
*/
|
||||
$schedule->command('monitolite:notify')->everyMinute();
|
||||
}
|
||||
}
|
10
app/Events/Event.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
abstract class Event
|
||||
{
|
||||
use SerializesModels;
|
||||
}
|
16
app/Events/ExampleEvent.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
class ExampleEvent extends Event
|
||||
{
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
54
app/Exceptions/Handler.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
namespace App\Exceptions;
|
||||
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Laravel\Lumen\Exceptions\Handler as ExceptionHandler;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpException;
|
||||
use Throwable;
|
||||
|
||||
class Handler extends ExceptionHandler
|
||||
{
|
||||
/**
|
||||
* A list of the exception types that should not be reported.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dontReport = [
|
||||
AuthorizationException::class,
|
||||
HttpException::class,
|
||||
ModelNotFoundException::class,
|
||||
ValidationException::class,
|
||||
];
|
||||
|
||||
/**
|
||||
* Report or log an exception.
|
||||
*
|
||||
* This is a great spot to send exceptions to Sentry, Bugsnag, etc.
|
||||
*
|
||||
* @param \Throwable $exception
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function report(Throwable $exception)
|
||||
{
|
||||
parent::report($exception);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render an exception into an HTTP response.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Throwable $exception
|
||||
* @return \Illuminate\Http\Response|\Illuminate\Http\JsonResponse
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function render($request, Throwable $exception)
|
||||
{
|
||||
return parent::render($request, $exception);
|
||||
}
|
||||
}
|
172
app/Http/Controllers/ApiController.php
Normal file
|
@ -0,0 +1,172 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Exception;
|
||||
use \Carbon\Carbon;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class ApiController extends Controller
|
||||
{
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
|
||||
public function getTasks() {
|
||||
$tasks = [];
|
||||
|
||||
$query = Task
|
||||
::leftJoin('groups', 'groups.id', 'tasks.group_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',
|
||||
'groups.name as group_name')
|
||||
->get()
|
||||
;
|
||||
|
||||
//dd($query->toSql());
|
||||
|
||||
foreach ($query as $t) {
|
||||
if (is_null($t->group_id)) {
|
||||
$group_id = $t->id;
|
||||
$group_name = 'ungrouped';
|
||||
}
|
||||
else {
|
||||
$group_id = $t->group_id;
|
||||
$group_name = $t->group_name;
|
||||
}
|
||||
|
||||
if (empty($tasks[$group_id])) {
|
||||
$tasks[$group_id] = [
|
||||
'id' => $group_id,
|
||||
'name' => $group_name,
|
||||
'tasks' => null
|
||||
];
|
||||
}
|
||||
$tasks[$group_id]['tasks'][$t->id] = $t;
|
||||
}
|
||||
|
||||
return response()->json($tasks);
|
||||
}
|
||||
|
||||
public function getTaskDetails(Request $request, $id) {
|
||||
$days = ($request->input('days', 15) - 1);
|
||||
|
||||
$task = Task::with(['group'])
|
||||
->findOrFail($id)
|
||||
;
|
||||
|
||||
if (! is_null($task)) {
|
||||
// First, we get the first date of the stats
|
||||
// In this case, one month ago
|
||||
$first_day = Carbon::now()->startOfDay()->subDays($days);
|
||||
// Then we get all history for the past month
|
||||
$history = $task
|
||||
->history()
|
||||
->orderBy('created_at', 'desc')
|
||||
->where('created_at', '>', $first_day->toDateString())
|
||||
->selectRaw('id, date(created_at) as date, created_at, status, duration, output')
|
||||
->get()
|
||||
;
|
||||
|
||||
// Then we start building an array for the entire month
|
||||
$stats = $times = [];
|
||||
$tmpdate = Carbon::now()->subDays($days);
|
||||
do {
|
||||
$stats['uptime'][$tmpdate->toDateString()] = [
|
||||
'up' => 0,
|
||||
'down' => 0
|
||||
];
|
||||
|
||||
$stats['times'][$tmpdate->toDateString()] = [
|
||||
'duration' => 0,
|
||||
'count' => 0
|
||||
];
|
||||
|
||||
$tmpdate = $tmpdate->addDay();
|
||||
}
|
||||
while ($tmpdate->lt(Carbon::now()));
|
||||
|
||||
// Then we populate the stats data
|
||||
$prev = null;
|
||||
if (! is_null($history)) {
|
||||
$history = $history->reverse();
|
||||
|
||||
foreach ($history as $k => $r) {
|
||||
if (empty($stats['uptime'][$r->date])) {
|
||||
$stats['uptime'][$r->date] = [
|
||||
'up' => 0,
|
||||
'down' => 0
|
||||
];
|
||||
}
|
||||
|
||||
// Populating the stats
|
||||
if ($r->status == 1) {
|
||||
++$stats['uptime'][$r->date]['up'];
|
||||
}
|
||||
else {
|
||||
++$stats['uptime'][$r->date]['down'];
|
||||
}
|
||||
|
||||
// Populating the response times
|
||||
if ($r->status == 1 && $r->duration > 0) {
|
||||
$stats['times'][$r->date]['duration'] += $r->duration;
|
||||
$stats['times'][$r->date]['count'] ++;
|
||||
}
|
||||
|
||||
// We only take tasks when status has changed between them
|
||||
if (! is_null($prev) && $r->status == $prev) {
|
||||
unset($history[$k]);
|
||||
}
|
||||
$prev = $r->status;
|
||||
}
|
||||
}
|
||||
|
||||
// Getting the notifications sent
|
||||
$notifications = $task
|
||||
->notifications()
|
||||
->with(['contact', 'task_history'])
|
||||
->where('notifications.created_at', '>', $first_day->toDateString())
|
||||
->orderBy('notifications.created_at', 'desc')
|
||||
->get()
|
||||
;
|
||||
|
||||
return response()->json([
|
||||
'task' => $task,
|
||||
'stats' => $stats,
|
||||
'history' => $history,
|
||||
'notifications' => $notifications,
|
||||
'first_day' => $first_day->toDateTimeString()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleTaskStatus(Request $request, $id) {
|
||||
$active = $request->input('active', null);
|
||||
|
||||
if (is_null($active)) {
|
||||
throw new ApiException('Invalid parameters');
|
||||
}
|
||||
$active = intval($active);
|
||||
|
||||
$task = Task::findOrFail($id);
|
||||
$task->active = $active;
|
||||
|
||||
if ($task->save()) {
|
||||
return response()->json($task);
|
||||
}
|
||||
else {
|
||||
throw new ApiException('Cannot disable this task');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ApiException extends Exception {}
|
10
app/Http/Controllers/Controller.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Laravel\Lumen\Routing\Controller as BaseController;
|
||||
|
||||
class Controller extends BaseController
|
||||
{
|
||||
//
|
||||
}
|
44
app/Http/Middleware/Authenticate.php
Normal file
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Auth\Factory as Auth;
|
||||
|
||||
class Authenticate
|
||||
{
|
||||
/**
|
||||
* The authentication guard factory instance.
|
||||
*
|
||||
* @var \Illuminate\Contracts\Auth\Factory
|
||||
*/
|
||||
protected $auth;
|
||||
|
||||
/**
|
||||
* Create a new middleware instance.
|
||||
*
|
||||
* @param \Illuminate\Contracts\Auth\Factory $auth
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(Auth $auth)
|
||||
{
|
||||
$this->auth = $auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @param string|null $guard
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next, $guard = null)
|
||||
{
|
||||
if ($this->auth->guard($guard)->guest()) {
|
||||
return response('Unauthorized.', 401);
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
20
app/Http/Middleware/ExampleMiddleware.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
|
||||
class ExampleMiddleware
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param \Closure $next
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle($request, Closure $next)
|
||||
{
|
||||
return $next($request);
|
||||
}
|
||||
}
|
24
app/Jobs/Job.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
abstract class Job implements ShouldQueue
|
||||
{
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Queueable Jobs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This job base class provides a central location to place any logic that
|
||||
| is shared across all of your jobs. The trait included with the class
|
||||
| provides access to the "queueOn" and "delay" queue helper methods.
|
||||
|
|
||||
*/
|
||||
|
||||
use InteractsWithQueue, Queueable, SerializesModels;
|
||||
}
|
31
app/Listeners/ExampleListener.php
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Events\ExampleEvent;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
|
||||
class ExampleListener
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param \App\Events\ExampleEvent $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle(ExampleEvent $event)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
48
app/Mail/TaskNotification.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Mail;
|
||||
|
||||
use App\Models\Notification;
|
||||
use Illuminate\Mail\Mailable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class TaskNotification extends Mailable
|
||||
{
|
||||
use SerializesModels;
|
||||
|
||||
/**
|
||||
* The order instance.
|
||||
*
|
||||
* @var \App\Models\Order
|
||||
*/
|
||||
protected $report;
|
||||
|
||||
/**
|
||||
* Create a new message instance.
|
||||
*
|
||||
* @param \App\Models\Order $order
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($report)
|
||||
{
|
||||
$this->report = $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the message.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
return $this
|
||||
->subject('Monitolite Alert Report')
|
||||
->from(env('MAIL_FROM_ADDRESS', 'noreply@monitolite.fr'), env('MAIL_FROM_NAME', 'Monitolite'))
|
||||
->markdown('emails.notification')
|
||||
->with([
|
||||
'report' => $this->report,
|
||||
'url' => env('APP_URL')
|
||||
])
|
||||
;
|
||||
}
|
||||
}
|
22
app/Models/Contact.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Contact extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
24
app/Models/Group.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Group extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
|
||||
public function tasks() {
|
||||
return $this->hasMany('App\Models\Task');
|
||||
}
|
||||
|
||||
}
|
40
app/Models/Notification.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Notification extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
public function contact() {
|
||||
return $this->belongsTo('App\Models\Contact');
|
||||
}
|
||||
|
||||
public function task_history() {
|
||||
return $this->belongsTo('App\Models\TaskHistory');
|
||||
}
|
||||
|
||||
public static function addNotificationTask(TaskHistory $history) {
|
||||
$contacts = $history->task->contacts()->get();
|
||||
if (! is_null($contacts)) {
|
||||
foreach ($contacts as $c) {
|
||||
$notification = new Notification;
|
||||
$notification->contact_id = $c->id;
|
||||
$notification->task_history_id = $history->id;
|
||||
$notification->status = 'pending';
|
||||
$notification->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
40
app/Models/Task.php
Normal file
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Task extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
public $timestamps = [
|
||||
'created_at',
|
||||
'updated_at',
|
||||
'executed_at'
|
||||
];
|
||||
|
||||
public function group() {
|
||||
return $this->belongsTo('App\Models\Group');
|
||||
}
|
||||
|
||||
public function contacts() {
|
||||
return $this->belongsToMany('App\Models\Contact');
|
||||
}
|
||||
|
||||
public function history() {
|
||||
return $this->hasMany('App\Models\TaskHistory');
|
||||
}
|
||||
|
||||
public function notifications() {
|
||||
return $this->hasManyThrough('App\Models\Notification', 'App\Models\TaskHistory');
|
||||
}
|
||||
}
|
22
app/Models/TaskContact.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class TaskContact extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
29
app/Models/TaskHistory.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class TaskHistory extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $table = 'task_history';
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [];
|
||||
|
||||
public function notifications() {
|
||||
return $this->hasMany('App\Models\Notification');
|
||||
}
|
||||
|
||||
public function task() {
|
||||
return $this->belongsTo('App\Models\Task');
|
||||
}
|
||||
|
||||
}
|
33
app/Models/User.php
Normal file
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Auth\Authenticatable;
|
||||
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
|
||||
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Laravel\Lumen\Auth\Authorizable;
|
||||
|
||||
class User extends Model implements AuthenticatableContract, AuthorizableContract
|
||||
{
|
||||
use Authenticatable, Authorizable, HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', 'email',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes excluded from the model's JSON form.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
];
|
||||
}
|
18
app/Providers/AppServiceProvider.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
39
app/Providers/AuthServiceProvider.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Boot the authentication services for the application.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
// Here you may define how you wish users to be authenticated for your Lumen
|
||||
// application. The callback which receives the incoming request instance
|
||||
// should return either a User instance or null. You're free to obtain
|
||||
// the User instance via an API token or any other method necessary.
|
||||
|
||||
$this->app['auth']->viaRequest('api', function ($request) {
|
||||
if ($request->input('api_token')) {
|
||||
return User::where('api_token', $request->input('api_token'))->first();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
19
app/Providers/EventServiceProvider.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Laravel\Lumen\Providers\EventServiceProvider as ServiceProvider;
|
||||
|
||||
class EventServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* The event listener mappings for the application.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $listen = [
|
||||
\App\Events\ExampleEvent::class => [
|
||||
\App\Listeners\ExampleListener::class,
|
||||
],
|
||||
];
|
||||
}
|
35
artisan
Executable file
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env php
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Create The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| First we need to get an application instance. This creates an instance
|
||||
| of the application / container and bootstraps the application so it
|
||||
| is ready to receive HTTP / Console requests from the environment.
|
||||
|
|
||||
*/
|
||||
|
||||
$app = require __DIR__.'/bootstrap/app.php';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Run The Artisan Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| When we run the console application, the current CLI command will be
|
||||
| executed in this console and the response sent back to a terminal
|
||||
| or another output device for the developers. Here goes nothing!
|
||||
|
|
||||
*/
|
||||
|
||||
$kernel = $app->make(
|
||||
'Illuminate\Contracts\Console\Kernel'
|
||||
);
|
||||
|
||||
exit($kernel->handle(new ArgvInput, new ConsoleOutput));
|
134
bootstrap/app.php
Normal file
|
@ -0,0 +1,134 @@
|
|||
<?php
|
||||
|
||||
require_once __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
(new Laravel\Lumen\Bootstrap\LoadEnvironmentVariables(
|
||||
dirname(__DIR__)
|
||||
))->bootstrap();
|
||||
|
||||
date_default_timezone_set(env('APP_TIMEZONE', 'UTC'));
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Create The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here we will load the environment and create the application instance
|
||||
| that serves as the central piece of this framework. We'll use this
|
||||
| application as an "IoC" container and router for this framework.
|
||||
|
|
||||
*/
|
||||
|
||||
$app = new Laravel\Lumen\Application(
|
||||
dirname(__DIR__)
|
||||
);
|
||||
|
||||
$app->withFacades();
|
||||
|
||||
$app->withEloquent();
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register Container Bindings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Now we will register a few bindings in the service container. We will
|
||||
| register the exception handler and the console kernel. You may add
|
||||
| your own bindings here if you like or you can make another file.
|
||||
|
|
||||
*/
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Debug\ExceptionHandler::class,
|
||||
App\Exceptions\Handler::class
|
||||
);
|
||||
|
||||
$app->singleton(
|
||||
Illuminate\Contracts\Console\Kernel::class,
|
||||
App\Console\Kernel::class
|
||||
);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register Config Files
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Now we will register the "app" configuration file. If the file exists in
|
||||
| your configuration directory it will be loaded; otherwise, we'll load
|
||||
| the default version. You may register other files below as needed.
|
||||
|
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a required HACK for Lumen
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function setDbTimezone() {
|
||||
$offset = timezone_offset_get(new \DateTimeZone(date_default_timezone_get()), new \DateTime());
|
||||
return sprintf("%s%02d:%02d", ($offset >= 0) ? '+' : '-', abs($offset / 3600), abs($offset % 3600));
|
||||
}
|
||||
|
||||
$app->configure('app');
|
||||
$app->configure('database');
|
||||
$app->configure('mail');
|
||||
$app->alias('mail.manager', Illuminate\Mail\MailManager::class);
|
||||
$app->alias('mail.manager', Illuminate\Contracts\Mail\Factory::class);
|
||||
|
||||
$app->alias('mailer', Illuminate\Mail\Mailer::class);
|
||||
$app->alias('mailer', Illuminate\Contracts\Mail\Mailer::class);
|
||||
$app->alias('mailer', Illuminate\Contracts\Mail\MailQueue::class);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register Middleware
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next, we will register the middleware with the application. These can
|
||||
| be global middleware that run before and after each request into a
|
||||
| route or middleware that'll be assigned to some specific routes.
|
||||
|
|
||||
*/
|
||||
|
||||
// $app->middleware([
|
||||
// App\Http\Middleware\ExampleMiddleware::class
|
||||
// ]);
|
||||
|
||||
// $app->routeMiddleware([
|
||||
// 'auth' => App\Http\Middleware\Authenticate::class,
|
||||
// ]);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Register Service Providers
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here we will register all of the application's service providers which
|
||||
| are used to bind services into the container. Service providers are
|
||||
| totally optional, so you are not required to uncomment this line.
|
||||
|
|
||||
*/
|
||||
|
||||
// $app->register(App\Providers\AppServiceProvider::class);
|
||||
// $app->register(App\Providers\AuthServiceProvider::class);
|
||||
// $app->register(App\Providers\EventServiceProvider::class);
|
||||
$app->register(Illuminate\Mail\MailServiceProvider::class);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Load The Application Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Next we will include the routes file so that they can all be added to
|
||||
| the application. This will provide all of the URLs the application
|
||||
| can respond to, as well as the controllers that may handle them.
|
||||
|
|
||||
*/
|
||||
|
||||
$app->router->group([
|
||||
'namespace' => 'App\Http\Controllers',
|
||||
], function ($router) {
|
||||
require __DIR__.'/../routes/web.php';
|
||||
});
|
||||
|
||||
return $app;
|
41
composer.json
Normal file
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"name": "laravel/lumen",
|
||||
"description": "The Laravel Lumen Framework.",
|
||||
"keywords": ["framework", "laravel", "lumen"],
|
||||
"license": "MIT",
|
||||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0",
|
||||
"illuminate/mail": "^8.77",
|
||||
"laravel/lumen-framework": "^8.3.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"fakerphp/faker": "^1.9.1",
|
||||
"mockery/mockery": "^1.3.1",
|
||||
"phpunit/phpunit": "^9.5.10"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"App\\": "app/",
|
||||
"Database\\Factories\\": "database/factories/",
|
||||
"Database\\Seeders\\": "database/seeders/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"tests/"
|
||||
]
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": "dist",
|
||||
"sort-packages": true,
|
||||
"optimize-autoloader": true
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"scripts": {
|
||||
"post-root-package-install": [
|
||||
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
|
||||
]
|
||||
}
|
||||
}
|
7847
composer.lock
generated
Normal file
137
config/database.php
Normal file
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Database Connection Name
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may specify which of the database connections below you wish
|
||||
| to use as your default connection for all database work. Of course
|
||||
| you may use many connections at once using the Database library.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('DB_CONNECTION', 'mysql'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Database Connections
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here are each of the database connections setup for your application.
|
||||
| Of course, examples of configuring each database platform that is
|
||||
| supported by Laravel is shown below to make development simple.
|
||||
|
|
||||
|
|
||||
| All database work in Laravel is done through the PHP PDO facilities
|
||||
| so make sure you have the driver for your particular database of
|
||||
| choice installed on your machine before you begin development.
|
||||
|
|
||||
*/
|
||||
|
||||
'connections' => [
|
||||
|
||||
'sqlite' => [
|
||||
'driver' => 'sqlite',
|
||||
'database' => env('DB_DATABASE', database_path('database.sqlite')),
|
||||
'prefix' => env('DB_PREFIX', ''),
|
||||
],
|
||||
|
||||
'mysql' => [
|
||||
'driver' => 'mysql',
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', 3306),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'unix_socket' => env('DB_SOCKET', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8mb4'),
|
||||
'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'),
|
||||
'prefix' => env('DB_PREFIX', ''),
|
||||
'strict' => env('DB_STRICT_MODE', true),
|
||||
'engine' => env('DB_ENGINE', null),
|
||||
'timezone' => setDbTimezone(),
|
||||
],
|
||||
|
||||
'pgsql' => [
|
||||
'driver' => 'pgsql',
|
||||
'host' => env('DB_HOST', '127.0.0.1'),
|
||||
'port' => env('DB_PORT', 5432),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => env('DB_PREFIX', ''),
|
||||
'schema' => env('DB_SCHEMA', 'public'),
|
||||
'sslmode' => env('DB_SSL_MODE', 'prefer'),
|
||||
],
|
||||
|
||||
'sqlsrv' => [
|
||||
'driver' => 'sqlsrv',
|
||||
'host' => env('DB_HOST', 'localhost'),
|
||||
'port' => env('DB_PORT', 1433),
|
||||
'database' => env('DB_DATABASE', 'forge'),
|
||||
'username' => env('DB_USERNAME', 'forge'),
|
||||
'password' => env('DB_PASSWORD', ''),
|
||||
'charset' => env('DB_CHARSET', 'utf8'),
|
||||
'prefix' => env('DB_PREFIX', ''),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Migration Repository Table
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This table keeps track of all the migrations that have already run for
|
||||
| your application. Using this information, we can determine which of
|
||||
| the migrations on disk haven't actually been run in the database.
|
||||
|
|
||||
*/
|
||||
|
||||
'migrations' => 'migrations',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Redis Databases
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Redis is an open source, fast, and advanced key-value store that also
|
||||
| provides a richer set of commands than a typical key-value systems
|
||||
| such as APC or Memcached. Laravel makes it easy to dig right in.
|
||||
|
|
||||
*/
|
||||
|
||||
'redis' => [
|
||||
|
||||
'client' => env('REDIS_CLIENT', 'phpredis'),
|
||||
|
||||
'options' => [
|
||||
'cluster' => env('REDIS_CLUSTER', 'redis'),
|
||||
'prefix' => env('REDIS_PREFIX', Str::slug(env('APP_NAME', 'lumen'), '_').'_database_'),
|
||||
],
|
||||
|
||||
'default' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'password' => env('REDIS_PASSWORD', null),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_DB', '0'),
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'url' => env('REDIS_URL'),
|
||||
'host' => env('REDIS_HOST', '127.0.0.1'),
|
||||
'password' => env('REDIS_PASSWORD', null),
|
||||
'port' => env('REDIS_PORT', '6379'),
|
||||
'database' => env('REDIS_CACHE_DB', '1'),
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
117
config/mail.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Default Mailer
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| This option controls the default mailer that is used to send any email
|
||||
| messages sent by your application. Alternative mailers may be setup
|
||||
| and used as needed; however, this mailer will be used by default.
|
||||
|
|
||||
*/
|
||||
|
||||
'default' => env('MAIL_MAILER', 'smtp'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Mailer Configurations
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here you may configure all of the mailers used by your application plus
|
||||
| their respective settings. Several examples have been configured for
|
||||
| you and you are free to add your own as your application requires.
|
||||
|
|
||||
| Laravel supports a variety of mail "transport" drivers to be used while
|
||||
| sending an e-mail. You will specify which one you are using for your
|
||||
| mailers below. You are free to add additional mailers as required.
|
||||
|
|
||||
| Supported: "smtp", "sendmail", "mailgun", "ses",
|
||||
| "postmark", "log", "array", "failover"
|
||||
|
|
||||
*/
|
||||
|
||||
'mailers' => [
|
||||
'smtp' => [
|
||||
'transport' => 'smtp',
|
||||
'host' => env('MAIL_HOST', 'smtp.mailgun.org'),
|
||||
'port' => env('MAIL_PORT', 587),
|
||||
'encryption' => env('MAIL_ENCRYPTION', 'tls'),
|
||||
'username' => env('MAIL_USERNAME'),
|
||||
'password' => env('MAIL_PASSWORD'),
|
||||
'timeout' => null,
|
||||
],
|
||||
|
||||
'ses' => [
|
||||
'transport' => 'ses',
|
||||
],
|
||||
|
||||
'mailgun' => [
|
||||
'transport' => 'mailgun',
|
||||
],
|
||||
|
||||
'postmark' => [
|
||||
'transport' => 'postmark',
|
||||
],
|
||||
|
||||
'sendmail' => [
|
||||
'transport' => 'sendmail',
|
||||
'path' => env('MAIL_SENDMAIL_PATH', '/usr/sbin/sendmail -t -i'),
|
||||
],
|
||||
|
||||
'log' => [
|
||||
'transport' => 'log',
|
||||
'channel' => env('MAIL_LOG_CHANNEL'),
|
||||
],
|
||||
|
||||
'array' => [
|
||||
'transport' => 'array',
|
||||
],
|
||||
|
||||
'failover' => [
|
||||
'transport' => 'failover',
|
||||
'mailers' => [
|
||||
'smtp',
|
||||
'log',
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Global "From" Address
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| You may wish for all e-mails sent by your application to be sent from
|
||||
| the same address. Here, you may specify a name and address that is
|
||||
| used globally for all e-mails that are sent by your application.
|
||||
|
|
||||
*/
|
||||
|
||||
'from' => [
|
||||
'address' => env('MAIL_FROM_ADDRESS', 'noreply@monitolite.fr'),
|
||||
'name' => env('MAIL_FROM_NAME', 'Monitolite'),
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Markdown Mail Settings
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| If you are using Markdown based email rendering, you may configure your
|
||||
| theme and component paths here, allowing you to customize the design
|
||||
| of the emails. Or, you may simply stick with the Laravel defaults!
|
||||
|
|
||||
*/
|
||||
|
||||
'markdown' => [
|
||||
'theme' => 'default',
|
||||
|
||||
'paths' => [
|
||||
resource_path('views/vendor/mail'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
29
database/factories/UserFactory.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The name of the factory's corresponding model.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $model = User::class;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->name,
|
||||
'email' => $this->faker->unique()->safeEmail,
|
||||
];
|
||||
}
|
||||
}
|
0
database/migrations/.gitkeep
Normal file
|
@ -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
|
@ -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');
|
||||
});
|
||||
}
|
||||
}
|
18
database/seeders/DatabaseSeeder.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
{
|
||||
/**
|
||||
* Run the database seeds.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
// $this->call('UsersTableSeeder');
|
||||
}
|
||||
}
|
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_TYPE'};
|
||||
my $hostname = $ENV{'DB_HOST'};
|
||||
my $database = $ENV{'DB_NAME'};
|
||||
my $login = $ENV{'DB_USER'};
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
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 => 0 },
|
||||
protocols_allowed => ['http', 'https']
|
||||
);
|
||||
$check->timeout(5);
|
||||
$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) = @_;
|
||||
my $query = $dbh->prepare('INSERT INTO tasks_history (status, datetime, task_id) VALUES(' . $status . ', "'.$datetime.'", ' . $task_id . ')');
|
||||
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 = 'ALERT: 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 = 'RECOVERY: 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;
|
||||
}
|
16852
package-lock.json
generated
Normal file
20
package.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"laravel-mix": "^6.0.39",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
"sass": "^1.45.0",
|
||||
"sass-loader": "^12.4.0",
|
||||
"vue-loader": "^15.9.8",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"apexcharts": "^3.32.0",
|
||||
"axios": "^0.24.0",
|
||||
"moment": "^2.29.1",
|
||||
"vue": "^2.6.14",
|
||||
"vue-apexcharts": "^1.6.2",
|
||||
"vue-loading-overlay": "^3.4.2",
|
||||
"vue-router": "^3.5.3",
|
||||
"vuex": "^3.6.2"
|
||||
}
|
||||
}
|
17
phpunit.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="vendor/autoload.php"
|
||||
colors="true"
|
||||
>
|
||||
<testsuites>
|
||||
<testsuite name="Application Test Suite">
|
||||
<directory suffix="Test.php">./tests</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="CACHE_DRIVER" value="array"/>
|
||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||
</php>
|
||||
</phpunit>
|
21
public/.htaccess
Normal file
|
@ -0,0 +1,21 @@
|
|||
<IfModule mod_rewrite.c>
|
||||
<IfModule mod_negotiation.c>
|
||||
Options -MultiViews -Indexes
|
||||
</IfModule>
|
||||
|
||||
RewriteEngine On
|
||||
|
||||
# Handle Authorization Header
|
||||
RewriteCond %{HTTP:Authorization} .
|
||||
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
|
||||
|
||||
# Redirect Trailing Slashes If Not A Folder...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [L,R=301]
|
||||
|
||||
# Handle Front Controller...
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteRule ^ index.php [L]
|
||||
</IfModule>
|
4
public/css/app.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
@import url(https://fonts.googleapis.com/css2?family=Hind:wght@300;400;500;600;700&display=swap);
|
||||
@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)}}
|
||||
|
||||
/*# sourceMappingURL=app.css.map*/
|
1
public/css/app.css.map
Normal file
BIN
public/fonts/digital.ttf
Normal file
BIN
public/img/bush.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
1
public/img/disable.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title/><g data-name="Layer 51" id="Layer_51"><path d="M16,2A14,14,0,1,0,30,16,14,14,0,0,0,16,2ZM4,16A11.89,11.89,0,0,1,6.85,8.26L23.74,25.15A12,12,0,0,1,4,16Zm21.15,7.74L8.26,6.85A12,12,0,0,1,25.15,23.74Z"/></g></svg>
|
After Width: | Height: | Size: 300 B |
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/down.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M22 15h-3V3h3a1 1 0 0 1 1 1v10a1 1 0 0 1-1 1zm-5.293 1.293l-6.4 6.4a.5.5 0 0 1-.654.047L8.8 22.1a1.5 1.5 0 0 1-.553-1.57L9.4 16H3a2 2 0 0 1-2-2v-2.104a2 2 0 0 1 .15-.762L4.246 3.62A1 1 0 0 1 5.17 3H16a1 1 0 0 1 1 1v11.586a1 1 0 0 1-.293.707z"/></g></svg>
|
After Width: | Height: | Size: 385 B |
1
public/img/error.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M4 20v-6a8 8 0 1 1 16 0v6h1v2H3v-2h1zm2-6h2a4 4 0 0 1 4-4V8a6 6 0 0 0-6 6zm5-12h2v3h-2V2zm8.778 2.808l1.414 1.414-2.12 2.121-1.415-1.414 2.121-2.121zM2.808 6.222l1.414-1.414 2.121 2.12L4.93 8.344 2.808 6.222z"/></g></svg>
|
After Width: | Height: | Size: 352 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
1
public/img/external.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M10 6v2H5v11h11v-5h2v6a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h6zm11-3v8h-2V6.413l-7.793 7.794-1.414-1.414L17.585 5H13V3h8z"/></g></svg>
|
After Width: | Height: | Size: 273 B |
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 |
1
public/img/http.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg id="Layer_1" style="enable-background:new 0 0 128 128;" version="1.1" viewBox="0 0 128 128" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><path d="M64,126c34.2,0,62-27.8,62-62S98.2,2,64,2S2,29.8,2,64S29.8,126,64,126z M16,88.7l25.2-0.2c2.8,10.1,7.5,19.9,13.9,28.7 C38,114.4,23.7,103.5,16,88.7z M47.6,47H79c2.3,11,2.3,22.3,0.2,33.3l-31.6,0.2C45.3,69.4,45.3,58,47.6,47z M63.3,114.9 c-6.3-8.1-10.9-17-13.7-26.4l27.5-0.2C74.2,97.7,69.6,106.7,63.3,114.9z M71.3,117.5c6.6-9,11.3-18.9,14.1-29.3l26.9-0.2 C104.5,103.7,89.3,115,71.3,117.5z M118,64c0,5.6-0.9,11-2.4,16l-28.3,0.2c2-11,1.9-22.2-0.2-33.2h28.1C117,52.3,118,58.1,118,64z M111.8,39H85.2c-2.9-10-7.5-19.7-13.9-28.5C89,12.9,103.9,23.8,111.8,39z M76.9,39H49.7c2.9-9.2,7.4-17.9,13.6-25.9 C69.5,21.1,74,29.8,76.9,39z M55.1,10.8C48.8,19.5,44.2,29,41.4,39H16.2C23.9,24.3,38.1,13.6,55.1,10.8z M39.5,47 c-2.1,11.1-2.1,22.4-0.1,33.5l-26.7,0.2C10.9,75.4,10,69.8,10,64c0-5.9,1-11.7,2.8-17H39.5z"/></g></svg>
|
After Width: | Height: | Size: 1 KiB |
1
public/img/info.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-11v6h2v-6h-2zm0-4v2h2V7h-2z"/></g></svg>
|
After Width: | Height: | Size: 248 B |
1
public/img/menu.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M12 3c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 14c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-7c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></g></svg>
|
After Width: | Height: | Size: 290 B |
1
public/img/off.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg fill="none" height="20" viewBox="0 0 20 20" width="20" xmlns="http://www.w3.org/2000/svg"><path d="M6 12C4.89543 12 4 11.1046 4 10C4 8.89543 4.89543 8 6 8C7.10457 8 8 8.89543 8 10C8 11.1046 7.10457 12 6 12Z" fill="#212121"/><path d="M18 10C18 7.79086 16.2091 6 14 6H6C3.79086 6 2 7.79086 2 10C2 12.2091 3.79086 14 6 14H14C16.2091 14 18 12.2091 18 10ZM14 7C15.6569 7 17 8.34315 17 10C17 11.6569 15.6569 13 14 13H6C4.34315 13 3 11.6569 3 10C3 8.34315 4.34315 7 6 7H14Z" fill="#212121"/></svg>
|
After Width: | Height: | Size: 517 B |
1
public/img/on.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 7C4.23858 7 2 9.23858 2 12C2 14.7614 4.23858 17 7 17H17C19.7614 17 22 14.7614 22 12C22 9.23858 19.7614 7 17 7H7ZM16.75 14.5C15.3693 14.5 14.25 13.3807 14.25 12C14.25 10.6193 15.3693 9.5 16.75 9.5C18.1307 9.5 19.25 10.6193 19.25 12C19.25 13.3807 18.1307 14.5 16.75 14.5Z" fill="#212121"/></svg>
|
After Width: | Height: | Size: 422 B |
1
public/img/ping.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg enable-background="new 0 0 32 32" id="Layer_4" version="1.1" viewBox="0 0 32 32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g><polygon fill="none" points="12,3 12,8 31,8 31,14 1,14 " stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/><polygon fill="none" points="20,29 20,24 1,24 1,18 31,18 " stroke="#000000" stroke-linejoin="round" stroke-miterlimit="10" stroke-width="2"/></g></svg>
|
After Width: | Height: | Size: 508 B |
1
public/img/see.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M1.181 12C2.121 6.88 6.608 3 12 3c5.392 0 9.878 3.88 10.819 9-.94 5.12-5.427 9-10.819 9-5.392 0-9.878-3.88-10.819-9zM12 17a5 5 0 1 0 0-10 5 5 0 0 0 0 10zm0-2a3 3 0 1 1 0-6 3 3 0 0 1 0 6z"/></g></svg>
|
After Width: | Height: | Size: 330 B |
1
public/img/success.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M10 15.172l9.192-9.193 1.415 1.414L10 18l-6.364-6.364 1.414-1.414z"/></g></svg>
|
After Width: | Height: | Size: 210 B |
1
public/img/trash.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M4 8h16v13a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V8zm2 2v10h12V10H6zm3 2h2v6H9v-6zm4 0h2v6h-2v-6zM7 5V3a1 1 0 0 1 1-1h8a1 1 0 0 1 1 1v2h5v2H2V5h5zm2-1v1h6V4H9z"/></g></svg>
|
After Width: | Height: | Size: 294 B |
1
public/img/unknown.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm2-1.645A3.502 3.502 0 0 0 12 6.5a3.501 3.501 0 0 0-3.433 2.813l1.962.393A1.5 1.5 0 1 1 12 11.5a1 1 0 0 0-1 1V14h2v-.645z"/></g></svg>
|
After Width: | Height: | Size: 354 B |
1
public/img/up.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M2 9h3v12H2a1 1 0 0 1-1-1V10a1 1 0 0 1 1-1zm5.293-1.293l6.4-6.4a.5.5 0 0 1 .654-.047l.853.64a1.5 1.5 0 0 1 .553 1.57L14.6 8H21a2 2 0 0 1 2 2v2.104a2 2 0 0 1-.15.762l-3.095 7.515a1 1 0 0 1-.925.619H8a1 1 0 0 1-1-1V8.414a1 1 0 0 1 .293-.707z"/></g></svg>
|
After Width: | Height: | Size: 383 B |
1
public/img/warning.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M0 0h24v24H0z" fill="none"/><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-1-7v2h2v-2h-2zm0-8v6h2V7h-2z"/></g></svg>
|
After Width: | Height: | Size: 247 B |
28
public/index.php
Normal file
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Create The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| First we need to get an application instance. This creates an instance
|
||||
| of the application / container and bootstraps the application so it
|
||||
| is ready to receive HTTP / Console requests from the environment.
|
||||
|
|
||||
*/
|
||||
|
||||
$app = require __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Run The Application
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Once we have the application, we can handle the incoming request
|
||||
| through the kernel, and send the associated response back to
|
||||
| the client's browser allowing them to enjoy the creative
|
||||
| and wonderful application we have prepared for them.
|
||||
|
|
||||
*/
|
||||
|
||||
$app->run();
|
3
public/js/app.js
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
4
public/mix-manifest.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"/js/app.js": "/js/app.js",
|
||||
"/css/app.css": "/css/app.css"
|
||||
}
|
86
resources/js/app.js
Normal file
|
@ -0,0 +1,86 @@
|
|||
//window.Vue = require('vue')
|
||||
|
||||
import Vue from 'vue'
|
||||
|
||||
import Vuex from 'vuex'
|
||||
Vue.use(Vuex)
|
||||
|
||||
import VueRouter from 'vue-router'
|
||||
Vue.use(VueRouter)
|
||||
|
||||
import axios from 'axios'
|
||||
Vue.prototype.$http = axios
|
||||
|
||||
import moment from 'moment'
|
||||
Vue.prototype.moment = moment
|
||||
|
||||
import VueApexCharts from 'vue-apexcharts'
|
||||
Vue.use(VueApexCharts)
|
||||
Vue.component('apexchart', VueApexCharts)
|
||||
|
||||
import VueLoading from 'vue-loading-overlay';
|
||||
import 'vue-loading-overlay/dist/vue-loading.css';
|
||||
Vue.use(VueLoading, {
|
||||
// Optional parameters
|
||||
//container: this.fullPage ? null : this.$refs.formContainer,
|
||||
canCancel: true,
|
||||
backgroundColor: '#000',
|
||||
color: '#0a9f9a',
|
||||
width: 128,
|
||||
height: 128,
|
||||
opacity: 0.9,
|
||||
loader: 'dots'
|
||||
})
|
||||
|
||||
import Home from '../views/app.vue'
|
||||
import TaskDetails from '../views/taskdetails.vue'
|
||||
const router = new VueRouter({
|
||||
mode: 'history',
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
path: '/task/:id',
|
||||
name: 'taskdetails',
|
||||
component: TaskDetails,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
tasks: null
|
||||
},
|
||||
mutations: {
|
||||
setTasks(state, tasks) {
|
||||
state.tasks = tasks
|
||||
},
|
||||
updateTask(state, update) {
|
||||
let tasks = state.tasks
|
||||
|
||||
if (
|
||||
tasks.hasOwnProperty(update.group_id) &&
|
||||
tasks[update.group_id].hasOwnProperty('tasks') &&
|
||||
tasks[update.group_id]['tasks'].hasOwnProperty(update.id)
|
||||
) {
|
||||
tasks[update.group_id]['tasks'][update.id] = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var runApp = function() {
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
components: { Home },
|
||||
store,
|
||||
}).$mount('#app')
|
||||
}
|
||||
|
||||
window.addEventListener('load', function () {
|
||||
runApp();
|
||||
})
|
280
resources/sass/app.scss
Normal file
|
@ -0,0 +1,280 @@
|
|||
@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
|
||||
**/
|
||||
$bg_color: #0a9f9a;
|
||||
$up_color: #8adf8a;
|
||||
$down_color: #f79292;
|
||||
$unknown_color: rgb(245, 214, 158);
|
||||
$inactive_color: #dfdfdf;
|
||||
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
scroll-behavior: smooth;
|
||||
|
||||
body {
|
||||
padding: 10px;
|
||||
font-family: 'Hind', sans-serif;
|
||||
font-size: 1rem;
|
||||
color: #3D3D3D;
|
||||
background-image: url(../img/bush.png);
|
||||
background-attachment: fixed;
|
||||
|
||||
|
||||
a, a:visited {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
max-width: 1000px;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
margin-top: .8rem;
|
||||
margin-bottom: .8rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.4rem;
|
||||
text-align: center;
|
||||
margin: 3rem 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.4rem;
|
||||
background-color: $bg_color;
|
||||
margin: 0;
|
||||
padding: .8rem;
|
||||
color: rgb(240, 240, 240);
|
||||
position: relative;
|
||||
|
||||
small {
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
right: .7rem;
|
||||
top: .7rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
div.round {
|
||||
-webkit-box-shadow: 3px 3px 6px 0px rgba(0,0,0,0.3);
|
||||
-moz-box-shadow: 3px 3px 6px 0px rgba(0,0,0,0.3);
|
||||
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;
|
||||
|
||||
h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
img {
|
||||
vertical-align: sub;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #ABC;
|
||||
font-size: 14px;
|
||||
border-spacing : 0;
|
||||
border-collapse : collapse;
|
||||
|
||||
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;
|
||||
vertical-align: middle;
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
&.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 {
|
||||
|
||||
.new-group {
|
||||
margin: .2rem;
|
||||
display: inline-block;
|
||||
border-radius: .4rem;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
|
||||
.square {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
line-height: 1.2rem;
|
||||
min-width: 1.4rem;
|
||||
padding: .2rem .6rem;
|
||||
|
||||
&:not(:first-of-type) {
|
||||
border-left: 1px solid white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.tasks {
|
||||
.task {
|
||||
margin-top: 2rem;
|
||||
padding: 0;
|
||||
-webkit-box-shadow: 3px 3px 6px 0px rgba(0,0,0,0.3);
|
||||
-moz-box-shadow: 3px 3px 6px 0px rgba(0,0,0,0.3);
|
||||
box-shadow: 3px 3px 6px 0px rgba(0,0,0,0.3);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.spacer {
|
||||
clear:both;
|
||||
line-height: 0;
|
||||
padding: 0;
|
||||
margin:0;
|
||||
}
|
||||
|
||||
.block-content {
|
||||
padding: .8rem;
|
||||
}
|
||||
|
||||
|
||||
.highlight {
|
||||
background-color: #166260;
|
||||
padding: 0px 1rem;
|
||||
display: inline-block;
|
||||
color: #FFF;
|
||||
vertical-align: middle;
|
||||
border-radius: .5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.small {
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.up {
|
||||
background-color: $up_color;
|
||||
}
|
||||
|
||||
.down {
|
||||
background-color: $down_color;
|
||||
}
|
||||
|
||||
.unknown {
|
||||
background-color: $unknown_color;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
background-color: $inactive_color !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.refreshed-time {
|
||||
text-align: right;
|
||||
font-size: .8rem;
|
||||
margin-bottom: 2rem;
|
||||
|
||||
.clock {
|
||||
font-family: Digital7;
|
||||
font-size: 1.2rem;
|
||||
background-color: #000;
|
||||
border-radius: 4px;
|
||||
color: #FFF;
|
||||
padding: .3rem .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
0
resources/views/.gitkeep
Normal file
16
resources/views/app.blade.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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="{{ url('js/app.js') }}"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{{ url('css/app.css') }}" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app">
|
||||
<router-view></router-view>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
64
resources/views/app.vue
Normal file
|
@ -0,0 +1,64 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<h1>MonitoLite Dashboard</h1>
|
||||
<p class="refreshed-time">Last refresh: <br /><span class="clock">{{ refreshedTime }}</span></p>
|
||||
<quick-view></quick-view>
|
||||
<task-list></task-list>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import TaskList from './components/tasklist.vue'
|
||||
import QuickView from './components/quickview.vue'
|
||||
export default{
|
||||
components: {
|
||||
QuickView,
|
||||
TaskList,
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
refreshed_time: null,
|
||||
refresh: null,
|
||||
loading: true,
|
||||
color: '#FF0000',
|
||||
size: '10rem',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
refreshedTime: function() {
|
||||
return this.refreshed_time != null ? this.moment(this.refreshed_time).format('HH:mm:ss') : 'never'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getTasks: function() {
|
||||
this.$http.get('/api/getTasks')
|
||||
.then(response => this.$store.commit('setTasks', response.data))
|
||||
.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.')
|
||||
})
|
||||
.then(() => {
|
||||
this.refreshed_time = this.moment();
|
||||
this.loading.hide()
|
||||
})
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
clearTimeout(this.refresh)
|
||||
next();
|
||||
},
|
||||
mounted: function() {
|
||||
this.loading = this.$loading.show()
|
||||
this.getTasks()
|
||||
this.refresh = window.setInterval(() => {
|
||||
this.getTasks();
|
||||
}, 60000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
24
resources/views/components/addtask.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<div>
|
||||
<form
|
||||
v-on:submit.prevent="addTask"
|
||||
>
|
||||
<button>Add task</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: [
|
||||
'tasks'
|
||||
],
|
||||
methods: {
|
||||
addTask: function() {
|
||||
this.$http.post('api.php?a=add_task')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
65
resources/views/components/quickview.vue
Normal file
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<div class="quick-view round">
|
||||
<h3>
|
||||
Quick overview
|
||||
</h3>
|
||||
<div class="block-content">
|
||||
<div
|
||||
v-if="tasks && Object.keys(tasks).length > 0"
|
||||
>
|
||||
<div
|
||||
v-for="group in tasks"
|
||||
v-bind:key="group.id"
|
||||
class="new-group"
|
||||
:title="'Group: '+group.name"
|
||||
>
|
||||
<a :href="'#group-'+group.id">
|
||||
<p
|
||||
v-for="task in group.tasks"
|
||||
v-bind:key="task.id"
|
||||
:href="'#task-'+task.id"
|
||||
:class="statusText(task.status)+(task.active == 0 ? ' inactive' : '')"
|
||||
class="square"
|
||||
>
|
||||
<span class="small">{{task.id }}</span>
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
<p class="spacer"> </p>
|
||||
</div>
|
||||
<div
|
||||
class="no-data"
|
||||
v-else
|
||||
>
|
||||
Sorry, there is no task here.
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
tasks: function() {
|
||||
return this.$store.state.tasks
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
statusText: function (status) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return 'up';
|
||||
break;
|
||||
case 0:
|
||||
return 'down';
|
||||
break;
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
116
resources/views/components/tasklist.vue
Normal file
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<div class="tasks">
|
||||
<div
|
||||
v-for="group in tasks"
|
||||
v-bind:key="group.id"
|
||||
class="task round"
|
||||
>
|
||||
<a :name="'group-'+group.id"></a>
|
||||
<h3>
|
||||
Tasks for <span class="highlight">{{ group.name }} <small>(#{{ group.id }})</small></span>
|
||||
<!-- <p class="context-menu"><img src="/img/menu.svg" width="40" /></p> -->
|
||||
</h3>
|
||||
|
||||
<div class="block-content">
|
||||
<table id="tasks_tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">Up?</th>
|
||||
<th width="*">Host</th>
|
||||
<th width="10%">Type</th>
|
||||
<th width="20%">Last checked</th>
|
||||
<th width="13%">Frequency (min)</th>
|
||||
<th width="5%">Active</th>
|
||||
<th width="5%">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="task in group.tasks"
|
||||
v-bind:key="task.id"
|
||||
:class="task.active == 0 ? 'inactive' : ''"
|
||||
>
|
||||
<td :class="statusText(task.status)">
|
||||
<img :src="'/img/'+statusText(task.status)+'.svg'" width="16" alt="Status" />
|
||||
</td>
|
||||
<td>
|
||||
<img src="/img/external.svg" alt="View host" width="16">
|
||||
<a :href="task.host" target="_blank">{{ task.host }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<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() }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
>
|
||||
Never
|
||||
</span>
|
||||
<td>{{ task.frequency / 60 }}</td>
|
||||
<td :class="task.active == 0 ? 'inactive' : ''">
|
||||
<a
|
||||
v-on:click.prevent="disableTask(task.id, task.active)"
|
||||
href="#"
|
||||
:title="task.active == 1 ? 'Disable task' : 'Enable task'"
|
||||
>
|
||||
<img :src="task.active == 1 ? '/img/on.svg' : '/img/off.svg'" alt="Disable" width="24" />
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<router-link :to="{ name: 'taskdetails', params: { id: task.id }}">
|
||||
<img src="/img/see.svg" alt="Details" width="20" />
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
components: {
|
||||
},
|
||||
computed: {
|
||||
tasks: function() {
|
||||
return this.$store.state.tasks
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
statusText: function (status) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return 'up';
|
||||
break;
|
||||
case 0:
|
||||
return 'down';
|
||||
break;
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
},
|
||||
disableTask: function(task_id, current_status) {
|
||||
this.loading = this.$loading.show()
|
||||
this.$http.patch('/api/toggleTaskStatus/'+task_id, {
|
||||
active: + !current_status
|
||||
})
|
||||
.then(response => {
|
||||
this.$store.commit('updateTask', response.data)
|
||||
})
|
||||
.then(() => {
|
||||
this.loading.hide()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
24
resources/views/emails/notification.blade.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
@component('mail::message')
|
||||
# Monitolite Notification Report
|
||||
|
||||
Hello {{ $report['contact']['firstname'] }},
|
||||
|
||||
You will find below the full report digest of the Monitolite monitoring application.
|
||||
|
||||
@component('mail::table')
|
||||
| Host | Status | Datetime |
|
||||
|:------------:|:--------:|:--------:|
|
||||
@foreach ($report['tasks'] as $t)
|
||||
@foreach ($t['history'] as $h)
|
||||
| [{{ $h['task']['host'] }}]({{ $h['task']['host'] }}) | {{ $h['status'] == 1 ? '**UP**': '**DOWN**' }} | {{ date('Y-m-d H:i:s', strtotime($h['created_at'])) }} |
|
||||
@endforeach
|
||||
@endforeach
|
||||
@endcomponent
|
||||
|
||||
@component('mail::button', ['url' => $url])
|
||||
View the dashboard
|
||||
@endcomponent
|
||||
|
||||
Thanks,<br>
|
||||
{{ config('app.name') }}
|
||||
@endcomponent
|
363
resources/views/taskdetails.vue
Normal file
|
@ -0,0 +1,363 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="container"
|
||||
v-if="task.id != null"
|
||||
>
|
||||
<h1>
|
||||
<span class="highlight">{{ task.type }}</span> for host <span class="highlight">{{ task.host }}</span>
|
||||
<!-- <p class="context-menu"><img src="/img/menu.svg" width="40" /></p> -->
|
||||
</h1>
|
||||
|
||||
Show:
|
||||
<select
|
||||
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>
|
||||
</select>
|
||||
|
||||
<!-- Uptime chart block -->
|
||||
<div id="chart" class="round">
|
||||
<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>
|
||||
|
||||
<!-- Response time chart block -->
|
||||
<div id="chart" class="round" v-if="task.type == 'http'">
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
<!-- History backlog -->
|
||||
<div class="round">
|
||||
<h3>Last {{ days }} days history log</h3>
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
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>
|
||||
<span v-if="h.output">
|
||||
{{ h.output }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<i>No output</i>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="h.duration != null">{{ h.duration+'s' }}</span>
|
||||
<span v-else><i>No duration</i></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<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 && Object.keys(notifications).length > 0">
|
||||
<table id="tasks_tbl">
|
||||
<thead>
|
||||
<tr>
|
||||
<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>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="n in notifications"
|
||||
v-bind:key="n.id"
|
||||
>
|
||||
<td>{{ moment(n.created_at).format('YYYY-MM-DD') }}</td>
|
||||
<td>{{ moment(n.created_at).format('HH:mm:ss') }}</td>
|
||||
<td>{{ n.contact.firstname }}</td>
|
||||
<td>{{ n.contact.surname }}</td>
|
||||
<td>{{ n.contact.email }}</td>
|
||||
<td>{{ n.task_history.status == 1 ? 'UP' : 'DOWN' }}</td>
|
||||
<td>{{ n.status.toUpperCase() }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<p class="no-data" v-else>No notification to display here</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default{
|
||||
data: function() {
|
||||
return {
|
||||
task: {
|
||||
id: null
|
||||
},
|
||||
history: null,
|
||||
notifications: null,
|
||||
refresh: null,
|
||||
loader: null,
|
||||
days: 3,
|
||||
first_day: null,
|
||||
|
||||
charts: {
|
||||
uptime: {
|
||||
render: false,
|
||||
},
|
||||
response: {
|
||||
render: false,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
statusText: function (status) {
|
||||
switch (status) {
|
||||
case 1:
|
||||
return 'up';
|
||||
break;
|
||||
case 0:
|
||||
return 'down';
|
||||
break;
|
||||
default:
|
||||
return 'unknown';
|
||||
}
|
||||
},
|
||||
refreshTask: function(callback) {
|
||||
this.$http.post('/api/getTask/'+this.task.id, {
|
||||
days: this.days
|
||||
})
|
||||
.then(response => {
|
||||
this.task = response.data.task
|
||||
this.history = response.data.history
|
||||
this.first_day = new Date(response.data.first_day).getTime();
|
||||
this.notifications = response.data.notifications
|
||||
this.refreshUptimeGraph(response.data.stats.uptime)
|
||||
|
||||
if (this.task.type == 'http') {
|
||||
this.refreshResponseTimeGraph(response.data.stats.times)
|
||||
}
|
||||
|
||||
this.loader.hide()
|
||||
})
|
||||
.then(() => {
|
||||
if (this.refresh == null) {
|
||||
this.refresh = window.setInterval(() => {
|
||||
this.refreshTask()
|
||||
}, 10000)
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
//TODO: do something
|
||||
})
|
||||
.then(() => {
|
||||
this.loader.hide()
|
||||
})
|
||||
},
|
||||
refreshResponseTimeGraph: function(stats) {
|
||||
let data = [];
|
||||
let xaxis = [];
|
||||
|
||||
for (let date in stats) {
|
||||
xaxis.push(new Date(date).getTime())
|
||||
|
||||
if (stats[date]['count'] > 0) {
|
||||
data.push(Math.round( (stats[date]['duration'] / stats[date]['count']) * 100) / 100)
|
||||
}
|
||||
else {
|
||||
data.push(0)
|
||||
}
|
||||
}
|
||||
|
||||
this.charts.response.options = {
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
//min: this.first_day,
|
||||
categories: xaxis,
|
||||
labels: {
|
||||
show: true,
|
||||
rotate: -45,
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function (value) {
|
||||
return (Math.round(value * 100) / 100) + "s";
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
x: {
|
||||
format: "dd MMM yyyy"
|
||||
}
|
||||
},
|
||||
chart: {
|
||||
type: 'line',
|
||||
height: 350,
|
||||
stacked: false
|
||||
},
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetX: 0,
|
||||
offsetY: 50
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: true,
|
||||
},
|
||||
colors: ['#00955c'],
|
||||
stroke: {
|
||||
curve: 'smooth',
|
||||
},
|
||||
fill: {
|
||||
type: 'gradient',
|
||||
gradient: {
|
||||
//shade: 'dark',
|
||||
shadeIntensity: 1,
|
||||
type: 'vertical',
|
||||
opacityFrom: 1,
|
||||
opacityTo: 1,
|
||||
colorStops: [
|
||||
{
|
||||
offset: 20,
|
||||
color: "#FAD375",
|
||||
opacity: 1
|
||||
},
|
||||
{
|
||||
offset: 40,
|
||||
color: "#61DBC3",
|
||||
opacity: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
this.charts.response.series = [{
|
||||
name: 'Response time',
|
||||
data: data
|
||||
}]
|
||||
|
||||
this.charts.response.render = true
|
||||
},
|
||||
refreshUptimeGraph: function(stats) {
|
||||
let xaxis = [];
|
||||
let new_data_a = [];
|
||||
let new_data_b = [];
|
||||
|
||||
for (let date in stats) {
|
||||
let total = stats[date]['up'] + stats[date]['down']
|
||||
|
||||
xaxis.push(new Date(date).getTime())
|
||||
if (total > 0) {
|
||||
new_data_a.push( Math.round(stats[date]['up'] / total * 100) )
|
||||
new_data_b.push( Math.round(stats[date]['down'] / total * 100) )
|
||||
}
|
||||
else {
|
||||
new_data_a.push( 0 )
|
||||
new_data_b.push( 0 )
|
||||
}
|
||||
}
|
||||
|
||||
this.charts.uptime.options = {
|
||||
xaxis: {
|
||||
type: 'datetime',
|
||||
min: this.first_day,
|
||||
categories: xaxis,
|
||||
tickAmount: 6,
|
||||
labels: {
|
||||
show: true,
|
||||
rotate: -45,
|
||||
}
|
||||
},
|
||||
yaxis: {
|
||||
labels: {
|
||||
formatter: function (value) {
|
||||
return value + "%";
|
||||
}
|
||||
}
|
||||
},
|
||||
tooltip: {
|
||||
x: {
|
||||
format: "yyyy MMM dd"
|
||||
}
|
||||
},
|
||||
chart: {
|
||||
type: 'bar',
|
||||
height: 350,
|
||||
stacked: true,
|
||||
stackType: '100%'
|
||||
},
|
||||
legend: {
|
||||
position: 'right',
|
||||
offsetX: 0,
|
||||
offsetY: 50
|
||||
},
|
||||
}
|
||||
this.charts.uptime.series = [{
|
||||
name: 'UP',
|
||||
data: new_data_a,
|
||||
color: '#00955c'
|
||||
},
|
||||
{
|
||||
name: 'DOWN',
|
||||
data: new_data_b,
|
||||
color: '#ef3232'
|
||||
}]
|
||||
|
||||
this.charts.uptime.render = true
|
||||
},
|
||||
},
|
||||
mounted: function() {
|
||||
this.loader = this.$loading.show()
|
||||
this.task.id = this.$route.params.id ?? null
|
||||
|
||||
if (this.task.id != null) {
|
||||
this.refreshTask()
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
clearTimeout(this.refresh);
|
||||
next();
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
24
routes/web.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
/** @var \Laravel\Lumen\Routing\Router $router */
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Application Routes
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Here is where you can register all of the routes for an application.
|
||||
| It is a breeze. Simply tell Lumen the URIs it should respond to
|
||||
| and give it the Closure to call when that URI is requested.
|
||||
|
|
||||
*/
|
||||
|
||||
$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 () {
|
||||
return View('app');
|
||||
});
|
BIN
screenshot.png
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 300 KiB |
BIN
screenshot2.png
Normal file
After Width: | Height: | Size: 169 KiB |
114
sql/create.sql
|
@ -1,114 +0,0 @@
|
|||
-- MySQL dump 10.13 Distrib 5.1.37, for debian-linux-gnu (x86_64)
|
||||
--
|
||||
-- Host: localhost Database: monitoring
|
||||
-- ------------------------------------------------------
|
||||
-- Server version 5.1.37-1ubuntu5
|
||||
|
||||
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
|
||||
/*!40101 SET NAMES utf8 */;
|
||||
/*!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 */;
|
||||
|
||||
--
|
||||
-- Current Database: `monitoring`
|
||||
--
|
||||
|
||||
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `monitoring` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci */;
|
||||
|
||||
USE `monitoring`;
|
||||
|
||||
--
|
||||
-- Table structure for table `contacts`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `contacts`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `contacts` (
|
||||
`id` int(11) 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(1) NOT NULL DEFAULT '1',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
|
||||
--
|
||||
-- Table structure for table `notifications`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `notifications`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `notifications` (
|
||||
`task_id` int(11) unsigned NOT NULL,
|
||||
`contact_id` int(11) unsigned NOT NULL,
|
||||
PRIMARY KEY (`task_id`,`contact_id`),
|
||||
KEY `contact_id` (`contact_id`),
|
||||
CONSTRAINT `notifications_ibfk_2` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `notifications_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
|
||||
--
|
||||
-- Table structure for table `tasks`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `tasks`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tasks` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`host` varchar(255) NOT NULL,
|
||||
`type` enum('ping','http') NOT NULL,
|
||||
`params` varchar(255) NOT NULL,
|
||||
`creation_date` datetime NOT NULL,
|
||||
`frequency` int(10) unsigned NOT NULL,
|
||||
`last_execution` datetime NULL,
|
||||
`active` int(1) NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `host` (`host`,`type`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||
|
||||
|
||||
--
|
||||
-- Table structure for table `tasks_history`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `tasks_history`;
|
||||
/*!40101 SET @saved_cs_client = @@character_set_client */;
|
||||
/*!40101 SET character_set_client = utf8 */;
|
||||
CREATE TABLE `tasks_history` (
|
||||
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`status` int(1) unsigned NOT NULL,
|
||||
`datetime` datetime NOT NULL,
|
||||
`task_id` int(11) unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `task_id` (`task_id`),
|
||||
CONSTRAINT `tasks_history_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE NO ACTION
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
/*!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 */;
|
||||
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
|
||||
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
|
||||
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
|
||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||
|
||||
-- Dump completed on 2010-03-04 16:41:51
|
2
storage/app/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|