From 970cc05915a61d766817fd4b11b1beb63724121b Mon Sep 17 00:00:00 2001 From: Henry Whitaker Date: Thu, 9 Apr 2020 20:22:43 +0100 Subject: [PATCH] Added backup/restore function --- app/Helpers/BackupHelper.php | 31 ++ app/Http/Controllers/BackupController.php | 44 +++ app/Speedtest.php | 2 +- config/app.php | 2 +- public/css/main.css | 4 + public/js/app.js | 376 +++++++++++++++++++++- resources/js/components/Data/Backup.js | 43 +++ resources/js/components/Data/DataRow.js | 31 ++ resources/js/components/Data/Restore.js | 111 +++++++ resources/js/components/Home/Footer.js | 7 + resources/js/components/Home/HomePage.js | 2 + resources/views/app.blade.php | 2 + routes/api.php | 9 + 13 files changed, 660 insertions(+), 4 deletions(-) create mode 100644 app/Helpers/BackupHelper.php create mode 100644 app/Http/Controllers/BackupController.php create mode 100644 resources/js/components/Data/Backup.js create mode 100644 resources/js/components/Data/DataRow.js create mode 100644 resources/js/components/Data/Restore.js diff --git a/app/Helpers/BackupHelper.php b/app/Helpers/BackupHelper.php new file mode 100644 index 00000000..82e01c8e --- /dev/null +++ b/app/Helpers/BackupHelper.php @@ -0,0 +1,31 @@ + $test['ping'], + 'download' => $test['download'], + 'upload' => $test['upload'], + 'created_at' => $test['created_at'], + ]); + } catch(Exception $e) { + continue; + } + } + } +} diff --git a/app/Http/Controllers/BackupController.php b/app/Http/Controllers/BackupController.php new file mode 100644 index 00000000..5aa83d4e --- /dev/null +++ b/app/Http/Controllers/BackupController.php @@ -0,0 +1,44 @@ +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); + } +} diff --git a/app/Speedtest.php b/app/Speedtest.php index 0ec2f91e..7a263e41 100644 --- a/app/Speedtest.php +++ b/app/Speedtest.php @@ -12,7 +12,7 @@ class Speedtest extends Model * @var array */ protected $fillable = [ - 'ping', 'download', 'upload' + 'ping', 'download', 'upload', 'created_at' ]; protected $table = 'speedtests'; diff --git a/config/app.php b/config/app.php index 455def69..0243232d 100644 --- a/config/app.php +++ b/config/app.php @@ -129,7 +129,7 @@ return [ |-------------------------------------------------------------------------- */ - 'version' => '1.0.0', + 'version' => '1.1.0', /* |-------------------------------------------------------------------------- diff --git a/public/css/main.css b/public/css/main.css index edb5423a..09b012a1 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -43,3 +43,7 @@ .Toastify__toast--info { background: #1f5a81 !important; } + +.Toastify__toast--success { + background: #28922b !important; +} diff --git a/public/js/app.js b/public/js/app.js index b9c2f212..2246af1c 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -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)); } }]); diff --git a/resources/js/components/Data/Backup.js b/resources/js/components/Data/Backup.js new file mode 100644 index 00000000..b8886fe2 --- /dev/null +++ b/resources/js/components/Data/Backup.js @@ -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 ( + + ); + } +} + +if (document.getElementById('Backup')) { + ReactDOM.render(, document.getElementById('Backup')); +} diff --git a/resources/js/components/Data/DataRow.js b/resources/js/components/Data/DataRow.js new file mode 100644 index 00000000..65d1dbbf --- /dev/null +++ b/resources/js/components/Data/DataRow.js @@ -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 ( + + + +

Use these buttons to backup/restore your data

+ +
+ + + + + + +
+ ); + } +} + +if (document.getElementById('DataRow')) { + ReactDOM.render(, document.getElementById('DataRow')); +} diff --git a/resources/js/components/Data/Restore.js b/resources/js/components/Data/Restore.js new file mode 100644 index 00000000..1c472efa --- /dev/null +++ b/resources/js/components/Data/Restore.js @@ -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 ( + <> + + + + + Restore from a backup + + +

Upload your JSON backup file here:

+ + + + {filename} + + + {uploadReady === true && + + } +
+
+ + ); + } +} + +if (document.getElementById('Restore')) { + ReactDOM.render(, document.getElementById('Restore')); +} diff --git a/resources/js/components/Home/Footer.js b/resources/js/components/Home/Footer.js index 9f6fe1aa..c37d05d6 100644 --- a/resources/js/components/Home/Footer.js +++ b/resources/js/components/Home/Footer.js @@ -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 ( +

Speedtest Tracker Version: {version}

See the code on GitHub

diff --git a/resources/js/components/Home/HomePage.js b/resources/js/components/Home/HomePage.js index 5af993e8..c2c9730e 100644 --- a/resources/js/components/Home/HomePage.js +++ b/resources/js/components/Home/HomePage.js @@ -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 {
+