Initial setup for new frontend.

This commit is contained in:
baldo 2022-02-22 15:39:39 +01:00
parent 7671bfd4d3
commit 59f7897d8e
33 changed files with 2594 additions and 12 deletions

17
frontend/src/App.vue Normal file
View file

@ -0,0 +1,17 @@
<script setup lang="ts">
import {RouterLink, RouterView} from "vue-router";
import PageHeader from "@/components/PageHeader.vue";
import PageFooter from "@/components/PageFooter.vue";
</script>
<template>
<PageHeader />
<main>
<RouterView />
</main>
<PageFooter />
</template>
<style lang="scss" scoped></style>

View file

@ -0,0 +1,19 @@
<script setup lang="ts">
import { useVersionStore } from "@/stores/version";
const version = useVersionStore();
function refresh(): void {
version.refresh();
}
refresh();
</script>
<template>
<footer>
{{ version.getVersion }}
</footer>
</template>
<style lang="scss" scoped></style>

View file

@ -0,0 +1,15 @@
<script setup lang="ts">
</script>
<template>
<header>
<div class="wrapper">
<nav>
<RouterLink to="/">Home</RouterLink> |
<RouterLink to="/admin">Admin</RouterLink>
</nav>
</div>
</header>
</template>
<style lang="scss" scoped></style>

12
frontend/src/main.ts Normal file
View file

@ -0,0 +1,12 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(createPinia());
app.use(router);
app.mount("#app");

View file

@ -0,0 +1,21 @@
import { createRouter, createWebHistory } from "vue-router";
import AdminDashboardView from "@/views/AdminDashboardView.vue";
import HomeView from "@/views/HomeView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/admin",
name: "admin",
component: AdminDashboardView,
},
],
});
export default router;

View file

@ -0,0 +1,29 @@
import { defineStore } from "pinia";
import { isStatistics, type Statistics } from "@/types";
import { internalApi } from "@/utils/Api";
interface StatisticsStoreState {
statistics: Statistics | null;
}
export const useStatisticsStore = defineStore({
id: "statistics",
state(): StatisticsStoreState {
return {
statistics: null,
};
},
getters: {
getStatistics(state: StatisticsStoreState): Statistics | null {
return state.statistics;
},
},
actions: {
async refresh(): Promise<void> {
this.statistics = await internalApi.get<Statistics>(
"statistics",
isStatistics
);
},
},
});

View file

@ -0,0 +1,38 @@
import { defineStore } from "pinia";
import { isObject, isVersion, type Version } from "@/types";
import { api } from "@/utils/Api";
interface VersionResponse {
version: Version;
}
function isVersionResponse(arg: unknown): arg is VersionResponse {
return isObject(arg) && isVersion((arg as VersionResponse).version);
}
interface VersionStoreState {
version: Version | null;
}
export const useVersionStore = defineStore({
id: "version",
state(): VersionStoreState {
return {
version: null,
};
},
getters: {
getVersion(state: VersionStoreState): Version | null {
return state.version;
},
},
actions: {
async refresh(): Promise<void> {
const response = await api.get<VersionResponse>(
"version",
isVersionResponse
);
this.version = response.version;
},
},
});

View file

@ -0,0 +1 @@
export * from "./shared";

View file

@ -0,0 +1 @@
../../../server/types/shared.ts

37
frontend/src/utils/Api.ts Normal file
View file

@ -0,0 +1,37 @@
class Api {
private baseURL: string = import.meta.env.BASE_URL;
private apiPrefix = "api/";
constructor(apiPrefix?: string) {
if (apiPrefix) {
this.apiPrefix = apiPrefix;
}
}
private toURL(path: string): string {
return this.baseURL + this.apiPrefix + path;
}
async get<T>(path: string, isT: (arg: unknown) => arg is T): Promise<T> {
const url = this.toURL(path);
const result = await fetch(url);
const json = await result.json();
if (!isT(json)) {
console.log(json);
throw new Error(`API get result has wrong type. ${url} => ${json}`);
}
return json;
}
}
export const api = new Api();
class InternalApi extends Api {
constructor() {
super("internal/api/");
}
}
export const internalApi = new InternalApi();

View file

@ -0,0 +1,30 @@
<script setup lang="ts">
import { useStatisticsStore } from "@/stores/statistics";
const statistics = useStatisticsStore();
function refresh(): void {
statistics.refresh();
}
refresh();
</script>
<template>
<main>
<div v-if="statistics.getStatistics">
<h1>Nodes</h1>
<div>
Registered: {{ statistics.getStatistics.nodes.registered }}<br />
With VPN-key: {{ statistics.getStatistics.nodes.withVPN }}<br />
With coordinates: {{ statistics.getStatistics.nodes.withCoords }}<br />
Monitoring active: {{ statistics.getStatistics.nodes.monitoring.active }}<br />
Monitoring pending: {{ statistics.getStatistics.nodes.monitoring.pending }}
</div>
<button @click="refresh()">Refresh</button>
</div>
</main>
</template>
<style lang="scss" scoped></style>

View file

@ -0,0 +1,8 @@
<script setup lang="ts">
</script>
<template>
Home
</template>
<style lang="scss" scoped></style>