diff --git a/.gitignore b/.gitignore index fadb807b38bbbade09582f298c5ace3d94b0c031..323de17c5dfb94e304ace7a3ff8d52f5551d7d63 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ cachedKgDataset.json tmp storybook-static documentation.json + +.k8s/secret-*.yml diff --git a/.k8s/certificate-siibra-explorer.yml b/.k8s/certificate-siibra-explorer.yml new file mode 100644 index 0000000000000000000000000000000000000000..7b74dafa228951b14da53aed7ab5f13b1416e00f --- /dev/null +++ b/.k8s/certificate-siibra-explorer.yml @@ -0,0 +1,21 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: siibra-explorer-certificate +spec: + secretName: siibra-explorer-prod-secret + renewBefore: 120h + commonName: siibra-explorer.apps.tc.humanbrainproject.eu + isCA: false + privateKey: + algorithm: RSA + encoding: PKCS1 + size: 2048 + usages: + - server auth + dnsNames: + # (CHANGE ME! same as `commonName`) + - siibra-explorer.apps.tc.humanbrainproject.eu + issuerRef: + name: letsencrypt-production-issuer-1 + kind: ClusterIssuer \ No newline at end of file diff --git a/.k8s/configmap-siibra-explorer.yml b/.k8s/configmap-siibra-explorer.yml new file mode 100644 index 0000000000000000000000000000000000000000..9dd25153d1449e599ba4eedab890228be7ee096e --- /dev/null +++ b/.k8s/configmap-siibra-explorer.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +data: + HOST_PATHNAME: "/viewer" + HOSTNAME: "https://siibra-explorer.apps.tc.humanbrainproject.eu" + SIIBRA_CACHEDIR: /siibra-api-volume + HBP_DISCOVERY_URL: "https://iam.ebrains.eu/auth/realms/hbp" + REDIS_ADDR: "cache-redis-service" + V2_7_PLUGIN_URLS: "https://siibra-toolbox-jugex.apps.hbp.eu/viewer_plugin/manifest.json;https://ngpy.apps.hbp.eu/viewer_plugin/manifest.json" + LOGGER_DIR: "/sxplr-log" + +kind: ConfigMap +metadata: + name: siibra-sxplr-common diff --git a/.k8s/deployment-explorer.yml b/.k8s/deployment-explorer.yml new file mode 100644 index 0000000000000000000000000000000000000000..a70407dbd0f9ce344f39f0b1d4b786c5ec3d6e08 --- /dev/null +++ b/.k8s/deployment-explorer.yml @@ -0,0 +1,51 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: siibra-explorer + flavor: prod + name: siibra-explorer-prod-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: siibra-explorer + flavor: prod + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: siibra-explorer + flavor: prod + spec: + containers: + - envFrom: + - configMapRef: + name: siibra-sxplr-common + - secretRef: + name: siibra-sxplr-secret + image: docker-registry.ebrains.eu/siibra/siibra-explorer:master + name: siibra-explorer-prod-container + ports: + - containerPort: 8080 + protocol: TCP + resources: + limits: + cpu: "400m" + memory: 500Mi + requests: + cpu: "200m" + memory: 100Mi + volumeMounts: + - mountPath: /sxplr-log + name: log-volume + restartPolicy: Always + volumes: + - name: log-volume + persistentVolumeClaim: + claimName: log-volume-claim + diff --git a/.k8s/deployment-redis.yml b/.k8s/deployment-redis.yml new file mode 100644 index 0000000000000000000000000000000000000000..263e06258257b16c71e489d62e2b37fc9c5c0449 --- /dev/null +++ b/.k8s/deployment-redis.yml @@ -0,0 +1,33 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: sxplr-redis + role: cache + name: redis-cache-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: sxplr-redis + template: + metadata: + labels: + app: sxplr-redis + role: cache + spec: + containers: + - image: docker-registry.ebrains.eu/monitoring/redis:alpine3.17 + imagePullPolicy: IfNotPresent + name: sxplr-redis + ports: + - containerPort: 6379 + protocol: TCP + resources: + limits: + cpu: 200m + memory: 1Gi + requests: + cpu: 100m + memory: 128Mi + restartPolicy: Always diff --git a/.k8s/example-secret-siibra-explorer.yml b/.k8s/example-secret-siibra-explorer.yml new file mode 100644 index 0000000000000000000000000000000000000000..e4118f80194842f5a40ffc849eec748fd4ce2180 --- /dev/null +++ b/.k8s/example-secret-siibra-explorer.yml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Secret +metadata: + name: siibra-sxplr-secret +type: Opaque +data: + # n.b. echo -n "foobar" | base64 + # or else the new line will also be encoded, and you will + # wonder why your application does not work + OVERWRITE_API_ENDPOINT: Zm9vYmFy + HBP_CLIENTID_V2: Zm9vYmFy + HBP_CLIENTSECRET_V2: Zm9vYmFy + SXPLR_EBRAINS_IAM_SA_CLIENT_ID: Zm9vYmFy + SXPLR_EBRAINS_IAM_SA_CLIENT_SECRET: Zm9vYmFy + diff --git a/.k8s/ingress-sxplr.yml b/.k8s/ingress-sxplr.yml new file mode 100644 index 0000000000000000000000000000000000000000..a35f32f05b59844de24f5baddd8b6ac3e209db94 --- /dev/null +++ b/.k8s/ingress-sxplr.yml @@ -0,0 +1,22 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: siibra-explorer-prod-ingress + annotations: + # nginx.ingress.kubernetes.io/rewrite-target: / +spec: + tls: + - hosts: + - siibra-explorer.apps.tc.humanbrainproject.eu + secretName: siibra-explorer-prod-secret + rules: + - host: siibra-explorer.apps.tc.humanbrainproject.eu + http: + paths: + - path: /viewer + pathType: Prefix + backend: + service: + name: siibra-explorer-prod-service + port: + number: 8080 diff --git a/.k8s/pvc-log-volume.yml b/.k8s/pvc-log-volume.yml new file mode 100644 index 0000000000000000000000000000000000000000..ea0aabd8090b27ab6a5eb06d7dbf142f335d8b5a --- /dev/null +++ b/.k8s/pvc-log-volume.yml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: log-volume-claim + labels: + type: longhorn-pvc +spec: + # https://wiki.ebrains.eu/bin/view/Collabs/migration-faq/EBRAINS%20Kubernetes%20README/?srid=01ZnoA2n#HDownloadandconfigureyourkubeconfigfile + storageClassName: longhorn-1 + resources: + requests: + storage: 4Gi + accessModes: + - ReadWriteMany diff --git a/.k8s/service-explorer.yml b/.k8s/service-explorer.yml new file mode 100644 index 0000000000000000000000000000000000000000..474668de535aa5349d115ee132d0e291d936842c --- /dev/null +++ b/.k8s/service-explorer.yml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: siibra-explorer-prod-service +spec: + ports: + - port: 8080 + protocol: TCP + targetPort: 8080 + selector: + app: siibra-explorer + flavor: prod + type: ClusterIP diff --git a/.k8s/service-redis.yml b/.k8s/service-redis.yml new file mode 100644 index 0000000000000000000000000000000000000000..729063427240446a52f94d170819751c29016c97 --- /dev/null +++ b/.k8s/service-redis.yml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: cache-redis-service +spec: + ports: + - port: 6379 + protocol: TCP + targetPort: 6379 + selector: + app: sxplr-redis + type: ClusterIP diff --git a/backend/app/config.py b/backend/app/config.py index ec7bd8c3853c94cff2f11eae7c23ed3648ec7b22..bc824fd52776656cb2cbaf480f918d091a342e51 100644 --- a/backend/app/config.py +++ b/backend/app/config.py @@ -3,7 +3,6 @@ import os SESSION_SECRET = os.getenv("SESSION_SECRET", "hey joline, remind me to set a more secure session secret") HOST_PATHNAME = os.getenv("HOST_PATHNAME", "") -PORT = os.getenv("PORT") HOSTNAME = os.getenv("HOSTNAME", "http://localhost:3000") @@ -14,13 +13,10 @@ LOCAL_CDN = os.getenv("LOCAL_CDN") HBP_CLIENTID_V2 = os.getenv("HBP_CLIENTID_V2", "no hbp id") HBP_CLIENTSECRET_V2 = os.getenv("HBP_CLIENTSECRET_V2", "no hbp client secret") -HBP_DISCOVERY_URL = "https://iam.ebrains.eu/auth/realms/hbp" - BUILD_TEXT = os.getenv("BUILD_TEXT", "") # REDIS env var -REDIS_PROTO = os.getenv("REDIS_PROTO") REDIS_ADDR = os.getenv("REDIS_ADDR") or os.getenv("REDIS_RATE_LIMITING_DB_EPHEMERAL_PORT_6379_TCP_ADDR") or "localhost" REDIS_PORT = os.getenv("REDIS_PORT") or os.getenv("REDIS_RATE_LIMITING_DB_EPHEMERAL_PORT_6379_TCP_PORT") or "6379" REDIS_USERNAME = os.getenv("REDIS_USERNAME") diff --git a/backend/requirements.txt b/backend/requirements.txt index c321ab8212610d7d4f96853cc375492df8393ef8..3afe4cbd03ae7ed0d2b2714d058662f18d900518 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -5,8 +5,4 @@ uvicorn[standard] # required as a asgi itsdangerous # required for starlette session middleware httpx # required for async uvicorn redis - -# cherrypicks https://github.com/HumanBrainProject/ebrains-drive/pull/20 and https://github.com/HumanBrainProject/ebrains-drive/pull/22 -# once both merged and published, use ebrains-drive instead -git+https://github.com/xgui3783/ebrains-drive.git@tmp_fixDeleteUseIO - +ebrains-drive>=0.6.0 diff --git a/docs/releases/v2.14.1.md b/docs/releases/v2.14.1.md new file mode 100644 index 0000000000000000000000000000000000000000..7f3d1ba8fc0b77b35f2bda9ec2c676c83183e31b --- /dev/null +++ b/docs/releases/v2.14.1.md @@ -0,0 +1,12 @@ +# v2.4.1 + +## Bugfixes + +- fixed PLI best view point +- fixed font size of section header under feature +- temporarily fixed nehuba navigation panel + +## Under the hood stuff + +- updated backend requirements, removed unused environment variable definitions +- added k8s deployment config diff --git a/package.json b/package.json index 22253b1c38ba48c197710f0f78d2e34dee792e8e..12cb12804b487c93bb108c201f5c1cbd37968315 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "siibra-explorer", - "version": "2.14.0", + "version": "2.14.1", "description": "siibra-explorer - explore brain atlases. Based on humanbrainproject/nehuba & google/neuroglancer. Built with angular", "scripts": { "lint": "eslint src --ext .ts", diff --git a/src/atlasComponents/sapi/translateV3.ts b/src/atlasComponents/sapi/translateV3.ts index e1c7efa0b9a2969e217fbc56f411c01171205e82..b0e0ad5a458608a930f1eb76f739dd2aba7844c2 100644 --- a/src/atlasComponents/sapi/translateV3.ts +++ b/src/atlasComponents/sapi/translateV3.ts @@ -32,7 +32,23 @@ const TMP_META_REGISTRY: Record<string, MetaV1Schema> = { data: { type: "image/3d" }, - transform: [[7.325973427896315e-8,2.866510051546811e-8,-1,-16600000],[-0.9899035692214966,0.14174138009548187,-6.845708355740499e-8,70884888],[-0.14174138009548187,-0.9899035692214966,-3.875962661936683e-8,64064704],[0,0,0,1]] + transform: [[7.325973427896315e-8,2.866510051546811e-8,-1,-16600000],[-0.9899035692214966,0.14174138009548187,-6.845708355740499e-8,70884888],[-0.14174138009548187,-0.9899035692214966,-3.875962661936683e-8,64064704],[0,0,0,1]], + bestViewPoints: [{ + type: "enclosed", + points: [{ + type: "point", + value: [-16.625, -81.397, 42.385] + },{ + type: "point", + value: [-16.625, -65.063, -67.262] + },{ + type: "point", + value: [-16.625, 85.858, -44.395] + },{ + type: "point", + value: [-16.625, 71.157, 63.871] + }] + }], }, "https://1um.brainatlas.eu/cyto_reconstructions/ebrains_release/BB_1um/VOI_1/precomputed": { version: 1, diff --git a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html index 8036ecca8901496183703b76e28d7c62c3918b8a..25a7643b6e7315249dd34ec480b831d78dec4bdc 100644 --- a/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html +++ b/src/atlasComponents/sapiViews/core/region/region/rich/region.rich.template.html @@ -188,7 +188,7 @@ <div class="section-divider"></div> - <h2 class="sxplr-ml-2 mat-h4">Related Regions</h2> + <span class="sxplr-ml-2 mat-h4 text-muted">Features of Related Regions</span> <div class="h-carousel"> <div class="h-carousel-item-container"> diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts index 7c8c9842cdf45a91b3b18880a0991bce68f6e6d6..df15daa10962bb9b2a739d65da5535bd6c8ab8fd 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.spec.ts @@ -7,7 +7,6 @@ import { NEVER, of } from "rxjs" import { ShareModule } from "src/share" import { StateModule } from "src/state" import { MockStore, provideMockStore } from "@ngrx/store/testing" -import { By } from "@angular/platform-browser" import { NoopAnimationsModule } from "@angular/platform-browser/animations" import { FormsModule, ReactiveFormsModule } from "@angular/forms" import { UtilModule } from "src/util" @@ -15,7 +14,6 @@ import { NEHUBA_CONFIG_SERVICE_TOKEN } from "../config.service" import { QuickTourModule } from "src/ui/quickTour/module"; import { atlasSelection } from "src/state" import { SxplrTemplate } from "src/atlasComponents/sapi/sxplrTypes" -import { MatSlideToggle } from "src/sharedModules/angularMaterial.exports" import { NEHUBA_INSTANCE_INJTKN } from "../util" const mockNehubaConfig = { @@ -125,19 +123,11 @@ describe('> statusCard.component.ts', () => { setNavigationState: setNavigationStateSpy, } as any - fixture.componentInstance.statusPanelRealSpace = true fixture.componentInstance.textNavigateTo('1, 0, 0') expect(setNavigationStateSpy).toHaveBeenCalledWith({ position: [1e6, 0, 0], positionReal: true }) - - fixture.componentInstance.statusPanelRealSpace = false - fixture.componentInstance.textNavigateTo('1, 0, 0') - expect(setNavigationStateSpy).toHaveBeenCalledWith({ - position: [1, 0, 0], - positionReal: false - }) }) }) }) diff --git a/src/viewerModule/nehuba/statusCard/statusCard.component.ts b/src/viewerModule/nehuba/statusCard/statusCard.component.ts index b8e6e0817457f07fc8a16e092a032017c4a8f915..1b1f6f6f0c1b9b875b638e5f9b8a5b37cc3796f8 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.component.ts +++ b/src/viewerModule/nehuba/statusCard/statusCard.component.ts @@ -8,11 +8,11 @@ import { import { select, Store } from "@ngrx/store"; import { LoggingService } from "src/logging"; import { NehubaViewerUnit } from "../nehubaViewer/nehubaViewer.component"; -import { Observable, combineLatest } from "rxjs"; -import { map, filter, startWith, throttleTime, takeUntil, switchMap, shareReplay, debounceTime } from "rxjs/operators"; +import { Observable, concat, of } from "rxjs"; +import { map, filter, takeUntil, switchMap, shareReplay, debounceTime } from "rxjs/operators"; import { Clipboard, MatBottomSheet, MatDialog, MatSnackBar } from "src/sharedModules/angularMaterial.exports" import { ARIA_LABELS, QUICKTOUR_DESC } from 'common/constants' -import { FormControl, FormGroup, UntypedFormControl } from "@angular/forms"; +import { FormControl, FormGroup } from "@angular/forms"; import { NEHUBA_INSTANCE_INJTKN } from '../util' import { IQuickTourData } from "src/ui/quickTour/constrants"; @@ -59,20 +59,12 @@ export class StatusCardComponent { public readonly navVal$ = this.nehubaViewer$.pipe( filter(v => !!v), switchMap(nehubaViewer => - combineLatest([ - this.statusPanelRealSpace$, + concat( + of(`nehubaViewer initialising`), nehubaViewer.viewerPosInReal$.pipe( - filter(v => !!v) - ), - nehubaViewer.viewerPosInVoxel$.pipe( - filter(v => !!v) - ), - ]).pipe( - map(([realFlag, real, voxel]) => realFlag - ? real.map(v => `${ (v / 1e6).toFixed(3) }mm`).join(', ') - : voxel.map(v => v.toFixed(3)).join(', ') ), - startWith(`nehubaViewer initialising`), - throttleTime(16), + filter(v => !!v), + map(real => real.map(v => `${ (v / 1e6).toFixed(3) }mm`).join(', ')) + ) ) ), shareReplay(1), @@ -80,20 +72,13 @@ export class StatusCardComponent { public readonly mouseVal$ = this.nehubaViewer$.pipe( filter(v => !!v), switchMap(nehubaViewer => - combineLatest([ - this.statusPanelRealSpace$, + concat( + of(``), nehubaViewer.mousePosInReal$.pipe( filter(v => !!v), - ), - nehubaViewer.mousePosInVoxel$.pipe( - filter(v => !!v) - ), - ]).pipe( - map(([realFlag, real, voxel]) => realFlag - ? real.map(v => `${ (v/1e6).toFixed(3) }mm`).join(', ') - : voxel.map(v => v.toFixed(3)).join(', ')), - startWith(``), - ) + map(real => real.map(v => `${ (v/1e6).toFixed(3) }mm`).join(', ')) + ) + ), ) ) @@ -139,11 +124,6 @@ export class StatusCardComponent { ).subscribe( viewer => this.nehubaViewer = viewer ) - this.statusPanelFormCtrl.valueChanges.pipe( - takeUntil(this.#destroy$) - ).subscribe(val => { - this.statusPanelRealSpace = val - }) this.store$.pipe( select(atlasSelection.selectors.navigation) ).pipe( @@ -199,18 +179,12 @@ export class StatusCardComponent { .map(Number) } - public statusPanelFormCtrl = new UntypedFormControl(true, []) - public statusPanelRealSpace = true - public statusPanelRealSpace$ = this.statusPanelFormCtrl.valueChanges.pipe( - startWith(true) - ) - public textNavigateTo(string: string): void { if (string.split(/[\s|,]+/).length >= 3 && string.split(/[\s|,]+/).slice(0, 3).every(entry => !isNaN(Number(entry.replace(/mm/, ''))))) { - const pos = (string.split(/[\s|,]+/).slice(0, 3).map((entry) => Number(entry.replace(/mm/, '')) * (this.statusPanelRealSpace ? 1000000 : 1))) + const pos = (string.split(/[\s|,]+/).slice(0, 3).map((entry) => Number(entry.replace(/mm/, '')) * 1000000)) this.nehubaViewer.setNavigationState({ position : (pos as [number, number, number]), - positionReal : this.statusPanelRealSpace, + positionReal : true, }) } else { this.log.log('input did not parse to coordinates ', string) diff --git a/src/viewerModule/nehuba/statusCard/statusCard.template.html b/src/viewerModule/nehuba/statusCard/statusCard.template.html index 50c06ab83a0960ab5e2acc2a46e20063c902a823..3dab5a5b1a8fbd4762d35e10887a55dbcf779582 100644 --- a/src/viewerModule/nehuba/statusCard/statusCard.template.html +++ b/src/viewerModule/nehuba/statusCard/statusCard.template.html @@ -50,7 +50,7 @@ <mat-form-field class="flex-grow-1"> <mat-label> - {{ (statusPanelRealSpace$ | async) ? 'Physical Coord' : 'Voxel Coord' }} + Physical Coord </mat-label> <input type="text" matInput