2024-01-23 15:09:44 -05:00
< 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" ;
2024-12-05 22:22:55 +08:00
import { parseInterface , parsePeers } from "@/utilities/parseConfigurationFile.js" ;
2024-12-04 17:50:16 +08:00
import { ref } from "vue" ;
2024-12-05 22:22:55 +08:00
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
2024-01-23 15:09: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
} )
2024-12-05 22:22:55 +08:00
const dashboardStore = DashboardConfigurationStore ( ) ;
2024-12-04 17:50:16 +08:00
2024-12-05 22:22:55 +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 : "" ,
2025-04-24 18:51:26 +03:30
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
} ,
2024-02-11 23:53:51 -05:00
numberOfAvailableIPs : "0" ,
error : false ,
errorMessage : "" ,
success : false ,
2024-12-05 22:22:55 +08:00
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 ;
2024-02-11 23:53:51 -05:00
} ,
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 ` )
2024-02-11 23:53:51 -05:00
} else {
this . error = true ;
this . errorMessage = res . message ;
document . querySelector ( ` # ${ res . data } ` ) . classList . remove ( "is-valid" )
document . querySelector ( ` # ${ res . data } ` ) . classList . add ( "is-invalid" )
2024-08-11 19:20:03 -04:00
this . loading = false ;
2024-02-11 23:53:51 -05:00
}
} )
}
2024-12-05 22:22:55 +08:00
} ,
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-02-11 23:53:51 -05:00
}
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-12-05 22:22:55 +08:00
}
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
}
}
2024-12-05 22:22:55 +08:00
} ,
mounted ( ) {
const fileUpload = document . querySelector ( "#fileUpload" ) ;
fileUpload . addEventListener ( "change" , this . readFile , false )
2024-01-31 12:06:44 -05:00
}
2024-01-23 15:09: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" >
2024-12-05 22:22:55 +08:00
< 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 >
2024-12-05 22:22:55 +08:00
< 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 >
2024-12-05 22:22:55 +08:00
< / button >
< input type = "file" id = "fileUpload" multiple class = "d-none" accept = "text/plain" / >
< / div >
2024-01-23 15:09:44 -05:00
< / div >
2024-01-31 12:06:44 -05:00
2024-02-11 23:53:51 -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"
2024-02-11 23:53:51 -05:00
: disabled = "this.loading"
2024-01-31 12:06:44 -05:00
required >
< div class = "invalid-feedback" >
2024-02-11 23:53:51 -05:00
< 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 >
2024-02-11 23:53:51 -05:00
< 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 >
2024-02-11 23:53:51 -05:00
< / 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
2024-02-11 23:53:51 -05:00
: 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 >
2024-02-11 23:53:51 -05:00
< 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 >
2024-02-11 23:53:51 -05:00
< 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"
2024-02-11 23:53:51 -05:00
: disabled = "this.loading"
2024-01-31 12:06:44 -05:00
required >
2024-02-11 23:53:51 -05:00
< 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 >
2024-02-11 23:53:51 -05:00
< / 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"
2024-02-11 23:53:51 -05:00
: disabled = "this.loading"
2024-01-31 12:06:44 -05:00
required >
< div class = "invalid-feedback" >
2024-02-11 23:53:51 -05:00
< div v-if = "this.error" > {{ this.errorMessage }} < / div >
< div v-else >
2024-09-23 03:07:48 +08:00
IP Address / CIDR is invalid
2024-02-11 23:53:51 -05:00
< / 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 >
2024-02-11 23:53:51 -05:00
< 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" >
2024-02-11 23:53:51 -05:00
< 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 >
2024-02-11 23:53:51 -05:00
< / 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 >
2024-02-11 23:53:51 -05:00
< / span >
< span v-else class = "d-flex w-100 align-items-center" >
2024-09-23 03:07:48 +08:00
< LocaleText t = "Saving..." > < / LocaleText >
2024-02-11 23:53:51 -05:00
< span class = "ms-2 spinner-border spinner-border-sm" role = "status" >
< / span >
< / span >
2024-01-31 12:06:44 -05:00
< / button >
< / form >
2024-01-23 15:09:44 -05:00
< / div >
< / div >
< / template >
< style scoped >
2024-12-03 02:34:45 +08:00
. protocolBtnGroup a {
transition : all 0.2 s ease - in - out ;
}
2024-01-23 15:09:44 -05:00
< / style >