Browse Source

Added import support

pull/4/head
Per-Arne 5 years ago
parent
commit
93cd5dbf3a
  1. 2
      wg_dashboard_frontend/package.json
  2. 2
      wg_dashboard_frontend/src/app/app.module.ts
  3. 26
      wg_dashboard_frontend/src/app/layout/layout.module.ts
  4. 4
      wg_dashboard_frontend/src/app/layout/layout/layout.component.html
  5. 23
      wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.html
  6. 181
      wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.ts
  7. 7
      wg_dashboard_frontend/src/app/services/config.service.ts
  8. 5
      wg_dashboard_frontend/src/app/services/server.service.ts
  9. 2
      wg_dashboard_frontend/src/theme/styles.scss

2
wg_dashboard_frontend/package.json

@ -40,7 +40,9 @@
"@angular/platform-browser": "9.1.0", "@angular/platform-browser": "9.1.0",
"@angular/platform-browser-dynamic": "9.1.0", "@angular/platform-browser-dynamic": "9.1.0",
"@angular/router": "9.1.0", "@angular/router": "9.1.0",
"@jedmao/ini-parser": "^0.2.4",
"angular-material-dynamic-themes": "^1.0.4", "angular-material-dynamic-themes": "^1.0.4",
"angular-notifier": "^6.0.1",
"angularx-qrcode": "^2.1.0", "angularx-qrcode": "^2.1.0",
"classlist.js": "1.1.20150312", "classlist.js": "1.1.20150312",
"core-js": "3.6.4", "core-js": "3.6.4",

2
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 { MatListModule } from '@angular/material/list';
import { FlexLayoutModule } from '@angular/flex-layout'; import { FlexLayoutModule } from '@angular/flex-layout';
import {CookieService} from "ngx-cookie-service"; import {CookieService} from "ngx-cookie-service";
import {NotifierModule} from "angular-notifier";
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -39,6 +40,7 @@ import {CookieService} from "ngx-cookie-service";
MatSidenavModule, MatSidenavModule,
MatListModule, MatListModule,
FlexLayoutModule, FlexLayoutModule,
NotifierModule
], ],
providers: [ providers: [

26
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 { RouterModule } from '@angular/router';
import { MatMenuModule } from '@angular/material/menu'; import { MatMenuModule } from '@angular/material/menu';
import {MatSlideToggleModule} from "@angular/material/slide-toggle"; import {MatSlideToggleModule} from "@angular/material/slide-toggle";
import {NotifierModule} from "angular-notifier";
@NgModule({ @NgModule({
declarations: [LayoutComponent], declarations: [LayoutComponent],
imports: [ imports: [
CommonModule, CommonModule,
MatSidenavModule, MatSidenavModule,
MatToolbarModule, MatToolbarModule,
MatListModule, MatListModule,
MatIconModule, MatIconModule,
MatButtonModule, MatButtonModule,
FlexLayoutModule, FlexLayoutModule,
RouterModule, RouterModule,
MatMenuModule, MatMenuModule,
MatSlideToggleModule, MatSlideToggleModule,
], NotifierModule,
],
exports: [ exports: [
], ],

4
wg_dashboard_frontend/src/app/layout/layout/layout.component.html

@ -79,7 +79,5 @@
</mat-sidenav-content> </mat-sidenav-content>
</mat-sidenav-container> </mat-sidenav-container>
<notifier-container></notifier-container>
</div> </div>

23
wg_dashboard_frontend/src/app/page/dashboard/add-server/add-server.component.html

@ -1,4 +1,20 @@
<mat-card class="dashboard-card"> <mat-card class="dashboard-card">
<mat-card-title class="card-container-left">
Add WireGuard Server
</mat-card-title>
<mat-card-title class="card-container-right">
<input #confInput hidden="true" type="file" multiple onclick="this.value=null" (change)="parseFiles($event)" accept=".conf"/>
<button
mat-flat-button
color="primary"
(click)="confInput.click()"
matTooltip="Import existing wireguard configuration. You can select both server and peer configuration. The number of imported peers are described near the submit button."
>Import Configuration</button>
</mat-card-title>
<mat-card-content class="dashboard-card-content"> <mat-card-content class="dashboard-card-content">
<form [formGroup]="serverForm" class="add-server-form"> <form [formGroup]="serverForm" class="add-server-form">
@ -94,10 +110,14 @@
<ng-container *ngIf="isEdit">Edit Server</ng-container> <ng-container *ngIf="isEdit">Edit Server</ng-container>
</button> </button>
<button mat-raised-button color="warn" (click)="isEdit = false; serverForm.reset()"> <button mat-raised-button color="warn" (click)="resetForm()">
Reset Reset
</button> </button>
<div *ngIf="this.serverForm.value['peers'] && this.serverForm.value['peers'].length > 0">
Importing <b>{{this.serverForm.value['peers'].length}}</b> peers.
</div>
</div> </div>
</form> </form>
@ -105,6 +125,5 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

181
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 { Server } from '../../../interfaces/server';
import { ServerService } from '../../../services/server.service'; import { ServerService } from '../../../services/server.service';
import { DataService } from '../../../services/data.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({ @Component({
selector: 'app-add-server', selector: 'app-add-server',
templateUrl: './add-server.component.html', templateUrl: './add-server.component.html',
@ -15,28 +19,50 @@ export class AddServerComponent implements OnInit {
@Input() servers: Server[]; @Input() servers: Server[];
serverForm = new FormGroup({ // Translates from wg configuration keywords to form and backend keywords
address: new FormControl('', [IPValidator.isIPAddress]), wgConfTranslation = {
interface: new FormControl('', [Validators.required, Validators.minLength(3)]), "Address": "address",
listen_port: new FormControl('', [Validators.required, NumberValidator.stringIsNumber]), "PrivateKey": "private_key",
endpoint: new FormControl('', Validators.required), "ListenPort": "listen_port",
private_key: new FormControl('' ), "PostUp": "post_up",
public_key: new FormControl('' ), "PostDown": "post_down",
shared_key: new FormControl('' ), "PublicKey": "public_key",
post_up: new FormControl(''),
post_down: new FormControl(''), // Peer
"Endpoint": "endpoint",
// Unused on backend "AllowedIPs": "allowed_ips",
configuration: new FormControl(''), "DNS": "dns"
is_running: new FormControl(false), }
peers: new FormControl([]),
}); serverForm: FormGroup = null;
isEdit = false; isEdit = false;
editServer: Server = null; 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 { ngOnInit(): void {
this.initForm();
this.comm.on('server-edit').subscribe((data: Server) => { this.comm.on('server-edit').subscribe((data: Server) => {
this.isEdit = true; 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) { add(form: Server) {
if (this.isEdit) { if (this.isEdit) {
@ -84,4 +222,11 @@ export class AddServerComponent implements OnInit {
}); });
}); });
} }
resetForm() {
this.isEdit = false;
this.editServer = null;
this.initForm()
}
} }

7
wg_dashboard_frontend/src/app/services/config.service.ts

@ -1,20 +1,22 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { throwError } from 'rxjs'; import { throwError } from 'rxjs';
import {NotifierService} from "angular-notifier";
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class ConfigService { export class ConfigService {
public applicationName = 'Wireguard Manager'; public applicationName = 'WireGuard Manager';
constructor() { } constructor(private notify: NotifierService) { }
public handleError(error: HttpErrorResponse) { public handleError(error: HttpErrorResponse) {
if (error.error instanceof ErrorEvent) { if (error.error instanceof ErrorEvent) {
// A client-side or network error occurred. Handle it accordingly. // A client-side or network error occurred. Handle it accordingly.
console.error('An error occurred:', error.error.message); console.error('An error occurred:', error.error.message);
this.notify.notify("error", error.error.message)
} else { } else {
// The backend returned an unsuccessful response code. // The backend returned an unsuccessful response code.
// The response body may contain clues as to what went wrong, // The response body may contain clues as to what went wrong,
@ -22,6 +24,7 @@ export class ConfigService {
`Backend returned code ${error.status}, ` + `Backend returned code ${error.status}, ` +
`body was: ${error.error}`); `body was: ${error.error}`);
} }
this.notify.notify("error", error.error.detail)
// return an observable with a user-facing error message // return an observable with a user-facing error message
return throwError( return throwError(
'Something bad happened; please try again later.'); 'Something bad happened; please try again later.');

5
wg_dashboard_frontend/src/app/services/server.service.ts

@ -6,6 +6,7 @@ import { catchError } from 'rxjs/operators';
import { Server } from '../interfaces/server'; import { Server } from '../interfaces/server';
import { Peer } from '../interfaces/peer'; import { Peer } from '../interfaces/peer';
import { Observable, Subscribable } from 'rxjs'; import { Observable, Subscribable } from 'rxjs';
import {NotifierService} from "angular-notifier";
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@ -17,7 +18,7 @@ export class ServerService {
public wgURL = this.base + "wg"; 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<Server> { public serverPerformAction(action: string, item: any): Subscribable<Server> {
return this.http.post(this.serverURL + '/' + action, item) 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<Peer> { public addPeer(server_interface: any): Subscribable<Peer> {

2
wg_dashboard_frontend/src/theme/styles.scss

@ -2,7 +2,7 @@
$material-icons-font-path: '~material-icons/iconfont/'; $material-icons-font-path: '~material-icons/iconfont/';
@import '~material-icons/iconfont/material-icons.scss'; @import '~material-icons/iconfont/material-icons.scss';
@import "~angular-notifier/styles";
@import '~@angular/material/theming'; @import '~@angular/material/theming';
@include mat-core(); @include mat-core();

Loading…
Cancel
Save