├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .npmrc ├── .prettierrc ├── Dockerfile ├── Makefile ├── README.md ├── appConfig.js ├── package.json ├── resources ├── appserver │ ├── static │ │ ├── boston_marathon │ │ │ └── marathon_map.png │ │ ├── buttercup_background.png │ │ ├── buttercup_spaceport │ │ │ └── background.png │ │ ├── emergency_department_ops │ │ │ ├── Ambulance_ic.svg │ │ │ ├── HomeResolved_ic.svg │ │ │ ├── HospitalAdmitted_ic.svg │ │ │ ├── PatientNurseRatio_ic.svg │ │ │ ├── TriagePerHour_ic.svg │ │ │ ├── WaitTime_ic.svg │ │ │ └── WalkIn_ic.svg │ │ └── sfo_airport │ │ │ └── sfo_airport_background.svg │ └── templates │ │ └── dashboard.html └── default │ ├── app.conf │ └── data │ └── ui │ ├── nav │ └── default.xml │ └── views │ ├── absolute_dashboard.xml │ ├── boston_marathon.xml │ ├── buttercup.xml │ ├── buttercup_spaceport.xml │ ├── custom_data_source.xml │ ├── donut.xml │ ├── dynamic_theming.xml │ ├── emergency_department_ops.xml │ ├── grid_dashboard.xml │ ├── radar.xml │ ├── sfo_airport.xml │ └── visualizations.xml ├── src └── pages │ ├── absolute_dashboard │ ├── definition.json │ └── index.jsx │ ├── boston_marathon │ ├── definition.json │ └── index.jsx │ ├── buttercup │ ├── definition.json │ └── index.jsx │ ├── buttercup_spaceport │ ├── definition.js │ ├── index.jsx │ ├── inputs │ │ ├── AgeInput.jsx │ │ ├── RadioBar.jsx │ │ ├── Rating.jsx │ │ └── index.js │ ├── presets │ │ └── index.js │ ├── svg │ │ ├── ISS.svg │ │ ├── amenities.svg │ │ ├── journey.svg │ │ ├── legend.svg │ │ └── seats.svg │ └── visualizations │ │ ├── IFrame.jsx │ │ └── index.js │ ├── custom_data_source │ ├── PostDataSource.js │ ├── README.md │ ├── definition.json │ ├── index.jsx │ └── utils.js │ ├── donut │ ├── Donut.jsx │ ├── definition.json │ └── index.jsx │ ├── dynamic_theming │ ├── Dashboard.jsx │ ├── definition.json │ └── index.jsx │ ├── emergency_department_ops │ ├── definition.js │ └── index.jsx │ ├── radar │ ├── Radar.jsx │ ├── definition.json │ └── index.jsx │ ├── sfo_airport │ ├── definition.json │ └── index.jsx │ └── visualizations │ ├── definition.json │ └── index.jsx ├── tools ├── prepare.js └── symlink.js ├── webpack.config.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-react"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['airbnb', 'prettier'], 3 | plugins: ['prettier'], 4 | rules: { 5 | 'prettier/prettier': ['error'], 6 | 'react/jsx-indent': 'off', 7 | 'react/jsx-indent-props': 'off', 8 | 'class-methods-use-this': 'off', 9 | 'react/function-component-definition': 'off', 10 | 'react/forbid-prop-types': 'off', 11 | 'react/require-default-props': 'off', 12 | }, 13 | env: { 14 | browser: true, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .vscode/ 4 | node_modules 5 | npm-debug.log 6 | yarn-error.log 7 | *.map 8 | *.iml 9 | /build 10 | .cache 11 | .node-gyp 12 | /splunkapps 13 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 110, 3 | "singleQuote": true, 4 | "tabWidth": 4, 5 | "trailingComma": "es5" 6 | } 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16.20-alpine 2 | 3 | # Install git and other tools 4 | RUN apk add --update ca-certificates git make \ 5 | && rm -rf /var/cache/apk/* 6 | 7 | RUN mkdir src/ 8 | WORKDIR /src 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build-image: 2 | docker build -t dashboard-builder . 3 | 4 | run: 5 | docker run -it -v $$PWD:/src --name builder dashboard-builder make --directory /src package-app 6 | 7 | package-app: 8 | NODE_OPTIONS=--max-old-space-size=8192 && yarn install && yarn run build && cd build/dashboard_framework_examples && tar -czf ../../dashboard-framework-examples.tar.gz * 9 | 10 | start: 11 | docker run --platform linux/amd64 -d -v $$PWD/build/dashboard_framework_examples:/opt/splunk/etc/apps/dashboard_framework_examples/ -p 8000:8000 -e "SPLUNK_PASSWORD=changemeplease1" -e "SPLUNK_START_ARGS=--accept-license" --name splunk splunk/splunk:9.1 12 | @echo "Check 'docker logs splunk' to see the status of the container" 13 | @open http://localhost:8000 14 | 15 | down: 16 | @docker rm -f builder || true 17 | @docker rm -f splunk || true 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Splunk Dashboard Framework Examples 2 | 3 | ## Prerequisite 4 | * Install [nodejs](https://nodejs.org/en/) 16.x. 5 | * Install Splunk Enterprise locally and have $SPLUNK_HOME env variable setup. 6 | * In Windows environment, to avoid any file permission issues start the command prompt with "Run as Administrator" to run the commands mentioned in the [Development](#development) section. 7 | 8 | ## Development 9 | * `npm install` - install dependencies. 10 | * `npm run dev` - start the project in dev mode. This command will symlink the project into your Splunk instance. 11 | * Restart your Splunk instance if it's the first time you setup this project. `Dashboard Framework Examples` application should shows up in app bar. 12 | 13 | 14 | ## How to create a new page 15 | * Add an xml file in `resources/default/data/ui/views`. 16 | * Modify `resources/default/data/ui/nav/default.xml` to include your new page. 17 | * Create a new folder under `src/pages/` with the same name of the new xml file. 18 | * Create `index.jsx` and bootstrap the page using `@splunk/react-page`. 19 | * Restart Splunk, your new page should shows up. 20 | 21 | 22 | # Package the app 23 | 24 | Use the following steps to package the Dashboard app. 25 | 26 | Requirements: 27 | * Make 28 | * [Docker](https://docs.docker.com/install/) 29 | * NOTE: May not run on Apple Silicon Macs. YMMV. 30 | 31 | Steps: 32 | * Run `make build-image` to build the image to package the app. 33 | * Run `make run` to package the app with NodeJS. 34 | * The app (`tgz`) will be created in the `splunkapps` folder. 35 | * To start Splunk (`9.x`) with the dashboard app run `make start` (username: `admin` password: `changemeplease1`). 36 | * Remove all containers run `make down` 37 | -------------------------------------------------------------------------------- /appConfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | id: 'dashboard_framework_examples', 3 | }; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard-framework-examples", 3 | "version": "0.0.2", 4 | "repository": "git@github.com:splunk/dashboard-conf19-examples.git", 5 | "author": "dashboard team at Splunk ", 6 | "license": "MIT", 7 | "scripts": { 8 | "build": "npm run prepare_splunk && cross-env NODE_ENV=production webpack --config ./webpack.config.js", 9 | "package": "echo package", 10 | "symlink": "node ./tools/symlink", 11 | "prepare_splunk": "node ./tools/prepare", 12 | "dev": "npm run prepare_splunk && npm run symlink && cross-env NODE_ENV=development webpack --config ./webpack.config.js --watch", 13 | "lint": "cross-env eslint src" 14 | }, 15 | "dependencies": { 16 | "@babel/polyfill": "^7.12.1", 17 | "@splunk/dashboard-context": "^26.0.0", 18 | "@splunk/dashboard-core": "^26.0.0", 19 | "@splunk/dashboard-inputs": "^26.0.0", 20 | "@splunk/dashboard-presets": "^26.0.0", 21 | "@splunk/datasource-utils": "^26.0.0", 22 | "@splunk/datasources": "^26.0.0", 23 | "@splunk/react-icons": "^3", 24 | "@splunk/react-page": "6.1.0", 25 | "@splunk/react-ui": "^4.17.0", 26 | "@splunk/splunk-utils": "^2.3.4", 27 | "@splunk/themes": "^0.16.1", 28 | "@splunk/visualization-context": "^25.9.0", 29 | "chart.js": "^3", 30 | "d3": "^7", 31 | "prop-types": "^15.8.1", 32 | "react": "^16.9.0", 33 | "react-chartjs-2": "^4", 34 | "react-dom": "^16.9.0", 35 | "styled-components": "^5.1.1" 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.22.9", 39 | "@babel/preset-env": "^7.22.9", 40 | "@babel/preset-react": "^7.22.5", 41 | "babel-loader": "^8", 42 | "cross-env": "^7.0.3", 43 | "eslint": "8.46.0", 44 | "eslint-config-airbnb": "19.0.4", 45 | "eslint-config-prettier": "^8.9.0", 46 | "eslint-plugin-import": "^2.28.0", 47 | "eslint-plugin-jsx-a11y": "^6.7.1", 48 | "eslint-plugin-prettier": "^5.0.0", 49 | "eslint-plugin-react": "^7.33.1", 50 | "eslint-plugin-react-hooks": "^4.6.0", 51 | "file-loader": "^6.2.0", 52 | "fs-extra": "^11.1.1", 53 | "lodash.template": "^4.5.0", 54 | "prettier": "^3.0.0", 55 | "url-loader": "^4.1.1", 56 | "webpack": "^4.46.0", 57 | "webpack-cli": "^4.10.0" 58 | }, 59 | "engines": { 60 | "node": "^16" 61 | }, 62 | "resolutions": { 63 | "@splunk/themes": "^0.16.1" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /resources/appserver/static/boston_marathon/marathon_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/dashboard-conf19-examples/488c8905658b38f45340a2f893b8cbfa7d7e63c9/resources/appserver/static/boston_marathon/marathon_map.png -------------------------------------------------------------------------------- /resources/appserver/static/buttercup_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/dashboard-conf19-examples/488c8905658b38f45340a2f893b8cbfa7d7e63c9/resources/appserver/static/buttercup_background.png -------------------------------------------------------------------------------- /resources/appserver/static/buttercup_spaceport/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/splunk/dashboard-conf19-examples/488c8905658b38f45340a2f893b8cbfa7d7e63c9/resources/appserver/static/buttercup_spaceport/background.png -------------------------------------------------------------------------------- /resources/appserver/static/emergency_department_ops/Ambulance_ic.svg: -------------------------------------------------------------------------------- 1 | Ambulance_ic -------------------------------------------------------------------------------- /resources/appserver/static/emergency_department_ops/HomeResolved_ic.svg: -------------------------------------------------------------------------------- 1 | HomeResolved_ic -------------------------------------------------------------------------------- /resources/appserver/static/emergency_department_ops/HospitalAdmitted_ic.svg: -------------------------------------------------------------------------------- 1 | HospitalAdmitted_ic -------------------------------------------------------------------------------- /resources/appserver/static/emergency_department_ops/PatientNurseRatio_ic.svg: -------------------------------------------------------------------------------- 1 | PatientNurseRatio_ic -------------------------------------------------------------------------------- /resources/appserver/static/emergency_department_ops/TriagePerHour_ic.svg: -------------------------------------------------------------------------------- 1 | TriagePerHour_ic -------------------------------------------------------------------------------- /resources/appserver/static/emergency_department_ops/WaitTime_ic.svg: -------------------------------------------------------------------------------- 1 | WaitTime_ic -------------------------------------------------------------------------------- /resources/appserver/static/emergency_department_ops/WalkIn_ic.svg: -------------------------------------------------------------------------------- 1 | WalkIn_ic -------------------------------------------------------------------------------- /resources/appserver/static/sfo_airport/sfo_airport_background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /resources/appserver/templates/dashboard.html: -------------------------------------------------------------------------------- 1 | <%! 2 | app_name = cherrypy.request.path_info.split('/')[3] 3 | app_root = "/" + "/".join(["static","app",app_name]) 4 | config_qs = dict(autoload=1) 5 | %>\ 6 | 7 | 8 | 9 | 10 | 11 | 12 | ${_('Loading...')} 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /resources/default/app.conf: -------------------------------------------------------------------------------- 1 | [ui] 2 | is_visible = 1 3 | label = Dashboard Framework Examples 4 | 5 | [launcher] 6 | author = Splunk 7 | description = Dashboard Framework Examples 8 | version = 0.0.2 9 | 10 | [install] 11 | is_configured = 1 12 | 13 | [package] 14 | id = dashboard_framework_examples 15 | -------------------------------------------------------------------------------- /resources/default/data/ui/nav/default.xml: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/absolute_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/boston_marathon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/buttercup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/buttercup_spaceport.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/custom_data_source.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/donut.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/dynamic_theming.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/emergency_department_ops.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/grid_dashboard.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/radar.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/sfo_airport.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /resources/default/data/ui/views/visualizations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/pages/absolute_dashboard/definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataSources": { 3 | "total_count_search": { 4 | "type": "ds.search", 5 | "options": { "query": "index=_internal | stats count" } 6 | }, 7 | "error_count_search": { 8 | "type": "ds.search", 9 | "options": { "query": "index=_internal error | stats count" } 10 | }, 11 | "info_count_search": { 12 | "type": "ds.search", 13 | "options": { "query": "index=_internal info | stats count" } 14 | }, 15 | "warning_count_search": { 16 | "type": "ds.search", 17 | "options": { "query": "index=_internal warning| stats count" } 18 | }, 19 | "event_by_component_search": { 20 | "type": "ds.search", 21 | "options": { "query": "index=_internal | stats count by component" } 22 | }, 23 | "timechart_search": { 24 | "type": "ds.search", 25 | "options": { "query": "index=_internal | timechart count" } 26 | } 27 | }, 28 | "inputs": {}, 29 | "layout": { 30 | "type": "absolute", 31 | "options": { "width": 1600, "height": 1000, "display": "auto-scale" }, 32 | "structure": [ 33 | { "item": "sv_total_event", "position": { "h": 260, "w": 380, "x": 20, "y": 20 } }, 34 | { "item": "sv_info", "position": { "h": 260, "w": 380, "x": 410, "y": 20 } }, 35 | { "item": "sv_warning", "position": { "h": 260, "w": 380, "x": 800, "y": 20 } }, 36 | { "item": "sv_error", "position": { "h": 260, "w": 380, "x": 1190, "y": 20 } }, 37 | { "item": "sv_event_by_component", "position": { "h": 250, "w": 770, "x": 800, "y": 290 } }, 38 | { "item": "event_over_time", "position": { "h": 250, "w": 770, "x": 20, "y": 290 } } 39 | ] 40 | }, 41 | "title": "Simple Dashboard", 42 | "description": "", 43 | "visualizations": { 44 | "sv_total_event": { 45 | "title": "_internal event count", 46 | "type": "splunk.singlevalue", 47 | "options": { "backgroundColor": "#53a051" }, 48 | "dataSources": { "primary": "total_count_search" } 49 | }, 50 | "sv_error": { 51 | "title": "error count", 52 | "type": "splunk.singlevalue", 53 | "options": { "backgroundColor": "#dc4e41" }, 54 | "dataSources": { "primary": "error_count_search" } 55 | }, 56 | "sv_warning": { 57 | "title": "warning count", 58 | "type": "splunk.singlevalue", 59 | "options": { "backgroundColor": "#f8be34" }, 60 | "dataSources": { "primary": "warning_count_search" } 61 | }, 62 | "sv_info": { 63 | "title": "info count", 64 | "type": "splunk.singlevalue", 65 | "options": { "backgroundColor": "#294e70" }, 66 | "dataSources": { "primary": "info_count_search" } 67 | }, 68 | "sv_event_by_component": { 69 | "title": "_internal event count by component", 70 | "type": "splunk.pie", 71 | "dataSources": { "primary": "event_by_component_search" } 72 | }, 73 | "event_over_time": { 74 | "title": "_internal event count over time", 75 | "type": "splunk.area", 76 | "dataSources": { "primary": "timechart_search" } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/pages/absolute_dashboard/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 4 | import { DashboardCore } from '@splunk/dashboard-core'; 5 | import EnterpriseViewOnlyPreset from '@splunk/dashboard-presets/EnterpriseViewOnlyPreset'; 6 | import definition from './definition.json'; 7 | 8 | // use DashboardCore to render a simple dashboard 9 | layout( 10 | 11 | 12 | , 13 | { 14 | pageTitle: 'Absolute Layout Dashboard', 15 | hideFooter: true, 16 | layout: 'fixed', 17 | } 18 | ); 19 | -------------------------------------------------------------------------------- /src/pages/boston_marathon/definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataSources": { 3 | "speed": { 4 | "name": "search1", 5 | "type": "ds.test", 6 | "options": { 7 | "data": { 8 | "fields": [ 9 | { 10 | "name": "foo" 11 | }, 12 | { 13 | "name": "bar" 14 | } 15 | ], 16 | "columns": [ 17 | [ 18 | "7.41" 19 | ], 20 | [ 21 | "1" 22 | ] 23 | ] 24 | }, 25 | "meta": {} 26 | } 27 | }, 28 | "runTime": { 29 | "name": "search4", 30 | "type": "ds.test", 31 | "options": { 32 | "data": { 33 | "fields": [ 34 | { 35 | "name": "Alan" 36 | }, 37 | { 38 | "name": "Emily" 39 | }, 40 | { 41 | "name": "Jasmin" 42 | } 43 | ], 44 | "columns": [ 45 | [ 46 | "1:05:57" 47 | ], 48 | [ 49 | "1:05:57" 50 | ], 51 | [ 52 | "1:05:57" 53 | ] 54 | ] 55 | }, 56 | "meta": {} 57 | } 58 | }, 59 | "uvIndex": { 60 | "name": "search11", 61 | "type": "ds.test", 62 | "options": { 63 | "data": { 64 | "fields": [ 65 | { 66 | "name": "foo" 67 | }, 68 | { 69 | "name": "bar" 70 | } 71 | ], 72 | "columns": [ 73 | [ 74 | "7" 75 | ], 76 | [ 77 | "1" 78 | ] 79 | ] 80 | }, 81 | "meta": {} 82 | } 83 | }, 84 | "bodyTemp": { 85 | "name": "search5", 86 | "type": "ds.test", 87 | "options": { 88 | "data": { 89 | "fields": [ 90 | { 91 | "name": "Alan" 92 | }, 93 | { 94 | "name": "Emily" 95 | }, 96 | { 97 | "name": "Jasmin" 98 | } 99 | ], 100 | "columns": [ 101 | [ 102 | "99.6" 103 | ], 104 | [ 105 | "98.7" 106 | ], 107 | [ 108 | "99.4" 109 | ] 110 | ] 111 | }, 112 | "meta": {} 113 | } 114 | }, 115 | "humidity": { 116 | "name": "search10", 117 | "type": "ds.test", 118 | "options": { 119 | "data": { 120 | "fields": [ 121 | { 122 | "name": "foo" 123 | }, 124 | { 125 | "name": "bar" 126 | } 127 | ], 128 | "columns": [ 129 | [ 130 | "63" 131 | ], 132 | [ 133 | "1" 134 | ] 135 | ] 136 | }, 137 | "meta": {} 138 | } 139 | }, 140 | "elevation": { 141 | "name": "search3", 142 | "type": "ds.test", 143 | "options": { 144 | "data": { 145 | "fields": [ 146 | { 147 | "name": "foo" 148 | }, 149 | { 150 | "name": "bar" 151 | } 152 | ], 153 | "columns": [ 154 | [ 155 | "72" 156 | ], 157 | [ 158 | "1" 159 | ] 160 | ] 161 | }, 162 | "meta": {} 163 | } 164 | }, 165 | "heartRate": { 166 | "name": "search2", 167 | "type": "ds.test", 168 | "options": { 169 | "data": { 170 | "fields": [ 171 | { 172 | "name": "Alan" 173 | }, 174 | { 175 | "name": "Emily" 176 | }, 177 | { 178 | "name": "Jasmin" 179 | } 180 | ], 181 | "columns": [ 182 | [ 183 | "165" 184 | ], 185 | [ 186 | "178" 187 | ], 188 | [ 189 | "172" 190 | ] 191 | ] 192 | }, 193 | "meta": {} 194 | } 195 | }, 196 | "leaderBoard": { 197 | "name": "search8", 198 | "type": "ds.test", 199 | "options": { 200 | "data": { 201 | "fields": [ 202 | { 203 | "name": "Place" 204 | }, 205 | { 206 | "name": "Runner" 207 | }, 208 | { 209 | "name": "Number" 210 | }, 211 | { 212 | "name": "Gap (s)" 213 | } 214 | ], 215 | "columns": [ 216 | [ 217 | "87", 218 | "88", 219 | "89", 220 | "90", 221 | "91", 222 | "92", 223 | "93", 224 | "94" 225 | ], 226 | [ 227 | "May Cummings", 228 | "Annie Zhang", 229 | "Ho Simpson", 230 | "Alan Duggan", 231 | "Emily Melendez", 232 | "Jasmin Rossi", 233 | "Santiago Benson", 234 | "Frederick Kamran" 235 | ], 236 | [ 237 | "343", 238 | "1482", 239 | "1526", 240 | "974", 241 | "154", 242 | "796", 243 | "892", 244 | "1984" 245 | ], 246 | [ 247 | "4.1", 248 | "2.3", 249 | "3.6", 250 | "5.4", 251 | "2.7", 252 | "2.1", 253 | "1.8", 254 | "7.5" 255 | ] 256 | ] 257 | }, 258 | "meta": {} 259 | } 260 | }, 261 | "paceOverTime": { 262 | "name": "search7", 263 | "type": "ds.test", 264 | "options": { 265 | "data": { 266 | "fields": [ 267 | { 268 | "name": "Time" 269 | }, 270 | { 271 | "name": "Elevation (ft)" 272 | }, 273 | { 274 | "name": "Pace" 275 | } 276 | ], 277 | "columns": [ 278 | [ 279 | "1", 280 | "2", 281 | "3", 282 | "4", 283 | "5", 284 | "6", 285 | "7", 286 | "8", 287 | "9", 288 | "10", 289 | "11", 290 | "12", 291 | "13", 292 | "14", 293 | "15", 294 | "16", 295 | "17", 296 | "18" 297 | ], 298 | [ 299 | "72", 300 | "72.5", 301 | "73", 302 | "74", 303 | "76", 304 | "78", 305 | "77", 306 | "76", 307 | "74", 308 | "73", 309 | "73", 310 | "73", 311 | "75", 312 | "77", 313 | "78", 314 | "79", 315 | "79", 316 | "78" 317 | ], 318 | [ 319 | "7.7", 320 | "7.6", 321 | "7.4", 322 | "7.3", 323 | "7.3", 324 | "7.2", 325 | "7.3", 326 | "7.4", 327 | "7.5", 328 | "7.5", 329 | "7.6", 330 | "7.6", 331 | "7.4", 332 | "7.3", 333 | "7.2", 334 | "7.2", 335 | "7.3", 336 | "7.4" 337 | ] 338 | ] 339 | }, 340 | "meta": {} 341 | } 342 | }, 343 | "totalCalories": { 344 | "name": "search6", 345 | "type": "ds.test", 346 | "options": { 347 | "data": { 348 | "fields": [ 349 | { 350 | "name": "foo" 351 | }, 352 | { 353 | "name": "bar" 354 | } 355 | ], 356 | "columns": [ 357 | [ 358 | "457" 359 | ], 360 | [ 361 | "1" 362 | ] 363 | ] 364 | }, 365 | "meta": {} 366 | } 367 | }, 368 | "temperatureOverTime": { 369 | "name": "search9", 370 | "type": "ds.test", 371 | "options": { 372 | "data": { 373 | "fields": [ 374 | { 375 | "name": "Time" 376 | }, 377 | { 378 | "name": "Temp (F)" 379 | } 380 | ], 381 | "columns": [ 382 | [ 383 | "1", 384 | "2", 385 | "3", 386 | "4", 387 | "5", 388 | "6", 389 | "7", 390 | "8", 391 | "9", 392 | "10", 393 | "11", 394 | "12", 395 | "13", 396 | "14", 397 | "15", 398 | "16", 399 | "17", 400 | "18" 401 | ], 402 | [ 403 | "67", 404 | "67.5", 405 | "67.8", 406 | "70", 407 | "70", 408 | "70.2", 409 | "70.4", 410 | "70.5", 411 | "70.6", 412 | "70.9", 413 | "71.5", 414 | "71.8", 415 | "72.3", 416 | "72.7", 417 | "73.0", 418 | "73.3", 419 | "73.4", 420 | "73.5" 421 | ] 422 | ] 423 | }, 424 | "meta": {} 425 | } 426 | } 427 | }, 428 | "inputs": { 429 | "input1": { 430 | "type": "input.dropdown", 431 | "title": "Runner", 432 | "options": { 433 | "items": [ 434 | { 435 | "label": "Alan Duggan", 436 | "value": "Alan" 437 | }, 438 | { 439 | "label": "Emily Melendez", 440 | "value": "Emily" 441 | }, 442 | { 443 | "label": "Jasmin Rossi", 444 | "value": "Jasmin" 445 | } 446 | ], 447 | "token": "runner", 448 | "defaultValue": "Alan" 449 | } 450 | } 451 | }, 452 | "layout": { 453 | "type": "absolute", 454 | "options": { 455 | "width": 1300, 456 | "height": 750 457 | }, 458 | "structure": [ 459 | { 460 | "item": "viz_eGQh2uAJ", 461 | "type": "block", 462 | "position": { 463 | "h": 730, 464 | "w": 1280, 465 | "x": 10, 466 | "y": 10 467 | } 468 | }, 469 | { 470 | "item": "viz_NktRExv6", 471 | "type": "block", 472 | "position": { 473 | "h": 50, 474 | "w": 240, 475 | "x": 1050, 476 | "y": 30 477 | } 478 | }, 479 | { 480 | "item": "viz_FVpjprmP", 481 | "type": "block", 482 | "position": { 483 | "h": 80, 484 | "w": 210, 485 | "x": 1060, 486 | "y": 280 487 | } 488 | }, 489 | { 490 | "item": "viz_K0dFZZih", 491 | "type": "block", 492 | "position": { 493 | "h": 80, 494 | "w": 210, 495 | "x": 20, 496 | "y": 190 497 | } 498 | }, 499 | { 500 | "item": "viz_ldWRmtDi", 501 | "type": "block", 502 | "position": { 503 | "h": 80, 504 | "w": 210, 505 | "x": 20, 506 | "y": 100 507 | } 508 | }, 509 | { 510 | "item": "viz_cArGnIJu", 511 | "type": "block", 512 | "position": { 513 | "h": 80, 514 | "w": 210, 515 | "x": 20, 516 | "y": 370 517 | } 518 | }, 519 | { 520 | "item": "viz_H5PHKh4r", 521 | "type": "block", 522 | "position": { 523 | "h": 80, 524 | "w": 210, 525 | "x": 20, 526 | "y": 280 527 | } 528 | }, 529 | { 530 | "item": "viz_jhTJWAGU", 531 | "type": "block", 532 | "position": { 533 | "h": 80, 534 | "w": 210, 535 | "x": 1060, 536 | "y": 90 537 | } 538 | }, 539 | { 540 | "item": "viz_Ll1FxjWg", 541 | "type": "block", 542 | "position": { 543 | "h": 80, 544 | "w": 210, 545 | "x": 1060, 546 | "y": 190 547 | } 548 | }, 549 | { 550 | "item": "viz_grgU4NU9", 551 | "type": "block", 552 | "position": { 553 | "h": 80, 554 | "w": 210, 555 | "x": 1060, 556 | "y": 370 557 | } 558 | }, 559 | { 560 | "item": "viz_5HKW2R1q", 561 | "type": "block", 562 | "position": { 563 | "h": 250, 564 | "w": 320, 565 | "x": 960, 566 | "y": 480 567 | } 568 | }, 569 | { 570 | "item": "viz_inbNXOtv", 571 | "type": "block", 572 | "position": { 573 | "h": 250, 574 | "w": 320, 575 | "x": 630, 576 | "y": 480 577 | } 578 | }, 579 | { 580 | "item": "viz_9eU4LWyM", 581 | "type": "block", 582 | "position": { 583 | "h": 250, 584 | "w": 600, 585 | "x": 20, 586 | "y": 480 587 | } 588 | }, 589 | { 590 | "item": "viz_eSKryv8C", 591 | "type": "block", 592 | "position": { 593 | "h": 50, 594 | "w": 210, 595 | "x": 20, 596 | "y": 30 597 | } 598 | }, 599 | { 600 | "item": "viz_JIL3sb0a", 601 | "type": "block", 602 | "position": { 603 | "h": 60, 604 | "w": 500, 605 | "x": 280, 606 | "y": 30 607 | } 608 | }, 609 | { 610 | "item": "viz_q4YzAPyo", 611 | "type": "block", 612 | "position": { 613 | "h": 450, 614 | "w": 770, 615 | "x": 260, 616 | "y": 30 617 | } 618 | }, 619 | { 620 | "item": "viz_ZyjJqSmo", 621 | "type": "block", 622 | "position": { 623 | "h": 50, 624 | "w": 240, 625 | "x": 570, 626 | "y": 30 627 | } 628 | } 629 | ], 630 | "globalInputs": [ 631 | "input1" 632 | ] 633 | }, 634 | "title": "Boston Marathon", 635 | "description": "", 636 | "defaults": {}, 637 | "visualizations": { 638 | "viz_5HKW2R1q": { 639 | "type": "splunk.line", 640 | "title": "Ambient Temperature over time", 641 | "dataSources": { 642 | "primary": "temperatureOverTime" 643 | }, 644 | "options": { 645 | "yAxisAbbreviation": "off", 646 | "y2AxisAbbreviation": "off", 647 | "showRoundedY2AxisLabels": false, 648 | "legendTruncation": "ellipsisMiddle", 649 | "showY2MajorGridLines": true, 650 | "seriesColors": [ 651 | "#5FBCFF" 652 | ], 653 | "backgroundColor": "#15161B", 654 | "legendDisplay": "top" 655 | }, 656 | "context": {} 657 | }, 658 | "viz_9eU4LWyM": { 659 | "type": "splunk.table", 660 | "title": "LeaderBoard", 661 | "dataSources": { 662 | "primary": "leaderBoard" 663 | }, 664 | "options": { 665 | "count": 20, 666 | "tableFormat": { 667 | "data": "> table | formatByType(formattedConfig)", 668 | "rowColors": "> table | pick(rowColorsConfig)", 669 | "rowBackgroundColors": "> table | pick(rowBackgroundColorsConfig)", 670 | "headerColor": "#F5F5F5", 671 | "headerBackgroundColor": "#33343B" 672 | } 673 | }, 674 | "context": { 675 | "formattedConfig": { 676 | "number": { 677 | "precision": 0, 678 | "thousandSeparated": false, 679 | "unitPosition": "after" 680 | }, 681 | "string": { 682 | "unitPosition": "after" 683 | } 684 | }, 685 | "rowColorsConfig": [ 686 | "#ACACAD", 687 | "#ACACAD" 688 | ], 689 | "rowBackgroundColorsConfig": [ 690 | "#15161B", 691 | "#23242B" 692 | ] 693 | } 694 | }, 695 | "viz_FVpjprmP": { 696 | "type": "splunk.singlevalue", 697 | "title": "Elevation", 698 | "dataSources": { 699 | "primary": "elevation" 700 | }, 701 | "context": {}, 702 | "options": { 703 | "showSparklineAreaGraph": false, 704 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 705 | "unit": "ft", 706 | "backgroundColor": "#33343B" 707 | } 708 | }, 709 | "viz_H5PHKh4r": { 710 | "type": "splunk.singlevalue", 711 | "title": "$runner$'s Body Temperature", 712 | "dataSources": { 713 | "primary": "bodyTemp" 714 | }, 715 | "context": { 716 | "fillRangeValueContext": [ 717 | { 718 | "from": 100, 719 | "value": "#cb3b43" 720 | }, 721 | { 722 | "to": 100, 723 | "from": 99.8, 724 | "value": "#ff7152" 725 | }, 726 | { 727 | "to": 99.8, 728 | "from": 99.5, 729 | "value": "#fc9850" 730 | }, 731 | { 732 | "to": 99.5, 733 | "value": "#53a051" 734 | } 735 | ] 736 | }, 737 | "options": { 738 | "showSparklineAreaGraph": false, 739 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 740 | "unit": "F", 741 | "numberPrecision": 1, 742 | "backgroundColor": "> primary | seriesByIndex(0) | rangeValue(fillRangeValueContext) | lastPoint()" 743 | } 744 | }, 745 | "viz_JIL3sb0a": { 746 | "options": { 747 | "markdown": "Running Boston", 748 | "fontSize": "custom", 749 | "customFontSize": 24, 750 | "fontFamily": "Splunk Platform Sans", 751 | "rotation": 0 752 | }, 753 | "type": "splunk.markdown", 754 | "context": {} 755 | }, 756 | "viz_K0dFZZih": { 757 | "type": "splunk.singlevalue", 758 | "title": "$runner$'s Heart Rate", 759 | "dataSources": { 760 | "primary": "heartRate" 761 | }, 762 | "context": { 763 | "fillRangeValueContext": [ 764 | { 765 | "from": 180, 766 | "value": "#cb3b43" 767 | }, 768 | { 769 | "to": 180, 770 | "from": 160, 771 | "value": "#ff7152" 772 | }, 773 | { 774 | "to": 160, 775 | "from": 150, 776 | "value": "#fc9850" 777 | }, 778 | { 779 | "to": 150, 780 | "from": 140, 781 | "value": "#f4df7a" 782 | }, 783 | { 784 | "to": 140, 785 | "from": 130, 786 | "value": "#4beba8" 787 | }, 788 | { 789 | "to": 130, 790 | "value": "#5fbcff" 791 | } 792 | ] 793 | }, 794 | "options": { 795 | "showSparklineAreaGraph": false, 796 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 797 | "unit": "bpm", 798 | "backgroundColor": "> primary | seriesByIndex(0) | rangeValue(fillRangeValueContext) | lastPoint()" 799 | } 800 | }, 801 | "viz_Ll1FxjWg": { 802 | "type": "splunk.singlevalue", 803 | "title": "Humidity", 804 | "dataSources": { 805 | "primary": "humidity" 806 | }, 807 | "context": {}, 808 | "options": { 809 | "showSparklineAreaGraph": false, 810 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 811 | "unit": "%", 812 | "backgroundColor": "#33343B", 813 | "trendDisplay": "off", 814 | "sparklineDisplay": "off" 815 | } 816 | }, 817 | "viz_NktRExv6": { 818 | "options": { 819 | "markdown": " Environment Data", 820 | "fontSize": "custom", 821 | "customFontSize": 24, 822 | "fontFamily": "Splunk Platform Sans", 823 | "rotation": 0 824 | }, 825 | "type": "splunk.markdown", 826 | "context": {} 827 | }, 828 | "viz_ZyjJqSmo": { 829 | "options": { 830 | "markdown": "Running Boston", 831 | "fontSize": "custom", 832 | "customFontSize": 24, 833 | "fontFamily": "Splunk Platform Sans", 834 | "rotation": 0 835 | }, 836 | "type": "splunk.markdown", 837 | "context": {} 838 | }, 839 | "viz_cArGnIJu": { 840 | "type": "splunk.singlevalue", 841 | "title": "$runner$'s Live Pace", 842 | "dataSources": { 843 | "primary": "speed" 844 | }, 845 | "context": { 846 | "fillRangeValueContext": [ 847 | { 848 | "from": 7, 849 | "value": "#53a051" 850 | }, 851 | { 852 | "to": 7, 853 | "from": 6.5, 854 | "value": "#f1813f" 855 | }, 856 | { 857 | "to": 6.5, 858 | "value": "#dc4e41" 859 | } 860 | ] 861 | }, 862 | "options": { 863 | "showSparklineAreaGraph": false, 864 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 865 | "unit": "mph", 866 | "numberPrecision": 1, 867 | "backgroundColor": "> primary | seriesByIndex(0) | rangeValue(fillRangeValueContext) | lastPoint()" 868 | } 869 | }, 870 | "viz_eGQh2uAJ": { 871 | "type": "splunk.rectangle", 872 | "options": { 873 | "fillColor": "#15161B", 874 | "strokeColor": "#15161B", 875 | "strokeWidth": 0, 876 | "strokeOpacity": 1 877 | }, 878 | "context": {} 879 | }, 880 | "viz_eSKryv8C": { 881 | "options": { 882 | "markdown": "Runner Data", 883 | "fontSize": "custom", 884 | "customFontSize": 24, 885 | "fontFamily": "Splunk Platform Sans", 886 | "rotation": 0 887 | }, 888 | "type": "splunk.markdown", 889 | "context": {} 890 | }, 891 | "viz_grgU4NU9": { 892 | "type": "splunk.singlevalue", 893 | "title": "UV Index", 894 | "dataSources": { 895 | "primary": "uvIndex" 896 | }, 897 | "context": {}, 898 | "options": { 899 | "showSparklineAreaGraph": false, 900 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 901 | "unit": "", 902 | "backgroundColor": "#33343B", 903 | "trendDisplay": "off", 904 | "sparklineDisplay": "off" 905 | } 906 | }, 907 | "viz_inbNXOtv": { 908 | "type": "splunk.line", 909 | "title": "Pace over time", 910 | "dataSources": { 911 | "primary": "paceOverTime" 912 | }, 913 | "options": { 914 | "yAxisAbbreviation": "off", 915 | "y2AxisAbbreviation": "off", 916 | "showRoundedY2AxisLabels": false, 917 | "legendTruncation": "ellipsisMiddle", 918 | "showY2MajorGridLines": true, 919 | "seriesColors": [ 920 | "#5FBCFF", 921 | "#C6335F" 922 | ], 923 | "y2Fields": [ 924 | "Pace" 925 | ], 926 | "showOverlayY2Axis": true, 927 | "backgroundColor": "#15161B", 928 | "legendDisplay": "top", 929 | "x": "> primary | seriesByIndex(0)", 930 | "y2": "> primary | seriesByName(\"\") | pointByIndex(2)" 931 | }, 932 | "context": {} 933 | }, 934 | "viz_jhTJWAGU": { 935 | "type": "splunk.singlevalue", 936 | "title": "Temperature", 937 | "dataSources": { 938 | "primary": "temperatureOverTime" 939 | }, 940 | "context": {}, 941 | "options": { 942 | "showSparklineAreaGraph": false, 943 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 944 | "unit": "F", 945 | "backgroundColor": "#33343B", 946 | "trendDisplay": "off", 947 | "sparklineDisplay": "off" 948 | } 949 | }, 950 | "viz_ldWRmtDi": { 951 | "type": "splunk.singlevalue", 952 | "title": "$runner$'s Run Time", 953 | "dataSources": { 954 | "primary": "runTime" 955 | }, 956 | "context": {}, 957 | "options": { 958 | "showSparklineAreaGraph": false, 959 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 960 | "backgroundColor": "#33343B" 961 | } 962 | }, 963 | "viz_q4YzAPyo": { 964 | "options": { 965 | "src": "https://raw.githubusercontent.com/splunk/dashboard-conf19-examples/master/resources/appserver/static/boston_marathon/marathon_map.png", 966 | "preserveAspectRatio": true 967 | }, 968 | "type": "splunk.image", 969 | "context": {} 970 | } 971 | } 972 | } 973 | -------------------------------------------------------------------------------- /src/pages/boston_marathon/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import { SplunkThemeProvider } from '@splunk/themes'; 4 | import { DashboardCore } from '@splunk/dashboard-core'; 5 | import EnterprisePreset from '@splunk/dashboard-presets/EnterprisePreset'; 6 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 7 | import definition from './definition.json'; 8 | 9 | // use DashboardCore to render a simple dashboard 10 | layout( 11 | 12 | 13 | 14 | 15 | , 16 | { 17 | pageTitle: 'Boston Marathon Dashboard', 18 | hideFooter: true, 19 | layout: 'fixed', 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /src/pages/buttercup/definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataSources": { 3 | "radar_ds": { 4 | "type": "ds.test", 5 | "options": { 6 | "data": { 7 | "fields": [ 8 | { 9 | "name": "Eating" 10 | }, 11 | { 12 | "name": "Drinking" 13 | }, 14 | { 15 | "name": "Sleeping" 16 | }, 17 | { 18 | "name": "Designing" 19 | }, 20 | { 21 | "name": "Coding" 22 | }, 23 | { 24 | "name": "Cycling" 25 | }, 26 | { 27 | "name": "Running" 28 | } 29 | ], 30 | "columns": [ 31 | [ 32 | 65, 33 | 28 34 | ], 35 | [ 36 | 59, 37 | 48 38 | ], 39 | [ 40 | 90, 41 | 40 42 | ], 43 | [ 44 | 81, 45 | 19 46 | ], 47 | [ 48 | 56, 49 | 96 50 | ], 51 | [ 52 | 55, 53 | 27 54 | ], 55 | [ 56 | 40, 57 | 100 58 | ] 59 | ] 60 | } 61 | } 62 | }, 63 | "game_stats": { 64 | "type": "ds.test", 65 | "options": { 66 | "data": { 67 | "fields": [ 68 | { 69 | "name": "game_events" 70 | }, 71 | { 72 | "name": "time" 73 | } 74 | ], 75 | "columns": [ 76 | [ 77 | 900, 78 | 1483, 79 | 1220, 80 | 790, 81 | 1453, 82 | 507, 83 | 863, 84 | 1132, 85 | 725, 86 | 12, 87 | 1394, 88 | 1193, 89 | 508, 90 | 1418, 91 | 334, 92 | 227, 93 | 1179, 94 | 673, 95 | 1009, 96 | 1194, 97 | 352, 98 | 273, 99 | 260, 100 | 191, 101 | 1271, 102 | 1255, 103 | 1030, 104 | 514, 105 | 401, 106 | 0, 107 | 215, 108 | 1158, 109 | 856, 110 | 569, 111 | 406, 112 | 1384, 113 | 734, 114 | 817, 115 | 1020, 116 | 47, 117 | 499, 118 | 943, 119 | 126, 120 | 1249, 121 | 334, 122 | 633, 123 | 1227, 124 | 853, 125 | 574, 126 | 861 127 | ], 128 | [ 129 | 1567636168, 130 | 1567639768, 131 | 1567643368, 132 | 1567646968, 133 | 1567650568, 134 | 1567654168, 135 | 1567657768, 136 | 1567661368, 137 | 1567664968, 138 | 1567668568, 139 | 1567672168, 140 | 1567675768, 141 | 1567679368, 142 | 1567682968, 143 | 1567686568, 144 | 1567690168, 145 | 1567693768, 146 | 1567697368, 147 | 1567700968, 148 | 1567704568, 149 | 1567708168, 150 | 1567711768, 151 | 1567715368, 152 | 1567718968, 153 | 1567722568, 154 | 1567726168, 155 | 1567729768, 156 | 1567733368, 157 | 1567736968, 158 | 1567740568, 159 | 1567744168, 160 | 1567747768, 161 | 1567751368, 162 | 1567754968, 163 | 1567758568, 164 | 1567762168, 165 | 1567765768, 166 | 1567769368, 167 | 1567772968, 168 | 1567776568, 169 | 1567780168, 170 | 1567783768, 171 | 1567787368, 172 | 1567790968, 173 | 1567794568, 174 | 1567798168, 175 | 1567801768, 176 | 1567805368, 177 | 1567808968, 178 | 1567812568 179 | ] 180 | ] 181 | } 182 | } 183 | }, 184 | "games_per_day": { 185 | "type": "ds.test", 186 | "options": { 187 | "data": { 188 | "fields": [ 189 | { 190 | "name": "device" 191 | }, 192 | { 193 | "name": "games" 194 | } 195 | ], 196 | "columns": [ 197 | [ 198 | "Sun", 199 | "Mon", 200 | "Tue", 201 | "Wed", 202 | "Thu", 203 | "Fri", 204 | "Sat" 205 | ], 206 | [ 207 | 761, 208 | 423, 209 | 314, 210 | 299, 211 | 312, 212 | 434, 213 | 671 214 | ] 215 | ] 216 | } 217 | } 218 | }, 219 | "leader_search": { 220 | "type": "ds.test", 221 | "options": { 222 | "data": { 223 | "fields": [ 224 | { 225 | "name": "User" 226 | }, 227 | { 228 | "name": "Top Score" 229 | }, 230 | { 231 | "name": "Total Games" 232 | }, 233 | { 234 | "name": "Total Play Time" 235 | } 236 | ], 237 | "columns": [ 238 | [ 239 | "User 1", 240 | "User 2", 241 | "User 3", 242 | "User 4", 243 | "User 5", 244 | "User 6", 245 | "User 7", 246 | "User 8", 247 | "User 9", 248 | "User 10" 249 | ], 250 | [ 251 | 63, 252 | 59, 253 | 47, 254 | 42, 255 | 23, 256 | 16, 257 | 15, 258 | 8, 259 | 4, 260 | 1 261 | ], 262 | [ 263 | 761, 264 | 671, 265 | 333, 266 | 150, 267 | 10, 268 | 6, 269 | 5, 270 | 1, 271 | 1, 272 | 1 273 | ], 274 | [ 275 | "5 hrs", 276 | "3 hrs", 277 | "2 hrs", 278 | "1 hrs", 279 | "30 min", 280 | "20 min", 281 | "10 min", 282 | "1 min", 283 | "30 sec", 284 | "3 sec" 285 | ] 286 | ] 287 | } 288 | } 289 | }, 290 | "games_per_user": { 291 | "type": "ds.test", 292 | "options": { 293 | "data": { 294 | "fields": [ 295 | { 296 | "name": "games_range" 297 | }, 298 | { 299 | "name": "users" 300 | } 301 | ], 302 | "columns": [ 303 | [ 304 | "1", 305 | "2", 306 | "3 - 5", 307 | " 6 - 10", 308 | "11+" 309 | ], 310 | [ 311 | 1234, 312 | 2678, 313 | 5246, 314 | 4157, 315 | 2234 316 | ] 317 | ] 318 | } 319 | } 320 | }, 321 | "last_hour_games": { 322 | "type": "ds.test", 323 | "options": { 324 | "data": { 325 | "fields": [ 326 | { 327 | "name": "count" 328 | } 329 | ], 330 | "columns": [ 331 | [ 332 | 13, 333 | 79, 334 | 91, 335 | 35, 336 | 5, 337 | 95, 338 | 95, 339 | 13, 340 | 24, 341 | 27, 342 | 18, 343 | 87, 344 | 61, 345 | 75, 346 | 52, 347 | 35, 348 | 23, 349 | 69, 350 | 0, 351 | 46, 352 | 63, 353 | 93, 354 | 16, 355 | 10, 356 | 13, 357 | 72, 358 | 93, 359 | 64, 360 | 38, 361 | 6, 362 | 50, 363 | 96, 364 | 92, 365 | 3, 366 | 28, 367 | 9, 368 | 63, 369 | 43, 370 | 19, 371 | 51, 372 | 82, 373 | 48, 374 | 71, 375 | 68, 376 | 23, 377 | 34, 378 | 1, 379 | 77, 380 | 11, 381 | 68 382 | ] 383 | ] 384 | } 385 | } 386 | }, 387 | "games_per_device": { 388 | "type": "ds.test", 389 | "options": { 390 | "data": { 391 | "fields": [ 392 | { 393 | "name": "device" 394 | }, 395 | { 396 | "name": "games" 397 | } 398 | ], 399 | "columns": [ 400 | [ 401 | "Android", 402 | "Mac", 403 | "iPhone", 404 | "iPad", 405 | "Windows", 406 | "Other" 407 | ], 408 | [ 409 | 12345, 410 | 11123, 411 | 10345, 412 | 10200, 413 | 9987, 414 | 5432 415 | ] 416 | ] 417 | } 418 | } 419 | }, 420 | "multi_game_users": { 421 | "type": "ds.test", 422 | "options": { 423 | "data": { 424 | "fields": [ 425 | { 426 | "name": "count" 427 | } 428 | ], 429 | "columns": [ 430 | [ 431 | 1000, 432 | 554, 433 | 671 434 | ] 435 | ] 436 | } 437 | } 438 | }, 439 | "top_score_search": { 440 | "type": "ds.test", 441 | "options": { 442 | "data": { 443 | "fields": [ 444 | { 445 | "name": "max" 446 | } 447 | ], 448 | "columns": [ 449 | [ 450 | "63" 451 | ] 452 | ] 453 | } 454 | } 455 | }, 456 | "single_game_users": { 457 | "type": "ds.test", 458 | "options": { 459 | "data": { 460 | "fields": [ 461 | { 462 | "name": "count" 463 | } 464 | ], 465 | "columns": [ 466 | [ 467 | 164 468 | ] 469 | ] 470 | } 471 | } 472 | }, 473 | "total_game_search": { 474 | "type": "ds.test", 475 | "options": { 476 | "data": { 477 | "fields": [ 478 | { 479 | "name": "count" 480 | } 481 | ], 482 | "columns": [ 483 | [ 484 | "13832145" 485 | ] 486 | ] 487 | } 488 | } 489 | }, 490 | "total_user_search": { 491 | "type": "ds.test", 492 | "options": { 493 | "data": { 494 | "fields": [ 495 | { 496 | "name": "count" 497 | } 498 | ], 499 | "columns": [ 500 | [ 501 | "71896" 502 | ] 503 | ] 504 | } 505 | } 506 | }, 507 | "yoy_player_growth": { 508 | "type": "ds.test", 509 | "options": { 510 | "data": { 511 | "fields": [ 512 | { 513 | "name": "hardcoded_data" 514 | } 515 | ], 516 | "columns": [ 517 | [ 518 | "57%" 519 | ] 520 | ] 521 | } 522 | } 523 | }, 524 | "score_distribution": { 525 | "type": "ds.test", 526 | "options": { 527 | "data": { 528 | "fields": [ 529 | { 530 | "name": "score" 531 | }, 532 | { 533 | "name": "games" 534 | } 535 | ], 536 | "columns": [ 537 | [ 538 | "< 5", 539 | "5 - 19", 540 | "20 - 39", 541 | "40+" 542 | ], 543 | [ 544 | 1234, 545 | 2678, 546 | 1910, 547 | 978 548 | ] 549 | ] 550 | } 551 | } 552 | }, 553 | "yoy_engagement_growth": { 554 | "type": "ds.test", 555 | "options": { 556 | "data": { 557 | "fields": [ 558 | { 559 | "name": "hardcoded_data" 560 | } 561 | ], 562 | "columns": [ 563 | [ 564 | "42%" 565 | ] 566 | ] 567 | } 568 | } 569 | }, 570 | "avg_games_per_user_search": { 571 | "type": "ds.test", 572 | "options": { 573 | "data": { 574 | "fields": [ 575 | { 576 | "name": "count" 577 | }, 578 | { 579 | "name": "avg_games_played" 580 | } 581 | ], 582 | "columns": [ 583 | [ 584 | 9, 585 | 0, 586 | 8, 587 | 1, 588 | 11, 589 | 8, 590 | 11, 591 | 14, 592 | 5, 593 | 11, 594 | 9, 595 | 5, 596 | 2, 597 | 12, 598 | 5, 599 | 11, 600 | 6, 601 | 13, 602 | 7, 603 | 4, 604 | 11, 605 | 14, 606 | 7, 607 | 9, 608 | 5, 609 | 14, 610 | 4, 611 | 1, 612 | 13, 613 | 6, 614 | 9, 615 | 8, 616 | 0, 617 | 4, 618 | 12, 619 | 14, 620 | 2, 621 | 4, 622 | 12, 623 | 9, 624 | 8, 625 | 4, 626 | 4, 627 | 10, 628 | 8, 629 | 14, 630 | 7, 631 | 10, 632 | 12, 633 | 9 634 | ], 635 | [ 636 | 7.92 637 | ] 638 | ] 639 | } 640 | } 641 | } 642 | }, 643 | "inputs": {}, 644 | "layout": { 645 | "type": "absolute", 646 | "options": { 647 | "width": 1500, 648 | "height": 600, 649 | "display": "auto-scale", 650 | "backgroundImage": { 651 | "x": 0, 652 | "y": 0, 653 | "src": "https://raw.githubusercontent.com/splunk/dashboard-conf19-examples/master/resources/appserver/static/buttercup_background.png", 654 | "sizeType": "contain" 655 | }, 656 | "canvasBackgroundColor": "transparent", 657 | "layoutItemBackgroundColor": "transparent" 658 | }, 659 | "structure": [ 660 | { 661 | "item": "yoy_engagement_growth", 662 | "position": { 663 | "h": 70, 664 | "w": 80, 665 | "x": 113, 666 | "y": 113 667 | } 668 | }, 669 | { 670 | "item": "yoy_player_growth", 671 | "position": { 672 | "h": 70, 673 | "w": 80, 674 | "x": 113, 675 | "y": 433 676 | } 677 | }, 678 | { 679 | "item": "top_score", 680 | "position": { 681 | "h": 50, 682 | "w": 200, 683 | "x": 380, 684 | "y": 330 685 | } 686 | }, 687 | { 688 | "item": "total_users", 689 | "position": { 690 | "h": 60, 691 | "w": 220, 692 | "x": 370, 693 | "y": 240 694 | } 695 | }, 696 | { 697 | "item": "leader_board", 698 | "position": { 699 | "h": 140, 700 | "w": 390, 701 | "x": 1110, 702 | "y": 160 703 | } 704 | }, 705 | { 706 | "item": "punch_card", 707 | "position": { 708 | "h": 145, 709 | "w": 400, 710 | "x": 1105, 711 | "y": 325 712 | } 713 | }, 714 | { 715 | "item": "total_games", 716 | "position": { 717 | "h": 100, 718 | "w": 360, 719 | "x": 670, 720 | "y": 190 721 | } 722 | }, 723 | { 724 | "item": "avg_games_per_user_search", 725 | "position": { 726 | "h": 110, 727 | "w": 350, 728 | "x": 680, 729 | "y": 327 730 | } 731 | }, 732 | { 733 | "item": "last_hour_games", 734 | "position": { 735 | "h": 140, 736 | "w": 350, 737 | "x": 680, 738 | "y": 460 739 | } 740 | }, 741 | { 742 | "item": "score_distribution", 743 | "position": { 744 | "h": 160, 745 | "w": 230, 746 | "x": 370, 747 | "y": 400 748 | } 749 | }, 750 | { 751 | "item": "games_per_user", 752 | "position": { 753 | "h": 170, 754 | "w": 220, 755 | "x": 370, 756 | "y": 40 757 | } 758 | }, 759 | { 760 | "item": "games_per_day", 761 | "position": { 762 | "h": 90, 763 | "w": 390, 764 | "x": 1110, 765 | "y": 480 766 | } 767 | }, 768 | { 769 | "item": "games_per_device", 770 | "position": { 771 | "h": 90, 772 | "w": 400, 773 | "x": 1100, 774 | "y": 40 775 | } 776 | }, 777 | { 778 | "item": "single_game_users", 779 | "position": { 780 | "h": 100, 781 | "w": 120, 782 | "x": 710, 783 | "y": 10 784 | } 785 | }, 786 | { 787 | "item": "multi_game_users", 788 | "position": { 789 | "h": 100, 790 | "w": 110, 791 | "x": 870, 792 | "y": 10 793 | } 794 | } 795 | ] 796 | }, 797 | "title": "Buttercup Games", 798 | "description": "", 799 | "defaults": {}, 800 | "visualizations": { 801 | "top_score": { 802 | "type": "splunk.singlevalue", 803 | "dataSources": { 804 | "primary": "top_score_search" 805 | }, 806 | "context": {}, 807 | "options": { 808 | "showSparklineAreaGraph": false, 809 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")" 810 | } 811 | }, 812 | "punch_card": { 813 | "type": "splunk.table", 814 | "dataSources": { 815 | "primary": "game_stats" 816 | }, 817 | "options": { 818 | "count": 20, 819 | "tableFormat": { 820 | "data": "> table | formatByType(formattedConfig)" 821 | }, 822 | "backgroundColor": "transparent" 823 | }, 824 | "context": { 825 | "formattedConfig": { 826 | "number": { 827 | "precision": 0, 828 | "thousandSeparated": false, 829 | "unitPosition": "after" 830 | }, 831 | "string": { 832 | "unitPosition": "after" 833 | } 834 | } 835 | } 836 | }, 837 | "total_games": { 838 | "type": "splunk.singlevalue", 839 | "dataSources": { 840 | "primary": "total_game_search" 841 | }, 842 | "context": {}, 843 | "options": { 844 | "showSparklineAreaGraph": false, 845 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")" 846 | } 847 | }, 848 | "total_users": { 849 | "type": "splunk.singlevalue", 850 | "dataSources": { 851 | "primary": "total_user_search" 852 | }, 853 | "context": {}, 854 | "options": { 855 | "showSparklineAreaGraph": false, 856 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")" 857 | } 858 | }, 859 | "leader_board": { 860 | "type": "splunk.table", 861 | "dataSources": { 862 | "primary": "leader_search" 863 | }, 864 | "options": { 865 | "count": 20, 866 | "tableFormat": { 867 | "data": "> table | formatByType(formattedConfig)" 868 | } 869 | }, 870 | "context": { 871 | "formattedConfig": { 872 | "number": { 873 | "precision": 0, 874 | "thousandSeparated": false, 875 | "unitPosition": "after" 876 | }, 877 | "string": { 878 | "unitPosition": "after" 879 | } 880 | } 881 | } 882 | }, 883 | "games_per_day": { 884 | "type": "splunk.line", 885 | "dataSources": { 886 | "primary": "games_per_day" 887 | }, 888 | "options": { 889 | "yAxisAbbreviation": "off", 890 | "y2AxisAbbreviation": "off", 891 | "showRoundedY2AxisLabels": false, 892 | "legendTruncation": "ellipsisMiddle", 893 | "showY2MajorGridLines": true, 894 | "backgroundColor": "transparent", 895 | "legendDisplay": "off" 896 | }, 897 | "context": {} 898 | }, 899 | "games_per_user": { 900 | "type": "splunk.column", 901 | "dataSources": { 902 | "primary": "games_per_user" 903 | }, 904 | "options": { 905 | "yAxisAbbreviation": "off", 906 | "y2AxisAbbreviation": "off", 907 | "showRoundedY2AxisLabels": false, 908 | "legendTruncation": "ellipsisMiddle", 909 | "showY2MajorGridLines": true, 910 | "seriesColors": [ 911 | "#ffffff" 912 | ], 913 | "backgroundColor": "transparent", 914 | "legendDisplay": "off", 915 | "yAxisMajorTickInterval": 1, 916 | "xAxisTitleVisibility": "hide", 917 | "yAxisTitleVisibility": "hide" 918 | }, 919 | "context": {} 920 | }, 921 | "last_hour_games": { 922 | "type": "splunk.singlevalue", 923 | "dataSources": { 924 | "primary": "last_hour_games" 925 | }, 926 | "context": {}, 927 | "options": { 928 | "showSparklineAreaGraph": false, 929 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 930 | "underLabel": "Compared to 1 hour before", 931 | "backgroundColor": "transparent" 932 | } 933 | }, 934 | "games_per_device": { 935 | "type": "splunk.line", 936 | "dataSources": { 937 | "primary": "games_per_device" 938 | }, 939 | "options": { 940 | "yAxisAbbreviation": "off", 941 | "y2AxisAbbreviation": "off", 942 | "showRoundedY2AxisLabels": false, 943 | "legendTruncation": "ellipsisMiddle", 944 | "showY2MajorGridLines": true, 945 | "lineWidth": 5, 946 | "seriesColors": [ 947 | "#ffffff" 948 | ], 949 | "backgroundColor": "transparent", 950 | "legendDisplay": "off", 951 | "xAxisTitleVisibility": "hide", 952 | "yAxisTitleVisibility": "hide" 953 | }, 954 | "context": {} 955 | }, 956 | "multi_game_users": { 957 | "type": "splunk.singlevalueradial", 958 | "dataSources": { 959 | "primary": "multi_game_users" 960 | }, 961 | "context": {}, 962 | "options": { 963 | "majorValue": "> primary | seriesByIndex(0) | lastPoint()", 964 | "trendValue": "> primary | seriesByIndex(0) | delta(-2)" 965 | } 966 | }, 967 | "single_game_users": { 968 | "type": "splunk.singlevalueradial", 969 | "dataSources": { 970 | "primary": "single_game_users" 971 | }, 972 | "context": {}, 973 | "options": { 974 | "majorValue": "> primary | seriesByIndex(0) | lastPoint()", 975 | "trendValue": "> primary | seriesByIndex(0) | delta(-2)", 976 | "trendDisplay": "absolute" 977 | } 978 | }, 979 | "yoy_player_growth": { 980 | "type": "splunk.singlevalue", 981 | "dataSources": { 982 | "primary": "yoy_player_growth" 983 | }, 984 | "context": {}, 985 | "options": { 986 | "showSparklineAreaGraph": false, 987 | "sparklineValues": "> primary | seriesByIndex(0)", 988 | "backgroundColor": "transparent" 989 | } 990 | }, 991 | "score_distribution": { 992 | "type": "splunk.column", 993 | "dataSources": { 994 | "primary": "score_distribution" 995 | }, 996 | "options": { 997 | "yAxisAbbreviation": "off", 998 | "y2AxisAbbreviation": "off", 999 | "showRoundedY2AxisLabels": false, 1000 | "legendTruncation": "ellipsisMiddle", 1001 | "showY2MajorGridLines": true, 1002 | "seriesColors": [ 1003 | "#ffffff" 1004 | ], 1005 | "backgroundColor": "transparent", 1006 | "legendDisplay": "off", 1007 | "xAxisTitleVisibility": "hide", 1008 | "yAxisTitleVisibility": "hide" 1009 | }, 1010 | "context": {} 1011 | }, 1012 | "yoy_engagement_growth": { 1013 | "type": "splunk.singlevalue", 1014 | "dataSources": { 1015 | "primary": "yoy_engagement_growth" 1016 | }, 1017 | "context": {}, 1018 | "options": { 1019 | "showSparklineAreaGraph": false, 1020 | "sparklineValues": "> primary | seriesByIndex(0)", 1021 | "backgroundColor": "transparent" 1022 | } 1023 | }, 1024 | "avg_games_per_user_search": { 1025 | "type": "splunk.singlevalue", 1026 | "dataSources": { 1027 | "primary": "avg_games_per_user_search" 1028 | }, 1029 | "context": {}, 1030 | "options": { 1031 | "showSparklineAreaGraph": false, 1032 | "sparklineValues": "> primary | seriesByTypes(\"number\", \"string\")", 1033 | "sparklineAreaColor": "#ffffff", 1034 | "sparklineStrokeColor": "#ffffff", 1035 | "trendDisplay": "off" 1036 | } 1037 | } 1038 | } 1039 | } 1040 | -------------------------------------------------------------------------------- /src/pages/buttercup/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import { SplunkThemeProvider } from '@splunk/themes'; 4 | import { DashboardCore } from '@splunk/dashboard-core'; 5 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 6 | import EnterpriseViewOnlyPreset from '@splunk/dashboard-presets/EnterpriseViewOnlyPreset'; 7 | import Radar from '../radar/Radar'; 8 | import Donut from '../donut/Donut'; 9 | import definition from './definition.json'; 10 | 11 | const Preset = { 12 | ...EnterpriseViewOnlyPreset, 13 | visualizations: { 14 | ...EnterpriseViewOnlyPreset.visualizations, 15 | 'viz.radar': Radar, 16 | 'viz.donut': Donut, 17 | }, 18 | }; 19 | 20 | // use DashboardCore to render a simple dashboard 21 | layout( 22 | 23 | 24 | 25 | 26 | , 27 | { 28 | pageTitle: 'Buttercup Games Dashboard', 29 | hideFooter: true, 30 | layout: 'fixed', 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import { SplunkThemeProvider } from '@splunk/themes'; 4 | import { DashboardCore } from '@splunk/dashboard-core'; 5 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 6 | import Preset from './presets'; 7 | import definition from './definition'; 8 | 9 | const flags = { enableShowHide: true, enableSmartSourceDS: true }; 10 | 11 | // use DashboardCore to render a simple dashboard 12 | layout( 13 | 14 | 15 | 16 | 17 | , 18 | { 19 | pageTitle: 'Buttercup Spaceport', 20 | hideFooter: true, 21 | layout: 'fixed', 22 | } 23 | ); 24 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/inputs/AgeInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import T from 'prop-types'; 3 | import { withInputWrapper } from '@splunk/dashboard-inputs'; 4 | import SUISelect from '@splunk/react-ui/Select'; 5 | 6 | const defaultItems = [ 7 | { label: 'Young Adults', value: '0,39' }, 8 | { label: 'Middle Aged Adults', value: '40,59' }, 9 | { label: 'Older Adults', value: '60,1000' }, 10 | ]; 11 | 12 | const defaultOptions = { items: defaultItems }; 13 | 14 | const AgeSelect = ({ value, onValueChange, options: { defaultValue = '0,39', items } = defaultOptions }) => { 15 | const handleClick = useCallback( 16 | (evt, { value: itemValue }) => { 17 | onValueChange(evt, itemValue); 18 | }, 19 | [onValueChange] 20 | ); 21 | 22 | return ( 23 | 24 | {(items || defaultItems).map(({ label: l, value: v }) => ( 25 | 26 | ))} 27 | 28 | ); 29 | }; 30 | 31 | AgeSelect.propTypes = { 32 | value: T.string, 33 | onValueChange: T.func, 34 | options: T.object, 35 | }; 36 | 37 | AgeSelect.defaultProps = { 38 | onValueChange: () => undefined, 39 | }; 40 | 41 | /** 42 | * Transforms the value or values from the input to a set of token: value pairs 43 | * @param {String} value Select only the selected item value 44 | * @param {Object} meta 45 | * @param {String} meta.token The token name 46 | * @param {String} meta.prefix Content that prepends the value 47 | * @param {String} meta.suffix Content that appends the value 48 | * @returns {Object} 49 | */ 50 | AgeSelect.valueToTokens = (value, { token }) => { 51 | if (!token) { 52 | return {}; 53 | } 54 | if (!value) { 55 | return { 56 | [token]: null, 57 | }; 58 | } 59 | const vals = value.split(','); 60 | return { 61 | [`${token}.min`]: `${vals[0]}`, 62 | [`${token}.max`]: `${vals[1]}`, 63 | }; 64 | }; 65 | 66 | export default withInputWrapper(AgeSelect); 67 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/inputs/RadioBar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback } from 'react'; 2 | import T from 'prop-types'; 3 | import { withInputWrapper } from '@splunk/dashboard-inputs'; 4 | import SUIRadioBar from '@splunk/react-ui/RadioBar'; 5 | 6 | const noItems = []; 7 | const noOptions = {}; 8 | 9 | const RadioBar = ({ value, onValueChange, options: { defaultValue, items = noItems } = noOptions }) => { 10 | const handleClick = useCallback( 11 | (evt, { value: itemValue }) => { 12 | onValueChange(evt, itemValue); 13 | }, 14 | [onValueChange] 15 | ); 16 | 17 | return ( 18 | 19 | {items.map(({ label: l, value: v }) => ( 20 | 21 | ))} 22 | 23 | ); 24 | }; 25 | 26 | RadioBar.propTypes = { 27 | value: T.string, 28 | onValueChange: T.func, 29 | options: T.object, 30 | }; 31 | 32 | RadioBar.defaultProps = { 33 | onValueChange: () => undefined, 34 | }; 35 | 36 | /** 37 | * Transforms the value or values from the input to a set of token: value pairs 38 | * @param {String} value Select only the selected item value 39 | * @param {Object} meta 40 | * @param {String} meta.token The token name 41 | * @param {String} meta.prefix Content that prepends the value 42 | * @param {String} meta.suffix Content that appends the value 43 | * @returns {Object} 44 | */ 45 | RadioBar.valueToTokens = (value, { token }) => { 46 | if (!token) { 47 | return {}; 48 | } 49 | if (!value) { 50 | return { 51 | [token]: null, 52 | }; 53 | } 54 | return { 55 | [token]: `${value}`, 56 | }; 57 | }; 58 | 59 | export default withInputWrapper(RadioBar); 60 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/inputs/Rating.jsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import T from 'prop-types'; 3 | import styled from 'styled-components'; 4 | import SVG from '@splunk/react-icons/SVG'; 5 | import Tooltip from '@splunk/react-ui/Tooltip'; 6 | import { withInputWrapper } from '@splunk/dashboard-inputs'; 7 | 8 | const Title = styled.div` 9 | font-weight: bold; 10 | `; 11 | 12 | const IconContainer = styled.div` 13 | margin: 5px 0; 14 | display: flex; 15 | align-items: center; 16 | 17 | & > svg { 18 | margin-right: 5px; 19 | } 20 | `; 21 | 22 | const FlexContainer = styled.div` 23 | margin: 5px 0; 24 | display: flex; 25 | justify-content: space-between; 26 | align-items: center; 27 | width: 150px; 28 | `; 29 | 30 | // eslint-disable-next-line react/prop-types 31 | const KeyValueViewer = ({ title, value }) => ( 32 | 33 | {title} 34 | {value} 35 | 36 | ); 37 | 38 | const Rating1 = ( 39 | 40 | 48 | 49 | ); 50 | 51 | const Rating2 = ( 52 | 53 | 61 | 62 | ); 63 | 64 | const Rating3 = ( 65 | 66 | 74 | 75 | ); 76 | 77 | const Rating4 = ( 78 | 79 | 85 | 86 | ); 87 | 88 | const Rating5 = ( 89 | 90 | 96 | 97 | ); 98 | 99 | const defaultOptions = {}; 100 | 101 | const Rating = ({ dataSources, loading: isLoading, isError, options: { tooltipTitle } = defaultOptions }) => { 102 | const rating = useMemo(() => { 103 | if (isLoading || isError || !dataSources || !dataSources.primary || !dataSources.primary.data) { 104 | return 0; 105 | } 106 | let total = 0; 107 | dataSources.primary.data.columns[1].forEach((val) => { 108 | total += parseFloat(val); 109 | }); 110 | return Math.round(total / dataSources.primary.data.columns[1].length); 111 | }, [isLoading, isError, dataSources]); 112 | 113 | const { icon, ratingText } = useMemo(() => { 114 | let el = null; 115 | let txt = ''; 116 | 117 | switch (rating) { 118 | case 1: 119 | el = Rating1; 120 | txt = 'Very Dissatisfied'; 121 | break; 122 | case 2: 123 | el = Rating2; 124 | txt = 'Dissatisfied'; 125 | break; 126 | case 3: 127 | el = Rating3; 128 | txt = 'Neutral'; 129 | break; 130 | case 4: 131 | el = Rating4; 132 | txt = 'Satisfied'; 133 | break; 134 | case 5: 135 | el = Rating5; 136 | txt = 'Very Satisfied'; 137 | break; 138 | default: 139 | } 140 | return { icon: el, ratingText: txt }; 141 | }, [rating]); 142 | 143 | const tooltipContent = useMemo(() => { 144 | if (!dataSources.primary || !dataSources.primary.data) { 145 | return null; 146 | } 147 | 148 | const { columns } = dataSources.primary.data; 149 | 150 | return ( 151 | <> 152 | {tooltipTitle} 153 | 154 | {icon} 155 | {ratingText} 156 | 157 | {columns[0].map((key, idx) => ( 158 | 159 | ))} 160 | 161 | ); 162 | }, [dataSources, tooltipTitle, icon, ratingText]); 163 | 164 | if (!icon) { 165 | return null; 166 | } 167 | 168 | // We wanted a light tooltip, default behavior is to invert colors 169 | return ( 170 | 171 | {icon} 172 | 173 | ); 174 | }; 175 | 176 | Rating.propTypes = { 177 | dataSources: T.object, 178 | loading: T.bool, 179 | isError: T.bool, 180 | options: T.object, 181 | }; 182 | 183 | export default withInputWrapper(Rating); 184 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/inputs/index.js: -------------------------------------------------------------------------------- 1 | export { default as AgeInput } from './AgeInput'; 2 | export { default as RadioBar } from './RadioBar'; 3 | export { default as Rating } from './Rating'; 4 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/presets/index.js: -------------------------------------------------------------------------------- 1 | // For a known dashboard, its even better to cull the preset down to only the things that you need 2 | import DashboardPresets from '@splunk/dashboard-presets/EnterprisePreset'; 3 | // custom content 4 | import { AgeInput, RadioBar, Rating } from '../inputs'; 5 | import { IFrame } from '../visualizations'; 6 | 7 | export default { 8 | ...DashboardPresets, 9 | visualizations: { 10 | ...DashboardPresets.visualizations, 11 | 'viz.iframe': IFrame, 12 | }, 13 | inputs: { 14 | ...DashboardPresets.inputs, 15 | 'input.radiobar': RadioBar, 16 | 'input.demographics': AgeInput, 17 | 'input.rating': Rating, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/svg/journey.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/svg/legend.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/svg/seats.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name: Ellipse 8 6 | value: vacant 7 | name: Ellipse 9 8 | value: unsold 9 | name: Ellipse 14 10 | value: vacant 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | name: Ellipse 12 27 | value: unsold 28 | name: Ellipse 13 29 | value: boarded 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/visualizations/IFrame.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import T from 'prop-types'; 3 | import styled from 'styled-components'; 4 | 5 | const Container = styled.div.attrs(({ $width, $height }) => ({ 6 | style: { width: `${$width}px`, height: `${$height}px` }, 7 | }))``; 8 | 9 | const Frame = styled.iframe.attrs(({ $width, $height }) => ({ 10 | width: $width, 11 | height: $height, 12 | }))` 13 | border: 0; 14 | overflow: hidden; 15 | margin: 0; 16 | `; 17 | 18 | const IFrameViz = ({ options, width, height }) => ( 19 | 20 | 21 | 22 | ); 23 | 24 | IFrameViz.propTypes = { 25 | options: T.object, 26 | width: T.number, 27 | height: T.number, 28 | }; 29 | 30 | export default IFrameViz; 31 | -------------------------------------------------------------------------------- /src/pages/buttercup_spaceport/visualizations/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | export { default as IFrame } from './IFrame'; 3 | -------------------------------------------------------------------------------- /src/pages/custom_data_source/PostDataSource.js: -------------------------------------------------------------------------------- 1 | import DataSource from '@splunk/datasources/DataSource'; 2 | import DataSet from '@splunk/datasource-utils/DataSet'; 3 | import { url } from './utils'; 4 | 5 | class PostDataSource extends DataSource { 6 | request() { 7 | return async (subscriber) => { 8 | const response = await fetch(url); 9 | 10 | if (!response.ok) { 11 | subscriber.error({ 12 | level: 'error', 13 | message: `${response.status}: ${response.statusText}`, 14 | }); 15 | } 16 | 17 | const posts = await response.json(); 18 | 19 | const data = DataSet.fromJSONArray(null, posts); 20 | 21 | subscriber.next({ 22 | data, 23 | }); 24 | 25 | subscriber.complete(); 26 | }; 27 | } 28 | } 29 | 30 | export default PostDataSource; 31 | -------------------------------------------------------------------------------- /src/pages/custom_data_source/README.md: -------------------------------------------------------------------------------- 1 | ### Steps to create a custom data source 2 | 3 | 1. identify source of new data 4 | 1. build data source component 5 | 1. import base `DataSource` class and `DataSet` class 6 | 1. create a new class that extends `DataSource` 7 | 1. override the `request` function 8 | 1. `request` function returns a subscribe function 9 | 1. inside the subscribe function, fetch data from the source 10 | 1. transform data using `DataSet` 11 | 1. send data to dashboard-framework by calling `.next()` on subscriber. This can be repeated as many times as needed 12 | 1. call `.complete()` on subscriber to let dashboard-framework know all data has been pushed 13 | 1. (optional) call `.error()` when anything goes wrong 14 | 1. include the new data source in preset 15 | 1. use the new data source in definition 16 | -------------------------------------------------------------------------------- /src/pages/custom_data_source/definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataSources": { 3 | "total_count_search": { 4 | "type": "ds.search", 5 | "options": { 6 | "query": "index=_internal | stats count", 7 | "queryParameters": { 8 | "earliest": "-15m@m", 9 | "latest": "now" 10 | } 11 | } 12 | }, 13 | "mypost": { 14 | "type": "post" 15 | } 16 | }, 17 | "layout": { 18 | "type": "absolute", 19 | "options": { 20 | "display": "auto-scale" 21 | }, 22 | "structure": [ 23 | { 24 | "item": "sv_total_event", 25 | "position": { 26 | "x": 1, 27 | "y": 1, 28 | "w": 400, 29 | "h": 400 30 | } 31 | }, 32 | { 33 | "item": "mytable", 34 | "position": { 35 | "x": 500, 36 | "y": 1, 37 | "w": 600, 38 | "h": 600 39 | } 40 | } 41 | ] 42 | }, 43 | "title": "Simple Dashboard", 44 | "description": "", 45 | "visualizations": { 46 | "sv_total_event": { 47 | "title": "index=_internal event count of last 15 minutes", 48 | "type": "splunk.singlevalue", 49 | "options": { 50 | "backgroundColor": "#ffffff" 51 | }, 52 | "dataSources": { 53 | "primary": "total_count_search" 54 | } 55 | }, 56 | "mytable": { 57 | "type": "splunk.table", 58 | "dataSources": { 59 | "primary": "mypost" 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/pages/custom_data_source/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import DashboardCore from '@splunk/dashboard-core'; 4 | import EnterpriseViewOnlyPreset from '@splunk/dashboard-presets/EnterpriseViewOnlyPreset'; 5 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 6 | import definition from './definition.json'; 7 | import PostDataSource from './PostDataSource'; 8 | 9 | const preset = { 10 | ...EnterpriseViewOnlyPreset, 11 | dataSources: { 12 | ...EnterpriseViewOnlyPreset.dataSources, 13 | post: PostDataSource, 14 | }, 15 | }; 16 | 17 | // use DashboardCore to render a simple dashboard 18 | layout( 19 | 20 | 21 | , 22 | { 23 | pageTitle: 'Custom Data Source', 24 | hideFooter: true, 25 | layout: 'fixed', 26 | } 27 | ); 28 | -------------------------------------------------------------------------------- /src/pages/custom_data_source/utils.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | 3 | // note this module is just for demo purpose 4 | 5 | export const url = 'https://jsonplaceholder.typicode.com/posts'; 6 | 7 | // backup 8 | // NOTE: the response is an object, the users are in `data` property 9 | // export const url = 'https://reqres.in/api/users'; 10 | -------------------------------------------------------------------------------- /src/pages/donut/Donut.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-env browser */ 2 | import React, { useCallback, useMemo } from 'react'; 3 | import T from 'prop-types'; 4 | import * as d3 from 'd3'; 5 | import styled from 'styled-components'; 6 | 7 | const useD3 = (renderChartFn) => { 8 | const ref = React.useRef(); 9 | 10 | React.useEffect(() => { 11 | renderChartFn(d3.select(ref.current)); 12 | }, [renderChartFn]); 13 | 14 | return ref; 15 | }; 16 | 17 | const Container = styled.div` 18 | background: ${(props) => props.background}; 19 | color: white; 20 | font-weight: 600; 21 | font-size: 14px; 22 | font-family: 'Splunk Platform Sans', sans-serif; 23 | width: ${(props) => props.width}; 24 | height: ${(props) => props.width}; 25 | `; 26 | 27 | const processData = (dataSources) => { 28 | // remember encoding 29 | if (!dataSources || !dataSources.primary || !dataSources.primary.data) { 30 | return { 31 | label: [], 32 | value: [], 33 | _meta: { 34 | fieldNames: { 35 | label: '', 36 | value: '', 37 | }, 38 | }, 39 | }; 40 | } 41 | // todo: should use Base Parser. (need fix one issue in parser) 42 | const fieldNames = dataSources.primary.data.fields.map((f) => f.name); 43 | const donutData = { 44 | label: dataSources.primary.data.columns[0], 45 | value: dataSources.primary.data.columns[1], 46 | _meta: { 47 | fieldNames: { 48 | label: fieldNames[0], 49 | value: fieldNames[1], 50 | }, 51 | }, 52 | }; 53 | 54 | return donutData; 55 | }; 56 | 57 | const Donut = ({ dataSources, width, height, options }) => { 58 | const d3Data = useMemo(() => processData(dataSources), [dataSources]); 59 | 60 | const d3RenderFn = useCallback( 61 | (svg) => { 62 | const { value } = d3Data; 63 | const internalHeight = height - 25; 64 | const radius = Math.min(width, internalHeight) / 2.5; 65 | 66 | const colorScheme = options.colorScheme || 'schemeCategory10'; 67 | const color = d3.scaleOrdinal(d3[colorScheme]); 68 | 69 | const pie = d3 70 | .pie() 71 | .value((d) => d) 72 | .sort(null); 73 | const arc = d3 74 | .arc() 75 | .innerRadius(radius - 50) 76 | .outerRadius(radius - 10); 77 | 78 | svg.append('g').attr('transform', `translate(${width / 2.5},${height / 2})`); 79 | 80 | // join data 81 | let arcs = svg.selectAll('.arc').data(pie(value)); 82 | 83 | // remove unneeded arcs 84 | arcs.exit().remove(); 85 | 86 | // enter arc 87 | arcs = arcs.enter().append('g').attr('class', 'arc'); 88 | 89 | arcs.append('path') 90 | .attr('fill', (d, i) => color(i)) 91 | .attr('d', arc); 92 | 93 | // note: for demo purpose 94 | // arcs.append('text') 95 | // .attr('transform', (d) => { 96 | // const [x, y] = arc.centroid(d); 97 | // return `translate(${x - 8},${y})`; 98 | // }) 99 | // .style('font-size', 8) 100 | // .text((d) => d.value); 101 | 102 | // const { label } = d3Data; 103 | // const legend = svg.selectAll('.legend').data(label).enter().append('g').attr('class', 'legend'); 104 | 105 | // legend 106 | // .append('rect') 107 | // .attr('width', 10) 108 | // .attr('height', 8) 109 | // .attr('x', radius) 110 | // .attr('y', (d, i) => i * 10) 111 | // .attr('fill', (d, i) => color(i)); 112 | 113 | // legend 114 | // .append('text') 115 | // .attr('fill', 'white') 116 | // .style('font-size', 10) 117 | // .attr('x', radius + 12) 118 | // .attr('y', (d, i) => i * 10) 119 | // .attr('dy', '0.8em') 120 | // .text((d) => d); 121 | }, 122 | [d3Data, width, height, options] 123 | ); 124 | 125 | const ref = useD3(d3RenderFn); 126 | 127 | return ( 128 | 129 | 130 | 131 | ); 132 | }; 133 | 134 | Donut.propTypes = { 135 | /** 136 | * width in pixel or string, defaults to 100% 137 | */ 138 | width: T.oneOfType([T.string, T.number]), 139 | /** 140 | * height in pixel or string 141 | */ 142 | height: T.oneOfType([T.string, T.number]), 143 | /** 144 | * visualization formatting options 145 | */ 146 | options: T.object, 147 | /** 148 | * datasource state which include data and request params, object key indicate the datasource type. 149 | */ 150 | dataSources: T.objectOf( 151 | T.shape({ 152 | /** 153 | * current request params 154 | */ 155 | requestParams: T.object, 156 | /** 157 | * current dataset 158 | */ 159 | data: T.shape({ 160 | fields: T.array, 161 | columns: T.array, 162 | }), 163 | /** 164 | * error 165 | */ 166 | error: T.shape({ 167 | level: T.string, 168 | message: T.string, 169 | }), 170 | /** 171 | * meta data that came with the dataset 172 | */ 173 | meta: T.object, 174 | }) 175 | ), 176 | }; 177 | 178 | Donut.defaultProps = { 179 | options: {}, 180 | height: 250, 181 | width: 600, 182 | dataSources: {}, 183 | }; 184 | 185 | export default Donut; 186 | -------------------------------------------------------------------------------- /src/pages/donut/definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataSources": { 3 | "ds_1": { 4 | "type": "ds.test", 5 | "options": { 6 | "data": { 7 | "fields": [{ "name": "games_range" }, { "name": "users" }], 8 | "columns": [["1", "2", "3 - 5", " 6 - 10", "11+"], [1234, 2678, 5246, 4157, 2234]] 9 | } 10 | } 11 | } 12 | }, 13 | "inputs": {}, 14 | "layout": { 15 | "type": "absolute", 16 | "options": { 17 | "display": "auto-scale" 18 | }, 19 | "structure": [ 20 | { "item": "viz_text", "type": "block", "position": { "h": 340, "w": 370, "x": 50, "y": 30 } }, 21 | { "item": "viz_donut", "type": "block", "position": { "h": 250, "w": 600, "x": 430, "y": 30 } } 22 | ] 23 | }, 24 | "description": "", 25 | "visualizations": { 26 | "viz_text": { 27 | "type": "splunk.markdown", 28 | "options": { "markdown": "D3 Donut", "customFontSize": 40, "backgroundColor": "transparent", "fontSize": "custom" } 29 | }, 30 | "viz_donut": { "type": "viz.donut", "options": {}, "dataSources": { "primary": "ds_1" } } } 31 | } 32 | -------------------------------------------------------------------------------- /src/pages/donut/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import { SplunkThemeProvider } from '@splunk/themes'; 4 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 5 | import { DashboardCore } from '@splunk/dashboard-core'; 6 | import EnterpriseViewOnlyPreset from '@splunk/dashboard-presets/EnterpriseViewOnlyPreset'; 7 | import definition from './definition.json'; 8 | import Donut from './Donut'; 9 | 10 | const CustomPreset = { 11 | ...EnterpriseViewOnlyPreset, 12 | visualizations: { 13 | ...EnterpriseViewOnlyPreset.visualizations, 14 | 'viz.donut': Donut, 15 | }, 16 | }; 17 | 18 | // use DashboardCore to render a simple dashboard 19 | layout( 20 | 21 | 22 | 23 | 24 | , 25 | { 26 | pageTitle: 'D3 Donut Chart', 27 | hideFooter: true, 28 | layout: 'fixed', 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /src/pages/dynamic_theming/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import React, { useCallback, useState } from 'react'; 2 | import Select from '@splunk/react-ui/Select'; 3 | import styled from 'styled-components'; 4 | import DashboardCore from '@splunk/dashboard-core'; 5 | import EnterpriseViewOnlyPreset from '@splunk/dashboard-presets/EnterpriseViewOnlyPreset'; 6 | import { SplunkThemeProvider } from '@splunk/themes'; 7 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 8 | import definition from './definition.json'; 9 | 10 | const Container = styled.div` 11 | position: relative; 12 | `; 13 | 14 | const ThemeSwitcher = styled.div` 15 | position: absolute; 16 | right: 10px; 17 | top: 10px; 18 | z-index: 100; 19 | `; 20 | 21 | function Dashboard() { 22 | const [color, setColor] = useState('dark'); 23 | 24 | const handleColorChange = useCallback((_e, { value }) => { 25 | setColor(value); 26 | }, []); 27 | 28 | return ( 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | 45 | export default Dashboard; 46 | -------------------------------------------------------------------------------- /src/pages/dynamic_theming/definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataSources": { 3 | "total_count_search": { 4 | "type": "ds.search", 5 | "options": { "query": "index=_internal | stats count" } 6 | }, 7 | "error_count_search": { 8 | "type": "ds.search", 9 | "options": { "query": "index=_internal error | stats count" } 10 | }, 11 | "info_count_search": { 12 | "type": "ds.search", 13 | "options": { "query": "index=_internal info | stats count" } 14 | }, 15 | "warning_count_search": { 16 | "type": "ds.search", 17 | "options": { "query": "index=_internal warning| stats count" } 18 | }, 19 | "event_by_component_search": { 20 | "type": "ds.search", 21 | "options": { "query": "index=_internal | stats count by component" } 22 | }, 23 | "timechart_search": { 24 | "type": "ds.search", 25 | "options": { "query": "index=_internal | timechart count" } 26 | } 27 | }, 28 | "inputs": {}, 29 | "layout": { 30 | "type": "absolute", 31 | "options": { "width": 1600, "height": 1000, "display": "auto-scale" }, 32 | "structure": [ 33 | { "item": "sv_total_event", "position": { "h": 260, "w": 380, "x": 20, "y": 20 } }, 34 | { "item": "sv_info", "position": { "h": 260, "w": 380, "x": 410, "y": 20 } }, 35 | { "item": "sv_warning", "position": { "h": 260, "w": 380, "x": 800, "y": 20 } }, 36 | { "item": "sv_error", "position": { "h": 260, "w": 380, "x": 1190, "y": 20 } }, 37 | { "item": "sv_event_by_component", "position": { "h": 250, "w": 770, "x": 800, "y": 290 } }, 38 | { "item": "event_over_time", "position": { "h": 250, "w": 770, "x": 20, "y": 290 } } 39 | ] 40 | }, 41 | "title": "Simple Dashboard", 42 | "description": "", 43 | "visualizations": { 44 | "sv_total_event": { 45 | "title": "_internal event count", 46 | "type": "splunk.singlevalue", 47 | "options": { "backgroundColor": "#53a051" }, 48 | "dataSources": { "primary": "total_count_search" } 49 | }, 50 | "sv_error": { 51 | "title": "error count", 52 | "type": "splunk.singlevalue", 53 | "options": { "backgroundColor": "#dc4e41" }, 54 | "dataSources": { "primary": "error_count_search" } 55 | }, 56 | "sv_warning": { 57 | "title": "warning count", 58 | "type": "splunk.singlevalue", 59 | "options": { "backgroundColor": "#f8be34" }, 60 | "dataSources": { "primary": "warning_count_search" } 61 | }, 62 | "sv_info": { 63 | "title": "info count", 64 | "type": "splunk.singlevalue", 65 | "options": { "backgroundColor": "#294e70" }, 66 | "dataSources": { "primary": "info_count_search" } 67 | }, 68 | "sv_event_by_component": { 69 | "title": "_internal event count by component", 70 | "type": "splunk.pie", 71 | "dataSources": { "primary": "event_by_component_search" } 72 | }, 73 | "event_over_time": { 74 | "title": "_internal event count over time", 75 | "type": "splunk.area", 76 | "dataSources": { "primary": "timechart_search" } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/pages/dynamic_theming/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import Dashboard from './Dashboard'; 4 | 5 | // use DashboardCore to render a simple dashboard 6 | layout(, { 7 | pageTitle: 'Dynamic Theming', 8 | hideFooter: true, 9 | layout: 'fixed', 10 | }); 11 | -------------------------------------------------------------------------------- /src/pages/emergency_department_ops/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import { SplunkThemeProvider } from '@splunk/themes'; 4 | import DashboardCore from '@splunk/dashboard-core'; 5 | import EnterprisePreset from '@splunk/dashboard-presets/EnterprisePreset'; 6 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 7 | import definition from './definition'; 8 | 9 | // use DashboardCore to render a simple dashboard 10 | layout( 11 | 12 | 13 | 14 | 15 | , 16 | { 17 | pageTitle: 'Emergency Department Ops', 18 | hideFooter: true, 19 | layout: 'fixed', 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /src/pages/radar/Radar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import T from 'prop-types'; 3 | import { 4 | Chart as ChartJS, 5 | RadialLinearScale, 6 | PointElement, 7 | LineElement, 8 | Filler, 9 | Tooltip, 10 | Legend, 11 | } from 'chart.js'; 12 | import { Radar } from 'react-chartjs-2'; 13 | 14 | // Register components for the chart 15 | ChartJS.register(RadialLinearScale, PointElement, LineElement, Filler, Tooltip, Legend); 16 | 17 | // Options for each dataset. These could also be passed in from the visualization config options 18 | const defaultOptions = { 19 | backgroundColor: ['rgba(179,181,198,0.2)', 'rgba(255,99,132,0.2)'], 20 | borderColor: ['rgba(179,181,198,1)', 'rgba(255,99,132,1)'], 21 | pointBackgroundColor: ['rgba(179,181,198,1)', 'rgba(255,99,132,1)'], 22 | pointBorderColor: '#fff', 23 | pointHoverBackgroundColor: '#fff', 24 | pointHoverBorderColor: ['rgba(179,181,198,1)', 'rgba(255,99,132,1)'], 25 | }; 26 | 27 | /** 28 | * Pulls out a flattened set of options for a dataset, e.g. where a list of colors are given for a specific option it will chose one 29 | * @param {Object} options The options object 30 | * @param {Number} itr An iterator representing which dataset is being configured 31 | * @returns {Object} A flattened set of options for the given dataset 32 | */ 33 | const getOptions = (options, itr) => { 34 | const config = {}; 35 | Object.keys(options).forEach((key) => { 36 | config[key] = Array.isArray(options[key]) ? options[key][itr] : options[key]; 37 | }); 38 | 39 | return config; 40 | }; 41 | 42 | /** 43 | * Extract data from the datasource to the chart.js format 44 | * @param {Object} dataSources Hashmap of configured datasources 45 | * @param {Object} options Visualization options to be applied to the dataset 46 | */ 47 | const extractData = (dataSources, options) => { 48 | if (!dataSources || !dataSources.primary || !dataSources.primary.data) { 49 | return { 50 | labels: [], 51 | datasets: [], 52 | }; 53 | } 54 | 55 | /** 56 | * Converting data to Chart.js expected format. 57 | * Typically, this would be handled with a DSL parser where the user-defined context prop 58 | * is read to determine how to process the dataSources and its data 59 | */ 60 | // Pulling the labels as the column names 61 | const labels = dataSources.primary.data.fields.map((l) => l.name); 62 | // initial format for Chart.js data 63 | const chartData = { 64 | labels, 65 | datasets: [], 66 | }; 67 | 68 | // Pulling data for each column into the dataset 69 | dataSources.primary.data.columns.forEach((col) => { 70 | col.forEach((val, itr) => { 71 | // Create a new dataset object if it doesn't already exist 72 | if (!chartData.datasets[itr]) { 73 | chartData.datasets.push({ 74 | // Add the options to style the dataset 75 | ...getOptions({ ...defaultOptions, ...options }, itr), 76 | label: `data${itr}`, 77 | data: [], 78 | }); 79 | } 80 | 81 | // Add data from the column to the dataset 82 | const ds = chartData.datasets[itr]; 83 | ds.data.push(val); 84 | }); 85 | }); 86 | 87 | return chartData; 88 | }; 89 | 90 | const RadarViz = ({ options, width, height, dataSources }) => { 91 | const chartData = extractData(dataSources, options); 92 | 93 | // Provide some defaults for chart options 94 | const displayLegend = options.legend === undefined ? true : !!options.legend; 95 | const legendPosition = 96 | options.legendPosition && ['top', 'bottom', 'left', 'right'].includes(options.legendPosition) 97 | ? options.legendPosition 98 | : 'top'; 99 | 100 | return ( 101 | 126 | ); 127 | }; 128 | 129 | RadarViz.propTypes = { 130 | /** 131 | * width in pixel or string, defaults to 100% 132 | */ 133 | width: T.oneOfType([T.string, T.number]), 134 | /** 135 | * height in pixel or string 136 | */ 137 | height: T.oneOfType([T.string, T.number]), 138 | /** 139 | * visualization formatting options 140 | */ 141 | options: T.object, 142 | /** 143 | * datasource state which include data and request params, object key indicate the datasource type. 144 | */ 145 | dataSources: T.objectOf( 146 | T.shape({ 147 | /** 148 | * current request params 149 | */ 150 | requestParams: T.object, 151 | /** 152 | * current dataset 153 | */ 154 | data: T.shape({ 155 | fields: T.array, 156 | columns: T.array, 157 | }), 158 | /** 159 | * error 160 | */ 161 | error: T.shape({ 162 | level: T.string, 163 | message: T.string, 164 | }), 165 | /** 166 | * meta data that came with the dataset 167 | */ 168 | meta: T.object, 169 | }) 170 | ), 171 | }; 172 | 173 | RadarViz.defaultProps = { 174 | options: {}, 175 | height: 250, 176 | width: 300, 177 | dataSources: {}, 178 | }; 179 | 180 | export default RadarViz; 181 | -------------------------------------------------------------------------------- /src/pages/radar/definition.json: -------------------------------------------------------------------------------- 1 | { 2 | "dataSources": { 3 | "ds_radar": { 4 | "type": "ds.test", 5 | "options": { 6 | "data": { 7 | "fields": [ 8 | { "name": "Eating" }, 9 | { "name": "Drinking" }, 10 | { "name": "Sleeping" }, 11 | { "name": "Designing" }, 12 | { "name": "Coding" }, 13 | { "name": "Cycling" }, 14 | { "name": "Running" } 15 | ], 16 | "columns": [[65, 28], [59, 48], [90, 40], [81, 19], [56, 96], [55, 27], [40, 100]] 17 | } 18 | } 19 | } 20 | }, 21 | "inputs": {}, 22 | "layout": { 23 | "type": "absolute", 24 | "options": { 25 | "display": "auto-scale" 26 | }, 27 | "structure": [ 28 | { 29 | "item": "viz_aeTdCp9A", 30 | "type": "block", 31 | "position": { "h": 200, "w": 400, "x": 430, "y": 30 } 32 | }, 33 | { "item": "viz_EvMPCkP5", "type": "block", "position": { "h": 340, "w": 370, "x": 50, "y": 30 } } 34 | ] 35 | }, 36 | "title": "Radar Chart", 37 | "description": "", 38 | "visualizations": { 39 | "viz_EvMPCkP5": { 40 | "type": "splunk.markdown", 41 | "options": { "markdown": "Chart.JS Radar", "customFontSize": 40, "fontSize": "custom", "backgroundColor": "transparent" } 42 | }, 43 | "viz_aeTdCp9A": { 44 | "type": "viz.radar", 45 | "options": { 46 | "legend": true, 47 | "legendPosition": "right" 48 | }, 49 | "dataSources": { "primary": "ds_radar" } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/pages/radar/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import { SplunkThemeProvider } from '@splunk/themes'; 4 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 5 | import { DashboardCore } from '@splunk/dashboard-core'; 6 | import EnterpriseViewOnlyPreset from '@splunk/dashboard-presets/EnterpriseViewOnlyPreset'; 7 | import Radar from './Radar'; 8 | import definition from './definition.json'; 9 | 10 | const CustomPreset = { 11 | ...EnterpriseViewOnlyPreset, 12 | visualizations: { 13 | ...EnterpriseViewOnlyPreset.visualizations, 14 | 'viz.radar': Radar, 15 | }, 16 | }; 17 | 18 | // use DashboardCore to render a simple dashboard 19 | layout( 20 | 21 | 22 | 23 | 24 | , 25 | { 26 | pageTitle: 'Radar Chart', 27 | hideFooter: true, 28 | layout: 'fixed', 29 | } 30 | ); 31 | -------------------------------------------------------------------------------- /src/pages/sfo_airport/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import { SplunkThemeProvider } from '@splunk/themes'; 4 | import { DashboardContextProvider } from '@splunk/dashboard-context'; 5 | import DashboardCore from '@splunk/dashboard-core'; 6 | import EnterpriseViewOnlyPreset from '@splunk/dashboard-presets/EnterpriseViewOnlyPreset'; 7 | import definition from './definition.json'; 8 | 9 | // use DashboardCore to render a simple dashboard 10 | layout( 11 | 12 | 13 | 14 | 15 | , 16 | { 17 | pageTitle: 'San Francisco Airport Dashboard', 18 | hideFooter: true, 19 | layout: 'fixed', 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /src/pages/visualizations/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import layout from '@splunk/react-page'; 3 | import { SplunkThemeProvider } from '@splunk/themes'; 4 | import { DashboardContextProvider, GeoRegistry, GeoJsonProvider } from '@splunk/dashboard-context'; 5 | 6 | import { DashboardCore } from '@splunk/dashboard-core'; 7 | import EnterpriseViewOnlyPreset from '@splunk/dashboard-presets/EnterpriseViewOnlyPreset'; 8 | import definition from './definition.json'; 9 | 10 | const gr = GeoRegistry.create(); 11 | gr.addDefaultProvider(new GeoJsonProvider()); 12 | 13 | // use DashboardCore to render a simple dashboard 14 | layout( 15 | 16 | 21 | 22 | 23 | , 24 | { 25 | pageTitle: 'Visualizations', 26 | hideFooter: true, 27 | layout: 'fixed', 28 | } 29 | ); 30 | -------------------------------------------------------------------------------- /tools/prepare.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | const path = require('path'); 4 | const fs = require('fs-extra'); 5 | const template = require('lodash.template'); 6 | const appConfig = require('../appConfig'); 7 | 8 | const buildPath = path.join(__dirname, '..', 'build', appConfig.id); 9 | const resourcePath = path.join(__dirname, '..', 'resources'); 10 | 11 | const resourceMeta = { 12 | applicationId: appConfig.id, 13 | }; 14 | 15 | /** 16 | * walk a dir and return all files 17 | * @param {*} dir 18 | */ 19 | const walkDir = (dir) => { 20 | let files = []; 21 | fs.readdirSync(dir).forEach((f) => { 22 | const dirPath = path.join(dir, f); 23 | const isDirectory = fs.statSync(dirPath).isDirectory(); 24 | if (isDirectory) { 25 | files = files.concat(walkDir(dirPath)); 26 | } else { 27 | files.push(path.join(dir, f)); 28 | } 29 | }); 30 | return files; 31 | }; 32 | 33 | /** 34 | * copy file from resources and replace variables 35 | * @param {*} source 36 | * @param {*} dist 37 | */ 38 | const copyAndReplace = (source, dist) => { 39 | const files = walkDir(source); 40 | files.forEach((f) => { 41 | let content = fs.readFileSync(f); 42 | if (path.extname(f) === '.xml') { 43 | const compiled = template(content); 44 | content = compiled(resourceMeta); 45 | } 46 | const filePath = path.join(dist, f.replace(source, '')); 47 | fs.outputFileSync(filePath, content); 48 | }); 49 | }; 50 | 51 | if (fs.existsSync(buildPath)) { 52 | fs.removeSync(buildPath); 53 | } 54 | 55 | fs.mkdirpSync(buildPath, { recursive: true }); 56 | 57 | copyAndReplace(resourcePath, buildPath); 58 | 59 | console.log(`${appConfig.id} is prepared`); 60 | -------------------------------------------------------------------------------- /tools/symlink.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | const path = require('path'); 4 | const fs = require('fs-extra'); 5 | const appConfig = require('../appConfig'); 6 | 7 | const buildPath = path.join(__dirname, '..', 'build', appConfig.id); 8 | 9 | if (!process.env.SPLUNK_HOME) { 10 | console.log('Please set SPLUNK_HOME'); 11 | process.exit(1); 12 | } 13 | 14 | const appDir = path.join(process.env.SPLUNK_HOME, 'etc', 'apps', appConfig.id); 15 | 16 | fs.ensureSymlinkSync(buildPath, appDir); 17 | 18 | console.log(`${appConfig.id} is symlinked to ${appDir}`); 19 | 20 | const confPath = path.join(process.env.SPLUNK_HOME, 'etc', 'system', 'local', 'web.conf'); 21 | 22 | const defaultConfFile = 23 | '[settings]\n' + 24 | 'minify_css = False\n' + 25 | 'minify_js = False\n' + 26 | 'js_logger_mode = None\n' + 27 | '\n' + 28 | 'cacheEntriesLimit = 0\n' + 29 | 'cacheBytesLimit = 0\n' + 30 | 'enableWebDebug = True\n'; 31 | 32 | if (!fs.existsSync(confPath)) { 33 | fs.writeFileSync(confPath, defaultConfFile); 34 | console.log('Wrote default config to:', confPath); 35 | } 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const appConfig = require('./appConfig'); 4 | 5 | const srcDir = path.join(__dirname, 'src'); 6 | const pages = fs.readdirSync(path.join(srcDir, 'pages')).reduce((entries, name) => { 7 | // eslint-disable-next-line no-param-reassign 8 | entries[name] = path.join(srcDir, 'pages', name, 'index.jsx'); 9 | return entries; 10 | }, {}); 11 | 12 | const jsBuildDir = path.join(__dirname, 'build', appConfig.id, 'appserver', 'static', 'build'); 13 | 14 | module.exports = { 15 | mode: process.env.NODE_ENV !== 'production' ? 'development' : 'production', 16 | entry: pages, 17 | output: { 18 | path: jsBuildDir, 19 | }, 20 | resolve: { 21 | extensions: ['.js', '.jsx'], 22 | }, 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.jsx?$/, 27 | exclude: /node_modules/, 28 | use: ['babel-loader'], 29 | }, 30 | { 31 | test: /\.txt$/, 32 | use: ['raw-loader'], 33 | }, 34 | { 35 | test: /\.(png|jpg|jpeg|gif|svg|woff|woff2)$/, 36 | use: [ 37 | { 38 | loader: 'url-loader', 39 | options: { 40 | name: '[hash].[ext]', 41 | limit: 100000, 42 | }, 43 | }, 44 | ], 45 | }, 46 | { 47 | test: /\.(eot|ttf|wav|mp3)$/, 48 | use: [ 49 | { 50 | loader: 'file-loader', 51 | options: { 52 | name: '[hash].[ext]', 53 | }, 54 | }, 55 | ], 56 | }, 57 | ], 58 | }, 59 | // stats: 'errors-only', 60 | }; 61 | --------------------------------------------------------------------------------