add domain feature
This commit is contained in:
parent
288bdb3956
commit
56362da724
@ -21,6 +21,7 @@ import {UnavailableComponent} from './pages/unavailable/unavailable.component';
|
|||||||
import {NotfoundComponent} from './pages/notfound/notfound.component';
|
import {NotfoundComponent} from './pages/notfound/notfound.component';
|
||||||
import {UserComponent} from './pages/user/user.component'
|
import {UserComponent} from './pages/user/user.component'
|
||||||
import {AliasesComponent} from './pages/account/aliases/aliases.component';
|
import {AliasesComponent} from './pages/account/aliases/aliases.component';
|
||||||
|
import {DomainsComponent} from './pages/account/domains/domains.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: '', redirectTo: "/services", pathMatch: 'full'},
|
{path: '', redirectTo: "/services", pathMatch: 'full'},
|
||||||
@ -41,7 +42,8 @@ const routes: Routes = [
|
|||||||
{path: 'profile', component: ProfileComponent, canActivate: [AuthenticatedGuard]},
|
{path: 'profile', component: ProfileComponent, canActivate: [AuthenticatedGuard]},
|
||||||
{path: 'security', component: SecurityComponent, canActivate: [AuthenticatedGuard]},
|
{path: 'security', component: SecurityComponent, canActivate: [AuthenticatedGuard]},
|
||||||
{path: 'voucher', component: VoucherComponent, canActivate: [AuthenticatedGuard]},
|
{path: 'voucher', component: VoucherComponent, canActivate: [AuthenticatedGuard]},
|
||||||
{path: 'aliases', component: AliasesComponent, canActivate: [AuthenticatedGuard]}
|
{path: 'aliases', component: AliasesComponent, canActivate: [AuthenticatedGuard]},
|
||||||
|
{path: 'domains', component: DomainsComponent, canActivate: [AuthenticatedGuard]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{path: 'register', component: RegisterComponent, canActivate: [AnonymousGuard]},
|
{path: 'register', component: RegisterComponent, canActivate: [AnonymousGuard]},
|
||||||
|
@ -26,6 +26,7 @@ import {VoucherComponent} from './pages/account/voucher/voucher.component';
|
|||||||
import {VoucherDialog} from './pages/account/voucher/voucher.component';
|
import {VoucherDialog} from './pages/account/voucher/voucher.component';
|
||||||
import {InfoComponent} from './pages/account/info/info.component';
|
import {InfoComponent} from './pages/account/info/info.component';
|
||||||
import {AliasesComponent} from './pages/account/aliases/aliases.component';
|
import {AliasesComponent} from './pages/account/aliases/aliases.component';
|
||||||
|
import {DomainsComponent} from './pages/account/domains/domains.component';
|
||||||
import {ProfileComponent} from './pages/account/profile/profile.component';
|
import {ProfileComponent} from './pages/account/profile/profile.component';
|
||||||
import {PasswordComponent} from './pages/password/password.component';
|
import {PasswordComponent} from './pages/password/password.component';
|
||||||
import {PasswordResetComponent} from './pages/password-reset/password-reset.component';
|
import {PasswordResetComponent} from './pages/password-reset/password-reset.component';
|
||||||
@ -77,6 +78,7 @@ export class XhrInterceptor implements HttpInterceptor {
|
|||||||
VoucherDialog,
|
VoucherDialog,
|
||||||
InfoComponent,
|
InfoComponent,
|
||||||
AliasesComponent,
|
AliasesComponent,
|
||||||
|
DomainsComponent,
|
||||||
ProfileComponent,
|
ProfileComponent,
|
||||||
PasswordComponent,
|
PasswordComponent,
|
||||||
PasswordResetComponent,
|
PasswordResetComponent,
|
||||||
|
@ -6,7 +6,13 @@
|
|||||||
<a mat-tab-link routerLink="profile" routerLinkActive="active">{{'profile' | i18n}}</a>
|
<a mat-tab-link routerLink="profile" routerLinkActive="active">{{'profile' | i18n}}</a>
|
||||||
<a mat-tab-link routerLink="security" routerLinkActive="active">{{'security' | i18n}}</a>
|
<a mat-tab-link routerLink="security" routerLinkActive="active">{{'security' | i18n}}</a>
|
||||||
<a mat-tab-link routerLink="voucher" routerLinkActive="active">{{'vouchers' | i18n}}</a>
|
<a mat-tab-link routerLink="voucher" routerLinkActive="active">{{'vouchers' | i18n}}</a>
|
||||||
<a mat-tab-link routerLink="aliases" routerLinkActive="active">{{'user.aliases' | i18n}}</a>
|
<a *ngIf="advancedView" mat-tab-link routerLink="aliases" routerLinkActive="active">{{'user.aliases' | i18n}}</a>
|
||||||
|
<a *ngIf="advancedView" mat-tab-link routerLink="domains" routerLinkActive="active">{{'user.domains' | i18n}}</a>
|
||||||
|
<a mat-tab-link>
|
||||||
|
<mat-slide-toggle [(ngModel)]="advancedView">
|
||||||
|
<span *ngIf="!advancedView">{{'account.advanced' | i18n}}</span>
|
||||||
|
</mat-slide-toggle>
|
||||||
|
</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
@ -9,7 +9,7 @@ import { AuthService } from './../../services/auth.service';
|
|||||||
})
|
})
|
||||||
export class AccountComponent implements OnInit {
|
export class AccountComponent implements OnInit {
|
||||||
|
|
||||||
|
advancedView: boolean = false;
|
||||||
auth;
|
auth;
|
||||||
|
|
||||||
constructor(private authService: AuthService) {
|
constructor(private authService: AuthService) {
|
||||||
|
59
src/app/pages/account/domains/domains.component.html
Normal file
59
src/app/pages/account/domains/domains.component.html
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<h3>{{'user.domains' | i18n}}</h3>
|
||||||
|
|
||||||
|
<table mat-table matSort [dataSource]="domains" (matSortChange)="sortData($event)">
|
||||||
|
|
||||||
|
<ng-container matColumnDef="domain">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="domain"> {{'user.domains.domain' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let domain"> {{ domain.domain}} </td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
|
<ng-container matColumnDef="secret">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="secret"> {{'user.domains.secret' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let domain">
|
||||||
|
<a mat-button class="secret" (click)="copySecret(domain)" matTooltip="{{'user.domains.secret.copy' | i18n}}"
|
||||||
|
matTooltipPosition="above">{{ domain.secret}}</a>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
|
<ng-container matColumnDef="validated">
|
||||||
|
<th mat-header-cell *matHeaderCellDef mat-sort-header="validated"> {{'user.domains.validated' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let domain">
|
||||||
|
<mat-slide-toggle [checked]="domain.validated" disabled></mat-slide-toggle>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container matColumnDef="delete">
|
||||||
|
<th mat-header-cell *matHeaderCellDef class="align-right"> {{'user.domains.delete' | i18n}} </th>
|
||||||
|
<td mat-cell *matCellDef="let domain" class="text-right">
|
||||||
|
<a mat-icon-button>
|
||||||
|
<mat-icon (click)="confirmDelete(domain)">delete</mat-icon>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<tr mat-header-row *matHeaderRowDef="domainsColumns"></tr>
|
||||||
|
<tr mat-row *matRowDef="let myRowData; columns: domainsColumns"></tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<form [formGroup]="form" (ngSubmit)="create()" #formDirective="ngForm">
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-content>
|
||||||
|
<p>{{'user.domains.info' | i18n}}</p>
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput placeholder="{{'user.domains.domain' | i18n}}" formControlName="domain"
|
||||||
|
[(ngModel)]="domain.domain" required>
|
||||||
|
<mat-error>
|
||||||
|
{{'user.domains.error' | i18n}}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</mat-card-content>
|
||||||
|
<mat-card-actions>
|
||||||
|
<button *ngIf="!working" mat-raised-button color="primary" [disabled]="form.invalid">
|
||||||
|
{{'user.domains.create' | i18n}}
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
</form>
|
31
src/app/pages/account/domains/domains.component.scss
Normal file
31
src/app/pages/account/domains/domains.component.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
mat-form-field {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-header-cell,
|
||||||
|
.mat-cell {
|
||||||
|
&.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.align-right {
|
||||||
|
display: flex;
|
||||||
|
padding: 21px 0;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secret {
|
||||||
|
width: 200px;
|
||||||
|
max-width: 200px;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secret:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
margin-right: 25px;
|
||||||
|
}
|
25
src/app/pages/account/domains/domains.component.spec.ts
Normal file
25
src/app/pages/account/domains/domains.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { DomainsComponent } from './domains.component';
|
||||||
|
|
||||||
|
describe('DomainsComponent', () => {
|
||||||
|
let component: DomainsComponent;
|
||||||
|
let fixture: ComponentFixture<DomainsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
declarations: [ DomainsComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DomainsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
132
src/app/pages/account/domains/domains.component.ts
Normal file
132
src/app/pages/account/domains/domains.component.ts
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import {Component, OnInit, ViewChild} from '@angular/core';
|
||||||
|
import {I18nService} from '../../../services/i18n.service';
|
||||||
|
import {Sort} from '@angular/material/sort';
|
||||||
|
import {UserDomainService} from '../../../services/userdomain.service';
|
||||||
|
import {FormBuilder, FormGroup, Validators, NgForm} from '@angular/forms';
|
||||||
|
import {MatDialog} from '@angular/material/dialog';
|
||||||
|
import {ConfirmDialog} from '../../../ui/confirm/confirm.component';
|
||||||
|
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-account-domains',
|
||||||
|
templateUrl: './domains.component.html',
|
||||||
|
styleUrls: ['./domains.component.scss']
|
||||||
|
})
|
||||||
|
export class DomainsComponent implements OnInit {
|
||||||
|
|
||||||
|
form: FormGroup;
|
||||||
|
@ViewChild('formDirective') private formDirective: NgForm;
|
||||||
|
domains: any[] = [];
|
||||||
|
domain: any = {
|
||||||
|
visibility: "PRIVATE"
|
||||||
|
};
|
||||||
|
success: boolean;
|
||||||
|
working: boolean;
|
||||||
|
|
||||||
|
domainsColumns = ["domain", "secret", "validated", "delete"];
|
||||||
|
visibilities = ["PRIVATE", "PROTECTED", "PUBLIC"];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private formBuilder: FormBuilder,
|
||||||
|
private userDomainService: UserDomainService,
|
||||||
|
private i18n: I18nService,
|
||||||
|
private snackBar: MatSnackBar,
|
||||||
|
public dialog: MatDialog) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.form = this.formBuilder.group({
|
||||||
|
domain: ['', Validators.required],
|
||||||
|
// visibility: ['', Validators.required],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
create(): void {
|
||||||
|
this.working = true;
|
||||||
|
this.userDomainService.create(this.domain).subscribe(response => {
|
||||||
|
this.update();
|
||||||
|
this.formDirective.resetForm();
|
||||||
|
this.domain = {
|
||||||
|
visibility: "PRIVATE"
|
||||||
|
};
|
||||||
|
this.working = false;
|
||||||
|
}, (error) => {
|
||||||
|
this.working = false;
|
||||||
|
if(error.status == 409) {
|
||||||
|
let errors = {};
|
||||||
|
for(let code of error.error) {
|
||||||
|
errors[code.field] = errors[code.field] || {};
|
||||||
|
errors[code.field][code.code] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let code in errors) {
|
||||||
|
this.form.get(code).setErrors(errors[code]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDomain(domain) {
|
||||||
|
this.userDomainService.update(domain).subscribe((response: any) => {
|
||||||
|
domain = response;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.userDomainService.get().subscribe((data: any) => {
|
||||||
|
this.domains = data;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
confirmDelete(domain) {
|
||||||
|
const dialogRef = this.dialog.open(ConfirmDialog, {
|
||||||
|
data: {
|
||||||
|
'label': 'user.domains.confirmDelete',
|
||||||
|
'args': [domain.domain]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(result => {
|
||||||
|
if(result) {
|
||||||
|
this.userDomainService.delete(domain.id).subscribe((result: any) => {
|
||||||
|
this.update();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
copySecret(domain: any) {
|
||||||
|
const selBox = document.createElement('textarea');
|
||||||
|
selBox.value = domain.secret;
|
||||||
|
document.body.appendChild(selBox);
|
||||||
|
selBox.focus();
|
||||||
|
selBox.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(selBox);
|
||||||
|
this.snackBar.open(this.i18n.get("user.domains.secret.copied", []), this.i18n.get("close", []), {
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sortData(sort: Sort) {
|
||||||
|
const data = this.domains.slice();
|
||||||
|
if(!sort.active || sort.direction === '') {
|
||||||
|
this.domains = data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.domains = data.sort((a, b) => {
|
||||||
|
const isAsc = sort.direction === 'asc';
|
||||||
|
switch(sort.active) {
|
||||||
|
case 'domain': return this.compare(a.domain, b.domain, isAsc);
|
||||||
|
case 'validated': return this.compare(a.validated, b.validated, isAsc);
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
compare(a: number | string | String, b: number | string | String, isAsc: boolean) {
|
||||||
|
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||||
|
}
|
||||||
|
}
|
30
src/app/services/userdomain.service.ts
Normal file
30
src/app/services/userdomain.service.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {HttpClient} from '@angular/common/http';
|
||||||
|
|
||||||
|
import {environment} from '../../environments/environment';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root',
|
||||||
|
})
|
||||||
|
export class UserDomainService {
|
||||||
|
|
||||||
|
constructor(private http: HttpClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
get() {
|
||||||
|
return this.http.get(environment.apiUrl + "/users/domains");
|
||||||
|
}
|
||||||
|
|
||||||
|
create(alias) {
|
||||||
|
return this.http.post(environment.apiUrl + "/users/domains", alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(alias) {
|
||||||
|
return this.http.patch(environment.apiUrl + "/users/domains", alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(id) {
|
||||||
|
return this.http.delete(environment.apiUrl + "/users/domains/" + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"account": "Account",
|
"account": {
|
||||||
|
".": "Account",
|
||||||
|
"advanced": {
|
||||||
|
".": "Erweitert"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"confirm": "Bestätigen",
|
"confirm": "Bestätigen",
|
||||||
@ -315,10 +320,25 @@
|
|||||||
"create": "Alternativen Namen anlegen",
|
"create": "Alternativen Namen anlegen",
|
||||||
"delete": "Löschen",
|
"delete": "Löschen",
|
||||||
"error": "Dieser alternative Name ist leider nicht zulässig.",
|
"error": "Dieser alternative Name ist leider nicht zulässig.",
|
||||||
"info": "Du kannst hier alternative Namen erstellen. Die Anzahl wird über eine Quota begrenzt.",
|
"info": "Du kannst hier alternative Namen anlegen. Die Anzahl wird über eine Quota begrenzt.",
|
||||||
"left": "Du kannst noch {0} alternative(n) Namen anlegen.",
|
"left": "Du kannst noch {0} alternative(n) Namen anlegen.",
|
||||||
"noQuota": "Deine Quota für alternative Namen ist leider aufgebraucht."
|
"noQuota": "Deine Quota für alternative Namen ist leider aufgebraucht."
|
||||||
},
|
},
|
||||||
|
"domains": {
|
||||||
|
".": "Domains",
|
||||||
|
"domain": "Domain",
|
||||||
|
"confirmDelete": "Möchtest du wirklich die Domain '{0}' löschen?",
|
||||||
|
"create": "Domain hinzufügen",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"error": "Diese Domain ist leider nicht zulässig.",
|
||||||
|
"info": "Du kannst hier Domains hinzufügen.",
|
||||||
|
"secret": {
|
||||||
|
".": "Secret",
|
||||||
|
"copy": "Secret in die Zwischenablage kopieren",
|
||||||
|
"copied": "In die Zwischenablage kopiert"
|
||||||
|
},
|
||||||
|
"validated": "Validiert"
|
||||||
|
},
|
||||||
"unavailable": {
|
"unavailable": {
|
||||||
".": "Zugriff verweigert",
|
".": "Zugriff verweigert",
|
||||||
"text": "Dieser Benutzer existiert nicht oder du hast keine Berechtigungen, um auf das Profil zuzugreifen."
|
"text": "Dieser Benutzer existiert nicht oder du hast keine Berechtigungen, um auf das Profil zuzugreifen."
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
{
|
{
|
||||||
"account": "Account",
|
"account": {
|
||||||
|
".": "Account",
|
||||||
|
"advanced": {
|
||||||
|
".": "Advanced"
|
||||||
|
}
|
||||||
|
},
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"confirm": "Confirm",
|
"confirm": "Confirm",
|
||||||
@ -319,6 +324,21 @@
|
|||||||
"left": "You have {0} Alias(es) left.",
|
"left": "You have {0} Alias(es) left.",
|
||||||
"noQuota": "Your quota for Aliases is depleted."
|
"noQuota": "Your quota for Aliases is depleted."
|
||||||
},
|
},
|
||||||
|
"domains": {
|
||||||
|
".": "Domains",
|
||||||
|
"domain": "Domain",
|
||||||
|
"confirmDelete": "Are you sure you want to delete your domain '{0}'?",
|
||||||
|
"create": "Add Domain",
|
||||||
|
"delete": "Delete",
|
||||||
|
"error": "This domain is invalid.",
|
||||||
|
"info": "You can add Domains here.",
|
||||||
|
"secret": {
|
||||||
|
".": "Secret",
|
||||||
|
"copy": "Copy secret to clipboard",
|
||||||
|
"copied": "Copied to clipboard"
|
||||||
|
},
|
||||||
|
"validated": "Validated"
|
||||||
|
},
|
||||||
"unavailable": {
|
"unavailable": {
|
||||||
".": "Access denied",
|
".": "Access denied",
|
||||||
"text": "The provided user does not exists or you have insufficient privileges to access profile."
|
"text": "The provided user does not exists or you have insufficient privileges to access profile."
|
||||||
|
Loading…
Reference in New Issue
Block a user