mirror of
https://github.com/henrywhitaker3/Speedtest-Tracker.git
synced 2025-12-24 06:28:27 +01:00
Merge pull request #133 from henrywhitaker3/dev-docker
Merge dev into latest
This commit is contained in:
23
Dockerfile
23
Dockerfile
@@ -1,27 +1,8 @@
|
||||
FROM linuxserver/nginx
|
||||
MAINTAINER henrywhitaker3@outlook.com
|
||||
|
||||
# Install apt stuff
|
||||
RUN apk add --no-cache --upgrade \
|
||||
python3 \
|
||||
py-pip \
|
||||
supervisor
|
||||
COPY conf/ /
|
||||
|
||||
# Install speedtest-cli
|
||||
RUN pip3 install speedtest-cli
|
||||
|
||||
# Copy over static files
|
||||
COPY conf/ /setup/
|
||||
|
||||
# Setup new init script
|
||||
RUN cp /setup/entrypoint/init.sh /etc/cont-init.d/50-speedtest
|
||||
|
||||
# Update webroot
|
||||
RUN cp /setup/default /defaults/default
|
||||
|
||||
RUN mkdir -p /etc/services.d/supervisord/ && \
|
||||
cp /setup/supervisor-service.sh /etc/services.d/supervisord/run && \
|
||||
mkdir -p /etc/supervisor.d/ && \
|
||||
cp /setup/laravel-worker.conf /etc/supervisor.d/laravel-worker.ini
|
||||
EXPOSE 80 443
|
||||
|
||||
VOLUME ["/config"]
|
||||
|
||||
28
README.md
28
README.md
@@ -1,8 +1,10 @@
|
||||
# Speedtest Tracker
|
||||
|
||||
[](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits)  [](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
|
||||
[](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits)  [](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
|
||||
|
||||
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses the [speedtest-cli](https://github.com/sivel/speedtest-cli) package to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.
|
||||
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses [Ookla's Speedtest cli](https://www.speedtest.net/apps/cli) to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.
|
||||
|
||||
Disclaimer: You will need to accept Ookla's [EULA](https://www.speedtest.net/about/eula) and privacy agreements in order to use this container.
|
||||
|
||||

|
||||
|
||||
@@ -10,8 +12,8 @@ This program runs a speedtest check every hour and graphs the results. The back-
|
||||
|
||||
- Automatically run a speedtest every hour
|
||||
- Graph of previous speedtests going back x days
|
||||
- Backup/restore data in JSON format
|
||||
- Slack/Discord notifications
|
||||
- Backup/restore data in JSON/CSV format
|
||||
- Slack/Discord/Telegram notifications
|
||||
- Organizr integration
|
||||
|
||||
## Usage
|
||||
@@ -22,6 +24,9 @@ docker create \
|
||||
-p 8765:80 \
|
||||
-v /path/to/data:/config \
|
||||
-e SLACK_WEBHOOK=webhook `#optional` \
|
||||
-e PUID=uid `#optional` \
|
||||
-e PGID=gid `#optional` \
|
||||
-e OOKLA_EULA_GDPR=true \
|
||||
--restart unless-stopped \
|
||||
henrywhitaker3/speedtest-tracker
|
||||
```
|
||||
@@ -30,11 +35,16 @@ docker create \
|
||||
|
||||
Container images are configured using parameters passed at runtime (such as those above). These parameters are separated by a colon and indicate `<external>:<internal>` respectively. For example, `-p 8080:80` would expose port `80` from inside the container to be accessible from the host's IP on port `8080` outside the container.
|
||||
|
||||
| Parameter | Function |
|
||||
| :----: | --- |
|
||||
| `-p 8765:80` | Exposes the webserver on port 8765 |
|
||||
| `-e SLACK_WEBHOOK` | Put a slack webhook here to get slack notifications when a speedtest is run. To use discord webhooks, just append `/slack` to the end of your discord webhook URL |
|
||||
| `-v /config` | All the config files reside here. |
|
||||
| Parameter | Function |
|
||||
| :----: | --- |
|
||||
| `-p 8765:80` | Exposes the webserver on port 8765 |
|
||||
| `-v /config` | All the config files reside here. |
|
||||
| `-e OOKLA_EULA_GDPR` | Set to 'true' to accept the Ookla [EULA](https://www.speedtest.net/about/eula) and privacy agreement. If this is not set, the container will not start |
|
||||
| `-e SLACK_WEBHOOK` | Optional. Put a slack webhook here to get slack notifications when a speedtest is run. To use discord webhooks, just append `/slack` to the end of your discord webhook URL |
|
||||
| `-e TELEGRAM_BOT_TOKEN` | Optional. Telegram bot API token. |
|
||||
| `-e TELEGRAM_CHAT_ID` | Optional. Telegram chat ID. |
|
||||
| `-e PUID` | Optional. Supply a local user ID for volume permissions |
|
||||
| `-e PGID` | Optional. Supply a local group ID for volume permissions |
|
||||
|
||||
|
||||
## Getting the Image
|
||||
|
||||
9
conf/defaults/crontab
Normal file
9
conf/defaults/crontab
Normal file
@@ -0,0 +1,9 @@
|
||||
# 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
|
||||
@@ -1,68 +0,0 @@
|
||||
#!/usr/bin/with-contenv bash
|
||||
# # This script sets up the speedtest app
|
||||
|
||||
# Copy site files to /config
|
||||
echo "Copying latest site files to config"
|
||||
cp -rfT /setup/site/ /config/www/
|
||||
chown -R abc:abc /config/www
|
||||
chmod -R 755 /config/www/storage
|
||||
chmod -R 755 /config/www/bootstrap
|
||||
|
||||
# Check for DB
|
||||
if [ ! -f /config/speed.db ]; then
|
||||
echo "Database file not found! Creating empty database"
|
||||
touch /config/speed.db
|
||||
chown abc:abc /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 /setup/site/.env.example /config/www/.env
|
||||
else
|
||||
echo "Env file exists"
|
||||
fi
|
||||
|
||||
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 cat /config/www/.env | grep -E "APP_KEY=[0-9A-Za-z:+\/=]{1,}" > /dev/null ; then
|
||||
echo "App key exists"
|
||||
else
|
||||
echo "Generating app key"
|
||||
php /config/www/artisan key:generate
|
||||
fi
|
||||
|
||||
# Check JWT secret exists
|
||||
if cat /config/www/.env | grep -E "JWT_SECRET=[0-9A-Za-z:+\/=]{1,}" > /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 .env"
|
||||
sed "s,SLACK_WEBHOOK=.*,SLACK_WEBHOOK=$SLACK_WEBHOOK," -i.bak /config/www/.env
|
||||
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
|
||||
|
||||
mkdir -p /config/log/speedtest
|
||||
echo "* * * * * php /config/www/artisan schedule:run >> /config/log/speedtest/cron.log" >> /etc/crontabs/root
|
||||
103
conf/etc/cont-init.d/50-speedtest
Normal file
103
conf/etc/cont-init.d/50-speedtest
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/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
|
||||
|
||||
echo "Ookla GDPR and EULA accepted. Downloading Speedtest CLI."
|
||||
cd /tmp
|
||||
wget https://bintray.com/ookla/download/download_file?file_path=ookla-speedtest-1.0.0-x86_64-linux.tgz -O speedtest.tgz > /dev/null
|
||||
tar zxvf speedtest.tgz > /dev/null
|
||||
cp speedtest /site/app/Bin/
|
||||
HOME=/config && timeout 5s s6-setuidgid abc /site/app/Bin/speedtest --accept-license --accept-gdpr > /dev/null
|
||||
HOME=/root
|
||||
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
|
||||
|
||||
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 .env"
|
||||
sed "s,SLACK_WEBHOOK=.*,SLACK_WEBHOOK=$SLACK_WEBHOOK," -i.bak /config/www/.env
|
||||
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
|
||||
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
|
||||
|
||||
mkdir -p /config/log/speedtest
|
||||
|
||||
cp /defaults/crontab /etc/crontabs/root
|
||||
|
||||
chown -R abc:abc /config
|
||||
3
conf/etc/services.d/speedtest/run
Normal file
3
conf/etc/services.d/speedtest/run
Normal 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
|
||||
@@ -1,9 +0,0 @@
|
||||
[program:laravel-worker]
|
||||
process_name=%(program_name)s_%(process_num)02d
|
||||
command=php /config/www/artisan queue:work
|
||||
autostart=true
|
||||
autorestart=true
|
||||
user=abc
|
||||
numprocs=1
|
||||
redirect_stderr=true
|
||||
logfile=/config/log/speedtest/laravel-worker.log
|
||||
@@ -24,4 +24,7 @@ REMEMBER_DAYS=30
|
||||
|
||||
SLACK_WEBHOOK=
|
||||
|
||||
TELEGRAM_BOT_TOKEN=
|
||||
TELEGRAM_CHAT_ID=
|
||||
|
||||
BASE_PATH=
|
||||
|
||||
2
conf/site/.gitignore
vendored
2
conf/site/.gitignore
vendored
@@ -8,5 +8,3 @@ Homestead.json
|
||||
Homestead.yaml
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
.vscode/
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
# Speedtest Tracker
|
||||
|
||||
[](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [](https://github.com/henrywhitaker3/Speedtest-Tracker/issues)  [](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
|
||||
[](https://hub.docker.com/r/henrywhitaker3/speedtest-tracker) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits) [](https://github.com/henrywhitaker3/Speedtest-Tracker/issues) [](https://github.com/henrywhitaker3/Speedtest-Tracker/commits)  [](https://github.com/henrywhitaker3/Speedtest-Tracker/blob/master/LICENSE)
|
||||
|
||||
This program runs a speedtest check every hour and graphs the results. The back-end is written in [Laravel](https://laravel.com/) and the front-end uses [React](https://reactjs.org/). It uses the [speedtest-cli](https://github.com/sivel/speedtest-cli) package to get the data and uses [Chart.js](https://www.chartjs.org/) to plot the results.
|
||||
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 [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.
|
||||
|
||||
Disclaimer: You will need to accept Ookla's EULA and privacy agreements in order to use this container.
|
||||
|
||||

|
||||
|
||||
@@ -10,8 +12,8 @@ This program runs a speedtest check every hour and graphs the results. The back-
|
||||
|
||||
- Automatically run a speedtest every hour
|
||||
- Graph of previous speedtests going back x days
|
||||
- Backup/restore data in JSON format
|
||||
- Slack/Discord notifications
|
||||
- Backup/restore data in JSON/CSV format
|
||||
- Slack/Discord/Telegram notifications
|
||||
- Organizr integration
|
||||
|
||||
## Installation & Setup
|
||||
@@ -26,6 +28,9 @@ docker create \
|
||||
-p 8765:80 \
|
||||
-v /path/to/data:/config \
|
||||
-e SLACK_WEBHOOK=webhook `#optional` \
|
||||
-e PUID=uid `#optional` \
|
||||
-e PGID=gid `#optional` \
|
||||
-e OOKLA_EULA_GDPR=true \
|
||||
--restart unless-stopped \
|
||||
henrywhitaker3/speedtest-tracker
|
||||
```
|
||||
@@ -39,7 +44,7 @@ This program has some dependencies, to install them you need to run the followin
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt update
|
||||
sudo apt install php-common php7.2 php7.2-cli php7.2-common php7.2-json php7.2-opcache php7.2-readline php-xml php-sqlite3 php-zip composer python3 python3-pip git
|
||||
sudo apt install php-common php7.2 php7.2-cli php7.2-common php7.2-json php7.2-opcache php7.2-readline php-xml php-sqlite3 php-zip php-mbstring composer python3 python3-pip git
|
||||
```
|
||||
```bash
|
||||
sudo apt install curl
|
||||
|
||||
1
conf/site/app/Bin/.gitignore
vendored
Normal file
1
conf/site/app/Bin/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
speedtest
|
||||
@@ -32,9 +32,9 @@ class SpeedtestCommand extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* Runs a speedtest synchroonously and displays the results..
|
||||
*
|
||||
* @return mixed
|
||||
* @return null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
@@ -42,6 +42,16 @@ class SpeedtestCommand extends Command
|
||||
|
||||
$results = SpeedtestHelper::runSpeedtest();
|
||||
|
||||
if(!is_object($results)) {
|
||||
$this->error('Something went wrong running the speedtest.');
|
||||
exit();
|
||||
}
|
||||
|
||||
if(property_exists($results, 'ping') && property_exists($results, 'download') && property_exists($results, 'upload')) {
|
||||
$this->error('Something went wrong running the speedtest.');
|
||||
exit();
|
||||
}
|
||||
|
||||
$this->info('Ping: ' . $results->ping . ' ms');
|
||||
$this->info('Download: ' . $results->download . ' Mbit/s');
|
||||
$this->info('Upload: ' . $results->upload . ' Mbit/s');
|
||||
|
||||
@@ -33,9 +33,9 @@ class SpeedtestLatestCommand extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
* Prints the latest speedtest values.
|
||||
*
|
||||
* @return mixed
|
||||
* @return null
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
46
conf/site/app/Console/Commands/SpeedtestOverviewCommand.php
Normal file
46
conf/site/app/Console/Commands/SpeedtestOverviewCommand.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Events\SpeedtestOverviewEvent;
|
||||
use App\Helpers\SpeedtestHelper;
|
||||
use App\Notifications\SpeedtestOverviewSlack;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
|
||||
class SpeedtestOverviewCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'speedtest:overview';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Trigger a speedtest overview event';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
event(new SpeedtestOverviewEvent());
|
||||
}
|
||||
}
|
||||
42
conf/site/app/Console/Commands/SpeedtestVersionCommand.php
Normal file
42
conf/site/app/Console/Commands/SpeedtestVersionCommand.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class SpeedtestVersionCommand extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'speedtest:version';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Displays the version number of this instance';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Speedtest Tracker v' . config('speedtest.version'));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Console;
|
||||
|
||||
use App\Events\SpeedtestOverviewEvent;
|
||||
use App\Helpers\SettingsHelper;
|
||||
use App\Helpers\SpeedtestHelper;
|
||||
use App\Jobs\SpeedtestJob;
|
||||
@@ -28,6 +29,7 @@ class Kernel extends ConsoleKernel
|
||||
protected function schedule(Schedule $schedule)
|
||||
{
|
||||
$schedule->job(new SpeedtestJob)->cron(SettingsHelper::get('schedule')['value']);
|
||||
$schedule->command('speedtest:overview')->cron('0 ' . SettingsHelper::get('speedtest_overview_time')->value . ' * * *');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
36
conf/site/app/Events/SpeedtestOverviewEvent.php
Normal file
36
conf/site/app/Events/SpeedtestOverviewEvent.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Events;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Broadcasting\PresenceChannel;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class SpeedtestOverviewEvent
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
/**
|
||||
* Create a new event instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return \Illuminate\Broadcasting\Channel|array
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
return new PrivateChannel('channel-name');
|
||||
}
|
||||
}
|
||||
@@ -3,29 +3,103 @@
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Speedtest;
|
||||
use DateTime;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class BackupHelper {
|
||||
public static function backup()
|
||||
{
|
||||
$data = Speedtest::get();
|
||||
|
||||
return $data;
|
||||
/**
|
||||
* Generates a backup of all speedtests.
|
||||
*
|
||||
* @param string $format json|csv
|
||||
* @return string $name Returns the filename of the backup.
|
||||
*/
|
||||
public static function backup(String $format = 'json')
|
||||
{
|
||||
$timestamp = new DateTime();
|
||||
$timestamp = $timestamp->format('Y-m-d_H:i:s');
|
||||
$name = 'speedtest_backup_' . $timestamp;
|
||||
|
||||
switch($format) {
|
||||
case 'csv':
|
||||
$data = Speedtest::get();
|
||||
|
||||
$csv = Storage::disk('local')->getDriver()->getAdapter()->getPathPrefix() . $name . '.csv';
|
||||
$name = $name . '.csv';
|
||||
$handle = fopen($csv, 'w+');
|
||||
fputcsv($handle, array('id', 'ping', 'download', 'upload', 'created_at', 'updated_at'));
|
||||
|
||||
foreach($data as $d) {
|
||||
fputcsv($handle, array($d->id, $d->ping, $d->download, $d->upload, $d->created_at, $d->updated_at));
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
break;
|
||||
case 'json':
|
||||
default:
|
||||
$data = Speedtest::get()->toJson();
|
||||
$name = $name . '.json';
|
||||
Storage::disk('local')->put($name, $data);
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
public static function restore($array)
|
||||
/**
|
||||
* Restore data from a backup in CSV or JSON format
|
||||
*
|
||||
* @param array|string $array Backup data
|
||||
* @param string $format json|csv
|
||||
* @return boolean
|
||||
*/
|
||||
public static function restore($array, $format)
|
||||
{
|
||||
foreach($array as $test) {
|
||||
try {
|
||||
$st = Speedtest::create([
|
||||
'ping' => $test['ping'],
|
||||
'download' => $test['download'],
|
||||
'upload' => $test['upload'],
|
||||
'created_at' => $test['created_at'],
|
||||
]);
|
||||
} catch(Exception $e) {
|
||||
continue;
|
||||
if($format == 'json') {
|
||||
foreach($array as $test) {
|
||||
try {
|
||||
$st = Speedtest::create([
|
||||
'ping' => $test['ping'],
|
||||
'download' => $test['download'],
|
||||
'upload' => $test['upload'],
|
||||
'created_at' => $test['created_at'],
|
||||
]);
|
||||
} catch(Exception $e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else if($format == 'csv') {
|
||||
$csv = explode(PHP_EOL, $array);
|
||||
$headers = 'id,ping,download,upload,created_at,updated_at';
|
||||
if($csv[0] != $headers) {
|
||||
Log::error('Incorrect CSV format');
|
||||
return false;
|
||||
}
|
||||
|
||||
unset($csv[0]);
|
||||
$csv = array_values($csv);
|
||||
|
||||
for($i = 0; $i < sizeof($csv); $i++) {
|
||||
$e = explode(',', $csv[$i]);
|
||||
try {
|
||||
$st = Speedtest::create([
|
||||
'ping' => $e[1],
|
||||
'download' => $e[2],
|
||||
'upload' => $e[3],
|
||||
'created_at' => substr($e[4], 1, -1),
|
||||
]);
|
||||
} catch(Exception $e) {
|
||||
Log::error($e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,13 @@ namespace App\Helpers;
|
||||
use App\Setting;
|
||||
|
||||
class SettingsHelper {
|
||||
|
||||
/**
|
||||
* Get a Setting object by name
|
||||
*
|
||||
* @param String $name The name field in the setting table
|
||||
* @return \App\Setting|boolean $name The Setting object. Returns false if no mathcing obj.
|
||||
*/
|
||||
public static function get(String $name)
|
||||
{
|
||||
$name = Setting::where('name', $name)->get();
|
||||
@@ -19,6 +26,13 @@ class SettingsHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create / update value for Setting object.
|
||||
*
|
||||
* @param String $name Name of setting
|
||||
* @param String $value Value of setting
|
||||
* @return \App\Setting
|
||||
*/
|
||||
public static function set(String $name, String $value)
|
||||
{
|
||||
$setting = SettingsHelper::get($name);
|
||||
@@ -36,6 +50,11 @@ class SettingsHelper {
|
||||
return $setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the app's base path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function getBase()
|
||||
{
|
||||
$base = env('BASE_PATH', '/');
|
||||
|
||||
@@ -3,11 +3,20 @@
|
||||
namespace App\Helpers;
|
||||
|
||||
use App\Speedtest;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use JsonException;
|
||||
|
||||
class SpeedtestHelper {
|
||||
|
||||
/**
|
||||
* Runs/processes speedtest output to created a Speedtest object
|
||||
*
|
||||
* @param boolean|string $output If false, new speedtest runs. If anything else, will try to parse as JSON for speedtest results.
|
||||
* @return \App\Speedtest|boolean
|
||||
*/
|
||||
public static function runSpeedtest($output = false)
|
||||
{
|
||||
if($output === false) {
|
||||
@@ -16,10 +25,19 @@ class SpeedtestHelper {
|
||||
|
||||
try {
|
||||
$output = json_decode($output, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
if(!SpeedtestHelper::checkOutputIsComplete($output)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$test = Speedtest::create([
|
||||
'ping' => $output['ping'],
|
||||
'download' => $output['download'] / 1000000,
|
||||
'upload' => $output['upload'] / 1000000
|
||||
'ping' => $output['ping']['latency'],
|
||||
'download' => SpeedtestHelper::convert($output['download']['bandwidth']),
|
||||
'upload' => SpeedtestHelper::convert($output['upload']['bandwidth']),
|
||||
'server_id' => $output['server']['id'],
|
||||
'server_name' => $output['server']['name'],
|
||||
'server_host' => $output['server']['host'] . ':' . $output['server']['port'],
|
||||
'url' => $output['result']['url'],
|
||||
]);
|
||||
} catch(JsonException $e) {
|
||||
Log::error('Failed to parse speedtest JSON');
|
||||
@@ -31,19 +49,60 @@ class SpeedtestHelper {
|
||||
return (isset($test)) ? $test : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output of executing speedtest binary.
|
||||
*
|
||||
* @return boolean|string
|
||||
*/
|
||||
public static function output()
|
||||
{
|
||||
$server = SettingsHelper::get('server')['value'];
|
||||
$binPath = app_path() . DIRECTORY_SEPARATOR . 'Bin' . DIRECTORY_SEPARATOR . 'speedtest';
|
||||
if($server != '' && $server != false) {
|
||||
$server = explode(',', $server);
|
||||
$server = $server[array_rand($server)];
|
||||
if($server == false) {
|
||||
Log::error('Speedtest server undefined');
|
||||
return false;
|
||||
}
|
||||
|
||||
return shell_exec('speedtest-cli --json --server ' . $server);
|
||||
return shell_exec('HOME=/config && ' . $binPath . ' -f json -s ' . $server);
|
||||
}
|
||||
|
||||
return shell_exec('speedtest-cli --json');
|
||||
return shell_exec('HOME=/config && ' . $binPath . ' -f json');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a 24 hour average of speedtest results
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function last24Hours()
|
||||
{
|
||||
$t = Carbon::now()->subDay();
|
||||
$s = Speedtest::select(DB::raw('AVG(ping) as ping, AVG(download) as download, AVG(upload) as upload'))
|
||||
->where('created_at', '>=', $t)
|
||||
->first()
|
||||
->toArray();
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts bytes/s to Mbps
|
||||
*
|
||||
* @param int|float $bytes
|
||||
* @return int|float
|
||||
*/
|
||||
public static function convert($bytes) {
|
||||
return ( $bytes * 8 ) / 1000000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the latest speedtest object.
|
||||
*
|
||||
* @return boolean|\App\Speedtest
|
||||
*/
|
||||
public static function latest()
|
||||
{
|
||||
$data = Speedtest::latest()->get();
|
||||
@@ -55,6 +114,12 @@ class SpeedtestHelper {
|
||||
return $data->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses network speeds and return converted to Mbps
|
||||
*
|
||||
* @param array $input
|
||||
* @return array
|
||||
*/
|
||||
public static function parseUnits($input)
|
||||
{
|
||||
$input = explode(' ', $input);
|
||||
@@ -82,4 +147,46 @@ class SpeedtestHelper {
|
||||
'unit' => 'Mbit/s'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the speedtest JSON output is complete/valid
|
||||
*
|
||||
* @param array $output
|
||||
* @return boolean
|
||||
*/
|
||||
public static function checkOutputIsComplete($output)
|
||||
{
|
||||
/**
|
||||
* Array of indexes that must exist in $output
|
||||
*/
|
||||
$checks = [
|
||||
'type' => 'result',
|
||||
'download' => [ 'bandwidth' => '*' ],
|
||||
'upload' => [ 'bandwidth' => '*' ],
|
||||
'ping' => [ 'latency' => '*' ],
|
||||
'server' => [
|
||||
'id' => '*',
|
||||
'name' => '*',
|
||||
'host' => '*',
|
||||
'port' => '*'
|
||||
],
|
||||
'result' => [
|
||||
'url' => '*'
|
||||
],
|
||||
];
|
||||
/**
|
||||
* Array of indexes that must not exist
|
||||
*/
|
||||
$checkMissing = [
|
||||
'type' => 'error'
|
||||
];
|
||||
|
||||
foreach($checks as $key => $value) {
|
||||
if(!isset($output[$key])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,10 +10,39 @@ use RecursiveIteratorIterator;
|
||||
use ZipArchive;
|
||||
|
||||
class UpdateHelper {
|
||||
/**
|
||||
* URL of updates
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $url;
|
||||
|
||||
/**
|
||||
* Current app version number
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $currentVersion;
|
||||
|
||||
/**
|
||||
* Username of GitHub repo
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $user;
|
||||
|
||||
/**
|
||||
* Name of GitHub repo
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $repo;
|
||||
|
||||
/**
|
||||
* Branch of GitHub repo
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $branch;
|
||||
|
||||
function __construct() {
|
||||
@@ -25,6 +54,11 @@ class UpdateHelper {
|
||||
$this->download = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns data on new version available
|
||||
*
|
||||
* @return boolean|array false|[ version, changelog ]
|
||||
*/
|
||||
public function check()
|
||||
{
|
||||
Log::info('Checking for new version');
|
||||
@@ -50,6 +84,11 @@ class UpdateHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest version number from GitHub
|
||||
*
|
||||
* @return array [ repo, branch, version ]
|
||||
*/
|
||||
public function checkLatestVersion()
|
||||
{
|
||||
$url = 'https://raw.githubusercontent.com/'
|
||||
@@ -78,6 +117,11 @@ class UpdateHelper {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the latest changelog from GitHub
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getChangelog()
|
||||
{
|
||||
$url = 'https://raw.githubusercontent.com/'
|
||||
@@ -97,6 +141,11 @@ class UpdateHelper {
|
||||
return $changelog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads the latest version from GitHub
|
||||
*
|
||||
* @return boolean|Exception
|
||||
*/
|
||||
public function downloadLatest()
|
||||
{
|
||||
Log::info('Downloading the latest version from GitHub');
|
||||
@@ -121,6 +170,11 @@ class UpdateHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts zip archive from update
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function extractFiles()
|
||||
{
|
||||
Log::info('Extracting the update');
|
||||
@@ -137,6 +191,11 @@ class UpdateHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace existing files with newly downloaded files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function updateFiles()
|
||||
{
|
||||
Log::info('Applying update');
|
||||
@@ -151,6 +210,14 @@ class UpdateHelper {
|
||||
Log::info('Successfully applied update');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes default templates from updated files.
|
||||
* This is for things like .env so that user specified files are not
|
||||
* overwritten.
|
||||
*
|
||||
* @param string $path
|
||||
* @return void
|
||||
*/
|
||||
private function deleteExcluded($path)
|
||||
{
|
||||
Log::info('Deleting excluded items from update directory');
|
||||
@@ -172,6 +239,11 @@ class UpdateHelper {
|
||||
Log::info('Excluded items deleted from update directory');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ZIP backup of current installation
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function backupCurrent()
|
||||
{
|
||||
Log::info('Backing up current installation');
|
||||
@@ -209,6 +281,11 @@ class UpdateHelper {
|
||||
Log::info('Backup created at: ' . $backupZip);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move updated files into server dir.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function moveFiles()
|
||||
{
|
||||
$new = array_filter(glob('/tmp/'.$this->repo.'-update/*'), 'is_dir');
|
||||
@@ -242,6 +319,11 @@ class UpdateHelper {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a copy of excluded, user customised files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function tempStoreExcludedFiles()
|
||||
{
|
||||
Log::info('Temporarily moving exluded files from root directory');
|
||||
@@ -263,6 +345,11 @@ class UpdateHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore user cusotmised files from the copy
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function restoreExcludedFiles()
|
||||
{
|
||||
Log::info('Restoring exluded files to root directory');
|
||||
@@ -284,6 +371,11 @@ class UpdateHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete update files from download dir.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function clearup()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -10,21 +10,39 @@ use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class BackupController extends Controller
|
||||
{
|
||||
public function backup()
|
||||
{
|
||||
$data = BackupHelper::backup();
|
||||
$timestamp = new DateTime();
|
||||
$timestamp = $timestamp->format('Y-m-d_H:i:s');
|
||||
$name = 'speedtest_backup_' . $timestamp . '.json';
|
||||
Storage::disk('local')->put($name, $data);
|
||||
|
||||
return Storage::disk('local')->download($name);
|
||||
/**
|
||||
* Get backup of speedtests
|
||||
*
|
||||
* @param Request $request
|
||||
* @return file
|
||||
*/
|
||||
public function backup(Request $request)
|
||||
{
|
||||
$validator = Validator::make($request->all(), [ 'format' => 'in:json,csv' ]);
|
||||
if($validator->fails()) {
|
||||
return response()->json([
|
||||
'method' => 'backup data',
|
||||
'error' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$filename = BackupHelper::backup($request->format);
|
||||
|
||||
return Storage::disk('local')->download($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retore from a backup
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function restore(Request $request)
|
||||
{
|
||||
$rule = [
|
||||
'data' => [ 'required', 'array' ],
|
||||
'data' => [ 'required' ],
|
||||
'format' => [ 'required', 'in:json,csv' ]
|
||||
];
|
||||
|
||||
$validator = Validator::make($request->all(), $rule);
|
||||
@@ -35,10 +53,14 @@ class BackupController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
BackupHelper::restore($request->data);
|
||||
|
||||
return response()->json([
|
||||
'method' => 'restore data from backup',
|
||||
], 200);
|
||||
if(BackupHelper::restore($request->data, $request->format) != false) {
|
||||
return response()->json([
|
||||
'method' => 'restore data from backup',
|
||||
], 200);
|
||||
} else {
|
||||
return response()->json([
|
||||
'method' => 'incorrect backup format',
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,34 @@ use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Return all settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
return Setting::get()->keyBy('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get setting by id
|
||||
*
|
||||
* @param Setting $setting
|
||||
* @return Setting
|
||||
*/
|
||||
public function get(Setting $setting)
|
||||
{
|
||||
return $setting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store/update a setting
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function store(Request $request)
|
||||
{
|
||||
$rule = [
|
||||
@@ -49,6 +67,54 @@ class SettingsController extends Controller
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk store/update a setting
|
||||
*
|
||||
* @param Request $request
|
||||
* @return Response
|
||||
*/
|
||||
public function bulkStore(Request $request)
|
||||
{
|
||||
$rule = [
|
||||
'data' => [ 'array', 'required' ],
|
||||
'data.*.name' => [ 'string', 'required' ],
|
||||
'data.*.value' => [ 'required' ],
|
||||
];
|
||||
|
||||
$validator = Validator::make($request->all(), $rule);
|
||||
if($validator->fails()) {
|
||||
return response()->json([
|
||||
'method' => 'Bulk store a setting',
|
||||
'error' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$settings = [];
|
||||
foreach($request->data as $d) {
|
||||
if($d['name'] == 'speedtest_overview_time') {
|
||||
$ok = [ '00', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23' ];
|
||||
if(!in_array($d['value'], $ok)) {
|
||||
return response()->json([
|
||||
'method' => 'Bulk store a setting',
|
||||
'error' => 'Invalid speedtest_overview_time value'
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
$setting = SettingsHelper::set($d['name'], $d['value']);
|
||||
array_push($settings, $setting);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'method' => 'Bulk store a setting',
|
||||
'data' => $settings,
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns instance config
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function config()
|
||||
{
|
||||
|
||||
|
||||
@@ -13,6 +13,12 @@ use Illuminate\Support\Facades\Validator;
|
||||
|
||||
class SpeedtestController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns paginated list of speedtests
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$data = Speedtest::orderBy('created_at', 'desc')
|
||||
@@ -24,6 +30,12 @@ class SpeedtestController extends Controller
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns speedtest going back 'x' days
|
||||
*
|
||||
* @param int $days
|
||||
* @return void
|
||||
*/
|
||||
public function time($days)
|
||||
{
|
||||
$rule = [
|
||||
@@ -50,6 +62,11 @@ class SpeedtestController extends Controller
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return latest speedtest
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function latest()
|
||||
{
|
||||
$data = SpeedtestHelper::latest();
|
||||
@@ -73,6 +90,11 @@ class SpeedtestController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a new speedtest
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -8,6 +8,12 @@ use Illuminate\Http\Request;
|
||||
|
||||
class UpdateController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Check for new update
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function checkForUpdate()
|
||||
{
|
||||
return response()->json([
|
||||
@@ -16,6 +22,11 @@ class UpdateController extends Controller
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download new update
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function downloadUpdate()
|
||||
{
|
||||
$dl = Updater::downloadLatest();
|
||||
@@ -33,6 +44,11 @@ class UpdateController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update extraction
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function extractUpdate()
|
||||
{
|
||||
$ex = Updater::extractFiles();
|
||||
@@ -50,6 +66,11 @@ class UpdateController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update file move
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function moveUpdate()
|
||||
{
|
||||
$cp = Updater::updateFiles();
|
||||
@@ -60,6 +81,11 @@ class UpdateController extends Controller
|
||||
], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local changelog
|
||||
*
|
||||
* @return Response
|
||||
*/
|
||||
public function changelog()
|
||||
{
|
||||
$url = base_path() . '/changelog.json';
|
||||
|
||||
@@ -25,9 +25,9 @@ class SpeedtestJob implements ShouldQueue
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
* Runs a speedtest
|
||||
*
|
||||
* @return void
|
||||
* @return \App\Speedtest
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Notifications\SpeedtestComplete;
|
||||
use App\Helpers\SettingsHelper;
|
||||
use App\Notifications\SpeedtestCompleteSlack;
|
||||
use App\Notifications\SpeedtestCompleteTelegram;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use NotificationChannels\Telegram\TelegramChannel;
|
||||
|
||||
class SpeedtestCompleteListener
|
||||
{
|
||||
@@ -22,21 +25,33 @@ class SpeedtestCompleteListener
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
* Handle what to do after speedtest completes
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
if(env('SLACK_WEBHOOK')) {
|
||||
if(SettingsHelper::get('speedtest_notifications')->value == true) {
|
||||
$data = $event->speedtest;
|
||||
try {
|
||||
Notification::route('slack', env('SLACK_WEBHOOK'))
|
||||
->notify(new SpeedtestComplete($data));
|
||||
} catch(Exception $e) {
|
||||
Log::notice('Your sleck webhook is invalid');
|
||||
Log::notice($e);
|
||||
if(env('SLACK_WEBHOOK')) {
|
||||
try {
|
||||
Notification::route('slack', env('SLACK_WEBHOOK'))
|
||||
->notify(new SpeedtestCompleteSlack($data));
|
||||
} catch(Exception $e) {
|
||||
Log::notice('Your sleck webhook is invalid');
|
||||
Log::notice($e);
|
||||
}
|
||||
}
|
||||
|
||||
if(env('TELEGRAM_BOT_TOKEN') && env('TELEGRAM_CHAT_ID')) {
|
||||
try {
|
||||
Notification::route(TelegramChannel::class, env('TELEGRAM_CHAT_ID'))
|
||||
->notify(new SpeedtestCompleteTelegram($data));
|
||||
} catch(Exception $e) {
|
||||
Log::notice('Your telegram settings are invalid');
|
||||
Log::notice($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
59
conf/site/app/Listeners/SpeedtestOverviewListener.php
Normal file
59
conf/site/app/Listeners/SpeedtestOverviewListener.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace App\Listeners;
|
||||
|
||||
use App\Helpers\SettingsHelper;
|
||||
use App\Helpers\SpeedtestHelper;
|
||||
use App\Notifications\SpeedtestOverviewSlack;
|
||||
use App\Notifications\SpeedtestOverviewTelegram;
|
||||
use Exception;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use NotificationChannels\Telegram\TelegramChannel;
|
||||
|
||||
class SpeedtestOverviewListener
|
||||
{
|
||||
/**
|
||||
* Create the event listener.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the event.
|
||||
*
|
||||
* @param object $event
|
||||
* @return void
|
||||
*/
|
||||
public function handle($event)
|
||||
{
|
||||
if(SettingsHelper::get('speedtest_overview_notification')->value == true) {
|
||||
$data = SpeedtestHelper::last24Hours();
|
||||
if(env('SLACK_WEBHOOK')) {
|
||||
try {
|
||||
Notification::route('slack', env('SLACK_WEBHOOK'))
|
||||
->notify(new SpeedtestOverviewSlack($data));
|
||||
} catch(Exception $e) {
|
||||
Log::notice('Your sleck webhook is invalid');
|
||||
Log::notice($e);
|
||||
}
|
||||
}
|
||||
|
||||
if(env('TELEGRAM_BOT_TOKEN') && env('TELEGRAM_CHAT_ID')) {
|
||||
try {
|
||||
Notification::route(TelegramChannel::class, env('TELEGRAM_CHAT_ID'))
|
||||
->notify(new SpeedtestOverviewTelegram($data));
|
||||
} catch(Exception $e) {
|
||||
Log::notice('Your telegram settings are invalid');
|
||||
Log::notice($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,11 @@ use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use NotificationChannels\Telegram\TelegramChannel;
|
||||
use NotificationChannels\Telegram\TelegramMessage;
|
||||
|
||||
class SpeedtestComplete extends Notification
|
||||
class SpeedtestCompleteSlack extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
@@ -20,6 +23,9 @@ class SpeedtestComplete extends Notification
|
||||
*/
|
||||
public function __construct($speedtest)
|
||||
{
|
||||
$speedtest->ping = number_format((float)$speedtest->ping, 1, '.', '');
|
||||
$speedtest->download = number_format((float)$speedtest->download, 1, '.', '');
|
||||
$speedtest->upload = number_format((float)$speedtest->upload, 1, '.', '');
|
||||
$this->speedtest = $speedtest;
|
||||
}
|
||||
|
||||
@@ -31,9 +37,17 @@ class SpeedtestComplete extends Notification
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return ['slack'];
|
||||
return [
|
||||
'slack',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format slack notification
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return SlackMessage
|
||||
*/
|
||||
public function toSlack($notifiable)
|
||||
{
|
||||
$speedtest = $this->speedtest;
|
||||
@@ -42,9 +56,9 @@ class SpeedtestComplete extends Notification
|
||||
->attachment(function ($attachment) use ($speedtest) {
|
||||
$attachment->title('New speedtest')
|
||||
->fields([
|
||||
'Ping' => number_format((float)$speedtest->ping, 1, '.', '') . ' ms',
|
||||
'Download' => number_format((float)$speedtest->download, 1, '.', '') . ' Mbit/s',
|
||||
'Upload' => number_format((float)$speedtest->upload, 1, '.', '') . ' Mbit/s',
|
||||
'Ping' => $speedtest->ping . ' ms',
|
||||
'Download' => $speedtest->download . ' Mbit/s',
|
||||
'Upload' => $speedtest->upload . ' Mbit/s',
|
||||
]);
|
||||
});
|
||||
}
|
||||
73
conf/site/app/Notifications/SpeedtestCompleteTelegram.php
Normal file
73
conf/site/app/Notifications/SpeedtestCompleteTelegram.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\Telegram\TelegramChannel;
|
||||
use NotificationChannels\Telegram\TelegramMessage;
|
||||
|
||||
class SpeedtestCompleteTelegram extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($speedtest)
|
||||
{
|
||||
$speedtest->ping = number_format((float)$speedtest->ping, 1, '.', '');
|
||||
$speedtest->download = number_format((float)$speedtest->download, 1, '.', '');
|
||||
$speedtest->upload = number_format((float)$speedtest->upload, 1, '.', '');
|
||||
$this->speedtest = $speedtest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return [
|
||||
TelegramChannel::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format tekegram notification
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return TelegramMessage
|
||||
*/
|
||||
public function toTelegram($notifiable)
|
||||
{
|
||||
$speedtest = $this->speedtest;
|
||||
$msg = "*New Speedtest*
|
||||
Ping: *$speedtest->ping*
|
||||
Download: *$speedtest->download*
|
||||
Upload: *$speedtest->upload*";
|
||||
return TelegramMessage::create()
|
||||
->to(env('TELEGRAM_CHAT_ID'))
|
||||
->content($msg)
|
||||
->options(['parse_mode' => 'Markdown']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
74
conf/site/app/Notifications/SpeedtestOverviewSlack.php
Normal file
74
conf/site/app/Notifications/SpeedtestOverviewSlack.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Messages\SlackMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
|
||||
class SpeedtestOverviewSlack extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($data)
|
||||
{
|
||||
$data['ping'] = number_format((float)$data['ping'], 1, '.', '');
|
||||
$data['download'] = number_format((float)$data['download'], 1, '.', '');
|
||||
$data['upload'] = number_format((float)$data['upload'], 1, '.', '');
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return [
|
||||
'slack'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format slack notification
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return SlackMessage
|
||||
*/
|
||||
public function toSlack($notifiable)
|
||||
{
|
||||
$data = $this->data;
|
||||
return (new SlackMessage)
|
||||
->warning()
|
||||
->attachment(function ($attachment) use ($data) {
|
||||
$attachment->title('Speedtest Daily Overview')
|
||||
->fields([
|
||||
'Average ping' => $data['ping'] . ' ms',
|
||||
'Average download' => $data['download'] . ' Mbit/s',
|
||||
'Average upload' => $data['upload'] . ' Mbit/s',
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
73
conf/site/app/Notifications/SpeedtestOverviewTelegram.php
Normal file
73
conf/site/app/Notifications/SpeedtestOverviewTelegram.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
|
||||
namespace App\Notifications;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Notifications\Messages\MailMessage;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use NotificationChannels\Telegram\TelegramChannel;
|
||||
use NotificationChannels\Telegram\TelegramMessage;
|
||||
|
||||
class SpeedtestOverviewTelegram extends Notification
|
||||
{
|
||||
use Queueable;
|
||||
|
||||
/**
|
||||
* Create a new notification instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($data)
|
||||
{
|
||||
$data['ping'] = number_format((float)$data['ping'], 1, '.', '');
|
||||
$data['download'] = number_format((float)$data['download'], 1, '.', '');
|
||||
$data['upload'] = number_format((float)$data['upload'], 1, '.', '');
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification's delivery channels.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function via($notifiable)
|
||||
{
|
||||
return [
|
||||
TelegramChannel::class
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Format tekegram notification
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return TelegramMessage
|
||||
*/
|
||||
public function toTelegram($notifiable)
|
||||
{
|
||||
$data = $this->data;
|
||||
$msg = "*Speedtest Daily Overview*
|
||||
Average ping: *".$data["ping"]."*
|
||||
Average download: *".$data["download"]."*
|
||||
Average upload: *".$data["upload"]."*";
|
||||
return TelegramMessage::create()
|
||||
->to(env('TELEGRAM_CHAT_ID'))
|
||||
->content($msg)
|
||||
->options(['parse_mode' => 'Markdown']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the array representation of the notification.
|
||||
*
|
||||
* @param mixed $notifiable
|
||||
* @return array
|
||||
*/
|
||||
public function toArray($notifiable)
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Events\SpeedtestCompleteEvent;
|
||||
use App\Events\SpeedtestOverviewEvent;
|
||||
use App\Listeners\SpeedtestCompleteListener;
|
||||
use App\Listeners\SpeedtestOverviewListener;
|
||||
use Illuminate\Auth\Events\Registered;
|
||||
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
|
||||
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
|
||||
@@ -23,6 +25,9 @@ class EventServiceProvider extends ServiceProvider
|
||||
SpeedtestCompleteEvent::class => [
|
||||
SpeedtestCompleteListener::class,
|
||||
],
|
||||
SpeedtestOverviewEvent::class => [
|
||||
SpeedtestOverviewListener::class
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,7 @@ class Cron implements Rule
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the validation rule passes.
|
||||
* Determine if the value is a valid CRON expression
|
||||
*
|
||||
* @param string $attribute
|
||||
* @param mixed $value
|
||||
|
||||
@@ -12,7 +12,14 @@ class Speedtest extends Model
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'ping', 'download', 'upload', 'created_at'
|
||||
'ping',
|
||||
'download',
|
||||
'upload',
|
||||
'created_at',
|
||||
'server_id',
|
||||
'server_name',
|
||||
'server_host',
|
||||
'url',
|
||||
];
|
||||
|
||||
protected $table = 'speedtests';
|
||||
|
||||
@@ -75,6 +75,11 @@ class User extends Authenticatable implements JWTSubject
|
||||
return $this->hasOne('\App\Auth\EmailVerification');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a user's login sessions
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function sessions()
|
||||
{
|
||||
return $this->hasMany('\App\Auth\LoginSession');
|
||||
|
||||
@@ -1,4 +1,66 @@
|
||||
{
|
||||
"1.7.1": [
|
||||
{
|
||||
"description": "Updated dependencies",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.7.0": [
|
||||
{
|
||||
"description": "Added notification toggles",
|
||||
"link": ""
|
||||
},
|
||||
{
|
||||
"description": "Added daily overview notification",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.6.1": [
|
||||
{
|
||||
"description": "Added cli option to get version number",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.6.0": [
|
||||
{
|
||||
"description": "Added telegram notifications",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.5.6": [
|
||||
{
|
||||
"description": "Auto-update all tests table",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.5.5": [
|
||||
{
|
||||
"description": "Store host/id of speedtest.net server",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.5.4": [
|
||||
{
|
||||
"description": "Updated dependencies",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.5.3": [
|
||||
{
|
||||
"description": "Changed speedtest client",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.5.2": [
|
||||
{
|
||||
"description": "Updated dependencies",
|
||||
"link": ""
|
||||
},
|
||||
{
|
||||
"description": "Added CSV backup/restore formats",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.5.1": [
|
||||
{
|
||||
"description": "Updated PHP dependencies",
|
||||
|
||||
@@ -9,10 +9,12 @@
|
||||
"license": "MIT",
|
||||
"require": {
|
||||
"php": "^7.2.5",
|
||||
"doctrine/dbal": "^2.10",
|
||||
"dragonmantank/cron-expression": "^2",
|
||||
"fideloper/proxy": "^4.2",
|
||||
"fruitcake/laravel-cors": "^2.0",
|
||||
"guzzlehttp/guzzle": "^6.3",
|
||||
"laravel-notification-channels/telegram": "^0.4.0",
|
||||
"laravel/framework": "^7.0",
|
||||
"laravel/slack-notification-channel": "^2.0",
|
||||
"laravel/tinker": "^2.0",
|
||||
|
||||
1353
conf/site/composer.lock
generated
1353
conf/site/composer.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -162,6 +162,8 @@ return [
|
||||
Illuminate\Validation\ValidationServiceProvider::class,
|
||||
Illuminate\View\ViewServiceProvider::class,
|
||||
|
||||
NotificationChannels\Telegram\TelegramServiceProvider::class,
|
||||
|
||||
/*
|
||||
* Package Service Providers...
|
||||
*/
|
||||
|
||||
@@ -30,4 +30,8 @@ return [
|
||||
'region' => env('AWS_DEFAULT_REGION', 'us-east-1'),
|
||||
],
|
||||
|
||||
'telegram-bot-api' => [
|
||||
'token' => env('TELEGRAM_BOT_TOKEN', 'YOUR BOT TOKEN HERE')
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
'version' => '1.5.1',
|
||||
'version' => '1.7.1',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class UpdateSpeedtestsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Schema::table('speedtests', function($table) {
|
||||
$table->integer('server_id')->nullable();
|
||||
$table->string('server_name')->nullable();
|
||||
$table->string('server_host')->nullable();
|
||||
$table->string('url')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('speedtests', function($table) {
|
||||
$table->dropColumn('server_id');
|
||||
});
|
||||
|
||||
Schema::table('speedtests', function($table) {
|
||||
$table->dropColumn('server_name');
|
||||
});
|
||||
|
||||
Schema::table('speedtests', function($table) {
|
||||
$table->dropColumn('server_host');
|
||||
});
|
||||
|
||||
Schema::table('speedtests', function($table) {
|
||||
$table->dropColumn('url');
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use App\Setting;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddNotificationsSettings extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
Setting::create([
|
||||
'name' => 'speedtest_notifications',
|
||||
'value' => true,
|
||||
'description' => 'Enable notifications for every speedtest that runs'
|
||||
]);
|
||||
|
||||
Setting::create([
|
||||
'name' => 'speedtest_overview_notification',
|
||||
'value' => true,
|
||||
'description' => 'Enable a daily notification with average values for the last 24 hours.'
|
||||
]);
|
||||
|
||||
Setting::create([
|
||||
'name' => 'speedtest_overview_time',
|
||||
'value' => '12',
|
||||
'description' => 'The hour (24-hour format) that the daily overview notification will be sent.'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Setting::whereIn('name', [
|
||||
'speedtest_notifications',
|
||||
'speedtest_overview_notification',
|
||||
'speedtest_overview_time',
|
||||
])->delete();
|
||||
}
|
||||
}
|
||||
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/0238d4c27c73d193ca2d816b8af615ff.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/0238d4c27c73d193ca2d816b8af615ff.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/3bb6c61af6bd266c6348e1bb9666dd22.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/3bb6c61af6bd266c6348e1bb9666dd22.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/45556b80537d0d19df958943d7977214.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/45556b80537d0d19df958943d7977214.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/4955dc06b473ad9dc2bb06a4d0149ebd.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/4955dc06b473ad9dc2bb06a4d0149ebd.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/78f667e96b307b500a57dbde79389f34.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/78f667e96b307b500a57dbde79389f34.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/844a9aafd0b464186d66fc84e368feba.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/844a9aafd0b464186d66fc84e368feba.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/88d84f388b208380e36c41a493010e54.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/88d84f388b208380e36c41a493010e54.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/ac62e96a3f2ad1d4c2ac67debd8f8477.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/ac62e96a3f2ad1d4c2ac67debd8f8477.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/e3f11024482117b3f061caa81e9c91fd.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/e3f11024482117b3f061caa81e9c91fd.json.gz
generated
vendored
Normal file
Binary file not shown.
BIN
conf/site/node_modules/.cache/babel-loader/fbfb472dbaf3783c6597f307bcb94c52.json.gz
generated
vendored
Normal file
BIN
conf/site/node_modules/.cache/babel-loader/fbfb472dbaf3783c6597f307bcb94c52.json.gz
generated
vendored
Normal file
Binary file not shown.
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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/3f/a1/eb35b03c25ea6671e5251da4b2ade7103ddfd40fa04108270268127804e2
generated
vendored
Normal file
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/3f/a1/eb35b03c25ea6671e5251da4b2ade7103ddfd40fa04108270268127804e2
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
1445738505eea9ecf1c6316bfa00d5e4f8e7b9bc {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"d0eb7143fab24c6a8eee\"}","integrity":"sha512-40raUk8AxZUpIpiQbdzRMGbr22HTLk5dvXGyq1tdXpYIVKmGqkTOHkp0805rvRV22VMPOzC523f+54iOm7uPFA==","time":1593035535440,"size":2530582}
|
||||
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/72/cf/83b05437dffb6b9f0cd1edbcfc3f3a6b6209f67e90d608d351f914b94d7c
generated
vendored
Normal file
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/72/cf/83b05437dffb6b9f0cd1edbcfc3f3a6b6209f67e90d608d351f914b94d7c
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
29e8e827f643e83dcbf4861045e8f6b33ec0ce43 {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"f37a73d0147609e4c181\"}","integrity":"sha512-7gt9YzGsmPp4KCLTRbXCAtGpbw8byW3G7dJlbOdGu8kKdGXagWWA4TZMeoyDDkL/r5lwEHT78NrvSLGKhS4/oQ==","time":1592674255184,"size":2518438}
|
||||
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/74/98/3335c272b22e65d2ca59590716cc4cd95584ef8ad6d390301cb3a3c65535
generated
vendored
Normal file
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/74/98/3335c272b22e65d2ca59590716cc4cd95584ef8ad6d390301cb3a3c65535
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
a70bcd8267a35b2bb95c0548579df554ef7a645d {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"63b9647686cac4d205cc\"}","integrity":"sha512-1IbzUCDiIryjYlsnM/Nymyc1ULFmxGdkj5Y4lAhsQlL7REc11CYoxIyiePZZoy+IbCyfrw8qVEQREMAU69UNXQ==","time":1592768500765,"size":2530607}
|
||||
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/a1/ef/49fbf8fc8849e9059b3b9ed16acb0e0f45f632a33dbf12cffe856177eef0
generated
vendored
Normal file
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/a1/ef/49fbf8fc8849e9059b3b9ed16acb0e0f45f632a33dbf12cffe856177eef0
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
4b1f5b6ad2f962e87fca86cc9013b53bc05faf55 {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"5d08c79e575268399f0e\"}","integrity":"sha512-NTa2Ah7hMH6QgAkijNHLzZkPF297n/wc9a82tnPsnOzSL4MZjjMtQVPoyNhsrHetNvf7HqBQyhqRXZ6SP+vd3w==","time":1592679656772,"size":2519155}
|
||||
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/f9/02/8939dff0d0552eec43ed5f1e47e892f94f744e1c841d0ce7dd7f50b59987
generated
vendored
Normal file
2
conf/site/node_modules/.cache/terser-webpack-plugin/index-v5/f9/02/8939dff0d0552eec43ed5f1e47e892f94f744e1c841d0ce7dd7f50b59987
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
8a39a57b3bb26f1d4342484d21be25efafbc291e {"key":"{\"terser\":\"4.6.10\",\"terser-webpack-plugin\":\"2.3.5\",\"terser-webpack-plugin-options\":{\"test\":new RegExp(\"\\\\.m?js(\\\\?.*)?$\", \"i\"),\"chunkFilter\":() => true,\"warningsFilter\":() => true,\"extractComments\":true,\"sourceMap\":true,\"cache\":true,\"cacheKeys\":defaultCacheKeys => defaultCacheKeys,\"parallel\":true,\"include\":undefined,\"exclude\":undefined,\"minify\":undefined,\"terserOptions\":{\"compress\":{\"warnings\":false},\"output\":{\"comments\":false}}},\"nodeVersion\":\"v10.19.0\",\"filename\":\"\\u002Fjs\\u002Fapp.js\",\"contentHash\":\"8e1727faad414ed63f5a\"}","integrity":"sha512-U4MQfP0Ychbi+DznMhKoxR1keBIoK8cnREMeuDxrzJbrTAoDzmS38xlBXfTuALGPEWQp61nXaCClikyg8usYMg==","time":1592414990817,"size":2511848}
|
||||
17
conf/site/node_modules/csv-file-validator/.travis.yml
generated
vendored
Normal file
17
conf/site/node_modules/csv-file-validator/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- stable
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
install:
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- npm run test
|
||||
|
||||
after_success:
|
||||
- npm run report-coverage
|
||||
26
conf/site/node_modules/csv-file-validator/CONTRIBUTING.md
generated
vendored
Normal file
26
conf/site/node_modules/csv-file-validator/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
# Contributing
|
||||
|
||||
✨ Thank you for contributing ✨
|
||||
|
||||
This is just guideliness.
|
||||
|
||||
Please feel free to contribute by submitting PR's for improvements to code snippets, explanations, etc.
|
||||
|
||||
## Submitting an issue
|
||||
|
||||
Found a problem? Have an enhancement?
|
||||
|
||||
First of all see if your issue or idea has already been [reported](https://github.com/shystruk/csv-file-validator/issues).
|
||||
|
||||
If do not, open a [new one](https://github.com/shystruk/csv-file-validator/issues/new).
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
- Fork this repository
|
||||
- Clone fork `git clone ...`
|
||||
- Navigate to the cloned directory
|
||||
- Crate a new branch for the feature `git checkout -b new-feature`
|
||||
- Make changes
|
||||
- Commit changes `git commit -am 'What is feature about? :)'`
|
||||
- Push to the branch `git push origin new-feature`
|
||||
- Submit a PR
|
||||
21
conf/site/node_modules/csv-file-validator/LICENSE
generated
vendored
Normal file
21
conf/site/node_modules/csv-file-validator/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Vasyl Stokolosa <v.stokol@gmail.com>
|
||||
|
||||
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.
|
||||
167
conf/site/node_modules/csv-file-validator/README.md
generated
vendored
Normal file
167
conf/site/node_modules/csv-file-validator/README.md
generated
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
# CSV File Validator [](https://twitter.com/intent/tweet?hashtags=javascript&original_referer=https%3A%2F%2Fpublish.twitter.com%2F&ref_src=twsrc%5Etfw&text=Validation%20of%20CSV%20file%20against%20user%20defined%20schema%20(returns%20back%20object%20with%20data%20and%20invalid%20messages)&tw_p=tweetbutton&url=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fcsv-file-validator&via=shystrukk) #
|
||||
[](https://opensource.org/licenses/mit-license.php)
|
||||
[](https://codecov.io/gh/shystruk/csv-file-validator)
|
||||
[](https://travis-ci.org/shystruk/csv-file-validator)
|
||||
[](https://snyk.io/test/github/shystruk/csv-file-validator?targetFile=package.json)
|
||||
[](https://badge.fury.io/js/csv-file-validator)
|
||||
|
||||
Validation of CSV file against user defined schema (returns back object with data and invalid messages)
|
||||
|
||||
## Getting csv-file-validator ##
|
||||
|
||||
#### npm
|
||||
`npm install --save csv-file-validator`
|
||||
|
||||
#### yarn
|
||||
`yarn add csv-file-validator --save`
|
||||
|
||||
## Example ##
|
||||
```javascript
|
||||
import CSVFileValidator from 'csv-file-validator'
|
||||
|
||||
CSVFileValidator(file, config)
|
||||
.then(csvData => {
|
||||
csvData.data // Array of objects from file
|
||||
csvData.inValidMessages // Array of error messages
|
||||
})
|
||||
.catch(err => {})
|
||||
```
|
||||
|
||||
Please see **Demo** for more details **/demo/index.html**
|
||||
|
||||

|
||||
|
||||
## API ##
|
||||
### CSVFileValidator(file, config) ###
|
||||
returns the Promise
|
||||
|
||||
## file ##
|
||||
Type: `File` <br>
|
||||
|
||||
.csv file
|
||||
|
||||
## config ##
|
||||
Type: `Object` <br>
|
||||
|
||||
Config object should contain **headers** array, array of row header (title) objects
|
||||
```javascript
|
||||
const config = {
|
||||
headers: []
|
||||
}
|
||||
```
|
||||
|
||||
### name
|
||||
Type: `String` <br>
|
||||
name of the row header (title)
|
||||
|
||||
### inputName
|
||||
Type: `String` <br>
|
||||
key name which will be return with value in a column
|
||||
|
||||
### optional
|
||||
Type: `Boolean` <br>
|
||||
|
||||
Makes column optional. If true column value will be return
|
||||
|
||||
### headerError
|
||||
Type: `Function` <br>
|
||||
|
||||
If a header name is omitted or is not the same as in config *name* headerError function will be called with arguments
|
||||
**headerName**
|
||||
|
||||
### required
|
||||
Type: `Boolean` <br>
|
||||
|
||||
If required is true than a column value will be checked if it is not empty
|
||||
|
||||
### requiredError
|
||||
Type: `Function` <br>
|
||||
|
||||
If value is empty requiredError function will be called with arguments
|
||||
**headerName, rowNumber, columnNumber**
|
||||
|
||||
### unique
|
||||
Type: `Boolean` <br>
|
||||
|
||||
If it is true all header (title) column values will be checked for uniqueness
|
||||
|
||||
### uniqueError
|
||||
Type: `Function` <br>
|
||||
|
||||
If one of the header value is not unique uniqueError function will be called with argument **headerName**
|
||||
|
||||
### validate
|
||||
Type: `Function` <br>
|
||||
|
||||
Validate column value. As an argument column value will be passed
|
||||
For e.g.
|
||||
```javascript
|
||||
function(email) {
|
||||
return isEmailValid(email)
|
||||
}
|
||||
```
|
||||
|
||||
### validateError
|
||||
Type: `Function` <br>
|
||||
|
||||
If validate returns false validateError function will be called with arguments **headerName, rowNumber, columnNumber**
|
||||
|
||||
### isArray
|
||||
Type: `Boolean` <br>
|
||||
|
||||
If column contains list of values separated by comma in return object it will be as an array
|
||||
|
||||
#### Config example ####
|
||||
```javascript
|
||||
const config = {
|
||||
headers: [
|
||||
{
|
||||
name: 'First Name',
|
||||
inputName: 'firstName',
|
||||
required: true,
|
||||
requiredError: function (headerName, rowNumber, columnNumber) {
|
||||
return `${headerName} is required in the ${rowNumber} row / ${columnNumber} column`
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Last Name',
|
||||
inputName: 'lastName',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
name: 'Email',
|
||||
inputName: 'email',
|
||||
unique: true,
|
||||
uniqueError: function (headerName) {
|
||||
return `${headerName} is not unique`
|
||||
},
|
||||
validate: function(email) {
|
||||
return isEmailValid(email)
|
||||
},
|
||||
validateError: function (headerName, rowNumber, columnNumber) {
|
||||
return `${headerName} is not valid in the ${rowNumber} row / ${columnNumber} column`
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Roles',
|
||||
inputName: 'roles',
|
||||
isArray: true
|
||||
},
|
||||
{
|
||||
name: 'Country',
|
||||
inputName: 'country',
|
||||
optional: true
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Any contributions you make **are greatly appreciated**.
|
||||
|
||||
Please read the [Contributions Guidelines](CONTRIBUTING.md) before submitting a PR.
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Vasyl Stokolosa](https://about.me/shystruk)
|
||||
4
conf/site/node_modules/csv-file-validator/demo/demo.csv
generated
vendored
Normal file
4
conf/site/node_modules/csv-file-validator/demo/demo.csv
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
First Name;Last Name;Email;Password;Roles
|
||||
Vasyl;Stokolosa;api01@test.com;123;admin, manager
|
||||
Vasyl_2;Stokolosa_2;api01@test.com;123123123;admin, manager, user
|
||||
;Stokolosa_2;api@test;123123123;admin, manager, user
|
||||
|
BIN
conf/site/node_modules/csv-file-validator/demo/demo.png
generated
vendored
Normal file
BIN
conf/site/node_modules/csv-file-validator/demo/demo.png
generated
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
7
conf/site/node_modules/csv-file-validator/demo/dist/bundle.js
generated
vendored
Normal file
7
conf/site/node_modules/csv-file-validator/demo/dist/bundle.js
generated
vendored
Normal file
File diff suppressed because one or more lines are too long
19
conf/site/node_modules/csv-file-validator/demo/index.html
generated
vendored
Normal file
19
conf/site/node_modules/csv-file-validator/demo/index.html
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>CSV File Validator Demo</title>
|
||||
<style>
|
||||
.red {
|
||||
color: red;
|
||||
font-size: 16px;
|
||||
margin: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<input type="file" accept=".csv" id="file" />
|
||||
|
||||
<div id="invalidMessages"></div>
|
||||
|
||||
<script src="dist/bundle.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
39
conf/site/node_modules/csv-file-validator/demo/index.js
generated
vendored
Normal file
39
conf/site/node_modules/csv-file-validator/demo/index.js
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
import CSVFileValidator from '../src/csv-file-validator'
|
||||
|
||||
const requiredError = (headerName, rowNumber, columnNumber) => {
|
||||
return `<div class="red">${headerName} is required in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
|
||||
}
|
||||
const validateError = (headerName, rowNumber, columnNumber) => {
|
||||
return `<div class="red">${headerName} is not valid in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
|
||||
}
|
||||
const uniqueError = (headerName) => {
|
||||
return `<div class="red">${headerName} is not unique</div>`
|
||||
}
|
||||
const isEmailValid = function (email) {
|
||||
const reqExp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/
|
||||
return reqExp.test(email)
|
||||
}
|
||||
const isPasswordValid = function (password) {
|
||||
return password.length >= 4
|
||||
}
|
||||
|
||||
const CSVConfig = {
|
||||
headers: [
|
||||
{ name: 'First Name', inputName: 'firstName', required: true, requiredError },
|
||||
{ name: 'Last Name', inputName: 'lastName', required: true, requiredError },
|
||||
{ name: 'Email', inputName: 'email', required: true, requiredError, unique: true, uniqueError, validate: isEmailValid, validateError },
|
||||
{ name: 'Password', inputName: 'password', required: true, requiredError, validate: isPasswordValid, validateError },
|
||||
{ name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true }
|
||||
]
|
||||
}
|
||||
|
||||
document.getElementById('file').onchange = function(event) {
|
||||
CSVFileValidator(event.target.files[0], CSVConfig)
|
||||
.then(csvData => {
|
||||
csvData.inValidMessages.forEach(message => {
|
||||
document.getElementById('invalidMessages').insertAdjacentHTML('beforeend', message)
|
||||
})
|
||||
console.log(csvData.inValidMessages)
|
||||
console.log(csvData.data)
|
||||
})
|
||||
}
|
||||
4088
conf/site/node_modules/csv-file-validator/demo/package-lock.json
generated
vendored
Normal file
4088
conf/site/node_modules/csv-file-validator/demo/package-lock.json
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
16
conf/site/node_modules/csv-file-validator/demo/package.json
generated
vendored
Normal file
16
conf/site/node_modules/csv-file-validator/demo/package.json
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"name": "demo",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
"name": "Vasyl Stokolosa",
|
||||
"email": "v.stokol@gmail.com",
|
||||
"url": "https://github.com/shystruk"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
9
conf/site/node_modules/csv-file-validator/demo/webpack.config.js
generated
vendored
Normal file
9
conf/site/node_modules/csv-file-validator/demo/webpack.config.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: './index.js',
|
||||
output: {
|
||||
filename: 'bundle.js',
|
||||
path: path.resolve(__dirname, 'dist')
|
||||
}
|
||||
};
|
||||
75
conf/site/node_modules/csv-file-validator/package.json
generated
vendored
Normal file
75
conf/site/node_modules/csv-file-validator/package.json
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"csv-file-validator@1.8.0",
|
||||
"/home/henry/Documents/git/Speedtest-tracker-docker/conf/site"
|
||||
]
|
||||
],
|
||||
"_from": "csv-file-validator@1.8.0",
|
||||
"_id": "csv-file-validator@1.8.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-+/wdJxbe9zk1KJv7GC5aCVOVrg10W7xWIypILuQsJ3ocegF/YueTarb8Dqg1snEfkPmh2aCjbhVXnu1gM3RRIA==",
|
||||
"_location": "/csv-file-validator",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "csv-file-validator@1.8.0",
|
||||
"name": "csv-file-validator",
|
||||
"escapedName": "csv-file-validator",
|
||||
"rawSpec": "1.8.0",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "1.8.0"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/csv-file-validator/-/csv-file-validator-1.8.0.tgz",
|
||||
"_spec": "1.8.0",
|
||||
"_where": "/home/henry/Documents/git/Speedtest-tracker-docker/conf/site",
|
||||
"author": {
|
||||
"name": "Vasyl Stokolosa",
|
||||
"email": "v.stokol@gmail.com",
|
||||
"url": "https://github.com/shystruk"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/shystruk/csv-file-validator/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"famulus": "2.1.2",
|
||||
"lodash": "4.17.15",
|
||||
"papaparse": "^5.2.0"
|
||||
},
|
||||
"description": "Validation of CSV file against user defined schema (returns back object with data and invalid messages)",
|
||||
"devDependencies": {
|
||||
"ava": "^0.25.0",
|
||||
"codecov.io": "^0.1.6",
|
||||
"nyc": "^11.4.1"
|
||||
},
|
||||
"directories": {
|
||||
"src": "src"
|
||||
},
|
||||
"homepage": "https://github.com/shystruk/csv-file-validator#readme",
|
||||
"keywords": [
|
||||
"csv parser",
|
||||
"parser",
|
||||
"validator",
|
||||
"csv validator",
|
||||
"csv file validator",
|
||||
"reviewer",
|
||||
"csv reviewer"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "./src/csv-file-validator.js",
|
||||
"name": "csv-file-validator",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/shystruk/csv-file-validator.git"
|
||||
},
|
||||
"scripts": {
|
||||
"coverage": "nyc report --reporter=lcov",
|
||||
"report-coverage": "cat ./coverage/lcov.info | codecov",
|
||||
"test": "nyc ava --browser && npm run coverage"
|
||||
},
|
||||
"version": "1.8.0"
|
||||
}
|
||||
140
conf/site/node_modules/csv-file-validator/src/csv-file-validator.js
generated
vendored
Normal file
140
conf/site/node_modules/csv-file-validator/src/csv-file-validator.js
generated
vendored
Normal file
@@ -0,0 +1,140 @@
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined'
|
||||
? module.exports = factory(require('papaparse'), require('lodash/uniqBy'), require('lodash/isFunction'), require('famulus/isValuesUnique'))
|
||||
: typeof define === 'function' && define.amd
|
||||
? define(['papaparse', 'lodash/uniqBy', 'lodash/isFunction', 'famulus/isValuesUnique'], factory)
|
||||
: (global.myBundle = factory(global.Papa,global._uniqBy,global._isFunction, global.isValuesUnique));
|
||||
}(this, (function (Papa, _uniqBy, _isFunction, isValuesUnique) {
|
||||
'use strict';
|
||||
|
||||
Papa = Papa && Papa.hasOwnProperty('default') ? Papa['default'] : Papa;
|
||||
isValuesUnique = isValuesUnique && isValuesUnique.hasOwnProperty('default') ? isValuesUnique['default'] : isValuesUnique;
|
||||
_uniqBy = _uniqBy && _uniqBy.hasOwnProperty('default') ? _uniqBy['default'] : _uniqBy;
|
||||
_isFunction = _isFunction && _isFunction.hasOwnProperty('default') ? _isFunction['default'] : _isFunction;
|
||||
|
||||
/**
|
||||
* @param {File} csvFile
|
||||
* @param {Object} config
|
||||
*/
|
||||
function CSVFileValidator(csvFile, config) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
Papa.parse(csvFile, {
|
||||
complete: function(results) {
|
||||
resolve(_prepareDataAndValidateFile(results.data, config));
|
||||
},
|
||||
error: function(error, file) {
|
||||
reject({ error: error, file: file });
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array} csvData
|
||||
* @param {Object} config
|
||||
* @private
|
||||
*/
|
||||
function _prepareDataAndValidateFile(csvData, config) {
|
||||
const file = {
|
||||
inValidMessages: [],
|
||||
data: []
|
||||
};
|
||||
|
||||
csvData.forEach(function(row, rowIndex) {
|
||||
const columnData = {};
|
||||
const headers = [];
|
||||
|
||||
for (let i = 0; i < config.headers.length; i++) {
|
||||
const data = config.headers[i];
|
||||
|
||||
if (!data.optional) {
|
||||
headers.push(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (row.length < headers.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
row.forEach(function(columnValue, columnIndex) {
|
||||
const valueConfig = config.headers[columnIndex];
|
||||
|
||||
if (!valueConfig) {
|
||||
return;
|
||||
}
|
||||
|
||||
// header validation
|
||||
if (rowIndex === 0) {
|
||||
if (valueConfig.name !== columnValue) {
|
||||
file.inValidMessages.push(
|
||||
_isFunction(valueConfig.headerError)
|
||||
? valueConfig.headerError(columnValue)
|
||||
: 'Header name ' + columnValue + ' is not correct or missing'
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueConfig.required && !columnValue.length) {
|
||||
file.inValidMessages.push(
|
||||
_isFunction(valueConfig.requiredError)
|
||||
? valueConfig.requiredError(valueConfig.name, rowIndex + 1, columnIndex + 1)
|
||||
: String(valueConfig.name + ' is required in the ' + (rowIndex + 1) + ' row / ' + (columnIndex + 1) + ' column')
|
||||
);
|
||||
} else if (valueConfig.validate && !valueConfig.validate(columnValue)) {
|
||||
file.inValidMessages.push(
|
||||
_isFunction(valueConfig.validateError)
|
||||
? valueConfig.validateError(valueConfig.name, rowIndex + 1, columnIndex + 1)
|
||||
: String(valueConfig.name + ' is not valid in the ' + (rowIndex + 1) + ' row / ' + (columnIndex + 1) + ' column')
|
||||
);
|
||||
}
|
||||
|
||||
if (valueConfig.optional) {
|
||||
columnData[valueConfig.inputName] = columnValue;
|
||||
}
|
||||
|
||||
if (valueConfig.isArray) {
|
||||
columnData[valueConfig.inputName] = columnValue.split(',').map(function(value) {
|
||||
return value.trim();
|
||||
});
|
||||
} else {
|
||||
columnData[valueConfig.inputName] = columnValue;
|
||||
}
|
||||
});
|
||||
|
||||
file.data.push(columnData);
|
||||
});
|
||||
|
||||
_checkUniqueFields(file, config);
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} file
|
||||
* @param {Object} config
|
||||
* @private
|
||||
*/
|
||||
function _checkUniqueFields(file, config) {
|
||||
if (!file.data.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
config.headers
|
||||
.filter(function(header) {
|
||||
return header.unique
|
||||
})
|
||||
.forEach(function(header) {
|
||||
if (!isValuesUnique(file.data, header.inputName)) {
|
||||
file.inValidMessages.push(
|
||||
_isFunction(header.uniqueError)
|
||||
? header.uniqueError(header.name)
|
||||
: String(header.name + ' is not unique')
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return CSVFileValidator;
|
||||
})));
|
||||
71
conf/site/node_modules/csv-file-validator/test.js
generated
vendored
Normal file
71
conf/site/node_modules/csv-file-validator/test.js
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
import test from 'ava';
|
||||
import CSVFileValidator from './src/csv-file-validator';
|
||||
|
||||
const CSVInvalidFile = `
|
||||
Vasyl;Stokolosa;v.stokol@gmail.com;123;admin,manager\n
|
||||
Vasyl_2;"";v.stokol@gmail.com;123123123;user
|
||||
`;
|
||||
|
||||
const CSVValidFile = `
|
||||
Vasyl;Stokolosa;v.stokol@gmail.com;123123;admin,manager\n
|
||||
Vasyl;Stokolosa;fake@test.com;123123123;user;Ukraine
|
||||
`;
|
||||
|
||||
const requiredError = (headerName, rowNumber, columnNumber) => (
|
||||
`<div class="red">${headerName} is required in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
|
||||
)
|
||||
|
||||
const validateError = (headerName, rowNumber, columnNumber) => (
|
||||
`<div class="red">${headerName} is not valid in the <strong>${rowNumber} row</strong> / <strong>${columnNumber} column</strong></div>`
|
||||
)
|
||||
|
||||
const isEmailValid = (email) => {
|
||||
const reqExp = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,3}$/
|
||||
return reqExp.test(email)
|
||||
}
|
||||
|
||||
const isPasswordValid = (password) => (password.length >= 4)
|
||||
const uniqueError = (headerName) => (`<div class="red">${headerName} is not unique</div>`)
|
||||
|
||||
const CSVConfig = {
|
||||
headers: [
|
||||
{ name: 'First Name', inputName: 'firstName', required: true, requiredError },
|
||||
{ name: 'Last Name', inputName: 'lastName', required: true, requiredError },
|
||||
{ name: 'Email', inputName: 'email', required: true, requiredError, unique: true, uniqueError, validate: isEmailValid, validateError },
|
||||
{ name: 'Password', inputName: 'password', required: true, requiredError, validate: isPasswordValid, validateError },
|
||||
{ name: 'Roles', inputName: 'roles', required: true, requiredError, isArray: true },
|
||||
{ name: 'Country', inputName: 'country', optional: true }
|
||||
]
|
||||
}
|
||||
|
||||
test('module should be a function', t => {
|
||||
t.is(typeof CSVFileValidator, 'function');
|
||||
});
|
||||
|
||||
test('should return an object with empty inValidMessages/data keys', async t => {
|
||||
const csvData = await CSVFileValidator('', {});
|
||||
|
||||
t.is(typeof csvData, 'object');
|
||||
t.deepEqual(csvData.inValidMessages, []);
|
||||
t.deepEqual(csvData.data, []);
|
||||
});
|
||||
|
||||
test('should validate .csv file and return invalid messages with data', async t => {
|
||||
const csvData = await CSVFileValidator(CSVInvalidFile, CSVConfig);
|
||||
|
||||
t.is(csvData.inValidMessages.length, 3);
|
||||
t.is(csvData.data.length, 2);
|
||||
});
|
||||
|
||||
test('should validate .csv file and return data, file is valid', async t => {
|
||||
const csvData = await CSVFileValidator(CSVValidFile, CSVConfig);
|
||||
|
||||
t.is(csvData.inValidMessages.length, 0);
|
||||
t.is(csvData.data.length, 2);
|
||||
});
|
||||
|
||||
test('should return optional column', async t => {
|
||||
const csvData = await CSVFileValidator(CSVValidFile, CSVConfig);
|
||||
|
||||
t.is(csvData.data[1].country, 'Ukraine');
|
||||
});
|
||||
77
conf/site/node_modules/famulus/.internal/dates.js
generated
vendored
Normal file
77
conf/site/node_modules/famulus/.internal/dates.js
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
var MILLISECONDS_IN_DAY = 86400000;
|
||||
var MILLISECONDS_IN_HOUR = 3600000;
|
||||
var MILLISECONDS_IN_MINUTE = 60000;
|
||||
|
||||
var HOURS_IN_DAY = 24;
|
||||
var MINUTES_IN_DAY = 1440;
|
||||
var MINUTES_IN_HOUR = 60;
|
||||
|
||||
/**
|
||||
* @param {Number} milliseconds
|
||||
* @return {Number}
|
||||
* @private
|
||||
*/
|
||||
function _getDaysDiff(milliseconds) {
|
||||
return Math.abs(Math.floor(milliseconds / MILLISECONDS_IN_DAY));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} milliseconds
|
||||
* @return {Number}
|
||||
* @private
|
||||
*/
|
||||
function _getHoursDiff(milliseconds) {
|
||||
return Math.abs(Math.floor((milliseconds % MILLISECONDS_IN_DAY) / MILLISECONDS_IN_HOUR));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} milliseconds
|
||||
* @return {Number}
|
||||
* @private
|
||||
*/
|
||||
function _getMinutesDiff(milliseconds) {
|
||||
return Math.abs(Math.round(((milliseconds % MILLISECONDS_IN_DAY) % MILLISECONDS_IN_HOUR) / MILLISECONDS_IN_MINUTE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} milliseconds
|
||||
* @return {Number}
|
||||
* @private
|
||||
*/
|
||||
function _differenceInHours(milliseconds) {
|
||||
var days = _getDaysDiff(milliseconds);
|
||||
|
||||
if (days !== 0) {
|
||||
return _getHoursDiff(milliseconds) + (days * HOURS_IN_DAY);
|
||||
}
|
||||
|
||||
return _getHoursDiff(milliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} milliseconds
|
||||
* @return {Number}
|
||||
* @private
|
||||
*/
|
||||
function _differenceInMinutes(milliseconds) {
|
||||
var days = _getDaysDiff(milliseconds),
|
||||
hours = _getHoursDiff(milliseconds);
|
||||
|
||||
if (days !== 0) {
|
||||
days = days * MINUTES_IN_DAY;
|
||||
}
|
||||
|
||||
if (hours !== 0) {
|
||||
hours = hours * MINUTES_IN_HOUR;
|
||||
}
|
||||
|
||||
return _getMinutesDiff(milliseconds) + days + hours;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
_getDaysDiff: _getDaysDiff,
|
||||
_getHoursDiff: _getHoursDiff,
|
||||
_getMinutesDiff: _getMinutesDiff,
|
||||
_differenceInHours: _differenceInHours,
|
||||
_differenceInMinutes: _differenceInMinutes
|
||||
};
|
||||
17
conf/site/node_modules/famulus/.travis.yml
generated
vendored
Normal file
17
conf/site/node_modules/famulus/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- stable
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
|
||||
install:
|
||||
- npm install
|
||||
|
||||
script:
|
||||
- npm run test
|
||||
|
||||
after_success:
|
||||
- npm run report-coverage
|
||||
9
conf/site/node_modules/famulus/CHANGELOG.md
generated
vendored
Normal file
9
conf/site/node_modules/famulus/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
## 2.1.0 (July 9, 2018)
|
||||
|
||||
* Added [objectInterface](docs/objectInterface.md) (in [#2](https://github.com/shystruk/famulus/pull/2))
|
||||
|
||||
|
||||
## 2.0.0 (June 26, 2018)
|
||||
|
||||
* Added [dateDifferenceFromNow](docs/dateDifferenceFromNow.md) (in [#1](https://github.com/shystruk/famulus/pull/1))
|
||||
* [dateDifference](docs/dateDifference.md) compares two dates (in [#1](https://github.com/shystruk/famulus/pull/1))
|
||||
39
conf/site/node_modules/famulus/CONTRIBUTING.md
generated
vendored
Normal file
39
conf/site/node_modules/famulus/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
# Contributing
|
||||
|
||||
✨ Thank you for contributing ✨
|
||||
|
||||
Please feel free to contribute by submitting PR's with your own helper, improvement to code snippets, explanations, etc.
|
||||
|
||||
|
||||
## Submitting an issue
|
||||
|
||||
Found a problem? Have an enhancement?
|
||||
|
||||
First of all see if your issue or idea has already been [reported](https://github.com/shystruk/famulus/issues).
|
||||
|
||||
If do not, open a [new one](https://github.com/shystruk/famulus/issues/new).
|
||||
|
||||
|
||||
## Add your own helper
|
||||
|
||||
- Please take for example helper **isValuesUnique.js**
|
||||
- Create *[helper_name].js* file in the root folder
|
||||
- In docs folder create *[helper_name].md* file and fill in data keeping the structure
|
||||
- In tests folder create *[helper_name].test.js* file and cover the helper by unit tests and much as possible
|
||||
- In famulus.js file import and export already created helper
|
||||
- In documentation section at README.md file add the helper to a specific category. If the category does not exist add the new one
|
||||
- Before submitting a PR please check *Submitting a pull request* section below
|
||||
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
- Fork this repository
|
||||
- Clone fork `git clone ...`
|
||||
- Navigate to the cloned directory
|
||||
- Install all dependencies `npm install`
|
||||
- Crate a new branch for the feature `git checkout -b new-feature`
|
||||
- Make changes
|
||||
- Run tests `npm run test`
|
||||
- Commit changes `git commit -am 'What is feature about? :)'`
|
||||
- Push to the branch `git push origin new-feature`
|
||||
- Submit a PR
|
||||
21
conf/site/node_modules/famulus/LICENSE
generated
vendored
Normal file
21
conf/site/node_modules/famulus/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Vasyl Stokolosa <v.stokol@gmail.com>
|
||||
|
||||
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.
|
||||
56
conf/site/node_modules/famulus/README.md
generated
vendored
Normal file
56
conf/site/node_modules/famulus/README.md
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
# famulus [](https://twitter.com/intent/tweet?hashtags=javascript%20%23helpers%20%23utilities&original_referer=https%3A%2F%2Fpublish.twitter.com%2F&ref_src=twsrc%5Etfw&text=JavaScript%20library%20that%20provides%20a%20useful%20functional%20programming%20helpers.%20Do%20not%20wait%2C%20add%20your%20own%20%F0%9F%94%A7%F0%9F%92%AA&tw_p=tweetbutton&url=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Ffamulus&via=shystrukk) #
|
||||
[](https://opensource.org/licenses/mit-license.php) [](https://codecov.io/gh/shystruk/famulus) [](https://travis-ci.org/shystruk/famulus)
|
||||
[](https://snyk.io/test/github/shystruk/famulus?targetFile=package.json)
|
||||
[](https://badge.fury.io/js/famulus)
|
||||
|
||||
JavaScript library that provides a useful functional programming helpers. Do not wait, fork and add your own 🔧💪
|
||||
|
||||
## Installation
|
||||
#### npm
|
||||
`npm install --save famulus`
|
||||
|
||||
#### yarn
|
||||
`yarn add famulus --save`
|
||||
|
||||
## Usage
|
||||
#### ES6 module
|
||||
```javascript
|
||||
import famulus from 'famulus'
|
||||
```
|
||||
```javascript
|
||||
import helper from 'famulus/helper'
|
||||
```
|
||||
|
||||
#### Node.js
|
||||
```javascript
|
||||
const famulus = require('famulus')
|
||||
```
|
||||
```javascript
|
||||
const helper = require('famulus/helper')
|
||||
```
|
||||
|
||||
## Documentation
|
||||
### String
|
||||
- [substr](docs/substr.md)
|
||||
|
||||
|
||||
### Object
|
||||
- [objectInterface](docs/objectInterface.md)
|
||||
|
||||
|
||||
### Array
|
||||
- [sortAndAddFirstElement](docs/sortAndAddFirstElement.md)
|
||||
- [isValuesUnique](docs/isValuesUnique.md)
|
||||
|
||||
|
||||
### Date
|
||||
- [dateDifferenceFromNow](docs/dateDifferenceFromNow.md)
|
||||
- [dateDifference](docs/dateDifference.md)
|
||||
|
||||
## Contributing
|
||||
|
||||
Please read the [Contributions Guidelines](CONTRIBUTING.md) before adding your own helper or improvement to code snippets, explanations, etc.
|
||||
|
||||
## License
|
||||
|
||||
MIT © [Vasyl Stokolosa](https://about.me/shystruk)
|
||||
57
conf/site/node_modules/famulus/dateDifference.js
generated
vendored
Normal file
57
conf/site/node_modules/famulus/dateDifference.js
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
var dates = require('./.internal/dates');
|
||||
|
||||
/**
|
||||
* Difference between dates which are passed, in formats 'milliseconds', 'days', 'hours', 'minutes'
|
||||
*
|
||||
* @customNeeds -
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @category Date
|
||||
*
|
||||
* @param {Date} date1 - The Date for compare
|
||||
* @param {Date} date2 - The Date for compare
|
||||
* @param {String} differenceType - [ 'days', 'hours', 'minutes', 'milliseconds', 'all' ]
|
||||
*
|
||||
* @returns {Number|Object} Returns the numeric value or object depends on passed differenceType param
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* famulus.dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'days')
|
||||
* // => 6
|
||||
*
|
||||
* famulus.dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'hours')
|
||||
* // => 144
|
||||
*
|
||||
* famulus.dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'minutes')
|
||||
* // => 8640
|
||||
*
|
||||
* famulus.dateDifference(new Date('06-26-2018'), new Date('06-20-2018'), 'milliseconds')
|
||||
* // => 518400000
|
||||
*
|
||||
* famulus.dateDifference(new Date('06-26-2018 10:10'), new Date('06-20-2018 08:00'), 'all')
|
||||
* // => {days: 6, hours: 2, minutes: 10, milliseconds: 526200000}
|
||||
*/
|
||||
function dateDifference(date1, date2, differenceType) {
|
||||
var diffMilliseconds = Math.abs(date1 - date2);
|
||||
|
||||
switch(differenceType) {
|
||||
case 'days':
|
||||
return dates._getDaysDiff(diffMilliseconds);
|
||||
case 'hours':
|
||||
return dates._differenceInHours(diffMilliseconds);
|
||||
case 'minutes':
|
||||
return dates._differenceInMinutes(diffMilliseconds);
|
||||
case 'milliseconds':
|
||||
return diffMilliseconds;
|
||||
|
||||
default:
|
||||
return {
|
||||
days: dates._getDaysDiff(diffMilliseconds),
|
||||
hours: dates._getHoursDiff(diffMilliseconds),
|
||||
minutes: dates._getMinutesDiff(diffMilliseconds),
|
||||
milliseconds: diffMilliseconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = dateDifference;
|
||||
52
conf/site/node_modules/famulus/dateDifferenceFromNow.js
generated
vendored
Normal file
52
conf/site/node_modules/famulus/dateDifferenceFromNow.js
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
var dates = require('./.internal/dates');
|
||||
|
||||
/**
|
||||
* Difference between now and date which is passed, in formats 'milliseconds', 'days', 'hours', 'minutes'
|
||||
*
|
||||
* @customNeeds -
|
||||
*
|
||||
* @since 2.0.0
|
||||
* @category Date
|
||||
*
|
||||
* @param {Date} date - The Date to inspect
|
||||
* @param {String} differenceType - [ 'days', 'hours', 'minutes', 'milliseconds', 'all' ]
|
||||
*
|
||||
* @returns {Number|Object} Returns the numeric value or object depends on passed differenceType param
|
||||
*
|
||||
* @example
|
||||
* example result for now is new Date('12-26-2017')
|
||||
*
|
||||
* famulus.dateDifferenceFromNow(new Date('12-20-2017'), 'milliseconds')
|
||||
* // => 555261242
|
||||
*
|
||||
* famulus.dateDifferenceFromNow(new Date('12-20-2017'), 'days')
|
||||
* // => 6
|
||||
*
|
||||
* famulus.dateDifferenceFromNow(new Date('12-20-2017'), 'hours')
|
||||
* // => 156
|
||||
*/
|
||||
function dateDifferenceFromNow(date, differenceType) {
|
||||
var now = new Date(),
|
||||
diffMilliseconds = Math.abs(date - now);
|
||||
|
||||
switch(differenceType) {
|
||||
case 'days':
|
||||
return dates._getDaysDiff(diffMilliseconds);
|
||||
case 'hours':
|
||||
return dates._differenceInHours(diffMilliseconds);
|
||||
case 'minutes':
|
||||
return dates._differenceInMinutes(diffMilliseconds);
|
||||
case 'milliseconds':
|
||||
return diffMilliseconds;
|
||||
|
||||
default:
|
||||
return {
|
||||
days: dates._getDaysDiff(diffMilliseconds),
|
||||
hours: dates._getHoursDiff(diffMilliseconds),
|
||||
minutes: dates._getMinutesDiff(diffMilliseconds),
|
||||
milliseconds: diffMilliseconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = dateDifferenceFromNow;
|
||||
47
conf/site/node_modules/famulus/docs/dateDifference.md
generated
vendored
Normal file
47
conf/site/node_modules/famulus/docs/dateDifference.md
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
## dateDifference(date1, date2, differenceType)
|
||||
[Source](../dateDifference.js)
|
||||
|
||||
Difference between dates which are passed, in formats 'milliseconds', 'days', 'hours', 'minutes'
|
||||
|
||||
#### Custom Needs
|
||||
|
||||
#### Since
|
||||
2.0.0
|
||||
|
||||
#### Category
|
||||
Date
|
||||
|
||||
#### Arguments
|
||||
{Date} date1 - The Date for compare<br>
|
||||
{Date} date2 - The Date for compare<br>
|
||||
{String} differenceType - [ 'days', 'hours', 'minutes', 'milliseconds', 'all' ]
|
||||
|
||||
#### Returns
|
||||
{Number|Object} Returns the numeric value or object depends on passed differenceType param
|
||||
|
||||
#### Example
|
||||
|
||||
```javascript
|
||||
dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'days')
|
||||
// => 6
|
||||
```
|
||||
|
||||
```javascript
|
||||
dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'hours')
|
||||
// => 144
|
||||
```
|
||||
|
||||
```javascript
|
||||
dateDifference(new Date('06-20-2018'), new Date('06-26-2018'), 'minutes')
|
||||
// => 8640
|
||||
```
|
||||
|
||||
```javascript
|
||||
dateDifference(new Date('06-26-2018'), new Date('06-20-2018'), 'milliseconds')
|
||||
// => 518400000
|
||||
```
|
||||
|
||||
```javascript
|
||||
dateDifference(new Date('06-26-2018 10:10'), new Date('06-20-2018 08:00'), 'all')
|
||||
// => {days: 6, hours: 2, minutes: 10, milliseconds: 526200000}
|
||||
```
|
||||
47
conf/site/node_modules/famulus/docs/dateDifferenceFromNow.md
generated
vendored
Normal file
47
conf/site/node_modules/famulus/docs/dateDifferenceFromNow.md
generated
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
## dateDifferenceFromNow(date, differenceType)
|
||||
[Source](../dateDifferenceFromNow.js)
|
||||
|
||||
Difference between now and date which is passed, in formats 'milliseconds', 'days', 'hours', 'minutes'
|
||||
|
||||
#### Custom Needs
|
||||
|
||||
#### Since
|
||||
2.0.0
|
||||
|
||||
#### Category
|
||||
Date
|
||||
|
||||
#### Arguments
|
||||
{Date} date - The Date to inspect<br>
|
||||
{String} differenceType - [ 'days', 'hours', 'minutes', 'milliseconds', 'all' ]
|
||||
|
||||
#### Returns
|
||||
{Number|Object} Returns the numeric value or object depends on passed differenceType param
|
||||
|
||||
#### Example
|
||||
Example result for now is Date('12-26-2017')
|
||||
|
||||
```javascript
|
||||
dateDifferenceFromNow(new Date('12-20-2017'), 'days')
|
||||
// => 6
|
||||
```
|
||||
|
||||
```javascript
|
||||
dateDifferenceFromNow(new Date('12-20-2017'), 'hours')
|
||||
// => 156
|
||||
```
|
||||
|
||||
```javascript
|
||||
dateDifferenceFromNow(new Date('12-20-2017'), 'minutes')
|
||||
// => 9381
|
||||
```
|
||||
|
||||
```javascript
|
||||
dateDifferenceFromNow(new Date('12-20-2017'), 'milliseconds')
|
||||
// => 555261242
|
||||
```
|
||||
|
||||
```javascript
|
||||
dateDifferenceFromNow(new Date('12-20-2017'), 'all')
|
||||
// => {days: 6, hours: 12, minutes: 30, milliseconds: 563406381}
|
||||
```
|
||||
33
conf/site/node_modules/famulus/docs/isValuesUnique.md
generated
vendored
Normal file
33
conf/site/node_modules/famulus/docs/isValuesUnique.md
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
## isValuesUnique(array, keyName)
|
||||
[Source](../isValuesUnique.js)
|
||||
|
||||
Checking if values are unique
|
||||
|
||||
#### Custom Needs
|
||||
Having an array of objects and checking if values are unqiue by object key
|
||||
|
||||
#### Since
|
||||
1.3.0
|
||||
|
||||
#### Category
|
||||
Array
|
||||
|
||||
#### Arguments
|
||||
{Array} array - The array of objects<br>
|
||||
{String} keyName - Name of the object property from an array in which unique will be checking<br>
|
||||
|
||||
#### Returns
|
||||
{Boolean} Returns true if values are unique and false if not
|
||||
|
||||
#### Example
|
||||
Unique emails
|
||||
```javascript
|
||||
isValuesUnique([{email: 'api@test.com'}, {email: 'api@test.com'}], 'email');
|
||||
// => false
|
||||
```
|
||||
|
||||
Emails are not unique
|
||||
```javascript
|
||||
isValuesUnique([{email: 'api@test.com'}, {email: 'api_1@test.com'}], 'email');
|
||||
// => true
|
||||
```
|
||||
35
conf/site/node_modules/famulus/docs/objectInterface.md
generated
vendored
Normal file
35
conf/site/node_modules/famulus/docs/objectInterface.md
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
## objectInterface(config)(object)
|
||||
[Source](../objectInterface.js)
|
||||
|
||||
Interface for building an object by configuration
|
||||
|
||||
#### Custom Needs
|
||||
Have an interface for building an object based on configuration
|
||||
|
||||
#### Since
|
||||
2.1.0
|
||||
|
||||
#### Category
|
||||
Object
|
||||
|
||||
#### Arguments
|
||||
{Array} config - Keys with configuration<br>
|
||||
```
|
||||
[
|
||||
'key/value' - "OR" if no value, set value after "/",
|
||||
'key|this.firstName + " " + this.lastName' - set value from the expression after "|" which is bind to the passed object,
|
||||
'key:new Date()' - set value from the expression after ":"
|
||||
]
|
||||
```
|
||||
|
||||
#### Returns
|
||||
{Object}
|
||||
|
||||
#### Example
|
||||
```javascript
|
||||
var email = objectInterface(['body', 'count/1', 'sender|this.firstName + " " + this.lastName', 'isRead: false', 'created: new Date()'])
|
||||
// => function
|
||||
|
||||
email({body: 'Hello world!', count: '', firstName: 'Vasyl', lastName: 'Stokolosa', another: ''})
|
||||
// => {body: "Hello world!", count: 1, created: Mon Jul 09 2018 10:31:08, isRead: false, sender: "Vasyl Stokolosa"}
|
||||
```
|
||||
28
conf/site/node_modules/famulus/docs/sortAndAddFirstElement.md
generated
vendored
Normal file
28
conf/site/node_modules/famulus/docs/sortAndAddFirstElement.md
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
## sortAndAddFirstElement(array, sortBy, element)
|
||||
[Source](../sortAndAddFirstElement.js)
|
||||
|
||||
Sort an array by the name of existing property and add a first element into array
|
||||
|
||||
#### Custom Needs
|
||||
Sort array by name and add first element<br>
|
||||
For e.g. user names - [ {name: 'All'}, {name: 'Aron'}, {name: 'Bob'} ]
|
||||
|
||||
#### Since
|
||||
1.1.0
|
||||
|
||||
#### Category
|
||||
Array
|
||||
|
||||
#### Arguments
|
||||
{Array} array - The array to sort and add<br>
|
||||
{String} sortBy - Name of the property from an array by which array will be sorted<br>
|
||||
{*Any} element - Element which is added into an array
|
||||
|
||||
#### Returns
|
||||
{Array} Returns the new array
|
||||
|
||||
#### Example
|
||||
```javascript
|
||||
sortAndAddFirstElement([{name:'Bob'}, {name:'Aron'}], 'name', {name:'All'});
|
||||
// => [ {name:'All'}, {name:'Aron'}, {name:'Bob'} ]
|
||||
```
|
||||
38
conf/site/node_modules/famulus/docs/substr.md
generated
vendored
Normal file
38
conf/site/node_modules/famulus/docs/substr.md
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
## substr(string, start, length)
|
||||
[Source](../substr.js)
|
||||
|
||||
Extracts parts of a string, beginning at the character at the specified position, and returns the specified number of characters.
|
||||
The substr() does not change the original string.
|
||||
|
||||
#### Custom Needs
|
||||
Validate string type for preventing SyntaxError
|
||||
|
||||
#### Since
|
||||
1.0.0
|
||||
|
||||
#### Category
|
||||
String
|
||||
|
||||
#### Arguments
|
||||
{String} string - The string to extract<br>
|
||||
{Number} start - The position where to start the extraction. First character is at index 0<br>
|
||||
{Number?} length - Optional. The number of characters to extract. If omitted, it extracts the rest of the string
|
||||
|
||||
#### Returns
|
||||
{String} Returns extract part of a string
|
||||
|
||||
#### Example
|
||||
```javascript
|
||||
substr('Hello World!', 0, 5)
|
||||
// => 'Hello'
|
||||
```
|
||||
|
||||
```javascript
|
||||
substr({}, 0, 5)
|
||||
// => {}
|
||||
```
|
||||
|
||||
```javascript
|
||||
substr('Hello World!', 6)
|
||||
// => 'World!'
|
||||
```
|
||||
13
conf/site/node_modules/famulus/famulus.js
generated
vendored
Normal file
13
conf/site/node_modules/famulus/famulus.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
var substr = require('./substr');
|
||||
var sortAndAddFirstElement = require('./sortAndAddFirstElement');
|
||||
var dateDifferenceFromNow = require('./dateDifferenceFromNow');
|
||||
var isValuesUnique = require('./isValuesUnique');
|
||||
var objectInterface = require('./objectInterface');
|
||||
|
||||
module.exports = {
|
||||
substr: substr,
|
||||
sortAndAddFirstElement: sortAndAddFirstElement,
|
||||
dateDifferenceFromNow: dateDifferenceFromNow,
|
||||
isValuesUnique: isValuesUnique,
|
||||
objectInterface: objectInterface
|
||||
};
|
||||
26
conf/site/node_modules/famulus/isValuesUnique.js
generated
vendored
Normal file
26
conf/site/node_modules/famulus/isValuesUnique.js
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
var _uniqBy = require('lodash/uniqBy');
|
||||
|
||||
/**
|
||||
* Checking if values are unique
|
||||
*
|
||||
* @customNeeds
|
||||
* For e.g. [{email:'api@test.com'}, {email:'api@test.com'}] - email is not valid
|
||||
*
|
||||
* @since 1.3.0
|
||||
* @category Array
|
||||
*
|
||||
* @param {Array} array - The array of objects
|
||||
* @param {String} keyName - Name of the object property from an array in which unique will be checking
|
||||
*
|
||||
* @returns {Boolean} Returns true if values are unique and false if not
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* famulus.isValuesUnique([{email:'api@test.com'}, {email:'api@test.com'}], 'email')
|
||||
* // => false
|
||||
*/
|
||||
function isValuesUnique(array, keyName) {
|
||||
return _uniqBy(array, keyName).length === array.length;
|
||||
}
|
||||
|
||||
module.exports = isValuesUnique;
|
||||
51
conf/site/node_modules/famulus/objectInterface.js
generated
vendored
Normal file
51
conf/site/node_modules/famulus/objectInterface.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Interface for building an object by configuration
|
||||
*
|
||||
* @customNeeds - have an interface for building an object based on configuration
|
||||
*
|
||||
* @since 2.1.0
|
||||
* @category Object
|
||||
*
|
||||
* @param {Array} config - Keys with configuration
|
||||
* [
|
||||
* 'key/value' - "OR" if no value, set value after "/",
|
||||
* 'key|this.firstName + " " + this.lastName' - set value from the expression after "|" which is bind to the passed object,
|
||||
* 'key:new Date()' - set value from the expression after ":"
|
||||
* ]
|
||||
*
|
||||
* @returns {Function}
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* var email = objectInterface(['body', 'count/1', 'sender|this.firstName + " " + this.lastName', 'isRead: false', 'created: new Date()'])
|
||||
* // => function
|
||||
*
|
||||
* email({body: 'Hello world!', count: '', firstName: 'Vasyl', lastName: 'Stokolosa', another: ''})
|
||||
* // => {body: "Hello world!", count: 1, created: Mon Jul 09 2018 10:31:08, isRead: false, sender: "Vasyl Stokolosa"}
|
||||
*/
|
||||
function objectInterface(config) {
|
||||
return function(obj) {
|
||||
var result = {};
|
||||
|
||||
for (var i = 0; i < config.length; i++) {
|
||||
var OR, NEXT, REAL;
|
||||
|
||||
if ((OR = config[i].split('/')) && OR[1]) {
|
||||
result[OR[0]] = obj[OR[0]] || Function('return ' + OR[1])();
|
||||
}
|
||||
else if ((NEXT = config[i].split('|')) && NEXT[1]) {
|
||||
result[NEXT[0]] = Function('return ' + NEXT[1]).call(obj);
|
||||
}
|
||||
else if ((REAL = config[i].split(':')) && REAL[1]) {
|
||||
result[REAL[0]] = Function('return ' + REAL[1])();
|
||||
}
|
||||
else {
|
||||
result[config[i]] = obj[config[i]];
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = objectInterface;
|
||||
66
conf/site/node_modules/famulus/package.json
generated
vendored
Normal file
66
conf/site/node_modules/famulus/package.json
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"famulus@2.1.2",
|
||||
"/home/henry/Documents/git/Speedtest-tracker-docker/conf/site"
|
||||
]
|
||||
],
|
||||
"_from": "famulus@2.1.2",
|
||||
"_id": "famulus@2.1.2",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-UjfF9lOEP6IFLC/DTwUe5KbCYINbuYYJS+mivlnWyK8yqt/9WYHrJ4RihZ0pa9HVxQObu8IWroJOyyt8dXCVkw==",
|
||||
"_location": "/famulus",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "version",
|
||||
"registry": true,
|
||||
"raw": "famulus@2.1.2",
|
||||
"name": "famulus",
|
||||
"escapedName": "famulus",
|
||||
"rawSpec": "2.1.2",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "2.1.2"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/csv-file-validator"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/famulus/-/famulus-2.1.2.tgz",
|
||||
"_spec": "2.1.2",
|
||||
"_where": "/home/henry/Documents/git/Speedtest-tracker-docker/conf/site",
|
||||
"author": {
|
||||
"name": "Vasyl Stokolosa",
|
||||
"email": "v.stokol@gmail.com",
|
||||
"url": "https://github.com/shystruk"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/shystruk/famulus/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.15"
|
||||
},
|
||||
"description": "JavaScript library that provides a useful functional programming helpers. Add your own.",
|
||||
"devDependencies": {
|
||||
"ava": "^0.24.0",
|
||||
"codecov.io": "^0.1.6",
|
||||
"nyc": "^11.4.1"
|
||||
},
|
||||
"homepage": "https://github.com/shystruk/famulus#readme",
|
||||
"keywords": [
|
||||
"utilities",
|
||||
"helpers",
|
||||
"helper"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "famulus.js",
|
||||
"name": "famulus",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/shystruk/famulus.git"
|
||||
},
|
||||
"scripts": {
|
||||
"coverage": "nyc report --reporter=lcov",
|
||||
"report-coverage": "cat ./coverage/lcov.info | codecov",
|
||||
"test": "nyc ava tests && npm run coverage"
|
||||
},
|
||||
"version": "2.1.2"
|
||||
}
|
||||
30
conf/site/node_modules/famulus/sortAndAddFirstElement.js
generated
vendored
Normal file
30
conf/site/node_modules/famulus/sortAndAddFirstElement.js
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* Sort an array by the name of existing property and add a first element into array
|
||||
*
|
||||
* @customNeeds Sort array by name and add first element.
|
||||
* For e.g. user names - [ {name: 'All'}, {name: 'Aron'}, {name: 'Bob'} ]
|
||||
*
|
||||
* @since 1.1.0
|
||||
* @category Array
|
||||
*
|
||||
* @param {Array} array - The array to sort and add
|
||||
* @param {String} sortBy - Name of the property from an array by which array will be sorted
|
||||
* @param {*} element - Element which is added into an array
|
||||
*
|
||||
* @returns {Array} Returns the new array
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* famulus.sortAndAddFirstElement([{name:'Bob'}, {name:'Aron'}], 'name', {name:'All'})
|
||||
* // => [ {name:'All'}, {name:'Aron'}, {name:'Bob'} ]
|
||||
*/
|
||||
function sortAndAddFirstElement(array, sortBy, element) {
|
||||
return _(array)
|
||||
.sortBy(sortBy)
|
||||
.unshift(element)
|
||||
.value();
|
||||
}
|
||||
|
||||
module.exports = sortAndAddFirstElement;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user