diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy index a137526a4..8db64fd67 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver @@ -8,6 +9,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResol import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataQueryProtocolScheme import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ReloadableMetadataResolverAttributes +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User @@ -28,11 +30,13 @@ class DevConfig { private final RoleRepository roleRepository private final MetadataResolverRepository metadataResolverRepository + private final EntityDescriptorRepository entityDescriptorRepository - DevConfig(UserRepository adminUserRepository, MetadataResolverRepository metadataResolverRepository, RoleRepository roleRepository) { + DevConfig(UserRepository adminUserRepository, MetadataResolverRepository metadataResolverRepository, RoleRepository roleRepository, EntityDescriptorRepository entityDescriptorRepository) { this.adminUserRepository = adminUserRepository this.metadataResolverRepository = metadataResolverRepository this.roleRepository = roleRepository + this.entityDescriptorRepository = entityDescriptorRepository } @Transactional @@ -139,4 +143,17 @@ class DevConfig { return it }) } + + @Profile('ed') + @Transactional + @Bean + EntityDescriptor ed() { + return this.entityDescriptorRepository.save(new EntityDescriptor().with { + it.createdBy = 'nonadmin' + it.entityID = 'testID' + it.serviceEnabled = true + it.serviceProviderName = 'testSP' + it + }) + } } diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy index e77097af6..9151e4ef4 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy @@ -55,11 +55,6 @@ class RelyingPartyOverridesJsonSchemaValidatingControllerAdvice extends RequestB ] as HttpInputMessage } - @ExceptionHandler(JsonSchemaValidationFailedException) - final ResponseEntity handleJsonSchemaValidationFailedException(JsonSchemaValidationFailedException ex) { - ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse("400", String.join('\n', ex.errors))) - } - @PostConstruct void init() { this.jsonSchemaLocation = metadataSourcesSchema(this.jsonSchemaResourceLocationRegistry); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index 03cdf92b5..adcabd2f0 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -17,7 +17,9 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -30,6 +32,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import javax.annotation.PostConstruct; +import javax.xml.ws.Response; import java.net.URI; import java.util.stream.Collectors; @@ -199,6 +202,28 @@ public ResponseEntity getOneXml(@PathVariable String resourceId) throws Marsh } } + @Transactional + @GetMapping(value = "/EntityDescriptor/disabledNonAdmin") + public Iterable getDisabledAndNotOwnedByAdmin() { + return entityDescriptorRepository.findAllDisabledAndNotOwnedByAdmin() + .map(ed -> entityDescriptorService.createRepresentationFromDescriptor(ed)) + .collect(Collectors.toList()); + } + + @Secured("ROLE_ADMIN") + @DeleteMapping(value = "/EntityDescriptor/{resourceId}") + public ResponseEntity deleteOne(@PathVariable String resourceId) { + EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); + if (ed == null) { + return ResponseEntity.notFound().build(); + } else if (ed.isServiceEnabled()) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, "Deleting an enabled Metadata Source is not allowed. Disable the source and try again.")); + } else { + entityDescriptorRepository.delete(ed); + return ResponseEntity.noContent().build(); + } + } + private static URI getResourceUriFor(EntityDescriptor ed) { return ServletUriComponentsBuilder .fromCurrentServletMapping().path("/api/EntityDescriptor") diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java index d918594db..ebaa4da08 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java @@ -3,6 +3,7 @@ import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse; import edu.internet2.tier.shibboleth.admin.ui.domain.exceptions.MetadataFileNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaValidationFailedException; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; @@ -12,8 +13,8 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.client.HttpClientErrorException; -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.NOT_FOUND; /** @@ -56,4 +57,9 @@ public final ResponseEntity metadataFileNotFoundHandler(MetadataF ErrorResponse errorResponse = new ErrorResponse(INTERNAL_SERVER_ERROR.toString(), ex.getLocalizedMessage()); return new ResponseEntity<>(errorResponse, INTERNAL_SERVER_ERROR); } + + @ExceptionHandler(JsonSchemaValidationFailedException.class) + public final ResponseEntity handleJsonSchemaValidationFailedException(JsonSchemaValidationFailedException ex) { + return ResponseEntity.status(BAD_REQUEST).body(new ErrorResponse("400", String.join("\n", ex.getErrors()))); + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java index eac2a6d2f..fac8d91fb 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java @@ -1,5 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.jsonschema; +import lombok.Getter; + import java.util.List; /** @@ -8,11 +10,12 @@ * * @author Dmitriy Kopylenko */ -class JsonSchemaValidationFailedException extends RuntimeException { +@Getter +public class JsonSchemaValidationFailedException extends RuntimeException { List errors; - JsonSchemaValidationFailedException(List errors) { - this.errors = errors; + JsonSchemaValidationFailedException(List errors) { + this.errors = (List) errors; } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java index d87bf1367..8a6596594 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java @@ -1,8 +1,8 @@ package edu.internet2.tier.shibboleth.admin.ui.repository; import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import java.util.stream.Stream; @@ -10,7 +10,7 @@ /** * Repository to manage {@link EntityDescriptor} instances. */ -public interface EntityDescriptorRepository extends CrudRepository { +public interface EntityDescriptorRepository extends JpaRepository { EntityDescriptor findByEntityID(String entityId); @@ -21,5 +21,9 @@ public interface EntityDescriptorRepository extends CrudRepository findAllStreamByCustomQuery(); + @Query("select e from EntityDescriptor e, User u join u.roles r " + + "where e.createdBy = u.username and e.serviceEnabled = false and r.name in ('ROLE_USER', 'ROLE_NONE')") + Stream findAllDisabledAndNotOwnedByAdmin(); + Stream findAllStreamByCreatedBy(String createdBy); } diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 5ad8870f7..dfa101c41 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -387,6 +387,10 @@ label.role=Role label.delete=Delete? label.delete-request=Delete Request +label.enable=Enable +label.disable=Disable +label.enable-metadata-sources=Enable Metadata Sources + 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/src/app/admin/action/collection.action.ts b/ui/src/app/admin/action/admin-collection.action.ts similarity index 94% rename from ui/src/app/admin/action/collection.action.ts rename to ui/src/app/admin/action/admin-collection.action.ts index 8c9c3e420..96c2ae937 100644 --- a/ui/src/app/admin/action/collection.action.ts +++ b/ui/src/app/admin/action/admin-collection.action.ts @@ -11,7 +11,6 @@ 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', @@ -52,12 +51,6 @@ 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; @@ -133,7 +126,6 @@ export type AdminCollectionActionsUnion = | LoadAdminRequest | LoadAdminSuccess | LoadAdminError - | LoadNewUsersRequest | AddAdminRequest | AddAdminSuccess | AddAdminFail diff --git a/ui/src/app/admin/action/metadata-collection.action.ts b/ui/src/app/admin/action/metadata-collection.action.ts new file mode 100644 index 000000000..01795d49b --- /dev/null +++ b/ui/src/app/admin/action/metadata-collection.action.ts @@ -0,0 +1,90 @@ +import { Action } from '@ngrx/store'; +import { MetadataResolver } from '../../metadata/domain/model'; +import { Update } from '@ngrx/entity'; + +export enum MetadataCollectionActionTypes { + UPDATE_METADATA_REQUEST = '[Admin Metadata Collection] Update Request', + UPDATE_METADATA_SUCCESS = '[Admin Metadata Collection] Update Success', + UPDATE_METADATA_FAIL = '[Admin Metadata Collection] Update Fail', + UPDATE_METADATA_CONFLICT = '[Admin Metadata Collection] Update Conflict', + + LOAD_METADATA_REQUEST = '[Admin Metadata Collection] Load Metadata REQUEST', + LOAD_METADATA_SUCCESS = '[Admin Metadata Collection] Load Metadata SUCCESS', + LOAD_METADATA_ERROR = '[Admin Metadata Collection] Load Metadata ERROR', + + REMOVE_METADATA = '[Admin Metadata Collection] Remove Metadata', + REMOVE_METADATA_SUCCESS = '[Admin Metadata Collection] Remove Metadata Success', + REMOVE_METADATA_FAIL = '[Admin Metadata Collection] Remove Metadata Fail', +} + +export class LoadMetadataRequest implements Action { + readonly type = MetadataCollectionActionTypes.LOAD_METADATA_REQUEST; + + constructor() { } +} + +export class LoadMetadataSuccess implements Action { + readonly type = MetadataCollectionActionTypes.LOAD_METADATA_SUCCESS; + + constructor(public payload: MetadataResolver[]) { } +} + +export class LoadMetadataError implements Action { + readonly type = MetadataCollectionActionTypes.LOAD_METADATA_ERROR; + + constructor(public payload: any) { } +} + +export class UpdateMetadataRequest implements Action { + readonly type = MetadataCollectionActionTypes.UPDATE_METADATA_REQUEST; + + constructor(public payload: MetadataResolver) { } +} + +export class UpdateMetadataSuccess implements Action { + readonly type = MetadataCollectionActionTypes.UPDATE_METADATA_SUCCESS; + + constructor(public payload: Update) { } +} + +export class UpdateMetadataFail implements Action { + readonly type = MetadataCollectionActionTypes.UPDATE_METADATA_FAIL; + + constructor(public payload: any) { } +} + +export class UpdateMetadataConflict implements Action { + readonly type = MetadataCollectionActionTypes.UPDATE_METADATA_CONFLICT; + + constructor(public payload: MetadataResolver) { } +} + +export class RemoveMetadataRequest implements Action { + readonly type = MetadataCollectionActionTypes.REMOVE_METADATA; + + constructor(public payload: MetadataResolver) { } +} + +export class RemoveMetadataSuccess implements Action { + readonly type = MetadataCollectionActionTypes.REMOVE_METADATA_SUCCESS; + + constructor(public payload: MetadataResolver) { } +} + +export class RemoveMetadataFail implements Action { + readonly type = MetadataCollectionActionTypes.REMOVE_METADATA_FAIL; + + constructor(public payload: MetadataResolver) { } +} + +export type MetadataCollectionActionsUnion = + | LoadMetadataRequest + | LoadMetadataSuccess + | LoadMetadataError + | RemoveMetadataRequest + | RemoveMetadataSuccess + | RemoveMetadataFail + | UpdateMetadataRequest + | UpdateMetadataSuccess + | UpdateMetadataFail + | UpdateMetadataConflict; diff --git a/ui/src/app/admin/admin.component.spec.ts b/ui/src/app/admin/admin.component.spec.ts index 4cb5cb3e0..909e20c03 100644 --- a/ui/src/app/admin/admin.component.spec.ts +++ b/ui/src/app/admin/admin.component.spec.ts @@ -1,21 +1,30 @@ import { TestBed, ComponentFixture } from '@angular/core/testing'; import { AdminComponent } from './admin.component'; import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; +import * as fromAdmin from './reducer'; describe('Admin Root Component', () => { let fixture: ComponentFixture; let instance: AdminComponent; + let store: Store; beforeEach(() => { TestBed.configureTestingModule({ imports: [ - RouterTestingModule + RouterTestingModule, + StoreModule.forRoot({ + admin: combineReducers(fromAdmin.reducers) + }) ], declarations: [ AdminComponent ], }); + store = TestBed.get(Store); + spyOn(store, 'dispatch'); + fixture = TestBed.createComponent(AdminComponent); instance = fixture.componentInstance; }); @@ -24,5 +33,6 @@ describe('Admin Root Component', () => { fixture.detectChanges(); expect(fixture).toBeDefined(); + expect(store.dispatch).toHaveBeenCalled(); }); }); diff --git a/ui/src/app/admin/admin.component.ts b/ui/src/app/admin/admin.component.ts index b3314e7ec..f4670889d 100644 --- a/ui/src/app/admin/admin.component.ts +++ b/ui/src/app/admin/admin.component.ts @@ -1,10 +1,18 @@ import { Component } from '@angular/core'; +import * as fromRoot from '../app.reducer'; +import { Store } from '@ngrx/store'; +import { LoadAdminRequest } from './action/admin-collection.action'; + @Component({ selector: 'admin-page', templateUrl: './admin.component.html', styleUrls: [] }) export class AdminComponent { - constructor() { } + constructor( + private store: Store + ) { + this.store.dispatch(new LoadAdminRequest()); + } } diff --git a/ui/src/app/admin/admin.module.ts b/ui/src/app/admin/admin.module.ts index 476575d1b..fd25105a4 100644 --- a/ui/src/app/admin/admin.module.ts +++ b/ui/src/app/admin/admin.module.ts @@ -11,13 +11,16 @@ import { AdminManagementPageComponent } from './container/admin-management.compo import { AdminComponent } from './admin.component'; import { reducers } from './reducer'; import { AdminService } from './service/admin.service'; -import { AdminCollectionEffects } from './effect/collection.effect'; +import { AdminCollectionEffects } from './effect/admin-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'; +import { EnableMetadataComponent } from './component/enable-metadata.component'; +import { ManagerModule } from '../metadata/manager/manager.module'; +import { MetadataCollectionEffects } from './effect/metadata-collection.effect'; @NgModule({ declarations: [ @@ -26,7 +29,8 @@ import { UserManagementComponent } from './component/user-management.component'; DeleteUserDialogComponent, UserManagementComponent, ActionRequiredPageComponent, - AccessRequestComponent + AccessRequestComponent, + EnableMetadataComponent ], entryComponents: [ DeleteUserDialogComponent @@ -35,13 +39,14 @@ import { UserManagementComponent } from './component/user-management.component'; CommonModule, I18nModule, StoreModule.forFeature('admin', reducers), - EffectsModule.forFeature([AdminCollectionEffects]), + EffectsModule.forFeature([AdminCollectionEffects, MetadataCollectionEffects]), FormsModule, RouterModule, HttpClientModule, SharedModule, I18nModule, - NgbModalModule + NgbModalModule, + ManagerModule ], providers: [ AdminService diff --git a/ui/src/app/admin/component/access-request.component.html b/ui/src/app/admin/component/access-request.component.html index a9b96b586..cab5ab0b6 100644 --- a/ui/src/app/admin/component/access-request.component.html +++ b/ui/src/app/admin/component/access-request.component.html @@ -1,68 +1,53 @@ -
-
-
-
-
- User Access Request -
-
+ + +
+
+

There are no new user requests at this time.

- -
-
-

There are no new user requests at this time.

-
-
-
- -
-
-
-
-
-
- UserId -
-
{{ user.username }}
-
- Email -
-
{{ user.emailAddress }}
-
-
-
-
- Name -
-
{{ user.firstName }} {{ user.lastName }}
- -
- -
-
+
+ + +
+
+
+
+
+
+ UserId
-
- +
{{ user.username }}
+
+ Email +
+
{{ user.emailAddress }}
+
+
+
+
+ Name +
+
{{ user.firstName }} {{ user.lastName }}
+ +
+
+
+ +
- +
-
\ No newline at end of file + \ No newline at end of file diff --git a/ui/src/app/admin/component/enable-metadata.component.html b/ui/src/app/admin/component/enable-metadata.component.html new file mode 100644 index 000000000..cba54f177 --- /dev/null +++ b/ui/src/app/admin/component/enable-metadata.component.html @@ -0,0 +1,17 @@ +
    +
  • + + +
  • +
\ No newline at end of file diff --git a/ui/src/app/admin/component/enable-metadata.component.ts b/ui/src/app/admin/component/enable-metadata.component.ts new file mode 100644 index 000000000..3c463eb9d --- /dev/null +++ b/ui/src/app/admin/component/enable-metadata.component.ts @@ -0,0 +1,82 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { MetadataEntity, MetadataResolver } from '../../metadata/domain/model'; +import * as fromDashboard from '../../metadata/manager/reducer'; +import * as fromMetadata from '../reducer'; +import { ToggleEntityDisplay } from '../../metadata/manager/action/manager.action'; +import { DeleteDialogComponent } from '../../metadata/manager/component/delete-dialog.component'; +import { PreviewEntity } from '../../metadata/domain/action/entity.action'; +import { FileBackedHttpMetadataResolver } from '../../metadata/domain/entity'; +import { RemoveMetadataRequest, UpdateMetadataRequest, LoadMetadataRequest } from '../action/metadata-collection.action'; + +@Component({ + selector: 'enable-metadata', + templateUrl: './enable-metadata.component.html' +}) + +export class EnableMetadataComponent implements OnInit { + resolvers$: Observable; + loading$: Observable; + + total$: Observable; + page = 1; + limit = 8; + + entitiesOpen$: Observable<{ [key: string]: boolean }>; + + constructor( + private store: Store, + private router: Router, + private modalService: NgbModal + ) { + this.store.dispatch(new LoadMetadataRequest()); + + this.resolvers$ = this.store + .select(fromMetadata.getMetadataCollection) + .pipe( + map(resolvers => resolvers.map(r => new FileBackedHttpMetadataResolver(r))) + ); + this.loading$ = this.store.select(fromDashboard.getSearchLoading); + this.entitiesOpen$ = this.store.select(fromDashboard.getOpenProviders); + + this.total$ = this.resolvers$.pipe(map(list => list.length)); + } + + ngOnInit(): void {} + + edit(entity: MetadataEntity): void { + this.router.navigate(['metadata', 'resolver', entity.getId(), 'edit']); + } + + toggleEntity(entity: MetadataEntity): void { + this.store.dispatch(new ToggleEntityDisplay(entity.getId())); + } + + openPreviewDialog(entity: MetadataEntity): void { + this.store.dispatch(new PreviewEntity({ id: entity.getId(), entity })); + } + + toggleResolverEnabled(entity: MetadataResolver, enabled: boolean): void { + let update = { ...entity, serviceEnabled: enabled }; + this.store.dispatch(new UpdateMetadataRequest(update)); + } + + deleteResolver(entity: MetadataResolver): void { + this.modalService + .open(DeleteDialogComponent) + .result + .then( + success => { + this.store.dispatch(new RemoveMetadataRequest(entity)); + }, + err => { + console.log('Cancelled'); + } + ); + } +} diff --git a/ui/src/app/admin/component/user-management.component.html b/ui/src/app/admin/component/user-management.component.html index 900e0dd5b..683f15447 100644 --- a/ui/src/app/admin/component/user-management.component.html +++ b/ui/src/app/admin/component/user-management.component.html @@ -1,49 +1,34 @@ -
-
-
-
-
- User Maintenance -
-
-
-
-
-

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

-
- - - - - - - - - - - - - - - - - - - -
UserIdNameEmailRoleDelete?
{{ user.username }}{{ user.firstName }} {{ user.lastName }}{{ user.emailAddress }} - - - -
-
-
-
\ No newline at end of file +
+

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

+
+ + + + + + + + + + + + + + + + + + + +
UserIdNameEmailRoleDelete?
{{ user.username }}{{ user.firstName }} {{ user.lastName }}{{ user.emailAddress }} + + + +
\ No newline at end of file diff --git a/ui/src/app/admin/component/user-management.component.ts b/ui/src/app/admin/component/user-management.component.ts index 79aaacc78..40c0cb8d7 100644 --- a/ui/src/app/admin/component/user-management.component.ts +++ b/ui/src/app/admin/component/user-management.component.ts @@ -6,7 +6,7 @@ import * as fromRoot from '../../app.reducer'; import * as fromCore from '../../core/reducer'; import * as fromAdmin from '../reducer'; -import { UpdateAdminRequest, RemoveAdminRequest } from '../action/collection.action'; +import { UpdateAdminRequest, RemoveAdminRequest } from '../action/admin-collection.action'; import { Admin } from '../model/admin'; import { DeleteUserDialogComponent } from '../component/delete-user-dialog.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -35,7 +35,7 @@ export class UserManagementComponent implements OnInit { } ngOnInit(): void { - this.users$ = this.store.select(fromAdmin.getAllConfiguredUsers); + this.users$ = this.store.select(fromAdmin.getAllConfiguredAdmins); this.hasUsers$ = this.users$.pipe(map(userList => userList.length > 0)); this.users$ = this.store.select(fromAdmin.getAllAdmins); let user$ = this.store.select(fromCore.getUser); diff --git a/ui/src/app/admin/container/action-required.component.html b/ui/src/app/admin/container/action-required.component.html index 4bb706337..0d5c70823 100644 --- a/ui/src/app/admin/container/action-required.component.html +++ b/ui/src/app/admin/container/action-required.component.html @@ -1 +1,27 @@ - \ No newline at end of file +
+
+
+
+
+ Enable Metadata Sources +
+
+
+
+ +
+
+
+
+
+
+
+
+ User Access Request +
+
+
+ +
+
+ diff --git a/ui/src/app/admin/container/action-required.component.ts b/ui/src/app/admin/container/action-required.component.ts index e920c81e1..f504f36e4 100644 --- a/ui/src/app/admin/container/action-required.component.ts +++ b/ui/src/app/admin/container/action-required.component.ts @@ -3,8 +3,6 @@ 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, @@ -15,7 +13,5 @@ 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 index 77864802b..8a07130d2 100644 --- a/ui/src/app/admin/container/admin-management.component.html +++ b/ui/src/app/admin/container/admin-management.component.html @@ -1 +1,15 @@ - \ No newline at end of file +
+
+
+
+
+ User Maintenance +
+
+
+
+ +
+
+
+ diff --git a/ui/src/app/admin/container/admin-management.component.ts b/ui/src/app/admin/container/admin-management.component.ts index e9dd5ff68..fa657d5b5 100644 --- a/ui/src/app/admin/container/admin-management.component.ts +++ b/ui/src/app/admin/container/admin-management.component.ts @@ -2,8 +2,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; - -import { LoadAdminRequest } from '../action/collection.action'; +import { LoadAdminRequest } from '../action/admin-collection.action'; @Component({ selector: 'admin-management-page', @@ -15,7 +14,5 @@ export class AdminManagementPageComponent { constructor( private store: Store - ) { - this.store.dispatch(new LoadAdminRequest()); - } + ) {} } diff --git a/ui/src/app/admin/effect/collection.effect.ts b/ui/src/app/admin/effect/admin-collection.effect.ts similarity index 88% rename from ui/src/app/admin/effect/collection.effect.ts rename to ui/src/app/admin/effect/admin-collection.effect.ts index 84d2aa3f2..36a71884a 100644 --- a/ui/src/app/admin/effect/collection.effect.ts +++ b/ui/src/app/admin/effect/admin-collection.effect.ts @@ -11,9 +11,8 @@ import { UpdateAdminRequest, UpdateAdminSuccess, RemoveAdminRequest, - RemoveAdminSuccess, - LoadNewUsersRequest -} from '../action/collection.action'; + RemoveAdminSuccess +} from '../action/admin-collection.action'; import { AdminService } from '../service/admin.service'; import { AddNotification } from '../../notification/action/notification.action'; import { Notification, NotificationType } from '../../notification/model/notification'; @@ -31,14 +30,6 @@ 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), diff --git a/ui/src/app/admin/effect/metadata-collection.effect.ts b/ui/src/app/admin/effect/metadata-collection.effect.ts new file mode 100644 index 000000000..bb9604916 --- /dev/null +++ b/ui/src/app/admin/effect/metadata-collection.effect.ts @@ -0,0 +1,128 @@ +import { Injectable } from '@angular/core'; +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { of } from 'rxjs'; +import { map, catchError, switchMap, tap, withLatestFrom } from 'rxjs/operators'; + +import { + MetadataCollectionActionTypes, + LoadMetadataRequest, + LoadMetadataSuccess, + LoadMetadataError, + RemoveMetadataRequest, + RemoveMetadataSuccess, + RemoveMetadataFail, + UpdateMetadataRequest, + UpdateMetadataSuccess, + UpdateMetadataFail, + UpdateMetadataConflict +} from '../action/metadata-collection.action'; +import { ResolverService } from '../../metadata/domain/service/resolver.service'; +import { removeNulls } from '../../shared/util'; +import { AddNotification } from '../../notification/action/notification.action'; +import { Notification, NotificationType } from '../../notification/model/notification'; +import { I18nService } from '../../i18n/service/i18n.service'; +import * as fromRoot from '../../app.reducer'; +import * as fromI18n from '../../i18n/reducer'; + + +/* istanbul ignore next */ +@Injectable() +export class MetadataCollectionEffects { + + @Effect() + loadMetadatas$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.LOAD_METADATA_REQUEST), + switchMap(() => + this.descriptorService + .queryForAdmin() + .pipe( + map(descriptors => new LoadMetadataSuccess(descriptors)), + catchError(error => of(new LoadMetadataError(error))) + ) + ) + ); + + @Effect() + updateMetadata$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.UPDATE_METADATA_REQUEST), + map(action => action.payload), + switchMap(provider => { + return this.descriptorService + .update(removeNulls(provider)) + .pipe( + map(p => new UpdateMetadataSuccess({ + id: p.id, + changes: p + })), + catchError(err => { + if (err.status === 409) { + return of(new UpdateMetadataConflict(provider)); + } + return of(new UpdateMetadataFail({ + errorCode: err.status, + errorMessage: `${err.statusText} - ${err.message}` + })); + }) + ); + }) + ); + + @Effect() + removeMetadataSuccessReload$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.REMOVE_METADATA_SUCCESS), + map(action => action.payload), + map(provider => new LoadMetadataRequest()) + ); + + @Effect() + updateMetadataSuccessNotification$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.UPDATE_METADATA_SUCCESS), + map(action => action.payload), + withLatestFrom(this.store.select(fromI18n.getMessages)), + map(([error, messages]) => new AddNotification( + new Notification( + NotificationType.Success, + `Metadata Source has been enabled`, + 8000 + ) + )) + ); + + @Effect() + updateMetadataFailNotification$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.UPDATE_METADATA_FAIL), + map(action => action.payload), + withLatestFrom(this.store.select(fromI18n.getMessages)), + map(([error, messages]) => new AddNotification( + new Notification( + NotificationType.Danger, + `${error.errorCode}: ${this.i18nService.translate(error.errorMessage, null, messages)}`, + 8000 + ) + )) + ); + + @Effect() + removeMetadata$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.REMOVE_METADATA), + map(action => action.payload), + switchMap(entity => + this.descriptorService + .remove(entity) + .pipe( + map(p => new RemoveMetadataSuccess(entity)), + catchError(err => of(new RemoveMetadataFail(err))) + ) + ) + ); + + constructor( + private descriptorService: ResolverService, + private actions$: Actions, + private router: Router, + private store: Store, + private i18nService: I18nService + ) { } +} /* istanbul ignore next */ diff --git a/ui/src/app/admin/reducer/collection.reducer.spec.ts b/ui/src/app/admin/reducer/admin-collection.reducer.spec.ts similarity index 93% rename from ui/src/app/admin/reducer/collection.reducer.spec.ts rename to ui/src/app/admin/reducer/admin-collection.reducer.spec.ts index ac0349889..4c26df392 100644 --- a/ui/src/app/admin/reducer/collection.reducer.spec.ts +++ b/ui/src/app/admin/reducer/admin-collection.reducer.spec.ts @@ -1,11 +1,11 @@ -import { reducer, initialState as snapshot } from './collection.reducer'; -import * as fromAdmin from './collection.reducer'; +import { reducer, initialState as snapshot } from './admin-collection.reducer'; +import * as fromAdmin from './admin-collection.reducer'; import { AdminCollectionActionTypes, LoadAdminSuccess, UpdateAdminSuccess, RemoveAdminSuccess -} from '../action/collection.action'; +} from '../action/admin-collection.action'; import { Admin } from '../model/admin'; let users = [ diff --git a/ui/src/app/admin/reducer/collection.reducer.ts b/ui/src/app/admin/reducer/admin-collection.reducer.ts similarity index 97% rename from ui/src/app/admin/reducer/collection.reducer.ts rename to ui/src/app/admin/reducer/admin-collection.reducer.ts index 70d8ba9a4..ce404d57d 100644 --- a/ui/src/app/admin/reducer/collection.reducer.ts +++ b/ui/src/app/admin/reducer/admin-collection.reducer.ts @@ -1,6 +1,6 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import { Admin } from '../model/admin'; -import { AdminCollectionActionsUnion, AdminCollectionActionTypes } from '../action/collection.action'; +import { AdminCollectionActionsUnion, AdminCollectionActionTypes } from '../action/admin-collection.action'; export interface CollectionState extends EntityState { selectedAdminId: string | null; diff --git a/ui/src/app/admin/reducer/index.spec.ts b/ui/src/app/admin/reducer/index.spec.ts new file mode 100644 index 000000000..f93b0557e --- /dev/null +++ b/ui/src/app/admin/reducer/index.spec.ts @@ -0,0 +1,40 @@ +import * as fromIndex from './'; +import { MetadataResolver } from '../../metadata/domain/model'; +import { User } from '../../core/model/user'; + +// export const totalUserFn = (users) => users.length; +// export const totalMetadataFn = (md) => md.filter(obj => !obj.serviceEnabled).length; +// export const totalActionsFn = (users, md) => md + users; + +let resolvers: MetadataResolver[] = [ + { id: '1', entityId: 'foo', serviceEnabled: true, serviceProviderName: 'bar', createdDate: 'Date' } as MetadataResolver, + { id: '2', entityId: 'baz', serviceEnabled: false, serviceProviderName: 'fin', createdDate: 'Date' } as MetadataResolver +]; + +let users: User[] = [ + { + username: 'foo', + role: 'admin', + firstName: 'foo', + lastName: 'bar', + emailAddress: 'foo@bar.com' + } +]; + +describe('admin dashboard state selectors', () => { + describe('totalUserFn', () => { + it('should get the length of the provided array', () => { + expect(fromIndex.totalUserFn(users)).toBe(1); + }); + }); + describe('totalMetadataFn', () => { + it('should get the length of the provided list after filtering enabled resolvers', () => { + expect(fromIndex.totalMetadataFn(resolvers)).toBe(1); + }); + }); + describe('totalActionsFn', () => { + it('should return the sum of the total users and metadata', () => { + expect(fromIndex.totalActionsFn(1, 2)).toBe(3); + }); + }); +}); diff --git a/ui/src/app/admin/reducer/index.ts b/ui/src/app/admin/reducer/index.ts index 31a5265ea..651870b76 100644 --- a/ui/src/app/admin/reducer/index.ts +++ b/ui/src/app/admin/reducer/index.ts @@ -1,37 +1,61 @@ import { createSelector, createFeatureSelector } from '@ngrx/store'; import * as fromRoot from '../../core/reducer'; -import * as fromCollection from './collection.reducer'; +import * as fromAdminCollection from './admin-collection.reducer'; +import * as fromMetadataCollection from './metadata-collection.reducer'; +import { getInCollectionFn } from '../../metadata/domain/domain.util'; export interface AdminState { - collection: fromCollection.CollectionState; + admins: fromAdminCollection.CollectionState; + metadata: fromMetadataCollection.CollectionState; } export const reducers = { - collection: fromCollection.reducer + admins: fromAdminCollection.reducer, + metadata: fromMetadataCollection.reducer }; export interface State extends fromRoot.State { 'admin': AdminState; } -export const getCollectionFromStateFn = (state: AdminState) => state.collection; +export const getAdminsCollectionFromStateFn = (state: AdminState) => state.admins; +export const getMetadataCollectionFromStateFn = (state: AdminState) => state.metadata; -export const getAdminState = createFeatureSelector('admin'); +export const getFeatureState = createFeatureSelector('admin'); /* * Select pieces of Admin Collection */ -export const getCollectionState = createSelector(getAdminState, getCollectionFromStateFn); -export const getAllAdmins = createSelector(getCollectionState, fromCollection.selectAllAdmins); -export const getCollectionSaving = createSelector(getCollectionState, fromCollection.getIsSaving); +export const getAdminCollectionState = createSelector(getFeatureState, getAdminsCollectionFromStateFn); +export const getAllAdmins = createSelector(getAdminCollectionState, fromAdminCollection.selectAllAdmins); +export const getCollectionSaving = createSelector(getAdminCollectionState, fromAdminCollection.getIsSaving); -export const getAdminEntities = createSelector(getCollectionState, fromCollection.selectAdminEntities); -export const getSelectedAdminId = createSelector(getCollectionState, fromCollection.getSelectedAdminId); +export const getAdminEntities = createSelector(getAdminCollectionState, fromAdminCollection.selectAdminEntities); +export const getSelectedAdminId = createSelector(getAdminCollectionState, fromAdminCollection.getSelectedAdminId); export const getSelectedAdmin = createSelector(getAdminEntities, getSelectedAdminId, (entities, selectedId) => { return selectedId && entities[selectedId]; }); -export const getAdminIds = createSelector(getCollectionState, fromCollection.selectAdminIds); +export const getAdminIds = createSelector(getAdminCollectionState, fromAdminCollection.selectAdminIds); +export const getAllConfiguredAdmins = createSelector(getAllAdmins, (admins) => admins.filter(a => a.role !== 'ROLE_NONE')); 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); +/* + * Select pieces of Metadata Collection +*/ +export const getMetadataCollectionState = createSelector(getFeatureState, getMetadataCollectionFromStateFn); + +export const getMetadataEntities = createSelector(getMetadataCollectionState, fromMetadataCollection.selectMetadataEntities); +export const getSelectedMetadataId = createSelector(getMetadataCollectionState, fromMetadataCollection.getSelectedMetadataId); +export const getMetadataIds = createSelector(getMetadataCollectionState, fromMetadataCollection.selectMetadataIds); + +export const getMetadataCollection = createSelector(getMetadataCollectionState, getMetadataIds, fromMetadataCollection.selectAllMetadata); +export const getSelectedMetadata = createSelector(getMetadataEntities, getSelectedMetadataId, getInCollectionFn); + +export const totalUserFn = (users) => users.length; +export const totalMetadataFn = (md) => md.filter(obj => !obj.serviceEnabled).length; +export const totalActionsFn = (users, md) => md + users; + +export const getTotalNewUsers = createSelector(getAllNewUsers, totalUserFn); +export const getTotalNewMetadata = createSelector(getMetadataCollection, totalMetadataFn); + +export const getTotalActionsRequired = createSelector(getTotalNewUsers, getTotalNewMetadata, totalActionsFn); diff --git a/ui/src/app/admin/reducer/metadata-collection.reducer.spec.ts b/ui/src/app/admin/reducer/metadata-collection.reducer.spec.ts new file mode 100644 index 000000000..a1b5b0c70 --- /dev/null +++ b/ui/src/app/admin/reducer/metadata-collection.reducer.spec.ts @@ -0,0 +1,72 @@ +import { reducer } from './metadata-collection.reducer'; +import * as fromCollection from './metadata-collection.reducer'; +import * as resolverActions from '../action/metadata-collection.action'; +import { MetadataResolver } from '../../metadata/domain/model'; + +let resolvers: MetadataResolver[] = [ + { id: '1', entityId: 'foo', serviceProviderName: 'bar', createdDate: 'Tue Apr 17 2018 13:33:54 GMT-0700 (MST)' } as MetadataResolver, + { id: '2', entityId: 'baz', serviceProviderName: 'fin', createdDate: 'Tue Apr 17 2018 13:34:07 GMT-0700 (MST)' } as MetadataResolver +], +snapshot: fromCollection.CollectionState = { + ids: [resolvers[0].id, resolvers[1].id], + entities: { + [resolvers[0].id]: resolvers[0], + [resolvers[1].id]: resolvers[1] + }, + selectedMetadataId: null +}; + +describe('Resolver Reducer', () => { + const initialState: fromCollection.CollectionState = { + ids: [], + entities: {}, + selectedMetadataId: null, + }; + + describe('undefined action', () => { + it('should return the default state', () => { + const result = reducer(undefined, {} as any); + + expect(result).toEqual(initialState); + }); + }); + + describe('Load Providers: Success', () => { + it('should add the loaded resolvers to the collection', () => { + const action = new resolverActions.LoadMetadataSuccess(resolvers); + const result = reducer(initialState, action); + + expect(result).toEqual( + Object.assign({}, initialState, snapshot) + ); + }); + }); + + describe('Update Providers: Success', () => { + it('should update the draft of the specified id', () => { + let changes = { ...resolvers[1], serviceEnabled: true }, + expected = { + ids: [resolvers[0].id, resolvers[1].id], + entities: { + [resolvers[0].id]: resolvers[0], + [resolvers[1].id]: changes + }, + selectedMetadataId: null + }; + const action = new resolverActions.UpdateMetadataSuccess({ id: changes.id, changes }); + const result = reducer({ ...snapshot }, action); + + expect(result).toEqual( + Object.assign({}, initialState, expected) + ); + }); + + it('should return state if the entityId is not found', () => { + let changes = { ...resolvers[1], serviceEnabled: true, id: '4' }; + const action = new resolverActions.UpdateMetadataSuccess({ id: changes.id, changes }); + const result = reducer({ ...snapshot }, action); + + expect(result).toEqual(snapshot); + }); + }); +}); diff --git a/ui/src/app/admin/reducer/metadata-collection.reducer.ts b/ui/src/app/admin/reducer/metadata-collection.reducer.ts new file mode 100644 index 000000000..dc4006841 --- /dev/null +++ b/ui/src/app/admin/reducer/metadata-collection.reducer.ts @@ -0,0 +1,59 @@ +import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; +import { MetadataResolver } from '../../metadata/domain/model'; +import { MetadataCollectionActionsUnion, MetadataCollectionActionTypes } from '../action/metadata-collection.action'; + +export interface CollectionState extends EntityState { + selectedMetadataId: string | null; +} + +export function sortByDate(a: MetadataResolver, b: MetadataResolver): number { + return a.createdDate.localeCompare(b.createdDate); +} + +export const adapter: EntityAdapter = createEntityAdapter({ + sortComparer: sortByDate, + selectId: (model: MetadataResolver) => model.id +}); + +export const initialState: CollectionState = adapter.getInitialState({ + selectedMetadataId: null +}); + +export function reducer(state = initialState, action: MetadataCollectionActionsUnion): CollectionState { + switch (action.type) { + case MetadataCollectionActionTypes.LOAD_METADATA_SUCCESS: { + return adapter.addAll(action.payload, { + ...state, + selectedMetadataId: state.selectedMetadataId + }); + } + + case MetadataCollectionActionTypes.UPDATE_METADATA_SUCCESS: { + return adapter.updateOne(action.payload, state); + } + + case MetadataCollectionActionTypes.LOAD_METADATA_ERROR: { + return adapter.removeAll({ + ...state + }); + } + + case MetadataCollectionActionTypes.REMOVE_METADATA_SUCCESS: { + return adapter.removeOne(action.payload.id, { + ...state + }); + } + + default: { + return state; + } + } +} + +export const getSelectedMetadataId = (state: CollectionState) => state.selectedMetadataId; +export const { + selectIds: selectMetadataIds, + selectEntities: selectMetadataEntities, + selectAll: selectAllMetadata, + selectTotal: selectMetadataTotal +} = adapter.getSelectors(); diff --git a/ui/src/app/metadata/domain/service/resolver.service.ts b/ui/src/app/metadata/domain/service/resolver.service.ts index 895e086f0..2a158f75f 100644 --- a/ui/src/app/metadata/domain/service/resolver.service.ts +++ b/ui/src/app/metadata/domain/service/resolver.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { Observable, throwError } from 'rxjs'; +import { Observable, throwError, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { MetadataResolver } from '../model'; @@ -21,6 +21,13 @@ export class ResolverService { ); } + queryForAdmin(): Observable { + return this.http.get(`${this.base}${this.endpoint}/disabledNonAdmin`, {}) + .pipe( + catchError(err => throwError([])) + ); + } + find(id: string): Observable { return this.http.get(`${ this.base }${ this.endpoint }/${ id }`) .pipe( diff --git a/ui/src/app/metadata/manager/component/entity-item.component.ts b/ui/src/app/metadata/manager/component/entity-item.component.ts index b2453c474..f9d1745c3 100644 --- a/ui/src/app/metadata/manager/component/entity-item.component.ts +++ b/ui/src/app/metadata/manager/component/entity-item.component.ts @@ -12,6 +12,9 @@ import { MetadataTypes } from '../../domain/domain.type'; export class EntityItemComponent { types = MetadataTypes; @Input() isOpen: boolean; + @Input() allowDelete: boolean; + @Input() showAdminFunctions: boolean; + @Output() toggleEnabled = new EventEmitter(); @Output() select = new EventEmitter(); @Output() toggle = new EventEmitter(); @Output() preview = new EventEmitter(); diff --git a/ui/src/app/metadata/manager/component/resolver-item.component.html b/ui/src/app/metadata/manager/component/resolver-item.component.html index ff734e4a6..0524c9d10 100644 --- a/ui/src/app/metadata/manager/component/resolver-item.component.html +++ b/ui/src/app/metadata/manager/component/resolver-item.component.html @@ -1,7 +1,7 @@
-
+
  @@ -21,11 +21,27 @@ {{ entity.getDisplayId() }}
-
- + -
@@ -66,7 +82,7 @@
-