@@ -17,11 +17,21 @@ | |||
export default { | |||
name: 'App', | |||
mounted() { | |||
fetch("/api/auth/validate", { method: "POST" }).then(result => result.json()).then(json => { | |||
if (!json.logged) { | |||
localStorage.setItem("token", null) | |||
} | |||
}) | |||
let token = localStorage.getItem("token") | |||
if (token != null) { | |||
let headers = new Headers(); | |||
headers.append("Authorization", "Bearer " + token) | |||
fetch(this.$api + "/api/auth/validate", { method: "POST", headers: headers }).then(result => result.json()).then(json => { | |||
if (json.logged) { | |||
localStorage.setItem("token", json.token) | |||
} | |||
else { | |||
localStorage.removeItem("token") | |||
} | |||
}) | |||
} | |||
} | |||
} | |||
</script> | |||
@@ -7,6 +7,8 @@ import 'material-design-icons-iconfont/dist/material-design-icons.css' | |||
Vue.config.productionTip = false | |||
Vue.prototype.$api = "http://localhost:8081" | |||
Vue.use(Vuetify) | |||
new Vue({ | |||
render: h => h(App), | |||
@@ -3,13 +3,15 @@ import Vue from 'vue' | |||
import Index from './views/Index.vue' | |||
import Sumbit from './views/Submit.vue' | |||
import Login from './views/auth/Login.vue' | |||
import UBenchmarks from './views/auth/UBenchmarks.vue' | |||
Vue.use(VueRouter) | |||
const routes = [ | |||
{ path: '/', component: Index }, | |||
{ path: '/submit', component: Sumbit }, | |||
{ path: '/login', component: Login } | |||
{ path: '/login', component: Login }, | |||
{ path: '/unconfirmedBenchmarks', component: UBenchmarks } | |||
]; | |||
export default new VueRouter({ | |||
@@ -1,12 +1,12 @@ | |||
<template> | |||
<div id="index"> | |||
<v-card class="bench" :loading="loading"> | |||
<v-card class="bench elevation-5" :loading="loading"> | |||
<v-card-title> | |||
<h2>Benchmarks</h2> | |||
<v-spacer></v-spacer> | |||
<v-text-field class="search" v-model="search" append-icon="magnify" label="Search" single-line hide-details></v-text-field> | |||
</v-card-title> | |||
<v-data-table :search="search" multi-sort :headers="headers" :items="benchmarks" :items-per-page="5" class="elevation-5"> | |||
<v-data-table :search="search" multi-sort :headers="headers" :items="benchmarks" :items-per-page="5"> | |||
<template v-slot:item.timestamp="{ item }"> | |||
<span>{{ new Date(item.timestamp).toLocaleDateString() }}</span> | |||
</template> | |||
@@ -54,7 +54,7 @@ export default { | |||
} | |||
}, | |||
mounted() { | |||
fetch("/api/benchmarks").then(result => result.json()).then(json => { | |||
fetch(this.$api + "/api/benchmarks").then(result => result.json()).then(json => { | |||
this.benchmarks = json | |||
this.loading = false | |||
}) | |||
@@ -67,5 +67,6 @@ export default { | |||
margin: 10%; | |||
margin-top: 5%; | |||
margin-bottom: 2%; | |||
padding: 2%; | |||
} | |||
</style> |
@@ -13,6 +13,7 @@ | |||
<v-btn @click="submit()" color="blue">Sumbit</v-btn> | |||
</v-form> | |||
</v-card> | |||
<h4>Back to <router-link to="/">Benchmarks</router-link></h4> | |||
</div> | |||
</template> | |||
@@ -29,28 +30,37 @@ export default { | |||
alertType: "error", | |||
alertMessage: "", | |||
alertShow: false, | |||
submitted: false, | |||
} | |||
}, | |||
methods: { | |||
submit() { | |||
if (this.valid) { | |||
fetch("/api/submit", { | |||
if (!this.submitted && this.valid) { | |||
this.submitted = true | |||
fetch(this.$api + "/api/submit", { | |||
method: "POST", | |||
body: JSON.stringify({ | |||
vendor: this.vendor, | |||
model: this.model, | |||
hashrate: this.hashrate, | |||
miner: this.miner, | |||
user: this.user | |||
minerVersion: this.miner, | |||
owner: this.user | |||
}) | |||
}).then(result => result.json()).then(json => { | |||
this.alertType = json.success ? "success" : "error" | |||
this.alertMessage = json.message | |||
this.alertShow = true | |||
if (json.success) { | |||
setTimeout(() => this.$router.push("/"), 5000) | |||
} else { | |||
this.submitted = false | |||
} | |||
}).catch(() => { | |||
this.alertType = "error" | |||
this.alertMessage = "An error has occurred !" | |||
this.alertShow = true | |||
this.submitted = false | |||
}) | |||
} | |||
} | |||
@@ -23,16 +23,17 @@ export default { | |||
alertShow: false | |||
} | |||
}, | |||
mounted() { | |||
let token = localStorage.getItem("token") | |||
if (token != null && token.length == 86) | |||
{ | |||
this.$router.push("/unconfirmedBenchmarks") | |||
} | |||
}, | |||
methods: { | |||
mounted() { | |||
if (localStorage.getItem("token") != null) | |||
{ | |||
this.$router.push("/unconfirmedBenchmarks") | |||
} | |||
}, | |||
login() { | |||
if (this.valid) { | |||
fetch("/api/auth/login", { | |||
fetch(this.$api + "/api/auth/login", { | |||
method: "POST", | |||
body: JSON.stringify({ | |||
username: this.username, | |||
@@ -41,11 +42,11 @@ export default { | |||
}).then(result => result.json()).then(json => { | |||
let valid = json.token != null | |||
this.alertType = valid ? "success" : "error" | |||
this.alertMessage = valid ? "You are now logged in!" : (json.error + ": " + json.message) | |||
this.alertMessage = valid ? "You are now logged in!" : json.message | |||
this.alertShow = true | |||
if (valid) { | |||
localStorage.setItem("token", json.token) | |||
setTimeout(() => this.$router.push("/unconfirmedBenchmarks"), 1000 * 5) | |||
setTimeout(() => this.$router.push("/unconfirmedBenchmarks"), 5000) | |||
} | |||
}).catch(() => { | |||
this.alertType = "error" | |||
@@ -1,12 +1,124 @@ | |||
<template> | |||
<div id="ubenchmarks"> | |||
<v-card class="bench elevation-5" :loading="loading"> | |||
<v-alert v-for="(alert, i) in this.alerts" :key="i" :type="alert.type">{{ alert.message }}</v-alert> | |||
<v-card-title> | |||
<h2>Unconfirmed Benchmarks</h2> | |||
<v-spacer></v-spacer> | |||
<v-text-field class="search" v-model="search" append-icon="magnify" label="Search" single-line hide-details></v-text-field> | |||
</v-card-title> | |||
<v-data-table v-model="selected" show-select :search="search" multi-sort :headers="headers" :items="benchmarks" :items-per-page="5"> | |||
<template v-slot:item.timestamp="{ item }"> | |||
<span>{{ new Date(item.timestamp).toLocaleDateString() }}</span> | |||
</template> | |||
</v-data-table> | |||
<div class="buttons"> | |||
<v-btn @click="update(false)" color="red">Delete</v-btn> | |||
<v-btn @click="update(true)" color="green">Confirm</v-btn> | |||
</div> | |||
</v-card> | |||
<h4>Back to confirmed <router-link to="/">Benchmarks</router-link></h4> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
data() { | |||
return { | |||
loading: true, | |||
search: "", | |||
selected: [], | |||
headers: [ | |||
{ | |||
text: "Vendor", | |||
align: "start", | |||
value: "vendor" | |||
}, | |||
{ | |||
text: "Model", | |||
value: "model" | |||
}, | |||
{ | |||
text: "Hashrate (h/s)", | |||
value: "hashrate" | |||
}, | |||
{ | |||
text: "Miner", | |||
value: "minerVersion" | |||
}, | |||
{ | |||
text: "Submitted On", | |||
value: "timestamp", | |||
class: "Date" | |||
}, | |||
{ | |||
text: "User", | |||
value: "owner" | |||
} | |||
], | |||
benchmarks: [], | |||
alerts: [] | |||
} | |||
}, | |||
mounted() { | |||
let token = localStorage.getItem("token") | |||
if (token == null || token.length != 86) //TODO must wait App.mounted() | |||
{ | |||
this.$router.push("/login") | |||
return | |||
} | |||
let headers = new Headers(); | |||
headers.append("Authorization", "Bearer " + token) | |||
fetch(this.$api + "/api/unconfirmedBenchmarks", { headers: headers }).then(result => result.json()).then(json => { | |||
this.benchmarks = json | |||
this.loading = false | |||
}) | |||
}, | |||
methods: { | |||
update(confirm) { | |||
let headers = new Headers(); | |||
let token = localStorage.getItem("token") | |||
headers.append("Authorization", "Bearer " + token) | |||
let localSelected = this.selected | |||
for (let bench of localSelected) | |||
{ | |||
fetch(this.$api + "/api/" + (confirm ? "confirm" : "delete"), { | |||
method: "POST", | |||
headers: headers, | |||
body: JSON.stringify({ | |||
benchID: bench.id | |||
}) | |||
}).then(result => result.json()).then(json => { | |||
this.alerts.push({ | |||
type: json.success ? "success" : "error", | |||
message: json.message | |||
}) | |||
setTimeout(() => this.alerts.shift(), 5000) | |||
if (json.success) { | |||
let index = this.benchmarks.findIndex(x => x.id == bench.id) | |||
this.benchmarks.splice(index, 1) | |||
this.selected.shift() | |||
} | |||
}) | |||
} | |||
} | |||
} | |||
} | |||
</script> | |||
<style scoped> | |||
.bench { | |||
margin: 10%; | |||
margin-top: 5%; | |||
margin-bottom: 2%; | |||
padding: 2%; | |||
} | |||
.buttons { | |||
display: flex; | |||
justify-content: space-evenly; | |||
} | |||
</style> |
@@ -21,6 +21,11 @@ public class Benchmark { | |||
this.timestamp = timestamp; | |||
} | |||
public void setId(int id) | |||
{ | |||
this.id = id; | |||
} | |||
public int getId() | |||
{ | |||
return id; | |||
@@ -22,4 +22,4 @@ public class Main | |||
{ | |||
return paladin; | |||
} | |||
} | |||
} |
@@ -7,6 +7,7 @@ import com.google.inject.Inject | |||
import fr.litarvan.paladin.Session | |||
import fr.litarvan.paladin.http.Controller | |||
import fr.litarvan.paladin.http.routing.JsonBody | |||
import fr.litarvan.paladin.http.routing.RequestParams | |||
import fr.slixe.benchmarks.User | |||
import fr.slixe.benchmarks.http.InvalidParameterException | |||
@@ -19,6 +20,7 @@ public class AuthController extends Controller { | |||
@Inject | |||
private AuthService authService | |||
@JsonBody | |||
@RequestParams(required = ["username", "password"]) | |||
def login(String username, String password, Session session) | |||
{ | |||
@@ -32,7 +34,7 @@ public class AuthController extends Controller { | |||
throw new InvalidParameterException("Username or password is incorrect") | |||
} | |||
log.info(String.format("User %s is now logged in from %s.", user.getUsername())) | |||
log.info(String.format("User %s is now logged in.", user.getUsername())) | |||
session[User] = user | |||
@@ -44,6 +46,7 @@ public class AuthController extends Controller { | |||
def validate(Session session) | |||
{ | |||
[ | |||
token: session.token, | |||
logged: session[User] != null | |||
] | |||
} | |||
@@ -7,6 +7,7 @@ import com.google.gson.Gson; | |||
import com.google.inject.Inject; | |||
import fr.litarvan.paladin.http.Controller; | |||
import fr.litarvan.paladin.http.routing.JsonBody | |||
import fr.litarvan.paladin.http.routing.RequestParams; | |||
import fr.slixe.benchmarks.Benchmark; | |||
import fr.slixe.benchmarks.Benchmark.Vendor | |||
@@ -47,12 +48,13 @@ public class MainController extends Controller { | |||
* @param String minerVersion (XMRig 5.9.0) | |||
* @param String owner (Slixe) | |||
*/ | |||
@JsonBody | |||
@RequestParams(required = ["vendor", "model", "hashrate", "minerVersion", "owner"]) | |||
def submit(Vendor vendor, String model, long hashrate, String minerVersion, String owner) | |||
{ | |||
log.debug("A new benchmark has been submitted!") | |||
Benchmark benchmark = new Benchmark(benchmarkService.lastBenchId(), vendor, model, hashrate, minerVersion, owner) | |||
Benchmark benchmark = new Benchmark(benchmarkService.lastUnconfirmedBenchId(), vendor, model, hashrate, minerVersion, owner, System.currentTimeMillis()) | |||
benchmarkService.addUnconfirmedBenchmarks(benchmark) | |||
[ | |||
@@ -66,6 +68,7 @@ public class MainController extends Controller { | |||
* accessible from: api/confirm | |||
* @param int benchID | |||
*/ | |||
@JsonBody | |||
@RequestParams(required = ["benchID"]) | |||
def confirm(int benchID) | |||
{ | |||
@@ -84,6 +87,7 @@ public class MainController extends Controller { | |||
* accessible from: api/delete | |||
* @param int benchID | |||
*/ | |||
@JsonBody | |||
@RequestParams(required = ["benchID"]) | |||
def delete(int benchID) | |||
{ | |||
@@ -11,6 +11,7 @@ import com.google.gson.JsonSerializationContext; | |||
import com.google.gson.JsonSerializer; | |||
import fr.slixe.benchmarks.User; | |||
import fr.slixe.benchmarks.service.AuthService; | |||
public class UserAdapter implements JsonSerializer<User>, JsonDeserializer<User> { | |||
@@ -20,9 +21,19 @@ public class UserAdapter implements JsonSerializer<User>, JsonDeserializer<User> | |||
JsonObject jsonObject = json.getAsJsonObject(); | |||
String username = jsonObject.get("username").getAsString(); | |||
String hashedPassword = jsonObject.get("hashedPassword").getAsString(); | |||
String salt = jsonObject.get("salt").getAsString(); | |||
String hashedPassword = "no password"; | |||
if (jsonObject.has("password")) { | |||
String password = jsonObject.get("password").getAsString(); | |||
hashedPassword = AuthService.hash(password, salt); | |||
} | |||
else if (jsonObject.has("hashedPassword")) { | |||
hashedPassword = jsonObject.get("hashedPassword").getAsString(); | |||
} | |||
return new User(username, hashedPassword, salt); | |||
} | |||
@@ -91,6 +91,7 @@ public class BenchmarkService { | |||
if (opt.isPresent()) { | |||
Benchmark benchmark = opt.get(); | |||
this.unconfirmedBenchmarks.remove(benchmark); | |||
benchmark.setId(lastBenchId()); | |||
this.confirmedBenchmarks.add(benchmark); | |||
saveBenchmarks(); | |||
} | |||
@@ -113,15 +114,22 @@ public class BenchmarkService { | |||
return result; | |||
} | |||
public int lastUnconfirmedBenchId() | |||
{ | |||
if (this.unconfirmedBenchmarks.size() == 0) | |||
return 0; | |||
int b = this.unconfirmedBenchmarks.get(this.unconfirmedBenchmarks.size() - 1).getId() + 1; | |||
return b; | |||
} | |||
public int lastBenchId() | |||
{ | |||
if (this.confirmedBenchmarks.size() == 0 && this.unconfirmedBenchmarks.size() == 0) | |||
if (this.confirmedBenchmarks.size() == 0) | |||
return 0; | |||
int a = this.confirmedBenchmarks.get(this.confirmedBenchmarks.size() - 1).getId() + 1; | |||
int b = this.unconfirmedBenchmarks.get(this.unconfirmedBenchmarks.size() - 1).getId() + 1; | |||
return a < b ? b : a; | |||
return a; | |||
} | |||
public List<Benchmark> getBenchmarks() | |||
@@ -2,22 +2,22 @@ package config; | |||
group '/api', { | |||
get '/benchmarks', 'main:benchmarks' | |||
post '/submit', 'main:submit' | |||
group '', { | |||
get '/unconfirmedBenchmarks' | |||
post '/submit' | |||
post '/confirm' | |||
post '/delete' | |||
}, [ | |||
action: 'main', | |||
middleware: 'auth' | |||
] | |||
} | |||
group '/auth', { | |||
post '/validate' | |||
post '/login' | |||
post '/logout' | |||
}, [ | |||
action: 'auth' | |||
] | |||
} | |||
group '/api/auth', { //not recognized in group /api | |||
post '/validate' | |||
post '/login' | |||
post '/logout' | |||
}, [ | |||
action: 'auth' | |||
] |