Added backup/restore function

This commit is contained in:
Henry Whitaker
2020-04-09 20:22:43 +01:00
parent f44fe2616d
commit 970cc05915
13 changed files with 660 additions and 4 deletions

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Helpers;
use App\Speedtest;
use Exception;
class BackupHelper {
public static function backup()
{
$data = Speedtest::get();
return $data;
}
public static function restore($array)
{
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;
}
}
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\BackupHelper;
use DateTime;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
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);
}
public function restore(Request $request)
{
$rule = [
'data' => [ 'required', 'array' ],
];
$validator = Validator::make($request->all(), $rule);
if($validator->fails()) {
return response()->json([
'error' => $validator->errors()
], 422);
}
BackupHelper::restore($request->data);
return response()->json([
'method' => 'restore data from backup',
], 200);
}
}

View File

@@ -12,7 +12,7 @@ class Speedtest extends Model
* @var array
*/
protected $fillable = [
'ping', 'download', 'upload'
'ping', 'download', 'upload', 'created_at'
];
protected $table = 'speedtests';

View File

@@ -129,7 +129,7 @@ return [
|--------------------------------------------------------------------------
*/
'version' => '1.0.0',
'version' => '1.1.0',
/*
|--------------------------------------------------------------------------

4
public/css/main.css vendored
View File

@@ -43,3 +43,7 @@
.Toastify__toast--info {
background: #1f5a81 !important;
}
.Toastify__toast--success {
background: #28922b !important;
}

376
public/js/app.js vendored
View File

@@ -126473,6 +126473,367 @@ window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
/***/ }),
/***/ "./resources/js/components/Data/Backup.js":
/*!************************************************!*\
!*** ./resources/js/components/Data/Backup.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 Backup; });
/* 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 react_bootstrap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! react-bootstrap */ "./node_modules/react-bootstrap/esm/index.js");
/* harmony import */ var react_toastify__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-toastify */ "./node_modules/react-toastify/esm/react-toastify.js");
/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! axios */ "./node_modules/axios/index.js");
/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(axios__WEBPACK_IMPORTED_MODULE_4__);
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 Backup = /*#__PURE__*/function (_Component) {
_inherits(Backup, _Component);
var _super = _createSuper(Backup);
function Backup() {
var _this;
_classCallCheck(this, Backup);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _super.call.apply(_super, [this].concat(args));
_defineProperty(_assertThisInitialized(_this), "backup", function () {
var url = '/api/backup';
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].info('Your backup has started downloading...');
axios__WEBPACK_IMPORTED_MODULE_4___default.a.get(url, {
responseType: 'blob'
}).then(function (resp) {
var a = document.createElement('a');
a.href = url;
a.download = "";
document.body.appendChild(a);
a.click();
a.remove();
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].success('Backup downloaded');
})["catch"](function (err) {
console.log(err);
});
});
return _this;
}
_createClass(Backup, [{
key: "render",
value: function render() {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Button"], {
variant: "primary",
className: "mx-2",
onClick: this.backup
}, "Backup");
}
}]);
return Backup;
}(react__WEBPACK_IMPORTED_MODULE_0__["Component"]);
if (document.getElementById('Backup')) {
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(Backup, null), document.getElementById('Backup'));
}
/***/ }),
/***/ "./resources/js/components/Data/DataRow.js":
/*!*************************************************!*\
!*** ./resources/js/components/Data/DataRow.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 DataRow; });
/* 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 react_bootstrap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! react-bootstrap */ "./node_modules/react-bootstrap/esm/index.js");
/* harmony import */ var _Backup__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./Backup */ "./resources/js/components/Data/Backup.js");
/* harmony import */ var _Restore__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./Restore */ "./resources/js/components/Data/Restore.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); }
var DataRow = /*#__PURE__*/function (_Component) {
_inherits(DataRow, _Component);
var _super = _createSuper(DataRow);
function DataRow() {
_classCallCheck(this, DataRow);
return _super.apply(this, arguments);
}
_createClass(DataRow, [{
key: "render",
value: function render() {
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", null, "Use these buttons to backup/restore your data"))), /*#__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(_Backup__WEBPACK_IMPORTED_MODULE_3__["default"], null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Restore__WEBPACK_IMPORTED_MODULE_4__["default"], null))));
}
}]);
return DataRow;
}(react__WEBPACK_IMPORTED_MODULE_0__["Component"]);
if (document.getElementById('DataRow')) {
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(DataRow, null), document.getElementById('DataRow'));
}
/***/ }),
/***/ "./resources/js/components/Data/Restore.js":
/*!*************************************************!*\
!*** ./resources/js/components/Data/Restore.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 Restore; });
/* 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 react_bootstrap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! react-bootstrap */ "./node_modules/react-bootstrap/esm/index.js");
/* harmony import */ var react_toastify__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! react-toastify */ "./node_modules/react-toastify/esm/react-toastify.js");
/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! axios */ "./node_modules/axios/index.js");
/* harmony import */ var axios__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(axios__WEBPACK_IMPORTED_MODULE_4__);
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 Restore = /*#__PURE__*/function (_Component) {
_inherits(Restore, _Component);
var _super = _createSuper(Restore);
function Restore(props) {
var _this;
_classCallCheck(this, Restore);
_this = _super.call(this, props);
_defineProperty(_assertThisInitialized(_this), "showModal", function () {
_this.setState({
show: true
});
});
_defineProperty(_assertThisInitialized(_this), "hideModal", function () {
_this.setState({
show: false
});
});
_defineProperty(_assertThisInitialized(_this), "readFile", function (e) {
var file = e.target.files[0];
var reader = new FileReader();
reader.readAsText(file, 'UTF-8');
reader.onload = function (evt) {
try {
var data = evt.target.result.trim();
var data = JSON.parse(data);
this.setState({
data: data,
uploadReady: true,
filename: file.name
});
} catch (e) {
console.log(e);
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].error('Your upload file is not valid JSON');
}
}.bind(_assertThisInitialized(_this));
reader.onerror = function (evt) {
react_toastify__WEBPACK_IMPORTED_MODULE_3__["toast"].error('Something went wrong parsing your backup file.');
};
});
_defineProperty(_assertThisInitialized(_this), "uploadFile", function () {
var data = {
data: _this.state.data
};
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...');
_this.setState({
show: false,
data: null,
uploadReady: false,
filename: 'Upload your backup JSON'
});
})["catch"](function (err) {
console.log(err);
});
});
_this.state = {
show: false,
data: null,
uploadReady: false,
filename: 'Upload your backup JSON'
};
return _this;
}
_createClass(Restore, [{
key: "render",
value: function render() {
var show = this.state.show;
var uploadReady = this.state.uploadReady;
var filename = this.state.filename;
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react__WEBPACK_IMPORTED_MODULE_0___default.a.Fragment, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Button"], {
variant: "secondary",
className: "mx-2",
onClick: this.showModal
}, "Restore"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Modal"], {
show: show,
onHide: this.hideModal,
animation: true
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Modal"].Header, {
closeButton: true
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Modal"].Title, null, "Restore from a backup")), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Modal"].Body, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("p", null, "Upload your JSON backup file here:"), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Form"].File, {
id: "restoreFileInput",
label: "Upload JSON file",
className: "mb-3",
custom: true
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Form"].File.Input, {
onChange: this.readFile
}), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Form"].File.Label, {
"data-browse": "Choose file"
}, filename)), uploadReady === true && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(react_bootstrap__WEBPACK_IMPORTED_MODULE_2__["Button"], {
variant: "secondary",
onClick: this.uploadFile
}, "Restore"))));
}
}]);
return Restore;
}(react__WEBPACK_IMPORTED_MODULE_0__["Component"]);
if (document.getElementById('Restore')) {
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(Restore, null), document.getElementById('Restore'));
}
/***/ }),
/***/ "./resources/js/components/ErrorPage.js":
/*!**********************************************!*\
!*** ./resources/js/components/ErrorPage.js ***!
@@ -127342,20 +127703,29 @@ var Footer = /*#__PURE__*/function (_Component) {
var _super = _createSuper(Footer);
function Footer(props) {
var _this;
_classCallCheck(this, Footer);
return _super.call(this, props);
_this = _super.call(this, props);
_this.state = {
version: document.querySelector('meta[name="version"]').content
};
return _this;
}
_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", {
className: "text-muted"
}, "See the code on ", /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("a", {
href: "https://github.com/henrywhitaker3/Speedtest-Tracker",
@@ -127393,6 +127763,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _Graphics_HistoryGraph__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../Graphics/HistoryGraph */ "./resources/js/components/Graphics/HistoryGraph.js");
/* harmony import */ var _Graphics_LatestResults__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../Graphics/LatestResults */ "./resources/js/components/Graphics/LatestResults.js");
/* harmony import */ var _Footer__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./Footer */ "./resources/js/components/Home/Footer.js");
/* harmony import */ var _Data_DataRow__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../Data/DataRow */ "./resources/js/components/Data/DataRow.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"); } }
@@ -127421,6 +127792,7 @@ function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || func
var HomePage = /*#__PURE__*/function (_Component) {
_inherits(HomePage, _Component);
@@ -127437,7 +127809,7 @@ var HomePage = /*#__PURE__*/function (_Component) {
value: function render() {
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", {
className: "my-4"
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Graphics_LatestResults__WEBPACK_IMPORTED_MODULE_3__["default"], null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Graphics_HistoryGraph__WEBPACK_IMPORTED_MODULE_2__["default"], null)), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Footer__WEBPACK_IMPORTED_MODULE_4__["default"], null));
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Graphics_LatestResults__WEBPACK_IMPORTED_MODULE_3__["default"], null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Graphics_HistoryGraph__WEBPACK_IMPORTED_MODULE_2__["default"], null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Data_DataRow__WEBPACK_IMPORTED_MODULE_5__["default"], null)), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(_Footer__WEBPACK_IMPORTED_MODULE_4__["default"], null));
}
}]);

43
resources/js/components/Data/Backup.js vendored Normal file
View File

@@ -0,0 +1,43 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Button } from 'react-bootstrap';
import { toast } from 'react-toastify';
import Axios from 'axios';
export default class Backup extends Component {
backup = () => {
var url = '/api/backup';
toast.info('Your backup has started downloading...');
Axios.get(url, {
responseType: 'blob'
})
.then((resp) => {
var a = document.createElement('a');
a.href = url;
a.download = "";
document.body.appendChild(a);
a.click();
a.remove();
toast.success('Backup downloaded');
})
.catch((err) => {
console.log(err);
})
}
render() {
return (
<Button
variant="primary"
className="mx-2"
onClick={this.backup}
>Backup</Button>
);
}
}
if (document.getElementById('Backup')) {
ReactDOM.render(<Backup />, document.getElementById('Backup'));
}

31
resources/js/components/Data/DataRow.js vendored Normal file
View File

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

111
resources/js/components/Data/Restore.js vendored Normal file
View File

@@ -0,0 +1,111 @@
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Button, Modal, Form, Tooltip, OverlayTrigger } from 'react-bootstrap';
import { toast } from 'react-toastify';
import Axios from 'axios';
export default class Restore extends Component {
constructor(props) {
super(props);
this.state = {
show: false,
data: null,
uploadReady: false,
filename: 'Upload your backup JSON'
};
}
showModal = () => {
this.setState({
show: true
});
}
hideModal = () => {
this.setState({
show: false
});
}
readFile = (e) => {
var file = e.target.files[0];
var reader = new FileReader();
reader.readAsText(file, 'UTF-8');
reader.onload = function(evt) {
try {
var data = evt.target.result.trim();
var data = JSON.parse(data);
this.setState({
data: data,
uploadReady: true,
filename: file.name
});
} catch(e) {
console.log(e);
toast.error('Your upload file is not valid JSON');
}
}.bind(this)
reader.onerror = function (evt) {
toast.error('Something went wrong parsing your backup file.');
}
}
uploadFile = () => {
var data = { data: this.state.data };
var url = '/api/restore';
Axios.post(url, data)
.then((resp) => {
toast.success('Your is being restored...');
this.setState({
show: false,
data: null,
uploadReady: false,
filename: 'Upload your backup JSON'
});
})
.catch((err) => {
console.log(err);
})
}
render() {
var show = this.state.show;
var uploadReady = this.state.uploadReady;
var filename = this.state.filename;
return (
<>
<Button variant="secondary" className="mx-2" onClick={this.showModal}>Restore</Button>
<Modal show={show} onHide={this.hideModal} animation={true}>
<Modal.Header closeButton>
<Modal.Title>Restore from a backup</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Upload your JSON backup file here:</p>
<Form.File
id="restoreFileInput"
label="Upload JSON file"
className="mb-3"
custom
>
<Form.File.Input onChange={this.readFile} />
<Form.File.Label data-browse="Choose file">
{filename}
</Form.File.Label>
</Form.File>
{uploadReady === true &&
<Button variant="secondary" onClick={this.uploadFile}>Restore</Button>
}
</Modal.Body>
</Modal>
</>
);
}
}
if (document.getElementById('Restore')) {
ReactDOM.render(<Restore />, document.getElementById('Restore'));
}

View File

@@ -6,13 +6,20 @@ import { Col } from 'react-bootstrap';
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>
<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>

View File

@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
import HistoryGraph from '../Graphics/HistoryGraph';
import LatestResults from '../Graphics/LatestResults';
import Footer from './Footer';
import DataRow from '../Data/DataRow';
export default class HomePage extends Component {
@@ -12,6 +13,7 @@ export default class HomePage extends Component {
<div className="my-4">
<LatestResults />
<HistoryGraph />
<DataRow />
</div>
<Footer />
</div>

View File

@@ -4,6 +4,8 @@
<meta charset="utf-8">
<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') }}">
<link href="/icons/themify/themify-icons.css" rel="stylesheet">
<link rel="stylesheet" href="/css/bootstrap.dark.min.css">

View File

@@ -28,3 +28,12 @@ Route::group([
Route::get('run', 'SpeedtestController@run')
->name('speedtest.run');
});
Route::group([
'middleware' => 'api'
], function () {
Route::get('backup', 'BackupController@backup')
->name('data.backup');
Route::post('restore', 'BackupController@restore')
->name('data.restore');
});