diff --git a/frontend/src/app/apitypes/Record.apitype.ts b/frontend/src/app/apitypes/Record.apitype.ts new file mode 100644 index 0000000..2b73058 --- /dev/null +++ b/frontend/src/app/apitypes/Record.apitype.ts @@ -0,0 +1,20 @@ +export class RecordApitype { + + public id = 0; + + public name = ''; + + public type = ''; + + public content = ''; + + public priority = 0; + + public ttl = 0; + + public domain = 0; + + constructor(init: Object) { + Object.assign(this, init); + } +} diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index f03a371..f94587d 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -1,3 +1,5 @@ +import { EditAuthLineComponent } from './pages/edit-auth/edit-auth-line.component'; +import { RecordsOperation } from './operations/records.operations'; import { LoggedOutGuard } from './services/logged-out-guard.service'; import { NativeGuard } from './services/native-guard.service'; import { SearchComponent } from './partials/search/search.component'; @@ -64,7 +66,8 @@ import { UsersComponent } from './pages/users/users.component'; UsersComponent, EditUserComponent, CreateUserComponent, - SearchComponent + SearchComponent, + EditAuthLineComponent ], imports: [ BrowserModule, @@ -79,6 +82,7 @@ import { UsersComponent } from './pages/users/users.component'; PasswordOperation, DomainsOperation, UsersOperation, + RecordsOperation, AuthGuard, AdminGuard, NativeGuard, diff --git a/frontend/src/app/operations/records.operations.ts b/frontend/src/app/operations/records.operations.ts new file mode 100644 index 0000000..d1a3a4a --- /dev/null +++ b/frontend/src/app/operations/records.operations.ts @@ -0,0 +1,99 @@ +import { RecordApitype } from './../apitypes/Record.apitype'; +import { SoaApitype } from './../apitypes/Soa.apitype'; +import { DomainApitype } from './../apitypes/Domain.apitype'; +import { ListApitype } from './../apitypes/List.apitype'; +import { Injectable } from '@angular/core'; +import { HttpService } from '../services/http.service'; +import { StateService } from '../services/state.service'; +import { SessionApitype } from '../apitypes/Session.apitype'; + +@Injectable() +export class RecordsOperation { + + constructor(private http: HttpService, private gs: StateService) { } + + public async getListForDomain(domainId: number, page?: number, pageSize?: number, queryName?: string, + type?: Array, queryContent?: string, sort?: Array | string, ): Promise> { + try { + return new ListApitype(await this.http.get('/records', { + domain: domainId, + page: page, + pagesize: pageSize, + queryName: queryName, + type: type, + queryContent: queryContent, + sort: sort + })); + } catch (e) { + console.error(e); + return new ListApitype({ paging: {}, results: [] }); + } + } + + public async delete(recordId: number): Promise { + try { + await this.http.delete(['/records', recordId.toString()]); + return true; + } catch (e) { + console.error(e); + return false; + } + } + + public async getSingle(recordId: number): Promise { + try { + return new RecordApitype(await this.http.get(['/records', recordId.toString()])); + } catch (e) { + console.error(e); + return new RecordApitype({}); + } + } + + public async updateRecord(recordId: number, name?: string, type?: string, content?: string, + priority?: number, ttl?: number): Promise { + const data = {}; + if (name !== null && name !== undefined) { + data['name'] = name; + } + if (type !== null && type !== undefined) { + data['type'] = type; + } + if (content !== null && content !== undefined) { + data['content'] = content; + } + if (priority !== null && priority !== undefined) { + data['priority'] = priority; + } + if (ttl !== null && ttl !== undefined) { + data['ttl'] = ttl; + } + + try { + await this.http.put(['/records', recordId.toString()], data); + + return true; + } catch (e) { + console.error(e); + return false; + } + } + + public async create(domainId: number, name: string, type: string, content: string, + priority: number, ttl: number): Promise { + try { + const result = new RecordApitype(await this.http.post('/records', { + name: name, + type: type, + content: content, + priority: priority, + ttl: ttl, + domain: domainId + })); + + return result; + } catch (e) { + console.error(e); + return new RecordApitype({}); + } + } +} diff --git a/frontend/src/app/pages/edit-auth/edit-auth-line.component.html b/frontend/src/app/pages/edit-auth/edit-auth-line.component.html new file mode 100644 index 0000000..caca792 --- /dev/null +++ b/frontend/src/app/pages/edit-auth/edit-auth-line.component.html @@ -0,0 +1,48 @@ + + {{ entry.id }} + + +
{{ fullName() }}
+
+ +
+ {{ domainPart() }} +
+
+ + + {{ inputType.value }} + + + +
{{ inputContent.value }}
+ + + + {{ inputPriority.value }} +
+ +
+ Must be positive integer. +
+
+ + + {{ inputTtl.value }} +
+ +
+ Must be positive integer. +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/frontend/src/app/pages/edit-auth/edit-auth-line.component.ts b/frontend/src/app/pages/edit-auth/edit-auth-line.component.ts new file mode 100644 index 0000000..444c3d7 --- /dev/null +++ b/frontend/src/app/pages/edit-auth/edit-auth-line.component.ts @@ -0,0 +1,81 @@ +import { RecordsOperation } from './../../operations/records.operations'; +import { StateService } from './../../services/state.service'; +import { DomainApitype } from './../../apitypes/Domain.apitype'; +import { FormControl, FormBuilder, Validators } from '@angular/forms'; +import { RecordApitype } from './../../apitypes/Record.apitype'; +import { Component, OnInit, Input, OnChanges, SimpleChanges, EventEmitter, Output } from '@angular/core'; + +@Component({ + // tslint:disable-next-line:component-selector + selector: '[app-edit-auth-line]', + templateUrl: './edit-auth-line.component.html' +}) +export class EditAuthLineComponent implements OnInit, OnChanges { + + @Input() entry: RecordApitype; + @Input() domain: DomainApitype; + + @Output() recordUpdated = new EventEmitter(); + + public editMode = false; + + public inputName: FormControl; + public inputType: FormControl; + public inputContent: FormControl; + public inputPriority: FormControl; + public inputTtl: FormControl; + + constructor(private fb: FormBuilder, public gs: StateService, private records: RecordsOperation) { + this.setupFormControls(); + } + + ngOnInit(): void { + } + + ngOnChanges(changes: SimpleChanges): void { + this.editMode = false; + + this.inputName.reset(this.recordPart()); + this.inputType.reset(this.entry.type); + this.inputContent.reset(this.entry.content); + this.inputPriority.reset(this.entry.priority); + this.inputTtl.reset(this.entry.ttl); + } + + public async setupFormControls() { + this.inputName = this.fb.control(''); + this.inputType = this.fb.control(''); + this.inputContent = this.fb.control(''); + this.inputPriority = this.fb.control('', [Validators.required, Validators.pattern(/^[0-9]+$/)]); + this.inputTtl = this.fb.control('', [Validators.required, Validators.pattern(/^[0-9]+$/)]); + } + + public async onEditClick() { + this.editMode = true; + } + + public domainPart(): string { + return '.' + this.domain.name; + } + + public recordPart(): string { + const pos = this.entry.name.lastIndexOf(this.domain.name); + return this.entry.name.substr(0, pos).replace(/\.$/, ''); + } + + public fullName(): string { + if (this.inputName.value !== '') { + return this.inputName.value + '.' + this.domain.name; + } else { + return this.domain.name; + } + } + + public async onSave() { + await this.records.updateRecord(this.entry.id, this.fullName(), + this.inputType.value, this.inputContent.value, this.inputPriority.value, this.inputTtl.value); + + this.editMode = false; + this.recordUpdated.emit(); + } +} diff --git a/frontend/src/app/pages/edit-auth/edit-auth.component.html b/frontend/src/app/pages/edit-auth/edit-auth.component.html index 0c6e5e3..330e604 100644 --- a/frontend/src/app/pages/edit-auth/edit-auth.component.html +++ b/frontend/src/app/pages/edit-auth/edit-auth.component.html @@ -1,9 +1,9 @@
-

Update SOA data for {{ domainName }}

+

Update SOA data for {{ domain.name }}

-
+
@@ -67,4 +67,59 @@
- \ No newline at end of file + +
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + +
+ ID + + +
+ Name + + +
+
+
+ Type + + +
+
+
+ Content + + +
+
+ Priority + + + TTL + +
+
+
+
+ \ No newline at end of file diff --git a/frontend/src/app/pages/edit-auth/edit-auth.component.ts b/frontend/src/app/pages/edit-auth/edit-auth.component.ts index 8edb7cc..75b5c09 100644 --- a/frontend/src/app/pages/edit-auth/edit-auth.component.ts +++ b/frontend/src/app/pages/edit-auth/edit-auth.component.ts @@ -1,9 +1,16 @@ +import { EditAuthLineComponent } from './edit-auth-line.component'; +import { RecordApitype } from './../../apitypes/Record.apitype'; +import { StateService } from './../../services/state.service'; +import { RecordsOperation } from './../../operations/records.operations'; import { DomainApitype } from './../../apitypes/Domain.apitype'; import { SoaApitype } from './../../apitypes/Soa.apitype'; import { DomainsOperation } from './../../operations/domains.operations'; -import { FormGroup, FormBuilder, Validators } from '@angular/forms'; +import { FormGroup, FormBuilder, Validators, FormControl } from '@angular/forms'; import { ActivatedRoute, ParamMap } from '@angular/router'; import { Component, OnInit } from '@angular/core'; +import { PagingApitype } from '../../apitypes/Paging.apitype'; +import { SortEventDatatype } from '../../datatypes/sort-event.datatype'; +import 'rxjs/add/operator/filter'; @Component({ selector: 'app-edit-auth', @@ -15,11 +22,24 @@ export class EditAuthComponent implements OnInit { public type = ''; - public domainName = ''; + public domain: DomainApitype = new DomainApitype({}); public domainId = 0; - constructor(private route: ActivatedRoute, private fb: FormBuilder, private domains: DomainsOperation) { } + public pagingInfo = new PagingApitype({}); + public pageRequested = 1; + + public recordList: RecordApitype[] = []; + + public sortField = ''; + public sortOrder = 'asc'; + + public queryNameInput: FormControl; + public queryContentInput: FormControl; + public typeFilter: FormControl; + + constructor(private route: ActivatedRoute, private fb: FormBuilder, public gs: StateService, + private domains: DomainsOperation, private records: RecordsOperation) { } ngOnInit() { this.createForm(); @@ -33,7 +53,7 @@ export class EditAuthComponent implements OnInit { this.domainId = +params.get('domainId'); this.domains.getSingle(this.domainId).then((domain: DomainApitype) => { - this.domainName = domain.name; + this.domain = domain; }); this.domains.getSoa(this.domainId).then((soa: SoaApitype) => { @@ -47,6 +67,12 @@ export class EditAuthComponent implements OnInit { serial: soa.serial }); }); + + this.queryNameInput.reset(); + this.queryContentInput.reset(); + + // this triggers also a reload of the records, therefore this function is ommited here + this.typeFilter.reset(); } private async updateSerial() { @@ -66,13 +92,62 @@ export class EditAuthComponent implements OnInit { ttl: ['', [Validators.required, Validators.pattern(/^[0-9]+$/)]], serial: [''] }); + + this.queryNameInput = new FormControl(''); + this.queryNameInput.valueChanges.filter((d) => d !== null).debounceTime(500).subscribe(() => this.loadRecords()); + + this.queryContentInput = new FormControl(''); + this.queryContentInput.valueChanges.filter((d) => d !== null).debounceTime(500).subscribe(() => this.loadRecords()); + + this.typeFilter = new FormControl(null); + this.typeFilter.valueChanges.subscribe(() => this.loadRecords()); } - public async onSubmit() { + public async onSoaSubmit() { const v = this.soaForm.value; await this.domains.setSoa(this.domainId, v.primary, v.email, v.refresh, v.retry, v.expire, v.ttl); this.soaForm.markAsPristine(); await this.updateSerial(); } + public async loadRecords() { + const sortStr = this.sortField !== '' ? this.sortField + '-' + this.sortOrder : null; + const queryName = this.queryNameInput.value !== '' ? this.queryNameInput.value : null; + const queryContent = this.queryContentInput.value !== '' ? this.queryContentInput.value : null; + const typeFilter = this.typeFilter.value; + + const res = await this.records.getListForDomain(this.domainId, this.pageRequested, + this.gs.pageSize, queryName, typeFilter, queryContent, sortStr); + + this.pagingInfo = res.paging; + this.recordList = res.results; + if (res.paging.total < this.pageRequested && res.paging.total > 1) { + this.pageRequested = Math.max(1, res.paging.total); + await this.loadRecords(); + } + } + + public async onPageChange(newPage: number) { + this.pageRequested = newPage; + await this.loadRecords(); + } + + public async onPagesizeChange(pagesize: number) { + this.gs.pageSize = pagesize; + this.pageRequested = 1; + await this.loadRecords(); + } + + public async onSortEvent(sortEvent: SortEventDatatype) { + if (sortEvent.order === 0) { + this.sortField = ''; + this.sortOrder = 'asc'; + } else { + this.sortField = sortEvent.field; + this.sortOrder = sortEvent.order === 1 ? 'asc' : 'desc'; + } + + await this.loadRecords(); + } + } diff --git a/frontend/src/app/partials/select/select.component.html b/frontend/src/app/partials/select/select.component.html index c0ce7f7..012a151 100644 --- a/frontend/src/app/partials/select/select.component.html +++ b/frontend/src/app/partials/select/select.component.html @@ -1,11 +1,13 @@