Initial commit
This commit is contained in:
85
web/frontend/src/components/AttemptList.vue
Normal file
85
web/frontend/src/components/AttemptList.vue
Normal file
@@ -0,0 +1,85 @@
|
||||
<template>
|
||||
<div class="attemptlist">
|
||||
<h1>Attempt list</h1>
|
||||
<p>
|
||||
<b-table
|
||||
striped
|
||||
hover
|
||||
:items="prettiedAttempts"
|
||||
:fields="fields"
|
||||
></b-table>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { BvTableFieldArray } from 'bootstrap-vue';
|
||||
import ago from 's-ago';
|
||||
import { LoginAttempt, fakeAttempt, fakeAttemptStream } from '@/apiary/apiary';
|
||||
|
||||
@Component
|
||||
export default class AttemptList extends Vue {
|
||||
@Prop() private items!: LoginAttempt[];
|
||||
|
||||
attempts: LoginAttempt[];
|
||||
|
||||
fields: BvTableFieldArray = [
|
||||
{
|
||||
key: 'date',
|
||||
sortable: true,
|
||||
// formatter: (value: string): string => ago(new Date(value)),
|
||||
sortByFormatted: false,
|
||||
},
|
||||
{
|
||||
key: 'username',
|
||||
},
|
||||
{
|
||||
key: 'password',
|
||||
},
|
||||
{
|
||||
key: 'remoteIP',
|
||||
sortable: true,
|
||||
},
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attempts = [];
|
||||
}
|
||||
|
||||
prettiedAttempts(): LoginAttempt[] {
|
||||
const prettied = this.attempts.map<LoginAttempt>((value: LoginAttempt) => ({
|
||||
date: ago(new Date(value.date)),
|
||||
remoteIP: value.remoteIP,
|
||||
username: value.username,
|
||||
password: value.password,
|
||||
sshClientVersion: value.sshClientVersion,
|
||||
connectionUUID: value.connectionUUID,
|
||||
}));
|
||||
return prettied;
|
||||
}
|
||||
|
||||
mounted(): void {
|
||||
/**
|
||||
console.log(this);
|
||||
const at: LoginAttempt[] = [];
|
||||
|
||||
for (let i = 0; i < 5; i += 1) {
|
||||
at.push(fakeAttempt());
|
||||
}
|
||||
|
||||
setInterval(() => {
|
||||
at.push(fakeAttempt());
|
||||
}, 1000);
|
||||
*/
|
||||
|
||||
const attemptStream = fakeAttemptStream();
|
||||
attemptStream.addEventListener('message', (ev: MessageEvent<string>) => {
|
||||
console.log(ev);
|
||||
const parsed: LoginAttempt = JSON.parse(ev.data);
|
||||
this.attempts.unshift(parsed);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
59
web/frontend/src/components/HelloWorld.vue
Normal file
59
web/frontend/src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="hello">
|
||||
<h1>{{ msg }}</h1>
|
||||
<p>
|
||||
For a guide and recipes on how to configure / customize this project,<br>
|
||||
check out the
|
||||
<a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
|
||||
</p>
|
||||
<h3>Installed CLI Plugins</h3>
|
||||
<ul>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript" target="_blank" rel="noopener">typescript</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
|
||||
</ul>
|
||||
<h3>Essential Links</h3>
|
||||
<ul>
|
||||
<li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
|
||||
<li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
|
||||
<li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
|
||||
<li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
|
||||
<li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
|
||||
</ul>
|
||||
<h3>Ecosystem</h3>
|
||||
<ul>
|
||||
<li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
|
||||
<li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
|
||||
<li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
|
||||
<li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
|
||||
<li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
|
||||
@Component
|
||||
export default class HelloWorld extends Vue {
|
||||
@Prop() private msg!: string;
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style scoped lang="scss">
|
||||
h3 {
|
||||
margin: 40px 0 0;
|
||||
}
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 10px;
|
||||
}
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
</style>
|
26
web/frontend/src/components/Stats.vue
Normal file
26
web/frontend/src/components/Stats.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div class="stats">
|
||||
<h1>Stats!</h1>
|
||||
<stats-username />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import StatsUsername from '@/components/StatsUsername.vue';
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
StatsUsername,
|
||||
},
|
||||
})
|
||||
export default class Stats extends Vue {
|
||||
@Prop() private msg!: string;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.stats {
|
||||
align-content: center;
|
||||
width: 70%;
|
||||
}
|
||||
</style>
|
67
web/frontend/src/components/StatsUsername.vue
Normal file
67
web/frontend/src/components/StatsUsername.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="stats-container">
|
||||
<h2>Top 10 usernames</h2>
|
||||
<div class="chart-container">
|
||||
<canvas id="chart" widht="400" height="400"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||
import { StatResult } from '@/apiary/apiary';
|
||||
import axios from 'axios';
|
||||
import Chart from 'chart.js/auto';
|
||||
import randomColor from 'randomcolor';
|
||||
|
||||
@Component
|
||||
export default class StatsUsername extends Vue {
|
||||
@Prop() private msg!: string;
|
||||
|
||||
stats: StatResult[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.stats = [];
|
||||
}
|
||||
|
||||
mounted(): void {
|
||||
axios
|
||||
.get<StatResult[]>('/api/stats?type=username&limit=10')
|
||||
.then((resp) => {
|
||||
this.stats = resp.data;
|
||||
this.renderPie();
|
||||
});
|
||||
}
|
||||
|
||||
renderPie(): void {
|
||||
const elem = document.getElementById('chart') as HTMLCanvasElement;
|
||||
const ctx = elem.getContext('2d') as CanvasRenderingContext2D;
|
||||
const sortedStats = this.stats.sort();
|
||||
const values = sortedStats.map((s) => s.count);
|
||||
const headers = sortedStats.map((s) => s.name);
|
||||
const colors = sortedStats.map(() => randomColor());
|
||||
|
||||
const chart = new Chart(ctx, {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: headers,
|
||||
options: {},
|
||||
datasets: [
|
||||
{
|
||||
data: values,
|
||||
backgroundColor: colors,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-container {
|
||||
max-width: 500px;
|
||||
max-height: 500px;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user