This commit is contained in:
Lurkars
2021-03-11 19:52:37 +01:00
parent cf46cf6fd0
commit c4321d99cb
41 changed files with 1389 additions and 471 deletions
+5 -2
View File
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { AuthGuard, AuthUpdateGuard, AuthenticatedGuard, AnonymousGuard } from './auth/auth.guard';
import { HomeComponent, ImprintComponent, PrivacyPolicyComponent } from './pages/home/home.component';
import { HomeComponent, ImprintComponent, PrivacyPolicyComponent, TermsOfServiceComponent } from './pages/home/home.component';
import { HomeClubComponent } from './pages/home/club/home-club.component';
import { HomeGeneralComponent } from './pages/home/general/home-general.component';
import { HomePrivacyComponent } from './pages/home/privacy/home-privacy.component';
@@ -18,13 +18,14 @@ import { RegisterComponent } from './pages/register/register.component';
import { TokensComponent } from './pages/tokens/tokens.component';
import { AppsComponent } from './pages/apps/apps.component';
import { InfoComponent } from './pages/account/info/info.component';
import { ProfileComponent } from './pages/account/profile/profile.component';
import { VoucherComponent } from './pages/account/voucher/voucher.component';
import { SecurityComponent } from './pages/account/security/security.component';
import { UnavailableComponent } from './pages/unavailable/unavailable.component';
import { NotfoundComponent } from './pages/notfound/notfound.component';
const routes: Routes = [
{ path: '', redirectTo: "/general", pathMatch: 'full' },
{ path: '', redirectTo: "/account", pathMatch: 'full' },
{
path: '', component: HomeComponent, canActivate: [AuthUpdateGuard], runGuardsAndResolvers: 'always', children: [
{ path: 'general', component: HomeGeneralComponent, canActivate: [AuthUpdateGuard] },
@@ -35,6 +36,7 @@ const routes: Routes = [
},
{ path: 'imprint', component: ImprintComponent, canActivate: [AuthUpdateGuard] },
{ path: 'privacy-policy', component: PrivacyPolicyComponent, canActivate: [AuthUpdateGuard] },
{ path: 'terms-of-service', component: TermsOfServiceComponent, canActivate: [AuthUpdateGuard] },
{ path: 'login', component: LoginComponent, canActivate: [AnonymousGuard] },
{ path: 'login/totp', component: LoginTotpComponent, canActivate: [AnonymousGuard] },
{ path: 'external-login', component: FormLoginComponent, canActivate: [AnonymousGuard] },
@@ -46,6 +48,7 @@ const routes: Routes = [
path: 'account', component: AccountComponent, canActivate: [AuthenticatedGuard], children: [
{ path: 'info', component: InfoComponent, canActivate: [AuthenticatedGuard] },
{ path: 'profile', component: ProfileComponent, canActivate: [AuthenticatedGuard] },
{ path: 'voucher', component: VoucherComponent, canActivate: [AuthenticatedGuard] },
{ path: 'security', component: SecurityComponent, canActivate: [AuthenticatedGuard] }
]
-3
View File
@@ -24,9 +24,6 @@
<mat-sidenav #sidenav [mode]="isBiggerScreen() ? 'side' : 'over'" [(opened)]="opened"
(click)="!isBiggerScreen() && opened=false">
<mat-nav-list>
<a routerLink="/general" mat-list-item>
<mat-icon>home</mat-icon> {{'home' | i18n}}
</a>
<a *ngIf="!auth || auth && !auth.authenticated" routerLink="/login" routerLinkActive="active" mat-list-item>
<mat-icon>login</mat-icon> {{'login' | i18n}}
</a>
+16 -11
View File
@@ -1,10 +1,11 @@
import { Component, HostListener } from '@angular/core';
import {Component, HostListener} from '@angular/core';
import { AuthService } from './services/auth.service';
import { I18nService } from './services/i18n.service';
import { Router } from '@angular/router';
import { DomSanitizer } from '@angular/platform-browser';
import { MatIconRegistry } from '@angular/material/icon';
import {AuthService} from './services/auth.service';
import {I18nService} from './services/i18n.service';
import {Router} from '@angular/router';
import {DomSanitizer} from '@angular/platform-browser';
import {MatIconRegistry} from '@angular/material/icon';
import {DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE} from '@angular/material/core';
@Component({
selector: 'app-root',
@@ -19,19 +20,21 @@ export class AppComponent {
locales;
auth;
constructor(private i18n: I18nService, private authService: AuthService, private router: Router, private iconRegistry: MatIconRegistry, private sanitizer: DomSanitizer) {
constructor(private i18n: I18nService, private authService: AuthService, private router: Router, private iconRegistry: MatIconRegistry, private sanitizer: DomSanitizer, private _adapter: DateAdapter<any>) {
this.currentLocale = this.i18n.getLocale();
this.locales = this.i18n.getLocales();
this.authService.auth.subscribe(data => {
this.auth = data;
})
this._adapter.setLocale(this.currentLocale);
iconRegistry.addSvgIcon('logo', sanitizer.bypassSecurityTrustResourceUrl('assets/icons/logo.svg'));
}
ngOnInit() {
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
if (width < 768) {
if(width < 768) {
this.opened = false;
} else {
this.opened = true;
@@ -45,13 +48,15 @@ export class AppComponent {
logout() {
this.authService.logout().subscribe(data => {
this.router.navigate([""]);
this.router.navigate([""]).then(() => {
window.location.reload();
});
})
}
isBiggerScreen() {
const width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
if (width < 768) {
if(width < 768) {
return false;
} else {
return true;
@@ -60,7 +65,7 @@ export class AppComponent {
@HostListener('window:resize', ['$event'])
onResize(event) {
if (event.target.innerWidth < 768) {
if(event.target.innerWidth < 768) {
this.opened = false;
} else {
this.opened = true;
+43 -39
View File
@@ -1,47 +1,49 @@
import { NgModule, Injectable, APP_INITIALIZER } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { HttpClientModule, HttpInterceptor, HttpHandler, HttpRequest, HTTP_INTERCEPTORS } from '@angular/common/http';
import { MaterialModule } from './material/material.module';
import { QRCodeModule } from 'angularx-qrcode';
import {NgModule, Injectable, APP_INITIALIZER} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser';
import {AppRoutingModule} from './app-routing.module';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {HttpClientModule, HttpInterceptor, HttpHandler, HttpRequest, HTTP_INTERCEPTORS} from '@angular/common/http';
import {MaterialModule} from './material/material.module';
import {QRCodeModule} from 'angularx-qrcode';
import { I18nPipe } from './utils/i18n.pipe';
import { HomeComponent, ImprintComponent, PrivacyPolicyComponent } from './pages/home/home.component';
import { HomeClubComponent } from './pages/home/club/home-club.component';
import { HomeGeneralComponent } from './pages/home/general/home-general.component';
import { HomePrivacyComponent } from './pages/home/privacy/home-privacy.component';
import { HomeServicesComponent } from './pages/home/services/home-services.component';
import { AccountComponent } from './pages/account/account.component';
import { AppsComponent } from './pages/apps/apps.component';
import { AppComponent } from './app.component';
import { LoginComponent } from './pages/login/login.component';
import { LoginTotpComponent } from './pages/login-totp/login-totp.component';
import { FormLoginComponent } from './pages/form-login/form-login.component';
import { FormLoginTotpComponent } from './pages/form-login-totp/form-login-totp.component';
import { TokensComponent } from './pages/tokens/tokens.component';
import { PermissionsComponent } from './ui/permissions/permissions.component';
import { QuotasComponent } from './ui/quotas/quotas.component';
import { SecurityComponent, SecurityTotpDialog } from './pages/account/security/security.component';
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 { PasswordComponent } from './pages/password/password.component';
import { PasswordResetComponent } from './pages/password-reset/password-reset.component';
import { RegisterComponent, RegisterDialog } from './pages/register/register.component';
import { UsernameDialog } from './pages/register/username-dialog/username.dialog';
import { UnavailableComponent } from './pages/unavailable/unavailable.component';
import { NotfoundComponent } from './pages/notfound/notfound.component';
import { HtmlComponent } from './utils/html/html.component';
import {I18nPipe} from './utils/i18n.pipe';
import {HomeComponent, ImprintComponent, PrivacyPolicyComponent, TermsOfServiceComponent} from './pages/home/home.component';
import {HomeClubComponent} from './pages/home/club/home-club.component';
import {HomeGeneralComponent} from './pages/home/general/home-general.component';
import {HomePrivacyComponent} from './pages/home/privacy/home-privacy.component';
import {HomeServicesComponent} from './pages/home/services/home-services.component';
import {AccountComponent} from './pages/account/account.component';
import {AppsComponent} from './pages/apps/apps.component';
import {AppComponent} from './app.component';
import {LoginComponent} from './pages/login/login.component';
import {LoginTotpComponent} from './pages/login-totp/login-totp.component';
import {FormLoginComponent} from './pages/form-login/form-login.component';
import {FormLoginTotpComponent} from './pages/form-login-totp/form-login-totp.component';
import {TokensComponent} from './pages/tokens/tokens.component';
import {PermissionsComponent} from './ui/permissions/permissions.component';
import {ProfileFieldDialog, ProfileFieldsComponent, ProfileFieldBlob} from './ui/profilefields/profilefields.component';
import {QuotasComponent} from './ui/quotas/quotas.component';
import {SecurityComponent, SecurityTotpDialog} from './pages/account/security/security.component';
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 {ProfileComponent} from './pages/account/profile/profile.component';
import {PasswordComponent} from './pages/password/password.component';
import {PasswordResetComponent} from './pages/password-reset/password-reset.component';
import {RegisterComponent, RegisterDialog} from './pages/register/register.component';
import {UsernameDialog} from './pages/register/username-dialog/username.dialog';
import {UnavailableComponent} from './pages/unavailable/unavailable.component';
import {NotfoundComponent} from './pages/notfound/notfound.component';
import {HtmlComponent} from './utils/html/html.component';
import { I18nService } from './services/i18n.service';
import {I18nService} from './services/i18n.service';
export function init_app(i18n: I18nService) {
return () => i18n.fetch(i18n.getLocale()).then(response => { }, error => { });
return () => i18n.fetch(i18n.getLocale()).then(response => {}, error => {});
}
@Injectable()
@@ -59,7 +61,7 @@ export class XhrInterceptor implements HttpInterceptor {
declarations: [
I18nPipe,
AppComponent,
HomeComponent, ImprintComponent, PrivacyPolicyComponent,
HomeComponent, ImprintComponent, PrivacyPolicyComponent, TermsOfServiceComponent,
HomeClubComponent,
HomeGeneralComponent,
HomePrivacyComponent,
@@ -72,12 +74,14 @@ export class XhrInterceptor implements HttpInterceptor {
TokensComponent,
AppsComponent,
PermissionsComponent,
ProfileFieldsComponent, ProfileFieldDialog, ProfileFieldBlob,
QuotasComponent,
SecurityComponent,
SecurityTotpDialog,
VoucherComponent,
VoucherDialog,
InfoComponent,
ProfileComponent,
PasswordComponent,
PasswordResetComponent,
RegisterComponent,
@@ -98,7 +102,7 @@ export class XhrInterceptor implements HttpInterceptor {
QRCodeModule,
],
exports: [MaterialModule],
providers: [{ provide: APP_INITIALIZER, useFactory: init_app, deps: [I18nService], multi: true }, { provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor, multi: true }],
providers: [{provide: APP_INITIALIZER, useFactory: init_app, deps: [I18nService], multi: true}, {provide: HTTP_INTERCEPTORS, useClass: XhrInterceptor, multi: true}],
bootstrap: [AppComponent],
})
export class AppModule {
+3 -1
View File
@@ -42,6 +42,7 @@ import { MatTooltipModule } from '@angular/material/tooltip';
import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSortModule } from '@angular/material/sort';
import { MatTableModule } from '@angular/material/table';
import { MatMomentDateModule } from '@angular/material-moment-adapter';
@NgModule({
declarations: [],
@@ -81,7 +82,8 @@ import { MatTableModule } from '@angular/material/table';
MatTooltipModule,
MatPaginatorModule,
MatSortModule,
MatTableModule
MatTableModule,
MatMomentDateModule
],
exports: [
MatAutocompleteModule,
@@ -3,6 +3,7 @@
<nav mat-tab-nav-bar>
<a mat-tab-link routerLink="info" routerLinkActive="active">{{'info' | i18n}}</a>
<a mat-tab-link routerLink="profile" routerLinkActive="active">{{'profile' | i18n}}</a>
<a mat-tab-link routerLink="voucher" routerLinkActive="active">{{'vouchers' | i18n}}</a>
<a mat-tab-link routerLink="security" routerLinkActive="active">{{'security' | i18n}}</a>
</nav>
@@ -1,4 +1,6 @@
<h3>{{'permissions' | i18n}}</h3>
<app-permissions [permissions]="permissions"></app-permissions>
<h3>{{'quotas' | i18n}}</h3>
<app-quotas [quotas]="quotas"></app-quotas>
<app-quotas [quotas]="quotas"></app-quotas>
<h3>{{'profile' | i18n}}</h3>
<app-profilefields [profileFields]="profileFields"></app-profilefields>
+7 -1
View File
@@ -1,6 +1,7 @@
import { Component, OnInit } from '@angular/core';
import { PermissionService } from './../../../services/permission.service';
import { QuotaService } from './../../../services/quota.service';
import { ProfileService } from './../../../services/profile.service';
@Component({
selector: 'app-account-info',
@@ -11,8 +12,9 @@ export class InfoComponent implements OnInit {
permissions = [];
quotas = [];
profileFields = [];
constructor(private permissionService: PermissionService, private quotaService: QuotaService) { }
constructor(private permissionService: PermissionService, private quotaService: QuotaService, private profileService : ProfileService) { }
ngOnInit(): void {
this.permissionService.permissions().subscribe((data: any) => {
@@ -22,6 +24,10 @@ export class InfoComponent implements OnInit {
this.quotaService.quotas().subscribe((data: any) => {
this.quotas = data;
})
this.profileService.getAll().subscribe((data: any) => {
this.profileFields = data;
})
}
}
@@ -0,0 +1 @@
<app-profilefields [profileFields]="profileFields" [edit]="true"></app-profilefields>
@@ -0,0 +1,8 @@
table {
width: 100%;
}
td.mat-cell {
padding: 12px;
}
@@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ProfileComponent} from './profile.component';
describe('ProfileComponent', () => {
let component: ProfileComponent;
let fixture: ComponentFixture<ProfileComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ProfileComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,24 @@
import {Component, OnInit} from '@angular/core';
import {ProfileService} from '../../../services/profile.service';
import {I18nService} from '../../../services/i18n.service';
@Component({
selector: 'app-account-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss']
})
export class ProfileComponent implements OnInit {
profileFields = [];
types = ["TEXT", "NUMBER", "DATE", "URL", "EMAIL", "BOOL"];
visibilities = ["PRIVATE", "PROTECTED", "PUBLIC"];
constructor(private profileService: ProfileService, private i18n: I18nService) {}
ngOnInit(): void {
this.profileService.getAll().subscribe((data: any) => {
this.profileFields = data;
})
}
}
@@ -12,5 +12,5 @@
</div>
<div mat-dialog-actions>
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
<button [disabled]="code.invalid" mat-raised-button [mat-dialog-close]="code.value" cdkFocusInitial color="accent">{{'security.2fa.totp.enable' | i18n}}</button>
<button [disabled]="code.invalid" mat-raised-button [mat-dialog-close]="code.value" color="accent">{{'security.2fa.totp.enable' | i18n}}</button>
</div>
@@ -23,7 +23,7 @@
{{'home.club.charter' | i18n}}
</mat-panel-title>
</mat-expansion-panel-header>
<app-html [template]="'club/charter'"></app-html>
<app-html class="text-justify" [template]="'club/charter'"></app-html>
</mat-expansion-panel>
</mat-accordion>
+14
View File
@@ -41,4 +41,18 @@ export class PrivacyPolicyComponent implements OnInit {
ngOnInit(): void {
}
}
@Component({
selector: 'app-terms-of-service',
templateUrl: './home.terms-of-service.html'
})
export class TermsOfServiceComponent implements OnInit {
constructor() {
}
ngOnInit(): void {
}
}
@@ -0,0 +1 @@
<app-html [template]="'terms-of-service'"></app-html>
+44 -26
View File
@@ -1,15 +1,15 @@
import { Component, OnInit, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Router } from '@angular/router';
import {Component, OnInit, Inject, ViewChild, ElementRef} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
import {Router} from '@angular/router';
import { UserService } from './../../services/user.service';
import { ItemService } from './../../services/item.service';
import { I18nService } from './../../services/i18n.service';
import { MatchingValidator } from './../../utils/matching.validator';
import {UserService} from './../../services/user.service';
import {ItemService} from './../../services/item.service';
import {I18nService} from './../../services/i18n.service';
import {MatchingValidator} from './../../utils/matching.validator';
import { uniqueNamesGenerator, Config, adjectives, colors, animals } from 'unique-names-generator';
import {uniqueNamesGenerator, Config, adjectives, colors, animals} from 'unique-names-generator';
var openpgp = require('openpgp');
@@ -71,10 +71,10 @@ export class RegisterComponent implements OnInit {
register() {
this.missingToken = false;
if (this.form.valid && !this.working) {
if(this.form.valid && !this.working) {
this.working = true;
let pgpOption = {
userIds: [{ name: this.model.username, email: this.model.username + "@we.bstly.de" }],
userIds: [{name: this.model.username, email: this.model.username + "@we.bstly.de"}],
curve: "ed25519",
}
@@ -82,18 +82,21 @@ export class RegisterComponent implements OnInit {
openpgp.generateKey(pgpOption).then((key) => {
privKey = key.privateKeyArmored;
pubKey = key.publicKeyArmored;
this.model.publicKey = pubKey;
this.model.profileFields = [
{"name": "publicKey", "type": "BLOB", "visibility": "PROTECTED", "blob": pubKey}
]
if(this.model.email) {
this.model.profileFields.push({"name": "email", "type": "EMAIL", "visibility": "PRITAVE", "value": this.model.email})
}
if(this.model.primaryEmail) {
this.model.profileFields.push({"name": "primaryEmail", "type": "BOOL", "visibility": "PRITAVE", "value": this.model.primaryEmail})
}
this.userService.register(this.model).subscribe((result: any) => {
result.privateKey = privKey;
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(privKey));
element.setAttribute('download', result.username + ".private.key");
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
const dialogRef = this.dialog.open(RegisterDialog, {
closeOnNavigation: false,
disableClose: true,
@@ -101,7 +104,7 @@ export class RegisterComponent implements OnInit {
});
dialogRef.afterClosed().subscribe(result => {
if (result) {
if(result) {
this.success = true;
}
});
@@ -109,20 +112,21 @@ export class RegisterComponent implements OnInit {
this.working = false;
}, (error) => {
this.working = false;
if (error.status == 401) {
if(error.status == 401) {
this.missingToken = true;
} else if (error.status == 409) {
} else if(error.status == 409) {
let errors = {};
for (let code of error.error) {
for(let code of error.error) {
errors[code.field] = errors[code.field] || {};
errors[code.field][code.code] = true;
}
for (let code in errors) {
for(let code in errors) {
this.form.get(code).setErrors(errors[code]);
}
}
})
})
}
}
@@ -136,8 +140,22 @@ export class RegisterComponent implements OnInit {
})
export class RegisterDialog {
public downloaded: boolean = false;
@ViewChild("downloadKey", {read: ElementRef}) downloadKey: ElementRef;
constructor(private router: Router,
public dialogRef: MatDialogRef<RegisterDialog>,
@Inject(MAT_DIALOG_DATA) public data: any) { }
@Inject(MAT_DIALOG_DATA) public data: any) {
}
ngAfterViewInit() {
this.downloadKey.nativeElement.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(this.data.privateKey));
this.downloadKey.nativeElement.setAttribute('download', this.data.username + ".private.key");
}
setDownloaded() {
this.downloaded = true;
}
}
+13 -5
View File
@@ -1,5 +1,5 @@
<h1 mat-dialog-title>{{data.username}}</h1>
<div mat-dialog-content>
<mat-dialog-content>
<h3>{{'permissions' | i18n}}</h3>
<app-permissions [permissions]="data.permissions"></app-permissions>
<h3>{{'quotas' | i18n}}</h3>
@@ -10,11 +10,19 @@
<mat-label>{{'pgp.privateKey' | i18n}}</mat-label>
<textarea matInput readonly [(ngModel)]="data.privateKey"></textarea>
</mat-form-field>
</div>
<div mat-dialog-actions>
<mat-slide-toggle [(ngModel)]="data.confirmClose">
</mat-dialog-content>
<mat-dialog-actions>
<a mat-raised-button color="primary" #downloadKey (click)="setDownloaded()">{{'pgp.privateKey.downloadKey' |
i18n}}</a>
<button mat-icon-button [matTooltip]="'pgp.privateKey.help' | i18n" matTooltipPosition="after">
<mat-icon>help</mat-icon>
</button>
</mat-dialog-actions>
<br />
<mat-dialog-actions>
<mat-slide-toggle [(ngModel)]="data.confirmClose" [disabled]="!downloaded">
{{'pgp.privateKey.confirmStore' | i18n}}
</mat-slide-toggle>
<button mat-button [disabled]="!data.confirmClose" [mat-dialog-close]="true">{{'ok' | i18n}}</button>
</div>
</mat-dialog-actions>
+33
View File
@@ -0,0 +1,33 @@
import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {environment} from '../../environments/environment';
@Injectable({
providedIn: 'root',
})
export class ProfileService {
constructor(private http: HttpClient) {
}
getAll() {
return this.http.get(environment.apiUrl + "/profiles");
}
getAllForUser(username) {
return this.http.get(environment.apiUrl + "/profiles/" + username);
}
getForUser(username, name) {
return this.http.get(environment.apiUrl + "/profiles/" + username + "/" + name);
}
createOrUpdate(profileModel) {
return this.http.post(environment.apiUrl + "/profiles", profileModel);
}
delete(name) {
return this.http.delete(environment.apiUrl + "/profiles/" + name);
}
}
@@ -6,9 +6,14 @@
</td>
</ng-container>
<ng-container matColumnDef="starts">
<th mat-header-cell *matHeaderCellDef mat-sort-header="starts"> {{'permissions.starts' | i18n}} </th>
<td mat-cell *matCellDef="let permission">{{permission.starts | date:datetimeformat}}</td>
</ng-container>
<ng-container matColumnDef="expires">
<th mat-header-cell *matHeaderCellDef mat-sort-header="expires"> {{'permissions.expires' | i18n}} </th>
<td mat-cell *matCellDef="let permission">{{permission.expires | date}}</td>
<td mat-cell *matCellDef="let permission">{{permission.expires | date:datetimeformat}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="permissionColumns"></tr>
@@ -9,13 +9,14 @@ import { I18nService } from './../../services/i18n.service';
})
export class PermissionsComponent implements OnInit {
datetimeformat: String;
@Input() permissions;
permissionColumns = ["name", "expires"];
permissionColumns = ["name", "starts", "expires"];
constructor(private i18n: I18nService) { }
ngOnInit(): void {
this.datetimeformat = this.i18n.get('date-time-format',[]);
}
sortData(sort: Sort) {
@@ -28,7 +29,8 @@ export class PermissionsComponent implements OnInit {
this.permissions = data.sort((a, b) => {
const isAsc = sort.direction === 'asc';
switch (sort.active) {
case 'name': return this.compare(this.i18n.get('permissions.' + a.name,[]), this.i18n.get('permissions.' + b.name,[]), isAsc);
case 'name': return this.compare(this.i18n.get('permissions.' + a.name, []), this.i18n.get('permissions.' + b.name, []), isAsc);
case 'starts': return this.compare(a.starts, b.starts, isAsc);
case 'expires': return this.compare(a.expires, b.expires, isAsc);
default: return 0;
}
@@ -0,0 +1,9 @@
<h1 mat-dialog-title></h1>
<mat-dialog-content>
<pre>
{{profileField.blob}}
</pre>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button mat-dialog-close>{{'close' | i18n}}</button>
</mat-dialog-actions>
@@ -0,0 +1,3 @@
mat-form-field {
display: block;
}
@@ -0,0 +1,66 @@
<h1 mat-dialog-title></h1>
<mat-dialog-content>
<form [formGroup]="form">
<mat-form-field>
<input matInput type="text" min="3" [(ngModel)]="profileField.name" formControlName="name"
placeholder="{{'profileField.name' | i18n}}">
</mat-form-field>
<mat-form-field>
<mat-select [(ngModel)]="profileField.type" formControlName="type" placeholder="{{'profileField.type' | i18n}}">
<mat-option *ngFor="let type of types" [value]="type">
{{'profileField.type.' + type | i18n}}
</mat-option>
</mat-select>
</mat-form-field>
<div [ngSwitch]="profileField.type">
<mat-form-field *ngSwitchCase="'TEXT'">
<input matInput type="text" max="255" [(ngModel)]="profileField.value" formControlName="value"
placeholder="{{'profileField.value' | i18n}}">
</mat-form-field>
<mat-form-field *ngSwitchCase="'DATE'">
<input matInput [matDatepicker]="picker" [(ngModel)]="profileField.value" formControlName="value"
placeholder="{{'profileField.value' | i18n}}">
<mat-datepicker-toggle matSuffix [for]="picker"></mat-datepicker-toggle>
<mat-datepicker #picker></mat-datepicker>
</mat-form-field>
<mat-form-field *ngSwitchCase="'URL'">
<input matInput type="url" [(ngModel)]="profileField.value" formControlName="value"
placeholder="{{'profileField.value' | i18n}}">
</mat-form-field>
<mat-form-field *ngSwitchCase="'EMAIL'">
<input matInput type="email" [(ngModel)]="profileField.value" formControlName="value"
placeholder="{{'profileField.value' | i18n}}">
</mat-form-field>
<mat-form-field *ngSwitchCase="'NUMBER'">
<input matInput type="number" [(ngModel)]="profileField.value" formControlName="value"
placeholder="{{'profileField.value' | i18n}}">
</mat-form-field>
<mat-form-field *ngSwitchCase="'BLOB'">
<textarea matInput [(ngModel)]="profileField.blob" formControlName="blob"
placeholder="{{'profileField.value' | i18n}}"></textarea>
</mat-form-field>
</div>
<mat-form-field>
<mat-select [(ngModel)]="profileField.visibility" formControlName="visibility"
placeholder="{{'profileField.visibility' | i18n}}">
<mat-option *ngFor="let visibility of visibilities" [value]="visibility">
{{'profileField.visibility.' + visibility | i18n}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field>
<input matInput type="number" min="0" [(ngModel)]="profileField.index" formControlName="index"
placeholder="{{'profileField.index' | i18n}}">
</mat-form-field>
</form>
</mat-dialog-content>
<mat-dialog-actions>
<button mat-button [mat-dialog-close]="false">{{'cancel' | i18n}}</button>
<button [disabled]="form.invalid" mat-raised-button [mat-dialog-close]="profileField" color="accent">{{'save' | i18n}}</button>
</mat-dialog-actions>
@@ -0,0 +1,3 @@
mat-form-field {
display: block;
}
@@ -0,0 +1,55 @@
<table mat-table matSort [dataSource]="profileFields" (matSortChange)="sortData($event)" matSortActive="index"
matSortDirection="asc">
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header="name"> {{'profileField.name' | i18n}} </th>
<td mat-cell *matCellDef="let profileField"> {{'profileField.name.' + profileField.name | i18n}} </td>
</ng-container>
<ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef mat-sort-header="value"> {{'profileField.value' | i18n}} </th>
<td mat-cell *matCellDef="let profileField">
<div [ngSwitch]="profileField.type">
<span *ngSwitchCase="'TEXT'">{{profileField.value}}</span>
<span *ngSwitchCase="'DATE'">{{profileField.value | date:datetimeformat}}</span>
<a *ngSwitchCase="'URL'" href="{{profileField.value}}">{{profileField.value}}</a>
<a *ngSwitchCase="'EMAIL'" href="mailto:{{profileField.value}}">{{profileField.value}}</a>
<span *ngSwitchCase="'NUMBER'">{{profileField.value}}</span>
<button *ngSwitchCase="'BLOB'" mat-raised-button
(click)="openBlob(profileField)">{{'profileField.openBlob' | i18n}}</button>
</div>
</td>
</ng-container>
<ng-container matColumnDef="visibility" *ngIf="edit">
<th mat-header-cell *matHeaderCellDef mat-sort-header="visibility"> {{'profileField.visibility' | i18n}} </th>
<td mat-cell *matCellDef="let profileField"> {{'profileField.visibility.' + profileField.visibility | i18n}}
</td>
</ng-container>
<ng-container matColumnDef="edit" *ngIf="edit">
<th mat-header-cell *matHeaderCellDef> {{'profileField.edit' | i18n}} </th>
<td mat-cell *matCellDef="let profileField">
<a mat-icon-button>
<mat-icon (click)="openEdit(profileField)">edit</mat-icon>
</a>
</td>
</ng-container>
<ng-container matColumnDef="delete" *ngIf="edit">
<th mat-header-cell *matHeaderCellDef> {{'profileField.delete' | i18n}} </th>
<td mat-cell *matCellDef="let profileField">
<a mat-icon-button>
<mat-icon (click)="confirmDelete(profileField)">delete</mat-icon>
</a>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="profileFieldColumns"></tr>
<tr mat-row *matRowDef="let myRowData; columns: profileFieldColumns"></tr>
</table>
<br>
<div *ngIf="edit" class="text-center">
<button mat-raised-button color="primary" (click)="openCreate()">{{'profileField.create' |
i18n}}</button>
</div>
@@ -0,0 +1,3 @@
table {
width: 100%;
}
@@ -0,0 +1,25 @@
import {ComponentFixture, TestBed} from '@angular/core/testing';
import {ProfileFieldsComponent} from './profilefields.component';
describe('ProfileFieldsComponent', () => {
let component: ProfileFieldsComponent;
let fixture: ComponentFixture<ProfileFieldsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ProfileFieldsComponent]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ProfileFieldsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
@@ -0,0 +1,159 @@
import {Component, OnInit, Inject, Input} from '@angular/core';
import {Sort} from '@angular/material/sort';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog';
import {I18nService} from '../../services/i18n.service';
import {ProfileService} from '../../services/profile.service';
@Component({
selector: 'app-profilefields',
templateUrl: './profilefields.component.html',
styleUrls: ['./profilefields.component.scss']
})
export class ProfileFieldsComponent implements OnInit {
@Input() profileFields: Array<any>;
@Input() edit;
profileFieldColumns = ["name", "value"];
constructor(private i18n: I18nService, private profileService: ProfileService, public dialog: MatDialog) {}
ngOnInit(): void {
if(this.edit) {
this.profileFieldColumns.push("visibility");
this.profileFieldColumns.push("edit");
this.profileFieldColumns.push("delete");
}
}
sortData(sort: Sort) {
const data = this.profileFields.slice();
if(!sort.active || sort.direction === '') {
this.profileFields = data;
return;
}
this.profileFields = data.sort((a, b) => {
const isAsc = sort.direction === 'asc';
switch(sort.active) {
case 'name': return this.compare(this.i18n.get('profileField.name.' + a.name, []), this.i18n.get('profileField.name.' + b.name, []), isAsc);
case 'value': return this.compare(a.value, b.value, isAsc);
case 'index': return this.compare(a.index, b.index, isAsc);
default: return 0;
}
});
}
compare(a: number | string | String, b: number | string | String, isAsc: boolean) {
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
}
openEdit(profileField) {
const resetProfileField = JSON.parse(JSON.stringify(profileField));
const dialogRef = this.dialog.open(ProfileFieldDialog, {
data: profileField,
minWidth : '400px'
});
dialogRef.afterClosed().subscribe(result => {
if(result) {
this.profileService.createOrUpdate(result).subscribe();
} else {
profileField.name = resetProfileField.name;
profileField.value = resetProfileField.value;
profileField.type = resetProfileField.type;
profileField.visibility = resetProfileField.visibility;
profileField.index = resetProfileField.index;
}
});
}
confirmDelete(profileField) {
this.profileService.delete(profileField.name).subscribe((result: any) => {
this.profileFields.splice(this.profileFields.indexOf(profileField), 1);
this.profileFields = [...this.profileFields];
})
}
openCreate() {
const dialogRef = this.dialog.open(ProfileFieldDialog, {
data: {"type": "TEXT", "visibility": "PRIVATE"},
minWidth : '400px'
});
dialogRef.afterClosed().subscribe(result => {
if(result) {
this.profileService.createOrUpdate(result).subscribe((result: any) => {
this.profileFields.push(result);
this.profileFields = [...this.profileFields];
});
}
});
}
openBlob(profileField) {
this.dialog.open(ProfileFieldBlob, {
data: profileField,
minWidth : '400px'
});
}
}
@Component({
selector: 'app-profilefield-dialog',
templateUrl: 'profilefield.dialog.html',
styleUrls: ['./profilefield.dialog.scss']
})
export class ProfileFieldDialog {
form: FormGroup;
profileField;
types = ["TEXT", "NUMBER", "DATE", "URL", "EMAIL", "BOOL", "BLOB"];
visibilities = ["PRIVATE", "PROTECTED", "PUBLIC"];
constructor(
private formBuilder: FormBuilder,
public dialogRef: MatDialogRef<ProfileFieldDialog>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.profileField = data;
}
ngOnInit() {
this.form = this.formBuilder.group({
name: ['', Validators.required],
type: ['', Validators.required],
value: [''],
blob: [''],
visibility: ['', Validators.required],
index: ['']
});
}
}
@Component({
selector: 'app-profilefield-blob',
templateUrl: 'profilefield.blob.html',
styleUrls: ['./profilefield.blob.scss']
})
export class ProfileFieldBlob {
profileField;
constructor(
public dialogRef: MatDialogRef<ProfileFieldDialog>,
@Inject(MAT_DIALOG_DATA) public data: any) {
this.profileField = data;
}
}