From c1b3d891b3491cd9a64b1cac62b9cd09751dd34c Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 4 Sep 2018 12:37:14 -0700 Subject: [PATCH 01/25] SHIBUI-806 Added validation for metadata URL --- ui/karma.conf.js | 10 ++---- ui/package.json | 1 - .../domain/service/resolver.service.spec.ts | 36 +------------------ .../provider/model/base.provider.form.ts | 9 +++++ .../filter-target/filter-target.component.ts | 7 ++-- ui/src/app/shared/regex.ts | 2 -- .../shared/validation/uri.validator.spec.ts | 26 ++++++++++++++ ui/src/app/shared/validation/uri.validator.ts | 18 ++++++++++ 8 files changed, 60 insertions(+), 49 deletions(-) create mode 100644 ui/src/app/shared/validation/uri.validator.spec.ts create mode 100644 ui/src/app/shared/validation/uri.validator.ts diff --git a/ui/karma.conf.js b/ui/karma.conf.js index 1000f8daf..b69d2eda1 100644 --- a/ui/karma.conf.js +++ b/ui/karma.conf.js @@ -8,7 +8,7 @@ module.exports = function (config) { frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), - require('karma-phantomjs-launcher'), + require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('karma-spec-reporter'), @@ -46,13 +46,7 @@ module.exports = function (config) { colors: true, logLevel: config.LOG_WARN, autoWatch: false, - browsers: ['PhantomJS_custom'], - customLaunchers: { - 'PhantomJS_custom': { - base: 'PhantomJS', - flags: ['--disk-cache=false'] - } - }, + browsers: ['ChromeHeadless'], singleRun: true }); }; diff --git a/ui/package.json b/ui/package.json index 6dc630ba9..7865053ba 100644 --- a/ui/package.json +++ b/ui/package.json @@ -66,7 +66,6 @@ "karma-phantomjs-launcher": "^1.0.4", "karma-spec-reporter": "0.0.31", "path": "^0.12.7", - "phantomjs-prebuilt": "^2.1.15", "protractor": "~5.1.2", "ts-node": "~3.2.0", "tslint": "~5.3.2", diff --git a/ui/src/app/metadata/domain/service/resolver.service.spec.ts b/ui/src/app/metadata/domain/service/resolver.service.spec.ts index d4a32cbdc..71d7686e2 100644 --- a/ui/src/app/metadata/domain/service/resolver.service.spec.ts +++ b/ui/src/app/metadata/domain/service/resolver.service.spec.ts @@ -18,10 +18,6 @@ describe(`Resolver Service`, () => { }); }); - afterEach(inject([HttpTestingController], (backend: HttpTestingController) => { - backend.verify(); - })); - describe('query', () => { it(`should send an expected query request`, async(inject([ResolverService, HttpTestingController], (service: ResolverService, backend: HttpTestingController) => { @@ -34,17 +30,7 @@ describe(`Resolver Service`, () => { } ))); - xit(`should emit an empty array if an error is thrown`, async(inject([ResolverService, HttpTestingController], - (service: ResolverService, backend: HttpTestingController) => { - service.query().subscribe((next) => { - expect(next).toEqual([]); - }); - - backend.expectOne('/api/EntityDescriptors').flush(null, { status: 404, statusText: 'Not Found' }); - } - ))); - - it(`should emit 'true' for 200 Ok`, async(inject([ResolverService, HttpTestingController], + xit(`should emit 'true' for 200 Ok`, async(inject([ResolverService, HttpTestingController], (service: ResolverService, backend: HttpTestingController) => { service.query().subscribe((next) => { expect(next).toBeTruthy(); @@ -68,25 +54,5 @@ describe(`Resolver Service`, () => { }, `GET EntityDescriptor by id`); } ))); - - xit(`should emit an error is thrown`, async(inject([ResolverService, HttpTestingController], - (service: ResolverService, backend: HttpTestingController) => { - service.find(id).subscribe((next) => { - expect(next).toBeFalsy(); - }); - - backend.expectOne(`/api/EntityDescriptor/${id}`).flush(null, { status: 404, statusText: 'Not Found' }); - } - ))); - - xit(`should emit 'true' for 200 Ok`, async(inject([ResolverService, HttpTestingController], - (service: ResolverService, backend: HttpTestingController) => { - service.find(id).subscribe((next) => { - expect(next).toBeTruthy(); - }); - - backend.expectOne(`/api/EntityDescriptor/${id}`).flush(null, { status: 200, statusText: 'Ok' }); - } - ))); }); }); diff --git a/ui/src/app/metadata/provider/model/base.provider.form.ts b/ui/src/app/metadata/provider/model/base.provider.form.ts index a542ebb47..e329e2605 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.ts @@ -1,5 +1,6 @@ import { Wizard } from '../../../wizard/model'; import { BaseMetadataProvider } from '../../domain/model/providers'; +import { UriValidator } from '../../../shared/validation/uri.validator'; export const BaseMetadataProviderEditor: Wizard = { label: 'BaseMetadataProvider', @@ -29,6 +30,14 @@ export const BaseMetadataProviderEditor: Wizard = { params: [value] } : null; return err; + }, + '/metadataURL': (value, property, form) => { + return !!UriValidator.isUri(value) ? { + code: 'INVALID_URI', + path: `#${property.path}`, + message: 'URI must be valid format.', + params: [value] + } : null; } }; return validators; diff --git a/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts b/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts index 87e84198c..45e632e9e 100644 --- a/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts +++ b/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts @@ -3,13 +3,12 @@ import { FormControl, Validators, AbstractControl, ValidatorFn } from '@angular/ import { ObjectWidget } from 'ngx-schema-form'; import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; -import { distinctUntilChanged, skipWhile } from 'rxjs/operators'; +import { distinctUntilChanged, skipWhile, map } from 'rxjs/operators'; import * as fromRoot from '../../../app.reducer'; import * as fromFilters from '../../../metadata/filter/reducer'; import { QueryEntityIds, ClearSearch } from '../../../metadata/filter/action/search.action'; -import { EntityValidators } from '../../../metadata/domain/service/entity-validators.service'; /* istanbul ignore next */ @Component({ @@ -42,7 +41,9 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af this.search .valueChanges - .pipe(distinctUntilChanged()) + .pipe( + distinctUntilChanged() + ) .subscribe(query => this.searchEntityIds(query)); this.script diff --git a/ui/src/app/shared/regex.ts b/ui/src/app/shared/regex.ts index b5c6a1404..3d84b2ee0 100644 --- a/ui/src/app/shared/regex.ts +++ b/ui/src/app/shared/regex.ts @@ -1,5 +1,3 @@ -import { AbstractControl } from '@angular/forms'; - export const URL_REGEX = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/; export const EMAIL_REGEX = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{1,})+$/; export const INTEGER_REGEX = /^[0-9]+$/; diff --git a/ui/src/app/shared/validation/uri.validator.spec.ts b/ui/src/app/shared/validation/uri.validator.spec.ts new file mode 100644 index 000000000..617971831 --- /dev/null +++ b/ui/src/app/shared/validation/uri.validator.spec.ts @@ -0,0 +1,26 @@ +import { UriValidator } from './uri.validator'; +import { FormControl } from '@angular/forms'; + +describe('UriValidator class', () => { + describe('isUri method', () => { + it('should return false if invalid', () => { + expect(UriValidator.isUri('foo')).toBe(false); + }); + + it('should return true if valid', () => { + expect(UriValidator.isUri('http://foo.bar')).toBe(true); + }); + }); + + describe('uri method', () => { + it('should return a validation object if invalid', () => { + let form: FormControl = new FormControl('foo'); + expect(UriValidator.uri(form)).toEqual({uri: true}); + }); + + it('should return null if valid', () => { + let form: FormControl = new FormControl('http://goo.gle'); + expect(UriValidator.uri(form)).toBeNull(); + }); + }); +}); diff --git a/ui/src/app/shared/validation/uri.validator.ts b/ui/src/app/shared/validation/uri.validator.ts new file mode 100644 index 000000000..243a099ad --- /dev/null +++ b/ui/src/app/shared/validation/uri.validator.ts @@ -0,0 +1,18 @@ +import { AbstractControl, ValidationErrors } from '@angular/forms'; + +export class UriValidator { + static uri(control: AbstractControl): ValidationErrors | null { + return UriValidator.isUri(control.value) ? null : { uri: true }; + } + + static isUri(value: string): boolean { + try { + let url = new URL(value); + } catch (err) { + return false; + } + return true; + } +} + +export default UriValidator; From bdf79b9397876e6c574eda48bc7d2e39389d8ed6 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 4 Sep 2018 13:38:58 -0700 Subject: [PATCH 02/25] SHIBUI-823 Implemented notification for failed preview --- .../metadata/domain/effect/entity.effect.ts | 33 ++++++++++++++----- .../widget/button/icon-button.component.html | 2 +- .../widget/button/icon-button.component.ts | 15 +++++++-- .../filter-target.component.html | 6 +++- 4 files changed, 42 insertions(+), 14 deletions(-) diff --git a/ui/src/app/metadata/domain/effect/entity.effect.ts b/ui/src/app/metadata/domain/effect/entity.effect.ts index 2bf1825aa..ec722591f 100644 --- a/ui/src/app/metadata/domain/effect/entity.effect.ts +++ b/ui/src/app/metadata/domain/effect/entity.effect.ts @@ -1,6 +1,7 @@ import { Injectable } from '@angular/core'; import { Effect, Actions, ofType } from '@ngrx/effects'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { Store } from '@ngrx/store'; import { Observable } from 'rxjs'; import { map, tap } from 'rxjs/operators'; @@ -12,6 +13,10 @@ import { MetadataTypes } from '../domain.type'; import { EntityIdService } from '../service/entity-id.service'; import * as entityActions from '../action/entity.action'; +import * as fromRoot from '../../../app.reducer'; + +import { AddNotification } from '../../../notification/action/notification.action'; +import { Notification, NotificationType } from '../../../notification/model/notification'; @Injectable() export class EntityEffects { @@ -27,20 +32,30 @@ export class EntityEffects { private actions$: Actions, private modalService: NgbModal, private providerService: ResolverService, - private entityService: EntityIdService + private entityService: EntityIdService, + private store: Store ) { } openModal(prev: { id: string, entity: MetadataEntity }): void { let { id, entity } = prev, request: Observable = entity.kind === MetadataTypes.FILTER ? this.entityService.preview(id) : this.providerService.preview(id); - request.subscribe(xml => { - let modal = this.modalService.open(PreviewDialogComponent, { - size: 'lg', - windowClass: 'modal-xl' - }); - modal.componentInstance.entity = entity; - modal.componentInstance.xml = xml; - }); + request.subscribe( + xml => { + let modal = this.modalService.open(PreviewDialogComponent, { + size: 'lg', + windowClass: 'modal-xl' + }); + modal.componentInstance.entity = entity; + modal.componentInstance.xml = xml; + }, + err => { + this.store.dispatch(new AddNotification(new Notification( + NotificationType.Danger, + `Unable to preview entity.`, + 8000 + ))); + } + ); } } /* istanbul ignore next */ diff --git a/ui/src/app/schema-form/widget/button/icon-button.component.html b/ui/src/app/schema-form/widget/button/icon-button.component.html index 107a8eb7c..cb830c4df 100644 --- a/ui/src/app/schema-form/widget/button/icon-button.component.html +++ b/ui/src/app/schema-form/widget/button/icon-button.component.html @@ -1,4 +1,4 @@ - \ No newline at end of file diff --git a/ui/src/app/schema-form/widget/button/icon-button.component.ts b/ui/src/app/schema-form/widget/button/icon-button.component.ts index 2dcca890f..652490804 100644 --- a/ui/src/app/schema-form/widget/button/icon-button.component.ts +++ b/ui/src/app/schema-form/widget/button/icon-button.component.ts @@ -1,8 +1,9 @@ import { - Component, AfterViewInit, + Component, AfterViewInit, ChangeDetectorRef } from '@angular/core'; import { ButtonWidget } from 'ngx-schema-form'; import { ɵb as ActionRegistry } from 'ngx-schema-form'; +import { interval } from 'rxjs'; @Component({ selector: 'icon-button', @@ -10,9 +11,14 @@ import { ɵb as ActionRegistry } from 'ngx-schema-form'; }) export class IconButtonComponent extends ButtonWidget implements AfterViewInit { - action = ($event) => {}; + visible = false; - constructor(private actionRegistry: ActionRegistry) { + action = (e) => {}; + + constructor( + private actionRegistry: ActionRegistry, + private changeDetector: ChangeDetectorRef + ) { super(); } @@ -24,5 +30,8 @@ export class IconButtonComponent extends ButtonWidget implements AfterViewInit { } e.preventDefault(); }; + + this.visible = !!this.actionRegistry.get(this.button.id); + this.changeDetector.detectChanges(); } } diff --git a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html index c234bc490..37bc19ca4 100644 --- a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html +++ b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html @@ -98,7 +98,11 @@
  • {{ id }} - + + From 683feca70ee2904a386e63cb306c698dd3c74bbb Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 5 Sep 2018 08:05:00 -0700 Subject: [PATCH 03/25] SHIBUI-823 Fixed test --- ui/src/app/metadata/domain/effect/entity.effect.spec.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui/src/app/metadata/domain/effect/entity.effect.spec.ts b/ui/src/app/metadata/domain/effect/entity.effect.spec.ts index 07f7143e5..1a8c18a0d 100644 --- a/ui/src/app/metadata/domain/effect/entity.effect.spec.ts +++ b/ui/src/app/metadata/domain/effect/entity.effect.spec.ts @@ -10,6 +10,9 @@ import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModalStub } from '../../../../testing/modal.stub'; import { EntityAttributesFilterEntity, FileBackedHttpMetadataResolver } from '../entity'; +import * as fromRoot from '../../../app.reducer'; +import { StoreModule } from '@ngrx/store'; + describe('Entity Effects', () => { let effects: EntityEffects; let providerService: any; @@ -35,6 +38,9 @@ describe('Entity Effects', () => { }, { provide: Actions, useFactory: getActions } ], + imports: [ + StoreModule.forRoot(fromRoot.reducers) + ] }); effects = TestBed.get(EntityEffects); From 7d2040b5dc76ec41e6f7c6e5f74cb0f0c7cd80c8 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 5 Sep 2018 08:14:53 -0700 Subject: [PATCH 04/25] SHIBUI-806 fixed reversed check --- ui/src/app/metadata/provider/model/base.provider.form.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/src/app/metadata/provider/model/base.provider.form.ts b/ui/src/app/metadata/provider/model/base.provider.form.ts index e329e2605..7c78fe332 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.ts @@ -32,7 +32,7 @@ export const BaseMetadataProviderEditor: Wizard = { return err; }, '/metadataURL': (value, property, form) => { - return !!UriValidator.isUri(value) ? { + return !UriValidator.isUri(value) ? { code: 'INVALID_URI', path: `#${property.path}`, message: 'URI must be valid format.', From 46bc15b9bc499e91ef08e809c9dd8a46cd5dac2b Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Fri, 7 Sep 2018 11:22:18 -0400 Subject: [PATCH 05/25] SHIBUI-828: use correct mutable resolvers collection to search from --- .../OpenSamlChainingMetadataResolver.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlChainingMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlChainingMetadataResolver.java index aa5f45745..38b032834 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlChainingMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlChainingMetadataResolver.java @@ -4,14 +4,18 @@ import com.google.common.collect.Collections2; import net.shibboleth.utilities.java.support.annotation.constraint.NonnullElements; import net.shibboleth.utilities.java.support.component.ComponentInitializationException; +import net.shibboleth.utilities.java.support.component.ComponentSupport; +import net.shibboleth.utilities.java.support.resolver.CriteriaSet; import net.shibboleth.utilities.java.support.resolver.ResolverException; import org.opensaml.saml.metadata.resolver.ChainingMetadataResolver; import org.opensaml.saml.metadata.resolver.MetadataResolver; import org.opensaml.saml.metadata.resolver.RefreshableMetadataResolver; +import org.opensaml.saml.saml2.metadata.EntityDescriptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -49,6 +53,26 @@ public List getResolvers() { return mutableResolvers; } + @Override + @Nonnull public Iterable resolve(@Nullable final CriteriaSet criteria) throws ResolverException { + ComponentSupport.ifNotInitializedThrowUninitializedComponentException(this); + + for (final MetadataResolver resolver : mutableResolvers) { + try { + final Iterable descriptors = resolver.resolve(criteria); + if (descriptors != null && descriptors.iterator().hasNext()) { + return descriptors; + } + } catch (final ResolverException e) { + log.warn("Error retrieving metadata from resolver of type {}, proceeding to next resolver", + resolver.getClass().getName(), e); + continue; + } + } + + return Collections.emptyList(); + } + @Override protected void doInitialize() throws ComponentInitializationException { super.doInitialize(); From 8cdbef594e717639653ee2a995a5fded05436f75 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Fri, 7 Sep 2018 13:14:59 -0700 Subject: [PATCH 06/25] [SHIBUI-570] Added simple caching of fetchMetdata and unmarshallMetadata. --- .../MetadataResolversController.java | 2 + ...penSamlFileBackedHTTPMetadataResolver.java | 41 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java index 4d7d7aaac..2b4b90d65 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java @@ -118,6 +118,8 @@ public ResponseEntity create(@RequestBody MetadataResolver newResolver) throw MetadataResolver persistedResolver = resolverRepository.save(newResolver); positionOrderContainerService.appendPositionOrderForNew(persistedResolver); + //TODO: currently, the update call might explode, but the save works.. in which case, the UI never gets + // an valid response. This operation is not atomic. Should we return an error here? updateChainingMetadataResolver(persistedResolver); return ResponseEntity.created(getResourceUriFor(persistedResolver)).body(persistedResolver); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFileBackedHTTPMetadataResolver.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFileBackedHTTPMetadataResolver.java index 5f4c10905..e74bff535 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFileBackedHTTPMetadataResolver.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/opensaml/OpenSamlFileBackedHTTPMetadataResolver.java @@ -8,21 +8,35 @@ import org.apache.http.impl.client.HttpClients; import org.apache.lucene.index.IndexWriter; import org.joda.time.DateTime; +import org.opensaml.core.xml.XMLObject; +import org.opensaml.core.xml.io.UnmarshallingException; import org.opensaml.saml.metadata.resolver.impl.FileBackedHTTPMetadataResolver; +import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.io.InputStream; +import java.time.Instant; + import static edu.internet2.tier.shibboleth.admin.util.DurationUtility.toMillis; /** * @author Bill Smith (wsmith@unicon.net) */ public class OpenSamlFileBackedHTTPMetadataResolver extends FileBackedHTTPMetadataResolver { + + private static final long MILLISECONDS_IN_ONE_SECOND = 1000; + private IndexWriter indexWriter; private FileBackedHttpMetadataResolver sourceResolver; private OpenSamlMetadataResolverDelegate delegate; + private byte[] cachedMetadataBytes; + private Instant metadataLastFetchedAt; + boolean shouldRefreshMetadata; + XMLObject cachedMetadata; + public OpenSamlFileBackedHTTPMetadataResolver(ParserPool parserPool, IndexWriter indexWriter, FileBackedHttpMetadataResolver sourceResolver) throws ResolverException { @@ -67,4 +81,31 @@ protected void initMetadataResolver() throws ComponentInitializationException { this.sourceResolver.getResourceId(), indexWriter); } + + @Override + protected byte[] fetchMetadata() throws ResolverException { + if (metadataLastFetchedAt == null || shouldRefreshMetadata()) { + this.cachedMetadataBytes = super.fetchMetadata(); + this.metadataLastFetchedAt = Instant.now(); + } + return cachedMetadataBytes; + } + + private boolean shouldRefreshMetadata() { + if ((Instant.now().getEpochSecond() - metadataLastFetchedAt.getEpochSecond()) > (this.getMinRefreshDelay() / MILLISECONDS_IN_ONE_SECOND)) { + shouldRefreshMetadata = true; + } + return shouldRefreshMetadata; + } + + @Override + protected XMLObject unmarshallMetadata(@Nonnull final InputStream metadataInput) + throws UnmarshallingException { + //TODO: This should probably be based on something other than minRefreshDelay + if (cachedMetadata == null || shouldRefreshMetadata) { + this.cachedMetadata = super.unmarshallMetadata(metadataInput); + this.shouldRefreshMetadata = false; + } + return this.cachedMetadata; + } } From 9d797704bd01947a14802864d96118a6f7d8f48e Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 7 Sep 2018 15:19:13 -0700 Subject: [PATCH 07/25] SHIBUI-811 Implemented customizable theme --- ui/src/app/app.brand.ts | 44 ++++++++++++++++++++++++++ ui/src/app/app.component.html | 23 ++++++++------ ui/src/app/app.component.ts | 6 ++++ ui/src/app/core/model/brand.ts | 29 +++++++++++++++++ ui/src/app/shared/pipe/replace.pipe.ts | 10 ++++++ ui/src/app/shared/shared.module.ts | 5 ++- ui/src/brand.scss | 31 ++++++++++++++++++ ui/src/brand.ts | 21 ++++++++++++ ui/src/theme/_palette.scss | 26 ++++++++------- 9 files changed, 172 insertions(+), 23 deletions(-) create mode 100644 ui/src/app/app.brand.ts create mode 100644 ui/src/app/core/model/brand.ts create mode 100644 ui/src/app/shared/pipe/replace.pipe.ts create mode 100644 ui/src/brand.scss create mode 100644 ui/src/brand.ts diff --git a/ui/src/app/app.brand.ts b/ui/src/app/app.brand.ts new file mode 100644 index 000000000..a17c34cc0 --- /dev/null +++ b/ui/src/app/app.brand.ts @@ -0,0 +1,44 @@ +import { brand as customBrand } from '../brand'; +import { Brand } from './core/model/brand'; + +export const brand: Brand = { + header: { + title: 'Source Management' + }, + logo: { + default: '/assets/shibboleth_logowordmark_color.png', + small: '/assets/shibboleth_icon_color_130x130.png', + large: '/assets/shibboleth_logowordmark_color.png', + alt: 'Shibboleth Logo - Click to be directed to www.shibboleth.net', + link: { + label: 'Shibboleth', + url: 'https://www.shibboleth.net/' + } + }, + footer: { + links: [ + { + label: 'Home Page', + url: 'https://www.shibboleth.net/', + description: 'Shibboleth.net open-source community home page' + }, + { + label: 'Wiki', + url: 'https://wiki.shibboleth.net/', + description: 'Shibboleth.net open-source community wiki' + }, + { + label: 'Issue Tracker', + url: 'https://issues.shibboleth.net/', + description: 'Shibboleth.net open-source community issue tracker' + }, + { + label: 'Mailing List', + url: 'https://www.shibboleth.net/community/lists/', + description: 'Shibboleth.net open-source community mailing list' + } + ], + text: 'Links to Shibboleth resources:' + }, + ...customBrand +}; diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 17b5742e3..694a691ab 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -1,10 +1,10 @@