├── .babelrc ├── .dockerignore ├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── docker-image-scan.yml │ ├── master.yml │ └── tags.yml ├── .gitignore ├── .travis.yml ├── .travis └── build_docker.sh ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── app ├── .htaccess ├── 404.html ├── config │ ├── default-env.json │ ├── default.json │ └── visualizer.json ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── images │ ├── loading.gif │ └── openhim-logo-green.png ├── index.html ├── robots.txt ├── scripts │ ├── app.js │ ├── controllers │ │ ├── about.js │ │ ├── auditDetails.js │ │ ├── audits.js │ │ ├── auditsContentModal.js │ │ ├── certificates.js │ │ ├── certificatesmodal.js │ │ ├── channel-tabs │ │ │ ├── alerts.js │ │ │ ├── basicInfo.js │ │ │ ├── dataControl.js │ │ │ ├── requestMatching.js │ │ │ ├── routes.js │ │ │ └── userAccess.js │ │ ├── channelMonitoring.js │ │ ├── channels.js │ │ ├── channelsmodal.js │ │ ├── clients.js │ │ ├── clientsmodal.js │ │ ├── confirmModal.js │ │ ├── contactGroups.js │ │ ├── contactGroupsModal.js │ │ ├── dashboard.js │ │ ├── exportImport.js │ │ ├── exportImportModal.js │ │ ├── forgotPassword.js │ │ ├── index.js │ │ ├── login.js │ │ ├── logs.js │ │ ├── mediatorConfigModal.js │ │ ├── mediatorDetails.js │ │ ├── mediators.js │ │ ├── monitoring.js │ │ ├── profile.js │ │ ├── setPassword.js │ │ ├── sidebar.js │ │ ├── taskDetails.js │ │ ├── tasks.js │ │ ├── transactionDetails.js │ │ ├── transactions.js │ │ ├── transactionsAddReqResModal.js │ │ ├── transactionsBodyModal.js │ │ ├── transactionsRerunModal.js │ │ ├── users.js │ │ ├── usersmodal.js │ │ ├── visualizer.js │ │ └── visualizerModal.js │ ├── directives │ │ ├── audit-operation.js │ │ ├── autofocus.js │ │ ├── footer-version.html │ │ ├── footer-version.js │ │ ├── graph-directives-morris.js │ │ ├── index.js │ │ ├── mediator-config-display.js │ │ ├── mediator-config.js │ │ ├── metrics-date-type-selector.js │ │ ├── transaction-body-downloader.js │ │ └── visualizer.js │ ├── external │ │ ├── angular-bootstrap-datetimepicker-directive.js │ │ ├── angular-taglist-directive.html │ │ └── angular-taglist-directive.js │ ├── index.js │ ├── services │ │ ├── alerting.js │ │ ├── auditLookupMap.js │ │ ├── authinterceptor.js │ │ ├── index.js │ │ ├── keycloak.js │ │ ├── login.js │ │ ├── mediatorDisplay.js │ │ ├── metrics.js │ │ ├── notify.js │ │ └── rest.js │ └── utils │ │ ├── index.js │ │ ├── sidebar.js │ │ └── utils.js ├── styles │ ├── angular-taglist-directive.css │ ├── highlightjs │ │ └── github.css │ ├── main.css │ └── morris.css ├── template.html └── views │ ├── about.html │ ├── auditDetails.html │ ├── audits.html │ ├── auditsContentModal.html │ ├── certificateModal.html │ ├── certificates.html │ ├── channelMonitoring.html │ ├── channels.html │ ├── channelsmodal.html │ ├── clients.html │ ├── clientsmodal.html │ ├── confirmModal.html │ ├── contactGroups.html │ ├── contactGroupsModal.html │ ├── dashboard.html │ ├── exportImport.html │ ├── exportImportModal.html │ ├── forgotPassword.html │ ├── login.html │ ├── logs.html │ ├── mediatorConfigModal.html │ ├── mediatorDetails.html │ ├── mediators.html │ ├── monitoring.html │ ├── partials │ ├── audit-filter-settings.html │ ├── audit-operation.html │ ├── channels-tab-alerts.html │ ├── channels-tab-basic-info.html │ ├── channels-tab-data-control.html │ ├── channels-tab-logs.html │ ├── channels-tab-request-matching.html │ ├── channels-tab-routes.html │ ├── channels-tab-user-access.html │ ├── clients-tab-authentication.html │ ├── clients-tab-basic-info.html │ ├── mediator-config-display.html │ ├── mediator-config.html │ ├── metrics-date-type-selector.html │ ├── taskDetails-filter-settings.html │ ├── tasks-filter-settings.html │ ├── transaction-filter-settings.html │ ├── user-settings-tabs.html │ └── visualizer-settings.html │ ├── profile.html │ ├── setPassword.html │ ├── sidebar.html │ ├── taskDetails.html │ ├── tasks.html │ ├── transactionDetails.html │ ├── transactions.html │ ├── transactionsAddReqResModal.html │ ├── transactionsBodyModal.html │ ├── transactionsRerunModal.html │ ├── users.html │ ├── usersmodal.html │ ├── visualizer.html │ └── visualizerModal.html ├── docker-entrypoint.sh ├── infrastructure ├── centos │ ├── Dockerfile │ └── Vagrantfile ├── development │ └── env │ │ ├── .gitignore │ │ ├── Vagrantfile │ │ └── openhim-console.pp └── docker-compose.yml ├── karma.conf.js ├── package-lock.json ├── package.json ├── packaging ├── README.md ├── build-docker-centos-rpm.sh ├── build-release-zip.sh ├── create-deb.sh └── targets │ └── trusty │ ├── debian │ ├── changelog │ ├── config │ ├── control │ ├── install │ ├── postinst │ ├── postrm │ ├── rules │ └── templates │ └── etc │ └── nginx │ └── sites-available │ └── openhim-console ├── test ├── runner.html └── spec │ ├── controllers │ ├── about.js │ ├── certificates.js │ ├── certificatesmodal.js │ ├── channelMonitoring.js │ ├── channels.js │ ├── channelsmodal.js │ ├── clients.js │ ├── clientsmodal.js │ ├── contactGroups.js │ ├── contactGroupsModal.js │ ├── dashboard.js │ ├── exportimport.js │ ├── exportimportModal.js │ ├── forgotPassword.js │ ├── login.js │ ├── logs.js │ ├── mediatorDetails.js │ ├── mediators.js │ ├── profile.js │ ├── setPassword.js │ ├── taskDetails.js │ ├── tasks.js │ ├── testAuditDetails.js │ ├── testAudits.js │ ├── transactionDetails.js │ ├── transactions.js │ ├── transactionsAddReqResModal.js │ ├── users.js │ ├── usersmodal.js │ ├── visualizer.js │ └── visualizerModal.js │ ├── directives │ └── transaction-body-downloader.js │ └── services │ ├── authinterceptor.js │ ├── login.js │ ├── notify.js │ └── rest.js ├── versionManager.js ├── webpack.common.js ├── webpack.development.js └── webpack.production.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "sourceMaps": true, 3 | "presets": [ 4 | [ 5 | "@babel/env", 6 | { 7 | "targets": { 8 | "browsers": [ 9 | "last 2 versions", 10 | "IE 9" 11 | ] 12 | } 13 | } 14 | ] 15 | ] 16 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | infrastructure 3 | packaging 4 | test 5 | Dockerfile 6 | .dockerignore 7 | .travis 8 | .travis.yml 9 | .editorconfig 10 | .gitignore 11 | .gitattributes 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /.github/workflows/docker-image-scan.yml: -------------------------------------------------------------------------------- 1 | name: Scan latest docker image 2 | 3 | on: 4 | schedule: 5 | - cron: '0 5 * * *' 6 | 7 | jobs: 8 | scan-images: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | 14 | - name: Build an image from Dockerfile 15 | run: | 16 | docker build -t jembi/openhim-console:${{ github.sha }} . 17 | 18 | - name: Run trivy vulnerability scanner for the OpenHIM console image 19 | uses: aquasecurity/trivy-action@master 20 | with: 21 | image-ref: jembi/openhim-console:${{ github.sha }} 22 | format: 'sarif' 23 | output: 'trivy-results.sarif' 24 | 25 | - name: Upload Trivy scan results to Github Security tab 26 | uses: github/codeql-action/upload-sarif@v2 27 | if: always() 28 | with: 29 | sarif_file: 'trivy-results.sarif' 30 | -------------------------------------------------------------------------------- /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: Push OpenHIM Console Docker Image On Commit To Master 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | 14 | strategy: 15 | matrix: 16 | node-version: [14.x] 17 | 18 | steps: 19 | - uses: actions/checkout@v3 20 | 21 | - name: Set up QEMU 22 | uses: docker/setup-qemu-action@v2 23 | 24 | - name: Login to Docker Hub 25 | uses: docker/login-action@v2 26 | with: 27 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 28 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 29 | 30 | - name: Set up Docker Buildx 31 | id: buildx 32 | uses: docker/setup-buildx-action@v2 33 | 34 | - name: Build and push 35 | id: docker_build 36 | uses: docker/build-push-action@v4 37 | with: 38 | context: ./ 39 | file: ./Dockerfile 40 | platforms: linux/amd64,linux/arm64 41 | push: true 42 | tags: jembi/openhim-console:latest 43 | -------------------------------------------------------------------------------- /.github/workflows/tags.yml: -------------------------------------------------------------------------------- 1 | name: Push OpenHIM Console Docker Image On Tag 2 | 3 | on: 4 | push: 5 | tags: ["*"] 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Set up QEMU 21 | uses: docker/setup-qemu-action@v2 22 | 23 | - name: Set env 24 | run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV 25 | 26 | - name: Login to Docker Hub 27 | uses: docker/login-action@v2 28 | with: 29 | username: ${{ secrets.DOCKER_HUB_USERNAME }} 30 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 31 | 32 | - name: Set up Docker Buildx 33 | id: buildx 34 | uses: docker/setup-buildx-action@v2 35 | 36 | - name: Build and push 37 | id: docker_build 38 | uses: docker/build-push-action@v4 39 | with: 40 | context: ./ 41 | file: ./Dockerfile 42 | platforms: linux/amd64,linux/arm64 43 | push: true 44 | tags: jembi/openhim-console:${{ env.RELEASE_VERSION }} 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .tmp 4 | .sass-cache 5 | .idea/** 6 | .project 7 | openhim-webapp-2.sublime-project 8 | openhim-webapp-2.sublime-workspace 9 | packaging/.gnupg 10 | packaging/.npm 11 | packaging/builds/* 12 | packaging/.gitconfig 13 | packaging/Vagrantfile 14 | \#* 15 | *~ 16 | .#* 17 | \#*\# 18 | **/.vagrant 19 | npm-debug.log 20 | .vscode 21 | .vagrant -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | before_script: npm run build:prod 3 | after_success: 4 | - npm run coverage 5 | - '.travis/build_docker.sh' 6 | node_js: 7 | - 'lts/gallium' 8 | sudo: false 9 | notifications: 10 | slack: 11 | rooms: 12 | - jembihealthsystems:mlQYVFbijxcZkesCt7G5VBoM 13 | on_success: change 14 | on_failure: always 15 | -------------------------------------------------------------------------------- /.travis/build_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 4 | curl -H 'Content-Type: application/json' --data '{"source_type": "Branch", "source_name": "console"}' -X POST https://registry.hub.docker.com/u/jembi/openhim-console/trigger/f4e50fb6-7d96-4668-84bb-b456ba4fab6e/ 5 | elif [ "$TRAVIS_BRANCH" == "test" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 6 | curl -H 'Content-Type: application/json' --data '{"docker_tag": "test"}' -X POST https://registry.hub.docker.com/u/jembi/openhim-console/trigger/f4e50fb6-7d96-4668-84bb-b456ba4fab6e/ 7 | elif [ "$TRAVIS_BRANCH" == "staging" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 8 | curl -H 'Content-Type: application/json' --data '{"docker_tag": "staging"}' -X POST https://registry.hub.docker.com/u/jembi/openhim-console/trigger/f4e50fb6-7d96-4668-84bb-b456ba4fab6e/ 9 | else 10 | echo "Docker image will only be built for commits to master/test/staging" 11 | fi 12 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## *v1.13.2 / 2019-09-16* 4 | 5 | * Fixed user and channel permissions issue 6 | 7 | ## *v1.13.1 / 2019-09-05* 8 | 9 | * Update Dependencies 10 | 11 | ## *v1.13.0 / 2019-02-04* 12 | 13 | ## Final release 14 | 15 | ## *v1.13.0-rc.2 / 2019-02-01* 16 | 17 | ## Release candidate with bug fix and new script 18 | 19 | ### Bug Fix 20 | 21 | * Fixed role deselection on edit client modal 22 | 23 | ### New Script 24 | 25 | * Build CentOS RPM package via docker 26 | 27 | --- 28 | 29 | ## *v1.13.0-rc.1 / 2019-01-22* 30 | 31 | ## Release candidate with various bug fixes / code refactoring and dependency upgrades 32 | 33 | ### Bug Fixes 34 | 35 | * Remove width limit on multi-select that caused truncated inputs 36 | * Fixed tool-tips with HTML elements that weren't being rendered on hover 37 | * Update webpack to hot reload when changes are made to the scripts 38 | * Fix graph missing labels by disabling additional minification 39 | * Fix show reruns button in transactions 40 | * Fix the redirect to rerun transactions from the popover 41 | 42 | ### Code Cleanup 43 | 44 | * Fix broken Channel test 45 | * Update tests for dependency upgrades 46 | * Remove console logs 47 | * Fix channel success message wording 48 | 49 | ### Updates/Upgrades 50 | 51 | * Update dependency minor versions and patches for security updates 52 | * Upgrade dependency major versions where possible 53 | 54 | ### Internal Refactoring 55 | 56 | * Separate out basicInfo, routes, dataControl, alerts, requestMatching and userAccess controllers from the channelsModal controller into their own files 57 | 58 | ### Enhancements 59 | 60 | * Add icon to transaction log to indicate that a transaction is a rerun 61 | * Travis CI runs console against Node Carbon, Dubnium and latest with code coverage 62 | * Travis CI build status is posted to Jembi slack channel 63 | 64 | --- 65 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build Production Console in Node 2 | FROM node:14.17-alpine as build 3 | 4 | RUN apk add git 5 | 6 | WORKDIR /app 7 | 8 | COPY . . 9 | 10 | RUN npm install 11 | 12 | RUN npm run prepare 13 | 14 | # Serve built project with nginx 15 | FROM nginx:mainline-alpine 16 | 17 | WORKDIR /usr/share/nginx/html 18 | 19 | COPY --from=build /app/dist ./ 20 | 21 | COPY ./docker-entrypoint.sh /usr/local/bin/ 22 | 23 | RUN chmod +x /usr/local/bin/docker-entrypoint.sh 24 | 25 | ENTRYPOINT [ "/bin/sh", "/usr/local/bin/docker-entrypoint.sh" ] 26 | -------------------------------------------------------------------------------- /app/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page Not Found :( 6 | 141 | 142 | 143 |
144 |

Not found :(

145 |

Sorry, but the page you were trying to view does not exist.

146 |

It looks like this was the result of either:

147 | 151 | 154 | 155 |
156 | 157 | 158 | -------------------------------------------------------------------------------- /app/config/default-env.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.18.2", 3 | "minimumCoreVersion": "5.2.0", 4 | "protocol": "${OPENHIM_CONSOLE_PROTOCOL}", 5 | "host": "${OPENHIM_CORE_MEDIATOR_HOSTNAME}", 6 | "hostPath": "${OPENHIM_CONSOLE_HOSTPATH}", 7 | "port": "${OPENHIM_MEDIATOR_API_PORT}", 8 | "title": "Admin Console", 9 | "footerTitle": "OpenHIM Administration Console", 10 | "footerPoweredBy": "Powered by OpenHIM", 11 | "loginBanner": "", 12 | "mediatorLastHeartbeatWarningSeconds": ${OPENHIM_MEDIATOR_HEALTH_WARNING_TIMEOUT}, 13 | "mediatorLastHeartbeatDangerSeconds": ${OPENHIM_MEDIATOR_HEALTH_DANGER_TIMEOUT}, 14 | "showLoginForm": ${OPENHIM_CONSOLE_SHOW_LOGIN}, 15 | "ssoEnabled": ${KC_OPENHIM_SSO_ENABLED}, 16 | "keyCloakUrl": "${KC_FRONTEND_URL}", 17 | "keyCloakRealm": "${KC_REALM_NAME}", 18 | "keyCloakClientId": "${KC_OPENHIM_CLIENT_ID}" 19 | } 20 | -------------------------------------------------------------------------------- /app/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.18.2", 3 | "minimumCoreVersion": "5.0.0", 4 | "protocol": "https", 5 | "host": "localhost", 6 | "hostPath": "", 7 | "port": 8080, 8 | "title": "Admin Console", 9 | "footerTitle": "OpenHIM Administration Console", 10 | "footerPoweredBy": "Powered by OpenHIM", 11 | "loginBanner": "", 12 | "mediatorLastHeartbeatWarningSeconds": 60, 13 | "mediatorLastHeartbeatDangerSeconds": 120, 14 | "showLoginForm": true, 15 | "ssoEnabled": false, 16 | "keyCloakUrl": "http://localhost:9088", 17 | "keyCloakRealm": "platform-realm", 18 | "keyCloakClientId": "openhim-oauth" 19 | } 20 | -------------------------------------------------------------------------------- /app/config/visualizer.json: -------------------------------------------------------------------------------- 1 | { 2 | "components": [], 3 | "channels": [], 4 | "mediators": [], 5 | "color": { 6 | "inactive": "#cccccc", 7 | "active": "#4cae4c", 8 | "error": "#d43f3a", 9 | "text": "#000000" 10 | }, 11 | "size": { 12 | "responsive": true, 13 | "width": 1000, 14 | "height": 400, 15 | "padding": 20 16 | }, 17 | "time": { 18 | "updatePeriod": 200, 19 | "minDisplayPeriod": 500, 20 | "maxSpeed": 5, 21 | "maxTimeout": 5000 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jembi/openhim-console/75c19817179623aa4d8950b80ffe2d3398bf1141/app/favicon.ico -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jembi/openhim-console/75c19817179623aa4d8950b80ffe2d3398bf1141/app/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jembi/openhim-console/75c19817179623aa4d8950b80ffe2d3398bf1141/app/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /app/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jembi/openhim-console/75c19817179623aa4d8950b80ffe2d3398bf1141/app/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /app/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jembi/openhim-console/75c19817179623aa4d8950b80ffe2d3398bf1141/app/images/loading.gif -------------------------------------------------------------------------------- /app/images/openhim-logo-green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jembi/openhim-console/75c19817179623aa4d8950b80ffe2d3398bf1141/app/images/openhim-logo-green.png -------------------------------------------------------------------------------- /app/robots.txt: -------------------------------------------------------------------------------- 1 | # robotstxt.org 2 | 3 | User-agent: * 4 | Disallow: / 5 | -------------------------------------------------------------------------------- /app/scripts/controllers/about.js: -------------------------------------------------------------------------------- 1 | import { isCoreVersionCompatible, getTimeForTimezone } from '../utils' 2 | 3 | export function AboutCtrl ($scope, $interval, Api, Alerting, config) { 4 | $scope.aboutInfo = {} 5 | const success = function (result) { 6 | $scope.aboutInfo = result 7 | buildAboutInfoObject() 8 | 9 | $scope.updateTime(result.serverTimezone) 10 | $scope.clock = $interval(function () { 11 | $scope.updateTime(result.serverTimezone) 12 | }, 1000) 13 | } 14 | 15 | const error = function (err) { 16 | Alerting.AlertAddServerMsg(err.status) 17 | } 18 | 19 | Api.About.get(success, error) 20 | 21 | $scope.updateTime = function (timezone) { 22 | $scope.aboutInfo.serverTime = getTimeForTimezone(timezone) 23 | } 24 | 25 | const buildAboutInfoObject = function () { 26 | $scope.aboutInfo.currentConsoleVersion = config.version 27 | $scope.aboutInfo.minimumCoreVersion = config.minimumCoreVersion 28 | 29 | const maxCoreMajorVersion = parseInt(config.minimumCoreVersion.split('.')[0]) + 1 30 | $scope.aboutInfo.maximumCoreVersion = maxCoreMajorVersion + '.0.0' 31 | 32 | $scope.aboutInfo.compatible = isCoreVersionCompatible($scope.aboutInfo.minimumCoreVersion, $scope.aboutInfo.currentCoreVersion) 33 | } 34 | 35 | $scope.$on('$destroy', function () { 36 | if (angular.isDefined($scope.clock)) { 37 | $interval.cancel($scope.clock) 38 | } 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /app/scripts/controllers/auditDetails.js: -------------------------------------------------------------------------------- 1 | import auditsContentModal from '~/views/auditsContentModal' 2 | import { AuditsContentModalCtrl } from './' 3 | 4 | export function AuditDetailsCtrl ($scope, $uibModal, $location, $routeParams, Api, Alerting, AuditLookups) { 5 | /***************************************************/ 6 | /** Initial page load functions **/ 7 | /***************************************************/ 8 | 9 | // setup audit lookup objects 10 | $scope.eventActionMap = AuditLookups.eventActionMap() 11 | $scope.eventOutcomeMap = AuditLookups.eventOutcomeMap() 12 | 13 | const querySuccess = function (auditDetails) { 14 | $scope.auditDetails = auditDetails 15 | } 16 | 17 | const queryError = function (err) { 18 | // on error - add server error alert 19 | Alerting.AlertAddServerMsg(err.status) 20 | } 21 | 22 | // get the Data for the supplied ID and store in 'auditsDetails' object 23 | Api.Audits.get({ auditId: $routeParams.auditId }, querySuccess, queryError) 24 | 25 | /***************************************************/ 26 | /** Initial page load functions **/ 27 | /***************************************************/ 28 | 29 | /*******************************************************************/ 30 | /** Transactions List and Detail view functions **/ 31 | /*******************************************************************/ 32 | 33 | // setup filter options 34 | $scope.returnFilterObject = function () { 35 | const filtersObject = {} 36 | 37 | filtersObject.filterPage = 0 38 | filtersObject.filterLimit = 0 39 | filtersObject.parentID = $routeParams.auditId 40 | return filtersObject 41 | } 42 | 43 | // location provider - load audit details 44 | $scope.viewTransactionDetails = function (path) { 45 | // do audits details redirection when clicked on TD 46 | $location.path(path) 47 | } 48 | 49 | /*******************************************************************/ 50 | /** Transactions List and Detail view functions **/ 51 | /*******************************************************************/ 52 | 53 | /********************************************************************/ 54 | /** Transactions View Body Functions **/ 55 | /********************************************************************/ 56 | 57 | $scope.viewContentDetails = function (type, content) { 58 | $uibModal.open({ 59 | template: auditsContentModal, 60 | controller: AuditsContentModalCtrl, 61 | resolve: { 62 | auditData: function () { 63 | return { type, content } 64 | } 65 | } 66 | }) 67 | } 68 | 69 | /********************************************************************/ 70 | /** Transactions View Body Functions **/ 71 | /********************************************************************/ 72 | } 73 | -------------------------------------------------------------------------------- /app/scripts/controllers/auditsContentModal.js: -------------------------------------------------------------------------------- 1 | import { isBase64String, decodeBase64, beautifyIndent } from '../utils' 2 | 3 | export function AuditsContentModalCtrl ($scope, $uibModalInstance, auditData) { 4 | $scope.auditData = auditData 5 | 6 | // is content view raw audit message 7 | if (auditData.type === 'Raw Audit Message') { 8 | // transform body with indentation/formatting 9 | const bodyTransform = beautifyIndent('application/xml', auditData.content) 10 | $scope.auditData.content = bodyTransform.content 11 | $scope.bodyTransformLang = bodyTransform.lang 12 | } else { 13 | if (isBase64String(auditData.content)) { 14 | $scope.auditData.content = decodeBase64(auditData.content) 15 | } 16 | } 17 | 18 | $scope.cancel = function () { 19 | $uibModalInstance.dismiss('cancel') 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/scripts/controllers/channel-tabs/basicInfo.js: -------------------------------------------------------------------------------- 1 | import { getTimeForTimezone, getTimezoneOffset } from '../../utils' 2 | 3 | export function channelBasicInfoCtrl ($scope, $timeout, $interval, Api, Notify, Alerting) { 4 | const updateTime = function (timezone) { 5 | $scope.aboutInfo.serverTime = getTimeForTimezone(timezone) 6 | } 7 | 8 | const success = function (result) { 9 | $scope.aboutInfo = result 10 | $scope.aboutInfo.serverTimezoneOffset = getTimezoneOffset(result.serverTimezone) 11 | updateTime(result.serverTimezone) 12 | $scope.clock = $interval(function () { 13 | updateTime(result.serverTimezone) 14 | }, 1000) 15 | } 16 | 17 | const error = function (err) { 18 | Alerting.AlertAddServerMsg(err.status) 19 | } 20 | 21 | Api.About.get(success, error) 22 | 23 | const setUrlPattern = function () { 24 | $scope.channel.$promise.then(function () { 25 | // check if urlPattern has regex delimiters 26 | const urlPatternLength = $scope.channel.urlPattern.length 27 | if ($scope.channel.urlPattern.indexOf('^') === 0 && $scope.channel.urlPattern.indexOf('$') === urlPatternLength - 1) { 28 | const urlPattern = $scope.channel.urlPattern 29 | // remove delimiters 30 | $scope.channel.urlPattern = urlPattern.slice(1, -1) 31 | } else { 32 | // update checkbox if no regex delimiters 33 | $scope.urlPattern.regex = false 34 | } 35 | }) 36 | } 37 | 38 | // Mannually Trigger Polling Channels 39 | $scope.manuallyTriggerChannel = function () { 40 | Alerting.AlertReset('manualTrigger') 41 | Api.TriggerPollingChannels.save({ channelId: $scope.channel._id }, { _id: $scope.channel._id }, function () { 42 | Alerting.AlertAddMsg('manualTrigger', 'success', 'Channel Triggered') 43 | $timeout(function () { 44 | Alerting.AlertReset('manualTrigger') 45 | }, 5000) 46 | }) 47 | } 48 | 49 | // if update/channelDuplicate is true 50 | if ($scope.update || $scope.channelDuplicate) { 51 | setUrlPattern() 52 | } else { 53 | // set default options if new channel 54 | $scope.channel.type = 'http' 55 | $scope.channel.authType = 'private' 56 | $scope.channel.status = 'enabled' 57 | } 58 | 59 | $scope.$on('$destroy', function () { 60 | if (angular.isDefined($scope.clock)) { 61 | $interval.cancel($scope.clock) 62 | } 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /app/scripts/controllers/channel-tabs/requestMatching.js: -------------------------------------------------------------------------------- 1 | export function channelRequestMatchingCtrl ($scope, Api) { 2 | // object for the taglist roles 3 | $scope.taglistClientRoleOptions = [] 4 | $scope.taglistUserRoleOptions = [] 5 | 6 | // watch parent scope for 'users' change 7 | $scope.$watch('users', function () { 8 | // setup user groups taglist options 9 | angular.forEach($scope.users, function (user) { 10 | angular.forEach(user.groups, function (group) { 11 | if ($scope.taglistUserRoleOptions.indexOf(group) === -1) { 12 | $scope.taglistUserRoleOptions.push(group) 13 | } 14 | }) 15 | }) 16 | }) 17 | 18 | // get the roles for the client taglist option 19 | Api.Clients.query(function (clients) { 20 | angular.forEach(clients, function (client) { 21 | if ($scope.taglistClientRoleOptions.indexOf(client.clientID) === -1) { 22 | $scope.taglistClientRoleOptions.push(client.clientID) 23 | } 24 | angular.forEach(client.roles, function (role) { 25 | if ($scope.taglistClientRoleOptions.indexOf(role) === -1) { 26 | $scope.taglistClientRoleOptions.push(role) 27 | } 28 | }) 29 | }) 30 | }, 31 | function () { /* server error - could not connect to API to get clients */ }) 32 | 33 | // if update is true 34 | if ($scope.update) { 35 | $scope.channel.$promise.then(function () { 36 | if ($scope.channel.matchContentRegex) { $scope.matching.contentMatching = 'RegEx matching' } 37 | if ($scope.channel.matchContentJson) { $scope.matching.contentMatching = 'JSON matching' } 38 | if ($scope.channel.matchContentXpath) { $scope.matching.contentMatching = 'XML matching' } 39 | 40 | if ($scope.channel.matchContentRegex || $scope.channel.matchContentJson || $scope.channel.matchContentXpath) { 41 | $scope.matching.showRequestMatching = true 42 | } 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/scripts/controllers/channel-tabs/userAccess.js: -------------------------------------------------------------------------------- 1 | export function channelUserAccessCtrl ($scope) { 2 | // object for the taglist roles 3 | $scope.taglistUserRoleOptions = [] 4 | 5 | // watch parent scope for 'users' change 6 | $scope.$watch('users', function () { 7 | // setup user groups taglist options 8 | angular.forEach($scope.users, function (user) { 9 | angular.forEach(user.groups, function (group) { 10 | if ($scope.taglistUserRoleOptions.indexOf(group) === -1) { 11 | $scope.taglistUserRoleOptions.push(group) 12 | } 13 | }) 14 | }) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /app/scripts/controllers/confirmModal.js: -------------------------------------------------------------------------------- 1 | export function ConfirmModalCtrl ($scope, $uibModalInstance, confirmObject) { 2 | $scope.confirmObject = confirmObject 3 | 4 | $scope.confirmed = function () { 5 | $uibModalInstance.close() 6 | } 7 | 8 | $scope.cancelled = function () { 9 | $uibModalInstance.dismiss('cancel') 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/scripts/controllers/contactGroups.js: -------------------------------------------------------------------------------- 1 | import contactGroupsModal from '~/views/contactGroupsModal' 2 | import confirmModal from '~/views/confirmModal' 3 | import { ContactGroupsModalCtrl, ConfirmModalCtrl } from './' 4 | 5 | export function ContactGroupsCtrl ($scope, $uibModal, Api, Alerting) { 6 | /************************************************/ 7 | /** Initial load & onChanged **/ 8 | /************************************************/ 9 | const querySuccess = function (contactGroups) { 10 | $scope.contactGroups = contactGroups 11 | if (contactGroups.length === 0) { 12 | Alerting.AlertAddMsg('bottom', 'warning', 'There are currently no contact lists created') 13 | } 14 | } 15 | 16 | const queryError = function (err) { 17 | // on error - add server error alert 18 | Alerting.AlertAddServerMsg(err.status) 19 | } 20 | 21 | // do the initial request 22 | Api.ContactGroups.query(querySuccess, queryError) 23 | 24 | $scope.$on('contactGroupChanged', function () { 25 | Api.ContactGroups.query(querySuccess, queryError) 26 | }) 27 | /************************************************/ 28 | /** Initial load & onChanged **/ 29 | /************************************************/ 30 | 31 | /**********************************************************/ 32 | /** Add/edit contactGroups popup modal **/ 33 | /**********************************************************/ 34 | $scope.addContactGroup = function () { 35 | Alerting.AlertReset() 36 | $uibModal.open({ 37 | template: contactGroupsModal, 38 | controller: ContactGroupsModalCtrl, 39 | resolve: { 40 | contactGroup: function () { 41 | } 42 | } 43 | }) 44 | } 45 | 46 | $scope.editContactGroup = function (contactGroup) { 47 | Alerting.AlertReset() 48 | 49 | $uibModal.open({ 50 | template: contactGroupsModal, 51 | controller: ContactGroupsModalCtrl, 52 | resolve: { 53 | contactGroup: function () { 54 | return contactGroup 55 | } 56 | } 57 | }) 58 | } 59 | /**********************************************************/ 60 | /** Add/edit contactGroups popup modal **/ 61 | /**********************************************************/ 62 | 63 | /*******************************************/ 64 | /** Delete Confirmation **/ 65 | /*******************************************/ 66 | const deleteSuccess = function () { 67 | // On success 68 | $scope.contactGroups = Api.ContactGroups.query() 69 | Alerting.AlertAddMsg('top', 'success', 'The contact list has been deleted successfully') 70 | } 71 | 72 | const deleteError = function (err) { 73 | if (err.status === 409) { 74 | let warningMessage = 'Could not delete the contact list because it is associated with the following channels: ' 75 | for (let i = 0; i < err.data.length; i++) { 76 | if (i > 0) { 77 | warningMessage += ', ' 78 | } 79 | warningMessage += err.data[i].name 80 | } 81 | Alerting.AlertAddMsg('top', 'warning', warningMessage) 82 | } else { 83 | // add the error message 84 | Alerting.AlertAddMsg('top', 'danger', 'An error has occurred while deleting the contact list: #' + err.status + ' - ' + err.data) 85 | } 86 | } 87 | 88 | $scope.confirmDelete = function (contactGroup) { 89 | Alerting.AlertReset() 90 | 91 | const deleteObject = { 92 | title: 'Delete Contact Group', 93 | button: 'Delete', 94 | message: 'Are you sure you wish to delete the Contact list "' + contactGroup.group + '"?' 95 | } 96 | 97 | const modalInstance = $uibModal.open({ 98 | template: confirmModal, 99 | controller: ConfirmModalCtrl, 100 | resolve: { 101 | confirmObject: function () { 102 | return deleteObject 103 | } 104 | } 105 | }) 106 | 107 | modalInstance.result.then(function () { 108 | // Delete confirmed - delete the contact group 109 | contactGroup.$remove(deleteSuccess, deleteError) 110 | }, function () { 111 | // delete cancelled - do nothing 112 | }) 113 | } 114 | 115 | /*******************************************/ 116 | /** Delete Confirmation **/ 117 | /*******************************************/ 118 | } 119 | -------------------------------------------------------------------------------- /app/scripts/controllers/forgotPassword.js: -------------------------------------------------------------------------------- 1 | export function ForgotPasswordCtrl ($scope, $location, Alerting, Api) { 2 | $scope.userEmail = '' 3 | $scope.showFormCtrl = true 4 | $scope.linkUserEmail = '' 5 | 6 | $scope.$watch('userEmail', function (newVal, oldVal) { 7 | if (newVal || newVal !== oldVal) { 8 | $scope.linkUserEmail = '?email=' + newVal 9 | } 10 | }) 11 | 12 | if ($location.search().email) { 13 | $scope.userEmail = $location.search().email 14 | } 15 | 16 | $scope.submitRequest = function () { 17 | // reset alert object 18 | Alerting.AlertReset() 19 | const userEmail = $scope.userEmail 20 | 21 | if (!userEmail) { 22 | Alerting.AlertAddMsg('forgotPassword', 'danger', 'Please provide your email address') 23 | } else if (userEmail === 'root@openhim.org') { 24 | Alerting.AlertAddMsg('forgotPassword', 'danger', 'Cannot reset password for "root@openhim.org"') 25 | } else { 26 | Alerting.AlertAddMsg('forgotPassword', 'warning', 'Busy checking your credentials...') 27 | 28 | // send request to API - create token/expiry for email user 29 | Api.UserPasswordResetRequest.get({ email: userEmail }, function () { 30 | Alerting.AlertReset() 31 | Alerting.AlertAddMsg('forgotPassword', 'info', 'Password reset email has been sent...') 32 | $scope.showFormCtrl = false 33 | }, function (err) { 34 | Alerting.AlertReset() 35 | if (err.status === 404) { 36 | Alerting.AlertAddMsg('forgotPassword', 'danger', 'Could not authenticate email address') 37 | } else { 38 | Alerting.AlertAddMsg('forgotPassword', 'danger', 'An error occurred while trying to request a password reset. Please contact your system administrator') 39 | } 40 | }) 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/scripts/controllers/index.js: -------------------------------------------------------------------------------- 1 | export * from './about' 2 | export * from './auditDetails' 3 | export * from './auditsContentModal' 4 | export * from './audits' 5 | export * from './certificates' 6 | export * from './certificatesmodal' 7 | export * from './channelMonitoring' 8 | export * from './channels' 9 | export * from './channelsmodal' 10 | export * from './clients' 11 | export * from './clientsmodal' 12 | export * from './confirmModal' 13 | export * from './contactGroups' 14 | export * from './contactGroupsModal' 15 | export * from './dashboard' 16 | export * from './exportImport' 17 | export * from './exportImportModal' 18 | export * from './forgotPassword' 19 | export * from './login' 20 | export * from './logs' 21 | export * from './mediatorConfigModal' 22 | export * from './mediatorDetails' 23 | export * from './mediators' 24 | export * from './monitoring' 25 | export * from './profile' 26 | export * from './setPassword' 27 | export * from './sidebar' 28 | export * from './taskDetails' 29 | export * from './tasks' 30 | export * from './transactionDetails' 31 | export * from './transactionsAddReqResModal' 32 | export * from './transactionsBodyModal' 33 | export * from './transactions' 34 | export * from './transactionsRerunModal' 35 | export * from './users' 36 | export * from './usersmodal' 37 | export * from './visualizer' 38 | export * from './visualizerModal' 39 | 40 | export * from './channel-tabs/basicInfo' 41 | export * from './channel-tabs/requestMatching' 42 | export * from './channel-tabs/userAccess' 43 | export * from './channel-tabs/routes' 44 | export * from './channel-tabs/dataControl' 45 | export * from './channel-tabs/alerts' 46 | -------------------------------------------------------------------------------- /app/scripts/controllers/logs.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export function LogsCtrl ($scope, $location, Api) { 4 | function formatLog (log) { 5 | return moment(log.timestamp).format('YYYY-MM-DD HH:mm:ss.SSS') + ' - ' + log.level + ': [' + log.label + '] ' + log.message + '\n' 6 | } 7 | 8 | let lastFetch 9 | const locParams = $location.search() 10 | $scope.params = angular.equals({}, locParams) ? { level: 'info' } : locParams 11 | $scope.logs = '' 12 | let linesAdded = 0 13 | 14 | if ($scope.params.from || $scope.params.until) { 15 | $scope.autoupdate = false 16 | $scope.tailLogs = false 17 | } else { 18 | $scope.autoupdate = true 19 | $scope.tailLogs = true 20 | } 21 | 22 | // fetch initial logs 23 | $scope.logs = '' 24 | lastFetch = new Date() 25 | 26 | let fromDate, untilDate 27 | if ($scope.params.from && $scope.params.from.length > 0) { 28 | fromDate = moment($scope.params.from).format() 29 | } 30 | if ($scope.params.until && $scope.params.until.length > 0) { 31 | untilDate = moment($scope.params.until).format() 32 | } 33 | 34 | const localParam = { 35 | from: fromDate, 36 | until: untilDate, 37 | level: $scope.params.level 38 | } 39 | if (!$scope.params.until) { 40 | localParam.until = lastFetch.toISOString() 41 | } 42 | Api.Logs.query(localParam, function (results) { 43 | results.forEach(function (log) { 44 | $scope.logs += formatLog(log) 45 | }) 46 | }) 47 | 48 | function fetchMoreLogs () { 49 | const now = new Date() 50 | const localParam = { 51 | from: new Date(lastFetch).toISOString(), 52 | until: now.toISOString(), 53 | level: $scope.params.level 54 | } 55 | lastFetch = now 56 | Api.Logs.query(localParam, function (results) { 57 | results.forEach(function (log) { 58 | $scope.logs += formatLog(log) 59 | linesAdded++ 60 | if (linesAdded > 10000) { 61 | // Prevent logs string from growing too much, remove from front 62 | $scope.logs = $scope.logs.substring($scope.logs.indexOf('\n') + 1) 63 | } 64 | }) 65 | }) 66 | } 67 | 68 | const autoUpdateInterval = setInterval(function () { 69 | if ($scope.autoupdate) { 70 | fetchMoreLogs() 71 | } 72 | }, 1000) 73 | 74 | const scrollInterval = setInterval(function () { 75 | if ($scope.tailLogs === true) { 76 | const textarea = document.getElementById('textarea') 77 | // scroll to bottom 78 | textarea.scrollTop = textarea.scrollHeight 79 | } 80 | }, 50) 81 | 82 | $scope.reload = function () { 83 | // sync params with location, this reloads the controller 84 | $location.search($scope.params) 85 | } 86 | 87 | $scope.$on('$locationChangeStart', function () { 88 | // clear existing intervals whenever the location is changed 89 | clearInterval(autoUpdateInterval) 90 | clearInterval(scrollInterval) 91 | }) 92 | 93 | $scope.reset = function () { 94 | delete $scope.params.from 95 | delete $scope.params.until 96 | $scope.params.level = 'info' 97 | $scope.reload() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/scripts/controllers/mediatorConfigModal.js: -------------------------------------------------------------------------------- 1 | export function MediatorConfigModalCtrl ($rootScope, $scope, $uibModalInstance, $timeout, Api, Notify, Alerting, mediator) { 2 | $scope.mediator = Api.Mediators.get({ urn: mediator.urn }, function () { 3 | if (!$scope.mediator.config) { 4 | $scope.mediator.config = {} 5 | } 6 | }) 7 | 8 | function notifyUser () { 9 | Notify.notify('mediatorConfigChanged') 10 | $uibModalInstance.close() 11 | }; 12 | 13 | function success () { 14 | Alerting.AlertAddMsg('top', 'success', 'The mediator configuration was updated successfully') 15 | notifyUser() 16 | }; 17 | 18 | function error (err) { 19 | Alerting.AlertAddMsg('top', 'danger', 'An error has occurred while saving the mediator config: #' + err.status + ' - ' + err.data) 20 | notifyUser() 21 | }; 22 | 23 | $scope.saveConfig = function () { 24 | new Api.MediatorConfig($scope.mediator.config).$update({ urn: $scope.mediator.urn }, success, error) 25 | } 26 | 27 | $scope.cancel = function () { 28 | $uibModalInstance.dismiss('cancel') 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/scripts/controllers/mediatorDetails.js: -------------------------------------------------------------------------------- 1 | import mediatorConfigModal from '~/views/mediatorConfigModal' 2 | import { MediatorConfigModalCtrl } from './' 3 | 4 | export function MediatorDetailsCtrl ($rootScope, $scope, $uibModal, $location, $routeParams, Api, Alerting, MediatorDisplay) { 5 | const createParamDefMap = function (mediator) { 6 | const map = {} 7 | if (mediator.config) { 8 | Object.keys(mediator.config).map(function (param) { 9 | map[param] = mediator.configDefs.filter(function (def) { 10 | return def.param === param 11 | })[0] 12 | }) 13 | } 14 | return map 15 | } 16 | 17 | const querySuccess = function (mediatorDetails) { 18 | MediatorDisplay.formatMediator(mediatorDetails) 19 | $scope.mediatorDetails = mediatorDetails 20 | $scope.mediatorDefsMap = createParamDefMap(mediatorDetails) 21 | } 22 | 23 | const queryError = function (err) { 24 | // on error - add server error alert 25 | Alerting.AlertAddServerMsg(err.status) 26 | } 27 | 28 | $scope.$on('mediatorConfigChanged', function () { 29 | Api.Mediators.get({ urn: $routeParams.urn }, querySuccess, queryError) 30 | }) 31 | 32 | // get the Data for the supplied ID and store in 'mediatorDetails' object 33 | Api.Mediators.get({ urn: $routeParams.urn }, querySuccess, queryError) 34 | 35 | $scope.editMediatorConfig = function () { 36 | Alerting.AlertReset() 37 | 38 | $uibModal.open({ 39 | template: mediatorConfigModal, 40 | controller: MediatorConfigModalCtrl, 41 | resolve: { 42 | mediator: function () { 43 | return $scope.mediatorDetails 44 | } 45 | } 46 | }) 47 | } 48 | 49 | $scope.addChannel = function (channelName) { 50 | Alerting.AlertReset('top') 51 | Api.MediatorChannels.save({ urn: $routeParams.urn }, [channelName], function () { 52 | Alerting.AlertAddMsg('top', 'success', 'Successfully installed mediator channel') 53 | }, function () { 54 | Alerting.AlertAddMsg('top', 'danger', 'Oops, something went wrong. Could not install mediator channel.') 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/scripts/controllers/mediators.js: -------------------------------------------------------------------------------- 1 | import confirmModal from '~/views/confirmModal' 2 | import mediatorConfigModal from '~/views/mediatorConfigModal' 3 | import { ConfirmModalCtrl, MediatorConfigModalCtrl } from './' 4 | 5 | export function MediatorsCtrl ($scope, $uibModal, $location, Api, Alerting, MediatorDisplay) { 6 | /******************************************************************/ 7 | /** These are the functions for the Mediators initial load **/ 8 | /******************************************************************/ 9 | 10 | const querySuccess = function (mediators) { 11 | $scope.mediators = mediators 12 | if (mediators.length === 0) { 13 | Alerting.AlertAddMsg('bottom', 'warning', 'There are currently no mediators created') 14 | } else { 15 | MediatorDisplay.formatMediators(mediators) 16 | } 17 | } 18 | 19 | const queryError = function (err) { 20 | // on error - add server error alert 21 | Alerting.AlertAddServerMsg(err.status) 22 | } 23 | 24 | // do the initial request 25 | Api.Mediators.query(querySuccess, queryError) 26 | 27 | /******************************************************************/ 28 | /** These are the functions for the Mediators initial load **/ 29 | /******************************************************************/ 30 | 31 | // location provider - load transaction details 32 | $scope.viewMediatorDetails = function (path, $event) { 33 | // do mediators details redirection when clicked on TD 34 | if ($event.target.tagName === 'TD') { 35 | $location.path(path) 36 | } 37 | } 38 | 39 | /***********************************/ 40 | /** Delete Mediator Functions **/ 41 | /***********************************/ 42 | 43 | const deleteSuccess = function () { 44 | // On success 45 | $scope.mediators = Api.Mediators.query(querySuccess, queryError) 46 | Alerting.AlertAddMsg('top', 'success', 'The Mediator has been deleted successfully') 47 | } 48 | 49 | const deleteError = function (err) { 50 | // add the error message 51 | Alerting.AlertAddMsg('top', 'danger', 'An error has occurred while deleting the Mediator: #' + err.status + ' - ' + err.data) 52 | } 53 | 54 | $scope.confirmDelete = function (mediator) { 55 | Alerting.AlertReset() 56 | 57 | const deleteObject = { 58 | title: 'Delete Mediator', 59 | button: 'Delete', 60 | message: 'Are you sure you wish to delete the mediator "' + mediator.name + '"?' 61 | } 62 | 63 | const modalInstance = $uibModal.open({ 64 | template: confirmModal, 65 | controller: ConfirmModalCtrl, 66 | resolve: { 67 | confirmObject: function () { 68 | return deleteObject 69 | } 70 | } 71 | }) 72 | 73 | modalInstance.result.then(function () { 74 | // Delete confirmed - delete the user 75 | mediator.$remove(deleteSuccess, deleteError) 76 | }, function () { 77 | // delete cancelled - do nothing 78 | }) 79 | } 80 | 81 | /***********************************/ 82 | /** Delete Mediator Functions **/ 83 | /***********************************/ 84 | 85 | $scope.editMediatorConfig = function (mediator) { 86 | Alerting.AlertReset() 87 | 88 | $uibModal.open({ 89 | template: mediatorConfigModal, 90 | controller: MediatorConfigModalCtrl, 91 | resolve: { 92 | mediator: function () { 93 | return mediator 94 | } 95 | } 96 | }) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/scripts/controllers/monitoring.js: -------------------------------------------------------------------------------- 1 | export function MonitoringCtrl ($scope) { 2 | $scope.setSomething = true 3 | } 4 | -------------------------------------------------------------------------------- /app/scripts/controllers/sidebar.js: -------------------------------------------------------------------------------- 1 | export function SidebarCtrl ($scope, $location) { 2 | $scope.isCurrent = function (path) { 3 | if (path.length > 1 && $location.path().substr(0, path.length) === path) { 4 | return true 5 | } else if ($location.path() === path) { 6 | return true 7 | } else { 8 | return false 9 | } 10 | } 11 | 12 | $(window).scroll(function (e) { 13 | // Get the position of the location where the scroller starts. 14 | const scrollerAnchor = $('.scollNavAnchor').offsetParent().scrollTop() 15 | if (scrollerAnchor >= 50) { 16 | // show the scroll to button 17 | $('#scrollToTop').css('display', 'block') 18 | $('.header').css('box-shadow', '0px 0px 10px #888') 19 | } else { 20 | // hide the scroll to button 21 | $('#scrollToTop').css('display', 'none') 22 | $('.header').css('box-shadow', 'none') 23 | } 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /app/scripts/controllers/transactionsAddReqResModal.js: -------------------------------------------------------------------------------- 1 | import { 2 | beautifyIndent, 3 | returnContentType 4 | } from '../utils' 5 | 6 | export function TransactionsAddReqResModalCtrl ($scope, $uibModal, $uibModalInstance, record, channel, transactionId, recordType, index) { 7 | $scope.record = record 8 | $scope.channel = channel // optional 9 | $scope.viewFullBody = false 10 | $scope.viewFullBodyType = null 11 | $scope.viewFullBodyContent = null 12 | $scope.fullBodyTransformLang = null 13 | $scope.transactionId = transactionId 14 | $scope.recordPathRequest = recordType + '[' + index + '].request' 15 | $scope.recordPathResponse = recordType + '[' + index + '].response' 16 | 17 | // transform request body with indentation/formatting 18 | if (record.request && record.request.body) { 19 | if (record.request.headers && returnContentType(record.request.headers)) { 20 | const requestTransform = beautifyIndent(returnContentType(record.request.headers), record.request.body) 21 | $scope.record.request.body = requestTransform.content 22 | $scope.requestTransformLang = requestTransform.lang 23 | } 24 | } 25 | 26 | // transform response body with indentation/formatting 27 | if (record.response && record.response.body) { 28 | if (record.response.headers && returnContentType(record.response.headers)) { 29 | const responseTransform = beautifyIndent(returnContentType(record.response.headers), record.response.body) 30 | $scope.record.response.body = responseTransform.content 31 | $scope.responseTransformLang = responseTransform.lang 32 | } 33 | } 34 | 35 | $scope.toggleFullView = function (type, bodyContent, contentType) { 36 | // if both parameters supplied - view body message 37 | if (type && bodyContent) { 38 | $scope.viewFullBody = true 39 | $scope.viewFullBodyType = type 40 | $scope.viewFullBodyContent = bodyContent 41 | $scope.fullBodyTransformLang = contentType 42 | } else { 43 | $scope.viewFullBody = false 44 | } 45 | } 46 | 47 | $scope.cancel = function () { 48 | $uibModalInstance.dismiss('cancel') 49 | } 50 | 51 | /*********************************************************************/ 52 | /** Transactions View Route Functions **/ 53 | /*********************************************************************/ 54 | } 55 | -------------------------------------------------------------------------------- /app/scripts/controllers/transactionsBodyModal.js: -------------------------------------------------------------------------------- 1 | import { beautifyIndent, returnContentType } from '../utils' 2 | 3 | export function TransactionsBodyModalCtrl ($scope, $uibModalInstance, bodyData) { 4 | $scope.bodyData = bodyData 5 | 6 | // transform body with indentation/formatting 7 | if ($scope.bodyData.content) { 8 | if (bodyData.headers && returnContentType(bodyData.headers)) { 9 | const bodyTransform = beautifyIndent(returnContentType(bodyData.headers), bodyData.content) 10 | $scope.bodyData.content = bodyTransform.content 11 | } 12 | } 13 | 14 | $scope.cancel = function () { 15 | $uibModalInstance.dismiss('cancel') 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /app/scripts/controllers/transactionsRerunModal.js: -------------------------------------------------------------------------------- 1 | const MAX_BATCH_SIZE = 64 2 | 3 | function * getBatchSizes (currentBatchSize) { 4 | yield { value: 1, label: 'One at a time' } 5 | 6 | let currentValue = 2 7 | while (currentValue <= Math.min(currentBatchSize, MAX_BATCH_SIZE)) { 8 | yield { value: currentValue, label: `${currentValue} at a time` } 9 | currentValue *= 2 10 | } 11 | } 12 | 13 | export function TransactionsRerunModalCtrl ($scope, $uibModalInstance, Api, Notify, Alerting, transactionsSelected, rerunTransactionsSelected) { 14 | $scope.rerunSuccess = false 15 | $scope.transactionsSelected = transactionsSelected 16 | $scope.rerunTransactionsSelected = rerunTransactionsSelected 17 | $scope.taskSetup = {} 18 | $scope.taskSetup.batchSize = 1 19 | $scope.taskSetup.paused = false 20 | $scope.batchSizes = Array.from( 21 | getBatchSizes($scope.transactionsCount ? $scope.transactionsCount : transactionsSelected.length) 22 | ) 23 | 24 | if (rerunTransactionsSelected === 1 && transactionsSelected.length === 1) { 25 | Alerting.AlertAddMsg('rerun', 'warning', 'This transaction has already been rerun') 26 | } else if (rerunTransactionsSelected > 0) { 27 | Alerting.AlertAddMsg('rerun', 'warning', rerunTransactionsSelected + ' of these transactions have already been rerun') 28 | } 29 | 30 | function onSuccess () { 31 | // On success 32 | Notify.notify('TasksChanged') 33 | $scope.rerunSuccess = true 34 | $scope.$emit('transactionRerunSuccess') 35 | }; 36 | 37 | function createTask (tIds, onSuccess) { 38 | $scope.task = new Api.Tasks({ tids: tIds, batchSize: $scope.taskSetup.batchSize, paused: $scope.taskSetup.paused }) 39 | $scope.task.$save({}, onSuccess) 40 | } 41 | 42 | $scope.confirmRerun = function () { 43 | if ($scope.bulkRerunActive) { 44 | const filters = $scope.returnFilters() 45 | filters.pauseQueue = $scope.taskSetup.paused 46 | filters.batchSize = $scope.taskSetup.batchSize 47 | 48 | $scope.rerunTasks = new Api.BulkReruns(filters) 49 | $scope.rerunTasks.$save({}, onSuccess) 50 | } else { 51 | createTask($scope.transactionsSelected, onSuccess) 52 | } 53 | $scope.rerunSuccess = true 54 | } 55 | 56 | $scope.cancel = function () { 57 | $uibModalInstance.dismiss('cancel') 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/scripts/directives/audit-operation.js: -------------------------------------------------------------------------------- 1 | export function auditOperation () { 2 | return { 3 | restrict: 'E', 4 | templateUrl: 'views/partials/audit-operation.html', 5 | scope: { 6 | audit: '=audit' 7 | }, 8 | link: (scope, element, attrs) => { 9 | scope.expanded = false 10 | 11 | scope.toggleExpanded = () => { 12 | scope.expanded = !scope.expanded 13 | } 14 | 15 | scope.isObject = maybeObject => { 16 | return ( 17 | typeof maybeObject !== 'string' && 18 | typeof maybeObject !== 'boolean' && 19 | typeof maybeObject !== 'number' 20 | ) 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/scripts/directives/autofocus.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | export function focus ($timeout) { 4 | return { 5 | scope: { 6 | trigger: '@focus' 7 | }, 8 | link: function (scope, element) { 9 | scope.$watch('trigger', function (value) { 10 | if (value === 'true') { 11 | $timeout(function () { 12 | element[0].focus() 13 | }) 14 | } 15 | }) 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/scripts/directives/footer-version.html: -------------------------------------------------------------------------------- 1 | 2 | (Console v{{ footerConsoleVersion }} | Core v{{ footerCoreVersion }}) 3 | -------------------------------------------------------------------------------- /app/scripts/directives/footer-version.js: -------------------------------------------------------------------------------- 1 | import { isCoreVersionCompatible } from '../utils' 2 | import * as footerTemplate from './footer-version.html' 3 | 4 | export function footerVersion (Api, config) { 5 | return { 6 | template: footerTemplate, 7 | scope: false, 8 | link: function (scope) { 9 | const success = function (result) { 10 | scope.footerCoreVersion = result.currentCoreVersion 11 | scope.footerConsoleVersion = config.version 12 | scope.footerVersionsCompatible = isCoreVersionCompatible(config.minimumCoreVersion, scope.footerCoreVersion) 13 | } 14 | 15 | scope.$root.$watch('sessionUser', function (newVal) { 16 | if (newVal) { 17 | Api.About.get(success) 18 | } else { 19 | scope.footerCoreVersion = null 20 | scope.footerConsoleVersion = null 21 | } 22 | }) 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/scripts/directives/graph-directives-morris.js: -------------------------------------------------------------------------------- 1 | import { viewPage } from '../utils' 2 | import raphael from 'raphael' 3 | import 'morris.js' 4 | 5 | window.Raphael = raphael 6 | const { Morris } = window 7 | 8 | export function morrisLineChart ($parse) { 9 | return { 10 | restrict: 'EA', 11 | link: function (scope, elem, attrs) { 12 | const element = attrs.id 13 | const exp = $parse(attrs.data) 14 | 15 | // on data change either create graph or update values 16 | scope.$watchCollection(exp, function (newVal) { 17 | const data = newVal 18 | 19 | if (data) { 20 | // if morris bar chart exist then update it 21 | if (scope.morrisLineChart) { 22 | scope.morrisLineChart.setData(data.data) 23 | } else { 24 | // create Morris Line Chart if it doesnt yet exist 25 | scope.morrisLineChart = new Morris.Line({ 26 | element, 27 | data: data.data, 28 | xkey: data.xkey, 29 | ykeys: data.ykeys, 30 | labels: data.labels, 31 | postUnits: data.postunits, 32 | resize: true 33 | }) 34 | } 35 | } 36 | }) 37 | } 38 | } 39 | } 40 | 41 | export function morrisBarChart ($parse) { 42 | return { 43 | restrict: 'EA', 44 | link: function (scope, elem, attrs) { 45 | const element = attrs.id 46 | const exp = $parse(attrs.data) 47 | 48 | // on data change either create graph or update values 49 | scope.$watchCollection(exp, function (newVal) { 50 | const data = newVal 51 | 52 | if (data) { 53 | // if morris bar chart exist then update it 54 | if (scope.morrisBarChart) { 55 | scope.morrisBarChart.setData(data.data) 56 | } else { 57 | // create Morris Bar Chart if it doesnt yet exist 58 | scope.morrisBarChart = new Morris.Bar({ 59 | element, 60 | data: data.data, 61 | xkey: data.xkey, 62 | ykeys: data.ykeys, 63 | labels: data.labels, 64 | barColors: data.colors, 65 | stacked: data.stacked, 66 | barRatio: 0.4, 67 | xLabelMargin: 10, 68 | resize: true, 69 | hideHover: 'auto' 70 | }).on('click', function (i, row) { 71 | if (row.link) { 72 | // on status click direct user to channel metrics page 73 | viewPage(row.link) 74 | } 75 | }) 76 | } 77 | } 78 | }) 79 | } 80 | } 81 | } 82 | 83 | export function morrisDonutChart ($parse) { 84 | return { 85 | restrict: 'EA', 86 | link: function (scope, elem, attrs) { 87 | const element = attrs.id 88 | const exp = $parse(attrs.data) 89 | 90 | // on data change either create graph or update values 91 | scope.$watchCollection(exp, function (newVal) { 92 | const data = newVal 93 | 94 | // we have to rebuild the morris chart else new colours won't get picked up 95 | // in general this approach is a bit problematic with the other charts, so should be avoided 96 | // (e.g. onClick events not getting cleared...) 97 | 98 | elem.empty() 99 | if (data) { 100 | scope.morrisDonutChart = new Morris.Donut({ 101 | element, 102 | data: data.data, 103 | colors: data.colors, 104 | resize: true, 105 | formatter: function (y) { 106 | return y + '%' 107 | } 108 | }) 109 | } 110 | }) 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /app/scripts/directives/index.js: -------------------------------------------------------------------------------- 1 | export * from './autofocus' 2 | export * from './footer-version' 3 | export * from './graph-directives-morris' 4 | export * from './mediator-config-display' 5 | export * from './mediator-config' 6 | export * from './metrics-date-type-selector' 7 | export * from './transaction-body-downloader' 8 | export * from './visualizer' 9 | export * from './audit-operation' 10 | -------------------------------------------------------------------------------- /app/scripts/directives/mediator-config-display.js: -------------------------------------------------------------------------------- 1 | import * as configDisplay from '~/views/partials/mediator-config-display.html' 2 | // Mediator Config Display Directive 3 | // 4 | // Makes use of recursive rendering trick from here: 5 | // http://sporto.github.io/blog/2013/06/24/nested-recursive-directives-in-angular/ 6 | 7 | export function mediatorConfigDisplay () { 8 | return { 9 | restrict: 'EA', 10 | scope: { 11 | configDefs: '=', 12 | config: '=' 13 | }, 14 | template: configDisplay, 15 | link: function (scope) { 16 | scope.mediatorDefsMap = {} 17 | 18 | scope.$watch('configDefs', function (configDefs) { 19 | if (configDefs) { 20 | configDefs.map(function (def) { 21 | scope.mediatorDefsMap[def.param] = def 22 | }) 23 | } 24 | }) 25 | } 26 | } 27 | } 28 | 29 | export function mediatorNestedConfigDisplay ($compile) { 30 | return { 31 | restrict: 'EA', 32 | scope: { 33 | configDefs: '=', 34 | config: '=' 35 | }, 36 | template: '
', 37 | link: function (scope, element) { 38 | if (scope.config) { 39 | element.append('
') 40 | $compile(element.contents())(scope) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/scripts/directives/mediator-config.js: -------------------------------------------------------------------------------- 1 | import mediatorConfigView from '~/views/partials/mediator-config.html' 2 | // Mediator Config Directive 3 | // 4 | // Makes use of recursive rendering trick from here: 5 | // http://sporto.github.io/blog/2013/06/24/nested-recursive-directives-in-angular/ 6 | export function mediatorConfig () { 7 | return { 8 | restrict: 'EA', 9 | scope: { 10 | configDefs: '=', 11 | config: '=' 12 | }, 13 | template: mediatorConfigView, 14 | link: function (scope) { 15 | scope.inputKeys = {} 16 | scope.inputValues = {} 17 | 18 | scope.removeMapping = function (param, mapping) { 19 | delete scope.config[param][mapping] 20 | } 21 | 22 | scope.addNewMapping = function (param) { 23 | if (!scope.config[param]) { 24 | scope.config[param] = {} 25 | } 26 | scope.config[param][scope.inputKeys[param]] = scope.inputValues[param] 27 | scope.inputKeys[param] = '' 28 | scope.inputValues[param] = '' 29 | } 30 | 31 | scope.doesNewKeyExist = function (param) { 32 | return scope.config[param] && scope.config[param][scope.inputKeys[param]] 33 | } 34 | 35 | scope.isNewKeyValid = function (param) { 36 | return scope.inputKeys[param] && !scope.doesNewKeyExist(param) 37 | } 38 | 39 | scope.inputKeysForArrays = {} 40 | scope.inputValuesForArrays = {} 41 | 42 | scope.removeMappingInArray = function (param, index, mapping) { 43 | delete scope.config[param][index][mapping] 44 | } 45 | 46 | scope.addNewMappingInArray = function (param, index) { 47 | if (!scope.config[param][index]) { 48 | scope.config[param][index] = {} 49 | } 50 | scope.config[param][index][scope.inputKeysForArrays[param + index]] = scope.inputValuesForArrays[param + index] 51 | scope.inputKeysForArrays[param + index] = '' 52 | scope.inputValuesForArrays[param + index] = '' 53 | } 54 | 55 | scope.doesNewKeyExistInArray = function (param, index) { 56 | return scope.config[param][index] && scope.config[param][index][scope.inputKeysForArrays[param + index]] 57 | } 58 | 59 | scope.isNewKeyValidInArray = function (param, index) { 60 | return scope.inputKeysForArrays[param + index] && !scope.doesNewKeyExistInArray(param, index) 61 | } 62 | 63 | scope.removeArrayItem = function (param, index) { 64 | scope.config[param].splice(index, 1) 65 | } 66 | 67 | scope.addNewArrayItem = function (def) { 68 | if (!scope.config[def.param]) { 69 | scope.config[def.param] = [] 70 | } 71 | 72 | let newItem = '' 73 | switch (def.type) { 74 | case 'bool': newItem = false; break 75 | case 'number': newItem = 0; break 76 | case 'option': newItem = def.values[0]; break 77 | case 'map': newItem = {}; break 78 | case 'struct': newItem = {}; break 79 | } 80 | 81 | scope.config[def.param].push(newItem) 82 | } 83 | } 84 | } 85 | } 86 | 87 | export function mediatorNestedConfig ($compile) { 88 | return { 89 | restrict: 'EA', 90 | scope: { 91 | configDefs: '=', 92 | config: '=' 93 | }, 94 | template: '
', 95 | link: function (scope, element) { 96 | if (scope.configDefs) { 97 | if (!scope.config) { 98 | scope.config = {} 99 | } 100 | 101 | element.append('
') 102 | $compile(element.contents())(scope) 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /app/scripts/directives/metrics-date-type-selector.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | import metricsDateTypeSelectorView from '~/views/partials/metrics-date-type-selector.html' 3 | 4 | export function metricsDateTypeSelector () { 5 | return { 6 | restrict: 'EA', 7 | template: metricsDateTypeSelectorView, 8 | scope: { 9 | selectedDateType: '=', 10 | onChange: '=?' 11 | }, 12 | link: function (scope) { 13 | scope.optionsEnabled = { 14 | minute: true, 15 | hour: true, 16 | day: true, 17 | week: true, 18 | month: true, 19 | year: true 20 | } 21 | 22 | if (!scope.onChange) { 23 | scope.onChange = function () { } 24 | } 25 | 26 | function processOptions () { 27 | const from = moment(scope.selectedDateType.from) 28 | const until = moment(scope.selectedDateType.until) 29 | 30 | const diff = function (unit) { 31 | return Math.abs(from.diff(until, unit)) 32 | } 33 | 34 | const maxBreakdown = 120 35 | 36 | if (from.isAfter(until)) { 37 | scope.selectedDateType.from = scope.selectedDateType.until 38 | } 39 | 40 | scope.optionsEnabled.minute = true 41 | scope.optionsEnabled.hour = true 42 | scope.optionsEnabled.day = true 43 | scope.optionsEnabled.week = true 44 | scope.optionsEnabled.month = true 45 | scope.optionsEnabled.year = true 46 | 47 | if (diff('minutes') > maxBreakdown) { 48 | scope.optionsEnabled.minute = false 49 | if (scope.selectedDateType.type === 'minute') { 50 | scope.selectedDateType.type = 'hour' 51 | } 52 | } 53 | if (diff('hours') > maxBreakdown) { 54 | scope.optionsEnabled.hour = false 55 | if (scope.selectedDateType.type === 'hour') { 56 | scope.selectedDateType.type = 'day' 57 | } 58 | } 59 | if (diff('days') > maxBreakdown) { 60 | scope.optionsEnabled.day = false 61 | if (scope.selectedDateType.type === 'day') { 62 | scope.selectedDateType.type = 'week' 63 | } 64 | } 65 | if (diff('weeks') > maxBreakdown) { 66 | scope.optionsEnabled.week = false 67 | if (scope.selectedDateType.type === 'week') { 68 | scope.selectedDateType.type = 'month' 69 | } 70 | } 71 | if (diff('months') > maxBreakdown) { 72 | scope.optionsEnabled.month = false 73 | if (scope.selectedDateType.type === 'month') { 74 | scope.selectedDateType.type = 'year' 75 | } 76 | } 77 | } 78 | 79 | scope.customDateBreakdownChange = function () { 80 | processOptions() 81 | scope.onChange() 82 | } 83 | 84 | // process for initial values 85 | processOptions() 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /app/scripts/directives/transaction-body-downloader.js: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash' 2 | import { saveAs } from 'file-saver' 3 | import { buildBlob } from '../utils' 4 | 5 | export function transactionBodyDownloader (Api) { 6 | return { 7 | restrict: 'EA', 8 | template: '
', 9 | scope: { 10 | transactionId: '=', 11 | path: '=' 12 | }, 13 | link: function (scope) { 14 | scope.download = function () { 15 | const onSuccess = function (trx) { 16 | const subTrx = _.get(trx, scope.path) 17 | 18 | let contentType = 'text/plain' // default 19 | if (subTrx.headers && subTrx.headers['content-type']) { 20 | contentType = subTrx.headers['content-type'] 21 | } 22 | 23 | let extension 24 | if (contentType.indexOf('json') > -1) { 25 | extension = '.json' 26 | } else if (contentType.indexOf('xml') > -1) { 27 | extension = '.xml' 28 | } else { 29 | extension = '.txt' 30 | } 31 | 32 | const bodyBlob = buildBlob(subTrx.body, contentType) 33 | const filename = scope.transactionId + '_' + _.camelCase(scope.path) + extension 34 | saveAs(bodyBlob, filename) 35 | } 36 | 37 | const onError = function (err) { 38 | console.log(err) 39 | } 40 | 41 | Api.Transactions.get({ transactionId: scope.transactionId, filterRepresentation: 'full' }, onSuccess, onError) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/scripts/external/angular-bootstrap-datetimepicker-directive.js: -------------------------------------------------------------------------------- 1 | import 'eonasdan-bootstrap-datetimepicker' 2 | 3 | // Just exports the module name 4 | export const moduleName = 'datetimepicker' 5 | angular 6 | .module('datetimepicker', []) 7 | .provider('datetimepicker', function () { 8 | let defaultOptions = {} 9 | 10 | this.setOptions = function (options) { 11 | defaultOptions = options 12 | } 13 | 14 | this.$get = function () { 15 | return { 16 | getOptions: function () { 17 | return defaultOptions 18 | } 19 | } 20 | } 21 | }) 22 | .directive('datetimepicker', [ 23 | '$timeout', 24 | 'datetimepicker', 25 | function ($timeout, 26 | datetimepicker) { 27 | const defaultOptions = datetimepicker.getOptions() 28 | 29 | return { 30 | require: '?ngModel', 31 | restrict: 'AE', 32 | scope: { 33 | datetimepickerOptions: '@' 34 | }, 35 | link: function ($scope, $element, $attrs, ngModelCtrl) { 36 | const passedInOptions = $scope.$eval($attrs.datetimepickerOptions) 37 | const options = Object.assign({}, defaultOptions, passedInOptions) 38 | 39 | $element 40 | .on('dp.change', function (e) { 41 | if (ngModelCtrl) { 42 | $timeout(function () { 43 | ngModelCtrl.$setViewValue(e.target.value) 44 | }) 45 | } 46 | }) 47 | .datetimepicker(options) 48 | 49 | function setPickerValue () { 50 | let date = options.defaultDate || null 51 | 52 | if (ngModelCtrl && ngModelCtrl.$viewValue) { 53 | date = ngModelCtrl.$viewValue 54 | } 55 | 56 | $element 57 | .data('DateTimePicker') 58 | .date(date) 59 | } 60 | 61 | if (ngModelCtrl) { 62 | ngModelCtrl.$render = function () { 63 | setPickerValue() 64 | } 65 | } 66 | 67 | setPickerValue() 68 | } 69 | } 70 | } 71 | ]) 72 | -------------------------------------------------------------------------------- /app/scripts/external/angular-taglist-directive.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | x {{tag}} 4 |
5 |
6 |
-------------------------------------------------------------------------------- /app/scripts/external/angular-taglist-directive.js: -------------------------------------------------------------------------------- 1 | import template from './angular-taglist-directive.html' 2 | 3 | export const angularTaglist = 'angular_taglist_directive' 4 | 5 | angular.module(angularTaglist, []).directive('taglist', ['$timeout', function ($timeout) { 6 | return { 7 | restrict: 'EA', 8 | replace: true, 9 | scope: { 10 | tagData: '=', 11 | taglistBlurTimeout: '=' 12 | }, 13 | transclude: true, 14 | template, 15 | compile: function (tElement, tAttrs, transcludeFn) { 16 | return function (scope, element, attrs) { 17 | element.bind('click', function () { 18 | element[0].getElementsByTagName('input')[0].focus() 19 | }) 20 | 21 | const input = angular.element(element[0].getElementsByTagName('div')[0].getElementsByTagName('input')[0]) 22 | 23 | input.bind('blur', function () { 24 | if (scope.taglistBlurTimeout) { 25 | $timeout(function () { 26 | addTag(input[0]) 27 | }, scope.taglistBlurTimeout) 28 | } else { 29 | addTag(input[0]) 30 | } 31 | }) 32 | input.bind('keydown', function (evt) { 33 | if (evt.altKey || evt.metaKey || evt.ctrlKey || evt.shiftKey) { 34 | return 35 | } 36 | if (evt.which === 188 || evt.which === 13) { // 188 = comma, 13 = return 37 | evt.preventDefault() 38 | addTag(this) 39 | } else if (evt.which === 8 && /* 8 = delete */ 40 | this.value.trim().length === 0 && 41 | element[0].getElementsByClassName('tag').length > 0) { 42 | evt.preventDefault() 43 | scope.$apply(function () { 44 | scope.tagData.splice(scope.tagData.length - 1, 1) 45 | }) 46 | } 47 | }) 48 | 49 | function addTag (element) { 50 | if (!scope.tagData) { 51 | scope.tagData = [] 52 | } 53 | const val = element.value.trim() 54 | if (val.length === 0) { 55 | return 56 | } 57 | if (scope.tagData.indexOf(val) >= 0) { 58 | return 59 | } 60 | scope.$apply(function () { 61 | scope.tagData.push(val) 62 | element.value = '' 63 | }) 64 | } 65 | } 66 | } 67 | } 68 | }]) 69 | -------------------------------------------------------------------------------- /app/scripts/services/alerting.js: -------------------------------------------------------------------------------- 1 | /* NB! remember to include the factory (Alerting) into your Controllers */ 2 | /* {{alert.msg}} */ 3 | 4 | export function Alerting ($rootScope) { 5 | $rootScope.alerts = {} 6 | 7 | return { 8 | AlertAddMsg: function (alertScope, alertType, alertMsg) { 9 | // check if alertScope object exists 10 | if (!$rootScope.alerts[alertScope]) { 11 | $rootScope.alerts[alertScope] = [] 12 | } 13 | 14 | // create alertObject 15 | const alertObject = { type: alertType, msg: alertMsg } 16 | 17 | const scopeAlerts = $rootScope.alerts[alertScope] 18 | 19 | if (!scopeAlerts.some(el => el.type === alertType && el.msg === alertMsg)) { 20 | scopeAlerts.push(alertObject) 21 | } 22 | }, 23 | AlertAddServerMsg: function (errCode) { 24 | let alertMsg 25 | switch (errCode) { 26 | case 401: 27 | alertMsg = 'The request is not authorised by the server. Please try to log in again.' 28 | break 29 | case 403: 30 | alertMsg = 'The request has been forbidden by the server. Please contact the server administrator' 31 | break 32 | case 404: 33 | alertMsg = 'The request resource could not be found' 34 | break 35 | default: 36 | alertMsg = 'A server-side error has occurred. Please contact the server administrator' 37 | } 38 | 39 | // check if server object exists 40 | if (!$rootScope.alerts.server) { 41 | $rootScope.alerts.server = [] 42 | } 43 | 44 | // create alertObject 45 | const alertObject = { type: 'danger', msg: alertMsg } 46 | 47 | // push alertObject to appropriate alertScope 48 | $rootScope.alerts.server.push(alertObject) 49 | }, 50 | AlertReset: function (alertScope) { 51 | if (!alertScope) { 52 | // reset the alerts objects 53 | $rootScope.alerts = {} 54 | } else { 55 | if ($rootScope.alerts) { 56 | // reset the alerts objects 57 | $rootScope.alerts[alertScope] = undefined 58 | } 59 | } 60 | }, 61 | AlertValidationMsgs: function () { 62 | $rootScope.validationRequiredMsg = 'This field is required!' 63 | $rootScope.validationPasswordConfirmMsg = 'Please confirm you password!' 64 | $rootScope.validationFormErrorsMsg = 'There appears to be some errors in your form. Please correct and try again.' 65 | } 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/scripts/services/auditLookupMap.js: -------------------------------------------------------------------------------- 1 | export function AuditLookups () { 2 | return { 3 | eventActionMap: function () { 4 | const eventActionMap = {} 5 | eventActionMap.C = 'Create (C)' 6 | eventActionMap.R = 'Read (R)' 7 | eventActionMap.U = 'Update (U)' 8 | eventActionMap.D = 'Delete (D)' 9 | eventActionMap.E = 'Execute (E)' 10 | 11 | return eventActionMap 12 | }, 13 | eventOutcomeMap: function () { 14 | const eventOutcomeMap = {} 15 | eventOutcomeMap[0] = 'Success (0)' 16 | eventOutcomeMap[4] = 'Minor Failure (4)' 17 | eventOutcomeMap[8] = 'Serious Failure (8)' 18 | eventOutcomeMap[12] = 'Major Failure (12)' 19 | 20 | return eventOutcomeMap 21 | } 22 | 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/scripts/services/authinterceptor.js: -------------------------------------------------------------------------------- 1 | export function Authinterceptor ($q, $location) { 2 | return { 3 | responseError: function (response) { 4 | // Redirect user to login page in case he is not authorized 5 | if (response.status === 401) { 6 | localStorage.removeItem('consoleSession') 7 | 8 | $location.path('/login') 9 | } 10 | return $q.reject(response) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/scripts/services/index.js: -------------------------------------------------------------------------------- 1 | export * from './alerting' 2 | export * from './auditLookupMap' 3 | export * from './authinterceptor' 4 | export * from './login' 5 | export * from './mediatorDisplay' 6 | export * from './metrics' 7 | export * from './notify' 8 | export * from './rest' 9 | export * from './keycloak' 10 | -------------------------------------------------------------------------------- /app/scripts/services/keycloak.js: -------------------------------------------------------------------------------- 1 | import Keycloak from 'keycloak-js' 2 | 3 | let keycloakInstance = null 4 | 5 | export function keycloak (config) { 6 | let keycloakState = '' 7 | // Init SSO with keycloak 8 | if (config.ssoEnabled) { 9 | if (!keycloakInstance) { 10 | // return a single instance of keycloak 11 | keycloakInstance = new Keycloak({ 12 | url: config.keyCloakUrl, 13 | realm: config.keyCloakRealm, 14 | clientId: config.keyCloakClientId 15 | }) 16 | keycloakInstance.init({ checkLoginIframe: false }) 17 | } 18 | } 19 | return { 20 | keycloakInstance, 21 | setKeycloakState: function (state) { 22 | keycloakState = state 23 | }, 24 | getKeycloakState: function () { 25 | return keycloakState 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/scripts/services/login.js: -------------------------------------------------------------------------------- 1 | export function login (Api, $rootScope, keycloak) { 2 | let userProfile = {} 3 | 4 | return { 5 | login: function (email, password, done) { 6 | // fetch salt from openhim-core server and work out password hash 7 | Api.AuthenticateLocal.save({ username: email, password }, function () { 8 | // on success 9 | // Verify that you can make authenticated requests 10 | Api.Users.get({ email }, function (profile) { 11 | userProfile = profile 12 | done('Authentication Success') 13 | }, function () { 14 | // Throw error upon failure 15 | done('Authentication Failed') 16 | }) 17 | }, function (err) { 18 | if (err.status < 100) { 19 | // If the status is outside the possible http status range no then http error 20 | done('Internal Server Error') 21 | } else { 22 | // if error returns a status then server is active - user not authenticated 23 | done('Authentication Failed') 24 | } 25 | } 26 | ) 27 | }, 28 | loginWithKeyCloak: function (code, sessionState, state, done) { 29 | // fetch salt from openhim-core server and work out password hash 30 | Api.AuthenticateOpenid.getToken({ code, sessionState, state }, function (authDetails) { 31 | userProfile = authDetails.user 32 | done('Authentication Success', authDetails.user) 33 | }, function (err) { 34 | if (err.status < 100) { 35 | // If the status is outside the possible http status range no then http error 36 | done('Internal Server Error') 37 | } else { 38 | // if error returns a status then server is active - user not authenticated 39 | done('Authentication Failed') 40 | } 41 | }) 42 | }, 43 | logout: function (done) { 44 | Api.Logout.get( 45 | {}, 46 | function () { 47 | // Cleanup of keycloak session 48 | const keycloakState = keycloak.getKeycloakState() 49 | const isKeycloakLogin = $rootScope.sessionUser && $rootScope.sessionProvider === 'openid' && keycloakState 50 | userProfile = null 51 | $rootScope.sessionUser = null 52 | $rootScope.navMenuVisible = false 53 | localStorage.removeItem('consoleSession') 54 | 55 | if (isKeycloakLogin) { 56 | localStorage.removeItem(`kc-callback-${keycloakState}`) 57 | keycloak.keycloakInstance.logout({ redirectUri: window.location.origin }) 58 | } 59 | 60 | done('Logout Successful') 61 | }, function () { 62 | done('Internal Server Error') 63 | } 64 | ) 65 | }, 66 | getLoggedInUser: function () { 67 | return userProfile 68 | }, 69 | isLoggedIn: function () { 70 | return userProfile !== null && Object.keys(userProfile).length > 0 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/scripts/services/mediatorDisplay.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | 3 | export function MediatorDisplay (config) { 4 | const _formatMediator = function (mediator) { 5 | const secondsDiffNow = function () { 6 | return Math.abs(Date.now() - moment(mediator._lastHeartbeat)) / 1000 7 | } 8 | 9 | if (!mediator._lastHeartbeat) { 10 | mediator.lastHeartbeatStatus = 'never' 11 | mediator.lastHeartbeatDisplayExplain = 'No heartbeats have ever been received from this mediator. Perhaps it doesn\'t support heartbeats.' 12 | } else { 13 | if (moment(mediator._lastHeartbeat).isAfter(Date.now())) { 14 | mediator.lastHeartbeatStatus = 'warning' 15 | mediator.lastHeartbeatDisplayExplain = 'Heartbeat set in the future' 16 | } else if (secondsDiffNow() < config.mediatorLastHeartbeatWarningSeconds) { 17 | mediator.lastHeartbeatStatus = 'success' 18 | } else if (secondsDiffNow() < config.mediatorLastHeartbeatDangerSeconds) { 19 | mediator.lastHeartbeatStatus = 'warning' 20 | mediator.lastHeartbeatDisplayExplain = 'No heartbeats received in over ' + config.mediatorLastHeartbeatWarningSeconds + ' s' 21 | } else { 22 | mediator.lastHeartbeatStatus = 'danger' 23 | mediator.lastHeartbeatDisplayExplain = 'No heartbeats received in over ' + config.mediatorLastHeartbeatDangerSeconds + ' s' 24 | } 25 | mediator.lastHeartbeatDisplay = moment(mediator._lastHeartbeat).fromNow() 26 | } 27 | 28 | if (mediator._uptime) { 29 | // generate human-friendly display string, e.g. 4 days 30 | mediator.uptimeDisplay = moment().subtract(mediator._uptime, 'seconds').fromNow(true) 31 | mediator.uptimeSince = moment(mediator._lastHeartbeat).subtract(mediator._uptime, 'seconds').toDate() 32 | } 33 | } 34 | 35 | return { 36 | formatMediator: _formatMediator, 37 | 38 | formatMediators: function (mediators) { 39 | angular.forEach(mediators, _formatMediator) 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/scripts/services/metrics.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import moment from 'moment' 3 | 4 | export function Metrics () { 5 | return { 6 | refreshDatesForSelectedPeriod: function (selectedPeriod) { 7 | if (selectedPeriod.period === '1h') { 8 | selectedPeriod.from = moment().subtract(1, 'hour').toDate() 9 | selectedPeriod.until = moment().toDate() 10 | selectedPeriod.type = 'minute' 11 | } else if (selectedPeriod.period === '1d') { 12 | selectedPeriod.from = moment().subtract(1, 'day').toDate() 13 | selectedPeriod.until = moment().toDate() 14 | selectedPeriod.type = 'hour' 15 | } else if (selectedPeriod.period === '1w') { 16 | selectedPeriod.from = moment().subtract(1, 'week').toDate() 17 | selectedPeriod.until = moment().toDate() 18 | selectedPeriod.type = 'day' 19 | } else if (selectedPeriod.period === '1m') { 20 | selectedPeriod.from = moment().subtract(1, 'month').toDate() 21 | selectedPeriod.until = moment().toDate() 22 | selectedPeriod.type = 'day' 23 | } else if (selectedPeriod.period === '1y') { 24 | selectedPeriod.from = moment().subtract(1, 'year').toDate() 25 | selectedPeriod.until = moment().toDate() 26 | selectedPeriod.type = 'month' 27 | } else if (selectedPeriod.period === '5y') { 28 | selectedPeriod.from = moment().subtract(5, 'years').toDate() 29 | selectedPeriod.until = moment().toDate() 30 | selectedPeriod.type = 'month' 31 | } 32 | }, 33 | 34 | buildLineChartData: function (selectedPeriod, metrics, key, label, valueFormatter) { 35 | const graphData = [] 36 | const from = moment(selectedPeriod.from) 37 | const until = moment(selectedPeriod.until) 38 | const unit = selectedPeriod.type + 's' 39 | let avgResponseTimeTotal = 0 40 | 41 | let loadTotal = 0 42 | let avgResponseTime = 0 43 | 44 | const diff = Math.abs(from.diff(until, unit)) 45 | for (let i = 0; i <= diff; i++) { 46 | const timestamp = from.clone().add(i, unit) 47 | let value = 0 48 | 49 | for (let j = 0; j < metrics.length; j++) { 50 | const ts = moment(metrics[j].timestamp) 51 | 52 | if (timestamp.isSame(ts, selectedPeriod.type)) { 53 | value = metrics[j][key] 54 | if (valueFormatter) { 55 | value = valueFormatter(value) 56 | } 57 | loadTotal += metrics[j].total 58 | avgResponseTimeTotal += metrics[j].avgResp 59 | } 60 | } 61 | 62 | graphData.push({ timestamp: timestamp.format('YYYY-MM-DD HH:mm'), value }) 63 | } 64 | 65 | avgResponseTime = (avgResponseTimeTotal / metrics.length).toFixed(2) 66 | return { 67 | data: graphData, 68 | xkey: 'timestamp', 69 | ykeys: ['value'], 70 | labels: [label], 71 | loadTotal, 72 | avgResponseTime 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/scripts/services/notify.js: -------------------------------------------------------------------------------- 1 | export function Notify ($rootScope) { 2 | const notifyService = {} 3 | 4 | notifyService.notify = function (event) { 5 | $rootScope.$broadcast(event) 6 | } 7 | 8 | return notifyService 9 | } 10 | -------------------------------------------------------------------------------- /app/scripts/services/rest.js: -------------------------------------------------------------------------------- 1 | export function Api ($rootScope, $resource, config) { 2 | // fetch API server details 3 | const protocol = config.protocol 4 | const host = config.host 5 | const hostPath = (config.hostPath || '').replace(/(^\/)|(\/$)/g, '') 6 | const port = config.port 7 | const server = protocol + '://' + host + ':' + port + (/^\s*$/.test(hostPath) ? '' : '/' + hostPath) 8 | 9 | return { 10 | Me: $resource(server + '/me'), 11 | 12 | AuthenticateLocal: $resource(server + '/authenticate/local', {}, { 13 | save: { method: 'POST' } 14 | }), 15 | 16 | AuthenticateOpenid: $resource(server + '/authenticate/openid', {}, { 17 | getToken: { method: 'POST' } 18 | }), 19 | 20 | Logout: $resource(server + '/logout'), 21 | 22 | AuthenticationTypes: $resource(`${server}/authentication/types`), 23 | 24 | Channels: $resource(server + '/channels/:channelId', { channelId: '@_id' }, { 25 | update: { method: 'PUT' }, 26 | audits: { 27 | method: 'GET', 28 | url: server + '/channels/:channelId/audits', 29 | isArray: true 30 | } 31 | }), 32 | 33 | TriggerPollingChannels: $resource(server + '/channels/:channelId/trigger', { channelId: '@_id' }, {}), 34 | 35 | Roles: $resource(server + '/roles/:name', { name: '@name' }, { 36 | update: { method: 'PUT' } 37 | }), 38 | 39 | Users: $resource(server + '/users/:email', { email: '@email' }, { 40 | update: { method: 'PUT' } 41 | }), 42 | 43 | Clients: $resource(server + '/clients/:clientId/:property', { clientId: '@_id', property: '@property' }, { 44 | update: { method: 'PUT' } 45 | }), 46 | 47 | Transactions: $resource(server + '/transactions/:transactionId', { transactionId: '@_id' }), 48 | 49 | BulkReruns: $resource(server + '/bulkrerun'), 50 | 51 | Mediators: $resource(server + '/mediators/:urn', { urn: '@urn' }, { 52 | update: { method: 'PUT' } 53 | }), 54 | MediatorConfig: $resource(server + '/mediators/:urn/config', { urn: '@urn' }, { 55 | update: { method: 'PUT' } 56 | }), 57 | MediatorChannels: $resource(server + '/mediators/:urn/channels', { urn: '@urn' }), 58 | 59 | // add the metric endpoints 60 | MetricsChannels: $resource(server + '/metrics/channels/:channelId'), 61 | MetricsTimeseries: $resource(server + '/metrics/timeseries/:type'), 62 | MetricsTimeseriesChannel: $resource(server + '/metrics/timeseries/:type/channels/:channelId'), 63 | 64 | Tasks: $resource(server + '/tasks/:taskId', { taskId: '@_id' }, { 65 | update: { method: 'PUT' } 66 | }), 67 | 68 | ContactGroups: $resource(server + '/groups/:groupId', { groupId: '@_id' }, { 69 | update: { method: 'PUT' } 70 | }), 71 | 72 | Events: $resource(server + '/events/:receivedTime'), 73 | Heartbeat: $resource(server + '/heartbeat'), 74 | 75 | // endpoint to restart the core server 76 | Restart: $resource(server + '/restart', {}), 77 | 78 | // User Token 79 | UserPasswordToken: $resource(server + '/token/:token', { token: '@token' }, { 80 | update: { method: 'PUT' } 81 | }), 82 | 83 | // user reset password request 84 | UserPasswordResetRequest: $resource(server + '/password-reset-request/:email', { email: '@email' }, {}), 85 | 86 | Keystore: $resource(server + '/keystore/:type/:property', { type: '@type', property: '@property' }, { 87 | update: { method: 'PUT' } 88 | }), 89 | 90 | Certificates: $resource(server + '/certificates', {}), 91 | 92 | // ATNA Audit log endpoint 93 | Audits: $resource(server + '/audits/:auditId', { auditId: '@_id' }), 94 | AuditsFilterOptions: $resource(server + '/audits-filter-options/', {}), 95 | 96 | // Logs API 97 | Logs: $resource(server + '/logs'), 98 | 99 | // Metadata API 100 | Metadata: $resource(server + '/metadata', {}, { 101 | save: { method: 'POST', isArray: true }, 102 | query: { method: 'GET', isArray: true } 103 | }), 104 | 105 | MetadataValidation: $resource(server + '/metadata/validate', {}, { 106 | save: { method: 'POST', isArray: true } 107 | }), 108 | 109 | // Visualizer API 110 | Visualizers: $resource(server + '/visualizers/:id', { id: '@_id' }, { 111 | update: { method: 'PUT' } 112 | }), 113 | 114 | // About page (versions) API 115 | About: $resource(server + '/about') 116 | 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/scripts/utils/index.js: -------------------------------------------------------------------------------- 1 | export * from './sidebar' 2 | export * from './utils' 3 | -------------------------------------------------------------------------------- /app/scripts/utils/sidebar.js: -------------------------------------------------------------------------------- 1 | export function toggleSubMenu (element) { 2 | const $li = $(element).parent('li') 3 | const $ul = $(element).next('ul') 4 | 5 | if ($li.hasClass('open')) { 6 | $ul.slideUp(350) 7 | $li.removeClass('open') 8 | } else { 9 | $('.nav > li > ul').slideUp(350) 10 | $('.nav > li').removeClass('open') 11 | $ul.slideDown(350) 12 | $li.addClass('open') 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/styles/angular-taglist-directive.css: -------------------------------------------------------------------------------- 1 | div.taglist { border:1px solid #CCC; background: #FFF; padding:5px; width:300px; } 2 | div.taglist span.tag { border: 1px solid #a5d24a; -moz-border-radius:2px; -webkit-border-radius:2px; display: block; float: left; padding: 5px; text-decoration:none; background: #cde69c; color: #638421; margin-right: 5px; margin-bottom:5px;font-family: sans-serif; font-size:13px;} 3 | div.taglist span.tag a { font-weight: bold; color: #82ad2b; text-decoration:none; font-size: 11px; } 4 | div.taglist div.tag-input input { margin:0px; font-family: sans-serif; font-size: 13px; border:1px solid transparent; padding:5px; background: transparent; color: #000; outline:0px; margin-right:5px; margin-bottom:5px; } 5 | .tags_clear { clear: both; width: 100%; height: 0px; } 6 | -------------------------------------------------------------------------------- /app/styles/highlightjs/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #333; 31 | font-weight: bold; 32 | } 33 | 34 | .hljs-number, 35 | .hljs-hexcolor, 36 | .ruby .hljs-constant { 37 | color: #008080; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-tag .hljs-value, 42 | .hljs-phpdoc, 43 | .hljs-dartdoc, 44 | .tex .hljs-formula { 45 | color: #d14; 46 | } 47 | 48 | .hljs-title, 49 | .hljs-id, 50 | .scss .hljs-preprocessor { 51 | color: #900; 52 | font-weight: bold; 53 | } 54 | 55 | .hljs-list .hljs-keyword, 56 | .hljs-subst { 57 | font-weight: normal; 58 | } 59 | 60 | .hljs-class .hljs-title, 61 | .hljs-type, 62 | .vhdl .hljs-literal, 63 | .tex .hljs-command { 64 | color: #458; 65 | font-weight: bold; 66 | } 67 | 68 | .hljs-tag, 69 | .hljs-tag .hljs-title, 70 | .hljs-rules .hljs-property, 71 | .django .hljs-tag .hljs-keyword { 72 | color: #000080; 73 | font-weight: normal; 74 | } 75 | 76 | .hljs-attribute, 77 | .hljs-variable, 78 | .lisp .hljs-body { 79 | color: #008080; 80 | } 81 | 82 | .hljs-regexp { 83 | color: #009926; 84 | } 85 | 86 | .hljs-symbol, 87 | .ruby .hljs-symbol .hljs-string, 88 | .lisp .hljs-keyword, 89 | .clojure .hljs-keyword, 90 | .scheme .hljs-keyword, 91 | .tex .hljs-special, 92 | .hljs-prompt { 93 | color: #990073; 94 | } 95 | 96 | .hljs-built_in { 97 | color: #0086b3; 98 | } 99 | 100 | .hljs-preprocessor, 101 | .hljs-pragma, 102 | .hljs-pi, 103 | .hljs-doctype, 104 | .hljs-shebang, 105 | .hljs-cdata { 106 | color: #999; 107 | font-weight: bold; 108 | } 109 | 110 | .hljs-deletion { 111 | background: #fdd; 112 | } 113 | 114 | .hljs-addition { 115 | background: #dfd; 116 | } 117 | 118 | .diff .hljs-change { 119 | background: #0086b3; 120 | } 121 | 122 | .hljs-chunk { 123 | color: #aaa; 124 | } 125 | -------------------------------------------------------------------------------- /app/styles/morris.css: -------------------------------------------------------------------------------- 1 | .morris-hover{position:absolute;z-index:1000}.morris-hover.morris-default-style{border-radius:10px;padding:6px;color:#666;background:rgba(255,255,255,0.8);border:solid 2px rgba(230,230,230,0.8);font-family:sans-serif;font-size:12px;text-align:center}.morris-hover.morris-default-style .morris-hover-row-label{font-weight:bold;margin:0.25em 0} 2 | .morris-hover.morris-default-style .morris-hover-point{white-space:nowrap;margin:0.1em 0} 3 | -------------------------------------------------------------------------------- /app/views/about.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 | 8 |
9 |
10 |

  About the OpenHIM

11 |

12 | Details about the application. 13 |

14 |
15 |
16 | 17 | 18 |
19 | 20 |
21 |

Running versions of Core and Console:

22 |
OpenHIM Admin Console v{{ aboutInfo.currentConsoleVersion }}
23 |
OpenHIM Core v{{ aboutInfo.currentCoreVersion }}
24 |
Versions are 25 | Compatible 26 | Incompatible 27 |
28 | 29 | 30 |
31 |

Versions of Core compatible with this version of Console:

32 |
{{ aboutInfo.minimumCoreVersion }} <= Core < {{ aboutInfo.maximumCoreVersion }}
33 |
34 |
35 | 36 | 37 |
38 |

Server Timezone:

39 |
Timezone: {{ aboutInfo.serverTimezone }}
40 |
Server Time: {{ aboutInfo.serverTime }}
41 |
42 |
43 | 44 | 45 |
{{ alert.msg }}
46 | 47 | 48 |
49 |
50 |
51 |
52 | -------------------------------------------------------------------------------- /app/views/auditsContentModal.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 18 | 19 | -------------------------------------------------------------------------------- /app/views/clientsmodal.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | 57 | 58 | 83 | -------------------------------------------------------------------------------- /app/views/confirmModal.html: -------------------------------------------------------------------------------- 1 | 4 | 7 | 11 | -------------------------------------------------------------------------------- /app/views/contactGroups.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 |
9 |

  Contact Lists

10 |

11 | Manage OpenHIM contact lists. These contact lists are used for transaction alerting (found in each channel's configuration) and user reports (found in each user's configuration). 12 |

13 |
14 |
15 | 16 | 17 |
{{alert.msg}}
18 | 19 | 20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 42 |
#ListUsers
{{ $index +1 }}{{ group.group }} {{ user.user }}{{$last ? '' : ', '}} 37 | 38 | 39 |
43 |
44 | 45 | 46 |
47 | 48 |
49 | 50 | 51 |
{{alert.msg}}
52 | 53 | 54 | 55 |
{{alert.msg}}
56 | 57 | 58 | 59 |
60 |
61 |
62 |
63 |
64 | -------------------------------------------------------------------------------- /app/views/dashboard.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 | 8 |
9 |
10 |

  Dashboard

11 |
12 |
13 | 14 | 15 |
{{alert.msg}}
16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 |
25 |
26 |
27 | Active Channels:
{{activeChannels}} 28 |
29 |
30 | Total Transactions:
{{transactionLoadData.loadTotal}} 31 |
32 |
33 | Average Response Time:
{{transactionLoadData.avgResponseTime}} ms 34 |
35 |
36 |
37 | 38 |
39 |
40 |
Transaction Load (per {{selectedDateType.type}})
41 |
42 | 43 | 44 |
{{alert.msg}}
45 | 46 | 47 |
48 |
52 |
53 |
54 |
55 | 56 |
57 |
58 |
Average Response Time (ms)
59 |
60 | 61 | 62 |
{{alert.msg}}
63 | 64 | 65 |
66 |
70 |
71 |
72 |
73 | 74 |
75 |
76 |
Transaction Statuses
77 |
78 | 79 | 80 |
{{alert.msg}}
81 | 82 | 83 |
84 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | -------------------------------------------------------------------------------- /app/views/forgotPassword.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 24 |
25 |
-------------------------------------------------------------------------------- /app/views/login.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 70 |
71 |
72 | -------------------------------------------------------------------------------- /app/views/logs.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 |
9 |

  Server Logs

10 |
11 |
12 | 13 |
14 | 15 | 16 |
17 |
18 | 19 | 25 |
26 |
27 | 28 | 29 |
30 |
31 | 32 | 33 |
34 |
35 | 38 |
39 | 40 |
41 | 42 |
43 |
44 | 45 | 46 |
47 | 48 |
49 | 50 |
51 |
52 |
53 |
54 |
55 | -------------------------------------------------------------------------------- /app/views/mediatorConfigModal.html: -------------------------------------------------------------------------------- 1 | 8 |
9 |
10 | This mediator has does not have any config options that can be specified here. 11 |
12 |
13 |
14 | 17 | 18 | 24 |
-------------------------------------------------------------------------------- /app/views/mediators.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |
8 |
9 |
10 |

  Mediators

11 |

12 | Mediators are add on services that run separately from the OpenHIM. They register themselves with the OpenHIM and once that is done they will be displayed here and their configuration details may be modified. Also, if a mediator is registered it will allow you to easily add routes that point to it in the channel configuration. 13 |

14 |
15 |
16 | 17 | 18 |
{{alert.msg}}
19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 54 | 55 | 59 | 60 | 61 |
#URNNameDescriptionVersionLast HeartbeatUptime at Heartbeat
42 | {{ $index +1 }} 43 | {{ mediator.urn }}{{ mediator.name }}{{ mediator.description }}{{ mediator.version }} 49 | Never 50 | {{mediator.lastHeartbeatDisplay}} 51 |
52 |
53 |
{{ mediator.uptimeDisplay }} 56 | 57 | 58 |
62 | 63 |
64 | 65 |
66 | 67 |
68 | 69 | 70 |
{{alert.msg}}
71 | 72 | 73 | 74 |
{{alert.msg}}
75 | 76 | 77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | -------------------------------------------------------------------------------- /app/views/monitoring.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 | -------------------------------------------------------------------------------- /app/views/partials/audit-operation.html: -------------------------------------------------------------------------------- 1 | 4 |
Made {{audit.ops.length}} change(s)
5 |
    6 |
  • 7 |
    8 | 9 |
    10 |
    11 | Added {{op.path}} with value: 12 |
    {{op.value | json}}
    13 |
    14 |
    15 | Added {{op.path}} with value {{op.value | json}} 16 |
    17 |
    18 | 19 |
    20 |
    21 | Replaced {{op.path}} with: 22 |
    {{op.value | json}}
    23 |
    24 |
    25 | Replaced {{op.path}} with {{op.value | json}} 26 |
    27 |
    28 | 29 |
    Removed {{op.path}}
    30 |
    31 |
  • 32 |
33 | -------------------------------------------------------------------------------- /app/views/partials/channels-tab-logs.html: -------------------------------------------------------------------------------- 1 |
2 | No audits found 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 19 | 20 | 21 |
DateUserChanges
{{audit.date | date:'medium'}}{{audit.updatedBy.name}} 17 | 18 |
22 | -------------------------------------------------------------------------------- /app/views/partials/channels-tab-user-access.html: -------------------------------------------------------------------------------- 1 |

2 | Manage the users and group that have access to view or rerun this channel's transactions. Ensure that these restrictions are used to hide an sensitive information that may be contained in a transaction. By default only admins can view and interact with a channel's transactions. 3 |

4 | 5 |
6 | 10 | 11 |
12 |
13 | 17 | 18 |
19 |
20 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /app/views/partials/clients-tab-basic-info.html: -------------------------------------------------------------------------------- 1 |

Describe some basic information about the client.

2 | 3 |
4 |
5 | 6 | 7 |
{{validationRequiredMsg}}
8 |
9 |
10 | 11 | 12 |
{{validationRequiredMsg}}
13 |
14 |
15 | 16 |
17 |
18 | 19 | 20 |
21 |
22 | 23 | 24 |
25 |
26 |
27 |
28 | 29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 |
37 |
38 | 39 | 40 |
41 |
42 | 43 | 44 |
45 |
46 | 47 |
48 |
49 | 50 |
51 | 52 | {{role.name}} 53 |
54 |
55 |
Either 'Assign Existing Roles' or 'Add New Role' is required!
56 |
57 |
58 | 59 | 60 |
Role/Client already exists
61 |
Either 'Assign Existing Roles' or 'Add New Role' is required!
62 |
63 |
64 | -------------------------------------------------------------------------------- /app/views/partials/mediator-config-display.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 60 | 61 |
4 | 5 | {{mediatorDefsMap[configKey].displayName}} 6 | {{configKey}} 7 | 8 | 9 | 11 | 12 |
13 |
{{configValue}}
14 |
15 | Show Values 16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 |
{{mk}}: {{mv}}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | 32 |
33 | 34 | 35 | 55 | 56 |
36 |
{{arrayValue}}
37 |
38 | Show Values 39 |
40 |
41 | 42 | 43 | 44 | 45 | 46 |
{{amk}}: {{amv}}
47 |
48 |
49 |
50 |
51 |
None
52 |
53 |
54 |
57 |
58 | 59 |
62 | -------------------------------------------------------------------------------- /app/views/partials/metrics-date-type-selector.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 |
19 | 27 |
28 |
29 | 32 | 33 |
34 |
35 | 38 | 39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /app/views/partials/taskDetails-filter-settings.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 | 16 |
17 |
18 | 19 | 30 |
31 |
32 | 33 | 41 |
42 |
43 | 47 | 54 |
55 |
56 | 57 |
58 | 59 |
60 | -------------------------------------------------------------------------------- /app/views/partials/tasks-filter-settings.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 9 | 20 |
21 |
22 | 26 | 34 |
35 |
36 | 40 | 49 |
50 |
51 | 55 | 61 |
62 |
63 | 64 |
65 | 66 |
67 | -------------------------------------------------------------------------------- /app/views/partials/user-settings-tabs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List Settings 5 |
6 | 10 |
11 | 12 | 13 |
14 |
15 |
16 | 20 |
21 | 22 | 23 |
24 |
25 |
26 | 27 | 28 | 29 | General Settings 30 |
31 | 35 |
36 | 37 | 38 |
39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /app/views/sidebar.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /app/views/transactionsBodyModal.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 15 | -------------------------------------------------------------------------------- /app/views/transactionsRerunModal.html: -------------------------------------------------------------------------------- 1 | 5 |
6 | 30 | 52 |
53 | -------------------------------------------------------------------------------- /app/views/visualizerModal.html: -------------------------------------------------------------------------------- 1 | 13 | 20 | 21 | 36 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | # Set defaults for the environment variables 2 | export OPENHIM_CONSOLE_PROTOCOL=${OPENHIM_CONSOLE_PROTOCOL:-"https"} 3 | export OPENHIM_CONSOLE_HOSTPATH=${OPENHIM_CONSOLE_HOSTPATH:-""} 4 | export OPENHIM_CORE_MEDIATOR_HOSTNAME=${OPENHIM_CORE_MEDIATOR_HOSTNAME:-"localhost"} 5 | export OPENHIM_MEDIATOR_API_PORT=${OPENHIM_MEDIATOR_API_PORT:-"8080"} 6 | export OPENHIM_MEDIATOR_HEALTH_WARNING_TIMEOUT=${OPENHIM_MEDIATOR_HEALTH_WARNING_TIMEOUT:-"60"} 7 | export OPENHIM_MEDIATOR_HEALTH_DANGER_TIMEOUT=${OPENHIM_MEDIATOR_HEALTH_DANGER_TIMEOUT:-"120"} 8 | export OPENHIM_CONSOLE_SHOW_LOGIN=${OPENHIM_CONSOLE_SHOW_LOGIN:-"true"} 9 | export KC_OPENHIM_SSO_ENABLED=${KC_OPENHIM_SSO_ENABLED:-"false"} 10 | export KC_FRONTEND_URL=${KC_FRONTEND_URL:-"http://localhost:9088"} 11 | export KC_REALM_NAME=${KC_REALM_NAME:-"platform-realm"} 12 | export KC_OPENHIM_CLIENT_ID=${KC_OPENHIM_CLIENT_ID:-"openhim-oauth"} 13 | 14 | cat config/default-env.json | envsubst | tee config/default.json 15 | 16 | nginx -g "daemon off;" 17 | -------------------------------------------------------------------------------- /infrastructure/centos/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM centos 2 | 3 | LABEL Jembi Health Systems NPC 4 | 5 | # install dependencies 6 | RUN yum install -y gcc gcc-c++ \ 7 | make cmake git \ 8 | rpm-build redhat-rpm-config && \ 9 | yum clean all 10 | 11 | RUN curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash 12 | RUN source ~/.bashrc 13 | RUN nvm install --lts 14 | RUN npm --version && node -v 15 | -------------------------------------------------------------------------------- /infrastructure/centos/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 5 | # configures the configuration version (we support older styles for 6 | # backwards compatibility). Please don't change it unless you know what 7 | # you're doing. 8 | Vagrant.configure("2") do |config| 9 | # The most common configuration options are documented and commented below. 10 | # For a complete reference, please see the online documentation at 11 | # https://docs.vagrantup.com. 12 | 13 | # Every Vagrant development environment requires a box. You can search for 14 | # boxes at https://vagrantcloud.com/search. 15 | config.vm.box = "centos/7" 16 | 17 | # Disable automatic box update checking. If you disable this, then 18 | # boxes will only be checked for updates when the user runs 19 | # `vagrant box outdated`. This is not recommended. 20 | # config.vm.box_check_update = false 21 | 22 | # Create a forwarded port mapping which allows access to a specific port 23 | # within the machine from a port on the host machine. In the example below, 24 | # accessing "localhost:8080" will access port 80 on the guest machine. 25 | # NOTE: This will enable public access to the opened port 26 | # config.vm.network "forwarded_port", guest: 80, host: 8080 27 | 28 | # Create a forwarded port mapping which allows access to a specific port 29 | # within the machine from a port on the host machine and only allow access 30 | # via 127.0.0.1 to disable public access 31 | # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" 32 | 33 | # Create a private network, which allows host-only access to the machine 34 | # using a specific IP. 35 | # config.vm.network "private_network", ip: "192.168.33.10" 36 | 37 | # Create a public network, which generally matched to bridged network. 38 | # Bridged networks make the machine appear as another physical device on 39 | # your network. 40 | # config.vm.network "public_network" 41 | 42 | # Share an additional folder to the guest VM. The first argument is 43 | # the path on the host to the actual folder. The second argument is 44 | # the path on the guest to mount the folder. And the optional third 45 | # argument is a set of non-required options. 46 | # config.vm.synced_folder "/source", "/destination" 47 | 48 | # Provider-specific configuration so you can fine-tune various 49 | # backing providers for Vagrant. These expose provider-specific options. 50 | # Example for VirtualBox: 51 | # 52 | # config.vm.provider "virtualbox" do |vb| 53 | # Display the VirtualBox GUI when booting the machine 54 | # vb.gui = true 55 | 56 | # Customize the amount of memory on the VM: 57 | # vb.memory = "1024" 58 | # end 59 | # 60 | # View the documentation for the provider you are using for more 61 | # information on available options. 62 | 63 | # Enable provisioning with a shell script. Additional provisioners such as 64 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 65 | # documentation for more information about their specific syntax and use. 66 | config.vm.provision "shell", privileged: false, inline: <<-SHELL 67 | sudo yum -y update 68 | sudo yum install -y git rpm-build redhat-rpm-config gcc-c++ make 69 | 70 | curl --silent --location https://rpm.nodesource.com/setup_8.x | sudo bash - 71 | sudo yum install -y nodejs 72 | 73 | # download master and install dependencies 74 | git clone https://github.com/jembi/openhim-console.git 75 | cd openhim-console/ 76 | npm install && npm install speculate 77 | 78 | # generate SPEC file for rpmbuild 79 | npm run spec 80 | 81 | # Link source folder with default rpmbuild 82 | ln -s ~/openhim-console ~/rpmbuild 83 | 84 | #Build rpm package 85 | rpmbuild -bb ~/rpmbuild/SPECS/openhim-console.spec 86 | SHELL 87 | 88 | end 89 | -------------------------------------------------------------------------------- /infrastructure/development/env/.gitignore: -------------------------------------------------------------------------------- 1 | .vagrant 2 | -------------------------------------------------------------------------------- /infrastructure/development/env/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | VAGRANTFILE_API_VERSION = "2" 5 | 6 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 7 | 8 | # Use trusty64 base box 9 | config.vm.box = "ubuntu/trusty64" 10 | config.vm.provider "virtualbox" do |v| 11 | v.customize ["modifyvm", :id, "--memory", 1024] 12 | end 13 | 14 | # Configure port forwarding 15 | config.vm.network "forwarded_port", guest: 9000, host: 9000 16 | 17 | config.vm.provision :shell do |shell| 18 | shell.inline = "mkdir -p /etc/puppet/modules; 19 | puppet module install willdurand/nodejs" 20 | end 21 | 22 | # Setup LC_ALL locale flag 23 | config.vm.provision :shell do |shell| 24 | shell.inline = "echo 'LC_ALL=\"en_US.UTF-8\"' >> /etc/default/locale" 25 | end 26 | 27 | config.vm.synced_folder "../../../", "/openhim-console" 28 | 29 | config.vm.provision :puppet do |puppet| 30 | puppet.manifests_path = "./" 31 | puppet.manifest_file = "openhim-console.pp" 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /infrastructure/development/env/openhim-console.pp: -------------------------------------------------------------------------------- 1 | # Puppet manifest 2 | # 3 | # Required modules: 4 | # willdurand/nodejs 5 | # 6 | 7 | # defaults for Exec 8 | Exec { 9 | path => ["/bin", "/sbin", "/usr/bin", "/usr/sbin", "/usr/local/bin", "/usr/local/sbin", "/usr/local/node/node-default/bin/"], 10 | user => "root", 11 | } 12 | 13 | # Install required packages 14 | Package { ensure => "installed" } 15 | package { "git": } 16 | package { "libfontconfig1": } 17 | 18 | class { "nodejs": 19 | version => "stable", 20 | make_install => false, 21 | } 22 | 23 | exec { "npm-install": 24 | cwd => "/openhim-console", 25 | command => "npm install", 26 | require => Class["nodejs"], 27 | } 28 | 29 | exec { "install-bower": 30 | cwd => "/openhim-console", 31 | command => "npm install -g bower", 32 | unless => "npm list -g bower", 33 | require => Class["nodejs"], 34 | } 35 | 36 | exec { "install-grunt": 37 | cwd => "/openhim-console", 38 | command => "npm install -g grunt-cli", 39 | unless => "npm list -g grunt-cli", 40 | require => Class["nodejs"], 41 | } 42 | 43 | exec { "bower-install": 44 | cwd => "/openhim-console", 45 | command => "bower --allow-root install", 46 | require => Exec["install-bower"], 47 | } 48 | -------------------------------------------------------------------------------- /infrastructure/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | networks: 3 | openhim: 4 | 5 | services: 6 | mongo-db: 7 | container_name: mongo-db 8 | image: mongo:4.0 9 | networks: 10 | - openhim 11 | volumes: 12 | - "mongo-data:/data/db" 13 | restart: unless-stopped 14 | 15 | openhim-core: 16 | container_name: openhim-core 17 | image: jembi/openhim-core:latest 18 | restart: unless-stopped 19 | environment: 20 | mongo_url: "mongodb://mongo-db/openhim-dev" 21 | mongo_atnaUrl: "mongodb://mongo-db/openhim-dev" 22 | NODE_ENV: "production" 23 | ports: 24 | - "8080:8080" 25 | - "6000:5000" 26 | - "6001:5001" 27 | networks: 28 | - openhim 29 | healthcheck: 30 | test: "curl -sS http://openhim-core:8080/heartbeat || exit 1" 31 | interval: 30s 32 | timeout: 30s 33 | retries: 3 34 | 35 | openhim-console: 36 | container_name: openhim-console 37 | image: jembi/openhim-console:latest 38 | restart: unless-stopped 39 | networks: 40 | - openhim 41 | ports: 42 | - "9090:80" 43 | volumes: 44 | - "../app/config/default.json:/usr/share/nginx/html/config/default.json" 45 | healthcheck: 46 | test: "curl -sS http://openhim-console || exit 1" 47 | interval: 30s 48 | timeout: 30s 49 | retries: 3 50 | 51 | volumes: 52 | mongo-data: 53 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Karma configuration 4 | // http://karma-runner.github.io/0.10/config/configuration-file.html 5 | 6 | module.exports = function (config) { 7 | config.set({ 8 | // base path, that will be used to resolve files and exclude 9 | basePath: '', 10 | 11 | // testing framework to use (jasmine/mocha/qunit/...) 12 | frameworks: ['mocha', 'sinon-chai', 'dirty-chai'], 13 | 14 | // list of files / patterns to load in the browser 15 | files: [ 16 | 'node_modules/angular/angular.js', 17 | 'node_modules/angular-mocks/angular-mocks.js', 18 | 'node_modules/moment/moment.js', 19 | 'dist/bundle.js', 20 | { pattern: 'dist/bundle.js.map', included: false, watched: false }, 21 | 'dist/index.html', 22 | { pattern: 'dist/fonts/*', watched: false, included: false, served: true, nocache: false }, 23 | 'test/spec/controllers/*.js', 24 | 'test/spec/services/*.js' 25 | ], 26 | proxies: { 27 | '/fonts/': 'http://localhost:8090/base/dist/fonts/' 28 | }, 29 | reporters: ['mocha'], 30 | 31 | // list of files / patterns to exclude 32 | exclude: [], 33 | 34 | captureTimeout: 300000, 35 | browserDisconnectTolerance: 3, 36 | browserDisconnectTimeout : 300000, 37 | browserNoActivityTimeout : 300000, 38 | 39 | // web server port 40 | port: 8090, 41 | mochaReporter: { 42 | showDiff: true, 43 | output: 'autowatch' 44 | }, 45 | 46 | // Start these browsers, currently available: 47 | // - Chrome 48 | // - ChromeCanary 49 | // - Firefox 50 | // - Opera 51 | // - Safari (only Mac) 52 | // - PhantomJS 53 | // - IE (only Windows) 54 | browsers: ['ChromeHeadless'] 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openhim-console", 3 | "description": "This application provides a web application to configure and manage the OpenHIM-core component.", 4 | "version": "1.18.3", 5 | "dependencies": { 6 | "http-server": "^14.1.1", 7 | "keycloak-js": "^20.0.3", 8 | "replace": "^1.2.2", 9 | "uuid": "^8.3.2" 10 | }, 11 | "devDependencies": { 12 | "@babel/core": "^7.22.1", 13 | "@babel/polyfill": "^7.12.1", 14 | "@babel/preset-env": "^7.15.6", 15 | "@kariudo/angular-fullscreen": "^1.0.2", 16 | "@types/rimraf": "^3.0.2", 17 | "angular": "^1.8.3", 18 | "angular-bootstrap-colorpicker": "^3.0.32", 19 | "angular-cookies": "^1.8.2", 20 | "angular-file-upload": "^2.6.1", 21 | "angular-highlightjs": "^0.7.1", 22 | "angular-mocks": "^1.8.2", 23 | "angular-resource": "^1.8.2", 24 | "angular-route": "^1.8.2", 25 | "angular-sanitize": "^1.8.2", 26 | "angular-ui-bootstrap": "^2.5.0", 27 | "babel-loader": "^8.2.2", 28 | "bootstrap": "^3.4.1", 29 | "chai": "^4.3.7", 30 | "copy-webpack-plugin": "^6.4.1", 31 | "crypto-js": "^4.1.1", 32 | "css-loader": "^4.3.0", 33 | "d3": "^7.8.4", 34 | "dirty-chai": "^2.0.1", 35 | "eonasdan-bootstrap-datetimepicker": "^4.17.49", 36 | "file-loader": "^6.2.0", 37 | "file-saver": "^2.0.5", 38 | "html-loader": "^1.3.2", 39 | "html-webpack-plugin": "^4.5.2", 40 | "jquery": "^3.6.0", 41 | "json-loader": "^0.5.7", 42 | "karma": "^6.4.2", 43 | "karma-chrome-launcher": "^3.1.0", 44 | "karma-dirty-chai": "^2.0.0", 45 | "karma-mocha": "^2.0.1", 46 | "karma-mocha-reporter": "^2.2.5", 47 | "karma-sinon-chai": "^2.0.2", 48 | "lodash": "^4.17.21", 49 | "mini-css-extract-plugin": "^2.7.6", 50 | "mocha": "^10.2.0", 51 | "moment": "^2.29.4", 52 | "moment-timezone": "^0.5.43", 53 | "morris.js": "git+https://github.com/morrisjs/morris.js.git#77c24f6b70df2b23c7bd5d4d67240850d1b83005", 54 | "ng-file-upload": "^12.2.13", 55 | "pretty-data": "^0.40.0", 56 | "raphael": "^2.3.0", 57 | "rimraf": "^3.0.2", 58 | "sinon": "^9.2.4", 59 | "sinon-chai": "3.5.0", 60 | "speculate": "^2.1.1", 61 | "standard": "^17.0.0", 62 | "terser-webpack-plugin": "^4.2.3", 63 | "url-loader": "^4.1.1", 64 | "webpack": "^5.84.1", 65 | "webpack-bundle-analyzer": "^4.8.0", 66 | "webpack-cli": "^5.1.1", 67 | "webpack-dev-server": "^4.15.0", 68 | "webpack-merge": "^5.8.0", 69 | "webpack-sources": "^1.4.3" 70 | }, 71 | "engines": { 72 | "node": ">=0.12.0" 73 | }, 74 | "standard": { 75 | "globals": [ 76 | "angular", 77 | "localStorage", 78 | "Blob", 79 | "sessionStorage", 80 | "FileReader", 81 | "$" 82 | ] 83 | }, 84 | "spec": { 85 | "requires": [ 86 | "nodejs" 87 | ], 88 | "environment": { 89 | "NODE_ENV": "production" 90 | }, 91 | "post": [ 92 | "npm install -g http-server" 93 | ] 94 | }, 95 | "scripts": { 96 | "start": "http-server ./dist -p 9000", 97 | "start:dev": "webpack-dev-server --config webpack.development.js", 98 | "clean": "rimraf dist", 99 | "lint": "standard 'app/**/*.js'", 100 | "lint:fix": "npm run lint -- --fix", 101 | "test": "karma start --single-run", 102 | "test:watch": "karma start --auto-watch", 103 | "prepare": "npm run clean && npm run build:prod", 104 | "build": "webpack --config webpack.development.js", 105 | "build:prod": "NODE_OPTIONS=--max_old_space_size=4096 webpack --config webpack.production.js", 106 | "version": "node versionManager.js && git add -u", 107 | "spec": "speculate" 108 | }, 109 | "repository": { 110 | "type": "git", 111 | "url": "https://github.com/jembi/openhim-console.git" 112 | }, 113 | "keywords": [ 114 | "openhim", 115 | "openhie" 116 | ], 117 | "author": "Jembi Health Systems NPC", 118 | "license": "MPL-2.0", 119 | "bugs": { 120 | "url": "https://github.com/jembi/openhim-console/issues" 121 | }, 122 | "homepage": "http://openhim.org" 123 | } 124 | -------------------------------------------------------------------------------- /packaging/README.md: -------------------------------------------------------------------------------- 1 | # Packaging OpenHIM Console 2 | 3 | ## Debian Packaging 4 | 5 | To create a debian package execute `./create-deb.sh`. This will run you through the process of creating a deb file. It can even upload a package to launchpad for inclusion in the ubuntu repositories if you so choose. 6 | 7 | To upload to launchpad you will have to have a public key create in gpg and registered with launchpad. You must link you .gnupg folder to the /packaging folder of this project for the upload to use it. From inside the /packaging folder execute `ln -s ~/.gnupg`. 8 | 9 | You must also have an environment variable set with the id of the key to use. View your keys with `gpg --list-keys` and export the id (the part after the '/') with `export DEB_SIGN_KEYID=xxx`. Now you should be all set to upload to launchpad. Use the following details when running the **./create-deb** script. 10 | 11 | Login: openhie 12 | PPA: release 13 | 14 | ## Bundled Release 15 | 16 | A bundled release will ensure all the relevant dependencies are downloaded and bundled into a built version of the OpenHIM console. Only the relevant scripts needed to run the OpenHIM console are added to the bundled release. 17 | 18 | To create a new build release execute the below command. This does assume that your Linux distribution has the `zip` and `tar` modules installed 19 | 20 | `./build-release-zip.sh ` 21 | 22 | E.g 23 | 24 | `./build-release-zip.sh v5.2.4` 25 | 26 | ## CentOS RPM Packaging 27 | 28 | Building the CentOS package makes uses of a CentOS docker container which runs various commands to build the package. 29 | 30 | Execute the `build-docker-centos-rpm.sh` bash script with a specific release version as an argument to build the RPM package on a specific release version. 31 | 32 | `build-docker-centos-rpm.sh 1.13.0-rc.1` will build and RPM package for the 1.13.0-rc.1 release of the OpenHIM Console 33 | 34 | Once the bash script has completed and cleaned up after itself, you will see the built rpm package in the directory of this script. The package will look something like: 35 | `openhim-console-1.13.0-rc.1-1.x86_64.rpm` 36 | -------------------------------------------------------------------------------- /packaging/build-docker-centos-rpm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RELEASE_VERSION=$1 4 | if [ -z ${RELEASE_VERSION} ] 5 | then 6 | echo "You need so specify the release version you wish to build: e.g './build-docker-centos-rpm.sh 1.18.0'" 7 | echo "https://github.com/jembi/openhim-console/releases" 8 | exit 9 | fi 10 | 11 | # Set docker container name to build RPM package 12 | containerName=openhim-console-centos-rpm 13 | 14 | # Define the CentOS version to build in the docker container 15 | docker pull centos:7 16 | 17 | docker run -t -d --rm --name $containerName centos:7 /bin/bash 18 | 19 | echo "Update packages: " 20 | docker exec -it $containerName sh -c "yum -y update" 21 | 22 | echo "Install needed packages: " 23 | docker exec -it $containerName sh -c "yum install -y git rpm-build redhat-rpm-config gcc-c++ make" 24 | 25 | echo "Install needed packages: " 26 | docker exec -it $containerName sh -c "curl --silent --location https://rpm.nodesource.com/setup_14.x | bash -" 27 | 28 | echo "Install needed packages: " 29 | docker exec -it $containerName sh -c "yum install -y nodejs" 30 | 31 | echo "Fetch release version from Github" 32 | docker exec -it $containerName sh -c "mkdir /openhim-console && curl -sL 'https://github.com/jembi/openhim-console/archive/v$RELEASE_VERSION.tar.gz' | tar --strip-components=1 -zxv -C /openhim-console" 33 | 34 | echo "npm install && npm install speculate && npm run build" 35 | docker exec -it $containerName sh -c "cd /openhim-console && npm install && npm install speculate && npm run build:prod && npm run spec" 36 | 37 | echo "Symlink the openhim-console folder with the rpmbuild folder" 38 | docker exec -it $containerName sh -c "ln -s /openhim-console ~/rpmbuild" 39 | 40 | # if the Release Version incluldes a dash, apply workaround for rpmbuild to not break on dashes 41 | if [[ "${RELEASE_VERSION}" == *"-"* ]] 42 | then 43 | RELEASE_VERSION_TEMP=${RELEASE_VERSION//-/_} 44 | echo "Release Version contains unsupported dash (-) for building rpm package. Replacing with underscore (_) temporarily" 45 | docker exec -it $containerName sh -c "sed -i 's/$RELEASE_VERSION/$RELEASE_VERSION_TEMP/g' ~/rpmbuild/SPECS/openhim-console.spec" 46 | fi 47 | 48 | echo "Build RPM package from spec" 49 | docker exec -it $containerName sh -c "rpmbuild -bb ~/rpmbuild/SPECS/openhim-console.spec" 50 | 51 | # if the Release Version incluldes a dash, apply workaround for rpmbuild to not break on dashes 52 | if [[ "${RELEASE_VERSION}" == *"-"* ]] 53 | then 54 | RELEASE_VERSION_TEMP=${RELEASE_VERSION//-/_} 55 | echo "Rename the generated RPM package to the expected release version name (revert the changes from underscore to dashes)" 56 | docker exec -it $containerName sh -c "mv /openhim-console/RPMS/x86_64/openhim-console-$RELEASE_VERSION_TEMP-1.x86_64.rpm /openhim-console/RPMS/x86_64/openhim-console-$RELEASE_VERSION-1.x86_64.rpm" 57 | fi 58 | 59 | echo "Extract RPM package from container" 60 | docker cp $containerName:/openhim-console/RPMS/x86_64/openhim-console-$RELEASE_VERSION-1.x86_64.rpm . 61 | 62 | # Stop the container to ensure it gets cleaned up after running to commands 63 | echo "Removing the container" 64 | docker stop $containerName 65 | -------------------------------------------------------------------------------- /packaging/build-release-zip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | if (( $# < 1)); then 6 | echo "OpenHIM release build: Builds a specific tagged release ready for deployment"; 7 | echo "Usage: $0 TAG"; 8 | exit 0; 9 | fi 10 | 11 | tag=$1; 12 | shift; 13 | 14 | echo "NB!" 15 | echo "To create the tagged build, various git interactions need to take place. " 16 | echo "This will create a temporary branch as well as remove any changes you have havent yet committed" 17 | read -p "Do you wish to proceed? [y/N]" -n 1 -r 18 | 19 | echo "" 20 | 21 | if [[ $REPLY =~ ^[Yy]$ ]]; then 22 | cd .. 23 | 24 | echo "Git: setup branch/tag" 25 | git checkout -- . 26 | git checkout master 27 | git pull origin master 28 | git fetch --tags 29 | git checkout tags/$tag -b "build-release-$tag" 30 | 31 | echo "npm: clean and build package" 32 | rm -rf node_modules 33 | npm install 34 | 35 | echo "zip: build release version: $tag" 36 | cd dist/ 37 | zip -r ../packaging/build.openhim-console.$tag.zip . 38 | tar -zcvf ../packaging/build.openhim-console.$tag.tar.gz . 39 | cd .. 40 | 41 | echo "Git cleanup" 42 | git checkout -- . 43 | git checkout master 44 | git branch -D "build-release-$tag" 45 | 46 | echo "New OpenHIM Console build zipped"; 47 | fi 48 | -------------------------------------------------------------------------------- /packaging/create-deb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #Exit on error 3 | set -e 4 | 5 | CONSOLEDIR=/usr/share/openhim-console 6 | 7 | HOME=`pwd` 8 | AWK=/usr/bin/awk 9 | HEAD=/usr/bin/head 10 | DCH=/usr/bin/dch 11 | 12 | cd $HOME/targets 13 | TARGETS=(*) 14 | echo "Targets: $TARGETS" 15 | cd $HOME 16 | 17 | PKG=openhim-console 18 | 19 | echo -n "Which version of the OpenHIM-console (from github releases) would you like this package to install? (eg. 1.3.0) " 20 | read OPENHIM_VERSION 21 | 22 | echo -n "Would you like to upload the build(s) to Launchpad? [y/N] " 23 | read UPLOAD 24 | if [[ "$UPLOAD" == "y" || "$UPLOAD" == "Y" ]]; then 25 | if [ -n "$LAUNCHPADPPALOGIN" ]; then 26 | echo Using $LAUNCHPADPPALOGIN for Launchpad PPA login 27 | echo "To Change You can do: export LAUNCHPADPPALOGIN=$LAUNCHPADPPALOGIN" 28 | else 29 | echo -n "Enter your launchpad login for the ppa and press [ENTER]: " 30 | read LAUNCHPADPPALOGIN 31 | echo "You can do: export LAUNCHPADPPALOGIN=$LAUNCHPADPPALOGIN to avoid this step in the future" 32 | fi 33 | 34 | if [ -n "${DEB_SIGN_KEYID}" ]; then 35 | echo Using ${DEB_SIGN_KEYID} for Launchpad PPA login 36 | echo "To Change You can do: export DEB_SIGN_KEYID=${DEB_SIGN_KEYID}" 37 | echo "For unsigned you can do: export DEB_SIGN_KEYID=" 38 | else 39 | echo "No DEB_SIGN_KEYID key has been set. Will create an unsigned" 40 | echo "To set a key for signing do: export DEB_SIGN_KEYID=" 41 | echo "Use gpg --list-keys to see the available keys" 42 | fi 43 | 44 | echo -n "Enter the name of the PPA: " 45 | read PPA 46 | fi 47 | 48 | 49 | BUILDDIR=$HOME/builds 50 | 51 | 52 | for TARGET in "${TARGETS[@]}" 53 | do 54 | TARGETDIR=$HOME/targets/$TARGET 55 | RLS=`$HEAD -1 $TARGETDIR/debian/changelog | $AWK '{print $2}' | $AWK -F~ '{print $1}' | $AWK -F\( '{print $2}'` 56 | BUILDNO=$((${RLS##*-}+1)) 57 | 58 | if [ -z "$BUILDNO" ]; then 59 | BUILDNO=1 60 | fi 61 | 62 | BUILD=${PKG}_${OPENHIM_VERSION}-${BUILDNO}~${TARGET} 63 | echo "Building $BUILD ..." 64 | 65 | # Update changelog 66 | cd $TARGETDIR 67 | echo "Updating changelog for build ..." 68 | $DCH -Mv "${OPENHIM_VERSION}-${BUILDNO}~${TARGET}" --distribution "${TARGET}" "Release Debian Build ${OPENHIM_VERSION}-${BUILDNO}. Find v${OPENHIM_VERSION} changelog here: https://github.com/jembi/openhim-console/releases" 69 | 70 | # Clear and create packaging directory 71 | PKGDIR=${BUILDDIR}/${BUILD} 72 | rm -fr $PKGDIR 73 | mkdir -p $PKGDIR 74 | cp -R $TARGETDIR/* $PKGDIR 75 | 76 | # Fetch openhim-console from github releases 77 | mkdir -p $PKGDIR$CONSOLEDIR 78 | wget -O /tmp/openhim-console.tar.gz https://github.com/jembi/openhim-console/releases/download/v${OPENHIM_VERSION}/openhim-console-v${OPENHIM_VERSION}.tar.gz 79 | tar -vxzf /tmp/openhim-console.tar.gz --directory $PKGDIR$CONSOLEDIR 80 | 81 | cd $PKGDIR 82 | if [[ "$UPLOAD" == "y" || "$UPLOAD" == "Y" ]] && [[ -n "${DEB_SIGN_KEYID}" && -n "{$LAUNCHPADLOGIN}" ]]; then 83 | echo "Uploading to PPA ${LAUNCHPADPPALOGIN}/${PPA}" 84 | 85 | CHANGES=${BUILDDIR}/${BUILD}_source.changes 86 | 87 | DPKGCMD="dpkg-buildpackage -k${DEB_SIGN_KEYID} -S -sa " 88 | $DPKGCMD 89 | DPUTCMD="dput ppa:$LAUNCHPADPPALOGIN/$PPA $CHANGES" 90 | $DPUTCMD 91 | else 92 | echo "Not uploading to launchpad" 93 | DPKGCMD="dpkg-buildpackage -uc -us" 94 | $DPKGCMD 95 | fi 96 | done 97 | -------------------------------------------------------------------------------- /packaging/targets/trusty/debian/config: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /usr/share/debconf/confmodule 4 | 5 | db_input critical openhim-console/selecthost || true 6 | db_input critical openhim-console/selectport || true 7 | 8 | db_go || true 9 | -------------------------------------------------------------------------------- /packaging/targets/trusty/debian/control: -------------------------------------------------------------------------------- 1 | Source: openhim-console 2 | Maintainer: Ryan Crichton 3 | Section: web 4 | Priority: optional 5 | Standards-Version: 3.9.1 6 | Build-Depends: debhelper (>= 7) 7 | Homepage: https://github.com/openhie/openhim-console 8 | 9 | 10 | Package: openhim-console 11 | Architecture: all 12 | Depends: nginx, jq 13 | Recommends: openhim-core-js 14 | Description: The OpenHIM Console provides a web application to configure and manage the OpenHIM Core component. It provides the following features: 15 | -Configure and manage OpenHIM channels 16 | -View logged transactions 17 | -Configure clients that can access particular routes 18 | -Monitor the operations of the OpenHIM application 19 | -------------------------------------------------------------------------------- /packaging/targets/trusty/debian/install: -------------------------------------------------------------------------------- 1 | /usr/share/openhim-console/* 2 | /etc/nginx/sites-available/* 3 | -------------------------------------------------------------------------------- /packaging/targets/trusty/debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | CONSOLEDIR=/usr/share/openhim-console 5 | CONFIGFILE=$CONSOLEDIR/config/default.json 6 | ETCCONFIGFILE=/etc/openhim/console-config.json 7 | 8 | . /usr/share/debconf/confmodule 9 | db_get openhim-console/selecthost 10 | HOST=$RET 11 | db_get openhim-console/selectport 12 | PORT=$RET 13 | 14 | # Keep existing config file if there is one 15 | mkdir /etc/openhim || true 16 | if [ ! -f $ETCCONFIGFILE ]; then 17 | cp $CONFIGFILE $ETCCONFIGFILE 18 | else 19 | echo "Existing config file found, using that." 20 | fi 21 | 22 | # Replace the config with a link to a config in /etc/openhim/ 23 | rm $CONFIGFILE 24 | ln -s $ETCCONFIGFILE $CONFIGFILE 25 | 26 | # Set host and port of openhim-core 27 | cat $ETCCONFIGFILE | jq 'to_entries | map(if .key == "host" then . + {"value": "'$HOST'"} else . end) | from_entries' > /tmp/config.json && mv /tmp/config.json $ETCCONFIGFILE 28 | cat $ETCCONFIGFILE | jq 'to_entries | map(if .key == "port" then . + {"value": "'$PORT'"} else . end) | from_entries' > /tmp/config.json && mv /tmp/config.json $ETCCONFIGFILE 29 | 30 | # Enable the openhim-console site in nginx 31 | ln -sf /etc/nginx/sites-available/openhim-console /etc/nginx/sites-enabled/ 32 | # Disable the default site - can't have two sites on port 80 without server_name config 33 | rm -f /etc/nginx/sites-enabled/default 34 | 35 | service nginx reload 36 | 37 | echo "Successfully installed the OpenHIM-console!" 38 | 39 | exit 0 40 | -------------------------------------------------------------------------------- /packaging/targets/trusty/debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ "$1" = "purge" -a -e /usr/share/debconf/confmodule ]; then 5 | # Source debconf library. 6 | . /usr/share/debconf/confmodule 7 | # Remove my changes to the db. 8 | db_purge 9 | fi 10 | -------------------------------------------------------------------------------- /packaging/targets/trusty/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # Uncomment this to turn on verbose mode. 3 | export DH_VERBOSE=1 4 | 5 | REVISION := $(shell head -1 debian/changelog | sed 's/.*(//;s/).*//;s/.*-//') 6 | 7 | build: build-stamp 8 | build-stamp: 9 | dh_testdir 10 | touch build-stamp 11 | 12 | clean: 13 | dh_testdir 14 | dh_testroot 15 | rm -f build-stamp 16 | dh_clean 17 | 18 | install: build 19 | dh_testdir 20 | dh_testroot 21 | dh_prep 22 | dh_installdirs 23 | dh_install 24 | 25 | # Build architecture-independent files here. 26 | binary-indep: build install 27 | dh_testdir 28 | dh_testroot 29 | dh_installchangelogs 30 | dh_installdocs 31 | dh_installdebconf 32 | dh_link 33 | dh_compress 34 | dh_fixperms 35 | dh_installdeb 36 | dh_gencontrol 37 | dh_md5sums 38 | dh_builddeb 39 | 40 | # Build architecture-dependent files here. 41 | binary-arch: 42 | 43 | binary: binary-indep binary-arch 44 | .PHONY: build clean binary-indep binary-arch binary install -------------------------------------------------------------------------------- /packaging/targets/trusty/debian/templates: -------------------------------------------------------------------------------- 1 | Template: openhim-console/selecthost 2 | Type: string 3 | Default: localhost 4 | Description: What is the *public* hostname that the OpenHIM core server is running on? 5 | 6 | Template: openhim-console/selectport 7 | Type: string 8 | Default: 8080 9 | Description: What is the port that the OpenHIM core server API is running on? 10 | -------------------------------------------------------------------------------- /packaging/targets/trusty/etc/nginx/sites-available/openhim-console: -------------------------------------------------------------------------------- 1 | # Site config for the OpenHIM-console 2 | server { 3 | root /usr/share/openhim-console; 4 | index index.html; 5 | 6 | location / { 7 | try_files $uri $uri/ =404; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/runner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | End2end Test Runner 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/spec/controllers/about.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | 'use strict' 3 | /* jshint expr: true */ 4 | /* global sinon: false */ 5 | 6 | describe('Controller: AboutCtrl', function () { 7 | // load the controller's module 8 | beforeEach(module('openhimConsoleApp')) 9 | 10 | // setup config constant to be used for API server details 11 | beforeEach(function () { 12 | module('openhimConsoleApp', function ($provide) { 13 | $provide.constant('config', { version: '1.7.0', minimumCoreVersion: '3.0.0', protocol: 'https', host: 'localhost', hostPath: '', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 14 | }) 15 | }) 16 | 17 | var scope, createController, httpBackend, modalSpy // eslint-disable-line 18 | 19 | var coreResponse = { 20 | currentCoreVersion: '3.0.0', 21 | serverTimeZone: 'Africa/Johannesburg' 22 | } 23 | var meResponse = { 24 | user: { 25 | email: 'test@user.org', 26 | firstname: 'test', 27 | surname: 'test', 28 | groups: [ 29 | 'admin' 30 | ] 31 | } 32 | } 33 | 34 | // Initialize the controller and a mock scope 35 | beforeEach(inject(function ($controller, $rootScope, $httpBackend, $uibModal) { 36 | httpBackend = $httpBackend 37 | 38 | $httpBackend.when('GET', new RegExp('.*/about')).respond(coreResponse) 39 | 40 | modalSpy = sinon.spy($uibModal, 'open') 41 | 42 | createController = function () { 43 | $httpBackend.when('GET', new RegExp('.*/me')).respond(meResponse) 44 | 45 | scope = $rootScope.$new() 46 | return $controller('AboutCtrl', { $scope: scope }) 47 | } 48 | })) 49 | 50 | afterEach(function () { 51 | httpBackend.verifyNoOutstandingExpectation() 52 | httpBackend.verifyNoOutstandingRequest() 53 | }) 54 | 55 | it('should attach about information to the scope', function () { 56 | httpBackend.expectGET(new RegExp('.*/about')) 57 | createController() 58 | httpBackend.flush() 59 | 60 | scope.aboutInfo.currentConsoleVersion.should.equal('1.7.0') 61 | scope.aboutInfo.currentCoreVersion.should.equal('3.0.0') 62 | 63 | scope.aboutInfo.minimumCoreVersion.should.equal('3.0.0') 64 | scope.aboutInfo.maximumCoreVersion.should.equal('4.0.0') 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /test/spec/controllers/certificatesmodal.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global sinon: false */ 3 | 4 | describe('Controller: CertificatesModalCtrl', function () { 5 | // load the controller's module 6 | beforeEach(module('openhimConsoleApp')) 7 | 8 | // setup config constant to be used for API server details 9 | beforeEach(function () { 10 | module('openhimConsoleApp', function ($provide) { 11 | $provide.constant('config', { protocol: 'https', host: 'localhost', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 12 | }) 13 | }) 14 | 15 | var scope, createController, httpBackend 16 | var meResponse = { 17 | user: { 18 | email: 'test@user.org', 19 | firstname: 'test', 20 | surname: 'test', 21 | groups: [ 22 | 'admin' 23 | ] 24 | } 25 | } 26 | 27 | // Initialize the controller and a mock scope 28 | beforeEach(inject(function ($controller, $rootScope, $httpBackend) { 29 | httpBackend = $httpBackend 30 | 31 | $httpBackend.when('POST', new RegExp('.*/certificates')).respond( 32 | { 33 | certificate: '---BEGIN CERTIFICATE ---', 34 | key: '---BEGIN KEY ---' 35 | } 36 | ) 37 | 38 | scope = $rootScope.$new() 39 | var modalInstance = sinon.spy() 40 | 41 | createController = function () { 42 | $httpBackend.when('GET', new RegExp('.*/me')).respond(meResponse) 43 | $httpBackend.flush() 44 | 45 | var cert 46 | cert = { 47 | $save: sinon.spy() 48 | } 49 | return $controller('CertificatesModalCtrl', { 50 | $scope: scope, 51 | $uibModalInstance: modalInstance, 52 | cert: cert, 53 | certType: 'client' 54 | }) 55 | } 56 | })) 57 | 58 | afterEach(function () { 59 | httpBackend.verifyNoOutstandingExpectation() 60 | httpBackend.verifyNoOutstandingRequest() 61 | }) 62 | 63 | it('should run validateFormCertificates() for any validation errors - ngErrors.hasErrors -> TRUE', function () { 64 | createController() 65 | scope.cert.commonName = '' 66 | scope.cert.days = '' 67 | scope.cert.country = '' 68 | // run the validate 69 | scope.validateFormCertificates() 70 | scope.ngError.should.have.property('commonName', true) 71 | scope.ngError.should.have.property('days', true) 72 | }) 73 | 74 | it('should run validateFormCertificates() for any validation errors when the form is submitted - ngErrors.hasErrors -> TRUE', function () { 75 | createController() 76 | scope.cert.commonName = 'testCommonName' 77 | scope.cert.days = '' 78 | scope.cert.country = 'south africa' 79 | // run the 80 | scope.submitFormCertificate() 81 | scope.ngError.should.have.property('days', true) 82 | scope.ngError.should.have.property('country', true) // more than 2 letters 83 | }) 84 | 85 | it('should generate the download links', function () { 86 | createController() 87 | scope.cert.commonName = 'testCommonName' 88 | scope.cert.days = '365' 89 | scope.cert.country = 'ZA' 90 | // submit the form 91 | scope.submitFormCertificate() 92 | // Generate the file Names 93 | scope.keyName.should.equal('testCommonName.key.pem') 94 | scope.certName.should.equal('testCommonName.cert.crt') 95 | httpBackend.flush() 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /test/spec/controllers/contactGroups.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global sinon: false */ 3 | 4 | describe('Controller: ContactGroupsCtrl', function () { 5 | // load the controller's module 6 | beforeEach(module('openhimConsoleApp')) 7 | 8 | // setup config constant to be used for API server details 9 | beforeEach(function () { 10 | module('openhimConsoleApp', function ($provide) { 11 | $provide.constant('config', { protocol: 'https', host: 'localhost', hostPath: '', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 12 | }) 13 | }) 14 | 15 | var scope, createController, httpBackend, modalSpy 16 | var meResponse = { 17 | user: { 18 | email: 'test@user.org', 19 | firstname: 'test', 20 | surname: 'test', 21 | groups: [ 22 | 'admin' 23 | ] 24 | } 25 | } 26 | 27 | // Initialize the controller and a mock scope 28 | beforeEach(inject(function ($controller, $rootScope, $httpBackend, $uibModal) { 29 | httpBackend = $httpBackend 30 | 31 | $httpBackend.when('GET', new RegExp('.*/groups')).respond([ 32 | { group: 'Group 1', users: [{ user: 'User 1', method: 'sms', maxAlerts: 'no max' }] }, 33 | { group: 'Group 2', users: [{ user: 'User 22', method: 'email', maxAlerts: 'no max' }] }, 34 | { group: 'Group 3', users: [{ user: 'User 333', method: 'email', maxAlerts: '1 per hour' }] }, 35 | { group: 'Group 4', users: [{ user: 'User 4444', method: 'sms', maxAlerts: '1 per day' }] } 36 | ]) 37 | 38 | $httpBackend.when('GET', new RegExp('.*/users')).respond([ 39 | { firstname: 'User1', lastname: 'User1111', email: 'user1@email.com' }, 40 | { firstname: 'User22', lastname: 'User2222', email: 'user2@email.com' }, 41 | { firstname: 'User333', lastname: 'User3333', email: 'user3@email.com' }, 42 | { firstname: 'User4444', lastname: 'User4444', email: 'user4@email.com' } 43 | ]) 44 | 45 | modalSpy = sinon.spy($uibModal, 'open') 46 | 47 | createController = function () { 48 | $httpBackend.when('GET', new RegExp('.*/me')).respond(meResponse) 49 | 50 | scope = $rootScope.$new() 51 | return $controller('ContactGroupsCtrl', { $scope: scope }) 52 | } 53 | })) 54 | 55 | afterEach(function () { 56 | httpBackend.verifyNoOutstandingExpectation() 57 | httpBackend.verifyNoOutstandingRequest() 58 | }) 59 | 60 | it('should attach a list of contact groups to the scope', function () { 61 | httpBackend.expectGET(new RegExp('.*/groups')) 62 | createController() 63 | httpBackend.flush() 64 | scope.contactGroups.length.should.equal(4) 65 | }) 66 | 67 | it('should open a modal to confirm deletion of a client', function () { 68 | createController() 69 | httpBackend.flush() 70 | 71 | scope.confirmDelete(scope.contactGroups[0]) 72 | modalSpy.should.have.been.calledOnce() 73 | }) 74 | 75 | it('should open a modal to add a contact group', function () { 76 | createController() 77 | httpBackend.expectGET(new RegExp('.*/users')) 78 | scope.addContactGroup() 79 | modalSpy.should.have.been.calledOnce() 80 | 81 | httpBackend.flush() 82 | }) 83 | 84 | it('should open a modal to edit a contact group', function () { 85 | createController() 86 | httpBackend.expectGET(new RegExp('.*/users')) 87 | scope.editContactGroup() 88 | modalSpy.should.have.been.calledOnce() 89 | httpBackend.flush() 90 | }) 91 | }) 92 | -------------------------------------------------------------------------------- /test/spec/controllers/forgotPassword.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | describe('Controller: ForgotPasswordCtrl', function () { 4 | // load the controller's module 5 | beforeEach(module('openhimConsoleApp')) 6 | 7 | // setup config constant to be used for API server details 8 | beforeEach(function () { 9 | module('openhimConsoleApp', function ($provide) { 10 | $provide.constant('config', { protocol: 'https', host: 'localhost', hostPath: '', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 11 | }) 12 | }) 13 | 14 | var scope, createController, httpBackend 15 | 16 | // Initialize the controller and a mock scope 17 | beforeEach(inject(function ($controller, $rootScope, $httpBackend) { 18 | httpBackend = $httpBackend 19 | 20 | $httpBackend.when('GET', new RegExp('.*/password-reset-request/test@test.com')).respond('Successfully set user token/expiry for password reset.') 21 | $httpBackend.when('GET', new RegExp('.*/password-reset-request/fake@email.com')).respond(404) 22 | 23 | createController = function () { 24 | // Shouldn't be authenticating when requesting for password reset 25 | httpBackend.when('GET', new RegExp('.*/me')).respond(404) 26 | httpBackend.flush() 27 | 28 | scope = $rootScope.$new() 29 | return $controller('ForgotPasswordCtrl', { $scope: scope }) 30 | } 31 | })) 32 | 33 | afterEach(function () { 34 | httpBackend.verifyNoOutstandingExpectation() 35 | httpBackend.verifyNoOutstandingRequest() 36 | }) 37 | 38 | it('should test that the controller loaded', function () { 39 | createController() 40 | scope.should.have.property('userEmail', '') 41 | scope.should.have.property('showFormCtrl', true) 42 | }) 43 | 44 | it('should submit the form and return error for no email supplied', function () { 45 | createController() 46 | scope.should.have.property('userEmail', '') 47 | scope.should.have.property('showFormCtrl', true) 48 | 49 | scope.submitRequest() 50 | 51 | scope.alerts.should.have.property('forgotPassword') 52 | scope.alerts.forgotPassword[0].should.have.property('type', 'danger') 53 | scope.alerts.forgotPassword[0].should.have.property('msg', 'Please provide your email address') 54 | }) 55 | 56 | it('should submit the form and return error for trying to reset "root@openhim.org"', function () { 57 | createController() 58 | 59 | scope.userEmail = 'root@openhim.org' 60 | scope.submitRequest() 61 | 62 | scope.alerts.should.have.property('forgotPassword') 63 | scope.alerts.forgotPassword[0].should.have.property('type', 'danger') 64 | scope.alerts.forgotPassword[0].should.have.property('msg', 'Cannot reset password for "root@openhim.org"') 65 | }) 66 | 67 | it('should submit the form and return error for trying to reset an invalid email', function () { 68 | createController() 69 | 70 | scope.userEmail = 'fake@email.com' 71 | scope.submitRequest() 72 | 73 | scope.alerts.should.have.property('forgotPassword') 74 | scope.alerts.forgotPassword[0].should.have.property('type', 'warning') 75 | scope.alerts.forgotPassword[0].should.have.property('msg', 'Busy checking your credentials...') 76 | 77 | httpBackend.flush() 78 | 79 | scope.alerts.forgotPassword[0].should.have.property('type', 'danger') 80 | scope.alerts.forgotPassword[0].should.have.property('msg', 'Could not authenticate email address') 81 | }) 82 | 83 | it('should submit the form and send a reset email to the user', function () { 84 | createController() 85 | 86 | scope.userEmail = 'test@test.com' 87 | scope.submitRequest() 88 | 89 | scope.alerts.should.have.property('forgotPassword') 90 | scope.alerts.forgotPassword[0].should.have.property('type', 'warning') 91 | scope.alerts.forgotPassword[0].should.have.property('msg', 'Busy checking your credentials...') 92 | 93 | httpBackend.flush() 94 | 95 | scope.alerts.forgotPassword[0].should.have.property('type', 'info') 96 | scope.alerts.forgotPassword[0].should.have.property('msg', 'Password reset email has been sent...') 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /test/spec/controllers/logs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | describe('Controller: LogsCtrl', function () { 4 | // load the controller's module 5 | beforeEach(module('openhimConsoleApp')) 6 | 7 | // setup config constant to be used for API server details 8 | beforeEach(function () { 9 | module('openhimConsoleApp', function ($provide) { 10 | $provide.constant('config', { protocol: 'https', host: 'localhost', hostPath: '', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 11 | }) 12 | }) 13 | 14 | var scope, createController, httpBackend, location, rootScope 15 | var meResponse = { 16 | user: { 17 | email: 'test@user.org', 18 | firstname: 'test', 19 | surname: 'test', 20 | groups: [ 21 | 'admin' 22 | ] 23 | } 24 | } 25 | 26 | // Initialize the controller and a mock scope 27 | beforeEach(inject(function ($controller, $rootScope, $httpBackend, $location) { 28 | httpBackend = $httpBackend 29 | location = $location 30 | rootScope = $rootScope 31 | 32 | $httpBackend.when('GET', new RegExp('.*/logs')).respond([ 33 | { 34 | label: 'worker1', 35 | meta: {}, 36 | level: 'info', 37 | timestamp: '2015-10-29T09:40:31.536Z', 38 | message: 'Some message' 39 | }, 40 | { 41 | label: 'worker1', 42 | meta: {}, 43 | level: 'info', 44 | timestamp: '2015-10-29T09:40:39.128Z', 45 | message: 'Another message' 46 | } 47 | ]) 48 | 49 | createController = function () { 50 | $httpBackend.when('GET', new RegExp('.*/me')).respond(meResponse) 51 | 52 | scope = $rootScope.$new() 53 | return $controller('LogsCtrl', { $scope: scope }) 54 | } 55 | })) 56 | 57 | afterEach(function () { 58 | httpBackend.verifyNoOutstandingExpectation() 59 | httpBackend.verifyNoOutstandingRequest() 60 | }) 61 | 62 | it('should fetch initial logs', function () { 63 | createController() 64 | httpBackend.flush() 65 | 66 | var logArr = scope.logs.split('\n') 67 | logArr[0].should.contain(' - info: [worker1] Some message') 68 | logArr[1].should.contain(' - info: [worker1] Another message') 69 | }) 70 | 71 | it('should send data range in ISO 8601 format', function () { 72 | location.search({ level: 'debug', from: '2015-11-03 10:50', until: '2015-11-03 11:00' }) 73 | httpBackend.when('GET', new RegExp('.*/me')).respond(meResponse) 74 | httpBackend.flush() 75 | 76 | rootScope.$apply() 77 | 78 | httpBackend.expectGET(new RegExp('.*/logs\\?from=2015-11-03T10:50:00.*&level=debug&until=2015-11-03T11:00:00.*')) 79 | 80 | createController() 81 | httpBackend.flush() 82 | }) 83 | 84 | it('should reset the scope', function () { 85 | location.search({ level: 'debug', from: '2015-11-03 10:50', until: '2015-11-03 11:00' }) 86 | httpBackend.when('GET', new RegExp('.*/me')).respond(meResponse) 87 | httpBackend.flush() 88 | 89 | rootScope.$apply() 90 | 91 | createController() 92 | httpBackend.flush() 93 | scope.reset() 94 | 95 | httpBackend.when('GET', new RegExp('.*/me')).respond(meResponse) 96 | httpBackend.flush() 97 | 98 | scope.params.level.should.equal('info') 99 | expect(scope.params.from).to.not.exist() 100 | expect(scope.params.until).to.not.exist() 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /test/spec/controllers/testAuditDetails.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* jshint expr: true */ 3 | /* global sinon: false */ 4 | 5 | describe('Controller: AuditDetailsCtrl', function () { 6 | // load the controller's module 7 | beforeEach(module('openhimConsoleApp')) 8 | 9 | // setup config constant to be used for API server details 10 | beforeEach(function () { 11 | module('openhimConsoleApp', function ($provide) { 12 | $provide.constant('config', { protocol: 'https', host: 'localhost', hostPath: '', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 13 | }) 14 | }) 15 | 16 | var scope, createController, httpBackend, modalSpy // eslint-disable-line 17 | var meResponse = { 18 | user: { 19 | email: 'test@user.org', 20 | firstname: 'test', 21 | surname: 'test', 22 | groups: [ 23 | 'admin' 24 | ] 25 | } 26 | } 27 | 28 | // Initialize the controller and a mock scope 29 | beforeEach(inject(function ($controller, $rootScope, $httpBackend, $uibModal) { 30 | httpBackend = $httpBackend 31 | 32 | $httpBackend.when('GET', new RegExp('.*/audits/538ed0867962a27d5df259b0')).respond({ 33 | _id: '538ed0867962a27d5df259b0', 34 | rawMessage: 'This will be the raw ATNA message that gets received to be used as a backup reference', 35 | eventIdentification: { 36 | eventDateTime: '2015-02-17T15:38:25.282+02:00', 37 | eventOutcomeIndicator: '0', 38 | eventActionCode: 'R', 39 | eventID: { code: '222', displayName: 'Read', codeSystemName: 'DCM' }, 40 | eventTypeCode: { code: 'ITI-9', displayName: 'PIX Read', codeSystemName: 'IHE Transactions' } 41 | }, 42 | activeParticipant: [ 43 | { 44 | userID: 'pix|pix', 45 | alternativeUserID: '2100', 46 | userIsRequestor: 'false', 47 | networkAccessPointID: 'localhost', 48 | networkAccessPointTypeCode: '1', 49 | roleIDCode: { code: '110152', displayName: 'Destination', codeSystemName: 'DCM' } 50 | } 51 | ], 52 | auditSourceIdentification: { auditSourceID: 'openhim' }, 53 | participantObjectIdentification: [ 54 | { 55 | participantObjectID: '975cac30-68e5-11e4-bf2a-04012ce65b02^^^ECID&ECID&ISO', 56 | participantObjectTypeCode: '1', 57 | participantObjectTypeCodeRole: '1', 58 | participantObjectIDTypeCode: { code: '2', displayName: 'PatientNumber', codeSystemName: 'RFC-3881' } 59 | } 60 | ] 61 | }) 62 | 63 | $httpBackend.when('GET', new RegExp('.*/users/test@user.org')).respond({ _id: '539846c240f2eb682ffeca4b', email: 'test@user.org', firstname: 'test', surname: 'test', groups: ['admin', 'test', 'other'] }) 64 | 65 | modalSpy = sinon.spy($uibModal, 'open') 66 | 67 | createController = function () { 68 | $httpBackend.when('GET', new RegExp('.*/me')).respond(meResponse) 69 | 70 | scope = $rootScope.$new() 71 | return $controller('AuditDetailsCtrl', { $scope: scope, $routeParams: { auditId: '538ed0867962a27d5df259b0' } }) 72 | } 73 | })) 74 | 75 | afterEach(function () { 76 | httpBackend.verifyNoOutstandingExpectation() 77 | httpBackend.verifyNoOutstandingRequest() 78 | }) 79 | 80 | it('should attach a single audit to the scope', function () { 81 | createController() 82 | httpBackend.flush() 83 | scope.auditDetails.eventIdentification.eventDateTime.should.equal('2015-02-17T15:38:25.282+02:00') 84 | scope.auditDetails.activeParticipant.length.should.equal(1) 85 | scope.auditDetails.activeParticipant[0].userID.should.equal('pix|pix') 86 | scope.auditDetails.auditSourceIdentification.auditSourceID.should.equal('openhim') 87 | scope.auditDetails.participantObjectIdentification.length.should.equal(1) 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /test/spec/controllers/transactionDetails.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* jshint expr: true */ 3 | /* global sinon: false */ 4 | 5 | describe('Controller: TransactionDetailsCtrl', function () { 6 | // load the controller's module 7 | beforeEach(module('openhimConsoleApp')) 8 | 9 | // setup config constant to be used for API server details 10 | beforeEach(function () { 11 | module('openhimConsoleApp', function ($provide) { 12 | $provide.constant('config', { protocol: 'https', host: 'localhost', hostPath: '', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 13 | }) 14 | }) 15 | 16 | var scope, createController, httpBackend, modalSpy // eslint-disable-line 17 | var meResponse = { 18 | user: { 19 | email: 'test@user.org', 20 | firstname: 'test', 21 | surname: 'test', 22 | groups: [ 23 | 'admin' 24 | ] 25 | } 26 | } 27 | 28 | // Initialize the controller and a mock scope 29 | beforeEach(inject(function ($controller, $rootScope, $httpBackend, $uibModal) { 30 | httpBackend = $httpBackend 31 | 32 | $httpBackend.when('GET', new RegExp('.*/transactions/538ed0867962a27d5df259b0')).respond({ _id: '5322fe9d8b6add4b2b059ff5', name: 'Transaction 1', urlPattern: 'sample/api', channelID: '5322fe9d8b6add4b2b059dd8', clientID: '5344fe7d8b6add4b2b069dd7' }) 33 | $httpBackend.when('GET', new RegExp('.*/transactions?.*')).respond([{ name: 'Transaction 5', urlPattern: 'sample/api', _id: '5322fe9d8b6add4b2basd979', parentID: '5322fe9d8b6add4b2b059ff5' }]) 34 | 35 | $httpBackend.when('GET', new RegExp('.*/users/test@user.org')).respond({ _id: '539846c240f2eb682ffeca4b', email: 'test@user.org', firstname: 'test', surname: 'test', groups: ['admin', 'test', 'other'] }) 36 | 37 | $httpBackend.when('GET', new RegExp('.*/channels/5322fe9d8b6add4b2b059dd8')).respond({ _id: '5322fe9d8b6add4b2b059dd8', name: 'Sample JsonStub Channel 1', urlPattern: 'sample/api', allow: ['PoC'], txRerunAcl: ['test'], routes: [{ host: 'jsonstub.com', port: 80, primary: true }] }) 38 | 39 | $httpBackend.when('GET', new RegExp('.*/clients/5344fe7d8b6add4b2b069dd7')).respond({ _id: '5344fe7d8b6add4b2b069dd7', clientID: 'test1', clientDomain: 'test1.openhim.org', name: 'Test 1', roles: ['test'], passwordAlgorithm: 'sha512', passwordHash: '1234', passwordSalt: '1234' }) 40 | 41 | modalSpy = sinon.spy($uibModal, 'open') 42 | 43 | createController = function () { 44 | $httpBackend.when('GET', new RegExp('.*/me')).respond(meResponse) 45 | 46 | scope = $rootScope.$new() 47 | return $controller('TransactionDetailsCtrl', { $scope: scope, $routeParams: { transactionId: '538ed0867962a27d5df259b0' } }) 48 | } 49 | })) 50 | 51 | afterEach(function () { 52 | httpBackend.verifyNoOutstandingExpectation() 53 | httpBackend.verifyNoOutstandingRequest() 54 | }) 55 | 56 | it('should attach a single transaction to the scope', function () { 57 | createController() 58 | httpBackend.flush() 59 | scope.transactionDetails.name.should.equal('Transaction 1') 60 | scope.childTransactions.length.should.equal(1) 61 | }) 62 | 63 | it('should attach a single channel object to the scope', function () { 64 | createController() 65 | httpBackend.flush() 66 | 67 | scope.channel.name.should.equal('Sample JsonStub Channel 1') 68 | scope.channel.urlPattern.should.equal('sample/api') 69 | scope.channel.routes.length.should.equal(1) 70 | }) 71 | 72 | it('should attach a single client to the scope', function () { 73 | createController() 74 | httpBackend.flush() 75 | 76 | scope.client.name.should.equal('Test 1') 77 | scope.client.clientID.should.equal('test1') 78 | scope.client.clientDomain.should.equal('test1.openhim.org') 79 | scope.client.roles.length.should.equal(1) 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /test/spec/controllers/transactionsAddReqResModal.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global sinon: false */ 3 | 4 | describe('Controller: TransactionsAddReqResModalCtrl', function () { 5 | // load the controller's module 6 | beforeEach(module('openhimConsoleApp')) 7 | 8 | // setup config constant to be used for API server details 9 | beforeEach(function () { 10 | module('openhimConsoleApp', function ($provide) { 11 | $provide.constant('config', { protocol: 'https', host: 'localhost', hostPath: '', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 12 | }) 13 | }) 14 | 15 | var scope, createController, modalInstance, record, channel 16 | 17 | // Initialize the controller and a mock scope 18 | beforeEach(inject(function ($controller, $rootScope) { 19 | createController = function () { 20 | record = { 21 | name: 'second', 22 | _id: '53df5d525b6d133c7de9eb56', 23 | response: { 24 | status: 301, 25 | body: 'this is dummy body content', 26 | timestamp: '2014-08-04T10:15:46.007Z' 27 | }, 28 | request: { 29 | path: '/', 30 | querystring: '', 31 | method: 'GET' 32 | } 33 | } 34 | 35 | channel = { 36 | type: 'http' 37 | } 38 | 39 | scope = $rootScope.$new() 40 | modalInstance = sinon.spy() 41 | return $controller('TransactionsAddReqResModalCtrl', { 42 | $scope: scope, 43 | $uibModalInstance: modalInstance, 44 | record: record, 45 | channel: channel, 46 | transactionId: record._id, 47 | recordType: 'routes', 48 | index: 0 49 | }) 50 | } 51 | })) 52 | 53 | it('should attach a single record object to the scope', function () { 54 | createController() 55 | 56 | scope.record.name.should.equal('second') 57 | scope.record.response.status.should.equal(301) 58 | scope.record.request.method.should.equal('GET') 59 | }) 60 | 61 | it('should attach the channel object to the scope', function () { 62 | createController() 63 | 64 | scope.channel.type.should.equal('http') 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /test/spec/services/authinterceptor.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global CryptoJS: false */ 3 | 4 | describe('Service: Authinterceptor', function () { 5 | // load the service's module 6 | beforeEach(module('openhimConsoleApp')) 7 | 8 | // setup config constant to be used for API server details 9 | beforeEach(function () { 10 | module('openhimConsoleApp', function ($provide) { 11 | $provide.constant('config', { protocol: 'https', host: 'localhost', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 12 | }) 13 | }) 14 | 15 | // instantiate service 16 | var Authinterceptor, location 17 | beforeEach(inject(function (_Authinterceptor_, $location) { 18 | Authinterceptor = _Authinterceptor_ 19 | location = $location 20 | })) 21 | 22 | it('should redirect to login page if not authorised', function () { 23 | var response = { 24 | status: 401 25 | } 26 | Authinterceptor.responseError(response) 27 | var currentLocation = location.path(); 28 | currentLocation.should.be.equal('/login') 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /test/spec/services/login.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | describe('Service: login', function () { 4 | // load the service's module 5 | beforeEach(module('openhimConsoleApp')) 6 | 7 | // setup config constant to be used for API server details 8 | beforeEach(function () { 9 | module('openhimConsoleApp', function ($provide) { 10 | $provide.constant('config', { protocol: 'https', host: 'localhost', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 11 | }) 12 | }) 13 | 14 | // instantiate service 15 | var login, httpBackend 16 | beforeEach(inject(function (_login_, $httpBackend) { 17 | login = _login_ 18 | 19 | httpBackend = $httpBackend 20 | 21 | httpBackend.when('GET', new RegExp('.*/me')).respond(404) 22 | 23 | httpBackend.when('POST', new RegExp('.*/authenticate/local')).respond({ 24 | body: "User Authenticated successfully" 25 | }) 26 | 27 | httpBackend.when('GET', new RegExp('.*/users/.*')).respond({ 28 | __v: 0, 29 | _id: '539846c240f2eb682ffeca4b', 30 | email: 'test@user.org', 31 | firstname: 'test', 32 | surname: 'test', 33 | groups: [ 34 | 'admin' 35 | ] 36 | }) 37 | 38 | httpBackend.when('GET', new RegExp('.*/logout')).respond(201) 39 | })) 40 | 41 | afterEach(function () { 42 | httpBackend.verifyNoOutstandingExpectation() 43 | httpBackend.verifyNoOutstandingRequest() 44 | }) 45 | 46 | it('should login a user and fetch the currently logged in user', function () { 47 | httpBackend.expectPOST(new RegExp('.*/authenticate/local')) 48 | httpBackend.expectGET(new RegExp('.*/users/test@user.org')) 49 | 50 | login.login('test@user.org', 'test-password', function () {}) 51 | httpBackend.flush() 52 | 53 | var user = login.getLoggedInUser() 54 | 55 | user.should.exist() 56 | user.should.have.property('email', 'test@user.org') 57 | }) 58 | 59 | it('should logout a user', function () { 60 | httpBackend.expectGET(new RegExp('.*/logout')) 61 | 62 | login.logout(function () {}) 63 | httpBackend.flush() 64 | 65 | var user = login.getLoggedInUser() 66 | expect((user === null)).to.be.true() 67 | expect(login.isLoggedIn()).to.be.false() 68 | }) 69 | 70 | it('should check if a user is currently logged in', function () { 71 | httpBackend.flush() 72 | expect(login.isLoggedIn()).to.be.false() 73 | 74 | login.login('test@user.org', 'test-password', function () {}) 75 | httpBackend.flush() 76 | 77 | expect(login.isLoggedIn()).to.be.true() 78 | 79 | login.logout(function () {}) 80 | httpBackend.flush() 81 | 82 | expect(login.isLoggedIn()).to.be.false() 83 | }) 84 | }) 85 | -------------------------------------------------------------------------------- /test/spec/services/notify.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | /* global sinon:false */ 3 | 4 | describe('Service: Notify', function () { 5 | // load the service's module 6 | beforeEach(module('openhimConsoleApp')) 7 | 8 | // setup config constant to be used for API server details 9 | beforeEach(function () { 10 | module('openhimConsoleApp', function ($provide) { 11 | $provide.constant('config', { protocol: 'https', host: 'localhost', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 12 | }) 13 | }) 14 | 15 | // instantiate service 16 | var Notify 17 | var rootScope 18 | beforeEach(inject(function (_Notify_, $rootScope) { 19 | Notify = _Notify_ 20 | rootScope = $rootScope 21 | sinon.spy(rootScope, '$broadcast') 22 | })) 23 | 24 | it('should broadcast an event', function () { 25 | Notify.should.be.ok() 26 | Notify.notify('testEvent') 27 | rootScope.$broadcast.should.have.been.calledWith('testEvent') 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /test/spec/services/rest.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | describe('Service: Api', function () { 4 | // load the service's module 5 | beforeEach(module('openhimConsoleApp')) 6 | 7 | // setup config constant to be used for API server details 8 | beforeEach(function () { 9 | module('openhimConsoleApp', function ($provide) { 10 | $provide.constant('config', { protocol: 'https', host: 'localhost', port: 8080, title: 'Title', footerTitle: 'FooterTitle', footerPoweredBy: 'FooterPoweredBy' }) 11 | }) 12 | }) 13 | 14 | // instantiate service 15 | var Api 16 | beforeEach(inject(function (_Api_) { 17 | Api = _Api_ 18 | })) 19 | 20 | it('should define an Api service', function () { 21 | Api.should.be.ok() 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /versionManager.js: -------------------------------------------------------------------------------- 1 | var currentConsoleVersion = require('./package.json').version 2 | var https = require('https') 3 | var replace = require('replace') 4 | 5 | // Get latest version of core from Github 6 | var options = { 7 | protocol: 'https:', 8 | hostname: 'github.com', 9 | path: '/jembi/openhim-core-js/releases/latest', 10 | method: 'GET' 11 | } 12 | 13 | var replaceStringInline = function (inlineRegex, replacement, paths) { 14 | replace({ 15 | regex: inlineRegex, 16 | replacement: replacement, 17 | paths: paths, 18 | recursive: true, 19 | silent: true 20 | }) 21 | } 22 | 23 | var req = https.request(options, function (res) { 24 | var data = '' 25 | res.setEncoding('utf8') 26 | 27 | res.on('data', (chunk) => { 28 | data += chunk 29 | }) 30 | 31 | res.on('end', () => { 32 | var minimumCoreVersion = data.split('releases/tag/v')[1].substring(0, 5) 33 | // Add latest version of core to console config 34 | replaceStringInline(/"minimumCoreVersion":"[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}"/, `"minimumCoreVersion": "${minimumCoreVersion}"`, ['./app/config/default.json']) 35 | // Edit readme with compatible version of core 36 | replaceStringInline(/badge\/openhim--core-[0-9]{1,2}\.[0-9]{1,2}/, `badge/openhim--core-${minimumCoreVersion.substr(0, 3)}`, ['README.md']) 37 | replaceStringInline(/openhim.readthedocs.org\/en\/v[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}/, `openhim.readthedocs.org/en/v${minimumCoreVersion}`, ['README.md']) 38 | }) 39 | }) 40 | 41 | req.on('error', (e) => { 42 | console.log(`Problem fetching core version from github: ${e.message}`) 43 | }) 44 | 45 | req.end() 46 | 47 | // Keep version of console consistent throughout project 48 | replaceStringInline(/"version":"[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}"/, `"version": "${currentConsoleVersion}"`, ['./app/config/default.json']) 49 | replaceStringInline(/"version":"[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}"/, `"version": "${currentConsoleVersion}"`, ['./.travis/test.json']) 50 | replaceStringInline(/"version":"[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}"/, `"version": "${currentConsoleVersion}"`, ['./.travis/staging.json']) 51 | -------------------------------------------------------------------------------- /webpack.common.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const path = require('path') 4 | const webpack = require('webpack') 5 | const HtmlWebpackPlugin = require('html-webpack-plugin') 6 | const CopyWebpackPlugin = require('copy-webpack-plugin') 7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 8 | 9 | module.exports = { 10 | entry: './app/scripts/index', 11 | output: { 12 | path: path.resolve(__dirname, 'dist'), 13 | filename: 'bundle.js' 14 | }, 15 | resolve: { 16 | extensions: ['.js', '.json', '.jsx', '.css', '.html'], 17 | alias: { 18 | 'morris.js': path.resolve(__dirname, 'node_modules/morris.js/morris.js'), 19 | '~': path.resolve(__dirname, 'app') 20 | } 21 | }, 22 | target: 'web', 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.(ttf|eot|woff|woff2|svg)$/, 27 | loader: 'file-loader', 28 | options: { 29 | name: 'fonts/[name].[ext]' 30 | } 31 | }, 32 | { 33 | test: /\.(png|jpg|gif)$/, 34 | use: [ 35 | { 36 | loader: 'url-loader', 37 | options: { 38 | limit: 8192 39 | } 40 | } 41 | ] 42 | }, 43 | { 44 | test: /\.m?js$/, 45 | exclude: /(node_modules|bower_components)/, 46 | include: [path.resolve(__dirname, 'app')], 47 | use: { 48 | loader: 'babel-loader', 49 | options: { 50 | presets: ['@babel/preset-env'] 51 | } 52 | } 53 | } 54 | ] 55 | }, 56 | plugins: [ 57 | new webpack.ProvidePlugin({ 58 | $: 'jquery', 59 | jQuery: 'jquery', 60 | 'window.jQuery': 'jquery' 61 | }), 62 | new HtmlWebpackPlugin({ 63 | template: 'app/template.html' 64 | }), 65 | new MiniCssExtractPlugin({ 66 | filename: 'styles.css' 67 | }), 68 | new CopyWebpackPlugin({patterns: [ 69 | { from: 'app/404.html' }, 70 | { from: 'app/favicon.ico' }, 71 | { from: 'app/robots.txt' }, 72 | { 73 | context: 'app/config', 74 | from: '*', 75 | to: 'config/' 76 | } 77 | ]})] 78 | } 79 | -------------------------------------------------------------------------------- /webpack.development.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { merge } = require('webpack-merge') 4 | const common = require('./webpack.common.js') 5 | const path = require('path') 6 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 7 | 8 | console.log('Creating development bundle') 9 | 10 | module.exports = merge(common, { 11 | mode: 'development', 12 | module: { 13 | rules: [ 14 | { 15 | test: /\.html$/, 16 | include: [path.resolve(__dirname, 'app')], 17 | use: [ 18 | { 19 | loader: 'html-loader', 20 | options: { 21 | minimize: false 22 | } 23 | } 24 | ] 25 | }, 26 | { 27 | test: /\.css$/, 28 | use: [ 29 | { 30 | loader: MiniCssExtractPlugin.loader, 31 | options: {} 32 | }, 33 | 'css-loader' 34 | ] 35 | } 36 | ] 37 | }, 38 | devtool: 'source-map', 39 | devServer: { 40 | static: path.resolve(__dirname, 'app'), 41 | port: 9000, 42 | open: true, 43 | hot: true 44 | } 45 | }) 46 | -------------------------------------------------------------------------------- /webpack.production.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { merge } = require('webpack-merge') 4 | const common = require('./webpack.common.js') 5 | 6 | const path = require('path') 7 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 8 | const TerserPlugin = require('terser-webpack-plugin') 9 | 10 | console.log('Creating production bundle') 11 | 12 | module.exports = merge(common, { 13 | mode: 'production', 14 | optimization: { 15 | minimizer: [ 16 | new TerserPlugin({ 17 | terserOptions: { 18 | mangle: false 19 | } 20 | }) 21 | ] 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.html$/, 27 | include: [path.resolve(__dirname, 'app')], 28 | use: [ 29 | { 30 | loader: 'html-loader', 31 | options: { 32 | minimize: true 33 | } 34 | } 35 | ] 36 | }, 37 | { 38 | test: /\.css$/, 39 | use: [ 40 | { 41 | loader: MiniCssExtractPlugin.loader 42 | }, 43 | 'css-loader' 44 | ] 45 | } 46 | ] 47 | } 48 | }) 49 | --------------------------------------------------------------------------------