1
0
mirror of https://github.com/donaldzou/WGDashboard.git synced 2024-11-22 07:10:09 +01:00

Peer restore works

Still need to SQL Dump the table aswell
This commit is contained in:
Donald Zou 2024-10-16 17:44:49 +08:00
parent 51712ed2a8
commit e2e821881c
6 changed files with 367 additions and 138 deletions

View File

@ -461,27 +461,10 @@ class WireguardConfiguration:
self.PreDown: str = ""
self.PostDown: str = ""
self.SaveConfig: bool = True
self.Name = name
if name is not None:
self.Name = name
self.__parser.read_file(open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf')))
sections = self.__parser.sections()
if "Interface" not in sections:
raise self.InvalidConfigurationFileException(
"[Interface] section not found in " + os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'))
interfaceConfig = dict(self.__parser.items("Interface", True))
for i in dir(self):
if str(i) in interfaceConfig.keys():
if isinstance(getattr(self, i), bool):
setattr(self, i, _strToBool(interfaceConfig[i]))
else:
setattr(self, i, interfaceConfig[i])
if self.PrivateKey:
self.PublicKey = self.__getPublicKey()
self.Status = self.getStatus()
self.__parseConfigurationFile()
else:
self.Name = data["ConfigurationName"]
for i in dir(self):
@ -505,10 +488,33 @@ class WireguardConfiguration:
f"{self.Name}.conf"), "w+") as configFile:
self.__parser.write(configFile)
self.Peers: list[Peer] = []
self.__createDatabase()
self.__initPeersList()
def __initPeersList(self):
self.Peers: list[Peer] = []
self.getPeersList()
self.getRestrictedPeersList()
def __parseConfigurationFile(self):
self.__parser.read_file(open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf')))
sections = self.__parser.sections()
if "Interface" not in sections:
raise self.InvalidConfigurationFileException(
"[Interface] section not found in " + os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'))
interfaceConfig = dict(self.__parser.items("Interface", True))
for i in dir(self):
if str(i) in interfaceConfig.keys():
if isinstance(getattr(self, i), bool):
setattr(self, i, _strToBool(interfaceConfig[i]))
else:
setattr(self, i, interfaceConfig[i])
if self.PrivateKey:
self.PublicKey = self.__getPublicKey()
self.Status = self.getStatus()
def __createDatabase(self):
existingTables = sqlSelect("SELECT name FROM sqlite_master WHERE type='table'").fetchall()
@ -923,10 +929,9 @@ class WireguardConfiguration:
os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'),
os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f'{self.Name}_{datetime.now().strftime("%Y%m%d%H%M%S")}.conf')
)
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'w') as f:
f.write("\n".join(original))
def getBackups(self):
def getBackups(self) -> list[dict[str: str, str: str, str: str]]:
backups = []
directory = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup')
@ -946,8 +951,35 @@ class WireguardConfiguration:
return backups
def restoreBackup(self, backupFileName: str):
pass
def restoreBackup(self, backupFileName: str) -> bool:
backups = list(map(lambda x : x['filename'], self.getBackups()))
if backupFileName not in backups:
return False
self.backupConfigurationFile()
if self.Status:
self.toggleConfiguration()
target = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', backupFileName)
if not os.path.exists(target):
return False
targetContent = open(target, 'r').read()
try:
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'w') as f:
f.write(targetContent)
except Exception as e:
return False
self.__parseConfigurationFile()
self.__initPeersList()
return True
def deleteBackup(self, backupFileName: str) -> bool:
backups = list(map(lambda x : x['filename'], self.getBackups()))
if backupFileName not in backups:
return False
try:
os.remove(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', backupFileName))
except Exception as e:
return False
return True
def updateConfigurationSettings(self, newData: dict) -> tuple[bool, str]:
if self.Status:
@ -976,6 +1008,8 @@ class WireguardConfiguration:
dataChanged = True
print(original[line])
if dataChanged:
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf'), 'w') as f:
f.write("\n".join(original))
self.backupConfigurationFile()
@ -1691,6 +1725,46 @@ def API_getWireguardConfigurationBackup():
return ResponseObject(False, "Configuration does not exist")
return ResponseObject(data=WireguardConfigurations[configurationName].getBackups())
@app.get(f'{APP_PREFIX}/api/createWireguardConfigurationBackup')
def API_createWireguardConfigurationBackup():
configurationName = request.args.get('configurationName')
if configurationName is None or configurationName not in WireguardConfigurations.keys():
return ResponseObject(False, "Configuration does not exist")
return ResponseObject(status=WireguardConfigurations[configurationName].backupConfigurationFile(),
data=WireguardConfigurations[configurationName].getBackups())
@app.post(f'{APP_PREFIX}/api/deleteWireguardConfigurationBackup')
def API_deleteWireguardConfigurationBackup():
data = request.get_json()
if ("configurationName" not in data.keys() or
"backupFileName" not in data.keys() or
len(data['configurationName']) == 0 or
len(data['backupFileName']) == 0):
return ResponseObject(False,
"Please provide configurationName and backupFileName in body")
configurationName = data['configurationName']
backupFileName = data['backupFileName']
if configurationName not in WireguardConfigurations.keys():
return ResponseObject(False, "Configuration does not exist")
return ResponseObject(WireguardConfigurations[configurationName].deleteBackup(backupFileName))
@app.post(f'{APP_PREFIX}/api/restoreWireguardConfigurationBackup')
def API_restoreWireguardConfigurationBackup():
data = request.get_json()
if ("configurationName" not in data.keys() or
"backupFileName" not in data.keys() or
len(data['configurationName']) == 0 or
len(data['backupFileName']) == 0):
return ResponseObject(False,
"Please provide configurationName and backupFileName in body")
configurationName = data['configurationName']
backupFileName = data['backupFileName']
if configurationName not in WireguardConfigurations.keys():
return ResponseObject(False, "Configuration does not exist")
return ResponseObject(WireguardConfigurations[configurationName].restoreBackup(backupFileName))
@app.get(f'{APP_PREFIX}/api/getDashboardConfiguration')
def API_getDashboardConfiguration():
return ResponseObject(data=DashboardConfig.toJson())

View File

@ -0,0 +1,144 @@
<script setup>
import dayjs from "dayjs";
import {computed, ref} from "vue";
import {fetchPost} from "@/utilities/fetch.js";
import {useRoute} from "vue-router";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
const props = defineProps(["b", "delay"])
const deleteConfirmation = ref(false)
const restoreConfirmation = ref(false)
const route = useRoute()
const emit = defineEmits(["refresh", "refreshPeersList"])
const store = DashboardConfigurationStore()
const loading = ref(false);
const deleteBackup = () => {
loading.value = true;
fetchPost("/api/deleteWireguardConfigurationBackup", {
configurationName: route.params.id,
backupFileName: props.b.filename
}, (res) => {
loading.value = false;
if (res.status){
emit("refresh")
store.newMessage("Server", "Backup deleted", "success")
}else{
store.newMessage("Server", "Backup failed to delete", "danger")
}
})
}
const restoreBackup = () => {
loading.value = true;
fetchPost("/api/restoreWireguardConfigurationBackup", {
configurationName: route.params.id,
backupFileName: props.b.filename
}, (res) => {
loading.value = false;
if (res.status){
emit("refresh")
store.newMessage("Server", "Backup restored", "success")
}else{
store.newMessage("Server", "Backup failed to restore", "danger")
}
})
}
const delaySeconds = computed(() => {
return props.delay + 's'
})
</script>
<template>
<div class="card my-0 rounded-3">
<div class="card-body position-relative">
<Transition name="zoomReversed">
<div
v-if="deleteConfirmation"
class="position-absolute w-100 h-100 confirmationContainer start-0 top-0 rounded-3 d-flex p-2">
<div class="m-auto">
<h5>Are you sure to delete this backup?</h5>
<div class="d-flex gap-2 align-items-center justify-content-center">
<button class="btn btn-danger rounded-3"
:disabled="loading"
@click='deleteBackup()'>
Yes
</button>
<button
@click="deleteConfirmation = false"
:disabled="loading"
class="btn bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3">
No
</button>
</div>
</div>
</div>
</Transition>
<Transition name="zoomReversed">
<div
v-if="restoreConfirmation"
class="position-absolute w-100 h-100 confirmationContainer start-0 top-0 rounded-3 d-flex p-2">
<div class="m-auto">
<h5>Are you sure to restore this backup?</h5>
<div class="d-flex gap-2 align-items-center justify-content-center">
<button
:disabled="loading"
@click="restoreBackup()"
class="btn btn-danger rounded-3">
Yes
</button>
<button
@click="restoreConfirmation = false"
:disabled="loading"
class="btn bg-secondary-subtle text-secondary-emphasis border-secondary-subtle rounded-3">
No
</button>
</div>
</div>
</div>
</Transition>
<div class="d-flex gap-3">
<div class="d-flex flex-column">
<small class="text-muted">
Filename
</small>
<samp>{{b.filename}}</samp>
</div>
<div class="d-flex flex-column">
<small class="text-muted">
Backup Date
</small>
{{dayjs(b.backupDate, "YYYYMMDDHHmmss").format("YYYY-MM-DD HH:mm:ss")}}
</div>
<div class="d-flex gap-2 align-items-center ms-auto">
<button
@click="restoreConfirmation = true"
class="btn bg-warning-subtle text-warning-emphasis border-warning-subtle rounded-3 btn-sm">
<i class="bi bi-clock-history"></i>
</button>
<button
@click="deleteConfirmation = true"
class="btn bg-danger-subtle text-danger-emphasis border-danger-subtle rounded-3 btn-sm">
<i class="bi bi-trash-fill"></i>
</button>
</div>
</div>
<hr>
<textarea class="form-control rounded-3" :value="b.content"
disabled
style="height: 400px; font-family: var(--bs-font-monospace),sans-serif !important;"></textarea>
</div>
</div>
</template>
<style scoped>
.confirmationContainer{
background-color: rgba(0, 0, 0, 0.53);
z-index: 9999;
backdrop-filter: blur(1px);
-webkit-backdrop-filter: blur(1px);
}
.list1-enter-active{
transition-delay: v-bind(delaySeconds) !important;
}
</style>

View File

@ -0,0 +1,122 @@
<script setup>
import {onBeforeUnmount, onMounted, reactive, ref} from "vue";
import {fetchGet} from "@/utilities/fetch.js";
import {useRoute} from "vue-router";
import dayjs from "dayjs";
import LocaleText from "@/components/text/localeText.vue";
import Backup from "@/components/configurationComponents/backupRestoreComponents/backup.vue";
const route = useRoute()
const backups = ref([])
const loading = ref(true)
const emit = defineEmits(["close", "refreshPeersList"])
onMounted(() => {
loadBackup();
})
const loadBackup = () => {
loading.value = true
fetchGet("/api/getWireguardConfigurationBackup", {
configurationName: route.params.id
}, (res) => {
backups.value = res.data;
loading.value = false;
})
}
const createBackup = () => {
fetchGet("/api/createWireguardConfigurationBackup", {
configurationName: route.params.id
}, (res) => {
backups.value = res.data;
loading.value = false;
})
}
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll" ref="editConfigurationContainer">
<div class="d-flex h-100 w-100">
<div class="modal-dialog-centered dashboardModal w-100 h-100 overflow-x-scroll flex-column gap-3 mx-3">
<div class="my-5 d-flex gap-3 flex-column position-relative">
<div class="title">
<div class="d-flex mb-3">
<h4 class="mb-0">
<LocaleText t="Backup & Restore"></LocaleText>
</h4>
<button type="button" class="btn-close ms-auto" @click="$emit('close')"></button>
</div>
<button
@click="createBackup()"
class="btn bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3 w-100">
<i class="bi bi-plus-circle-fill me-2"></i> Create Backup
</button>
</div>
<div class="position-relative d-flex flex-column gap-3">
<TransitionGroup name="list1" >
<div class="text-center title"
key="spinner"
v-if="loading && backups.length === 0">
<div class="spinner-border"></div>
</div>
<div class="card my-0 rounded-3"
v-else-if="!loading && backups.length === 0"
key="noBackups"
>
<div class="card-body text-center text-muted">
<i class="bi bi-x-circle-fill me-2"></i> No backup yet, click the button above to create backup.
</div>
</div>
<Backup
@refresh="loadBackup()"
@refreshPeersList="emit('refreshPeersList')"
:b="b" v-for="(b, index) in backups"
:delay="index*0.05"
:key="b.filename"
></Backup>
</TransitionGroup>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.card, .title{
width: 100%;
}
@media screen and (min-width: 700px) {
.card, .title{
width: 700px;
}
}
.animate__fadeInUp{
animation-timing-function: cubic-bezier(0.42, 0, 0.22, 1.0)
}
.list1-move, /* apply transition to moving elements */
.list1-enter-active,
.list1-leave-active {
transition: all 0.5s cubic-bezier(0.42, 0, 0.22, 1.0);
}
.list1-enter-from,
.list1-leave-to {
opacity: 0;
transform: translateY(30px);
}
/* ensure leaving items are taken out of layout flow so that moving
animations can be calculated correctly. */
.list1-leave-active {
width: 100%;
position: absolute;
}
</style>

View File

@ -4,9 +4,6 @@ import {onMounted, reactive, ref, useTemplateRef, watch} from "vue";
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
import {fetchPost} from "@/utilities/fetch.js";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import router from "@/router/index.js";
import ConfigurationBackupRestore
from "@/components/configurationComponents/editConfigurationComponents/configurationBackupRestore.vue";
const props = defineProps({
configurationInfo: Object
})

View File

@ -1,109 +0,0 @@
<script setup>
import {onBeforeUnmount, onMounted, reactive, ref} from "vue";
import {fetchGet} from "@/utilities/fetch.js";
import {useRoute} from "vue-router";
import dayjs from "dayjs";
import LocaleText from "@/components/text/localeText.vue";
const route = useRoute()
const backups = ref([])
const loading = ref(false)
const emit = defineEmits(["close"])
onMounted(() => {
loadBackup();
})
const loadBackup = () => {
loading.value = true
fetchGet("/api/getWireguardConfigurationBackup", {
configurationName: route.params.id
}, (res) => {
backups.value = res.data;
loading.value = false;
})
}
</script>
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll" ref="editConfigurationContainer">
<div class="d-flex h-100 w-100">
<div class="modal-dialog-centered dashboardModal w-100 h-100 overflow-x-scroll flex-column gap-3 mx-3">
<div class="my-5 d-flex gap-3 flex-column position-relative">
<div class="title">
<div class="d-flex mb-3">
<h4 class="mb-0">
<LocaleText t="Backup & Restore"></LocaleText>
</h4>
<button type="button" class="btn-close ms-auto" @click="$emit('close')"></button>
</div>
<button class="btn bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-3 w-100">
<i class="bi bi-plus-circle-fill me-2"></i> Create Backup
</button>
</div>
<div class="position-relative d-flex gap-3 flex-column">
<div class="text-center" v-if="loading">
<div class="spinner-border"></div>
</div>
<div class="card animate__animated animate__fadeInUp animate__fast my-0 rounded-3"
v-else-if="!loading && backups.length === 0">
<div class="card-body text-center text-muted">
<i class="bi bi-x-circle-fill me-2"></i> No backup yet, click the button above to create backup.
</div>
</div>
<div class="card animate__animated animate__fadeInUp animate__fast my-0 rounded-3"
v-for="(b, index) in backups" :style="{'animation-delay': index*0.05 + 's'}">
<div class="card-body">
<div class="d-flex gap-3">
<div class="d-flex flex-column">
<small class="text-muted">
Filename
</small>
<samp>{{b.filename}}</samp>
</div>
<div class="d-flex flex-column">
<small class="text-muted">
Backup Date
</small>
{{dayjs(b.backupDate, "YYYYMMDDHHmmss").format("YYYY-MM-DD HH:mm:ss")}}
</div>
<div class="d-flex gap-2 align-items-center ms-auto">
<button class="btn bg-warning-subtle text-warning-emphasis border-warning-subtle rounded-3 btn-sm">
<i class="bi bi-clock-history"></i>
</button>
<button class="btn bg-danger-subtle text-danger-emphasis border-danger-subtle rounded-3 btn-sm">
<i class="bi bi-trash-fill"></i>
</button>
</div>
</div>
<hr>
<textarea class="form-control rounded-3" :value="b.content"
disabled
style="height: 400px; font-family: var(--bs-font-monospace),sans-serif !important;"></textarea>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.card, .title{
width: 100%;
}
@media screen and (min-width: 700px) {
.card, .title{
width: 700px;
}
}
.animate__fadeInUp{
animation-timing-function: cubic-bezier(0.42, 0, 0.22, 1.0)
}
</style>

View File

@ -45,7 +45,7 @@ import LocaleText from "@/components/text/localeText.vue";
import EditConfiguration from "@/components/configurationComponents/editConfiguration.vue";
import SelectPeers from "@/components/configurationComponents/selectPeers.vue";
import ConfigurationBackupRestore
from "@/components/configurationComponents/editConfigurationComponents/configurationBackupRestore.vue";
from "@/components/configurationComponents/configurationBackupRestore.vue";
Chart.register(
ArcElement,
@ -682,6 +682,7 @@ export default {
<Transition name="zoom">
<ConfigurationBackupRestore
@close="backupRestore.modalOpen = false"
@refreshPeersList="this.getPeers()"
v-if="backupRestore.modalOpen"></ConfigurationBackupRestore>
</Transition>