mirror of
https://github.com/henrywhitaker3/Speedtest-Tracker.git
synced 2025-12-21 13:23:04 +01:00
Added methods to delete speedtests
This commit is contained in:
@@ -5,6 +5,7 @@ namespace App\Helpers;
|
|||||||
use App\Speedtest;
|
use App\Speedtest;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
use Illuminate\Support\Facades\File;
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
@@ -253,4 +254,55 @@ class SpeedtestHelper {
|
|||||||
|
|
||||||
return $rate;
|
return $rate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a backup of the SQLite database
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function dbBackup()
|
||||||
|
{
|
||||||
|
if(env('DB_CONNECTION') === 'sqlite') {
|
||||||
|
if(env('DB_DATABASE') !== null) {
|
||||||
|
$current = env('DB_DATABASE');
|
||||||
|
if(File::copy($current, $current . '.bak')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all speedtests from the database
|
||||||
|
*
|
||||||
|
* @return boolean|string
|
||||||
|
*/
|
||||||
|
public static function deleteAll()
|
||||||
|
{
|
||||||
|
Cache::flush();
|
||||||
|
|
||||||
|
if(SpeedtestHelper::dbBackup() !== false) {
|
||||||
|
if(sizeof(Speedtest::whereNotNull('id')->get()) > 0) {
|
||||||
|
if(Speedtest::whereNotNull('id')->delete()) {
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'msg' => 'There was an error backing up the database. No speedtests have been deleted.'
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,4 +147,45 @@ class SpeedtestController extends Controller
|
|||||||
], 500);
|
], 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all speedtests from db
|
||||||
|
*
|
||||||
|
* @return Response
|
||||||
|
*/
|
||||||
|
public function deleteAll()
|
||||||
|
{
|
||||||
|
$ret = SpeedtestHelper::deleteAll();
|
||||||
|
|
||||||
|
if($ret['success']) {
|
||||||
|
return response()->json([
|
||||||
|
'method' => 'delete all speedtests from the database',
|
||||||
|
'success' => true
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'method' => 'delete all speedtests from the database',
|
||||||
|
'success' => false,
|
||||||
|
'error' => $ret['msg'],
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a specific speedtest from the database
|
||||||
|
*
|
||||||
|
* @param Speedtest $speedtest
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function delete(Speedtest $speedtest)
|
||||||
|
{
|
||||||
|
$speedtest->delete();
|
||||||
|
|
||||||
|
Cache::flush();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'method' => 'delete a speedtest from the database',
|
||||||
|
'success' => true,
|
||||||
|
], 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
database/.gitignore
vendored
1
database/.gitignore
vendored
@@ -1,2 +1,3 @@
|
|||||||
*.sqlite
|
*.sqlite
|
||||||
*.sqlite-journal
|
*.sqlite-journal
|
||||||
|
*.bak
|
||||||
|
|||||||
2
public/js/app.js
vendored
2
public/js/app.js
vendored
File diff suppressed because one or more lines are too long
24
resources/js/components/Graphics/TableRow.js
vendored
24
resources/js/components/Graphics/TableRow.js
vendored
@@ -1,6 +1,8 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Modal } from 'react-bootstrap';
|
import { Modal, Button } from 'react-bootstrap';
|
||||||
|
import Axios from 'axios';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
export default class TableRow extends Component {
|
export default class TableRow extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -25,6 +27,25 @@ export default class TableRow extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete = (id) => {
|
||||||
|
var url = 'api/speedtest/delete/' + id;
|
||||||
|
|
||||||
|
Axios.delete(url)
|
||||||
|
.then((resp) => {
|
||||||
|
console.log(resp);
|
||||||
|
toast.success('Speedtest deleted');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if(err.response.status == 404) {
|
||||||
|
toast.warning('Speedtest not found');
|
||||||
|
} else {
|
||||||
|
toast.error('Something went wrong');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.toggleShow();
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var e = this.state.data;
|
var e = this.state.data;
|
||||||
var show = this.state.show;
|
var show = this.state.show;
|
||||||
@@ -52,6 +73,7 @@ export default class TableRow extends Component {
|
|||||||
{e.scheduled != undefined &&
|
{e.scheduled != undefined &&
|
||||||
<p>Type: {e.scheduled == true ? 'scheduled' : 'manual'}</p>
|
<p>Type: {e.scheduled == true ? 'scheduled' : 'manual'}</p>
|
||||||
}
|
}
|
||||||
|
<Button variant="danger" onClick={() => { this.delete(e.id) }}>Delete</Button>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
</Modal>
|
</Modal>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
2
resources/js/components/Home/HomePage.js
vendored
2
resources/js/components/Home/HomePage.js
vendored
@@ -5,7 +5,7 @@ import LatestResults from '../Graphics/LatestResults';
|
|||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import DataRow from '../Data/DataRow';
|
import DataRow from '../Data/DataRow';
|
||||||
import TestsTable from '../Graphics/TestsTable';
|
import TestsTable from '../Graphics/TestsTable';
|
||||||
import Settings from './Settings';
|
import Settings from '../Settings/Settings';
|
||||||
|
|
||||||
export default class HomePage extends Component {
|
export default class HomePage extends Component {
|
||||||
|
|
||||||
|
|||||||
66
resources/js/components/Settings/ResetSettings.js
vendored
Normal file
66
resources/js/components/Settings/ResetSettings.js
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Modal, Button } from 'react-bootstrap';
|
||||||
|
import SettingsModalCard from './SettingsModalCard';
|
||||||
|
import Axios from 'axios';
|
||||||
|
import { toast } from 'react-toastify';
|
||||||
|
|
||||||
|
export default class ResetSettings extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
show: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleShow = () => {
|
||||||
|
if(this.state.show) {
|
||||||
|
this.setState({ show: false });
|
||||||
|
} else {
|
||||||
|
this.setState({ show:true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAll = () => {
|
||||||
|
var url = 'api/speedtest/delete/all';
|
||||||
|
|
||||||
|
Axios.delete(url)
|
||||||
|
.then((resp) => {
|
||||||
|
toast.success('All speedtests have been deleted.');
|
||||||
|
this.toggleShow();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if(err.response.data.error == undefined) {
|
||||||
|
toast.error('Something went wrong.');
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.error(err.response.data.error);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var show = this.state.show;
|
||||||
|
const title = 'Reset Speedtests';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingsModalCard title={title} description="Bulk delete speedtests from the database." toggleShow={this.toggleShow} />
|
||||||
|
<Modal show={show} onHide={this.toggleShow}>
|
||||||
|
<Modal.Header>
|
||||||
|
<Modal.Title>{title}</Modal.Title>
|
||||||
|
</Modal.Header>
|
||||||
|
<Modal.Body>
|
||||||
|
<h4>Clear all speedtests</h4>
|
||||||
|
<p className="text-muted">If using SQLite, a backup of the database will be stored in the location of the current database.</p>
|
||||||
|
<Button onClick={this.deleteAll} variant="danger">Delete all</Button>
|
||||||
|
</Modal.Body>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('ResetSettings')) {
|
||||||
|
ReactDOM.render(<ResetSettings />, document.getElementById('ResetSettings'));
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom';
|
|||||||
import { Card, Form, Button, Modal, Row, Col } from 'react-bootstrap';
|
import { Card, Form, Button, Modal, Row, Col } from 'react-bootstrap';
|
||||||
import Axios from 'axios';
|
import Axios from 'axios';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
|
import SettingsModalCard from '../Settings/SettingsModalCard';
|
||||||
|
|
||||||
export default class SettingWithModal extends Component {
|
export default class SettingWithModal extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -98,15 +99,7 @@ export default class SettingWithModal extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Card className="m-2 setting-card">
|
<SettingsModalCard title={title} description={description} toggleShow={this.toggleShow} />
|
||||||
<Card.Body className="d-flex align-items-center">
|
|
||||||
<div>
|
|
||||||
<h4>{title}</h4>
|
|
||||||
<p>{description}</p>
|
|
||||||
<Button variant="primary" onClick={this.toggleShow}>Edit</Button>
|
|
||||||
</div>
|
|
||||||
</Card.Body>
|
|
||||||
</Card>
|
|
||||||
<Modal show={show} onHide={this.toggleShow}>
|
<Modal show={show} onHide={this.toggleShow}>
|
||||||
<Modal.Header>
|
<Modal.Header>
|
||||||
<Modal.Title>{title}</Modal.Title>
|
<Modal.Title>{title}</Modal.Title>
|
||||||
@@ -130,7 +123,15 @@ export default class SettingWithModal extends Component {
|
|||||||
readonly = true;
|
readonly = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(e.type == 'checkbox') {
|
if(e.type == 'info') {
|
||||||
|
return (
|
||||||
|
<Row key={e.obj.id} className="d-flex align-items-center">
|
||||||
|
<Col md={md} sm={sm}>
|
||||||
|
<p>{e.obj.content}</p>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
} else if(e.type == 'checkbox') {
|
||||||
return (
|
return (
|
||||||
<Row key={e.obj.id} className="d-flex align-items-center">
|
<Row key={e.obj.id} className="d-flex align-items-center">
|
||||||
<Col md={md} sm={sm}>
|
<Col md={md} sm={sm}>
|
||||||
@@ -5,6 +5,7 @@ import Loader from '../Loader';
|
|||||||
import Axios from 'axios';
|
import Axios from 'axios';
|
||||||
import Setting from './Setting';
|
import Setting from './Setting';
|
||||||
import SettingWithModal from './SettingWithModal';
|
import SettingWithModal from './SettingWithModal';
|
||||||
|
import ResetSettings from './ResetSettings';
|
||||||
|
|
||||||
export default class Settings extends Component {
|
export default class Settings extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -157,6 +158,9 @@ export default class Settings extends Component {
|
|||||||
}
|
}
|
||||||
]} />
|
]} />
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col lg={{ span: 4 }} md={{ span: 6 }} sm={{ span: 12 }}>
|
||||||
|
<ResetSettings />
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
37
resources/js/components/Settings/SettingsModalCard.js
vendored
Normal file
37
resources/js/components/Settings/SettingsModalCard.js
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import { Card, Button } from 'react-bootstrap';
|
||||||
|
|
||||||
|
export default class SettingsModalCard extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
title: this.props.title,
|
||||||
|
description: this.props.description,
|
||||||
|
toggleShow: this.props.toggleShow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
var title = this.state.title;
|
||||||
|
var description = this.state.description;
|
||||||
|
var toggleShow = this.state.toggleShow;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="m-2 setting-card">
|
||||||
|
<Card.Body className="d-flex align-items-center">
|
||||||
|
<div>
|
||||||
|
<h4>{title}</h4>
|
||||||
|
<p>{description}</p>
|
||||||
|
<Button variant="primary" onClick={toggleShow}>Edit</Button>
|
||||||
|
</div>
|
||||||
|
</Card.Body>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.getElementById('SettingModalCard')) {
|
||||||
|
ReactDOM.render(<SettingsModalCard />, document.getElementById('SettingModalCard'));
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use App\Helpers\SpeedtestHelper;
|
||||||
use App\Http\Controllers\SpeedtestController;
|
use App\Http\Controllers\SpeedtestController;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
@@ -29,6 +30,13 @@ Route::group([
|
|||||||
->name('speedtest.fail');
|
->name('speedtest.fail');
|
||||||
Route::get('run', 'SpeedtestController@run')
|
Route::get('run', 'SpeedtestController@run')
|
||||||
->name('speedtest.run');
|
->name('speedtest.run');
|
||||||
|
|
||||||
|
Route::group([
|
||||||
|
'prefix' => 'delete'
|
||||||
|
], function () {
|
||||||
|
Route::delete('all', 'SpeedtestController@deleteAll');
|
||||||
|
Route::delete('{speedtest}', 'SpeedtestController@delete');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::group([
|
Route::group([
|
||||||
|
|||||||
@@ -30,6 +30,6 @@ Route::get(SettingsHelper::getBase() . 'files/{path?}', function($file) {
|
|||||||
->name('files');
|
->name('files');
|
||||||
|
|
||||||
Route::get('/{path?}', function() {
|
Route::get('/{path?}', function() {
|
||||||
return view('app', [ 'title' => 'Speedtest Checker' ]);
|
return view('app', [ 'title' => 'Speedtest Tracker' ]);
|
||||||
})->where('path', '^((?!\/api\/).)*$')
|
})->where('path', '^((?!\/api\/).)*$')
|
||||||
->name('react');
|
->name('react');
|
||||||
|
|||||||
Reference in New Issue
Block a user