diff --git a/README.md b/README.md index d107d4d1..e75e568f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Speedtest Tracker -[](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits)  [](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE) +[](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits)  [](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 [Ookla's Speedtest cli](https://www.speedtest.net/apps/cli) to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results. diff --git a/conf/site/README.md b/conf/site/README.md index 523f2bf5..1b738472 100644 --- a/conf/site/README.md +++ b/conf/site/README.md @@ -1,6 +1,6 @@ # Speedtest Tracker -[](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits)  [](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE) +[](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits)  [](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. diff --git a/conf/site/app/Actions/GetFailedSpeedtestData.php b/conf/site/app/Actions/GetFailedSpeedtestData.php new file mode 100644 index 00000000..7ffd586a --- /dev/null +++ b/conf/site/app/Actions/GetFailedSpeedtestData.php @@ -0,0 +1,48 @@ +addDays(1); + + return Cache::remember('failure-rate-' . $days, $ttl, function () use ($days) { + $range = [ + Carbon::today() + ]; + for ($i = 0; $i < ($days - 1); $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']; + + array_push($rate, [ + 'date' => $day->toDateString(), + 'success' => $success, + 'failure' => $fail, + ]); + } + + return array_reverse($rate); + }); + } +} diff --git a/conf/site/app/Actions/GetLatestSpeedtestData.php b/conf/site/app/Actions/GetLatestSpeedtestData.php new file mode 100644 index 00000000..ec6b8044 --- /dev/null +++ b/conf/site/app/Actions/GetLatestSpeedtestData.php @@ -0,0 +1,52 @@ + $data, + ]; + + if (SettingsHelper::get('show_average')) { + $avg = Speedtest::select(DB::raw('AVG(ping) as ping, AVG(download) as download, AVG(upload) as upload')) + ->where('failed', false) + ->first() + ->toArray(); + $response['average'] = $avg; + } + + if (SettingsHelper::get('show_max')) { + $max = Speedtest::select(DB::raw('MAX(ping) as ping, MAX(download) as download, MAX(upload) as upload')) + ->where('failed', false) + ->first() + ->toArray(); + $response['maximum'] = $max; + } + + if (SettingsHelper::get('show_min')) { + $min = Speedtest::select(DB::raw('MIN(ping) as ping, MIN(download) as download, MIN(upload) as upload')) + ->where('failed', false) + ->first() + ->toArray(); + $response['minimum'] = $min; + } + + return $response; + } +} diff --git a/conf/site/app/Actions/GetSpeedtestTimeData.php b/conf/site/app/Actions/GetSpeedtestTimeData.php new file mode 100644 index 00000000..105baa00 --- /dev/null +++ b/conf/site/app/Actions/GetSpeedtestTimeData.php @@ -0,0 +1,37 @@ +addDays(1); + + return Cache::remember('speedtest-days-' . $days, $ttl, function () use ($days) { + $showFailed = (bool)SettingsHelper::get('show_failed_tests_on_graph')->value; + + if ($showFailed === true) { + return Speedtest::where('created_at', '>=', Carbon::now()->subDays($days)) + ->orderBy('created_at', 'asc') + ->get(); + } + + return Speedtest::where('created_at', '>=', Carbon::now()->subDays($days)) + ->where('failed', false) + ->orderBy('created_at', 'asc') + ->get(); + }); + } +} diff --git a/conf/site/app/Actions/QueueSpeedtest.php b/conf/site/app/Actions/QueueSpeedtest.php new file mode 100644 index 00000000..e52038e8 --- /dev/null +++ b/conf/site/app/Actions/QueueSpeedtest.php @@ -0,0 +1,35 @@ +speedtestProvider = $speedtestProvider; + } + + /** + * Run the action. + * + * @return mixed + */ + public function run() + { + SettingsHelper::loadIntegrationConfig(); + + SpeedtestJob::dispatch(false, config('integrations'), $this->speedtestProvider); + } +} diff --git a/conf/site/app/Bin/.gitignore b/conf/site/app/Bin/.gitignore index 17febb8c..86b266cd 100644 --- a/conf/site/app/Bin/.gitignore +++ b/conf/site/app/Bin/.gitignore @@ -1 +1 @@ -speedtest +speedtest \ No newline at end of file diff --git a/conf/site/app/Casts/CommaSeparatedArrayCast.php b/conf/site/app/Casts/CommaSeparatedArrayCast.php new file mode 100644 index 00000000..b29ee17c --- /dev/null +++ b/conf/site/app/Casts/CommaSeparatedArrayCast.php @@ -0,0 +1,52 @@ +name, $this->shouldCast)) { + return $value; + } + + return explode(',', $value); + } + + /** + * Prepare the given value for storage. + * + * @param \Illuminate\Database\Eloquent\Model $model + * @param string $key + * @param mixed $value + * @param array $attributes + * @return mixed + */ + public function set($model, $key, $value, $attributes) + { + if (!in_array($model->name, $this->shouldCast)) { + return $value; + } + + return implode(',', $value); + } +} diff --git a/conf/site/app/Console/Commands/AcceptEULACommand.php b/conf/site/app/Console/Commands/AcceptEULACommand.php index 1a1f5747..1aac5a2f 100644 --- a/conf/site/app/Console/Commands/AcceptEULACommand.php +++ b/conf/site/app/Console/Commands/AcceptEULACommand.php @@ -38,6 +38,7 @@ class AcceptEULACommand extends Command */ public function handle() { - shell_exec(config('speedtest.home') . ' && ' . app_path() . '/Bin/speedtest --accept-license --accept-gdpr'); + $this->info('Acceping EULA'); + shell_exec(config('speedtest.home') . ' && timeout 3s ' . app_path() . '/Bin/speedtest --accept-license --accept-gdpr'); } } diff --git a/conf/site/app/Console/Commands/SpeedtestCommand.php b/conf/site/app/Console/Commands/SpeedtestCommand.php index 27b7fa9a..11f06553 100644 --- a/conf/site/app/Console/Commands/SpeedtestCommand.php +++ b/conf/site/app/Console/Commands/SpeedtestCommand.php @@ -3,6 +3,7 @@ namespace App\Console\Commands; use App\Helpers\SpeedtestHelper; +use App\Interfaces\SpeedtestProvider; use Illuminate\Console\Command; class SpeedtestCommand extends Command @@ -21,13 +22,17 @@ class SpeedtestCommand extends Command */ protected $description = 'Performs a new speedtest'; + private SpeedtestProvider $speedtestProvider; + /** * Create a new command instance. * * @return void */ - public function __construct() + public function __construct(SpeedtestProvider $speedtestProvider) { + $this->speedtestProvider = $speedtestProvider; + parent::__construct(); } @@ -40,14 +45,14 @@ class SpeedtestCommand extends Command { $this->info('Running speedtest, this might take a while...'); - $results = SpeedtestHelper::runSpeedtest(false, false); + $results = $this->speedtestProvider->run(false, false); - if(!is_object($results)) { + if (!is_object($results)) { $this->error('Something went wrong running the speedtest.'); exit(); } - if(property_exists($results, 'ping') && property_exists($results, 'download') && property_exists($results, 'upload')) { + if (property_exists($results, 'ping') && property_exists($results, 'download') && property_exists($results, 'upload')) { $this->error('Something went wrong running the speedtest.'); exit(); } diff --git a/conf/site/app/Console/Commands/SpeedtestLatestCommand.php b/conf/site/app/Console/Commands/SpeedtestLatestCommand.php index e76971d8..f78bf4be 100644 --- a/conf/site/app/Console/Commands/SpeedtestLatestCommand.php +++ b/conf/site/app/Console/Commands/SpeedtestLatestCommand.php @@ -3,6 +3,7 @@ namespace App\Console\Commands; use App\Helpers\SpeedtestHelper; +use App\Interfaces\SpeedtestProvider; use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; @@ -22,13 +23,17 @@ class SpeedtestLatestCommand extends Command */ protected $description = 'Returns the latest speedtest result'; + private SpeedtestProvider $speedtestProvider; + /** * Create a new command instance. * * @return void */ - public function __construct() + public function __construct(SpeedtestProvider $speedtestProvider) { + $this->speedtestProvider = $speedtestProvider; + parent::__construct(); } @@ -41,8 +46,8 @@ class SpeedtestLatestCommand extends Command { $latest = SpeedtestHelper::latest(); - if($latest) { - if($latest->scheduled) { + if ($latest) { + if ($latest->scheduled) { $extra = '(scheduled)'; } else { $extra = '(manual)'; @@ -50,7 +55,7 @@ class SpeedtestLatestCommand extends Command $this->info('Last speedtest run at: ' . $latest->created_at . ' ' . $extra); - if($latest->failed) { + if ($latest->failed) { $this->error('Speedtest failed'); } else { $this->info('Ping: ' . $latest->ping . ' ms'); @@ -62,7 +67,7 @@ class SpeedtestLatestCommand extends Command $this->info('Running speedtest, this might take a while...'); - $results = SpeedtestHelper::runSpeedtest(); + $results = $this->speedtestProvider->run(); $this->info('Ping: ' . $results->ping . ' ms'); $this->info('Download: ' . $results->download . ' Mbit/s'); diff --git a/conf/site/app/Console/Kernel.php b/conf/site/app/Console/Kernel.php index 44157a7a..9987e345 100644 --- a/conf/site/app/Console/Kernel.php +++ b/conf/site/app/Console/Kernel.php @@ -5,6 +5,7 @@ namespace App\Console; use App\Events\SpeedtestOverviewEvent; use App\Helpers\SettingsHelper; use App\Helpers\SpeedtestHelper; +use App\Interfaces\SpeedtestProvider; use App\Jobs\SpeedtestJob; use Illuminate\Console\Scheduling\Schedule; use Illuminate\Foundation\Console\Kernel as ConsoleKernel; @@ -29,9 +30,17 @@ class Kernel extends ConsoleKernel protected function schedule(Schedule $schedule) { if ((bool)SettingsHelper::get('schedule_enabled')->value) { - $schedule->job(new SpeedtestJob(true, config('integrations')))->cron(SettingsHelper::get('schedule')['value']); + $schedule->job( + new SpeedtestJob( + true, + config('integrations'), + app()->make(SpeedtestProvider::class) + ) + ) + ->cron(SettingsHelper::get('schedule')['value']) + ->timezone(env('TZ', 'UTC')); } - $schedule->command('speedtest:overview')->cron('0 ' . SettingsHelper::get('speedtest_overview_time')->value . ' * * *'); + $schedule->command('speedtest:overview')->cron('0 ' . SettingsHelper::get('speedtest_overview_time')->value . ' * * *')->timezone(env('TZ', 'UTC')); $schedule->command('speedtest:clear-sessions')->everyMinute(); } diff --git a/conf/site/app/Exceptions/SpeedtestFailureException.php b/conf/site/app/Exceptions/SpeedtestFailureException.php new file mode 100644 index 00000000..ca668814 --- /dev/null +++ b/conf/site/app/Exceptions/SpeedtestFailureException.php @@ -0,0 +1,10 @@ +first(); } else { $name = $name->keyBy('name'); return $name->all(); @@ -163,6 +164,10 @@ class SettingsHelper 'telegram_bot_token' => SettingsHelper::settingIsEditable('telegram_bot_token'), 'telegram_chat_id' => SettingsHelper::settingIsEditable('telegram_chat_id'), ], + 'tables' => [ + 'visible_columns' => SettingsHelper::get('visible_columns')->value, + 'hidden_columns' => SettingsHelper::get('hidden_columns')->value, + ], 'auth' => (bool)SettingsHelper::get('auth')->value ]; } diff --git a/conf/site/app/Helpers/SpeedtestHelper.php b/conf/site/app/Helpers/SpeedtestHelper.php index 10bf8628..b4bf3e10 100644 --- a/conf/site/app/Helpers/SpeedtestHelper.php +++ b/conf/site/app/Helpers/SpeedtestHelper.php @@ -3,6 +3,7 @@ namespace App\Helpers; use App\Speedtest; +use App\Utils\OoklaTester; use Carbon\Carbon; use Exception; use Henrywhitaker3\Healthchecks\Healthchecks; @@ -13,7 +14,8 @@ use Illuminate\Support\Facades\Log; use InvalidArgumentException; use JsonException; -class SpeedtestHelper { +class SpeedtestHelper +{ /** * Runs/processes speedtest output to created a Speedtest object @@ -21,79 +23,10 @@ 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|bool */ - public static function runSpeedtest($output = false, $scheduled = true) + public static function runSpeedtest() { - if($output === false) { - $output = SpeedtestHelper::output(); - } - - try { - $output = json_decode($output, true, 512, JSON_THROW_ON_ERROR); - - if(!SpeedtestHelper::checkOutputIsComplete($output)) { - $test = false; - } - - $test = Speedtest::create([ - 'ping' => $output['ping']['latency'], - 'download' => SpeedtestHelper::convert($output['download']['bandwidth']), - 'upload' => SpeedtestHelper::convert($output['upload']['bandwidth']), - 'server_id' => $output['server']['id'], - '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; - } - - if($test == false) { - Speedtest::create([ - 'ping' => 0, - 'upload' => 0, - 'download' => 0, - 'failed' => true, - 'scheduled' => $scheduled, - ]); - - return false; - } - - Cache::flush(); - - return $test; - } - - /** - * Gets the output of executing speedtest binary. - * - * @return boolean|string - */ - public static function output() - { - $server = SettingsHelper::get('server')['value']; - - $binPath = app_path() . DIRECTORY_SEPARATOR . 'Bin' . DIRECTORY_SEPARATOR . 'speedtest'; - $homePrefix = config('speedtest.home') . ' && '; - - if($server != '' && $server != false) { - $server = explode(',', $server); - $server = $server[array_rand($server)]; - if($server == false) { - Log::error('Speedtest server undefined'); - return false; - } - - return shell_exec($homePrefix . $binPath . ' -f json -s ' . $server); - } - - return shell_exec($homePrefix . $binPath . ' -f json'); + $tester = new OoklaTester(); + return $tester->run(); } /** @@ -105,10 +38,10 @@ 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(); + ->where('created_at', '>=', $t) + ->where('failed', false) + ->first() + ->toArray(); return $s; } @@ -119,8 +52,9 @@ class SpeedtestHelper { * @param int|float $bytes * @return int|float */ - public static function convert($bytes) { - return ( $bytes * 8 ) / 1000000; + public static function convert($bytes) + { + return ($bytes * 8) / 1000000; } /** @@ -132,7 +66,7 @@ class SpeedtestHelper { { $data = Speedtest::latest()->get(); - if($data->isEmpty()) { + if ($data->isEmpty()) { return false; } @@ -152,7 +86,7 @@ class SpeedtestHelper { $val = (float)$input[0]; $unit = explode('/', $input[1])[0]; - switch($unit) { + switch ($unit) { case 'Mbyte': $val = $val * 8; break; @@ -173,86 +107,6 @@ class SpeedtestHelper { ]; } - /** - * Checks that the speedtest JSON output is complete/valid - * - * @param array $output - * @return boolean - */ - public static function checkOutputIsComplete($output) - { - /** - * Array of indexes that must exist in $output - */ - $checks = [ - 'type' => 'result', - 'download' => [ 'bandwidth' => '*' ], - 'upload' => [ 'bandwidth' => '*' ], - 'ping' => [ 'latency' => '*' ], - 'server' => [ - 'id' => '*', - 'name' => '*', - 'host' => '*', - 'port' => '*' - ], - 'result' => [ - 'url' => '*' - ], - ]; - /** - * Array of indexes that must not exist - */ - $checkMissing = [ - 'type' => 'error' - ]; - - foreach($checks as $key => $value) { - if(!isset($output[$key])) { - return false; - } - } - - 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 - 1); $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']; - - array_push($rate, [ - 'date' => $day->toDateString(), - 'success' => $success, - 'failure' => $fail, - ]); - } - - return array_reverse($rate); - }); - - return $rate; - } - /** * Create a backup of the SQLite database * @@ -260,14 +114,14 @@ class SpeedtestHelper { */ public static function dbBackup() { - if(env('DB_CONNECTION') === 'sqlite') { - if(env('DB_DATABASE') !== null) { + if (env('DB_CONNECTION') === 'sqlite') { + if (env('DB_DATABASE') !== null) { $current = env('DB_DATABASE'); try { - if(File::copy($current, $current . '.bak')) { + if (File::copy($current, $current . '.bak')) { return true; } - }catch(Exception $e) { + } catch (Exception $e) { return false; } } @@ -289,8 +143,8 @@ class SpeedtestHelper { SpeedtestHelper::dbBackup(); - if(sizeof(Speedtest::whereNotNull('id')->get()) > 0) { - if(Speedtest::whereNotNull('id')->delete()) { + if (sizeof(Speedtest::whereNotNull('id')->get()) > 0) { + if (Speedtest::whereNotNull('id')->delete()) { return [ 'success' => true, ]; @@ -311,31 +165,31 @@ class SpeedtestHelper { */ public static function testIsLowerThanThreshold(String $type, Speedtest $test) { - if($type == 'percentage') { + if ($type == 'percentage') { $avg = Speedtest::select(DB::raw('AVG(ping) as ping, AVG(download) as download, AVG(upload) as upload')) - ->where('failed', false) - ->get() - ->toArray()[0]; + ->where('failed', false) + ->get() + ->toArray()[0]; $threshold = SettingsHelper::get('threshold_alert_percentage')->value; - if($threshold == '') { + if ($threshold == '') { return []; } $errors = []; - foreach($avg as $key => $value) { - if($key == 'ping') { - $threshold = (float)$value * (1 + ( $threshold / 100 )); + foreach ($avg as $key => $value) { + if ($key == 'ping') { + $threshold = (float)$value * (1 + ($threshold / 100)); - if($test->$key > $threshold) { + if ($test->$key > $threshold) { array_push($errors, $key); } - } else { - $threshold = (float)$value * (1 - ( $threshold / 100 )); + } else { + $threshold = (float)$value * (1 - ($threshold / 100)); - if($test->$key < $threshold) { + if ($test->$key < $threshold) { array_push($errors, $key); } } @@ -344,7 +198,7 @@ class SpeedtestHelper { return $errors; } - if($type == 'absolute') { + if ($type == 'absolute') { $thresholds = [ 'download' => SettingsHelper::get('threshold_alert_absolute_download')->value, 'upload' => SettingsHelper::get('threshold_alert_absolute_upload')->value, @@ -353,17 +207,17 @@ class SpeedtestHelper { $errors = []; - foreach($thresholds as $key => $value) { - if($value == '') { + foreach ($thresholds as $key => $value) { + if ($value == '') { continue; } - if($key == 'ping') { - if($test->$key > $value) { + if ($key == 'ping') { + if ($test->$key > $value) { array_push($errors, $key); } } else { - if($test->$key < $value) { + if ($test->$key < $value) { array_push($errors, $key); } } @@ -374,4 +228,42 @@ class SpeedtestHelper { throw new InvalidArgumentException(); } + + /** + * 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 - 1); $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']; + + array_push($rate, [ + 'date' => $day->toDateString(), + 'success' => $success, + 'failure' => $fail, + ]); + } + + return array_reverse($rate); + }); + + return $rate; + } } diff --git a/conf/site/app/Http/Controllers/HomepageDataController.php b/conf/site/app/Http/Controllers/HomepageDataController.php new file mode 100644 index 00000000..256fe189 --- /dev/null +++ b/conf/site/app/Http/Controllers/HomepageDataController.php @@ -0,0 +1,35 @@ + $days], + ['days' => ['required', 'numeric']], + ); + + if ($validator->fails()) { + return response()->json([ + 'method' => 'get speedtests in last x days', + 'error' => $validator->errors(), + ], 422); + } + + return [ + 'latest' => run(GetLatestSpeedtestData::class), + 'time' => run(GetSpeedtestTimeData::class), + 'fail' => run(GetFailedSpeedtestData::class), + 'config' => SettingsHelper::getConfig(), + ]; + } +} diff --git a/conf/site/app/Http/Controllers/SpeedtestController.php b/conf/site/app/Http/Controllers/SpeedtestController.php index ddb4b719..f4204ff0 100644 --- a/conf/site/app/Http/Controllers/SpeedtestController.php +++ b/conf/site/app/Http/Controllers/SpeedtestController.php @@ -2,6 +2,10 @@ namespace App\Http\Controllers; +use App\Actions\GetFailedSpeedtestData; +use App\Actions\GetLatestSpeedtestData; +use App\Actions\GetSpeedtestTimeData; +use App\Actions\QueueSpeedtest; use App\Helpers\SettingsHelper; use App\Helpers\SpeedtestHelper; use App\Jobs\SpeedtestJob; @@ -61,21 +65,7 @@ class SpeedtestController extends Controller ], 422); } - $ttl = Carbon::now()->addDays(1); - $data = Cache::remember('speedtest-days-' . $days, $ttl, function () use ($days) { - $showFailed = (bool)SettingsHelper::get('show_failed_tests_on_graph')->value; - - if ($showFailed === true) { - return Speedtest::where('created_at', '>=', Carbon::now()->subDays($days)) - ->orderBy('created_at', 'asc') - ->get(); - } - - return Speedtest::where('created_at', '>=', Carbon::now()->subDays($days)) - ->where('failed', false) - ->orderBy('created_at', 'asc') - ->get(); - }); + $data = run(GetSpeedtestTimeData::class, $days); return response()->json([ 'method' => 'get speedtests in last x days', @@ -105,7 +95,7 @@ class SpeedtestController extends Controller ], 422); } - $data = SpeedtestHelper::failureRate($days); + $data = run(GetFailedSpeedtestData::class, $days); return response()->json([ 'method' => 'get speedtests in last x days', @@ -121,39 +111,10 @@ class SpeedtestController extends Controller */ public function latest() { - $data = SpeedtestHelper::latest(); + $data = run(GetLatestSpeedtestData::class); - $response = [ - 'method' => 'get latest speedtest', - 'data' => $data, - ]; - - if (SettingsHelper::get('show_average')) { - $avg = Speedtest::select(DB::raw('AVG(ping) as ping, AVG(download) as download, AVG(upload) as upload')) - ->where('failed', false) - ->first() - ->toArray(); - $response['average'] = $avg; - } - - if (SettingsHelper::get('show_max')) { - $max = Speedtest::select(DB::raw('MAX(ping) as ping, MAX(download) as download, MAX(upload) as upload')) - ->where('failed', false) - ->first() - ->toArray(); - $response['maximum'] = $max; - } - - if (SettingsHelper::get('show_min')) { - $min = Speedtest::select(DB::raw('MIN(ping) as ping, MIN(download) as download, MIN(upload) as upload')) - ->where('failed', false) - ->first() - ->toArray(); - $response['minimum'] = $min; - } - - if ($data) { - return response()->json($response, 200); + if ($data['data']) { + return response()->json($data, 200); } else { return response()->json([ 'method' => 'get latest speedtest', @@ -170,8 +131,8 @@ class SpeedtestController extends Controller public function run() { try { - SettingsHelper::loadIntegrationConfig(); - $data = SpeedtestJob::dispatch(false, config('integrations')); + run(QueueSpeedtest::class); + return response()->json([ 'method' => 'run speedtest', 'data' => 'a new speedtest has been added to the queue' diff --git a/conf/site/app/Interfaces/SpeedtestProvider.php b/conf/site/app/Interfaces/SpeedtestProvider.php new file mode 100644 index 00000000..f0124d7f --- /dev/null +++ b/conf/site/app/Interfaces/SpeedtestProvider.php @@ -0,0 +1,11 @@ +scheduled = $scheduled; $this->config = $config; + $this->speedtestProvider = $speedtestProvider; } /** @@ -55,8 +60,8 @@ class SpeedtestJob implements ShouldQueue if ($this->config['healthchecks_enabled'] === true) { $this->healthcheck('start'); } - $output = SpeedtestHelper::output(); - $speedtest = SpeedtestHelper::runSpeedtest($output, $this->scheduled); + $output = $this->speedtestProvider->output(); + $speedtest = $this->speedtestProvider->run($output, $this->scheduled); if ($speedtest == false) { if ($this->config['healthchecks_enabled'] === true) { $this->healthcheck('fail'); diff --git a/conf/site/app/Providers/RouteServiceProvider.php b/conf/site/app/Providers/RouteServiceProvider.php index 916cdf51..7133239d 100644 --- a/conf/site/app/Providers/RouteServiceProvider.php +++ b/conf/site/app/Providers/RouteServiceProvider.php @@ -15,7 +15,7 @@ class RouteServiceProvider extends ServiceProvider * * @var string */ - protected $namespace = 'App\Http\Controllers'; + protected $namespace = null; /** * The path to the "home" route for your application. diff --git a/conf/site/app/Providers/SpeedtestServiceProvider.php b/conf/site/app/Providers/SpeedtestServiceProvider.php new file mode 100644 index 00000000..ce907155 --- /dev/null +++ b/conf/site/app/Providers/SpeedtestServiceProvider.php @@ -0,0 +1,28 @@ +app->singleton( + SpeedtestProvider::class, + function () { + return new OoklaTester(); + } + ); + } +} diff --git a/conf/site/app/Setting.php b/conf/site/app/Setting.php index 78c78765..075fa304 100644 --- a/conf/site/app/Setting.php +++ b/conf/site/app/Setting.php @@ -2,6 +2,7 @@ namespace App; +use App\Casts\CommaSeparatedArrayCast; use App\Helpers\SettingsHelper; use Illuminate\Database\Eloquent\Model; @@ -17,4 +18,8 @@ class Setting extends Model ]; protected $table = 'settings'; + + protected $casts = [ + 'value' => CommaSeparatedArrayCast::class, + ]; } diff --git a/conf/site/app/Utils/OoklaTester.php b/conf/site/app/Utils/OoklaTester.php new file mode 100644 index 00000000..74e617da --- /dev/null +++ b/conf/site/app/Utils/OoklaTester.php @@ -0,0 +1,128 @@ +output(); + } + + try { + $output = json_decode($output, true, 512, JSON_THROW_ON_ERROR); + + if (!$this->isOutputComplete($output)) { + $test = false; + } + + $test = Speedtest::create([ + 'ping' => $output['ping']['latency'], + 'download' => SpeedtestHelper::convert($output['download']['bandwidth']), + 'upload' => SpeedtestHelper::convert($output['upload']['bandwidth']), + 'server_id' => $output['server']['id'], + '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; + } + + if ($test == false) { + Speedtest::create([ + 'ping' => 0, + 'upload' => 0, + 'download' => 0, + 'failed' => true, + 'scheduled' => $scheduled, + ]); + + throw new SpeedtestFailureException(json_encode($output)); + } + + Cache::flush(); + + return $test; + } + + public function output() + { + $server = SettingsHelper::get('server')['value']; + + $binPath = app_path() . DIRECTORY_SEPARATOR . 'Bin' . DIRECTORY_SEPARATOR . 'speedtest'; + $homePrefix = config('speedtest.home') . ' && '; + + if ($server != '' && $server != false) { + $server = explode(',', $server); + $server = $server[array_rand($server)]; + if ($server == false) { + Log::error('Speedtest server undefined'); + return false; + } + + return shell_exec($homePrefix . $binPath . ' -f json -s ' . $server); + } + + return shell_exec($homePrefix . $binPath . ' -f json'); + } + + /** + * Checks that the speedtest JSON output is complete/valid + * + * @param array $output + * @return boolean + */ + public static function isOutputComplete($output) + { + /** + * Array of indexes that must exist in $output + */ + $checks = [ + 'type' => 'result', + 'download' => ['bandwidth' => '*'], + 'upload' => ['bandwidth' => '*'], + 'ping' => ['latency' => '*'], + 'server' => [ + 'id' => '*', + 'name' => '*', + 'host' => '*', + 'port' => '*' + ], + 'result' => [ + 'url' => '*' + ], + ]; + /** + * Array of indexes that must not exist + */ + $checkMissing = [ + 'type' => 'error' + ]; + + foreach ($checks as $key => $value) { + if (!isset($output[$key])) { + return false; + } + } + + return true; + } +} diff --git a/conf/site/changelog.json b/conf/site/changelog.json index b48b6c6f..7302b2c9 100644 --- a/conf/site/changelog.json +++ b/conf/site/changelog.json @@ -1,4 +1,28 @@ { + "1.11.1": [ + { + "description": "Add option to show/hide columns in the all tests table.", + "link": "" + }, + { + "description": "Add option to delete failed tests.", + "link": "" + } + ], + "1.11.0": [ + { + "description": "Upgrade to Laravel 8.", + "link": "" + }, + { + "description": "Refactor loads of back-end stuff.", + "link": "" + }, + { + "description": "Refactor front-end stuff.", + "link": "" + } + ], "1.10.4": [ { "description": "Updated dependencies.", diff --git a/conf/site/composer.json b/conf/site/composer.json index 9b12f87f..8f1c9eca 100644 --- a/conf/site/composer.json +++ b/conf/site/composer.json @@ -10,26 +10,29 @@ "require": { "php": "^7.2.5", "doctrine/dbal": "^2.10", - "dragonmantank/cron-expression": "^2", + "dragonmantank/cron-expression": "^3", "fideloper/proxy": "^4.2", "fruitcake/laravel-cors": "^2.0", - "guzzlehttp/guzzle": "^7.0", + "guzzlehttp/guzzle": "^7.0.1", "henrywhitaker3/healthchecks-io": "^1.0", + "henrywhitaker3/laravel-actions": "^1.0", "laravel-notification-channels/telegram": "^0.5.0", - "laravel/framework": "^7.0", + "laravel/framework": "^8.0", "laravel/slack-notification-channel": "^2.0", "laravel/tinker": "^2.0", - "laravel/ui": "^2.0", + "laravel/ui": "^3.0", "tymon/jwt-auth": "^1.0" }, "require-dev": { "barryvdh/laravel-ide-helper": "^2.8", - "facade/ignition": "^2.0", + "brianium/paratest": "^6.2", + "facade/ignition": "^2.3.6", "fzaninotto/faker": "^1.9.1", + "itsgoingd/clockwork": "^5.0", "mockery/mockery": "^1.3.1", - "phpunit/phpunit": "^9.5", + "nunomaduro/collision": "^5.3", "nunomaduro/larastan": "^0.7.0", - "nunomaduro/collision": "^5.3" + "phpunit/phpunit": "^9.5" }, "config": { "optimize-autoloader": true, diff --git a/conf/site/composer.lock b/conf/site/composer.lock index cd5b177b..bf6ff109 100644 --- a/conf/site/composer.lock +++ b/conf/site/composer.lock @@ -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": "19bd0375e29ab5c67bacc1ea15b2e4e8", + "content-hash": "102adc5121f97ad3ad15009f410fb8fa", "packages": [ { "name": "asm89/stack-cors", @@ -637,30 +637,32 @@ }, { "name": "dragonmantank/cron-expression", - "version": "v2.3.1", + "version": "v3.1.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "65b2d8ee1f10915efb3b55597da3404f096acba2" + "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/65b2d8ee1f10915efb3b55597da3404f096acba2", - "reference": "65b2d8ee1f10915efb3b55597da3404f096acba2", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", + "reference": "7a8c6e56ab3ffcc538d05e8155bb42269abf1a0c", "shasum": "" }, "require": { - "php": "^7.0|^8.0" + "php": "^7.2|^8.0", + "webmozart/assert": "^1.7.0" + }, + "replace": { + "mtdowling/cron-expression": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^6.4|^7.0|^8.0|^9.0" + "phpstan/extension-installer": "^1.0", + "phpstan/phpstan": "^0.12", + "phpstan/phpstan-webmozart-assert": "^0.12.7", + "phpunit/phpunit": "^7.0|^8.0|^9.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3-dev" - } - }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -671,11 +673,6 @@ "MIT" ], "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - }, { "name": "Chris Tankersley", "email": "chris@ctankersley.com", @@ -689,7 +686,7 @@ ], "support": { "issues": "https://github.com/dragonmantank/cron-expression/issues", - "source": "https://github.com/dragonmantank/cron-expression/tree/v2.3.1" + "source": "https://github.com/dragonmantank/cron-expression/tree/v3.1.0" }, "funding": [ { @@ -697,7 +694,7 @@ "type": "github" } ], - "time": "2020-10-13T00:52:37+00:00" + "time": "2020-11-24T19:55:57+00:00" }, { "name": "egulias/email-validator", @@ -902,6 +899,72 @@ ], "time": "2020-10-22T13:57:20+00:00" }, + { + "name": "graham-campbell/result-type", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/GrahamCampbell/Result-Type.git", + "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/7e279d2cd5d7fbb156ce46daada972355cea27bb", + "reference": "7e279d2cd5d7fbb156ce46daada972355cea27bb", + "shasum": "" + }, + "require": { + "php": "^7.0|^8.0", + "phpoption/phpoption": "^1.7.3" + }, + "require-dev": { + "phpunit/phpunit": "^6.5|^7.5|^8.5|^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "GrahamCampbell\\ResultType\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "graham@alt-three.com" + } + ], + "description": "An Implementation Of The Result Type", + "keywords": [ + "Graham Campbell", + "GrahamCampbell", + "Result Type", + "Result-Type", + "result" + ], + "support": { + "issues": "https://github.com/GrahamCampbell/Result-Type/issues", + "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.0.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type", + "type": "tidelift" + } + ], + "time": "2020-04-13T13:17:36+00:00" + }, { "name": "guzzlehttp/guzzle", "version": "7.2.0", @@ -1006,16 +1069,16 @@ }, { "name": "guzzlehttp/promises", - "version": "1.4.0", + "version": "1.4.1", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "60d379c243457e073cff02bc323a2a86cb355631" + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/60d379c243457e073cff02bc323a2a86cb355631", - "reference": "60d379c243457e073cff02bc323a2a86cb355631", + "url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d", + "reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d", "shasum": "" }, "require": { @@ -1055,9 +1118,9 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/1.4.0" + "source": "https://github.com/guzzle/promises/tree/1.4.1" }, - "time": "2020-09-30T07:37:28+00:00" + "time": "2021-03-07T09:25:29+00:00" }, { "name": "guzzlehttp/psr7", @@ -1189,6 +1252,63 @@ }, "time": "2020-08-21T23:17:42+00:00" }, + { + "name": "henrywhitaker3/laravel-actions", + "version": "v1.0.5", + "source": { + "type": "git", + "url": "https://github.com/henrywhitaker3/laravel-actions.git", + "reference": "2a8a7c0a0be7083c0c1fcbea0ba6a57220a17939" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/henrywhitaker3/laravel-actions/zipball/2a8a7c0a0be7083c0c1fcbea0ba6a57220a17939", + "reference": "2a8a7c0a0be7083c0c1fcbea0ba6a57220a17939", + "shasum": "" + }, + "require": { + "illuminate/support": "~7|~8" + }, + "require-dev": { + "orchestra/testbench": "~5|~6", + "phpunit/phpunit": "~9.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Henrywhitaker3\\LaravelActions\\LaravelActionsServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Henrywhitaker3\\LaravelActions\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Henry Whitaker", + "email": "henrywhitaker3@outlook.com", + "homepage": "https://github.com/henrywhitaker3" + } + ], + "description": "Simple actions package for Laravel", + "homepage": "https://github.com/henrywhitaker3/laravel-actions", + "keywords": [ + "LaravelActions", + "laravel" + ], + "support": { + "issues": "https://github.com/henrywhitaker3/laravel-actions/issues", + "source": "https://github.com/henrywhitaker3/laravel-actions/tree/v1.0.5" + }, + "time": "2021-02-06T09:50:49+00:00" + }, { "name": "laravel-notification-channels/telegram", "version": "0.5.1", @@ -1256,21 +1376,21 @@ }, { "name": "laravel/framework", - "version": "v7.30.4", + "version": "v8.31.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "9dd38140dc2924daa1a020a3d7a45f9ceff03df3" + "reference": "2aa5c2488d25178ebc097052c7897a0e463ddc35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/9dd38140dc2924daa1a020a3d7a45f9ceff03df3", - "reference": "9dd38140dc2924daa1a020a3d7a45f9ceff03df3", + "url": "https://api.github.com/repos/laravel/framework/zipball/2aa5c2488d25178ebc097052c7897a0e463ddc35", + "reference": "2aa5c2488d25178ebc097052c7897a0e463ddc35", "shasum": "" }, "require": { "doctrine/inflector": "^1.4|^2.0", - "dragonmantank/cron-expression": "^2.3.1", + "dragonmantank/cron-expression": "^3.0.2", "egulias/email-validator": "^2.1.10", "ext-json": "*", "ext-mbstring": "*", @@ -1280,23 +1400,22 @@ "monolog/monolog": "^2.0", "nesbot/carbon": "^2.31", "opis/closure": "^3.6", - "php": "^7.2.5|^8.0", + "php": "^7.3|^8.0", "psr/container": "^1.0", "psr/simple-cache": "^1.0", - "ramsey/uuid": "^3.7|^4.0", + "ramsey/uuid": "^4.0", "swiftmailer/swiftmailer": "^6.0", - "symfony/console": "^5.0", - "symfony/error-handler": "^5.0", - "symfony/finder": "^5.0", - "symfony/http-foundation": "^5.0", - "symfony/http-kernel": "^5.0", - "symfony/mime": "^5.0", - "symfony/polyfill-php73": "^1.17", - "symfony/process": "^5.0", - "symfony/routing": "^5.0", - "symfony/var-dumper": "^5.0", + "symfony/console": "^5.1.4", + "symfony/error-handler": "^5.1.4", + "symfony/finder": "^5.1.4", + "symfony/http-foundation": "^5.1.4", + "symfony/http-kernel": "^5.1.4", + "symfony/mime": "^5.1.4", + "symfony/process": "^5.1.4", + "symfony/routing": "^5.1.4", + "symfony/var-dumper": "^5.1.4", "tijsverkoyen/css-to-inline-styles": "^2.2.2", - "vlucas/phpdotenv": "^4.0", + "vlucas/phpdotenv": "^5.2", "voku/portable-ascii": "^1.4.8" }, "conflict": { @@ -1310,6 +1429,7 @@ "illuminate/broadcasting": "self.version", "illuminate/bus": "self.version", "illuminate/cache": "self.version", + "illuminate/collections": "self.version", "illuminate/config": "self.version", "illuminate/console": "self.version", "illuminate/container": "self.version", @@ -1322,6 +1442,7 @@ "illuminate/hashing": "self.version", "illuminate/http": "self.version", "illuminate/log": "self.version", + "illuminate/macroable": "self.version", "illuminate/mail": "self.version", "illuminate/notifications": "self.version", "illuminate/pagination": "self.version", @@ -1338,21 +1459,21 @@ }, "require-dev": { "aws/aws-sdk-php": "^3.155", - "doctrine/dbal": "^2.6", + "doctrine/dbal": "^2.6|^3.0", "filp/whoops": "^2.8", - "guzzlehttp/guzzle": "^6.3.1|^7.0.1", + "guzzlehttp/guzzle": "^6.5.5|^7.0.1", "league/flysystem-cached-adapter": "^1.0", - "mockery/mockery": "~1.3.3|^1.4.2", - "moontoast/math": "^1.1", - "orchestra/testbench-core": "^5.8", + "mockery/mockery": "^1.4.2", + "orchestra/testbench-core": "^6.8", "pda/pheanstalk": "^4.0", - "phpunit/phpunit": "^8.4|^9.3.3", + "phpunit/phpunit": "^8.5.8|^9.3.3", "predis/predis": "^1.1.1", - "symfony/cache": "^5.0" + "symfony/cache": "^5.1.4" }, "suggest": { "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage and SES mail driver (^3.155).", - "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6).", + "brianium/paratest": "Required to run tests in parallel (^6.0).", + "doctrine/dbal": "Required to rename columns and drop SQLite columns (^2.6|^3.0).", "ext-ftp": "Required to use the Flysystem FTP driver.", "ext-gd": "Required to use Illuminate\\Http\\Testing\\FileFactory::image().", "ext-memcached": "Required to use the memcache cache driver.", @@ -1361,37 +1482,42 @@ "ext-redis": "Required to use the Redis cache and queue drivers (^4.0|^5.0).", "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.8).", - "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.3.1|^7.0.1).", + "guzzlehttp/guzzle": "Required to use the HTTP Client, Mailgun mail driver and the ping methods on schedules (^6.5.5|^7.0.1).", "laravel/tinker": "Required to use the tinker console command (^2.0).", "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^1.0).", "league/flysystem-cached-adapter": "Required to use the Flysystem cache (^1.0).", "league/flysystem-sftp": "Required to use the Flysystem SFTP driver (^1.0).", - "mockery/mockery": "Required to use mocking (~1.3.3|^1.4.2).", - "moontoast/math": "Required to use ordered UUIDs (^1.1).", + "mockery/mockery": "Required to use mocking (^1.4.2).", "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^4.0).", - "phpunit/phpunit": "Required to use assertions and run tests (^8.4|^9.3.3).", + "phpunit/phpunit": "Required to use assertions and run tests (^8.5.8|^9.3.3).", "predis/predis": "Required to use the predis connector (^1.1.2).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", - "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0).", - "symfony/cache": "Required to PSR-6 cache bridge (^5.0).", - "symfony/filesystem": "Required to create relative storage directory symbolic links (^5.0).", + "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^4.0|^5.0).", + "symfony/cache": "Required to PSR-6 cache bridge (^5.1.4).", + "symfony/filesystem": "Required to enable support for relative symbolic links (^5.1.4).", "symfony/psr-http-message-bridge": "Required to use PSR-7 bridging features (^2.0).", "wildbit/swiftmailer-postmark": "Required to use Postmark mail driver (^3.0)." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "7.x-dev" + "dev-master": "8.x-dev" } }, "autoload": { "files": [ + "src/Illuminate/Collections/helpers.php", + "src/Illuminate/Events/functions.php", "src/Illuminate/Foundation/helpers.php", "src/Illuminate/Support/helpers.php" ], "psr-4": { - "Illuminate\\": "src/Illuminate/" + "Illuminate\\": "src/Illuminate/", + "Illuminate\\Support\\": [ + "src/Illuminate/Macroable/", + "src/Illuminate/Collections/" + ] } }, "notification-url": "https://packagist.org/downloads/", @@ -1414,7 +1540,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2021-01-21T14:10:48+00:00" + "time": "2021-03-04T15:22:36+00:00" }, { "name": "laravel/slack-notification-channel", @@ -1547,26 +1673,29 @@ }, { "name": "laravel/ui", - "version": "v2.5.0", + "version": "v3.2.0", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "d01a705763c243b07be795e9d1bb47f89260f73d" + "reference": "a1f82c6283c8373ea1958b8a27c3d5c98cade351" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/d01a705763c243b07be795e9d1bb47f89260f73d", - "reference": "d01a705763c243b07be795e9d1bb47f89260f73d", + "url": "https://api.github.com/repos/laravel/ui/zipball/a1f82c6283c8373ea1958b8a27c3d5c98cade351", + "reference": "a1f82c6283c8373ea1958b8a27c3d5c98cade351", "shasum": "" }, "require": { - "illuminate/console": "^7.0", - "illuminate/filesystem": "^7.0", - "illuminate/support": "^7.0", - "php": "^7.2.5|^8.0" + "illuminate/console": "^8.0", + "illuminate/filesystem": "^8.0", + "illuminate/support": "^8.0", + "php": "^7.3|^8.0" }, "type": "library", "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + }, "laravel": { "providers": [ "Laravel\\Ui\\UiServiceProvider" @@ -1596,9 +1725,9 @@ ], "support": { "issues": "https://github.com/laravel/ui/issues", - "source": "https://github.com/laravel/ui/tree/v2.5.0" + "source": "https://github.com/laravel/ui/tree/v3.2.0" }, - "time": "2020-11-03T19:45:19+00:00" + "time": "2021-01-06T19:20:22+00:00" }, { "name": "lcobucci/jwt", @@ -5459,37 +5588,39 @@ }, { "name": "vlucas/phpdotenv", - "version": "v4.2.0", + "version": "v5.3.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "da64796370fc4eb03cc277088f6fede9fde88482" + "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/da64796370fc4eb03cc277088f6fede9fde88482", - "reference": "da64796370fc4eb03cc277088f6fede9fde88482", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", + "reference": "b3eac5c7ac896e52deab4a99068e3f4ab12d9e56", "shasum": "" }, "require": { - "php": "^5.5.9 || ^7.0 || ^8.0", - "phpoption/phpoption": "^1.7.3", - "symfony/polyfill-ctype": "^1.17" + "ext-pcre": "*", + "graham-campbell/result-type": "^1.0.1", + "php": "^7.1.3 || ^8.0", + "phpoption/phpoption": "^1.7.4", + "symfony/polyfill-ctype": "^1.17", + "symfony/polyfill-mbstring": "^1.17", + "symfony/polyfill-php80": "^1.17" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.4.1", "ext-filter": "*", - "ext-pcre": "*", - "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20" + "phpunit/phpunit": "^7.5.20 || ^8.5.14 || ^9.5.1" }, "suggest": { - "ext-filter": "Required to use the boolean validator.", - "ext-pcre": "Required to use most of the library." + "ext-filter": "Required to use the boolean validator." }, "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "5.3-dev" } }, "autoload": { @@ -5521,7 +5652,7 @@ ], "support": { "issues": "https://github.com/vlucas/phpdotenv/issues", - "source": "https://github.com/vlucas/phpdotenv/tree/v4.2.0" + "source": "https://github.com/vlucas/phpdotenv/tree/v5.3.0" }, "funding": [ { @@ -5533,7 +5664,7 @@ "type": "tidelift" } ], - "time": "2021-01-20T15:11:48+00:00" + "time": "2021-01-20T15:23:13+00:00" }, { "name": "voku/portable-ascii", @@ -5608,49 +5739,102 @@ } ], "time": "2020-11-12T00:07:28+00:00" + }, + { + "name": "webmozart/assert", + "version": "1.9.1", + "source": { + "type": "git", + "url": "https://github.com/webmozarts/assert.git", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", + "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", + "shasum": "" + }, + "require": { + "php": "^5.3.3 || ^7.0 || ^8.0", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "phpstan/phpstan": "<0.12.20", + "vimeo/psalm": "<3.9.1" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.36 || ^7.5.13" + }, + "type": "library", + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.9.1" + }, + "time": "2020-07-08T17:02:28+00:00" } ], "packages-dev": [ { "name": "barryvdh/laravel-ide-helper", - "version": "v2.8.2", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "5515cabea39b9cf55f98980d0f269dc9d85cfcca" + "reference": "64a6b902583802c162cdccf7e76dc8619368bf1a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/5515cabea39b9cf55f98980d0f269dc9d85cfcca", - "reference": "5515cabea39b9cf55f98980d0f269dc9d85cfcca", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/64a6b902583802c162cdccf7e76dc8619368bf1a", + "reference": "64a6b902583802c162cdccf7e76dc8619368bf1a", "shasum": "" }, "require": { "barryvdh/reflection-docblock": "^2.0.6", "composer/composer": "^1.6 || ^2", - "doctrine/dbal": "~2.3", + "doctrine/dbal": "^2.6 || ^3", "ext-json": "*", - "illuminate/console": "^6 || ^7 || ^8", - "illuminate/filesystem": "^6 || ^7 || ^8", - "illuminate/support": "^6 || ^7 || ^8", - "php": ">=7.2", + "illuminate/console": "^8", + "illuminate/filesystem": "^8", + "illuminate/support": "^8", + "php": "^7.3 || ^8.0", "phpdocumentor/type-resolver": "^1.1.0" }, "require-dev": { "ext-pdo_sqlite": "*", "friendsofphp/php-cs-fixer": "^2", - "illuminate/config": "^6 || ^7 || ^8", - "illuminate/view": "^6 || ^7 || ^8", - "mockery/mockery": "^1.3.3", - "orchestra/testbench": "^4 || ^5 || ^6", + "illuminate/config": "^8", + "illuminate/view": "^8", + "mockery/mockery": "^1.4", + "orchestra/testbench": "^6", "phpunit/phpunit": "^8.5 || ^9", - "spatie/phpunit-snapshot-assertions": "^1.4 || ^2.2 || ^3 || ^4", + "spatie/phpunit-snapshot-assertions": "^3 || ^4", "vimeo/psalm": "^3.12" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "2.8-dev" + "dev-master": "2.9-dev" }, "laravel": { "providers": [ @@ -5687,7 +5871,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", - "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.8.2" + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v2.9.0" }, "funding": [ { @@ -5695,7 +5879,7 @@ "type": "github" } ], - "time": "2020-12-06T08:55:05+00:00" + "time": "2020-12-29T10:11:05+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -5749,6 +5933,86 @@ }, "time": "2018-12-13T10:34:14+00:00" }, + { + "name": "brianium/paratest", + "version": "v6.2.0", + "source": { + "type": "git", + "url": "https://github.com/paratestphp/paratest.git", + "reference": "9a94366983ce32c7724fc92e3b544327d4adb9be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/9a94366983ce32c7724fc92e3b544327d4adb9be", + "reference": "9a94366983ce32c7724fc92e3b544327d4adb9be", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-simplexml": "*", + "php": "^7.3 || ^8.0", + "phpunit/php-code-coverage": "^9.2.5", + "phpunit/php-file-iterator": "^3.0.5", + "phpunit/php-timer": "^5.0.3", + "phpunit/phpunit": "^9.5.1", + "sebastian/environment": "^5.1.3", + "symfony/console": "^4.4 || ^5.2", + "symfony/process": "^4.4 || ^5.2" + }, + "require-dev": { + "doctrine/coding-standard": "^8.2.0", + "ekino/phpstan-banned-code": "^0.3.1", + "ergebnis/phpstan-rules": "^0.15.3", + "ext-posix": "*", + "infection/infection": "^0.20.2", + "phpstan/phpstan": "^0.12.70", + "phpstan/phpstan-deprecation-rules": "^0.12.6", + "phpstan/phpstan-phpunit": "^0.12.17", + "phpstan/phpstan-strict-rules": "^0.12.9", + "squizlabs/php_codesniffer": "^3.5.8", + "symfony/filesystem": "^5.2.2", + "thecodingmachine/phpstan-strict-rules": "^0.12.1", + "vimeo/psalm": "^4.4.1" + }, + "bin": [ + "bin/paratest" + ], + "type": "library", + "autoload": { + "psr-4": { + "ParaTest\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Scaturro", + "email": "scaturrob@gmail.com", + "homepage": "http://brianscaturro.com", + "role": "Lead" + } + ], + "description": "Parallel testing for PHP", + "homepage": "https://github.com/paratestphp/paratest", + "keywords": [ + "concurrent", + "parallel", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/paratestphp/paratest/issues", + "source": "https://github.com/paratestphp/paratest/tree/v6.2.0" + }, + "time": "2021-01-29T15:25:31+00:00" + }, { "name": "composer/ca-bundle", "version": "1.2.9", @@ -6586,6 +6850,75 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "itsgoingd/clockwork", + "version": "v5.0.6", + "source": { + "type": "git", + "url": "https://github.com/itsgoingd/clockwork.git", + "reference": "1de3f9f9fc22217aa024f79ecbdf0fde418fc0a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/itsgoingd/clockwork/zipball/1de3f9f9fc22217aa024f79ecbdf0fde418fc0a1", + "reference": "1de3f9f9fc22217aa024f79ecbdf0fde418fc0a1", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=5.6", + "psr/log": "1.*" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Clockwork\\Support\\Laravel\\ClockworkServiceProvider" + ], + "aliases": { + "Clockwork": "Clockwork\\Support\\Laravel\\Facade" + } + } + }, + "autoload": { + "psr-4": { + "Clockwork\\": "Clockwork/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "itsgoingd", + "email": "itsgoingd@luzer.sk", + "homepage": "https://twitter.com/itsgoingd" + } + ], + "description": "php dev tools in your browser", + "homepage": "https://underground.works/clockwork", + "keywords": [ + "Devtools", + "debugging", + "laravel", + "logging", + "lumen", + "profiling", + "slim" + ], + "support": { + "issues": "https://github.com/itsgoingd/clockwork/issues", + "source": "https://github.com/itsgoingd/clockwork/tree/v5.0.6" + }, + "funding": [ + { + "url": "https://github.com/itsgoingd", + "type": "github" + } + ], + "time": "2020-12-27T00:18:25+00:00" + }, { "name": "justinrainbow/json-schema", "version": "5.2.10", @@ -9024,59 +9357,6 @@ } ], "time": "2020-07-12T23:59:07+00:00" - }, - { - "name": "webmozart/assert", - "version": "1.9.1", - "source": { - "type": "git", - "url": "https://github.com/webmozarts/assert.git", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389", - "reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0 || ^8.0", - "symfony/polyfill-ctype": "^1.8" - }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<3.9.1" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.36 || ^7.5.13" - }, - "type": "library", - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "support": { - "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.9.1" - }, - "time": "2020-07-08T17:02:28+00:00" } ], "aliases": [], diff --git a/conf/site/config/app.php b/conf/site/config/app.php index 8c3685ce..b3abb874 100644 --- a/conf/site/config/app.php +++ b/conf/site/config/app.php @@ -182,6 +182,7 @@ return [ * Custom providers... */ App\Providers\IntegrationsServiceProvider::class, + App\Providers\SpeedtestServiceProvider::class, ], diff --git a/conf/site/config/clockwork.php b/conf/site/config/clockwork.php new file mode 100644 index 00000000..6d866808 --- /dev/null +++ b/conf/site/config/clockwork.php @@ -0,0 +1,409 @@ + env('CLOCKWORK_ENABLE', null), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Features + |------------------------------------------------------------------------------------------------------------------ + | + | You can enable or disable various Clockwork features here. Some features have additional settings (eg. slow query + | threshold for database queries). + | + */ + + 'features' => [ + + // Cache usage stats and cache queries including results + 'cache' => [ + 'enabled' => env('CLOCKWORK_CACHE_ENABLED', true), + + // Collect cache queries including results (high performance impact with a high number of queries) + 'collect_queries' => env('CLOCKWORK_CACHE_QUERIES', false) + ], + + // Database usage stats and queries + 'database' => [ + 'enabled' => env('CLOCKWORK_DATABASE_ENABLED', true), + + // Collect database queries (high performance impact with a very high number of queries) + 'collect_queries' => env('CLOCKWORK_DATABASE_COLLECT_QUERIES', true), + + // Collect details of models updates (high performance impact with a lot of model updates) + 'collect_models_actions' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_ACTIONS', true), + + // Collect details of retrieved models (very high performance impact with a lot of models retrieved) + 'collect_models_retrieved' => env('CLOCKWORK_DATABASE_COLLECT_MODELS_RETRIEVED', false), + + // Query execution time threshold in miliseconds after which the query will be marked as slow + 'slow_threshold' => env('CLOCKWORK_DATABASE_SLOW_THRESHOLD'), + + // Collect only slow database queries + 'slow_only' => env('CLOCKWORK_DATABASE_SLOW_ONLY', false), + + // Detect and report duplicate (N+1) queries + 'detect_duplicate_queries' => env('CLOCKWORK_DATABASE_DETECT_DUPLICATE_QUERIES', false) + ], + + // Dispatched events + 'events' => [ + 'enabled' => env('CLOCKWORK_EVENTS_ENABLED', true), + + // Ignored events (framework events are ignored by default) + 'ignored_events' => [ + // App\Events\UserRegistered::class, + // 'user.registered' + ], + ], + + // Laravel log (you can still log directly to Clockwork with laravel log disabled) + 'log' => [ + 'enabled' => env('CLOCKWORK_LOG_ENABLED', true) + ], + + // Sent notifications + 'notifications' => [ + 'enabled' => env('CLOCKWORK_NOTIFICATIONS_ENABLED', true), + ], + + // Performance metrics + 'performance' => [ + // Allow collecting of client metrics. Requires separate clockwork-browser npm package. + 'client_metrics' => env('CLOCKWORK_PERFORMANCE_CLIENT_METRICS', true) + ], + + // Dispatched queue jobs + 'queue' => [ + 'enabled' => env('CLOCKWORK_QUEUE_ENABLED', true) + ], + + // Redis commands + 'redis' => [ + 'enabled' => env('CLOCKWORK_REDIS_ENABLED', true) + ], + + // Routes list + 'routes' => [ + 'enabled' => env('CLOCKWORK_ROUTES_ENABLED', false) + ], + + // Rendered views + 'views' => [ + 'enabled' => env('CLOCKWORK_VIEWS_ENABLED', true), + + // Collect views including view data (high performance impact with a high number of views) + 'collect_data' => env('CLOCKWORK_VIEWS_COLLECT_DATA', false), + + // Use Twig profiler instead of Laravel events for apps using laravel-twigbridge (more precise, but does + // not support collecting view data) + 'use_twig_profiler' => env('CLOCKWORK_VIEWS_USE_TWIG_PROFILER', false) + ] + + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Enable web UI + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork comes with a web UI accessibla via http://your.app/clockwork. Here you can enable or disable this + | feature. You can also set a custom path for the web UI. + | + */ + + 'web' => env('CLOCKWORK_WEB', true), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Enable toolbar + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can show a toolbar with basic metrics on all responses. Here you can enable or disable this feature. + | Requires a separate clockwork-browser npm library. + | + */ + + 'toolbar' => env('CLOCKWORK_TOOLBAR', false), + + /* + |------------------------------------------------------------------------------------------------------------------ + | HTTP requests collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork collects data about HTTP requests to your app. Here you can choose which requests should be collected. + | + */ + + 'requests' => [ + // With on-demand mode enabled, Clockwork will only profile requests when the browser extension is open or you + // manually pass a "clockwork-profile" cookie or get/post data key. + // Optionally you can specify a "secret" that has to be passed as the value to enable profiling. + 'on_demand' => env('CLOCKWORK_REQUESTS_ON_DEMAND', false), + + // Collect only errors (requests with HTTP 4xx and 5xx responses) + 'errors_only' => env('CLOCKWORK_REQUESTS_ERRORS_ONLY', false), + + // Response time threshold in miliseconds after which the request will be marked as slow + 'slow_threshold' => env('CLOCKWORK_REQUESTS_SLOW_THRESHOLD'), + + // Collect only slow requests + 'slow_only' => env('CLOCKWORK_REQUESTS_SLOW_ONLY', false), + + // Sample the collected requests (eg. set to 100 to collect only 1 in 100 requests) + 'sample' => env('CLOCKWORK_REQUESTS_SAMPLE', false), + + // List of URIs that should not be collected + 'except' => [ + '/horizon/.*', // Laravel Horizon requests + '/telescope/.*', // Laravel Telescope requests + '/files/*' + ], + + // List of URIs that should be collected, any other URI will not be collected if not empty + 'only' => [ + // '/api/.*' + ], + + // Don't collect OPTIONS requests, mostly used in the CSRF pre-flight requests and are rarely of interest + 'except_preflight' => env('CLOCKWORK_REQUESTS_EXCEPT_PREFLIGHT', true) + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Artisan commands collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can collect data about executed artisan commands. Here you can enable and configure which commands + | should be collected. + | + */ + + 'artisan' => [ + // Enable or disable collection of executed Artisan commands + 'collect' => env('CLOCKWORK_ARTISAN_COLLECT', false), + + // List of commands that should not be collected (built-in commands are not collected by default) + 'except' => [ + // 'inspire' + ], + + // List of commands that should be collected, any other command will not be collected if not empty + 'only' => [ + // 'inspire' + ], + + // Enable or disable collection of command output + 'collect_output' => env('CLOCKWORK_ARTISAN_COLLECT_OUTPUT', false), + + // Enable or disable collection of built-in Laravel commands + 'except_laravel_commands' => env('CLOCKWORK_ARTISAN_EXCEPT_LARAVEL_COMMANDS', true) + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Queue jobs collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can collect data about executed queue jobs. Here you can enable and configure which queue jobs should + | be collected. + | + */ + + 'queue' => [ + // Enable or disable collection of executed queue jobs + 'collect' => env('CLOCKWORK_QUEUE_COLLECT', false), + + // List of queue jobs that should not be collected + 'except' => [ + // App\Jobs\ExpensiveJob::class + ], + + // List of queue jobs that should be collected, any other queue job will not be collected if not empty + 'only' => [ + // App\Jobs\BuggyJob::class + ] + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Tests collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can collect data about executed tests. Here you can enable and configure which tests should be + | collected. + | + */ + + 'tests' => [ + // Enable or disable collection of ran tests + 'collect' => env('CLOCKWORK_TESTS_COLLECT', false), + + // List of tests that should not be collected + 'except' => [ + // Tests\Unit\ExampleTest::class + ] + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Enable data collection when Clockwork is disabled + |------------------------------------------------------------------------------------------------------------------ + | + | You can enable this setting to collect data even when Clockwork is disabled. Eg. for future analysis. + | + */ + + 'collect_data_always' => env('CLOCKWORK_COLLECT_DATA_ALWAYS', false), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Metadata storage + |------------------------------------------------------------------------------------------------------------------ + | + | Configure how is the metadata collected by Clockwork stored. Two options are available: + | - files - A simple fast storage implementation storing data in one-per-request files. + | - sql - Stores requests in a sql database. Supports MySQL, Postgresql, Sqlite and requires PDO. + | + */ + + 'storage' => env('CLOCKWORK_STORAGE', 'files'), + + // Path where the Clockwork metadata is stored + 'storage_files_path' => env('CLOCKWORK_STORAGE_FILES_PATH', storage_path('clockwork')), + + // Compress the metadata files using gzip, trading a little bit of performance for lower disk usage + 'storage_files_compress' => env('CLOCKWORK_STORAGE_FILES_COMPRESS', false), + + // SQL database to use, can be a name of database configured in database.php or a path to a sqlite file + 'storage_sql_database' => env('CLOCKWORK_STORAGE_SQL_DATABASE', storage_path('clockwork.sqlite')), + + // SQL table name to use, the table is automatically created and udpated when needed + 'storage_sql_table' => env('CLOCKWORK_STORAGE_SQL_TABLE', 'clockwork'), + + // Maximum lifetime of collected metadata in minutes, older requests will automatically be deleted, false to disable + 'storage_expiration' => env('CLOCKWORK_STORAGE_EXPIRATION', 60 * 24 * 7), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Authentication + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can be configured to require authentication before allowing access to the collected data. This might be + | useful when the application is publicly accessible. Setting to true will enable a simple authentication with a + | pre-configured password. You can also pass a class name of a custom implementation. + | + */ + + 'authentication' => env('CLOCKWORK_AUTHENTICATION', false), + + // Password for the simple authentication + 'authentication_password' => env('CLOCKWORK_AUTHENTICATION_PASSWORD', 'VerySecretPassword'), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Stack traces collection + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork can collect stack traces for log messages and certain data like database queries. Here you can set + | whether to collect stack traces, limit the number of collected frames and set further configuration. Collecting + | long stack traces considerably increases metadata size. + | + */ + + 'stack_traces' => [ + // Enable or disable collecting of stack traces + 'enabled' => env('CLOCKWORK_STACK_TRACES_ENABLED', true), + + // Limit the number of frames to be collected + 'limit' => env('CLOCKWORK_STACK_TRACES_LIMIT', 10), + + // List of vendor names to skip when determining caller, common vendors are automatically added + 'skip_vendors' => [ + // 'phpunit' + ], + + // List of namespaces to skip when determining caller + 'skip_namespaces' => [ + // 'Laravel' + ], + + // List of class names to skip when determining caller + 'skip_classes' => [ + // App\CustomLog::class + ] + + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Serialization + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork serializes the collected data to json for storage and transfer. Here you can configure certain aspects + | of serialization. Serialization has a large effect on the cpu time and memory usage. + | + */ + + // Maximum depth of serialized multi-level arrays and objects + 'serialization_depth' => env('CLOCKWORK_SERIALIZATION_DEPTH', 10), + + // A list of classes that will never be serialized (eg. a common service container class) + 'serialization_blackbox' => [ + \Illuminate\Container\Container::class, + \Illuminate\Foundation\Application::class, + \Laravel\Lumen\Application::class + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Register helpers + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork comes with a "clock" global helper function. You can use this helper to quickly log something and to + | access the Clockwork instance. + | + */ + + 'register_helpers' => env('CLOCKWORK_REGISTER_HELPERS', true), + + /* + |------------------------------------------------------------------------------------------------------------------ + | Send Headers for AJAX request + |------------------------------------------------------------------------------------------------------------------ + | + | When trying to collect data the AJAX method can sometimes fail if it is missing required headers. For example, an + | API might require a version number using Accept headers to route the HTTP request to the correct codebase. + | + */ + + 'headers' => [ + // 'Accept' => 'application/vnd.com.whatever.v1+json', + ], + + /* + |------------------------------------------------------------------------------------------------------------------ + | Server-Timing + |------------------------------------------------------------------------------------------------------------------ + | + | Clockwork supports the W3C Server Timing specification, which allows for collecting a simple performance metrics + | in a cross-browser way. Eg. in Chrome, your app, database and timeline event timings will be shown in the Dev + | Tools network tab. This setting specifies the max number of timeline events that will be sent. Setting to false + | will disable the feature. + | + */ + + 'server_timing' => env('CLOCKWORK_SERVER_TIMING', 10) + +]; diff --git a/conf/site/config/speedtest.php b/conf/site/config/speedtest.php index b440b48f..ceef7301 100644 --- a/conf/site/config/speedtest.php +++ b/conf/site/config/speedtest.php @@ -7,7 +7,7 @@ return [ |-------------------------------------------------------------------------- */ - 'version' => '1.10.4', + 'version' => '1.11.1', /* |-------------------------------------------------------------------------- diff --git a/conf/site/database/.gitignore b/conf/site/database/.gitignore index 67c575a3..cd84cd69 100644 --- a/conf/site/database/.gitignore +++ b/conf/site/database/.gitignore @@ -1,3 +1,4 @@ *.sqlite *.sqlite-journal *.bak +*.db_test* diff --git a/conf/site/database/migrations/2021_03_07_101259_add_speedtest_provider_setting.php b/conf/site/database/migrations/2021_03_07_101259_add_speedtest_provider_setting.php new file mode 100644 index 00000000..4510dc09 --- /dev/null +++ b/conf/site/database/migrations/2021_03_07_101259_add_speedtest_provider_setting.php @@ -0,0 +1,38 @@ + 'speedtest_provider', + 'value' => 'ookla', + 'description' => 'The provider/package used to run speedtests.' + ]); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Setting::whereIn('name', [ + 'speedtest_provider', + ])->delete(); + } +} diff --git a/conf/site/database/migrations/2021_03_07_121716_update_speedtest_server_settings_text.php b/conf/site/database/migrations/2021_03_07_121716_update_speedtest_server_settings_text.php new file mode 100644 index 00000000..217023d3 --- /dev/null +++ b/conf/site/database/migrations/2021_03_07_121716_update_speedtest_server_settings_text.php @@ -0,0 +1,37 @@ +setting = Setting::where('name', 'server')->first(); + } + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + $this->setting->description = '
Comma-separated list of speedtest.net server IDs picked randomly. Leave blank to use default settings.
;'; + $this->setting->save(); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $this->setting->description = 'Comma-separated list of speedtest.net servers picked randomly. Leave blank to use default settings.
'; + $this->setting->save(); + } +} diff --git a/conf/site/database/migrations/2021_04_10_082758_add_visible_columns_setting.php b/conf/site/database/migrations/2021_04_10_082758_add_visible_columns_setting.php new file mode 100644 index 00000000..e4e3663c --- /dev/null +++ b/conf/site/database/migrations/2021_04_10_082758_add_visible_columns_setting.php @@ -0,0 +1,40 @@ + 'visible_columns', + 'value' => [ + 'id', 'created_at', 'download', 'upload', 'ping' + ], + 'description' => 'Choose and order the columns shown in the "All Tests" table.' + ]); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Setting::whereIn('name', [ + 'visible_columns', + ])->delete(); + } +} diff --git a/conf/site/database/migrations/2021_04_10_102320_add_hidden_columns_setting.php b/conf/site/database/migrations/2021_04_10_102320_add_hidden_columns_setting.php new file mode 100644 index 00000000..5ddb6680 --- /dev/null +++ b/conf/site/database/migrations/2021_04_10_102320_add_hidden_columns_setting.php @@ -0,0 +1,40 @@ + 'hidden_columns', + 'value' => [ + 'server_id', 'server_name', 'server_host', 'url', 'scheduled', + ], + 'description' => 'Columns hidden from the "All Tests" table.' + ]); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Setting::whereIn('name', [ + 'hidden_columns', + ])->delete(); + } +} diff --git a/conf/site/package-lock.json b/conf/site/package-lock.json index 55799ff4..f35db760 100644 --- a/conf/site/package-lock.json +++ b/conf/site/package-lock.json @@ -1,5 +1,5 @@ { - "name": "site", + "name": "Speedtest-checker", "lockfileVersion": 2, "requires": true, "packages": { @@ -9,6 +9,7 @@ "chart.js": "^2.9.4", "csv-file-validator": "^1.10.1", "js-cookie": "^2.2.1", + "react-beautiful-dnd": "^13.1.0", "react-bootstrap": "^1.5.1", "react-chartjs-2": "^2.11.1", "react-router": "^5.2.0", @@ -1307,9 +1308,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", "dependencies": { "regenerator-runtime": "^0.13.4" } @@ -1409,6 +1410,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/invariant": { "version": "2.2.34", "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz", @@ -1452,6 +1462,17 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-redux": { + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz", + "integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz", @@ -3352,6 +3373,14 @@ "urix": "^0.1.0" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -7471,6 +7500,11 @@ "node": ">= 0.6" } }, + "node_modules/memoize-one": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", + "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" + }, "node_modules/memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -9516,6 +9550,11 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "node_modules/raf-schd": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz", + "integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -9572,7 +9611,6 @@ "version": "17.0.1", "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -9581,6 +9619,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-beautiful-dnd": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz", + "integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==", + "dependencies": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.5 || ^17.0.0", + "react-dom": "^16.8.5 || ^17.0.0" + } + }, "node_modules/react-bootstrap": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.1.tgz", @@ -9619,7 +9675,6 @@ "version": "17.0.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -9651,14 +9706,6 @@ "warning": "^4.0.3" } }, - "node_modules/react-overlays/node_modules/@babel/runtime": { - "version": "7.13.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", - "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, "node_modules/react-overlays/node_modules/csstype": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", @@ -9673,6 +9720,31 @@ "csstype": "^3.0.2" } }, + "node_modules/react-redux": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.3.tgz", + "integrity": "sha512-ZhAmQ1lrK+Pyi0ZXNMUZuYxYAZd59wFuVDGUt536kSGdD0ya9Q7BfsE95E3TsFLE3kOSFp5m6G5qbatE+Ic1+w==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/react-redux": "^7.1.16", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.13.1" + }, + "peerDependencies": { + "react": "^16.8.3 || ^17", + "redux": "^2.0.0 || ^3.0.0 || ^4.0.0-0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", @@ -9819,6 +9891,15 @@ "node": ">= 0.8" } }, + "node_modules/redux": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", + "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", + "dependencies": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -10368,7 +10449,6 @@ "version": "0.20.1", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz", "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==", - "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -11295,6 +11375,14 @@ "node": ">=4.0.0" } }, + "node_modules/symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -11905,6 +11993,14 @@ "node": ">=0.10.0" } }, + "node_modules/use-memo-one": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz", + "integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0" + } + }, "node_modules/util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", @@ -14321,9 +14417,9 @@ } }, "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", "requires": { "regenerator-runtime": "^0.13.4" } @@ -14417,6 +14513,15 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/invariant": { "version": "2.2.34", "resolved": "https://registry.npmjs.org/@types/invariant/-/invariant-2.2.34.tgz", @@ -14467,6 +14572,17 @@ } } }, + "@types/react-redux": { + "version": "7.1.16", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.16.tgz", + "integrity": "sha512-f/FKzIrZwZk7YEO9E1yoxIuDNRiDducxkFlkw/GNMGEnK9n4K8wJzlJBghpSuOVDgEUHoDkDF7Gi9lHNQR4siw==", + "requires": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "@types/react-transition-group": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.1.tgz", @@ -16148,6 +16264,14 @@ } } }, + "css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "requires": { + "tiny-invariant": "^1.0.6" + } + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -19504,6 +19628,11 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, + "memoize-one": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.1.1.tgz", + "integrity": "sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==" + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -21257,6 +21386,11 @@ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "dev": true }, + "raf-schd": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.2.tgz", + "integrity": "sha512-VhlMZmGy6A6hrkJWHLNTGl5gtgMUm+xfGza6wbwnE914yeQ5Ybm18vgM734RZhMgfw4tacUrWseGZlpUrrakEQ==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -21306,12 +21440,25 @@ "version": "17.0.1", "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, + "react-beautiful-dnd": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/react-beautiful-dnd/-/react-beautiful-dnd-13.1.0.tgz", + "integrity": "sha512-aGvblPZTJowOWUNiwd6tNfEpgkX5OxmpqxHKNW/4VmvZTNTbeiq7bA3bn5T+QSF2uibXB0D1DmJsb1aC/+3cUA==", + "requires": { + "@babel/runtime": "^7.9.2", + "css-box-model": "^1.2.0", + "memoize-one": "^5.1.1", + "raf-schd": "^4.0.2", + "react-redux": "^7.2.0", + "redux": "^4.0.4", + "use-memo-one": "^1.1.1" + } + }, "react-bootstrap": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.5.1.tgz", @@ -21350,7 +21497,6 @@ "version": "17.0.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", @@ -21382,14 +21528,6 @@ "warning": "^4.0.3" }, "dependencies": { - "@babel/runtime": { - "version": "7.13.9", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.9.tgz", - "integrity": "sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==", - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, "csstype": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz", @@ -21406,6 +21544,19 @@ } } }, + "react-redux": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.3.tgz", + "integrity": "sha512-ZhAmQ1lrK+Pyi0ZXNMUZuYxYAZd59wFuVDGUt536kSGdD0ya9Q7BfsE95E3TsFLE3kOSFp5m6G5qbatE+Ic1+w==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/react-redux": "^7.1.16", + "hoist-non-react-statics": "^3.3.2", + "loose-envify": "^1.4.0", + "prop-types": "^15.7.2", + "react-is": "^16.13.1" + } + }, "react-router": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.2.0.tgz", @@ -21550,6 +21701,15 @@ "source-map": "~0.5.0" } }, + "redux": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.5.tgz", + "integrity": "sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -22010,7 +22170,6 @@ "version": "0.20.1", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz", "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==", - "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -22827,6 +22986,11 @@ "util.promisify": "~1.0.0" } }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, "tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -23329,6 +23493,12 @@ "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", "dev": true }, + "use-memo-one": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.2.tgz", + "integrity": "sha512-u2qFKtxLsia/r8qG0ZKkbytbztzRb317XCkT7yP8wxL0tZ/CzK2G+WWie5vWvpyeP7+YoPIwbJoIHJ4Ba4k0oQ==", + "requires": {} + }, "util": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", diff --git a/conf/site/package.json b/conf/site/package.json index 7e0b7d4e..bc6e9638 100644 --- a/conf/site/package.json +++ b/conf/site/package.json @@ -29,6 +29,7 @@ "chart.js": "^2.9.4", "csv-file-validator": "^1.10.1", "js-cookie": "^2.2.1", + "react-beautiful-dnd": "^13.1.0", "react-bootstrap": "^1.5.1", "react-chartjs-2": "^2.11.1", "react-router": "^5.2.0", diff --git a/conf/site/resources/js/components/Graphics/HistoryGraph.js b/conf/site/resources/js/components/Graphics/HistoryGraph.js index 4eb4e49f..d1620a9b 100644 --- a/conf/site/resources/js/components/Graphics/HistoryGraph.js +++ b/conf/site/resources/js/components/Graphics/HistoryGraph.js @@ -11,7 +11,10 @@ export default class HistoryGraph extends Component { super(props) this.state = { - days: 7, + days: props.days, + time: props.dlUl, + fail: props.fail, + config: props.config, duData: {}, duOptions: {}, pingData: {}, @@ -26,233 +29,233 @@ export default class HistoryGraph extends Component { graph_failure_width: 6, graph_ping_enabled: true, graph_ping_width: 6, + firstUpdate: false, } } componentDidMount = () => { - this.getData(); - var int = setInterval(this.getData, 10000); + } + + componentDidUpdate() { + if( + this.state.time != this.props.dlUl || + this.state.fail != this.props.fail || + this.state.config != this.props.config || + this.state.days != this.props.days + ) { + this.setState({ + time: this.props.dlUl, + fail: this.props.fail, + config: this.props.config, + days: this.props.days + }); + + if(this.state.config !== null) { + this.processData(); + } + } + + if( + !this.state.firstUpdate && + this.state.config !== null + ) { + this.processData(); + this.setState({ + firstUpdate: true, + }); + } + } + + processData() { + this.processConfig(); + this.processDlUlPing(); + this.processFailure(); + } + + processConfig() { this.setState({ - interval: int, + graph_ul_dl_enabled: Boolean(Number(this.state.config.graphs.download_upload_graph_enabled.value)), + graph_ul_dl_width: this.state.config.graphs.download_upload_graph_width.value, + graph_ping_enabled: Boolean(Number(this.state.config.graphs.ping_graph_enabled.value)), + graph_ping_width: this.state.config.graphs.ping_graph_width.value, + graph_failure_enabled: Boolean(Number(this.state.config.graphs.failure_graph_enabled.value)), + graph_failure_width: this.state.config.graphs.failure_graph_width.value, }); } - componentWillUnmount() { - clearInterval(this.state.interval); - } + processDlUlPing() { + let days = this.state.days; - getDLULPing = (days) => { - var url = 'api/speedtest/time/' + days; - - Axios.get(url) - .then((resp) => { - var duData = { - labels: [], - datasets:[ - { - data: [], - label: 'Download', - borderColor: "#fca503", - fill: false, - }, - { - data: [], - label: 'Upload', - borderColor: "#3e95cd", - fill: false, - } - ], - }; - var duOptions = { - maintainAspectRatio: false, - responsive: true, - tooltips: { - callbacks: { - label: (item) => `${item.yLabel} Mbit/s`, - }, + var duData = { + labels: [], + datasets:[ + { + data: [], + label: 'Download', + borderColor: "#fca503", + fill: false, }, - title: { - display: false, - text: 'Speedtests results for the last ' + days + ' days', - }, - scales: { - xAxes: [{ - display: false, - scaleLabel: { - display: true, - labelString: 'DateTime' - } - }], - }, - elements: { - point:{ - radius: 0, - hitRadius: 8 - } + { + data: [], + label: 'Upload', + borderColor: "#3e95cd", + fill: false, } - }; - - var pingData = { - labels: [], - datasets:[ - { - data: [], - label: 'Ping', - borderColor: "#07db71", - fill: false, - }, - ], - }; - var pingOptions = { - maintainAspectRatio: false, - responsive: true, - tooltips: { - callbacks: { - label: (item) => `${item.yLabel} ms`, - }, + ], + }; + var duOptions = { + maintainAspectRatio: false, + responsive: true, + tooltips: { + callbacks: { + label: (item) => `${item.yLabel} Mbit/s`, }, - title: { + }, + title: { + display: false, + text: 'Speedtests results for the last ' + days + ' days', + }, + scales: { + xAxes: [{ 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 + scaleLabel: { + display: true, + labelString: 'DateTime' } + }], + }, + elements: { + point:{ + radius: 0, + hitRadius: 8 } } + }; - resp.data.data.forEach(e => { - var download = { - t: new Date(e.created_at), - y: e.download, - }; - var upload = { - t: new Date(e.created_at), - y: e.upload, - }; - var ping = { - t: new Date(e.created_at), - y: parseFloat(e.ping) - } - duData.datasets[0].data.push(download); - duData.datasets[1].data.push(upload); - pingData.datasets[0].data.push(ping); - duData.labels.push(new Date(e.created_at).toLocaleString()); - pingData.labels.push(new Date(e.created_at).toLocaleString()); - }); - - this.setState({ - duData: duData, - duOptions: duOptions, - pingData: pingData, - pingOptions: pingOptions, - loading: false, - }); - }) - .catch((err) => { - console.log(err); - }) - } - - getFailure = (days) => { - var url = 'api/speedtest/fail/' + days; - Axios.get(url) - .then((resp) => { - var failData = { - labels: [], - datasets: [ - { - data: [], - label: 'Successful', - backgroundColor: '#07db71' - }, - { - data: [], - label: 'Failed', - backgroundColor: '#E74C3C' - }, - ], - }; - var failOptions = { - maintainAspectRatio: false, - responsive: true, - tooltips: { - callbacks: { - label: (item) => `${item.yLabel} speedtests`, - }, + var pingData = { + labels: [], + datasets:[ + { + data: [], + label: 'Ping', + borderColor: "#07db71", + fill: false, }, - scales: { - xAxes: [{ - stacked: true - }], - yAxes: [{ - stacked: true - }] + ], + }; + var pingOptions = { + maintainAspectRatio: false, + responsive: true, + tooltips: { + callbacks: { + label: (item) => `${item.yLabel} ms`, + }, + }, + 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 } + } + } + + this.state.time.forEach(e => { + var download = { + t: new Date(e.created_at), + y: e.download, }; + var upload = { + t: new Date(e.created_at), + y: e.upload, + }; + var ping = { + t: new Date(e.created_at), + y: parseFloat(e.ping) + } + duData.datasets[0].data.push(download); + duData.datasets[1].data.push(upload); + pingData.datasets[0].data.push(ping); + duData.labels.push(new Date(e.created_at).toLocaleString()); + pingData.labels.push(new Date(e.created_at).toLocaleString()); + }); - resp.data.data.forEach(e => { - var success = {x: e.date, y: e.success}; - var fail = {x: e.date, y: e.failure}; - failData.datasets[0].data.push(success); - failData.datasets[1].data.push(fail); - failData.labels.push(new Date(e.date).toLocaleString([], {year: '2-digit', month:'2-digit', day:'2-digit'})); - }) - - this.setState({ - failData: failData, - failOptions: failOptions - }); - }) - .catch((err) => { - console.log(err); - }) + this.setState({ + duData: duData, + duOptions: duOptions, + pingData: pingData, + pingOptions: pingOptions, + loading: false, + }); } - 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, - }); + processFailure() { + let days = this.state.days; - this.getDLULPing(days); - this.getFailure(days); - }) - .catch((err) => { - console.log('Couldn\'t get the site config'); - console.log(err); + var failData = { + labels: [], + datasets: [ + { + data: [], + label: 'Successful', + backgroundColor: '#07db71' + }, + { + data: [], + label: 'Failed', + backgroundColor: '#E74C3C' + }, + ], + }; + var failOptions = { + maintainAspectRatio: false, + responsive: true, + tooltips: { + callbacks: { + label: (item) => `${item.yLabel} speedtests`, + }, + }, + scales: { + xAxes: [{ + stacked: true + }], + yAxes: [{ + stacked: true + }] + } + }; + + this.state.fail.forEach(e => { + var success = {x: e.date, y: e.success}; + var fail = {x: e.date, y: e.failure}; + failData.datasets[0].data.push(success); + failData.datasets[1].data.push(fail); + failData.labels.push(new Date(e.date).toLocaleString([], {year: '2-digit', month:'2-digit', day:'2-digit'})); }) + + this.setState({ + failData: failData, + failOptions: failOptions + }); } updateDays = (e) => { var days = e.target.value; if(days) { - this.getData(days); - clearInterval(this.state.int); - var int = setInterval(this.getData, 10000); toast.info('Showing results for the last ' + days + ' days'); - this.setState({ - days: days, - interval: int - }); + this.props.updateDays(days); } } diff --git a/conf/site/resources/js/components/Graphics/LatestResults.js b/conf/site/resources/js/components/Graphics/LatestResults.js index dd1bd5dc..c2a4fe01 100644 --- a/conf/site/resources/js/components/Graphics/LatestResults.js +++ b/conf/site/resources/js/components/Graphics/LatestResults.js @@ -12,42 +12,25 @@ export default class LatestResults extends Component { super(props) this.state = { - data: {}, + data: props.data, interval: null, loading: true, } } - componentDidMount = () => { - this.getData(); - var int = setInterval(this.getData, 10000); - this.setState({ - interval: int, - }); + componentDidUpdate() { + if(this.state.data !== this.props.data) { + this.setState({ + data: this.props.data, + loading: false, + }); + } } componentWillUnmount() { clearInterval(this.state.interval); } - getData = () => { - var url = 'api/speedtest/latest'; - - Axios.get(url) - .then((resp) => { - this.setState({ - data: resp.data, - loading: false - }); - }) - .catch((err) => { - this.setState({ - data: false - }); - console.log(err); - }) - } - newScan = () => { var url = 'api/speedtest/run?token=' + window.token; diff --git a/conf/site/resources/js/components/Graphics/TableRow.js b/conf/site/resources/js/components/Graphics/TableRow.js index ea8ca2ce..4e1f9d44 100644 --- a/conf/site/resources/js/components/Graphics/TableRow.js +++ b/conf/site/resources/js/components/Graphics/TableRow.js @@ -43,21 +43,66 @@ export default class TableRow extends Component { } }) + this.props.refresh(); this.toggleShow(); } + getDataFields = () => { + let allFields = this.props.allFields; + let data = this.state.data; + let processedFields = []; + + for(var key in allFields) { + let field = allFields[key]; + + let value = data[key]; + + if(field.type === 'date') { + value = new Date(value).toLocaleString(); + } else if(field.type === 'bool') { + value = Boolean(value) ? field.if_true : field.if_false + } + + let final = { + name: key, + key: field.alias, + value: value, + type: field.type + }; + + processedFields.push(final); + } + + let visible = []; + let inModal = []; + + window.config.tables.visible_columns.forEach(column => { + visible.push(processedFields.find(x => x.name == column)); + }); + + inModal = processedFields.filter(el => { + return !visible.includes(el); + }); + + return { + visible: visible, + modal: inModal + }; + } + render() { var e = this.state.data; var show = this.state.show; + var fields = this.getDataFields(); if(e.failed != true) { return (Server ID: {e.server_id}
-Name: {e.server_name}
-Host: {e.server_host}
-URL: Speedtest.net
- {e.scheduled != undefined && -Type: {e.scheduled == true ? 'scheduled' : 'manual'}
- } + {fields.modal.map((e, i) => { + if(e.type === 'url') { + return ( +{e.key}: Speedtest.net
+ ); + } else { + return ( +{e.key}: {e.value}
+ ); + } + })}| ID | -Time | -Download (Mbit/s) | -Upload (Mbit/s) | -Ping (ms) | + {window.config.tables.visible_columns.map((e, i) => { + return ( +{allFields[e].alias} | + ); + })}More |
|---|