mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2024-11-22 15:20:09 +01:00
Continue to refactor the UI and APIs :)
This commit is contained in:
parent
ba2bcaba07
commit
e6e070d89e
@ -16,6 +16,7 @@ from dataclasses import dataclass
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from json import JSONEncoder
|
from json import JSONEncoder
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
# PIP installed library
|
# PIP installed library
|
||||||
@ -114,6 +115,8 @@ class WireguardConfiguration:
|
|||||||
if self.PrivateKey:
|
if self.PrivateKey:
|
||||||
self.PublicKey = self.__getPublicKey()
|
self.PublicKey = self.__getPublicKey()
|
||||||
|
|
||||||
|
self.Status = self.__getStatus()
|
||||||
|
|
||||||
# Create tables in database
|
# Create tables in database
|
||||||
inspector = inspect(engine)
|
inspector = inspect(engine)
|
||||||
existingTable = inspector.get_table_names()
|
existingTable = inspector.get_table_names()
|
||||||
@ -127,7 +130,11 @@ class WireguardConfiguration:
|
|||||||
def __getPublicKey(self) -> str:
|
def __getPublicKey(self) -> str:
|
||||||
return subprocess.check_output(['wg', 'pubkey'], input=self.PrivateKey.encode()).decode().strip('\n')
|
return subprocess.check_output(['wg', 'pubkey'], input=self.PrivateKey.encode()).decode().strip('\n')
|
||||||
|
|
||||||
|
def __getStatus(self) -> bool:
|
||||||
|
return self.Name in dict(ifcfg.interfaces().items()).keys()
|
||||||
|
|
||||||
def toJSON(self):
|
def toJSON(self):
|
||||||
|
self.Status = self.__getStatus()
|
||||||
return self.__dict__
|
return self.__dict__
|
||||||
|
|
||||||
|
|
||||||
@ -190,6 +197,14 @@ class DashboardConfig:
|
|||||||
|
|
||||||
return True, self.__config[section][key]
|
return True, self.__config[section][key]
|
||||||
|
|
||||||
|
def toJSON(self) -> dict[str, dict[Any, Any]]:
|
||||||
|
the_dict = {}
|
||||||
|
for section in self.__config.sections():
|
||||||
|
the_dict[section] = {}
|
||||||
|
for key, val in self.__config.items(section):
|
||||||
|
the_dict[section][key] = val
|
||||||
|
return the_dict
|
||||||
|
|
||||||
|
|
||||||
def ResponseObject(status=True, message=None, data=None) -> Flask.response_class:
|
def ResponseObject(status=True, message=None, data=None) -> Flask.response_class:
|
||||||
response = Flask.make_response(app, {
|
response = Flask.make_response(app, {
|
||||||
@ -340,12 +355,13 @@ def API_AuthenticateLogin():
|
|||||||
|
|
||||||
@app.route('/api/getWireguardConfigurations', methods=["GET"])
|
@app.route('/api/getWireguardConfigurations', methods=["GET"])
|
||||||
def API_getWireguardConfigurations():
|
def API_getWireguardConfigurations():
|
||||||
pass
|
WireguardConfigurations = _getConfigurationList()
|
||||||
|
return ResponseObject(data=[wc.toJSON() for wc in WireguardConfigurations])
|
||||||
|
|
||||||
|
|
||||||
@app.route('/api/getDashboardConfiguration', methods=["GET"])
|
@app.route('/api/getDashboardConfiguration', methods=["GET"])
|
||||||
def API_getDashboardConfiguration():
|
def API_getDashboardConfiguration():
|
||||||
pass
|
return ResponseObject(data=DashboardConfig.toJSON())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -3,4 +3,5 @@ ifcfg
|
|||||||
icmplib
|
icmplib
|
||||||
flask-qrcode
|
flask-qrcode
|
||||||
gunicorn
|
gunicorn
|
||||||
certbot
|
certbot
|
||||||
|
sqlalchemy
|
51
src/static/app/src/components/configurationList.vue
Normal file
51
src/static/app/src/components/configurationList.vue
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<script>
|
||||||
|
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "configurationList",
|
||||||
|
async setup(){
|
||||||
|
const store = wgdashboardStore();
|
||||||
|
await store.getWireguardConfigurations();
|
||||||
|
return {store}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mt-4">
|
||||||
|
<h3 class="mb-3 text-body">Wireguard Configurations</h3>
|
||||||
|
<p class="text-muted" v-if="this.store.WireguardConfigurations.length === 0">You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".</p>
|
||||||
|
|
||||||
|
<div class="card conf_card rounded-3 shadow" v-else v-for="c in this.store.WireguardConfigurations" :key="c.Name">
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col card-col">
|
||||||
|
<small class="text-muted"><strong>CONFIGURATION</strong></small>
|
||||||
|
<h6 class="card-title" style="margin:0 !important;"><samp>{{c.Name}}</samp></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col card-col">
|
||||||
|
<small class="text-muted"><strong>STATUS</strong></small>
|
||||||
|
<h6><span>{{c.Status ? "Running":"Stopped"}}</span>
|
||||||
|
<span class="dot" :class="{active: c.Status}"></span></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm card-col">
|
||||||
|
<small class="text-muted"><strong>PUBLIC KEY</strong></small>
|
||||||
|
<h6 style="margin:0 !important;"><samp>{{c.PublicKey}}</samp></h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm index-switch">
|
||||||
|
<div class="switch-test">
|
||||||
|
<input type="checkbox" class="toggle--switch" checked :id="c.Name + '-switch'">
|
||||||
|
<label :for="c.Name + '-switch'" class="toggleLabel"></label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -1,34 +1,43 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "navbar"
|
name: "navbar",
|
||||||
|
setup(){
|
||||||
|
const store = wgdashboardStore()
|
||||||
|
return {store}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar border border-right p-0">
|
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-body-tertiary sidebar border border-right p-0">
|
||||||
<div class="sidebar-sticky pt-3">
|
<div class="sidebar-sticky pt-3">
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink class="nav-link" to="/">Home</RouterLink></li>
|
<RouterLink class="nav-link" to="/" exact-active-class="active">Home</RouterLink></li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<RouterLink class="nav-link" to="/settings">Settings</RouterLink></li>
|
<RouterLink class="nav-link" to="/settings" exact-active-class="active">Settings</RouterLink></li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr>
|
||||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||||
<span>Configurations</span>
|
<span>Configurations</span>
|
||||||
|
|
||||||
</h6>
|
</h6>
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
|
<li class="nav-item">
|
||||||
|
<RouterLink :to="'/configuration/'+c.Name" class="nav-link nav-conf-link" v-for="c in this.store.WireguardConfigurations">
|
||||||
|
<samp>{{c.Name}}</samp>
|
||||||
|
</RouterLink>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr>
|
||||||
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
|
||||||
<span>Tools</span>
|
<span>Tools</span>
|
||||||
</h6>
|
</h6>
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
<ul class="nav flex-column">
|
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#ping_modal" href="#">Ping</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#ping_modal" href="#">Ping</a></li>
|
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#traceroute_modal" href="#">Traceroute</a></li>
|
||||||
<li class="nav-item"><a class="nav-link" data-toggle="modal" data-target="#traceroute_modal" href="#">Traceroute</a></li>
|
|
||||||
</ul>
|
|
||||||
</ul>
|
</ul>
|
||||||
<hr>
|
<hr>
|
||||||
<ul class="nav flex-column">
|
<ul class="nav flex-column">
|
||||||
|
@ -2,8 +2,10 @@ import { createRouter, createWebHashHistory } from 'vue-router'
|
|||||||
import {cookie} from "../utilities/cookie.js";
|
import {cookie} from "../utilities/cookie.js";
|
||||||
import Index from "@/views/index.vue"
|
import Index from "@/views/index.vue"
|
||||||
import Signin from "@/views/signin.vue";
|
import Signin from "@/views/signin.vue";
|
||||||
import ConfigurationList from "@/views/configurationList.vue";
|
import ConfigurationList from "@/components/configurationList.vue";
|
||||||
import {fetchGet} from "@/utilities/fetch.js";
|
import {fetchGet} from "@/utilities/fetch.js";
|
||||||
|
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
|
||||||
|
import Settings from "@/views/settings.vue";
|
||||||
|
|
||||||
const checkAuth = async () => {
|
const checkAuth = async () => {
|
||||||
let result = false
|
let result = false
|
||||||
@ -17,6 +19,7 @@ const router = createRouter({
|
|||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
|
name: "Index",
|
||||||
path: '/',
|
path: '/',
|
||||||
component: Index,
|
component: Index,
|
||||||
meta: {
|
meta: {
|
||||||
@ -24,8 +27,14 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
name: "Configuration List",
|
||||||
path: '',
|
path: '',
|
||||||
component: ConfigurationList
|
component: ConfigurationList
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Settings",
|
||||||
|
path: '/settings',
|
||||||
|
component: Settings
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@ -36,8 +45,18 @@ const router = createRouter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
|
const store = wgdashboardStore();
|
||||||
|
|
||||||
if (to.meta.requiresAuth){
|
if (to.meta.requiresAuth){
|
||||||
if (cookie.getCookie("authToken") && await checkAuth()){
|
if (cookie.getCookie("authToken") && await checkAuth()){
|
||||||
|
|
||||||
|
console.log(to.name)
|
||||||
|
if (!store.DashboardConfiguration){
|
||||||
|
await store.getDashboardConfiguration()
|
||||||
|
}
|
||||||
|
if (!store.WireguardConfigurations && to.name !== "Configuration List"){
|
||||||
|
await store.getWireguardConfigurations()
|
||||||
|
}
|
||||||
next()
|
next()
|
||||||
}else{
|
}else{
|
||||||
next("/signin")
|
next("/signin")
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
import {defineStore} from "pinia";
|
||||||
|
import {fetchGet} from "@/utilities/fetch.js";
|
||||||
|
|
||||||
|
export const wgdashboardStore = defineStore('WGDashboardStore', {
|
||||||
|
state: () => ({
|
||||||
|
WireguardConfigurations: undefined,
|
||||||
|
DashboardConfiguration: undefined
|
||||||
|
}),
|
||||||
|
actions: {
|
||||||
|
async getWireguardConfigurations(){
|
||||||
|
await fetchGet("/api/getWireguardConfigurations", {}, (res) => {
|
||||||
|
console.log(res.status)
|
||||||
|
if (res.status) this.WireguardConfigurations = res.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async getDashboardConfiguration(){
|
||||||
|
await fetchGet("/api/getDashboardConfiguration", {}, (res) => {
|
||||||
|
console.log(res.status)
|
||||||
|
if (res.status) this.DashboardConfiguration = res.data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
@ -1,13 +0,0 @@
|
|||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: "configurationList"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
@ -8,11 +8,13 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container-fluid flex-grow-1">
|
<div class="container-fluid flex-grow-1 main" data-bs-theme="dark">
|
||||||
<div class="row h-100">
|
<div class="row h-100">
|
||||||
<Navbar></Navbar>
|
<Navbar></Navbar>
|
||||||
<main class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mb-4">
|
<main class="col-md-9 ml-sm-auto col-lg-10 px-md-4 mb-4">
|
||||||
<RouterView></RouterView>
|
<Suspense>
|
||||||
|
<RouterView></RouterView>
|
||||||
|
</Suspense>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
19
src/static/app/src/views/settings.vue
Normal file
19
src/static/app/src/views/settings.vue
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<script>
|
||||||
|
import {wgdashboardStore} from "@/stores/wgdashboardStore.js";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "settings",
|
||||||
|
setup(){
|
||||||
|
const store = wgdashboardStore();
|
||||||
|
return {store}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -15,7 +15,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server:{
|
server:{
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': 'http://127.0.0.1:10086/'
|
'/api': 'http://178.128.231.4:10086'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -51,6 +51,10 @@
|
|||||||
/* position: sticky;*/
|
/* position: sticky;*/
|
||||||
/* }*/
|
/* }*/
|
||||||
/*}*/
|
/*}*/
|
||||||
|
[data-bs-theme="dark"].main{
|
||||||
|
background-color: #1b1e21;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.sidebar .nav-link, .bottomNavContainer .nav-link{
|
.sidebar .nav-link, .bottomNavContainer .nav-link{
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -58,6 +62,18 @@
|
|||||||
transition: 0.2s cubic-bezier(0.82, -0.07, 0, 1.01);
|
transition: 0.2s cubic-bezier(0.82, -0.07, 0, 1.01);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .sidebar .nav-link{
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .sidebar .nav-link.active{
|
||||||
|
color: #74b7ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-bs-theme="dark"] .nav-link:hover{
|
||||||
|
background-color: #323844;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-link:hover {
|
.nav-link:hover {
|
||||||
padding-left: 30px;
|
padding-left: 30px;
|
||||||
background-color: #dfdfdf;
|
background-color: #dfdfdf;
|
||||||
@ -131,9 +147,10 @@
|
|||||||
border-radius: 50px;
|
border-radius: 50px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: auto !important;
|
margin-left: auto !important;
|
||||||
|
background-color: #6c757d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot-running {
|
.dot.active{
|
||||||
background-color: #28a745!important;
|
background-color: #28a745!important;
|
||||||
box-shadow: 0 0 0 0.2rem #28a74545;
|
box-shadow: 0 0 0 0.2rem #28a74545;
|
||||||
}
|
}
|
||||||
@ -142,10 +159,6 @@
|
|||||||
margin-left: 0.3rem;
|
margin-left: 0.3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dot-stopped {
|
|
||||||
background-color: #6c757d!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-running {
|
.card-running {
|
||||||
border-color: #28a745;
|
border-color: #28a745;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user