mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2024-11-22 07:10:09 +01:00
The UI for New Configuration is done
This commit is contained in:
parent
5f4a364095
commit
6b6ad05e3a
@ -99,27 +99,31 @@ class WireguardConfiguration:
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
def __init__(self, name):
|
||||
self.Name = name
|
||||
self.__parser.read(os.path.join(WG_CONF_PATH, f'{self.Name}.conf'))
|
||||
sections = self.__parser.sections()
|
||||
if "Interface" not in sections:
|
||||
raise self.InvalidConfigurationFileException(
|
||||
"[Interface] section not found in " + os.path.join(WG_CONF_PATH, 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])
|
||||
def __init__(self, name: str = None):
|
||||
if name is not None:
|
||||
self.Name = name
|
||||
self.__parser.read(os.path.join(WG_CONF_PATH, f'{self.Name}.conf'))
|
||||
sections = self.__parser.sections()
|
||||
if "Interface" not in sections:
|
||||
raise self.InvalidConfigurationFileException(
|
||||
"[Interface] section not found in " + os.path.join(WG_CONF_PATH, 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()
|
||||
if self.PrivateKey:
|
||||
self.PublicKey = self.__getPublicKey()
|
||||
|
||||
self.Status = self.__getStatus()
|
||||
self.Status = self.__getStatus()
|
||||
|
||||
# Create tables in database
|
||||
# Create tables in database
|
||||
self.__createDatabase()
|
||||
|
||||
def __createDatabase(self):
|
||||
inspector = inspect(engine)
|
||||
existingTable = inspector.get_table_names()
|
||||
if self.Name not in existingTable:
|
||||
@ -139,6 +143,9 @@ class WireguardConfiguration:
|
||||
self.Status = self.__getStatus()
|
||||
return self.__dict__
|
||||
|
||||
def newConfiguration(self):
|
||||
pass
|
||||
|
||||
|
||||
def iPv46RegexCheck(ip):
|
||||
return re.match(
|
||||
@ -447,7 +454,11 @@ def API_AuthenticateLogin():
|
||||
resp.set_cookie("authToken", authToken)
|
||||
session.permanent = True
|
||||
return resp
|
||||
return ResponseObject(False, "Username, password or OTP is incorrect.")
|
||||
|
||||
if totpEnabled:
|
||||
return ResponseObject(False, "Sorry, your username, password or OTP is incorrect.")
|
||||
else:
|
||||
return ResponseObject(False, "Sorry, your username or password is incorrect.")
|
||||
|
||||
|
||||
@app.route('/api/signout')
|
||||
@ -463,6 +474,28 @@ def API_getWireguardConfigurations():
|
||||
return ResponseObject(data=[wc.toJSON() for wc in WireguardConfigurations])
|
||||
|
||||
|
||||
@app.route('/api/addWireguardConfiguration', methods=["POST"])
|
||||
def API_addWireguardConfiguration():
|
||||
data = request.get_json()
|
||||
keys = [
|
||||
"ConfigurationName",
|
||||
"Address",
|
||||
"ListenPort",
|
||||
"PrivateKey",
|
||||
"PublicKey",
|
||||
"PresharedKey",
|
||||
"PreUp",
|
||||
"PreDown",
|
||||
"PostUp",
|
||||
"PostDown",
|
||||
"UsePreSharedKey"
|
||||
]
|
||||
requiredKeys = [
|
||||
"ConfigurationName", "Address", "ListenPort", "PrivateKey"
|
||||
]
|
||||
|
||||
|
||||
|
||||
@app.route('/api/getDashboardConfiguration', methods=["GET"])
|
||||
def API_getDashboardConfiguration():
|
||||
return ResponseObject(data=DashboardConfig.toJSON())
|
||||
|
20
src/static/app/package-lock.json
generated
20
src/static/app/package-lock.json
generated
@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.2",
|
||||
"bootstrap-icons": "^1.11.2",
|
||||
"cidr-tools": "^7.0.4",
|
||||
"pinia": "^2.1.7",
|
||||
"qrcode": "^1.5.3",
|
||||
"uuid": "^9.0.1",
|
||||
@ -755,6 +756,17 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/cidr-tools": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cidr-tools/-/cidr-tools-7.0.4.tgz",
|
||||
"integrity": "sha512-bKd6xC01ObuVKvJPGdV9Rz02KFO3mtHwMe/QTlcVuFAmU5n3RN/F3FgppHZaQjM+c/1i9YB9rgKNH/5iVqwCoA==",
|
||||
"dependencies": {
|
||||
"ip-bigint": "^8.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||
@ -897,6 +909,14 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/ip-bigint": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ip-bigint/-/ip-bigint-8.0.2.tgz",
|
||||
"integrity": "sha512-UMKHGx7+4O2mD/6jnpNtt4UMA0tRQ3XAiNVYlbLssFU1LegKqKwPqbqtLVW7lQU/c6rCWI1hcxxs4TP96Xa+rQ==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
|
@ -11,6 +11,7 @@
|
||||
"dependencies": {
|
||||
"bootstrap": "^5.3.2",
|
||||
"bootstrap-icons": "^1.11.2",
|
||||
"cidr-tools": "^7.0.4",
|
||||
"pinia": "^2.1.7",
|
||||
"qrcode": "^1.5.3",
|
||||
"uuid": "^9.0.1",
|
||||
|
@ -17,9 +17,9 @@ export default {
|
||||
<div class="container">
|
||||
<div class="d-flex mb-4 ">
|
||||
<h3 class="text-body">Wireguard Configurations</h3>
|
||||
<RouterLink to="/new_configuration" class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto rounded-5">
|
||||
<i class="bi bi-plus-circle-fill me-2"></i>
|
||||
<RouterLink to="/new_configuration" class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto rounded-3">
|
||||
Configuration
|
||||
<i class="bi bi-plus-circle-fill ms-2"></i>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<p class="text-muted" v-if="this.wireguardConfigurationsStore.Configurations.length === 0">You don't have any WireGuard configurations yet. Please check the configuration folder or change it in "Settings". By default the folder is "/etc/wireguard".</p>
|
||||
|
313
src/static/app/src/utilities/wireguard.js
Normal file
313
src/static/app/src/utilities/wireguard.js
Normal file
@ -0,0 +1,313 @@
|
||||
/*! SPDX-License-Identifier: GPL-2.0
|
||||
*
|
||||
* Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
function gf(init) {
|
||||
var r = new Float64Array(16);
|
||||
if (init) {
|
||||
for (var i = 0; i < init.length; ++i)
|
||||
r[i] = init[i];
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
function pack(o, n) {
|
||||
var b, m = gf(), t = gf();
|
||||
for (var i = 0; i < 16; ++i)
|
||||
t[i] = n[i];
|
||||
carry(t);
|
||||
carry(t);
|
||||
carry(t);
|
||||
for (var j = 0; j < 2; ++j) {
|
||||
m[0] = t[0] - 0xffed;
|
||||
for (var i = 1; i < 15; ++i) {
|
||||
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
|
||||
m[i - 1] &= 0xffff;
|
||||
}
|
||||
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
|
||||
b = (m[15] >> 16) & 1;
|
||||
m[14] &= 0xffff;
|
||||
cswap(t, m, 1 - b);
|
||||
}
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[2 * i] = t[i] & 0xff;
|
||||
o[2 * i + 1] = t[i] >> 8;
|
||||
}
|
||||
}
|
||||
|
||||
function carry(o) {
|
||||
var c;
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
o[(i + 1) % 16] += (i < 15 ? 1 : 38) * Math.floor(o[i] / 65536);
|
||||
o[i] &= 0xffff;
|
||||
}
|
||||
}
|
||||
|
||||
function cswap(p, q, b) {
|
||||
var t, c = ~(b - 1);
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
t = c & (p[i] ^ q[i]);
|
||||
p[i] ^= t;
|
||||
q[i] ^= t;
|
||||
}
|
||||
}
|
||||
|
||||
function add(o, a, b) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] + b[i]) | 0;
|
||||
}
|
||||
|
||||
function subtract(o, a, b) {
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = (a[i] - b[i]) | 0;
|
||||
}
|
||||
|
||||
function multmod(o, a, b) {
|
||||
var t = new Float64Array(31);
|
||||
for (var i = 0; i < 16; ++i) {
|
||||
for (var j = 0; j < 16; ++j)
|
||||
t[i + j] += a[i] * b[j];
|
||||
}
|
||||
for (var i = 0; i < 15; ++i)
|
||||
t[i] += 38 * t[i + 16];
|
||||
for (var i = 0; i < 16; ++i)
|
||||
o[i] = t[i];
|
||||
carry(o);
|
||||
carry(o);
|
||||
}
|
||||
|
||||
function invert(o, i) {
|
||||
var c = gf();
|
||||
for (var a = 0; a < 16; ++a)
|
||||
c[a] = i[a];
|
||||
for (var a = 253; a >= 0; --a) {
|
||||
multmod(c, c, c);
|
||||
if (a !== 2 && a !== 4)
|
||||
multmod(c, c, i);
|
||||
}
|
||||
for (var a = 0; a < 16; ++a)
|
||||
o[a] = c[a];
|
||||
}
|
||||
|
||||
function clamp(z) {
|
||||
z[31] = (z[31] & 127) | 64;
|
||||
z[0] &= 248;
|
||||
}
|
||||
|
||||
function generatePublicKey(privateKey) {
|
||||
var r, z = new Uint8Array(32);
|
||||
var a = gf([1]),
|
||||
b = gf([9]),
|
||||
c = gf(),
|
||||
d = gf([1]),
|
||||
e = gf(),
|
||||
f = gf(),
|
||||
_121665 = gf([0xdb41, 1]),
|
||||
_9 = gf([9]);
|
||||
for (var i = 0; i < 32; ++i)
|
||||
z[i] = privateKey[i];
|
||||
clamp(z);
|
||||
for (var i = 254; i >= 0; --i) {
|
||||
r = (z[i >>> 3] >>> (i & 7)) & 1;
|
||||
cswap(a, b, r);
|
||||
cswap(c, d, r);
|
||||
add(e, a, c);
|
||||
subtract(a, a, c);
|
||||
add(c, b, d);
|
||||
subtract(b, b, d);
|
||||
multmod(d, e, e);
|
||||
multmod(f, a, a);
|
||||
multmod(a, c, a);
|
||||
multmod(c, b, e);
|
||||
add(e, a, c);
|
||||
subtract(a, a, c);
|
||||
multmod(b, a, a);
|
||||
subtract(c, d, f);
|
||||
multmod(a, c, _121665);
|
||||
add(a, a, d);
|
||||
multmod(c, c, a);
|
||||
multmod(a, d, f);
|
||||
multmod(d, b, _9);
|
||||
multmod(b, e, e);
|
||||
cswap(a, b, r);
|
||||
cswap(c, d, r);
|
||||
}
|
||||
invert(c, c);
|
||||
multmod(a, a, c);
|
||||
pack(z, a);
|
||||
return z;
|
||||
}
|
||||
|
||||
function generatePresharedKey() {
|
||||
var privateKey = new Uint8Array(32);
|
||||
window.crypto.getRandomValues(privateKey);
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
function generatePrivateKey() {
|
||||
var privateKey = generatePresharedKey();
|
||||
clamp(privateKey);
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
function encodeBase64(dest, src) {
|
||||
var input = Uint8Array.from([(src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63]);
|
||||
for (var i = 0; i < 4; ++i)
|
||||
dest[i] = input[i] + 65 +
|
||||
(((25 - input[i]) >> 8) & 6) -
|
||||
(((51 - input[i]) >> 8) & 75) -
|
||||
(((61 - input[i]) >> 8) & 15) +
|
||||
(((62 - input[i]) >> 8) & 3);
|
||||
}
|
||||
|
||||
function keyToBase64(key) {
|
||||
var i, base64 = new Uint8Array(44);
|
||||
for (i = 0; i < 32 / 3; ++i)
|
||||
encodeBase64(base64.subarray(i * 4), key.subarray(i * 3));
|
||||
encodeBase64(base64.subarray(i * 4), Uint8Array.from([key[i * 3 + 0], key[i * 3 + 1], 0]));
|
||||
base64[43] = 61;
|
||||
return String.fromCharCode.apply(null, base64);
|
||||
}
|
||||
|
||||
function base64ToKey(base64) {
|
||||
let binary_string = window.atob(base64);
|
||||
let len = binary_string.length;
|
||||
let bytes = new Uint8Array(len);
|
||||
for (let i = 0; i < len; i++) {
|
||||
bytes[i] = binary_string.charCodeAt(i);
|
||||
}
|
||||
let uint8 = new Uint8Array(bytes.buffer);
|
||||
return uint8;
|
||||
}
|
||||
|
||||
function putU32(b, n)
|
||||
{
|
||||
b.push(n & 0xff, (n >>> 8) & 0xff, (n >>> 16) & 0xff, (n >>> 24) & 0xff);
|
||||
}
|
||||
|
||||
function putU16(b, n)
|
||||
{
|
||||
b.push(n & 0xff, (n >>> 8) & 0xff);
|
||||
}
|
||||
|
||||
function putBytes(b, a)
|
||||
{
|
||||
for (var i = 0; i < a.length; ++i)
|
||||
b.push(a[i] & 0xff);
|
||||
}
|
||||
|
||||
function encodeString(s)
|
||||
{
|
||||
var utf8 = unescape(encodeURIComponent(s));
|
||||
var b = new Uint8Array(utf8.length);
|
||||
for (var i = 0; i < utf8.length; ++i)
|
||||
b[i] = utf8.charCodeAt(i);
|
||||
return b;
|
||||
}
|
||||
|
||||
function crc32(b)
|
||||
{
|
||||
if (!crc32.table) {
|
||||
crc32.table = [];
|
||||
for (var c = 0, n = 0; n < 256; c = ++n) {
|
||||
for (var k = 0; k < 8; ++k)
|
||||
c = ((c & 1) ? (0xedb88320 ^ (c >>> 1)) : (c >>> 1));
|
||||
crc32.table[n] = c;
|
||||
}
|
||||
}
|
||||
var crc = -1;
|
||||
for (var i = 0; i < b.length; ++i)
|
||||
crc = (crc >>> 8) ^ crc32.table[(crc ^ b[i]) & 0xff];
|
||||
return (crc ^ (-1)) >>> 0;
|
||||
}
|
||||
|
||||
function createZipFile(files)
|
||||
{
|
||||
var b = [];
|
||||
var cd = [];
|
||||
var offset = 0;
|
||||
|
||||
for (var i = 0; i < files.length; ++i) {
|
||||
var name = encodeString(files[i].filename);
|
||||
var contents = encodeString(files[i].content);
|
||||
var crc = crc32(contents);
|
||||
|
||||
putU32(b, 0x04034b50); /* signature */
|
||||
putU16(b, 20); /* version needed */
|
||||
putU16(b, 0); /* flags */
|
||||
putU16(b, 0); /* compression method */
|
||||
putU16(b, 0); /* mtime */
|
||||
putU16(b, 0); /* mdate */
|
||||
putU32(b, crc); /* crc32 */
|
||||
putU32(b, contents.length); /* compressed size */
|
||||
putU32(b, contents.length); /* uncompressed size */
|
||||
putU16(b, name.length); /* file name length */
|
||||
putU16(b, 0); /* extra field length */
|
||||
putBytes(b, name);
|
||||
putBytes(b, contents);
|
||||
|
||||
putU32(cd, 0x02014b50); /* signature */
|
||||
putU16(cd, 0); /* version made */
|
||||
putU16(cd, 20); /* version needed */
|
||||
putU16(cd, 0); /* flags */
|
||||
putU16(cd, 0); /* compression method */
|
||||
putU16(cd, 0); /* mtime */
|
||||
putU16(cd, 0); /* mdate */
|
||||
putU32(cd, crc); /* crc32 */
|
||||
putU32(cd, contents.length); /* compressed size */
|
||||
putU32(cd, contents.length); /* uncompressed size */
|
||||
putU16(cd, name.length); /* file name length */
|
||||
putU16(cd, 0); /* extra field length */
|
||||
putU16(cd, 0); /* file comment length */
|
||||
putU16(cd, 0); /* disk number start */
|
||||
putU16(cd, 0); /* internal file attributes */
|
||||
putU32(cd, 32); /* external file attributes - 'archive' bit set (32) */
|
||||
putU32(cd, offset); /* relative offset of local header */
|
||||
putBytes(cd, name); /* file name */
|
||||
|
||||
offset += 30 + contents.length + name.length
|
||||
}
|
||||
putBytes(b, cd); /* central directory */
|
||||
putU32(b, 0x06054b50); /* end of central directory signature */
|
||||
putU16(b, 0); /* number of this disk */
|
||||
putU16(b, 0); /* number of disk with central directory start */
|
||||
putU16(b, files.length); /* number of entries on disk */
|
||||
putU16(b, files.length); /* number of entries */
|
||||
putU32(b, cd.length); /* length of central directory */
|
||||
putU32(b, offset); /* offset to start of central directory */
|
||||
putU16(b, 0); /* zip comment size */
|
||||
return Uint8Array.from(b);
|
||||
}
|
||||
|
||||
window.wireguard = {
|
||||
generateKeypair: function() {
|
||||
var privateKey = generatePrivateKey();
|
||||
var publicKey = generatePublicKey(privateKey);
|
||||
var presharedKey = generatePresharedKey();
|
||||
return {
|
||||
publicKey: keyToBase64(publicKey),
|
||||
privateKey: keyToBase64(privateKey),
|
||||
presharedKey: keyToBase64(presharedKey)
|
||||
};
|
||||
},
|
||||
generatePublicKey: function (privateKey){
|
||||
privateKey = base64ToKey(privateKey);
|
||||
return keyToBase64(generatePublicKey(privateKey));
|
||||
},
|
||||
|
||||
generateZipFiles: function(res){
|
||||
var files = res.peers;
|
||||
var zipFile = createZipFile(files);
|
||||
var blob = new Blob([zipFile], { type: "application/zip" });
|
||||
var a = document.createElement("a");
|
||||
a.download = res.filename;
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.style.display = "none";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
}
|
||||
};
|
||||
})();
|
@ -1,22 +1,250 @@
|
||||
<script>
|
||||
import {parse} from "cidr-tools";
|
||||
import '@/utilities/wireguard.js'
|
||||
import {WireguardConfigurationsStore} from "@/stores/WireguardConfigurationsStore.js";
|
||||
|
||||
export default {
|
||||
name: "newConfiguration"
|
||||
name: "newConfiguration",
|
||||
setup(){
|
||||
const store = WireguardConfigurationsStore()
|
||||
return {store}
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
newConfiguration: {
|
||||
ConfigurationName: "",
|
||||
Address: "",
|
||||
ListenPort: "",
|
||||
PrivateKey: "",
|
||||
PublicKey: "",
|
||||
PresharedKey: "",
|
||||
PreUp: "",
|
||||
PreDown: "",
|
||||
PostUp: "",
|
||||
PostDown: "",
|
||||
UsePreSharedKey: false
|
||||
},
|
||||
numberOfAvailableIPs: "0"
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.wireguardGenerateKeypair();
|
||||
},
|
||||
methods: {
|
||||
wireguardGenerateKeypair(){
|
||||
const wg = window.wireguard.generateKeypair();
|
||||
this.newConfiguration.PrivateKey = wg.privateKey;
|
||||
this.newConfiguration.PublicKey = wg.publicKey;
|
||||
this.newConfiguration.PresharedKey = wg.presharedKey;
|
||||
}
|
||||
},
|
||||
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
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'newConfiguration.Address'(newVal){
|
||||
let ele = document.querySelector("#Address");
|
||||
ele.classList.remove("is-invalid", "is-valid")
|
||||
try{
|
||||
if (newVal.trim().split("/").filter(x => x.length > 0).length !== 2){
|
||||
throw Error()
|
||||
}
|
||||
let p = parse(newVal);
|
||||
let i = p.end - p.start;
|
||||
this.numberOfAvailableIPs = i.toLocaleString();
|
||||
ele.classList.add("is-valid")
|
||||
}catch (e) {
|
||||
this.numberOfAvailableIPs = "0";
|
||||
ele.classList.add("is-invalid")
|
||||
}
|
||||
},
|
||||
'newConfiguration.ListenPort'(newVal){
|
||||
let ele = document.querySelector("#ListenPort");
|
||||
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")
|
||||
}
|
||||
},
|
||||
'newConfiguration.ConfigurationName'(newVal){
|
||||
|
||||
let ele = document.querySelector("#ConfigurationName");
|
||||
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")
|
||||
}
|
||||
},
|
||||
'newConfiguration.PrivateKey'(newVal){
|
||||
let ele = document.querySelector("#PrivateKey");
|
||||
ele.classList.remove("is-invalid", "is-valid")
|
||||
|
||||
try{
|
||||
wireguard.generatePublicKey(newVal)
|
||||
ele.classList.add("is-valid")
|
||||
}catch (e) {
|
||||
ele.classList.add("is-invalid")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mt-4">
|
||||
<div class="container">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="mb-3 d-flex align-items-center gap-4">
|
||||
<RouterLink to="/">
|
||||
<h3 class="mb-0 text-body">
|
||||
<i class="bi bi-chevron-left"></i>
|
||||
</h3>
|
||||
</RouterLink>
|
||||
<h3 class="text-body mb-0">New Configuration</h3>
|
||||
</div>
|
||||
<div class="container mb-4">
|
||||
<div class="mb-4 d-flex align-items-center gap-4">
|
||||
<RouterLink to="/">
|
||||
<h3 class="mb-0 text-body">
|
||||
<i class="bi bi-chevron-left"></i>
|
||||
</h3>
|
||||
</RouterLink>
|
||||
<h3 class="text-body mb-0">New Configuration</h3>
|
||||
</div>
|
||||
|
||||
<form class="text-body d-flex flex-column gap-3">
|
||||
<div class="card rounded-3 shadow">
|
||||
<div class="card-header">Configuration Name</div>
|
||||
<div class="card-body">
|
||||
<input type="text" class="form-control" placeholder="ex. wg1" id="ConfigurationName"
|
||||
v-model="this.newConfiguration.ConfigurationName"
|
||||
required>
|
||||
<div class="invalid-feedback">
|
||||
Configuration name is invalid. Possible reasons:
|
||||
<ul class="mb-0">
|
||||
<li>Configuration name already exist.</li>
|
||||
<li>Configuration name can only contain 15 lower/uppercase alphabet, numbers, "_"(underscore), "="(equal), "+"(plus), "."(period/dot), "-"(dash/hyphen)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card rounded-3 shadow">
|
||||
<div class="card-header">Private Key / Public Key / Pre-Shared Key</div>
|
||||
<div class="card-body" style="font-family: var(--bs-font-monospace)">
|
||||
<div class="mb-2">
|
||||
<label class="text-muted fw-bold mb-1"><small>PRIVATE KEY</small></label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="PrivateKey" required
|
||||
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 class="row">
|
||||
<div class="col-sm">
|
||||
<label class="text-muted fw-bold mb-1"><small>PUBLIC KEY</small></label>
|
||||
<input type="text" class="form-control" id="PublicKey"
|
||||
v-model="this.newConfiguration.PublicKey" disabled
|
||||
>
|
||||
</div>
|
||||
<div class="col-sm" v-if="this.newConfiguration.UsePreSharedKey">
|
||||
<label class="text-muted fw-bold mb-1"><small>PRE-SHARED KEY</small></label>
|
||||
<input type="text" class="form-control" id="PresharedKey"
|
||||
v-model="this.newConfiguration.PresharedKey" disabled
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-check form-switch mt-2">
|
||||
<input class="form-check-input" type="checkbox" role="switch" id="UsePreSharedKey" v-model="this.newConfiguration.UsePreSharedKey">
|
||||
<label class="form-check-label" for="UsePreSharedKey"><small>Use Pre-Shared Key?</small></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="card rounded-3 shadow">
|
||||
<div class="card-header">Listen Port</div>
|
||||
<div class="card-body">
|
||||
<input type="number" class="form-control" placeholder="0-65353" id="ListenPort"
|
||||
min="1"
|
||||
max="65353"
|
||||
v-model="this.newConfiguration.ListenPort"
|
||||
required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card rounded-3 shadow">
|
||||
<div class="card-header d-flex align-items-center">
|
||||
IP Address & Range
|
||||
<span class="badge rounded-pill text-bg-success ms-auto">{{ numberOfAvailableIPs }} Available IPs</span>
|
||||
</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"
|
||||
required>
|
||||
<div class="invalid-feedback">
|
||||
IP address & range is invalid.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
Optional Settings
|
||||
</button>
|
||||
</h2>
|
||||
<div id="newConfigurationOptionalAccordionCollapse"
|
||||
class="accordion-collapse collapse" data-bs-parent="#newConfigurationOptionalAccordion">
|
||||
<div class="accordion-body d-flex flex-column gap-3">
|
||||
<div class="card rounded-3">
|
||||
<div class="card-header">PreUp</div>
|
||||
<div class="card-body">
|
||||
<input type="text" class="form-control" id="preUp" v-model="this.newConfiguration.PreUp">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card rounded-3">
|
||||
<div class="card-header">PreDown</div>
|
||||
<div class="card-body">
|
||||
<input type="text" class="form-control" id="preDown" v-model="this.newConfiguration.PreDown">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card rounded-3">
|
||||
<div class="card-header">PostUp</div>
|
||||
<div class="card-body">
|
||||
<input type="text" class="form-control" id="postUp" v-model="this.newConfiguration.PostUp">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card rounded-3">
|
||||
<div class="card-header">PostDown</div>
|
||||
<div class="card-body">
|
||||
<input type="text" class="form-control" id="postDown" v-model="this.newConfiguration.PostDown">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <RouterLink to="/new_configuration" class="btn btn-success rounded-3 shadow ms-auto rounded-3">-->
|
||||
<!-- <i class="bi bi-save me-2"></i>-->
|
||||
<!-- Save-->
|
||||
<!-- </RouterLink>-->
|
||||
<button class="btn btn-dark btn-brand rounded-3 px-3 py-2 shadow ms-auto" :disabled="!this.goodToSubmit">
|
||||
|
||||
Save Configuration
|
||||
<i class="bi bi-save-fill ms-2"></i>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,51 +0,0 @@
|
||||
const { createApp, ref } = Vue;
|
||||
import Index from './index.js'
|
||||
import Signin from './signin/signin.js'
|
||||
const {createPinia} = Pinia
|
||||
import {cookie} from "./cookie.js";
|
||||
|
||||
const app = createApp({
|
||||
template: `
|
||||
<nav class="navbar bg-dark fixed-top" data-bs-theme="dark">
|
||||
<div class="container-fluid">
|
||||
<span class="navbar-brand mb-0 h1">WGDashboard</span>
|
||||
</div>
|
||||
</nav>
|
||||
<RouterView></RouterView>
|
||||
`
|
||||
});
|
||||
const pinia = createPinia()
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
component: Index,
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/signin', component: Signin
|
||||
}
|
||||
]
|
||||
|
||||
const router = VueRouter.createRouter({
|
||||
// 4. Provide the history implementation to use. We are using the hash history for simplicity here.
|
||||
history: VueRouter.createWebHashHistory(),
|
||||
routes, // short for `routes: routes`
|
||||
});
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
if (to.meta.requiresAuth){
|
||||
if (cookie.getCookie("auth")){
|
||||
next()
|
||||
}else{
|
||||
next("/signin")
|
||||
}
|
||||
}else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
app.use(router);
|
||||
app.use(pinia)
|
||||
app.mount('#app1');
|
@ -1,9 +0,0 @@
|
||||
export const cookie = {
|
||||
|
||||
//https://stackoverflow.com/a/15724300
|
||||
getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
export default {
|
||||
template: `
|
||||
this is idex
|
||||
`
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
export const fetchGet = async (url, params=undefined, callback=undefined) => {
|
||||
const urlSearchParams = new URLSearchParams(params);
|
||||
await fetch(`${url}?${urlSearchParams.toString()}}`, {
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
}
|
||||
})
|
||||
.then(x => x.json())
|
||||
.then(x => callback ? callback(x) : undefined)
|
||||
.catch(() => {
|
||||
alert("Error occurred! Check console")
|
||||
});
|
||||
}
|
||||
|
||||
export const fetchPost = async (url, body, callback) => {
|
||||
await fetch(`${url}`, {
|
||||
headers: {
|
||||
"content-type": "application/json"
|
||||
},
|
||||
method: "POST",
|
||||
body: JSON.stringify(body)
|
||||
})
|
||||
.then(x => x.json())
|
||||
.then(x => callback ? callback(x) : undefined)
|
||||
// .catch(() => {
|
||||
// alert("Error occurred! Check console")
|
||||
// });
|
||||
}
|
@ -1,52 +0,0 @@
|
||||
import {fetchPost} from "./fetch.js";
|
||||
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
username: "",
|
||||
password: ""
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<div class="container-fluid login-container-fluid h-100 d-flex">
|
||||
<div class="login-box m-auto" style="width: 500px;">
|
||||
<h1 class="text-center">Sign in</h1>
|
||||
<h5 class="text-center">to WGDashboard</h5>
|
||||
<div class="m-auto">
|
||||
<div class="alert alert-danger d-none" role="alert" style="margin-top: 1rem; margin-bottom: 0rem;"></div>
|
||||
<div class="form-group">
|
||||
<label for="username" class="text-left" style="font-size: 1rem"><i class="bi bi-person-circle"></i></label>
|
||||
<input type="text" v-model="username" class="form-control" id="username" name="username" placeholder="Username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password" class="text-left" style="font-size: 1rem"><i class="bi bi-key-fill"></i></label>
|
||||
<input type="password" v-model="password" class="form-control" id="password" name="password" placeholder="Password" required>
|
||||
</div>
|
||||
<button class="btn btn-dark w-100 mt-4" @click="this.auth()">Sign In</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
async auth(){
|
||||
if (this.username && this.password){
|
||||
await fetchPost("/auth", {
|
||||
username: this.username,
|
||||
password: this.password
|
||||
}, (response) => {
|
||||
console.log(response)
|
||||
})
|
||||
}else{
|
||||
document.querySelectorAll("input[required]").forEach(x => {
|
||||
if (x.value.length === 0){
|
||||
x.classList.remove("is-valid")
|
||||
x.classList.add("is-invalid")
|
||||
}else{
|
||||
x.classList.remove("is-invalid")
|
||||
x.classList.add("is-valid")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const wgdStore = defineStore('WGDashboardStore', {
|
||||
|
||||
})
|
@ -61,35 +61,55 @@
|
||||
inherits: false;
|
||||
}
|
||||
|
||||
@property --degree{
|
||||
@property --distance2{
|
||||
syntax: '<percentage>';
|
||||
initial-value: 0%;
|
||||
inherits: false;
|
||||
}
|
||||
|
||||
@property --degree{
|
||||
syntax: '<angle>';
|
||||
initial-value: 234deg;
|
||||
inherits: false;
|
||||
}
|
||||
|
||||
.dashboardLogo{
|
||||
background: rgb(23,139,255);
|
||||
background: linear-gradient(234deg, var(--brandColor1) var(--degree), var(--brandColor2) 100%);
|
||||
background: linear-gradient(234deg, var(--brandColor1) var(--distance2), var(--brandColor2) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
transition: --brandColor1 1s, --brandColor2 0.3s, --degree 1s !important;
|
||||
transition: --brandColor1 1s, --brandColor2 0.3s, --distance2 1s !important;
|
||||
|
||||
}
|
||||
|
||||
.btn-brand{
|
||||
/*background: rgb(23,139,255);*/
|
||||
background: linear-gradient(234deg, var(--brandColor1) var(--degree), var(--brandColor2) 100%);
|
||||
background: linear-gradient(var(--degree), var(--brandColor1) var(--distance2), var(--brandColor2) 100%);
|
||||
border: 0 !important;
|
||||
transition: --brandColor1 1s, --brandColor2 1s, --degree 0.5s !important;
|
||||
transition: --brandColor1 1s, --brandColor2 1s, --distance2 0.5s !important;
|
||||
}
|
||||
|
||||
.btn-brand.loading{
|
||||
animation: spin infinite forwards 3s linear;
|
||||
}
|
||||
|
||||
.btn-brand:hover, .dashboardLogo:hover{
|
||||
--brandColor1: #009dff;
|
||||
--brandColor2: #ff875b;
|
||||
--degree: 30%;
|
||||
--distance2: 30%;
|
||||
}
|
||||
|
||||
.signInBtn.signedIn{
|
||||
--degree: 100%;
|
||||
--distance2: 100%;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0%{
|
||||
--degree: 234deg;
|
||||
}
|
||||
100%{
|
||||
--degree: 594deg;
|
||||
}
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"].main,
|
||||
|
Loading…
Reference in New Issue
Block a user