Merge pull request #187 from henrywhitaker3/dev

Merge dev into master
This commit is contained in:
Henry Whitaker
2020-07-20 17:13:12 +01:00
committed by GitHub
47 changed files with 2186 additions and 507 deletions

View File

@@ -1,6 +1,6 @@
# Speedtest Tracker
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker?style=flat-square)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [![commit_freq](https://img.shields.io/github/commit-activity/m/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) ![version](https://img.shields.io/badge/version-v1.7.1-success?style=flat-square) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker?style=flat-square)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [![commit_freq](https://img.shields.io/github/commit-activity/m/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) ![version](https://img.shields.io/badge/version-v1.7.8-success?style=flat-square) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses the [Ookla's speedtest cli](https://www.speedtest.net/apps/cli) package to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.
@@ -35,6 +35,31 @@ docker create \
henrywhitaker3/speedtest-tracker
```
### Using Docker Compose
```yml
speedtest:
container_name: speedtest
image: henrywhitaker3/speedtest-tracker:dev
ports:
- 8765:80
volumes:
- /path/to/data:/config
environment:
- TZ=
- PGID=
- PUID=
- SLACK_WEBHOOK=
- BASE_PATH=/speedtest
- OOKLA_EULA_GDPR=true
logging:
driver: json-file
options:
max-file: 10
max-size: 200k
restart: unless-stopped
```
#### Parameters
Container images are configured using parameters passed at runtime (such as those above). These parameters are separated by a colon and indicate `<external>:<internal>` respectively. For example, `-p 8080:80` would expose port `80` from inside the container to be accessible from the host's IP on port `8080` outside the container.

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Console\Commands;
use App\Helpers\SettingsHelper;
use Illuminate\Console\Command;
class GetConfig extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'speedtest:config';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Get the application configuration';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$this->info(json_encode(SettingsHelper::getConfig(), JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands;
use App\Helpers\SettingsHelper;
use Illuminate\Console\Command;
class SetSlackWebhook extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'speedtest:slack {webhook : The slack webhook to store}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Set the slack webhook setting';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$webhook = $this->argument('webhook');
SettingsHelper::set('slack_webhook', $webhook);
$this->info('Slack webhook updated');
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Console\Commands;
use App\Helpers\SettingsHelper;
use Illuminate\Console\Command;
class SetTelegramOptions extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'speedtest:telegram
{--bot= : The telegram bot token}
{--chat= : The telegram chat ID}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Set the telegram settings';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
$bot = $this->option('bot');
$chat = $this->option('chat');
SettingsHelper::set('telegram_bot_token', $bot);
SettingsHelper::set('telegram_chat_id', $chat);
$this->info('Telegram options updated');
}
}

View File

@@ -40,7 +40,7 @@ class SpeedtestCommand extends Command
{
$this->info('Running speedtest, this might take a while...');
$results = SpeedtestHelper::runSpeedtest();
$results = SpeedtestHelper::runSpeedtest(false, false);
if(!is_object($results)) {
$this->error('Something went wrong running the speedtest.');

View File

@@ -42,10 +42,21 @@ class SpeedtestLatestCommand extends Command
$latest = SpeedtestHelper::latest();
if($latest) {
$this->info('Last speedtest run at: ' . $latest->created_at);
$this->info('Ping: ' . $latest->ping . ' ms');
$this->info('Download: ' . $latest->download . ' Mbit/s');
$this->info('Upload: ' . $latest->upload . ' Mbit/s');
if($latest->scheduled) {
$extra = '(scheduled)';
} else {
$extra = '(manual)';
}
$this->info('Last speedtest run at: ' . $latest->created_at . ' ' . $extra);
if($latest->failed) {
$this->error('Speedtest failed');
} else {
$this->info('Ping: ' . $latest->ping . ' ms');
$this->info('Download: ' . $latest->download . ' Mbit/s');
$this->info('Upload: ' . $latest->upload . ' Mbit/s');
}
} else {
$this->info('No speedtests have been run yet.');

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Console\Commands;
use App\Helpers\SettingsHelper;
use Illuminate\Console\Command;
class TestNotification extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'speedtest:notification';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Send test notifications to all notification agents';
/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
SettingsHelper::testNotification();
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class SpeedtestFailedEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TestNotificationEvent
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $agents;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct($agents)
{
$this->agents = $agents;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}

View File

@@ -2,7 +2,9 @@
namespace App\Helpers;
use App\Events\TestNotificationEvent;
use App\Setting;
use Carbon\Carbon;
class SettingsHelper {
@@ -33,10 +35,14 @@ class SettingsHelper {
* @param String $value Value of setting
* @return \App\Setting
*/
public static function set(String $name, String $value)
public static function set(String $name, $value)
{
$setting = SettingsHelper::get($name);
if($value === false) {
$value = "0";
}
if($setting !== false) {
$setting->value = $value;
$setting->save();
@@ -70,4 +76,97 @@ class SettingsHelper {
}
return $base;
}
/**
* Check whether a setting is defined in ENV vars or through DB
*
* @param string $key
* @return boolean
*/
public static function settingIsEditable(string $key)
{
$results = [];
// Try exact key
$val = exec('echo $' . $key);
if($val == "") {
array_push($results, true);
} else {
array_push($results, false);
}
// Try key all caps
$val = exec('echo $' . strtoupper($key));
if($val == "") {
array_push($results, true);
} else {
array_push($results, false);
}
if(env($key, false) == false) {
array_push($results, true);
} else {
array_push($results, false);
}
if(env(strtoupper($key), false) == false) {
array_push($results, true);
} else {
array_push($results, false);
}
if(in_array(false, $results)) {
return false;
}
return true;
}
/**
* Get the application config
*
* @return array
*/
public static function getConfig()
{
return [
'base' => SettingsHelper::getBase(),
'graphs' => [
'download_upload_graph_enabled' => SettingsHelper::get('download_upload_graph_enabled'),
'download_upload_graph_width' => SettingsHelper::get('download_upload_graph_width'),
'ping_graph_enabled' => SettingsHelper::get('ping_graph_enabled'),
'ping_graph_width' => SettingsHelper::get('ping_graph_width'),
'failure_graph_enabled' => SettingsHelper::get('failure_graph_enabled'),
'failure_graph_width' => SettingsHelper::get('failure_graph_width'),
],
'editable' => [
'slack_webhook' => SettingsHelper::settingIsEditable('slack_webhook'),
'telegram_bot_token' => SettingsHelper::settingIsEditable('telegram_bot_token'),
'telegram_chat_id' => SettingsHelper::settingIsEditable('telegram_chat_id'),
]
];
}
/**
* Send test notification to agents
*
* @param boolean|string $agent
* @return void
*/
public static function testNotification($agent = true)
{
$agents = [ 'slack', 'telegram' ];
if($agent === true) {
event(new TestNotificationEvent($agents));
return true;
}
if(in_array($agent, $agents)) {
event(new TestNotificationEvent([ $agent ]));
return true;
}
}
}

View File

@@ -5,6 +5,7 @@ namespace App\Helpers;
use App\Speedtest;
use Carbon\Carbon;
use Exception;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use JsonException;
@@ -17,7 +18,7 @@ class SpeedtestHelper {
* @param boolean|string $output If false, new speedtest runs. If anything else, will try to parse as JSON for speedtest results.
* @return \App\Speedtest|boolean
*/
public static function runSpeedtest($output = false)
public static function runSpeedtest($output = false, $scheduled = true)
{
if($output === false) {
$output = SpeedtestHelper::output();
@@ -27,7 +28,7 @@ class SpeedtestHelper {
$output = json_decode($output, true, 512, JSON_THROW_ON_ERROR);
if(!SpeedtestHelper::checkOutputIsComplete($output)) {
return false;
$test = false;
}
$test = Speedtest::create([
@@ -38,15 +39,34 @@ class SpeedtestHelper {
'server_name' => $output['server']['name'],
'server_host' => $output['server']['host'] . ':' . $output['server']['port'],
'url' => $output['result']['url'],
'scheduled' => $scheduled
]);
} catch(JsonException $e) {
Log::error('Failed to parse speedtest JSON');
Log::error($output);
$test = false;
} catch(Exception $e) {
Log::error($e->getMessage());
$test = false;
}
return (isset($test)) ? $test : false;
if(!$test) {
Speedtest::create([
'ping' => 0,
'upload' => 0,
'download' => 0,
'failed' => true,
'scheduled' => $scheduled,
]);
}
if(!isset($test) || $test == false) {
return false;
}
Cache::flush();
return $test;
}
/**
@@ -82,6 +102,7 @@ class SpeedtestHelper {
$t = Carbon::now()->subDay();
$s = Speedtest::select(DB::raw('AVG(ping) as ping, AVG(download) as download, AVG(upload) as upload'))
->where('created_at', '>=', $t)
->where('failed', false)
->first()
->toArray();
@@ -189,4 +210,47 @@ class SpeedtestHelper {
return true;
}
/**
* Get a percentage rate of failure by days
*
* @param integer $days number of days to get rate for
* @return integer percentage fail rate
*/
public static function failureRate(int $days)
{
$ttl = Carbon::now()->addDays(1);
$rate = Cache::remember('failure-rate-' . $days, $ttl, function () use ($days) {
$range = [
Carbon::today()
];
for($i = 0; $i < $days; $i++) {
$prev = end($range);
$new = $prev->copy()->subDays(1);
array_push($range, $new);
}
$rate = [];
foreach($range as $day) {
$success = Speedtest::select(DB::raw('COUNT(id) as rate'))->whereDate('created_at', $day)->where('failed', false)->get()[0]['rate'];
$fail = Speedtest::select(DB::raw('COUNT(id) as rate'))->whereDate('created_at', $day)->where('failed', true)->get()[0]['rate'];
if(( $success + $fail ) == 0) {
$percentage = 0;
} else {
$percentage = round(( $fail / ( $success + $fail ) * 100 ), 1);
}
array_push($rate, [
'date' => $day->toDateString(),
'rate' => $percentage
]);
}
return array_reverse($rate);
});
return $rate;
}
}

View File

@@ -6,6 +6,7 @@ use App\Helpers\SettingsHelper;
use App\Rules\Cron;
use App\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
class SettingsController extends Controller
@@ -78,7 +79,6 @@ class SettingsController extends Controller
$rule = [
'data' => [ 'array', 'required' ],
'data.*.name' => [ 'string', 'required' ],
'data.*.value' => [ 'required' ],
];
$validator = Validator::make($request->all(), $rule);
@@ -91,6 +91,10 @@ class SettingsController extends Controller
$settings = [];
foreach($request->data as $d) {
if(!isset($d['value']) || $d['value'] == null) {
$d['value'] = '';
}
if($d['name'] == 'speedtest_overview_time') {
$ok = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23' ];
if(!in_array($d['value'], $ok)) {
@@ -100,7 +104,17 @@ class SettingsController extends Controller
], 422);
}
}
$setting = SettingsHelper::set($d['name'], $d['value']);
$setting = SettingsHelper::get($d['name']);
if($setting == false) {
$setting = SettingsHelper::set($d['name'], $d['value']);
} else if(SettingsHelper::settingIsEditable($setting->name)) {
$setting = SettingsHelper::set($d['name'], $d['value']);
} else {
continue;
}
array_push($settings, $setting);
}
@@ -117,12 +131,15 @@ class SettingsController extends Controller
*/
public function config()
{
return SettingsHelper::getConfig();
}
public function testNotification()
{
SettingsHelper::testNotification();
$config = [
'base' => SettingsHelper::getBase()
];
return $config;
return response()->json([
'method' => 'test notificaiton agents'
], 200);
}
}

View File

@@ -8,6 +8,7 @@ use App\Speedtest;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
@@ -51,9 +52,43 @@ class SpeedtestController extends Controller
], 422);
}
$data = Speedtest::where('created_at', '>=', Carbon::now()->subDays($days))
->orderBy('created_at', 'asc')
->get();
$ttl = Carbon::now()->addDays(1);
$data = Cache::remember('speedtest-days-' . $days, $ttl, function () use ($days) {
return Speedtest::where('created_at', '>=', Carbon::now()->subDays($days))
->where('failed', false)
->orderBy('created_at', 'asc')
->get();
});
return response()->json([
'method' => 'get speedtests in last x days',
'days' => $days,
'data' => $data
], 200);
}
/**
* Returns speedtest failure rate going back 'x' days
*
* @param int $days
* @return void
*/
public function fail($days)
{
$rule = [
'days' => [ 'required', 'integer' ],
];
$validator = Validator::make([ 'days' => $days ], $rule);
if($validator->fails()) {
return response()->json([
'method' => 'get speedtests in last x days',
'error' => $validator->errors(),
], 422);
}
$data = SpeedtestHelper::failureRate($days);
return response()->json([
'method' => 'get speedtests in last x days',
@@ -71,8 +106,10 @@ class SpeedtestController extends Controller
{
$data = SpeedtestHelper::latest();
$avg = Speedtest::select(DB::raw('AVG(ping) as ping, AVG(download) as download, AVG(upload) as upload'))
->where('failed', false)
->get();
$max = Speedtest::select(DB::raw('MAX(ping) as ping, MAX(download) as download, MAX(upload) as upload'))
->where('failed', false)
->get();
if($data) {
@@ -98,7 +135,7 @@ class SpeedtestController extends Controller
public function run()
{
try {
$data = SpeedtestJob::dispatch();
$data = SpeedtestJob::dispatch(false);
return response()->json([
'method' => 'run speedtest',
'data' => 'a new speedtest has been added to the queue'

View File

@@ -39,7 +39,7 @@ class Kernel extends HttpKernel
],
'api' => [
'throttle:100,1',
'throttle:200,1',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];

View File

@@ -3,25 +3,29 @@
namespace App\Jobs;
use App\Events\SpeedtestCompleteEvent;
use App\Events\SpeedtestFailedEvent;
use App\Helpers\SpeedtestHelper;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class SpeedtestJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
private $scheduled;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct()
public function __construct($scheduled = true)
{
//
$this->scheduled = $scheduled;
}
/**
@@ -32,8 +36,12 @@ class SpeedtestJob implements ShouldQueue
public function handle()
{
$output = SpeedtestHelper::output();
$speedtest = SpeedtestHelper::runSpeedtest($output);
event(new SpeedtestCompleteEvent($speedtest));
$speedtest = SpeedtestHelper::runSpeedtest($output, $this->scheduled);
if($speedtest == false) {
event(new SpeedtestFailedEvent());
} else {
event(new SpeedtestCompleteEvent($speedtest));
}
return $speedtest;
}
}

View File

@@ -34,9 +34,9 @@ class SpeedtestCompleteListener
{
if(SettingsHelper::get('speedtest_notifications')->value == true) {
$data = $event->speedtest;
if(env('SLACK_WEBHOOK')) {
if(SettingsHelper::get('slack_webhook')) {
try {
Notification::route('slack', env('SLACK_WEBHOOK'))
Notification::route('slack', SettingsHelper::get('slack_webhook')->value)
->notify(new SpeedtestCompleteSlack($data));
} catch(Exception $e) {
Log::notice('Your sleck webhook is invalid');
@@ -44,9 +44,10 @@ class SpeedtestCompleteListener
}
}
if(env('TELEGRAM_BOT_TOKEN') && env('TELEGRAM_CHAT_ID')) {
if(SettingsHelper::get('telegram_bot_token') && SettingsHelper::get('telegram_chat_id')) {
try {
Notification::route(TelegramChannel::class, env('TELEGRAM_CHAT_ID'))
config([ 'services.telegram-bot-api' => [ 'token' => SettingsHelper::get('telegram_bot_token')->value ] ]);
Notification::route(TelegramChannel::class, SettingsHelper::get('telegram_chat_id')->value)
->notify(new SpeedtestCompleteTelegram($data));
} catch(Exception $e) {
Log::notice('Your telegram settings are invalid');

View File

@@ -0,0 +1,55 @@
<?php
namespace App\Listeners;
use App\Helpers\SettingsHelper;
use App\Notifications\SpeedtestFailedSlack;
use App\Notifications\SpeedtestFailedTelegram;
use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class SpeedtestFailedListener
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
if(SettingsHelper::get('slack_webhook')) {
try {
Notification::route('slack', SettingsHelper::get('slack_webhook')->value)
->notify(new SpeedtestFailedSlack());
} catch(Exception $e) {
Log::notice('Your sleck webhook is invalid');
Log::notice($e);
}
}
if(SettingsHelper::get('telegram_bot_token') && SettingsHelper::get('telegram_chat_id')) {
try {
config([ 'services.telegram-bot-api' => [ 'token' => SettingsHelper::get('telegram_bot_token')->value ] ]);
Notification::route(TelegramChannel::class, SettingsHelper::get('telegram_chat_id')->value)
->notify(new SpeedtestFailedTelegram());
} catch(Exception $e) {
Log::notice('Your telegram settings are invalid');
Log::notice($e);
}
}
}
}

View File

@@ -35,9 +35,9 @@ class SpeedtestOverviewListener
{
if(SettingsHelper::get('speedtest_overview_notification')->value == true) {
$data = SpeedtestHelper::last24Hours();
if(env('SLACK_WEBHOOK')) {
if(SettingsHelper::get('slack_webhook')) {
try {
Notification::route('slack', env('SLACK_WEBHOOK'))
Notification::route('slack', SettingsHelper::get('slack_webhook')->value)
->notify(new SpeedtestOverviewSlack($data));
} catch(Exception $e) {
Log::notice('Your sleck webhook is invalid');
@@ -45,9 +45,10 @@ class SpeedtestOverviewListener
}
}
if(env('TELEGRAM_BOT_TOKEN') && env('TELEGRAM_CHAT_ID')) {
if(SettingsHelper::get('telegram_bot_token') && SettingsHelper::get('telegram_chat_id')) {
try {
Notification::route(TelegramChannel::class, env('TELEGRAM_CHAT_ID'))
config([ 'services.telegram-bot-api' => [ 'token' => SettingsHelper::get('telegram_bot_token')->value ] ]);
Notification::route(TelegramChannel::class, SettingsHelper::get('telegram_chat_id')->value)
->notify(new SpeedtestOverviewTelegram($data));
} catch(Exception $e) {
Log::notice('Your telegram settings are invalid');

View File

@@ -0,0 +1,84 @@
<?php
namespace App\Listeners;
use App\Helpers\SettingsHelper;
use App\Notifications\TestSlackNotification;
use App\Notifications\TestTelegramNotification;
use Exception;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
use NotificationChannels\Telegram\TelegramChannel;
class TestNotificationListener
{
private $agents;
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
foreach($event->agents as $agent) {
if($agent == 'slack') {
$this->slackNotification();
}
if($agent == 'telegram') {
$this->telegramNotification();
}
}
}
/**
* Send a slack notification
*
* @return void
*/
private function slackNotification()
{
if(SettingsHelper::get('slack_webhook')) {
try {
Notification::route('slack', SettingsHelper::get('slack_webhook')->value)
->notify(new TestSlackNotification());
} catch(Exception $e) {
Log::notice('Your sleck webhook is invalid');
Log::notice($e);
}
}
}
/**
* Send a telegram notification
*
* @return void
*/
private function telegramNotification()
{
if(SettingsHelper::get('telegram_bot_token') && SettingsHelper::get('telegram_chat_id')) {
try {
config([ 'services.telegram-bot-api' => [ 'token' => SettingsHelper::get('telegram_bot_token')->value ] ]);
Notification::route(TelegramChannel::class, SettingsHelper::get('telegram_bot_token')->value)
->notify(new TestTelegramNotification());
} catch(Exception $e) {
Log::notice('Your telegram settings are invalid');
Log::notice($e);
}
}
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Helpers\SettingsHelper;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -40,7 +41,7 @@ class SpeedtestCompleteTelegram extends Notification
}
/**
* Format tekegram notification
* Format telegram notification
*
* @param mixed $notifiable
* @return TelegramMessage
@@ -53,7 +54,7 @@ Ping: *$speedtest->ping*
Download: *$speedtest->download*
Upload: *$speedtest->upload*";
return TelegramMessage::create()
->to(env('TELEGRAM_CHAT_ID'))
->to(SettingsHelper::get('telegram_chat_id')->value)
->content($msg)
->options(['parse_mode' => 'Markdown']);
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class SpeedtestFailedSlack extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return [
'slack',
];
}
/**
* Format slack notification
*
* @param mixed $notifiable
* @return SlackMessage
*/
public function toSlack($notifiable)
{
return (new SlackMessage)
->error()
->attachment(function ($attachment) {
$attachment->title('Failed speedtest')
->content('Something went wrong running your speedtest');
});
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Notifications;
use App\Helpers\SettingsHelper;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\Telegram\TelegramChannel;
use NotificationChannels\Telegram\TelegramMessage;
class SpeedtestFailedTelegram extends Notification implements ShouldQueue
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct()
{
//
}
public function via($notifiable)
{
return [
TelegramChannel::class
];
}
/**
* Format tekegram notification
*
* @param mixed $notifiable
* @return TelegramMessage
*/
public function toTelegram($notifiable)
{
$msg = "Error: something went wrong running your speedtest";
return TelegramMessage::create()
->to(SettingsHelper::get('telegram_chat_id')->value)
->content($msg)
->options(['parse_mode' => 'Markdown']);
}
}

View File

@@ -2,6 +2,7 @@
namespace App\Notifications;
use App\Helpers\SettingsHelper;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
@@ -53,7 +54,7 @@ Average ping: *".$data["ping"]."*
Average download: *".$data["download"]."*
Average upload: *".$data["upload"]."*";
return TelegramMessage::create()
->to(env('TELEGRAM_CHAT_ID'))
->to(SettingsHelper::get('telegram_chat_id')->value)
->content($msg)
->options(['parse_mode' => 'Markdown']);
}

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Notifications;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
class TestSlackNotification extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return [
'slack'
];
}
/**
* Format slack notification
*
* @param mixed $notifiable
* @return SlackMessage
*/
public function toSlack($notifiable)
{
return (new SlackMessage)
->warning()
->attachment(function ($attachment) {
$attachment->title('Test notification');
});
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Notifications;
use App\Helpers\SettingsHelper;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
use NotificationChannels\Telegram\TelegramChannel;
use NotificationChannels\Telegram\TelegramMessage;
class TestTelegramNotification extends Notification
{
use Queueable;
/**
* Create a new notification instance.
*
* @return void
*/
public function __construct()
{
//
}
/**
* Get the notification's delivery channels.
*
* @param mixed $notifiable
* @return array
*/
public function via($notifiable)
{
return [
TelegramChannel::class
];
}
/**
* Format tekegram notification
*
* @param mixed $notifiable
* @return TelegramMessage
*/
public function toTelegram($notifiable)
{
$msg = "Test notification";
return TelegramMessage::create()
->to(SettingsHelper::get('telegram_chat_id')->value)
->content($msg)
->options(['parse_mode' => 'Markdown']);
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
* @return array
*/
public function toArray($notifiable)
{
return [
//
];
}
}

View File

@@ -3,9 +3,13 @@
namespace App\Providers;
use App\Events\SpeedtestCompleteEvent;
use App\Events\SpeedtestFailedEvent;
use App\Events\SpeedtestOverviewEvent;
use App\Events\TestNotificationEvent;
use App\Listeners\SpeedtestCompleteListener;
use App\Listeners\SpeedtestFailedListener;
use App\Listeners\SpeedtestOverviewListener;
use App\Listeners\TestNotificationListener;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@@ -28,6 +32,12 @@ class EventServiceProvider extends ServiceProvider
SpeedtestOverviewEvent::class => [
SpeedtestOverviewListener::class
],
SpeedtestFailedEvent::class => [
SpeedtestFailedListener::class
],
TestNotificationEvent::class => [
TestNotificationListener::class
]
];
/**

View File

@@ -2,6 +2,7 @@
namespace App;
use App\Helpers\SettingsHelper;
use Illuminate\Database\Eloquent\Model;
class Setting extends Model

View File

@@ -20,6 +20,8 @@ class Speedtest extends Model
'server_name',
'server_host',
'url',
'scheduled',
'failed',
];
protected $table = 'speedtests';

View File

@@ -1,4 +1,70 @@
{
"1.7.8": [
{
"description": "Updated dependencies",
"link": ""
}
],
"1.7.7": [
{
"description": "Fixed speedtest:latest output",
"link": ""
},
{
"description": "Updated dependencies",
"link": ""
}
],
"1.7.6": [
{
"description": "Fixed issue with page failing to load",
"link": ""
}
],
"1.7.5": [
{
"description": "Moved notification agent settings to DB",
"link": ""
},
{
"description": "Updated dependencies",
"link": ""
}
],
"1.7.4": [
{
"description": "Stopped failed tests appearing in graphs",
"link": ""
},
{
"description": "Added failure rate graph",
"link": ""
},
{
"description": "Updated dependencies",
"link": ""
}
],
"1.7.3": [
{
"description": "Updated dependencies",
"link": ""
},
{
"description": "Added notifications and logging of failed tests",
"link": ""
}
],
"1.7.2": [
{
"description": "Updated UI for speedtest info",
"link": ""
},
{
"description": "Added field to track manual vs scheduled tests",
"link": ""
}
],
"1.7.1": [
{
"description": "Updated dependencies",

View File

@@ -13,7 +13,7 @@
"dragonmantank/cron-expression": "^2",
"fideloper/proxy": "^4.2",
"fruitcake/laravel-cors": "^2.0",
"guzzlehttp/guzzle": "^6.3",
"guzzlehttp/guzzle": "^7.0",
"laravel-notification-channels/telegram": "^0.4.0",
"laravel/framework": "^7.0",
"laravel/slack-notification-channel": "^2.0",

376
composer.lock generated
View File

@@ -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": "1f8e2d768bdcfbd5bf18db3f114479c8",
"content-hash": "d1d54df76d42dd6744e4fb47afdf6045",
"packages": [
{
"name": "asm89/stack-cors",
@@ -834,37 +834,44 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.5.5",
"version": "7.0.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e"
"reference": "2d9d3c186a6637a43193e66b097c50e4451eaab2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/2d9d3c186a6637a43193e66b097c50e4451eaab2",
"reference": "2d9d3c186a6637a43193e66b097c50e4451eaab2",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.6.1",
"php": ">=5.5",
"symfony/polyfill-intl-idn": "^1.17.0"
"php": "^7.2.5",
"psr/http-client": "^1.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"ergebnis/composer-normalize": "^2.0",
"ext-curl": "*",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"php-http/client-integration-tests": "dev-phpunit8",
"phpunit/phpunit": "^8.5.5",
"psr/log": "^1.1"
},
"suggest": {
"ext-curl": "Required for CURL handler support",
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.5-dev"
"dev-master": "7.0-dev"
}
},
"autoload": {
@@ -884,6 +891,11 @@
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "Guzzle is a PHP HTTP client library",
@@ -894,10 +906,12 @@
"framework",
"http",
"http client",
"psr-18",
"psr-7",
"rest",
"web service"
],
"time": "2020-06-16T21:01:06+00:00"
"time": "2020-06-27T10:33:25+00:00"
},
{
"name": "guzzlehttp/promises",
@@ -1023,21 +1037,21 @@
},
{
"name": "laravel-notification-channels/telegram",
"version": "0.4.0",
"version": "0.4.1",
"source": {
"type": "git",
"url": "https://github.com/laravel-notification-channels/telegram.git",
"reference": "9e4bb2fbf1a7a06e8849fa2d50bf57fa7c4483e3"
"reference": "55a9d8bce07f456e0c675909dba9b21acda376d6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel-notification-channels/telegram/zipball/9e4bb2fbf1a7a06e8849fa2d50bf57fa7c4483e3",
"reference": "9e4bb2fbf1a7a06e8849fa2d50bf57fa7c4483e3",
"url": "https://api.github.com/repos/laravel-notification-channels/telegram/zipball/55a9d8bce07f456e0c675909dba9b21acda376d6",
"reference": "55a9d8bce07f456e0c675909dba9b21acda376d6",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/guzzle": "^6.2",
"guzzlehttp/guzzle": "^6.2 || ^7.0",
"illuminate/notifications": "^5.5 || ^6.0 || ^7.0",
"illuminate/support": "^5.5 || ^6.0 || ^7.0",
"php": ">=7.1"
@@ -1080,20 +1094,20 @@
"telegram notification",
"telegram notifications channel"
],
"time": "2020-06-02T06:05:27+00:00"
"time": "2020-07-06T19:01:02+00:00"
},
{
"name": "laravel/framework",
"version": "v7.17.2",
"version": "v7.20.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "d16ff3a0a66d98e04163456b39c4b7302cf50a40"
"reference": "682ea946bc136aa686d5a64940ab3d4a24d5a613"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/d16ff3a0a66d98e04163456b39c4b7302cf50a40",
"reference": "d16ff3a0a66d98e04163456b39c4b7302cf50a40",
"url": "https://api.github.com/repos/laravel/framework/zipball/682ea946bc136aa686d5a64940ab3d4a24d5a613",
"reference": "682ea946bc136aa686d5a64940ab3d4a24d5a613",
"shasum": ""
},
"require": {
@@ -1237,24 +1251,24 @@
"framework",
"laravel"
],
"time": "2020-06-24T13:11:25+00:00"
"time": "2020-07-14T13:42:44+00:00"
},
{
"name": "laravel/slack-notification-channel",
"version": "v2.0.2",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/slack-notification-channel.git",
"reference": "ecc90a70791195d6f5e20b2732a5eb1eb9619d10"
"reference": "d0a7f53342a5daa74e43e1b08dc8a7e83db152d8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/ecc90a70791195d6f5e20b2732a5eb1eb9619d10",
"reference": "ecc90a70791195d6f5e20b2732a5eb1eb9619d10",
"url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/d0a7f53342a5daa74e43e1b08dc8a7e83db152d8",
"reference": "d0a7f53342a5daa74e43e1b08dc8a7e83db152d8",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0",
"guzzlehttp/guzzle": "^6.0|^7.0",
"illuminate/notifications": "~5.8.0|^6.0|^7.0",
"php": "^7.1.3"
},
@@ -1265,7 +1279,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
"dev-master": "2.x-dev"
},
"laravel": {
"providers": [
@@ -1294,20 +1308,20 @@
"notifications",
"slack"
],
"time": "2019-08-27T14:40:26+00:00"
"time": "2020-06-30T20:34:53+00:00"
},
{
"name": "laravel/tinker",
"version": "v2.4.0",
"version": "v2.4.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/tinker.git",
"reference": "cde90a7335a2130a4488beb68f4b2141869241db"
"reference": "3c9ef136ca59366bc1b50b7f2500a946d5149c62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/tinker/zipball/cde90a7335a2130a4488beb68f4b2141869241db",
"reference": "cde90a7335a2130a4488beb68f4b2141869241db",
"url": "https://api.github.com/repos/laravel/tinker/zipball/3c9ef136ca59366bc1b50b7f2500a946d5149c62",
"reference": "3c9ef136ca59366bc1b50b7f2500a946d5149c62",
"shasum": ""
},
"require": {
@@ -1358,20 +1372,20 @@
"laravel",
"psysh"
],
"time": "2020-04-07T15:01:31+00:00"
"time": "2020-07-07T15:10:00+00:00"
},
{
"name": "laravel/ui",
"version": "v2.0.3",
"version": "v2.1.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/ui.git",
"reference": "15368c5328efb7ce94f35ca750acde9b496ab1b1"
"reference": "da9350533d0da60d5dc42fb7de9c561c72129bba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/ui/zipball/15368c5328efb7ce94f35ca750acde9b496ab1b1",
"reference": "15368c5328efb7ce94f35ca750acde9b496ab1b1",
"url": "https://api.github.com/repos/laravel/ui/zipball/da9350533d0da60d5dc42fb7de9c561c72129bba",
"reference": "da9350533d0da60d5dc42fb7de9c561c72129bba",
"shasum": ""
},
"require": {
@@ -1413,7 +1427,7 @@
"laravel",
"ui"
],
"time": "2020-04-29T15:06:45+00:00"
"time": "2020-06-30T20:56:33+00:00"
},
{
"name": "lcobucci/jwt",
@@ -1472,16 +1486,16 @@
},
{
"name": "league/commonmark",
"version": "1.5.0",
"version": "1.5.1",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/commonmark.git",
"reference": "fc33ca12575e98e57cdce7d5f38b2ca5335714b3"
"reference": "6d74caf6abeed5fd85d6ec20da23d7269cd0b46f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/fc33ca12575e98e57cdce7d5f38b2ca5335714b3",
"reference": "fc33ca12575e98e57cdce7d5f38b2ca5335714b3",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/6d74caf6abeed5fd85d6ec20da23d7269cd0b46f",
"reference": "6d74caf6abeed5fd85d6ec20da23d7269cd0b46f",
"shasum": ""
},
"require": {
@@ -1563,7 +1577,7 @@
"type": "tidelift"
}
],
"time": "2020-06-21T20:50:13+00:00"
"time": "2020-06-27T12:50:08+00:00"
},
{
"name": "league/flysystem",
@@ -1811,16 +1825,16 @@
},
{
"name": "nesbot/carbon",
"version": "2.35.0",
"version": "2.36.1",
"source": {
"type": "git",
"url": "https://github.com/briannesbitt/Carbon.git",
"reference": "4b9bd835261ef23d36397a46a76b496a458305e5"
"reference": "ee7378a36cc62952100e718bcc58be4c7210e55f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/4b9bd835261ef23d36397a46a76b496a458305e5",
"reference": "4b9bd835261ef23d36397a46a76b496a458305e5",
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ee7378a36cc62952100e718bcc58be4c7210e55f",
"reference": "ee7378a36cc62952100e718bcc58be4c7210e55f",
"shasum": ""
},
"require": {
@@ -1832,9 +1846,10 @@
"require-dev": {
"doctrine/orm": "^2.7",
"friendsofphp/php-cs-fixer": "^2.14 || ^3.0",
"kylekatarnls/multi-tester": "^1.1",
"kylekatarnls/multi-tester": "^2.0",
"phpmd/phpmd": "^2.8",
"phpstan/phpstan": "^0.11",
"phpstan/extension-installer": "^1.0",
"phpstan/phpstan": "^0.12.30",
"phpunit/phpunit": "^7.5 || ^8.0",
"squizlabs/php_codesniffer": "^3.4"
},
@@ -1851,6 +1866,11 @@
"providers": [
"Carbon\\Laravel\\ServiceProvider"
]
},
"phpstan": {
"includes": [
"extension.neon"
]
}
},
"autoload": {
@@ -1890,20 +1910,20 @@
"type": "tidelift"
}
],
"time": "2020-05-24T18:27:52+00:00"
"time": "2020-07-04T12:29:56+00:00"
},
{
"name": "nikic/php-parser",
"version": "v4.4.0",
"version": "v4.6.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120"
"reference": "c346bbfafe2ff60680258b631afb730d186ed864"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120",
"reference": "bd43ec7152eaaab3bd8c6d0aa95ceeb1df8ee120",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/c346bbfafe2ff60680258b631afb730d186ed864",
"reference": "c346bbfafe2ff60680258b631afb730d186ed864",
"shasum": ""
},
"require": {
@@ -1942,7 +1962,7 @@
"parser",
"php"
],
"time": "2020-04-10T16:34:50+00:00"
"time": "2020-07-02T17:12:47+00:00"
},
{
"name": "opis/closure",
@@ -2165,6 +2185,55 @@
],
"time": "2019-01-08T18:20:26+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"time": "2020-06-29T06:28:15+00:00"
},
{
"name": "psr/http-message",
"version": "1.0.1",
@@ -2312,16 +2381,16 @@
},
{
"name": "psy/psysh",
"version": "v0.10.3",
"version": "v0.10.4",
"source": {
"type": "git",
"url": "https://github.com/bobthecow/psysh.git",
"reference": "2bde2fa03e05dff0aee834598b951d6fc7c6fe02"
"reference": "a8aec1b2981ab66882a01cce36a49b6317dc3560"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/2bde2fa03e05dff0aee834598b951d6fc7c6fe02",
"reference": "2bde2fa03e05dff0aee834598b951d6fc7c6fe02",
"url": "https://api.github.com/repos/bobthecow/psysh/zipball/a8aec1b2981ab66882a01cce36a49b6317dc3560",
"reference": "a8aec1b2981ab66882a01cce36a49b6317dc3560",
"shasum": ""
},
"require": {
@@ -2380,7 +2449,7 @@
"interactive",
"shell"
],
"time": "2020-04-07T06:44:48+00:00"
"time": "2020-05-03T19:32:03+00:00"
},
{
"name": "ralouphie/getallheaders",
@@ -2794,16 +2863,16 @@
},
{
"name": "symfony/deprecation-contracts",
"version": "v2.1.2",
"version": "v2.1.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337"
"reference": "5e20b83385a77593259c9f8beb2c43cd03b2ac14"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337",
"reference": "dd99cb3a0aff6cadd2a8d7d7ed72c2161e218337",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/5e20b83385a77593259c9f8beb2c43cd03b2ac14",
"reference": "5e20b83385a77593259c9f8beb2c43cd03b2ac14",
"shasum": ""
},
"require": {
@@ -2813,6 +2882,10 @@
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
@@ -2850,7 +2923,7 @@
"type": "tidelift"
}
],
"time": "2020-05-27T08:34:37+00:00"
"time": "2020-06-06T08:49:21+00:00"
},
{
"name": "symfony/error-handler",
@@ -3011,16 +3084,16 @@
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v2.1.2",
"version": "v2.1.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "405952c4e90941a17e52ef7489a2bd94870bb290"
"reference": "f6f613d74cfc5a623fc36294d3451eb7fa5a042b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/405952c4e90941a17e52ef7489a2bd94870bb290",
"reference": "405952c4e90941a17e52ef7489a2bd94870bb290",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/f6f613d74cfc5a623fc36294d3451eb7fa5a042b",
"reference": "f6f613d74cfc5a623fc36294d3451eb7fa5a042b",
"shasum": ""
},
"require": {
@@ -3034,6 +3107,10 @@
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
@@ -3079,7 +3156,7 @@
"type": "tidelift"
}
],
"time": "2020-05-20T17:43:50+00:00"
"time": "2020-07-06T13:23:11+00:00"
},
{
"name": "symfony/finder",
@@ -3950,16 +4027,16 @@
},
{
"name": "symfony/polyfill-php72",
"version": "v1.17.0",
"version": "v1.17.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "f048e612a3905f34931127360bdd2def19a5e582"
"reference": "3d9c70ff1b9f6bb618f9954b2f7f760220c2b38a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582",
"reference": "f048e612a3905f34931127360bdd2def19a5e582",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/3d9c70ff1b9f6bb618f9954b2f7f760220c2b38a",
"reference": "3d9c70ff1b9f6bb618f9954b2f7f760220c2b38a",
"shasum": ""
},
"require": {
@@ -3969,6 +4046,10 @@
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
@@ -4015,7 +4096,7 @@
"type": "tidelift"
}
],
"time": "2020-05-12T16:47:27+00:00"
"time": "2020-06-06T08:46:27+00:00"
},
{
"name": "symfony/polyfill-php73",
@@ -4397,16 +4478,16 @@
},
{
"name": "symfony/service-contracts",
"version": "v2.1.2",
"version": "v2.1.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/service-contracts.git",
"reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b"
"reference": "58c7475e5457c5492c26cc740cc0ad7464be9442"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/66a8f0957a3ca54e4f724e49028ab19d75a8918b",
"reference": "66a8f0957a3ca54e4f724e49028ab19d75a8918b",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/58c7475e5457c5492c26cc740cc0ad7464be9442",
"reference": "58c7475e5457c5492c26cc740cc0ad7464be9442",
"shasum": ""
},
"require": {
@@ -4420,6 +4501,10 @@
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
@@ -4465,7 +4550,7 @@
"type": "tidelift"
}
],
"time": "2020-05-20T17:43:50+00:00"
"time": "2020-07-06T13:23:11+00:00"
},
{
"name": "symfony/string",
@@ -4646,16 +4731,16 @@
},
{
"name": "symfony/translation-contracts",
"version": "v2.1.2",
"version": "v2.1.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/translation-contracts.git",
"reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e"
"reference": "616a9773c853097607cf9dd6577d5b143ffdcd63"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/e5ca07c8f817f865f618aa072c2fe8e0e637340e",
"reference": "e5ca07c8f817f865f618aa072c2fe8e0e637340e",
"url": "https://api.github.com/repos/symfony/translation-contracts/zipball/616a9773c853097607cf9dd6577d5b143ffdcd63",
"reference": "616a9773c853097607cf9dd6577d5b143ffdcd63",
"shasum": ""
},
"require": {
@@ -4668,6 +4753,10 @@
"extra": {
"branch-alias": {
"dev-master": "2.1-dev"
},
"thanks": {
"name": "symfony/contracts",
"url": "https://github.com/symfony/contracts"
}
},
"autoload": {
@@ -4713,7 +4802,7 @@
"type": "tidelift"
}
],
"time": "2020-05-20T17:43:50+00:00"
"time": "2020-07-06T13:23:11+00:00"
},
{
"name": "symfony/var-dumper",
@@ -4807,26 +4896,26 @@
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "2.2.2",
"version": "2.2.3",
"source": {
"type": "git",
"url": "https://github.com/tijsverkoyen/CssToInlineStyles.git",
"reference": "dda2ee426acd6d801d5b7fd1001cde9b5f790e15"
"reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/dda2ee426acd6d801d5b7fd1001cde9b5f790e15",
"reference": "dda2ee426acd6d801d5b7fd1001cde9b5f790e15",
"url": "https://api.github.com/repos/tijsverkoyen/CssToInlineStyles/zipball/b43b05cf43c1b6d849478965062b6ef73e223bb5",
"reference": "b43b05cf43c1b6d849478965062b6ef73e223bb5",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"php": "^5.5 || ^7.0",
"php": "^5.5 || ^7.0 || ^8.0",
"symfony/css-selector": "^2.7 || ^3.0 || ^4.0 || ^5.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0"
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0 || ^7.5"
},
"type": "library",
"extra": {
@@ -4852,7 +4941,7 @@
],
"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",
"time": "2019-10-24T08:53:34+00:00"
"time": "2020-07-13T06:12:54+00:00"
},
{
"name": "tymon/jwt-auth",
@@ -4936,22 +5025,22 @@
},
{
"name": "vlucas/phpdotenv",
"version": "v4.1.7",
"version": "v4.1.8",
"source": {
"type": "git",
"url": "https://github.com/vlucas/phpdotenv.git",
"reference": "db63b2ea280fdcf13c4ca392121b0b2450b51193"
"reference": "572af79d913627a9d70374d27a6f5d689a35de32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/db63b2ea280fdcf13c4ca392121b0b2450b51193",
"reference": "db63b2ea280fdcf13c4ca392121b0b2450b51193",
"url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/572af79d913627a9d70374d27a6f5d689a35de32",
"reference": "572af79d913627a9d70374d27a6f5d689a35de32",
"shasum": ""
},
"require": {
"php": "^5.5.9 || ^7.0 || ^8.0",
"phpoption/phpoption": "^1.7.3",
"symfony/polyfill-ctype": "^1.16"
"symfony/polyfill-ctype": "^1.17"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
@@ -5006,7 +5095,7 @@
"type": "tidelift"
}
],
"time": "2020-06-07T18:25:35+00:00"
"time": "2020-07-14T19:22:52+00:00"
},
{
"name": "voku/portable-ascii",
@@ -5148,16 +5237,16 @@
},
{
"name": "facade/flare-client-php",
"version": "1.3.2",
"version": "1.3.4",
"source": {
"type": "git",
"url": "https://github.com/facade/flare-client-php.git",
"reference": "db1e03426e7f9472c9ecd1092aff00f56aa6c004"
"reference": "0eeb0de4fc1078433f0915010bd8f41e998adcb4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/db1e03426e7f9472c9ecd1092aff00f56aa6c004",
"reference": "db1e03426e7f9472c9ecd1092aff00f56aa6c004",
"url": "https://api.github.com/repos/facade/flare-client-php/zipball/0eeb0de4fc1078433f0915010bd8f41e998adcb4",
"reference": "0eeb0de4fc1078433f0915010bd8f41e998adcb4",
"shasum": ""
},
"require": {
@@ -5165,9 +5254,11 @@
"illuminate/pipeline": "^5.5|^6.0|^7.0",
"php": "^7.1",
"symfony/http-foundation": "^3.3|^4.1|^5.0",
"symfony/mime": "^3.4|^4.0|^5.1",
"symfony/var-dumper": "^3.4|^4.0|^5.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"larapack/dd": "^1.1",
"phpunit/phpunit": "^7.5.16",
"spatie/phpunit-snapshot-assertions": "^2.0"
@@ -5200,24 +5291,24 @@
],
"funding": [
{
"url": "https://www.patreon.com/spatie",
"type": "patreon"
"url": "https://github.com/spatie",
"type": "github"
}
],
"time": "2020-03-02T15:52:04+00:00"
"time": "2020-07-13T23:25:57+00:00"
},
{
"name": "facade/ignition",
"version": "2.0.7",
"version": "2.3.3",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition.git",
"reference": "e6bedc1e74507d584fbcb041ebe0f7f215109cf2"
"reference": "cc7df15806aad8a9915148ea4daf7f0dd0be45b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition/zipball/e6bedc1e74507d584fbcb041ebe0f7f215109cf2",
"reference": "e6bedc1e74507d584fbcb041ebe0f7f215109cf2",
"url": "https://api.github.com/repos/facade/ignition/zipball/cc7df15806aad8a9915148ea4daf7f0dd0be45b5",
"reference": "cc7df15806aad8a9915148ea4daf7f0dd0be45b5",
"shasum": ""
},
"require": {
@@ -5236,7 +5327,8 @@
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"mockery/mockery": "^1.3",
"orchestra/testbench": "5.0"
"orchestra/testbench": "5.0",
"psalm/plugin-laravel": "^1.2"
},
"suggest": {
"laravel/telescope": "^3.1"
@@ -5275,25 +5367,30 @@
"laravel",
"page"
],
"time": "2020-06-08T09:14:08+00:00"
"time": "2020-07-14T11:34:42+00:00"
},
{
"name": "facade/ignition-contracts",
"version": "1.0.0",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/facade/ignition-contracts.git",
"reference": "f445db0fb86f48e205787b2592840dd9c80ded28"
"reference": "aeab1ce8b68b188a43e81758e750151ad7da796b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/facade/ignition-contracts/zipball/f445db0fb86f48e205787b2592840dd9c80ded28",
"reference": "f445db0fb86f48e205787b2592840dd9c80ded28",
"url": "https://api.github.com/repos/facade/ignition-contracts/zipball/aeab1ce8b68b188a43e81758e750151ad7da796b",
"reference": "aeab1ce8b68b188a43e81758e750151ad7da796b",
"shasum": ""
},
"require": {
"php": "^7.1"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^2.14",
"phpunit/phpunit": "^7.5|^8.0",
"vimeo/psalm": "^3.12"
},
"type": "library",
"autoload": {
"psr-4": {
@@ -5319,20 +5416,20 @@
"flare",
"ignition"
],
"time": "2019-08-30T14:06:08+00:00"
"time": "2020-07-14T10:10:28+00:00"
},
{
"name": "filp/whoops",
"version": "2.7.2",
"version": "2.7.3",
"source": {
"type": "git",
"url": "https://github.com/filp/whoops.git",
"reference": "17d0d3f266c8f925ebd035cd36f83cf802b47d4a"
"reference": "5d5fe9bb3d656b514d455645b3addc5f7ba7714d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/filp/whoops/zipball/17d0d3f266c8f925ebd035cd36f83cf802b47d4a",
"reference": "17d0d3f266c8f925ebd035cd36f83cf802b47d4a",
"url": "https://api.github.com/repos/filp/whoops/zipball/5d5fe9bb3d656b514d455645b3addc5f7ba7714d",
"reference": "5d5fe9bb3d656b514d455645b3addc5f7ba7714d",
"shasum": ""
},
"require": {
@@ -5380,7 +5477,7 @@
"throwable",
"whoops"
],
"time": "2020-05-05T12:28:07+00:00"
"time": "2020-06-14T09:00:00+00:00"
},
{
"name": "fzaninotto/faker",
@@ -5434,20 +5531,20 @@
},
{
"name": "hamcrest/hamcrest-php",
"version": "v2.0.0",
"version": "v2.0.1",
"source": {
"type": "git",
"url": "https://github.com/hamcrest/hamcrest-php.git",
"reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad"
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad",
"reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad",
"url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
"reference": "8c3d0a3f6af734494ad8f6fbbee0ba92422859f3",
"shasum": ""
},
"require": {
"php": "^5.3|^7.0"
"php": "^5.3|^7.0|^8.0"
},
"replace": {
"cordoval/hamcrest-php": "*",
@@ -5455,14 +5552,13 @@
"kodova/hamcrest-php": "*"
},
"require-dev": {
"phpunit/php-file-iterator": "1.3.3",
"phpunit/phpunit": "~4.0",
"satooshi/php-coveralls": "^1.0"
"phpunit/php-file-iterator": "^1.4 || ^2.0",
"phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.0-dev"
"dev-master": "2.1-dev"
}
},
"autoload": {
@@ -5472,35 +5568,35 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD"
"BSD-3-Clause"
],
"description": "This is the PHP port of Hamcrest Matchers",
"keywords": [
"test"
],
"time": "2016-01-20T08:20:44+00:00"
"time": "2020-07-09T08:09:16+00:00"
},
{
"name": "mockery/mockery",
"version": "1.3.1",
"version": "1.3.2",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
"reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be"
"reference": "9b6f117dd7d36dc3858d8d8ddf9b3d584fcae283"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mockery/mockery/zipball/f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be",
"reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be",
"url": "https://api.github.com/repos/mockery/mockery/zipball/9b6f117dd7d36dc3858d8d8ddf9b3d584fcae283",
"reference": "9b6f117dd7d36dc3858d8d8ddf9b3d584fcae283",
"shasum": ""
},
"require": {
"hamcrest/hamcrest-php": "~2.0",
"hamcrest/hamcrest-php": "^2.0.1",
"lib-pcre": ">=7.0",
"php": ">=5.6.0"
},
"require-dev": {
"phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0"
"phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0|~9.0"
},
"type": "library",
"extra": {
@@ -5543,7 +5639,7 @@
"test double",
"testing"
],
"time": "2019-12-26T09:49:15+00:00"
"time": "2020-07-09T08:23:05+00:00"
},
{
"name": "myclabs/deep-copy",
@@ -6333,7 +6429,7 @@
"type": "github"
}
],
"time": "2020-06-15T10:45:47+00:00"
"time": "2020-06-22T07:06:58+00:00"
},
{
"name": "scrivo/highlight.php",

View File

@@ -7,7 +7,7 @@ return [
|--------------------------------------------------------------------------
*/
'version' => '1.7.1',
'version' => '1.7.8',
/*
|--------------------------------------------------------------------------

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateSpeedtestsAddManualColumn extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('speedtests', function($table) {
$table->boolean('scheduled')->nullable();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('speedtests', function($table) {
$table->dropColumn('scheduled');
});
}
}

View File

@@ -0,0 +1,32 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class UpdateSpeedtestAddFailedColumn extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('speedtests', function($table) {
$table->boolean('failed')->default(false);
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('speedtests', function($table) {
$table->dropColumn('failed');
});
}
}

View File

@@ -0,0 +1,70 @@
<?php
use App\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddGraphSettings extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Setting::create([
'name' => 'download_upload_graph_enabled',
'value' => true,
'description' => 'Enable the download/upload graph'
]);
Setting::create([
'name' => 'download_upload_graph_width',
'value' => 6,
'description' => 'Set the width of the download/upload graph'
]);
Setting::create([
'name' => 'ping_graph_enabled',
'value' => true,
'description' => 'Enable the ping graph'
]);
Setting::create([
'name' => 'ping_graph_width',
'value' => 6,
'description' => 'Set the width of the ping graph'
]);
Setting::create([
'name' => 'failure_graph_enabled',
'value' => true,
'description' => 'Enable the failure rate graph'
]);
Setting::create([
'name' => 'failure_graph_width',
'value' => 6,
'description' => 'Set the width of the failure rate graph'
]);
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Setting::whereIn('name', [
'download_upload_graph_enabled',
'download_upload_graph_width',
'ping_graph_enabled',
'ping_graph_width',
'failure_graph_enabled',
'failure_graph_width'
])->delete();
}
}

View File

@@ -0,0 +1,56 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddNotificationAgentSettings extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if(!SettingsHelper::get('slack_webhook')) {
Setting::create([
'name' => 'slack_webhook',
'value' => '',
'description' => ''
]);
}
if(!SettingsHelper::get('telegram_chat_id')) {
Setting::create([
'name' => 'telegram_chat_id',
'value' => '',
'description' => ''
]);
}
if(!SettingsHelper::get('telegram_bot_token')) {
Setting::create([
'name' => 'telegram_bot_token',
'value' => '',
'description' => ''
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Setting::whereIn('name', [
'slack_webhook',
'telegram_chat_id',
'telegram_bot_token',
])->delete();
}
}

582
package-lock.json generated
View File

@@ -95,37 +95,37 @@
}
},
"@babel/helper-builder-react-jsx": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.1.tgz",
"integrity": "sha512-KXzzpyWhXgzjXIlJU1ZjIXzUPdej1suE6vzqgImZ/cpAsR/CC8gUcX4EWRmDfWz/cs6HOCPMBIJ3nKoXt3BFuw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.10.4.tgz",
"integrity": "sha512-5nPcIZ7+KKDxT1427oBivl9V9YTal7qk0diccnh7RrcgrT/pGFOjgGw1dgryyx1GvHEpXVfoDF6Ak3rTiWh8Rg==",
"dev": true,
"requires": {
"@babel/helper-annotate-as-pure": "^7.10.1",
"@babel/types": "^7.10.1"
"@babel/helper-annotate-as-pure": "^7.10.4",
"@babel/types": "^7.10.4"
},
"dependencies": {
"@babel/helper-annotate-as-pure": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz",
"integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz",
"integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==",
"dev": true,
"requires": {
"@babel/types": "^7.10.1"
"@babel/types": "^7.10.4"
}
},
"@babel/helper-validator-identifier": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
"integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
"dev": true
},
"@babel/types": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz",
"integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz",
"integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.10.1",
"@babel/helper-validator-identifier": "^7.10.4",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
@@ -133,47 +133,47 @@
}
},
"@babel/helper-builder-react-jsx-experimental": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.1.tgz",
"integrity": "sha512-irQJ8kpQUV3JasXPSFQ+LCCtJSc5ceZrPFVj6TElR6XCHssi3jV8ch3odIrNtjJFRZZVbrOEfJMI79TPU/h1pQ==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-builder-react-jsx-experimental/-/helper-builder-react-jsx-experimental-7.10.4.tgz",
"integrity": "sha512-LyacH/kgQPgLAuaWrvvq1+E7f5bLyT8jXCh7nM67sRsy2cpIGfgWJ+FCnAKQXfY+F0tXUaN6FqLkp4JiCzdK8Q==",
"dev": true,
"requires": {
"@babel/helper-annotate-as-pure": "^7.10.1",
"@babel/helper-module-imports": "^7.10.1",
"@babel/types": "^7.10.1"
"@babel/helper-annotate-as-pure": "^7.10.4",
"@babel/helper-module-imports": "^7.10.4",
"@babel/types": "^7.10.4"
},
"dependencies": {
"@babel/helper-annotate-as-pure": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz",
"integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz",
"integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==",
"dev": true,
"requires": {
"@babel/types": "^7.10.1"
"@babel/types": "^7.10.4"
}
},
"@babel/helper-module-imports": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.1.tgz",
"integrity": "sha512-SFxgwYmZ3HZPyZwJRiVNLRHWuW2OgE5k2nrVs6D9Iv4PPnXVffuEHy83Sfx/l4SqF+5kyJXjAyUmrG7tNm+qVg==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz",
"integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==",
"dev": true,
"requires": {
"@babel/types": "^7.10.1"
"@babel/types": "^7.10.4"
}
},
"@babel/helper-validator-identifier": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
"integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
"dev": true
},
"@babel/types": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz",
"integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz",
"integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.10.1",
"@babel/helper-validator-identifier": "^7.10.4",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
@@ -194,147 +194,147 @@
}
},
"@babel/helper-create-class-features-plugin": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.1.tgz",
"integrity": "sha512-bwhdehBJZt84HuPUcP1HaTLuc/EywVS8rc3FgsEPDcivg+DCW+SHuLHVkYOmcBA1ZfI+Z/oZjQc/+bPmIO7uAA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.10.4.tgz",
"integrity": "sha512-9raUiOsXPxzzLjCXeosApJItoMnX3uyT4QdM2UldffuGApNrF8e938MwNpDCK9CPoyxrEoCgT+hObJc3mZa6lQ==",
"requires": {
"@babel/helper-function-name": "^7.10.1",
"@babel/helper-member-expression-to-functions": "^7.10.1",
"@babel/helper-optimise-call-expression": "^7.10.1",
"@babel/helper-plugin-utils": "^7.10.1",
"@babel/helper-replace-supers": "^7.10.1",
"@babel/helper-split-export-declaration": "^7.10.1"
"@babel/helper-function-name": "^7.10.4",
"@babel/helper-member-expression-to-functions": "^7.10.4",
"@babel/helper-optimise-call-expression": "^7.10.4",
"@babel/helper-plugin-utils": "^7.10.4",
"@babel/helper-replace-supers": "^7.10.4",
"@babel/helper-split-export-declaration": "^7.10.4"
},
"dependencies": {
"@babel/code-frame": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
"integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
"requires": {
"@babel/highlight": "^7.10.1"
"@babel/highlight": "^7.10.4"
}
},
"@babel/generator": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.1.tgz",
"integrity": "sha512-AT0YPLQw9DI21tliuJIdplVfLHya6mcGa8ctkv7n4Qv+hYacJrKmNWIteAK1P9iyLikFIAkwqJ7HAOqIDLFfgA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz",
"integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==",
"requires": {
"@babel/types": "^7.10.1",
"@babel/types": "^7.10.4",
"jsesc": "^2.5.1",
"lodash": "^4.17.13",
"source-map": "^0.5.0"
}
},
"@babel/helper-function-name": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.1.tgz",
"integrity": "sha512-fcpumwhs3YyZ/ttd5Rz0xn0TpIwVkN7X0V38B9TWNfVF42KEkhkAAuPCQ3oXmtTRtiPJrmZ0TrfS0GKF0eMaRQ==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz",
"integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==",
"requires": {
"@babel/helper-get-function-arity": "^7.10.1",
"@babel/template": "^7.10.1",
"@babel/types": "^7.10.1"
"@babel/helper-get-function-arity": "^7.10.4",
"@babel/template": "^7.10.4",
"@babel/types": "^7.10.4"
}
},
"@babel/helper-get-function-arity": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.1.tgz",
"integrity": "sha512-F5qdXkYGOQUb0hpRaPoetF9AnsXknKjWMZ+wmsIRsp5ge5sFh4c3h1eH2pRTTuy9KKAA2+TTYomGXAtEL2fQEw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz",
"integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==",
"requires": {
"@babel/types": "^7.10.1"
"@babel/types": "^7.10.4"
}
},
"@babel/helper-member-expression-to-functions": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.1.tgz",
"integrity": "sha512-u7XLXeM2n50gb6PWJ9hoO5oO7JFPaZtrh35t8RqKLT1jFKj9IWeD1zrcrYp1q1qiZTdEarfDWfTIP8nGsu0h5g==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.4.tgz",
"integrity": "sha512-m5j85pK/KZhuSdM/8cHUABQTAslV47OjfIB9Cc7P+PvlAoBzdb79BGNfw8RhT5Mq3p+xGd0ZfAKixbrUZx0C7A==",
"requires": {
"@babel/types": "^7.10.1"
"@babel/types": "^7.10.4"
}
},
"@babel/helper-optimise-call-expression": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.1.tgz",
"integrity": "sha512-a0DjNS1prnBsoKx83dP2falChcs7p3i8VMzdrSbfLhuQra/2ENC4sbri34dz/rWmDADsmF1q5GbfaXydh0Jbjg==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz",
"integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==",
"requires": {
"@babel/types": "^7.10.1"
"@babel/types": "^7.10.4"
}
},
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA=="
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg=="
},
"@babel/helper-replace-supers": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.1.tgz",
"integrity": "sha512-SOwJzEfpuQwInzzQJGjGaiG578UYmyi2Xw668klPWV5n07B73S0a9btjLk/52Mlcxa+5AdIYqws1KyXRfMoB7A==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.10.4.tgz",
"integrity": "sha512-sPxZfFXocEymYTdVK1UNmFPBN+Hv5mJkLPsYWwGBxZAxaWfFu+xqp7b6qWD0yjNuNL2VKc6L5M18tOXUP7NU0A==",
"requires": {
"@babel/helper-member-expression-to-functions": "^7.10.1",
"@babel/helper-optimise-call-expression": "^7.10.1",
"@babel/traverse": "^7.10.1",
"@babel/types": "^7.10.1"
"@babel/helper-member-expression-to-functions": "^7.10.4",
"@babel/helper-optimise-call-expression": "^7.10.4",
"@babel/traverse": "^7.10.4",
"@babel/types": "^7.10.4"
}
},
"@babel/helper-split-export-declaration": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.1.tgz",
"integrity": "sha512-UQ1LVBPrYdbchNhLwj6fetj46BcFwfS4NllJo/1aJsT+1dLTEnXJL0qHqtY7gPzF8S2fXBJamf1biAXV3X077g==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.10.4.tgz",
"integrity": "sha512-pySBTeoUff56fL5CBU2hWm9TesA4r/rOkI9DyJLvvgz09MB9YtfIYe3iBriVaYNaPe+Alua0vBIOVOLs2buWhg==",
"requires": {
"@babel/types": "^7.10.1"
"@babel/types": "^7.10.4"
}
},
"@babel/helper-validator-identifier": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
"integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw=="
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw=="
},
"@babel/highlight": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
"integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz",
"integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==",
"requires": {
"@babel/helper-validator-identifier": "^7.10.1",
"@babel/helper-validator-identifier": "^7.10.4",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
},
"@babel/parser": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.1.tgz",
"integrity": "sha512-AUTksaz3FqugBkbTZ1i+lDLG5qy8hIzCaAxEtttU6C0BtZZU9pkNZtWSVAht4EW9kl46YBiyTGMp9xTTGqViNg=="
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz",
"integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA=="
},
"@babel/template": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.1.tgz",
"integrity": "sha512-OQDg6SqvFSsc9A0ej6SKINWrpJiNonRIniYondK2ViKhB06i3c0s+76XUft71iqBEe9S1OKsHwPAjfHnuvnCig==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz",
"integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==",
"requires": {
"@babel/code-frame": "^7.10.1",
"@babel/parser": "^7.10.1",
"@babel/types": "^7.10.1"
"@babel/code-frame": "^7.10.4",
"@babel/parser": "^7.10.4",
"@babel/types": "^7.10.4"
}
},
"@babel/traverse": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.1.tgz",
"integrity": "sha512-C/cTuXeKt85K+p08jN6vMDz8vSV0vZcI0wmQ36o6mjbuo++kPMdpOYw23W2XH04dbRt9/nMEfA4W3eR21CD+TQ==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.4.tgz",
"integrity": "sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==",
"requires": {
"@babel/code-frame": "^7.10.1",
"@babel/generator": "^7.10.1",
"@babel/helper-function-name": "^7.10.1",
"@babel/helper-split-export-declaration": "^7.10.1",
"@babel/parser": "^7.10.1",
"@babel/types": "^7.10.1",
"@babel/code-frame": "^7.10.4",
"@babel/generator": "^7.10.4",
"@babel/helper-function-name": "^7.10.4",
"@babel/helper-split-export-declaration": "^7.10.4",
"@babel/parser": "^7.10.4",
"@babel/types": "^7.10.4",
"debug": "^4.1.0",
"globals": "^11.1.0",
"lodash": "^4.17.13"
}
},
"@babel/types": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz",
"integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz",
"integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==",
"requires": {
"@babel/helper-validator-identifier": "^7.10.1",
"@babel/helper-validator-identifier": "^7.10.4",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
@@ -574,18 +574,18 @@
}
},
"@babel/plugin-proposal-class-properties": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.1.tgz",
"integrity": "sha512-sqdGWgoXlnOdgMXU+9MbhzwFRgxVLeiGBqTrnuS7LC2IBU31wSsESbTUreT2O418obpfPdGUR2GbEufZF1bpqw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.10.4.tgz",
"integrity": "sha512-vhwkEROxzcHGNu2mzUC0OFFNXdZ4M23ib8aRRcJSsW8BZK9pQMD7QB7csl97NBbgGZO7ZyHUyKDnxzOaP4IrCg==",
"requires": {
"@babel/helper-create-class-features-plugin": "^7.10.1",
"@babel/helper-plugin-utils": "^7.10.1"
"@babel/helper-create-class-features-plugin": "^7.10.4",
"@babel/helper-plugin-utils": "^7.10.4"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA=="
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg=="
}
}
},
@@ -697,18 +697,18 @@
}
},
"@babel/plugin-syntax-jsx": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.1.tgz",
"integrity": "sha512-+OxyOArpVFXQeXKLO9o+r2I4dIoVoy6+Uu0vKELrlweDM3QJADZj+Z+5ERansZqIZBcLj42vHnDI8Rz9BnRIuQ==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.10.4.tgz",
"integrity": "sha512-KCg9mio9jwiARCB7WAcQ7Y1q+qicILjoK8LP/VkPkEKaf5dkaZZK1EcTe91a3JJlZ3qy6L5s9X52boEYi8DM9g==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.10.1"
"@babel/helper-plugin-utils": "^7.10.4"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
"dev": true
}
}
@@ -999,135 +999,135 @@
}
},
"@babel/plugin-transform-react-display-name": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.1.tgz",
"integrity": "sha512-rBjKcVwjk26H3VX8pavMxGf33LNlbocMHdSeldIEswtQ/hrjyTG8fKKILW1cSkODyRovckN/uZlGb2+sAV9JUQ==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.10.4.tgz",
"integrity": "sha512-Zd4X54Mu9SBfPGnEcaGcOrVAYOtjT2on8QZkLKEq1S/tHexG39d9XXGZv19VfRrDjPJzFmPfTAqOQS1pfFOujw==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.10.1"
"@babel/helper-plugin-utils": "^7.10.4"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
"dev": true
}
}
},
"@babel/plugin-transform-react-jsx": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.1.tgz",
"integrity": "sha512-MBVworWiSRBap3Vs39eHt+6pJuLUAaK4oxGc8g+wY+vuSJvLiEQjW1LSTqKb8OUPtDvHCkdPhk7d6sjC19xyFw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.10.4.tgz",
"integrity": "sha512-L+MfRhWjX0eI7Js093MM6MacKU4M6dnCRa/QPDwYMxjljzSCzzlzKzj9Pk4P3OtrPcxr2N3znR419nr3Xw+65A==",
"dev": true,
"requires": {
"@babel/helper-builder-react-jsx": "^7.10.1",
"@babel/helper-builder-react-jsx-experimental": "^7.10.1",
"@babel/helper-plugin-utils": "^7.10.1",
"@babel/plugin-syntax-jsx": "^7.10.1"
"@babel/helper-builder-react-jsx": "^7.10.4",
"@babel/helper-builder-react-jsx-experimental": "^7.10.4",
"@babel/helper-plugin-utils": "^7.10.4",
"@babel/plugin-syntax-jsx": "^7.10.4"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
"dev": true
}
}
},
"@babel/plugin-transform-react-jsx-development": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.1.tgz",
"integrity": "sha512-XwDy/FFoCfw9wGFtdn5Z+dHh6HXKHkC6DwKNWpN74VWinUagZfDcEJc3Y8Dn5B3WMVnAllX8Kviaw7MtC5Epwg==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.10.4.tgz",
"integrity": "sha512-RM3ZAd1sU1iQ7rI2dhrZRZGv0aqzNQMbkIUCS1txYpi9wHQ2ZHNjo5TwX+UD6pvFW4AbWqLVYvKy5qJSAyRGjQ==",
"dev": true,
"requires": {
"@babel/helper-builder-react-jsx-experimental": "^7.10.1",
"@babel/helper-plugin-utils": "^7.10.1",
"@babel/plugin-syntax-jsx": "^7.10.1"
"@babel/helper-builder-react-jsx-experimental": "^7.10.4",
"@babel/helper-plugin-utils": "^7.10.4",
"@babel/plugin-syntax-jsx": "^7.10.4"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
"dev": true
}
}
},
"@babel/plugin-transform-react-jsx-self": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.1.tgz",
"integrity": "sha512-4p+RBw9d1qV4S749J42ZooeQaBomFPrSxa9JONLHJ1TxCBo3TzJ79vtmG2S2erUT8PDDrPdw4ZbXGr2/1+dILA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.10.4.tgz",
"integrity": "sha512-yOvxY2pDiVJi0axdTWHSMi5T0DILN+H+SaeJeACHKjQLezEzhLx9nEF9xgpBLPtkZsks9cnb5P9iBEi21En3gg==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.10.1",
"@babel/plugin-syntax-jsx": "^7.10.1"
"@babel/helper-plugin-utils": "^7.10.4",
"@babel/plugin-syntax-jsx": "^7.10.4"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
"dev": true
}
}
},
"@babel/plugin-transform-react-jsx-source": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.1.tgz",
"integrity": "sha512-neAbaKkoiL+LXYbGDvh6PjPG+YeA67OsZlE78u50xbWh2L1/C81uHiNP5d1fw+uqUIoiNdCC8ZB+G4Zh3hShJA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.10.4.tgz",
"integrity": "sha512-FTK3eQFrPv2aveerUSazFmGygqIdTtvskG50SnGnbEUnRPcGx2ylBhdFIzoVS1ty44hEgcPoCAyw5r3VDEq+Ug==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.10.1",
"@babel/plugin-syntax-jsx": "^7.10.1"
"@babel/helper-plugin-utils": "^7.10.4",
"@babel/plugin-syntax-jsx": "^7.10.4"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
"dev": true
}
}
},
"@babel/plugin-transform-react-pure-annotations": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.1.tgz",
"integrity": "sha512-mfhoiai083AkeewsBHUpaS/FM1dmUENHBMpS/tugSJ7VXqXO5dCN1Gkint2YvM1Cdv1uhmAKt1ZOuAjceKmlLA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.10.4.tgz",
"integrity": "sha512-+njZkqcOuS8RaPakrnR9KvxjoG1ASJWpoIv/doyWngId88JoFlPlISenGXjrVacZUIALGUr6eodRs1vmPnF23A==",
"dev": true,
"requires": {
"@babel/helper-annotate-as-pure": "^7.10.1",
"@babel/helper-plugin-utils": "^7.10.1"
"@babel/helper-annotate-as-pure": "^7.10.4",
"@babel/helper-plugin-utils": "^7.10.4"
},
"dependencies": {
"@babel/helper-annotate-as-pure": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.1.tgz",
"integrity": "sha512-ewp3rvJEwLaHgyWGe4wQssC2vjks3E80WiUe2BpMb0KhreTjMROCbxXcEovTrbeGVdQct5VjQfrv9EgC+xMzCw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz",
"integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==",
"dev": true,
"requires": {
"@babel/types": "^7.10.1"
"@babel/types": "^7.10.4"
}
},
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
"dev": true
},
"@babel/helper-validator-identifier": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
"integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz",
"integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==",
"dev": true
},
"@babel/types": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.1.tgz",
"integrity": "sha512-L2yqUOpf3tzlW9GVuipgLEcZxnO+96SzR6fjXMuxxNkIgFJ5+07mHCZ+HkHqaeZu8+3LKnNJJ1bKbjBETQAsrA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz",
"integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.10.1",
"@babel/helper-validator-identifier": "^7.10.4",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
@@ -1303,24 +1303,24 @@
}
},
"@babel/preset-react": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.1.tgz",
"integrity": "sha512-Rw0SxQ7VKhObmFjD/cUcKhPTtzpeviEFX1E6PgP+cYOhQ98icNqtINNFANlsdbQHrmeWnqdxA4Tmnl1jy5tp3Q==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.10.4.tgz",
"integrity": "sha512-BrHp4TgOIy4M19JAfO1LhycVXOPWdDbTRep7eVyatf174Hff+6Uk53sDyajqZPu8W1qXRBiYOfIamek6jA7YVw==",
"dev": true,
"requires": {
"@babel/helper-plugin-utils": "^7.10.1",
"@babel/plugin-transform-react-display-name": "^7.10.1",
"@babel/plugin-transform-react-jsx": "^7.10.1",
"@babel/plugin-transform-react-jsx-development": "^7.10.1",
"@babel/plugin-transform-react-jsx-self": "^7.10.1",
"@babel/plugin-transform-react-jsx-source": "^7.10.1",
"@babel/plugin-transform-react-pure-annotations": "^7.10.1"
"@babel/helper-plugin-utils": "^7.10.4",
"@babel/plugin-transform-react-display-name": "^7.10.4",
"@babel/plugin-transform-react-jsx": "^7.10.4",
"@babel/plugin-transform-react-jsx-development": "^7.10.4",
"@babel/plugin-transform-react-jsx-self": "^7.10.4",
"@babel/plugin-transform-react-jsx-source": "^7.10.4",
"@babel/plugin-transform-react-pure-annotations": "^7.10.4"
},
"dependencies": {
"@babel/helper-plugin-utils": {
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.1.tgz",
"integrity": "sha512-fvoGeXt0bJc7VMWZGCAEBEMo/HAjW2mP8apF5eXK0wSqwLAVHAISCWRoLMBMUs2kqeaG77jltVqu4Hn8Egl3nA==",
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz",
"integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==",
"dev": true
}
}
@@ -1406,9 +1406,9 @@
"dev": true
},
"@popperjs/core": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.3.3.tgz",
"integrity": "sha512-yEvVC8RfhRPkD9TUn7cFcLcgoJePgZRAOR7T21rcRY5I8tpuhzeWfGa7We7tB14fe9R7wENdqUABcMdwD4SQLw=="
"version": "2.4.4",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.4.4.tgz",
"integrity": "sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg=="
},
"@restart/context": {
"version": "2.1.4",
@@ -1416,14 +1416,19 @@
"integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q=="
},
"@restart/hooks": {
"version": "0.3.22",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.22.tgz",
"integrity": "sha512-tW0T3hP6emYNOc76/iC96rlu+f7JYLSVk/Wnn+7dj1gJUcw4CkQNLy16vx2mBLtVKsFMZ9miVEZXat8blruDHQ==",
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.3.25.tgz",
"integrity": "sha512-m2v3N5pxTsIiSH74/sb1yW8D9RxkJidGW+5Mfwn/lHb2QzhZNlaU1su7abSyT9EGf0xS/0waLjrf7/XxQHUk7w==",
"requires": {
"lodash": "^4.17.15",
"lodash-es": "^4.17.15"
}
},
"@types/classnames": {
"version": "2.2.10",
"resolved": "https://registry.npmjs.org/@types/classnames/-/classnames-2.2.10.tgz",
"integrity": "sha512-1UzDldn9GfYYEsWWnn/P4wkTlkZDH7lDb0wBMGbtIQc9zXEQq7FlKBdZUn6OBqD8sKZZ2RQO2mAjGpXiDGoRmQ=="
},
"@types/events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
@@ -1441,6 +1446,17 @@
"@types/node": "*"
}
},
"@types/invariant": {
"version": "2.2.33",
"resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.33.tgz",
"integrity": "sha512-/jUNmS8d4bCKdqslfxW6dg/9Gksfzxz67IYfqApHn+HvHlMVXwYv2zpTDnS/yaK9BB0i0GlBTaYci0EFE62Hmw=="
},
"@types/json-schema": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -1465,14 +1481,22 @@
"dev": true
},
"@types/react": {
"version": "16.9.34",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.34.tgz",
"integrity": "sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow==",
"version": "16.9.43",
"resolved": "https://registry.npmjs.org/@types/react/-/react-16.9.43.tgz",
"integrity": "sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg==",
"requires": {
"@types/prop-types": "*",
"csstype": "^2.2.0"
}
},
"@types/react-transition-group": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.0.tgz",
"integrity": "sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w==",
"requires": {
"@types/react": "*"
}
},
"@types/warning": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz",
@@ -2757,17 +2781,6 @@
}
}
},
"clone-deep": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
"integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==",
"dev": true,
"requires": {
"is-plain-object": "^2.0.4",
"kind-of": "^6.0.2",
"shallow-clone": "^3.0.0"
}
},
"coa": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz",
@@ -3387,6 +3400,13 @@
"famulus": "2.1.2",
"lodash": "4.17.15",
"papaparse": "^5.2.0"
},
"dependencies": {
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
}
}
},
"cyclist": {
@@ -6212,6 +6232,12 @@
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
},
"klona": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/klona/-/klona-1.1.2.tgz",
"integrity": "sha512-xf88rTeHiXk+XE2Vhi6yj8Wm3gMZrygGdKjJqN8HkV+PwF/t50/LdAKHoHpPcxFAlmQszTZ1CugrK25S7qDRLA==",
"dev": true
},
"laravel-mix": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-5.0.4.tgz",
@@ -6331,9 +6357,9 @@
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
},
"lodash-es": {
"version": "4.17.15",
@@ -8323,20 +8349,25 @@
}
},
"react-bootstrap": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.0.1.tgz",
"integrity": "sha512-xMHwsvDN7sIv26P9wWiosWjITZije2dRCjEJHVfV2KFoSJY+8uv2zttEw0XMB7xviQcW3zuIGLJXuj8vf6lYEg==",
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.2.2.tgz",
"integrity": "sha512-G+QcEyBqFtakBNghdDugie+yU/ABDeqw3n+SOeRGxEn1m0dbIyHTroZpectcQk6FB3aS4RJGkZTuLVYH86Cu2A==",
"requires": {
"@babel/runtime": "^7.4.2",
"@restart/context": "^2.1.4",
"@restart/hooks": "^0.3.21",
"@types/react": "^16.9.23",
"@types/classnames": "^2.2.10",
"@types/invariant": "^2.2.33",
"@types/prop-types": "^15.7.3",
"@types/react": "^16.9.35",
"@types/react-transition-group": "^4.4.0",
"@types/warning": "^3.0.0",
"classnames": "^2.2.6",
"dom-helpers": "^5.1.2",
"invariant": "^2.2.4",
"prop-types": "^15.7.2",
"prop-types-extra": "^1.1.0",
"react-overlays": "^3.1.2",
"react-overlays": "^4.0.0",
"react-transition-group": "^4.0.0",
"uncontrollable": "^7.0.0",
"warning": "^4.0.3"
@@ -8374,9 +8405,9 @@
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"react-overlays": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-3.1.3.tgz",
"integrity": "sha512-FH82W0R9lFJm/YCTDeSvEKQxXyTaZmjMEQlAjRhgjQhknTkyMsft+X4Wep5l95QveqdxGVxl/P41WUOzTGJUcw==",
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-4.0.0.tgz",
"integrity": "sha512-LpznWocwgeB5oWKg6cDdkqKP7MbX4ClKbJqgZGUMXPRBBYcqrgM6TjjZ/8DeurNU//GuqwQMjhmo/JVma4XEWw==",
"requires": {
"@babel/runtime": "^7.4.5",
"@popperjs/core": "^2.0.0",
@@ -8476,9 +8507,9 @@
}
},
"react-toastify": {
"version": "6.0.6",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-6.0.6.tgz",
"integrity": "sha512-NdHiMhj76Z877kZlXuelVfJONslvpmDTL95FVAoBy2kkU75hiqR5+pu1GdJZfRWPhen9ecdb58d3HmefaJ06Yw==",
"version": "6.0.8",
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-6.0.8.tgz",
"integrity": "sha512-NSqCNwv+C4IfR+c92PFZiNyeBwOJvigrP2bcRi2f6Hg3WqcHhEHOknbSQOs9QDFuqUjmK3SOrdvScQ3z63ifXg==",
"requires": {
"classnames": "^2.2.6",
"prop-types": "^15.7.2",
@@ -8499,9 +8530,9 @@
}
},
"react-transition-group": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
"integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==",
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.1.tgz",
"integrity": "sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
@@ -8916,31 +8947,65 @@
"dev": true
},
"sass": {
"version": "1.26.9",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.9.tgz",
"integrity": "sha512-t8AkRVi+xvba4yZiLWkJdgJHBFCB3Dh4johniQkPy9ywkgFHNasXFEFP+RG/F6LhQ+aoE4aX+IorIWQjS0esVw==",
"version": "1.26.10",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.10.tgz",
"integrity": "sha512-bzN0uvmzfsTvjz0qwccN1sPm2HxxpNI/Xa+7PlUEMS+nQvbyuEK7Y0qFqxlPHhiNHb1Ze8WQJtU31olMObkAMw==",
"dev": true,
"requires": {
"chokidar": ">=2.0.0 <4.0.0"
}
},
"sass-loader": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-8.0.2.tgz",
"integrity": "sha512-7o4dbSK8/Ol2KflEmSco4jTjQoV988bM82P9CZdmo9hR3RLnvNc0ufMNdMrB0caq38JQ/FgF4/7RcbcfKzxoFQ==",
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-9.0.2.tgz",
"integrity": "sha512-nphcum3jNI442njnrZ5wJgSNX5lfEOHOKHCLf+PrTIaleploKqAMUuT9CVKjf+lyi6c2MCGPHh1vb9nGsjnZJA==",
"dev": true,
"requires": {
"clone-deep": "^4.0.1",
"loader-utils": "^1.2.3",
"klona": "^1.1.1",
"loader-utils": "^2.0.0",
"neo-async": "^2.6.1",
"schema-utils": "^2.6.1",
"semver": "^6.3.0"
"schema-utils": "^2.7.0",
"semver": "^7.3.2"
},
"dependencies": {
"ajv": {
"version": "6.12.3",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
"integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"schema-utils": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==",
"dev": true
}
}
@@ -9163,15 +9228,6 @@
"safe-buffer": "^5.0.1"
}
},
"shallow-clone": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz",
"integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==",
"dev": true,
"requires": {
"kind-of": "^6.0.2"
}
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",

View File

@@ -10,28 +10,28 @@
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"@babel/preset-react": "^7.10.1",
"@babel/preset-react": "^7.10.4",
"axios": "^0.19",
"bootstrap": "^4.5.0",
"cross-env": "^7.0",
"jquery": "^3.5",
"laravel-mix": "^5.0.1",
"lodash": "^4.17.13",
"lodash": "^4.17.19",
"popper.js": "^1.12",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"resolve-url-loader": "^3.1.0",
"sass": "^1.26.9",
"sass-loader": "^8.0.0"
"sass": "^1.26.10",
"sass-loader": "^9.0.2"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.10.1",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"chart.js": "^2.9.3",
"csv-file-validator": "^1.8.0",
"react-bootstrap": "^1.0.1",
"react-bootstrap": "^1.2.2",
"react-chartjs-2": "^2.9.0",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-toastify": "^6.0.6"
"react-toastify": "^6.0.8"
}
}

8
public/css/main.css vendored
View File

@@ -55,3 +55,11 @@
.setting-card {
height: 270px;
}
.home-graph {
height: 480px;
}
.form-control:disabled {
cursor: not-allowed;
}

2
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -16,8 +16,16 @@ export default class HistoryGraph extends Component {
duOptions: {},
pingData: {},
pingOptions: {},
failData: {},
failOptions: {},
loading: true,
interval: null,
graph_ul_dl_enabled: true,
graph_ul_dl_width: 6,
graph_failure_enabled: true,
graph_failure_width: 6,
graph_ping_enabled: true,
graph_ping_width: 6,
}
}
@@ -29,7 +37,7 @@ export default class HistoryGraph extends Component {
});
}
getData = (days = this.state.days) => {
getDLULPing = (days) => {
var url = 'api/speedtest/time/' + days;
Axios.get(url)
@@ -52,6 +60,7 @@ export default class HistoryGraph extends Component {
],
};
var duOptions = {
maintainAspectRatio: false,
tooltips: {
callbacks: {
label: (item) => `${item.yLabel} Mbit/s`,
@@ -90,6 +99,7 @@ export default class HistoryGraph extends Component {
],
};
var pingOptions = {
maintainAspectRatio: false,
tooltips: {
callbacks: {
label: (item) => `${item.yLabel} ms`,
@@ -149,6 +159,96 @@ export default class HistoryGraph extends Component {
})
}
getFailure = (days) => {
var url = 'api/speedtest/fail/' + days;
Axios.get(url)
.then((resp) => {
var failData = {
labels: [],
datasets:[
{
data: [],
label: 'Failure',
borderColor: "#E74C3C",
fill: false,
},
],
};
var failOptions = {
maintainAspectRatio: false,
tooltips: {
callbacks: {
label: (item) => `${item.yLabel} %`,
},
},
title: {
display: false,
text: 'Ping results for the last ' + days + ' days',
},
scales: {
xAxes: [{
display: false,
scaleLabel: {
display: true,
labelString: 'DateTime'
}
}],
},
elements: {
point:{
radius: 0,
hitRadius: 8
}
}
}
resp.data.data.forEach(e => {
var date = new Date(e.date);
var fail = {
t: date,
y: e.rate
};
failData.datasets[0].data.push(fail);
failData.labels.push(date.getFullYear() + '/' + ('0' + (date.getMonth() + 1)).slice(-2) + '/' + ('0' + date.getDay()).slice(-2));
});
this.setState({
failData: failData,
failOptions: failOptions
});
})
.catch((err) => {
console.log(err);
})
}
getData = (days = this.state.days) => {
Axios.get('api/settings/config')
.then((resp) => {
var data = resp.data.graphs;
this.setState({
graph_ul_dl_enabled: Boolean(Number(data.download_upload_graph_enabled.value)),
graph_ul_dl_width: data.download_upload_graph_width.value,
graph_ping_enabled: Boolean(Number(data.ping_graph_enabled.value)),
graph_ping_width: data.ping_graph_width.value,
graph_failure_enabled: Boolean(Number(data.failure_graph_enabled.value)),
graph_failure_width: data.failure_graph_width.value,
});
if(this.state.graph_ul_dl_enabled || this.state.graph_ping_enabled) {
this.getDLULPing(days);
}
if(this.state.graph_failure_enabled) {
this.getFailure(days);
}
})
.catch((err) => {
console.log('Couldn\'t get the site config');
console.log(err);
})
}
updateDays = (e) => {
var days = e.target.value;
if(days) {
@@ -169,8 +269,39 @@ export default class HistoryGraph extends Component {
var duOptions = this.state.duOptions;
var pingData = this.state.pingData;
var pingOptions = this.state.pingOptions;
var failData = this.state.failData;
var failOptions = this.state.failOptions;
var days = this.state.days;
var graph_ul_dl_enabled = this.state.graph_ul_dl_enabled;
var graph_ul_dl_width = this.state.graph_ul_dl_width;
var graph_ping_enabled = this.state.graph_ping_enabled;
var graph_ping_width = this.state.graph_ping_width;
var graph_failure_enabled = this.state.graph_failure_enabled;
var graph_failure_width = this.state.graph_failure_width;
var dlClasses = 'my-2 home-graph ';
var pingClasses = 'my-2 home-graph ';
var failureClasses = 'my-2 home-graph ';
if(graph_ul_dl_enabled == true) {
//
} else {
dlClasses += 'd-none ';
}
if(graph_ping_enabled == true) {
//
} else {
pingClasses += 'd-none ';
}
if(graph_failure_enabled == true) {
//
} else {
failureClasses += 'd-none ';
}
if(loading) {
return (
<div>
@@ -180,31 +311,43 @@ export default class HistoryGraph extends Component {
} else {
return (
<Container className="mb-4 mt-1" fluid>
<Row>
<Col
lg={{ span: 6 }}
md={{ span: 12 }}
lg={{ span: graph_ul_dl_width }}
md={{ span: graph_ul_dl_width }}
sm={{ span: 12 }}
xs={{ span: 12 }}
className="my-2"
className={dlClasses}
>
<Card className="shadow-sm">
<Card.Body>
<Line data={duData} options={duOptions} />
<Line data={duData} options={duOptions} height={440} />
</Card.Body>
</Card>
</Col>
<Col
lg={{ span: 6 }}
md={{ span: 12 }}
lg={{ span: graph_ping_width }}
md={{ span: graph_ping_width }}
sm={{ span: 12 }}
xs={{ span: 12 }}
className="my-2"
className={pingClasses}
>
<Card className="shadow-sm">
<Card.Body>
<Line data={pingData} options={pingOptions} />
<Line data={pingData} options={pingOptions} height={440} />
</Card.Body>
</Card>
</Col>
<Col
lg={{ span: graph_failure_width }}
md={{ span: graph_failure_width }}
sm={{ span: 12 }}
xs={{ span: 12 }}
className={failureClasses}
>
<Card className="shadow-sm">
<Card.Body>
<Line data={failData} options={pingOptions} height={440} />
</Card.Body>
</Card>
</Col>
@@ -217,7 +360,6 @@ export default class HistoryGraph extends Component {
<Form.Control id="duDaysInput" className="d-inline-block mx-2" defaultValue={days} onInput={this.updateDays}></Form.Control>
<h4 className="d-inline mb-0">days</h4>
</div>
{/* <p className="text-muted">This data refreshes every 10 seconds</p> */}
</div>
</Col>
</Row>

View File

@@ -112,6 +112,7 @@ export default class LatestResults extends Component {
value={parseFloat(data.data.ping).toFixed(1)}
avg={parseFloat(data.average.ping).toFixed(1)}
max={parseFloat(data.max.ping).toFixed(1)}
failed={data.data.failed}
unit="ms"
icon="ping"
/>
@@ -127,6 +128,7 @@ export default class LatestResults extends Component {
value={parseFloat(data.data.download).toFixed(1)}
avg={parseFloat(data.average.download).toFixed(1)}
max={parseFloat(data.max.download).toFixed(1)}
failed={data.data.failed}
unit="Mbit/s"
icon="dl"
/>
@@ -142,6 +144,7 @@ export default class LatestResults extends Component {
value={parseFloat(data.data.upload).toFixed(1)}
avg={parseFloat(data.average.upload).toFixed(1)}
max={parseFloat(data.max.upload).toFixed(1)}
failed={data.data.failed}
unit="Mbit/s"
icon="ul"
/>

View File

@@ -29,33 +29,49 @@ export default class TableRow extends Component {
var e = this.state.data;
var show = this.state.show;
return (
<tr>
<td>{e.id}</td>
<td>{new Date(e.created_at).toLocaleString()}</td>
<td>{e.download}</td>
<td>{e.upload}</td>
<td>{e.ping}</td>
{e.server_host != null ?
<td>
<span onClick={this.toggleShow} className="ti-arrow-top-right mouse"></span>
<Modal show={show} onHide={this.toggleShow}>
<Modal.Header>
<Modal.Title>More info</Modal.Title>
</Modal.Header>
<Modal.Body className="text-center">
<p>Server ID: {e.server_id}</p>
<p>Name: {e.server_name}</p>
<p>Host: {e.server_host}</p>
<a href={e.url} target="_blank" rel="noopener noreferer">Speedtest.net</a>
</Modal.Body>
</Modal>
</td>
:
if(e.failed != true) {
return (
<tr>
<td>{e.id}</td>
<td>{new Date(e.created_at).toLocaleString()}</td>
<td>{e.download}</td>
<td>{e.upload}</td>
<td>{e.ping}</td>
{e.server_host != null ?
<td>
<span onClick={this.toggleShow} className="ti-arrow-top-right mouse"></span>
<Modal show={show} onHide={this.toggleShow}>
<Modal.Header>
<Modal.Title>More info</Modal.Title>
</Modal.Header>
<Modal.Body className="text-center">
<p>Server ID: {e.server_id}</p>
<p>Name: {e.server_name}</p>
<p>Host: {e.server_host}</p>
<p>URL: <a href={e.url} target="_blank" rel="noopener noreferer">Speedtest.net</a></p>
{e.scheduled != undefined &&
<p>Type: {e.scheduled == true ? 'scheduled' : 'manual'}</p>
}
</Modal.Body>
</Modal>
</td>
:
<td></td>
}
</tr>
);
} else {
return (
<tr>
<td>{e.id}</td>
<td>{new Date(e.created_at).toLocaleString()}</td>
<td><span className="ti-close text-danger"></span></td>
<td><span className="ti-close text-danger"></span></td>
<td><span className="ti-close text-danger"></span></td>
<td></td>
}
</tr>
);
</tr>
);
}
}
}

View File

@@ -12,7 +12,8 @@ export default class Widget extends Component {
unit: this.props.unit,
icon: this.props.icon,
avg: this.props.avg,
max: this.props.max
max: this.props.max,
failed: this.props.failed,
}
}
@@ -36,6 +37,7 @@ export default class Widget extends Component {
var icon = this.state.icon;
var max = this.state.max;
var avg = this.state.avg;
var failed = Boolean(Number(this.state.failed));
switch(icon) {
case 'ping':
@@ -60,7 +62,7 @@ export default class Widget extends Component {
</div>
<div className="text-truncate">
<h3 className="d-inline">{value}</h3>
<h3 className="d-inline">{(!failed) ? value : <span className="ti-close text-danger"></span> }</h3>
<p className="d-inline ml-2">{unit} (current)</p>
</div>
<div className="text-muted text-truncate">

View File

@@ -12,7 +12,8 @@ export default class SettingWithModal extends Component {
title: this.props.title,
description: this.props.description,
settings: this.props.settings,
show: false
show: false,
autoClose: this.props.autoClose
}
}
@@ -26,11 +27,13 @@ export default class SettingWithModal extends Component {
var settings = this.state.settings;
settings.forEach(e => {
var res = {
name: e.obj.name,
value: e.obj.value
};
data.push(res);
if(e.type !== 'button-get') {
var res = {
name: e.obj.name,
value: e.obj.value
};
data.push(res);
}
});
data = {
@@ -40,7 +43,9 @@ export default class SettingWithModal extends Component {
Axios.post(url, data)
.then((resp) => {
toast.success(this.state.title + ' updated');
this.toggleShow();
if(this.state.autoClose) {
this.toggleShow();
}
})
.catch((err) => {
if(err.response.status == 422) {
@@ -111,33 +116,137 @@ export default class SettingWithModal extends Component {
var name = e.obj.name.split('_');
name[0] = this.ucfirst(name[0]);
name = name.join(' ');
if(e.obj.description == null || e.obj.description == '') {
var sm = { span: 12 };
var md = { span: 12 };
} else {
var sm = { span: 12 };
var md = { span: 6 };
}
var readonly = false;
if(window.config.editable[e.obj.name] == false) {
readonly = true;
}
if(e.type == 'checkbox') {
return (
<Row key={e.obj.id} className="d-flex align-items-center">
<Col md={{ span: 6 }} sm={{ span: 12 }}>
<Col md={md} sm={sm}>
<Form.Group controlId={e.obj.name}>
<Form.Check type="checkbox" label={name} defaultChecked={e.obj.value} onInput={this.updateValue} />
{readonly ?
<>
<Form.Check type="checkbox" disabled label={name} defaultChecked={Boolean(Number(e.obj.value))} onInput={this.updateValue} />
<Form.Text className="text-muted">This setting is defined as an env variable and is not editable.</Form.Text>
</>
:
<Form.Check type="checkbox" label={name} defaultChecked={Boolean(Number(e.obj.value))} onInput={this.updateValue} />
}
</Form.Group>
</Col>
<Col md={{ span: 6 }} sm={{ span: 12 }}>
<p>{e.obj.description}</p>
</Col>
{e.description == null &&
<Col md={md} sm={sm}>
<p>{e.obj.description}</p>
</Col>
}
</Row>
);
} else if(e.type == 'number') {
return (
<Row key={e.obj.id}>
<Col md={{ span: 6 }} sm={{ span: 12 }}>
<Col md={md} sm={sm}>
<Form.Group controlId={e.obj.name}>
<Form.Label>{name}</Form.Label>
<Form.Control type="number" min={e.min} max={e.max} defaultValue={e.obj.value} onInput={this.updateValue} />
{readonly ?
<>
<Form.Control type="number" disabled min={e.min} max={e.max} defaultValue={e.obj.value} onInput={this.updateValue} />
<Form.Text className="text-muted">This setting is defined as an env variable and is not editable.</Form.Text>
</>
:
<Form.Control type="number" min={e.min} max={e.max} defaultValue={e.obj.value} onInput={this.updateValue} />
}
</Form.Group>
</Col>
<Col md={{ span: 6 }} sm={{ span: 12 }}>
<p>{e.obj.description}</p>
</Col>
{e.description == null &&
<Col md={md} sm={sm}>
<p>{e.obj.description}</p>
</Col>
}
</Row>
);
} else if(e.type == 'text') {
return (
<Row key={e.obj.id}>
<Col md={md} sm={sm}>
<Form.Group controlId={e.obj.name}>
<Form.Label>{name}</Form.Label>
{readonly ?
<>
<Form.Control type="text" disabled defaultValue={e.obj.value} onInput={this.updateValue} />
<Form.Text className="text-muted">This setting is defined as an env variable and is not editable.</Form.Text>
</>
:
<Form.Control type="text" defaultValue={e.obj.value} onInput={this.updateValue} />
}
</Form.Group>
</Col>
{e.description == null &&
<Col md={md} sm={sm}>
<p>{e.obj.description}</p>
</Col>
}
</Row>
);
} else if(e.type == 'select') {
return (
<Row key={e.obj.id}>
<Col md={md} sm={sm}>
<Form.Group controlId={e.obj.name}>
<Form.Label>{name}</Form.Label>
{readonly ?
<>
<Form.Control as="select" disabled defaultValue={e.obj.value} onInput={this.updateValue}>
{e.options.map((e,i) => {
return (
<option key={i} value={e.value}>{e.name}</option>
)
})}
</Form.Control>
<Form.Text className="text-muted">This setting is defined as an env variable and is not editable.</Form.Text>
</>
:
<Form.Control as="select" defaultValue={e.obj.value} onInput={this.updateValue}>
{e.options.map((e,i) => {
return (
<option key={i} value={e.value}>{e.name}</option>
)
})}
</Form.Control>
}
</Form.Group>
</Col>
{e.description == null &&
<Col md={md} sm={sm}>
<p>{e.obj.description}</p>
</Col>
}
</Row>
)
} else if(e.type == 'button-get') {
return (
<Row key={e.obj.id}>
<Col md={md} sm={sm}>
<p>{name}</p>
<Button onClick={() => { Axios.get(e.url) }} >{name}</Button>
</Col>
{e.description == null &&
<Col md={md} sm={sm}>
<p>{e.obj.description}</p>
</Col>
}
</Row>
)
}
})}
<Button variant="primary" type="submit" onClick={this.update} >Save</Button>

View File

@@ -61,7 +61,86 @@ export default class Settings extends Component {
<Setting name={e.server.name} value={e.server.value} description={e.server.description} />
</Col>
<Col lg={{ span: 4 }} md={{ span: 6 }} sm={{ span: 12 }}>
<SettingWithModal title="Notification settings" description="Control which types of notifications the server sends." settings={[
<SettingWithModal title="Graph settings" description="Control settings for the graphs." autoClose={true} settings={[
{
obj: e.download_upload_graph_enabled,
type: 'checkbox'
},
{
obj: e.download_upload_graph_width,
type: 'select',
options: [
{
name: 'Full-width',
'value': 12
},
{
name: 'Half-width',
'value': 6
}
],
},
{
obj: e.ping_graph_enabled,
type: 'checkbox'
},
{
obj: e.ping_graph_width,
type: 'select',
options: [
{
name: 'Full-width',
'value': 12
},
{
name: 'Half-width',
'value': 6
}
],
},
{
obj: e.failure_graph_enabled,
type: 'checkbox'
},
{
obj: e.failure_graph_width,
type: 'select',
options: [
{
name: 'Full-width',
'value': 12
},
{
name: 'Half-width',
'value': 6
}
],
}
]} />
</Col>
<Col lg={{ span: 4 }} md={{ span: 6 }} sm={{ span: 12 }}>
<SettingWithModal title="Notification settings" description="Control which types of notifications the server sends." autoClose={false} settings={[
{
obj: e.slack_webhook,
type: 'text'
},
{
obj: e.telegram_bot_token,
type: 'text'
},
{
obj: e.telegram_chat_id,
type: 'text'
},
{
obj: {
id: (Math.floor(Math.random() * 10000) + 1),
name: "Test notifications",
description: "After saving your updated notification settings, use this to check your settings are correct."
},
type: 'button-get',
url: 'api/settings/test-notification'
},
{
obj: e.speedtest_notifications,
type: 'checkbox'

View File

@@ -25,6 +25,8 @@ Route::group([
->name('speedtest.latest');
Route::get('time/{time}', 'SpeedtestController@time')
->name('speedtest.time');
Route::get('fail/{time}', 'SpeedtestController@fail')
->name('speedtest.fail');
Route::get('run', 'SpeedtestController@run')
->name('speedtest.run');
});
@@ -60,6 +62,8 @@ Route::group([
], function () {
Route::get('/config', 'SettingsController@config')
->name('settings.config');
Route::get('/test-notification', 'SettingsController@testNotification')
->name('settings.test_notification');
Route::get('/', 'SettingsController@index')
->name('settings.index');
Route::put('/', 'SettingsController@store')