diff --git a/deploy/app.js b/deploy/app.js index e2260239c9dc507b9079e7a510b2750c20c5d27d..8b815a3607c836f6810190d68ffb632916bc2ba5 100644 --- a/deploy/app.js +++ b/deploy/app.js @@ -10,6 +10,19 @@ if (process.env.NODE_ENV !== 'production') { app.use(require('cors')()) } +app.use((req, _, next) => { + if (/main\.bundle\.js$/.test(req.originalUrl)){ + const xForwardedFor = req.headers['x-forwarded-for'] + const ip = req.connection.remoteAddress + console.log({ + type: 'visitorLog', + xForwardedFor, + ip + }) + } + next() +}) + /** * load env first, then load other modules */ @@ -44,6 +57,11 @@ const PUBLIC_PATH = process.env.NODE_ENV === 'production' ? path.join(__dirname, 'public') : path.join(__dirname, '..', 'dist', 'aot') +/** + * well known path + */ +app.use('/.well-known', express.static(path.join(__dirname, 'well-known'))) + app.use(express.static(PUBLIC_PATH)) app.use((req, res, next) => { diff --git a/deploy/auth/hbp-oidc.js b/deploy/auth/hbp-oidc.js index cbb7ce83f0d34de5814048a3e2986a660640f40c..a6963750d8469a224c04018c1ef63209cfa9bfc8 100644 --- a/deploy/auth/hbp-oidc.js +++ b/deploy/auth/hbp-oidc.js @@ -17,23 +17,27 @@ const cb = (tokenset, {sub, given_name, family_name, ...rest}, done) => { } module.exports = async (app) => { - const { oidcStrategy } = await configureAuth({ - clientId, - clientSecret, - discoveryUrl, - redirectUri, - cb, - scope: 'openid offline_access', - clientConfig: { - redirect_uris: [ redirectUri ], - response_types: [ 'code' ] - } - }) - - passport.use('hbp-oidc', oidcStrategy) - app.get('/hbp-oidc/auth', passport.authenticate('hbp-oidc')) - app.get('/hbp-oidc/cb', passport.authenticate('hbp-oidc', { - successRedirect: '/', - failureRedirect: '/' - })) + try { + const { oidcStrategy } = await configureAuth({ + clientId, + clientSecret, + discoveryUrl, + redirectUri, + cb, + scope: 'openid offline_access', + clientConfig: { + redirect_uris: [ redirectUri ], + response_types: [ 'code' ] + } + }) + + passport.use('hbp-oidc', oidcStrategy) + app.get('/hbp-oidc/auth', passport.authenticate('hbp-oidc')) + app.get('/hbp-oidc/cb', passport.authenticate('hbp-oidc', { + successRedirect: '/', + failureRedirect: '/' + })) + } catch (e) { + console.error(e) + } } diff --git a/deploy/auth/index.js b/deploy/auth/index.js index bfd8fab1724d4a1f052734684b2440baa1374e79..8c3895710418e81e78ef7aa5aea483013a799886 100644 --- a/deploy/auth/index.js +++ b/deploy/auth/index.js @@ -14,10 +14,8 @@ module.exports = async (app) => { passport.deserializeUser((id, done) => { const user = objStoreDb.get(id) - if (user) - return done(null, user) - else - return done(null, false) + if (user) return done(null, user) + else return done(null, false) }) await hbpOidc(app) diff --git a/deploy/catchError.js b/deploy/catchError.js index 0fdc484348a9e6f8e1f1fc9072eaec5b8de5e1d9..38e1a000f5b78c53d5275bef01caf2ec9cbfb0db 100644 --- a/deploy/catchError.js +++ b/deploy/catchError.js @@ -2,7 +2,7 @@ module.exports = ({code = 500, error = 'an error had occured', trace = 'undefine /** * probably use more elaborate logging? */ - console.log('Catching error', { + console.error('Catching error', { code, error, trace diff --git a/deploy/datasets/index.js b/deploy/datasets/index.js index edb58cdf6e225786565b1cd7b3a7614f97d952fb..aae9728421904d2487af620abc6a51e18159529d 100644 --- a/deploy/datasets/index.js +++ b/deploy/datasets/index.js @@ -106,7 +106,7 @@ const PUBLIC_PATH = process.env.NODE_ENV === 'production' const RECEPTOR_PATH = path.join(PUBLIC_PATH, 'res', 'image') fs.readdir(RECEPTOR_PATH, (err, files) => { if (err) { - console.log('reading receptor error', err) + console.warn('reading receptor error', err) return } files.forEach(file => previewFileMap.set(`res/image/receptor/${file}`, path.join(RECEPTOR_PATH, file))) @@ -144,7 +144,7 @@ datasetsRouter.get('/downloadKgFiles', checkKgQuery, cacheMaxAge24Hr, async (req res.setHeader('Content-Type', 'application/zip') stream.pipe(res) } catch (e) { - console.log('datasets/index#downloadKgFiles', e) + console.warn('datasets/index#downloadKgFiles', e) res.status(400).send(e) } }) diff --git a/deploy/datasets/supplements/previewFile.js b/deploy/datasets/supplements/previewFile.js index ea178530c1ed118387393dd8754f325b2ff521a2..311ec7d273becf80a84d74615128924cb302551a 100644 --- a/deploy/datasets/supplements/previewFile.js +++ b/deploy/datasets/supplements/previewFile.js @@ -13,7 +13,7 @@ let previewMap = new Map(), const readFile = (filename) => new Promise((resolve) => { fs.readFile(path.join(__dirname, 'data', filename), 'utf-8', (err, data) => { if (err){ - console.log('read file error', err) + console.warn('read file error', err) return resolve([]) } resolve(JSON.parse(data)) diff --git a/deploy/logging/index.js b/deploy/logging/index.js new file mode 100644 index 0000000000000000000000000000000000000000..173ec49afb0c3d45332e0c04fbae38283541a3e6 --- /dev/null +++ b/deploy/logging/index.js @@ -0,0 +1,43 @@ +const request = require('request') +const qs = require('querystring') + +class Logger { + constructor(name, { protocol = 'http', host = 'localhost', port = '24224', username = '', password = '' } = {}){ + this.name = qs.escape(name) + this.protocol = protocol + this.host = host + this.port = port + this.username = username + this.password = password + } + + emit(logLevel, message, callback){ + const { + name, + protocol, + host, + port, + username, + password + } = this + + const auth = username !== '' ? `${username}:${password}@` : '' + const url = `${protocol}://${auth}${host}:${port}/${name}.${qs.escape(logLevel)}` + const formData = { + json: JSON.stringify(message) + } + if (callback) { + request.post({ + url, + formData + }, callback) + } else { + return request.post({ + url, + formData + }) + } + } +} + +module.exports = Logger \ No newline at end of file diff --git a/deploy/server.js b/deploy/server.js index 85596178cccb7433a77195c447e3efa665cd2583..4256b418128deae6033e4f2bfe29aa94a6c33008 100644 --- a/deploy/server.js +++ b/deploy/server.js @@ -5,6 +5,57 @@ if (process.env.NODE_ENV !== 'production') { }) } +if (process.env.FLUENT_HOST) { + const Logger = require('./logging') + + const name = process.env.IAV_NAME || 'IAV' + const stage = process.env.IAV_STAGE || 'unnamed-stage' + + const protocol = process.env.FLUENT_PROTOCOL || 'http' + const host = process.env.FLUENT_HOST || 'localhost' + const port = process.env.FLUENT_PORT || 24224 + + const prefix = `${name}.${stage}` + + const log = new Logger(prefix, { + protocol, + host, + port + }) + + const handleRequestCallback = (err, resp, body) => { + if (err) { + process.stderr.write(`fluentD logging failed\n`) + process.stderr.write(err.toString()) + process.stderr.write('\n') + } + + if (resp && resp.statusCode >= 400) { + process.stderr.write(`fluentD logging responded error\n`) + process.stderr.write(resp.toString()) + process.stderr.write('\n') + } + } + + const emitInfo = message => log.emit('info', { message }, handleRequestCallback) + + const emitWarn = message => log.emit('warn', { message }, handleRequestCallback) + + const emitError = message => log.emit('error', { message }, handleRequestCallback) + + console.log('starting fluentd logging') + + console.log = function () { + emitInfo([...arguments]) + } + console.warn = function () { + emitWarn([...arguments]) + } + console.emitError = function () { + emitError([...arguments]) + } +} + const app = require('./app') const PORT = process.env.PORT || 3000 diff --git a/deploy/well-known/robot.txt b/deploy/well-known/robot.txt new file mode 100644 index 0000000000000000000000000000000000000000..4f9540ba358a64607438da92eebe85889fdad50a --- /dev/null +++ b/deploy/well-known/robot.txt @@ -0,0 +1 @@ +User-agent: * \ No newline at end of file diff --git a/deploy/well-known/security.txt b/deploy/well-known/security.txt new file mode 100644 index 0000000000000000000000000000000000000000..42a2d3940dfcc87456b84a6165c9e741068c6b31 --- /dev/null +++ b/deploy/well-known/security.txt @@ -0,0 +1,2 @@ +# If you would like to report a security issue, please contact us via: +Contact: inm1-bda@fz-juelich.de \ No newline at end of file diff --git a/src/main.module.ts b/src/main.module.ts index 16755fad9dc20f87b53a44ed3230ae2c94fc348e..7c0b4504e7ac41bf2e358fea9c0f30777e595880 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -34,7 +34,7 @@ import { FloatingContainerDirective } from "./util/directives/floatingContainer. import { PluginFactoryDirective } from "./util/directives/pluginFactory.directive"; import { FloatingMouseContextualContainerDirective } from "./util/directives/floatingMouseContextualContainer.directive"; import { AuthService } from "./services/auth.service"; -import { ViewerConfiguration } from "./services/state/viewerConfig.store"; +import { ViewerConfiguration, LOCAL_STORAGE_CONST } from "./services/state/viewerConfig.store"; import { FixedMouseContextualContainerDirective } from "./util/directives/FixedMouseContextualContainerDirective.directive"; import { DatabrowserService } from "./ui/databrowserModule/databrowser.service"; import { TransformOnhoverSegmentPipe } from "./atlasViewer/onhoverSegment.pipe"; @@ -146,9 +146,13 @@ export class MainModule{ authServce.authReloadState() store.pipe( select('viewerConfigState') - ).subscribe(({ gpuLimit }) => { - if (gpuLimit) - window.localStorage.setItem('iv-gpulimit', gpuLimit.toString()) + ).subscribe(({ gpuLimit, animation }) => { + if (gpuLimit) { + window.localStorage.setItem(LOCAL_STORAGE_CONST.GPU_LIMIT, gpuLimit.toString()) + } + if (typeof animation !== 'undefined' && animation !== null) { + window.localStorage.setItem(LOCAL_STORAGE_CONST.ANIMATION, animation.toString()) + } }) } } \ No newline at end of file diff --git a/src/res/css/extra_styles.css b/src/res/css/extra_styles.css index 899c1be568d579c4f7b4064bb929a930c743624b..19da12b63740924a679c68fea67f8f27b07eefed 100644 --- a/src/res/css/extra_styles.css +++ b/src/res/css/extra_styles.css @@ -356,4 +356,17 @@ markdown-dom pre code .w-50vw { width: 50vw!important; +} +/* TODO fix hack */ +/* ngx boostrap default z index for modal-container is 1050, which is higher than material tooltip 1000 */ +/* when migration away from ngx bootstrap is complete, remove these classes */ + +modal-container.modal +{ + z-index: 950; +} + +bs-modal-backdrop.modal-backdrop +{ + z-index: 940; } \ No newline at end of file diff --git a/src/services/state/viewerConfig.store.ts b/src/services/state/viewerConfig.store.ts index eb634fee9dbeda27a7e5915258048bd43b93c885..e6bf3881472833ec2f2adbf939ecbf41f21ee993 100644 --- a/src/services/state/viewerConfig.store.ts +++ b/src/services/state/viewerConfig.store.ts @@ -21,16 +21,29 @@ export const CONFIG_CONSTANTS = { } export const ACTION_TYPES = { + SET_ANIMATION: `SET_ANIMATION`, UPDATE_CONFIG: `UPDATE_CONFIG`, CHANGE_GPU_LIMIT: `CHANGE_GPU_LIMIT` } -const lsGpuLimit = localStorage.getItem('iv-gpulimit') +export const LOCAL_STORAGE_CONST = { + GPU_LIMIT: 'iv-gpulimit', + ANIMATION: 'iv-animationFlag' +} + +const lsGpuLimit = localStorage.getItem(LOCAL_STORAGE_CONST.GPU_LIMIT) +const lsAnimationFlag = localStorage.getItem(LOCAL_STORAGE_CONST.ANIMATION) const gpuLimit = lsGpuLimit && !isNaN(Number(lsGpuLimit)) ? Number(lsGpuLimit) : CONFIG_CONSTANTS.defaultGpuLimit -export function viewerConfigState(prevState:ViewerConfiguration = {animation: CONFIG_CONSTANTS.defaultAnimation, gpuLimit}, action:ViewerConfigurationAction) { +const animation = lsAnimationFlag && lsAnimationFlag === 'true' + ? true + : lsAnimationFlag === 'false' + ? false + : CONFIG_CONSTANTS.defaultAnimation + +export function viewerConfigState(prevState:ViewerConfiguration = {animation, gpuLimit}, action:ViewerConfigurationAction) { switch (action.type) { case ACTION_TYPES.UPDATE_CONFIG: return { diff --git a/src/ui/config/config.component.ts b/src/ui/config/config.component.ts index e0104405a1b4bc8efda4e8d5b8e18af4598b7b06..12a3a700a6f84bdf71fa77699c0be0b772756385 100644 --- a/src/ui/config/config.component.ts +++ b/src/ui/config/config.component.ts @@ -2,7 +2,11 @@ import { Component, OnInit, OnDestroy } from '@angular/core' import { Store, select } from '@ngrx/store'; import { ViewerConfiguration, ACTION_TYPES } from 'src/services/state/viewerConfig.store' import { Observable, Subject, Subscription } from 'rxjs'; -import { map, distinctUntilChanged, debounce, debounceTime } from 'rxjs/operators'; +import { map, distinctUntilChanged, debounceTime } from 'rxjs/operators'; +import { MatSlideToggleChange } from '@angular/material'; + +const GPU_TOOLTIP = `GPU TOOLTIP` +const ANIMATION_TOOLTIP = `ANIMATION_TOOLTIP` @Component({ selector: 'config-component', @@ -14,10 +18,15 @@ import { map, distinctUntilChanged, debounce, debounceTime } from 'rxjs/operator export class ConfigComponent implements OnInit, OnDestroy{ + public GPU_TOOLTIP = GPU_TOOLTIP + public ANIMATION_TOOLTIP = ANIMATION_TOOLTIP + /** * in MB */ public gpuLimit$: Observable<number> + + public animationFlag$: Observable<boolean> public keydown$: Subject<Event> = new Subject() private subscriptions: Subscription[] = [] @@ -31,6 +40,11 @@ export class ConfigComponent implements OnInit, OnDestroy{ distinctUntilChanged(), map(v => v / 1e6) ) + + this.animationFlag$ = this.store.pipe( + select('viewerConfigState'), + map((config:ViewerConfiguration) => config.animation), + ) } ngOnInit(){ @@ -64,6 +78,16 @@ export class ConfigComponent implements OnInit, OnDestroy{ }) } + public toggleAnimationFlag(ev: MatSlideToggleChange ){ + const { checked } = ev + this.store.dispatch({ + type: ACTION_TYPES.UPDATE_CONFIG, + config: { + animation: checked + } + }) + } + public setGpuPreset({value}: {value: number}) { this.store.dispatch({ type: ACTION_TYPES.UPDATE_CONFIG, diff --git a/src/ui/config/config.template.html b/src/ui/config/config.template.html index 18f84d0b297f9ef71422960695f032c567faafb1..dca3b164d0fbe6bd4235e50e856073835acb13d0 100644 --- a/src/ui/config/config.template.html +++ b/src/ui/config/config.template.html @@ -1,7 +1,8 @@ -<div class="input-group"> +<div class="input-group mb-2"> <span class="input-group-prepend"> <span class="input-group-text"> GPU Limit + <small tooltipClass="z-1060" [matTooltip]="GPU_TOOLTIP" class="ml-2 fas fa-question"></small> </span> </span> <input @@ -17,16 +18,25 @@ <div class="input-group-append"> <div (click)="setGpuPreset({ value: 100 })" class="btn btn-outline-secondary"> - 100 - </div> - <div (click)="setGpuPreset({ value: 500 })" class="btn btn-outline-secondary"> - 500 - </div> - <div (click)="setGpuPreset({ value: 1000 })" class="btn btn-outline-secondary"> - 1000 - </div> - <span class="input-group-text"> - MB - </span> + 100 </div> - </div> \ No newline at end of file + <div (click)="setGpuPreset({ value: 500 })" class="btn btn-outline-secondary"> + 500 + </div> + <div (click)="setGpuPreset({ value: 1000 })" class="btn btn-outline-secondary"> + 1000 + </div> + <span class="input-group-text"> + MB + </span> + </div> +</div> + +<div class="d-flex align-items-center"> + <mat-slide-toggle + [checked]="animationFlag$ | async" + (change)="toggleAnimationFlag($event)"> + Enable Animation + </mat-slide-toggle> + <small [matTooltip]="ANIMATION_TOOLTIP" class="ml-2 fas fa-question"></small> +</div> \ No newline at end of file diff --git a/src/ui/sharedModules/angularMaterial.module.ts b/src/ui/sharedModules/angularMaterial.module.ts index b63e2dfaa5a37e5caa567efebe4bf1c135e00a4b..fe3b30ad0b03a82f351a0d90fab08e89d821f06d 100644 --- a/src/ui/sharedModules/angularMaterial.module.ts +++ b/src/ui/sharedModules/angularMaterial.module.ts @@ -4,12 +4,13 @@ import { MatSidenavModule, MatCardModule, MatTabsModule, - MatTooltipModule + MatTooltipModule, + MatSlideToggleModule } from '@angular/material'; import { NgModule } from '@angular/core'; @NgModule({ - imports: [MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule], - exports: [MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule], + imports: [MatSlideToggleModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule], + exports: [MatSlideToggleModule, MatButtonModule, MatCheckboxModule, MatSidenavModule, MatCardModule, MatTabsModule, MatTooltipModule], }) export class AngularMaterialModule { } \ No newline at end of file