mirror of
https://github.com/henrywhitaker3/Speedtest-Tracker.git
synced 2025-12-21 21:33:08 +01:00
Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b95cb12ef | ||
|
|
8ff87c2e7a | ||
|
|
149c69ce64 | ||
|
|
933291c5fc | ||
|
|
29c36d88e2 | ||
|
|
4b167af50e | ||
|
|
7d0ea79798 | ||
|
|
375eab288d | ||
|
|
8e2ddd974a | ||
|
|
d78c3e2669 | ||
|
|
a83d4e363b | ||
|
|
334623454d | ||
|
|
a7aa3e7885 | ||
|
|
2e1022c116 | ||
|
|
355d38acb7 | ||
|
|
1e9887ac46 | ||
|
|
8538dd231f | ||
|
|
5793140e89 | ||
|
|
56ecff1a09 | ||
|
|
eff8f92016 | ||
|
|
8ae4bc602c | ||
|
|
9028ffb7cd | ||
|
|
62fde8fb2a | ||
|
|
601d46915c | ||
|
|
751acff32c | ||
|
|
4e133e97c7 | ||
|
|
61a85ced34 | ||
|
|
aa37a50f57 | ||
|
|
99ac0b008c | ||
|
|
b4147ce57a | ||
|
|
80b80811aa | ||
|
|
2d8bc7d71f | ||
|
|
a036ed7c8f | ||
|
|
01b097b4d3 | ||
|
|
a6326f6d98 | ||
|
|
ef34238043 | ||
|
|
8e556250aa | ||
|
|
87fc35c1c5 | ||
|
|
b6fca4e1a7 | ||
|
|
ae40f503e2 | ||
|
|
61da652b82 | ||
|
|
ec56337b99 | ||
|
|
0ed4674c3f | ||
|
|
dd56a667cd | ||
|
|
35a1de7333 | ||
|
|
f6de728265 | ||
|
|
d6da8962eb |
@@ -1,9 +1,11 @@
|
||||
# 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.
|
||||
|
||||
A demo can be found [here](https://speedtest.henrywhitaker.com)
|
||||
|
||||
Disclaimer: You will need to accept Ookla's EULA and privacy agreements in order to use this container.
|
||||
|
||||

|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Speedtest;
|
||||
use Cache;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
@@ -59,6 +60,7 @@ class BackupHelper {
|
||||
*/
|
||||
public static function restore($array, $format)
|
||||
{
|
||||
Cache::flush();
|
||||
if($format == 'json') {
|
||||
foreach($array as $test) {
|
||||
try {
|
||||
|
||||
@@ -4,9 +4,11 @@ namespace App\Helpers;
|
||||
|
||||
use App\Events\TestNotificationEvent;
|
||||
use App\Setting;
|
||||
use Cache;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class SettingsHelper {
|
||||
class SettingsHelper
|
||||
{
|
||||
|
||||
/**
|
||||
* Get a Setting object by name
|
||||
@@ -18,9 +20,9 @@ class SettingsHelper {
|
||||
{
|
||||
$name = Setting::where('name', $name)->get();
|
||||
|
||||
if(sizeof($name) == 0) {
|
||||
if (sizeof($name) == 0) {
|
||||
return false;
|
||||
} else if(sizeof($name) == 1) {
|
||||
} else if (sizeof($name) == 1) {
|
||||
return $name[0];
|
||||
} else {
|
||||
$name = $name->keyBy('name');
|
||||
@@ -39,11 +41,11 @@ class SettingsHelper {
|
||||
{
|
||||
$setting = SettingsHelper::get($name);
|
||||
|
||||
if($value === false) {
|
||||
if ($value === false) {
|
||||
$value = "0";
|
||||
}
|
||||
|
||||
if($setting !== false) {
|
||||
if ($setting !== false) {
|
||||
$setting->value = $value;
|
||||
$setting->save();
|
||||
} else {
|
||||
@@ -53,6 +55,10 @@ class SettingsHelper {
|
||||
]);
|
||||
}
|
||||
|
||||
if ($name == 'show_failed_tests_on_graph') {
|
||||
Cache::flush();
|
||||
}
|
||||
|
||||
return $setting;
|
||||
}
|
||||
|
||||
@@ -64,13 +70,13 @@ class SettingsHelper {
|
||||
public static function getBase()
|
||||
{
|
||||
$base = env('BASE_PATH', '/');
|
||||
if($base == '') {
|
||||
if ($base == '') {
|
||||
$base = '/';
|
||||
} else {
|
||||
if($base[0] != '/') {
|
||||
if ($base[0] != '/') {
|
||||
$base = '/' . $base;
|
||||
}
|
||||
if($base[-1] != '/') {
|
||||
if ($base[-1] != '/') {
|
||||
$base = $base . '/';
|
||||
}
|
||||
}
|
||||
@@ -90,7 +96,7 @@ class SettingsHelper {
|
||||
// Try exact key
|
||||
$val = exec('echo $' . $key);
|
||||
|
||||
if($val == "") {
|
||||
if ($val == "") {
|
||||
array_push($results, true);
|
||||
} else {
|
||||
array_push($results, false);
|
||||
@@ -99,25 +105,25 @@ class SettingsHelper {
|
||||
// Try key all caps
|
||||
$val = exec('echo $' . strtoupper($key));
|
||||
|
||||
if($val == "") {
|
||||
if ($val == "") {
|
||||
array_push($results, true);
|
||||
} else {
|
||||
array_push($results, false);
|
||||
}
|
||||
|
||||
if(env($key, false) == false) {
|
||||
if (env($key, false) == false) {
|
||||
array_push($results, true);
|
||||
} else {
|
||||
array_push($results, false);
|
||||
}
|
||||
|
||||
if(env(strtoupper($key), false) == false) {
|
||||
if (env(strtoupper($key), false) == false) {
|
||||
array_push($results, true);
|
||||
} else {
|
||||
array_push($results, false);
|
||||
}
|
||||
|
||||
if(in_array(false, $results)) {
|
||||
if (in_array(false, $results)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -133,6 +139,11 @@ class SettingsHelper {
|
||||
{
|
||||
return [
|
||||
'base' => SettingsHelper::getBase(),
|
||||
'widgets' => [
|
||||
'show_average' => (bool)SettingsHelper::get('show_average')->value,
|
||||
'show_max' => (bool)SettingsHelper::get('show_max')->value,
|
||||
'show_min' => (bool)SettingsHelper::get('show_min')->value,
|
||||
],
|
||||
'graphs' => [
|
||||
'download_upload_graph_enabled' => SettingsHelper::get('download_upload_graph_enabled'),
|
||||
'download_upload_graph_width' => SettingsHelper::get('download_upload_graph_width'),
|
||||
@@ -158,15 +169,15 @@ class SettingsHelper {
|
||||
*/
|
||||
public static function testNotification($agent = true)
|
||||
{
|
||||
$agents = [ 'slack', 'telegram' ];
|
||||
$agents = ['slack', 'telegram'];
|
||||
|
||||
if($agent === true) {
|
||||
if ($agent === true) {
|
||||
event(new TestNotificationEvent($agents));
|
||||
return true;
|
||||
}
|
||||
|
||||
if(in_array($agent, $agents)) {
|
||||
event(new TestNotificationEvent([ $agent ]));
|
||||
if (in_array($agent, $agents)) {
|
||||
event(new TestNotificationEvent([$agent]));
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -183,14 +194,14 @@ class SettingsHelper {
|
||||
'telegram_chat_id' => SettingsHelper::get('telegram_chat_id')->value,
|
||||
];
|
||||
|
||||
foreach($settings as $key => $value) {
|
||||
foreach ($settings as $key => $value) {
|
||||
$key = 'integrations.' . $key;
|
||||
|
||||
if($value === "") {
|
||||
if ($value === "") {
|
||||
$value = null;
|
||||
}
|
||||
|
||||
config()->set([ $key => $value ]);
|
||||
config()->set([$key => $value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ class SpeedtestController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
if((bool)SettingsHelper::get('auth')->value === true) {
|
||||
if ((bool)SettingsHelper::get('auth')->value === true) {
|
||||
$this->middleware('auth:api')
|
||||
->only([ 'run', 'delete', 'deleteAll' ]);
|
||||
->only(['run', 'delete', 'deleteAll']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class SpeedtestController extends Controller
|
||||
public function index()
|
||||
{
|
||||
$data = Speedtest::orderBy('created_at', 'desc')
|
||||
->paginate();
|
||||
->paginate();
|
||||
|
||||
return response()->json([
|
||||
'method' => 'index of speedtests',
|
||||
@@ -49,12 +49,12 @@ class SpeedtestController extends Controller
|
||||
public function time($days)
|
||||
{
|
||||
$rule = [
|
||||
'days' => [ 'required', 'integer' ],
|
||||
'days' => ['required', 'integer'],
|
||||
];
|
||||
|
||||
$validator = Validator::make([ 'days' => $days ], $rule);
|
||||
$validator = Validator::make(['days' => $days], $rule);
|
||||
|
||||
if($validator->fails()) {
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'method' => 'get speedtests in last x days',
|
||||
'error' => $validator->errors(),
|
||||
@@ -63,10 +63,18 @@ class SpeedtestController extends Controller
|
||||
|
||||
$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();
|
||||
->where('failed', false)
|
||||
->orderBy('created_at', 'asc')
|
||||
->get();
|
||||
});
|
||||
|
||||
return response()->json([
|
||||
@@ -85,12 +93,12 @@ class SpeedtestController extends Controller
|
||||
public function fail($days)
|
||||
{
|
||||
$rule = [
|
||||
'days' => [ 'required', 'integer' ],
|
||||
'days' => ['required', 'integer'],
|
||||
];
|
||||
|
||||
$validator = Validator::make([ 'days' => $days ], $rule);
|
||||
$validator = Validator::make(['days' => $days], $rule);
|
||||
|
||||
if($validator->fails()) {
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'method' => 'get speedtests in last x days',
|
||||
'error' => $validator->errors(),
|
||||
@@ -114,20 +122,38 @@ class SpeedtestController extends Controller
|
||||
public function latest()
|
||||
{
|
||||
$data = SpeedtestHelper::latest();
|
||||
$avg = Speedtest::select(DB::raw('AVG(ping) as ping, AVG(download) as download, AVG(upload) as upload'))
|
||||
->where('failed', false)
|
||||
->get();
|
||||
$max = Speedtest::select(DB::raw('MAX(ping) as ping, MAX(download) as download, MAX(upload) as upload'))
|
||||
->where('failed', false)
|
||||
->get();
|
||||
|
||||
if($data) {
|
||||
return response()->json([
|
||||
'method' => 'get latest speedtest',
|
||||
'data' => $data,
|
||||
'average' => $avg[0],
|
||||
'max' => $max[0],
|
||||
], 200);
|
||||
$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_average')) {
|
||||
$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);
|
||||
} else {
|
||||
return response()->json([
|
||||
'method' => 'get latest speedtest',
|
||||
@@ -150,7 +176,7 @@ class SpeedtestController extends Controller
|
||||
'method' => 'run speedtest',
|
||||
'data' => 'a new speedtest has been added to the queue'
|
||||
], 200);
|
||||
} catch(Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
return response()->json([
|
||||
'method' => 'run speedtest',
|
||||
'error' => $e
|
||||
@@ -167,7 +193,7 @@ class SpeedtestController extends Controller
|
||||
{
|
||||
$ret = SpeedtestHelper::deleteAll();
|
||||
|
||||
if($ret['success']) {
|
||||
if ($ret['success']) {
|
||||
return response()->json([
|
||||
'method' => 'delete all speedtests from the database',
|
||||
'success' => true
|
||||
|
||||
@@ -1,4 +1,38 @@
|
||||
{
|
||||
"1.9.7": [
|
||||
{
|
||||
"description": "Added option to display minimum values on the top widgets.",
|
||||
"link": ""
|
||||
},
|
||||
{
|
||||
"description": "New general settings section.",
|
||||
"link": ""
|
||||
},
|
||||
{
|
||||
"description": "Updated dependencies.",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.9.6": [
|
||||
{
|
||||
"description": "Clear the cache on restore.",
|
||||
"link": ""
|
||||
},
|
||||
{
|
||||
"description": "Updated dependencies.",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.9.5": [
|
||||
{
|
||||
"description": "Added toggle to show failed tests on graphs.",
|
||||
"link": ""
|
||||
},
|
||||
{
|
||||
"description": "Updated dependencies.",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.9.4": [
|
||||
{
|
||||
"description": "Changed integration config loading.",
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^7.0",
|
||||
"henrywhitaker3/healthchecks-io": "^1.0",
|
||||
"laravel-notification-channels/telegram": "^0.4.0",
|
||||
"laravel-notification-channels/telegram": "^0.5.0",
|
||||
"laravel/framework": "^7.0",
|
||||
"laravel/slack-notification-channel": "^2.0",
|
||||
"laravel/tinker": "^2.0",
|
||||
|
||||
591
composer.lock
generated
591
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,7 @@ return [
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
'version' => '1.9.4',
|
||||
'version' => '1.9.7',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use App\Helpers\SettingsHelper;
|
||||
use App\Setting;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddShowFailedTestsSetting extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if(!SettingsHelper::get('show_failed_tests_on_graph')) {
|
||||
Setting::create([
|
||||
'name' => 'show_failed_tests_on_graph',
|
||||
'value' => true,
|
||||
'description' => 'If enabled, failed tests will appear on the graphs as 0.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Setting::whereIn('name', [
|
||||
'show_failed_tests_on_graph',
|
||||
])->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
use App\Helpers\SettingsHelper;
|
||||
use App\Setting;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddWidgetCardSettings extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (!SettingsHelper::get('show_average')) {
|
||||
Setting::create([
|
||||
'name' => 'show_average',
|
||||
'value' => true,
|
||||
'description' => 'If enabled, the average value for speedtests will be shown in the widgets.'
|
||||
]);
|
||||
}
|
||||
|
||||
if (!SettingsHelper::get('show_max')) {
|
||||
Setting::create([
|
||||
'name' => 'show_max',
|
||||
'value' => true,
|
||||
'description' => 'If enabled, the maximum value for speedtests will be shown in the widgets.'
|
||||
]);
|
||||
}
|
||||
|
||||
if (!SettingsHelper::get('show_min')) {
|
||||
Setting::create([
|
||||
'name' => 'show_min',
|
||||
'value' => true,
|
||||
'description' => 'If enabled, the minimum value for speedtests will be shown in the widgets.'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Setting::whereIn('name', [
|
||||
'show_average',
|
||||
'show_max',
|
||||
'show_min',
|
||||
])->delete();
|
||||
}
|
||||
}
|
||||
50
package-lock.json
generated
50
package-lock.json
generated
@@ -6401,9 +6401,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"klona": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/klona/-/klona-1.1.2.tgz",
|
||||
"integrity": "sha512-xf88rTeHiXk+XE2Vhi6yj8Wm3gMZrygGdKjJqN8HkV+PwF/t50/LdAKHoHpPcxFAlmQszTZ1CugrK25S7qDRLA==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/klona/-/klona-2.0.3.tgz",
|
||||
"integrity": "sha512-CgPOT3ZadDpXxKcfV56lEQ9OQSZ42Mk26gnozI+uN/k39vzD8toUhRQoqsX0m9Q3eMPEfsLWmtyUpK/yqST4yg==",
|
||||
"dev": true
|
||||
},
|
||||
"laravel-mix": {
|
||||
@@ -9032,29 +9032,23 @@
|
||||
}
|
||||
},
|
||||
"sass-loader": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-9.0.3.tgz",
|
||||
"integrity": "sha512-fOwsP98ac1VMme+V3+o0HaaMHp8Q/C9P+MUazLFVi3Jl7ORGHQXL1XeRZt3zLSGZQQPC8xE42Y2WptItvGjDQg==",
|
||||
"version": "10.0.2",
|
||||
"resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-10.0.2.tgz",
|
||||
"integrity": "sha512-wV6NDUVB8/iEYMalV/+139+vl2LaRFlZGEd5/xmdcdzQcgmis+npyco6NsDTVOlNA3y2NV9Gcz+vHyFMIT+ffg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"klona": "^1.1.2",
|
||||
"klona": "^2.0.3",
|
||||
"loader-utils": "^2.0.0",
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^2.7.0",
|
||||
"schema-utils": "^2.7.1",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.12.3",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz",
|
||||
"integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
"ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
@@ -9067,21 +9061,15 @@
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"neo-async": {
|
||||
"version": "2.6.2",
|
||||
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||
"dev": true
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
|
||||
"integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
"@types/json-schema": "^7.0.5",
|
||||
"ajv": "^6.12.4",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
"react-dom": "^16.2.0",
|
||||
"resolve-url-loader": "^3.1.0",
|
||||
"sass": "^1.26.10",
|
||||
"sass-loader": "^9.0.3"
|
||||
"sass-loader": "^10.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
|
||||
2
public/js/app.js
vendored
2
public/js/app.js
vendored
File diff suppressed because one or more lines are too long
@@ -131,9 +131,7 @@ export default class LatestResults extends Component {
|
||||
>
|
||||
<Widget
|
||||
title="Ping"
|
||||
value={parseFloat(data.data.ping).toFixed(1)}
|
||||
avg={parseFloat(data.average.ping).toFixed(1)}
|
||||
max={parseFloat(data.max.ping).toFixed(1)}
|
||||
data={data}
|
||||
failed={data.data.failed}
|
||||
unit="ms"
|
||||
icon="ping"
|
||||
@@ -147,9 +145,7 @@ export default class LatestResults extends Component {
|
||||
>
|
||||
<Widget
|
||||
title="Download"
|
||||
value={parseFloat(data.data.download).toFixed(1)}
|
||||
avg={parseFloat(data.average.download).toFixed(1)}
|
||||
max={parseFloat(data.max.download).toFixed(1)}
|
||||
data={data}
|
||||
failed={data.data.failed}
|
||||
unit="Mbit/s"
|
||||
icon="dl"
|
||||
@@ -163,9 +159,7 @@ export default class LatestResults extends Component {
|
||||
>
|
||||
<Widget
|
||||
title="Upload"
|
||||
value={parseFloat(data.data.upload).toFixed(1)}
|
||||
avg={parseFloat(data.average.upload).toFixed(1)}
|
||||
max={parseFloat(data.max.upload).toFixed(1)}
|
||||
data={data}
|
||||
failed={data.data.failed}
|
||||
unit="Mbit/s"
|
||||
icon="ul"
|
||||
|
||||
101
resources/js/components/Graphics/Widget.js
vendored
101
resources/js/components/Graphics/Widget.js
vendored
@@ -8,38 +8,88 @@ export default class Widget extends Component {
|
||||
|
||||
this.state = {
|
||||
title: this.props.title,
|
||||
value: this.props.value,
|
||||
unit: this.props.unit,
|
||||
icon: this.props.icon,
|
||||
avg: this.props.avg,
|
||||
max: this.props.max,
|
||||
failed: this.props.failed,
|
||||
data: this.props.data
|
||||
}
|
||||
}
|
||||
|
||||
parseData(title, data) {
|
||||
var returnData = {};
|
||||
|
||||
|
||||
if(title == 'Ping') {
|
||||
returnData.value = parseFloat(data.data.ping).toFixed(1);
|
||||
|
||||
if(window.config.widgets.show_average) {
|
||||
returnData.avg = parseFloat(data.average.ping).toFixed(1);
|
||||
}
|
||||
|
||||
if(window.config.widgets.show_max) {
|
||||
returnData.max = parseFloat(data.maximum.ping).toFixed(1);
|
||||
}
|
||||
|
||||
if(window.config.widgets.show_max) {
|
||||
returnData.min = parseFloat(data.minimum.ping).toFixed(1);
|
||||
}
|
||||
}
|
||||
|
||||
if(title == 'Upload') {
|
||||
returnData.value = parseFloat(data.data.upload).toFixed(1);
|
||||
|
||||
if(window.config.widgets.show_average) {
|
||||
returnData.avg = parseFloat(data.average.upload).toFixed(1);
|
||||
}
|
||||
|
||||
if(window.config.widgets.show_max) {
|
||||
returnData.max = parseFloat(data.maximum.upload).toFixed(1);
|
||||
}
|
||||
|
||||
if(window.config.widgets.show_max) {
|
||||
returnData.min = parseFloat(data.minimum.upload).toFixed(1);
|
||||
}
|
||||
}
|
||||
|
||||
if(title == 'Download') {
|
||||
returnData.value = parseFloat(data.data.download).toFixed(1);
|
||||
|
||||
if(window.config.widgets.show_average) {
|
||||
returnData.avg = parseFloat(data.average.download).toFixed(1);
|
||||
}
|
||||
|
||||
if(window.config.widgets.show_max) {
|
||||
returnData.max = parseFloat(data.maximum.download).toFixed(1);
|
||||
}
|
||||
|
||||
if(window.config.widgets.show_max) {
|
||||
returnData.min = parseFloat(data.minimum.download).toFixed(1);
|
||||
}
|
||||
}
|
||||
|
||||
return returnData;
|
||||
}
|
||||
|
||||
componentDidUpdate = () => {
|
||||
if(this.props.title != this.state.title || this.props.value != this.state.value || this.props.unit != this.state.unit || this.props.icon != this.state.icon || this.props.avg != this.state.avg || this.props.max != this.state.max || this.props.failed != this.state.failed) {
|
||||
if(this.props.title != this.state.title || this.props.data != this.state.data || this.props.unit != this.state.unit || this.props.icon != this.state.icon || this.props.failed != this.state.failed) {
|
||||
this.setState({
|
||||
title: this.props.title,
|
||||
value: this.props.value,
|
||||
unit: this.props.unit,
|
||||
icon: this.props.icon,
|
||||
avg: this.props.avg,
|
||||
max: this.props.max,
|
||||
failed: this.props.failed,
|
||||
data: this.props.data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
var title = this.state.title;
|
||||
var value = this.state.value;
|
||||
var unit = this.state.unit;
|
||||
var icon = this.state.icon;
|
||||
var max = this.state.max;
|
||||
var avg = this.state.avg;
|
||||
var failed = Boolean(Number(this.state.failed));
|
||||
|
||||
var data = this.parseData(title, this.state.data);
|
||||
|
||||
switch(icon) {
|
||||
case 'ping':
|
||||
icon = <span className="ti-pulse icon text-success"></span>;
|
||||
@@ -63,17 +113,30 @@ export default class Widget extends Component {
|
||||
</div>
|
||||
|
||||
<div className="text-truncate">
|
||||
<h3 className="d-inline">{(!failed) ? value : <span className="ti-close text-danger"></span> }</h3>
|
||||
<h3 className="d-inline">{(!failed) ? data.value : <span className="ti-close text-danger"></span> }</h3>
|
||||
<p className="d-inline ml-2">{unit} (current)</p>
|
||||
</div>
|
||||
<div className="text-muted text-truncate">
|
||||
<h5 className="d-inline">{avg}</h5>
|
||||
<p className="d-inline ml-2">{unit} (average)</p>
|
||||
</div>
|
||||
<div className="text-muted text-truncate">
|
||||
<h5 className="d-inline">{max}</h5>
|
||||
<p className="d-inline ml-2">{unit} (maximum)</p>
|
||||
</div>
|
||||
|
||||
{window.config.widgets.show_average &&
|
||||
<div className="text-muted text-truncate">
|
||||
<h5 className="d-inline">{data.avg}</h5>
|
||||
<p className="d-inline ml-2">{unit} (average)</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
{window.config.widgets.show_max &&
|
||||
<div className="text-muted text-truncate">
|
||||
<h5 className="d-inline">{data.max}</h5>
|
||||
<p className="d-inline ml-2">{unit} (maximum)</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
{window.config.widgets.show_min &&
|
||||
<div className="text-muted text-truncate">
|
||||
<h5 className="d-inline">{data.min}</h5>
|
||||
<p className="d-inline ml-2">{unit} (minimum)</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Card.Body>
|
||||
|
||||
@@ -47,6 +47,10 @@ export default class SettingWithModal extends Component {
|
||||
if(this.state.autoClose) {
|
||||
this.toggleShow();
|
||||
}
|
||||
Axios.get('api/settings/config')
|
||||
.then((resp) => {
|
||||
window.config = resp.data;
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
if(err.response.status == 422) {
|
||||
@@ -194,7 +198,7 @@ export default class SettingWithModal extends Component {
|
||||
</Col>
|
||||
{e.description == null &&
|
||||
<Col md={md} sm={sm}>
|
||||
<p>{e.obj.description}</p>
|
||||
<p dangerouslySetInnerHTML={{ __html: e.obj.description}}></p>
|
||||
</Col>
|
||||
}
|
||||
</Row>
|
||||
|
||||
30
resources/js/components/Settings/Settings.js
vendored
30
resources/js/components/Settings/Settings.js
vendored
@@ -59,10 +59,28 @@ export default class Settings extends Component {
|
||||
return (
|
||||
<Row>
|
||||
<Col lg={{ span: 4 }} md={{ span: 6 }} sm={{ span: 12 }}>
|
||||
<Setting name={e.schedule.name} value={e.schedule.value} description={e.schedule.description} />
|
||||
</Col>
|
||||
<Col lg={{ span: 4 }} md={{ span: 6 }} sm={{ span: 12 }}>
|
||||
<Setting name={e.server.name} value={e.server.value} description={e.server.description} />
|
||||
<SettingWithModal title="General settings" description="Configure general settings for the app." autoClose={true} settings={[
|
||||
{
|
||||
obj: e.schedule,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
obj: e.server,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
obj: e.show_average,
|
||||
type: 'checkbox'
|
||||
},
|
||||
{
|
||||
obj: e.show_max,
|
||||
type: 'checkbox'
|
||||
},
|
||||
{
|
||||
obj: e.show_min,
|
||||
type: 'checkbox'
|
||||
},
|
||||
]} />
|
||||
</Col>
|
||||
<Col lg={{ span: 4 }} md={{ span: 6 }} sm={{ span: 12 }}>
|
||||
<SettingWithModal title="Graph settings" description="Control settings for the graphs." autoClose={true} settings={[
|
||||
@@ -119,6 +137,10 @@ export default class Settings extends Component {
|
||||
'value': 6
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
obj: e.show_failed_tests_on_graph,
|
||||
type: 'checkbox'
|
||||
}
|
||||
]} />
|
||||
</Col>
|
||||
|
||||
@@ -27,13 +27,13 @@ class APISpeedtestTest extends TestCase
|
||||
$dl = [];
|
||||
$ul = [];
|
||||
|
||||
for($i = 0; $i < 3; $i++) {
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$pingVal = $faker->randomFloat();
|
||||
array_push($ping,$pingVal);
|
||||
array_push($ping, $pingVal);
|
||||
$dlVal = $faker->randomFloat();
|
||||
array_push($dl,$dlVal);
|
||||
array_push($dl, $dlVal);
|
||||
$ulVal = $faker->randomFloat();
|
||||
array_push($ul,$ulVal);
|
||||
array_push($ul, $ulVal);
|
||||
|
||||
Speedtest::create([
|
||||
'ping' => $pingVal,
|
||||
@@ -70,7 +70,12 @@ class APISpeedtestTest extends TestCase
|
||||
'download',
|
||||
'upload',
|
||||
],
|
||||
'max' => [
|
||||
'maximum' => [
|
||||
'ping',
|
||||
'download',
|
||||
'upload',
|
||||
],
|
||||
'minimum' => [
|
||||
'ping',
|
||||
'download',
|
||||
'upload',
|
||||
|
||||
@@ -19,7 +19,7 @@ class LatestTest extends TestCase
|
||||
*/
|
||||
private $controller;
|
||||
|
||||
public function setUp() : void
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
@@ -53,6 +53,7 @@ class LatestTest extends TestCase
|
||||
|
||||
$this->assertArrayHasKey('data', $resp);
|
||||
$this->assertArrayHasKey('average', $resp);
|
||||
$this->assertArrayHasKey('max', $resp);
|
||||
$this->assertArrayHasKey('maximum', $resp);
|
||||
$this->assertArrayHasKey('minimum', $resp);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user