diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java index 3303a7e66..db8924242 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/security/controller/UsersController.java @@ -71,6 +71,13 @@ public ResponseEntity getOne(@PathVariable String username) { return ResponseEntity.ok(findUserOrThrowHttp404(username)); } + @PreAuthorize("hasRole('ADMIN')") + @Transactional + @GetMapping("/role/{rolename}") + public ResponseEntity getUsersWithRole(@PathVariable String rolename) { + return ResponseEntity.ok(userRepository.findByRoles_Name(rolename)); + } + @PreAuthorize("hasRole('ADMIN')") @Transactional @DeleteMapping("/{username}") diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index e6f30bfe3..5ad8870f7 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -378,11 +378,14 @@ label.nameid-formats-type=NameID Type label.select-filter-type=Select Filter Type label.admin=Admin +label.action-required=Action Required +label.user-access-request=User Access Request label.user-maintenance=User Maintenance label.user-id=UserId label.email=Email label.role=Role label.delete=Delete? +label.delete-request=Delete Request message.delete-user-title=Delete User? message.delete-user-body=You are requesting to delete a user. If you complete this process the user will be removed. This cannot be undone. Do you wish to continue? diff --git a/ui/package-lock.json b/ui/package-lock.json index 841137feb..95a0ac1c4 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -183,6 +183,7 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", + "fsevents": "1.2.7", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -525,6 +526,7 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", + "fsevents": "1.2.7", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -922,6 +924,7 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", + "fsevents": "1.2.7", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -1472,6 +1475,7 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", + "fsevents": "1.2.7", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -2807,6 +2811,7 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", + "fsevents": "1.2.7", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -4647,6 +4652,535 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, + "fsevents": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.12.1", + "node-pre-gyp": "0.10.3" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.3.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.3.5" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.2.0", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.3" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": "2.1.2" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.11" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.1.2", + "yallist": "3.0.3" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "2.3.5" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.9", + "iconv-lite": "0.4.24", + "sax": "1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.10.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.3", + "mkdirp": "0.5.1", + "needle": "2.2.4", + "nopt": "4.0.1", + "npm-packlist": "1.2.0", + "npmlog": "4.1.2", + "rc": "1.2.8", + "rimraf": "2.6.3", + "semver": "5.6.0", + "tar": "4.4.8" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.1", + "osenv": "0.1.5" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "3.0.1", + "npm-bundled": "1.0.5" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.5", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.6.0", + "ini": "1.3.5", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "1.1.1", + "fs-minipass": "1.2.5", + "minipass": "2.3.5", + "minizlib": "1.2.1", + "mkdirp": "0.5.1", + "safe-buffer": "5.1.2", + "yallist": "3.0.3" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + } + } + }, "fstream": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", @@ -7381,6 +7915,13 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "nan": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", + "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", + "dev": true, + "optional": true + }, "nanomatch": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", @@ -11103,6 +11644,7 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", + "fsevents": "1.2.7", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -11950,6 +12492,7 @@ "anymatch": "2.0.0", "async-each": "1.0.1", "braces": "2.3.2", + "fsevents": "1.2.7", "glob-parent": "3.1.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", diff --git a/ui/src/app/user/admin/action/collection.action.ts b/ui/src/app/admin/action/collection.action.ts similarity index 94% rename from ui/src/app/user/admin/action/collection.action.ts rename to ui/src/app/admin/action/collection.action.ts index 96c2ae937..8c9c3e420 100644 --- a/ui/src/app/user/admin/action/collection.action.ts +++ b/ui/src/app/admin/action/collection.action.ts @@ -11,6 +11,7 @@ export enum AdminCollectionActionTypes { UPDATE_ADMIN_SUCCESS = '[Admin Collection] Update Admin Success', UPDATE_ADMIN_FAIL = '[Admin Collection] Update Admin Fail', + LOAD_NEW_USERS_REQUEST = '[Admin Collection] Load New Users Request', LOAD_ADMIN_REQUEST = '[Admin Collection] Load Admin Request', LOAD_ADMIN_SUCCESS = '[Admin Collection] Load Admin Success', LOAD_ADMIN_ERROR = '[Admin Collection] Load Admin Error', @@ -51,6 +52,12 @@ export class LoadAdminRequest implements Action { constructor() { } } +export class LoadNewUsersRequest implements Action { + readonly type = AdminCollectionActionTypes.LOAD_NEW_USERS_REQUEST; + + constructor() { } +} + export class LoadAdminSuccess implements Action { readonly type = AdminCollectionActionTypes.LOAD_ADMIN_SUCCESS; @@ -126,6 +133,7 @@ export type AdminCollectionActionsUnion = | LoadAdminRequest | LoadAdminSuccess | LoadAdminError + | LoadNewUsersRequest | AddAdminRequest | AddAdminSuccess | AddAdminFail diff --git a/ui/src/app/user/admin/admin.component.html b/ui/src/app/admin/admin.component.html similarity index 100% rename from ui/src/app/user/admin/admin.component.html rename to ui/src/app/admin/admin.component.html diff --git a/ui/src/app/user/admin/admin.component.spec.ts b/ui/src/app/admin/admin.component.spec.ts similarity index 100% rename from ui/src/app/user/admin/admin.component.spec.ts rename to ui/src/app/admin/admin.component.spec.ts diff --git a/ui/src/app/user/admin/admin.component.ts b/ui/src/app/admin/admin.component.ts similarity index 100% rename from ui/src/app/user/admin/admin.component.ts rename to ui/src/app/admin/admin.component.ts diff --git a/ui/src/app/user/admin/admin.module.ts b/ui/src/app/admin/admin.module.ts similarity index 63% rename from ui/src/app/user/admin/admin.module.ts rename to ui/src/app/admin/admin.module.ts index f15e062dc..476575d1b 100644 --- a/ui/src/app/user/admin/admin.module.ts +++ b/ui/src/app/admin/admin.module.ts @@ -1,12 +1,12 @@ -import { NgModule, ModuleWithProviders } from '@angular/core'; +import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; -import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { FormsModule } from '@angular/forms'; import { StoreModule } from '@ngrx/store'; -import { SharedModule } from '../../shared/shared.module'; -import { I18nModule } from '../../i18n/i18n.module'; +import { SharedModule } from '../shared/shared.module'; +import { I18nModule } from '../i18n/i18n.module'; import { AdminManagementPageComponent } from './container/admin-management.component'; import { AdminComponent } from './admin.component'; import { reducers } from './reducer'; @@ -15,42 +15,36 @@ import { AdminCollectionEffects } from './effect/collection.effect'; import { EffectsModule } from '@ngrx/effects'; import { DeleteUserDialogComponent } from './component/delete-user-dialog.component'; import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; +import { ActionRequiredPageComponent } from './container/action-required.component'; +import { AccessRequestComponent } from './component/access-request.component'; +import { UserManagementComponent } from './component/user-management.component'; @NgModule({ declarations: [ AdminManagementPageComponent, AdminComponent, - DeleteUserDialogComponent + DeleteUserDialogComponent, + UserManagementComponent, + ActionRequiredPageComponent, + AccessRequestComponent ], entryComponents: [ DeleteUserDialogComponent ], imports: [ CommonModule, + I18nModule, + StoreModule.forFeature('admin', reducers), + EffectsModule.forFeature([AdminCollectionEffects]), FormsModule, RouterModule, HttpClientModule, SharedModule, I18nModule, NgbModalModule - ] -}) -export class UserAdminModule { - static forRoot(): ModuleWithProviders { - return { - ngModule: RootUserAdminModule, - providers: [ - AdminService - ] - }; - } -} - -@NgModule({ - imports: [ - UserAdminModule, - StoreModule.forFeature('admin', reducers), - EffectsModule.forFeature([AdminCollectionEffects]), ], + providers: [ + AdminService + ] }) -export class RootUserAdminModule { } +export class AdminModule { } diff --git a/ui/src/app/admin/component/access-request.component.html b/ui/src/app/admin/component/access-request.component.html new file mode 100644 index 000000000..a9b96b586 --- /dev/null +++ b/ui/src/app/admin/component/access-request.component.html @@ -0,0 +1,68 @@ +
+
+
+
+
+ User Access Request +
+
+
+ +
+
+

There are no new user requests at this time.

+
+
+
+ +
+
+
+
+
+
+ UserId +
+
{{ user.username }}
+
+ Email +
+
{{ user.emailAddress }}
+
+
+
+
+ Name +
+
{{ user.firstName }} {{ user.lastName }}
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
\ No newline at end of file diff --git a/ui/src/app/admin/component/access-request.component.ts b/ui/src/app/admin/component/access-request.component.ts new file mode 100644 index 000000000..e082761de --- /dev/null +++ b/ui/src/app/admin/component/access-request.component.ts @@ -0,0 +1,19 @@ +import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; +import { UserManagementComponent } from './user-management.component'; +import * as fromAdmin from '../reducer'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +@Component({ + selector: 'access-request-component', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './access-request.component.html', + styleUrls: [] +}) +export class AccessRequestComponent extends UserManagementComponent implements OnInit { + + ngOnInit(): void { + this.users$ = this.store.select(fromAdmin.getAllNewUsers); + this.hasUsers$ = this.users$.pipe(map(userList => userList.length > 0)); + } +} diff --git a/ui/src/app/user/admin/component/delete-user-dialog.component.html b/ui/src/app/admin/component/delete-user-dialog.component.html similarity index 100% rename from ui/src/app/user/admin/component/delete-user-dialog.component.html rename to ui/src/app/admin/component/delete-user-dialog.component.html diff --git a/ui/src/app/user/admin/component/delete-user-dialog.component.ts b/ui/src/app/admin/component/delete-user-dialog.component.ts similarity index 78% rename from ui/src/app/user/admin/component/delete-user-dialog.component.ts rename to ui/src/app/admin/component/delete-user-dialog.component.ts index 68c733517..64a5e26ac 100644 --- a/ui/src/app/user/admin/component/delete-user-dialog.component.ts +++ b/ui/src/app/admin/component/delete-user-dialog.component.ts @@ -1,4 +1,4 @@ -import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; diff --git a/ui/src/app/user/admin/container/admin-management.component.html b/ui/src/app/admin/component/user-management.component.html similarity index 90% rename from ui/src/app/user/admin/container/admin-management.component.html rename to ui/src/app/admin/component/user-management.component.html index ab42d3663..900e0dd5b 100644 --- a/ui/src/app/user/admin/container/admin-management.component.html +++ b/ui/src/app/admin/component/user-management.component.html @@ -8,7 +8,10 @@
- +
+

There are no users configured in the system for you to manage.

+
+
diff --git a/ui/src/app/user/admin/container/admin-management.component.ts b/ui/src/app/admin/component/user-management.component.ts similarity index 60% rename from ui/src/app/user/admin/container/admin-management.component.ts rename to ui/src/app/admin/component/user-management.component.ts index 3cf0b6b2f..79aaacc78 100644 --- a/ui/src/app/user/admin/container/admin-management.component.ts +++ b/ui/src/app/admin/component/user-management.component.ts @@ -1,39 +1,43 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Component, ChangeDetectionStrategy, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; -import * as fromRoot from '../../../app.reducer'; -import * as fromCore from '../../../core/reducer'; +import * as fromRoot from '../../app.reducer'; +import * as fromCore from '../../core/reducer'; import * as fromAdmin from '../reducer'; -import { LoadAdminRequest, UpdateAdminRequest, RemoveAdminRequest } from '../action/collection.action'; +import { UpdateAdminRequest, RemoveAdminRequest } from '../action/collection.action'; import { Admin } from '../model/admin'; -import { LoadRoleRequest } from '../../../core/action/configuration.action'; import { DeleteUserDialogComponent } from '../component/delete-user-dialog.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { map } from 'rxjs/operators'; @Component({ - selector: 'admin-management-page', + selector: 'user-management', changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './admin-management.component.html', + templateUrl: './user-management.component.html', styleUrls: [] }) -export class AdminManagementPageComponent { +export class UserManagementComponent implements OnInit { users$: Observable; currentUser: Admin; userSub: Subscription; roles$: Observable; + hasUsers$: Observable; + constructor( - private store: Store, - private modal: NgbModal + protected store: Store, + protected modal: NgbModal ) { - this.store.dispatch(new LoadAdminRequest()); - this.store.dispatch(new LoadRoleRequest()); + this.roles$ = this.store.select(fromCore.getUserRoles); + } + ngOnInit(): void { + this.users$ = this.store.select(fromAdmin.getAllConfiguredUsers); + this.hasUsers$ = this.users$.pipe(map(userList => userList.length > 0)); this.users$ = this.store.select(fromAdmin.getAllAdmins); - this.roles$ = this.store.select(fromCore.getRoles); let user$ = this.store.select(fromCore.getUser); this.userSub = user$.subscribe(u => this.currentUser = u); diff --git a/ui/src/app/admin/container/action-required.component.html b/ui/src/app/admin/container/action-required.component.html new file mode 100644 index 000000000..4bb706337 --- /dev/null +++ b/ui/src/app/admin/container/action-required.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/app/admin/container/action-required.component.ts b/ui/src/app/admin/container/action-required.component.ts new file mode 100644 index 000000000..e920c81e1 --- /dev/null +++ b/ui/src/app/admin/container/action-required.component.ts @@ -0,0 +1,21 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Store } from '@ngrx/store'; + +import * as fromRoot from '../../app.reducer'; + +import { LoadNewUsersRequest } from '../action/collection.action'; + +@Component({ + selector: 'action-required-page', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './action-required.component.html', + styleUrls: [] +}) +export class ActionRequiredPageComponent { + + constructor( + private store: Store + ) { + this.store.dispatch(new LoadNewUsersRequest()); + } +} diff --git a/ui/src/app/admin/container/admin-management.component.html b/ui/src/app/admin/container/admin-management.component.html new file mode 100644 index 000000000..77864802b --- /dev/null +++ b/ui/src/app/admin/container/admin-management.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ui/src/app/user/admin/container/admin-management.component.spec.ts b/ui/src/app/admin/container/admin-management.component.spec.ts similarity index 83% rename from ui/src/app/user/admin/container/admin-management.component.spec.ts rename to ui/src/app/admin/container/admin-management.component.spec.ts index d12a61632..440fe4038 100644 --- a/ui/src/app/user/admin/container/admin-management.component.spec.ts +++ b/ui/src/app/admin/container/admin-management.component.spec.ts @@ -3,7 +3,17 @@ import { FormsModule } from '@angular/forms'; import { StoreModule, Store, combineReducers } from '@ngrx/store'; import * as fromAdmin from '../reducer'; import { AdminManagementPageComponent } from './admin-management.component'; -import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { MockI18nModule } from '../../../testing/i18n.stub'; +import { Component } from '@angular/core'; + +@Component({ + selector: 'user-management', + template: '
' +}) +export class UserManagementComponent { + + constructor() {} +} describe('Admin Management Page Component', () => { let fixture: ComponentFixture; @@ -20,7 +30,8 @@ describe('Admin Management Page Component', () => { MockI18nModule ], declarations: [ - AdminManagementPageComponent + AdminManagementPageComponent, + UserManagementComponent ], }); diff --git a/ui/src/app/admin/container/admin-management.component.ts b/ui/src/app/admin/container/admin-management.component.ts new file mode 100644 index 000000000..e9dd5ff68 --- /dev/null +++ b/ui/src/app/admin/container/admin-management.component.ts @@ -0,0 +1,21 @@ +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Store } from '@ngrx/store'; + +import * as fromRoot from '../../app.reducer'; + +import { LoadAdminRequest } from '../action/collection.action'; + +@Component({ + selector: 'admin-management-page', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './admin-management.component.html', + styleUrls: [] +}) +export class AdminManagementPageComponent { + + constructor( + private store: Store + ) { + this.store.dispatch(new LoadAdminRequest()); + } +} diff --git a/ui/src/app/user/admin/effect/collection.effect.ts b/ui/src/app/admin/effect/collection.effect.ts similarity index 58% rename from ui/src/app/user/admin/effect/collection.effect.ts rename to ui/src/app/admin/effect/collection.effect.ts index 4958f1d8e..84d2aa3f2 100644 --- a/ui/src/app/user/admin/effect/collection.effect.ts +++ b/ui/src/app/admin/effect/collection.effect.ts @@ -11,9 +11,12 @@ import { UpdateAdminRequest, UpdateAdminSuccess, RemoveAdminRequest, - RemoveAdminSuccess + RemoveAdminSuccess, + LoadNewUsersRequest } from '../action/collection.action'; import { AdminService } from '../service/admin.service'; +import { AddNotification } from '../../notification/action/notification.action'; +import { Notification, NotificationType } from '../../notification/model/notification'; /* istanbul ignore next */ @@ -28,6 +31,14 @@ export class AdminCollectionEffects { )) ); + @Effect() + loadNewUsersRequest$ = this.actions$.pipe( + ofType(AdminCollectionActionTypes.LOAD_NEW_USERS_REQUEST), + switchMap(() => this.adminService.queryByRole('ROLE_NONE').pipe( + map(users => new LoadAdminSuccess(users)) + )) + ); + @Effect() updateAdminRequest$ = this.actions$.pipe( ofType(AdminCollectionActionTypes.UPDATE_ADMIN_REQUEST), @@ -40,6 +51,19 @@ export class AdminCollectionEffects { )) ); + @Effect() + updateAdminRoleSuccess$ = this.actions$.pipe( + ofType(AdminCollectionActionTypes.UPDATE_ADMIN_SUCCESS), + map(action => action.payload), + map(user => new AddNotification( + new Notification( + NotificationType.Success, + `User update successful for ${ user.changes.username }`, + 5000 + ) + )) + ); + @Effect() removeAdminRequest$ = this.actions$.pipe( ofType(AdminCollectionActionTypes.REMOVE_ADMIN_REQUEST), @@ -55,6 +79,19 @@ export class AdminCollectionEffects { map(action => new LoadAdminRequest()) ); + @Effect() + deleteAdminRoleSuccess$ = this.actions$.pipe( + ofType(AdminCollectionActionTypes.REMOVE_ADMIN_SUCCESS), + map(action => action.payload), + map(user => new AddNotification( + new Notification( + NotificationType.Success, + `User deleted.`, + 5000 + ) + )) + ); + constructor( private actions$: Actions, private adminService: AdminService, diff --git a/ui/src/app/user/admin/model/admin-entity.ts b/ui/src/app/admin/model/admin-entity.ts similarity index 100% rename from ui/src/app/user/admin/model/admin-entity.ts rename to ui/src/app/admin/model/admin-entity.ts diff --git a/ui/src/app/user/admin/model/admin.ts b/ui/src/app/admin/model/admin.ts similarity index 100% rename from ui/src/app/user/admin/model/admin.ts rename to ui/src/app/admin/model/admin.ts diff --git a/ui/src/app/user/admin/reducer/collection.reducer.spec.ts b/ui/src/app/admin/reducer/collection.reducer.spec.ts similarity index 100% rename from ui/src/app/user/admin/reducer/collection.reducer.spec.ts rename to ui/src/app/admin/reducer/collection.reducer.spec.ts diff --git a/ui/src/app/user/admin/reducer/collection.reducer.ts b/ui/src/app/admin/reducer/collection.reducer.ts similarity index 100% rename from ui/src/app/user/admin/reducer/collection.reducer.ts rename to ui/src/app/admin/reducer/collection.reducer.ts diff --git a/ui/src/app/user/admin/reducer/index.ts b/ui/src/app/admin/reducer/index.ts similarity index 77% rename from ui/src/app/user/admin/reducer/index.ts rename to ui/src/app/admin/reducer/index.ts index ad9dfd93f..31a5265ea 100644 --- a/ui/src/app/user/admin/reducer/index.ts +++ b/ui/src/app/admin/reducer/index.ts @@ -1,5 +1,5 @@ import { createSelector, createFeatureSelector } from '@ngrx/store'; -import * as fromRoot from '../../../core/reducer'; +import * as fromRoot from '../../core/reducer'; import * as fromCollection from './collection.reducer'; export interface AdminState { @@ -31,3 +31,7 @@ export const getSelectedAdmin = createSelector(getAdminEntities, getSelectedAdmi return selectedId && entities[selectedId]; }); export const getAdminIds = createSelector(getCollectionState, fromCollection.selectAdminIds); +export const getAllNewUsers = createSelector(getAllAdmins, (admins) => admins.filter(a => a.role === 'ROLE_NONE')); +export const getAllConfiguredUsers = createSelector(getAllAdmins, (admins) => admins.filter(a => a.role !== 'ROLE_NONE')); + +export const getTotalActionsRequired = createSelector(getAllNewUsers, (users) => users.length); diff --git a/ui/src/app/user/admin/service/admin.service.spec.ts b/ui/src/app/admin/service/admin.service.spec.ts similarity index 100% rename from ui/src/app/user/admin/service/admin.service.spec.ts rename to ui/src/app/admin/service/admin.service.spec.ts diff --git a/ui/src/app/user/admin/service/admin.service.ts b/ui/src/app/admin/service/admin.service.ts similarity index 81% rename from ui/src/app/user/admin/service/admin.service.ts rename to ui/src/app/admin/service/admin.service.ts index 7226f3de2..452cc8f7a 100644 --- a/ui/src/app/user/admin/service/admin.service.ts +++ b/ui/src/app/admin/service/admin.service.ts @@ -22,6 +22,14 @@ export class AdminService { ); } + queryByRole(role: string): Observable { + return this.http.get( + `${this.base}${this.endpoint}/role/${role}`, {} + ).pipe( + map(users => users.map(u => new AdminEntity(u))) + ); + } + update(user: Admin): Observable { return this.http.patch( `${this.base}${this.endpoint}/${user.username}`, {...user} diff --git a/ui/src/app/core/reducer/index.ts b/ui/src/app/core/reducer/index.ts index 7d420742b..f2d64e934 100644 --- a/ui/src/app/core/reducer/index.ts +++ b/ui/src/app/core/reducer/index.ts @@ -39,7 +39,11 @@ export const getVersionInfo = createSelector(getVersionState, fromVersion.getVer export const getVersionLoading = createSelector(getVersionState, fromVersion.getVersionIsLoading); export const getVersionError = createSelector(getVersionState, fromVersion.getVersionError); +export const filterRolesFn = (roles: string[]) => roles.filter(r => r !== 'ROLE_NONE'); +export const isUserAdminFn = (user) => user ? user.role === 'ROLE_ADMIN' : null; + export const getConfigState = createSelector(getCoreFeature, getConfigStateFn); export const getRoles = createSelector(getConfigState, fromConfig.getRoles); -export const isCurrentUserAdmin = createSelector(getUser, user => user ? user.role === 'ROLE_ADMIN' : null); +export const getUserRoles = createSelector(getRoles, filterRolesFn); +export const isCurrentUserAdmin = createSelector(getUser, isUserAdminFn); diff --git a/ui/src/app/dashboard/container/dashboard.component.html b/ui/src/app/dashboard/container/dashboard.component.html index 09c647146..29d93c132 100644 --- a/ui/src/app/dashboard/container/dashboard.component.html +++ b/ui/src/app/dashboard/container/dashboard.component.html @@ -1,4 +1,4 @@ -
+ \ No newline at end of file diff --git a/ui/src/app/dashboard/container/dashboard.component.ts b/ui/src/app/dashboard/container/dashboard.component.ts index c8562dfb1..0942da7d4 100644 --- a/ui/src/app/dashboard/container/dashboard.component.ts +++ b/ui/src/app/dashboard/container/dashboard.component.ts @@ -2,9 +2,10 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; +import * as fromAdmin from '../../admin/reducer'; import * as fromCore from '../../core/reducer'; import { Observable } from 'rxjs'; -import { User } from '../../core/model/user'; +import { LoadRoleRequest } from '../../core/action/configuration.action'; import { map } from 'rxjs/operators'; @Component({ @@ -15,13 +16,16 @@ import { map } from 'rxjs/operators'; }) export class DashboardPageComponent { - user$: Observable; + actionsRequired$: Observable; + hasActions$: Observable; isAdmin$: Observable; constructor( private store: Store ) { - this.user$ = this.store.select(fromCore.getUser); + this.actionsRequired$ = this.store.select(fromAdmin.getTotalActionsRequired); + this.hasActions$ = this.actionsRequired$.pipe(map(a => a > 0)); this.isAdmin$ = this.store.select(fromCore.isCurrentUserAdmin); + this.store.dispatch(new LoadRoleRequest()); } } diff --git a/ui/src/app/dashboard/dashboard.module.ts b/ui/src/app/dashboard/dashboard.module.ts index 4aa642ca8..bca0f80f1 100644 --- a/ui/src/app/dashboard/dashboard.module.ts +++ b/ui/src/app/dashboard/dashboard.module.ts @@ -7,7 +7,7 @@ import { WidgetRegistry } from 'ngx-schema-form'; import { DashboardPageComponent } from './container/dashboard.component'; import { DashboardRoutingModule } from './dashboard.routing'; import { MetadataModule } from '../metadata/metadata.module'; -import { UserModule } from '../user/user.module'; +import { AdminModule } from '../admin/admin.module'; @NgModule({ @@ -15,8 +15,9 @@ import { UserModule } from '../user/user.module'; CommonModule, DashboardRoutingModule, MetadataModule, - UserModule, - I18nModule + AdminModule, + I18nModule, + CommonModule ], providers: [ { provide: WidgetRegistry, useClass: CustomWidgetRegistry } diff --git a/ui/src/app/dashboard/dashboard.routing.ts b/ui/src/app/dashboard/dashboard.routing.ts index 3f86458f4..4a541b2e3 100644 --- a/ui/src/app/dashboard/dashboard.routing.ts +++ b/ui/src/app/dashboard/dashboard.routing.ts @@ -5,9 +5,9 @@ import { ManagerComponent } from '../metadata/manager/container/manager.componen import { MetadataPageComponent } from '../metadata/metadata.component'; import { DashboardResolversListComponent } from '../metadata/manager/container/dashboard-resolvers-list.component'; import { DashboardProvidersListComponent } from '../metadata/manager/container/dashboard-providers-list.component'; -import { UserPageComponent } from '../user/user.component'; -import { AdminComponent } from '../user/admin/admin.component'; -import { AdminManagementPageComponent } from '../user/admin/container/admin-management.component'; +import { ActionRequiredPageComponent } from '../admin/container/action-required.component'; +import { AdminComponent } from '../admin/admin.component'; +import { AdminManagementPageComponent } from '../admin/container/admin-management.component'; import { AdminGuard } from '../core/service/admin.guard'; const routes: Routes = [ @@ -33,19 +33,20 @@ const routes: Routes = [ ] }, { - path: 'users', - component: UserPageComponent, + path: 'admin', canActivate: [AdminGuard], children: [ - { path: '', redirectTo: 'admin', pathMatch: 'prefix' }, { - path: 'admin', + path: '', component: AdminComponent, children: [ - { path: '', redirectTo: 'management', pathMatch: 'prefix' }, { path: 'management', component: AdminManagementPageComponent + }, + { + path: 'actions', + component: ActionRequiredPageComponent } ] } diff --git a/ui/src/app/notification/model/notification.ts b/ui/src/app/notification/model/notification.ts index 7668d593d..4c7b237cd 100644 --- a/ui/src/app/notification/model/notification.ts +++ b/ui/src/app/notification/model/notification.ts @@ -9,7 +9,7 @@ export class Notification { } export enum NotificationType { - Success = 'alert-succes', + Success = 'alert-success', Info = 'alert-info', Warning = 'alert-warning', Danger = 'alert-danger' diff --git a/ui/src/app/user/user.component.html b/ui/src/app/user/user.component.html deleted file mode 100644 index 0680b43f9..000000000 --- a/ui/src/app/user/user.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/ui/src/app/user/user.component.spec.ts b/ui/src/app/user/user.component.spec.ts deleted file mode 100644 index 173e1409a..000000000 --- a/ui/src/app/user/user.component.spec.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { TestBed, ComponentFixture } from '@angular/core/testing'; -import { UserPageComponent } from './user.component'; -import { RouterTestingModule } from '@angular/router/testing'; - -describe('User Root Component', () => { - let fixture: ComponentFixture; - let instance: UserPageComponent; - - beforeEach(() => { - TestBed.configureTestingModule({ - imports: [ - RouterTestingModule - ], - declarations: [ - UserPageComponent - ], - }); - - fixture = TestBed.createComponent(UserPageComponent); - instance = fixture.componentInstance; - }); - - it('should compile', () => { - fixture.detectChanges(); - - expect(fixture).toBeDefined(); - }); -}); diff --git a/ui/src/app/user/user.component.ts b/ui/src/app/user/user.component.ts deleted file mode 100644 index 1c571d4b0..000000000 --- a/ui/src/app/user/user.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; - -@Component({ - selector: 'user-page', - changeDetection: ChangeDetectionStrategy.OnPush, - templateUrl: './user.component.html', - styleUrls: [] -}) -export class UserPageComponent { - - constructor() {} -} diff --git a/ui/src/app/user/user.module.ts b/ui/src/app/user/user.module.ts deleted file mode 100644 index 7d83b289c..000000000 --- a/ui/src/app/user/user.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { NgModule } from '@angular/core'; - -import { UserRoutingModule } from './user.routing'; -import { I18nModule } from '../i18n/i18n.module'; -import { CustomWidgetRegistry } from '../schema-form/registry'; -import { WidgetRegistry } from 'ngx-schema-form'; -import { UserPageComponent } from './user.component'; -import { UserAdminModule } from './admin/admin.module'; -import { CommonModule } from '@angular/common'; - - -@NgModule({ - imports: [ - UserRoutingModule, - UserAdminModule.forRoot(), - CommonModule, - I18nModule - ], - providers: [ - { provide: WidgetRegistry, useClass: CustomWidgetRegistry } - ], - declarations: [ - UserPageComponent - ] -}) -export class UserModule { } diff --git a/ui/src/app/user/user.routing.ts b/ui/src/app/user/user.routing.ts deleted file mode 100644 index 431d0dddd..000000000 --- a/ui/src/app/user/user.routing.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { UserPageComponent } from './user.component'; - - -const routes: Routes = [ - { - path: '', - component: UserPageComponent, - children: [] - }, -]; - -@NgModule({ - imports: [ - RouterModule.forChild(routes), - ], - exports: [RouterModule] -}) -export class UserRoutingModule { }
UserId