mirror of
https://github.com/henrywhitaker3/Speedtest-Tracker.git
synced 2026-01-02 11:07:20 +01:00
Merge pull request #7 from henrywhitaker3/v1-updater
Added updating function
This commit is contained in:
13
app/Facades/UpdaterFacade.php
Normal file
13
app/Facades/UpdaterFacade.php
Normal file
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace App\Facades;
|
||||
|
||||
use Illuminate\Support\Facades\Facade;
|
||||
|
||||
class UpdaterFacade extends Facade
|
||||
{
|
||||
protected static function getFacadeAccessor()
|
||||
{
|
||||
return 'updater';
|
||||
}
|
||||
}
|
||||
297
app/Helpers/UpdateHelper.php
Normal file
297
app/Helpers/UpdateHelper.php
Normal file
@@ -0,0 +1,297 @@
|
||||
<?php
|
||||
|
||||
namespace App\Helpers;
|
||||
|
||||
use Exception;
|
||||
use File;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use ZipArchive;
|
||||
|
||||
class UpdateHelper {
|
||||
public $url;
|
||||
public $currentVersion;
|
||||
public $user;
|
||||
public $repo;
|
||||
public $branch;
|
||||
|
||||
function __construct() {
|
||||
$this->currentVersion = config('speedtest.version');
|
||||
$this->user = config('speedtest.user');
|
||||
$this->repo = config('speedtest.repo');
|
||||
$this->branch = config('speedtest.branch');
|
||||
$this->latestVersion = 'unknown';
|
||||
$this->download = null;
|
||||
}
|
||||
|
||||
public function check()
|
||||
{
|
||||
Log::info('Checking for new version');
|
||||
|
||||
if($this->currentVersion === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$gitVersion = $this->checkLatestVersion();
|
||||
if($gitVersion === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if((bool)(version_compare($this->currentVersion, $gitVersion['version']))) {
|
||||
Log::info('New version found! v' . $gitVersion['version']);
|
||||
$changelog = $this->getChangelog();
|
||||
return [
|
||||
'version' => $gitVersion['version'],
|
||||
'changelog' => $changelog[$gitVersion['version']],
|
||||
];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function checkLatestVersion()
|
||||
{
|
||||
$url = 'https://raw.githubusercontent.com/'
|
||||
.$this->user
|
||||
.'/'
|
||||
.$this->repo
|
||||
.'/'
|
||||
.$this->branch
|
||||
.'/config/speedtest.php';
|
||||
|
||||
try {
|
||||
$gitFile = file_get_contents($url);
|
||||
} catch(Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pattern = "/'version' => '([0-9]{1,}\.[0-9]{1,}\.[0-9]{1,})'/";
|
||||
$version = [];
|
||||
preg_match($pattern, $gitFile, $version);
|
||||
$this->latestVersion = $version[1];
|
||||
|
||||
return [
|
||||
'repo' => $this->user . '/' . $this->repo,
|
||||
'branch' => $this->branch,
|
||||
'version' => $this->latestVersion,
|
||||
];
|
||||
}
|
||||
|
||||
public function getChangelog()
|
||||
{
|
||||
$url = 'https://raw.githubusercontent.com/'
|
||||
.$this->user
|
||||
.'/'
|
||||
.$this->repo
|
||||
.'/'
|
||||
.$this->branch
|
||||
.'/changelog.json';
|
||||
|
||||
try {
|
||||
$changelog = json_decode(file_get_contents($url), true);
|
||||
} catch(Exception $e) {
|
||||
$changelog = [];
|
||||
}
|
||||
|
||||
return $changelog;
|
||||
}
|
||||
|
||||
public function downloadLatest()
|
||||
{
|
||||
Log::info('Downloading the latest version from GitHub');
|
||||
$url = 'https://github.com/'
|
||||
.$this->user
|
||||
.'/'
|
||||
.$this->repo
|
||||
.'/archive/'
|
||||
.$this->branch
|
||||
.'.zip';
|
||||
|
||||
try {
|
||||
$zip = file_get_contents($url);
|
||||
$name = '/tmp/'.$this->repo.'-update.zip';
|
||||
file_put_contents($name, $zip);
|
||||
Log::info('New version successfully downloaded');
|
||||
return true;
|
||||
} catch(Exception $e) {
|
||||
Log::error('Couldn\'t download the update');
|
||||
Log::error($e);
|
||||
return $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function extractFiles()
|
||||
{
|
||||
Log::info('Extracting the update');
|
||||
$zip = new ZipArchive();
|
||||
$res = $zip->open('/tmp/'.$this->repo.'-update.zip');
|
||||
if($res === true) {
|
||||
$zip->extractTo('/tmp/'.$this->repo.'-update/');
|
||||
$zip->close();
|
||||
Log::info('Update extracted');
|
||||
return true;
|
||||
} else {
|
||||
Log::error('Couldn\'t extract the update');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function updateFiles()
|
||||
{
|
||||
Log::info('Applying update');
|
||||
$dir = array_filter(glob('/tmp/'.$this->repo.'-update/*'), 'is_dir');
|
||||
$dir = $dir[0].DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->deleteExcluded($dir);
|
||||
$this->backupCurrent();
|
||||
$this->moveFiles();
|
||||
$this->clearup();
|
||||
|
||||
Log::info('Successfully applied update');
|
||||
}
|
||||
|
||||
private function deleteExcluded($path)
|
||||
{
|
||||
Log::info('Deleting excluded items from update directory');
|
||||
$exclude_dirs = config('speedtest.exclude_dirs', []);
|
||||
foreach($exclude_dirs as $dir) {
|
||||
$dir = $path . $dir;
|
||||
Log::debug('Deleting excluded directory: ' . $dir);
|
||||
|
||||
File::deleteDirectory($dir);
|
||||
}
|
||||
|
||||
$exclude_files = config('speedtest.exclude_files', []);
|
||||
foreach($exclude_files as $file) {
|
||||
$file = $path . $file;
|
||||
Log::debug('Deleting excluded file: ' . $file);
|
||||
|
||||
File::delete($file);
|
||||
}
|
||||
Log::info('Excluded items deleted from update directory');
|
||||
}
|
||||
|
||||
private function backupCurrent()
|
||||
{
|
||||
Log::info('Backing up current installation');
|
||||
|
||||
$rootPath = realpath(base_path());
|
||||
$backupZip = '/tmp/speedtest-backup-'.time().'.zip';
|
||||
|
||||
// Initialize archive object
|
||||
$zip = new ZipArchive();
|
||||
$zip->open($backupZip, ZipArchive::CREATE | ZipArchive::OVERWRITE);
|
||||
|
||||
// Create recursive directory iterator
|
||||
/** @var SplFileInfo[] $files */
|
||||
$files = new RecursiveIteratorIterator(
|
||||
new RecursiveDirectoryIterator($rootPath),
|
||||
RecursiveIteratorIterator::LEAVES_ONLY
|
||||
);
|
||||
|
||||
foreach ($files as $name => $file)
|
||||
{
|
||||
// Skip directories (they would be added automatically)
|
||||
if (!$file->isDir())
|
||||
{
|
||||
// Get real and relative path for current file
|
||||
$filePath = $file->getRealPath();
|
||||
$relativePath = substr($filePath, strlen($rootPath) + 1);
|
||||
|
||||
// Add current file to archive
|
||||
$zip->addFile($filePath, $relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
// Zip archive will be created only after closing object
|
||||
$zip->close();
|
||||
Log::info('Backup created at: ' . $backupZip);
|
||||
}
|
||||
|
||||
private function moveFiles()
|
||||
{
|
||||
$new = array_filter(glob('/tmp/'.$this->repo.'-update/*'), 'is_dir');
|
||||
$new = $new[0].DIRECTORY_SEPARATOR;
|
||||
|
||||
foreach(File::files($new) as $file) {
|
||||
$filename = explode('/', $file);
|
||||
$filename = array_slice($filename, -1)[0];
|
||||
try {
|
||||
Log::info('Overwriting ' . $filename);
|
||||
Log::debug('From: ' . $file . ' to: ' . base_path().DIRECTORY_SEPARATOR.$filename);
|
||||
File::delete(base_path().DIRECTORY_SEPARATOR.$filename);
|
||||
File::move($file, base_path().DIRECTORY_SEPARATOR.$filename);
|
||||
} catch(Exception $e) {
|
||||
Log::error('Failed to overwrite: ' . $filename);
|
||||
Log::debug($e);
|
||||
}
|
||||
}
|
||||
|
||||
$this->tempStoreExcludedFiles();
|
||||
|
||||
foreach(File::directories($new) as $dir) {
|
||||
$dirname = explode('/', $dir);
|
||||
$dirname = array_slice($dirname, -1)[0];
|
||||
Log::info('Overwriting ' . $dir);
|
||||
File::deleteDirectory(base_path().DIRECTORY_SEPARATOR.$dirname);
|
||||
File::move($dir, base_path().DIRECTORY_SEPARATOR.$dirname);
|
||||
}
|
||||
|
||||
$this->restoreExcludedFiles();
|
||||
|
||||
}
|
||||
|
||||
private function tempStoreExcludedFiles()
|
||||
{
|
||||
Log::info('Temporarily moving exluded files from root directory');
|
||||
foreach(config('speedtest.exclude_files', []) as $file) {
|
||||
try {
|
||||
Log::info('Moving ' . $file);
|
||||
File::copy(base_path().DIRECTORY_SEPARATOR.$file, '/tmp/'.$file);
|
||||
} catch(Exception $e) {
|
||||
Log::error('Couldn\'t backup '.$file);
|
||||
}
|
||||
}
|
||||
foreach(config('speedtest.exclude_dirs', []) as $dir) {
|
||||
try {
|
||||
Log::info('Moving ' . $dir);
|
||||
File::copyDirectory(base_path().DIRECTORY_SEPARATOR.$dir, '/tmp/'.$dir);
|
||||
} catch(Exception $e) {
|
||||
Log::error('Couldn\'t backup '.$dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function restoreExcludedFiles()
|
||||
{
|
||||
Log::info('Restoring exluded files to root directory');
|
||||
foreach(config('speedtest.exclude_files', []) as $file) {
|
||||
try {
|
||||
Log::info('Moving ' . $file);
|
||||
File::copy('/tmp/'.$file, base_path().DIRECTORY_SEPARATOR.$file);
|
||||
} catch(Exception $e) {
|
||||
Log::error('Couldn\'t restore '.$file);
|
||||
}
|
||||
}
|
||||
foreach(config('speedtest.exclude_dirs', []) as $dir) {
|
||||
try {
|
||||
Log::info('Moving ' . $dir);
|
||||
File::copyDirectory('/tmp/'.$dir, base_path().DIRECTORY_SEPARATOR.$dir);
|
||||
} catch(Exception $e) {
|
||||
Log::error('Couldn\' restore ' . $dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function clearup()
|
||||
{
|
||||
try {
|
||||
File::deleteDirectory('/tmp/'.$this->repo.'-update/');
|
||||
Log::info('Deleted download directory');
|
||||
} catch(Exception $e) {
|
||||
Log::error('Failed cleaning up update');
|
||||
Log::error($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
app/Http/Controllers/UpdateController.php
Normal file
61
app/Http/Controllers/UpdateController.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Updater;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class UpdateController extends Controller
|
||||
{
|
||||
public function checkForUpdate()
|
||||
{
|
||||
return response()->json([
|
||||
'method' => 'check for updates',
|
||||
'update' => Updater::check(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
public function downloadUpdate()
|
||||
{
|
||||
$dl = Updater::downloadLatest();
|
||||
|
||||
if($dl) {
|
||||
return response()->json([
|
||||
'method' => 'download latest version',
|
||||
'success' => true,
|
||||
], 200);
|
||||
} else {
|
||||
return response()->json([
|
||||
'method' => 'download latest version',
|
||||
'success' => false,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function extractUpdate()
|
||||
{
|
||||
$ex = Updater::extractFiles();
|
||||
|
||||
if($ex) {
|
||||
return response()->json([
|
||||
'method' => 'extract latest version',
|
||||
'success' => true,
|
||||
], 200);
|
||||
} else {
|
||||
return response()->json([
|
||||
'method' => 'extract latest version',
|
||||
'success' => false,
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function moveUpdate()
|
||||
{
|
||||
$cp = Updater::updateFiles();
|
||||
|
||||
return response()->json([
|
||||
'method' => 'copy latest version',
|
||||
'success' => $cp,
|
||||
], 200);
|
||||
}
|
||||
}
|
||||
31
app/Providers/UpdaterServiceProvider.php
Normal file
31
app/Providers/UpdaterServiceProvider.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Helpers\UpdateHelper;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class UpdaterServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->app->bind('updater', function() {
|
||||
return new UpdateHelper();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
18
changelog.json
Normal file
18
changelog.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"1.2.0": [
|
||||
{
|
||||
"description": "Added an updating mechainism within the app to keep code up to date.",
|
||||
"link": ""
|
||||
}
|
||||
],
|
||||
"1.1.0": [
|
||||
{
|
||||
"description": "Added a version number to the app",
|
||||
"link": ""
|
||||
},
|
||||
{
|
||||
"description": "Added a backup/restore function",
|
||||
"link": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
10
composer.lock
generated
10
composer.lock
generated
@@ -690,16 +690,16 @@
|
||||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v7.5.1",
|
||||
"version": "v7.5.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "b2457b1ddb9c59396313eae4e948bdc5fa5251db"
|
||||
"reference": "3a3b3f7fea69813f5a03449c6314bfb42c3ccf78"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/b2457b1ddb9c59396313eae4e948bdc5fa5251db",
|
||||
"reference": "b2457b1ddb9c59396313eae4e948bdc5fa5251db",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/3a3b3f7fea69813f5a03449c6314bfb42c3ccf78",
|
||||
"reference": "3a3b3f7fea69813f5a03449c6314bfb42c3ccf78",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -837,7 +837,7 @@
|
||||
"framework",
|
||||
"laravel"
|
||||
],
|
||||
"time": "2020-04-07T18:52:38+00:00"
|
||||
"time": "2020-04-08T15:54:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/tinker",
|
||||
|
||||
@@ -123,14 +123,6 @@ return [
|
||||
|
||||
'cipher' => 'AES-256-CBC',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Version numebr
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
'version' => '1.1.0',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Autoloaded Service Providers
|
||||
@@ -182,6 +174,7 @@ return [
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\UpdaterServiceProvider::class,
|
||||
|
||||
],
|
||||
|
||||
@@ -234,7 +227,7 @@ return [
|
||||
'URL' => Illuminate\Support\Facades\URL::class,
|
||||
'Validator' => Illuminate\Support\Facades\Validator::class,
|
||||
'View' => Illuminate\Support\Facades\View::class,
|
||||
|
||||
'Updater' => App\Facades\UpdaterFacade::class,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
49
config/speedtest.php
Normal file
49
config/speedtest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
return [
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Version numebr
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
'version' => '1.2.0',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| GitHub Repo Variables
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
'user' => 'henrywhitaker3',
|
||||
'repo' => 'Speedtest-Tracker',
|
||||
'branch' => 'master',
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Excluded Dirs
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Dirs excluded from the self-updating function
|
||||
*/
|
||||
|
||||
'exclude_dirs' => [
|
||||
'node_modules/',
|
||||
'bootstrap/cache/',
|
||||
'bower/',
|
||||
'storage/',
|
||||
'vendor/',
|
||||
],
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Excluded Files
|
||||
|--------------------------------------------------------------------------
|
||||
|
|
||||
| Files excluded from the self-updating function
|
||||
*/
|
||||
|
||||
'exclude_files' => [
|
||||
'database/speed.db',
|
||||
],
|
||||
];
|
||||
225
public/js/app.js
vendored
225
public/js/app.js
vendored
@@ -126769,7 +126769,7 @@ var Restore = /*#__PURE__*/function (_Component) {
|
||||
};
|
||||
var url = '/api/restore';
|
||||
axios__WEBPACK_IMPORTED_MODULE_4___default.a.post(url, data).then(function (resp) {
|
||||
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].success('Your is being restored...');
|
||||
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].success('Your data is being restored...');
|
||||
|
||||
_this.setState({
|
||||
show: false,
|
||||
@@ -127670,6 +127670,7 @@ __webpack_require__.r(__webpack_exports__);
|
||||
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js");
|
||||
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);
|
||||
/* harmony import */ var react_bootstrap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! react-bootstrap */ "./node_modules/react-bootstrap/esm/index.js");
|
||||
/* harmony import */ var _Version__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./Version */ "./resources/js/components/Home/Version.js");
|
||||
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
@@ -127697,35 +127698,27 @@ function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || func
|
||||
|
||||
|
||||
|
||||
|
||||
var Footer = /*#__PURE__*/function (_Component) {
|
||||
_inherits(Footer, _Component);
|
||||
|
||||
var _super = _createSuper(Footer);
|
||||
|
||||
function Footer(props) {
|
||||
var _this;
|
||||
|
||||
function Footer() {
|
||||
_classCallCheck(this, Footer);
|
||||
|
||||
_this = _super.call(this, props);
|
||||
_this.state = {
|
||||
version: document.querySelector('meta[name="version"]').content
|
||||
};
|
||||
return _this;
|
||||
return _super.apply(this, arguments);
|
||||
}
|
||||
|
||||
_createClass(Footer, [{
|
||||
key: "render",
|
||||
value: function render() {
|
||||
var version = this.state.version;
|
||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Container"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Row"], null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Col"], {
|
||||
sm: {
|
||||
span: 12
|
||||
},
|
||||
className: "text-center"
|
||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
|
||||
className: "text-muted mb-0"
|
||||
}, "Speedtest Tracker Version: ", version), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
|
||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Version__WEBPACK_IMPORTED_MODULE_3__["default"], null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
|
||||
className: "text-muted"
|
||||
}, "See the code on ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("a", {
|
||||
href: "https://github.com/henrywhitaker3/Speedtest-Tracker",
|
||||
@@ -127824,6 +127817,212 @@ if (document.getElementById('homePage')) {
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./resources/js/components/Home/Version.js":
|
||||
/*!*************************************************!*\
|
||||
!*** ./resources/js/components/Home/Version.js ***!
|
||||
\*************************************************/
|
||||
/*! exports provided: default */
|
||||
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
__webpack_require__.r(__webpack_exports__);
|
||||
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return Version; });
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! react */ "./node_modules/react/index.js");
|
||||
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
||||
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! react-dom */ "./node_modules/react-dom/index.js");
|
||||
/* harmony import */ var react_dom__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(react_dom__WEBPACK_IMPORTED_MODULE_1__);
|
||||
/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! axios */ "./node_modules/axios/index.js");
|
||||
/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(axios__WEBPACK_IMPORTED_MODULE_2__);
|
||||
/* harmony import */ var react_toastify__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-toastify */ "./node_modules/react-toastify/esm/react-toastify.js");
|
||||
/* harmony import */ var react_bootstrap__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! react-bootstrap */ "./node_modules/react-bootstrap/esm/index.js");
|
||||
function _typeof(obj) { "@babel/helpers - typeof"; if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
|
||||
|
||||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
|
||||
|
||||
function _createSuper(Derived) { return function () { var Super = _getPrototypeOf(Derived), result; if (_isNativeReflectConstruct()) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; }
|
||||
|
||||
function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
|
||||
|
||||
function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
|
||||
|
||||
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
|
||||
|
||||
function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
|
||||
|
||||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
|
||||
|
||||
function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
|
||||
|
||||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
var Version = /*#__PURE__*/function (_Component) {
|
||||
_inherits(Version, _Component);
|
||||
|
||||
var _super = _createSuper(Version);
|
||||
|
||||
function Version(props) {
|
||||
var _this;
|
||||
|
||||
_classCallCheck(this, Version);
|
||||
|
||||
_this = _super.call(this, props);
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "checkForUpdates", function () {
|
||||
var url = '/api/update/check';
|
||||
axios__WEBPACK_IMPORTED_MODULE_2___default.a.get(url).then(function (resp) {
|
||||
var update = resp.data.update;
|
||||
|
||||
if (update !== false) {
|
||||
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].info('A new version of Speedtest Tracker is available (v' + update.version + '). Go to the bottom of the page to update.');
|
||||
|
||||
_this.setState({
|
||||
update: update.version,
|
||||
changelog: update.changelog
|
||||
});
|
||||
}
|
||||
})["catch"](function (err) {
|
||||
console.log(err);
|
||||
});
|
||||
});
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "showModal", function () {
|
||||
_this.setState({
|
||||
modalShow: true
|
||||
});
|
||||
});
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "hideModal", function () {
|
||||
_this.setState({
|
||||
modalShow: false
|
||||
});
|
||||
});
|
||||
|
||||
_defineProperty(_assertThisInitialized(_this), "updateApp", function () {
|
||||
_this.setState({
|
||||
showProgress: true,
|
||||
updateProgress: 0
|
||||
});
|
||||
|
||||
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].info('Downloading update');
|
||||
axios__WEBPACK_IMPORTED_MODULE_2___default.a.get('/api/update/download').then(function (resp) {
|
||||
_this.setState({
|
||||
updateProgress: 50
|
||||
});
|
||||
|
||||
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].info('Extracting update');
|
||||
axios__WEBPACK_IMPORTED_MODULE_2___default.a.get('/api/speedtest/extract').then(function (resp) {
|
||||
_this.setState({
|
||||
updateProgress: 75
|
||||
});
|
||||
|
||||
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].info('Applying update');
|
||||
axios__WEBPACK_IMPORTED_MODULE_2___default.a.get('/api/update/move').then(function (resp) {
|
||||
_this.setState({
|
||||
updateProgress: 100
|
||||
});
|
||||
|
||||
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].success('Update successful. Refreshing your page...');
|
||||
setTimeout(function () {
|
||||
location.reload(true);
|
||||
}, 5000);
|
||||
});
|
||||
});
|
||||
})["catch"](function (err) {
|
||||
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].error('Something went wrong...');
|
||||
});
|
||||
});
|
||||
|
||||
_this.state = {
|
||||
version: document.querySelector('meta[name="version"]').content,
|
||||
update: false,
|
||||
modalShow: false,
|
||||
changelog: [],
|
||||
showProgress: false,
|
||||
updateProgress: 0
|
||||
};
|
||||
return _this;
|
||||
}
|
||||
|
||||
_createClass(Version, [{
|
||||
key: "componentDidMount",
|
||||
value: function componentDidMount() {
|
||||
this.checkForUpdates();
|
||||
}
|
||||
}, {
|
||||
key: "render",
|
||||
value: function render() {
|
||||
var version = this.state.version;
|
||||
var update = this.state.update;
|
||||
var modalShow = this.state.modalShow;
|
||||
var changelog = this.state.changelog;
|
||||
var showProgress = this.state.showProgress;
|
||||
var updateProgress = this.state.updateProgress;
|
||||
|
||||
if (update === false) {
|
||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
|
||||
className: "text-muted mb-0"
|
||||
}, "Speedtest Tracker Version: ", version);
|
||||
} else {
|
||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", {
|
||||
className: "text-muted mb-0 d-inline"
|
||||
}, "Speedtest Tracker Version: ", version, " - "), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("a", {
|
||||
href: "#!",
|
||||
className: "mb-0 d-inline",
|
||||
onClick: this.showModal
|
||||
}, "New version available - v", update), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_4__["Modal"], {
|
||||
show: modalShow,
|
||||
onHide: this.hideModal,
|
||||
animation: true
|
||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_4__["Modal"].Header, {
|
||||
closeButton: true
|
||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_4__["Modal"].Title, null, "Update to v", update)), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_4__["Modal"].Body, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h5", null, "Changelog:"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("ul", null, changelog.map(function (e, i) {
|
||||
if (e.link == '') {
|
||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("li", {
|
||||
key: i
|
||||
}, e.description);
|
||||
} else {
|
||||
/*#__PURE__*/
|
||||
react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("li", {
|
||||
key: i
|
||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("a", {
|
||||
href: e.link,
|
||||
target: "_blank",
|
||||
rel: "noopener noreferer"
|
||||
}, e.description));
|
||||
}
|
||||
})), showProgress && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", null, "Update progress:"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_4__["ProgressBar"], {
|
||||
animated: true,
|
||||
now: updateProgress
|
||||
})), !showProgress && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_4__["Button"], {
|
||||
variant: "primary",
|
||||
onClick: this.updateApp
|
||||
}, "Update"))));
|
||||
}
|
||||
}
|
||||
}]);
|
||||
|
||||
return Version;
|
||||
}(react__WEBPACK_IMPORTED_MODULE_0__["Component"]);
|
||||
|
||||
|
||||
|
||||
if (document.getElementById('Version')) {
|
||||
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(Version, null), document.getElementById('Version'));
|
||||
}
|
||||
|
||||
/***/ }),
|
||||
|
||||
/***/ "./resources/js/components/Loader.js":
|
||||
/*!*******************************************!*\
|
||||
!*** ./resources/js/components/Loader.js ***!
|
||||
|
||||
2
resources/js/components/Data/Restore.js
vendored
2
resources/js/components/Data/Restore.js
vendored
@@ -57,7 +57,7 @@ export default class Restore extends Component {
|
||||
|
||||
Axios.post(url, data)
|
||||
.then((resp) => {
|
||||
toast.success('Your is being restored...');
|
||||
toast.success('Your data is being restored...');
|
||||
this.setState({
|
||||
show: false,
|
||||
data: null,
|
||||
|
||||
13
resources/js/components/Home/Footer.js
vendored
13
resources/js/components/Home/Footer.js
vendored
@@ -2,24 +2,15 @@ import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Container, Row } from 'react-bootstrap';
|
||||
import { Col } from 'react-bootstrap';
|
||||
import Version from './Version';
|
||||
|
||||
export default class Footer extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
version: document.querySelector('meta[name="version"]').content
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
var version = this.state.version;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Row>
|
||||
<Col sm={{ span: 12 }} className="text-center">
|
||||
<p className="text-muted mb-0">Speedtest Tracker Version: {version}</p>
|
||||
<Version />
|
||||
<p className="text-muted">See the code on <a href="https://github.com/henrywhitaker3/Speedtest-Tracker" target="_blank" rel="noopener noreferrer">GitHub</a></p>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
146
resources/js/components/Home/Version.js
vendored
Normal file
146
resources/js/components/Home/Version.js
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Axios from 'axios';
|
||||
import { toast } from 'react-toastify';
|
||||
import { Modal, ProgressBar } from 'react-bootstrap';
|
||||
import { Button } from 'react-bootstrap';
|
||||
|
||||
export default class Version extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
version: document.querySelector('meta[name="version"]').content,
|
||||
update: false,
|
||||
modalShow: false,
|
||||
changelog: [],
|
||||
showProgress: false,
|
||||
updateProgress: 0,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.checkForUpdates();
|
||||
}
|
||||
|
||||
checkForUpdates = () => {
|
||||
var url = '/api/update/check';
|
||||
|
||||
Axios.get(url)
|
||||
.then((resp) => {
|
||||
var update = resp.data.update;
|
||||
if(update !== false) {
|
||||
toast.info('A new version of Speedtest Tracker is available (v' + update.version + '). Go to the bottom of the page to update.');
|
||||
this.setState({
|
||||
update: update.version,
|
||||
changelog: update.changelog,
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
showModal = () => {
|
||||
this.setState({
|
||||
modalShow: true
|
||||
});
|
||||
}
|
||||
|
||||
hideModal = () => {
|
||||
this.setState({
|
||||
modalShow: false
|
||||
});
|
||||
}
|
||||
|
||||
updateApp = () => {
|
||||
this.setState({
|
||||
showProgress: true,
|
||||
updateProgress: 0,
|
||||
});
|
||||
toast.info('Downloading update');
|
||||
Axios.get('/api/update/download')
|
||||
.then((resp) => {
|
||||
this.setState({
|
||||
updateProgress: 50,
|
||||
});
|
||||
toast.info('Extracting update');
|
||||
Axios.get('/api/speedtest/extract')
|
||||
.then((resp) => {
|
||||
this.setState({
|
||||
updateProgress: 75,
|
||||
});
|
||||
toast.info('Applying update');
|
||||
Axios.get('/api/update/move')
|
||||
.then((resp) => {
|
||||
this.setState({
|
||||
updateProgress: 100,
|
||||
});
|
||||
toast.success('Update successful. Refreshing your page...');
|
||||
setTimeout(function() {
|
||||
location.reload(true);
|
||||
}, 5000);
|
||||
})
|
||||
})
|
||||
})
|
||||
.catch((err) => {
|
||||
toast.error('Something went wrong...');
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
var version = this.state.version;
|
||||
var update = this.state.update;
|
||||
var modalShow = this.state.modalShow;
|
||||
var changelog = this.state.changelog;
|
||||
var showProgress = this.state.showProgress;
|
||||
var updateProgress = this.state.updateProgress;
|
||||
|
||||
if(update === false) {
|
||||
return (
|
||||
<p className="text-muted mb-0">Speedtest Tracker Version: {version}</p>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<p className="text-muted mb-0 d-inline">Speedtest Tracker Version: {version} - </p>
|
||||
<a href="#!" className="mb-0 d-inline" onClick={this.showModal}>New version available - v{update}</a>
|
||||
|
||||
<Modal show={modalShow} onHide={this.hideModal} animation={true}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Update to v{update}</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<h5>Changelog:</h5>
|
||||
<ul>
|
||||
{changelog.map((e, i) => {
|
||||
if(e.link == '') {
|
||||
return (
|
||||
<li key={i}>{e.description}</li>
|
||||
);
|
||||
} else {
|
||||
<li key={i}><a href={e.link} target="_blank" rel="noopener noreferer">{e.description}</a></li>
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
{showProgress &&
|
||||
<div>
|
||||
<p>Update progress:</p>
|
||||
<ProgressBar animated now={updateProgress} />
|
||||
</div>
|
||||
}
|
||||
{!showProgress &&
|
||||
<Button variant="primary" onClick={this.updateApp}>Update</Button>
|
||||
}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (document.getElementById('Version')) {
|
||||
ReactDOM.render(<Version />, document.getElementById('Version'));
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="author" content="Henry Whitaker">
|
||||
<meta name="version" content="{{ config('app.version', 'Unknown') }}">
|
||||
<meta name="version" content="{{ config('speedtest.version', 'Unknown') }}">
|
||||
|
||||
<link href="/icons/themify/themify-icons.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/css/bootstrap.dark.min.css">
|
||||
|
||||
@@ -37,3 +37,17 @@ Route::group([
|
||||
Route::post('restore', 'BackupController@restore')
|
||||
->name('data.restore');
|
||||
});
|
||||
|
||||
Route::group([
|
||||
'middleware' => 'api',
|
||||
'prefix' => 'update',
|
||||
], function () {
|
||||
Route::get('check', 'UpdateController@checkForUpdate')
|
||||
->name('update.check');
|
||||
Route::get('download', 'UpdateController@downloadUpdate')
|
||||
->name('update.download');
|
||||
Route::get('extract', 'UpdateController@extractUpdate')
|
||||
->name('update.extract');
|
||||
Route::get('move', 'UpdateController@moveUpdate')
|
||||
->name('update.move');
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user