Compare commits

..

53 Commits

Author SHA1 Message Date
Henry Whitaker
e863a53b77 Add ziggy things 2021-11-15 23:26:58 +00:00
Henry Whitaker
5bdb7490a4 fix inertia things 2021-11-15 23:14:01 +00:00
Henry Whitaker
68c1ccb23a controller things 2021-11-15 22:35:04 +00:00
Henry Whitaker
b3af5629dd added in inertia things 2021-11-15 22:34:54 +00:00
Henry Whitaker
c9748af04f Remove all react things 2021-11-15 22:00:35 +00:00
Henry Whitaker
3955a2dad0 Merge pull request #705 from henrywhitaker3/fix/failure-graph
Fix bug where failure graph would not update
2021-09-11 09:31:06 +01:00
Henry Whitaker
6905a5aac0 Fix bug where failure graph would not update 2021-09-11 09:29:21 +01:00
Henry Whitaker
b3ba6a8b06 Merge remote-tracking branch 'origin/dev' into dev 2021-09-10 20:18:22 +01:00
Henry Whitaker
1e1a2c9b69 Use uname for arch download instead of env
from #683
2021-09-10 20:18:14 +01:00
Henry Whitaker
1c00beed86 Merge pull request #704 from henrywhitaker3/alpha
Docker image build step
2021-09-10 20:04:17 +01:00
Henry Whitaker
603f618720 Add for master also 2021-09-10 20:03:12 +01:00
Henry Whitaker
fa41bdd40d Move to dev 2021-09-10 20:01:30 +01:00
Henry Whitaker
9d2f1858a0 Fix the things 2021-09-10 19:53:36 +01:00
Henry Whitaker
6c6a12bb5f ugh 2021-09-10 19:51:34 +01:00
Henry Whitaker
5885824c54 Fix secret name 2021-09-10 19:49:26 +01:00
Henry Whitaker
ea78415157 Fix branch selector 2021-09-10 19:47:32 +01:00
Henry Whitaker
efecd28afb Add build step to workflow 2021-09-10 19:47:08 +01:00
Henry Whitaker
8d32d06768 Merge remote-tracking branch 'origin/master' into alpha 2021-09-10 19:26:31 +01:00
Henry Whitaker
8cb2e8a323 Merge pull request #591 from henrywhitaker3/dependabot/add-v2-config-file
Upgrade to GitHub-native Dependabot
2021-09-10 19:25:50 +01:00
Henry Whitaker
58e82dc9a8 Actually pass days to the action 2021-09-10 18:58:02 +01:00
Henry Whitaker
0dff534dfd Also do the arm dockerfile 2021-09-10 18:54:46 +01:00
Henry Whitaker
fba69514b2 undo dumb thing 2021-09-10 18:50:40 +01:00
Henry Whitaker
1fcaa795cd Chmod +x the artsan file 2021-09-10 18:48:36 +01:00
Henry Whitaker
858dcc1f7b Merge pull request #685 from henrywhitaker3/fix/680/last-x-days
#680: Fix issue where the data would refresh before the react state had updated
2021-09-10 18:41:40 +01:00
Henry Whitaker
283de67e17 Merge pull request #686 from henrywhitaker3/feat/sort-out-dockerfiles
Feat/sort out dockerfiles
2021-09-10 18:40:35 +01:00
Henry Whitaker
d92dd33b84 Update install type 2021-09-10 18:38:50 +01:00
Henry Whitaker
0c8e074a86 Fix the eula stuff 2021-09-10 18:38:26 +01:00
Henry Whitaker
0384f61f15 Added dockerfiles to the main branches 2021-09-10 18:30:50 +01:00
Henry Whitaker
98e037bf97 Update version #s 2021-09-10 18:20:03 +01:00
Henry Whitaker
0b1645cda0 Fix issue where the data would refresh before the react state had updated 2021-09-10 18:16:17 +01:00
Henry Whitaker
370c73e1a5 Merge pull request #671 from henrywhitaker3/dev 2021-08-28 18:33:10 +01:00
Henry Whitaker
9fb573e785 Fix workflows 2021-08-28 18:31:53 +01:00
dependabot-preview[bot]
f6bb77ce71 Upgrade to GitHub-native Dependabot 2021-04-28 22:18:14 +00:00
Henry Whitaker
219c5d6b7a Merge pull request #563 from henrywhitaker3/alpha
Add InfluxDB integration
2021-04-11 10:35:08 +01:00
Henry Whitaker
ce549b5b7a Try to stop chrome form autofilling 2021-04-11 10:31:28 +01:00
Henry Whitaker
a962865867 Add db field to settings page 2021-04-11 09:40:30 +01:00
Henry Whitaker
48fbbc3713 Added version 1 fields to settings page 2021-04-11 09:13:13 +01:00
Henry Whitaker
f809f816c1 Used a mock ooklatester in tests
Speed up test suite
2021-04-11 08:46:01 +01:00
Henry Whitaker
91b7ec7fab Added helm chart to readme 2021-04-10 23:48:29 +01:00
Henry Whitaker
5dbc6b900a Added username/password fields 2021-04-10 23:43:35 +01:00
Henry Whitaker
4dc3e5b09c Move influx interface into interfaces folder 2021-04-10 22:57:11 +01:00
Henry Whitaker
7cf8dfc0b2 Moved auth models into right folder 2021-04-10 22:55:19 +01:00
Henry Whitaker
99c64af482 Move models into Models dir 2021-04-10 22:32:51 +01:00
Henry Whitaker
0b8ccfd8d3 Added speedtest seeder 2021-04-10 22:27:31 +01:00
Henry Whitaker
7b16f1ab09 Remove influx console command 2021-04-10 22:08:07 +01:00
Henry Whitaker
845359726d Formatting 2021-04-10 22:02:27 +01:00
Henry Whitaker
4e6049b7ec Remove logging 2021-04-10 21:58:41 +01:00
Henry Whitaker
5db1106c37 Adds support for influxdb version 1 2021-04-10 21:57:31 +01:00
Henry Whitaker
cb5abcfa98 Merge branch 'alpha' into dev 2021-04-10 16:35:20 +01:00
Henry Whitaker
76e21bfe9d Remove clockwork 2021-04-10 16:33:00 +01:00
Henry Whitaker
492b5df6ae Auth on delete 2021-04-10 13:51:56 +01:00
Henry Whitaker
f92c040f63 Fix itny bugs 2021-04-10 13:48:03 +01:00
Henry Whitaker
2ed811e949 Merge pull request #520 from henrywhitaker3/dev 2021-03-07 11:03:27 +00:00
147 changed files with 215852 additions and 158872 deletions

19
.dockerignore Normal file
View File

@@ -0,0 +1,19 @@
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.phpunit.result.cache
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.vscode/
_ide_helper.php
.idea
.config
reports/

79
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,79 @@
version: 2
updates:
- package-ecosystem: composer
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
target-branch: dev
ignore:
- dependency-name: laravel/framework
versions:
- 7.30.3
- 8.36.2
- 8.37.0
- 8.38.0
- dependency-name: doctrine/dbal
versions:
- 2.13.0
- 3.0.0
- dependency-name: phpunit/phpunit
versions:
- 9.5.1
- 9.5.3
- dependency-name: facade/ignition
versions:
- 2.5.10
- 2.5.11
- 2.5.12
- 2.5.13
- 2.5.9
- dependency-name: laravel/tinker
versions:
- 2.6.0
- dependency-name: nunomaduro/collision
versions:
- 5.3.0
- dependency-name: nunomaduro/larastan
versions:
- 0.6.13
- package-ecosystem: npm
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10
target-branch: dev
ignore:
- dependency-name: chart.js
versions:
- 3.1.0
- 3.1.1
- dependency-name: laravel-mix
versions:
- 6.0.16
- dependency-name: y18n
versions:
- 4.0.1
- 4.0.2
- dependency-name: react-dom
versions:
- 17.0.2
- dependency-name: react
versions:
- 17.0.2
- dependency-name: react-bootstrap
versions:
- 1.4.3
- 1.5.0
- dependency-name: sass
versions:
- 1.32.5
- 1.32.6
- 1.32.7
- dependency-name: "@babel/plugin-proposal-class-properties"
versions:
- 7.12.13
- dependency-name: react-toastify
versions:
- 7.0.1
- 7.0.2

View File

@@ -1,59 +1,29 @@
name: Dev
name: Build Dev Image
on:
push:
branches: [ dev ]
jobs:
laravel-tests:
build:
runs-on: ubuntu-latest
steps:
- uses: shivammathur/setup-php@b7d1d9c9a92d8d8463ce36d7f60da34d461724f8
with:
php-version: '7.4'
- uses: actions/checkout@v2
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Update .env with secrets
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
HEALTHCHECKS_UUID: ${{ secrets.HEALTHCHECKS_UUID }}
run: |
echo SLACK_WEBHOOK=$SLACK_WEBHOOK >> .env
echo TELEGRAM_BOT_TOKEN=$TELEGRAM_BOT_TOKEN >> .env
echo TELEGRAM_CHAT_ID=$TELEGRAM_CHAT_ID >> .env
echo HEALTHCHECKS_UUID=$HEALTHCHECKS_UUID >> .env
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Create Database
run: |
mkdir -p database
touch database/database.sqlite
- name: Generate key
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: php artisan key:generate
- name: Generate JWT key
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: php artisan jwt:secret
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Download Speedtest binary
run: wget https://bintray.com/ookla/download/download_file?file_path=ookla-speedtest-1.0.0-x86_64-linux.tgz -O speedtest.tgz && tar zxvf speedtest.tgz && mv speedtest app/Bin/
- name: Accept EULA
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: php artisan speedtest:eula
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: vendor/bin/phpunit
- name: Checkout
uses: actions/checkout@v2
- name: Docker Setup Buildx
uses: docker/setup-buildx-action@v1.2.0
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./docker/Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: henrywhitaker3/speedtest-tracker:dev,henrywhitaker3/speedtest-tracker:dev-arm

29
.github/workflows/laravel-master.yml vendored Normal file
View File

@@ -0,0 +1,29 @@
name: Build Latest Image
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Docker Setup Buildx
uses: docker/setup-buildx-action@v1.2.0
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v2
with:
context: .
file: ./docker/Dockerfile
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: henrywhitaker3/speedtest-tracker:latest,henrywhitaker3/speedtest-tracker:latest-arm

View File

@@ -46,7 +46,7 @@ jobs:
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Download Speedtest binary
run: wget https://bintray.com/ookla/download/download_file?file_path=ookla-speedtest-1.0.0-x86_64-linux.tgz -O speedtest.tgz && tar zxvf speedtest.tgz && mv speedtest app/Bin/
run: wget https://install.speedtest.net/app/cli/ookla-speedtest-1.0.0-x86_64-linux.tgz -O speedtest.tgz && tar zxvf speedtest.tgz && mv speedtest app/Bin/
- name: Accept EULA
env:
DB_CONNECTION: sqlite

View File

@@ -1,59 +0,0 @@
name: Stable
on:
push:
branches: [ master ]
jobs:
laravel-tests:
runs-on: ubuntu-latest
steps:
- uses: shivammathur/setup-php@b7d1d9c9a92d8d8463ce36d7f60da34d461724f8
with:
php-version: '7.4'
- uses: actions/checkout@v2
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Update .env with secrets
env:
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
HEALTHCHECKS_UUID: ${{ secrets.HEALTHCHECKS_UUID }}
run: |
echo SLACK_WEBHOOK=$SLACK_WEBHOOK >> .env
echo TELEGRAM_BOT_TOKEN=$TELEGRAM_BOT_TOKEN >> .env
echo TELEGRAM_CHAT_ID=$TELEGRAM_CHAT_ID >> .env
echo HEALTHCHECKS_UUID=$HEALTHCHECKS_UUID >> .env
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Create Database
run: |
mkdir -p database
touch database/database.sqlite
- name: Generate key
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: php artisan key:generate
- name: Generate JWT key
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: php artisan jwt:secret
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Download Speedtest binary
run: wget https://bintray.com/ookla/download/download_file?file_path=ookla-speedtest-1.0.0-x86_64-linux.tgz -O speedtest.tgz && tar zxvf speedtest.tgz && mv speedtest app/Bin/
- name: Accept EULA
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: php artisan speedtest:eula
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: vendor/bin/phpunit

View File

@@ -1,6 +1,6 @@
# Speedtest Tracker
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker?style=flat-square)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/henrywhitaker3/Speedtest-Tracker/Stable?label=master&logo=github&style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/henrywhitaker3/Speedtest-Tracker/Dev?label=dev&logo=github&style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [![commit_freq](https://img.shields.io/github/commit-activity/m/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) ![version](https://img.shields.io/badge/version-v1.11.1-success?style=flat-square) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
[![Docker pulls](https://img.shields.io/docker/pulls/henrywhitaker3/speedtest-tracker?style=flat-square)](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/henrywhitaker3/Speedtest-Tracker/Stable?label=master&logo=github&style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/henrywhitaker3/Speedtest-Tracker/Dev?label=dev&logo=github&style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/actions) [![last_commit](https://img.shields.io/github/last-commit/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [![issues](https://img.shields.io/github/issues/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [![commit_freq](https://img.shields.io/github/commit-activity/m/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) ![version](https://img.shields.io/badge/version-v1.12.2-success?style=flat-square) [![license](https://img.shields.io/github/license/henrywhitaker3/Speedtest-Tracker?style=flat-square)](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses the [Ookla's speedtest cli](https://www.speedtest.net/apps/cli) package to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.
@@ -18,6 +18,7 @@ Disclaimer: You will need to accept Ookla's EULA and privacy agreements in order
- Slack/Discord/Telegram notifications
- [healthchecks.io](https://healthchecks.io) integration
- Organizr integration
- InfluxDB integration (currently v1 only, v2 is a WIP)
## Installation & Setup
@@ -84,6 +85,8 @@ Container images are configured using parameters passed at runtime (such as thos
| `-e PUID` | Optional. Supply a local user ID for volume permissions |
| `-e PGID` | Optional. Supply a local group ID for volume permissions |
| `-e AUTH` | Optional. Set to 'true' to enable authentication for the app |
| `-e INFLUXDB_RETENTION`| Optional. Sets the InfluxDB retention period, defaults to `30d` |
| `-e INFLUXDB_HOST_TAG | Optional. Sets the InfluxDB host tag value, defaults to `speedtest` |
### Authentication
@@ -101,3 +104,7 @@ After enabling, you should change the password through the web UI.
### Manual Install
For manual installations, please follow the instructions [here](https://github.com/henrywhitaker3/Speedtest-Tracker/wiki/Manual-Installation).
### Kubernetes
There is a 3rd party helm chart available [here](https://github.com/sOblivionsCall/charts).

View File

@@ -2,7 +2,7 @@
namespace App\Actions;
use App\Speedtest;
use App\Models\Speedtest;
use Cache;
use Carbon\Carbon;
use DB;

View File

@@ -4,7 +4,7 @@ namespace App\Actions;
use App\Helpers\SettingsHelper;
use App\Helpers\SpeedtestHelper;
use App\Speedtest;
use App\Models\Speedtest;
use DB;
use Henrywhitaker3\LaravelActions\Interfaces\ActionInterface;

View File

@@ -3,7 +3,7 @@
namespace App\Actions;
use App\Helpers\SettingsHelper;
use App\Speedtest;
use App\Models\Speedtest;
use Cache;
use Carbon\Carbon;
use Henrywhitaker3\LaravelActions\Interfaces\ActionInterface;

View File

@@ -39,6 +39,6 @@ class AcceptEULACommand extends Command
public function handle()
{
$this->info('Acceping EULA');
shell_exec(config('speedtest.home') . ' && timeout 3s ' . app_path() . '/Bin/speedtest --accept-license --accept-gdpr');
shell_exec(config('speedtest.home') . ' && timeout 10s ' . app_path() . '/Bin/speedtest --accept-license --accept-gdpr');
}
}

View File

@@ -2,7 +2,7 @@
namespace App\Console\Commands;
use App\Auth\LoginSession;
use App\Models\Auth\LoginSession;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Log;

View File

@@ -2,6 +2,7 @@
namespace App\Console\Commands;
use App\Exceptions\SpeedtestFailureException;
use App\Helpers\SpeedtestHelper;
use App\Interfaces\SpeedtestProvider;
use Illuminate\Console\Command;
@@ -45,9 +46,9 @@ class SpeedtestCommand extends Command
{
$this->info('Running speedtest, this might take a while...');
$results = $this->speedtestProvider->run(false, false);
if (!is_object($results)) {
try {
$results = $this->speedtestProvider->run(false, false);
} catch (SpeedtestFailureException $e) {
$this->error('Something went wrong running the speedtest.');
exit();
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class InfluxDBConnectionErrorException extends Exception
{
//
}

View File

@@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class InfluxDBNotEnabledException extends Exception
{
//
}

View File

@@ -2,7 +2,7 @@
namespace App\Helpers;
use App\Speedtest;
use App\Models\Speedtest;
use Cache;
use DateTime;
use Exception;

View File

@@ -2,8 +2,8 @@
namespace App\Helpers;
use App\Auth\EmailVerification;
use App\User;
use App\Models\Auth\EmailVerification;
use App\Models\User;
class EmailVerificationHelper {
public static function checkVerificationAttempt($userID, $token)

View File

@@ -3,7 +3,7 @@
namespace App\Helpers;
use App\Events\TestNotificationEvent;
use App\Setting;
use App\Models\Setting;
use Cache;
use Carbon\Carbon;

View File

@@ -2,7 +2,8 @@
namespace App\Helpers;
use App\Speedtest;
use App\Interfaces\SpeedtestProvider;
use App\Models\Speedtest;
use App\Utils\OoklaTester;
use Carbon\Carbon;
use Exception;
@@ -25,7 +26,7 @@ class SpeedtestHelper
*/
public static function runSpeedtest()
{
$tester = new OoklaTester();
$tester = app()->make(SpeedtestProvider::class);
return $tester->run();
}

View File

@@ -2,14 +2,14 @@
namespace App\Http\Controllers;
use App\Auth\EmailVerification;
use App\Auth\LoginSession as AuthLoginSession;
use App\Models\Auth\EmailVerification;
use App\Models\Auth\LoginSession as AuthLoginSession;
use App\Helpers\EmailVerificationHelper;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;
use App\LoginSession;
use App\Rules\CurrentPasswordMatches;
use App\User;
use App\Models\User;
use DateTime;
use Hash;
use Illuminate\Support\Facades\Request as RequestFacade;

View File

@@ -27,8 +27,8 @@ class HomepageDataController extends Controller
return [
'latest' => run(GetLatestSpeedtestData::class),
'time' => run(GetSpeedtestTimeData::class),
'fail' => run(GetFailedSpeedtestData::class),
'time' => run(GetSpeedtestTimeData::class, $days),
'fail' => run(GetFailedSpeedtestData::class, $days),
'config' => SettingsHelper::getConfig(),
];
}

View File

@@ -15,7 +15,7 @@ class IntegrationsController extends Controller
{
public function __construct()
{
if((bool)SettingsHelper::get('auth')->value === true) {
if ((bool)SettingsHelper::get('auth')->value === true) {
$this->middleware('auth:api');
}
}
@@ -31,15 +31,15 @@ class IntegrationsController extends Controller
try {
// SettingsHelper::loadIntegrationConfig();
if($method == 'success') {
if ($method == 'success') {
Healthcheck::success();
}
if($method == 'fail') {
if ($method == 'fail') {
Healthcheck::fail();
}
if($method == 'start') {
if ($method == 'start') {
Healthcheck::start();
}
@@ -47,19 +47,19 @@ class IntegrationsController extends Controller
'method' => $methodResp,
'success' => true
], 200);
} catch(InvalidUuidStringException $e) {
} catch (InvalidUuidStringException $e) {
return response()->json([
'method' => $methodResp,
'success' => false,
'error' => 'Invalid UUID'
], 422);
} catch(HealthchecksUuidNotFoundException $e) {
} catch (HealthchecksUuidNotFoundException $e) {
return response()->json([
'method' => $methodResp,
'success' => false,
'error' => 'UUID not found'
], 404);
} catch(Exception $e) {
} catch (Exception $e) {
return response()->json([
'method' => $methodResp,
'success' => false,

View File

@@ -4,7 +4,7 @@ namespace App\Http\Controllers;
use App\Helpers\SettingsHelper;
use App\Rules\Cron;
use App\Setting;
use App\Models\Setting;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;

View File

@@ -9,7 +9,7 @@ use App\Actions\QueueSpeedtest;
use App\Helpers\SettingsHelper;
use App\Helpers\SpeedtestHelper;
use App\Jobs\SpeedtestJob;
use App\Speedtest;
use App\Models\Speedtest;
use Carbon\Carbon;
use Exception;
use Illuminate\Http\Request;

View File

@@ -36,6 +36,7 @@ class Kernel extends HttpKernel
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
\App\Http\Middleware\HandleInertiaRequests::class,
],
'api' => [

View File

@@ -2,7 +2,7 @@
namespace App\Http\Middleware;
use App\Auth\LoginSession;
use App\Models\Auth\LoginSession;
use Closure;
use Exception;

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Http\Middleware;
use Illuminate\Http\Request;
use Inertia\Middleware;
class HandleInertiaRequests extends Middleware
{
/**
* The root template that's loaded on the first page visit.
*
* @see https://inertiajs.com/server-side-setup#root-template
* @var string
*/
protected $rootView = 'app';
/**
* Determines the current asset version.
*
* @see https://inertiajs.com/asset-versioning
* @param \Illuminate\Http\Request $request
* @return string|null
*/
public function version(Request $request)
{
return parent::version($request);
}
/**
* Defines the props that are shared by default.
*
* @see https://inertiajs.com/shared-data
* @param \Illuminate\Http\Request $request
* @return array
*/
public function share(Request $request)
{
return array_merge(parent::share($request), [
//
]);
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Interfaces;
use App\Models\Speedtest;
interface InfluxDBWrapperInterface
{
public function testConnection(): bool;
public function doesDatabaseExist(string $database): bool;
public function createDatabase(string $database): bool;
public function store(Speedtest $speedtest): bool;
}

View File

@@ -2,7 +2,7 @@
namespace App\Interfaces;
use App\Speedtest;
use App\Models\Speedtest;
interface SpeedtestProvider
{

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Auth;
namespace App\Models\Auth;
use Illuminate\Database\Eloquent\Model;

View File

@@ -1,6 +1,6 @@
<?php
namespace App\Auth;
namespace App\Models\Auth;
use Illuminate\Database\Eloquent\Model;

View File

@@ -1,6 +1,6 @@
<?php
namespace App;
namespace App\Models;
use App\Casts\CommaSeparatedArrayCast;
use App\Helpers\SettingsHelper;

45
app/Models/Speedtest.php Normal file
View File

@@ -0,0 +1,45 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Speedtest extends Model
{
use HasFactory;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'ping',
'download',
'upload',
'created_at',
'server_id',
'server_name',
'server_host',
'url',
'scheduled',
'failed',
];
protected $table = 'speedtests';
public function formatForInfluxDB()
{
return [
'id' => (int) $this->id,
'download' => (float) $this->download,
'upload' => (float) $this->upload,
'ping' => (float) $this->ping,
'server_id' => (int) $this->server_id,
'server_host' => $this->server_host,
'server_name' => $this->server_name,
'scheduled' => (bool) $this->scheduled,
];
}
}

View File

@@ -1,6 +1,6 @@
<?php
namespace App;
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
@@ -107,7 +107,7 @@ class User extends Authenticatable implements JWTSubject
public function setPasswordAttribute($password)
{
if ( !empty($password) ) {
if (!empty($password)) {
$this->attributes['password'] = Hash::make($password);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace App\Observers;
use App\Exceptions\InfluxDBNotEnabledException;
use App\Models\Speedtest;
use App\Utils\InfluxDB\InfluxDB;
use Exception;
use Log;
class SpeedtestObserver
{
/**
* Handle the Speedtest "created" event.
*
* @param \App\Speedtest $speedtest
* @return void
*/
public function created(Speedtest $speedtest)
{
try {
InfluxDB::connect()
->store($speedtest);
} catch (InfluxDBNotEnabledException $e) {
// /
} catch (Exception $e) {
Log::error($e);
}
}
}

View File

@@ -10,6 +10,8 @@ use App\Listeners\SpeedtestCompleteListener;
use App\Listeners\SpeedtestFailedListener;
use App\Listeners\SpeedtestOverviewListener;
use App\Listeners\TestNotificationListener;
use App\Observers\SpeedtestObserver;
use App\Models\Speedtest;
use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
@@ -49,6 +51,6 @@ class EventServiceProvider extends ServiceProvider
{
parent::boot();
//
Speedtest::observe(SpeedtestObserver::class);
}
}

View File

@@ -1,28 +0,0 @@
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Speedtest extends Model
{
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'ping',
'download',
'upload',
'created_at',
'server_id',
'server_name',
'server_host',
'url',
'scheduled',
'failed',
];
protected $table = 'speedtests';
}

View File

@@ -0,0 +1,113 @@
<?php
namespace App\Utils\InfluxDB;
use App\Exceptions\InfluxDBConnectionErrorException;
use App\Exceptions\InfluxDBNotEnabledException;
use App\Helpers\SettingsHelper;
use App\Models\Speedtest;
use InfluxDB\Client as Version1;
use InfluxDB2\Client as Version2;
use App\Interfaces\InfluxDBWrapperInterface as Client;
class InfluxDB
{
private Client $client;
private string $database;
public function __construct(Client $client)
{
$this->client = $client;
}
/**
* Connect to influx db
*
* @param string $host
* @param integer $port
* @param string $database
* @return InfluxDB
*/
public static function connect()
{
if (!(bool) SettingsHelper::get('influx_db_enabled')->value) {
throw new InfluxDBNotEnabledException();
}
$host = SettingsHelper::get('influx_db_host')->value;
$port = SettingsHelper::get('influx_db_port')->value;
$username = SettingsHelper::get('influx_db_username')->value;
$password = SettingsHelper::get('influx_db_password')->value;
$database = SettingsHelper::get('influx_db_database')->value;
$version = (int) SettingsHelper::get('influx_db_version')->value;
$wrapper = $version === 1
? new InfluxDBVersion1Wrapper(
new Version1(
str_replace(['http://', 'https://'], '', $host),
$port,
$username,
$password
)
)
: new InfluxDBVersion2Wrapper(
new Version2([
'url' => $host . ':' . $port,
'token' => '',
])
);
return (new self($wrapper))->setDatabase($database)
->testConnection();
}
/**
* Set the database field
*
* @param string $database
* @return InfluxDB
*/
public function setDatabase(string $database): InfluxDB
{
$this->database = $database;
return $this;
}
/**
* Test the connection
*
* @throws InfluxDBConnectionErrorException
* @return InfluxDB
*/
public function testConnection(): InfluxDB
{
if (!$this->client->testConnection()) {
throw new InfluxDBConnectionErrorException();
}
if (!$this->doesDatabaseExist()) {
$this->createDatabase();
}
return $this;
}
public function doesDatabaseExist(): bool
{
return $this->client->doesDatabaseExist($this->database);
}
public function createDatabase(): bool
{
return $this->client->createDatabase($this->database);
}
public function store(Speedtest $speedtest): InfluxDB
{
$this->client->store($speedtest);
return $this;
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Utils\InfluxDB;
use App\Interfaces\InfluxDBWrapperInterface;
use App\Models\Speedtest;
use Exception;
use InfluxDB\Client;
use InfluxDB\Database;
use InfluxDB\Database\RetentionPolicy;
use InfluxDB\Point;
use Log;
class InfluxDBVersion1Wrapper implements InfluxDBWrapperInterface
{
private Client $client;
private Database $database;
public function __construct(Client $client)
{
$this->client = $client;
}
public function testConnection(): bool
{
try {
$this->client->listDatabases();
return true;
} catch (Exception $e) {
Log::error($e);
return false;
}
}
public function doesDatabaseExist(string $database): bool
{
$this->database = $this->client->selectDB($database);
return (bool) $this->database->exists();
}
public function createDatabase(string $database): bool
{
try {
$this->database->create(
new RetentionPolicy(
'speedtest_retention_policy',
config('services.influxdb.retention'),
1,
true
)
);
return true;
} catch (Exception $e) {
Log::error($e);
return false;
}
}
public function store(Speedtest $speedtest): bool
{
return $this->database->writePoints([
new Point(
'speedtest',
null,
['host' => config('services.influxdb.host')],
$speedtest->formatForInfluxDB(),
)
]);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Utils\InfluxDB;
use App\Interfaces\InfluxDBWrapperInterface;
use App\Models\Speedtest;
use InfluxDB2\Client;
class InfluxDBVersion2Wrapper implements InfluxDBWrapperInterface
{
private Client $client;
public function __construct(Client $client)
{
$this->client = $client;
}
public function testConnection(): bool
{
return $this->client->health()->getStatus() !== 'pass';
}
public function doesDatabaseExist(string $database): bool
{
return true;
}
public function createDatabase(string $database): bool
{
return true;
}
public function store(Speedtest $speedtest): bool
{
return true;
}
}

View File

@@ -6,7 +6,7 @@ use App\Exceptions\SpeedtestFailureException;
use App\Helpers\SettingsHelper;
use App\Helpers\SpeedtestHelper;
use App\Interfaces\SpeedtestProvider;
use App\Speedtest;
use App\Models\Speedtest;
use Cache;
use Exception;
use JsonException;

View File

@@ -1,4 +1,22 @@
{
"1.12.2": [
{
"description": "Fixed a bug where the latest X days widget would not update for the failure graph",
"link": ""
}
],
"1.12.1": [
{
"description": "Fixed a bug where the latest X days widget would not update (#680)",
"link": "https://github.com/henrywhitaker3/Speedtest-Tracker/pull/680"
}
],
"1.12.0": [
{
"description": "Added InfluxDB intergation.",
"link": ""
}
],
"1.11.1": [
{
"description": "Add option to show/hide columns in the all tests table.",

View File

@@ -16,11 +16,15 @@
"guzzlehttp/guzzle": "^7.0.1",
"henrywhitaker3/healthchecks-io": "^1.0",
"henrywhitaker3/laravel-actions": "^1.0",
"inertiajs/inertia-laravel": "^0.4.5",
"influxdata/influxdb-client-php": "^1.12",
"influxdb/influxdb-php": "^1.15",
"laravel-notification-channels/telegram": "^0.5.0",
"laravel/framework": "^8.0",
"laravel/slack-notification-channel": "^2.0",
"laravel/tinker": "^2.0",
"laravel/ui": "^3.0",
"tightenco/ziggy": "^1.4",
"tymon/jwt-auth": "^1.0"
},
"require-dev": {
@@ -28,7 +32,6 @@
"brianium/paratest": "^6.2",
"facade/ignition": "^2.3.6",
"fzaninotto/faker": "^1.9.1",
"itsgoingd/clockwork": "^5.0",
"mockery/mockery": "^1.3.1",
"nunomaduro/collision": "^5.3",
"nunomaduro/larastan": "^0.7.0",
@@ -46,12 +49,10 @@
},
"autoload": {
"psr-4": {
"App\\": "app/"
},
"classmap": [
"database/seeds",
"database/factories"
]
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
}
},
"autoload-dev": {
"psr-4": {

317
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "102adc5121f97ad3ad15009f410fb8fa",
"content-hash": "3175378417ef75a6f3d9fa7e5834f9b0",
"packages": [
{
"name": "asm89/stack-cors",
@@ -1309,6 +1309,186 @@
},
"time": "2021-02-06T09:50:49+00:00"
},
{
"name": "inertiajs/inertia-laravel",
"version": "v0.4.5",
"source": {
"type": "git",
"url": "https://github.com/inertiajs/inertia-laravel.git",
"reference": "406b15af162e78be5c7793b25aadd5a183eea84b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/inertiajs/inertia-laravel/zipball/406b15af162e78be5c7793b25aadd5a183eea84b",
"reference": "406b15af162e78be5c7793b25aadd5a183eea84b",
"shasum": ""
},
"require": {
"ext-json": "*",
"laravel/framework": "^5.4|^6.0|^7.0|^8.0",
"php": "^7.2|^8.0"
},
"require-dev": {
"orchestra/testbench": "^4.0|^5.0|^6.0",
"phpunit/phpunit": "^8.0|^9.0",
"roave/security-advisories": "dev-master"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Inertia\\ServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Inertia\\": "src"
},
"files": [
"./helpers.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jonathan Reinink",
"email": "jonathan@reinink.ca",
"homepage": "https://reinink.ca"
}
],
"description": "The Laravel adapter for Inertia.js.",
"keywords": [
"inertia",
"laravel"
],
"support": {
"issues": "https://github.com/inertiajs/inertia-laravel/issues",
"source": "https://github.com/inertiajs/inertia-laravel/tree/v0.4.5"
},
"funding": [
{
"url": "https://github.com/reinink",
"type": "github"
}
],
"time": "2021-10-27T09:37:59+00:00"
},
{
"name": "influxdata/influxdb-client-php",
"version": "1.12.0",
"source": {
"type": "git",
"url": "https://github.com/influxdata/influxdb-client-php.git",
"reference": "e04f802a4d9c52b5b497077673269e8463fdb6ea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/influxdata/influxdb-client-php/zipball/e04f802a4d9c52b5b497077673269e8463fdb6ea",
"reference": "e04f802a4d9c52b5b497077673269e8463fdb6ea",
"shasum": ""
},
"require": {
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"guzzlehttp/guzzle": "^6.2|^7.0.1",
"php": ">=7.1"
},
"require-dev": {
"phpunit/phpunit": "^7.4|^9.1",
"squizlabs/php_codesniffer": "~2.6"
},
"type": "library",
"autoload": {
"psr-4": {
"InfluxDB2\\": "src/InfluxDB2"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "InfluxDB (v2+) Client Library for PHP",
"homepage": "https://www.github.com/influxdata/influxdb-client-php",
"keywords": [
"influxdb"
],
"support": {
"issues": "https://github.com/influxdata/influxdb-client-php/issues",
"source": "https://github.com/influxdata/influxdb-client-php/tree/1.12.0"
},
"time": "2021-04-01T06:28:57+00:00"
},
{
"name": "influxdb/influxdb-php",
"version": "1.15.2",
"source": {
"type": "git",
"url": "https://github.com/influxdata/influxdb-php.git",
"reference": "d6e59f4f04ab9107574fda69c2cbe36671253d03"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/influxdata/influxdb-php/zipball/d6e59f4f04ab9107574fda69c2cbe36671253d03",
"reference": "d6e59f4f04ab9107574fda69c2cbe36671253d03",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0|^7.0",
"php": "^5.5 || ^7.0 || ^8.0"
},
"require-dev": {
"dms/phpunit-arraysubset-asserts": "^0.2.1",
"phpunit/phpunit": "^9.5"
},
"suggest": {
"ext-curl": "Curl extension, needed for Curl driver",
"stefanotorresi/influxdb-php-async": "An asyncronous client for InfluxDB, implemented via ReactPHP."
},
"type": "library",
"autoload": {
"psr-4": {
"InfluxDB\\": "src/InfluxDB"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Stephen Hoogendijk",
"email": "stephen@tca0.nl"
},
{
"name": "Daniel Martinez",
"email": "danimartcas@hotmail.com"
},
{
"name": "Gianluca Arbezzano",
"email": "gianarb92@gmail.com"
}
],
"description": "InfluxDB client library for PHP",
"keywords": [
"client",
"influxdata",
"influxdb",
"influxdb class",
"influxdb client",
"influxdb library",
"time series"
],
"support": {
"issues": "https://github.com/influxdata/influxdb-php/issues",
"source": "https://github.com/influxdata/influxdb-php/tree/1.15.2"
},
"time": "2020-12-26T17:45:17+00:00"
},
{
"name": "laravel-notification-channels/telegram",
"version": "0.5.1",
@@ -5449,6 +5629,72 @@
],
"time": "2021-02-18T23:11:19+00:00"
},
{
"name": "tightenco/ziggy",
"version": "v1.4.2",
"source": {
"type": "git",
"url": "https://github.com/tighten/ziggy.git",
"reference": "620c135281062b9f6b53a75b07f99a4339267277"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/tighten/ziggy/zipball/620c135281062b9f6b53a75b07f99a4339267277",
"reference": "620c135281062b9f6b53a75b07f99a4339267277",
"shasum": ""
},
"require": {
"laravel/framework": ">=5.4@dev"
},
"require-dev": {
"orchestra/testbench": "^6.0",
"phpunit/phpunit": "^9.2"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Tightenco\\Ziggy\\ZiggyServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Tightenco\\Ziggy\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Daniel Coulbourne",
"email": "daniel@tighten.co"
},
{
"name": "Jake Bathman",
"email": "jake@tighten.co"
},
{
"name": "Jacob Baker-Kretzmar",
"email": "jacob@tighten.co"
}
],
"description": "Generates a Blade directive exporting all of your named Laravel routes. Also provides a nice route() helper function in JavaScript.",
"homepage": "https://github.com/tighten/ziggy",
"keywords": [
"Ziggy",
"javascript",
"laravel",
"routes"
],
"support": {
"issues": "https://github.com/tighten/ziggy/issues",
"source": "https://github.com/tighten/ziggy/tree/v1.4.2"
},
"time": "2021-10-01T13:55:26+00:00"
},
{
"name": "tijsverkoyen/css-to-inline-styles",
"version": "2.2.3",
@@ -6850,75 +7096,6 @@
},
"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",

View File

@@ -62,7 +62,7 @@ return [
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
'model' => App\Models\User::class,
],
// 'users' => [

View File

@@ -34,4 +34,9 @@ return [
'token' => env('TELEGRAM_BOT_TOKEN', 'YOUR BOT TOKEN HERE')
],
'influxdb' => [
'retention' => env('INFLUXDB_RETENTION', '30d'),
'host' => env('INFLUXDB_HOST_TAG', 'speedtest'),
],
];

View File

@@ -7,7 +7,7 @@ return [
|--------------------------------------------------------------------------
*/
'version' => '1.11.1',
'version' => '1.12.2',
/*
|--------------------------------------------------------------------------
@@ -15,7 +15,7 @@ return [
|--------------------------------------------------------------------------
*/
'install' => 'manual',
'install' => 'docker',
/*
|--------------------------------------------------------------------------
@@ -23,7 +23,7 @@ return [
|--------------------------------------------------------------------------
*/
'home' => 'HOME=' . base_path() . DIRECTORY_SEPARATOR,
'home' => 'HOME=/config',
/*
|--------------------------------------------------------------------------

View File

@@ -0,0 +1,32 @@
<?php
namespace Database\Factories;
use App\Models\Speedtest;
use Illuminate\Database\Eloquent\Factories\Factory;
class SpeedtestFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Speedtest::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'download' => rand(15, 900),
'upload' => rand(15, 900),
'ping' => rand(1, 25),
'scheduled' => (bool) rand(0, 1),
'failed' => false,
];
}
}

View File

@@ -2,7 +2,7 @@
/** @var \Illuminate\Database\Eloquent\Factory $factory */
use App\User;
use App\Models\User;
use Faker\Generator as Faker;
use Illuminate\Support\Str;

View File

@@ -1,6 +1,6 @@
<?php
use App\User;
use App\Models\User;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,6 +1,6 @@
<?php
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,6 +1,6 @@
<?php
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,6 +1,6 @@
<?php
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -1,7 +1,7 @@
<?php
use App\Helpers\SettingsHelper;
use App\Setting;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

View File

@@ -0,0 +1,92 @@
<?php
use App\Helpers\SettingsHelper;
use App\Models\Setting;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddInfluxDbSettings extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
if (!SettingsHelper::get('influx_db_enabled')) {
Setting::create([
'name' => 'influx_db_enabled',
'value' => false,
'description' => 'Enable the InfluxDB integration for speedtests.'
]);
}
if (!SettingsHelper::get('influx_db_host')) {
Setting::create([
'name' => 'influx_db_host',
'value' => '',
'description' => 'InfluxDB hostname, include the protocol (http:// or https://).'
]);
}
if (!SettingsHelper::get('influx_db_port')) {
Setting::create([
'name' => 'influx_db_port',
'value' => '',
'description' => 'InfluxDB port'
]);
}
if (!SettingsHelper::get('influx_db_database')) {
Setting::create([
'name' => 'influx_db_database',
'value' => '',
'description' => 'InfluxDB database'
]);
}
if (!SettingsHelper::get('influx_db_username')) {
Setting::create([
'name' => 'influx_db_username',
'value' => '',
'description' => 'InfluxDB username'
]);
}
if (!SettingsHelper::get('influx_db_password')) {
Setting::create([
'name' => 'influx_db_password',
'value' => '',
'description' => 'InfluxDB password'
]);
}
if (!SettingsHelper::get('influx_db_version')) {
Setting::create([
'name' => 'influx_db_version',
'value' => 1,
'description' => 'InfluxDB version'
]);
}
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Setting::whereIn('name', [
'influx_db_enabled',
'influx_db_host',
'influx_db_port',
'influx_db_database',
'influx_db_username',
'influx_db_password',
'influx_db_version',
])->delete();
}
}

View File

@@ -1,5 +1,8 @@
<?php
namespace Database\Seeders;
use Database\Seeders\SpeedtestSeeder;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
@@ -11,6 +14,6 @@ class DatabaseSeeder extends Seeder
*/
public function run()
{
// $this->call(UserSeeder::class);
$this->call(SpeedtestSeeder::class);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Database\Seeders;
use App\Models\Speedtest;
use Illuminate\Database\Seeder;
class SpeedtestSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
Speedtest::factory()
->count(250)
->create();
}
}

Binary file not shown.

11
docker/Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM linuxserver/nginx
LABEL maintainer=henrywhitaker3@outlook.com
ENV arch='x86_64'
COPY docker/conf/ /
COPY . /site
EXPOSE 80 443
VOLUME ["/config"]

11
docker/arm.Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM linuxserver/nginx:arm32v7-latest
LABEL maintainer=henrywhitaker3@outlook.com
ENV arch='arm'
COPY docker/conf/ /
COPY . /site
EXPOSE 80 443
VOLUME ["/config"]

View File

@@ -0,0 +1,10 @@
# do daily/weekly/monthly maintenance
# min hour day month weekday command
*/15 * * * * run-parts /etc/periodic/15min
0 * * * * run-parts /etc/periodic/hourly
0 2 * * * run-parts /etc/periodic/daily
0 3 * * 6 run-parts /etc/periodic/weekly
0 5 1 * * run-parts /etc/periodic/monthly
# speedtest cron
* * * * * php /config/www/artisan schedule:run >> /config/log/speedtest/cron.log
# */5 * * * * php /config/www/artisan queue:retry all >> /config/log/speedtest.cron.log

View File

@@ -0,0 +1,30 @@
server {
listen 80 default_server;
listen 443 ssl;
root /config/www/public;
index index.html index.htm index.php;
server_name _;
ssl_certificate /config/keys/cert.crt;
ssl_certificate_key /config/keys/cert.key;
client_max_body_size 0;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# With php5-cgi alone:
fastcgi_pass 127.0.0.1:9000;
# With php5-fpm:
#fastcgi_pass unix:/var/run/php5-fpm.sock;
fastcgi_index index.php;
include /etc/nginx/fastcgi_params;
}
}

View File

@@ -0,0 +1,145 @@
#!/usr/bin/with-contenv bash
# # This script sets up the speedtest app
function eulaError()
{
echo "##################################################################################################################################"
echo "##################################################################################################################################"
echo "You haven't accepted the Ookla EULA. Please re-create the container with the environment variable 'OOKLA_EULA_GDPR' set to 'true'."
echo "##################################################################################################################################"
echo "##################################################################################################################################"
exit 1
}
# Do Ookla stuff
if [ -z ${OOKLA_EULA_GDPR+x} ]; then
eulaError
else
if [ $OOKLA_EULA_GDPR != "true" ]; then
eulaError
fi
if [ ! -f /config/www/app/Bin/speedtest ]; then
echo "Ookla GDPR and EULA accepted. Downloading Speedtest CLI."
cd /tmp
if [ $(uname -m) == "x86_64" ]; then
wget https://install.speedtest.net/app/cli/ookla-speedtest-1.0.0-x86_64-linux.tgz -O speedtest.tgz > /dev/null
else
wget https://install.speedtest.net/app/cli/ookla-speedtest-1.0.0-arm-linux.tgz -O speedtest.tgz > /dev/null
fi
tar zxvf speedtest.tgz > /dev/null
cp speedtest /site/app/Bin/
chmod +x /site/artisan
HOME=/config && s6-setuidgid abc /site/app/Bin/speedtest --accept-license --accept-gdpr > /dev/null
HOME=/root
else
HOME=/config && s6-setuidgid abc /site/app/Bin/speedtest --accept-license --accept-gdpr > /dev/null
HOME=/root
fi
fi
# Copy site files to /config
echo "Copying latest site files to config"
cp -rfT /site/ /config/www/
# Check for DB
if [ ! -f /config/speed.db ]; then
echo "Database file not found! Creating empty database"
touch /config/speed.db
else
echo "Database file exists"
chown abc:abc /config/speed.db
fi
# Check for .env
if [ ! -f /config/www/.env ]; then
echo "Env file not found! Creating .env file"
cp /site/.env.example /config/www/.env
else
echo "Env file exists"
fi
if [ ! -f /config/www/.composer-time ]; then
echo 'Removing old packages'
rm -rf /config/www/vendor/
fi
echo 'Updating packages'
apk add composer
cd /config/www && composer install && echo date > /config/www/.composer-time
sed "s,DB_DATABASE=.*,DB_DATABASE=/config/speed.db," -i.bak /config/www/.env
echo "Running database migrations"
php /config/www/artisan migrate
# Check app key exists
if grep -E "APP_KEY=[0-9A-Za-z:+\/=]{1,}" /config/www/.env > /dev/null; then
echo "App key exists"
else
echo "Generating app key"
php /config/www/artisan key:generate
fi
# Check JWT secret exists
if grep -E "JWT_SECRET=[0-9A-Za-z:+\/=]{1,}" /config/www/.env > /dev/null ; then
echo "JWT secret exists"
else
echo "Generating JWT secret"
php /config/www/artisan jwt:secret
fi
if [ -z ${SLACK_WEBHOOK+x} ]; then
echo "Slack webhook is unset"
sed "s,SLACK_WEBHOOK=.*,SLACK_WEBHOOK=," -i.bak /config/www/.env
else
echo "Slack webhook set, updating db"
sed "s,SLACK_WEBHOOK=.*,SLACK_WEBHOOK=$SLACK_WEBHOOK," -i.bak /config/www/.env
php /config/www/artisan speedtest:slack $SLACK_WEBHOOK
fi
if [ -z ${TELEGRAM_BOT_TOKEN+x} ] && [ -z ${TELEGRAM_CHAT_ID+x} ]; then
echo "Telegram chat id and bot token unset"
sed "s,TELEGRAM_BOT_TOKEN=.*,TELEGRAM_BOT_TOKEN=," -i.bak /config/www/.env
sed "s,TELEGRAM_CHAT_ID=.*,TELEGRAM_CHAT_ID=," -i.bak /config/www/.env
else
echo "Telegram chat id and bot token set, updating .env"
sed "s,TELEGRAM_BOT_TOKEN=.*,TELEGRAM_BOT_TOKEN=$TELEGRAM_BOT_TOKEN," -i.bak /config/www/.env
sed "s,TELEGRAM_CHAT_ID=.*,TELEGRAM_CHAT_ID=$TELEGRAM_CHAT_ID," -i.bak /config/www/.env
php /config/www/artisan speedtest:telegram --chat=$TELEGRAM_CHAT_ID --bot=$TELEGRAM_BOT_TOKEN
fi
if [ -z ${BASE_PATH+x} ]; then
echo "Base path is unset"
sed "s,BASE_PATH=.*,BASE_PATH=," -i.bak /config/www/.env
else
echo "Base path set, updating .env"
sed "s,BASE_PATH=.*,BASE_PATH=$BASE_PATH," -i.bak /config/www/.env
fi
if [ -z ${AUTH+x} ]; then
echo "AUTH variable not set. Disabling authentication"
php /config/www/artisan speedtest:auth --disable
else
if [ $AUTH == 'true' ]; then
echo "AUTH variable set. Enabling authentication"
php /config/www/artisan speedtest:auth --enable
else
echo "AUTH variable set, but not to 'true'. Disabling authentication"
php /config/www/artisan speedtest:auth --disable
fi
fi
echo "Clearing old jobs from queue"
php /config/www/artisan queue:clear
mkdir -p /config/log/speedtest
cp /defaults/crontab /etc/crontabs/root
chown -R abc:abc /config
chmod +x /config/www/app/Bin/speedtest
chmod -R 777 /config/www/storage/clockwork

View File

@@ -0,0 +1,439 @@
; Start a new pool named 'www'.
; the variable $pool can be used in any directive and will be replaced by the
; pool name ('www' here)
[www]
; Per pool prefix
; It only applies on the following directives:
; - 'access.log'
; - 'slowlog'
; - 'listen' (unixsocket)
; - 'chroot'
; - 'chdir'
; - 'php_values'
; - 'php_admin_values'
; When not set, the global prefix (or /usr) applies instead.
; Note: This directive can also be relative to the global prefix.
; Default Value: none
;prefix = /path/to/pools/$pool
; Unix user/group of processes
; Note: The user is mandatory. If the group is not set, the default user's group
; will be used.
user = abc
group = abc
; The address on which to accept FastCGI requests.
; Valid syntaxes are:
; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on
; a specific port;
; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on
; a specific port;
; 'port' - to listen on a TCP socket to all addresses
; (IPv6 and IPv4-mapped) on a specific port;
; '/path/to/unix/socket' - to listen on a unix socket.
; Note: This value is mandatory.
listen = 127.0.0.1:9000
; Set listen(2) backlog.
; Default Value: 511 (-1 on FreeBSD and OpenBSD)
;listen.backlog = 511
; Set permissions for unix socket, if one is used. In Linux, read/write
; permissions must be set in order to allow connections from a web server. Many
; BSD-derived systems allow connections regardless of permissions. The owner
; and group can be specified either by name or by their numeric IDs.
; Default Values: user and group are set as the running user
; mode is set to 0660
;listen.owner = nobody
;listen.group = abc
;listen.mode = 0660
; When POSIX Access Control Lists are supported you can set them using
; these options, value is a comma separated list of user/group names.
; When set, listen.owner and listen.group are ignored
;listen.acl_users =
;listen.acl_groups =
; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect.
; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original
; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address
; must be separated by a comma. If this value is left blank, connections will be
; accepted from any ip address.
; Default Value: any
;listen.allowed_clients = 127.0.0.1
; Specify the nice(2) priority to apply to the pool processes (only if set)
; The value can vary from -19 (highest priority) to 20 (lower priority)
; Note: - It will only work if the FPM master process is launched as root
; - The pool processes will inherit the master process priority
; unless it specified otherwise
; Default Value: no set
; process.priority = -19
; Set the process dumpable flag (PR_SET_DUMPABLE prctl) even if the process user
; or group is differrent than the master process user. It allows to create process
; core dump and ptrace the process for the pool user.
; Default Value: no
; process.dumpable = yes
; Choose how the process manager will control the number of child processes.
; Possible Values:
; static - a fixed number (pm.max_children) of child processes;
; dynamic - the number of child processes are set dynamically based on the
; following directives. With this process management, there will be
; always at least 1 children.
; pm.max_children - the maximum number of children that can
; be alive at the same time.
; pm.start_servers - the number of children created on startup.
; pm.min_spare_servers - the minimum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is less than this
; number then some children will be created.
; pm.max_spare_servers - the maximum number of children in 'idle'
; state (waiting to process). If the number
; of 'idle' processes is greater than this
; number then some children will be killed.
; ondemand - no children are created at startup. Children will be forked when
; new requests will connect. The following parameter are used:
; pm.max_children - the maximum number of children that
; can be alive at the same time.
; pm.process_idle_timeout - The number of seconds after which
; an idle process will be killed.
; Note: This value is mandatory.
pm = dynamic
; The number of child processes to be created when pm is set to 'static' and the
; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'.
; This value sets the limit on the number of simultaneous requests that will be
; served. Equivalent to the ApacheMaxClients directive with mpm_prefork.
; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP
; CGI. The below defaults are based on a server without much resources. Don't
; forget to tweak pm.* to fit your needs.
; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand'
; Note: This value is mandatory.
pm.max_children = 5
; The number of child processes created on startup.
; Note: Used only when pm is set to 'dynamic'
; Default Value: (min_spare_servers + max_spare_servers) / 2
pm.start_servers = 2
; The desired minimum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.min_spare_servers = 1
; The desired maximum number of idle server processes.
; Note: Used only when pm is set to 'dynamic'
; Note: Mandatory when pm is set to 'dynamic'
pm.max_spare_servers = 3
; The number of seconds after which an idle process will be killed.
; Note: Used only when pm is set to 'ondemand'
; Default Value: 10s
;pm.process_idle_timeout = 10s;
; The number of requests each child process should execute before respawning.
; This can be useful to work around memory leaks in 3rd party libraries. For
; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS.
; Default Value: 0
;pm.max_requests = 500
; The URI to view the FPM status page. If this value is not set, no URI will be
; recognized as a status page. It shows the following informations:
; pool - the name of the pool;
; process manager - static, dynamic or ondemand;
; start time - the date and time FPM has started;
; start since - number of seconds since FPM has started;
; accepted conn - the number of request accepted by the pool;
; listen queue - the number of request in the queue of pending
; connections (see backlog in listen(2));
; max listen queue - the maximum number of requests in the queue
; of pending connections since FPM has started;
; listen queue len - the size of the socket queue of pending connections;
; idle processes - the number of idle processes;
; active processes - the number of active processes;
; total processes - the number of idle + active processes;
; max active processes - the maximum number of active processes since FPM
; has started;
; max children reached - number of times, the process limit has been reached,
; when pm tries to start more children (works only for
; pm 'dynamic' and 'ondemand');
; Value are updated in real time.
; Example output:
; pool: www
; process manager: static
; start time: 01/Jul/2011:17:53:49 +0200
; start since: 62636
; accepted conn: 190460
; listen queue: 0
; max listen queue: 1
; listen queue len: 42
; idle processes: 4
; active processes: 11
; total processes: 15
; max active processes: 12
; max children reached: 0
;
; By default the status page output is formatted as text/plain. Passing either
; 'html', 'xml' or 'json' in the query string will return the corresponding
; output syntax. Example:
; http://www.foo.bar/status
; http://www.foo.bar/status?json
; http://www.foo.bar/status?html
; http://www.foo.bar/status?xml
;
; By default the status page only outputs short status. Passing 'full' in the
; query string will also return status for each pool process.
; Example:
; http://www.foo.bar/status?full
; http://www.foo.bar/status?json&full
; http://www.foo.bar/status?html&full
; http://www.foo.bar/status?xml&full
; The Full status returns for each process:
; pid - the PID of the process;
; state - the state of the process (Idle, Running, ...);
; start time - the date and time the process has started;
; start since - the number of seconds since the process has started;
; requests - the number of requests the process has served;
; request duration - the duration in µs of the requests;
; request method - the request method (GET, POST, ...);
; request URI - the request URI with the query string;
; content length - the content length of the request (only with POST);
; user - the user (PHP_AUTH_USER) (or '-' if not set);
; script - the main script called (or '-' if not set);
; last request cpu - the %cpu the last request consumed
; it's always 0 if the process is not in Idle state
; because CPU calculation is done when the request
; processing has terminated;
; last request memory - the max amount of memory the last request consumed
; it's always 0 if the process is not in Idle state
; because memory calculation is done when the request
; processing has terminated;
; If the process is in Idle state, then informations are related to the
; last request the process has served. Otherwise informations are related to
; the current request being served.
; Example output:
; ************************
; pid: 31330
; state: Running
; start time: 01/Jul/2011:17:53:49 +0200
; start since: 63087
; requests: 12808
; request duration: 1250261
; request method: GET
; request URI: /test_mem.php?N=10000
; content length: 0
; user: -
; script: /home/fat/web/docs/php/test_mem.php
; last request cpu: 0.00
; last request memory: 0
;
; Note: There is a real-time FPM status monitoring sample web page available
; It's available in: /usr/share/php7/fpm/status.html
;
; Note: The value must start with a leading slash (/). The value can be
; anything, but it may not be a good idea to use the .php extension or it
; may conflict with a real PHP file.
; Default Value: not set
;pm.status_path = /status
; The ping URI to call the monitoring page of FPM. If this value is not set, no
; URI will be recognized as a ping page. This could be used to test from outside
; that FPM is alive and responding, or to
; - create a graph of FPM availability (rrd or such);
; - remove a server from a group if it is not responding (load balancing);
; - trigger alerts for the operating team (24/7).
; Note: The value must start with a leading slash (/). The value can be
; anything, but it may not be a good idea to use the .php extension or it
; may conflict with a real PHP file.
; Default Value: not set
;ping.path = /ping
; This directive may be used to customize the response of a ping request. The
; response is formatted as text/plain with a 200 response code.
; Default Value: pong
;ping.response = pong
; The access log file
; Default: not set
;access.log = log/php7/$pool.access.log
; The access log format.
; The following syntax is allowed
; %%: the '%' character
; %C: %CPU used by the request
; it can accept the following format:
; - %{user}C for user CPU only
; - %{system}C for system CPU only
; - %{total}C for user + system CPU (default)
; %d: time taken to serve the request
; it can accept the following format:
; - %{seconds}d (default)
; - %{miliseconds}d
; - %{mili}d
; - %{microseconds}d
; - %{micro}d
; %e: an environment variable (same as $_ENV or $_SERVER)
; it must be associated with embraces to specify the name of the env
; variable. Some exemples:
; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e
; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e
; %f: script filename
; %l: content-length of the request (for POST request only)
; %m: request method
; %M: peak of memory allocated by PHP
; it can accept the following format:
; - %{bytes}M (default)
; - %{kilobytes}M
; - %{kilo}M
; - %{megabytes}M
; - %{mega}M
; %n: pool name
; %o: output header
; it must be associated with embraces to specify the name of the header:
; - %{Content-Type}o
; - %{X-Powered-By}o
; - %{Transfert-Encoding}o
; - ....
; %p: PID of the child that serviced the request
; %P: PID of the parent of the child that serviced the request
; %q: the query string
; %Q: the '?' character if query string exists
; %r: the request URI (without the query string, see %q and %Q)
; %R: remote IP address
; %s: status (response code)
; %t: server time the request was received
; it can accept a strftime(3) format:
; %d/%b/%Y:%H:%M:%S %z (default)
; The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag
; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
; %T: time the log has been written (the request has finished)
; it can accept a strftime(3) format:
; %d/%b/%Y:%H:%M:%S %z (default)
; The strftime(3) format must be encapsuled in a %{<strftime_format>}t tag
; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t
; %u: remote user
;
; Default: "%R - %u %t \"%m %r\" %s"
;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%"
; The log file for slow requests
; Default Value: not set
; Note: slowlog is mandatory if request_slowlog_timeout is set
;slowlog = log/php7/$pool.slow.log
; The timeout for serving a single request after which a PHP backtrace will be
; dumped to the 'slowlog' file. A value of '0s' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_slowlog_timeout = 0
; Depth of slow log stack trace.
; Default Value: 20
;request_slowlog_trace_depth = 20
; The timeout for serving a single request after which the worker process will
; be killed. This option should be used when the 'max_execution_time' ini option
; does not stop script execution for some reason. A value of '0' means 'off'.
; Available units: s(econds)(default), m(inutes), h(ours), or d(ays)
; Default Value: 0
;request_terminate_timeout = 0
; The timeout set by 'request_terminate_timeout' ini option is not engaged after
; application calls 'fastcgi_finish_request' or when application has finished and
; shutdown functions are being called (registered via register_shutdown_function).
; This option will enable timeout limit to be applied unconditionally
; even in such cases.
; Default Value: no
;request_terminate_timeout_track_finished = no
; Set open file descriptor rlimit.
; Default Value: system defined value
;rlimit_files = 1024
; Set max core size rlimit.
; Possible Values: 'unlimited' or an integer greater or equal to 0
; Default Value: system defined value
;rlimit_core = 0
; Chroot to this directory at the start. This value must be defined as an
; absolute path. When this value is not set, chroot is not used.
; Note: you can prefix with '$prefix' to chroot to the pool prefix or one
; of its subdirectories. If the pool prefix is not set, the global prefix
; will be used instead.
; Note: chrooting is a great security feature and should be used whenever
; possible. However, all PHP paths will be relative to the chroot
; (error_log, sessions.save_path, ...).
; Default Value: not set
;chroot =
; Chdir to this directory at the start.
; Note: relative path can be used.
; Default Value: current directory or / when chroot
;chdir = /var/www
; Redirect worker stdout and stderr into main error log. If not set, stdout and
; stderr will be redirected to /dev/null according to FastCGI specs.
; Note: on highloaded environement, this can cause some delay in the page
; process time (several ms).
; Default Value: no
;catch_workers_output = yes
; Decorate worker output with prefix and suffix containing information about
; the child that writes to the log and if stdout or stderr is used as well as
; log level and time. This options is used only if catch_workers_output is yes.
; Settings to "no" will output data as written to the stdout or stderr.
; Default value: yes
;decorate_workers_output = no
; Clear environment in FPM workers
; Prevents arbitrary environment variables from reaching FPM worker processes
; by clearing the environment in workers before env vars specified in this
; pool configuration are added.
; Setting to "no" will make all environment variables available to PHP code
; via getenv(), $_ENV and $_SERVER.
; Default Value: yes
clear_env = no
; Limits the extensions of the main script FPM will allow to parse. This can
; prevent configuration mistakes on the web server side. You should only limit
; FPM to .php extensions to prevent malicious users to use other extensions to
; execute php code.
; Note: set an empty value to allow all extensions.
; Default Value: .php
;security.limit_extensions = .php .php3 .php4 .php5 .php7
; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from
; the current environment.
; Default Value: clean env
;env[HOSTNAME] = $HOSTNAME
;env[PATH] = /usr/local/bin:/usr/bin:/bin
;env[TMP] = /tmp
;env[TMPDIR] = /tmp
;env[TEMP] = /tmp
; Additional php.ini defines, specific to this pool of workers. These settings
; overwrite the values previously defined in the php.ini. The directives are the
; same as the PHP SAPI:
; php_value/php_flag - you can set classic ini defines which can
; be overwritten from PHP call 'ini_set'.
; php_admin_value/php_admin_flag - these directives won't be overwritten by
; PHP call 'ini_set'
; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no.
; Defining 'extension' will load the corresponding shared extension from
; extension_dir. Defining 'disable_functions' or 'disable_classes' will not
; overwrite previously defined php.ini values, but will append the new value
; instead.
; Note: path INI options can be relative and will be expanded with the prefix
; (pool, global or /usr)
; Default Value: nothing is defined by default except the values in php.ini and
; specified at startup with the -d argument
;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com
;php_flag[display_errors] = off
;php_admin_value[error_log] = /var/log/php7/$pool.error.log
;php_admin_flag[log_errors] = on
;php_admin_value[memory_limit] = 32M

View File

@@ -0,0 +1,3 @@
#!/usr/bin/with-contenv bash
exec s6-setuidgid abc php /config/www/artisan queue:work --timeout=120 >> /config/log/speedtest/queue.log

23621
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,38 +2,32 @@
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
"production": "mix --production"
},
"devDependencies": {
"@babel/preset-react": "^7.12.13",
"autoprefixer": "^10.4.0",
"axios": "^0.21",
"bootstrap": "^4.6.0",
"cross-env": "^7.0",
"jquery": "^3.5",
"laravel-mix": "^5.0.9",
"laravel-mix": "^6.0.27",
"lodash": "^4.17.21",
"popper.js": "^1.12",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"resolve-url-loader": "^3.1.2",
"resolve-url-loader": "^4.0.0",
"sass": "^1.32.8",
"sass-loader": "^10.1.1"
"sass-loader": "^10.1.1",
"tailwindcss": "^2.0.1",
"vue": "^2.5.17",
"vue-loader": "^15.9.8",
"vue-template-compiler": "^2.6.10"
},
"dependencies": {
"@babel/plugin-proposal-class-properties": "^7.13.0",
"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",
"react-router-dom": "^5.2.0",
"react-toastify": "^7.0.3"
"@inertiajs/inertia": "^0.10.1",
"@inertiajs/inertia-vue": "^0.7.2",
"@inertiajs/progress": "^0.2.6",
"dayjs": "^1.10.7",
"laravel-mix-tailwind": "^0.1.2"
}
}

198498
public/css/app.css vendored

File diff suppressed because it is too large Load Diff

146371
public/js/app.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,138 +0,0 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/* @license
Papa Parse
v5.2.0
https://github.com/mholt/PapaParse
License: MIT
*/
/*!
* Bootstrap v4.5.3 (https://getbootstrap.com/)
* Copyright 2011-2020 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
*/
/*!
Copyright (c) 2017 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/
/*!
* Chart.js v2.9.4
* https://www.chartjs.org
* (c) 2020 Chart.js Contributors
* Released under the MIT License
*/
/*!
* JavaScript Cookie v2.2.1
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
/*!
* Sizzle CSS Selector Engine v2.3.5
* https://sizzlejs.com/
*
* Copyright JS Foundation and other contributors
* Released under the MIT license
* https://js.foundation/
*
* Date: 2020-03-14
*/
/*!
* jQuery JavaScript Library v3.5.1
* https://jquery.com/
*
* Includes Sizzle.js
* https://sizzlejs.com/
*
* Copyright JS Foundation and other contributors
* Released under the MIT license
* https://jquery.org/license
*
* Date: 2020-05-04T22:49Z
*/
/**
* @license
* Lodash <https://lodash.com/>
* Copyright OpenJS Foundation and other contributors <https://openjsf.org/>
* Released under MIT license <https://lodash.com/license>
* Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
* Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
*/
/** @license React v0.19.1
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.13.1
* react-is.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.14.0
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v16.14.0
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**!
* @fileOverview Kickass library to create and place poppers near their reference elements.
* @version 1.16.1
* @license
* Copyright (c) 2016 Federico Zivolo and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
//! moment.js
//! moment.js locale configuration

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

227
public/js/resources_js_Pages_Home_vue.js vendored Normal file
View File

@@ -0,0 +1,227 @@
"use strict";
(self["webpackChunk"] = self["webpackChunk"] || []).push([["resources_js_Pages_Home_vue"],{
/***/ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-5[0].rules[0].use[0]!./node_modules/vue-loader/lib/index.js??vue-loader-options!./resources/js/Pages/Home.vue?vue&type=script&lang=js&":
/*!******************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/babel-loader/lib/index.js??clonedRuleSet-5[0].rules[0].use[0]!./node_modules/vue-loader/lib/index.js??vue-loader-options!./resources/js/Pages/Home.vue?vue&type=script&lang=js& ***!
\******************************************************************************************************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
//
//
//
//
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({});
/***/ }),
/***/ "./resources/js/Pages/Home.vue":
/*!*************************************!*\
!*** ./resources/js/Pages/Home.vue ***!
\*************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _Home_vue_vue_type_template_id_6a63e488___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./Home.vue?vue&type=template&id=6a63e488& */ "./resources/js/Pages/Home.vue?vue&type=template&id=6a63e488&");
/* harmony import */ var _Home_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./Home.vue?vue&type=script&lang=js& */ "./resources/js/Pages/Home.vue?vue&type=script&lang=js&");
/* harmony import */ var _node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! !../../../node_modules/vue-loader/lib/runtime/componentNormalizer.js */ "./node_modules/vue-loader/lib/runtime/componentNormalizer.js");
/* normalize component */
;
var component = (0,_node_modules_vue_loader_lib_runtime_componentNormalizer_js__WEBPACK_IMPORTED_MODULE_2__["default"])(
_Home_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_1__["default"],
_Home_vue_vue_type_template_id_6a63e488___WEBPACK_IMPORTED_MODULE_0__.render,
_Home_vue_vue_type_template_id_6a63e488___WEBPACK_IMPORTED_MODULE_0__.staticRenderFns,
false,
null,
null,
null
)
/* hot reload */
if (false) { var api; }
component.options.__file = "resources/js/Pages/Home.vue"
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (component.exports);
/***/ }),
/***/ "./resources/js/Pages/Home.vue?vue&type=script&lang=js&":
/*!**************************************************************!*\
!*** ./resources/js/Pages/Home.vue?vue&type=script&lang=js& ***!
\**************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var _node_modules_babel_loader_lib_index_js_clonedRuleSet_5_0_rules_0_use_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Home_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/babel-loader/lib/index.js??clonedRuleSet-5[0].rules[0].use[0]!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Home.vue?vue&type=script&lang=js& */ "./node_modules/babel-loader/lib/index.js??clonedRuleSet-5[0].rules[0].use[0]!./node_modules/vue-loader/lib/index.js??vue-loader-options!./resources/js/Pages/Home.vue?vue&type=script&lang=js&");
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (_node_modules_babel_loader_lib_index_js_clonedRuleSet_5_0_rules_0_use_0_node_modules_vue_loader_lib_index_js_vue_loader_options_Home_vue_vue_type_script_lang_js___WEBPACK_IMPORTED_MODULE_0__["default"]);
/***/ }),
/***/ "./resources/js/Pages/Home.vue?vue&type=template&id=6a63e488&":
/*!********************************************************************!*\
!*** ./resources/js/Pages/Home.vue?vue&type=template&id=6a63e488& ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "render": () => (/* reexport safe */ _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_Home_vue_vue_type_template_id_6a63e488___WEBPACK_IMPORTED_MODULE_0__.render),
/* harmony export */ "staticRenderFns": () => (/* reexport safe */ _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_Home_vue_vue_type_template_id_6a63e488___WEBPACK_IMPORTED_MODULE_0__.staticRenderFns)
/* harmony export */ });
/* harmony import */ var _node_modules_vue_loader_lib_loaders_templateLoader_js_vue_loader_options_node_modules_vue_loader_lib_index_js_vue_loader_options_Home_vue_vue_type_template_id_6a63e488___WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! -!../../../node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!../../../node_modules/vue-loader/lib/index.js??vue-loader-options!./Home.vue?vue&type=template&id=6a63e488& */ "./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options!./resources/js/Pages/Home.vue?vue&type=template&id=6a63e488&");
/***/ }),
/***/ "./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options!./resources/js/Pages/Home.vue?vue&type=template&id=6a63e488&":
/*!***********************************************************************************************************************************************************************************************************!*\
!*** ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib/index.js??vue-loader-options!./resources/js/Pages/Home.vue?vue&type=template&id=6a63e488& ***!
\***********************************************************************************************************************************************************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "render": () => (/* binding */ render),
/* harmony export */ "staticRenderFns": () => (/* binding */ staticRenderFns)
/* harmony export */ });
var render = function () {
var _vm = this
var _h = _vm.$createElement
var _c = _vm._self._c || _h
return _c("div", [_vm._v("Hello")])
}
var staticRenderFns = []
render._withStripped = true
/***/ }),
/***/ "./node_modules/vue-loader/lib/runtime/componentNormalizer.js":
/*!********************************************************************!*\
!*** ./node_modules/vue-loader/lib/runtime/componentNormalizer.js ***!
\********************************************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (/* binding */ normalizeComponent)
/* harmony export */ });
/* globals __VUE_SSR_CONTEXT__ */
// IMPORTANT: Do NOT use ES2015 features in this file (except for modules).
// This module is a runtime utility for cleaner component module output and will
// be included in the final webpack user bundle.
function normalizeComponent (
scriptExports,
render,
staticRenderFns,
functionalTemplate,
injectStyles,
scopeId,
moduleIdentifier, /* server only */
shadowMode /* vue-cli only */
) {
// Vue.extend constructor export interop
var options = typeof scriptExports === 'function'
? scriptExports.options
: scriptExports
// render functions
if (render) {
options.render = render
options.staticRenderFns = staticRenderFns
options._compiled = true
}
// functional template
if (functionalTemplate) {
options.functional = true
}
// scopedId
if (scopeId) {
options._scopeId = 'data-v-' + scopeId
}
var hook
if (moduleIdentifier) { // server build
hook = function (context) {
// 2.3 injection
context =
context || // cached call
(this.$vnode && this.$vnode.ssrContext) || // stateful
(this.parent && this.parent.$vnode && this.parent.$vnode.ssrContext) // functional
// 2.2 with runInNewContext: true
if (!context && typeof __VUE_SSR_CONTEXT__ !== 'undefined') {
context = __VUE_SSR_CONTEXT__
}
// inject component styles
if (injectStyles) {
injectStyles.call(this, context)
}
// register component module identifier for async chunk inferrence
if (context && context._registeredComponents) {
context._registeredComponents.add(moduleIdentifier)
}
}
// used by ssr in case component is cached and beforeCreate
// never gets called
options._ssrRegister = hook
} else if (injectStyles) {
hook = shadowMode
? function () {
injectStyles.call(
this,
(options.functional ? this.parent : this).$root.$options.shadowRoot
)
}
: injectStyles
}
if (hook) {
if (options.functional) {
// for template-only hot-reload because in that case the render fn doesn't
// go through the normalizer
options._injectStyles = hook
// register for functional component in vue file
var originalRender = options.render
options.render = function renderWithStyleInjection (h, context) {
hook.call(context)
return originalRender(h, context)
}
} else {
// inject component registration as beforeCreate hook
var existing = options.beforeCreate
options.beforeCreate = existing
? [].concat(existing, hook)
: [hook]
}
}
return {
exports: scriptExports,
options: options
}
}
/***/ })
}]);

View File

@@ -1,4 +1,4 @@
{
"/js/app.js": "/js/app.js",
"/css/app.css": "/css/app.css"
"/js/app.js": "/js/app.js?id=3f160e1be04f20c83775",
"/css/app.css": "/css/app.css?id=56fcadeb3d7b8e0e21d6"
}

View File

@@ -0,0 +1,12 @@
<template>
<div>
Hello
<!-- <div>{{ relativeDate('2021-10-15 00:00:00') }}</div> -->
</div>
</template>
<script>
export default {
}
</script>

32
resources/js/app.js vendored
View File

@@ -1,15 +1,23 @@
/**
* First we will load all of this project's JavaScript dependencies which
* includes React and other helpers. It's a great starting point while
* building robust, powerful web applications using React + Laravel.
*/
require("./bootstrap");
require('./bootstrap');
// Import modules...
import Vue from 'vue';
import { createInertiaApp } from '@inertiajs/inertia-vue';
/**
* Next, we will create a fresh React component instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
import { InertiaProgress } from "@inertiajs/progress";
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
require('./index');
Vue.use(InertiaProgress);
createInertiaApp({
resolve: name => require(`./Pages/${name}`),
setup({ el, App, props }) {
new Vue({
render: h => h(App, props),
}).$mount(el)
},
});
InertiaProgress.init({ color: "#4B5563" });

View File

@@ -1,18 +1,5 @@
window._ = require('lodash');
/**
* We'll load jQuery and the Bootstrap jQuery plugin which provides support
* for JavaScript based Bootstrap features such as modals and tabs. This
* code may be modified to fit the specific needs of your application.
*/
try {
window.Popper = require('popper.js').default;
window.$ = window.jQuery = require('jquery');
require('bootstrap');
} catch (e) {}
/**
* We'll load the axios HTTP library which allows us to easily issue requests
* to our Laravel back-end. This library automatically handles sending the
@@ -37,5 +24,5 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
// broadcaster: 'pusher',
// key: process.env.MIX_PUSHER_APP_KEY,
// cluster: process.env.MIX_PUSHER_APP_CLUSTER,
// encrypted: true
// forceTLS: true
// });

View File

@@ -1,70 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Row, Col} from 'react-bootstrap';
import SessionsTable from './SessionsTable';
import ResetPassword from './ResetPassword';
export default class Authentication extends Component {
constructor(props) {
super(props)
this.state = {
showCollapse: false,
showModal: false
}
}
toggleCollapse = () => {
if(this.state.showCollapse) {
this.setState({
showCollapse: false
});
} else {
this.setState({
showCollapse: true
});
}
}
toggleModal = () => {
if(this.state.showModal) {
this.setState({
showModal: false
});
} else {
this.setState({
showModal: true
});
}
}
render() {
var showCollapse = this.state.showCollapse;
var showModal = this.state.showModal;
if( (window.config.auth == true && window.authenticated == true)) {
return (
<div>
<Row>
<Col sm={{ span: 12 }} className="text-center">
<ResetPassword />
</Col>
</Row>
<Row>
<Col sm={{ span: 12 }} className="text-center">
<SessionsTable />
</Col>
</Row>
</div>
);
} else {
return (
<></>
);
}
}
}
if (document.getElementById('Authentication')) {
ReactDOM.render(<Authentication />, document.getElementById('Authentication'));
}

View File

@@ -1,111 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Container, Row, Col, Collapse, Button, Modal, Form } from 'react-bootstrap';
import SessionsTable from './SessionsTable';
import Axios from 'axios';
import { toast } from 'react-toastify';
export default class ResetPassword extends Component {
constructor(props) {
super(props)
this.state = {
showModal: false,
currentPassword: '',
newPassword: '',
newPasswordConfirmation: '',
logoutDevices: false
}
}
toggleModal = () => {
if(this.state.showModal) {
this.setState({
showModal: false
});
} else {
this.setState({
showModal: true
});
}
}
updateTextField = (e) => {
this.setState({
[e.target.id]: e.target.value
})
}
updateCheckbox = (e) => {
this.setState({
[e.target.id]: e.target.checked
});
}
changePassword = (e) => {
e.preventDefault();
var data = {
currentPassword: this.state.currentPassword,
newPassword: this.state.newPassword,
newPassword_confirmation: this.state.newPasswordConfirmation,
logoutDevices: this.state.logoutDevices
}
var url = 'api/auth/change-password?token=' + window.token;
Axios.post(url, data)
.then((resp) => {
toast.success('Password updated');
this.toggleModal();
if(this.state.logoutDevices == true) {
location.reload(true);
}
})
.catch((err) => {
if(err.response) {
for(var key in err.response.data.error) {
toast.error(err.response.data.error[key][0]);
}
}
})
}
render() {
var showModal = this.state.showModal;
return (
<div>
<Button variant="primary" onClick={this.toggleModal} className="mb-3">Change password</Button>
<Modal show={showModal} onHide={this.toggleModal}>
<Modal.Header closeButton>
<Modal.Title>Change password</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form onSubmit={this.changePassword}>
<Form.Group controlId="currentPassword">
<Form.Label>Current password</Form.Label>
<Form.Control type="password" onInput={this.updateTextField} required />
</Form.Group>
<Form.Group controlId="newPassword">
<Form.Label>New Password</Form.Label>
<Form.Control type="password" onInput={this.updateTextField} required />
</Form.Group>
<Form.Group controlId="newPasswordConfirmation">
<Form.Label>Confirm New Password</Form.Label>
<Form.Control type="password" onInput={this.updateTextField} required />
</Form.Group>
<Button variant="primary" type="submit" className="d-inline-block">Change password</Button>
<Form.Group controlId="logoutDevices" className="d-inline-block ml-2">
<Form.Check type="checkbox" label="Log everywhere out" onInput={this.updateCheckbox} />
</Form.Group>
</Form>
</Modal.Body>
</Modal>
</div>
);
}
}
if (document.getElementById('ResetPassword')) {
ReactDOM.render(<ResetPassword />, document.getElementById('ResetPassword'));
}

View File

@@ -1,67 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Container, Row, Col, Table } from 'react-bootstrap';
import Axios from 'axios';
export default class SessionsTable extends Component {
constructor(props) {
super(props)
this.state = {
sessions: []
}
}
componentDidMount() {
this.getSessions();
}
getSessions = () => {
var url = 'api/auth/sessions?token=' + window.token;
Axios.get(url)
.then((resp) => {
this.setState({
sessions: resp.data.response
})
})
}
render() {
var sessions = this.state.sessions;
return (
<Container className="mb-4">
<Row>
<Col sm={{ span: 12 }} className="mb-3 text-center">
<h5>Login Sessions</h5>
<Table responsive>
<thead>
<tr>
<th>IP</th>
<th>Expires</th>
<th>Created at</th>
</tr>
</thead>
<tbody>
{sessions.map((e,i) => {
return(
<tr key={i}>
<td>{e.ip}</td>
<td>{new Date(e.expires * 1000).toLocaleDateString() + ' ' + new Date(e.expires * 1000).toLocaleTimeString()}</td>
<td>{e.created_at}</td>
</tr>
)
})}
</tbody>
</Table>
</Col>
</Row>
</Container>
);
}
}
if (document.getElementById('SessionsTable')) {
ReactDOM.render(<SessionsTable />, document.getElementById('SessionsTable'));
}

View File

@@ -1,42 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Button, Dropdown, DropdownButton } from 'react-bootstrap';
import { toast } from 'react-toastify';
import Axios from 'axios';
export default class Backup extends Component {
backup = (format) => {
var url = 'api/backup?format=' + format + '&token=' + window.token;
toast.info('Your backup has started downloading...');
Axios.get(url, {
responseType: 'blob'
})
.then((resp) => {
var a = document.createElement('a');
a.href = url;
a.download = "";
document.body.appendChild(a);
a.click();
a.remove();
toast.success('Backup downloaded');
})
.catch((err) => {
console.log(err);
})
}
render() {
return (
<DropdownButton title="Backup" variant="primary" className="m-2 d-inline-block">
<Dropdown.Item href="#" onClick={() => { this.backup('json') }}>JSON</Dropdown.Item>
<Dropdown.Item href="#" onClick={() => { this.backup('csv') }}>CSV</Dropdown.Item>
</DropdownButton>
);
}
}
if (document.getElementById('Backup')) {
ReactDOM.render(<Backup />, document.getElementById('Backup'));
}

View File

@@ -1,140 +0,0 @@
import React, { Component, version } from 'react';
import ReactDOM from 'react-dom';
import Axios from 'axios';
import { Modal, Collapse, Button } from 'react-bootstrap';
export default class Changelog extends Component {
constructor(props) {
super(props)
this.state = {
changelog: {},
modal: false,
loading: true,
hidden: false,
}
}
componentDidMount = () => {
if( (window.config.auth == true && window.authenticated == true) || window.config.auth == false) {
this.getChangelog();
}
}
getChangelog = () => {
Axios.get('api/update/changelog?token=' + window.token)
.then((resp) => {
this.setState({
changelog: resp.data.data,
loading: false
});
})
}
showModal = () => {
this.setState({
modal: true,
});
}
hideModal = () => {
this.setState({
modal: false,
});
}
toggleHidden = () => {
var hidden = this.state.hidden;
if(hidden) {
this.setState({
hidden: false
});
} else {
this.setState({
hidden: true
});
}
}
versionList = (key, data) => {
return (
<div key={key}>
<h5>Version: {key}</h5>
<ul>
{data.map((e,i) => {
if(e.link == '') {
return <li key={key.split('.').join() + i}>{e.description}</li>
} else {
return <li key={key + i}><a href={e.link} target="_blank" rel="noopener noreferer">{e.description}</a></li>
}
})}
</ul>
</div>
);
}
makeChangelog() {
var changelog = this.state.changelog;
var versionsVis = [];
var versionsHid = [];
var i = 0;
for(var key in changelog) {
if(i <= 5) {
versionsVis.push(this.versionList(key, changelog[key]));
} else {
versionsHid.push(this.versionList(key, changelog[key]));
}
i++;
}
return {
visible: versionsVis,
hidden: versionsHid
};
}
render() {
var show = this.state.modal;
var loading = this.state.loading;
var showHidden = this.state.hidden;
if(loading) {
return <></>
} else {
var changelog = this.makeChangelog();
return (
<div className="text-muted ml-1 d-inline-block">
<i className="ti-link mouse" onClick={this.showModal} />
<Modal show={show} onHide={this.hideModal} animation={true}>
<Modal.Body>
<h3>Changelog:</h3>
{changelog.visible}
{changelog.hidden.length > 5 &&
<>
<Collapse in={showHidden}>
<div>
{changelog.hidden}
</div>
</Collapse>
<div className="w-100 text-center">
{showHidden ?
<Button variant="primary" className="mx-auto mouse" onClick={this.toggleHidden}>Show less</Button>
:
<Button variant="primary" className="mx-auto mouse" onClick={this.toggleHidden}>Show more</Button>
}
</div>
</>
}
</Modal.Body>
</Modal>
</div>
);
}
}
}
if (document.getElementById('Changelog')) {
ReactDOM.render(<Changelog />, document.getElementById('Changelog'));
}

View File

@@ -1,37 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Container } from 'react-bootstrap';
import { Row } from 'react-bootstrap';
import { Col } from 'react-bootstrap';
import Backup from './Backup';
import Restore from './Restore';
export default class DataRow extends Component {
render() {
if( (window.config.auth == true && window.authenticated == true) || window.config.auth == false) {
return (
<Container className="mb-4">
<Row>
<Col sm={{ span: 12 }} className="text-center">
<p>Use these buttons to backup/restore your data</p>
</Col>
</Row>
<Row>
<Col sm={{ span: 12 }} className="text-center">
<Backup />
<Restore />
</Col>
</Row>
</Container>
);
} else {
return (
<></>
)
}
}
}
if (document.getElementById('DataRow')) {
ReactDOM.render(<DataRow />, document.getElementById('DataRow'));
}

View File

@@ -1,209 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Button, Modal, Form, Tooltip, OverlayTrigger, Dropdown, DropdownButton } from 'react-bootstrap';
import { toast } from 'react-toastify';
import Axios from 'axios';
import CSVFileValidator from 'csv-file-validator';
export default class Restore extends Component {
constructor(props) {
super(props);
this.state = {
show: false,
data: null,
uploadReady: false,
filename: 'Upload your backup',
format: 'json'
};
}
showModal = (format) => {
this.setState({
show: true,
format: format
});
}
hideModal = () => {
this.setState({
show: false
});
}
readFile = (e, format) => {
var file = e.target.files[0];
var reader = new FileReader();
reader.readAsText(file, 'UTF-8');
reader.onload = function(evt) {
var data = evt.target.result.trim();
if(format == 'csv') {
var csv = data.substr(45);
var config = {
headers: [
{
name: "id",
inputName: 'id',
required: false,
},
{
name: "ping",
inputName: 'ping',
required: true,
requiredError: function (headerName, rowNumber, columnNumber) {
return `${headerName} is required in the ${rowNumber} row / ${columnNumber} column`
}
},
{
name: "upload",
inputName: 'upload',
required: true,
requiredError: function (headerName, rowNumber, columnNumber) {
return `${headerName} is required in the ${rowNumber} row / ${columnNumber} column`
}
},
{
name: "download",
inputName: 'download',
required: true,
requiredError: function (headerName, rowNumber, columnNumber) {
return `${headerName} is required in the ${rowNumber} row / ${columnNumber} column`
}
},
{
name: "created_at",
inputName: 'created_at',
required: false,
},
{
name: "server_id",
inputName: 'server_id',
required: false,
},
{
name: "server_name",
inputName: 'server_name',
required: false,
},
{
name: "server_host",
inputName: 'server_host',
required: false,
},
{
name: "url",
inputName: 'url',
required: false,
},
{
name: "scheduled",
inputName: 'scheduled',
required: false,
},
{
name: "failed",
inputName: 'failed',
required: false,
},
{
name: "updated_at",
inputName: 'updated_at',
required: false,
}
]
};
CSVFileValidator(csv, config)
.then((e) => {
if(e.inValidMessages.length > 0) {
toast.error('Your upload file is not valid ' + format.toUpperCase());
} else {
this.setState({
data: data,
uploadReady: true,
filename: file.name
});
}
})
.catch((e) => {
toast.error('Your upload file is not valid ' + format.toUpperCase());
})
} else {
try {
var data = JSON.parse(data);
this.setState({
data: data,
uploadReady: true,
filename: file.name
});
} catch(e) {
console.log(e);
toast.error('Your upload file is not valid ' + format.toUpperCase());
}
}
}.bind(this)
reader.onerror = function (evt) {
toast.error('Something went wrong parsing your backup file.');
}
}
uploadFile = () => {
var data = { data: this.state.data, format: this.state.format };
var url = 'api/restore?token=' + window.token;
Axios.post(url, data)
.then((resp) => {
toast.success('Your data is being restored...');
this.setState({
show: false,
data: null,
uploadReady: false,
filename: 'Upload your backup'
});
})
.catch((err) => {
console.log(err);
})
}
render() {
var show = this.state.show;
var uploadReady = this.state.uploadReady;
var filename = this.state.filename;
return (
<>
<DropdownButton variant="secondary" title="Restore" className="m-2 d-inline-block">
<Dropdown.Item href="#" onClick={() => { this.showModal('json') }}>JSON</Dropdown.Item>
<Dropdown.Item href="#" onClick={() => { this.showModal('csv') }}>CSV</Dropdown.Item>
</DropdownButton>
<Modal show={show} onHide={this.hideModal} animation={true}>
<Modal.Header closeButton>
<Modal.Title>Restore from a backup</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Upload your {this.state.format.toUpperCase()} backup file here:</p>
<Form.File
id="restoreFileInput"
label={"Upload " + this.state.format.toUpperCase() + " file"}
className="mb-3"
custom
>
<Form.File.Input onChange={(e) => { this.readFile(e, this.state.format) }} />
<Form.File.Label data-browse="Choose file">
{filename}
</Form.File.Label>
</Form.File>
{uploadReady === true &&
<Button variant="secondary" onClick={this.uploadFile}>Restore</Button>
}
</Modal.Body>
</Modal>
</>
);
}
}
if (document.getElementById('Restore')) {
ReactDOM.render(<Restore />, document.getElementById('Restore'));
}

View File

@@ -1,89 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Link } from "react-router-dom";
import { Container } from 'react-bootstrap';
import { Row } from 'react-bootstrap';
import { Col } from 'react-bootstrap';
import { Button } from 'react-bootstrap';
export default class ErrorPage extends Component {
constructor(props) {
super(props);
var colour = '';
var message = false;
switch(this.props.code.toString()[0]) {
case 2:
case '2':
colour = 'success';
break;
case 4:
case '4':
colour = 'danger';
break;
case 5:
case '5':
default:
colour = 'warning';
break;
}
switch(this.props.code) {
case '400':
message = 'Bad request'
break;
case '401':
message = 'You aren\'t authenticated';
break;
case '403':
message = 'You aren\'t authorised to view this page';
break;
case '404':
message = 'Page not found';
break;
case '405':
message = 'Method not allowed'
break;
case '413':
message = 'Request too large'
break;
case '422':
message = 'Your request was unprocessable'
break;
}
this.state = {
code: this.props.code,
colour: colour,
message: message
};
}
render() {
const colour = this.state.colour;
const code = this.state.code;
const message = this.state.message;
return (
<Container fluid>
<Row className="fullscreen text-center align-items-center">
<Col
lg={{ span: 2, offset: 5}}
md={{ span: 4, offset: 4}}
sm={{ span: 4, offset: 4}}
xs={{ span: 12}}
>
<h1 className={'text-' + colour + ' mb-0'}>{code}</h1>
{message &&
<p className={colour + '-text mt-0 mb-2'}>{message}</p>
}
<Link to="/" className={'waves-effect waves-' + colour + ' btn ' + colour}><Button variant={colour}>Go home</Button></Link>
</Col>
</Row>
</Container>
);
}
}
if (document.getElementById('errorpage')) {
ReactDOM.render(<ErrorPage />, document.getElementById('errorpage'));
}

View File

@@ -1,23 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
export default class Index extends Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
return (
<div>
</div>
);
}
}
if (document.getElementById('main')) {
ReactDOM.render(<Index />, document.getElementById('main'));
}

View File

@@ -1,392 +0,0 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Axios from 'axios';
import { Spinner, Container, Row, Form, Card } from 'react-bootstrap';
import { Line, Bar } from 'react-chartjs-2';
import { Col } from 'react-bootstrap';
import { toast } from 'react-toastify';
export default class HistoryGraph extends Component {
constructor(props) {
super(props)
this.state = {
days: props.days,
time: props.dlUl,
fail: props.fail,
config: props.config,
duData: {},
duOptions: {},
pingData: {},
pingOptions: {},
failData: {},
failOptions: {},
loading: true,
interval: null,
graph_ul_dl_enabled: true,
graph_ul_dl_width: 6,
graph_failure_enabled: true,
graph_failure_width: 6,
graph_ping_enabled: true,
graph_ping_width: 6,
firstUpdate: false,
}
}
componentDidMount = () => {
}
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({
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,
});
}
processDlUlPing() {
let days = this.state.days;
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`,
},
},
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
}
}
};
var pingData = {
labels: [],
datasets:[
{
data: [],
label: 'Ping',
borderColor: "#07db71",
fill: false,
},
],
};
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());
});
this.setState({
duData: duData,
duOptions: duOptions,
pingData: pingData,
pingOptions: pingOptions,
loading: false,
});
}
processFailure() {
let days = this.state.days;
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) {
toast.info('Showing results for the last ' + days + ' days');
this.props.updateDays(days);
}
}
render() {
var loading = this.state.loading;
var duData = this.state.duData;
var duOptions = this.state.duOptions;
var pingData = this.state.pingData;
var pingOptions = this.state.pingOptions;
var failData = this.state.failData;
var failOptions = {
maintainAspectRatio: false,
responsive: true,
tooltips: {
callbacks: {
label: (item) => `${item.yLabel} speedtests`,
},
},
scales: {
xAxes: [{
stacked: true,
gridLines: {
display: false
}
}],
yAxes: [{
stacked: true,
ticks: {
stepSize: 1
}
}]
}
};
var days = this.state.days;
var graph_ul_dl_enabled = this.state.graph_ul_dl_enabled;
var graph_ul_dl_width = this.state.graph_ul_dl_width;
var graph_ping_enabled = this.state.graph_ping_enabled;
var graph_ping_width = this.state.graph_ping_width;
var graph_failure_enabled = this.state.graph_failure_enabled;
var graph_failure_width = this.state.graph_failure_width;
var dlClasses = 'my-2 home-graph ';
var pingClasses = 'my-2 home-graph ';
var failureClasses = 'my-2 home-graph ';
if(graph_ul_dl_enabled == true) {
//
} else {
dlClasses += 'd-none ';
}
if(graph_ping_enabled == true) {
//
} else {
pingClasses += 'd-none ';
}
if(graph_failure_enabled == true) {
//
} else {
failureClasses += 'd-none ';
}
if(loading) {
return (
<div>
<Spinner animation="grow" />
</div>
)
} else {
return (
<Container className="mb-4 mt-1" fluid>
<Row>
<Col
lg={{ span: graph_ul_dl_width }}
md={{ span: graph_ul_dl_width }}
sm={{ span: 12 }}
xs={{ span: 12 }}
className={dlClasses}
>
<Card className="shadow-sm">
<Card.Body>
<Line data={duData} options={duOptions} height={440} />
</Card.Body>
</Card>
</Col>
<Col
lg={{ span: graph_ping_width }}
md={{ span: graph_ping_width }}
sm={{ span: 12 }}
xs={{ span: 12 }}
className={pingClasses}
>
<Card className="shadow-sm">
<Card.Body>
<Line data={pingData} options={pingOptions} height={440} />
</Card.Body>
</Card>
</Col>
<Col
lg={{ span: graph_failure_width }}
md={{ span: graph_failure_width }}
sm={{ span: 12 }}
xs={{ span: 12 }}
className={failureClasses}
>
<Card className="shadow-sm h-100">
<Card.Body className="w-100 h-100">
<Bar data={failData} options={failOptions} height={440} />
</Card.Body>
</Card>
</Col>
</Row>
<Row>
<Col sm={{ span: 12 }}>
<div className="text-center">
<div className="d-inline-flex align-items-center mb-2">
<h4 className="d-inline mb-0">Show results for the last</h4>
<Form.Control id="duDaysInput" className="d-inline-block mx-2" defaultValue={days} onInput={this.updateDays}></Form.Control>
<h4 className="d-inline mb-0">days</h4>
</div>
</div>
</Col>
</Row>
</Container>
);
}
}
}
if (document.getElementById('HistoryGraph')) {
ReactDOM.render(<HistoryGraph />, document.getElementById('HistoryGraph'));
}

Some files were not shown because too many files have changed in this diff Show More