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:
parent
51712ed2a8
commit
e2e821881c
124
src/dashboard.py
124
src/dashboard.py
@ -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())
|
||||
|
@ -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>
|
@ -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>
|
@ -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
|
||||
})
|
||||
|
@ -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>
|
@ -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>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user