Compare commits
83 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 |
53 changed files with 2872 additions and 3435 deletions
20
.env.example
20
.env.example
|
@ -1,9 +1,10 @@
|
|||
APP_NAME=Monitolite
|
||||
APP_ENV=local
|
||||
APP_ENV=production
|
||||
APP_KEY=
|
||||
APP_DEBUG=true
|
||||
APP_DEBUG=false
|
||||
APP_URL=http://localhost
|
||||
APP_TIMEZONE=UTC
|
||||
DB_TIMEZONE="+1:00"
|
||||
|
||||
LOG_CHANNEL=stack
|
||||
LOG_SLACK_WEBHOOK_URL=
|
||||
|
@ -18,11 +19,14 @@ DB_PASSWORD=secret
|
|||
CACHE_DRIVER=file
|
||||
QUEUE_CONNECTION=sync
|
||||
|
||||
SMTP_HOST=localhost
|
||||
SMTP_USER=
|
||||
SMTP_PASSWORD=
|
||||
SMTP_PORT=25
|
||||
SMTP_SSL=1
|
||||
MAIL_FROM=axel@monitolite.fr
|
||||
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"
|
76
README.md
76
README.md
|
@ -7,8 +7,10 @@ I figured it could be useful for others so I **rewrote** and **updated** it from
|
|||
## What it does
|
||||
|
||||
**MonitoLite** is a very simple monitoring tool developed in PHP powered by Lumen (by Laravel). It supports :
|
||||
* **ping monitoring**: sends a `ping` command to the specified host. Raises an alert if the host is down
|
||||
* **http monitoring**: requests the provided URL and raises an alert if the URL returns an error. Optionally you may specify a string to search on the page using the `param` database field. It raises an alert if the specified text could not be found on the page.
|
||||
* **PING monitoring**: sends a `ping` command to the specified host. Raises an alert if the host is down
|
||||
* **HTTP monitoring**: requests the provided URL and raises an alert if the URL returns an error. Optionally you may specify a string to search on the page using the `param` database field. It raises an alert if the specified text could not be found on the page.
|
||||
* **FTP monitoring**: connects to the provided FTP server as anonymous (authentication not supported yet).
|
||||
* **DNS monitoring**: runs a DNS lookup on a given DNS server for the hostname specified in the params
|
||||
|
||||
In case of an alert, the script sends an email notifications to the specified contacts (one or many).
|
||||
The script also sends a recovery email notification when the alert is over.
|
||||
|
@ -19,10 +21,20 @@ 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
|
||||
|
||||
|
@ -36,43 +48,51 @@ It comes with a very straightforward dashboard written in PHP. This is **optiona
|
|||
|
||||
* clone this repo
|
||||
* 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 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 backend yet)
|
||||
* run the script: `cd /var/www/<your-path> && php artisan monitolite:monitoring:run`
|
||||
* 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:monitoring:run > /dev/null`
|
||||
* if everything works, you may create a CRON `* * * * * cd /var/www/<your-path> && php artisan monitolite:run > /dev/null`
|
||||
|
||||
|
||||
## Settings
|
||||
|
||||
* DB_TYPE=mysql
|
||||
* 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_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
|
||||
* 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
|
||||
|
||||
## MORE INFORMATION COMING SOON.
|
||||
|
||||
## 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)
|
||||
* 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)
|
||||
[ ] 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)
|
||||
|
|
51
app/Console/Commands/CleanHistory.php
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
|
||||
]);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -2,21 +2,27 @@
|
|||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use \Exception;
|
||||
use Illuminate\Queue\Console\MonitorCommand;
|
||||
use App\Models\Task;
|
||||
use App\Models\TaskHistory;
|
||||
use App\Models\Notification;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class RunMonitoring extends Command
|
||||
{
|
||||
private $rounds = 50;
|
||||
private $limit = 50;
|
||||
private $max_tries = 3;
|
||||
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'monitolite:monitoring:run {rounds?}';
|
||||
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.
|
||||
|
@ -48,68 +54,127 @@ class RunMonitoring extends Command
|
|||
public function handle()
|
||||
{
|
||||
$count = 0;
|
||||
$rounds = $this->argument('rounds') ?? $this->rounds;
|
||||
$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 = app('db')->select('
|
||||
SELECT id, host, type, params
|
||||
FROM tasks
|
||||
WHERE ( DATE_SUB(now(), INTERVAL frequency SECOND) > last_execution OR last_execution IS NULL )
|
||||
AND active = 1
|
||||
ORDER BY last_execution ASC
|
||||
LIMIT :limit
|
||||
', [
|
||||
'limit' => $rounds
|
||||
]);
|
||||
$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).' to process. Better get started ...');
|
||||
$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) {
|
||||
$last_status = $new_status = $output = null;
|
||||
$bar->advance();
|
||||
|
||||
// Getting current task last status
|
||||
$query = DB::table('tasks_history')
|
||||
->select('status')
|
||||
->where('task_id', $task->id)
|
||||
->orderBy('datetime', 'DESC')
|
||||
->first()
|
||||
;
|
||||
if ($query !== false && ! is_null($query)) {
|
||||
$last_status = $query->status;
|
||||
}
|
||||
$previous_status = $task->status;
|
||||
|
||||
try {
|
||||
switch ($task->type) {
|
||||
case 'ping':
|
||||
$new_status = $this->checkPing($task);
|
||||
$result = $this->checkPing($task);
|
||||
break;
|
||||
|
||||
case 'http':
|
||||
$new_status = $this->checkHttp($task);
|
||||
$result = $this->checkRequest($task, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||
break;
|
||||
|
||||
case 'ftp':
|
||||
$result = $this->checkRequest($task, CURLPROTO_FTP | CURLPROTO_FTPS);
|
||||
break;
|
||||
|
||||
case 'dns':
|
||||
$result = $this->checkDns($task);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Nothing to do here
|
||||
continue 2;
|
||||
throw new Exception('Unknown type "'.$task->type.'"');
|
||||
}
|
||||
|
||||
$this->saveHistory($task, true);
|
||||
$new_status = 1;
|
||||
$history = $this->saveHistory($task, true, 'success', $result['duration'] ?? null);
|
||||
}
|
||||
catch(MonitoringException $e) {
|
||||
$this->saveHistory($task, false, $e->getMessage());
|
||||
$history = $this->saveHistory($task, false, $e->getMessage());
|
||||
}
|
||||
catch(Exception $e) {
|
||||
$this->saveHistory($task, false, $e->getMessage());
|
||||
//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();
|
||||
|
@ -117,42 +182,40 @@ class RunMonitoring extends Command
|
|||
|
||||
if (!empty($this->results)) {
|
||||
$this->table(
|
||||
['Host', 'Result', 'Message'],
|
||||
['ID', 'Host', 'Type', 'Result', 'Attempts', 'Message'],
|
||||
$this->results
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final private function saveHistory($task, $status, $output = null) {
|
||||
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
|
||||
];
|
||||
|
||||
$insert = DB::table('tasks_history')
|
||||
->insert([
|
||||
'status' => $status === true ? 1 : 0,
|
||||
'datetime' => $date,
|
||||
'output' => $output ?? '',
|
||||
'task_id' => $task->id
|
||||
]
|
||||
);
|
||||
|
||||
if (false !== $insert) {
|
||||
DB::table('tasks')
|
||||
->where('id', $task->id)
|
||||
->update([
|
||||
'last_execution' => $date
|
||||
])
|
||||
;
|
||||
return true;
|
||||
}
|
||||
return $insert;
|
||||
}
|
||||
|
||||
final private function checkPing($task) {
|
||||
final private function checkPing(Task $task) {
|
||||
if (! function_exists('exec') || ! is_callable('exec')) {
|
||||
throw new MonitoringException('The "exec" command is required');
|
||||
}
|
||||
|
@ -196,17 +259,49 @@ class RunMonitoring extends Command
|
|||
return true;
|
||||
}
|
||||
|
||||
final private function checkHttp($task) {
|
||||
final private function checkDns(Task $task) {
|
||||
if (! function_exists('exec') || ! is_callable('exec')) {
|
||||
throw new MonitoringException('The "exec" command is required');
|
||||
}
|
||||
|
||||
if (is_null($task->params) || empty($task->params)) {
|
||||
throw new Exception('Params are required');
|
||||
}
|
||||
|
||||
$cmd = 'nslookup '.trim($task->params).' '.$task->host;
|
||||
|
||||
// If command failed
|
||||
if (false === $exec = exec($cmd.' '.$task->host, $output, $code)) {
|
||||
throw new MonitoringException('Unable to execute DNS lookup');
|
||||
}
|
||||
|
||||
// If command returned a non-zero code
|
||||
if ($code > 0) {
|
||||
throw new MonitoringException('DNS lookup task failed ('.$exec.')');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
final private function checkRequest(Task $task, $protocol = CURLPROTO_HTTP | CURLPROTO_HTTPS) {
|
||||
if (app()->environment() == 'local') {
|
||||
//throw new MonitoringException('Forcing error for testing');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Preparing cURL
|
||||
$opts = [
|
||||
CURLOPT_HEADER => true,
|
||||
CURLOPT_HTTPGET => true,
|
||||
CURLOPT_FRESH_CONNECT => true,
|
||||
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
|
||||
CURLOPT_PROTOCOLS => $protocol,
|
||||
CURLOPT_SSL_VERIFYHOST => 2,
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
CURLOPT_MAXREDIRS => 3,
|
||||
CURLOPT_FAILONERROR => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 3,
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
CURLOPT_URL => trim($task->host)
|
||||
];
|
||||
|
@ -214,16 +309,24 @@ class RunMonitoring extends Command
|
|||
$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 true;
|
||||
return [
|
||||
'result' => true,
|
||||
'duration' => $duration
|
||||
];
|
||||
}
|
||||
// We are looking for a string in the page
|
||||
else {
|
||||
if (strpos($result, $task->params) !== false) {
|
||||
return true;
|
||||
return [
|
||||
'result' => true,
|
||||
'output' => 'String was found in the page',
|
||||
'duration' => $duration
|
||||
];
|
||||
}
|
||||
else {
|
||||
throw new MonitoringException('Cannot find the required string into the page');
|
||||
|
|
94
app/Console/Commands/SendNotifications.php
Normal file
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']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,14 @@
|
|||
<?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
|
||||
*/
|
||||
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SyncCustomers extends Command
|
||||
|
@ -19,14 +18,14 @@ class SyncCustomers extends Command
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'customers:sync';
|
||||
protected $signature = 'monitolite:sync';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Synchronize all customers';
|
||||
protected $description = 'Synchronizes all customers\' websites with Monitolite';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
|
@ -108,14 +107,14 @@ class SyncCustomers extends Command
|
|||
|
||||
if (false === array_search(trim($c->domain), $tasks_flat)) {
|
||||
$ret = app('db')->insert('
|
||||
INSERT INTO tasks (`host`, `type`, `params`, `creation_date`, `frequency`, `active`, `group_id`)
|
||||
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' => 'propulsé par',
|
||||
'params' => 'restovisio.com',
|
||||
'creation_date' => date('Y-m-d H:i:s'),
|
||||
'frequency' => 600,
|
||||
'frequency' => 3600,
|
||||
'active' => 1,
|
||||
'group_id' => $c->id
|
||||
]);
|
||||
|
@ -125,7 +124,7 @@ class SyncCustomers extends Command
|
|||
|
||||
// Inserting contacts
|
||||
foreach ($contacts as $c) {
|
||||
app('db')->insert('INSERT INTO notifications (`task_id`, `contact_id`) VALUES (:task_id, :contact_id)', [
|
||||
app('db')->insert('INSERT INTO contact_task (`task_id`, `contact_id`) VALUES (:task_id, :contact_id)', [
|
||||
'task_id' => $task_id,
|
||||
'contact_id' => $c->id
|
||||
]);
|
||||
|
@ -147,7 +146,7 @@ class SyncCustomers extends Command
|
|||
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]);
|
||||
//app('db')->delete('DELETE FROM `tasks` WHERE host = ?', [$t->host]);
|
||||
}
|
||||
}
|
||||
$bar->finish();
|
||||
|
|
|
@ -5,8 +5,8 @@ namespace App\Console;
|
|||
use Illuminate\Console\Scheduling\Schedule;
|
||||
use Laravel\Lumen\Console\Kernel as ConsoleKernel;
|
||||
use App\Console\Commands\SyncCustomers;
|
||||
use App\Console\Commands\CleanHistory;
|
||||
use App\Console\Commands\RunMonitoring;
|
||||
use App\Console\Commands\SendNotifications;
|
||||
|
||||
class Kernel extends ConsoleKernel
|
||||
{
|
||||
|
@ -17,8 +17,8 @@ class Kernel extends ConsoleKernel
|
|||
*/
|
||||
protected $commands = [
|
||||
SyncCustomers::class,
|
||||
CleanHistory::class,
|
||||
RunMonitoring::class
|
||||
RunMonitoring::class,
|
||||
SendNotifications::class
|
||||
];
|
||||
|
||||
/**
|
||||
|
@ -34,12 +34,17 @@ class Kernel extends ConsoleKernel
|
|||
* You may safely remove this scheduled task
|
||||
*/
|
||||
if (env('CMS_ENABLE_SYNC') == true) {
|
||||
$schedule->command('monitolite:customers:sync')->hourly();
|
||||
$schedule->command('monitolite:sync')->hourly();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the main monitoring task
|
||||
*/
|
||||
$schedule->command('monitolite:monitoring:run')->everyMinute();
|
||||
$schedule->command('monitolite:run')->everyMinute();
|
||||
|
||||
/**
|
||||
* Send all the notifications
|
||||
*/
|
||||
$schedule->command('monitolite:notify')->everyMinute();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
namespace App\Http\Controllers;
|
||||
|
||||
use Exception;
|
||||
use \Carbon\Carbon;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
|
@ -22,14 +24,15 @@ class ApiController extends Controller
|
|||
public function getTasks() {
|
||||
$tasks = [];
|
||||
|
||||
$query = DB::select('
|
||||
SELECT DISTINCT t.id, t.host, t.type, t.params, t.frequency, t.creation_date, t.last_execution, t.active, t.group_id, h.status, h.output, g.name as group_name
|
||||
FROM `tasks` as t
|
||||
LEFT JOIN `tasks_history` as h ON (h.task_id = t.id)
|
||||
LEFT JOIN `groups` as g ON (g.id = t.group_id)
|
||||
WHERE (t.last_execution IS NULL OR h.datetime = t.last_execution)
|
||||
ORDER BY group_name ASC
|
||||
');
|
||||
$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)) {
|
||||
|
@ -54,44 +57,111 @@ class ApiController extends Controller
|
|||
return response()->json($tasks);
|
||||
}
|
||||
|
||||
public function getTaskDetails(Request $request, $id) {
|
||||
$days = ($request->input('days', 15) - 1);
|
||||
|
||||
public function getTaskDetails($id) {
|
||||
$query = DB::select('
|
||||
SELECT t.id, t.host, t.type, t.params, t.frequency, t.creation_date, t.last_execution, t.active, t.group_id, h.status, h.output, g.name as group_name
|
||||
FROM `tasks` as t
|
||||
LEFT JOIN `tasks_history` as h ON (h.task_id = t.id)
|
||||
LEFT JOIN `groups` as g ON (g.id = t.group_id)
|
||||
WHERE (t.last_execution IS NULL OR h.datetime = t.last_execution) AND t.id = :task_id
|
||||
LIMIT 1
|
||||
', [
|
||||
'task_id' => $id
|
||||
]);
|
||||
$task = Task::with(['group'])
|
||||
->findOrFail($id)
|
||||
;
|
||||
|
||||
if ($query) {
|
||||
foreach ($query as $q) {
|
||||
return response()->json($q);
|
||||
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) {
|
||||
if($active = $request->input('active')) {
|
||||
//throw new ApiException('Invalid parameters');
|
||||
}
|
||||
$active = $request->input('active', null);
|
||||
|
||||
if (is_null($active)) {
|
||||
throw new ApiException('Invalid parameters');
|
||||
}
|
||||
$active = intval($active);
|
||||
|
||||
$query = DB::update('
|
||||
UPDATE tasks
|
||||
SET active = :active
|
||||
WHERE id = :id
|
||||
', [
|
||||
'active' => $active,
|
||||
'id' => $id
|
||||
]);
|
||||
$task = Task::findOrFail($id);
|
||||
$task->active = $active;
|
||||
|
||||
if ($query !== false) {
|
||||
return $this->getTaskDetails($id);
|
||||
if ($task->save()) {
|
||||
return response()->json($task);
|
||||
}
|
||||
else {
|
||||
throw new ApiException('Cannot disable this task');
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
class ExampleJob extends Job
|
||||
{
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
48
app/Mail/TaskNotification.php
Normal file
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
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
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
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
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
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
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');
|
||||
}
|
||||
|
||||
}
|
|
@ -25,7 +25,7 @@ $app = new Laravel\Lumen\Application(
|
|||
|
||||
$app->withFacades();
|
||||
|
||||
// $app->withEloquent();
|
||||
$app->withEloquent();
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -59,7 +59,25 @@ $app->singleton(
|
|||
|
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
@ -94,6 +112,7 @@ $app->configure('app');
|
|||
// $app->register(App\Providers\AppServiceProvider::class);
|
||||
// $app->register(App\Providers\AuthServiceProvider::class);
|
||||
// $app->register(App\Providers\EventServiceProvider::class);
|
||||
$app->register(Illuminate\Mail\MailServiceProvider::class);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"type": "project",
|
||||
"require": {
|
||||
"php": "^7.3|^8.0",
|
||||
"illuminate/mail": "^8.77",
|
||||
"laravel/lumen-framework": "^8.3.1"
|
||||
},
|
||||
"require-dev": {
|
||||
|
|
745
composer.lock
generated
745
composer.lock
generated
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "43a8a6e444590514bb4b9f6a02112d0f",
|
||||
"content-hash": "5c34208f62c29296617bdbe23b54102e",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
|
@ -66,6 +66,81 @@
|
|||
],
|
||||
"time": "2021-08-15T20:50:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
"version": "v3.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dflydev/dflydev-dot-access-data.git",
|
||||
"reference": "0992cc19268b259a39e86f296da5f0677841f42c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/0992cc19268b259a39e86f296da5f0677841f42c",
|
||||
"reference": "0992cc19268b259a39e86f296da5f0677841f42c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.42",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.3",
|
||||
"scrutinizer/ocular": "1.6.0",
|
||||
"squizlabs/php_codesniffer": "^3.5",
|
||||
"vimeo/psalm": "^3.14"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "3.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Dflydev\\DotAccessData\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Dragonfly Development Inc.",
|
||||
"email": "info@dflydev.com",
|
||||
"homepage": "http://dflydev.com"
|
||||
},
|
||||
{
|
||||
"name": "Beau Simensen",
|
||||
"email": "beau@dflydev.com",
|
||||
"homepage": "http://beausimensen.com"
|
||||
},
|
||||
{
|
||||
"name": "Carlos Frutos",
|
||||
"email": "carlos@kiwing.it",
|
||||
"homepage": "https://github.com/cfrutos"
|
||||
},
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com"
|
||||
}
|
||||
],
|
||||
"description": "Given a deep data structure, access data by dot notation.",
|
||||
"homepage": "https://github.com/dflydev/dflydev-dot-access-data",
|
||||
"keywords": [
|
||||
"access",
|
||||
"data",
|
||||
"dot",
|
||||
"notation"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/dflydev/dflydev-dot-access-data/issues",
|
||||
"source": "https://github.com/dflydev/dflydev-dot-access-data/tree/v3.0.1"
|
||||
},
|
||||
"time": "2021-08-13T13:06:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/inflector",
|
||||
"version": "2.0.4",
|
||||
|
@ -1352,6 +1427,67 @@
|
|||
},
|
||||
"time": "2021-11-16T13:57:03+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/mail",
|
||||
"version": "v8.77.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/mail.git",
|
||||
"reference": "f2f7ea1002d6756bf3a1093e2246b83d41307ff3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/mail/zipball/f2f7ea1002d6756bf3a1093e2246b83d41307ff3",
|
||||
"reference": "f2f7ea1002d6756bf3a1093e2246b83d41307ff3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-json": "*",
|
||||
"illuminate/collections": "^8.0",
|
||||
"illuminate/container": "^8.0",
|
||||
"illuminate/contracts": "^8.0",
|
||||
"illuminate/macroable": "^8.0",
|
||||
"illuminate/support": "^8.0",
|
||||
"league/commonmark": "^1.3|^2.0.2",
|
||||
"php": "^7.3|^8.0",
|
||||
"psr/log": "^1.0|^2.0",
|
||||
"swiftmailer/swiftmailer": "^6.3",
|
||||
"tijsverkoyen/css-to-inline-styles": "^2.2.2"
|
||||
},
|
||||
"suggest": {
|
||||
"aws/aws-sdk-php": "Required to use the SES mail driver (^3.198.1).",
|
||||
"guzzlehttp/guzzle": "Required to use the Mailgun mail driver (^6.5.5|^7.0.1).",
|
||||
"wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)."
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "8.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Mail\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Mail package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"support": {
|
||||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2021-12-16T20:33:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/pagination",
|
||||
"version": "v8.76.2",
|
||||
|
@ -2020,6 +2156,191 @@
|
|||
},
|
||||
"time": "2021-11-30T15:53:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/commonmark",
|
||||
"version": "2.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/commonmark.git",
|
||||
"reference": "819276bc54e83c160617d3ac0a436c239e479928"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/819276bc54e83c160617d3ac0a436c239e479928",
|
||||
"reference": "819276bc54e83c160617d3ac0a436c239e479928",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-mbstring": "*",
|
||||
"league/config": "^1.1.1",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/event-dispatcher": "^1.0",
|
||||
"symfony/polyfill-php80": "^1.15"
|
||||
},
|
||||
"require-dev": {
|
||||
"cebe/markdown": "^1.0",
|
||||
"commonmark/cmark": "0.30.0",
|
||||
"commonmark/commonmark.js": "0.30.0",
|
||||
"composer/package-versions-deprecated": "^1.8",
|
||||
"erusev/parsedown": "^1.0",
|
||||
"ext-json": "*",
|
||||
"github/gfm": "0.29.0",
|
||||
"michelf/php-markdown": "^1.4",
|
||||
"phpstan/phpstan": "^0.12.88 || ^1.0.0",
|
||||
"phpunit/phpunit": "^9.5.5",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"symfony/finder": "^5.3",
|
||||
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0",
|
||||
"unleashedtech/php-coding-standard": "^3.1",
|
||||
"vimeo/psalm": "^4.7.3"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "2.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\CommonMark\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and GitHub-Flavored Markdown (GFM)",
|
||||
"homepage": "https://commonmark.thephpleague.com",
|
||||
"keywords": [
|
||||
"commonmark",
|
||||
"flavored",
|
||||
"gfm",
|
||||
"github",
|
||||
"github-flavored",
|
||||
"markdown",
|
||||
"md",
|
||||
"parser"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://commonmark.thephpleague.com/",
|
||||
"forum": "https://github.com/thephpleague/commonmark/discussions",
|
||||
"issues": "https://github.com/thephpleague/commonmark/issues",
|
||||
"rss": "https://github.com/thephpleague/commonmark/releases.atom",
|
||||
"source": "https://github.com/thephpleague/commonmark"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.colinodell.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.paypal.me/colinpodell/10.00",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/colinodell",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/league/commonmark",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-12-05T18:25:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/config",
|
||||
"version": "v1.1.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/thephpleague/config.git",
|
||||
"reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/thephpleague/config/zipball/a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
|
||||
"reference": "a9d39eeeb6cc49d10a6e6c36f22c4c1f4a767f3e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"dflydev/dot-access-data": "^3.0.1",
|
||||
"nette/schema": "^1.2",
|
||||
"php": "^7.4 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpstan/phpstan": "^0.12.90",
|
||||
"phpunit/phpunit": "^9.5.5",
|
||||
"scrutinizer/ocular": "^1.8.1",
|
||||
"unleashedtech/php-coding-standard": "^3.1",
|
||||
"vimeo/psalm": "^4.7.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"League\\Config\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Colin O'Dell",
|
||||
"email": "colinodell@gmail.com",
|
||||
"homepage": "https://www.colinodell.com",
|
||||
"role": "Lead Developer"
|
||||
}
|
||||
],
|
||||
"description": "Define configuration arrays with strict schemas and access values with dot notation",
|
||||
"homepage": "https://config.thephpleague.com",
|
||||
"keywords": [
|
||||
"array",
|
||||
"config",
|
||||
"configuration",
|
||||
"dot",
|
||||
"dot-access",
|
||||
"nested",
|
||||
"schema"
|
||||
],
|
||||
"support": {
|
||||
"docs": "https://config.thephpleague.com/",
|
||||
"issues": "https://github.com/thephpleague/config/issues",
|
||||
"rss": "https://github.com/thephpleague/config/releases.atom",
|
||||
"source": "https://github.com/thephpleague/config"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.colinodell.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.paypal.me/colinpodell/10.00",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/colinodell",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2021-08-14T12:15:32+00:00"
|
||||
},
|
||||
{
|
||||
"name": "monolog/monolog",
|
||||
"version": "2.3.5",
|
||||
|
@ -2215,6 +2536,153 @@
|
|||
],
|
||||
"time": "2021-12-03T14:59:52+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/schema",
|
||||
"version": "v1.2.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/schema.git",
|
||||
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/schema/zipball/9a39cef03a5b34c7de64f551538cbba05c2be5df",
|
||||
"reference": "9a39cef03a5b34c7de64f551538cbba05c2be5df",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"nette/utils": "^2.5.7 || ^3.1.5 || ^4.0",
|
||||
"php": ">=7.1 <8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "^2.3 || ^2.4",
|
||||
"phpstan/phpstan-nette": "^0.12",
|
||||
"tracy/tracy": "^2.7"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause",
|
||||
"GPL-2.0-only",
|
||||
"GPL-3.0-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"description": "📐 Nette Schema: validating data structures against a given Schema.",
|
||||
"homepage": "https://nette.org",
|
||||
"keywords": [
|
||||
"config",
|
||||
"nette"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/schema/issues",
|
||||
"source": "https://github.com/nette/schema/tree/v1.2.2"
|
||||
},
|
||||
"time": "2021-10-15T11:40:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nette/utils",
|
||||
"version": "v3.2.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/nette/utils.git",
|
||||
"reference": "2f261e55bd6a12057442045bf2c249806abc1d02"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/nette/utils/zipball/2f261e55bd6a12057442045bf2c249806abc1d02",
|
||||
"reference": "2f261e55bd6a12057442045bf2c249806abc1d02",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2 <8.2"
|
||||
},
|
||||
"conflict": {
|
||||
"nette/di": "<3.0.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"nette/tester": "~2.0",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"tracy/tracy": "^2.3"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-gd": "to use Image",
|
||||
"ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()",
|
||||
"ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()",
|
||||
"ext-json": "to use Nette\\Utils\\Json",
|
||||
"ext-mbstring": "to use Strings::lower() etc...",
|
||||
"ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()",
|
||||
"ext-xml": "to use Strings::length() etc. when mbstring is not available"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause",
|
||||
"GPL-2.0-only",
|
||||
"GPL-3.0-only"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "David Grudl",
|
||||
"homepage": "https://davidgrudl.com"
|
||||
},
|
||||
{
|
||||
"name": "Nette Community",
|
||||
"homepage": "https://nette.org/contributors"
|
||||
}
|
||||
],
|
||||
"description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.",
|
||||
"homepage": "https://nette.org",
|
||||
"keywords": [
|
||||
"array",
|
||||
"core",
|
||||
"datetime",
|
||||
"images",
|
||||
"json",
|
||||
"nette",
|
||||
"paginator",
|
||||
"password",
|
||||
"slugify",
|
||||
"string",
|
||||
"unicode",
|
||||
"utf-8",
|
||||
"utility",
|
||||
"validation"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/nette/utils/issues",
|
||||
"source": "https://github.com/nette/utils/tree/v3.2.6"
|
||||
},
|
||||
"time": "2021-11-24T15:47:23+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nikic/fast-route",
|
||||
"version": "v1.3.0",
|
||||
|
@ -2777,6 +3245,82 @@
|
|||
],
|
||||
"time": "2021-09-25T23:10:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "swiftmailer/swiftmailer",
|
||||
"version": "v6.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/swiftmailer/swiftmailer.git",
|
||||
"reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8a5d5072dca8f48460fce2f4131fcc495eec654c",
|
||||
"reference": "8a5d5072dca8f48460fce2f4131fcc495eec654c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"egulias/email-validator": "^2.0|^3.1",
|
||||
"php": ">=7.0.0",
|
||||
"symfony/polyfill-iconv": "^1.0",
|
||||
"symfony/polyfill-intl-idn": "^1.10",
|
||||
"symfony/polyfill-mbstring": "^1.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"mockery/mockery": "^1.0",
|
||||
"symfony/phpunit-bridge": "^4.4|^5.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-intl": "Needed to support internationalized email addresses"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "6.2-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/swift_required.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Chris Corbyn"
|
||||
},
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
}
|
||||
],
|
||||
"description": "Swiftmailer, free feature-rich PHP mailer",
|
||||
"homepage": "https://swiftmailer.symfony.com",
|
||||
"keywords": [
|
||||
"email",
|
||||
"mail",
|
||||
"mailer"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/swiftmailer/swiftmailer/issues",
|
||||
"source": "https://github.com/swiftmailer/swiftmailer/tree/v6.3.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/swiftmailer/swiftmailer",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"abandoned": "symfony/mailer",
|
||||
"time": "2021-10-18T15:26:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v5.4.1",
|
||||
|
@ -2876,6 +3420,72 @@
|
|||
],
|
||||
"time": "2021-12-09T11:22:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/css-selector",
|
||||
"version": "v5.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/css-selector.git",
|
||||
"reference": "44b933f98bb4b5220d10bed9ce5662f8c2d13dcc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/css-selector/zipball/44b933f98bb4b5220d10bed9ce5662f8c2d13dcc",
|
||||
"reference": "44b933f98bb4b5220d10bed9ce5662f8c2d13dcc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2.5",
|
||||
"symfony/polyfill-php80": "^1.16"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\CssSelector\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Jean-François Simon",
|
||||
"email": "jeanfrancois.simon@sensiolabs.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Converts CSS selectors to XPath expressions",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/css-selector/tree/v5.4.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-09-09T08:06:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/deprecation-contracts",
|
||||
"version": "v2.5.0",
|
||||
|
@ -3588,6 +4198,86 @@
|
|||
],
|
||||
"time": "2021-02-19T12:13:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-iconv",
|
||||
"version": "v1.23.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-iconv.git",
|
||||
"reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/63b5bb7db83e5673936d6e3b8b3e022ff6474933",
|
||||
"reference": "63b5bb7db83e5673936d6e3b8b3e022ff6474933",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-iconv": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "1.23-dev"
|
||||
},
|
||||
"thanks": {
|
||||
"name": "symfony/polyfill",
|
||||
"url": "https://github.com/symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Iconv\\": ""
|
||||
},
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for the Iconv extension",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"iconv",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-iconv/tree/v1.23.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2021-05-27T09:27:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-intl-grapheme",
|
||||
"version": "v1.23.1",
|
||||
|
@ -4732,6 +5422,59 @@
|
|||
],
|
||||
"time": "2021-12-01T15:04:08+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tijsverkoyen/css-to-inline-styles",
|
||||
"version": "2.2.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tijsverkoyen/CssToInlineStyles.git",
|
||||
"reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/da444caae6aca7a19c0c140f68c6182e337d5b1c",
|
||||
"reference": "da444caae6aca7a19c0c140f68c6182e337d5b1c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-dom": "*",
|
||||
"ext-libxml": "*",
|
||||
"php": "^5.5 || ^7.0 || ^8.0",
|
||||
"symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0 || ^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5 || ^8.5.21 || ^9.5.10"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "2.2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"TijsVerkoyen\\CssToInlineStyles\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"BSD-3-Clause"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Tijs Verkoyen",
|
||||
"email": "css_to_inline_styles@verkoyen.eu",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "CssToInlineStyles is a class that enables you to convert HTML-pages/files into HTML-pages/files with inline styles. This is very useful when you're sending emails.",
|
||||
"homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
|
||||
"support": {
|
||||
"issues": "https://github.com/tijsverkoyen/CssToInlineStyles/issues",
|
||||
"source": "https://github.com/tijsverkoyen/CssToInlineStyles/tree/2.2.4"
|
||||
},
|
||||
"time": "2021-12-08T09:12:39+00:00"
|
||||
},
|
||||
{
|
||||
"name": "vlucas/phpdotenv",
|
||||
"version": "v5.4.1",
|
||||
|
|
137
config/database.php
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
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'),
|
||||
],
|
||||
],
|
||||
|
||||
];
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateContactTaskTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('contact_task', function (Blueprint $table) {
|
||||
$table->bigInteger('task_id')->unsigned();
|
||||
$table->bigInteger('contact_id')->unsigned();
|
||||
$table->primary(['task_id', 'contact_id']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('contact_task');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateContactsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('contacts', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('surname', 200);
|
||||
$table->string('firstname', 200);
|
||||
$table->string('email', 250);
|
||||
$table->string('phone', 20);
|
||||
$table->timestamps();
|
||||
$table->tinyInteger('active')->default(1);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('contacts');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateGroupsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('groups', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('name', 128)->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('groups');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateNotificationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('notifications', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedBigInteger('contact_id');
|
||||
$table->unsignedBigInteger('task_history_id');
|
||||
$table->enum('status', ['pending', 'sent', 'error'])->default('pending');
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('notifications');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateTaskHistoryTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('task_history', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->unsignedTinyInteger('status');
|
||||
$table->text('output')->nullable();
|
||||
$table->float('duration', 5, 3)->unsigned()->nullable();
|
||||
$table->unsignedBigInteger('task_id');
|
||||
$table->timestamps();
|
||||
$table->index(['status', 'created_at']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('task_history');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateTasksArchivesTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tasks_archives', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->date('day');
|
||||
$table->unsignedInteger('uptime')->default('0');
|
||||
$table->unsignedBigInteger('task_id')->default('0');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tasks_archives');
|
||||
}
|
||||
}
|
42
database/migrations/2021_12_28_131705_create_tasks_table.php
Normal file
42
database/migrations/2021_12_28_131705_create_tasks_table.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class CreateTasksTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::create('tasks', function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('host');
|
||||
$table->enum('type', ['ping', 'http', 'dns', 'ftp']);
|
||||
$table->string('params')->nullable();
|
||||
$table->unsignedInteger('frequency');
|
||||
$table->unsignedTinyInteger('attempts')->default('0');
|
||||
$table->unsignedTinyInteger('active')->default(1);
|
||||
$table->unsignedTinyInteger('status')->nullable();
|
||||
$table->unsignedBigInteger('group_id')->nullable();
|
||||
$table->timestamps();
|
||||
$table->timestamp('executed_at')->nullable();
|
||||
|
||||
$table->unique(['host', 'type']);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('tasks');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddForeignKeysToContactTaskTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('contact_task', function (Blueprint $table) {
|
||||
$table->foreign(['task_id'], 'contact_task_ibfk_1')->references(['id'])->on('tasks')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
$table->foreign(['contact_id'], 'contact_task_ibfk_2')->references(['id'])->on('contacts')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('contact_task', function (Blueprint $table) {
|
||||
$table->dropForeign('contact_task_ibfk_1');
|
||||
$table->dropForeign('contact_task_ibfk_2');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddForeignKeysToNotificationsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('notifications', function (Blueprint $table) {
|
||||
$table->foreign(['contact_id'], 'contact_id_frgn')->references(['id'])->on('contacts')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
$table->foreign(['task_history_id'], 'task_history_id_frgn')->references(['id'])->on('task_history')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('notifications', function (Blueprint $table) {
|
||||
$table->dropForeign('contact_id_frgn');
|
||||
$table->dropForeign('task_history_id_frgn');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddForeignKeysToTaskHistoryTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('task_history', function (Blueprint $table) {
|
||||
$table->foreign(['task_id'], 'task_history_ibfk_1')->references(['id'])->on('tasks')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('task_history', function (Blueprint $table) {
|
||||
$table->dropForeign('task_history_ibfk_1');
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddForeignKeysToTasksTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('tasks', function (Blueprint $table) {
|
||||
$table->foreign(['group_id'], 'group_id_frgn')->references(['id'])->on('groups')->onUpdate('NO ACTION')->onDelete('CASCADE');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('tasks', function (Blueprint $table) {
|
||||
$table->dropForeign('group_id_frgn');
|
||||
});
|
||||
}
|
||||
}
|
350
monitolite.pl
350
monitolite.pl
|
@ -1,350 +0,0 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
################################
|
||||
# #
|
||||
# M O N I T O L I T E #
|
||||
# #
|
||||
# Lightweight Monitoring Tool #
|
||||
# #
|
||||
# @author: Axel de Vignon #
|
||||
# @copyright: www.vidax.net #
|
||||
# @license: Mozilla Public 1.1 #
|
||||
# #
|
||||
################################
|
||||
|
||||
use warnings;
|
||||
use strict;
|
||||
use DBI;
|
||||
use Dotenv;
|
||||
use Net::Ping;
|
||||
|
||||
use Email::MIME;
|
||||
use Email::Sender::Simple qw(sendmail);
|
||||
use Email::Sender::Transport::SMTP qw();
|
||||
|
||||
use LWP::Simple;
|
||||
use LWP::UserAgent;
|
||||
use LWP::Protocol::https;
|
||||
|
||||
my $query;
|
||||
my $result;
|
||||
my $tasks;
|
||||
my $update_query;
|
||||
my $emails;
|
||||
my $email;
|
||||
my $message;
|
||||
my $response;
|
||||
my $html;
|
||||
my $numtasks;
|
||||
my $previous_status;
|
||||
my $subject;
|
||||
my $datas;
|
||||
|
||||
|
||||
############################
|
||||
# #
|
||||
# S E T T I N G S #
|
||||
# #
|
||||
############################
|
||||
|
||||
Dotenv->load;
|
||||
|
||||
my $dbtype = $ENV{'DB_CONNECTION'};
|
||||
my $hostname = $ENV{'DB_HOST'};
|
||||
my $database = $ENV{'DB_DATABASE'};
|
||||
my $login = $ENV{'DB_USERNAME'};
|
||||
my $port = $ENV{'DB_PORT'};
|
||||
my $password = $ENV{'DB_PASSWORD'};
|
||||
my $email_from = $ENV{'MAIL_FROM'};
|
||||
my $number_tries = $ENV{'NB_TRIES'};
|
||||
my $days_history_archive = $ENV{'ARCHIVE_DAYS'};
|
||||
my $smtp_host = $ENV{'SMTP_HOST'};
|
||||
my $smtp_user = $ENV{'SMTP_USER'};
|
||||
my $smtp_password = $ENV{'SMTP_PASSWORD'};
|
||||
my $smtp_port = $ENV{'SMTP_PORT'};
|
||||
my $smtp_ssl = $ENV{'SMTP_SSL'};
|
||||
|
||||
|
||||
|
||||
############################
|
||||
|
||||
######
|
||||
# Testing database connection
|
||||
######
|
||||
my $dsn = "DBI:$dbtype:database=$database;host=$hostname;port=$port";
|
||||
my $dbh = DBI->connect($dsn, $login, $password) or output('cannot connect to database', 'ERROR', 1);
|
||||
|
||||
|
||||
######
|
||||
# Getting tasks
|
||||
######
|
||||
my $execution_time = server_time();
|
||||
my $query1 = $dbh->prepare('SELECT id, host, type, params FROM tasks WHERE ( DATE_SUB(now(), INTERVAL frequency SECOND) > last_execution OR last_execution IS NULL ) AND active = 1');
|
||||
$query1->execute() or output('Cannot execute query fetching all pending tasks', 'ERROR', 1);
|
||||
$numtasks = $query1->rows;
|
||||
|
||||
#####
|
||||
# Processing all tasks
|
||||
#####
|
||||
if ($numtasks > 0) {
|
||||
while ($tasks = $query1->fetchrow_hashref()) {
|
||||
print "\n";
|
||||
my $status = -1;
|
||||
$previous_status = -1;
|
||||
$message = 'Host is back up';
|
||||
|
||||
####
|
||||
# Getting last history for this host
|
||||
####
|
||||
my $query2 = $dbh->prepare('SELECT status FROM tasks_history WHERE task_id = ' . $tasks->{'id'} . ' ORDER BY datetime DESC LIMIT 1');
|
||||
$query2->execute() or output('Cannot get history for this task', 'ERROR', 0);
|
||||
|
||||
if ($query2->rows > 0) {
|
||||
my $history = $query2->fetchrow_hashref();
|
||||
$previous_status = $history->{'status'};
|
||||
}
|
||||
|
||||
if ($tasks->{'type'} =~ 'ping') {
|
||||
|
||||
# Ping check returned an error
|
||||
if (! check_ping($tasks->{'host'})) {
|
||||
$status = 0;
|
||||
output('Host "'. $tasks->{'host'} .'" [' . $tasks->{'type'} . '] is down', 'ALERT');
|
||||
$message = 'Host does not reply to ping. Timed out after 5s. Giving up...';
|
||||
|
||||
}
|
||||
# Ping check went fine
|
||||
else {
|
||||
$status = 1;
|
||||
output('Host "'. $tasks->{'host'} .'" [' . $tasks->{'type'} . '] is up', 'SUCCESS');
|
||||
}
|
||||
}
|
||||
elsif ($tasks->{'type'} =~ 'http') {
|
||||
$response = check_http($tasks->{'host'}, $tasks->{'params'});
|
||||
|
||||
# HTTP check went fine
|
||||
if ($response =~ 'OK') {
|
||||
$status = 1;
|
||||
output('Host "'. $tasks->{'host'} .'" [' . $tasks->{'type'} . '] is up', 'SUCCESS');
|
||||
}
|
||||
# HTTP check returned an error
|
||||
else {
|
||||
$status = 0;
|
||||
output('Host "'. $tasks->{'host'} .'" [' . $tasks->{'type'} . '] is down', 'ALERT');
|
||||
$message = 'HTTP response was: ' . $response;
|
||||
}
|
||||
}
|
||||
else {
|
||||
output('dunno how to process this task', 'DEBUG');
|
||||
next;
|
||||
}
|
||||
|
||||
# Notify on status changes only
|
||||
if ($previous_status != -1 && $status != $previous_status) {
|
||||
output('Should send notification', 'DEBUG');
|
||||
&send_notifications($tasks->{'id'}, $tasks->{'host'}, $tasks->{'type'}, $message, $status);
|
||||
}
|
||||
|
||||
# Saving Status into DB
|
||||
if ($status >= 0) {
|
||||
save_history($tasks->{'id'}, $status, $execution_time, $response);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
output('nothing to monitor, sleeping back', 'DEBUG');
|
||||
}
|
||||
|
||||
|
||||
|
||||
#####
|
||||
# Function used for the PING test
|
||||
#####
|
||||
sub check_ping {
|
||||
my ($host, $round) = @_;
|
||||
$round = 1 if (! $round);
|
||||
|
||||
my $ping = Net::Ping->new('icmp');
|
||||
output('ping check n°' . $round . ' on ' . $host, 'DEBUG');
|
||||
|
||||
if (! $ping->ping($host)) {
|
||||
$ping->close();
|
||||
|
||||
if ($number_tries && $round <= $number_tries) {
|
||||
sleep (2);
|
||||
return check_ping($host, $round + 1)
|
||||
}
|
||||
else {
|
||||
return undef;
|
||||
}
|
||||
|
||||
} else {
|
||||
$ping->close();
|
||||
return 'OK';
|
||||
}
|
||||
}
|
||||
|
||||
#####
|
||||
# Function used to check HTTP service
|
||||
#####
|
||||
sub check_http {
|
||||
my ($host, $find, $round) = @_;
|
||||
$round = 1 if (! $round);
|
||||
|
||||
$host = 'http://'.$host if ($host !~ m/^http/i);
|
||||
|
||||
my $check = LWP::UserAgent->new(
|
||||
ssl_opts => { verify_hostname => 1 },
|
||||
protocols_allowed => ['http', 'https']
|
||||
);
|
||||
$check->timeout(20);
|
||||
$check->env_proxy;
|
||||
|
||||
my $response = $check->get($host, ':content_cb' => \&process_data);
|
||||
output('http check n°' . $round . ' on ' . $host, 'DEBUG');
|
||||
|
||||
if ($response->is_success) {
|
||||
|
||||
if ($find && length($find) > 0) {
|
||||
output('searching "' . $find . '" into html content on ' . $host, 'DEBUG');
|
||||
|
||||
if ($html =~ m/$find/i) {
|
||||
output('html content found, looks fine', 'SUCCESS');
|
||||
return 'OK';
|
||||
}
|
||||
else {
|
||||
output('html content not found', 'ERROR');
|
||||
return 'Could not find "' . $find . '" into the page';
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
output('HTTP response error was: '.$response->status_line, 'DEBUG');
|
||||
if ($number_tries && $round < $number_tries) {
|
||||
sleep (2);
|
||||
return check_http($host, $find, $round + 1);
|
||||
}
|
||||
else {
|
||||
return $response->status_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#####
|
||||
# Save the page HTML content
|
||||
#####
|
||||
sub process_data {
|
||||
my ($content, $handler1, $handler2) = @_;
|
||||
$html .= $content;
|
||||
}
|
||||
|
||||
#####
|
||||
# Function managing DEBUG and OUTPUT
|
||||
#####
|
||||
sub output {
|
||||
my ($output, $level, $fatal) = @_;
|
||||
$output = server_time().' - '.$level.' - '.$output."\n";
|
||||
|
||||
if ($fatal && $fatal == 1) {
|
||||
die ('FATAL '.$output);
|
||||
}
|
||||
else {
|
||||
print ($output);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#####
|
||||
# Function that keeps an history
|
||||
#####
|
||||
sub save_history {
|
||||
my ($task_id, $status, $datetime, $response) = @_;
|
||||
my $query = $dbh->prepare('INSERT INTO `tasks_history` (`status`, `datetime`, `task_id`, `output`) VALUES(' . $status . ', "'.$datetime.'", ' . $task_id . ', "' . $response . '")');
|
||||
if ($query->execute()) {
|
||||
output('saving status to history', 'DEBUG');
|
||||
}
|
||||
else {
|
||||
output('cannot save status to history', 'ERROR');
|
||||
}
|
||||
|
||||
$update_query = $dbh->prepare('UPDATE `tasks` SET `last_execution` = "'.$datetime.'" WHERE id = ' . $task_id);
|
||||
if ($update_query->execute()) {
|
||||
output('saving last execution time for this task', 'DEBUG');
|
||||
}
|
||||
else {
|
||||
output('cannot save last execution time for this task', 'ERROR');
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#####
|
||||
# Function sending notifications
|
||||
#####
|
||||
sub send_notifications {
|
||||
my ($task_id, $host, $type, $message, $status) = @_;
|
||||
|
||||
if ($status == 0) {
|
||||
$subject = 'DOWN: host "' . $host . '" [' . $type . '] is down';
|
||||
$datas = "------ ALERT DETECTED BY MONITORING SERVICE ------ \n\n\nDATETIME: " . server_time() . " (server time)\nHOST: " . $host . "\nSERVICE: " . $type . "\nMESSAGE: " . $message;
|
||||
}
|
||||
else {
|
||||
$subject = 'UP: host "' . $host . '" [' . $type . '] is up';
|
||||
$datas = "------ RECOVERY DETECTED BY MONITORING SERVICE ------ \n\n\nDATETIME: " . server_time() . " (server time)\nHOST: " . $host . "\nSERVICE: " . $type . "\nMESSAGE: " . $message;
|
||||
}
|
||||
|
||||
my $query = $dbh->prepare('SELECT c.email FROM contacts as c JOIN notifications as n ON (n.contact_id = c.id) WHERE c.active = 1 AND n.task_id = '.$task_id);
|
||||
if ($query->execute()) {
|
||||
while ($emails = $query->fetchrow_hashref()) {
|
||||
|
||||
my $email = Email::MIME->create(
|
||||
header_str => [
|
||||
From => $email_from,
|
||||
To => $emails->{'email'},
|
||||
Subject => $subject
|
||||
],
|
||||
parts => [
|
||||
$datas
|
||||
],
|
||||
);
|
||||
eval {
|
||||
sendmail(
|
||||
$email,
|
||||
{
|
||||
from => $email_from,
|
||||
transport => Email::Sender::Transport::SMTP->new({
|
||||
host => $smtp_host,
|
||||
port => $smtp_port,
|
||||
sasl_username => $smtp_user,
|
||||
sasl_password => $smtp_password,
|
||||
ssl => $smtp_ssl,
|
||||
timeout => 10
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
output('Notification email was sent to '.$emails->{'email'}, 'DEBUG');
|
||||
};
|
||||
warn $@ if $@;
|
||||
}
|
||||
return 1
|
||||
}
|
||||
output('failed to send notifications', 'ERROR');
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
#####
|
||||
# Function getting datetime
|
||||
#####
|
||||
sub server_time {
|
||||
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
|
||||
my $now = (1900 + $year).'-'.($mon + 1).'-'.$mday.' '.$hour.':'.$min.':00';
|
||||
return $now;
|
||||
}
|
210
package-lock.json
generated
210
package-lock.json
generated
|
@ -1,13 +1,16 @@
|
|||
{
|
||||
"name": "web",
|
||||
"name": "monitolite",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"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"
|
||||
},
|
||||
|
@ -2396,6 +2399,19 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/apexcharts": {
|
||||
"version": "3.32.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.32.0.tgz",
|
||||
"integrity": "sha512-9VUyiTR2RgD4NIJOOdKxxi8tjzrKCBMr7HjWxsw+5lDPu/tZJLVudpgFhlNTIy3CbhxQ4edaiTttmbo6eDDgiA==",
|
||||
"dependencies": {
|
||||
"svg.draggable.js": "^2.2.2",
|
||||
"svg.easing.js": "^2.0.0",
|
||||
"svg.filter.js": "^2.0.2",
|
||||
"svg.pathmorphing.js": "^0.1.3",
|
||||
"svg.resize.js": "^1.4.3",
|
||||
"svg.select.js": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/array-flatten": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
|
||||
|
@ -8539,6 +8555,89 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.draggable.js": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
|
||||
"dependencies": {
|
||||
"svg.js": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.easing.js": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
|
||||
"integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=",
|
||||
"dependencies": {
|
||||
"svg.js": ">=2.3.x"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.filter.js": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
|
||||
"integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=",
|
||||
"dependencies": {
|
||||
"svg.js": "^2.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.js": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
|
||||
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA=="
|
||||
},
|
||||
"node_modules/svg.pathmorphing.js": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
|
||||
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
|
||||
"dependencies": {
|
||||
"svg.js": "^2.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.resize.js": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
|
||||
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
|
||||
"dependencies": {
|
||||
"svg.js": "^2.6.5",
|
||||
"svg.select.js": "^2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.resize.js/node_modules/svg.select.js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
|
||||
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
|
||||
"dependencies": {
|
||||
"svg.js": "^2.2.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svg.select.js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
|
||||
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
|
||||
"dependencies": {
|
||||
"svg.js": "^2.6.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/svgo": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz",
|
||||
|
@ -8906,6 +9005,14 @@
|
|||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
|
||||
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
|
||||
},
|
||||
"node_modules/vue-apexcharts": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.2.tgz",
|
||||
"integrity": "sha512-9HS3scJwWgKjmkcWIf+ndNDR0WytUJD8Ju0V2ZYcjYtlTLwJAf2SKUlBZaQTkDmwje/zMgulvZRi+MXmi+WkKw==",
|
||||
"peerDependencies": {
|
||||
"apexcharts": "^3.26.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-hot-reload-api": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
|
||||
|
@ -8937,6 +9044,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-loading-overlay": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-loading-overlay/-/vue-loading-overlay-3.4.2.tgz",
|
||||
"integrity": "sha512-xcB+NPjl76eA0uggm707x3ZFgrNosZXpynHipyS3K+rrK1NztOV49R1LY+/4ij5W1KYANp7eRI2EIHrxCpmWAw==",
|
||||
"engines": {
|
||||
"node": ">=6.9.0",
|
||||
"npm": ">=3.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.3.tgz",
|
||||
|
@ -11290,6 +11409,19 @@
|
|||
"picomatch": "^2.0.4"
|
||||
}
|
||||
},
|
||||
"apexcharts": {
|
||||
"version": "3.32.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.32.0.tgz",
|
||||
"integrity": "sha512-9VUyiTR2RgD4NIJOOdKxxi8tjzrKCBMr7HjWxsw+5lDPu/tZJLVudpgFhlNTIy3CbhxQ4edaiTttmbo6eDDgiA==",
|
||||
"requires": {
|
||||
"svg.draggable.js": "^2.2.2",
|
||||
"svg.easing.js": "^2.0.0",
|
||||
"svg.filter.js": "^2.0.2",
|
||||
"svg.pathmorphing.js": "^0.1.3",
|
||||
"svg.resize.js": "^1.4.3",
|
||||
"svg.select.js": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"array-flatten": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz",
|
||||
|
@ -15953,6 +16085,70 @@
|
|||
"has-flag": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"svg.draggable.js": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz",
|
||||
"integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==",
|
||||
"requires": {
|
||||
"svg.js": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"svg.easing.js": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz",
|
||||
"integrity": "sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=",
|
||||
"requires": {
|
||||
"svg.js": ">=2.3.x"
|
||||
}
|
||||
},
|
||||
"svg.filter.js": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz",
|
||||
"integrity": "sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=",
|
||||
"requires": {
|
||||
"svg.js": "^2.2.5"
|
||||
}
|
||||
},
|
||||
"svg.js": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz",
|
||||
"integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA=="
|
||||
},
|
||||
"svg.pathmorphing.js": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz",
|
||||
"integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==",
|
||||
"requires": {
|
||||
"svg.js": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"svg.resize.js": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz",
|
||||
"integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==",
|
||||
"requires": {
|
||||
"svg.js": "^2.6.5",
|
||||
"svg.select.js": "^2.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"svg.select.js": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz",
|
||||
"integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==",
|
||||
"requires": {
|
||||
"svg.js": "^2.2.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"svg.select.js": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz",
|
||||
"integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==",
|
||||
"requires": {
|
||||
"svg.js": "^2.6.5"
|
||||
}
|
||||
},
|
||||
"svgo": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz",
|
||||
|
@ -16228,6 +16424,12 @@
|
|||
"resolved": "https://registry.npmjs.org/vue/-/vue-2.6.14.tgz",
|
||||
"integrity": "sha512-x2284lgYvjOMj3Za7kqzRcUSxBboHqtgRE2zlos1qWaOye5yUmHn42LB1250NJBLRwEcdrB0JRwyPTEPhfQjiQ=="
|
||||
},
|
||||
"vue-apexcharts": {
|
||||
"version": "1.6.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-apexcharts/-/vue-apexcharts-1.6.2.tgz",
|
||||
"integrity": "sha512-9HS3scJwWgKjmkcWIf+ndNDR0WytUJD8Ju0V2ZYcjYtlTLwJAf2SKUlBZaQTkDmwje/zMgulvZRi+MXmi+WkKw==",
|
||||
"requires": {}
|
||||
},
|
||||
"vue-hot-reload-api": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz",
|
||||
|
@ -16247,6 +16449,12 @@
|
|||
"vue-style-loader": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"vue-loading-overlay": {
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/vue-loading-overlay/-/vue-loading-overlay-3.4.2.tgz",
|
||||
"integrity": "sha512-xcB+NPjl76eA0uggm707x3ZFgrNosZXpynHipyS3K+rrK1NztOV49R1LY+/4ij5W1KYANp7eRI2EIHrxCpmWAw==",
|
||||
"requires": {}
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.3.tgz",
|
||||
|
|
|
@ -8,9 +8,12 @@
|
|||
"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"
|
||||
}
|
||||
|
|
|
@ -1,218 +1,4 @@
|
|||
@import url(https://fonts.googleapis.com/css2?family=Hind:wght@300;400;500;600;700&display=swap);
|
||||
@font-face {
|
||||
font-family: "Digital7";
|
||||
src: url("/fonts/digital.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
/**
|
||||
* SETTINGS
|
||||
**/
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
@font-face{font-family:Digital7;font-style:normal;font-weight:400;src:url(/fonts/digital.ttf) format("truetype")}*{margin:0;padding:0}html{font-size:100%;scroll-behavior:smooth}html body{background-attachment:fixed;background-image:url(../img/bush.png);color:#3d3d3d;font-family:Hind,sans-serif;font-size:1rem;padding:10px}html body a,html body a:visited{color:inherit;text-decoration:inherit}html body a:hover,html body a:visited:hover{text-decoration:underline}html body .container{margin:0 auto;max-width:1000px;padding:0}html body h1,html body h2,html body h3,html body h4{margin-bottom:.8rem;margin-top:.8rem}html body h1{font-size:2.4rem;margin:3rem 0;text-align:center}html body h2{font-size:1.4rem;margin-top:0;padding-top:0}html body h3{background-color:#0a9f9a;color:#f0f0f0;font-size:1.4rem;margin:0;padding:.8rem;position:relative}html body h3 small{font-size:.9rem}html body h3 .context-menu{cursor:pointer;font-size:1rem;position:absolute;right:.7rem;top:.7rem}html body div.round{background-color:#fff;border-radius:5px;box-shadow:3px 3px 6px 0 rgba(0,0,0,.3);margin-bottom:3rem;overflow:hidden;position:relative}html body div.round h3{margin-bottom:1rem}html body img{vertical-align:sub}html body table{border:1px solid #abc;border-collapse:collapse;border-spacing:0;font-size:14px}html body table th{background-color:#e6eeee}html body table td,html body table th{border:1px solid #9ccece;padding:.3rem}html body table td{background-color:#fff;color:#3d3d3d;text-align:center}html body table td,html body table td img{vertical-align:middle}html body table td.right{text-align:right}html body table#contacts_tbl,html body table#tasks_tbl{width:100%}html body .no-data{color:#727272;font-size:.9rem;font-style:italic;margin-bottom:1.3rem;text-align:center}html body .quick-view .new-group{border-radius:.4rem;cursor:pointer;display:inline-block;margin:.2rem;overflow:hidden}html body .quick-view .new-group .square{float:left;height:100%;line-height:1.2rem;margin:0;min-width:1.4rem;padding:.2rem .6rem;text-align:center;vertical-align:middle}html body .quick-view .new-group .square:not(:first-of-type){border-left:1px solid #fff}html body .tasks .task{background-color:#fff;border-radius:5px;box-shadow:3px 3px 6px 0 rgba(0,0,0,.3);margin-top:2rem;overflow:hidden;padding:0;position:relative}html body .spacer{clear:both;line-height:0;margin:0;padding:0}html body .block-content{padding:.8rem}html body .highlight{background-color:#166260;border-radius:.5rem;color:#fff;display:inline-block;font-size:1rem;padding:0 1rem;vertical-align:middle}html body .small{font-size:.8rem}html body .hidden{display:none}html body .up{background-color:#8adf8a}html body .down{background-color:#f79292}html body .unknown{background-color:#f5d69e}html body .inactive{background-color:#dfdfdf!important;opacity:.5}html body .refreshed-time{font-size:.8rem;margin-bottom:2rem;text-align:right}html body .refreshed-time .clock{background-color:#000;border-radius:4px;color:#fff;font-family:Digital7;font-size:1.2rem;padding:.3rem .5rem}@-webkit-keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}@keyframes shake{10%,90%{transform:translate3d(-1px,0,0)}20%,80%{transform:translate3d(2px,0,0)}30%,50%,70%{transform:translate3d(-4px,0,0)}40%,60%{transform:translate3d(4px,0,0)}}
|
||||
|
||||
html {
|
||||
font-size: 100%;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
html body {
|
||||
padding: 10px;
|
||||
font-family: "Hind", sans-serif;
|
||||
font-size: 1rem;
|
||||
color: #3D3D3D;
|
||||
background-image: url(../img/bush.png);
|
||||
background-attachment: fixed;
|
||||
}
|
||||
html body a, html body a:visited {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
html body a:hover, html body a:visited:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
html body .container {
|
||||
padding: 0;
|
||||
margin: 0 auto;
|
||||
max-width: 1000px;
|
||||
}
|
||||
html body h1, html body h2, html body h3, html body h4 {
|
||||
margin-top: 0.8rem;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
html body h1 {
|
||||
font-size: 2.4rem;
|
||||
text-align: center;
|
||||
margin: 3rem 0;
|
||||
}
|
||||
html body h2 {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
html body h3 {
|
||||
font-size: 1.4rem;
|
||||
background-color: #0a9f9a;
|
||||
margin: 0;
|
||||
padding: 0.8rem;
|
||||
color: #f0f0f0;
|
||||
position: relative;
|
||||
}
|
||||
html body h3 small {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
html body h3 .context-menu {
|
||||
position: absolute;
|
||||
right: 0.7rem;
|
||||
top: 0.7rem;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
}
|
||||
html body img {
|
||||
vertical-align: sub;
|
||||
}
|
||||
html body table {
|
||||
border: 1px solid #ABC;
|
||||
font-size: 14px;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
html body table th {
|
||||
background-color: #e6EEEE;
|
||||
border: 1px solid #9ccece;
|
||||
padding: 0.3rem;
|
||||
}
|
||||
html body table td {
|
||||
color: #3D3D3D;
|
||||
padding: 0.3rem;
|
||||
background-color: #FFF;
|
||||
border: 1px solid #9ccece;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
html body table td img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
html body table td.right {
|
||||
text-align: right;
|
||||
}
|
||||
html body table#tasks_tbl, html body table#contacts_tbl {
|
||||
width: 100%;
|
||||
}
|
||||
html body .quick-view {
|
||||
box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
html body .quick-view .new-group {
|
||||
margin: 0.2rem;
|
||||
display: inline-block;
|
||||
border-radius: 0.4rem;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
html body .quick-view .new-group .square {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
float: left;
|
||||
line-height: 1.2rem;
|
||||
min-width: 1.4rem;
|
||||
padding: 0.2rem 0.6rem;
|
||||
}
|
||||
html body .quick-view .new-group .square:not(:first-of-type) {
|
||||
border-left: 1px solid white;
|
||||
}
|
||||
html body .tasks .task {
|
||||
margin-top: 2rem;
|
||||
padding: 0;
|
||||
box-shadow: 3px 3px 6px 0px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
html body .spacer {
|
||||
clear: both;
|
||||
line-height: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
html body .block-content {
|
||||
padding: 0.8rem;
|
||||
}
|
||||
html body .highlight {
|
||||
background-color: #166260;
|
||||
padding: 0px 1rem;
|
||||
display: inline-block;
|
||||
color: #FFF;
|
||||
vertical-align: middle;
|
||||
border-radius: 0.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
html body .small {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
html body .hidden {
|
||||
display: none;
|
||||
}
|
||||
html body .up {
|
||||
background-color: #8adf8a;
|
||||
}
|
||||
html body .down {
|
||||
background-color: #f79292;
|
||||
}
|
||||
html body .unknown {
|
||||
background-color: #f5d69e;
|
||||
}
|
||||
html body .inactive {
|
||||
background-color: #dfdfdf !important;
|
||||
opacity: 0.5;
|
||||
}
|
||||
html body .refreshed-time {
|
||||
text-align: right;
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
html body .refreshed-time .clock {
|
||||
font-family: Digital7;
|
||||
font-size: 1.2rem;
|
||||
background-color: #000;
|
||||
border-radius: 4px;
|
||||
color: #FFF;
|
||||
padding: 0.3rem 0.5rem;
|
||||
}
|
||||
|
||||
@-webkit-keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translate3d(-1px, 0, 0);
|
||||
}
|
||||
20%, 80% {
|
||||
transform: translate3d(2px, 0, 0);
|
||||
}
|
||||
30%, 50%, 70% {
|
||||
transform: translate3d(-4px, 0, 0);
|
||||
}
|
||||
40%, 60% {
|
||||
transform: translate3d(4px, 0, 0);
|
||||
}
|
||||
}
|
||||
/*# sourceMappingURL=app.css.map*/
|
1
public/css/app.css.map
Normal file
1
public/css/app.css.map
Normal file
File diff suppressed because one or more lines are too long
1
public/img/dns.svg
Normal file
1
public/img/dns.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg data-name="Layer 1" id="Layer_1" viewBox="0 0 272 272" xmlns="http://www.w3.org/2000/svg"><defs><style>.cls-1{fill:#424242;}.cls-2{fill:#f3f4f2;}</style></defs><title/><rect class="cls-1" height="8" width="136" x="68" y="68"/><rect class="cls-1" height="8" width="136" x="68" y="92"/><rect class="cls-1" height="8" width="136" x="68" y="116"/><rect class="cls-1" height="8" width="136" x="68" y="140"/><rect class="cls-1" height="8" width="136" x="68" y="164"/><rect class="cls-1" height="8" width="136" x="68" y="188"/><rect class="cls-1" height="8" width="136" x="68" y="212"/><path class="cls-1" d="M216,40H192V16l-8-8H48V264H224V48Zm0,216H56V16H184V48h32Z"/><rect class="cls-2" height="8" width="136" x="68" y="68"/><rect class="cls-2" height="8" width="136" x="68" y="92"/><rect class="cls-2" height="8" width="136" x="68" y="116"/><rect class="cls-2" height="8" width="136" x="68" y="140"/><rect class="cls-2" height="8" width="136" x="68" y="164"/><rect class="cls-2" height="8" width="136" x="68" y="188"/><rect class="cls-2" height="8" width="136" x="68" y="212"/><path class="cls-1" d="M184,8V48h40Zm8,32V27.31L204.69,40Z"/><rect class="cls-1" height="8" width="136" x="68" y="68"/><rect class="cls-1" height="8" width="136" x="68" y="92"/><rect class="cls-1" height="8" width="136" x="68" y="116"/><rect class="cls-1" height="8" width="136" x="68" y="140"/><rect class="cls-1" height="8" width="136" x="68" y="164"/><rect class="cls-1" height="8" width="136" x="68" y="188"/><rect class="cls-1" height="8" width="136" x="68" y="212"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
1
public/img/ftp.svg
Normal file
1
public/img/ftp.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<?xml version="1.0" ?><svg data-name="Layer 1" id="Layer_1" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="M7,25A7,7,0,0,1,7,11a1,1,0,0,1,0,2A5,5,0,0,0,7,23a1,1,0,0,1,0,2Z"/><path d="M25,25a1,1,0,0,1,0-2,5,5,0,0,0,0-10,1,1,0,0,1,0-2,7,7,0,0,1,0,14Z"/><path d="M25,13a1,1,0,0,1-1-1A8,8,0,0,0,8,12a1,1,0,0,1-2,0,10,10,0,0,1,20,0A1,1,0,0,1,25,13Z"/><path d="M21,22H11a1,1,0,0,1,0-2H21a1,1,0,0,1,0,2Z"/><path d="M21,28H11a1,1,0,0,1,0-2H21a1,1,0,0,1,0,2Z"/><path d="M21,28a1,1,0,0,1-.83-.45l-2-3a1,1,0,1,1,1.66-1.1l2,3a1,1,0,0,1-.28,1.38A.94.94,0,0,1,21,28Z"/><path d="M19,31a.94.94,0,0,1-.55-.17,1,1,0,0,1-.28-1.38l2-3a1,1,0,0,1,1.66,1.1l-2,3A1,1,0,0,1,19,31Z"/><path d="M13,25a1,1,0,0,1-.83-.45l-2-3a1,1,0,0,1,1.66-1.1l2,3a1,1,0,0,1-.28,1.38A.94.94,0,0,1,13,25Z"/><path d="M11,22a.94.94,0,0,1-.55-.17,1,1,0,0,1-.28-1.38l2-3a1,1,0,0,1,1.66,1.1l-2,3A1,1,0,0,1,11,22Z"/><path d="M9,25H7a1,1,0,0,1,0-2H9a1,1,0,0,1,0,2Z"/><path d="M25,25H23a1,1,0,0,1,0-2h2a1,1,0,0,1,0,2Z"/></svg>
|
After Width: | Height: | Size: 989 B |
2503
public/js/app.js
2503
public/js/app.js
File diff suppressed because one or more lines are too long
29
public/js/app.js.LICENSE.txt
Normal file
29
public/js/app.js.LICENSE.txt
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*!
|
||||
* ApexCharts v3.32.0
|
||||
* (c) 2018-2021 ApexCharts
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* Vue.js v2.6.14
|
||||
* (c) 2014-2021 Evan You
|
||||
* Released under the MIT License.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* vuex v3.6.2
|
||||
* (c) 2021 Evan You
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/*! svg.draggable.js - v2.2.2 - 2019-01-08
|
||||
* https://github.com/svgdotjs/svg.draggable.js
|
||||
* Copyright (c) 2019 Wout Fierens; Licensed MIT */
|
||||
|
||||
/*! svg.filter.js - v2.0.2 - 2016-02-24
|
||||
* https://github.com/wout/svg.filter.js
|
||||
* Copyright (c) 2016 Wout Fierens; Licensed MIT */
|
||||
|
||||
//! moment.js
|
||||
|
||||
//! moment.js locale configuration
|
1
public/js/app.js.map
Normal file
1
public/js/app.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -14,6 +14,23 @@ 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'
|
||||
|
|
|
@ -90,49 +90,7 @@ html {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
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%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.quick-view {
|
||||
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);
|
||||
|
@ -140,6 +98,63 @@ html {
|
|||
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;
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>MonitoLite - Network monitoring tool</title>
|
||||
<script type="text/javascript" src="/js/app.js"></script>
|
||||
<link type="text/css" rel="stylesheet" href="/css/app.css" />
|
||||
<script type="text/javascript" src="{{ url('js/app.js') }}"></script>
|
||||
<link type="text/css" rel="stylesheet" href="{{ url('css/app.css') }}" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -11,15 +11,18 @@
|
|||
|
||||
import TaskList from './components/tasklist.vue'
|
||||
import QuickView from './components/quickview.vue'
|
||||
|
||||
export default{
|
||||
components: {
|
||||
QuickView,
|
||||
TaskList
|
||||
TaskList,
|
||||
},
|
||||
data: function() {
|
||||
return {
|
||||
refreshed_time: null
|
||||
refreshed_time: null,
|
||||
refresh: null,
|
||||
loading: true,
|
||||
color: '#FF0000',
|
||||
size: '10rem',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -31,14 +34,23 @@
|
|||
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()
|
||||
})
|
||||
.catch(error => window.alert('Cannot get tasks'))
|
||||
this.refreshed_time = this.moment();
|
||||
}
|
||||
},
|
||||
beforeRouteLeave(to, from, next) {
|
||||
clearTimeout(this.refresh)
|
||||
next();
|
||||
},
|
||||
mounted: function() {
|
||||
this.loading = this.$loading.show()
|
||||
this.getTasks()
|
||||
this.refresh = window.setInterval(() => {
|
||||
this.getTasks();
|
||||
|
|
|
@ -1,28 +1,39 @@
|
|||
<template>
|
||||
<div class="quick-view">
|
||||
<div class="quick-view round">
|
||||
<h3>
|
||||
Quick overview
|
||||
</h3>
|
||||
<div class="block-content">
|
||||
<div
|
||||
v-for="group in tasks"
|
||||
v-bind:key="group.id"
|
||||
class="new-group"
|
||||
:title="'Group: '+group.name"
|
||||
v-if="tasks && Object.keys(tasks).length > 0"
|
||||
>
|
||||
<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
|
||||
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>
|
||||
<p class="spacer"> </p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div
|
||||
v-for="group in tasks"
|
||||
v-bind:key="group.id"
|
||||
class="task"
|
||||
class="task round"
|
||||
>
|
||||
<a :name="'group-'+group.id"></a>
|
||||
<h3>
|
||||
|
@ -17,7 +17,7 @@
|
|||
<tr>
|
||||
<th width="5%">Up?</th>
|
||||
<th width="*">Host</th>
|
||||
<th width="5%">Type</th>
|
||||
<th width="10%">Type</th>
|
||||
<th width="20%">Last checked</th>
|
||||
<th width="13%">Frequency (min)</th>
|
||||
<th width="5%">Active</th>
|
||||
|
@ -38,14 +38,14 @@
|
|||
<a :href="task.host" target="_blank">{{ task.host }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<img :src="task.type == 'http' ? '/img/http.svg' : '/img/ping.svg'" width="16" alt="Type of check" :title="'Type: '+task.type" />
|
||||
<img :src="'/img/'+task.type+'.svg'" width="16" alt="Type of check" :title="'Type: '+task.type" />
|
||||
{{ task.type.toUpperCase() }}
|
||||
</td>
|
||||
<td>
|
||||
<span
|
||||
v-if="task.last_execution"
|
||||
v-if="task.executed_at"
|
||||
>
|
||||
{{ moment(task.last_execution).fromNow() }}
|
||||
<img src="/img/info.svg" alt="Infos" width="16" :title="'Result: '+task.output" />
|
||||
{{ moment(task.executed_at).fromNow() }}
|
||||
</span>
|
||||
<span
|
||||
v-else
|
||||
|
@ -83,7 +83,7 @@ export default {
|
|||
computed: {
|
||||
tasks: function() {
|
||||
return this.$store.state.tasks
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
statusText: function (status) {
|
||||
|
@ -99,10 +99,16 @@ export default {
|
|||
}
|
||||
},
|
||||
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(response => {
|
||||
this.$store.commit('updateTask', response.data)
|
||||
})
|
||||
.then(() => {
|
||||
this.loading.hide()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
24
resources/views/emails/notification.blade.php
Normal file
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
|
|
@ -1,9 +1,123 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<h3>
|
||||
Task {{ task.id }}
|
||||
<!-- <p class="context-menu"><img src="/img/menu.svg" width="40" /></p> -->
|
||||
</h3>
|
||||
<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>
|
||||
|
||||
|
@ -12,17 +126,238 @@
|
|||
export default{
|
||||
data: function() {
|
||||
return {
|
||||
task: null
|
||||
task: {
|
||||
id: null
|
||||
},
|
||||
history: null,
|
||||
notifications: null,
|
||||
refresh: null,
|
||||
loader: null,
|
||||
days: 3,
|
||||
first_day: null,
|
||||
|
||||
charts: {
|
||||
uptime: {
|
||||
render: false,
|
||||
},
|
||||
response: {
|
||||
render: false,
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
mounted: function() {
|
||||
let task_id = this.$route.params.id ?? null
|
||||
console.log(task_id)
|
||||
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 (task_id != null) {
|
||||
this.$http.get('/api/getTask/'+task_id)
|
||||
.then(response => this.task = response.data)
|
||||
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>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
|
|
@ -13,10 +13,10 @@
|
|||
|
|
||||
*/
|
||||
|
||||
$router->group(['prefix' => 'api/'], function () use ($router) {
|
||||
$router->get('getTasks/', ['uses' => 'ApiController@getTasks']);
|
||||
$router->get('getTask/{id}', ['uses' => 'ApiController@getTaskDetails']);
|
||||
$router->patch('toggleTaskStatus/{id}', ['uses' => 'ApiController@toggleTaskStatus']);
|
||||
$router->group(['prefix' => '/api'], function () use ($router) {
|
||||
$router->get('/getTasks/', ['uses' => 'ApiController@getTasks']);
|
||||
$router->post('/getTask/{id}', ['uses' => 'ApiController@getTaskDetails']);
|
||||
$router->patch('/toggleTaskStatus/{id}', ['uses' => 'ApiController@toggleTaskStatus']);
|
||||
});
|
||||
|
||||
$router->get('/{route:.*}/', function () {
|
||||
|
|
BIN
screenshot2.png
Normal file
BIN
screenshot2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 169 KiB |
104
sql/create.sql
104
sql/create.sql
|
@ -1,104 +0,0 @@
|
|||
#
|
||||
# SQL Export
|
||||
# Created by Querious (300063)
|
||||
# Created: 19 December 2021 at 10:19:50 CET
|
||||
# Encoding: Unicode (UTF-8)
|
||||
#
|
||||
|
||||
|
||||
SET @ORIG_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS;
|
||||
SET FOREIGN_KEY_CHECKS = 0;
|
||||
|
||||
SET @ORIG_UNIQUE_CHECKS = @@UNIQUE_CHECKS;
|
||||
SET UNIQUE_CHECKS = 0;
|
||||
|
||||
SET @ORIG_TIME_ZONE = @@TIME_ZONE;
|
||||
SET TIME_ZONE = '+00:00';
|
||||
|
||||
SET @ORIG_SQL_MODE = @@SQL_MODE;
|
||||
SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';
|
||||
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `tasks_history`;
|
||||
DROP TABLE IF EXISTS `notifications`;
|
||||
DROP TABLE IF EXISTS `tasks`;
|
||||
DROP TABLE IF EXISTS `groups`;
|
||||
DROP TABLE IF EXISTS `contacts`;
|
||||
|
||||
|
||||
CREATE TABLE `contacts` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`surname` varchar(200) NOT NULL,
|
||||
`firstname` varchar(200) NOT NULL,
|
||||
`email` varchar(250) NOT NULL,
|
||||
`phone` varchar(20) NOT NULL,
|
||||
`creation_date` datetime NOT NULL,
|
||||
`active` int NOT NULL DEFAULT '1',
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
|
||||
CREATE TABLE `groups` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`name` varchar(128) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
PRIMARY KEY (`id`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=244 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_unicode_ci;
|
||||
|
||||
|
||||
CREATE TABLE `tasks` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`host` varchar(255) NOT NULL,
|
||||
`type` enum('ping','http') NOT NULL,
|
||||
`params` varchar(255) NOT NULL,
|
||||
`creation_date` datetime NOT NULL,
|
||||
`frequency` int unsigned NOT NULL,
|
||||
`last_execution` datetime DEFAULT NULL,
|
||||
`active` int NOT NULL DEFAULT '0',
|
||||
`group_id` int unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `host` (`host`,`type`),
|
||||
KEY `group_id_frgn` (`group_id`),
|
||||
CONSTRAINT `group_id_frgn` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=147 DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
|
||||
CREATE TABLE `notifications` (
|
||||
`task_id` int unsigned NOT NULL,
|
||||
`contact_id` int unsigned NOT NULL,
|
||||
PRIMARY KEY (`task_id`,`contact_id`),
|
||||
KEY `contact_id` (`contact_id`),
|
||||
CONSTRAINT `notifications_ibfk_1` FOREIGN KEY (`task_id`) REFERENCES `tasks` (`id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `notifications_ibfk_2` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
|
||||
CREATE TABLE `tasks_history` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`status` int unsigned NOT NULL,
|
||||
`datetime` datetime NOT NULL,
|
||||
`output` text CHARACTER SET utf8 COLLATE utf8_general_ci,
|
||||
`task_id` int 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 CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=88 DEFAULT CHARSET=utf8mb3;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
SET FOREIGN_KEY_CHECKS = @ORIG_FOREIGN_KEY_CHECKS;
|
||||
|
||||
SET UNIQUE_CHECKS = @ORIG_UNIQUE_CHECKS;
|
||||
|
||||
SET @ORIG_TIME_ZONE = @@TIME_ZONE;
|
||||
SET TIME_ZONE = @ORIG_TIME_ZONE;
|
||||
|
||||
SET SQL_MODE = @ORIG_SQL_MODE;
|
||||
|
||||
|
||||
|
||||
# Export Finished: 19 December 2021 at 10:19:50 CET
|
||||
|
Loading…
Add table
Reference in a new issue