From: Bill Erickson Date: Fri, 20 Apr 2018 19:57:24 +0000 (-0400) Subject: LP#1626157 staff login perm; tidying X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=314ea41fb02560ff9abe3d184d5fbdf2a5d63f28;p=working%2FEvergreen.git LP#1626157 staff login perm; tidying Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/eg2/src/app/common.module.ts b/Open-ILS/src/eg2/src/app/common.module.ts index 4e2021dc8c..8cc94c74e5 100644 --- a/Open-ILS/src/eg2/src/app/common.module.ts +++ b/Open-ILS/src/eg2/src/app/common.module.ts @@ -14,7 +14,7 @@ import {EgAuthService} from '@eg/core/auth.service'; import {EgPermService} from '@eg/core/perm.service'; import {EgPcrudService} from '@eg/core/pcrud.service'; import {EgOrgService} from '@eg/core/org.service'; -import {EgAudioService} from '@eg/share/audio.service'; +import {EgAudioService} from '@eg/share/util/audio.service'; @NgModule({ declarations: [ diff --git a/Open-ILS/src/eg2/src/app/core/perm.service.ts b/Open-ILS/src/eg2/src/app/core/perm.service.ts index a2c439bbec..2e535d14e9 100644 --- a/Open-ILS/src/eg2/src/app/core/perm.service.ts +++ b/Open-ILS/src/eg2/src/app/core/perm.service.ts @@ -4,7 +4,7 @@ import {EgOrgService} from './org.service'; import {EgAuthService} from './auth.service'; interface HasPermAtResult { - [permName: string]: number[]; + [permName: string]: any[]; // org IDs or org unit objects } interface HasPermHereResult { diff --git a/Open-ILS/src/eg2/src/app/migration.module.ts b/Open-ILS/src/eg2/src/app/migration.module.ts index 88c84ae7d5..5c878b54c4 100644 --- a/Open-ILS/src/eg2/src/app/migration.module.ts +++ b/Open-ILS/src/eg2/src/app/migration.module.ts @@ -33,7 +33,6 @@ import {EgOrgService} from '@eg/core/org.service'; // Downgraded components //import {EgDialogComponent} from '@eg/share/dialog/dialog.component'; //import {EgConfirmDialogComponent} from '@eg/share/dialog/confirm.component'; -import {EgHelloWorldComponent} from '@eg/share/hello-world.component'; declare var angular: any; @@ -46,12 +45,10 @@ declare var angular: any; EgCommonModule.forRoot() ], declarations: [ - EgHelloWorldComponent, //EgDialogComponent, //EgConfirmDialogComponent ], entryComponents: [ - EgHelloWorldComponent, //EgDialogComponent, //EgConfirmDialogComponent ] @@ -81,8 +78,6 @@ export class EgMigrationModule { .factory('eg2Pcrud', downgradeInjectable(EgPcrudService)) .factory('eg2Org', downgradeInjectable(EgOrgService)) .factory('ng2Title', downgradeInjectable(Title)) - .directive('eg2HelloWorld', - downgradeComponent({component: EgHelloWorldComponent})) /* .directive('eg2ConfirmDialog', downgradeComponent({component: EgConfirmDialogComponent})) diff --git a/Open-ILS/src/eg2/src/app/share/README b/Open-ILS/src/eg2/src/app/share/README index 1a8b6e1646..9da6f8a11a 100644 --- a/Open-ILS/src/eg2/src/app/share/README +++ b/Open-ILS/src/eg2/src/app/share/README @@ -1,7 +1,5 @@ -Common Angular services and associated types/classes. - -This collection of services MIGHT be used by practically all applications. -They are NOT automatically imported/exported by the base module and should -be loaded within the requesting application as needed. +Shared Angular services, components, directives, and associated classes. +These items are NOT automatically imported to the base module. Import +as needed. diff --git a/Open-ILS/src/eg2/src/app/share/audio.service.ts b/Open-ILS/src/eg2/src/app/share/audio.service.ts deleted file mode 100644 index 971fe7e932..0000000000 --- a/Open-ILS/src/eg2/src/app/share/audio.service.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Plays audio files (alerts, generally) by key name. Each sound uses a - * dot-path to indicate the sound. - * - * For example: - * - * this.audio.play('warning.checkout.no_item'); - * - * URLs are tested in the following order until an audio file is found - * or no other paths are left to check. - * - * /audio/notifications/warning/checkout/not_found.wav - * /audio/notifications/warning/checkout.wav - * /audio/notifications/warning.wav - * - * Files are only played when sounds are configured to play via - * workstation settings. - */ -import {Injectable, EventEmitter} from '@angular/core'; -import {EgStoreService} from '@eg/core/store.service'; -const AUDIO_BASE_URL = '/audio/notifications/'; - -@Injectable() -export class EgAudioService { - - // map of requested audio path to resolved path - private urlCache: {[path:string] : string} = {}; - - constructor(private store: EgStoreService) {} - - play(path: string): void { - if (path) { - this.playUrl(path, path); - } - } - - playUrl(path: string, origPath: string): void { - //console.debug(`audio: playUrl(${path}, ${origPath})`); - - this.store.getItem('eg.audio.disable').then(audioDisabled => { - if (audioDisabled) return; - - let url = this.urlCache[path] || - AUDIO_BASE_URL + path.replace(/\./g, '/') + '.wav'; - - let player = new Audio(url); - - player.onloadeddata = () => { - this.urlCache[origPath] = url; - player.play(); - console.debug(`audio: ${url}`); - }; - - if (this.urlCache[path]) { - // when serving from the cache, avoid secondary URL lookups. - return; - } - - player.onerror = () => { - // Unable to play path at the requested URL. - - if (!path.match(/\./)) { - // all fall-through options have been exhausted. - // No path to play. - console.warn( - "No suitable URL found for path '" + origPath + "'"); - return; - } - - // Fall through to the next (more generic) option - path = path.replace(/\.[^\.]+$/, ''); - this.playUrl(path, origPath); - } - }); - } -} - - diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts index f59c6c12e1..8b0483f3e7 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {EgOrgService} from '@eg/core/org.service'; -import {EgUnapiService} from '@eg/share/unapi.service'; +import {EgUnapiService} from '@eg/share/catalog/unapi.service'; import {EgIdlObject} from '@eg/core/idl.service'; import {EgNetService} from '@eg/core/net.service'; import {EgPcrudService} from '@eg/core/pcrud.service'; diff --git a/Open-ILS/src/eg2/src/app/share/catalog/unapi.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/unapi.service.ts new file mode 100644 index 0000000000..9034ae43f0 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/catalog/unapi.service.ts @@ -0,0 +1,54 @@ +import {Injectable, EventEmitter} from '@angular/core'; +import {EgOrgService} from '@eg/core/org.service'; + +/* +TODO: Add Display Fields to UNAPI +https://library.biz/opac/extras/unapi?id=tag::U2@bre/1{bre.extern,holdings_xml,mra}/BR1/0&format=mods32 +*/ + +const UNAPI_PATH = '/opac/extras/unapi?id=tag::U2@'; + +interface EgUnapiParams { + target: string; // bre, ... + id: number | string; // 1 | 1,2,3,4,5 + extras: string; // {holdings_xml,mra,...} + format: string; // mods32, marxml, ... + orgId?: number; // org unit ID + depth?: number; // org unit depth +}; + +@Injectable() +export class EgUnapiService { + + constructor(private org: EgOrgService) {} + + createUrl(params: EgUnapiParams): string { + let depth = params.depth || 0; + let org = params.orgId ? this.org.get(params.orgId) : this.org.root(); + + return `${UNAPI_PATH}${params.target}/${params.id}${params.extras}/` + + `${org.shortname()}/${depth}&format=${params.format}`; + } + + getAsXmlDocument(params: EgUnapiParams): Promise { + // XReq creates an XML document for us. Seems like the right + // tool for the job. + let url = this.createUrl(params); + return new Promise((resolve, reject) => { + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + if (this.status == 200) { + resolve(xhttp.responseXML); + } else { + reject(`UNAPI request failed for ${url}`); + } + } + } + xhttp.open("GET", url, true); + xhttp.send(); + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts index 3ba94a3c47..5e17f6f60d 100644 --- a/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts +++ b/Open-ILS/src/eg2/src/app/share/fm-editor/fm-editor.component.ts @@ -12,7 +12,7 @@ interface CustomFieldTemplate { // Allow the caller to pass in a free-form context blob to // be addedto the caller's custom template context, along // with our stock context. - context?: [fields: string]: any + context?: {[fields: string]: any} } interface CustomFieldContext { @@ -268,7 +268,7 @@ export class FmRecordEditorComponent // Returns a context object to be inserted into a custom // field template. - customTemplateFieldContext(fieldDef: any): FmEditorCustomFieldContext { + customTemplateFieldContext(fieldDef: any): CustomFieldContext { return Object.assign( { record : this.record, field: fieldDef // from this.fields diff --git a/Open-ILS/src/eg2/src/app/share/hello-world.component.ts b/Open-ILS/src/eg2/src/app/share/hello-world.component.ts deleted file mode 100644 index 92daf1ccfa..0000000000 --- a/Open-ILS/src/eg2/src/app/share/hello-world.component.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {Component, Input} from '@angular/core'; - -@Component({ - selector: 'eg-hello-world', - template: ` -
Hello, World {{message}}!
- ` -}) -export class EgHelloWorldComponent { - @Input() public message: string; - constructor() {} -} - - diff --git a/Open-ILS/src/eg2/src/app/share/org-select.component.html b/Open-ILS/src/eg2/src/app/share/org-select.component.html deleted file mode 100644 index 2a4bd3a0c2..0000000000 --- a/Open-ILS/src/eg2/src/app/share/org-select.component.html +++ /dev/null @@ -1,17 +0,0 @@ - - - -{{r.label}} - - - diff --git a/Open-ILS/src/eg2/src/app/share/org-select.component.ts b/Open-ILS/src/eg2/src/app/share/org-select.component.ts deleted file mode 100644 index 627dd4e803..0000000000 --- a/Open-ILS/src/eg2/src/app/share/org-select.component.ts +++ /dev/null @@ -1,152 +0,0 @@ -import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core'; -import {Observable} from 'rxjs/Observable'; -import {map, debounceTime} from 'rxjs/operators'; -import {Subject} from 'rxjs/Subject'; -import {EgAuthService} from '@eg/core/auth.service'; -import {EgStoreService} from '@eg/core/store.service'; -import {EgOrgService} from '@eg/core/org.service'; -import {EgIdlObject} from '@eg/core/idl.service'; -import {NgbTypeahead, NgbTypeaheadSelectItemEvent} - from '@ng-bootstrap/ng-bootstrap'; - -// Use a unicode char for spacing instead of ASCII=32 so the browser -// won't collapse the nested display entries down to a single space. -const PAD_SPACE: string = ' '; // U+2007 - -interface OrgDisplay { - id: number; - label: string; - disabled: boolean; -} - -@Component({ - selector: 'eg-org-select', - templateUrl: './org-select.component.html' -}) -export class EgOrgSelectComponent implements OnInit { - - selected: OrgDisplay; - hidden: number[] = []; - disabled: number[] = []; - click$ = new Subject(); - startOrg: EgIdlObject; - - @ViewChild('instance') instance: NgbTypeahead; - - // Placeholder text for selector input - @Input() placeholder: string = ''; - @Input() stickySetting: string; - - // Org unit field displayed in the selector - @Input() displayField: string = 'shortname'; - - // Apply a default org unit value when none is set. - // First tries workstation org unit, then user home org unit. - // An onChange event WILL be generated when a default is applied. - @Input() applyDefault: boolean = false; - - // List of org unit IDs to exclude from the selector - @Input() set hideOrgs(ids: number[]) { - if (ids) this.hidden = ids; - } - - // List of org unit IDs to disable in the selector - @Input() set disableOrgs(ids: number[]) { - if (ids) this.disabled = ids; - } - - // Apply an org unit value at load time. - // This will NOT result in an onChange event. - @Input() set initialOrg(org: EgIdlObject) { - if (org) this.startOrg = org; - } - - // Apply an org unit value by ID at load time. - // This will NOT result in an onChange event. - @Input() set initialOrgId(id: number) { - if (id) this.startOrg = this.org.get(id); - } - - // Modify the selected org unit via data binding. - // This WILL result in an onChange event firing. - @Input() set applyOrg(org: EgIdlObject) { - if (org) this.selected = this.formatForDisplay(org); - } - - // Modify the selected org unit by ID via data binding. - // This WILL result in an onChange event firing. - @Input() set applyOrgId(id: number) { - if (id) this.selected = this.formatForDisplay(this.org.get(id)); - } - - // Emitted when the org unit value is changed via the selector. - // Does not fire on initialOrg - @Output() onChange = new EventEmitter(); - - constructor( - private auth: EgAuthService, - private store: EgStoreService, - private org: EgOrgService - ) {} - - ngOnInit() { - - // Apply a default org unit if desired and possible. - if (!this.startOrg && this.applyDefault && this.auth.user()) { - // note: ws_ou defaults to home_ou on the server - // when when no workstation is used - this.startOrg = this.org.get(this.auth.user().ws_ou()); - this.selected = this.formatForDisplay( - this.org.get(this.auth.user().ws_ou()) - ); - - // avoid notifying mid-digest - setTimeout(() => this.onChange.emit(this.startOrg), 0); - } - - if (this.startOrg) { - this.selected = this.formatForDisplay(this.startOrg); - } - } - - // Format for display in the selector drop-down and input. - formatForDisplay(org: EgIdlObject): OrgDisplay { - return { - id : org.id(), - label : PAD_SPACE.repeat(org.ou_type().depth()) - + org[this.displayField](), - disabled : false - }; - } - - // Fired by the typeahead to inform us of a change. - orgChanged(selEvent: NgbTypeaheadSelectItemEvent) { - this.onChange.emit(this.org.get(selEvent.item.id)); - } - - // Remove the tree-padding spaces when matching. - formatter = (result: OrgDisplay) => result.label.trim(); - - filter = (text$: Observable): Observable => { - return text$ - .debounceTime(200) - .distinctUntilChanged() - .merge(this.click$.filter(() => !this.instance.isPopupOpen())) - .map(term => { - - return this.org.list().filter(org => { - - // Find orgs matching the search term - return org[this.displayField]() - .toLowerCase().indexOf(term.toLowerCase()) > -1 - - }).filter(org => { // Exclude hidden orgs - return this.hidden.filter( - id => {return org.id() == id}).length == 0; - - }).map(org => {return this.formatForDisplay(org)}) - }); - } -} - - diff --git a/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.html b/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.html new file mode 100644 index 0000000000..2a4bd3a0c2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.html @@ -0,0 +1,17 @@ + + + +{{r.label}} + + + diff --git a/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.ts b/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.ts new file mode 100644 index 0000000000..627dd4e803 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/org-select/org-select.component.ts @@ -0,0 +1,152 @@ +import {Component, OnInit, Input, Output, ViewChild, EventEmitter} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import {map, debounceTime} from 'rxjs/operators'; +import {Subject} from 'rxjs/Subject'; +import {EgAuthService} from '@eg/core/auth.service'; +import {EgStoreService} from '@eg/core/store.service'; +import {EgOrgService} from '@eg/core/org.service'; +import {EgIdlObject} from '@eg/core/idl.service'; +import {NgbTypeahead, NgbTypeaheadSelectItemEvent} + from '@ng-bootstrap/ng-bootstrap'; + +// Use a unicode char for spacing instead of ASCII=32 so the browser +// won't collapse the nested display entries down to a single space. +const PAD_SPACE: string = ' '; // U+2007 + +interface OrgDisplay { + id: number; + label: string; + disabled: boolean; +} + +@Component({ + selector: 'eg-org-select', + templateUrl: './org-select.component.html' +}) +export class EgOrgSelectComponent implements OnInit { + + selected: OrgDisplay; + hidden: number[] = []; + disabled: number[] = []; + click$ = new Subject(); + startOrg: EgIdlObject; + + @ViewChild('instance') instance: NgbTypeahead; + + // Placeholder text for selector input + @Input() placeholder: string = ''; + @Input() stickySetting: string; + + // Org unit field displayed in the selector + @Input() displayField: string = 'shortname'; + + // Apply a default org unit value when none is set. + // First tries workstation org unit, then user home org unit. + // An onChange event WILL be generated when a default is applied. + @Input() applyDefault: boolean = false; + + // List of org unit IDs to exclude from the selector + @Input() set hideOrgs(ids: number[]) { + if (ids) this.hidden = ids; + } + + // List of org unit IDs to disable in the selector + @Input() set disableOrgs(ids: number[]) { + if (ids) this.disabled = ids; + } + + // Apply an org unit value at load time. + // This will NOT result in an onChange event. + @Input() set initialOrg(org: EgIdlObject) { + if (org) this.startOrg = org; + } + + // Apply an org unit value by ID at load time. + // This will NOT result in an onChange event. + @Input() set initialOrgId(id: number) { + if (id) this.startOrg = this.org.get(id); + } + + // Modify the selected org unit via data binding. + // This WILL result in an onChange event firing. + @Input() set applyOrg(org: EgIdlObject) { + if (org) this.selected = this.formatForDisplay(org); + } + + // Modify the selected org unit by ID via data binding. + // This WILL result in an onChange event firing. + @Input() set applyOrgId(id: number) { + if (id) this.selected = this.formatForDisplay(this.org.get(id)); + } + + // Emitted when the org unit value is changed via the selector. + // Does not fire on initialOrg + @Output() onChange = new EventEmitter(); + + constructor( + private auth: EgAuthService, + private store: EgStoreService, + private org: EgOrgService + ) {} + + ngOnInit() { + + // Apply a default org unit if desired and possible. + if (!this.startOrg && this.applyDefault && this.auth.user()) { + // note: ws_ou defaults to home_ou on the server + // when when no workstation is used + this.startOrg = this.org.get(this.auth.user().ws_ou()); + this.selected = this.formatForDisplay( + this.org.get(this.auth.user().ws_ou()) + ); + + // avoid notifying mid-digest + setTimeout(() => this.onChange.emit(this.startOrg), 0); + } + + if (this.startOrg) { + this.selected = this.formatForDisplay(this.startOrg); + } + } + + // Format for display in the selector drop-down and input. + formatForDisplay(org: EgIdlObject): OrgDisplay { + return { + id : org.id(), + label : PAD_SPACE.repeat(org.ou_type().depth()) + + org[this.displayField](), + disabled : false + }; + } + + // Fired by the typeahead to inform us of a change. + orgChanged(selEvent: NgbTypeaheadSelectItemEvent) { + this.onChange.emit(this.org.get(selEvent.item.id)); + } + + // Remove the tree-padding spaces when matching. + formatter = (result: OrgDisplay) => result.label.trim(); + + filter = (text$: Observable): Observable => { + return text$ + .debounceTime(200) + .distinctUntilChanged() + .merge(this.click$.filter(() => !this.instance.isPopupOpen())) + .map(term => { + + return this.org.list().filter(org => { + + // Find orgs matching the search term + return org[this.displayField]() + .toLowerCase().indexOf(term.toLowerCase()) > -1 + + }).filter(org => { // Exclude hidden orgs + return this.hidden.filter( + id => {return org.id() == id}).length == 0; + + }).map(org => {return this.formatForDisplay(org)}) + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/share/unapi.service.ts b/Open-ILS/src/eg2/src/app/share/unapi.service.ts deleted file mode 100644 index 9034ae43f0..0000000000 --- a/Open-ILS/src/eg2/src/app/share/unapi.service.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {Injectable, EventEmitter} from '@angular/core'; -import {EgOrgService} from '@eg/core/org.service'; - -/* -TODO: Add Display Fields to UNAPI -https://library.biz/opac/extras/unapi?id=tag::U2@bre/1{bre.extern,holdings_xml,mra}/BR1/0&format=mods32 -*/ - -const UNAPI_PATH = '/opac/extras/unapi?id=tag::U2@'; - -interface EgUnapiParams { - target: string; // bre, ... - id: number | string; // 1 | 1,2,3,4,5 - extras: string; // {holdings_xml,mra,...} - format: string; // mods32, marxml, ... - orgId?: number; // org unit ID - depth?: number; // org unit depth -}; - -@Injectable() -export class EgUnapiService { - - constructor(private org: EgOrgService) {} - - createUrl(params: EgUnapiParams): string { - let depth = params.depth || 0; - let org = params.orgId ? this.org.get(params.orgId) : this.org.root(); - - return `${UNAPI_PATH}${params.target}/${params.id}${params.extras}/` + - `${org.shortname()}/${depth}&format=${params.format}`; - } - - getAsXmlDocument(params: EgUnapiParams): Promise { - // XReq creates an XML document for us. Seems like the right - // tool for the job. - let url = this.createUrl(params); - return new Promise((resolve, reject) => { - var xhttp = new XMLHttpRequest(); - xhttp.onreadystatechange = function() { - if (this.readyState == 4) { - if (this.status == 200) { - resolve(xhttp.responseXML); - } else { - reject(`UNAPI request failed for ${url}`); - } - } - } - xhttp.open("GET", url, true); - xhttp.send(); - }); - } -} - - diff --git a/Open-ILS/src/eg2/src/app/share/util/audio.service.ts b/Open-ILS/src/eg2/src/app/share/util/audio.service.ts new file mode 100644 index 0000000000..971fe7e932 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/share/util/audio.service.ts @@ -0,0 +1,78 @@ +/** + * Plays audio files (alerts, generally) by key name. Each sound uses a + * dot-path to indicate the sound. + * + * For example: + * + * this.audio.play('warning.checkout.no_item'); + * + * URLs are tested in the following order until an audio file is found + * or no other paths are left to check. + * + * /audio/notifications/warning/checkout/not_found.wav + * /audio/notifications/warning/checkout.wav + * /audio/notifications/warning.wav + * + * Files are only played when sounds are configured to play via + * workstation settings. + */ +import {Injectable, EventEmitter} from '@angular/core'; +import {EgStoreService} from '@eg/core/store.service'; +const AUDIO_BASE_URL = '/audio/notifications/'; + +@Injectable() +export class EgAudioService { + + // map of requested audio path to resolved path + private urlCache: {[path:string] : string} = {}; + + constructor(private store: EgStoreService) {} + + play(path: string): void { + if (path) { + this.playUrl(path, path); + } + } + + playUrl(path: string, origPath: string): void { + //console.debug(`audio: playUrl(${path}, ${origPath})`); + + this.store.getItem('eg.audio.disable').then(audioDisabled => { + if (audioDisabled) return; + + let url = this.urlCache[path] || + AUDIO_BASE_URL + path.replace(/\./g, '/') + '.wav'; + + let player = new Audio(url); + + player.onloadeddata = () => { + this.urlCache[origPath] = url; + player.play(); + console.debug(`audio: ${url}`); + }; + + if (this.urlCache[path]) { + // when serving from the cache, avoid secondary URL lookups. + return; + } + + player.onerror = () => { + // Unable to play path at the requested URL. + + if (!path.match(/\./)) { + // all fall-through options have been exhausted. + // No path to play. + console.warn( + "No suitable URL found for path '" + origPath + "'"); + return; + } + + // Fall through to the next (more generic) option + path = path.replace(/\.[^\.]+$/, ''); + this.playUrl(path, origPath); + } + }); + } +} + + diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts index 7438ec9bae..4bb63fd33c 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/catalog.module.ts @@ -1,6 +1,6 @@ import {NgModule} from '@angular/core'; import {EgStaffCommonModule} from '@eg/staff/common.module'; -import {EgUnapiService} from '@eg/share/unapi.service'; +import {EgUnapiService} from '@eg/share/catalog/unapi.service'; import {EgCatalogRoutingModule} from './routing.module'; import {EgCatalogService} from '@eg/share/catalog/catalog.service'; import {EgCatalogUrlService} from '@eg/share/catalog/catalog-url.service'; diff --git a/Open-ILS/src/eg2/src/app/staff/common.module.ts b/Open-ILS/src/eg2/src/app/staff/common.module.ts index 762c1aa877..c8aeb1d152 100644 --- a/Open-ILS/src/eg2/src/app/staff/common.module.ts +++ b/Open-ILS/src/eg2/src/app/staff/common.module.ts @@ -1,7 +1,7 @@ import {NgModule, ModuleWithProviders} from '@angular/core'; import {EgCommonModule} from '@eg/common.module'; import {EgStaffBannerComponent} from './share/staff-banner.component'; -import {EgOrgSelectComponent} from '@eg/share/org-select.component'; +import {EgOrgSelectComponent} from '@eg/share/org-select/org-select.component'; import {EgDialogComponent} from '@eg/share/dialog/dialog.component'; import {EgConfirmDialogComponent} from '@eg/share/dialog/confirm.component'; import {EgPromptDialogComponent} from '@eg/share/dialog/prompt.component'; diff --git a/Open-ILS/src/eg2/src/app/staff/resolver.service.ts b/Open-ILS/src/eg2/src/app/staff/resolver.service.ts index 8f2fe5a0fe..301979fb18 100644 --- a/Open-ILS/src/eg2/src/app/staff/resolver.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/resolver.service.ts @@ -6,6 +6,7 @@ import {Router, Resolve, RouterStateSnapshot, import {EgStoreService} from '@eg/core/store.service'; import {EgNetService} from '@eg/core/net.service'; import {EgAuthService, EgAuthWsState} from '@eg/core/auth.service'; +import {EgPermService} from '@eg/core/perm.service'; const LOGIN_PATH = '/staff/login'; const WS_MANAGE_PATH = '/staff/admin/workstation/workstations/manage'; @@ -25,7 +26,8 @@ export class EgStaffResolver implements Resolve> { private ngLocation: Location, private store: EgStoreService, private net: EgNetService, - private auth: EgAuthService + private auth: EgAuthService, + private perm: EgPermService, ) {} resolve( @@ -50,12 +52,20 @@ export class EgStaffResolver implements Resolve> { this.auth.testAuthToken().then( tokenOk => { console.debug('EgStaffResolver: authtoken verified'); - this.auth.verifyWorkstation().then( - wsOk => { - this.loadStartupData() - .then(ok => this.observer.complete()) - }, - wsNotOk => this.handleInvalidWorkstation(path) + this.confirmStaffPerms().then( + hasPerms => { + this.auth.verifyWorkstation().then( + wsOk => { + this.loadStartupData() + .then(ok => this.observer.complete()) + }, + wsNotOk => this.handleInvalidWorkstation(path) + ); + }, + hasNotPerms => { + this.observer.error( + 'User does not have staff permissions'); + } ); }, tokenNotOk => this.handleInvalidToken(state) @@ -64,6 +74,27 @@ export class EgStaffResolver implements Resolve> { return observable; } + + // Confirm the user has the STAFF_LOGIN permission anywhere before + // allowing the staff sub-tree to load. This will prevent users + // with valid, non-staff authtokens from attempting to connect and + // subsequently getting redirected to the workstation admin page + // (since they won't have a valid WS either). + confirmStaffPerms(): Promise { + return new Promise((resolve, reject) => { + this.perm.hasWorkPermAt(['STAFF_LOGIN']).then( + permMap => { + if (permMap.STAFF_LOGIN.length) { + resolve('perm check OK'); + } else { + reject('perm check faield'); + } + } + ); + }); + } + + // A page that's not the login page was requested without a // valid auth token. Send the caller back to the login page. handleInvalidToken(state: RouterStateSnapshot): void { diff --git a/Open-ILS/src/templates/staff/circ/checkin/t_checkin.tt2 b/Open-ILS/src/templates/staff/circ/checkin/t_checkin.tt2 index 974f08a402..42d6e67d60 100644 --- a/Open-ILS/src/templates/staff/circ/checkin/t_checkin.tt2 +++ b/Open-ILS/src/templates/staff/circ/checkin/t_checkin.tt2 @@ -5,16 +5,6 @@ - - - -