mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-04-19 08:55:12 +00:00
UI for restore configuration is done
This commit is contained in:
parent
82a472f368
commit
a606626053
@ -462,11 +462,13 @@ class WireguardConfiguration:
|
|||||||
self.PostDown: str = ""
|
self.PostDown: str = ""
|
||||||
self.SaveConfig: bool = True
|
self.SaveConfig: bool = True
|
||||||
self.Name = name
|
self.Name = name
|
||||||
|
self.__configPath = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf')
|
||||||
|
|
||||||
if name is not None:
|
if name is not None:
|
||||||
self.__parseConfigurationFile()
|
self.__parseConfigurationFile()
|
||||||
else:
|
else:
|
||||||
self.Name = data["ConfigurationName"]
|
self.Name = data["ConfigurationName"]
|
||||||
|
self.__configPath = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf')
|
||||||
for i in dir(self):
|
for i in dir(self):
|
||||||
if str(i) in data.keys():
|
if str(i) in data.keys():
|
||||||
if isinstance(getattr(self, i), bool):
|
if isinstance(getattr(self, i), bool):
|
||||||
@ -484,8 +486,7 @@ class WireguardConfiguration:
|
|||||||
"SaveConfig": "true"
|
"SaveConfig": "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
with open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1],
|
with open(self.__configPath, "w+") as configFile:
|
||||||
f"{self.Name}.conf"), "w+") as configFile:
|
|
||||||
self.__parser.write(configFile)
|
self.__parser.write(configFile)
|
||||||
|
|
||||||
|
|
||||||
@ -498,7 +499,7 @@ class WireguardConfiguration:
|
|||||||
self.getRestrictedPeersList()
|
self.getRestrictedPeersList()
|
||||||
|
|
||||||
def __parseConfigurationFile(self):
|
def __parseConfigurationFile(self):
|
||||||
self.__parser.read_file(open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], f'{self.Name}.conf')))
|
self.__parser.read_file(open(self.__configPath))
|
||||||
sections = self.__parser.sections()
|
sections = self.__parser.sections()
|
||||||
if "Interface" not in sections:
|
if "Interface" not in sections:
|
||||||
raise self.InvalidConfigurationFileException(
|
raise self.InvalidConfigurationFileException(
|
||||||
@ -510,16 +511,14 @@ class WireguardConfiguration:
|
|||||||
setattr(self, i, _strToBool(interfaceConfig[i]))
|
setattr(self, i, _strToBool(interfaceConfig[i]))
|
||||||
else:
|
else:
|
||||||
setattr(self, i, interfaceConfig[i])
|
setattr(self, i, interfaceConfig[i])
|
||||||
|
|
||||||
if self.PrivateKey:
|
if self.PrivateKey:
|
||||||
self.PublicKey = self.__getPublicKey()
|
self.PublicKey = self.__getPublicKey()
|
||||||
|
|
||||||
self.Status = self.getStatus()
|
self.Status = self.getStatus()
|
||||||
|
|
||||||
def __dropDatabase(self):
|
def __dropDatabase(self):
|
||||||
existingTables = sqlSelect(f"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '{self.Name}%'").fetchall()
|
existingTables = sqlSelect(f"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '{self.Name}%'").fetchall()
|
||||||
for t in existingTables:
|
for t in existingTables:
|
||||||
sqlUpdate(f"DROP TABLE {t['name']}")
|
sqlUpdate("DROP TABLE '%s'" % t['name'])
|
||||||
|
|
||||||
existingTables = sqlSelect(f"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '{self.Name}%'").fetchall()
|
existingTables = sqlSelect(f"SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '{self.Name}%'").fetchall()
|
||||||
|
|
||||||
@ -950,8 +949,7 @@ class WireguardConfiguration:
|
|||||||
for l in self.__dumpDatabase():
|
for l in self.__dumpDatabase():
|
||||||
f.write(l + "\n")
|
f.write(l + "\n")
|
||||||
|
|
||||||
|
def getBackups(self, databaseContent: bool = False) -> list[dict[str: str, str: str, str: str]]:
|
||||||
def getBackups(self) -> list[dict[str: str, str: str, str: str]]:
|
|
||||||
backups = []
|
backups = []
|
||||||
|
|
||||||
directory = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup')
|
directory = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup')
|
||||||
@ -963,11 +961,16 @@ class WireguardConfiguration:
|
|||||||
if _regexMatch(f"^({self.Name})_(.*)\.(conf)$", f):
|
if _regexMatch(f"^({self.Name})_(.*)\.(conf)$", f):
|
||||||
s = re.search(f"^({self.Name})_(.*)\.(conf)$", f)
|
s = re.search(f"^({self.Name})_(.*)\.(conf)$", f)
|
||||||
date = s.group(2)
|
date = s.group(2)
|
||||||
backups.append({
|
d = {
|
||||||
"filename": f,
|
"filename": f,
|
||||||
"backupDate": date,
|
"backupDate": date,
|
||||||
"content": open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f), 'r').read()
|
"content": open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f), 'r').read()
|
||||||
})
|
}
|
||||||
|
if f.replace(".conf", ".sql") in list(os.listdir(directory)):
|
||||||
|
d['database'] = True
|
||||||
|
if databaseContent:
|
||||||
|
d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read()
|
||||||
|
backups.append(d)
|
||||||
|
|
||||||
return backups
|
return backups
|
||||||
|
|
||||||
@ -1047,7 +1050,15 @@ class WireguardConfiguration:
|
|||||||
if not status:
|
if not status:
|
||||||
return False, msg
|
return False, msg
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
def deleteConfiguration(self):
|
||||||
|
if self.getStatus():
|
||||||
|
self.toggleConfiguration()
|
||||||
|
os.remove(self.__configPath)
|
||||||
|
self.__dropDatabase()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class Peer:
|
class Peer:
|
||||||
def __init__(self, tableData, configuration: WireguardConfiguration):
|
def __init__(self, tableData, configuration: WireguardConfiguration):
|
||||||
self.configuration = configuration
|
self.configuration = configuration
|
||||||
@ -1545,6 +1556,8 @@ def sqlUpdate(statement: str, paramters: tuple = ()) -> sqlite3.Cursor:
|
|||||||
with sqldb:
|
with sqldb:
|
||||||
cursor = sqldb.cursor()
|
cursor = sqldb.cursor()
|
||||||
try:
|
try:
|
||||||
|
statement = statement.rstrip(';')
|
||||||
|
s = f'BEGIN TRANSACTION;{statement};END TRANSACTION;'
|
||||||
cursor.execute(statement, paramters)
|
cursor.execute(statement, paramters)
|
||||||
sqldb.commit()
|
sqldb.commit()
|
||||||
except sqlite3.OperationalError as error:
|
except sqlite3.OperationalError as error:
|
||||||
@ -1748,6 +1761,18 @@ def API_updateWireguardConfiguration():
|
|||||||
|
|
||||||
return ResponseObject(status, message=msg, data=WireguardConfigurations[name])
|
return ResponseObject(status, message=msg, data=WireguardConfigurations[name])
|
||||||
|
|
||||||
|
@app.post(f'{APP_PREFIX}/api/deleteWireguardConfiguration')
|
||||||
|
def API_deleteWireguardConfiguration():
|
||||||
|
data = request.get_json()
|
||||||
|
if "Name" not in data.keys() or data.get("Name") is None or data.get("Name") not in WireguardConfigurations.keys():
|
||||||
|
return ResponseObject(False, "Please provide the configuration name you want to delete")
|
||||||
|
|
||||||
|
status = WireguardConfigurations[data.get("Name")].deleteConfiguration()
|
||||||
|
|
||||||
|
if status:
|
||||||
|
WireguardConfigurations.pop(data.get("Name"))
|
||||||
|
return ResponseObject(status)
|
||||||
|
|
||||||
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationBackup')
|
@app.get(f'{APP_PREFIX}/api/getWireguardConfigurationBackup')
|
||||||
def API_getWireguardConfigurationBackup():
|
def API_getWireguardConfigurationBackup():
|
||||||
configurationName = request.args.get('configurationName')
|
configurationName = request.args.get('configurationName')
|
||||||
@ -1755,6 +1780,43 @@ def API_getWireguardConfigurationBackup():
|
|||||||
return ResponseObject(False, "Configuration does not exist")
|
return ResponseObject(False, "Configuration does not exist")
|
||||||
return ResponseObject(data=WireguardConfigurations[configurationName].getBackups())
|
return ResponseObject(data=WireguardConfigurations[configurationName].getBackups())
|
||||||
|
|
||||||
|
@app.get(f'{APP_PREFIX}/api/getAllWireguardConfigurationBackup')
|
||||||
|
def API_getAllWireguardConfigurationBackup():
|
||||||
|
data = {
|
||||||
|
"ExistingConfigurations": {},
|
||||||
|
"NonExistingConfigurations": {}
|
||||||
|
}
|
||||||
|
existingConfiguration = WireguardConfigurations.keys()
|
||||||
|
for i in existingConfiguration:
|
||||||
|
b = WireguardConfigurations[i].getBackups(True)
|
||||||
|
if len(b) > 0:
|
||||||
|
data['ExistingConfigurations'][i] = WireguardConfigurations[i].getBackups(True)
|
||||||
|
|
||||||
|
directory = os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup')
|
||||||
|
files = [(file, os.path.getctime(os.path.join(directory, file)))
|
||||||
|
for file in os.listdir(directory) if os.path.isfile(os.path.join(directory, file))]
|
||||||
|
files.sort(key=lambda x: x[1], reverse=True)
|
||||||
|
|
||||||
|
for f, ct in files:
|
||||||
|
if _regexMatch(f"^(.*)_(.*)\.(conf)$", f):
|
||||||
|
s = re.search(f"^(.*)_(.*)\.(conf)$", f)
|
||||||
|
name = s.group(1)
|
||||||
|
if name not in existingConfiguration:
|
||||||
|
if name not in data['NonExistingConfigurations'].keys():
|
||||||
|
data['NonExistingConfigurations'][name] = []
|
||||||
|
|
||||||
|
date = s.group(2)
|
||||||
|
d = {
|
||||||
|
"filename": f,
|
||||||
|
"backupDate": date,
|
||||||
|
"content": open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f), 'r').read()
|
||||||
|
}
|
||||||
|
if f.replace(".conf", ".sql") in list(os.listdir(directory)):
|
||||||
|
d['database'] = True
|
||||||
|
d['databaseContent'] = open(os.path.join(DashboardConfig.GetConfig("Server", "wg_conf_path")[1], 'WGDashboard_Backup', f.replace(".conf", ".sql")), 'r').read()
|
||||||
|
data['NonExistingConfigurations'][name].append(d)
|
||||||
|
return ResponseObject(data=data)
|
||||||
|
|
||||||
@app.get(f'{APP_PREFIX}/api/createWireguardConfigurationBackup')
|
@app.get(f'{APP_PREFIX}/api/createWireguardConfigurationBackup')
|
||||||
def API_createWireguardConfigurationBackup():
|
def API_createWireguardConfigurationBackup():
|
||||||
configurationName = request.args.get('configurationName')
|
configurationName = request.args.get('configurationName')
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
<script setup>
|
||||||
|
import {onMounted, ref} from "vue";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
configurationName: String,
|
||||||
|
backups: Array,
|
||||||
|
open: false,
|
||||||
|
selectedConfigurationBackup: Object
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits(["select"])
|
||||||
|
const showBackups = ref(props.open)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.selectedConfigurationBackup){
|
||||||
|
document.querySelector(`#${props.selectedConfigurationBackup.filename.replace('.conf', '')}`).scrollIntoView({
|
||||||
|
behavior: "smooth"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="card rounded-3 shadow-sm">
|
||||||
|
<a role="button" class="card-body d-flex align-items-center text-decoration-none" @click="showBackups = !showBackups">
|
||||||
|
<div class="d-flex gap-3 align-items-center">
|
||||||
|
<h6 class="mb-0">
|
||||||
|
<samp>
|
||||||
|
{{configurationName}}
|
||||||
|
</samp>
|
||||||
|
</h6>
|
||||||
|
<small class="text-muted">
|
||||||
|
{{backups.length}} {{backups.length > 1 ? "Backups": "Backup" }}
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
<h5 class="ms-auto mb-0 dropdownIcon text-muted" :class="{active: showBackups}">
|
||||||
|
<i class="bi bi-chevron-down"></i>
|
||||||
|
</h5>
|
||||||
|
</a>
|
||||||
|
<div class="card-footer p-3 d-flex flex-column gap-2" v-if="showBackups">
|
||||||
|
<div class="card rounded-3 shadow-sm animate__animated"
|
||||||
|
:key="b.filename"
|
||||||
|
@click="() => {emit('select', b)}"
|
||||||
|
:id="b.filename.replace('.conf', '')"
|
||||||
|
role="button" v-for="b in backups">
|
||||||
|
<div class="card-body d-flex p-3 gap-3 align-items-center">
|
||||||
|
<small>
|
||||||
|
<i class="bi bi-file-earmark me-2"></i>
|
||||||
|
<samp>{{b.filename}}</samp>
|
||||||
|
</small>
|
||||||
|
<small>
|
||||||
|
<i class="bi bi-clock-history me-2"></i>
|
||||||
|
<samp>{{dayjs(b.backupDate).format("YYYY-MM-DD HH:mm:ss")}}</samp>
|
||||||
|
</small>
|
||||||
|
<small >
|
||||||
|
<i class="bi bi-database me-2"></i>
|
||||||
|
{{b.database? "Yes" : "No" }}
|
||||||
|
</small>
|
||||||
|
<small class="text-muted ms-auto">
|
||||||
|
<i class="bi bi-chevron-right"></i>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dropdownIcon{
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.dropdownIcon.active{
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,318 @@
|
|||||||
|
<script setup>
|
||||||
|
import {computed, onMounted, reactive, ref, watch} from "vue";
|
||||||
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
|
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||||
|
import {parse} from "cidr-tools";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
selectedConfigurationBackup: Object
|
||||||
|
})
|
||||||
|
|
||||||
|
const newConfiguration = reactive({
|
||||||
|
ConfigurationName: props.selectedConfigurationBackup.filename.split("_")[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
const lineSplit = props.selectedConfigurationBackup.content.split("\n");
|
||||||
|
|
||||||
|
for(let line of lineSplit){
|
||||||
|
if( line === "[Peer]") break
|
||||||
|
if (line.length > 0){
|
||||||
|
let l = line.replace(" = ", "=").split("=")
|
||||||
|
if (l[0] === "ListenPort"){
|
||||||
|
newConfiguration[l[0]] = parseInt(l[1])
|
||||||
|
}else{
|
||||||
|
newConfiguration[l[0]] = l[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const error = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
|
const errorMessage = ref("")
|
||||||
|
const store = WireguardConfigurationsStore()
|
||||||
|
|
||||||
|
const wireguardGenerateKeypair = () => {
|
||||||
|
const wg = window.wireguard.generateKeypair();
|
||||||
|
newConfiguration.PrivateKey = wg.privateKey;
|
||||||
|
newConfiguration.PublicKey = wg.publicKey;
|
||||||
|
newConfiguration.PresharedKey = wg.presharedKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateConfigurationName = computed(() => {
|
||||||
|
return /^[a-zA-Z0-9_=+.-]{1,15}$/.test(newConfiguration.ConfigurationName)
|
||||||
|
&& newConfiguration.ConfigurationName.length > 0
|
||||||
|
&& !store.Configurations.find(x => x.Name === newConfiguration.ConfigurationName)
|
||||||
|
})
|
||||||
|
|
||||||
|
const validatePrivateKey = computed(() => {
|
||||||
|
try{
|
||||||
|
wireguard.generatePublicKey(newConfiguration.PrivateKey)
|
||||||
|
}catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
const validateListenPort = computed(() => {
|
||||||
|
return newConfiguration.ListenPort > 0
|
||||||
|
&& newConfiguration.ListenPort <= 65353
|
||||||
|
&& Number.isInteger(newConfiguration.ListenPort)
|
||||||
|
&& !store.Configurations.find(x => parseInt(x.ListenPort) === newConfiguration.ListenPort)
|
||||||
|
})
|
||||||
|
|
||||||
|
const validateAddress = computed(() => {
|
||||||
|
try{
|
||||||
|
parse(newConfiguration.Address)
|
||||||
|
return true
|
||||||
|
}catch (e){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const validateForm = computed(() => {
|
||||||
|
return validateAddress.value
|
||||||
|
&& validateListenPort.value
|
||||||
|
&& validatePrivateKey.value
|
||||||
|
&& validateConfigurationName.value
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
document.querySelector("main").scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: "smooth"
|
||||||
|
})
|
||||||
|
watch(() => validatePrivateKey, (newVal) => {
|
||||||
|
if (newVal){
|
||||||
|
newConfiguration.PublicKey = wireguard.generatePublicKey(newConfiguration.PrivateKey)
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
immediate: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const availableIPAddress = computed(() => {
|
||||||
|
let p;
|
||||||
|
try{
|
||||||
|
p = parse(newConfiguration.Address);
|
||||||
|
}catch (e){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return p.end - p.start
|
||||||
|
})
|
||||||
|
|
||||||
|
const peersCount = computed(() => {
|
||||||
|
if (props.selectedConfigurationBackup.database){
|
||||||
|
let l = props.selectedConfigurationBackup.databaseContent.split("\n")
|
||||||
|
return l.filter(x => x.search('INSERT INTO "(.*)"') >= 0).length
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const restrictedPeersCount = computed(() => {
|
||||||
|
if (props.selectedConfigurationBackup.database){
|
||||||
|
let l = props.selectedConfigurationBackup.databaseContent.split("\n")
|
||||||
|
return l.filter(x => x.search('INSERT INTO "(.*)_restrict_access"') >= 0).length
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="d-flex flex-column gap-5" id="confirmBackup">
|
||||||
|
<form class="d-flex flex-column gap-3">
|
||||||
|
<div class="d-flex flex-column flex-sm-row align-items-start align-items-sm-center gap-3">
|
||||||
|
<h4 class="mb-0">
|
||||||
|
<LocaleText t="Configuration File"></LocaleText>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-muted mb-1" for="ConfigurationName"><small>
|
||||||
|
<LocaleText t="Configuration Name"></LocaleText>
|
||||||
|
</small></label>
|
||||||
|
<input type="text" class="form-control rounded-3" placeholder="ex. wg1" id="ConfigurationName"
|
||||||
|
v-model="newConfiguration.ConfigurationName"
|
||||||
|
:class="[validateConfigurationName ? 'is-valid':'is-invalid']"
|
||||||
|
:disabled="loading"
|
||||||
|
required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<div v-if="error">{{errorMessage}}</div>
|
||||||
|
<div v-else>
|
||||||
|
<LocaleText t="Configuration name is invalid. Possible reasons:"></LocaleText>
|
||||||
|
<ul class="mb-0">
|
||||||
|
<li>
|
||||||
|
<LocaleText t="Configuration name already exist."></LocaleText>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<LocaleText t="Configuration name can only contain 15 lower/uppercase alphabet, numbers, underscore, equal sign, plus sign, period and hyphen."></LocaleText>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div>
|
||||||
|
<label class="text-muted mb-1" for="PrivateKey"><small>
|
||||||
|
<LocaleText t="Private Key"></LocaleText>
|
||||||
|
</small></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control rounded-start-3" id="PrivateKey" required
|
||||||
|
:disabled="loading"
|
||||||
|
:class="[validatePrivateKey ? 'is-valid':'is-invalid']"
|
||||||
|
v-model="newConfiguration.PrivateKey" disabled
|
||||||
|
>
|
||||||
|
<button class="btn btn-outline-primary rounded-end-3" type="button"
|
||||||
|
title="Regenerate Private Key"
|
||||||
|
@click="wireguardGenerateKeypair()"
|
||||||
|
>
|
||||||
|
<i class="bi bi-arrow-repeat"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<div>
|
||||||
|
<label class="text-muted mb-1" for="PublicKey"><small>
|
||||||
|
<LocaleText t="Public Key"></LocaleText>
|
||||||
|
</small></label>
|
||||||
|
<input type="text" class="form-control rounded-3" id="PublicKey"
|
||||||
|
v-model="newConfiguration.PublicKey" disabled
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-muted mb-1" for="ListenPort"><small>
|
||||||
|
<LocaleText t="Listen Port"></LocaleText>
|
||||||
|
</small></label>
|
||||||
|
<input type="number" class="form-control rounded-3" placeholder="0-65353" id="ListenPort"
|
||||||
|
min="1"
|
||||||
|
max="65353"
|
||||||
|
v-model="newConfiguration.ListenPort"
|
||||||
|
:class="[validateListenPort ? 'is-valid':'is-invalid']"
|
||||||
|
:disabled="loading"
|
||||||
|
required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<div v-if="error">{{errorMessage}}</div>
|
||||||
|
<div v-else>
|
||||||
|
<LocaleText t="Listen Port is invalid. Possible reasons:"></LocaleText>
|
||||||
|
<ul class="mb-0">
|
||||||
|
<li>
|
||||||
|
<LocaleText t="Invalid port."></LocaleText>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<LocaleText t="Port is assigned to existing WireGuard Configuration. "></LocaleText>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-muted mb-1 d-flex" for="ListenPort">
|
||||||
|
<small>
|
||||||
|
<LocaleText t="IP Address/CIDR"></LocaleText>
|
||||||
|
</small>
|
||||||
|
<small class="ms-auto" :class="[availableIPAddress > 0 ? 'text-success':'text-danger']">
|
||||||
|
{{availableIPAddress}} Available IP Address
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
<input type="text" class="form-control"
|
||||||
|
placeholder="Ex: 10.0.0.1/24" id="Address"
|
||||||
|
v-model="newConfiguration.Address"
|
||||||
|
:class="[validateAddress ? 'is-valid':'is-invalid']"
|
||||||
|
:disabled="loading"
|
||||||
|
required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
<div v-if="error">{{errorMessage}}</div>
|
||||||
|
<div v-else>
|
||||||
|
<LocaleText t="IP Address/CIDR is invalid"></LocaleText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="accordion" id="newConfigurationOptionalAccordion">
|
||||||
|
<div class="accordion-item">
|
||||||
|
<h2 class="accordion-header">
|
||||||
|
<button class="accordion-button collapsed rounded-3"
|
||||||
|
type="button" data-bs-toggle="collapse" data-bs-target="#newConfigurationOptionalAccordionCollapse">
|
||||||
|
<LocaleText t="Optional Settings"></LocaleText>
|
||||||
|
</button>
|
||||||
|
</h2>
|
||||||
|
<div id="newConfigurationOptionalAccordionCollapse"
|
||||||
|
class="accordion-collapse collapse "
|
||||||
|
data-bs-parent="#newConfigurationOptionalAccordion">
|
||||||
|
<div class="accordion-body d-flex flex-column gap-3">
|
||||||
|
<div>
|
||||||
|
<label class="text-muted mb-1" for="PreUp"><small>
|
||||||
|
<LocaleText t="PreUp"></LocaleText>
|
||||||
|
</small></label>
|
||||||
|
<input type="text" class="form-control rounded-3" id="PreUp" v-model="newConfiguration.PreUp">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-muted mb-1" for="PreDown"><small>
|
||||||
|
<LocaleText t="PreDown"></LocaleText>
|
||||||
|
</small></label>
|
||||||
|
<input type="text" class="form-control rounded-3" id="PreDown" v-model="newConfiguration.PreDown">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-muted mb-1" for="PostUp"><small>
|
||||||
|
<LocaleText t="PostUp"></LocaleText>
|
||||||
|
</small></label>
|
||||||
|
<input type="text" class="form-control rounded-3" id="PostUp" v-model="newConfiguration.PostUp">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-muted mb-1" for="PostDown"><small>
|
||||||
|
<LocaleText t="PostDown"></LocaleText>
|
||||||
|
</small></label>
|
||||||
|
<input type="text" class="form-control rounded-3" id="PostDown" v-model="newConfiguration.PostDown">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<div class="d-flex flex-column gap-3">
|
||||||
|
<div class="d-flex flex-column flex-sm-row align-items-start align-items-sm-center gap-3">
|
||||||
|
<h4 class="mb-0">
|
||||||
|
<LocaleText t="Database File"></LocaleText>
|
||||||
|
</h4>
|
||||||
|
<h4 class="mb-0 ms-auto" :class="[selectedConfigurationBackup.database ? 'text-success':'text-danger']">
|
||||||
|
<i class="bi" :class="[selectedConfigurationBackup.database ? 'bi-check-circle-fill':'bi-x-circle-fill']"></i>
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
<div v-if="selectedConfigurationBackup.database">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="card text-bg-success rounded-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="bi bi-person-fill me-2"></i> Contain <strong>{{peersCount}}</strong> Peer{{peersCount > 1 ? 's':''}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm">
|
||||||
|
<div class="card text-bg-warning rounded-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<i class="bi bi-person-fill-lock me-2"></i> Contain <strong>{{restrictedPeersCount}}</strong> Restricted Peer{{restrictedPeersCount > 1 ? 's':''}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<button class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto"
|
||||||
|
:disabled="!validateForm"
|
||||||
|
>
|
||||||
|
<i class="bi bi-clock-history me-2"></i> Restore
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
@ -126,14 +126,23 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="mt-5">
|
<div class="mt-5 text-body">
|
||||||
<div class="container mb-4">
|
<div class="container mb-4">
|
||||||
<div class="mb-4 d-flex align-items-center gap-4">
|
<div class="mb-4 d-flex align-items-center gap-4">
|
||||||
<RouterLink to="/" class="text-decoration-none">
|
<RouterLink to="/"
|
||||||
<h3 class="mb-0 text-body">
|
class="btn btn-dark btn-brand p-2 shadow" style="border-radius: 100%">
|
||||||
<i class="bi bi-chevron-left me-4"></i>
|
<h2 class="mb-0" style="line-height: 0">
|
||||||
<LocaleText t="New Configuration"></LocaleText>
|
<i class="bi bi-arrow-left-circle"></i>
|
||||||
</h3>
|
</h2>
|
||||||
|
</RouterLink>
|
||||||
|
<h2 class="mb-0">
|
||||||
|
<LocaleText t="New Configuration"></LocaleText>
|
||||||
|
</h2>
|
||||||
|
<RouterLink to="/restore_configuration"
|
||||||
|
class="btn btn-dark btn-brand p-2 shadow ms-auto" style="border-radius: 100%">
|
||||||
|
<h2 class="mb-0" style="line-height: 0">
|
||||||
|
<i class="bi bi-clock-history"></i>
|
||||||
|
</h2>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -168,8 +177,7 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
<div class="card rounded-3 shadow">
|
<div class="card rounded-3 shadow">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<LocaleText t="Private Key"></LocaleText> &
|
<LocaleText t="Private Key"></LocaleText> & <LocaleText t="Public Key"></LocaleText>
|
||||||
<LocaleText t="Public Key"></LocaleText>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" style="font-family: var(--bs-font-monospace)">
|
<div class="card-body" style="font-family: var(--bs-font-monospace)">
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
@ -286,13 +294,12 @@ export default {
|
|||||||
<i class="bi bi-check-circle-fill ms-2"></i>
|
<i class="bi bi-check-circle-fill ms-2"></i>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="!this.loading" class="d-flex w-100">
|
<span v-else-if="!this.loading" class="d-flex w-100">
|
||||||
<LocaleText t="Save Configuration"></LocaleText>
|
<i class="bi bi-save-fill me-2"></i>
|
||||||
<i class="bi bi-save-fill ms-2"></i>
|
<LocaleText t="Save"></LocaleText>
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="d-flex w-100 align-items-center">
|
<span v-else class="d-flex w-100 align-items-center">
|
||||||
<LocaleText t="Saving..."></LocaleText>
|
<LocaleText t="Saving..."></LocaleText>
|
||||||
<span class="ms-2 spinner-border spinner-border-sm" role="status">
|
<span class="ms-2 spinner-border spinner-border-sm" role="status">
|
||||||
<!-- <span class="visually-hidden">Loading...</span>-->
|
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
123
src/static/app/src/views/restoreConfiguration.vue
Normal file
123
src/static/app/src/views/restoreConfiguration.vue
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
<script setup>
|
||||||
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
|
import {onMounted, reactive, ref, watch} from "vue";
|
||||||
|
import {fetchGet} from "@/utilities/fetch.js";
|
||||||
|
import BackupGroup from "@/components/restoreConfigurationComponents/backupGroup.vue";
|
||||||
|
import ConfirmBackup from "@/components/restoreConfigurationComponents/confirmBackup.vue";
|
||||||
|
const backups = ref(undefined)
|
||||||
|
onMounted(() => {
|
||||||
|
fetchGet("/api/getAllWireguardConfigurationBackup", {}, (res) => {
|
||||||
|
backups.value = res.data
|
||||||
|
})
|
||||||
|
})
|
||||||
|
const confirm = ref(false)
|
||||||
|
const selectedConfigurationBackup = ref(undefined)
|
||||||
|
const selectedConfiguration = ref("")
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="mt-5 text-body">
|
||||||
|
<div class="container mb-4">
|
||||||
|
<div class="mb-5 d-flex align-items-center gap-4">
|
||||||
|
<RouterLink to="/new_configuration"
|
||||||
|
class="btn btn-dark btn-brand p-2 shadow" style="border-radius: 100%">
|
||||||
|
<h2 class="mb-0" style="line-height: 0">
|
||||||
|
<i class="bi bi-arrow-left-circle"></i>
|
||||||
|
</h2>
|
||||||
|
</RouterLink>
|
||||||
|
<h2 class="mb-0">
|
||||||
|
<LocaleText t="Restore Configuration"></LocaleText>
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div name="restore" v-if="backups" >
|
||||||
|
<div class="d-flex mb-5 align-items-center steps" role="button"
|
||||||
|
:class="{active: !confirm}"
|
||||||
|
@click="confirm = false" key="step1">
|
||||||
|
<div class=" d-flex text-decoration-none text-body flex-grow-1 align-items-center gap-3"
|
||||||
|
|
||||||
|
>
|
||||||
|
<h1 class="mb-0"
|
||||||
|
style="line-height: 0">
|
||||||
|
<i class="bi bi-1-circle-fill"></i>
|
||||||
|
</h1>
|
||||||
|
<div>
|
||||||
|
<h4 class="mb-0">Step 1</h4>
|
||||||
|
<small class="text-muted">
|
||||||
|
<LocaleText t="Select a backup you want to restore" v-if="!confirm"></LocaleText>
|
||||||
|
<LocaleText t="Click to change a backup" v-else></LocaleText>
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Transition name="zoomReversed">
|
||||||
|
<div class="ms-sm-auto" v-if="confirm">
|
||||||
|
<small class="text-muted">Selected Backup</small>
|
||||||
|
<h6>
|
||||||
|
<samp>{{selectedConfigurationBackup.filename}}</samp>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
<div id="step1Detail" v-if="!confirm">
|
||||||
|
<div class="mb-4">
|
||||||
|
<h5>Backup of existing WireGuard Configurations</h5>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex gap-3 flex-column">
|
||||||
|
<BackupGroup
|
||||||
|
@select="(b) => {selectedConfigurationBackup = b; selectedConfiguration = c; confirm = true}"
|
||||||
|
:open="selectedConfiguration === c"
|
||||||
|
:selectedConfigurationBackup="selectedConfigurationBackup"
|
||||||
|
v-for="c in Object.keys(backups.ExistingConfigurations)"
|
||||||
|
:configuration-name="c" :backups="backups.ExistingConfigurations[c]"></BackupGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<h5>Backup of non-existing WireGuard Configurations</h5>
|
||||||
|
<hr>
|
||||||
|
<div class="d-flex gap-3 flex-column">
|
||||||
|
<BackupGroup
|
||||||
|
@select="(b) => {selectedConfigurationBackup = b; selectedConfiguration = c; confirm = true}"
|
||||||
|
:selectedConfigurationBackup="selectedConfigurationBackup"
|
||||||
|
:open="selectedConfiguration === c"
|
||||||
|
v-for="c in Object.keys(backups.NonExistingConfigurations)"
|
||||||
|
:configuration-name="c" :backups="backups.NonExistingConfigurations[c]"></BackupGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="my-5" key="step2" id="step2">
|
||||||
|
<div class="steps d-flex text-decoration-none text-body flex-grow-1 align-items-center gap-3"
|
||||||
|
:class="{active: confirm}"
|
||||||
|
>
|
||||||
|
<h1 class="mb-0"
|
||||||
|
style="line-height: 0">
|
||||||
|
<i class="bi bi-2-circle-fill"></i>
|
||||||
|
</h1>
|
||||||
|
<div>
|
||||||
|
<h4 class="mb-0">Step 2</h4>
|
||||||
|
<small class="text-muted">
|
||||||
|
<LocaleText t="Backup not selected" v-if="!confirm"></LocaleText>
|
||||||
|
<LocaleText t="Confirm & edit restore information" v-else></LocaleText>
|
||||||
|
</small>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ConfirmBackup :selectedConfigurationBackup="selectedConfigurationBackup" v-if="confirm" key="confirm"></ConfirmBackup>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.steps{
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
opacity: 0.3;
|
||||||
|
|
||||||
|
&.active{
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -38,9 +38,9 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div class="mt-md-5 mt-3">
|
<div class="mt-md-5 mt-3">
|
||||||
<div class="container-md">
|
<div class="container-md">
|
||||||
<h3 class="mb-3 text-body">
|
<h2 class="mb-4 text-body">
|
||||||
<LocaleText t="Settings"></LocaleText>
|
<LocaleText t="Settings"></LocaleText>
|
||||||
</h3>
|
</h2>
|
||||||
|
|
||||||
<div class="card mb-4 shadow rounded-3">
|
<div class="card mb-4 shadow rounded-3">
|
||||||
<p class="card-header">
|
<p class="card-header">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user