Files
WGDashboard/src/static/app/src/views/newConfiguration.vue

427 lines
14 KiB
Vue
Raw Normal View History

<script>
2025-09-08 15:12:16 +08:00
import {parseCidr, containsCidr, mergeCidr, expandCidr} from "cidr-tools";
2024-01-31 12:06:44 -05:00
import '@/utilities/wireguard.js'
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
2024-12-04 17:50:16 +08:00
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
2024-09-23 03:07:48 +08:00
import LocaleText from "@/components/text/localeText.vue";
import {parseInterface, parsePeers} from "@/utilities/parseConfigurationFile.js";
2024-12-04 17:50:16 +08:00
import {ref} from "vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
2025-09-08 15:12:16 +08:00
import {exp} from "qrcode/lib/core/galois-field.js";
import NewConfigurationTemplates from "@/components/newConfigurationComponents/newConfigurationTemplates.vue";
2024-01-31 12:06:44 -05:00
export default {
2024-01-31 12:06:44 -05:00
name: "newConfiguration",
2025-09-08 15:12:16 +08:00
components: {NewConfigurationTemplates, LocaleText},
2024-12-04 17:50:16 +08:00
async setup(){
2024-01-31 12:06:44 -05:00
const store = WireguardConfigurationsStore()
2024-12-04 17:50:16 +08:00
const protocols = ref([])
await fetchGet("/api/protocolsEnabled", {}, (res) => {
protocols.value = res.data
})
const dashboardStore = DashboardConfigurationStore();
2024-12-04 17:50:16 +08:00
return {store, protocols, dashboardStore}
2024-01-31 12:06:44 -05:00
},
data(){
return {
newConfiguration: {
ConfigurationName: "",
Address: "",
ListenPort: "",
PrivateKey: "",
PublicKey: "",
PresharedKey: "",
PreUp: "",
PreDown: "",
PostUp: "",
2024-12-03 02:34:45 +08:00
PostDown: "",
Table: "",
2024-12-05 01:50:31 +08:00
Protocol: "wg",
Jc: 5,
Jmin: 49,
Jmax: 998,
S1: 17,
S2: 110,
2025-12-29 17:56:35 +01:00
S3: 1,
S4: 2,
2024-12-05 01:50:31 +08:00
H1: 0,
H2: 0,
H3: 0,
2025-12-29 17:56:35 +01:00
H4: 0,
2026-02-06 20:18:32 +01:00
I1: "0",
I2: "0",
I3: "0",
I4: "0",
I5: "0"
2024-01-31 12:06:44 -05:00
},
numberOfAvailableIPs: "0",
error: false,
errorMessage: "",
success: false,
loading: false,
parseInterfaceResult: undefined,
parsePeersResult: undefined
2024-01-31 12:06:44 -05:00
}
},
created() {
2024-12-05 01:50:31 +08:00
this.wireguardGenerateKeypair();
2026-02-06 20:18:32 +01:00
// Generate 4 random numbers for H1, H2, H3, H4
['H1', 'H2', 'H3', 'H4'].forEach(key => {
this.newConfiguration[key] = this.rand(1, 2**31);
});
// Initialize I1 to I5 as "0"
['I1', 'I2', 'I3', 'I4', 'I5'].forEach(key => {
this.newConfiguration[key] = "0";
});
2024-01-31 12:06:44 -05:00
},
methods: {
2024-12-05 01:50:31 +08:00
rand(min, max){
return Math.floor(Math.random() * (max - min) + min);
},
2024-01-31 12:06:44 -05:00
wireguardGenerateKeypair(){
const wg = window.wireguard.generateKeypair();
this.newConfiguration.PrivateKey = wg.privateKey;
this.newConfiguration.PublicKey = wg.publicKey;
this.newConfiguration.PresharedKey = wg.presharedKey;
},
async saveNewConfiguration(){
if (this.goodToSubmit){
this.loading = true;
await fetchPost("/api/addWireguardConfiguration", this.newConfiguration, async (res) => {
if (res.status){
this.success = true
await this.store.getConfigurations()
2024-09-23 03:07:48 +08:00
this.$router.push(`/configuration/${this.newConfiguration.ConfigurationName}/peers`)
}else{
this.error = true;
this.errorMessage = res.message;
document.querySelector(`#${res.data}`).classList.remove("is-valid")
document.querySelector(`#${res.data}`).classList.add("is-invalid")
this.loading = false;
}
})
}
},
openFileUpload(){
document.querySelector("#fileUpload").click();
},
readFile(e){
const file = e.target.files[0];
if (!file) return false;
const reader = new FileReader();
reader.onload = (evt) => {
this.parseInterfaceResult = parseInterface(evt.target.result);
this.parsePeersResult = parsePeers(evt.target.result);
let appliedFields = 0;
if (this.parseInterfaceResult){
this.newConfiguration.ConfigurationName = file.name.replace('.conf', '')
for (let i of Object.keys(this.parseInterfaceResult)){
if (Object.keys(this.newConfiguration).includes(i)){
this.newConfiguration[i] = this.parseInterfaceResult[i];
appliedFields += 1;
}
}
}
if (appliedFields > 0){
this.dashboardStore.newMessage("WGDashboard", `Parse successful! Updated ${appliedFields} field(s)`, "success")
}else {
this.dashboardStore.newMessage("WGDashboard", `Parse failed`, "danger")
}
};
reader.readAsText(file);
}
2024-01-31 12:06:44 -05:00
},
computed: {
goodToSubmit(){
let requirements = ["ConfigurationName", "Address", "ListenPort", "PrivateKey"]
let elements = [...document.querySelectorAll("input[required]")];
return requirements.find(x => {
return this.newConfiguration[x].length === 0
}) === undefined && elements.find(x => {
return x.classList.contains("is-invalid")
}) === undefined
}
2024-01-31 12:06:44 -05:00
},
watch: {
'newConfiguration.Address'(newVal){
let ele = document.querySelector("#Address");
2025-09-08 15:12:16 +08:00
if (ele){
ele.classList.remove("is-invalid", "is-valid")
try{
this.numberOfAvailableIPs = 0
newVal.replace(" ", "").split(",").forEach(x => {
let p = parseCidr(x);
let i = Number(p.end - p.start);
this.numberOfAvailableIPs += i + 1;
})
ele.classList.add("is-valid")
}catch (e) {
console.log(e)
this.numberOfAvailableIPs = "0";
ele.classList.add("is-invalid")
2024-01-31 12:06:44 -05:00
}
}
},
'newConfiguration.ListenPort'(newVal){
let ele = document.querySelector("#ListenPort");
2025-09-08 15:12:16 +08:00
if (ele){
ele.classList.remove("is-invalid", "is-valid")
if (newVal < 0 || newVal > 65353 || !Number.isInteger(newVal)){
ele.classList.add("is-invalid")
}else{
ele.classList.add("is-valid")
}
2024-01-31 12:06:44 -05:00
}
},
'newConfiguration.ConfigurationName'(newVal){
let ele = document.querySelector("#ConfigurationName");
2025-09-08 15:12:16 +08:00
if (ele){
ele.classList.remove("is-invalid", "is-valid")
if (!/^[a-zA-Z0-9_=+.-]{1,15}$/.test(newVal) || newVal.length === 0 || this.store.Configurations.find(x => x.Name === newVal)){
ele.classList.add("is-invalid")
}else{
ele.classList.add("is-valid")
}
2024-01-31 12:06:44 -05:00
}
},
'newConfiguration.PrivateKey'(newVal){
let ele = document.querySelector("#PrivateKey");
2025-09-08 15:12:16 +08:00
if (ele){
ele.classList.remove("is-invalid", "is-valid")
try{
wireguard.generatePublicKey(newVal)
ele.classList.add("is-valid")
}catch (e) {
ele.classList.add("is-invalid")
}
2024-01-31 12:06:44 -05:00
}
}
},
mounted() {
const fileUpload = document.querySelector("#fileUpload");
fileUpload.addEventListener("change", this.readFile, false)
2024-01-31 12:06:44 -05:00
}
}
</script>
<template>
2024-11-07 23:43:14 +08:00
<div class="mt-md-5 mt-3 text-body">
2024-01-31 12:06:44 -05:00
<div class="container mb-4">
<div class="mb-4 d-flex align-items-center gap-4 align-items-center">
2024-10-25 00:19:27 +08:00
<RouterLink to="/"
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="New Configuration"></LocaleText>
</h2>
<div class="d-flex gap-2 ms-auto">
<button class="titleBtn py-2 text-decoration-none btn text-primary-emphasis bg-primary-subtle rounded-3 border-1 border-primary-subtle"
@click="openFileUpload()"
type="button" aria-expanded="false">
<i class="bi bi-upload me-2"></i>
2024-12-26 00:06:37 +08:00
<LocaleText t="Open File"></LocaleText>
</button>
<input type="file" id="fileUpload" multiple class="d-none" accept="text/plain" />
</div>
</div>
2024-01-31 12:06:44 -05:00
<form class="text-body d-flex flex-column gap-3"
@submit="(e) => {e.preventDefault(); this.saveNewConfiguration();}"
>
2024-12-03 02:34:45 +08:00
<div class="card rounded-3 shadow">
<div class="card-header">
<LocaleText t="Protocol"></LocaleText>
</div>
<div class="card-body d-flex gap-2 protocolBtnGroup">
<a
2024-12-06 01:27:15 +08:00
v-if="this.protocols.includes('wg')"
2024-12-03 02:34:45 +08:00
@click="this.newConfiguration.Protocol = 'wg'"
:class="{'opacity-50': this.newConfiguration.Protocol !== 'wg'}"
class="btn btn-primary wireguardBg border-0 " style="flex-basis: 100%">
<i class="bi bi-check-circle-fill me-2" v-if="this.newConfiguration.Protocol === 'wg'"></i>
<i class="bi bi-circle me-2" v-else></i>
<strong>
WireGuard
</strong>
</a>
<a
@click="this.newConfiguration.Protocol = 'awg'"
2024-12-06 01:27:15 +08:00
v-if="this.protocols.includes('awg')"
2024-12-03 02:34:45 +08:00
:class="{'opacity-50': this.newConfiguration.Protocol !== 'awg'}"
class="btn btn-primary amneziawgBg border-0" style="flex-basis: 100%">
<i class="bi bi-check-circle-fill me-2" v-if="this.newConfiguration.Protocol === 'awg'"></i>
<i class="bi bi-circle me-2" v-else></i>
<strong>
AmneziaWG
</strong>
</a>
</div>
</div>
2024-01-31 12:06:44 -05:00
<div class="card rounded-3 shadow">
2024-09-23 03:07:48 +08:00
<div class="card-header">
<LocaleText t="Configuration Name"></LocaleText>
</div>
2024-01-31 12:06:44 -05:00
<div class="card-body">
<input type="text" class="form-control" placeholder="ex. wg1" id="ConfigurationName"
v-model="this.newConfiguration.ConfigurationName"
:disabled="this.loading"
2024-01-31 12:06:44 -05:00
required>
<div class="invalid-feedback">
<div v-if="this.error">{{this.errorMessage}}</div>
<div v-else>
2024-09-23 03:07:48 +08:00
<LocaleText t="Configuration name is invalid. Possible reasons:"></LocaleText>
<ul class="mb-0">
2024-09-23 03:07:48 +08:00
<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>
2024-01-31 12:06:44 -05:00
</div>
</div>
</div>
<div class="card rounded-3 shadow">
2024-09-23 03:07:48 +08:00
<div class="card-header">
2024-10-25 00:19:27 +08:00
<LocaleText t="Private Key"></LocaleText> & <LocaleText t="Public Key"></LocaleText>
2024-09-23 03:07:48 +08:00
</div>
2024-01-31 12:06:44 -05:00
<div class="card-body" style="font-family: var(--bs-font-monospace)">
<div class="mb-2">
2024-09-23 03:07:48 +08:00
<label class="text-muted fw-bold mb-1"><small>
<LocaleText t="Private Key"></LocaleText>
</small></label>
2024-01-31 12:06:44 -05:00
<div class="input-group">
<input type="text" class="form-control" id="PrivateKey" required
:disabled="this.loading"
2024-01-31 12:06:44 -05:00
v-model="this.newConfiguration.PrivateKey" disabled
>
<button class="btn btn-outline-primary" type="button"
title="Regenerate Private Key"
@click="wireguardGenerateKeypair()"
>
<i class="bi bi-arrow-repeat"></i>
</button>
</div>
</div>
<div>
2024-09-23 03:07:48 +08:00
<label class="text-muted fw-bold mb-1"><small>
<LocaleText t="Public Key"></LocaleText>
</small></label>
<input type="text" class="form-control" id="PublicKey"
v-model="this.newConfiguration.PublicKey" disabled
>
2024-01-31 12:06:44 -05:00
</div>
</div>
</div>
2025-09-08 15:12:16 +08:00
<NewConfigurationTemplates
@subnet="args => this.newConfiguration.Address = args"
@port="args => this.newConfiguration.ListenPort = args"
></NewConfigurationTemplates>
2024-01-31 12:06:44 -05:00
<div class="card rounded-3 shadow">
2024-09-23 03:07:48 +08:00
<div class="card-header">
<LocaleText t="Listen Port"></LocaleText>
</div>
2024-01-31 12:06:44 -05:00
<div class="card-body">
<input type="number" class="form-control" placeholder="0-65353" id="ListenPort"
min="1"
max="65353"
v-model="this.newConfiguration.ListenPort"
:disabled="this.loading"
2024-01-31 12:06:44 -05:00
required>
<div class="invalid-feedback">
<div v-if="this.error">{{this.errorMessage}}</div>
<div v-else>
2024-09-23 03:07:48 +08:00
<LocaleText t="Invalid port"></LocaleText>
</div>
</div>
2024-01-31 12:06:44 -05:00
</div>
</div>
<div class="card rounded-3 shadow">
<div class="card-header d-flex align-items-center">
2024-09-23 03:07:48 +08:00
<LocaleText t="IP Address/CIDR"></LocaleText>
2025-04-20 02:37:54 +08:00
<span class="badge rounded-pill text-bg-success ms-auto"><LocaleText :t="numberOfAvailableIPs + ' Available IP Address'"></LocaleText></span>
2024-01-31 12:06:44 -05:00
</div>
<div class="card-body">
<input type="text" class="form-control"
placeholder="Ex: 10.0.0.1/24" id="Address"
v-model="this.newConfiguration.Address"
:disabled="this.loading"
2024-01-31 12:06:44 -05:00
required>
<div class="invalid-feedback">
<div v-if="this.error">{{this.errorMessage}}</div>
<div v-else>
2024-09-23 03:07:48 +08:00
IP Address/CIDR is invalid
</div>
2024-01-31 12:06:44 -05:00
</div>
</div>
</div>
2025-05-01 22:28:43 +08:00
2024-01-31 12:06:44 -05:00
<hr>
<div class="accordion" id="newConfigurationOptionalAccordion">
<div class="accordion-item">
<h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#newConfigurationOptionalAccordionCollapse">
2024-09-23 03:07:48 +08:00
<LocaleText t="Optional Settings"></LocaleText>
2024-01-31 12:06:44 -05:00
</button>
</h2>
<div id="newConfigurationOptionalAccordionCollapse"
class="accordion-collapse collapse" data-bs-parent="#newConfigurationOptionalAccordion">
<div class="accordion-body d-flex flex-column gap-3">
2025-05-01 22:28:43 +08:00
<div class="card rounded-3" v-for="key in ['Table', 'PreUp', 'PreDown', 'PostUp', 'PostDown']">
2024-12-04 17:50:16 +08:00
<div class="card-header">{{ key }}</div>
2024-01-31 12:06:44 -05:00
<div class="card-body">
2024-12-04 17:50:16 +08:00
<input type="text"
class="form-control font-monospace" :id="key" v-model="this.newConfiguration[key]">
2024-01-31 12:06:44 -05:00
</div>
</div>
2024-12-05 01:50:31 +08:00
<div class="card rounded-3"
v-if="this.newConfiguration.Protocol === 'awg'"
2025-12-29 17:56:35 +01:00
v-for="key in ['Jc', 'Jmin', 'Jmax', 'S1', 'S2', 'S3', 'S4', 'H1', 'H2', 'H3', 'H4', 'I1', 'I2', 'I3', 'I4', 'I5']">
2024-12-05 01:50:31 +08:00
<div class="card-header">{{ key }}</div>
<div class="card-body">
<input type="text"
class="form-control font-monospace" :id="key" v-model="this.newConfiguration[key]">
</div>
</div>
2024-01-31 12:06:44 -05:00
</div>
</div>
</div>
</div>
<button class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto"
2024-09-23 03:07:48 +08:00
:disabled="!this.goodToSubmit || this.loading || this.success">
<span v-if="this.success" class="d-flex w-100">
2024-09-23 03:07:48 +08:00
<LocaleText t="Success"></LocaleText>!
<i class="bi bi-check-circle-fill ms-2"></i>
</span>
<span v-else-if="!this.loading" class="d-flex w-100">
2024-10-25 00:19:27 +08:00
<i class="bi bi-save-fill me-2"></i>
<LocaleText t="Save"></LocaleText>
</span>
<span v-else class="d-flex w-100 align-items-center">
2024-09-23 03:07:48 +08:00
<LocaleText t="Saving..."></LocaleText>
<span class="ms-2 spinner-border spinner-border-sm" role="status">
</span>
</span>
2024-01-31 12:06:44 -05:00
</button>
</form>
</div>
</div>
</template>
<style scoped>
2024-12-03 02:34:45 +08:00
.protocolBtnGroup a{
transition: all 0.2s ease-in-out;
}
</style>