From 56362da7241f9bca48231f7c023fe86f0818a2d9 Mon Sep 17 00:00:00 2001
From: _Bastler <_Bastler@bstly.de>
Date: Thu, 8 Apr 2021 09:45:50 +0200
Subject: [PATCH] add domain feature
---
src/app/app-routing.module.ts | 4 +-
src/app/app.module.ts | 2 +
src/app/pages/account/account.component.html | 8 +-
src/app/pages/account/account.component.ts | 6 +-
.../account/domains/domains.component.html | 59 ++++++++
.../account/domains/domains.component.scss | 31 ++++
.../account/domains/domains.component.spec.ts | 25 ++++
.../account/domains/domains.component.ts | 132 ++++++++++++++++++
src/app/services/userdomain.service.ts | 30 ++++
src/assets/i18n/de-informal.json | 30 +++-
src/assets/i18n/en.json | 30 +++-
11 files changed, 342 insertions(+), 15 deletions(-)
create mode 100644 src/app/pages/account/domains/domains.component.html
create mode 100644 src/app/pages/account/domains/domains.component.scss
create mode 100644 src/app/pages/account/domains/domains.component.spec.ts
create mode 100644 src/app/pages/account/domains/domains.component.ts
create mode 100644 src/app/services/userdomain.service.ts
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index 16da065..dc1c834 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -21,6 +21,7 @@ import {UnavailableComponent} from './pages/unavailable/unavailable.component';
import {NotfoundComponent} from './pages/notfound/notfound.component';
import {UserComponent} from './pages/user/user.component'
import {AliasesComponent} from './pages/account/aliases/aliases.component';
+import {DomainsComponent} from './pages/account/domains/domains.component';
const routes: Routes = [
{path: '', redirectTo: "/services", pathMatch: 'full'},
@@ -41,7 +42,8 @@ const routes: Routes = [
{path: 'profile', component: ProfileComponent, canActivate: [AuthenticatedGuard]},
{path: 'security', component: SecurityComponent, 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]},
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 05bd79b..5f57fef 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -26,6 +26,7 @@ import {VoucherComponent} from './pages/account/voucher/voucher.component';
import {VoucherDialog} from './pages/account/voucher/voucher.component';
import {InfoComponent} from './pages/account/info/info.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 {PasswordComponent} from './pages/password/password.component';
import {PasswordResetComponent} from './pages/password-reset/password-reset.component';
@@ -77,6 +78,7 @@ export class XhrInterceptor implements HttpInterceptor {
VoucherDialog,
InfoComponent,
AliasesComponent,
+ DomainsComponent,
ProfileComponent,
PasswordComponent,
PasswordResetComponent,
diff --git a/src/app/pages/account/account.component.html b/src/app/pages/account/account.component.html
index 7537e54..c56c37e 100644
--- a/src/app/pages/account/account.component.html
+++ b/src/app/pages/account/account.component.html
@@ -6,7 +6,13 @@
{{'profile' | i18n}}
{{'security' | i18n}}
{{'vouchers' | i18n}}
- {{'user.aliases' | i18n}}
+ {{'user.aliases' | i18n}}
+ {{'user.domains' | i18n}}
+
+
+ {{'account.advanced' | i18n}}
+
+
\ No newline at end of file
diff --git a/src/app/pages/account/account.component.ts b/src/app/pages/account/account.component.ts
index 32c23bf..7175b5f 100644
--- a/src/app/pages/account/account.component.ts
+++ b/src/app/pages/account/account.component.ts
@@ -1,6 +1,6 @@
-import { Component, OnInit } from '@angular/core';
+import {Component, OnInit} from '@angular/core';
-import { AuthService } from './../../services/auth.service';
+import {AuthService} from './../../services/auth.service';
@Component({
selector: 'app-account',
@@ -9,7 +9,7 @@ import { AuthService } from './../../services/auth.service';
})
export class AccountComponent implements OnInit {
-
+ advancedView: boolean = false;
auth;
constructor(private authService: AuthService) {
diff --git a/src/app/pages/account/domains/domains.component.html b/src/app/pages/account/domains/domains.component.html
new file mode 100644
index 0000000..249c7b5
--- /dev/null
+++ b/src/app/pages/account/domains/domains.component.html
@@ -0,0 +1,59 @@
+
{{'user.domains' | i18n}}
+
+
+
+
+ {{'user.domains.domain' | i18n}} |
+ {{ domain.domain}} |
+
+
+
+
+ {{'user.domains.secret' | i18n}} |
+
+ {{ domain.secret}}
+ |
+
+
+
+
+ {{'user.domains.validated' | i18n}} |
+
+
+ |
+
+
+
+ {{'user.domains.delete' | i18n}} |
+
+
+ delete
+
+ |
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/app/pages/account/domains/domains.component.scss b/src/app/pages/account/domains/domains.component.scss
new file mode 100644
index 0000000..864fb8b
--- /dev/null
+++ b/src/app/pages/account/domains/domains.component.scss
@@ -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;
+}
diff --git a/src/app/pages/account/domains/domains.component.spec.ts b/src/app/pages/account/domains/domains.component.spec.ts
new file mode 100644
index 0000000..9df4628
--- /dev/null
+++ b/src/app/pages/account/domains/domains.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DomainsComponent } from './domains.component';
+
+describe('DomainsComponent', () => {
+ let component: DomainsComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ DomainsComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DomainsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/pages/account/domains/domains.component.ts b/src/app/pages/account/domains/domains.component.ts
new file mode 100644
index 0000000..2060639
--- /dev/null
+++ b/src/app/pages/account/domains/domains.component.ts
@@ -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);
+ }
+}
diff --git a/src/app/services/userdomain.service.ts b/src/app/services/userdomain.service.ts
new file mode 100644
index 0000000..3f13b67
--- /dev/null
+++ b/src/app/services/userdomain.service.ts
@@ -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);
+ }
+
+}
\ No newline at end of file
diff --git a/src/assets/i18n/de-informal.json b/src/assets/i18n/de-informal.json
index d66526e..f05576f 100644
--- a/src/assets/i18n/de-informal.json
+++ b/src/assets/i18n/de-informal.json
@@ -1,5 +1,10 @@
{
- "account": "Account",
+ "account": {
+ ".": "Account",
+ "advanced": {
+ ".": "Erweitert"
+ }
+ },
"cancel": "Abbrechen",
"close": "Schließen",
"confirm": "Bestätigen",
@@ -315,10 +320,25 @@
"create": "Alternativen Namen anlegen",
"delete": "Löschen",
"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.",
"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": {
".": "Zugriff verweigert",
"text": "Dieser Benutzer existiert nicht oder du hast keine Berechtigungen, um auf das Profil zuzugreifen."
@@ -333,15 +353,15 @@
".": "Sichtbarkeit",
"PRIVATE": {
".": "Privat",
- "icon" : "lock"
+ "icon": "lock"
},
"PROTECTED": {
".": "Geschützt",
- "icon" : "shield"
+ "icon": "shield"
},
"PUBLIC": {
".": "Öffentlich",
- "icon" : "public"
+ "icon": "public"
}
},
"voucher": {
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index bcc4310..2b827b6 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -1,5 +1,10 @@
{
- "account": "Account",
+ "account": {
+ ".": "Account",
+ "advanced": {
+ ".": "Advanced"
+ }
+ },
"cancel": "Cancel",
"close": "Close",
"confirm": "Confirm",
@@ -230,7 +235,7 @@
"text": "You can add additional aliases besides your username.",
"title": "Aliases"
},
- "empty" : "You have insufficient permissions to use any services.",
+ "empty": "You have insufficient permissions to use any services.",
"gitea": {
"icon": "code",
"subtitle": "Git-Repositories",
@@ -319,6 +324,21 @@
"left": "You have {0} Alias(es) left.",
"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": {
".": "Access denied",
"text": "The provided user does not exists or you have insufficient privileges to access profile."
@@ -333,15 +353,15 @@
".": "Visibility",
"PRIVATE": {
".": "Private",
- "icon" : "lock"
+ "icon": "lock"
},
"PROTECTED": {
".": "Protected",
- "icon" : "shield"
+ "icon": "shield"
},
"PUBLIC": {
".": "Public",
- "icon" : "public"
+ "icon": "public"
}
},
"voucher": {