Selaa lähdekoodia

Project updated with JDL

Lauren Green 5 vuotta sitten
vanhempi
commit
89f0c72211
100 muutettua tiedostoa jossa 4034 lisäystä ja 0 poistoa
  1. 61
    0
      webapp/404.html
  2. 30
    0
      webapp/app/account/account.module.ts
  3. 12
    0
      webapp/app/account/account.route.ts
  4. 17
    0
      webapp/app/account/activate/activate.component.html
  5. 37
    0
      webapp/app/account/activate/activate.component.ts
  6. 12
    0
      webapp/app/account/activate/activate.route.ts
  7. 16
    0
      webapp/app/account/activate/activate.service.ts
  8. 19
    0
      webapp/app/account/index.ts
  9. 77
    0
      webapp/app/account/password-reset/finish/password-reset-finish.component.html
  10. 65
    0
      webapp/app/account/password-reset/finish/password-reset-finish.component.ts
  11. 12
    0
      webapp/app/account/password-reset/finish/password-reset-finish.route.ts
  12. 14
    0
      webapp/app/account/password-reset/finish/password-reset-finish.service.ts
  13. 46
    0
      webapp/app/account/password-reset/init/password-reset-init.component.html
  14. 43
    0
      webapp/app/account/password-reset/init/password-reset-init.component.ts
  15. 12
    0
      webapp/app/account/password-reset/init/password-reset-init.route.ts
  16. 14
    0
      webapp/app/account/password-reset/init/password-reset-init.service.ts
  17. 84
    0
      webapp/app/account/password/password-strength-bar.component.ts
  18. 23
    0
      webapp/app/account/password/password-strength-bar.scss
  19. 77
    0
      webapp/app/account/password/password.component.html
  20. 46
    0
      webapp/app/account/password/password.component.ts
  21. 14
    0
      webapp/app/account/password/password.route.ts
  22. 14
    0
      webapp/app/account/password/password.service.ts
  23. 124
    0
      webapp/app/account/register/register.component.html
  24. 75
    0
      webapp/app/account/register/register.component.ts
  25. 12
    0
      webapp/app/account/register/register.route.ts
  26. 14
    0
      webapp/app/account/register/register.service.ts
  27. 86
    0
      webapp/app/account/settings/settings.component.html
  28. 64
    0
      webapp/app/account/settings/settings.component.ts
  29. 14
    0
      webapp/app/account/settings/settings.route.ts
  30. 56
    0
      webapp/app/admin/admin.module.ts
  31. 18
    0
      webapp/app/admin/admin.route.ts
  32. 3
    0
      webapp/app/admin/audits/audit-data.model.ts
  33. 5
    0
      webapp/app/admin/audits/audit.model.ts
  34. 52
    0
      webapp/app/admin/audits/audits.component.html
  35. 128
    0
      webapp/app/admin/audits/audits.component.ts
  36. 17
    0
      webapp/app/admin/audits/audits.route.ts
  37. 25
    0
      webapp/app/admin/audits/audits.service.ts
  38. 46
    0
      webapp/app/admin/configuration/configuration.component.html
  39. 43
    0
      webapp/app/admin/configuration/configuration.component.ts
  40. 11
    0
      webapp/app/admin/configuration/configuration.route.ts
  41. 67
    0
      webapp/app/admin/configuration/configuration.service.ts
  42. 2
    0
      webapp/app/admin/docs/docs.component.html
  43. 9
    0
      webapp/app/admin/docs/docs.component.ts
  44. 11
    0
      webapp/app/admin/docs/docs.route.ts
  45. 36
    0
      webapp/app/admin/health/health-modal.component.html
  46. 41
    0
      webapp/app/admin/health/health-modal.component.ts
  47. 34
    0
      webapp/app/admin/health/health.component.html
  48. 66
    0
      webapp/app/admin/health/health.component.ts
  49. 11
    0
      webapp/app/admin/health/health.route.ts
  50. 133
    0
      webapp/app/admin/health/health.service.ts
  51. 28
    0
      webapp/app/admin/index.ts
  52. 3
    0
      webapp/app/admin/logs/log.model.ts
  53. 28
    0
      webapp/app/admin/logs/logs.component.html
  54. 32
    0
      webapp/app/admin/logs/logs.component.ts
  55. 11
    0
      webapp/app/admin/logs/logs.route.ts
  56. 19
    0
      webapp/app/admin/logs/logs.service.ts
  57. 56
    0
      webapp/app/admin/metrics/metrics-modal.component.html
  58. 46
    0
      webapp/app/admin/metrics/metrics-modal.component.ts
  59. 216
    0
      webapp/app/admin/metrics/metrics.component.html
  60. 77
    0
      webapp/app/admin/metrics/metrics.component.ts
  61. 11
    0
      webapp/app/admin/metrics/metrics.route.ts
  62. 18
    0
      webapp/app/admin/metrics/metrics.service.ts
  63. 19
    0
      webapp/app/admin/user-management/user-management-delete-dialog.component.html
  64. 29
    0
      webapp/app/admin/user-management/user-management-delete-dialog.component.ts
  65. 49
    0
      webapp/app/admin/user-management/user-management-detail.component.html
  66. 20
    0
      webapp/app/admin/user-management/user-management-detail.component.ts
  67. 124
    0
      webapp/app/admin/user-management/user-management-update.component.html
  68. 58
    0
      webapp/app/admin/user-management/user-management-update.component.ts
  69. 79
    0
      webapp/app/admin/user-management/user-management.component.html
  70. 146
    0
      webapp/app/admin/user-management/user-management.component.ts
  71. 68
    0
      webapp/app/admin/user-management/user-management.route.ts
  72. 23
    0
      webapp/app/app-routing.module.ts
  73. 8
    0
      webapp/app/app.constants.ts
  74. 14
    0
      webapp/app/app.main.ts
  75. 72
    0
      webapp/app/app.module.ts
  76. 9
    0
      webapp/app/blocks/config/prod.config.ts
  77. 14
    0
      webapp/app/blocks/config/uib-pagination.config.ts
  78. 25
    0
      webapp/app/blocks/interceptor/auth-expired.interceptor.ts
  79. 27
    0
      webapp/app/blocks/interceptor/auth.interceptor.ts
  80. 25
    0
      webapp/app/blocks/interceptor/errorhandler.interceptor.ts
  81. 37
    0
      webapp/app/blocks/interceptor/notification.interceptor.ts
  82. 19
    0
      webapp/app/core/auth/account.service.ts
  83. 59
    0
      webapp/app/core/auth/auth-jwt.service.ts
  84. 11
    0
      webapp/app/core/auth/csrf.service.ts
  85. 113
    0
      webapp/app/core/auth/principal.service.ts
  86. 46
    0
      webapp/app/core/auth/state-storage.service.ts
  87. 56
    0
      webapp/app/core/auth/user-route-access-service.ts
  88. 24
    0
      webapp/app/core/core.module.ts
  89. 14
    0
      webapp/app/core/index.ts
  90. 9
    0
      webapp/app/core/language/language.constants.ts
  91. 66
    0
      webapp/app/core/language/language.helper.ts
  92. 27
    0
      webapp/app/core/login/login-modal.service.ts
  93. 38
    0
      webapp/app/core/login/login.service.ts
  94. 12
    0
      webapp/app/core/user/account.model.ts
  95. 47
    0
      webapp/app/core/user/user.model.ts
  96. 39
    0
      webapp/app/core/user/user.service.ts
  97. 19
    0
      webapp/app/entities/cohort/cohort-delete-dialog.component.html
  98. 65
    0
      webapp/app/entities/cohort/cohort-delete-dialog.component.ts
  99. 31
    0
      webapp/app/entities/cohort/cohort-detail.component.html
  100. 0
    0
      webapp/app/entities/cohort/cohort-detail.component.ts

+ 61
- 0
webapp/404.html Näytä tiedosto

@@ -0,0 +1,61 @@
1
+<!doctype html>
2
+<html lang="en">
3
+<head>
4
+    <meta charset="utf-8">
5
+    <title>Page Not Found</title>
6
+    <meta name="viewport" content="width=device-width, initial-scale=1">
7
+    <link rel="shortcut icon" href="favicon.ico" />
8
+    <style>
9
+
10
+        * {
11
+            line-height: 1.2;
12
+            margin: 0;
13
+        }
14
+
15
+        html {
16
+            color: #888;
17
+            display: table;
18
+            font-family: sans-serif;
19
+            height: 100%;
20
+            text-align: center;
21
+            width: 100%;
22
+        }
23
+
24
+        body {
25
+            display: table-cell;
26
+            vertical-align: middle;
27
+            margin: 2em auto;
28
+        }
29
+
30
+        h1 {
31
+            color: #555;
32
+            font-size: 2em;
33
+            font-weight: 400;
34
+        }
35
+
36
+        p {
37
+            margin: 0 auto;
38
+            width: 280px;
39
+        }
40
+
41
+        @media only screen and (max-width: 280px) {
42
+
43
+            body, p {
44
+                width: 95%;
45
+            }
46
+
47
+            h1 {
48
+                font-size: 1.5em;
49
+                margin: 0 0 0.3em;
50
+            }
51
+
52
+        }
53
+
54
+    </style>
55
+</head>
56
+<body>
57
+    <h1>Page Not Found</h1>
58
+    <p>Sorry, but the page you were trying to view does not exist.</p>
59
+</body>
60
+</html>
61
+<!-- IE needs 512+ bytes: http://blogs.msdn.com/b/ieinternals/archive/2010/08/19/http-error-pages-in-internet-explorer.aspx -->

+ 30
- 0
webapp/app/account/account.module.ts Näytä tiedosto

@@ -0,0 +1,30 @@
1
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2
+import { RouterModule } from '@angular/router';
3
+
4
+import { ZipConnectSharedModule } from 'app/shared';
5
+
6
+import {
7
+    PasswordStrengthBarComponent,
8
+    RegisterComponent,
9
+    ActivateComponent,
10
+    PasswordComponent,
11
+    PasswordResetInitComponent,
12
+    PasswordResetFinishComponent,
13
+    SettingsComponent,
14
+    accountState
15
+} from './';
16
+
17
+@NgModule({
18
+    imports: [ZipConnectSharedModule, RouterModule.forChild(accountState)],
19
+    declarations: [
20
+        ActivateComponent,
21
+        RegisterComponent,
22
+        PasswordComponent,
23
+        PasswordStrengthBarComponent,
24
+        PasswordResetInitComponent,
25
+        PasswordResetFinishComponent,
26
+        SettingsComponent
27
+    ],
28
+    schemas: [CUSTOM_ELEMENTS_SCHEMA]
29
+})
30
+export class ZipConnectAccountModule {}

+ 12
- 0
webapp/app/account/account.route.ts Näytä tiedosto

@@ -0,0 +1,12 @@
1
+import { Routes } from '@angular/router';
2
+
3
+import { activateRoute, passwordRoute, passwordResetFinishRoute, passwordResetInitRoute, registerRoute, settingsRoute } from './';
4
+
5
+const ACCOUNT_ROUTES = [activateRoute, passwordRoute, passwordResetFinishRoute, passwordResetInitRoute, registerRoute, settingsRoute];
6
+
7
+export const accountState: Routes = [
8
+    {
9
+        path: '',
10
+        children: ACCOUNT_ROUTES
11
+    }
12
+];

+ 17
- 0
webapp/app/account/activate/activate.component.html Näytä tiedosto

@@ -0,0 +1,17 @@
1
+<div>
2
+    <div class="row justify-content-center">
3
+        <div class="col-md-8">
4
+            <h1 jhiTranslate="activate.title">Activation</h1>
5
+
6
+            <div class="alert alert-success" *ngIf="success">
7
+                <span jhiTranslate="activate.messages.success"><strong>Your user account has been activated.</strong> Please </span>
8
+                <a class="alert-link" (click)="login()" jhiTranslate="global.messages.info.authenticated.link">sign in</a>.
9
+            </div>
10
+
11
+            <div class="alert alert-danger" *ngIf="error" jhiTranslate="activate.messages.error">
12
+                <strong>Your user could not be activated.</strong> Please use the registration form to sign up.
13
+            </div>
14
+
15
+        </div>
16
+    </div>
17
+</div>

+ 37
- 0
webapp/app/account/activate/activate.component.ts Näytä tiedosto

@@ -0,0 +1,37 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
3
+import { ActivatedRoute } from '@angular/router';
4
+
5
+import { LoginModalService } from 'app/core';
6
+import { ActivateService } from './activate.service';
7
+
8
+@Component({
9
+    selector: 'jhi-activate',
10
+    templateUrl: './activate.component.html'
11
+})
12
+export class ActivateComponent implements OnInit {
13
+    error: string;
14
+    success: string;
15
+    modalRef: NgbModalRef;
16
+
17
+    constructor(private activateService: ActivateService, private loginModalService: LoginModalService, private route: ActivatedRoute) {}
18
+
19
+    ngOnInit() {
20
+        this.route.queryParams.subscribe(params => {
21
+            this.activateService.get(params['key']).subscribe(
22
+                () => {
23
+                    this.error = null;
24
+                    this.success = 'OK';
25
+                },
26
+                () => {
27
+                    this.success = null;
28
+                    this.error = 'ERROR';
29
+                }
30
+            );
31
+        });
32
+    }
33
+
34
+    login() {
35
+        this.modalRef = this.loginModalService.open();
36
+    }
37
+}

+ 12
- 0
webapp/app/account/activate/activate.route.ts Näytä tiedosto

@@ -0,0 +1,12 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { ActivateComponent } from './activate.component';
4
+
5
+export const activateRoute: Route = {
6
+    path: 'activate',
7
+    component: ActivateComponent,
8
+    data: {
9
+        authorities: [],
10
+        pageTitle: 'activate.title'
11
+    }
12
+};

+ 16
- 0
webapp/app/account/activate/activate.service.ts Näytä tiedosto

@@ -0,0 +1,16 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient, HttpParams } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+
7
+@Injectable({ providedIn: 'root' })
8
+export class ActivateService {
9
+    constructor(private http: HttpClient) {}
10
+
11
+    get(key: string): Observable<any> {
12
+        return this.http.get(SERVER_API_URL + 'api/activate', {
13
+            params: new HttpParams().set('key', key)
14
+        });
15
+    }
16
+}

+ 19
- 0
webapp/app/account/index.ts Näytä tiedosto

@@ -0,0 +1,19 @@
1
+export * from './activate/activate.component';
2
+export * from './activate/activate.service';
3
+export * from './activate/activate.route';
4
+export * from './password/password.component';
5
+export * from './password/password-strength-bar.component';
6
+export * from './password/password.service';
7
+export * from './password/password.route';
8
+export * from './password-reset/finish/password-reset-finish.component';
9
+export * from './password-reset/finish/password-reset-finish.service';
10
+export * from './password-reset/finish/password-reset-finish.route';
11
+export * from './password-reset/init/password-reset-init.component';
12
+export * from './password-reset/init/password-reset-init.service';
13
+export * from './password-reset/init/password-reset-init.route';
14
+export * from './register/register.component';
15
+export * from './register/register.service';
16
+export * from './register/register.route';
17
+export * from './settings/settings.component';
18
+export * from './settings/settings.route';
19
+export * from './account.route';

+ 77
- 0
webapp/app/account/password-reset/finish/password-reset-finish.component.html Näytä tiedosto

@@ -0,0 +1,77 @@
1
+<div>
2
+    <div class="row justify-content-center">
3
+        <div class="col-md-4">
4
+            <h1 jhiTranslate="reset.finish.title">Reset password</h1>
5
+
6
+            <div class="alert alert-danger" jhiTranslate="reset.finish.messages.keymissing" *ngIf="keyMissing">
7
+                <strong>The password reset key is missing.</strong>
8
+            </div>
9
+
10
+            <div class="alert alert-warning" *ngIf="!success && !keyMissing">
11
+                <p jhiTranslate="reset.finish.messages.info">Choose a new password</p>
12
+            </div>
13
+
14
+            <div class="alert alert-danger" *ngIf="error">
15
+                <p jhiTranslate="reset.finish.messages.error">Your password couldn't be reset. Remember a password request is only valid for 24 hours.</p>
16
+            </div>
17
+
18
+            <p class="alert alert-success" *ngIf="success">
19
+                <span jhiTranslate="reset.finish.messages.success"><strong>Your password has been reset.</strong> Please </span>
20
+                <a class="alert-link" (click)="login()" jhiTranslate="global.messages.info.authenticated.link">sign in</a>.
21
+            </p>
22
+
23
+            <div class="alert alert-danger" *ngIf="doNotMatch" jhiTranslate="global.messages.error.dontmatch">
24
+                The password and its confirmation do not match!
25
+            </div>
26
+
27
+            <div *ngIf="!keyMissing">
28
+                <form *ngIf="!success" name="form" role="form" (ngSubmit)="finishReset()" #passwordForm="ngForm">
29
+                    <div class="form-group">
30
+                        <label class="form-control-label" for="password" jhiTranslate="global.form.newpassword">New password</label>
31
+                        <input type="password" class="form-control" id="password" name="password" #passwordInput="ngModel"
32
+                               placeholder="{{'global.form.newpassword.placeholder' | translate}}"
33
+                               [(ngModel)]="resetAccount.password" minlength=4 maxlength=50 required>
34
+                        <div *ngIf="passwordInput.dirty && passwordInput.invalid">
35
+                            <small class="form-text text-danger"
36
+                               *ngIf="passwordInput.errors.required" jhiTranslate="global.messages.validate.newpassword.required">
37
+                                Your password is required.
38
+                            </small>
39
+                            <small class="form-text text-danger"
40
+                               *ngIf="passwordInput.errors.minlength" jhiTranslate="global.messages.validate.newpassword.minlength">
41
+                                Your password is required to be at least 4 characters.
42
+                            </small>
43
+                            <small class="form-text text-danger"
44
+                               *ngIf="passwordInput.errors.maxlength" jhiTranslate="global.messages.validate.newpassword.maxlength">
45
+                                Your password cannot be longer than 50 characters.
46
+                            </small>
47
+                        </div>
48
+                        <jhi-password-strength-bar [passwordToCheck]="resetAccount.password"></jhi-password-strength-bar>
49
+                    </div>
50
+
51
+                    <div class="form-group">
52
+                        <label class="form-control-label" for="confirmPassword" jhiTranslate="global.form.confirmpassword">New password confirmation</label>
53
+                        <input type="password" class="form-control" id="confirmPassword" name="confirmPassword" #confirmPasswordInput="ngModel"
54
+                               placeholder="{{'global.form.confirmpassword.placeholder' | translate}}"
55
+                               [(ngModel)]="confirmPassword" minlength=4 maxlength=50 required>
56
+                        <div *ngIf="confirmPasswordInput.dirty && confirmPasswordInput.invalid">
57
+                            <small class="form-text text-danger"
58
+                               *ngIf="confirmPasswordInput.errors.required" jhiTranslate="global.messages.validate.confirmpassword.required">
59
+                                Your password confirmation is required.
60
+                            </small>
61
+                            <small class="form-text text-danger"
62
+                               *ngIf="confirmPasswordInput.errors.minlength" jhiTranslate="global.messages.validate.confirmpassword.minlength">
63
+                                Your password confirmation is required to be at least 4 characters.
64
+                            </small>
65
+                            <small class="form-text text-danger"
66
+                               *ngIf="confirmPasswordInput.errors.maxlength" jhiTranslate="global.messages.validate.confirmpassword.maxlength">
67
+                                Your password confirmation cannot be longer than 50 characters.
68
+                            </small>
69
+                        </div>
70
+                    </div>
71
+                    <button type="submit" [disabled]="passwordForm.form.invalid" class="btn btn-primary" jhiTranslate="reset.finish.form.button">Reset Password</button>
72
+                </form>
73
+            </div>
74
+
75
+        </div>
76
+    </div>
77
+</div>

+ 65
- 0
webapp/app/account/password-reset/finish/password-reset-finish.component.ts Näytä tiedosto

@@ -0,0 +1,65 @@
1
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
2
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
3
+import { ActivatedRoute } from '@angular/router';
4
+
5
+import { LoginModalService } from 'app/core';
6
+import { PasswordResetFinishService } from './password-reset-finish.service';
7
+
8
+@Component({
9
+    selector: 'jhi-password-reset-finish',
10
+    templateUrl: './password-reset-finish.component.html'
11
+})
12
+export class PasswordResetFinishComponent implements OnInit, AfterViewInit {
13
+    confirmPassword: string;
14
+    doNotMatch: string;
15
+    error: string;
16
+    keyMissing: boolean;
17
+    resetAccount: any;
18
+    success: string;
19
+    modalRef: NgbModalRef;
20
+    key: string;
21
+
22
+    constructor(
23
+        private passwordResetFinishService: PasswordResetFinishService,
24
+        private loginModalService: LoginModalService,
25
+        private route: ActivatedRoute,
26
+        private elementRef: ElementRef,
27
+        private renderer: Renderer
28
+    ) {}
29
+
30
+    ngOnInit() {
31
+        this.route.queryParams.subscribe(params => {
32
+            this.key = params['key'];
33
+        });
34
+        this.resetAccount = {};
35
+        this.keyMissing = !this.key;
36
+    }
37
+
38
+    ngAfterViewInit() {
39
+        if (this.elementRef.nativeElement.querySelector('#password') != null) {
40
+            this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#password'), 'focus', []);
41
+        }
42
+    }
43
+
44
+    finishReset() {
45
+        this.doNotMatch = null;
46
+        this.error = null;
47
+        if (this.resetAccount.password !== this.confirmPassword) {
48
+            this.doNotMatch = 'ERROR';
49
+        } else {
50
+            this.passwordResetFinishService.save({ key: this.key, newPassword: this.resetAccount.password }).subscribe(
51
+                () => {
52
+                    this.success = 'OK';
53
+                },
54
+                () => {
55
+                    this.success = null;
56
+                    this.error = 'ERROR';
57
+                }
58
+            );
59
+        }
60
+    }
61
+
62
+    login() {
63
+        this.modalRef = this.loginModalService.open();
64
+    }
65
+}

+ 12
- 0
webapp/app/account/password-reset/finish/password-reset-finish.route.ts Näytä tiedosto

@@ -0,0 +1,12 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { PasswordResetFinishComponent } from './password-reset-finish.component';
4
+
5
+export const passwordResetFinishRoute: Route = {
6
+    path: 'reset/finish',
7
+    component: PasswordResetFinishComponent,
8
+    data: {
9
+        authorities: [],
10
+        pageTitle: 'global.menu.account.password'
11
+    }
12
+};

+ 14
- 0
webapp/app/account/password-reset/finish/password-reset-finish.service.ts Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+
7
+@Injectable({ providedIn: 'root' })
8
+export class PasswordResetFinishService {
9
+    constructor(private http: HttpClient) {}
10
+
11
+    save(keyAndPassword: any): Observable<any> {
12
+        return this.http.post(SERVER_API_URL + 'api/account/reset-password/finish', keyAndPassword);
13
+    }
14
+}

+ 46
- 0
webapp/app/account/password-reset/init/password-reset-init.component.html Näytä tiedosto

@@ -0,0 +1,46 @@
1
+<div>
2
+    <div class="row justify-content-center">
3
+        <div class="col-md-8">
4
+            <h1 jhiTranslate="reset.request.title">Reset your password</h1>
5
+
6
+            <div class="alert alert-danger" jhiTranslate="reset.request.messages.notfound" *ngIf="errorEmailNotExists">
7
+                <strong>Email address isn't registered!</strong> Please check and try again.
8
+            </div>
9
+
10
+            <div class="alert alert-warning" *ngIf="!success">
11
+                <p jhiTranslate="reset.request.messages.info">Enter the email address you used to register.</p>
12
+            </div>
13
+
14
+            <div class="alert alert-success" *ngIf="success === 'OK'">
15
+                <p jhiTranslate="reset.request.messages.success">Check your emails for details on how to reset your password.</p>
16
+            </div>
17
+
18
+            <form *ngIf="!success" name="form" role="form" (ngSubmit)="requestReset()" #resetRequestForm="ngForm">
19
+                <div class="form-group">
20
+                    <label class="form-control-label" for="email" jhiTranslate="global.form.email">Email</label>
21
+                    <input type="email" class="form-control" id="email" name="email" placeholder="{{'global.form.email.placeholder' | translate}}"
22
+                           [(ngModel)]="resetAccount.email" minlength=5 maxlength=254 #emailInput="ngModel" email required>
23
+                    <div *ngIf="emailInput.dirty && emailInput.invalid">
24
+                        <small class="form-text text-danger"
25
+                           *ngIf="emailInput.errors.required" jhiTranslate="global.messages.validate.email.required">
26
+                            Your email is required.
27
+                        </small>
28
+                        <small class="form-text text-danger"
29
+                           *ngIf="emailInput.errors.email" jhiTranslate="global.messages.validate.email.invalid">
30
+                            Your email is invalid.
31
+                        </small>
32
+                        <small class="form-text text-danger"
33
+                           *ngIf="emailInput.errors.minlength" jhiTranslate="global.messages.validate.email.minlength">
34
+                            Your email is required to be at least 5 characters.
35
+                        </small>
36
+                        <small class="form-text text-danger"
37
+                           *ngIf="emailInput.errors.maxlength" jhiTranslate="global.messages.validate.email.maxlength">
38
+                            Your email cannot be longer than 100 characters.
39
+                        </small>
40
+                    </div>
41
+                </div>
42
+                <button type="submit" [disabled]="resetRequestForm.form.invalid" class="btn btn-primary" jhiTranslate="reset.request.form.button">Reset</button>
43
+            </form>
44
+        </div>
45
+    </div>
46
+</div>

+ 43
- 0
webapp/app/account/password-reset/init/password-reset-init.component.ts Näytä tiedosto

@@ -0,0 +1,43 @@
1
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
2
+import { EMAIL_NOT_FOUND_TYPE } from 'app/shared';
3
+import { PasswordResetInitService } from './password-reset-init.service';
4
+
5
+@Component({
6
+    selector: 'jhi-password-reset-init',
7
+    templateUrl: './password-reset-init.component.html'
8
+})
9
+export class PasswordResetInitComponent implements OnInit, AfterViewInit {
10
+    error: string;
11
+    errorEmailNotExists: string;
12
+    resetAccount: any;
13
+    success: string;
14
+
15
+    constructor(private passwordResetInitService: PasswordResetInitService, private elementRef: ElementRef, private renderer: Renderer) {}
16
+
17
+    ngOnInit() {
18
+        this.resetAccount = {};
19
+    }
20
+
21
+    ngAfterViewInit() {
22
+        this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#email'), 'focus', []);
23
+    }
24
+
25
+    requestReset() {
26
+        this.error = null;
27
+        this.errorEmailNotExists = null;
28
+
29
+        this.passwordResetInitService.save(this.resetAccount.email).subscribe(
30
+            () => {
31
+                this.success = 'OK';
32
+            },
33
+            response => {
34
+                this.success = null;
35
+                if (response.status === 400 && response.error.type === EMAIL_NOT_FOUND_TYPE) {
36
+                    this.errorEmailNotExists = 'ERROR';
37
+                } else {
38
+                    this.error = 'ERROR';
39
+                }
40
+            }
41
+        );
42
+    }
43
+}

+ 12
- 0
webapp/app/account/password-reset/init/password-reset-init.route.ts Näytä tiedosto

@@ -0,0 +1,12 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { PasswordResetInitComponent } from './password-reset-init.component';
4
+
5
+export const passwordResetInitRoute: Route = {
6
+    path: 'reset/request',
7
+    component: PasswordResetInitComponent,
8
+    data: {
9
+        authorities: [],
10
+        pageTitle: 'global.menu.account.password'
11
+    }
12
+};

+ 14
- 0
webapp/app/account/password-reset/init/password-reset-init.service.ts Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+
7
+@Injectable({ providedIn: 'root' })
8
+export class PasswordResetInitService {
9
+    constructor(private http: HttpClient) {}
10
+
11
+    save(mail: string): Observable<any> {
12
+        return this.http.post(SERVER_API_URL + 'api/account/reset-password/init', mail);
13
+    }
14
+}

+ 84
- 0
webapp/app/account/password/password-strength-bar.component.ts Näytä tiedosto

@@ -0,0 +1,84 @@
1
+import { Component, ElementRef, Input, Renderer } from '@angular/core';
2
+
3
+@Component({
4
+    selector: 'jhi-password-strength-bar',
5
+    template: `
6
+        <div id="strength">
7
+            <small jhiTranslate="global.messages.validate.newpassword.strength">Password strength:</small>
8
+            <ul id="strengthBar">
9
+                <li class="point"></li>
10
+                <li class="point"></li>
11
+                <li class="point"></li>
12
+                <li class="point"></li>
13
+                <li class="point"></li>
14
+            </ul>
15
+        </div>`,
16
+    styleUrls: ['password-strength-bar.scss']
17
+})
18
+export class PasswordStrengthBarComponent {
19
+    colors = ['#F00', '#F90', '#FF0', '#9F0', '#0F0'];
20
+
21
+    constructor(private renderer: Renderer, private elementRef: ElementRef) {}
22
+
23
+    measureStrength(p: string): number {
24
+        let force = 0;
25
+        const regex = /[$-/:-?{-~!"^_`\[\]]/g; // "
26
+        const lowerLetters = /[a-z]+/.test(p);
27
+        const upperLetters = /[A-Z]+/.test(p);
28
+        const numbers = /[0-9]+/.test(p);
29
+        const symbols = regex.test(p);
30
+
31
+        const flags = [lowerLetters, upperLetters, numbers, symbols];
32
+        const passedMatches = flags.filter((isMatchedFlag: boolean) => {
33
+            return isMatchedFlag === true;
34
+        }).length;
35
+
36
+        force += 2 * p.length + (p.length >= 10 ? 1 : 0);
37
+        force += passedMatches * 10;
38
+
39
+        // penalty (short password)
40
+        force = p.length <= 6 ? Math.min(force, 10) : force;
41
+
42
+        // penalty (poor variety of characters)
43
+        force = passedMatches === 1 ? Math.min(force, 10) : force;
44
+        force = passedMatches === 2 ? Math.min(force, 20) : force;
45
+        force = passedMatches === 3 ? Math.min(force, 40) : force;
46
+
47
+        return force;
48
+    }
49
+
50
+    getColor(s: number): any {
51
+        let idx = 0;
52
+        if (s <= 10) {
53
+            idx = 0;
54
+        } else if (s <= 20) {
55
+            idx = 1;
56
+        } else if (s <= 30) {
57
+            idx = 2;
58
+        } else if (s <= 40) {
59
+            idx = 3;
60
+        } else {
61
+            idx = 4;
62
+        }
63
+        return { idx: idx + 1, col: this.colors[idx] };
64
+    }
65
+
66
+    @Input()
67
+    set passwordToCheck(password: string) {
68
+        if (password) {
69
+            const c = this.getColor(this.measureStrength(password));
70
+            const element = this.elementRef.nativeElement;
71
+            if (element.className) {
72
+                this.renderer.setElementClass(element, element.className, false);
73
+            }
74
+            const lis = element.getElementsByTagName('li');
75
+            for (let i = 0; i < lis.length; i++) {
76
+                if (i < c.idx) {
77
+                    this.renderer.setElementStyle(lis[i], 'backgroundColor', c.col);
78
+                } else {
79
+                    this.renderer.setElementStyle(lis[i], 'backgroundColor', '#DDD');
80
+                }
81
+            }
82
+        }
83
+    }
84
+}

+ 23
- 0
webapp/app/account/password/password-strength-bar.scss Näytä tiedosto

@@ -0,0 +1,23 @@
1
+/* ==========================================================================
2
+start Password strength bar style
3
+========================================================================== */
4
+ul#strength {
5
+    display: inline;
6
+    list-style: none;
7
+    margin: 0;
8
+    margin-left: 15px;
9
+    padding: 0;
10
+    vertical-align: 2px;
11
+}
12
+
13
+.point {
14
+    background: #ddd;
15
+    border-radius: 2px;
16
+    display: inline-block;
17
+    height: 5px;
18
+    margin-right: 1px;
19
+    width: 20px;
20
+    &:last-child {
21
+        margin: 0 !important;
22
+    }
23
+}

+ 77
- 0
webapp/app/account/password/password.component.html Näytä tiedosto

@@ -0,0 +1,77 @@
1
+<div>
2
+    <div class="row justify-content-center">
3
+        <div class="col-md-8">
4
+            <h2 jhiTranslate="password.title" translateValues="{username: '{{account.login}}'}" *ngIf="account">Password for [<b>{{account.login}}</b>]</h2>
5
+
6
+            <div class="alert alert-success" *ngIf="success" jhiTranslate="password.messages.success">
7
+                <strong>Password changed!</strong>
8
+            </div>
9
+            <div class="alert alert-danger" *ngIf="error"  jhiTranslate="password.messages.error">
10
+                <strong>An error has occurred!</strong> The password could not be changed.
11
+            </div>
12
+
13
+            <div class="alert alert-danger" *ngIf="doNotMatch" jhiTranslate="global.messages.error.dontmatch">
14
+                The password and its confirmation do not match!
15
+            </div>
16
+
17
+            <form name="form" role="form" (ngSubmit)="changePassword()" #passwordForm="ngForm">
18
+
19
+                <div class="form-group">
20
+                    <label class="form-control-label" for="currentPassword" jhiTranslate="global.form.currentpassword">Current password</label>
21
+                    <input type="password" class="form-control" id="currentPassword" name="currentPassword" #currentPasswordInput="ngModel"
22
+                           placeholder="{{'global.form.currentpassword.placeholder' | translate}}"
23
+                           [(ngModel)]="currentPassword" required>
24
+                    <div *ngIf="currentPasswordInput.dirty && currentPasswordInput.invalid">
25
+                        <small class="form-text text-danger"
26
+                               *ngIf="currentPasswordInput.errors.required" jhiTranslate="global.messages.validate.newpassword.required">
27
+                            Your password is required.
28
+                        </small>
29
+                    </div>
30
+                </div>
31
+                <div class="form-group">
32
+                    <label class="form-control-label" for="newPassword" jhiTranslate="global.form.newpassword">New password</label>
33
+                    <input type="password" class="form-control" id="newPassword" name="newPassword" #newPasswordInput="ngModel"
34
+                    placeholder="{{'global.form.newpassword.placeholder' | translate}}"
35
+                           [(ngModel)]="newPassword" minlength=4 maxlength=50 required>
36
+                    <div *ngIf="newPasswordInput.dirty && newPasswordInput.invalid">
37
+                        <small class="form-text text-danger"
38
+                           *ngIf="newPasswordInput.errors.required" jhiTranslate="global.messages.validate.newpassword.required">
39
+                            Your password is required.
40
+                        </small>
41
+                        <small class="form-text text-danger"
42
+                           *ngIf="newPasswordInput.errors.minlength" jhiTranslate="global.messages.validate.newpassword.minlength">
43
+                            Your password is required to be at least 4 characters.
44
+                        </small>
45
+                        <small class="form-text text-danger"
46
+                           *ngIf="newPasswordInput.errors.maxlength" jhiTranslate="global.messages.validate.newpassword.maxlength">
47
+                            Your password cannot be longer than 50 characters.
48
+                        </small>
49
+                    </div>
50
+                    <jhi-password-strength-bar [passwordToCheck]="newPassword"></jhi-password-strength-bar>
51
+                </div>
52
+                <div class="form-group">
53
+                    <label class="form-control-label" for="confirmPassword" jhiTranslate="global.form.confirmpassword">New password confirmation</label>
54
+                    <input type="password" class="form-control" id="confirmPassword" name="confirmPassword" #confirmPasswordInput="ngModel"
55
+                    placeholder="{{'global.form.confirmpassword.placeholder' | translate}}"
56
+                           [(ngModel)]="confirmPassword" minlength=4 maxlength=50 required>
57
+                    <div *ngIf="confirmPasswordInput.dirty && confirmPasswordInput.invalid">
58
+                        <small class="form-text text-danger"
59
+                           *ngIf="confirmPasswordInput.errors.required" jhiTranslate="global.messages.validate.confirmpassword.required">
60
+                            Your confirmation password is required.
61
+                        </small>
62
+                        <small class="form-text text-danger"
63
+                           *ngIf="confirmPasswordInput.errors.minlength" jhiTranslate="global.messages.validate.confirmpassword.minlength">
64
+                            Your confirmation password is required to be at least 4 characters.
65
+                        </small>
66
+                        <small class="form-text text-danger"
67
+                           *ngIf="confirmPasswordInput.errors.maxlength" jhiTranslate="global.messages.validate.confirmpassword.maxlength">
68
+                            Your confirmation password cannot be longer than 50 characters.
69
+                        </small>
70
+                    </div>
71
+                </div>
72
+
73
+                <button type="submit" [disabled]="passwordForm.form.invalid" class="btn btn-primary" jhiTranslate="password.form.button">Save</button>
74
+            </form>
75
+        </div>
76
+    </div>
77
+</div>

+ 46
- 0
webapp/app/account/password/password.component.ts Näytä tiedosto

@@ -0,0 +1,46 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+import { Principal } from 'app/core';
4
+import { PasswordService } from './password.service';
5
+
6
+@Component({
7
+    selector: 'jhi-password',
8
+    templateUrl: './password.component.html'
9
+})
10
+export class PasswordComponent implements OnInit {
11
+    doNotMatch: string;
12
+    error: string;
13
+    success: string;
14
+    account: any;
15
+    currentPassword: string;
16
+    newPassword: string;
17
+    confirmPassword: string;
18
+
19
+    constructor(private passwordService: PasswordService, private principal: Principal) {}
20
+
21
+    ngOnInit() {
22
+        this.principal.identity().then(account => {
23
+            this.account = account;
24
+        });
25
+    }
26
+
27
+    changePassword() {
28
+        if (this.newPassword !== this.confirmPassword) {
29
+            this.error = null;
30
+            this.success = null;
31
+            this.doNotMatch = 'ERROR';
32
+        } else {
33
+            this.doNotMatch = null;
34
+            this.passwordService.save(this.newPassword, this.currentPassword).subscribe(
35
+                () => {
36
+                    this.error = null;
37
+                    this.success = 'OK';
38
+                },
39
+                () => {
40
+                    this.success = null;
41
+                    this.error = 'ERROR';
42
+                }
43
+            );
44
+        }
45
+    }
46
+}

+ 14
- 0
webapp/app/account/password/password.route.ts Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { UserRouteAccessService } from 'app/core';
4
+import { PasswordComponent } from './password.component';
5
+
6
+export const passwordRoute: Route = {
7
+    path: 'password',
8
+    component: PasswordComponent,
9
+    data: {
10
+        authorities: ['ROLE_USER'],
11
+        pageTitle: 'global.menu.account.password'
12
+    },
13
+    canActivate: [UserRouteAccessService]
14
+};

+ 14
- 0
webapp/app/account/password/password.service.ts Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+
7
+@Injectable({ providedIn: 'root' })
8
+export class PasswordService {
9
+    constructor(private http: HttpClient) {}
10
+
11
+    save(newPassword: string, currentPassword: string): Observable<any> {
12
+        return this.http.post(SERVER_API_URL + 'api/account/change-password', { currentPassword, newPassword });
13
+    }
14
+}

+ 124
- 0
webapp/app/account/register/register.component.html Näytä tiedosto

@@ -0,0 +1,124 @@
1
+<div>
2
+    <div class="row justify-content-center">
3
+        <div class="col-md-8">
4
+            <h1 jhiTranslate="register.title">Registration</h1>
5
+
6
+            <div class="alert alert-success" *ngIf="success" jhiTranslate="register.messages.success">
7
+                <strong>Registration saved!</strong> Please check your email for confirmation.
8
+            </div>
9
+
10
+            <div class="alert alert-danger" *ngIf="error" jhiTranslate="register.messages.error.fail">
11
+                <strong>Registration failed!</strong> Please try again later.
12
+            </div>
13
+
14
+            <div class="alert alert-danger" *ngIf="errorUserExists" jhiTranslate="register.messages.error.userexists">
15
+                <strong>Login name already registered!</strong> Please choose another one.
16
+            </div>
17
+
18
+            <div class="alert alert-danger" *ngIf="errorEmailExists" jhiTranslate="register.messages.error.emailexists">
19
+                <strong>Email is already in use!</strong> Please choose another one.
20
+            </div>
21
+
22
+            <div class="alert alert-danger" *ngIf="doNotMatch" jhiTranslate="global.messages.error.dontmatch">
23
+                The password and its confirmation do not match!
24
+            </div>
25
+        </div>
26
+    </div>
27
+    <div class="row justify-content-center">
28
+        <div class="col-md-8">
29
+            <form name="form" role="form" (ngSubmit)="register()" #registerForm="ngForm" *ngIf="!success">
30
+                <div class="form-group">
31
+                    <label class="form-control-label" for="login" jhiTranslate="global.form.username">Username</label>
32
+                    <input type="text" class="form-control" [(ngModel)]="registerAccount.login" id="login" name="login" #login="ngModel" placeholder="{{'global.form.username.placeholder' | translate}}"
33
+                            required minlength="1" maxlength="50" pattern="^[_.@A-Za-z0-9-]*$">
34
+                    <div *ngIf="login.dirty && login.invalid">
35
+                        <small class="form-text text-danger" *ngIf="login.errors.required" jhiTranslate="register.messages.validate.login.required">
36
+                            Your username is required.
37
+                        </small>
38
+                        <small class="form-text text-danger" *ngIf="login.errors.minlength"
39
+                                jhiTranslate="register.messages.validate.login.minlength">
40
+                            Your username is required to be at least 1 character.
41
+                        </small>
42
+                        <small class="form-text text-danger" *ngIf="login.errors.maxlength"
43
+                                jhiTranslate="register.messages.validate.login.maxlength">
44
+                            Your username cannot be longer than 50 characters.
45
+                        </small>
46
+                        <small class="form-text text-danger" *ngIf="login.errors.pattern"
47
+                               jhiTranslate="register.messages.validate.login.pattern">
48
+                            Your username can only contain letters and digits.
49
+                        </small>
50
+                    </div>
51
+                </div>
52
+                <div class="form-group">
53
+                    <label class="form-control-label" for="email" jhiTranslate="global.form.email">Email</label>
54
+                    <input type="email" class="form-control" id="email" name="email" #email="ngModel" placeholder="{{'global.form.email.placeholder' | translate}}"
55
+                            [(ngModel)]="registerAccount.email" minlength=5 maxlength=254 email required>
56
+                    <div *ngIf="email.dirty && email.invalid">
57
+                        <small class="form-text text-danger" *ngIf="email.errors.required"
58
+                                jhiTranslate="global.messages.validate.email.required">
59
+                            Your email is required.
60
+                        </small>
61
+                        <small class="form-text text-danger" *ngIf="email.errors.invalid"
62
+                               jhiTranslate="global.messages.validate.email.invalid">
63
+                            Your email is invalid.
64
+                        </small>
65
+                        <small class="form-text text-danger" *ngIf="email.errors.minlength"
66
+                               jhiTranslate="global.messages.validate.email.minlength">
67
+                            Your email is required to be at least 5 characters.
68
+                        </small>
69
+                        <small class="form-text text-danger" *ngIf="email.errors.maxlength"
70
+                               jhiTranslate="global.messages.validate.email.maxlength">
71
+                            Your email cannot be longer than 100 characters.
72
+                        </small>
73
+                    </div>
74
+                </div>
75
+                <div class="form-group">
76
+                    <label class="form-control-label" for="password" jhiTranslate="global.form.newpassword">New password</label>
77
+                    <input type="password" class="form-control" id="password" name="password" #password="ngModel" placeholder="{{'global.form.newpassword.placeholder' | translate}}"
78
+                            [(ngModel)]="registerAccount.password" minlength=4 maxlength=50 required>
79
+                    <div *ngIf="password.dirty && password.invalid">
80
+                        <small class="form-text text-danger" *ngIf="password.errors.required"
81
+                                jhiTranslate="global.messages.validate.newpassword.required">
82
+                            Your password is required.
83
+                        </small>
84
+                        <small class="form-text text-danger" *ngIf="password.errors.minlength"
85
+                                jhiTranslate="global.messages.validate.newpassword.minlength">
86
+                            Your password is required to be at least 4 characters.
87
+                        </small>
88
+                        <small class="form-text text-danger" *ngIf="password.errors.maxlength"
89
+                                jhiTranslate="global.messages.validate.newpassword.maxlength">
90
+                            Your password cannot be longer than 50 characters.
91
+                        </small>
92
+                    </div>
93
+                    <jhi-password-strength-bar [passwordToCheck]="registerAccount.password"></jhi-password-strength-bar>
94
+                </div>
95
+                <div class="form-group">
96
+                    <label class="form-control-label" for="confirmPassword" jhiTranslate="global.form.confirmpassword">New password confirmation</label>
97
+                    <input type="password" class="form-control" id="confirmPassword" name="confirmPassword" #confirmPasswordInput="ngModel" placeholder="{{'global.form.confirmpassword.placeholder' | translate}}"
98
+                            [(ngModel)]="confirmPassword" minlength=4 maxlength=50 required>
99
+                    <div *ngIf="confirmPasswordInput.dirty && confirmPasswordInput.invalid">
100
+                        <small class="form-text text-danger" *ngIf="confirmPasswordInput.errors.required"
101
+                               jhiTranslate="global.messages.validate.confirmpassword.required">
102
+                            Your confirmation password is required.
103
+                        </small>
104
+                        <small class="form-text text-danger" *ngIf="confirmPasswordInput.errors.minlength"
105
+                              jhiTranslate="global.messages.validate.confirmpassword.minlength">
106
+                            Your confirmation password is required to be at least 4 characters.
107
+                        </small>
108
+                        <small class="form-text text-danger" *ngIf="confirmPasswordInput.errors.maxlength"
109
+                               jhiTranslate="global.messages.validate.confirmpassword.maxlength">
110
+                            Your confirmation password cannot be longer than 50 characters.
111
+                        </small>
112
+                    </div>
113
+                </div>
114
+
115
+                <button type="submit" [disabled]="registerForm.form.invalid" class="btn btn-primary" jhiTranslate="register.form.button">Register</button>
116
+            </form>
117
+            <p></p>
118
+            <div class="alert alert-warning">
119
+                <span jhiTranslate="global.messages.info.authenticated.prefix">If you want to </span>
120
+                <a class="alert-link" (click)="openLogin()" jhiTranslate="global.messages.info.authenticated.link">sign in</a><span jhiTranslate="global.messages.info.authenticated.suffix">, you can try the default accounts:<br/>- Administrator (login="admin" and password="admin") <br/>- User (login="user" and password="user").</span>
121
+            </div>
122
+        </div>
123
+    </div>
124
+</div>

+ 75
- 0
webapp/app/account/register/register.component.ts Näytä tiedosto

@@ -0,0 +1,75 @@
1
+import { Component, OnInit, AfterViewInit, Renderer, ElementRef } from '@angular/core';
2
+import { HttpErrorResponse } from '@angular/common/http';
3
+import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
4
+import { JhiLanguageService } from 'ng-jhipster';
5
+
6
+import { EMAIL_ALREADY_USED_TYPE, LOGIN_ALREADY_USED_TYPE } from 'app/shared';
7
+import { LoginModalService } from 'app/core';
8
+import { Register } from './register.service';
9
+
10
+@Component({
11
+    selector: 'jhi-register',
12
+    templateUrl: './register.component.html'
13
+})
14
+export class RegisterComponent implements OnInit, AfterViewInit {
15
+    confirmPassword: string;
16
+    doNotMatch: string;
17
+    error: string;
18
+    errorEmailExists: string;
19
+    errorUserExists: string;
20
+    registerAccount: any;
21
+    success: boolean;
22
+    modalRef: NgbModalRef;
23
+
24
+    constructor(
25
+        private languageService: JhiLanguageService,
26
+        private loginModalService: LoginModalService,
27
+        private registerService: Register,
28
+        private elementRef: ElementRef,
29
+        private renderer: Renderer
30
+    ) {}
31
+
32
+    ngOnInit() {
33
+        this.success = false;
34
+        this.registerAccount = {};
35
+    }
36
+
37
+    ngAfterViewInit() {
38
+        this.renderer.invokeElementMethod(this.elementRef.nativeElement.querySelector('#login'), 'focus', []);
39
+    }
40
+
41
+    register() {
42
+        if (this.registerAccount.password !== this.confirmPassword) {
43
+            this.doNotMatch = 'ERROR';
44
+        } else {
45
+            this.doNotMatch = null;
46
+            this.error = null;
47
+            this.errorUserExists = null;
48
+            this.errorEmailExists = null;
49
+            this.languageService.getCurrent().then(key => {
50
+                this.registerAccount.langKey = key;
51
+                this.registerService.save(this.registerAccount).subscribe(
52
+                    () => {
53
+                        this.success = true;
54
+                    },
55
+                    response => this.processError(response)
56
+                );
57
+            });
58
+        }
59
+    }
60
+
61
+    openLogin() {
62
+        this.modalRef = this.loginModalService.open();
63
+    }
64
+
65
+    private processError(response: HttpErrorResponse) {
66
+        this.success = null;
67
+        if (response.status === 400 && response.error.type === LOGIN_ALREADY_USED_TYPE) {
68
+            this.errorUserExists = 'ERROR';
69
+        } else if (response.status === 400 && response.error.type === EMAIL_ALREADY_USED_TYPE) {
70
+            this.errorEmailExists = 'ERROR';
71
+        } else {
72
+            this.error = 'ERROR';
73
+        }
74
+    }
75
+}

+ 12
- 0
webapp/app/account/register/register.route.ts Näytä tiedosto

@@ -0,0 +1,12 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { RegisterComponent } from './register.component';
4
+
5
+export const registerRoute: Route = {
6
+    path: 'register',
7
+    component: RegisterComponent,
8
+    data: {
9
+        authorities: [],
10
+        pageTitle: 'register.title'
11
+    }
12
+};

+ 14
- 0
webapp/app/account/register/register.service.ts Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+
7
+@Injectable({ providedIn: 'root' })
8
+export class Register {
9
+    constructor(private http: HttpClient) {}
10
+
11
+    save(account: any): Observable<any> {
12
+        return this.http.post(SERVER_API_URL + 'api/register', account);
13
+    }
14
+}

+ 86
- 0
webapp/app/account/settings/settings.component.html Näytä tiedosto

@@ -0,0 +1,86 @@
1
+<div>
2
+    <div class="row justify-content-center">
3
+        <div class="col-md-8">
4
+            <h2 jhiTranslate="settings.title" translateValues="{username: '{{settingsAccount.login}}'}" *ngIf="settingsAccount">User settings for [<b>{{settingsAccount.login}}</b>]</h2>
5
+
6
+            <div class="alert alert-success" *ngIf="success" jhiTranslate="settings.messages.success">
7
+                <strong>Settings saved!</strong>
8
+            </div>
9
+
10
+            <jhi-alert-error></jhi-alert-error>
11
+
12
+            <form name="form" role="form" (ngSubmit)="save()" #settingsForm="ngForm" *ngIf="settingsAccount" novalidate>
13
+
14
+                <div class="form-group">
15
+                    <label class="form-control-label" for="firstName" jhiTranslate="settings.form.firstname">First Name</label>
16
+                    <input type="text" class="form-control" id="firstName" name="firstName" placeholder="{{'settings.form.firstname.placeholder' | translate}}"
17
+                           [(ngModel)]="settingsAccount.firstName" minlength=1 maxlength=50 #firstNameInput="ngModel" required>
18
+                    <div *ngIf="firstNameInput.dirty && firstNameInput.invalid">
19
+                        <small class="form-text text-danger"
20
+                           *ngIf="firstNameInput.errors.required" jhiTranslate="settings.messages.validate.firstname.required">
21
+                            Your first name is required.
22
+                        </small>
23
+                        <small class="form-text text-danger"
24
+                           *ngIf="firstNameInput.errors.minlength" jhiTranslate="settings.messages.validate.firstname.minlength">
25
+                            Your first name is required to be at least 1 character.
26
+                        </small>
27
+                        <small class="form-text text-danger"
28
+                           *ngIf="firstNameInput.errors.maxlength" jhiTranslate="settings.messages.validate.firstname.maxlength">
29
+                            Your first name cannot be longer than 50 characters.
30
+                        </small>
31
+                    </div>
32
+                </div>
33
+                <div class="form-group">
34
+                    <label class="form-control-label" for="lastName" jhiTranslate="settings.form.lastname">Last Name</label>
35
+                    <input type="text" class="form-control" id="lastName" name="lastName" placeholder="{{'settings.form.lastname.placeholder' | translate}}"
36
+                           [(ngModel)]="settingsAccount.lastName" minlength=1 maxlength=50 #lastNameInput="ngModel" required>
37
+                    <div *ngIf="lastNameInput.dirty && lastNameInput.invalid">
38
+                        <small class="form-text text-danger"
39
+                           *ngIf="lastNameInput.errors.required" jhiTranslate="settings.messages.validate.lastname.required">
40
+                            Your last name is required.
41
+                        </small>
42
+                        <small class="form-text text-danger"
43
+                           *ngIf="lastNameInput.errors.minlength" jhiTranslate="settings.messages.validate.lastname.minlength">
44
+                            Your last name is required to be at least 1 character.
45
+                        </small>
46
+                        <small class="form-text text-danger"
47
+                           *ngIf="lastNameInput.errors.maxlength" jhiTranslate="settings.messages.validate.lastname.maxlength">
48
+                            Your last name cannot be longer than 50 characters.
49
+                        </small>
50
+                    </div>
51
+                </div>
52
+                <div class="form-group">
53
+                    <label class="form-control-label" for="email" jhiTranslate="global.form.email">Email</label>
54
+                    <input type="email" class="form-control" id="email" name="email" placeholder="{{'global.form.email.placeholder' | translate}}"
55
+                           [(ngModel)]="settingsAccount.email" minlength="5" maxlength="254" #emailInput="ngModel" email required>
56
+                    <div *ngIf="emailInput.dirty && emailInput.invalid">
57
+                        <small class="form-text text-danger"
58
+                           *ngIf="emailInput.errors.required" jhiTranslate="global.messages.validate.email.required">
59
+                            Your email is required.
60
+                        </small>
61
+                        <small class="form-text text-danger"
62
+                           *ngIf="emailInput.errors.email" jhiTranslate="global.messages.validate.email.invalid">
63
+                            Your email is invalid.
64
+                        </small>
65
+                        <small class="form-text text-danger"
66
+                           *ngIf="emailInput.errors.minlength" jhiTranslate="global.messages.validate.email.minlength">
67
+                            Your email is required to be at least 5 characters.
68
+                        </small>
69
+                        <small class="form-text text-danger"
70
+                           *ngIf="emailInput.errors.maxlength" jhiTranslate="global.messages.validate.email.maxlength">
71
+                            Your email cannot be longer than 100 characters.
72
+                        </small>
73
+                    </div>
74
+                </div>
75
+                <div class="form-group" *ngIf="languages && languages.length > 0">
76
+                    <label for="langKey" jhiTranslate="settings.form.language">Language</label>
77
+                    <select class="form-control" id="langKey" name="langKey" [(ngModel)]="settingsAccount.langKey">
78
+                        <option *ngFor="let language of languages" [value]="language">{{language | findLanguageFromKey}}</option>
79
+                    </select>
80
+                </div>
81
+                <button type="submit" [disabled]="settingsForm.form.invalid" class="btn btn-primary" jhiTranslate="settings.form.button">Save</button>
82
+            </form>
83
+        </div>
84
+    </div>
85
+
86
+</div>

+ 64
- 0
webapp/app/account/settings/settings.component.ts Näytä tiedosto

@@ -0,0 +1,64 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { JhiLanguageService } from 'ng-jhipster';
3
+
4
+import { Principal, AccountService, JhiLanguageHelper } from 'app/core';
5
+
6
+@Component({
7
+    selector: 'jhi-settings',
8
+    templateUrl: './settings.component.html'
9
+})
10
+export class SettingsComponent implements OnInit {
11
+    error: string;
12
+    success: string;
13
+    settingsAccount: any;
14
+    languages: any[];
15
+
16
+    constructor(
17
+        private account: AccountService,
18
+        private principal: Principal,
19
+        private languageService: JhiLanguageService,
20
+        private languageHelper: JhiLanguageHelper
21
+    ) {}
22
+
23
+    ngOnInit() {
24
+        this.principal.identity().then(account => {
25
+            this.settingsAccount = this.copyAccount(account);
26
+        });
27
+        this.languageHelper.getAll().then(languages => {
28
+            this.languages = languages;
29
+        });
30
+    }
31
+
32
+    save() {
33
+        this.account.save(this.settingsAccount).subscribe(
34
+            () => {
35
+                this.error = null;
36
+                this.success = 'OK';
37
+                this.principal.identity(true).then(account => {
38
+                    this.settingsAccount = this.copyAccount(account);
39
+                });
40
+                this.languageService.getCurrent().then(current => {
41
+                    if (this.settingsAccount.langKey !== current) {
42
+                        this.languageService.changeLanguage(this.settingsAccount.langKey);
43
+                    }
44
+                });
45
+            },
46
+            () => {
47
+                this.success = null;
48
+                this.error = 'ERROR';
49
+            }
50
+        );
51
+    }
52
+
53
+    copyAccount(account) {
54
+        return {
55
+            activated: account.activated,
56
+            email: account.email,
57
+            firstName: account.firstName,
58
+            langKey: account.langKey,
59
+            lastName: account.lastName,
60
+            login: account.login,
61
+            imageUrl: account.imageUrl
62
+        };
63
+    }
64
+}

+ 14
- 0
webapp/app/account/settings/settings.route.ts Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { UserRouteAccessService } from 'app/core';
4
+import { SettingsComponent } from './settings.component';
5
+
6
+export const settingsRoute: Route = {
7
+    path: 'settings',
8
+    component: SettingsComponent,
9
+    data: {
10
+        authorities: ['ROLE_USER'],
11
+        pageTitle: 'global.menu.account.settings'
12
+    },
13
+    canActivate: [UserRouteAccessService]
14
+};

+ 56
- 0
webapp/app/admin/admin.module.ts Näytä tiedosto

@@ -0,0 +1,56 @@
1
+import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
2
+import { RouterModule } from '@angular/router';
3
+import { JhiLanguageService } from 'ng-jhipster';
4
+import { JhiLanguageHelper } from 'app/core';
5
+import { ZipConnectSharedModule } from 'app/shared';
6
+/* jhipster-needle-add-admin-module-import - JHipster will add admin modules imports here */
7
+
8
+import {
9
+    adminState,
10
+    AuditsComponent,
11
+    UserMgmtComponent,
12
+    UserMgmtDetailComponent,
13
+    UserMgmtUpdateComponent,
14
+    UserMgmtDeleteDialogComponent,
15
+    LogsComponent,
16
+    JhiMetricsMonitoringModalComponent,
17
+    JhiMetricsMonitoringComponent,
18
+    JhiHealthModalComponent,
19
+    JhiHealthCheckComponent,
20
+    JhiConfigurationComponent,
21
+    JhiDocsComponent
22
+} from './';
23
+
24
+@NgModule({
25
+    imports: [
26
+        ZipConnectSharedModule,
27
+        RouterModule.forChild(adminState)
28
+        /* jhipster-needle-add-admin-module - JHipster will add admin modules here */
29
+    ],
30
+    declarations: [
31
+        AuditsComponent,
32
+        UserMgmtComponent,
33
+        UserMgmtDetailComponent,
34
+        UserMgmtUpdateComponent,
35
+        UserMgmtDeleteDialogComponent,
36
+        LogsComponent,
37
+        JhiConfigurationComponent,
38
+        JhiHealthCheckComponent,
39
+        JhiHealthModalComponent,
40
+        JhiDocsComponent,
41
+        JhiMetricsMonitoringComponent,
42
+        JhiMetricsMonitoringModalComponent
43
+    ],
44
+    providers: [{ provide: JhiLanguageService, useClass: JhiLanguageService }],
45
+    entryComponents: [UserMgmtDeleteDialogComponent, JhiHealthModalComponent, JhiMetricsMonitoringModalComponent],
46
+    schemas: [CUSTOM_ELEMENTS_SCHEMA]
47
+})
48
+export class ZipConnectAdminModule {
49
+    constructor(private languageService: JhiLanguageService, private languageHelper: JhiLanguageHelper) {
50
+        this.languageHelper.language.subscribe((languageKey: string) => {
51
+            if (languageKey !== undefined) {
52
+                this.languageService.changeLanguage(languageKey);
53
+            }
54
+        });
55
+    }
56
+}

+ 18
- 0
webapp/app/admin/admin.route.ts Näytä tiedosto

@@ -0,0 +1,18 @@
1
+import { Routes } from '@angular/router';
2
+
3
+import { auditsRoute, configurationRoute, docsRoute, healthRoute, logsRoute, metricsRoute, userMgmtRoute } from './';
4
+
5
+import { UserRouteAccessService } from 'app/core';
6
+
7
+const ADMIN_ROUTES = [auditsRoute, configurationRoute, docsRoute, healthRoute, logsRoute, ...userMgmtRoute, metricsRoute];
8
+
9
+export const adminState: Routes = [
10
+    {
11
+        path: '',
12
+        data: {
13
+            authorities: ['ROLE_ADMIN']
14
+        },
15
+        canActivate: [UserRouteAccessService],
16
+        children: ADMIN_ROUTES
17
+    }
18
+];

+ 3
- 0
webapp/app/admin/audits/audit-data.model.ts Näytä tiedosto

@@ -0,0 +1,3 @@
1
+export class AuditData {
2
+    constructor(public remoteAddress: string, public sessionId: string) {}
3
+}

+ 5
- 0
webapp/app/admin/audits/audit.model.ts Näytä tiedosto

@@ -0,0 +1,5 @@
1
+import { AuditData } from './audit-data.model';
2
+
3
+export class Audit {
4
+    constructor(public data: AuditData, public principal: string, public timestamp: string, public type: string) {}
5
+}

+ 52
- 0
webapp/app/admin/audits/audits.component.html Näytä tiedosto

@@ -0,0 +1,52 @@
1
+<div *ngIf="audits">
2
+    <h2 id="audits-page-heading" jhiTranslate="audits.title">Audits</h2>
3
+
4
+    <div class="row">
5
+        <div class="col-md-5">
6
+            <h4 jhiTranslate="audits.filter.title">Filter by date</h4>
7
+            <div class="input-group mb-3">
8
+                <div class="input-group-prepend">
9
+                    <span class="input-group-text" jhiTranslate="audits.filter.from">from</span>
10
+                </div>
11
+                <input type="date" class="form-control" name="start" [(ngModel)]="fromDate" (ngModelChange)="transition()" required/>
12
+
13
+                <div class="input-group-append">
14
+                    <span class="input-group-text" jhiTranslate="audits.filter.to">To</span>
15
+                </div>
16
+                <input type="date" class="form-control" name="end" [(ngModel)]="toDate" (ngModelChange)="transition()" required/>
17
+            </div>
18
+        </div>
19
+    </div>
20
+
21
+    <div class="table-responsive">
22
+        <table class="table table-sm table-striped">
23
+            <thead>
24
+            <tr jhiSort [(predicate)]="predicate" [(ascending)]="reverse" [callback]="transition.bind(this)">
25
+                <th jhiSortBy="auditEventDate"><span jhiTranslate="audits.table.header.date">Date</span><fa-icon [icon]="'sort'"></fa-icon></th>
26
+                <th jhiSortBy="principal"><span jhiTranslate="audits.table.header.principal">User</span><fa-icon [icon]="'sort'"></fa-icon></th>
27
+                <th jhiSortBy="auditEventType"><span jhiTranslate="audits.table.header.status">State</span><fa-icon [icon]="'sort'"></fa-icon></th>
28
+                <th><span jhiTranslate="audits.table.header.data">Extra data</span></th>
29
+            </tr>
30
+            </thead>
31
+            <tbody>
32
+            <tr *ngFor="let audit of audits">
33
+                <td><span>{{audit.timestamp| date:'medium'}}</span></td>
34
+                <td><small>{{audit.principal}}</small></td>
35
+                <td>{{audit.type}}</td>
36
+                <td>
37
+                    <span *ngIf="audit.data" ng-show="audit.data.message">{{audit.data.message}}</span>
38
+                    <span *ngIf="audit.data" ng-show="audit.data.remoteAddress"><span jhiTranslate="audits.table.data.remoteAddress">Remote Address</span> {{audit.data.remoteAddress}}</span>
39
+                </td>
40
+            </tr>
41
+            </tbody>
42
+        </table>
43
+    </div>
44
+    <div>
45
+        <div class="row justify-content-center">
46
+            <jhi-item-count [page]="page" [total]="totalItems" [itemsPerPage]="itemsPerPage"></jhi-item-count>
47
+        </div>
48
+        <div class="row justify-content-center">
49
+            <ngb-pagination [collectionSize]="totalItems" [(page)]="page" [pageSize]="itemsPerPage" [maxSize]="5" [rotate]="true" [boundaryLinks]="true" (pageChange)="loadPage(page)"></ngb-pagination>
50
+        </div>
51
+    </div>
52
+</div>

+ 128
- 0
webapp/app/admin/audits/audits.component.ts Näytä tiedosto

@@ -0,0 +1,128 @@
1
+import { Component, OnInit, OnDestroy } from '@angular/core';
2
+import { HttpResponse } from '@angular/common/http';
3
+import { DatePipe } from '@angular/common';
4
+import { ActivatedRoute, Router } from '@angular/router';
5
+import { JhiParseLinks, JhiAlertService } from 'ng-jhipster';
6
+
7
+import { ITEMS_PER_PAGE } from 'app/shared';
8
+import { Audit } from './audit.model';
9
+import { AuditsService } from './audits.service';
10
+
11
+@Component({
12
+    selector: 'jhi-audit',
13
+    templateUrl: './audits.component.html'
14
+})
15
+export class AuditsComponent implements OnInit, OnDestroy {
16
+    audits: Audit[];
17
+    fromDate: string;
18
+    itemsPerPage: any;
19
+    links: any;
20
+    queryCount: number;
21
+    page: number;
22
+    routeData: any;
23
+    predicate: any;
24
+    previousPage: any;
25
+    reverse: boolean;
26
+    toDate: string;
27
+    totalItems: number;
28
+
29
+    constructor(
30
+        private auditsService: AuditsService,
31
+        private alertService: JhiAlertService,
32
+        private parseLinks: JhiParseLinks,
33
+        private activatedRoute: ActivatedRoute,
34
+        private datePipe: DatePipe,
35
+        private router: Router
36
+    ) {
37
+        this.itemsPerPage = ITEMS_PER_PAGE;
38
+        this.routeData = this.activatedRoute.data.subscribe(data => {
39
+            this.page = data['pagingParams'].page;
40
+            this.previousPage = data['pagingParams'].page;
41
+            this.reverse = data['pagingParams'].ascending;
42
+            this.predicate = data['pagingParams'].predicate;
43
+        });
44
+    }
45
+
46
+    ngOnInit() {
47
+        this.today();
48
+        this.previousMonth();
49
+        this.loadAll();
50
+    }
51
+
52
+    ngOnDestroy() {
53
+        this.routeData.unsubscribe();
54
+    }
55
+
56
+    previousMonth() {
57
+        const dateFormat = 'yyyy-MM-dd';
58
+        let fromDate: Date = new Date();
59
+
60
+        if (fromDate.getMonth() === 0) {
61
+            fromDate = new Date(fromDate.getFullYear() - 1, 11, fromDate.getDate());
62
+        } else {
63
+            fromDate = new Date(fromDate.getFullYear(), fromDate.getMonth() - 1, fromDate.getDate());
64
+        }
65
+
66
+        this.fromDate = this.datePipe.transform(fromDate, dateFormat);
67
+    }
68
+
69
+    today() {
70
+        const dateFormat = 'yyyy-MM-dd';
71
+        // Today + 1 day - needed if the current day must be included
72
+        const today: Date = new Date();
73
+        today.setDate(today.getDate() + 1);
74
+        const date = new Date(today.getFullYear(), today.getMonth(), today.getDate());
75
+        this.toDate = this.datePipe.transform(date, dateFormat);
76
+    }
77
+
78
+    loadAll() {
79
+        this.auditsService
80
+            .query({
81
+                page: this.page - 1,
82
+                size: this.itemsPerPage,
83
+                sort: this.sort(),
84
+                fromDate: this.fromDate,
85
+                toDate: this.toDate
86
+            })
87
+            .subscribe(
88
+                (res: HttpResponse<Audit[]>) => this.onSuccess(res.body, res.headers),
89
+                (res: HttpResponse<any>) => this.onError(res.body)
90
+            );
91
+    }
92
+
93
+    sort() {
94
+        const result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')];
95
+        if (this.predicate !== 'id') {
96
+            result.push('id');
97
+        }
98
+        return result;
99
+    }
100
+
101
+    loadPage(page: number) {
102
+        if (page !== this.previousPage) {
103
+            this.previousPage = page;
104
+            this.transition();
105
+        }
106
+    }
107
+
108
+    transition() {
109
+        this.router.navigate(['/admin/audits'], {
110
+            queryParams: {
111
+                page: this.page,
112
+                sort: this.predicate + ',' + (this.reverse ? 'asc' : 'desc')
113
+            }
114
+        });
115
+        this.loadAll();
116
+    }
117
+
118
+    private onSuccess(data, headers) {
119
+        this.links = this.parseLinks.parse(headers.get('link'));
120
+        this.totalItems = headers.get('X-Total-Count');
121
+        this.queryCount = this.totalItems;
122
+        this.audits = data;
123
+    }
124
+
125
+    private onError(error) {
126
+        this.alertService.error(error.error, error.message, null);
127
+    }
128
+}

+ 17
- 0
webapp/app/admin/audits/audits.route.ts Näytä tiedosto

@@ -0,0 +1,17 @@
1
+import { Injectable } from '@angular/core';
2
+import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Route } from '@angular/router';
3
+import { JhiPaginationUtil, JhiResolvePagingParams } from 'ng-jhipster';
4
+
5
+import { AuditsComponent } from './audits.component';
6
+
7
+export const auditsRoute: Route = {
8
+    path: 'audits',
9
+    component: AuditsComponent,
10
+    resolve: {
11
+        pagingParams: JhiResolvePagingParams
12
+    },
13
+    data: {
14
+        pageTitle: 'audits.title',
15
+        defaultSort: 'auditEventDate,desc'
16
+    }
17
+};

+ 25
- 0
webapp/app/admin/audits/audits.service.ts Näytä tiedosto

@@ -0,0 +1,25 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { createRequestOption } from 'app/shared';
6
+import { SERVER_API_URL } from 'app/app.constants';
7
+import { Audit } from './audit.model';
8
+
9
+@Injectable({ providedIn: 'root' })
10
+export class AuditsService {
11
+    constructor(private http: HttpClient) {}
12
+
13
+    query(req: any): Observable<HttpResponse<Audit[]>> {
14
+        const params: HttpParams = createRequestOption(req);
15
+        params.set('fromDate', req.fromDate);
16
+        params.set('toDate', req.toDate);
17
+
18
+        const requestURL = SERVER_API_URL + 'management/audits';
19
+
20
+        return this.http.get<Audit[]>(requestURL, {
21
+            params,
22
+            observe: 'response'
23
+        });
24
+    }
25
+}

+ 46
- 0
webapp/app/admin/configuration/configuration.component.html Näytä tiedosto

@@ -0,0 +1,46 @@
1
+<div *ngIf="allConfiguration && configuration">
2
+    <h2 id="configuration-page-heading" jhiTranslate="configuration.title">Configuration</h2>
3
+
4
+    <span jhiTranslate="configuration.filter">Filter (by prefix)</span> <input type="text" [(ngModel)]="filter" class="form-control">
5
+    <h3>Spring configuration</h3>
6
+    <table class="table table-striped table-bordered table-responsive d-table">
7
+        <thead>
8
+        <tr>
9
+            <th class="w-40" (click)="orderProp = 'prefix'; reverse=!reverse"><span jhiTranslate="configuration.table.prefix">Prefix</span></th>
10
+            <th class="w-60" (click)="orderProp = 'properties'; reverse=!reverse"><span jhiTranslate="configuration.table.properties">Properties</span></th>
11
+        </tr>
12
+        </thead>
13
+        <tbody>
14
+        <tr *ngFor="let entry of (configuration | pureFilter:filter:'prefix' | orderBy:orderProp:reverse)">
15
+            <td><span>{{entry.prefix}}</span></td>
16
+            <td>
17
+                <div class="row" *ngFor="let key of keys(entry.properties)">
18
+                    <div class="col-md-4">{{key}}</div>
19
+                    <div class="col-md-8">
20
+                        <span class="float-right badge-secondary break">{{entry.properties[key] | json}}</span>
21
+                    </div>
22
+                </div>
23
+            </td>
24
+        </tr>
25
+        </tbody>
26
+    </table>
27
+    <div *ngFor="let key of keys(allConfiguration)">
28
+        <h4><span>{{key}}</span></h4>
29
+        <table class="table table-sm table-striped table-bordered table-responsive d-table">
30
+            <thead>
31
+            <tr>
32
+                <th class="w-40">Property</th>
33
+                <th class="w-60">Value</th>
34
+            </tr>
35
+            </thead>
36
+            <tbody>
37
+            <tr *ngFor="let item of allConfiguration[key]">
38
+                <td class="break">{{item.key}}</td>
39
+                <td class="break">
40
+                    <span class="float-right badge-secondary break">{{item.val}}</span>
41
+                </td>
42
+            </tr>
43
+            </tbody>
44
+        </table>
45
+    </div>
46
+</div>

+ 43
- 0
webapp/app/admin/configuration/configuration.component.ts Näytä tiedosto

@@ -0,0 +1,43 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+import { JhiConfigurationService } from './configuration.service';
4
+
5
+@Component({
6
+    selector: 'jhi-configuration',
7
+    templateUrl: './configuration.component.html'
8
+})
9
+export class JhiConfigurationComponent implements OnInit {
10
+    allConfiguration: any = null;
11
+    configuration: any = null;
12
+    configKeys: any[];
13
+    filter: string;
14
+    orderProp: string;
15
+    reverse: boolean;
16
+
17
+    constructor(private configurationService: JhiConfigurationService) {
18
+        this.configKeys = [];
19
+        this.filter = '';
20
+        this.orderProp = 'prefix';
21
+        this.reverse = false;
22
+    }
23
+
24
+    keys(dict): Array<string> {
25
+        return dict === undefined ? [] : Object.keys(dict);
26
+    }
27
+
28
+    ngOnInit() {
29
+        this.configurationService.get().subscribe(configuration => {
30
+            this.configuration = configuration;
31
+
32
+            for (const config of configuration) {
33
+                if (config.properties !== undefined) {
34
+                    this.configKeys.push(Object.keys(config.properties));
35
+                }
36
+            }
37
+        });
38
+
39
+        this.configurationService.getEnv().subscribe(configuration => {
40
+            this.allConfiguration = configuration;
41
+        });
42
+    }
43
+}

+ 11
- 0
webapp/app/admin/configuration/configuration.route.ts Näytä tiedosto

@@ -0,0 +1,11 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { JhiConfigurationComponent } from './configuration.component';
4
+
5
+export const configurationRoute: Route = {
6
+    path: 'jhi-configuration',
7
+    component: JhiConfigurationComponent,
8
+    data: {
9
+        pageTitle: 'configuration.title'
10
+    }
11
+};

+ 67
- 0
webapp/app/admin/configuration/configuration.service.ts Näytä tiedosto

@@ -0,0 +1,67 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient, HttpResponse } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+import { map } from 'rxjs/operators';
5
+
6
+import { SERVER_API_URL } from 'app/app.constants';
7
+
8
+@Injectable({ providedIn: 'root' })
9
+export class JhiConfigurationService {
10
+    constructor(private http: HttpClient) {}
11
+
12
+    get(): Observable<any> {
13
+        return this.http.get(SERVER_API_URL + 'management/configprops', { observe: 'response' }).pipe(
14
+            map((res: HttpResponse<any>) => {
15
+                const properties: any[] = [];
16
+                const propertiesObject = this.getConfigPropertiesObjects(res.body);
17
+                for (const key in propertiesObject) {
18
+                    if (propertiesObject.hasOwnProperty(key)) {
19
+                        properties.push(propertiesObject[key]);
20
+                    }
21
+                }
22
+
23
+                return properties.sort((propertyA, propertyB) => {
24
+                    return propertyA.prefix === propertyB.prefix ? 0 : propertyA.prefix < propertyB.prefix ? -1 : 1;
25
+                });
26
+            })
27
+        );
28
+    }
29
+
30
+    getConfigPropertiesObjects(res: Object) {
31
+        // This code is for Spring Boot 2
32
+        if (res['contexts'] !== undefined) {
33
+            for (const key in res['contexts']) {
34
+                // If the key is not bootstrap, it will be the ApplicationContext Id
35
+                // For default app, it is baseName
36
+                // For microservice, it is baseName-1
37
+                if (!key.startsWith('bootstrap')) {
38
+                    return res['contexts'][key]['beans'];
39
+                }
40
+            }
41
+        }
42
+        // by default, use the default ApplicationContext Id
43
+        return res['contexts']['ZipConnect']['beans'];
44
+    }
45
+
46
+    getEnv(): Observable<any> {
47
+        return this.http.get(SERVER_API_URL + 'management/env', { observe: 'response' }).pipe(
48
+            map((res: HttpResponse<any>) => {
49
+                const properties: any = {};
50
+                const propertySources = res.body['propertySources'];
51
+
52
+                for (const propertyObject of propertySources) {
53
+                    const name = propertyObject['name'];
54
+                    const detailProperties = propertyObject['properties'];
55
+                    const vals: any[] = [];
56
+                    for (const keyDetail in detailProperties) {
57
+                        if (detailProperties.hasOwnProperty(keyDetail)) {
58
+                            vals.push({ key: keyDetail, val: detailProperties[keyDetail]['value'] });
59
+                        }
60
+                    }
61
+                    properties[name] = vals;
62
+                }
63
+                return properties;
64
+            })
65
+        );
66
+    }
67
+}

+ 2
- 0
webapp/app/admin/docs/docs.component.html Näytä tiedosto

@@ -0,0 +1,2 @@
1
+<iframe src="swagger-ui/index.html" width="100%" height="900" seamless
2
+    target="_top" title="Swagger UI" class="border-0"></iframe>

+ 9
- 0
webapp/app/admin/docs/docs.component.ts Näytä tiedosto

@@ -0,0 +1,9 @@
1
+import { Component } from '@angular/core';
2
+
3
+@Component({
4
+    selector: 'jhi-docs',
5
+    templateUrl: './docs.component.html'
6
+})
7
+export class JhiDocsComponent {
8
+    constructor() {}
9
+}

+ 11
- 0
webapp/app/admin/docs/docs.route.ts Näytä tiedosto

@@ -0,0 +1,11 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { JhiDocsComponent } from './docs.component';
4
+
5
+export const docsRoute: Route = {
6
+    path: 'docs',
7
+    component: JhiDocsComponent,
8
+    data: {
9
+        pageTitle: 'global.menu.admin.apidocs'
10
+    }
11
+};

+ 36
- 0
webapp/app/admin/health/health-modal.component.html Näytä tiedosto

@@ -0,0 +1,36 @@
1
+<div class="modal-header">
2
+    <h4 class="modal-title" id="showHealthLabel">
3
+		{{'health.indicator.' + baseName(currentHealth.name) | translate}}
4
+        {{subSystemName(currentHealth.name)}}
5
+    </h4>
6
+    <button aria-label="Close" data-dismiss="modal" class="close" type="button" (click)="activeModal.dismiss('closed')"><span aria-hidden="true">&times;</span>
7
+    </button>
8
+</div>
9
+<div class="modal-body pad">
10
+    <div *ngIf="currentHealth.details">
11
+        <h5 jhiTranslate="health.details.properties">Properties</h5>
12
+        <div class="table-responsive">
13
+            <table class="table table-striped">
14
+                <thead>
15
+                    <tr>
16
+                        <th class="text-left" jhiTranslate="health.details.name">Name</th>
17
+                        <th class="text-left" jhiTranslate="health.details.value">Value</th>
18
+                    </tr>
19
+                </thead>
20
+                <tbody>
21
+                    <tr *ngFor="let entry of currentHealth.details.details | keys">
22
+                        <td class="text-left">{{entry.key}}</td>
23
+                        <td class="text-left">{{readableValue(entry.value)}}</td>
24
+                    </tr>
25
+                </tbody>
26
+            </table>
27
+        </div>
28
+    </div>
29
+    <div *ngIf="currentHealth.error">
30
+        <h4 jhiTranslate="health.details.error">Error</h4>
31
+            <pre>{{currentHealth.error}}</pre>
32
+    </div>
33
+</div>
34
+<div class="modal-footer">
35
+    <button data-dismiss="modal" class="btn btn-secondary float-left" type="button" (click)="activeModal.dismiss('closed')">Done</button>
36
+</div>

+ 41
- 0
webapp/app/admin/health/health-modal.component.ts Näytä tiedosto

@@ -0,0 +1,41 @@
1
+import { Component } from '@angular/core';
2
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
3
+
4
+import { JhiHealthService } from './health.service';
5
+
6
+@Component({
7
+    selector: 'jhi-health-modal',
8
+    templateUrl: './health-modal.component.html'
9
+})
10
+export class JhiHealthModalComponent {
11
+    currentHealth: any;
12
+
13
+    constructor(private healthService: JhiHealthService, public activeModal: NgbActiveModal) {}
14
+
15
+    baseName(name) {
16
+        return this.healthService.getBaseName(name);
17
+    }
18
+
19
+    subSystemName(name) {
20
+        return this.healthService.getSubSystemName(name);
21
+    }
22
+
23
+    readableValue(value: number) {
24
+        if (this.currentHealth.name === 'diskSpace') {
25
+            // Should display storage space in an human readable unit
26
+            const val = value / 1073741824;
27
+            if (val > 1) {
28
+                // Value
29
+                return val.toFixed(2) + ' GB';
30
+            } else {
31
+                return (value / 1048576).toFixed(2) + ' MB';
32
+            }
33
+        }
34
+
35
+        if (typeof value === 'object') {
36
+            return JSON.stringify(value);
37
+        } else {
38
+            return value.toString();
39
+        }
40
+    }
41
+}

+ 34
- 0
webapp/app/admin/health/health.component.html Näytä tiedosto

@@ -0,0 +1,34 @@
1
+<div>
2
+    <h2>
3
+        <span id="health-page-heading" jhiTranslate="health.title">Health Checks</span>
4
+        <button class="btn btn-primary float-right" (click)="refresh()">
5
+            <fa-icon [icon]="'sync'"></fa-icon> <span jhiTranslate="health.refresh.button">Refresh</span>
6
+        </button>
7
+    </h2>
8
+    <div class="table-responsive">
9
+        <table id="healthCheck" class="table table-striped">
10
+            <thead>
11
+                <tr>
12
+                    <th jhiTranslate="health.table.service">Service Name</th>
13
+                    <th class="text-center" jhiTranslate="health.table.status">Status</th>
14
+                    <th class="text-center" jhiTranslate="health.details.details">Details</th>
15
+                </tr>
16
+            </thead>
17
+            <tbody>
18
+                <tr *ngFor="let health of healthData">
19
+                    <td>{{'health.indicator.' + baseName(health.name) | translate}} {{subSystemName(health.name)}}</td>
20
+                    <td class="text-center">
21
+                        <span class="badge" [ngClass]="getBadgeClass(health.status)" jhiTranslate="{{'health.status.' + health.status}}">
22
+                            {{health.status}}
23
+                        </span>
24
+                    </td>
25
+                    <td class="text-center">
26
+                        <a class="hand" (click)="showHealth(health)" *ngIf="health.details || health.error">
27
+                            <fa-icon [icon]="'eye'"></fa-icon>
28
+                        </a>
29
+                    </td>
30
+                </tr>
31
+            </tbody>
32
+        </table>
33
+    </div>
34
+</div>

+ 66
- 0
webapp/app/admin/health/health.component.ts Näytä tiedosto

@@ -0,0 +1,66 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
3
+
4
+import { JhiHealthService } from './health.service';
5
+import { JhiHealthModalComponent } from './health-modal.component';
6
+
7
+@Component({
8
+    selector: 'jhi-health',
9
+    templateUrl: './health.component.html'
10
+})
11
+export class JhiHealthCheckComponent implements OnInit {
12
+    healthData: any;
13
+    updatingHealth: boolean;
14
+
15
+    constructor(private modalService: NgbModal, private healthService: JhiHealthService) {}
16
+
17
+    ngOnInit() {
18
+        this.refresh();
19
+    }
20
+
21
+    baseName(name: string) {
22
+        return this.healthService.getBaseName(name);
23
+    }
24
+
25
+    getBadgeClass(statusState) {
26
+        if (statusState === 'UP') {
27
+            return 'badge-success';
28
+        } else {
29
+            return 'badge-danger';
30
+        }
31
+    }
32
+
33
+    refresh() {
34
+        this.updatingHealth = true;
35
+
36
+        this.healthService.checkHealth().subscribe(
37
+            health => {
38
+                this.healthData = this.healthService.transformHealthData(health);
39
+                this.updatingHealth = false;
40
+            },
41
+            error => {
42
+                if (error.status === 503) {
43
+                    this.healthData = this.healthService.transformHealthData(error.error);
44
+                    this.updatingHealth = false;
45
+                }
46
+            }
47
+        );
48
+    }
49
+
50
+    showHealth(health: any) {
51
+        const modalRef = this.modalService.open(JhiHealthModalComponent);
52
+        modalRef.componentInstance.currentHealth = health;
53
+        modalRef.result.then(
54
+            result => {
55
+                // Left blank intentionally, nothing to do here
56
+            },
57
+            reason => {
58
+                // Left blank intentionally, nothing to do here
59
+            }
60
+        );
61
+    }
62
+
63
+    subSystemName(name: string) {
64
+        return this.healthService.getSubSystemName(name);
65
+    }
66
+}

+ 11
- 0
webapp/app/admin/health/health.route.ts Näytä tiedosto

@@ -0,0 +1,11 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { JhiHealthCheckComponent } from './health.component';
4
+
5
+export const healthRoute: Route = {
6
+    path: 'jhi-health',
7
+    component: JhiHealthCheckComponent,
8
+    data: {
9
+        pageTitle: 'health.title'
10
+    }
11
+};

+ 133
- 0
webapp/app/admin/health/health.service.ts Näytä tiedosto

@@ -0,0 +1,133 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+
7
+@Injectable({ providedIn: 'root' })
8
+export class JhiHealthService {
9
+    separator: string;
10
+
11
+    constructor(private http: HttpClient) {
12
+        this.separator = '.';
13
+    }
14
+
15
+    checkHealth(): Observable<any> {
16
+        return this.http.get(SERVER_API_URL + 'management/health');
17
+    }
18
+
19
+    transformHealthData(data): any {
20
+        const response = [];
21
+        this.flattenHealthData(response, null, data.details);
22
+        return response;
23
+    }
24
+
25
+    getBaseName(name): string {
26
+        if (name) {
27
+            const split = name.split('.');
28
+            return split[0];
29
+        }
30
+    }
31
+
32
+    getSubSystemName(name): string {
33
+        if (name) {
34
+            const split = name.split('.');
35
+            split.splice(0, 1);
36
+            const remainder = split.join('.');
37
+            return remainder ? ' - ' + remainder : '';
38
+        }
39
+    }
40
+
41
+    /* private methods */
42
+    private addHealthObject(result, isLeaf, healthObject, name): any {
43
+        const healthData: any = {
44
+            name
45
+        };
46
+
47
+        const details = {};
48
+        let hasDetails = false;
49
+
50
+        for (const key in healthObject) {
51
+            if (healthObject.hasOwnProperty(key)) {
52
+                const value = healthObject[key];
53
+                if (key === 'status' || key === 'error') {
54
+                    healthData[key] = value;
55
+                } else {
56
+                    if (!this.isHealthObject(value)) {
57
+                        details[key] = value;
58
+                        hasDetails = true;
59
+                    }
60
+                }
61
+            }
62
+        }
63
+
64
+        // Add the details
65
+        if (hasDetails) {
66
+            healthData.details = details;
67
+        }
68
+
69
+        // Only add nodes if they provide additional information
70
+        if (isLeaf || hasDetails || healthData.error) {
71
+            result.push(healthData);
72
+        }
73
+        return healthData;
74
+    }
75
+
76
+    private flattenHealthData(result, path, data): any {
77
+        for (const key in data) {
78
+            if (data.hasOwnProperty(key)) {
79
+                const value = data[key];
80
+                if (this.isHealthObject(value)) {
81
+                    if (this.hasSubSystem(value)) {
82
+                        this.addHealthObject(result, false, value, this.getModuleName(path, key));
83
+                        this.flattenHealthData(result, this.getModuleName(path, key), value);
84
+                    } else {
85
+                        this.addHealthObject(result, true, value, this.getModuleName(path, key));
86
+                    }
87
+                }
88
+            }
89
+        }
90
+        return result;
91
+    }
92
+
93
+    private getModuleName(path, name): string {
94
+        let result;
95
+        if (path && name) {
96
+            result = path + this.separator + name;
97
+        } else if (path) {
98
+            result = path;
99
+        } else if (name) {
100
+            result = name;
101
+        } else {
102
+            result = '';
103
+        }
104
+        return result;
105
+    }
106
+
107
+    private hasSubSystem(healthObject): boolean {
108
+        let result = false;
109
+
110
+        for (const key in healthObject) {
111
+            if (healthObject.hasOwnProperty(key)) {
112
+                const value = healthObject[key];
113
+                if (value && value.status) {
114
+                    result = true;
115
+                }
116
+            }
117
+        }
118
+        return result;
119
+    }
120
+
121
+    private isHealthObject(healthObject): boolean {
122
+        let result = false;
123
+
124
+        for (const key in healthObject) {
125
+            if (healthObject.hasOwnProperty(key)) {
126
+                if (key === 'status') {
127
+                    result = true;
128
+                }
129
+            }
130
+        }
131
+        return result;
132
+    }
133
+}

+ 28
- 0
webapp/app/admin/index.ts Näytä tiedosto

@@ -0,0 +1,28 @@
1
+export * from './audits/audits.component';
2
+export * from './audits/audits.service';
3
+export * from './audits/audits.route';
4
+export * from './audits/audit.model';
5
+export * from './audits/audit-data.model';
6
+export * from './configuration/configuration.component';
7
+export * from './configuration/configuration.service';
8
+export * from './configuration/configuration.route';
9
+export * from './docs/docs.component';
10
+export * from './docs/docs.route';
11
+export * from './health/health.component';
12
+export * from './health/health-modal.component';
13
+export * from './health/health.service';
14
+export * from './health/health.route';
15
+export * from './logs/logs.component';
16
+export * from './logs/logs.service';
17
+export * from './logs/logs.route';
18
+export * from './logs/log.model';
19
+export * from './metrics/metrics.component';
20
+export * from './metrics/metrics-modal.component';
21
+export * from './metrics/metrics.service';
22
+export * from './metrics/metrics.route';
23
+export * from './user-management/user-management-update.component';
24
+export * from './user-management/user-management-delete-dialog.component';
25
+export * from './user-management/user-management-detail.component';
26
+export * from './user-management/user-management.component';
27
+export * from './user-management/user-management.route';
28
+export * from './admin.route';

+ 3
- 0
webapp/app/admin/logs/log.model.ts Näytä tiedosto

@@ -0,0 +1,3 @@
1
+export class Log {
2
+    constructor(public name: string, public level: string) {}
3
+}

+ 28
- 0
webapp/app/admin/logs/logs.component.html Näytä tiedosto

@@ -0,0 +1,28 @@
1
+<div class="table-responsive" *ngIf="loggers">
2
+    <h2 id="logs-page-heading" jhiTranslate="logs.title">Logs</h2>
3
+
4
+    <p jhiTranslate="logs.nbloggers" translateValues="{total: '{{ loggers.length }}'}">There are {{ loggers.length }} loggers.</p>
5
+
6
+    <span jhiTranslate="logs.filter">Filter</span> <input type="text" [(ngModel)]="filter" class="form-control">
7
+
8
+    <table class="table table-sm table-striped table-bordered">
9
+        <thead>
10
+        <tr title="click to order">
11
+            <th (click)="orderProp = 'name'; reverse=!reverse"><span jhiTranslate="logs.table.name">Name</span></th>
12
+            <th (click)="orderProp = 'level'; reverse=!reverse"><span jhiTranslate="logs.table.level">Level</span></th>
13
+        </tr>
14
+        </thead>
15
+
16
+        <tr *ngFor="let logger of (loggers | pureFilter:filter:'name' | orderBy:orderProp:reverse)">
17
+            <td><small>{{logger.name | slice:0:140}}</small></td>
18
+            <td>
19
+                <button (click)="changeLevel(logger.name, 'TRACE')" [ngClass]="(logger.level=='TRACE') ? 'btn-primary' : 'btn-light'" class="btn btn-sm">TRACE</button>
20
+                <button (click)="changeLevel(logger.name, 'DEBUG')" [ngClass]="(logger.level=='DEBUG') ? 'btn-success' : 'btn-light'" class="btn btn-sm">DEBUG</button>
21
+                <button (click)="changeLevel(logger.name, 'INFO')" [ngClass]="(logger.level=='INFO') ? 'btn-info' : 'btn-light'" class="btn btn-sm">INFO</button>
22
+                <button (click)="changeLevel(logger.name, 'WARN')" [ngClass]="(logger.level=='WARN') ? 'btn-warning' : 'btn-light'" class="btn btn-sm">WARN</button>
23
+                <button (click)="changeLevel(logger.name, 'ERROR')" [ngClass]="(logger.level=='ERROR') ? 'btn-danger' : 'btn-light'" class="btn btn-sm">ERROR</button>
24
+                <button (click)="changeLevel(logger.name, 'OFF')" [ngClass]="(logger.level=='OFF') ? 'btn-secondary' : 'btn-light'" class="btn btn-sm">OFF</button>
25
+            </td>
26
+        </tr>
27
+    </table>
28
+</div>

+ 32
- 0
webapp/app/admin/logs/logs.component.ts Näytä tiedosto

@@ -0,0 +1,32 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+import { Log } from './log.model';
4
+import { LogsService } from './logs.service';
5
+
6
+@Component({
7
+    selector: 'jhi-logs',
8
+    templateUrl: './logs.component.html'
9
+})
10
+export class LogsComponent implements OnInit {
11
+    loggers: Log[];
12
+    filter: string;
13
+    orderProp: string;
14
+    reverse: boolean;
15
+
16
+    constructor(private logsService: LogsService) {
17
+        this.filter = '';
18
+        this.orderProp = 'name';
19
+        this.reverse = false;
20
+    }
21
+
22
+    ngOnInit() {
23
+        this.logsService.findAll().subscribe(response => (this.loggers = response.body));
24
+    }
25
+
26
+    changeLevel(name: string, level: string) {
27
+        const log = new Log(name, level);
28
+        this.logsService.changeLevel(log).subscribe(() => {
29
+            this.logsService.findAll().subscribe(response => (this.loggers = response.body));
30
+        });
31
+    }
32
+}

+ 11
- 0
webapp/app/admin/logs/logs.route.ts Näytä tiedosto

@@ -0,0 +1,11 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { LogsComponent } from './logs.component';
4
+
5
+export const logsRoute: Route = {
6
+    path: 'logs',
7
+    component: LogsComponent,
8
+    data: {
9
+        pageTitle: 'logs.title'
10
+    }
11
+};

+ 19
- 0
webapp/app/admin/logs/logs.service.ts Näytä tiedosto

@@ -0,0 +1,19 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient, HttpResponse } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+import { Log } from './log.model';
7
+
8
+@Injectable({ providedIn: 'root' })
9
+export class LogsService {
10
+    constructor(private http: HttpClient) {}
11
+
12
+    changeLevel(log: Log): Observable<HttpResponse<any>> {
13
+        return this.http.put(SERVER_API_URL + 'management/logs', log, { observe: 'response' });
14
+    }
15
+
16
+    findAll(): Observable<HttpResponse<Log[]>> {
17
+        return this.http.get<Log[]>(SERVER_API_URL + 'management/logs', { observe: 'response' });
18
+    }
19
+}

+ 56
- 0
webapp/app/admin/metrics/metrics-modal.component.html Näytä tiedosto

@@ -0,0 +1,56 @@
1
+<!-- Modal used to display the threads dump -->
2
+<div class="modal-header">
3
+    <h4 class="modal-title" jhiTranslate="metrics.jvm.threads.dump.title">Threads dump</h4>
4
+    <button type="button" class="close" (click)="activeModal.dismiss('closed')">&times;</button>
5
+</div>
6
+<div class="modal-body">
7
+    <span class="badge badge-primary" (click)="threadDumpFilter = {}">All&nbsp;<span class="badge badge-pill badge-default">{{threadDumpAll}}</span></span>&nbsp;
8
+    <span class="badge badge-success" (click)="threadDumpFilter = {threadState: 'RUNNABLE'}">Runnable&nbsp;<span class="badge badge-pill badge-default">{{threadDumpRunnable}}</span></span>&nbsp;
9
+    <span class="badge badge-info" (click)="threadDumpFilter = {threadState: 'WAITING'}">Waiting&nbsp;<span class="badge badge-pill badge-default">{{threadDumpWaiting}}</span></span>&nbsp;
10
+    <span class="badge badge-warning" (click)="threadDumpFilter = {threadState: 'TIMED_WAITING'}">Timed Waiting&nbsp;<span class="badge badge-pill badge-default">{{threadDumpTimedWaiting}}</span></span>&nbsp;
11
+    <span class="badge badge-danger" (click)="threadDumpFilter = {threadState: 'BLOCKED'}">Blocked&nbsp;<span class="badge badge-pill badge-default">{{threadDumpBlocked}}</span></span>&nbsp;
12
+    <div class="mt-2">&nbsp;</div>
13
+    Filter
14
+    <input type="text" [(ngModel)]="threadDumpFilter" class="form-control">
15
+    <div class="pad" *ngFor="let entry of threadDump | pureFilter:threadDumpFilter:'lockName' | keys">
16
+        <h6>
17
+            <span class="badge" [ngClass]="getBadgeClass(entry.value.threadState)">{{entry.value.threadState}}</span>&nbsp;{{entry.value.threadName}} (ID {{entry.value.threadId}})
18
+            <a (click)="entry.show = !entry.show" href="javascript:void(0);">
19
+               <span [hidden]="entry.show" jhiTranslate="metrics.jvm.threads.dump.show">Show StackTrace</span>
20
+               <span [hidden]="!entry.show" jhiTranslate="metrics.jvm.threads.dump.hide">Hide StackTrace</span>
21
+            </a>
22
+        </h6>
23
+        <div class="card" [hidden]="!entry.show">
24
+            <div class="card-body">
25
+                <div *ngFor="let st of entry.value.stackTrace | keys" class="break">
26
+                    <samp>{{st.value.className}}.{{st.value.methodName}}(<code>{{st.value.fileName}}:{{st.value.lineNumber}}</code>)</samp>
27
+                    <span class="mt-1"></span>
28
+                </div>
29
+            </div>
30
+        </div>
31
+        <table class="table table-sm table-responsive">
32
+            <thead>
33
+                <tr>
34
+                    <th jhiTranslate="metrics.jvm.threads.dump.blockedtime">Blocked Time</th>
35
+                    <th jhiTranslate="metrics.jvm.threads.dump.blockedcount">Blocked Count</th>
36
+                    <th jhiTranslate="metrics.jvm.threads.dump.waitedtime">Waited Time</th>
37
+                    <th jhiTranslate="metrics.jvm.threads.dump.waitedcount">Waited Count</th>
38
+                    <th jhiTranslate="metrics.jvm.threads.dump.lockname">Lock Name</th>
39
+                </tr>
40
+            </thead>
41
+            <tbody>
42
+                <tr>
43
+                    <td>{{entry.value.blockedTime}}</td>
44
+                    <td>{{entry.value.blockedCount}}</td>
45
+                    <td>{{entry.value.waitedTime}}</td>
46
+                    <td>{{entry.value.waitedCount}}</td>
47
+                    <td class="thread-dump-modal-lock" title="{{entry.value.lockName}}"><code>{{entry.value.lockName}}</code></td>
48
+                </tr>
49
+            </tbody>
50
+        </table>
51
+
52
+    </div>
53
+</div>
54
+<div class="modal-footer">
55
+    <button type="button" class="btn btn-secondary float-left" data-dismiss="modal" (click)="activeModal.dismiss('closed')">Done</button>
56
+</div>

+ 46
- 0
webapp/app/admin/metrics/metrics-modal.component.ts Näytä tiedosto

@@ -0,0 +1,46 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
3
+
4
+@Component({
5
+    selector: 'jhi-metrics-modal',
6
+    templateUrl: './metrics-modal.component.html'
7
+})
8
+export class JhiMetricsMonitoringModalComponent implements OnInit {
9
+    threadDumpFilter: any;
10
+    threadDump: any;
11
+    threadDumpAll = 0;
12
+    threadDumpBlocked = 0;
13
+    threadDumpRunnable = 0;
14
+    threadDumpTimedWaiting = 0;
15
+    threadDumpWaiting = 0;
16
+
17
+    constructor(public activeModal: NgbActiveModal) {}
18
+
19
+    ngOnInit() {
20
+        this.threadDump.forEach(value => {
21
+            if (value.threadState === 'RUNNABLE') {
22
+                this.threadDumpRunnable += 1;
23
+            } else if (value.threadState === 'WAITING') {
24
+                this.threadDumpWaiting += 1;
25
+            } else if (value.threadState === 'TIMED_WAITING') {
26
+                this.threadDumpTimedWaiting += 1;
27
+            } else if (value.threadState === 'BLOCKED') {
28
+                this.threadDumpBlocked += 1;
29
+            }
30
+        });
31
+
32
+        this.threadDumpAll = this.threadDumpRunnable + this.threadDumpWaiting + this.threadDumpTimedWaiting + this.threadDumpBlocked;
33
+    }
34
+
35
+    getBadgeClass(threadState) {
36
+        if (threadState === 'RUNNABLE') {
37
+            return 'badge-success';
38
+        } else if (threadState === 'WAITING') {
39
+            return 'badge-info';
40
+        } else if (threadState === 'TIMED_WAITING') {
41
+            return 'badge-warning';
42
+        } else if (threadState === 'BLOCKED') {
43
+            return 'badge-danger';
44
+        }
45
+    }
46
+}

+ 216
- 0
webapp/app/admin/metrics/metrics.component.html Näytä tiedosto

@@ -0,0 +1,216 @@
1
+<div>
2
+    <h2>
3
+        <span id="metrics-page-heading" jhiTranslate="metrics.title">Application Metrics</span>
4
+        <button class="btn btn-primary float-right" (click)="refresh()">
5
+            <fa-icon [icon]="'sync'"></fa-icon> <span jhiTranslate="metrics.refresh.button">Refresh</span>
6
+        </button>
7
+    </h2>
8
+
9
+    <h3 jhiTranslate="metrics.jvm.title">JVM Metrics</h3>
10
+    <div class="row" *ngIf="!updatingMetrics">
11
+        <div class="col-md-4">
12
+            <b jhiTranslate="metrics.jvm.memory.title">Memory</b>
13
+            <p><span jhiTranslate="metrics.jvm.memory.total">Total Memory</span> ({{metrics.gauges['jvm.memory.total.used'].value / 1048576 | number:'1.0-0'}}M / {{metrics.gauges['jvm.memory.total.max'].value / 1048576 | number:'1.0-0'}}M)</p>
14
+            <ngb-progressbar type="success" [max]="metrics.gauges['jvm.memory.total.max'].value" [value]="metrics.gauges['jvm.memory.total.used'].value" [striped]="true" [animated]="false">
15
+                <span>{{metrics.gauges['jvm.memory.total.used'].value * 100 / metrics.gauges['jvm.memory.total.max'].value  | number:'1.0-0'}}%</span>
16
+            </ngb-progressbar>
17
+            <p><span jhiTranslate="metrics.jvm.memory.heap">Heap Memory</span> ({{metrics.gauges['jvm.memory.heap.used'].value / 1048576 | number:'1.0-0'}}M / {{metrics.gauges['jvm.memory.heap.max'].value / 1048576 | number:'1.0-0'}}M)</p>
18
+            <ngb-progressbar [max]="metrics.gauges['jvm.memory.heap.max'].value" [value]="metrics.gauges['jvm.memory.heap.used'].value" [striped]="true" [animated]="false" type="success">
19
+                <span>{{metrics.gauges['jvm.memory.heap.used'].value * 100 / metrics.gauges['jvm.memory.heap.max'].value  | number:'1.0-0'}}%</span>
20
+            </ngb-progressbar>
21
+            <p><span jhiTranslate="metrics.jvm.memory.nonheap">Non-Heap Memory</span> ({{metrics.gauges['jvm.memory.non-heap.used'].value / 1048576 | number:'1.0-0'}}M / {{metrics.gauges['jvm.memory.non-heap.committed'].value / 1048576 | number:'1.0-0'}}M)</p>
22
+            <ngb-progressbar [max]="metrics.gauges['jvm.memory.non-heap.committed'].value" [value]="metrics.gauges['jvm.memory.non-heap.used'].value" [striped]="true" [animated]="false" type="success">
23
+                <span>{{metrics.gauges['jvm.memory.non-heap.used'].value * 100 / metrics.gauges['jvm.memory.non-heap.committed'].value  | number:'1.0-0'}}%</span>
24
+            </ngb-progressbar>
25
+        </div>
26
+        <div class="col-md-4">
27
+            <b jhiTranslate="metrics.jvm.threads.title">Threads</b> (Total: {{metrics.gauges['jvm.threads.count'].value}}) <a class="hand" (click)="refreshThreadDumpData()" data-toggle="modal" data-target="#threadDump"><fa-icon [icon]="'eye'"></fa-icon></a>
28
+            <p><span jhiTranslate="metrics.jvm.threads.runnable">Runnable</span> {{metrics.gauges['jvm.threads.runnable.count'].value}}</p>
29
+            <ngb-progressbar [value]="metrics.gauges['jvm.threads.runnable.count'].value" [max]="metrics.gauges['jvm.threads.count'].value" [striped]="true" [animated]="false" type="success">
30
+                <span>{{metrics.gauges['jvm.threads.runnable.count'].value * 100 / metrics.gauges['jvm.threads.count'].value  | number:'1.0-0'}}%</span>
31
+            </ngb-progressbar>
32
+            <p><span jhiTranslate="metrics.jvm.threads.timedwaiting">Timed Waiting</span> ({{metrics.gauges['jvm.threads.timed_waiting.count'].value}})</p>
33
+            <ngb-progressbar [value]="metrics.gauges['jvm.threads.timed_waiting.count'].value" [max]="metrics.gauges['jvm.threads.count'].value" [striped]="true" [animated]="false" type="warning">
34
+                <span>{{metrics.gauges['jvm.threads.timed_waiting.count'].value * 100 / metrics.gauges['jvm.threads.count'].value  | number:'1.0-0'}}%</span>
35
+            </ngb-progressbar>
36
+            <p><span jhiTranslate="metrics.jvm.threads.waiting">Waiting</span> ({{metrics.gauges['jvm.threads.waiting.count'].value}})</p>
37
+            <ngb-progressbar [value]="metrics.gauges['jvm.threads.waiting.count'].value" [max]="metrics.gauges['jvm.threads.count'].value" [striped]="true" [animated]="false" type="warning">
38
+                <span>{{metrics.gauges['jvm.threads.waiting.count'].value * 100 / metrics.gauges['jvm.threads.count'].value  | number:'1.0-0'}}%</span>
39
+            </ngb-progressbar>
40
+            <p><span jhiTranslate="metrics.jvm.threads.blocked">Blocked</span> ({{metrics.gauges['jvm.threads.blocked.count'].value}})</p>
41
+            <ngb-progressbar [value]="metrics.gauges['jvm.threads.blocked.count'].value" [max]="metrics.gauges['jvm.threads.count'].value" [striped]="true" [animated]="false" type="success">
42
+                <span>{{metrics.gauges['jvm.threads.blocked.count'].value * 100 / metrics.gauges['jvm.threads.count'].value  | number:'1.0-0'}}%</span>
43
+            </ngb-progressbar>
44
+        </div>
45
+        <div class="col-md-4">
46
+            <b jhiTranslate="metrics.jvm.gc.title">Garbage collections</b>
47
+            <div class="row" *ngIf="metrics.gauges['jvm.garbage.PS-MarkSweep.count']">
48
+                <div class="col-md-9" jhiTranslate="metrics.jvm.gc.marksweepcount">Mark Sweep count</div>
49
+                <div class="col-md-3 text-right">{{metrics.gauges['jvm.garbage.PS-MarkSweep.count'].value}}</div>
50
+            </div>
51
+            <div class="row" *ngIf="metrics.gauges['jvm.garbage.PS-MarkSweep.time']">
52
+                <div class="col-md-9" jhiTranslate="metrics.jvm.gc.marksweeptime">Mark Sweep time</div>
53
+                <div class="col-md-3 text-right">{{metrics.gauges['jvm.garbage.PS-MarkSweep.time'].value}}ms</div>
54
+            </div>
55
+            <div class="row" *ngIf="metrics.gauges['jvm.garbage.PS-Scavenge.count']">
56
+                <div class="col-md-9" jhiTranslate="metrics.jvm.gc.scavengecount">Scavenge count</div>
57
+                <div class="col-md-3 text-right">{{metrics.gauges['jvm.garbage.PS-Scavenge.count'].value}}</div>
58
+            </div>
59
+            <div class="row" *ngIf="metrics.gauges['jvm.garbage.PS-Scavenge.time']">
60
+                <div class="col-md-9" jhiTranslate="metrics.jvm.gc.scavengetime">Scavenge time</div>
61
+                <div class="col-md-3 text-right">{{metrics.gauges['jvm.garbage.PS-Scavenge.time'].value}}ms</div>
62
+            </div>
63
+        </div>
64
+    </div>
65
+    <div class="well well-lg" *ngIf="updatingMetrics" jhiTranslate="metrics.updating">Updating...</div>
66
+
67
+    <h3 jhiTranslate="metrics.jvm.http.title">HTTP requests (events per second)</h3>
68
+    <p *ngIf="metrics.counters">
69
+        <span jhiTranslate="metrics.jvm.http.active">Active requests</span> <b>{{metrics.counters['com.codahale.metrics.servlet.InstrumentedFilter.activeRequests'].count | number:'1.0-0'}}</b> - <span jhiTranslate="metrics.jvm.http.total">Total requests</span> <b>{{metrics.timers['com.codahale.metrics.servlet.InstrumentedFilter.requests'].count | number:'1.0-0'}}</b>
70
+    </p>
71
+    <div class="table-responsive" *ngIf="!updatingMetrics">
72
+        <table class="table table-striped">
73
+            <thead>
74
+            <tr>
75
+                <th jhiTranslate="metrics.jvm.http.table.code">Code</th>
76
+                <th jhiTranslate="metrics.jvm.http.table.count">Count</th>
77
+                <th class="text-right" jhiTranslate="metrics.jvm.http.table.mean">Mean</th>
78
+                <th class="text-right"><span jhiTranslate="metrics.jvm.http.table.average">Average</span> (1 min)</th>
79
+                <th class="text-right"><span jhiTranslate="metrics.jvm.http.table.average">Average</span> (5 min)</th>
80
+                <th class="text-right"><span jhiTranslate="metrics.jvm.http.table.average">Average</span> (15 min)</th>
81
+            </tr>
82
+            </thead>
83
+            <tbody>
84
+            <tr>
85
+                <td jhiTranslate="metrics.jvm.http.code.ok">OK</td>
86
+                <td>
87
+                    <ngb-progressbar [max]="metrics.timers['com.codahale.metrics.servlet.InstrumentedFilter.requests'].count" [value]="metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].count" [striped]="true" [animated]="false" type="success">
88
+                        <span>{{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].count}}</span>
89
+                    </ngb-progressbar>
90
+                </td>
91
+                <td class="text-right">
92
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].mean_rate) | number:'1.0-2'}}
93
+                </td>
94
+                <td class="text-right">
95
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].m1_rate) | number:'1.0-2'}}
96
+                </td>
97
+                <td class="text-right">
98
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].m5_rate) | number:'1.0-2'}}
99
+                </td>
100
+                <td class="text-right">
101
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.ok'].m15_rate) | number:'1.0-2'}}
102
+                </td>
103
+            </tr>
104
+            <tr>
105
+                <td jhiTranslate="metrics.jvm.http.code.notfound">Not Found</td>
106
+                <td>
107
+                    <ngb-progressbar [max]="metrics.timers['com.codahale.metrics.servlet.InstrumentedFilter.requests'].count" [value]="metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].count" [striped]="true" [animated]="false" type="success">
108
+                        <span>{{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].count}}</span>
109
+                    </ngb-progressbar>
110
+                </td>
111
+                <td class="text-right">
112
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].mean_rate) | number:'1.0-2'}}
113
+                </td>
114
+                <td class="text-right">
115
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].m1_rate) | number:'1.0-2'}}
116
+                </td>
117
+                <td class="text-right">
118
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].m5_rate) | number:'1.0-2'}}
119
+                </td>
120
+                <td class="text-right">
121
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.notFound'].m15_rate) | number:'1.0-2'}}
122
+                </td>
123
+            </tr>
124
+            <tr>
125
+                <td jhiTranslate="metrics.jvm.http.code.servererror">Server error</td>
126
+                <td>
127
+                    <ngb-progressbar [max]="metrics.timers['com.codahale.metrics.servlet.InstrumentedFilter.requests'].count" [value]="metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].count" [striped]="true" [animated]="false" type="success">
128
+                        <span>{{metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].count}}</span>
129
+                    </ngb-progressbar>
130
+                </td>
131
+                <td class="text-right">
132
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].mean_rate) | number:'1.0-2'}}
133
+                </td>
134
+                <td class="text-right">
135
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].m1_rate) | number:'1.0-2'}}
136
+                </td>
137
+                <td class="text-right">
138
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].m5_rate) | number:'1.0-2'}}
139
+                </td>
140
+                <td class="text-right">
141
+                    {{filterNaN(metrics.meters['com.codahale.metrics.servlet.InstrumentedFilter.responseCodes.serverError'].m15_rate) | number:'1.0-2'}}
142
+                </td>
143
+            </tr>
144
+            </tbody>
145
+        </table>
146
+    </div>
147
+
148
+    <h3 jhiTranslate="metrics.servicesstats.title">Services statistics (time in millisecond)</h3>
149
+    <div class="table-responsive" *ngIf="!updatingMetrics">
150
+        <table class="table table-striped">
151
+            <thead>
152
+            <tr>
153
+                <th jhiTranslate="metrics.servicesstats.table.name">Service name</th>
154
+                <th class="text-right" jhiTranslate="metrics.servicesstats.table.count">Count</th>
155
+                <th class="text-right" jhiTranslate="metrics.servicesstats.table.mean">Mean</th>
156
+                <th class="text-right" jhiTranslate="metrics.servicesstats.table.min">Min</th>
157
+                <th class="text-right" jhiTranslate="metrics.servicesstats.table.p50">p50</th>
158
+                <th class="text-right" jhiTranslate="metrics.servicesstats.table.p75">p75</th>
159
+                <th class="text-right" jhiTranslate="metrics.servicesstats.table.p95">p95</th>
160
+                <th class="text-right" jhiTranslate="metrics.servicesstats.table.p99">p99</th>
161
+                <th class="text-right" jhiTranslate="metrics.servicesstats.table.max">Max</th>
162
+            </tr>
163
+            </thead>
164
+            <tbody>
165
+            <tr *ngFor="let entry of servicesStats | keys">
166
+                <td>{{entry.key}}</td>
167
+                <td class="text-right">{{entry.value.count}}</td>
168
+                <td class="text-right">{{entry.value.mean * 1024 | number:'1.0-0'}}</td>
169
+                <td class="text-right">{{entry.value.min * 1024 | number:'1.0-0'}}</td>
170
+                <td class="text-right">{{entry.value.p50 * 1024 | number:'1.0-0'}}</td>
171
+                <td class="text-right">{{entry.value.p75 * 1024 | number:'1.0-0'}}</td>
172
+                <td class="text-right">{{entry.value.p95 * 1024 | number:'1.0-0'}}</td>
173
+                <td class="text-right">{{entry.value.p99 * 1024 | number:'1.0-0'}}</td>
174
+                <td class="text-right">{{entry.value.max * 1024 | number:'1.0-0'}}</td>
175
+            </tr>
176
+            </tbody>
177
+        </table>
178
+    </div>
179
+    <h3 jhiTranslate="metrics.datasource.title" *ngIf="metrics.gauges && metrics.gauges['HikariPool-1.pool.TotalConnections'] && metrics.gauges['HikariPool-1.pool.TotalConnections'].value > 0">DataSource statistics (time in millisecond)</h3>
180
+    <div class="table-responsive" *ngIf="!updatingMetrics && metrics.gauges && metrics.gauges['HikariPool-1.pool.TotalConnections'] && metrics.gauges['HikariPool-1.pool.TotalConnections'].value > 0">
181
+        <table class="table table-striped">
182
+            <thead>
183
+                <tr>
184
+                    <th><span jhiTranslate="metrics.datasource.usage">Usage</span> ({{metrics.gauges['HikariPool-1.pool.ActiveConnections'].value}} / {{metrics.gauges['HikariPool-1.pool.TotalConnections'].value}})</th>
185
+                    <th class="text-right" jhiTranslate="metrics.datasource.count">Count</th>
186
+                    <th class="text-right" jhiTranslate="metrics.datasource.mean">Mean</th>
187
+                    <th class="text-right" jhiTranslate="metrics.datasource.min">Min</th>
188
+                    <th class="text-right" jhiTranslate="metrics.datasource.p50">p50</th>
189
+                    <th class="text-right" jhiTranslate="metrics.datasource.p75">p75</th>
190
+                    <th class="text-right" jhiTranslate="metrics.datasource.p95">p95</th>
191
+                    <th class="text-right" jhiTranslate="metrics.datasource.p99">p99</th>
192
+                    <th class="text-right" jhiTranslate="metrics.datasource.max">Max</th>
193
+                </tr>
194
+            </thead>
195
+            <tbody>
196
+                <tr>
197
+                    <td>
198
+                        <div class="progress progress-striped">
199
+                            <ngb-progressbar [max]="metrics.gauges['HikariPool-1.pool.TotalConnections'].value" [value]="metrics.gauges['HikariPool-1.pool.ActiveConnections'].value" [striped]="true" [animated]="false" type="success">
200
+                                <span>{{metrics.gauges['HikariPool-1.pool.ActiveConnections'].value * 100 / metrics.gauges['HikariPool-1.pool.TotalConnections'].value  | number:'1.0-0'}}%</span>
201
+                            </ngb-progressbar>
202
+                        </div>
203
+                    </td>
204
+                    <td class="text-right">{{metrics.histograms['HikariPool-1.pool.Usage'].count}}</td>
205
+                    <td class="text-right">{{filterNaN(metrics.histograms['HikariPool-1.pool.Usage'].mean) | number:'1.0-2'}}</td>
206
+                    <td class="text-right">{{filterNaN(metrics.histograms['HikariPool-1.pool.Usage'].min) | number:'1.0-2'}}</td>
207
+                    <td class="text-right">{{filterNaN(metrics.histograms['HikariPool-1.pool.Usage'].p50) | number:'1.0-2'}}</td>
208
+                    <td class="text-right">{{filterNaN(metrics.histograms['HikariPool-1.pool.Usage'].p75) | number:'1.0-2'}}</td>
209
+                    <td class="text-right">{{filterNaN(metrics.histograms['HikariPool-1.pool.Usage'].p95) | number:'1.0-2'}}</td>
210
+                    <td class="text-right">{{filterNaN(metrics.histograms['HikariPool-1.pool.Usage'].p99) | number:'1.0-2'}}</td>
211
+                    <td class="text-right">{{filterNaN(metrics.histograms['HikariPool-1.pool.Usage'].max) | number:'1.0-2'}}</td>
212
+                </tr>
213
+            </tbody>
214
+        </table>
215
+    </div>
216
+</div>

+ 77
- 0
webapp/app/admin/metrics/metrics.component.ts Näytä tiedosto

@@ -0,0 +1,77 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
3
+
4
+import { JhiMetricsMonitoringModalComponent } from './metrics-modal.component';
5
+import { JhiMetricsService } from './metrics.service';
6
+
7
+@Component({
8
+    selector: 'jhi-metrics',
9
+    templateUrl: './metrics.component.html'
10
+})
11
+export class JhiMetricsMonitoringComponent implements OnInit {
12
+    metrics: any = {};
13
+    cachesStats: any = {};
14
+    servicesStats: any = {};
15
+    updatingMetrics = true;
16
+    JCACHE_KEY: string;
17
+
18
+    constructor(private modalService: NgbModal, private metricsService: JhiMetricsService) {
19
+        this.JCACHE_KEY = 'jcache.statistics';
20
+    }
21
+
22
+    ngOnInit() {
23
+        this.refresh();
24
+    }
25
+
26
+    refresh() {
27
+        this.updatingMetrics = true;
28
+        this.metricsService.getMetrics().subscribe(metrics => {
29
+            this.metrics = metrics;
30
+            this.updatingMetrics = false;
31
+            this.servicesStats = {};
32
+            this.cachesStats = {};
33
+            Object.keys(metrics.timers).forEach(key => {
34
+                const value = metrics.timers[key];
35
+                if (key.includes('web.rest') || key.includes('service')) {
36
+                    this.servicesStats[key] = value;
37
+                }
38
+            });
39
+            Object.keys(metrics.gauges).forEach(key => {
40
+                if (key.includes('jcache.statistics')) {
41
+                    const value = metrics.gauges[key].value;
42
+                    // remove gets or puts
43
+                    const index = key.lastIndexOf('.');
44
+                    const newKey = key.substr(0, index);
45
+
46
+                    // Keep the name of the domain
47
+                    this.cachesStats[newKey] = {
48
+                        name: this.JCACHE_KEY.length,
49
+                        value
50
+                    };
51
+                }
52
+            });
53
+        });
54
+    }
55
+
56
+    refreshThreadDumpData() {
57
+        this.metricsService.threadDump().subscribe(data => {
58
+            const modalRef = this.modalService.open(JhiMetricsMonitoringModalComponent, { size: 'lg' });
59
+            modalRef.componentInstance.threadDump = data.threads;
60
+            modalRef.result.then(
61
+                result => {
62
+                    // Left blank intentionally, nothing to do here
63
+                },
64
+                reason => {
65
+                    // Left blank intentionally, nothing to do here
66
+                }
67
+            );
68
+        });
69
+    }
70
+
71
+    filterNaN(input) {
72
+        if (isNaN(input)) {
73
+            return 0;
74
+        }
75
+        return input;
76
+    }
77
+}

+ 11
- 0
webapp/app/admin/metrics/metrics.route.ts Näytä tiedosto

@@ -0,0 +1,11 @@
1
+import { Route } from '@angular/router';
2
+
3
+import { JhiMetricsMonitoringComponent } from './metrics.component';
4
+
5
+export const metricsRoute: Route = {
6
+    path: 'jhi-metrics',
7
+    component: JhiMetricsMonitoringComponent,
8
+    data: {
9
+        pageTitle: 'metrics.title'
10
+    }
11
+};

+ 18
- 0
webapp/app/admin/metrics/metrics.service.ts Näytä tiedosto

@@ -0,0 +1,18 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+
7
+@Injectable({ providedIn: 'root' })
8
+export class JhiMetricsService {
9
+    constructor(private http: HttpClient) {}
10
+
11
+    getMetrics(): Observable<any> {
12
+        return this.http.get(SERVER_API_URL + 'management/metrics');
13
+    }
14
+
15
+    threadDump(): Observable<any> {
16
+        return this.http.get(SERVER_API_URL + 'management/threaddump');
17
+    }
18
+}

+ 19
- 0
webapp/app/admin/user-management/user-management-delete-dialog.component.html Näytä tiedosto

@@ -0,0 +1,19 @@
1
+<form name="deleteForm" (ngSubmit)="confirmDelete(user.login)">
2
+    <div class="modal-header">
3
+        <h4 class="modal-title" jhiTranslate="entity.delete.title">Confirm delete operation</h4>
4
+        <button type="button" class="close" data-dismiss="modal" aria-hidden="true"
5
+            (click)="clear()">&times;</button>
6
+    </div>
7
+    <div class="modal-body">
8
+        <jhi-alert-error></jhi-alert-error>
9
+        <p jhiTranslate="userManagement.delete.question" translateValues="{login: '{{user.login}}'}">Are you sure you want to delete this User?</p>
10
+    </div>
11
+    <div class="modal-footer">
12
+        <button type="button" class="btn btn-secondary" data-dismiss="modal" (click)="clear()">
13
+            <fa-icon [icon]="'ban'"></fa-icon>&nbsp;<span jhiTranslate="entity.action.cancel">Cancel</span>
14
+        </button>
15
+        <button type="submit" class="btn btn-danger">
16
+            <fa-icon [icon]="'times'"></fa-icon>&nbsp;<span jhiTranslate="entity.action.delete">Delete</span>
17
+        </button>
18
+    </div>
19
+</form>

+ 29
- 0
webapp/app/admin/user-management/user-management-delete-dialog.component.ts Näytä tiedosto

@@ -0,0 +1,29 @@
1
+import { Component } from '@angular/core';
2
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
3
+import { JhiEventManager } from 'ng-jhipster';
4
+
5
+import { User, UserService } from 'app/core';
6
+
7
+@Component({
8
+    selector: 'jhi-user-mgmt-delete-dialog',
9
+    templateUrl: './user-management-delete-dialog.component.html'
10
+})
11
+export class UserMgmtDeleteDialogComponent {
12
+    user: User;
13
+
14
+    constructor(private userService: UserService, public activeModal: NgbActiveModal, private eventManager: JhiEventManager) {}
15
+
16
+    clear() {
17
+        this.activeModal.dismiss('cancel');
18
+    }
19
+
20
+    confirmDelete(login) {
21
+        this.userService.delete(login).subscribe(response => {
22
+            this.eventManager.broadcast({
23
+                name: 'userListModification',
24
+                content: 'Deleted a user'
25
+            });
26
+            this.activeModal.dismiss(true);
27
+        });
28
+    }
29
+}

+ 49
- 0
webapp/app/admin/user-management/user-management-detail.component.html Näytä tiedosto

@@ -0,0 +1,49 @@
1
+<div class="row justify-content-center">
2
+    <div class="col-8">
3
+        <div *ngIf="user">
4
+            <h2>
5
+                <span jhiTranslate="userManagement.detail.title">User</span> [<b>{{user.login}}</b>]
6
+            </h2>
7
+            <dl class="row-md jh-entity-details">
8
+                <dt><span jhiTranslate="userManagement.login">Login</span></dt>
9
+                <dd>
10
+                    <span>{{user.login}}</span>
11
+                    <jhi-boolean
12
+                        [value]="user.activated"
13
+                        [textTrue]="'userManagement.activated' | translate"
14
+                        [textFalse]="'userManagement.deactivated' | translate">
15
+                    </jhi-boolean>
16
+                </dd>
17
+                <dt><span jhiTranslate="userManagement.firstName">First Name</span></dt>
18
+                <dd>{{user.firstName}}</dd>
19
+                <dt><span jhiTranslate="userManagement.lastName">Last Name</span></dt>
20
+                <dd>{{user.lastName}}</dd>
21
+                <dt><span jhiTranslate="userManagement.email">Email</span></dt>
22
+                <dd>{{user.email}}</dd>
23
+                <dt><span jhiTranslate="userManagement.langKey">Lang Key</span></dt>
24
+                <dd>{{user.langKey}}</dd>
25
+                <dt><span jhiTranslate="userManagement.createdBy">Created By</span></dt>
26
+                <dd>{{user.createdBy}}</dd>
27
+                <dt><span jhiTranslate="userManagement.createdDate">Created Date</span></dt>
28
+                <dd>{{user.createdDate | date:'dd/MM/yy HH:mm' }}</dd>
29
+                <dt><span jhiTranslate="userManagement.lastModifiedBy">Last Modified By</span></dt>
30
+                <dd>{{user.lastModifiedBy}}</dd>
31
+                <dt><span jhiTranslate="userManagement.lastModifiedDate">Last Modified Date</span></dt>
32
+                <dd>{{user.lastModifiedDate | date:'dd/MM/yy HH:mm'}}</dd>
33
+                <dt><span jhiTranslate="userManagement.profiles">Profiles</span></dt>
34
+                <dd>
35
+                    <ul class="list-unstyled">
36
+                        <li *ngFor="let authority of user.authorities">
37
+                            <span class="badge badge-info">{{authority}}</span>
38
+                        </li>
39
+                    </ul>
40
+                </dd>
41
+            </dl>
42
+            <button type="submit"
43
+                    routerLink="../../"
44
+                    class="btn btn-info">
45
+                <fa-icon [icon]="'arrow-left'"></fa-icon>&nbsp;<span jhiTranslate="entity.action.back"> Back</span>
46
+            </button>
47
+        </div>
48
+    </div>
49
+</div>

+ 20
- 0
webapp/app/admin/user-management/user-management-detail.component.ts Näytä tiedosto

@@ -0,0 +1,20 @@
1
+import { Component, OnInit, OnDestroy } from '@angular/core';
2
+import { ActivatedRoute } from '@angular/router';
3
+
4
+import { User } from 'app/core';
5
+
6
+@Component({
7
+    selector: 'jhi-user-mgmt-detail',
8
+    templateUrl: './user-management-detail.component.html'
9
+})
10
+export class UserMgmtDetailComponent implements OnInit {
11
+    user: User;
12
+
13
+    constructor(private route: ActivatedRoute) {}
14
+
15
+    ngOnInit() {
16
+        this.route.data.subscribe(({ user }) => {
17
+            this.user = user.body ? user.body : user;
18
+        });
19
+    }
20
+}

+ 124
- 0
webapp/app/admin/user-management/user-management-update.component.html Näytä tiedosto

@@ -0,0 +1,124 @@
1
+<div class="row justify-content-center">
2
+    <div class="col-8">
3
+        <form name="editForm" role="form" novalidate (ngSubmit)="save()" #editForm="ngForm">
4
+            <h2 id="myUserLabel" jhiTranslate="userManagement.home.createOrEditLabel">
5
+                Create or edit a User
6
+            </h2>
7
+            <div>
8
+                <jhi-alert-error></jhi-alert-error>
9
+                <div class="form-group" [hidden]="!user.id">
10
+                    <label jhiTranslate="global.field.id">ID</label>
11
+                    <input type="text" class="form-control" name="id"
12
+                        [(ngModel)]="user.id" readonly>
13
+                </div>
14
+
15
+                <div class="form-group">
16
+                    <label class="form-control-label" jhiTranslate="userManagement.login">Login</label>
17
+                    <input type="text" class="form-control" name="login" #loginInput="ngModel"
18
+                        [(ngModel)]="user.login" required minlength="1" maxlength="50" pattern="^[_.@A-Za-z0-9-]*$">
19
+
20
+                    <div *ngIf="loginInput.dirty && loginInput.invalid">
21
+                        <small class="form-text text-danger"
22
+                        *ngIf="loginInput.errors.required" jhiTranslate="entity.validation.required">
23
+                            This field is required.
24
+                        </small>
25
+
26
+                        <small class="form-text text-danger"
27
+                        *ngIf="loginInput.errors.maxlength" jhiTranslate="entity.validation.maxlength"
28
+                        translateValues="{max: 50}">
29
+                            This field cannot be longer than 50 characters.
30
+                        </small>
31
+
32
+                        <small class="form-text text-danger"
33
+                        *ngIf="loginInput.errors.pattern" jhiTranslate="entity.validation.patternLogin">
34
+                            This field can only contain letters, digits and e-mail addresses.
35
+                        </small>
36
+                    </div>
37
+                </div>
38
+                <div class="form-group">
39
+                    <label class="form-control-label" jhiTranslate="userManagement.firstName">First Name</label>
40
+                    <input type="text" class="form-control" name="firstName" #firstNameInput="ngModel"
41
+                        [(ngModel)]="user.firstName" maxlength="50">
42
+
43
+                    <div *ngIf="firstNameInput.dirty && firstNameInput.invalid">
44
+                        <small class="form-text text-danger"
45
+                        *ngIf="firstNameInput.errors.maxlength" jhiTranslate="entity.validation.maxlength"
46
+                        translateValues="{max: 50}">
47
+                            This field cannot be longer than 50 characters.
48
+                        </small>
49
+                    </div>
50
+                </div>
51
+                <div class="form-group">
52
+                    <label jhiTranslate="userManagement.lastName">Last Name</label>
53
+                    <input type="text" class="form-control" name="lastName" #lastNameInput="ngModel"
54
+                        [(ngModel)]="user.lastName" maxlength="50">
55
+
56
+                    <div *ngIf="lastNameInput.dirty && lastNameInput.invalid">
57
+                        <small class="form-text text-danger"
58
+                        *ngIf="lastNameInput.errors.maxlength" jhiTranslate="entity.validation.maxlength"
59
+                        translateValues="{max: 50}">
60
+                            This field cannot be longer than 50 characters.
61
+                        </small>
62
+                    </div>
63
+                </div>
64
+                <div class="form-group">
65
+                    <label class="form-control-label" jhiTranslate="userManagement.email">Email</label>
66
+                    <input type="email" class="form-control" name="email" #emailInput="ngModel"
67
+                        [(ngModel)]="user.email" minlength="5" required maxlength="254" email>
68
+
69
+                    <div *ngIf="emailInput.dirty && emailInput.invalid">
70
+                        <small class="form-text text-danger"
71
+                        *ngIf="emailInput.errors.required" jhiTranslate="entity.validation.required">
72
+                            This field is required.
73
+                        </small>
74
+
75
+                        <small class="form-text text-danger"
76
+                        *ngIf="emailInput.errors.maxlength" jhiTranslate="entity.validation.maxlength"
77
+                        translateValues="{max: 100}">
78
+                            This field cannot be longer than 100 characters.
79
+                        </small>
80
+
81
+                        <small class="form-text text-danger"
82
+                        *ngIf="emailInput.errors.minlength" jhiTranslate="entity.validation.minlength"
83
+                        translateValues="{min: 5}">
84
+                            This field is required to be at least 5 characters.
85
+                        </small>
86
+
87
+                        <small class="form-text text-danger"
88
+                        *ngIf="emailInput.errors.email" jhiTranslate="global.messages.validate.email.invalid">
89
+                            Your email is invalid.
90
+                        </small>
91
+                    </div>
92
+                </div>
93
+                <div class="form-check">
94
+                    <label class="form-check-label" for="activated">
95
+                        <input class="form-check-input" [disabled]="user.id === null" type="checkbox" id="activated" name="activated" [(ngModel)]="user.activated">
96
+                        <span jhiTranslate="userManagement.activated">Activated</span>
97
+                    </label>
98
+                </div>
99
+
100
+                <div class="form-group" *ngIf="languages && languages.length > 0">
101
+                    <label jhiTranslate="userManagement.langKey">Lang Key</label>
102
+                    <select class="form-control" id="langKey" name="langKey" [(ngModel)]="user.langKey">
103
+                        <option *ngFor="let language of languages" [value]="language">{{language | findLanguageFromKey}}</option>
104
+                    </select>
105
+                </div>
106
+                <div class="form-group">
107
+                    <label jhiTranslate="userManagement.profiles">Profiles</label>
108
+                    <select class="form-control" multiple name="authority" [(ngModel)]="user.authorities">
109
+                        <option *ngFor="let authority of authorities" [value]="authority">{{authority}}</option>
110
+                    </select>
111
+                </div>
112
+            </div>
113
+            <div>
114
+                <button type="button" class="btn btn-secondary" (click)="previousState()">
115
+                    <fa-icon [icon]="'ban'"></fa-icon>&nbsp;<span
116
+                    jhiTranslate="entity.action.cancel">Cancel</span>
117
+                </button>
118
+                <button type="submit" [disabled]="editForm.form.invalid || isSaving" class="btn btn-primary">
119
+                    <fa-icon [icon]="'save'"></fa-icon>&nbsp;<span jhiTranslate="entity.action.save">Save</span>
120
+                </button>
121
+            </div>
122
+        </form>
123
+    </div>
124
+</div>

+ 58
- 0
webapp/app/admin/user-management/user-management-update.component.ts Näytä tiedosto

@@ -0,0 +1,58 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { ActivatedRoute, Router } from '@angular/router';
3
+
4
+import { JhiLanguageHelper, User, UserService } from 'app/core';
5
+
6
+@Component({
7
+    selector: 'jhi-user-mgmt-update',
8
+    templateUrl: './user-management-update.component.html'
9
+})
10
+export class UserMgmtUpdateComponent implements OnInit {
11
+    user: User;
12
+    languages: any[];
13
+    authorities: any[];
14
+    isSaving: boolean;
15
+
16
+    constructor(
17
+        private languageHelper: JhiLanguageHelper,
18
+        private userService: UserService,
19
+        private route: ActivatedRoute,
20
+        private router: Router
21
+    ) {}
22
+
23
+    ngOnInit() {
24
+        this.isSaving = false;
25
+        this.route.data.subscribe(({ user }) => {
26
+            this.user = user.body ? user.body : user;
27
+        });
28
+        this.authorities = [];
29
+        this.userService.authorities().subscribe(authorities => {
30
+            this.authorities = authorities;
31
+        });
32
+        this.languageHelper.getAll().then(languages => {
33
+            this.languages = languages;
34
+        });
35
+    }
36
+
37
+    previousState() {
38
+        window.history.back();
39
+    }
40
+
41
+    save() {
42
+        this.isSaving = true;
43
+        if (this.user.id !== null) {
44
+            this.userService.update(this.user).subscribe(response => this.onSaveSuccess(response), () => this.onSaveError());
45
+        } else {
46
+            this.userService.create(this.user).subscribe(response => this.onSaveSuccess(response), () => this.onSaveError());
47
+        }
48
+    }
49
+
50
+    private onSaveSuccess(result) {
51
+        this.isSaving = false;
52
+        this.previousState();
53
+    }
54
+
55
+    private onSaveError() {
56
+        this.isSaving = false;
57
+    }
58
+}

+ 79
- 0
webapp/app/admin/user-management/user-management.component.html Näytä tiedosto

@@ -0,0 +1,79 @@
1
+<div>
2
+    <h2>
3
+        <span id="user-management-page-heading" jhiTranslate="userManagement.home.title">Users</span>
4
+        <button class="btn btn-primary float-right jh-create-entity" [routerLink]="['./new']">
5
+            <fa-icon [icon]="'plus'"></fa-icon> <span jhiTranslate="userManagement.home.createLabel">Create a new User</span>
6
+        </button>
7
+    </h2>
8
+    <jhi-alert></jhi-alert>
9
+    <div class="table-responsive" *ngIf="users">
10
+        <table class="table table-striped">
11
+            <thead>
12
+            <tr jhiSort [(predicate)]="predicate" [(ascending)]="reverse" [callback]="transition.bind(this)">
13
+                <th jhiSortBy="id"><span jhiTranslate="global.field.id">ID</span> <fa-icon [icon]="'sort'"></fa-icon></th>
14
+                <th jhiSortBy="login"><span jhiTranslate="userManagement.login">Login</span> <fa-icon [icon]="'sort'"></fa-icon></th>
15
+                <th jhiSortBy="email"><span jhiTranslate="userManagement.email">Email</span> <fa-icon [icon]="'sort'"></fa-icon></th>
16
+                <th></th>
17
+                <th jhiSortBy="langKey"> <span jhiTranslate="userManagement.langKey">Lang Key</span> <fa-icon [icon]="'sort'"></fa-icon></th>
18
+                <th><span jhiTranslate="userManagement.profiles">Profiles</span></th>
19
+                <th jhiSortBy="createdDate"><span jhiTranslate="userManagement.createdDate">Created Date</span> <fa-icon [icon]="'sort'"></fa-icon></th>
20
+                <th jhiSortBy="lastModifiedBy"><span jhiTranslate="userManagement.lastModifiedBy">Last Modified By</span> <fa-icon [icon]="'sort'"></fa-icon></th>
21
+                <th jhiSortBy="lastModifiedDate"><span jhiTranslate="userManagement.lastModifiedDate">Last Modified Date</span> <fa-icon [icon]="'sort'"></fa-icon></th>
22
+                <th></th>
23
+            </tr>
24
+            </thead>
25
+            <tbody *ngIf ="users">
26
+            <tr *ngFor="let user of users; trackBy: trackIdentity">
27
+                <td><a [routerLink]="['./', user.login, 'view']">{{user.id}}</a></td>
28
+                <td>{{user.login}}</td>
29
+                <td>{{user.email}}</td>
30
+                <td>
31
+                    <button class="btn btn-danger btn-sm" (click)="setActive(user, true)" *ngIf="!user.activated"
32
+                            jhiTranslate="userManagement.deactivated">Deactivated</button>
33
+                    <button class="btn btn-success btn-sm" (click)="setActive(user, false)" *ngIf="user.activated"
34
+                            [disabled]="currentAccount.login === user.login" jhiTranslate="userManagement.activated">Activated</button>
35
+                </td>
36
+                <td>{{user.langKey}}</td>
37
+                <td>
38
+                    <div *ngFor="let authority of user.authorities">
39
+                        <span class="badge badge-info">{{ authority }}</span>
40
+                    </div>
41
+                </td>
42
+                <td>{{user.createdDate | date:'dd/MM/yy HH:mm'}}</td>
43
+                <td>{{user.lastModifiedBy}}</td>
44
+                <td>{{user.lastModifiedDate | date:'dd/MM/yy HH:mm'}}</td>
45
+                <td class="text-right">
46
+                    <div class="btn-group flex-btn-group-container">
47
+                        <button type="submit"
48
+                                [routerLink]="['./', user.login, 'view']"
49
+                                class="btn btn-info btn-sm">
50
+                            <fa-icon [icon]="'eye'"></fa-icon>
51
+                            <span class="d-none d-md-inline" jhiTranslate="entity.action.view">View</span>
52
+                        </button>
53
+                        <button type="submit"
54
+                                [routerLink]="['./', user.login, 'edit']"
55
+                                queryParamsHandling="merge"
56
+                                class="btn btn-primary btn-sm">
57
+                            <fa-icon [icon]="'pencil-alt'"></fa-icon>
58
+                            <span class="d-none d-md-inline" jhiTranslate="entity.action.edit">Edit</span>
59
+                        </button>
60
+                        <button type="button" (click)="deleteUser(user)"
61
+                                class="btn btn-danger btn-sm" [disabled]="currentAccount.login === user.login">
62
+                            <fa-icon [icon]="'times'"></fa-icon>
63
+                            <span class="d-none d-md-inline" jhiTranslate="entity.action.delete">Delete</span>
64
+                        </button>
65
+                    </div>
66
+                </td>
67
+            </tr>
68
+            </tbody>
69
+        </table>
70
+    </div>
71
+    <div *ngIf="users">
72
+        <div class="row justify-content-center">
73
+            <jhi-item-count [page]="page" [total]="queryCount" [itemsPerPage]="itemsPerPage"></jhi-item-count>
74
+        </div>
75
+        <div class="row justify-content-center">
76
+            <ngb-pagination [collectionSize]="totalItems" [(page)]="page" [pageSize]="itemsPerPage" [maxSize]="5" [rotate]="true" [boundaryLinks]="true" (pageChange)="loadPage(page)"></ngb-pagination>
77
+        </div>
78
+    </div>
79
+</div>

+ 146
- 0
webapp/app/admin/user-management/user-management.component.ts Näytä tiedosto

@@ -0,0 +1,146 @@
1
+import { Component, OnInit, OnDestroy } from '@angular/core';
2
+import { HttpResponse } from '@angular/common/http';
3
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
4
+
5
+import { ActivatedRoute, Router } from '@angular/router';
6
+import { JhiEventManager, JhiParseLinks, JhiAlertService } from 'ng-jhipster';
7
+
8
+import { ITEMS_PER_PAGE } from 'app/shared';
9
+import { Principal, UserService, User } from 'app/core';
10
+import { UserMgmtDeleteDialogComponent } from 'app/admin';
11
+
12
+@Component({
13
+    selector: 'jhi-user-mgmt',
14
+    templateUrl: './user-management.component.html'
15
+})
16
+export class UserMgmtComponent implements OnInit, OnDestroy {
17
+    currentAccount: any;
18
+    users: User[];
19
+    error: any;
20
+    success: any;
21
+    routeData: any;
22
+    links: any;
23
+    totalItems: any;
24
+    queryCount: any;
25
+    itemsPerPage: any;
26
+    page: any;
27
+    predicate: any;
28
+    previousPage: any;
29
+    reverse: any;
30
+
31
+    constructor(
32
+        private userService: UserService,
33
+        private alertService: JhiAlertService,
34
+        private principal: Principal,
35
+        private parseLinks: JhiParseLinks,
36
+        private activatedRoute: ActivatedRoute,
37
+        private router: Router,
38
+        private eventManager: JhiEventManager,
39
+        private modalService: NgbModal
40
+    ) {
41
+        this.itemsPerPage = ITEMS_PER_PAGE;
42
+        this.routeData = this.activatedRoute.data.subscribe(data => {
43
+            this.page = data['pagingParams'].page;
44
+            this.previousPage = data['pagingParams'].page;
45
+            this.reverse = data['pagingParams'].ascending;
46
+            this.predicate = data['pagingParams'].predicate;
47
+        });
48
+    }
49
+
50
+    ngOnInit() {
51
+        this.principal.identity().then(account => {
52
+            this.currentAccount = account;
53
+            this.loadAll();
54
+            this.registerChangeInUsers();
55
+        });
56
+    }
57
+
58
+    ngOnDestroy() {
59
+        this.routeData.unsubscribe();
60
+    }
61
+
62
+    registerChangeInUsers() {
63
+        this.eventManager.subscribe('userListModification', response => this.loadAll());
64
+    }
65
+
66
+    setActive(user, isActivated) {
67
+        user.activated = isActivated;
68
+
69
+        this.userService.update(user).subscribe(response => {
70
+            if (response.status === 200) {
71
+                this.error = null;
72
+                this.success = 'OK';
73
+                this.loadAll();
74
+            } else {
75
+                this.success = null;
76
+                this.error = 'ERROR';
77
+            }
78
+        });
79
+    }
80
+
81
+    loadAll() {
82
+        this.userService
83
+            .query({
84
+                page: this.page - 1,
85
+                size: this.itemsPerPage,
86
+                sort: this.sort()
87
+            })
88
+            .subscribe(
89
+                (res: HttpResponse<User[]>) => this.onSuccess(res.body, res.headers),
90
+                (res: HttpResponse<any>) => this.onError(res.body)
91
+            );
92
+    }
93
+
94
+    trackIdentity(index, item: User) {
95
+        return item.id;
96
+    }
97
+
98
+    sort() {
99
+        const result = [this.predicate + ',' + (this.reverse ? 'asc' : 'desc')];
100
+        if (this.predicate !== 'id') {
101
+            result.push('id');
102
+        }
103
+        return result;
104
+    }
105
+
106
+    loadPage(page: number) {
107
+        if (page !== this.previousPage) {
108
+            this.previousPage = page;
109
+            this.transition();
110
+        }
111
+    }
112
+
113
+    transition() {
114
+        this.router.navigate(['/admin/user-management'], {
115
+            queryParams: {
116
+                page: this.page,
117
+                sort: this.predicate + ',' + (this.reverse ? 'asc' : 'desc')
118
+            }
119
+        });
120
+        this.loadAll();
121
+    }
122
+
123
+    deleteUser(user: User) {
124
+        const modalRef = this.modalService.open(UserMgmtDeleteDialogComponent, { size: 'lg', backdrop: 'static' });
125
+        modalRef.componentInstance.user = user;
126
+        modalRef.result.then(
127
+            result => {
128
+                // Left blank intentionally, nothing to do here
129
+            },
130
+            reason => {
131
+                // Left blank intentionally, nothing to do here
132
+            }
133
+        );
134
+    }
135
+
136
+    private onSuccess(data, headers) {
137
+        this.links = this.parseLinks.parse(headers.get('link'));
138
+        this.totalItems = headers.get('X-Total-Count');
139
+        this.queryCount = this.totalItems;
140
+        this.users = data;
141
+    }
142
+
143
+    private onError(error) {
144
+        this.alertService.error(error.error, error.message, null);
145
+    }
146
+}

+ 68
- 0
webapp/app/admin/user-management/user-management.route.ts Näytä tiedosto

@@ -0,0 +1,68 @@
1
+import { Injectable } from '@angular/core';
2
+import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot, Routes, CanActivate } from '@angular/router';
3
+import { JhiPaginationUtil, JhiResolvePagingParams } from 'ng-jhipster';
4
+
5
+import { Principal, User, UserService } from 'app/core';
6
+import { UserMgmtComponent } from './user-management.component';
7
+import { UserMgmtDetailComponent } from './user-management-detail.component';
8
+import { UserMgmtUpdateComponent } from './user-management-update.component';
9
+
10
+@Injectable({ providedIn: 'root' })
11
+export class UserResolve implements CanActivate {
12
+    constructor(private principal: Principal) {}
13
+
14
+    canActivate() {
15
+        return this.principal.identity().then(account => this.principal.hasAnyAuthority(['ROLE_ADMIN']));
16
+    }
17
+}
18
+
19
+@Injectable({ providedIn: 'root' })
20
+export class UserMgmtResolve implements Resolve<any> {
21
+    constructor(private service: UserService) {}
22
+
23
+    resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
24
+        const id = route.params['login'] ? route.params['login'] : null;
25
+        if (id) {
26
+            return this.service.find(id);
27
+        }
28
+        return new User();
29
+    }
30
+}
31
+
32
+export const userMgmtRoute: Routes = [
33
+    {
34
+        path: 'user-management',
35
+        component: UserMgmtComponent,
36
+        resolve: {
37
+            pagingParams: JhiResolvePagingParams
38
+        },
39
+        data: {
40
+            pageTitle: 'userManagement.home.title',
41
+            defaultSort: 'id,asc'
42
+        }
43
+    },
44
+    {
45
+        path: 'user-management/:login/view',
46
+        component: UserMgmtDetailComponent,
47
+        resolve: {
48
+            user: UserMgmtResolve
49
+        },
50
+        data: {
51
+            pageTitle: 'userManagement.home.title'
52
+        }
53
+    },
54
+    {
55
+        path: 'user-management/new',
56
+        component: UserMgmtUpdateComponent,
57
+        resolve: {
58
+            user: UserMgmtResolve
59
+        }
60
+    },
61
+    {
62
+        path: 'user-management/:login/edit',
63
+        component: UserMgmtUpdateComponent,
64
+        resolve: {
65
+            user: UserMgmtResolve
66
+        }
67
+    }
68
+];

+ 23
- 0
webapp/app/app-routing.module.ts Näytä tiedosto

@@ -0,0 +1,23 @@
1
+import { NgModule } from '@angular/core';
2
+import { RouterModule } from '@angular/router';
3
+import { errorRoute, navbarRoute } from './layouts';
4
+import { DEBUG_INFO_ENABLED } from 'app/app.constants';
5
+
6
+const LAYOUT_ROUTES = [navbarRoute, ...errorRoute];
7
+
8
+@NgModule({
9
+    imports: [
10
+        RouterModule.forRoot(
11
+            [
12
+                ...LAYOUT_ROUTES,
13
+                {
14
+                    path: 'admin',
15
+                    loadChildren: './admin/admin.module#ZipConnectAdminModule'
16
+                }
17
+            ],
18
+            { useHash: true, enableTracing: DEBUG_INFO_ENABLED }
19
+        )
20
+    ],
21
+    exports: [RouterModule]
22
+})
23
+export class ZipConnectAppRoutingModule {}

+ 8
- 0
webapp/app/app.constants.ts Näytä tiedosto

@@ -0,0 +1,8 @@
1
+// These constants are injected via webpack environment variables.
2
+// You can add more variables in webpack.common.js or in profile specific webpack.<dev|prod>.js files.
3
+// If you change the values in the webpack config files, you need to re run webpack to update the application
4
+
5
+export const VERSION = process.env.VERSION;
6
+export const DEBUG_INFO_ENABLED: boolean = !!process.env.DEBUG_INFO_ENABLED;
7
+export const SERVER_API_URL = process.env.SERVER_API_URL;
8
+export const BUILD_TIMESTAMP = process.env.BUILD_TIMESTAMP;

+ 14
- 0
webapp/app/app.main.ts Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
2
+import { ProdConfig } from './blocks/config/prod.config';
3
+import { ZipConnectAppModule } from './app.module';
4
+
5
+ProdConfig();
6
+
7
+if (module['hot']) {
8
+    module['hot'].accept();
9
+}
10
+
11
+platformBrowserDynamic()
12
+    .bootstrapModule(ZipConnectAppModule, { preserveWhitespaces: true })
13
+    .then(success => console.log(`Application started`))
14
+    .catch(err => console.error(err));

+ 72
- 0
webapp/app/app.module.ts Näytä tiedosto

@@ -0,0 +1,72 @@
1
+import './vendor.ts';
2
+
3
+import { NgModule } from '@angular/core';
4
+import { BrowserModule } from '@angular/platform-browser';
5
+import { HTTP_INTERCEPTORS } from '@angular/common/http';
6
+import { NgbDatepickerConfig } from '@ng-bootstrap/ng-bootstrap';
7
+import { Ng2Webstorage } from 'ngx-webstorage';
8
+import { NgJhipsterModule } from 'ng-jhipster';
9
+
10
+import { AuthInterceptor } from './blocks/interceptor/auth.interceptor';
11
+import { AuthExpiredInterceptor } from './blocks/interceptor/auth-expired.interceptor';
12
+import { ErrorHandlerInterceptor } from './blocks/interceptor/errorhandler.interceptor';
13
+import { NotificationInterceptor } from './blocks/interceptor/notification.interceptor';
14
+import { ZipConnectSharedModule } from 'app/shared';
15
+import { ZipConnectCoreModule } from 'app/core';
16
+import { ZipConnectAppRoutingModule } from './app-routing.module';
17
+import { ZipConnectHomeModule } from './home/home.module';
18
+import { ZipConnectAccountModule } from './account/account.module';
19
+import { ZipConnectEntityModule } from './entities/entity.module';
20
+import * as moment from 'moment';
21
+// jhipster-needle-angular-add-module-import JHipster will add new module here
22
+import { JhiMainComponent, NavbarComponent, FooterComponent, PageRibbonComponent, ActiveMenuDirective, ErrorComponent } from './layouts';
23
+
24
+@NgModule({
25
+    imports: [
26
+        BrowserModule,
27
+        ZipConnectAppRoutingModule,
28
+        Ng2Webstorage.forRoot({ prefix: 'jhi', separator: '-' }),
29
+        NgJhipsterModule.forRoot({
30
+            // set below to true to make alerts look like toast
31
+            alertAsToast: false,
32
+            alertTimeout: 5000,
33
+            i18nEnabled: true,
34
+            defaultI18nLang: 'en'
35
+        }),
36
+        ZipConnectSharedModule.forRoot(),
37
+        ZipConnectCoreModule,
38
+        ZipConnectHomeModule,
39
+        ZipConnectAccountModule,
40
+        // jhipster-needle-angular-add-module JHipster will add new module here
41
+        ZipConnectEntityModule
42
+    ],
43
+    declarations: [JhiMainComponent, NavbarComponent, ErrorComponent, PageRibbonComponent, ActiveMenuDirective, FooterComponent],
44
+    providers: [
45
+        {
46
+            provide: HTTP_INTERCEPTORS,
47
+            useClass: AuthInterceptor,
48
+            multi: true
49
+        },
50
+        {
51
+            provide: HTTP_INTERCEPTORS,
52
+            useClass: AuthExpiredInterceptor,
53
+            multi: true
54
+        },
55
+        {
56
+            provide: HTTP_INTERCEPTORS,
57
+            useClass: ErrorHandlerInterceptor,
58
+            multi: true
59
+        },
60
+        {
61
+            provide: HTTP_INTERCEPTORS,
62
+            useClass: NotificationInterceptor,
63
+            multi: true
64
+        }
65
+    ],
66
+    bootstrap: [JhiMainComponent]
67
+})
68
+export class ZipConnectAppModule {
69
+    constructor(private dpConfig: NgbDatepickerConfig) {
70
+        this.dpConfig.minDate = { year: moment().year() - 100, month: 1, day: 1 };
71
+    }
72
+}

+ 9
- 0
webapp/app/blocks/config/prod.config.ts Näytä tiedosto

@@ -0,0 +1,9 @@
1
+import { enableProdMode } from '@angular/core';
2
+import { DEBUG_INFO_ENABLED } from 'app/app.constants';
3
+
4
+export function ProdConfig() {
5
+    // disable debug data on prod profile to improve performance
6
+    if (!DEBUG_INFO_ENABLED) {
7
+        enableProdMode();
8
+    }
9
+}

+ 14
- 0
webapp/app/blocks/config/uib-pagination.config.ts Näytä tiedosto

@@ -0,0 +1,14 @@
1
+import { Injectable } from '@angular/core';
2
+import { NgbPaginationConfig } from '@ng-bootstrap/ng-bootstrap';
3
+import { ITEMS_PER_PAGE } from 'app/shared';
4
+
5
+@Injectable({ providedIn: 'root' })
6
+export class PaginationConfig {
7
+    // tslint:disable-next-line: no-unused-variable
8
+    constructor(private config: NgbPaginationConfig) {
9
+        config.boundaryLinks = true;
10
+        config.maxSize = 5;
11
+        config.pageSize = ITEMS_PER_PAGE;
12
+        config.size = 'sm';
13
+    }
14
+}

+ 25
- 0
webapp/app/blocks/interceptor/auth-expired.interceptor.ts Näytä tiedosto

@@ -0,0 +1,25 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent, HttpErrorResponse } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+import { tap } from 'rxjs/operators';
5
+import { LoginService } from 'app/core/login/login.service';
6
+
7
+@Injectable()
8
+export class AuthExpiredInterceptor implements HttpInterceptor {
9
+    constructor(private loginService: LoginService) {}
10
+
11
+    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
12
+        return next.handle(request).pipe(
13
+            tap(
14
+                (event: HttpEvent<any>) => {},
15
+                (err: any) => {
16
+                    if (err instanceof HttpErrorResponse) {
17
+                        if (err.status === 401) {
18
+                            this.loginService.logout();
19
+                        }
20
+                    }
21
+                }
22
+            )
23
+        );
24
+    }
25
+}

+ 27
- 0
webapp/app/blocks/interceptor/auth.interceptor.ts Näytä tiedosto

@@ -0,0 +1,27 @@
1
+import { Injectable } from '@angular/core';
2
+import { Observable } from 'rxjs';
3
+import { LocalStorageService, SessionStorageService } from 'ngx-webstorage';
4
+import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
5
+
6
+import { SERVER_API_URL } from 'app/app.constants';
7
+
8
+@Injectable()
9
+export class AuthInterceptor implements HttpInterceptor {
10
+    constructor(private localStorage: LocalStorageService, private sessionStorage: SessionStorageService) {}
11
+
12
+    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
13
+        if (!request || !request.url || (/^http/.test(request.url) && !(SERVER_API_URL && request.url.startsWith(SERVER_API_URL)))) {
14
+            return next.handle(request);
15
+        }
16
+
17
+        const token = this.localStorage.retrieve('authenticationToken') || this.sessionStorage.retrieve('authenticationToken');
18
+        if (!!token) {
19
+            request = request.clone({
20
+                setHeaders: {
21
+                    Authorization: 'Bearer ' + token
22
+                }
23
+            });
24
+        }
25
+        return next.handle(request);
26
+    }
27
+}

+ 25
- 0
webapp/app/blocks/interceptor/errorhandler.interceptor.ts Näytä tiedosto

@@ -0,0 +1,25 @@
1
+import { Injectable } from '@angular/core';
2
+import { JhiEventManager } from 'ng-jhipster';
3
+import { HttpInterceptor, HttpRequest, HttpErrorResponse, HttpHandler, HttpEvent } from '@angular/common/http';
4
+import { Observable } from 'rxjs';
5
+import { tap } from 'rxjs/operators';
6
+
7
+@Injectable()
8
+export class ErrorHandlerInterceptor implements HttpInterceptor {
9
+    constructor(private eventManager: JhiEventManager) {}
10
+
11
+    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
12
+        return next.handle(request).pipe(
13
+            tap(
14
+                (event: HttpEvent<any>) => {},
15
+                (err: any) => {
16
+                    if (err instanceof HttpErrorResponse) {
17
+                        if (!(err.status === 401 && (err.message === '' || (err.url && err.url.includes('/api/account'))))) {
18
+                            this.eventManager.broadcast({ name: 'zipConnectApp.httpError', content: err });
19
+                        }
20
+                    }
21
+                }
22
+            )
23
+        );
24
+    }
25
+}

+ 37
- 0
webapp/app/blocks/interceptor/notification.interceptor.ts Näytä tiedosto

@@ -0,0 +1,37 @@
1
+import { JhiAlertService } from 'ng-jhipster';
2
+import { HttpInterceptor, HttpRequest, HttpResponse, HttpHandler, HttpEvent } from '@angular/common/http';
3
+import { Injectable } from '@angular/core';
4
+import { Observable } from 'rxjs';
5
+import { tap } from 'rxjs/operators';
6
+
7
+@Injectable()
8
+export class NotificationInterceptor implements HttpInterceptor {
9
+    constructor(private alertService: JhiAlertService) {}
10
+
11
+    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
12
+        return next.handle(request).pipe(
13
+            tap(
14
+                (event: HttpEvent<any>) => {
15
+                    if (event instanceof HttpResponse) {
16
+                        const arr = event.headers.keys();
17
+                        let alert = null;
18
+                        let alertParams = null;
19
+                        arr.forEach(entry => {
20
+                            if (entry.toLowerCase().endsWith('app-alert')) {
21
+                                alert = event.headers.get(entry);
22
+                            } else if (entry.toLowerCase().endsWith('app-params')) {
23
+                                alertParams = event.headers.get(entry);
24
+                            }
25
+                        });
26
+                        if (alert) {
27
+                            if (typeof alert === 'string') {
28
+                                this.alertService.success(alert, { param: alertParams }, null);
29
+                            }
30
+                        }
31
+                    }
32
+                },
33
+                (err: any) => {}
34
+            )
35
+        );
36
+    }
37
+}

+ 19
- 0
webapp/app/core/auth/account.service.ts Näytä tiedosto

@@ -0,0 +1,19 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient, HttpResponse } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+import { Account } from 'app/core/user/account.model';
7
+
8
+@Injectable({ providedIn: 'root' })
9
+export class AccountService {
10
+    constructor(private http: HttpClient) {}
11
+
12
+    get(): Observable<HttpResponse<Account>> {
13
+        return this.http.get<Account>(SERVER_API_URL + 'api/account', { observe: 'response' });
14
+    }
15
+
16
+    save(account: any): Observable<HttpResponse<any>> {
17
+        return this.http.post(SERVER_API_URL + 'api/account', account, { observe: 'response' });
18
+    }
19
+}

+ 59
- 0
webapp/app/core/auth/auth-jwt.service.ts Näytä tiedosto

@@ -0,0 +1,59 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+import { map } from 'rxjs/operators';
5
+import { LocalStorageService, SessionStorageService } from 'ngx-webstorage';
6
+
7
+import { SERVER_API_URL } from 'app/app.constants';
8
+
9
+@Injectable({ providedIn: 'root' })
10
+export class AuthServerProvider {
11
+    constructor(private http: HttpClient, private $localStorage: LocalStorageService, private $sessionStorage: SessionStorageService) {}
12
+
13
+    getToken() {
14
+        return this.$localStorage.retrieve('authenticationToken') || this.$sessionStorage.retrieve('authenticationToken');
15
+    }
16
+
17
+    login(credentials): Observable<any> {
18
+        const data = {
19
+            username: credentials.username,
20
+            password: credentials.password,
21
+            rememberMe: credentials.rememberMe
22
+        };
23
+        return this.http.post(SERVER_API_URL + 'api/authenticate', data, { observe: 'response' }).pipe(map(authenticateSuccess.bind(this)));
24
+
25
+        function authenticateSuccess(resp) {
26
+            const bearerToken = resp.headers.get('Authorization');
27
+            if (bearerToken && bearerToken.slice(0, 7) === 'Bearer ') {
28
+                const jwt = bearerToken.slice(7, bearerToken.length);
29
+                this.storeAuthenticationToken(jwt, credentials.rememberMe);
30
+                return jwt;
31
+            }
32
+        }
33
+    }
34
+
35
+    loginWithToken(jwt, rememberMe) {
36
+        if (jwt) {
37
+            this.storeAuthenticationToken(jwt, rememberMe);
38
+            return Promise.resolve(jwt);
39
+        } else {
40
+            return Promise.reject('auth-jwt-service Promise reject'); // Put appropriate error message here
41
+        }
42
+    }
43
+
44
+    storeAuthenticationToken(jwt, rememberMe) {
45
+        if (rememberMe) {
46
+            this.$localStorage.store('authenticationToken', jwt);
47
+        } else {
48
+            this.$sessionStorage.store('authenticationToken', jwt);
49
+        }
50
+    }
51
+
52
+    logout(): Observable<any> {
53
+        return new Observable(observer => {
54
+            this.$localStorage.clear('authenticationToken');
55
+            this.$sessionStorage.clear('authenticationToken');
56
+            observer.complete();
57
+        });
58
+    }
59
+}

+ 11
- 0
webapp/app/core/auth/csrf.service.ts Näytä tiedosto

@@ -0,0 +1,11 @@
1
+import { Injectable } from '@angular/core';
2
+import { CookieService } from 'ngx-cookie';
3
+
4
+@Injectable({ providedIn: 'root' })
5
+export class CSRFService {
6
+    constructor(private cookieService: CookieService) {}
7
+
8
+    getCSRF(name = 'XSRF-TOKEN') {
9
+        return this.cookieService.get(name);
10
+    }
11
+}

+ 113
- 0
webapp/app/core/auth/principal.service.ts Näytä tiedosto

@@ -0,0 +1,113 @@
1
+import { Injectable } from '@angular/core';
2
+import { JhiLanguageService } from 'ng-jhipster';
3
+import { SessionStorageService } from 'ngx-webstorage';
4
+import { Observable, Subject } from 'rxjs';
5
+import { AccountService } from './account.service';
6
+
7
+@Injectable({ providedIn: 'root' })
8
+export class Principal {
9
+    private userIdentity: any;
10
+    private authenticated = false;
11
+    private authenticationState = new Subject<any>();
12
+
13
+    constructor(
14
+        private languageService: JhiLanguageService,
15
+        private sessionStorage: SessionStorageService,
16
+        private account: AccountService
17
+    ) {}
18
+
19
+    authenticate(identity) {
20
+        this.userIdentity = identity;
21
+        this.authenticated = identity !== null;
22
+        this.authenticationState.next(this.userIdentity);
23
+    }
24
+
25
+    hasAnyAuthority(authorities: string[]): Promise<boolean> {
26
+        return Promise.resolve(this.hasAnyAuthorityDirect(authorities));
27
+    }
28
+
29
+    hasAnyAuthorityDirect(authorities: string[]): boolean {
30
+        if (!this.authenticated || !this.userIdentity || !this.userIdentity.authorities) {
31
+            return false;
32
+        }
33
+
34
+        for (let i = 0; i < authorities.length; i++) {
35
+            if (this.userIdentity.authorities.includes(authorities[i])) {
36
+                return true;
37
+            }
38
+        }
39
+
40
+        return false;
41
+    }
42
+
43
+    hasAuthority(authority: string): Promise<boolean> {
44
+        if (!this.authenticated) {
45
+            return Promise.resolve(false);
46
+        }
47
+
48
+        return this.identity().then(
49
+            id => {
50
+                return Promise.resolve(id.authorities && id.authorities.includes(authority));
51
+            },
52
+            () => {
53
+                return Promise.resolve(false);
54
+            }
55
+        );
56
+    }
57
+
58
+    identity(force?: boolean): Promise<any> {
59
+        if (force === true) {
60
+            this.userIdentity = undefined;
61
+        }
62
+
63
+        // check and see if we have retrieved the userIdentity data from the server.
64
+        // if we have, reuse it by immediately resolving
65
+        if (this.userIdentity) {
66
+            return Promise.resolve(this.userIdentity);
67
+        }
68
+
69
+        // retrieve the userIdentity data from the server, update the identity object, and then resolve.
70
+        return this.account
71
+            .get()
72
+            .toPromise()
73
+            .then(response => {
74
+                const account = response.body;
75
+                if (account) {
76
+                    this.userIdentity = account;
77
+                    this.authenticated = true;
78
+
79
+                    // After retrieve the account info, the language will be changed to
80
+                    // the user's preferred language configured in the account setting
81
+                    const langKey = this.sessionStorage.retrieve('locale') || this.userIdentity.langKey;
82
+                    this.languageService.changeLanguage(langKey);
83
+                } else {
84
+                    this.userIdentity = null;
85
+                    this.authenticated = false;
86
+                }
87
+                this.authenticationState.next(this.userIdentity);
88
+                return this.userIdentity;
89
+            })
90
+            .catch(err => {
91
+                this.userIdentity = null;
92
+                this.authenticated = false;
93
+                this.authenticationState.next(this.userIdentity);
94
+                return null;
95
+            });
96
+    }
97
+
98
+    isAuthenticated(): boolean {
99
+        return this.authenticated;
100
+    }
101
+
102
+    isIdentityResolved(): boolean {
103
+        return this.userIdentity !== undefined;
104
+    }
105
+
106
+    getAuthenticationState(): Observable<any> {
107
+        return this.authenticationState.asObservable();
108
+    }
109
+
110
+    getImageUrl(): string {
111
+        return this.isIdentityResolved() ? this.userIdentity.imageUrl : null;
112
+    }
113
+}

+ 46
- 0
webapp/app/core/auth/state-storage.service.ts Näytä tiedosto

@@ -0,0 +1,46 @@
1
+import { Injectable } from '@angular/core';
2
+import { SessionStorageService } from 'ngx-webstorage';
3
+
4
+@Injectable({ providedIn: 'root' })
5
+export class StateStorageService {
6
+    constructor(private $sessionStorage: SessionStorageService) {}
7
+
8
+    getPreviousState() {
9
+        return this.$sessionStorage.retrieve('previousState');
10
+    }
11
+
12
+    resetPreviousState() {
13
+        this.$sessionStorage.clear('previousState');
14
+    }
15
+
16
+    storePreviousState(previousStateName, previousStateParams) {
17
+        const previousState = { name: previousStateName, params: previousStateParams };
18
+        this.$sessionStorage.store('previousState', previousState);
19
+    }
20
+
21
+    getDestinationState() {
22
+        return this.$sessionStorage.retrieve('destinationState');
23
+    }
24
+
25
+    storeUrl(url: string) {
26
+        this.$sessionStorage.store('previousUrl', url);
27
+    }
28
+
29
+    getUrl() {
30
+        return this.$sessionStorage.retrieve('previousUrl');
31
+    }
32
+
33
+    storeDestinationState(destinationState, destinationStateParams, fromState) {
34
+        const destinationInfo = {
35
+            destination: {
36
+                name: destinationState.name,
37
+                data: destinationState.data
38
+            },
39
+            params: destinationStateParams,
40
+            from: {
41
+                name: fromState.name
42
+            }
43
+        };
44
+        this.$sessionStorage.store('destinationState', destinationInfo);
45
+    }
46
+}

+ 56
- 0
webapp/app/core/auth/user-route-access-service.ts Näytä tiedosto

@@ -0,0 +1,56 @@
1
+import { Injectable, isDevMode } from '@angular/core';
2
+import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
3
+
4
+import { Principal } from '../';
5
+import { LoginModalService } from '../login/login-modal.service';
6
+import { StateStorageService } from './state-storage.service';
7
+
8
+@Injectable({ providedIn: 'root' })
9
+export class UserRouteAccessService implements CanActivate {
10
+    constructor(
11
+        private router: Router,
12
+        private loginModalService: LoginModalService,
13
+        private principal: Principal,
14
+        private stateStorageService: StateStorageService
15
+    ) {}
16
+
17
+    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Promise<boolean> {
18
+        const authorities = route.data['authorities'];
19
+        // We need to call the checkLogin / and so the principal.identity() function, to ensure,
20
+        // that the client has a principal too, if they already logged in by the server.
21
+        // This could happen on a page refresh.
22
+        return this.checkLogin(authorities, state.url);
23
+    }
24
+
25
+    checkLogin(authorities: string[], url: string): Promise<boolean> {
26
+        const principal = this.principal;
27
+        return Promise.resolve(
28
+            principal.identity().then(account => {
29
+                if (!authorities || authorities.length === 0) {
30
+                    return true;
31
+                }
32
+
33
+                if (account) {
34
+                    return principal.hasAnyAuthority(authorities).then(response => {
35
+                        if (response) {
36
+                            return true;
37
+                        }
38
+                        if (isDevMode()) {
39
+                            console.error('User has not any of required authorities: ', authorities);
40
+                        }
41
+                        return false;
42
+                    });
43
+                }
44
+
45
+                this.stateStorageService.storeUrl(url);
46
+                this.router.navigate(['accessdenied']).then(() => {
47
+                    // only show the login dialog, if the user hasn't logged in yet
48
+                    if (!account) {
49
+                        this.loginModalService.open();
50
+                    }
51
+                });
52
+                return false;
53
+            })
54
+        );
55
+    }
56
+}

+ 24
- 0
webapp/app/core/core.module.ts Näytä tiedosto

@@ -0,0 +1,24 @@
1
+import { NgModule, LOCALE_ID } from '@angular/core';
2
+import { DatePipe, registerLocaleData } from '@angular/common';
3
+import { HttpClientModule } from '@angular/common/http';
4
+import { Title } from '@angular/platform-browser';
5
+import locale from '@angular/common/locales/en';
6
+
7
+@NgModule({
8
+    imports: [HttpClientModule],
9
+    exports: [],
10
+    declarations: [],
11
+    providers: [
12
+        Title,
13
+        {
14
+            provide: LOCALE_ID,
15
+            useValue: 'en'
16
+        },
17
+        DatePipe
18
+    ]
19
+})
20
+export class ZipConnectCoreModule {
21
+    constructor() {
22
+        registerLocaleData(locale);
23
+    }
24
+}

+ 14
- 0
webapp/app/core/index.ts Näytä tiedosto

@@ -0,0 +1,14 @@
1
+export * from './auth/csrf.service';
2
+export * from './auth/state-storage.service';
3
+export * from './auth/account.service';
4
+export * from './auth/auth-jwt.service';
5
+export * from './language/language.helper';
6
+export * from './language/language.constants';
7
+export * from './user/account.model';
8
+export * from './user/user.model';
9
+export * from './auth/principal.service';
10
+export * from './auth/user-route-access-service';
11
+export * from './login/login-modal.service';
12
+export * from './login/login.service';
13
+export * from './user/user.service';
14
+export * from './core.module';

+ 9
- 0
webapp/app/core/language/language.constants.ts Näytä tiedosto

@@ -0,0 +1,9 @@
1
+/*
2
+    Languages codes are ISO_639-1 codes, see http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
3
+    They are written in English to avoid character encoding issues (not a perfect solution)
4
+*/
5
+export const LANGUAGES: string[] = [
6
+    'en',
7
+    'es'
8
+    // jhipster-needle-i18n-language-constant - JHipster will add/remove languages in this array
9
+];

+ 66
- 0
webapp/app/core/language/language.helper.ts Näytä tiedosto

@@ -0,0 +1,66 @@
1
+import { Injectable, RendererFactory2, Renderer2 } from '@angular/core';
2
+import { Title } from '@angular/platform-browser';
3
+import { Router, ActivatedRouteSnapshot } from '@angular/router';
4
+import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
5
+import { BehaviorSubject, Observable } from 'rxjs';
6
+
7
+import { LANGUAGES } from 'app/core/language/language.constants';
8
+
9
+@Injectable({ providedIn: 'root' })
10
+export class JhiLanguageHelper {
11
+    renderer: Renderer2 = null;
12
+    private _language: BehaviorSubject<string>;
13
+
14
+    constructor(
15
+        private translateService: TranslateService,
16
+        // tslint:disable-next-line: no-unused-variable
17
+        private rootRenderer: RendererFactory2,
18
+        private titleService: Title,
19
+        private router: Router
20
+    ) {
21
+        this._language = new BehaviorSubject<string>(this.translateService.currentLang);
22
+        this.renderer = rootRenderer.createRenderer(document.querySelector('html'), null);
23
+        this.init();
24
+    }
25
+
26
+    getAll(): Promise<any> {
27
+        return Promise.resolve(LANGUAGES);
28
+    }
29
+
30
+    get language(): Observable<string> {
31
+        return this._language.asObservable();
32
+    }
33
+
34
+    /**
35
+     * Update the window title using params in the following
36
+     * order:
37
+     * 1. titleKey parameter
38
+     * 2. $state.$current.data.pageTitle (current state page title)
39
+     * 3. 'global.title'
40
+     */
41
+    updateTitle(titleKey?: string) {
42
+        if (!titleKey) {
43
+            titleKey = this.getPageTitle(this.router.routerState.snapshot.root);
44
+        }
45
+
46
+        this.translateService.get(titleKey).subscribe(title => {
47
+            this.titleService.setTitle(title);
48
+        });
49
+    }
50
+
51
+    private init() {
52
+        this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
53
+            this._language.next(this.translateService.currentLang);
54
+            this.renderer.setAttribute(document.querySelector('html'), 'lang', this.translateService.currentLang);
55
+            this.updateTitle();
56
+        });
57
+    }
58
+
59
+    private getPageTitle(routeSnapshot: ActivatedRouteSnapshot) {
60
+        let title: string = routeSnapshot.data && routeSnapshot.data['pageTitle'] ? routeSnapshot.data['pageTitle'] : 'zipConnectApp';
61
+        if (routeSnapshot.firstChild) {
62
+            title = this.getPageTitle(routeSnapshot.firstChild) || title;
63
+        }
64
+        return title;
65
+    }
66
+}

+ 27
- 0
webapp/app/core/login/login-modal.service.ts Näytä tiedosto

@@ -0,0 +1,27 @@
1
+import { Injectable } from '@angular/core';
2
+import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
3
+
4
+import { JhiLoginModalComponent } from 'app/shared/login/login.component';
5
+
6
+@Injectable({ providedIn: 'root' })
7
+export class LoginModalService {
8
+    private isOpen = false;
9
+    constructor(private modalService: NgbModal) {}
10
+
11
+    open(): NgbModalRef {
12
+        if (this.isOpen) {
13
+            return;
14
+        }
15
+        this.isOpen = true;
16
+        const modalRef = this.modalService.open(JhiLoginModalComponent);
17
+        modalRef.result.then(
18
+            result => {
19
+                this.isOpen = false;
20
+            },
21
+            reason => {
22
+                this.isOpen = false;
23
+            }
24
+        );
25
+        return modalRef;
26
+    }
27
+}

+ 38
- 0
webapp/app/core/login/login.service.ts Näytä tiedosto

@@ -0,0 +1,38 @@
1
+import { Injectable } from '@angular/core';
2
+
3
+import { Principal } from '../auth/principal.service';
4
+import { AuthServerProvider } from '../auth/auth-jwt.service';
5
+
6
+@Injectable({ providedIn: 'root' })
7
+export class LoginService {
8
+    constructor(private principal: Principal, private authServerProvider: AuthServerProvider) {}
9
+
10
+    login(credentials, callback?) {
11
+        const cb = callback || function() {};
12
+
13
+        return new Promise((resolve, reject) => {
14
+            this.authServerProvider.login(credentials).subscribe(
15
+                data => {
16
+                    this.principal.identity(true).then(account => {
17
+                        resolve(data);
18
+                    });
19
+                    return cb();
20
+                },
21
+                err => {
22
+                    this.logout();
23
+                    reject(err);
24
+                    return cb(err);
25
+                }
26
+            );
27
+        });
28
+    }
29
+
30
+    loginWithToken(jwt, rememberMe) {
31
+        return this.authServerProvider.loginWithToken(jwt, rememberMe);
32
+    }
33
+
34
+    logout() {
35
+        this.authServerProvider.logout().subscribe();
36
+        this.principal.authenticate(null);
37
+    }
38
+}

+ 12
- 0
webapp/app/core/user/account.model.ts Näytä tiedosto

@@ -0,0 +1,12 @@
1
+export class Account {
2
+    constructor(
3
+        public activated: boolean,
4
+        public authorities: string[],
5
+        public email: string,
6
+        public firstName: string,
7
+        public langKey: string,
8
+        public lastName: string,
9
+        public login: string,
10
+        public imageUrl: string
11
+    ) {}
12
+}

+ 47
- 0
webapp/app/core/user/user.model.ts Näytä tiedosto

@@ -0,0 +1,47 @@
1
+export interface IUser {
2
+    id?: any;
3
+    login?: string;
4
+    firstName?: string;
5
+    lastName?: string;
6
+    email?: string;
7
+    activated?: boolean;
8
+    langKey?: string;
9
+    authorities?: any[];
10
+    createdBy?: string;
11
+    createdDate?: Date;
12
+    lastModifiedBy?: string;
13
+    lastModifiedDate?: Date;
14
+    password?: string;
15
+}
16
+
17
+export class User implements IUser {
18
+    constructor(
19
+        public id?: any,
20
+        public login?: string,
21
+        public firstName?: string,
22
+        public lastName?: string,
23
+        public email?: string,
24
+        public activated?: boolean,
25
+        public langKey?: string,
26
+        public authorities?: any[],
27
+        public createdBy?: string,
28
+        public createdDate?: Date,
29
+        public lastModifiedBy?: string,
30
+        public lastModifiedDate?: Date,
31
+        public password?: string
32
+    ) {
33
+        this.id = id ? id : null;
34
+        this.login = login ? login : null;
35
+        this.firstName = firstName ? firstName : null;
36
+        this.lastName = lastName ? lastName : null;
37
+        this.email = email ? email : null;
38
+        this.activated = activated ? activated : false;
39
+        this.langKey = langKey ? langKey : null;
40
+        this.authorities = authorities ? authorities : null;
41
+        this.createdBy = createdBy ? createdBy : null;
42
+        this.createdDate = createdDate ? createdDate : null;
43
+        this.lastModifiedBy = lastModifiedBy ? lastModifiedBy : null;
44
+        this.lastModifiedDate = lastModifiedDate ? lastModifiedDate : null;
45
+        this.password = password ? password : null;
46
+    }
47
+}

+ 39
- 0
webapp/app/core/user/user.service.ts Näytä tiedosto

@@ -0,0 +1,39 @@
1
+import { Injectable } from '@angular/core';
2
+import { HttpClient, HttpResponse } from '@angular/common/http';
3
+import { Observable } from 'rxjs';
4
+
5
+import { SERVER_API_URL } from 'app/app.constants';
6
+import { createRequestOption } from 'app/shared/util/request-util';
7
+import { IUser } from './user.model';
8
+
9
+@Injectable({ providedIn: 'root' })
10
+export class UserService {
11
+    public resourceUrl = SERVER_API_URL + 'api/users';
12
+
13
+    constructor(private http: HttpClient) {}
14
+
15
+    create(user: IUser): Observable<HttpResponse<IUser>> {
16
+        return this.http.post<IUser>(this.resourceUrl, user, { observe: 'response' });
17
+    }
18
+
19
+    update(user: IUser): Observable<HttpResponse<IUser>> {
20
+        return this.http.put<IUser>(this.resourceUrl, user, { observe: 'response' });
21
+    }
22
+
23
+    find(login: string): Observable<HttpResponse<IUser>> {
24
+        return this.http.get<IUser>(`${this.resourceUrl}/${login}`, { observe: 'response' });
25
+    }
26
+
27
+    query(req?: any): Observable<HttpResponse<IUser[]>> {
28
+        const options = createRequestOption(req);
29
+        return this.http.get<IUser[]>(this.resourceUrl, { params: options, observe: 'response' });
30
+    }
31
+
32
+    delete(login: string): Observable<HttpResponse<any>> {
33
+        return this.http.delete(`${this.resourceUrl}/${login}`, { observe: 'response' });
34
+    }
35
+
36
+    authorities(): Observable<string[]> {
37
+        return this.http.get<string[]>(SERVER_API_URL + 'api/users/authorities');
38
+    }
39
+}

+ 19
- 0
webapp/app/entities/cohort/cohort-delete-dialog.component.html Näytä tiedosto

@@ -0,0 +1,19 @@
1
+<form name="deleteForm" (ngSubmit)="confirmDelete(cohort.id)">
2
+    <div class="modal-header">
3
+        <h4 class="modal-title" jhiTranslate="entity.delete.title">Confirm delete operation</h4>
4
+        <button type="button" class="close" data-dismiss="modal" aria-hidden="true"
5
+                (click)="clear()">&times;</button>
6
+    </div>
7
+    <div class="modal-body">
8
+        <jhi-alert-error></jhi-alert-error>
9
+        <p id="jhi-delete-cohort-heading" jhiTranslate="zipConnectApp.cohort.delete.question" translateValues="{id: '{{cohort.id}}'}">Are you sure you want to delete this Cohort?</p>
10
+    </div>
11
+    <div class="modal-footer">
12
+        <button type="button" class="btn btn-secondary" data-dismiss="modal" (click)="clear()">
13
+            <fa-icon [icon]="'ban'"></fa-icon>&nbsp;<span jhiTranslate="entity.action.cancel">Cancel</span>
14
+        </button>
15
+        <button id="jhi-confirm-delete-cohort" type="submit" class="btn btn-danger">
16
+            <fa-icon [icon]="'times'"></fa-icon>&nbsp;<span jhiTranslate="entity.action.delete">Delete</span>
17
+        </button>
18
+    </div>
19
+</form>

+ 65
- 0
webapp/app/entities/cohort/cohort-delete-dialog.component.ts Näytä tiedosto

@@ -0,0 +1,65 @@
1
+import { Component, OnInit, OnDestroy } from '@angular/core';
2
+import { ActivatedRoute, Router } from '@angular/router';
3
+
4
+import { NgbActiveModal, NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
5
+import { JhiEventManager } from 'ng-jhipster';
6
+
7
+import { ICohort } from 'app/shared/model/cohort.model';
8
+import { CohortService } from './cohort.service';
9
+
10
+@Component({
11
+    selector: 'jhi-cohort-delete-dialog',
12
+    templateUrl: './cohort-delete-dialog.component.html'
13
+})
14
+export class CohortDeleteDialogComponent {
15
+    cohort: ICohort;
16
+
17
+    constructor(private cohortService: CohortService, public activeModal: NgbActiveModal, private eventManager: JhiEventManager) {}
18
+
19
+    clear() {
20
+        this.activeModal.dismiss('cancel');
21
+    }
22
+
23
+    confirmDelete(id: number) {
24
+        this.cohortService.delete(id).subscribe(response => {
25
+            this.eventManager.broadcast({
26
+                name: 'cohortListModification',
27
+                content: 'Deleted an cohort'
28
+            });
29
+            this.activeModal.dismiss(true);
30
+        });
31
+    }
32
+}
33
+
34
+@Component({
35
+    selector: 'jhi-cohort-delete-popup',
36
+    template: ''
37
+})
38
+export class CohortDeletePopupComponent implements OnInit, OnDestroy {
39
+    private ngbModalRef: NgbModalRef;
40
+
41
+    constructor(private activatedRoute: ActivatedRoute, private router: Router, private modalService: NgbModal) {}
42
+
43
+    ngOnInit() {
44
+        this.activatedRoute.data.subscribe(({ cohort }) => {
45
+            setTimeout(() => {
46
+                this.ngbModalRef = this.modalService.open(CohortDeleteDialogComponent as Component, { size: 'lg', backdrop: 'static' });
47
+                this.ngbModalRef.componentInstance.cohort = cohort;
48
+                this.ngbModalRef.result.then(
49
+                    result => {
50
+                        this.router.navigate([{ outlets: { popup: null } }], { replaceUrl: true, queryParamsHandling: 'merge' });
51
+                        this.ngbModalRef = null;
52
+                    },
53
+                    reason => {
54
+                        this.router.navigate([{ outlets: { popup: null } }], { replaceUrl: true, queryParamsHandling: 'merge' });
55
+                        this.ngbModalRef = null;
56
+                    }
57
+                );
58
+            }, 0);
59
+        });
60
+    }
61
+
62
+    ngOnDestroy() {
63
+        this.ngbModalRef = null;
64
+    }
65
+}

+ 31
- 0
webapp/app/entities/cohort/cohort-detail.component.html Näytä tiedosto

@@ -0,0 +1,31 @@
1
+<div class="row justify-content-center">
2
+    <div class="col-8">
3
+        <div *ngIf="cohort">
4
+            <h2><span jhiTranslate="zipConnectApp.cohort.detail.title">Cohort</span> {{cohort.id}}</h2>
5
+            <hr>
6
+            <jhi-alert-error></jhi-alert-error>
7
+            <dl class="row-md jh-entity-details">
8
+                <dt><span jhiTranslate="zipConnectApp.cohort.cohortId">Cohort Id</span></dt>
9
+                <dd>
10
+                    <span>{{cohort.cohortId}}</span>
11
+                </dd>
12
+                <dt><span jhiTranslate="zipConnectApp.cohort.gradDate">Grad Date</span></dt>
13
+                <dd>
14
+                    <span>{{cohort.gradDate}}</span>
15
+                </dd>
16
+            </dl>
17
+
18
+            <button type="submit"
19
+                    (click)="previousState()"
20
+                    class="btn btn-info">
21
+                <fa-icon [icon]="'arrow-left'"></fa-icon>&nbsp;<span jhiTranslate="entity.action.back"> Back</span>
22
+            </button>
23
+
24
+            <button type="button"
25
+                    [routerLink]="['/cohort', cohort.id, 'edit']"
26
+                    class="btn btn-primary">
27
+                <fa-icon [icon]="'pencil-alt'"></fa-icon>&nbsp;<span jhiTranslate="entity.action.edit"> Edit</span>
28
+            </button>
29
+        </div>
30
+    </div>
31
+</div>

+ 0
- 0
webapp/app/entities/cohort/cohort-detail.component.ts Näytä tiedosto


Some files were not shown because too many files changed in this diff