Merge branch 'restructure_project' of xicon/xtr-improved-frontend into master
This commit is contained in:
commit
cc8eb07afa
5
.babelrc.js
Normal file
5
.babelrc.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
'@babel/preset-env'
|
||||||
|
],
|
||||||
|
}
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
node_modules
|
16
README.md
16
README.md
@ -1,3 +1,17 @@
|
|||||||
# xtr-improved-frontend
|
# xtr-improved-frontend
|
||||||
|
|
||||||
This is the improved frontend/webgui for xtr.
|
This is the improved frontend/webgui for xtr.
|
||||||
|
|
||||||
|
## Install / Build guide
|
||||||
|
Install all needed node packages - `node_modules` folder will be created
|
||||||
|
```
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
To build final sources for production environment in `dist` folder
|
||||||
|
```
|
||||||
|
$ npm run build
|
||||||
|
```
|
||||||
|
Running app in development mode - accessible under http://localhost:8080/
|
||||||
|
````
|
||||||
|
$ npm run serve
|
||||||
|
````
|
0
index.html → dist/index.html
vendored
0
index.html → dist/index.html
vendored
0
main.js → dist/main.js
vendored
0
main.js → dist/main.js
vendored
7974
package-lock.json
generated
Normal file
7974
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "xtr",
|
||||||
|
"version": "0.0.2",
|
||||||
|
"description": "xtr",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf dist",
|
||||||
|
"build": "npm run clean && webpack --mode production",
|
||||||
|
"serve": "webpack-dev-server --mode development"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.6.4",
|
||||||
|
"@babel/preset-env": "^7.6.3",
|
||||||
|
"babel-loader": "^8.0.4",
|
||||||
|
"css-loader": "^1.0.1",
|
||||||
|
"html-webpack-plugin": "^3.2.0",
|
||||||
|
"rimraf": "^2.7.1",
|
||||||
|
"vue": "^2.5.17",
|
||||||
|
"vue-loader": "^15.7.2",
|
||||||
|
"vue-style-loader": "^4.1.2",
|
||||||
|
"vue-template-compiler": "^2.5.17",
|
||||||
|
"axios": "^0.19.0",
|
||||||
|
"webpack": "^4.41.2",
|
||||||
|
"webpack-cli": "^3.3.10",
|
||||||
|
"webpack-dev-server": "^3.9.0"
|
||||||
|
}
|
||||||
|
}
|
205
src/StatusBox.vue
Normal file
205
src/StatusBox.vue
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
<template>
|
||||||
|
<div id="statusBox">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-4">
|
||||||
|
<select v-model="selectedIp" id="serverSelectList">
|
||||||
|
<option v-for="(serverItem, index) in serverlist"
|
||||||
|
v-bind:value="serverItem"
|
||||||
|
>
|
||||||
|
{{ serverItem[0] }}:{{ serverItem[1] }} (AS{{ serverItem[2] }})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input v-model="target" placeholder="Enter Ip or Domain">
|
||||||
|
<a href="#" class="traceroutes" v-on:click="traceRoute()">⛟</a>
|
||||||
|
<span class="small">{{executionTime}}</span>
|
||||||
|
<div>
|
||||||
|
<span>History: {{ tableDataHistory.length }}</span>
|
||||||
|
<table :id="'tableId_'+boxNumber" class="table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>IP</td>
|
||||||
|
<td>HOSTNAME</td>
|
||||||
|
<td>PING</td>
|
||||||
|
<td>AS</td>
|
||||||
|
</tr>
|
||||||
|
<tr v-for="tData in tableData">
|
||||||
|
<td>{{ tData[0] }}</td>
|
||||||
|
<td>{{ tData[1] }}</td>
|
||||||
|
<td>{{ tData[2] }}</td>
|
||||||
|
<td :title="tData[4]">{{ tData[3] }}</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
serverlist: Array,
|
||||||
|
boxNumber: Number
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
tableDataHistory: {
|
||||||
|
handler() {
|
||||||
|
sessionStorage.setItem('tableDataHistory' + this.boxNumber, JSON.stringify(this.tableDataHistory));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
executionTime: {
|
||||||
|
handler() {
|
||||||
|
sessionStorage.setItem('executionTime' + this.boxNumber, JSON.stringify(this.executionTime));
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
selectedIp: {
|
||||||
|
handler() {
|
||||||
|
sessionStorage.setItem('selectedIp' + this.boxNumber, JSON.stringify(this.selectedIp));
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
target : {
|
||||||
|
handler() {
|
||||||
|
sessionStorage.setItem('target' + this.boxNumber, JSON.stringify(this.target));
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
tableData : {
|
||||||
|
handler() {
|
||||||
|
sessionStorage.setItem('tableData' + this.boxNumber, JSON.stringify(this.tableData));
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setLastExecutionTime: function setLastExecutionTime() {
|
||||||
|
var day = new Date();
|
||||||
|
var hours = day.getHours();
|
||||||
|
var minutes = day.getMinutes() ;
|
||||||
|
var seconds = day.getSeconds();
|
||||||
|
|
||||||
|
if (hours < 10) {
|
||||||
|
hours = '0' + hours;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minutes < 10) {
|
||||||
|
minutes = '0' + minutes;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seconds < 10) {
|
||||||
|
seconds = '0' + seconds;
|
||||||
|
}
|
||||||
|
this.executionTime = hours + ':' + minutes + ':' + seconds;
|
||||||
|
|
||||||
|
},
|
||||||
|
traceRoute: function traceRoute() {
|
||||||
|
// e.g. http://199.247.22.146:12111/v3/client/request/8.8.8.8
|
||||||
|
axios.get("http://"+ this.selectedIp[0] + ":" +this.selectedIp[1] + "/v3/client/request/" + this.target)
|
||||||
|
.then(response => {
|
||||||
|
// JSON responses are automatically parsed.
|
||||||
|
this.tableData = response.data;
|
||||||
|
this.setLastExecutionTime();
|
||||||
|
this.tableDataHistory.push(this.tableData);
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.executionTime = 'Check request'
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
resetData: function resetData() {
|
||||||
|
this.executionTime = 'Not execute yet',
|
||||||
|
this.selectedIp = ["199.247.22.146",12111,20473,1],
|
||||||
|
this.target = '8.8.8.8',
|
||||||
|
this.tableData = [ ],
|
||||||
|
this.tableDataHistory = [ ]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (sessionStorage.getItem('executionTime'+ this.boxNumber)) this.executionTime = JSON.parse(sessionStorage.getItem('executionTime'+ this.boxNumber));
|
||||||
|
if (sessionStorage.getItem('selectedIp'+ this.boxNumber)) {
|
||||||
|
this.selectedIp = JSON.parse(sessionStorage.getItem('selectedIp'+ this.boxNumber));
|
||||||
|
} else {
|
||||||
|
this.selectedIp = ["199.247.22.146",12111,20473,1]; // Change from static to dynamic
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionStorage.getItem('tableDataHistory'+ this.boxNumber)) this.tableDataHistory = JSON.parse(sessionStorage.getItem('tableDataHistory'+ this.boxNumber));
|
||||||
|
if (sessionStorage.getItem('target'+ this.boxNumber)) this.target = JSON.parse(sessionStorage.getItem('target'+ this.boxNumber));
|
||||||
|
if (sessionStorage.getItem('tableData'+ this.boxNumber)) this.tableData = JSON.parse(sessionStorage.getItem('tableData'+ this.boxNumber));
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
executionTime: 'Not execute yet',
|
||||||
|
selectedIp: ["199.247.22.146",12111,20473,1],
|
||||||
|
target: '8.8.8.8',
|
||||||
|
tableData : [ ],
|
||||||
|
tableDataHistory: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
span.small {
|
||||||
|
font-size: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.active {
|
||||||
|
border: 3px solid #9ac5e3;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td
|
||||||
|
{
|
||||||
|
border:1px solid grey;
|
||||||
|
font-size: 9pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traceroutes
|
||||||
|
{
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traceroutes:visited
|
||||||
|
{
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traceroutes:hover
|
||||||
|
{
|
||||||
|
color: #880000;
|
||||||
|
transform: rotate(-30deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.traceroutes:active
|
||||||
|
{
|
||||||
|
color: #bb0000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.traceroutes_rotating
|
||||||
|
{
|
||||||
|
display: inline-block;
|
||||||
|
text-decoration: none;
|
||||||
|
color: #bb0000;
|
||||||
|
animation: spin 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin
|
||||||
|
{
|
||||||
|
0% { transform: rotate(-30deg); }
|
||||||
|
25% { transform: rotate(0deg); }
|
||||||
|
50% { transform: rotate(30deg); }
|
||||||
|
75% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(-30deg); }
|
||||||
|
}
|
||||||
|
</style>
|
212
src/StatusBoxContainer.vue
Normal file
212
src/StatusBoxContainer.vue
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<template>
|
||||||
|
<div id="statusBoxContainer">
|
||||||
|
<div>
|
||||||
|
<button v-on:click="resetSession()">Reset whole session</button>
|
||||||
|
</div>
|
||||||
|
<!-- Hover icon section -->
|
||||||
|
<div id="info" class="info" title="test">
|
||||||
|
ⓘ
|
||||||
|
<div id="info_box" class="info_box">
|
||||||
|
This service is free for use by accepting the <a href="aup.txt">Acceptable Use Policy</a>.<br>
|
||||||
|
<br>
|
||||||
|
This is just an interface to to give a more GUI-ish
|
||||||
|
access to the self-registered XTR clients. No traceroute requests will be saved on this server. All request to the XTR clients
|
||||||
|
are handled by your browser.<br>
|
||||||
|
<br>
|
||||||
|
Want to add your server? <a href="xtr.pl">Download XTR client</a> now. Readme included in source code!<br>
|
||||||
|
Want to run your own master server? <a href="xtrd.pl">Download XTRd</a>. Readme included in source code!<br>
|
||||||
|
<br>
|
||||||
|
Questions?/Impress? -> <a href="mailto:rest@xicon.de">Friedrich Schrader</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<button v-on:click="executeSpecificBoxes(checkedBoxes)">Run selected</button>
|
||||||
|
<div style="display: inline-block;">
|
||||||
|
<input
|
||||||
|
:id="index"
|
||||||
|
type="checkbox"
|
||||||
|
v-for="(traceBox, index) in traceBoxes"
|
||||||
|
v-model="checkedBoxes"
|
||||||
|
:value="index"
|
||||||
|
@click="setActive(index)"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<button v-on:click="executeAllBoxes()">Request for all boxes</button>
|
||||||
|
<button v-on:click="addTraceBox()">Add new TraceBox</button>
|
||||||
|
|
||||||
|
<div class="grid-container">
|
||||||
|
<template v-for="(traceBox, index) in traceBoxes">
|
||||||
|
<component :is="traceBox" :ref="'boxref'" :key="index" v-bind:serverlist="listOfServers" v-bind:boxNumber="index"></component>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
|
import StatusBoxVue from './StatusBox.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
StatusBoxVue
|
||||||
|
},
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
listOfServers: [],
|
||||||
|
checkedBoxes: [],
|
||||||
|
traceBoxes: [
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
checkedBoxes : {
|
||||||
|
handler() {
|
||||||
|
sessionStorage.setItem('checkedBoxes', JSON.stringify(this.checkedBoxes));
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
|
||||||
|
} ,
|
||||||
|
listOfServers: {
|
||||||
|
handler() {
|
||||||
|
sessionStorage.setItem('listOfServers', JSON.stringify(this.listOfServers));
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
},
|
||||||
|
traceBoxes: {
|
||||||
|
handler() {
|
||||||
|
sessionStorage.setItem('traceBoxes', JSON.stringify(this.traceBoxes));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (sessionStorage.getItem('listOfServers')) {
|
||||||
|
this.listOfServers = JSON.parse(sessionStorage.getItem('listOfServers'));
|
||||||
|
} else {
|
||||||
|
this.getServer();
|
||||||
|
}
|
||||||
|
if (sessionStorage.getItem('traceBoxes')) {
|
||||||
|
this.traceBoxes = [];
|
||||||
|
JSON.parse(sessionStorage.getItem('traceBoxes')).forEach(element => {
|
||||||
|
this.traceBoxes.push(StatusBoxVue);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (sessionStorage.getItem('checkedBoxes')) this.checkedBoxes = JSON.parse(sessionStorage.getItem('checkedBoxes'));
|
||||||
|
|
||||||
|
this.setActiveFromSession(this.checkedBoxes);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetData: function () {
|
||||||
|
var boxesRefs = this.$refs.boxref;
|
||||||
|
|
||||||
|
boxesRefs.forEach(function(boxRef) {
|
||||||
|
boxRef.resetData();
|
||||||
|
});
|
||||||
|
this.checkedBoxes = [];
|
||||||
|
this.traceBoxes = [
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
StatusBoxVue,
|
||||||
|
];
|
||||||
|
},
|
||||||
|
resetSession: function resetSession() {
|
||||||
|
this.resetData();
|
||||||
|
sessionStorage.clear();
|
||||||
|
document.querySelectorAll('[id^=tableId_]').forEach(element => {
|
||||||
|
element.className = 'table table-striped';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
setActive: function setActive(tableId) {
|
||||||
|
var settedClasses = document.getElementById("tableId_"+tableId).className;
|
||||||
|
if (settedClasses == 'table table-striped') {
|
||||||
|
document.getElementById("tableId_"+tableId).className = 'table table-striped active';
|
||||||
|
} else {
|
||||||
|
document.getElementById("tableId_"+tableId).className = 'table table-striped';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setActiveFromSession: function setActiveFromSession(tableIds) {
|
||||||
|
tableIds.forEach(tableId => {
|
||||||
|
this.setActive(tableId);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
executeAllBoxes: function () {
|
||||||
|
var boxesRefs = this.$refs.boxref;
|
||||||
|
|
||||||
|
boxesRefs.forEach(function(boxRef) {
|
||||||
|
boxRef.traceRoute();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
executeSpecificBoxes: function (choosedBoxes) {
|
||||||
|
var boxesRefs = this.$refs.boxref;
|
||||||
|
|
||||||
|
choosedBoxes.forEach(function(choosedBox) {
|
||||||
|
boxesRefs.forEach(function(boxRef) {
|
||||||
|
if(choosedBox == boxRef.boxNumber) {
|
||||||
|
boxRef.traceRoute();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addTraceBox () {
|
||||||
|
this.traceBoxes.push(StatusBoxVue);
|
||||||
|
},
|
||||||
|
getServer: function getServerList() {
|
||||||
|
axios.get("http://xtr-master.xicon.eu/v3/server/list/online")
|
||||||
|
.then(response => {
|
||||||
|
// JSON responses are automatically parsed.
|
||||||
|
this.listOfServers = response.data;
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
this.errors.push(e)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
h4 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.grid-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto auto auto;
|
||||||
|
grid-gap: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info
|
||||||
|
{
|
||||||
|
display: block; position: absolute; right: 10px; top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info:hover .info_box
|
||||||
|
{
|
||||||
|
visibility: visible;
|
||||||
|
z-index: 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info .info_box
|
||||||
|
{
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 0px;
|
||||||
|
top: 0px;
|
||||||
|
visibility: hidden;
|
||||||
|
width: 502px;
|
||||||
|
border: solid 1px;
|
||||||
|
background-color: #999999;
|
||||||
|
font-size: 10pt;
|
||||||
|
}
|
||||||
|
</style>
|
13
src/index.html
Normal file
13
src/index.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>XTR :: xicon trace route</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<section>
|
||||||
|
<div id="statusBoxContainer"></div>
|
||||||
|
</section>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
7
src/main.js
Normal file
7
src/main.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import StatusBoxContainer from './StatusBoxContainer.vue';
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
el: '#statusBoxContainer',
|
||||||
|
render: b => b(StatusBoxContainer),
|
||||||
|
});
|
19
webpack.config.js
Normal file
19
webpack.config.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||||
|
const VueLoaderPlugin = require('vue-loader/lib/plugin');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
entry: './src/main.js',
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{ test: /\.js$/, use: 'babel-loader' },
|
||||||
|
{ test: /\.vue$/, use: 'vue-loader' },
|
||||||
|
{ test: /\.css$/, use: ['vue-style-loader', 'css-loader']},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: './src/index.html',
|
||||||
|
}),
|
||||||
|
new VueLoaderPlugin(),
|
||||||
|
]
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user