mirror of
https://github.com/vx3r/wg-gen-web.git
synced 2025-09-11 12:24:27 +00:00
oauth2 oidc, vuex store
This commit is contained in:
602
ui/package-lock.json
generated
602
ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,9 +11,11 @@
|
||||
"is-cidr": "^3.1.0",
|
||||
"moment": "^2.24.0",
|
||||
"vue": "^2.6.10",
|
||||
"vue-axios": "^2.1.5",
|
||||
"vue-moment": "^4.1.0",
|
||||
"vue-router": "^3.1.6",
|
||||
"vuetify": "^2.2.22"
|
||||
"vuetify": "^2.2.22",
|
||||
"vuex": "^3.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-router": "^4.3.1",
|
||||
|
129
ui/src/App.vue
129
ui/src/App.vue
@ -1,69 +1,36 @@
|
||||
<template>
|
||||
<v-app id="inspire">
|
||||
<Notification v-bind:notification="notification"/>
|
||||
<div v-if="this.isAuthenticated">
|
||||
<Header/>
|
||||
|
||||
<v-app-bar app>
|
||||
<img class="mr-3" :src="require('./assets/logo.png')" height="50" alt="Wg Gen Web"/>
|
||||
<v-toolbar-title to="/">Wg Gen Web</v-toolbar-title>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<v-toolbar-items>
|
||||
<v-btn to="/clients">
|
||||
Clients
|
||||
<v-icon right dark>mdi-account-network-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn to="/server">
|
||||
Server
|
||||
<v-icon right dark>mdi-vpn</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar-items>
|
||||
|
||||
</v-app-bar>
|
||||
|
||||
<v-content>
|
||||
<v-container>
|
||||
<router-view />
|
||||
</v-container>
|
||||
<Notification v-bind:notification="notification"/>
|
||||
</v-content>
|
||||
|
||||
<v-footer app>
|
||||
<v-row justify="start" no-gutters>
|
||||
<v-col cols="12" lg="6" md="12" sm="12">
|
||||
<div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'left'">
|
||||
<small>Copyright © {{ new Date().getFullYear() }}, Wg Gen Web.</small>
|
||||
<small>This work is licensed under a <a class="pr-1 pl-1" href="http://www.wtfpl.net/" target="_blank">WTFPL License.</a></small>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row justify="end" no-gutters>
|
||||
<v-col cols="12" lg="6" md="12" sm="12">
|
||||
<div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'right'">
|
||||
<small>Created with</small>
|
||||
<v-icon class="pr-1 pl-1">mdi-heart</v-icon><span>by</span><a class="pr-2 pl-1" href="mailto:vx3r@127-0-0-1.fr">vx3r</a>
|
||||
<a :href="'https://github.com/vx3r/wg-gen-web/commit/' + version"><kbd>Version: {{ version.substring(0,7) }}</kbd></a>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-footer>
|
||||
<v-content>
|
||||
<v-container>
|
||||
<router-view />
|
||||
</v-container>
|
||||
</v-content>
|
||||
|
||||
<Footer/>
|
||||
</div>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {ApiService} from "./services/ApiService";
|
||||
import Notification from './components/Notification'
|
||||
import Header from "./components/Header";
|
||||
import Footer from "./components/Footer";
|
||||
import {mapActions, mapGetters} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'App',
|
||||
|
||||
components: {
|
||||
Footer,
|
||||
Header,
|
||||
Notification
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
api: null,
|
||||
version:'N/A',
|
||||
notification: {
|
||||
show: false,
|
||||
color: '',
|
||||
@ -71,23 +38,69 @@
|
||||
},
|
||||
}),
|
||||
|
||||
mounted() {
|
||||
this.api = new ApiService();
|
||||
this.getVersion()
|
||||
computed:{
|
||||
...mapGetters({
|
||||
isAuthenticated: 'auth/isAuthenticated',
|
||||
authStatus: 'auth/authStatus',
|
||||
authRedirectUrl: 'auth/authRedirectUrl',
|
||||
authError: 'auth/error',
|
||||
clientError: 'client/error',
|
||||
serverError: 'server/error',
|
||||
})
|
||||
},
|
||||
|
||||
created () {
|
||||
this.$vuetify.theme.dark = true
|
||||
},
|
||||
|
||||
methods: {
|
||||
getVersion() {
|
||||
this.api.get('/server/version').then((res) => {
|
||||
this.version = res.version;
|
||||
}).catch((e) => {
|
||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||
});
|
||||
mounted() {
|
||||
if (this.$route.query.code && this.$route.query.state) {
|
||||
this.oauth2_exchange({
|
||||
code: this.$route.query.code,
|
||||
state: this.$route.query.state
|
||||
})
|
||||
} else {
|
||||
this.oauth2_url()
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
authError(newValue, oldValue) {
|
||||
console.log(newValue)
|
||||
this.notify('error', newValue);
|
||||
},
|
||||
|
||||
clientError(newValue, oldValue) {
|
||||
console.log(newValue)
|
||||
this.notify('error', newValue);
|
||||
},
|
||||
|
||||
serverError(newValue, oldValue) {
|
||||
console.log(newValue)
|
||||
this.notify('error', newValue);
|
||||
},
|
||||
|
||||
isAuthenticated(newValue, oldValue) {
|
||||
console.log(`Updating isAuthenticated from ${oldValue} to ${newValue}`);
|
||||
if (newValue === true) {
|
||||
this.$router.push('/clients')
|
||||
}
|
||||
},
|
||||
|
||||
authStatus(newValue, oldValue) {
|
||||
console.log(`Updating authStatus from ${oldValue} to ${newValue}`);
|
||||
if (newValue === 'redirect') {
|
||||
window.location.replace(this.authRedirectUrl)
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('auth', {
|
||||
oauth2_exchange: 'oauth2_exchange',
|
||||
oauth2_url: 'oauth2_url',
|
||||
}),
|
||||
|
||||
notify(color, msg) {
|
||||
this.notification.show = true;
|
||||
this.notification.color = color;
|
||||
|
@ -9,7 +9,7 @@
|
||||
</v-list-item-content>
|
||||
<v-btn
|
||||
color="success"
|
||||
@click="startAddClient"
|
||||
@click="startCreate"
|
||||
>
|
||||
Add new client
|
||||
<v-icon right dark>mdi-account-multiple-plus-outline</v-icon>
|
||||
@ -31,15 +31,15 @@
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="headline">{{ client.name }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ client.email }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>Created: {{ client.created | formatDate }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>Updated: {{ client.updated | formatDate }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>Created: {{ client.created | formatDate }} by {{ client.createdBy }}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>Updated: {{ client.updated | formatDate }} by {{ client.updatedBy }}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
|
||||
<v-list-item-avatar
|
||||
tile
|
||||
size="150"
|
||||
>
|
||||
<v-img :src="`${apiBaseUrl}/client/${client.id}/config?qrcode=true`"/>
|
||||
<v-img :src="'data:image/png;base64, ' + getClientQrcode(client.id)"/>
|
||||
</v-list-item-avatar>
|
||||
</v-list-item>
|
||||
|
||||
@ -55,61 +55,61 @@
|
||||
</v-chip>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
:href="`${apiBaseUrl}/client/${client.id}/config?qrcode=false`"
|
||||
v-on="on"
|
||||
>
|
||||
<span class="d-none d-lg-flex">Download</span>
|
||||
<v-icon right dark>mdi-cloud-download-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Download</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
v-on:click="forceFileDownload(client)"
|
||||
v-on="on"
|
||||
>
|
||||
<span class="d-none d-lg-flex">Download</span>
|
||||
<v-icon right dark>mdi-cloud-download-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Download</span>
|
||||
</v-tooltip>
|
||||
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
@click.stop="startUpdateClient(client)"
|
||||
v-on="on"
|
||||
>
|
||||
<span class="d-none d-lg-flex">Edit</span>
|
||||
<v-icon right dark>mdi-square-edit-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Edit</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
@click.stop="startUpdate(client)"
|
||||
v-on="on"
|
||||
>
|
||||
<span class="d-none d-lg-flex">Edit</span>
|
||||
<v-icon right dark>mdi-square-edit-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Edit</span>
|
||||
</v-tooltip>
|
||||
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
@click="deleteClient(client)"
|
||||
v-on="on"
|
||||
>
|
||||
<span class="d-none d-lg-flex">Delete</span>
|
||||
<v-icon right dark>mdi-trash-can-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Delete</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
@click="remove(client)"
|
||||
v-on="on"
|
||||
>
|
||||
<span class="d-none d-lg-flex">Delete</span>
|
||||
<v-icon right dark>mdi-trash-can-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Delete</span>
|
||||
</v-tooltip>
|
||||
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
@click="sendEmailClient(client.id)"
|
||||
v-on="on"
|
||||
>
|
||||
<span class="d-none d-lg-flex">Send Email</span>
|
||||
<v-icon right dark>mdi-email-send-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Send Email</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
@click="email(client)"
|
||||
v-on="on"
|
||||
>
|
||||
<span class="d-none d-lg-flex">Send Email</span>
|
||||
<v-icon right dark>mdi-email-send-outline</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Send Email</span>
|
||||
</v-tooltip>
|
||||
<v-spacer/>
|
||||
<v-tooltip right>
|
||||
<template v-slot:activator="{ on }">
|
||||
@ -118,7 +118,7 @@
|
||||
v-on="on"
|
||||
color="success"
|
||||
v-model="client.enable"
|
||||
v-on:change="updateClient(client)"
|
||||
v-on:change="update(client)"
|
||||
/>
|
||||
</template>
|
||||
<span> {{client.enable ? 'Disable' : 'Enable'}} this client</span>
|
||||
@ -133,7 +133,7 @@
|
||||
</v-row>
|
||||
<v-dialog
|
||||
v-if="client"
|
||||
v-model="dialogAddClient"
|
||||
v-model="dialogCreate"
|
||||
max-width="550"
|
||||
>
|
||||
<v-card>
|
||||
@ -210,14 +210,14 @@
|
||||
<v-btn
|
||||
:disabled="!valid"
|
||||
color="success"
|
||||
@click="addClient(client)"
|
||||
@click="create(client)"
|
||||
>
|
||||
Submit
|
||||
<v-icon right dark>mdi-check-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="dialogAddClient = false"
|
||||
@click="dialogCreate = false"
|
||||
>
|
||||
Cancel
|
||||
<v-icon right dark>mdi-close-circle-outline</v-icon>
|
||||
@ -227,7 +227,7 @@
|
||||
</v-dialog>
|
||||
<v-dialog
|
||||
v-if="client"
|
||||
v-model="dialogEditClient"
|
||||
v-model="dialogUpdate"
|
||||
max-width="550"
|
||||
>
|
||||
<v-card>
|
||||
@ -308,14 +308,14 @@
|
||||
<v-btn
|
||||
:disabled="!valid"
|
||||
color="success"
|
||||
@click="updateClient(client)"
|
||||
@click="update(client)"
|
||||
>
|
||||
Submit
|
||||
<v-icon right dark>mdi-check-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="dialogEditClient = false"
|
||||
@click="dialogUpdate = false"
|
||||
>
|
||||
Cancel
|
||||
<v-icon right dark>mdi-close-circle-outline</v-icon>
|
||||
@ -323,61 +323,50 @@
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
<Notification v-bind:notification="notification"/>
|
||||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
import {ApiService, API_BASE_URL} from '../services/ApiService'
|
||||
import Notification from '../components/Notification'
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'Clients',
|
||||
|
||||
components: {
|
||||
Notification
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
api: null,
|
||||
apiBaseUrl: API_BASE_URL,
|
||||
clients: [],
|
||||
notification: {
|
||||
show: false,
|
||||
color: '',
|
||||
text: '',
|
||||
},
|
||||
dialogAddClient: false,
|
||||
dialogEditClient: false,
|
||||
dialogCreate: false,
|
||||
dialogUpdate: false,
|
||||
client: null,
|
||||
server: null,
|
||||
valid: false,
|
||||
}),
|
||||
|
||||
computed:{
|
||||
...mapGetters({
|
||||
getClientQrcode: 'client/getClientQrcode',
|
||||
getClientConfig: 'client/getClientConfig',
|
||||
server: 'server/server',
|
||||
clients: 'client/clients',
|
||||
clientQrcodes: 'client/clientQrcodes',
|
||||
}),
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.api = new ApiService();
|
||||
this.getClients();
|
||||
this.getServer()
|
||||
this.readAllClients()
|
||||
this.readServer()
|
||||
},
|
||||
|
||||
methods: {
|
||||
getClients() {
|
||||
this.api.get('/client').then((res) => {
|
||||
this.clients = res
|
||||
}).catch((e) => {
|
||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||
});
|
||||
},
|
||||
...mapActions('client', {
|
||||
errorClient: 'error',
|
||||
readAllClients: 'readAll',
|
||||
creatClient: 'create',
|
||||
updateClient: 'update',
|
||||
deleteClient: 'delete',
|
||||
emailClient: 'email',
|
||||
}),
|
||||
...mapActions('server', {
|
||||
readServer: 'read',
|
||||
}),
|
||||
|
||||
getServer() {
|
||||
this.api.get('/server').then((res) => {
|
||||
this.server = res;
|
||||
}).catch((e) => {
|
||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||
});
|
||||
},
|
||||
|
||||
startAddClient() {
|
||||
this.dialogAddClient = true;
|
||||
startCreate() {
|
||||
this.client = {
|
||||
name: "",
|
||||
email: "",
|
||||
@ -385,91 +374,87 @@
|
||||
allowedIPs: this.server.allowedips,
|
||||
address: this.server.address,
|
||||
}
|
||||
this.dialogCreate = true;
|
||||
},
|
||||
addClient(client) {
|
||||
|
||||
create(client) {
|
||||
if (client.allowedIPs.length < 1) {
|
||||
this.notify('error', 'Please provide at least one valid CIDR address for client allowed IPs');
|
||||
this.errorClient('Please provide at least one valid CIDR address for client allowed IPs')
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < client.allowedIPs.length; i++){
|
||||
if (this.$isCidr(client.allowedIPs[i]) === 0) {
|
||||
this.notify('error', 'Invalid CIDR detected, please correct before submitting');
|
||||
this.errorClient('Invalid CIDR detected, please correct before submitting')
|
||||
return
|
||||
}
|
||||
}
|
||||
this.dialogAddClient = false;
|
||||
|
||||
this.api.post('/client', client).then((res) => {
|
||||
this.notify('success', `Client ${res.name} successfully added`);
|
||||
this.getClients()
|
||||
}).catch((e) => {
|
||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||
});
|
||||
this.dialogCreate = false;
|
||||
this.creatClient(client)
|
||||
},
|
||||
|
||||
deleteClient(client) {
|
||||
remove(client) {
|
||||
if(confirm(`Do you really want to delete ${client.name} ?`)){
|
||||
this.api.delete(`/client/${client.id}`).then((res) => {
|
||||
this.notify('success', "Client successfully deleted");
|
||||
this.getClients()
|
||||
}).catch((e) => {
|
||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||
});
|
||||
this.deleteClient(client)
|
||||
}
|
||||
},
|
||||
|
||||
sendEmailClient(id) {
|
||||
this.api.get(`/client/${id}/email`).then((res) => {
|
||||
this.notify('success', "Email successfully sent");
|
||||
this.getClients()
|
||||
}).catch((e) => {
|
||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||
});
|
||||
email(client) {
|
||||
if (!client.email){
|
||||
this.errorClient('Client email is not defined')
|
||||
return
|
||||
}
|
||||
|
||||
if(confirm(`Do you really want to send email to ${client.email} with all configurations ?`)){
|
||||
this.emailClient(client)
|
||||
}
|
||||
},
|
||||
|
||||
startUpdateClient(client) {
|
||||
startUpdate(client) {
|
||||
this.client = client;
|
||||
this.dialogEditClient = true;
|
||||
this.dialogUpdate = true;
|
||||
},
|
||||
updateClient(client) {
|
||||
|
||||
update(client) {
|
||||
// check allowed IPs
|
||||
if (client.allowedIPs.length < 1) {
|
||||
this.notify('error', 'Please provide at least one valid CIDR address for client allowed IPs');
|
||||
this.errorClient('Please provide at least one valid CIDR address for client allowed IPs');
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < client.allowedIPs.length; i++){
|
||||
if (this.$isCidr(client.allowedIPs[i]) === 0) {
|
||||
this.notify('error', 'Invalid CIDR detected, please correct before submitting');
|
||||
this.errorClient('Invalid CIDR detected, please correct before submitting');
|
||||
return
|
||||
}
|
||||
}
|
||||
// check address
|
||||
if (client.address.length < 1) {
|
||||
this.notify('error', 'Please provide at least one valid CIDR address for client');
|
||||
this.errorClient('Please provide at least one valid CIDR address for client');
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < client.address.length; i++){
|
||||
if (this.$isCidr(client.address[i]) === 0) {
|
||||
this.notify('error', 'Invalid CIDR detected, please correct before submitting');
|
||||
this.errorClient('Invalid CIDR detected, please correct before submitting');
|
||||
return
|
||||
}
|
||||
}
|
||||
// all good, submit
|
||||
this.dialogEditClient = false;
|
||||
|
||||
this.api.patch(`/client/${client.id}`, client).then((res) => {
|
||||
this.notify('success', `Client ${res.name} successfully updated`);
|
||||
this.getClients()
|
||||
}).catch((e) => {
|
||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||
});
|
||||
this.dialogUpdate = false;
|
||||
this.updateClient(client)
|
||||
},
|
||||
|
||||
notify(color, msg) {
|
||||
this.notification.show = true;
|
||||
this.notification.color = color;
|
||||
this.notification.text = msg;
|
||||
}
|
||||
forceFileDownload(client){
|
||||
let config = this.getClientConfig(client.id)
|
||||
if (!config) {
|
||||
this.errorClient('Failed to download client config');
|
||||
return
|
||||
}
|
||||
const url = window.URL.createObjectURL(new Blob([config]))
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', 'wg0.conf') //or any other extension
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
51
ui/src/components/Footer.vue
Normal file
51
ui/src/components/Footer.vue
Normal file
@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-footer app>
|
||||
<v-row justify="start" no-gutters>
|
||||
<v-col cols="12" lg="6" md="12" sm="12">
|
||||
<div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'left'">
|
||||
<small>Copyright © {{ new Date().getFullYear() }}, Wg Gen Web. </small>
|
||||
<small>This work is licensed under <a class="pr-1 pl-1" href="http://www.wtfpl.net/" target="_blank">WTFPL License.</a></small>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row justify="end" no-gutters>
|
||||
<v-col cols="12" lg="6" md="12" sm="12">
|
||||
<div :align="$vuetify.breakpoint.smAndDown ? 'center' : 'right'">
|
||||
<small>Created with</small>
|
||||
<v-icon class="pr-1 pl-1">mdi-heart</v-icon><span>by</span><a class="pr-2 pl-1" href="mailto:vx3r@127-0-0-1.fr">vx3r</a>
|
||||
<a :href="'https://github.com/vx3r/wg-gen-web/commit/' + version"><kbd>Version: {{ version.substring(0,7) }}</kbd></a>
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-footer>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapGetters} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'Footer',
|
||||
|
||||
data: () => ({
|
||||
|
||||
}),
|
||||
|
||||
computed:{
|
||||
...mapGetters({
|
||||
version: 'server/version',
|
||||
}),
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.versionServer()
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('server', {
|
||||
versionServer: 'version',
|
||||
}),
|
||||
}
|
||||
}
|
||||
</script>
|
77
ui/src/components/Header.vue
Normal file
77
ui/src/components/Header.vue
Normal file
@ -0,0 +1,77 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-app-bar app>
|
||||
<img class="mr-3" :src="require('../assets/logo.png')" height="50" alt="Wg Gen Web"/>
|
||||
<v-toolbar-title to="/">Wg Gen Web</v-toolbar-title>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<v-toolbar-items>
|
||||
<v-btn to="/clients">
|
||||
Clients
|
||||
<v-icon right dark>mdi-account-network-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn to="/server">
|
||||
Server
|
||||
<v-icon right dark>mdi-vpn</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar-items>
|
||||
|
||||
<v-menu
|
||||
left
|
||||
bottom
|
||||
>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-btn icon v-on="on">
|
||||
<v-icon>mdi-account-circle</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-card
|
||||
class="mx-auto"
|
||||
max-width="344"
|
||||
outlined
|
||||
>
|
||||
<v-list-item three-line>
|
||||
<v-list-item-content>
|
||||
<div class="overline mb-4">connected as</div>
|
||||
<v-list-item-title class="headline mb-1">{{user.name}}</v-list-item-title>
|
||||
<v-list-item-subtitle>Email: {{user.email}}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>Issuer: {{user.issuer}}</v-list-item-subtitle>
|
||||
<v-list-item-subtitle>Issued at: {{ user.issuedAt | formatDate }}</v-list-item-subtitle>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
<v-card-actions>
|
||||
<v-btn small
|
||||
v-on:click="logout()"
|
||||
>
|
||||
logout
|
||||
<v-icon small right dark>mdi-logout</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
|
||||
</v-app-bar>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapActions, mapGetters} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'Header',
|
||||
|
||||
computed:{
|
||||
...mapGetters({
|
||||
user: 'auth/user',
|
||||
}),
|
||||
},
|
||||
|
||||
methods: {
|
||||
...mapActions('auth', {
|
||||
logout: 'logout',
|
||||
}),
|
||||
}
|
||||
}
|
||||
</script>
|
@ -158,7 +158,7 @@
|
||||
<v-btn
|
||||
class="ma-2"
|
||||
color="success"
|
||||
:href="`${apiBaseUrl}/server/config`"
|
||||
v-on:click="forceFileDownload()"
|
||||
>
|
||||
Download server configuration
|
||||
<v-icon right dark>mdi-cloud-download-outline</v-icon>
|
||||
@ -167,52 +167,44 @@
|
||||
<v-btn
|
||||
class="ma-2"
|
||||
color="warning"
|
||||
@click="updateServer"
|
||||
@click="update"
|
||||
>
|
||||
Update server configuration
|
||||
<v-icon right dark>mdi-update</v-icon>
|
||||
</v-btn>
|
||||
<v-divider dark/>
|
||||
</v-row>
|
||||
<Notification v-bind:notification="notification"/>
|
||||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
import {API_BASE_URL, ApiService} from "../services/ApiService";
|
||||
import Notification from '../components/Notification'
|
||||
import {mapActions, mapGetters} from "vuex";
|
||||
|
||||
export default {
|
||||
name: 'Server',
|
||||
|
||||
components: {
|
||||
Notification
|
||||
},
|
||||
|
||||
data: () => ({
|
||||
api: null,
|
||||
server: null,
|
||||
apiBaseUrl: API_BASE_URL,
|
||||
notification: {
|
||||
show: false,
|
||||
color: '',
|
||||
text: '',
|
||||
},
|
||||
|
||||
}),
|
||||
|
||||
computed:{
|
||||
...mapGetters({
|
||||
server: 'server/server',
|
||||
config: 'server/config',
|
||||
}),
|
||||
},
|
||||
|
||||
mounted () {
|
||||
this.api = new ApiService();
|
||||
this.getServer()
|
||||
this.readServer()
|
||||
},
|
||||
|
||||
methods: {
|
||||
getServer() {
|
||||
this.api.get('/server').then((res) => {
|
||||
this.server = res;
|
||||
}).catch((e) => {
|
||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||
});
|
||||
},
|
||||
updateServer () {
|
||||
...mapActions('server', {
|
||||
errorServer: 'error',
|
||||
readServer: 'read',
|
||||
updateServer: 'update',
|
||||
}),
|
||||
|
||||
update() {
|
||||
// convert int values
|
||||
this.server.listenPort = parseInt(this.server.listenPort, 10);
|
||||
this.server.persistentKeepalive = parseInt(this.server.persistentKeepalive, 10);
|
||||
@ -220,12 +212,12 @@
|
||||
|
||||
// check server addresses
|
||||
if (this.server.address.length < 1) {
|
||||
this.notify('error', 'Please provide at least one valid CIDR address for server interface');
|
||||
this.errorServer('Please provide at least one valid CIDR address for server interface');
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this.server.address.length; i++){
|
||||
if (this.$isCidr(this.server.address[i]) === 0) {
|
||||
this.notify('error', `Invalid CIDR detected, please correct ${this.server.address[i]} before submitting`);
|
||||
this.errorServer(`Invalid CIDR detected, please correct ${this.server.address[i]} before submitting`);
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -233,35 +225,34 @@
|
||||
// check DNS correct
|
||||
for (let i = 0; i < this.server.dns.length; i++){
|
||||
if (this.$isCidr(this.server.dns[i] + '/32') === 0) {
|
||||
this.notify('error', `Invalid IP detected, please correct ${this.server.dns[i]} before submitting`);
|
||||
this.errorServer(`Invalid IP detected, please correct ${this.server.dns[i]} before submitting`);
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// check client AllowedIPs
|
||||
if (this.server.allowedips.length < 1) {
|
||||
this.notify('error', 'Please provide at least one valid CIDR address for client allowed IPs');
|
||||
this.errorServer('Please provide at least one valid CIDR address for client allowed IPs');
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < this.server.allowedips.length; i++){
|
||||
if (this.$isCidr(this.server.allowedips[i]) === 0) {
|
||||
this.notify('error', 'Invalid CIDR detected, please correct before submitting');
|
||||
this.errorServer('Invalid CIDR detected, please correct before submitting');
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.api.patch('/server', this.server).then((res) => {
|
||||
this.notify('success', "Server successfully updated");
|
||||
this.server = res;
|
||||
}).catch((e) => {
|
||||
this.notify('error', e.response.status + ' ' + e.response.statusText);
|
||||
});
|
||||
this.updateServer(this.server)
|
||||
},
|
||||
|
||||
forceFileDownload(){
|
||||
const url = window.URL.createObjectURL(new Blob([this.config]))
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', 'wg0.conf') //or any other extension
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
},
|
||||
notify(color, msg) {
|
||||
this.notification.show = true;
|
||||
this.notification.color = color;
|
||||
this.notification.text = msg;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -1,14 +1,18 @@
|
||||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './store'
|
||||
import vuetify from './plugins/vuetify';
|
||||
import './plugins/moment';
|
||||
import './plugins/cidr'
|
||||
import './plugins/axios'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
// Don't warn about using the dev version of Vue in development.
|
||||
Vue.config.productionTip = process.env.NODE_ENV === 'production'
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
vuetify,
|
||||
render: function (h) { return h(App) }
|
||||
}).$mount('#app')
|
||||
|
26
ui/src/plugins/axios.js
Normal file
26
ui/src/plugins/axios.js
Normal file
@ -0,0 +1,26 @@
|
||||
import Vue from 'vue'
|
||||
import axios from "axios";
|
||||
import VueAxios from "vue-axios";
|
||||
import TokenService from "../services/token.service";
|
||||
|
||||
Vue.use(VueAxios, axios);
|
||||
|
||||
let baseUrl = "/api/v1.0";
|
||||
if (process.env.NODE_ENV === "development"){
|
||||
baseUrl = process.env.VUE_APP_API_BASE_URL;
|
||||
}
|
||||
|
||||
Vue.axios.defaults.baseURL = baseUrl;
|
||||
|
||||
Vue.axios.interceptors.response.use(function (response) {
|
||||
return response;
|
||||
}, function (error) {
|
||||
if (401 === error.response.status) {
|
||||
TokenService.destroyToken();
|
||||
TokenService.destroyClientId();
|
||||
window.location = '/';
|
||||
} else {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
});
|
||||
|
@ -1,22 +1,19 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import store from "../store";
|
||||
|
||||
Vue.use(VueRouter);
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'index',
|
||||
component: function () {
|
||||
return import(/* webpackChunkName: "Index" */ '../views/Index.vue')
|
||||
},
|
||||
},
|
||||
{
|
||||
path: '/clients',
|
||||
name: 'clients',
|
||||
component: function () {
|
||||
return import(/* webpackChunkName: "Clients" */ '../views/Clients.vue')
|
||||
},
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/server',
|
||||
@ -24,6 +21,9 @@ const routes = [
|
||||
component: function () {
|
||||
return import(/* webpackChunkName: "Server" */ '../views/Server.vue')
|
||||
},
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@ -33,4 +33,16 @@ const router = new VueRouter({
|
||||
routes
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if(to.matched.some(record => record.meta.requiresAuth)) {
|
||||
if (store.getters["auth/isAuthenticated"]) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
next('/')
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
|
||||
export default router
|
||||
|
@ -1,40 +0,0 @@
|
||||
import axios from 'axios'
|
||||
|
||||
let baseUrl = "/api/v1.0";
|
||||
if (process.env.NODE_ENV === "development"){
|
||||
baseUrl = process.env.VUE_APP_API_BASE_URL
|
||||
}
|
||||
|
||||
export const API_BASE_URL = baseUrl;
|
||||
|
||||
export class ApiService {
|
||||
get(resource) {
|
||||
return axios
|
||||
.get(`${API_BASE_URL}${resource}`)
|
||||
.then(response => response.data)
|
||||
};
|
||||
|
||||
post(resource, data) {
|
||||
return axios
|
||||
.post(`${API_BASE_URL}${resource}`, data)
|
||||
.then(response => response.data)
|
||||
};
|
||||
|
||||
put(resource, data) {
|
||||
return axios
|
||||
.put(`${API_BASE_URL}${resource}`, data)
|
||||
.then(response => response.data)
|
||||
};
|
||||
|
||||
patch(resource, data) {
|
||||
return axios
|
||||
.patch(`${API_BASE_URL}${resource}`, data)
|
||||
.then(response => response.data)
|
||||
};
|
||||
|
||||
delete(resource) {
|
||||
return axios
|
||||
.delete(`${API_BASE_URL}${resource}`)
|
||||
.then(response => response.data)
|
||||
};
|
||||
}
|
59
ui/src/services/api.service.js
Normal file
59
ui/src/services/api.service.js
Normal file
@ -0,0 +1,59 @@
|
||||
import Vue from "vue";
|
||||
import TokenService from "./token.service";
|
||||
|
||||
const ApiService = {
|
||||
|
||||
setHeader() {
|
||||
Vue.axios.defaults.headers.common.Authorization = `${TokenService.getToken()}`;
|
||||
},
|
||||
|
||||
get(resource) {
|
||||
return Vue.axios.get(resource)
|
||||
.then(response => response.data)
|
||||
.catch(error => {
|
||||
throw new Error(`ApiService: ${error}`)
|
||||
});
|
||||
},
|
||||
|
||||
post(resource, params) {
|
||||
return Vue.axios.post(resource, params)
|
||||
.then(response => response.data)
|
||||
.catch(error => {
|
||||
throw new Error(`ApiService: ${error}`)
|
||||
});
|
||||
},
|
||||
|
||||
put(resource, params) {
|
||||
return Vue.axios.put(resource, params)
|
||||
.then(response => response.data)
|
||||
.catch(error => {
|
||||
throw new Error(`ApiService: ${error}`)
|
||||
});
|
||||
},
|
||||
|
||||
patch(resource, params) {
|
||||
return Vue.axios.patch(resource, params)
|
||||
.then(response => response.data)
|
||||
.catch(error => {
|
||||
throw new Error(`ApiService: ${error}`)
|
||||
});
|
||||
},
|
||||
|
||||
delete(resource) {
|
||||
return Vue.axios.delete(resource)
|
||||
.then(response => response.data)
|
||||
.catch(error => {
|
||||
throw new Error(`ApiService: ${error}`)
|
||||
});
|
||||
},
|
||||
|
||||
getWithConfig(resource, config) {
|
||||
return Vue.axios.get(resource, config)
|
||||
.then(response => response.data)
|
||||
.catch(error => {
|
||||
throw new Error(`ApiService: ${error}`)
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default ApiService;
|
35
ui/src/services/token.service.js
Normal file
35
ui/src/services/token.service.js
Normal file
@ -0,0 +1,35 @@
|
||||
const TOKEN_KEY = "token";
|
||||
const CLIENT_ID_KEY = "client_id";
|
||||
|
||||
export const getToken = () => {
|
||||
return window.localStorage.getItem(TOKEN_KEY);
|
||||
};
|
||||
|
||||
export const saveToken = token => {
|
||||
window.localStorage.setItem(TOKEN_KEY, token);
|
||||
};
|
||||
|
||||
export const destroyToken = () => {
|
||||
window.localStorage.removeItem(TOKEN_KEY);
|
||||
};
|
||||
|
||||
export const getClientId = () => {
|
||||
return window.localStorage.getItem(CLIENT_ID_KEY);
|
||||
};
|
||||
|
||||
export const saveClientId = token => {
|
||||
window.localStorage.setItem(CLIENT_ID_KEY, token);
|
||||
};
|
||||
|
||||
export const destroyClientId = () => {
|
||||
window.localStorage.removeItem(CLIENT_ID_KEY);
|
||||
};
|
||||
|
||||
export default {
|
||||
getToken,
|
||||
saveToken,
|
||||
destroyToken,
|
||||
getClientId,
|
||||
saveClientId,
|
||||
destroyClientId
|
||||
};
|
19
ui/src/store/index.js
Normal file
19
ui/src/store/index.js
Normal file
@ -0,0 +1,19 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import auth from "./modules/auth";
|
||||
import client from "./modules/client";
|
||||
import server from "./modules/server";
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {},
|
||||
getters : {},
|
||||
mutations: {},
|
||||
actions:{},
|
||||
modules: {
|
||||
auth,
|
||||
client,
|
||||
server
|
||||
}
|
||||
})
|
126
ui/src/store/modules/auth.js
Normal file
126
ui/src/store/modules/auth.js
Normal file
@ -0,0 +1,126 @@
|
||||
import ApiService from "../../services/api.service";
|
||||
import TokenService from "../../services/token.service";
|
||||
|
||||
const state = {
|
||||
error: null,
|
||||
user: null,
|
||||
authStatus: '',
|
||||
authRedirectUrl: '',
|
||||
};
|
||||
|
||||
const getters = {
|
||||
error(state) {
|
||||
return state.error;
|
||||
},
|
||||
user(state) {
|
||||
return state.user;
|
||||
},
|
||||
isAuthenticated(state) {
|
||||
return state.user !== null;
|
||||
},
|
||||
authRedirectUrl(state) {
|
||||
return state.authRedirectUrl
|
||||
},
|
||||
authStatus(state) {
|
||||
return state.authStatus
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
user({ commit }){
|
||||
ApiService.get("/auth/user")
|
||||
.then( resp => {
|
||||
commit('user', resp)
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err);
|
||||
commit('logout')
|
||||
});
|
||||
},
|
||||
|
||||
oauth2_url({ commit, dispatch }){
|
||||
if (TokenService.getToken()) {
|
||||
ApiService.setHeader();
|
||||
dispatch('user');
|
||||
return
|
||||
}
|
||||
ApiService.get("/auth/oauth2_url")
|
||||
.then(resp => {
|
||||
if (resp.codeUrl === '_magic_string_fake_auth_no_redirect_'){
|
||||
console.log("server report oauth2 is disabled, fake exchange")
|
||||
commit('authStatus', 'disabled')
|
||||
TokenService.saveClientId(resp.clientId)
|
||||
dispatch('oauth2_exchange', {code: "", state: resp.state})
|
||||
} else {
|
||||
commit('authStatus', 'redirect')
|
||||
commit('authRedirectUrl', resp)
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
commit('authStatus', 'error')
|
||||
commit('error', err);
|
||||
commit('logout')
|
||||
})
|
||||
},
|
||||
|
||||
oauth2_exchange({ commit, dispatch }, data){
|
||||
data.clientId = TokenService.getClientId()
|
||||
ApiService.post("/auth/oauth2_exchange", data)
|
||||
.then(resp => {
|
||||
commit('authStatus', 'success')
|
||||
commit('token', resp)
|
||||
dispatch('user');
|
||||
})
|
||||
.catch(err => {
|
||||
commit('authStatus', 'error')
|
||||
commit('error', err);
|
||||
commit('logout')
|
||||
})
|
||||
},
|
||||
|
||||
logout({ commit }){
|
||||
ApiService.get("/auth/logout")
|
||||
.then(resp => {
|
||||
commit('logout')
|
||||
})
|
||||
.catch(err => {
|
||||
commit('authStatus', '')
|
||||
commit('error', err);
|
||||
commit('logout')
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
error(state, error) {
|
||||
state.error = error;
|
||||
},
|
||||
authStatus(state, authStatus) {
|
||||
state.authStatus = authStatus;
|
||||
},
|
||||
authRedirectUrl(state, resp) {
|
||||
state.authRedirectUrl = resp.codeUrl;
|
||||
TokenService.saveClientId(resp.clientId);
|
||||
},
|
||||
token(state, token) {
|
||||
TokenService.saveToken(token);
|
||||
ApiService.setHeader();
|
||||
TokenService.destroyClientId();
|
||||
},
|
||||
user(state, user) {
|
||||
state.user = user;
|
||||
},
|
||||
logout(state) {
|
||||
state.user = null;
|
||||
TokenService.destroyToken();
|
||||
TokenService.destroyClientId();
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
177
ui/src/store/modules/client.js
Normal file
177
ui/src/store/modules/client.js
Normal file
@ -0,0 +1,177 @@
|
||||
import ApiService from "../../services/api.service";
|
||||
|
||||
const state = {
|
||||
error: null,
|
||||
clients: [],
|
||||
clientQrcodes: [],
|
||||
clientConfigs: []
|
||||
}
|
||||
|
||||
const getters = {
|
||||
error(state) {
|
||||
return state.error;
|
||||
},
|
||||
clients(state) {
|
||||
return state.clients;
|
||||
},
|
||||
getClientQrcode: (state) => (id) => {
|
||||
let item = state.clientQrcodes.find(item => item.id === id)
|
||||
// initial load fails, must wait promise and stuff...
|
||||
return item ? item.qrcode : "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
|
||||
},
|
||||
getClientConfig: (state) => (id) => {
|
||||
let item = state.clientConfigs.find(item => item.id === id)
|
||||
return item ? item.config : null
|
||||
}
|
||||
}
|
||||
|
||||
const actions = {
|
||||
error({ commit }, error){
|
||||
commit('error', error)
|
||||
},
|
||||
|
||||
readAll({ commit, dispatch }){
|
||||
ApiService.get("/client")
|
||||
.then(resp => {
|
||||
commit('clients', resp)
|
||||
dispatch('readQrcodes')
|
||||
dispatch('readConfigs')
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
create({ commit, dispatch }, client){
|
||||
ApiService.post("/client", client)
|
||||
.then(resp => {
|
||||
dispatch('readQrcode', resp)
|
||||
dispatch('readConfig', resp)
|
||||
commit('create', resp)
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
update({ commit, dispatch }, client){
|
||||
ApiService.patch(`/client/${client.id}`, client)
|
||||
.then(resp => {
|
||||
dispatch('readQrcode', resp)
|
||||
dispatch('readConfig', resp)
|
||||
commit('update', resp)
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
delete({ commit }, client){
|
||||
ApiService.delete(`/client/${client.id}`)
|
||||
.then(() => {
|
||||
commit('delete', client)
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
email({ commit }, client){
|
||||
ApiService.get(`/client/${client.id}/email`)
|
||||
.then(() => {
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
readQrcode({ state, commit }, client){
|
||||
ApiService.getWithConfig(`/client/${client.id}/config?qrcode=true`, {responseType: 'arraybuffer'})
|
||||
.then(resp => {
|
||||
let image = Buffer.from(resp, 'binary').toString('base64')
|
||||
commit('clientQrcodes', { client, image })
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
readConfig({ state, commit }, client){
|
||||
ApiService.getWithConfig(`/client/${client.id}/config?qrcode=false`, {responseType: 'arraybuffer'})
|
||||
.then(resp => {
|
||||
commit('clientConfigs', { client: client, config: resp })
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
readQrcodes({ state, dispatch }){
|
||||
state.clients.forEach(client => {
|
||||
dispatch('readQrcode', client)
|
||||
})
|
||||
},
|
||||
|
||||
readConfigs({ state, dispatch }){
|
||||
state.clients.forEach(client => {
|
||||
dispatch('readConfig', client)
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
error(state, error) {
|
||||
state.error = error;
|
||||
},
|
||||
clients(state, clients){
|
||||
state.clients = clients
|
||||
},
|
||||
create(state, client){
|
||||
state.clients.push(client)
|
||||
},
|
||||
update(state, client){
|
||||
let index = state.clients.findIndex(x => x.id === client.id);
|
||||
if (index !== -1) {
|
||||
state.clients.splice(index, 1);
|
||||
state.clients.push(client);
|
||||
} else {
|
||||
state.error = "update client failed, not in list"
|
||||
}
|
||||
},
|
||||
delete(state, client){
|
||||
let index = state.clients.findIndex(x => x.id === client.id);
|
||||
if (index !== -1) {
|
||||
state.clients.splice(index, 1);
|
||||
} else {
|
||||
state.error = "delete client failed, not in list"
|
||||
}
|
||||
},
|
||||
clientQrcodes(state, { client, image }){
|
||||
let index = state.clientQrcodes.findIndex(x => x.id === client.id);
|
||||
if (index !== -1) {
|
||||
state.clientQrcodes.splice(index, 1);
|
||||
}
|
||||
state.clientQrcodes.push({
|
||||
id: client.id,
|
||||
qrcode: image
|
||||
})
|
||||
},
|
||||
clientConfigs(state, { client, config }){
|
||||
let index = state.clientConfigs.findIndex(x => x.id === client.id);
|
||||
if (index !== -1) {
|
||||
state.clientConfigs.splice(index, 1);
|
||||
}
|
||||
state.clientConfigs.push({
|
||||
id: client.id,
|
||||
config: config
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
100
ui/src/store/modules/server.js
Normal file
100
ui/src/store/modules/server.js
Normal file
@ -0,0 +1,100 @@
|
||||
import ApiService from "../../services/api.service";
|
||||
|
||||
const state = {
|
||||
error: null,
|
||||
server: null,
|
||||
config: '',
|
||||
version: '_ci_build_not_run_properly_',
|
||||
}
|
||||
|
||||
const getters = {
|
||||
error(state) {
|
||||
return state.error;
|
||||
},
|
||||
|
||||
server(state) {
|
||||
return state.server;
|
||||
},
|
||||
|
||||
version(state) {
|
||||
return state.version;
|
||||
},
|
||||
|
||||
config(state) {
|
||||
return state.config;
|
||||
},
|
||||
}
|
||||
|
||||
const actions = {
|
||||
error({ commit }, error){
|
||||
commit('error', error)
|
||||
},
|
||||
|
||||
read({ commit, dispatch }){
|
||||
ApiService.get("/server")
|
||||
.then(resp => {
|
||||
commit('server', resp)
|
||||
dispatch('config')
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
update({ commit }, server){
|
||||
ApiService.patch(`/server`, server)
|
||||
.then(resp => {
|
||||
commit('server', resp)
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
config({ commit }){
|
||||
ApiService.getWithConfig("/server/config", {responseType: 'arraybuffer'})
|
||||
.then(resp => {
|
||||
commit('config', resp)
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
version({ commit }){
|
||||
ApiService.get("/server/version")
|
||||
.then(resp => {
|
||||
commit('version', resp.version)
|
||||
})
|
||||
.catch(err => {
|
||||
commit('error', err)
|
||||
})
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
const mutations = {
|
||||
error(state, error) {
|
||||
state.error = error;
|
||||
},
|
||||
|
||||
server(state, server){
|
||||
state.server = server
|
||||
},
|
||||
|
||||
config(state, config){
|
||||
state.config = config
|
||||
},
|
||||
|
||||
version(state, version){
|
||||
state.version = version
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
actions,
|
||||
mutations
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
created () {
|
||||
this.$router.replace({ name: 'clients' })
|
||||
}
|
||||
}
|
||||
</script>
|
Reference in New Issue
Block a user