Initial commit

This commit is contained in:
2021-04-10 07:58:01 +02:00
commit 4bb38a797c
49 changed files with 12483 additions and 0 deletions

63
web/frontend/src/App.vue Normal file
View File

@@ -0,0 +1,63 @@
<template>
<div id="app">
<div id="apiary-navbar">
<b-navbar type="dark" variant="dark">
<b-navbar-brand href="#">
<img
alt="logo"
src="logo192.png"
width="29"
height="29"
class="d-inline-block align-top"
/>
apiary.t-juice.club
</b-navbar-brand>
<b-navbar-nav>
<b-nav-item :to="'/'">Home</b-nav-item>
<b-nav-item :to="'/hello'">Hello</b-nav-item>
<b-nav-item :to="'/stats'">Stats</b-nav-item>
<b-nav-item :to="'/attempts'">Attempts</b-nav-item>
</b-navbar-nav>
</b-navbar>
</div>
<router-view></router-view>
<!-- <img alt="Vue logo" src="./assets/logo.png" /> -->
<!-- <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" /> -->
<!-- <AttemptList items="{ testAttempts }" /> -->
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import HelloWorld from '@/components/HelloWorld.vue';
import AttemptList from '@/components/AttemptList.vue';
import Stats from '@/components/Stats.vue';
@Component({
components: {
HelloWorld,
AttemptList,
Stats,
},
})
export default class App extends Vue {}
</script>
<style lang="scss">
$font-stack: 'Secular One';
#app {
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 0;
}
.navbar-brand {
font-family: 'Secular One', sans-serif;
}
.navbar-nav a {
font-family: 'Rubik';
}
</style>

View File

@@ -0,0 +1,8 @@
import { ApiaryAPI, LoginAttempt } from "@/apiary/apiary";
export class ApiaryDummyClient implements ApiaryAPI {
streamLoginAttempts(): any {
return 'a';
}
}

View File

@@ -0,0 +1,35 @@
import FakerStatic from 'faker';
export interface LoginAttempt {
readonly date: string;
readonly remoteIP: string;
readonly username: string;
readonly password: string;
readonly sshClientVersion: string;
readonly connectionUUID: string;
}
export interface StatResult {
name: string;
count: number;
}
export interface ApiaryAPI {
streamLoginAttempts(): ReadableStream<LoginAttempt>;
}
export function fakeAttempt(): LoginAttempt {
return {
date: FakerStatic.date.recent().toLocaleDateString(),
remoteIP: FakerStatic.internet.ip(),
username: FakerStatic.internet.userName(),
password: FakerStatic.internet.password(),
sshClientVersion: FakerStatic.lorem.words(2),
connectionUUID: FakerStatic.datatype.uuid(),
};
}
export function fakeAttemptStream(): EventSource {
const es = new EventSource('/stream');
return es;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View 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>

View 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>

View 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>

View 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>

64
web/frontend/src/main.ts Normal file
View File

@@ -0,0 +1,64 @@
import Vue from 'vue';
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue';
import VueRouter, { RouteConfig } from 'vue-router';
import AttemptList from '@/components/AttemptList.vue';
import HelloWorld from '@/components/HelloWorld.vue';
import Stats from '@/components/Stats.vue';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue/dist/bootstrap-vue.css';
import '@fontsource/rubik';
import '@fontsource/secular-one';
import { LoginAttempt } from '@/apiary/apiary';
import App from './App.vue';
Vue.config.productionTip = false;
// Make BootstrapVue available throughout your project
Vue.use(BootstrapVue);
// Optionally install the BootstrapVue icon components plugin
Vue.use(IconsPlugin);
Vue.use(VueRouter);
Vue.use(VueRouter);
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: HelloWorld,
alias: '/home',
},
{
path: '/hello',
name: 'Hello',
component: HelloWorld,
props: {
default: true,
msg: 'LOL',
},
},
{
path: '/attempts',
name: 'Attempt List',
component: AttemptList,
props: {
// items: testAttempts,
},
},
{
path: '/stats',
name: 'Stats',
component: Stats,
},
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
});
new Vue({
router,
render: (h) => h(App),
}).$mount('#app');

View File

@@ -0,0 +1,27 @@
import Vue from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';
import AttemptList from "../components/Simulator.vue";
import HelloWorld from "../components/HelloWorld.vue";
Vue.use(VueRouter);
const routes: Array<RouteConfig> = [
{
path: '/',
name: 'Home',
component: HelloWorld
},
{
path: '/attempts',
name: 'Attempt List',
component: AttemptList
}
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
});
export default router;

13
web/frontend/src/shims-tsx.d.ts vendored Normal file
View File

@@ -0,0 +1,13 @@
import Vue, { VNode } from 'vue';
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any
}
}
}

5
web/frontend/src/shims-vue.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module '*.vue' {
import Vue from 'vue';
export default Vue;
}