diff --git a/wg_dashboard_frontend/package.json b/wg_dashboard_frontend/package.json index 9dc3cf6..03dfce1 100644 --- a/wg_dashboard_frontend/package.json +++ b/wg_dashboard_frontend/package.json @@ -40,7 +40,9 @@ "@angular/platform-browser": "9.1.0", "@angular/platform-browser-dynamic": "9.1.0", "@angular/router": "9.1.0", + "@jedmao/ini-parser": "^0.2.4", "angular-material-dynamic-themes": "^1.0.4", + "angular-notifier": "^6.0.1", "angularx-qrcode": "^2.1.0", "classlist.js": "1.1.20150312", "core-js": "3.6.4", diff --git a/wg_dashboard_frontend/src/app/app.module.ts b/wg_dashboard_frontend/src/app/app.module.ts index aef8a7d..67eec4b 100644 --- a/wg_dashboard_frontend/src/app/app.module.ts +++ b/wg_dashboard_frontend/src/app/app.module.ts @@ -18,6 +18,7 @@ import { MatSidenavModule } from '@angular/material/sidenav'; import { MatListModule } from '@angular/material/list'; import { FlexLayoutModule } from '@angular/flex-layout'; import {CookieService} from "ngx-cookie-service"; +import {NotifierModule} from "angular-notifier"; @NgModule({ declarations: [ @@ -39,6 +40,7 @@ import {CookieService} from "ngx-cookie-service"; MatSidenavModule, MatListModule, FlexLayoutModule, + NotifierModule ], providers: [ diff --git a/wg_dashboard_frontend/src/app/layout/layout.module.ts b/wg_dashboard_frontend/src/app/layout/layout.module.ts index 11319b9..8cce19f 100644 --- a/wg_dashboard_frontend/src/app/layout/layout.module.ts +++ b/wg_dashboard_frontend/src/app/layout/layout.module.ts @@ -11,21 +11,23 @@ import { FlexLayoutModule } from '@angular/flex-layout'; import { RouterModule } from '@angular/router'; import { MatMenuModule } from '@angular/material/menu'; import {MatSlideToggleModule} from "@angular/material/slide-toggle"; +import {NotifierModule} from "angular-notifier"; @NgModule({ declarations: [LayoutComponent], - imports: [ - CommonModule, - MatSidenavModule, - MatToolbarModule, - MatListModule, - MatIconModule, - MatButtonModule, - FlexLayoutModule, - RouterModule, - MatMenuModule, - MatSlideToggleModule, - ], + imports: [ + CommonModule, + MatSidenavModule, + MatToolbarModule, + MatListModule, + MatIconModule, + MatButtonModule, + FlexLayoutModule, + RouterModule, + MatMenuModule, + MatSlideToggleModule, + NotifierModule, + ], exports: [ ], diff --git a/wg_dashboard_frontend/src/app/layout/layout/layout.component.html b/wg_dashboard_frontend/src/app/layout/layout/layout.component.html index d6b567c..d156832 100644 --- a/wg_dashboard_frontend/src/app/layout/layout/layout.component.html +++ b/wg_dashboard_frontend/src/app/layout/layout/layout.component.html @@ -79,7 +79,5 @@ - - - + diff --git a/wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.html b/wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.html index 9cbab1d..897a5be 100644 --- a/wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.html +++ b/wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.html @@ -1,4 +1,20 @@ + + + Add WireGuard Server + + + + + Import Configuration + + + @@ -94,10 +110,14 @@ Edit Server - + Reset + 0"> + Importing {{this.serverForm.value['peers'].length}} peers. + + @@ -105,6 +125,5 @@ - diff --git a/wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.ts b/wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.ts index 46721fc..760d8d2 100644 --- a/wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.ts +++ b/wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.ts @@ -5,7 +5,11 @@ import { NumberValidator } from '../../../validators/number.validator'; import { Server } from '../../../interfaces/server'; import { ServerService } from '../../../services/server.service'; import { DataService } from '../../../services/data.service'; - +import Parser, {Property, Section, Sections} from "@jedmao/ini-parser"; +import {Peer} from "../../../interfaces/peer"; +import {forkJoin, from, Observable, of} from "rxjs"; +import {concatAll, concatMap, filter, map, mergeAll, mergeMap, switchMap} from "rxjs/operators"; +import {NotifierService} from "angular-notifier"; @Component({ selector: 'app-add-server', templateUrl: './add-server.component.html', @@ -15,28 +19,50 @@ export class AddServerComponent implements OnInit { @Input() servers: Server[]; - serverForm = new FormGroup({ - address: new FormControl('', [IPValidator.isIPAddress]), - interface: new FormControl('', [Validators.required, Validators.minLength(3)]), - listen_port: new FormControl('', [Validators.required, NumberValidator.stringIsNumber]), - endpoint: new FormControl('', Validators.required), - private_key: new FormControl('' ), - public_key: new FormControl('' ), - shared_key: new FormControl('' ), - post_up: new FormControl(''), - post_down: new FormControl(''), - - // Unused on backend - configuration: new FormControl(''), - is_running: new FormControl(false), - peers: new FormControl([]), - }); + // Translates from wg configuration keywords to form and backend keywords + wgConfTranslation = { + "Address": "address", + "PrivateKey": "private_key", + "ListenPort": "listen_port", + "PostUp": "post_up", + "PostDown": "post_down", + "PublicKey": "public_key", + + // Peer + "Endpoint": "endpoint", + "AllowedIPs": "allowed_ips", + "DNS": "dns" + } + + serverForm: FormGroup = null; isEdit = false; editServer: Server = null; - constructor(private serverAPI: ServerService, private comm: DataService) { } + initForm(){ + this.serverForm = new FormGroup({ + address: new FormControl('', [IPValidator.isIPAddress]), + interface: new FormControl('', [Validators.required, Validators.minLength(3)]), + listen_port: new FormControl('', [Validators.required, NumberValidator.stringIsNumber]), + endpoint: new FormControl('', Validators.required), + private_key: new FormControl('' ), + public_key: new FormControl('' ), + shared_key: new FormControl('' ), + post_up: new FormControl(''), + post_down: new FormControl(''), + + // Unused on backend + configuration: new FormControl(''), + is_running: new FormControl(false), + peers: new FormControl([]), + }); + } + + constructor(private serverAPI: ServerService, private comm: DataService, private notify: NotifierService) { + + } ngOnInit(): void { + this.initForm(); this.comm.on('server-edit').subscribe((data: Server) => { this.isEdit = true; @@ -47,6 +73,118 @@ export class AddServerComponent implements OnInit { } + parseFiles($event){ + const files: File[] = $event.target.files; + + let observables = [] + Array.from(files).forEach( (file: File) => { + + let obs = from([file]) + .pipe(map(x => from(x.text()))) + .pipe(mergeMap(x => x)) + .pipe(map((x) => new Parser().parse(x).items)) + .pipe(map((x: Section[]) => x.filter(y => y.name !== "" && y.name != null))) + .pipe(map( (x: Section[]) => { + + let data: any = {} + // Store filename + data.fileName = file.name; + + // Convert nodes to key-value dict + x.forEach( (y: any) => { + y.nodes = y.nodes.reduce((result, filter) => { + result[this.wgConfTranslation[filter["key"]]] = filter["value"]; + return result; + },{}); + }) + data.sections = x; + + // Look for endpoint in peer configuration. TODO - Better way? + data.isClient = data.sections + .filter( section => Object.keys(section.nodes).find( nk => nk === "endpoint")) + .length > 0; + + // 'Detect' if its a client + return data + })); + observables.push(obs); + }); + + forkJoin(observables).subscribe(data => { + let server: any = data.filter((x: any) => !x.isClient); + + if(server.length !== 1) { + // TODO output error - should only be one server + this.notify.notify("error", "Detected multiple server files!") + return false; + } + server = server[0]; + + const peers = data.filter((x: any) => x.isClient); + //console.log(peers) + this.importProcessServer(server); + peers.forEach( peer => { + this.importProcessPeer(peer); + }) + }) + + } + + importProcessServer(server) { + let iFace: any = server.sections.find(x => x.name == "Interface") + const sPeers = server.sections.filter(x => x.name == "Peer"); + + if(iFace === null){ + // TODO error out - should have [interface] on server + this.notify.notify("error", "Could not find [Interface] section") + return false; + } + + iFace.nodes["peers"] = sPeers + .map( x => x.nodes) + .map( x => { + x.server_id = -1; + x.address = x.allowed_ips; // Allowed_ips in server is the address of the peer (Seen from server perspective) + x.allowed_ips = null; // This should be retrieved from peer data config + return x; + }) + this.serverForm.patchValue({ + interface: server.fileName.replace(".conf", "") + }) + this.serverForm.patchValue(iFace.nodes) + + + } + + importProcessPeer(peer){ + let formPeers = this.serverForm.controls.peers.value; + let iFace: any = peer.sections.find(x => x.name == "Interface") + const sPeers = peer.sections.filter(x => x.name == "Peer"); + + if(sPeers.length > 1) { + // TODO not supported for multi-server peers + this.notify.notify("error", "Multi-server peers are not supported! Peer " + peer.fileName + + " will be imported partially.") + + return false; + } + let sPeer = sPeers[0]; + + let formPeer = formPeers + .find(x => x.address.split("/")[0] === iFace.nodes.address.split("/")[0]) + formPeer.name = peer.fileName; + formPeer.private_key = iFace.nodes.private_key; + formPeer.allowed_ips = sPeer.nodes.allowed_ips; + formPeer.dns = iFace.nodes.dns; + this.serverForm.patchValue({ + endpoint: sPeer.nodes.endpoint.split(":")[0], + public_key: sPeer.nodes.public_key + + }) + } + + + add(form: Server) { if (this.isEdit) { @@ -84,4 +222,11 @@ export class AddServerComponent implements OnInit { }); }); } + + resetForm() { + this.isEdit = false; + this.editServer = null; + + this.initForm() + } } diff --git a/wg_dashboard_frontend/src/app/services/config.service.ts b/wg_dashboard_frontend/src/app/services/config.service.ts index a3fbab9..cc87719 100644 --- a/wg_dashboard_frontend/src/app/services/config.service.ts +++ b/wg_dashboard_frontend/src/app/services/config.service.ts @@ -1,20 +1,22 @@ import { Injectable } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; import { throwError } from 'rxjs'; +import {NotifierService} from "angular-notifier"; @Injectable({ providedIn: 'root', }) export class ConfigService { - public applicationName = 'Wireguard Manager'; + public applicationName = 'WireGuard Manager'; - constructor() { } + constructor(private notify: NotifierService) { } public handleError(error: HttpErrorResponse) { if (error.error instanceof ErrorEvent) { // A client-side or network error occurred. Handle it accordingly. console.error('An error occurred:', error.error.message); + this.notify.notify("error", error.error.message) } else { // The backend returned an unsuccessful response code. // The response body may contain clues as to what went wrong, @@ -22,6 +24,7 @@ export class ConfigService { `Backend returned code ${error.status}, ` + `body was: ${error.error}`); } + this.notify.notify("error", error.error.detail) // return an observable with a user-facing error message return throwError( 'Something bad happened; please try again later.'); diff --git a/wg_dashboard_frontend/src/app/services/server.service.ts b/wg_dashboard_frontend/src/app/services/server.service.ts index b4bb038..db8fa00 100644 --- a/wg_dashboard_frontend/src/app/services/server.service.ts +++ b/wg_dashboard_frontend/src/app/services/server.service.ts @@ -6,6 +6,7 @@ import { catchError } from 'rxjs/operators'; import { Server } from '../interfaces/server'; import { Peer } from '../interfaces/peer'; import { Observable, Subscribable } from 'rxjs'; +import {NotifierService} from "angular-notifier"; @Injectable({ providedIn: 'root', @@ -17,7 +18,7 @@ export class ServerService { public wgURL = this.base + "wg"; - constructor(private config: ConfigService, private http: HttpClient) { + constructor(private config: ConfigService, private http: HttpClient, private notify: NotifierService) { } @@ -27,7 +28,7 @@ export class ServerService { public serverPerformAction(action: string, item: any): Subscribable { return this.http.post(this.serverURL + '/' + action, item) - .pipe(catchError(this.config.handleError.bind(this))); + .pipe(catchError(this.config.handleError)); } public addPeer(server_interface: any): Subscribable { diff --git a/wg_dashboard_frontend/src/theme/styles.scss b/wg_dashboard_frontend/src/theme/styles.scss index 2a32a31..fcfde47 100644 --- a/wg_dashboard_frontend/src/theme/styles.scss +++ b/wg_dashboard_frontend/src/theme/styles.scss @@ -2,7 +2,7 @@ $material-icons-font-path: '~material-icons/iconfont/'; @import '~material-icons/iconfont/material-icons.scss'; - +@import "~angular-notifier/styles"; @import '~@angular/material/theming'; @include mat-core();