├── .babelrc
├── .eslintrc
├── .gitignore
├── .meteor
├── .finished-upgraders
├── .gitignore
├── .id
├── packages
├── platforms
├── release
└── versions
├── LICENSE
├── README.md
├── build_docker_compose.sh
├── client
├── configs
│ ├── context.js
│ └── startup.js
├── main.js
├── modules
│ ├── accounts
│ │ ├── actions
│ │ │ ├── accounts.js
│ │ │ └── index.js
│ │ ├── components
│ │ │ ├── creating_user.jsx
│ │ │ └── sign_in.jsx
│ │ ├── configs
│ │ │ └── accounts.js
│ │ ├── containers
│ │ │ ├── creating_user.js
│ │ │ └── sign_in.js
│ │ ├── index.js
│ │ └── routes.jsx
│ ├── admin
│ │ ├── actions
│ │ │ ├── admin.js
│ │ │ ├── databases.js
│ │ │ └── index.js
│ │ ├── components
│ │ │ ├── admin.import.styl
│ │ │ ├── databases
│ │ │ │ ├── database_form.jsx
│ │ │ │ ├── database_item.jsx
│ │ │ │ └── databases.jsx
│ │ │ ├── dialog_delete.jsx
│ │ │ ├── share_charts.jsx
│ │ │ ├── share_dashboards.jsx
│ │ │ ├── user_item.jsx
│ │ │ └── users_list.jsx
│ │ ├── containers
│ │ │ ├── databases
│ │ │ │ ├── database_form.js
│ │ │ │ └── databases.js
│ │ │ ├── share_charts.js
│ │ │ ├── share_dashboards.js
│ │ │ └── users_list.js
│ │ ├── index.js
│ │ └── routes.jsx
│ ├── core
│ │ ├── actions
│ │ │ ├── core.js
│ │ │ └── index.js
│ │ ├── components
│ │ │ ├── context_menu
│ │ │ │ ├── color_values.jsx
│ │ │ │ ├── context_menu.jsx
│ │ │ │ └── format_number.jsx
│ │ │ ├── core.import.styl
│ │ │ ├── export_to_image
│ │ │ │ ├── export_images_menu_item.jsx
│ │ │ │ ├── image_renderer.jsx
│ │ │ │ └── single_image_renderer.jsx
│ │ │ ├── layout.jsx
│ │ │ ├── material-ui-container.jsx
│ │ │ ├── notifications
│ │ │ │ ├── dialog_basic.jsx
│ │ │ │ ├── dialog_important.jsx
│ │ │ │ ├── dialog_interaction.jsx
│ │ │ │ └── snackbar_message.jsx
│ │ │ └── partial
│ │ │ │ ├── agg_func_icon_menu.jsx
│ │ │ │ ├── chart_constructor_block.jsx
│ │ │ │ ├── check_role.jsx
│ │ │ │ ├── database_drop_down.jsx
│ │ │ │ ├── documents_button.jsx
│ │ │ │ ├── drop_field.jsx
│ │ │ │ ├── emptyLoading.jsx
│ │ │ │ ├── generateLinks.js
│ │ │ │ ├── helpdesk_button.jsx
│ │ │ │ ├── loading.jsx
│ │ │ │ ├── navigation.jsx
│ │ │ │ └── sidebar.jsx
│ │ ├── containers
│ │ │ ├── check_role.js
│ │ │ ├── context_menu
│ │ │ │ ├── color_values.js
│ │ │ │ └── format_number.js
│ │ │ ├── database_drop_down.js
│ │ │ ├── drop_field.js
│ │ │ ├── export_to_image
│ │ │ │ └── image_renderer.js
│ │ │ ├── navigation.js
│ │ │ ├── notifications
│ │ │ │ ├── dialog_important.js
│ │ │ │ ├── dialog_interaction.js
│ │ │ │ └── snackbar_message.js
│ │ │ └── sidebar.js
│ │ └── index.js
│ ├── home
│ │ ├── actions
│ │ │ ├── dashboard.js
│ │ │ └── index.js
│ │ ├── components
│ │ │ ├── charts
│ │ │ │ ├── handle_dashboards.jsx
│ │ │ │ ├── handle_users.jsx
│ │ │ │ ├── item.jsx
│ │ │ │ └── list.jsx
│ │ │ ├── dashboards
│ │ │ │ ├── chart_footer.jsx
│ │ │ │ ├── chart_header.jsx
│ │ │ │ ├── dashboard.jsx
│ │ │ │ ├── data_visualisation.jsx
│ │ │ │ ├── handle_charts.jsx
│ │ │ │ ├── item.jsx
│ │ │ │ ├── list.jsx
│ │ │ │ ├── presentation_mode
│ │ │ │ │ ├── chart_viewer.jsx
│ │ │ │ │ └── nav_button.jsx
│ │ │ │ └── simple_table.jsx
│ │ │ ├── handle_users
│ │ │ │ ├── handle_users_menu_item.jsx
│ │ │ │ └── user_menu_item.jsx
│ │ │ ├── home.import.styl
│ │ │ └── home.jsx
│ │ ├── containers
│ │ │ ├── charts
│ │ │ │ ├── handle_dashboards.js
│ │ │ │ ├── handle_users.js
│ │ │ │ ├── handle_users_menu_item.js
│ │ │ │ └── list.js
│ │ │ ├── dashboards
│ │ │ │ ├── chart_viewer.js
│ │ │ │ ├── current_dashboard.js
│ │ │ │ ├── dashboard.js
│ │ │ │ ├── data_visualisation.js
│ │ │ │ ├── handle_charts.js
│ │ │ │ ├── handle_users_menu_item.js
│ │ │ │ └── list.js
│ │ │ └── home.js
│ │ ├── index.js
│ │ └── routes.jsx
│ ├── publishing
│ │ ├── components
│ │ │ ├── chart.jsx
│ │ │ ├── handle_publications_button.jsx
│ │ │ ├── iframe_dialog.jsx
│ │ │ ├── iframe_generator.jsx
│ │ │ ├── publishing.import.styl
│ │ │ └── share_layout.jsx
│ │ ├── containers
│ │ │ ├── chart.js
│ │ │ └── iframe_dialog.js
│ │ ├── index.js
│ │ └── routes.jsx
│ └── workplace
│ │ ├── actions
│ │ ├── charts.js
│ │ ├── collection_fields.js
│ │ ├── console.js
│ │ ├── index.js
│ │ ├── pivot.js
│ │ ├── preview.js
│ │ ├── tests
│ │ │ └── collection_fields.js
│ │ ├── workplace.js
│ │ └── workplace_state.js
│ │ ├── components
│ │ ├── collection_fields
│ │ │ ├── add_object_item.jsx
│ │ │ ├── collection_fields.jsx
│ │ │ ├── joining_collections.jsx
│ │ │ ├── object_item.jsx
│ │ │ └── object_tree.jsx
│ │ ├── collection_list
│ │ │ ├── collection_list.jsx
│ │ │ └── collections.jsx
│ │ ├── constructors
│ │ │ ├── chart-constructor.jsx
│ │ │ ├── constructor.jsx
│ │ │ ├── constructor_block.jsx
│ │ │ ├── constructor_block_field_item.jsx
│ │ │ └── start_constructor.jsx
│ │ ├── filters
│ │ │ ├── boolean_filter.jsx
│ │ │ ├── date_filter.jsx
│ │ │ ├── filter.jsx
│ │ │ └── filter_button.jsx
│ │ ├── pivotview
│ │ │ ├── pivot_table.jsx
│ │ │ └── pivot_table_header.jsx
│ │ ├── preview
│ │ │ ├── chart_canvas.jsx
│ │ │ ├── chart_sidebar.jsx
│ │ │ ├── chart_type_button.jsx
│ │ │ ├── data_type_menu.jsx
│ │ │ ├── data_visualisation.jsx
│ │ │ ├── expression_field.jsx
│ │ │ ├── grouping_button.jsx
│ │ │ ├── preview.jsx
│ │ │ ├── preview_header.jsx
│ │ │ ├── preview_header_col.jsx
│ │ │ ├── preview_pagination.jsx
│ │ │ ├── preview_toolbar.jsx
│ │ │ ├── save_chart_form.jsx
│ │ │ └── simple_table.jsx
│ │ ├── sidebar
│ │ │ ├── toggle_sidebar_state_tab.jsx
│ │ │ └── workplace_sidebar.jsx
│ │ ├── sql_editor
│ │ │ ├── sql_button.jsx
│ │ │ └── sql_editor.jsx
│ │ ├── workplace.import.styl
│ │ └── workplace.jsx
│ │ ├── containers
│ │ ├── collection_fields.js
│ │ ├── collections_list
│ │ │ ├── collections.js
│ │ │ └── collections_list.js
│ │ ├── constructors
│ │ │ ├── chart-constructor.js
│ │ │ └── constructor.js
│ │ ├── joining_collections.js
│ │ ├── object_tree.js
│ │ ├── preview
│ │ │ ├── chart_sidebar.js
│ │ │ ├── data_visualisation.js
│ │ │ ├── pivot_table.js
│ │ │ ├── preview.js
│ │ │ ├── preview_header.js
│ │ │ ├── preview_toolbar.js
│ │ │ └── save_chart_form.js
│ │ └── workplace.js
│ │ ├── index.js
│ │ └── routes.jsx
├── single_components
│ ├── chart_icons
│ │ ├── BarIcon.jsx
│ │ ├── BooleanTypeIcon.jsx
│ │ ├── DoughnutIcon.jsx
│ │ ├── IconWrapper.jsx
│ │ ├── LineIcon.jsx
│ │ ├── NumberTypeIcon.jsx
│ │ ├── PieIcon.jsx
│ │ ├── PolarAreaIcon.jsx
│ │ ├── RadarIcon.jsx
│ │ ├── SaveAsIcon.jsx
│ │ ├── SaveIcon.jsx
│ │ └── ScatterIcon.jsx
│ ├── form_fields
│ │ ├── drop_down.jsx
│ │ ├── filter_field.jsx
│ │ ├── formsy_multi_select.jsx
│ │ ├── formsy_text_input.jsx
│ │ ├── like_filter_info_btn.jsx
│ │ └── text_input.jsx
│ ├── get_chart.js
│ └── notificator.js
└── stylesheets
│ ├── grid.css
│ ├── main.styl
│ ├── react-treeview.css
│ └── stylus
│ ├── general.import.styl
│ ├── mixins.import.styl
│ └── variables.import.styl
├── config
├── docker
│ ├── README.txt
│ ├── databazel
│ │ └── Dockerfile
│ ├── docker-compose.yml
│ └── quasar
│ │ └── Dockerfile
├── etc_nginx_sites-enabled_your_domain.conf
├── mup.json
├── quasar.json
└── settings.json
├── databazel.sh
├── deploy-quasar.sh
├── deploy.sh
├── docs
├── OVERVIEW.md
└── PRODUCTION-DEPLOYMENT.md
├── lib
├── chart_constructor.js
├── collectionHelpers
│ ├── charts.js
│ ├── dashboards.js
│ └── users.js
├── collections.js
├── colors.js
├── common_functions.js
├── constants.js
├── data_processing.js
├── downloads.js
├── echarts_themes
│ └── macarons.js
├── i18n
│ ├── en.i18n.json
│ └── index.js
├── schemas
│ ├── charts.js
│ ├── chartsLocations.js
│ ├── dashboards.js
│ └── index.js
└── sql_parser.js
├── package.json
├── public
├── favicons
│ ├── android-chrome-144x144.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-36x36.png
│ ├── android-chrome-48x48.png
│ ├── android-chrome-72x72.png
│ ├── android-chrome-96x96.png
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-180x180.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ ├── apple-touch-icon-precomposed.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-194x194.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── manifest.json
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── mstile-70x70.png
│ └── safari-pinned-tab.svg
└── images
│ ├── background-min.jpg
│ ├── background.jpg
│ ├── backgroundNew.jpg
│ └── backgroundNew.svg
└── server
├── configs
├── accounts.js
├── index.js
└── startup.js
├── hooks
├── charts.js
├── dashboards.js
└── index.js
├── lib
├── email
│ ├── email_templates
│ │ ├── enrolmentLetter.jsx
│ │ ├── itemShareLetter.jsx
│ │ └── parts
│ │ │ ├── charts.jsx
│ │ │ └── mainLayout.jsx
│ ├── getItemShareLetterOptions.js
│ └── stylesheets
│ │ ├── charts.js
│ │ └── mainLayout.js
└── quasar.js
├── main.js
├── methods
├── accounts.js
├── charts.js
├── chartsLocations.js
├── dashboard.js
├── exportCsv.js
├── index.js
├── quasar.js
└── users.js
└── publications
├── charts.js
├── chartsLocations.js
├── dashboards.js
├── index.js
└── users.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "stage-2", "react"],
3 | "plugins": ["react-require", "babel-root-slash-import"]
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "meteor",
4 | "react",
5 | "jsx-a11y"
6 | ],
7 | "extends": [
8 | "airbnb",
9 | "plugin:meteor/recommended",
10 | "plugin:react/recommended"
11 | ],
12 | "rules": {
13 | "import/no-unresolved": 0,
14 | "meteor/eventmap-params": [
15 | 2, { "templateInstanceParamName": "instance" }
16 | ],
17 | "no-param-reassign": [
18 | "error", { "props": false }
19 | ],
20 | "no-underscore-dangle": [
21 | "error", { "allow": ["__", "_id"] }
22 | ],
23 | "no-use-before-define": [
24 | "error", { "functions": false, "classes": true }
25 | ],
26 | "react/display-name": 0,
27 | "new-cap": [
28 | "error", { "capIsNewExceptions": ["OneOf", "Maybe"] }
29 | ]
30 | }
31 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | /node_modules
3 | .idea/
--------------------------------------------------------------------------------
/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 | 0.9.4-platform-file
8 | notices-for-facebook-graph-api-2
9 | 1.2.0-standard-minifiers-package
10 | 1.2.0-meteor-platform-split
11 | 1.2.0-cordova-changes
12 | 1.2.0-breaking-changes
13 | 1.3.0-split-minifiers-package
14 |
--------------------------------------------------------------------------------
/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 | dev_bundle
3 |
--------------------------------------------------------------------------------
/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | xv3qcrsrs757nkku4n
8 |
--------------------------------------------------------------------------------
/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | # Check this file (and the other files in this directory) into your repository.
3 | #
4 | # 'meteor add' and 'meteor remove' will edit this file for you,
5 | # but you can also edit it by hand.
6 |
7 | meteor-base # Packages every Meteor app needs to have
8 | mobile-experience # Packages for a great mobile UX
9 | mongo # The database Meteor supports right now
10 | reactive-var # Reactive variable for tracker
11 | jquery # Helpful client-side library
12 | tracker # Meteor's client-side reactive programming library
13 |
14 | standard-minifier-css # CSS minifier run for production mode
15 | standard-minifier-js # JS minifier run for production mode
16 | es5-shim # ECMAScript 5 compatibility for older browsers.
17 | ecmascript # Enable ECMAScript2015+ syntax in app code
18 |
19 | kadira:flow-router
20 | reactive-dict
21 | stylus
22 | universe:i18n
23 | http
24 | meteorhacks:async
25 | check
26 | aldeed:simple-schema
27 | aldeed:collection2
28 | accounts-password
29 | dburles:collection-helpers
30 | matb33:collection-hooks
31 | email
32 | meteorhacks:kadira
33 |
--------------------------------------------------------------------------------
/.meteor/platforms:
--------------------------------------------------------------------------------
1 | server
2 | browser
3 |
--------------------------------------------------------------------------------
/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@1.3.2.4
2 |
--------------------------------------------------------------------------------
/.meteor/versions:
--------------------------------------------------------------------------------
1 | accounts-base@1.2.7
2 | accounts-password@1.1.8
3 | aldeed:collection2@2.9.1
4 | aldeed:collection2-core@1.1.1
5 | aldeed:schema-deny@1.0.1
6 | aldeed:schema-index@1.0.1
7 | aldeed:simple-schema@1.5.3
8 | allow-deny@1.0.4
9 | autoupdate@1.2.9
10 | babel-compiler@6.6.4
11 | babel-runtime@0.1.8
12 | base64@1.0.8
13 | binary-heap@1.0.8
14 | blaze@2.1.7
15 | blaze-tools@1.0.8
16 | boilerplate-generator@1.0.8
17 | caching-compiler@1.0.4
18 | callback-hook@1.0.8
19 | check@1.2.1
20 | dburles:collection-helpers@1.0.4
21 | ddp@1.2.5
22 | ddp-client@1.2.7
23 | ddp-common@1.2.5
24 | ddp-rate-limiter@1.0.4
25 | ddp-server@1.2.6
26 | deps@1.0.12
27 | diff-sequence@1.0.5
28 | ecmascript@0.4.3
29 | ecmascript-runtime@0.2.10
30 | ejson@1.0.11
31 | email@1.0.12
32 | es5-shim@4.5.10
33 | fastclick@1.0.11
34 | geojson-utils@1.0.8
35 | hot-code-push@1.0.4
36 | html-tools@1.0.9
37 | htmljs@1.0.9
38 | http@1.1.5
39 | id-map@1.0.7
40 | jquery@1.11.8
41 | kadira:flow-router@2.12.1
42 | launch-screen@1.0.11
43 | livedata@1.0.18
44 | localstorage@1.0.9
45 | logging@1.0.12
46 | matb33:collection-hooks@0.8.1
47 | mdg:validation-error@0.2.0
48 | meteor@1.1.14
49 | meteor-base@1.0.4
50 | meteorhacks:async@1.0.0
51 | meteorhacks:kadira@2.30.0
52 | meteorhacks:meteorx@1.4.1
53 | minifier-css@1.1.11
54 | minifier-js@1.1.11
55 | minimongo@1.0.16
56 | mobile-experience@1.0.4
57 | mobile-status-bar@1.0.12
58 | modules@0.6.1
59 | modules-runtime@0.6.3
60 | mongo@1.1.7
61 | mongo-id@1.0.4
62 | mongo-livedata@1.0.12
63 | npm-bcrypt@0.8.7
64 | npm-mongo@1.4.43
65 | observe-sequence@1.0.11
66 | ordered-dict@1.0.7
67 | promise@0.6.7
68 | raix:eventemitter@0.1.3
69 | random@1.0.9
70 | rate-limit@1.0.4
71 | reactive-dict@1.1.7
72 | reactive-var@1.0.9
73 | reload@1.1.8
74 | retry@1.0.7
75 | routepolicy@1.0.10
76 | service-configuration@1.0.9
77 | sha@1.0.7
78 | spacebars@1.0.11
79 | spacebars-compiler@1.0.11
80 | srp@1.0.8
81 | standard-minifier-css@1.0.6
82 | standard-minifier-js@1.0.6
83 | stylus@2.511.5
84 | tracker@1.0.13
85 | ui@1.0.11
86 | underscore@1.0.8
87 | universe:i18n@1.6.0
88 | universe:utilities@2.3.2
89 | url@1.0.9
90 | webapp@1.2.8
91 | webapp-hashing@1.0.9
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | 
4 |
5 | # THE KEY FEATURES
6 |
7 | Databazel natively connects to MongoDB and present the results in a visual dashboard reporting interface, without requiring any additional development on your end.
8 |
9 | No ETL. We believe you should keep data where it is and use tools that are smart enough to visualize data directly from MongoDB without building ETL.
10 |
11 | Our solution is completely web-based so all you need to get started is a web browser!
12 |
13 | Real-time analytics for your database.
14 |
15 | You can share the created charts with your teammates.
16 |
17 | [Overview](https://github.com/JSSolutions/Databazel/blob/master/docs/OVERVIEW.md)
18 |
19 | # Start application
20 |
21 | ## Config settings.json
22 |
23 | ```
24 |
25 | "gMail": {
26 | "_comment": "Enter login, password, host and port - was used https://www.mailgun.com/",
27 | "login": "login",
28 | "password": "password",
29 | "host": "host",
30 | "port": "port"
31 | }
32 |
33 | ```
34 |
35 | Run
36 |
37 | `sh databazel.sh`
38 |
39 | ## Default user
40 |
41 | Email: `admin@admin.com`
42 | Password: `admin`
43 |
44 | # License
45 |
46 | The source code for Databazel is made available under the [AGPL v3.0](http://www.gnu.org/licenses/agpl-3.0.html) General Public License (a commercial license is also available from [Apiko Software OU](https://jssolutionsdev.com/) ).
47 |
48 | -------------
49 |
50 | Made by [](http://jssolutionsdev.com/?github=Databazel) - [Custom Software Development Company](http://jssolutionsdev.com/?github=Databazel)
51 |
--------------------------------------------------------------------------------
/build_docker_compose.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | . ~/.nvm/nvm.sh && echo "nvm installed" || curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.0/install.sh | bash
4 |
5 | nvm install 0.10.46
6 | npm install npm -g
7 | cd ./node_modules && npm remove * && cd ../ || echo 'node_modules are not installed'
8 |
9 | npm cache clear
10 | npm install --production
11 |
12 | meteor build ../build --architecture os.linux.x86_64
13 |
14 | cd ../
15 |
16 | mkdir composed_app && cd composed_app || echo 'composed_app folder exists' && cd composed_app
17 | mkdir databazel_app || echo 'databazel_app folder exists'
18 |
19 | cp -af ../databazel/config/docker/* ./databazel_app
20 |
21 | cp -f ../build/databazel.tar.gz ./databazel_app/databazel
22 |
23 | tar --exclude='databazel_app/mongo_data' -cvzf databazel_app.tar.gz databazel_app
24 |
--------------------------------------------------------------------------------
/client/configs/context.js:
--------------------------------------------------------------------------------
1 | import * as Collections from '/lib/collections';
2 | import { Meteor } from 'meteor/meteor';
3 | import { FlowRouter } from 'meteor/kadira:flow-router';
4 | import { ReactiveDict } from 'meteor/reactive-dict';
5 | import { Tracker } from 'meteor/tracker';
6 | import Notificator from '../single_components/notificator';
7 | import MainLayout from '../modules/core/components/material-ui-container.jsx';
8 |
9 | FlowRouter.notFound = {
10 | action() {
11 | FlowRouter.go('home');
12 | },
13 | };
14 | const LocalState = new ReactiveDict();
15 |
16 | export default function () {
17 | return {
18 | Meteor,
19 | FlowRouter,
20 | Collections,
21 | LocalState,
22 | Tracker,
23 | MainLayout,
24 | Notificator: new Notificator(LocalState),
25 | };
26 | }
27 |
--------------------------------------------------------------------------------
/client/configs/startup.js:
--------------------------------------------------------------------------------
1 | import { Meteor } from 'meteor/meteor';
2 | import injectTapEventPlugin from 'react-tap-event-plugin';
3 |
4 | Meteor.startup(() => {
5 | injectTapEventPlugin();
6 | });
7 |
--------------------------------------------------------------------------------
/client/main.js:
--------------------------------------------------------------------------------
1 | import { createApp } from 'mantra-core';
2 | import initContext from './configs/context';
3 | import workplace from './modules/workplace';
4 | import accounts from './modules/accounts';
5 | import home from './modules/home';
6 | import core from './modules/core';
7 | import admin from './modules/admin';
8 | import publishing from './modules/publishing';
9 |
10 | const context = initContext();
11 | const app = createApp(context);
12 |
13 | app.loadModule(workplace);
14 | app.loadModule(home);
15 | app.loadModule(accounts);
16 | app.loadModule(admin);
17 | app.loadModule(core);
18 | app.loadModule(publishing);
19 | app.init();
20 |
--------------------------------------------------------------------------------
/client/modules/accounts/actions/accounts.js:
--------------------------------------------------------------------------------
1 | import { Accounts } from 'meteor/accounts-base';
2 |
3 | export default {
4 | signIn({ Meteor, LocalState, FlowRouter }, email, password) {
5 | Meteor.loginWithPassword(email, password, (err) => {
6 | if (err && err.reason) {
7 | LocalState.set('LOGIN_ERROR', err.reason);
8 | } else {
9 | FlowRouter.go('home');
10 | }
11 | });
12 | },
13 | inviteUser({ Meteor, LocalState, FlowRouter }, email) {
14 | Meteor.call('accounts.inviteUser', email, (err) => {
15 | if (err && err.reason) {
16 | LocalState.set('CREATING_USER_ERROR', err.reason);
17 | } else {
18 | FlowRouter.go('home');
19 | }
20 | });
21 | },
22 | resetPassword({ FlowRouter, LocalState }, newPassword, token) {
23 | Accounts.resetPassword(token, newPassword, (err) => {
24 | if (err && err.reason) {
25 | LocalState.set('CREATING_USER_ERROR', err.reason);
26 | } else {
27 | FlowRouter.go('home');
28 | }
29 | });
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/client/modules/accounts/actions/index.js:
--------------------------------------------------------------------------------
1 | import accounts from './accounts';
2 |
3 | export default {
4 | accounts,
5 | };
6 |
--------------------------------------------------------------------------------
/client/modules/accounts/configs/accounts.js:
--------------------------------------------------------------------------------
1 | import { Accounts } from 'meteor/accounts-base';
2 | import { FlowRouter } from 'meteor/kadira:flow-router';
3 |
4 | Accounts.onEnrollmentLink((token) => {
5 | FlowRouter.go('accounts.resetPassword', { token });
6 | });
7 |
--------------------------------------------------------------------------------
/client/modules/accounts/containers/creating_user.js:
--------------------------------------------------------------------------------
1 | import { useDeps, composeWithTracker, composeAll } from 'mantra-core';
2 | import CreatingUser from '../components/creating_user.jsx';
3 |
4 | export const composer = ({ context }, onData) => {
5 | const { LocalState, FlowRouter } = context();
6 | const creatingUserError = LocalState.get('CREATING_USER_ERROR');
7 | const token = FlowRouter.getParam('token');
8 | onData(null, { creatingUserError, token });
9 | };
10 |
11 | export const depsMapper = (context, actions) => ({
12 | inviteUser: actions.accounts.inviteUser,
13 | resetPassword: actions.accounts.resetPassword,
14 | context: () => context,
15 | });
16 |
17 | export default composeAll(
18 | composeWithTracker(composer),
19 | useDeps(depsMapper)
20 | )(CreatingUser);
21 |
--------------------------------------------------------------------------------
/client/modules/accounts/containers/sign_in.js:
--------------------------------------------------------------------------------
1 | import { useDeps, composeWithTracker, composeAll } from 'mantra-core';
2 | import SignIn from '../components/sign_in.jsx';
3 |
4 | export const composer = ({ context }, onData) => {
5 | const { LocalState } = context();
6 | const loginError = LocalState.get('LOGIN_ERROR');
7 | onData(null, { loginError });
8 | };
9 |
10 | export const depsMapper = (context, actions) => ({
11 | signIn: actions.accounts.signIn,
12 | context: () => context,
13 | });
14 |
15 | export default composeAll(
16 | composeWithTracker(composer),
17 | useDeps(depsMapper)
18 | )(SignIn);
19 |
--------------------------------------------------------------------------------
/client/modules/accounts/index.js:
--------------------------------------------------------------------------------
1 | import routes from './routes.jsx';
2 | import actions from './actions';
3 |
4 | export default {
5 | routes,
6 | actions,
7 | };
8 |
--------------------------------------------------------------------------------
/client/modules/accounts/routes.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { mount } from 'react-mounter';
3 | import SignIn from './containers/sign_in';
4 | import CreatingUser from './containers/creating_user';
5 |
6 | export default function (injectDeps, { FlowRouter, MainLayout }) {
7 | const MainLayoutCtx = injectDeps(MainLayout);
8 | const accountRoutes = FlowRouter.group({
9 | prefix: '/accounts',
10 | name: 'accounts',
11 | });
12 |
13 | accountRoutes.route('/sign-in', {
14 | name: 'accounts.signIn',
15 | action() {
16 | mount(MainLayoutCtx, {
17 | content: () => ,
18 | });
19 | },
20 | });
21 |
22 | accountRoutes.route('/reset-password/:token', {
23 | name: 'accounts.resetPassword',
24 | action() {
25 | mount(MainLayoutCtx, {
26 | content: () => ,
27 | });
28 | },
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/client/modules/admin/actions/admin.js:
--------------------------------------------------------------------------------
1 | import { _ } from 'meteor/underscore';
2 |
3 | export default {
4 | deleteUser({ Meteor, LocalState }, userId) {
5 | Meteor.call('users.deleteUser', userId, (err) => {
6 | if (err && err.reason) LocalState.set('DELETING_USER_ERROR', err.reason);
7 | });
8 | },
9 | shareDashboards({ Meteor }, { userId, selectedDashboards, currentDashboards }) {
10 | const removedFrom = _.difference(selectedDashboards, currentDashboards)[0];
11 | const addedTo = _.difference(currentDashboards, selectedDashboards)[0];
12 | Meteor.call('dashboard.handleSharingToUser',
13 | { entityId: addedTo || removedFrom, userId, isSharing: !!addedTo });
14 | },
15 | shareCharts({ Meteor }, { userId, selectedCharts, currentCharts }) {
16 | const removedFrom = _.difference(selectedCharts, currentCharts)[0];
17 | const addedTo = _.difference(currentCharts, selectedCharts)[0];
18 | Meteor.call('chart.handleSharingToUser',
19 | { entityId: addedTo || removedFrom, userId, isSharing: !!addedTo, fromDashboard: false });
20 | },
21 | };
22 |
--------------------------------------------------------------------------------
/client/modules/admin/actions/databases.js:
--------------------------------------------------------------------------------
1 | import i18n from 'meteor/universe:i18n';
2 |
3 | export default {
4 | createDatabase({ Meteor, Notificator }, mountName, uri, cb) {
5 | Meteor.call('quasar.createMount', mountName, uri, (err) => {
6 | if (err) Notificator.important(err.reason || 'Quasar error');
7 | cb(err);
8 | });
9 | },
10 | deleteDatabase({ Notificator, Meteor }, mountName, cb) {
11 | Notificator.interaction(`${i18n.__('confirm_delete_database')}`, {
12 | confirmLabel: i18n.__('delete'),
13 | confirmFunction() {
14 | Meteor.call('quasar.deleteMount', mountName, (err) => {
15 | if (err) Notificator.important(err.reason || 'Quasar error');
16 | else cb();
17 | });
18 | },
19 | title: i18n.__('delete'),
20 | });
21 | },
22 | updateDatabase({ Notificator, Meteor }, database, newDatabase, cb) {
23 | const confirmFunction = () => {
24 | Meteor.call('quasar.updateMount', database, newDatabase, (err) => {
25 | if (err) {
26 | Notificator.important(err.reason || 'Quasar error');
27 | Meteor.call('quasar.createMount', database.mountName, database.mongoUrl, () => cb(err));
28 | } else {
29 | Meteor.call('charts.updateChartMount', database, newDatabase, () => cb(err));
30 | }
31 | });
32 | };
33 |
34 | Notificator.interaction(`${i18n.__('confirm_update_database')}`, {
35 | confirmFunction,
36 | cancelFunction: () => cb(true),
37 | confirmLabel: i18n.__('update'),
38 | title: i18n.__('warning'),
39 | });
40 | },
41 | };
42 |
--------------------------------------------------------------------------------
/client/modules/admin/actions/index.js:
--------------------------------------------------------------------------------
1 | import admin from './admin';
2 | import databases from './databases';
3 |
4 | export default {
5 | admin,
6 | databases,
7 | };
8 |
--------------------------------------------------------------------------------
/client/modules/admin/components/admin.import.styl:
--------------------------------------------------------------------------------
1 | .databases-list
2 | max-width 600px
3 | margin auto
4 | position relative
5 |
6 | .database-form
7 | padding 0 18px 25px 18px
8 |
9 | .edit-database-form
10 | padding 0 18px
11 | position relative
12 | .form
13 | width calc(100% - 38px)
14 |
15 | .database-loading
16 | width 100%
17 | height 100%
18 | position absolute
19 | top 0
20 | background-color rgba(255, 255, 255, .6)
21 |
--------------------------------------------------------------------------------
/client/modules/admin/components/databases/database_form.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import Formsy from 'formsy-react';
3 | import FormsyText from 'formsy-material-ui/lib/FormsyText';
4 | import i18n from 'meteor/universe:i18n';
5 |
6 | const styles = {
7 | mountName: {
8 | width: '120px',
9 | marginRight: '18px',
10 | },
11 | mongoUrl: {
12 | width: 'calc(100% - 138px)',
13 | },
14 | error: {
15 | position: 'absolute',
16 | top: '70px',
17 | },
18 | };
19 |
20 | class AddDatabaseForm extends React.Component {
21 | componentDidMount() {
22 | this.refs.mountName.focus();
23 | }
24 | render() {
25 | const { database } = this.props;
26 | return (
27 | this.props.submit(model)}
30 | noValidate
31 | >
32 |
41 |
55 |
56 |
57 | );
58 | }
59 | }
60 |
61 | AddDatabaseForm.propTypes = {
62 | database: PropTypes.object,
63 | submit: PropTypes.func,
64 | };
65 |
66 | export default AddDatabaseForm;
67 |
--------------------------------------------------------------------------------
/client/modules/admin/components/dialog_delete.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import Dialog from 'material-ui/Dialog';
3 | import FlatButton from 'material-ui/FlatButton';
4 | import T from '/lib/i18n';
5 |
6 | export default class DialogDelete extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.deleteUser = this.deleteUser.bind(this);
10 | this.closeDialog = this.closeDialog.bind(this);
11 | }
12 |
13 | deleteUser() {
14 | const { user, deleteUser, setIsDialogDeleteOpen } = this.props;
15 | deleteUser(user._id);
16 | setIsDialogDeleteOpen(false);
17 | }
18 |
19 | closeDialog() {
20 | this.props.setIsDialogDeleteOpen(false);
21 | }
22 |
23 | render() {
24 | const { user, isOpen } = this.props;
25 | const actions = [
26 | ,
31 | ,
37 | ];
38 |
39 | return (
40 |
41 |
50 |
51 | );
52 | }
53 | }
54 |
55 | DialogDelete.propTypes = {
56 | user: PropTypes.oneOfType([
57 | PropTypes.object,
58 | PropTypes.bool,
59 | ]),
60 | deleteUser: PropTypes.func,
61 | setIsDialogDeleteOpen: PropTypes.func,
62 | isOpen: PropTypes.bool,
63 | };
64 |
--------------------------------------------------------------------------------
/client/modules/admin/containers/databases/database_form.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DatabaseForm from '../../components/databases/database_form.jsx';
3 | import { useDeps, composeWithTracker, composeAll } from 'mantra-core';
4 |
5 | export const composer = ({ context, database }, onData) => {
6 | const { Meteor } = context();
7 | if (database) {
8 | Meteor.call('quasar.getMount', database.name, (err, mount) => {
9 | database.mongoUri = mount.connectionUri;
10 | onData(null, { database });
11 | });
12 | } else {
13 | onData(null, {});
14 | }
15 | };
16 |
17 | export default composeAll(
18 | composeWithTracker(composer, () => ),
19 | useDeps()
20 | )(DatabaseForm);
21 |
--------------------------------------------------------------------------------
/client/modules/admin/containers/databases/databases.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Databases from '../../components/databases/databases.jsx';
3 | import Loading from '/client/modules/core/components/partial/loading.jsx';
4 | import { useDeps, composeWithTracker, composeAll } from 'mantra-core';
5 |
6 | export const composer = ({ context }, onData) => {
7 | const { Meteor } = context();
8 | Meteor.call('quasar.getMetadata', '/', (err, mounts) => {
9 | if (err) return onData(null, { isQuasarError: true });
10 | return onData(null, { databases: mounts });
11 | });
12 | };
13 |
14 | export const depsMapper = (context, actions) => ({
15 | createDatabase: actions.databases.createDatabase,
16 | deleteDatabase: actions.databases.deleteDatabase,
17 | updateDatabase: actions.databases.updateDatabase,
18 | context: () => context,
19 | });
20 |
21 | export default composeAll(
22 | composeWithTracker(composer, () => ),
23 | useDeps(depsMapper)
24 | )(Databases);
25 |
--------------------------------------------------------------------------------
/client/modules/admin/containers/share_charts.js:
--------------------------------------------------------------------------------
1 | import ShareCharts from '../components/share_charts.jsx';
2 | import { useDeps, composeWithTracker, composeAll } from 'mantra-core';
3 | import { _ } from 'meteor/underscore';
4 |
5 | export const composer = ({ context, userId }, onData) => {
6 | const { Meteor, Collections } = context();
7 | if (Meteor.subscribe('charts.light').ready()) {
8 | const charts = Collections.Charts.find({}, { fields: { users: 1, chartName: 1 } }).fetch();
9 | const selectedCharts = charts.reduce((previous, chart) => {
10 | if (~chart.users.indexOf(userId)) previous.push(chart._id);
11 | return previous;
12 | }, []);
13 | const defaultCharts = _.clone(selectedCharts);
14 | onData(null, { charts, selectedCharts, defaultCharts, userId });
15 | } else {
16 | onData(null, { userId });
17 | }
18 | };
19 |
20 | export const depsMapper = (context, actions) => ({
21 | context: () => context,
22 | shareCharts: actions.admin.shareCharts,
23 | });
24 |
25 | export default composeAll(
26 | composeWithTracker(composer),
27 | useDeps(depsMapper)
28 | )(ShareCharts);
29 |
--------------------------------------------------------------------------------
/client/modules/admin/containers/share_dashboards.js:
--------------------------------------------------------------------------------
1 | import ShareDashboards from '../components/share_dashboards.jsx';
2 | import { useDeps, composeWithTracker, composeAll } from 'mantra-core';
3 | import { _ } from 'meteor/underscore';
4 |
5 | export const composer = ({ context, userId }, onData) => {
6 | const { Meteor, Collections } = context();
7 | if (Meteor.subscribe('dashboards').ready()) {
8 | const dashboards = Collections.Dashboards.find({}).fetch();
9 | const selectedDashboards = dashboards.reduce((previous, board) => {
10 | if (!!~board.users.indexOf(userId)) previous.push(board._id);
11 | return previous;
12 | }, []);
13 | const defaultDashboards = _.clone(selectedDashboards);
14 | onData(null, { dashboards, selectedDashboards, defaultDashboards, userId });
15 | } else {
16 | onData(null, { userId });
17 | }
18 | };
19 |
20 | export const depsMapper = (context, actions) => ({
21 | context: () => context,
22 | shareDashboards: actions.admin.shareDashboards,
23 | });
24 |
25 | export default composeAll(
26 | composeWithTracker(composer),
27 | useDeps(depsMapper)
28 | )(ShareDashboards);
29 |
--------------------------------------------------------------------------------
/client/modules/admin/containers/users_list.js:
--------------------------------------------------------------------------------
1 | import UsersList from '../components/users_list.jsx';
2 | import { useDeps, composeWithTracker, composeAll } from 'mantra-core';
3 |
4 | export const composer = ({ context }, onData) => {
5 | const { Meteor, FlowRouter } = context();
6 | if (Meteor.subscribe('users').ready()) {
7 | const users = Meteor.users.find().fetch();
8 | const inviteUser = () => FlowRouter.go('inviteUser');
9 | onData(null, { users, inviteUser });
10 | }
11 | };
12 |
13 | export const depsMapper = (context, actions) => ({
14 | inviteUser: actions.accounts.inviteUser,
15 | deleteUser: actions.admin.deleteUser,
16 | meteorMethodCall: actions.core.meteorMethodCall,
17 | context: () => context,
18 | });
19 |
20 | export default composeAll(
21 | composeWithTracker(composer),
22 | useDeps(depsMapper)
23 | )(UsersList);
24 |
--------------------------------------------------------------------------------
/client/modules/admin/index.js:
--------------------------------------------------------------------------------
1 | import routes from './routes.jsx';
2 | import actions from './actions';
3 |
4 | export default {
5 | routes,
6 | actions,
7 | };
8 |
--------------------------------------------------------------------------------
/client/modules/admin/routes.jsx:
--------------------------------------------------------------------------------
1 | import { mount } from 'react-mounter';
2 | import React from 'react';
3 | import CheckRole from '/client/modules/core/containers/check_role';
4 | import UsersList from '/client/modules/admin/containers/users_list';
5 | import CreatingUser from '/client/modules/accounts/containers/creating_user';
6 | import Databases from '/client/modules/admin/containers/databases/databases';
7 |
8 | export default function (injectDeps, { FlowRouter, MainLayout }) {
9 | const MainLayoutCtx = injectDeps(MainLayout);
10 | FlowRouter.route('/users-list', {
11 | name: 'usersList',
12 | action() {
13 | mount(MainLayoutCtx, {
14 | content: () => ,
15 | });
16 | },
17 | });
18 |
19 | FlowRouter.route('/invite-user', {
20 | name: 'inviteUser',
21 | action() {
22 | mount(MainLayoutCtx, {
23 | content: () => ,
24 | });
25 | },
26 | });
27 |
28 | FlowRouter.route('/databases', {
29 | name: 'databases',
30 | action() {
31 | mount(MainLayoutCtx, {
32 | content: () => ,
33 | });
34 | },
35 | });
36 | }
37 |
--------------------------------------------------------------------------------
/client/modules/core/actions/index.js:
--------------------------------------------------------------------------------
1 | import core from './core';
2 |
3 | export default {
4 | core,
5 | };
6 |
--------------------------------------------------------------------------------
/client/modules/core/components/context_menu/color_values.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import i18n from 'meteor/universe:i18n';
3 | import FormatColorFill from 'material-ui/svg-icons/editor/format-color-fill';
4 | import Toggle from 'material-ui/Toggle';
5 | import { ListItem } from 'material-ui/List';
6 |
7 | class FormatNumberItem extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.handleChange = this.handleChange.bind(this);
11 | }
12 |
13 | handleChange(foo, isValuesColored) {
14 | const { pivot, updateViewObj } = this.props;
15 | Object.assign(pivot, { isValuesColored });
16 | updateViewObj({ pivot });
17 | }
18 |
19 | render() {
20 | const { menuParams: { styles }, pivot } = this.props;
21 | return (
22 | }
25 | style={styles.root}
26 | innerDivStyle={styles.innerDiv}
27 | rightToggle={
28 |
33 | }
34 | />
35 | );
36 | }
37 | }
38 |
39 | FormatNumberItem.propTypes = {
40 | itemsParams: PropTypes.object.isRequired,
41 | updateViewObj: PropTypes.func.isRequired,
42 | menuParams: PropTypes.object.isRequired,
43 | pivot: PropTypes.object.isRequired,
44 | };
45 |
46 | export default FormatNumberItem;
47 |
--------------------------------------------------------------------------------
/client/modules/core/components/context_menu/format_number.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import i18n from 'meteor/universe:i18n';
3 | import FormatNumberIcon from 'material-ui/svg-icons/image/filter-1';
4 | import Formsy from 'formsy-react';
5 | import FormsyText from 'formsy-material-ui/lib/FormsyText';
6 | import { ListItem } from 'material-ui/List';
7 | import { _ } from 'meteor/underscore';
8 |
9 | class FormatNumberItem extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.handleChange = this.handleChange.bind(this);
13 | }
14 |
15 | handleChange({ template }) {
16 | const { itemsParams: { field }, updateSelectedField, menuParams } = this.props;
17 | field.numberTemplate = template;
18 | updateSelectedField(field);
19 | menuParams.closeContextMenu();
20 | }
21 |
22 | render() {
23 | const { itemsParams: { field }, menuParams: { styles } } = this.props;
24 | return (
25 | }
28 | className="format-number-item"
29 | style={styles.root}
30 | innerDivStyle={styles.innerDiv}
31 | nestedListStyle={styles.nestedList}
32 | nestedItems={[
33 |
34 |
35 |
42 |
43 | ,
44 | ]}
45 | />
46 | );
47 | }
48 | }
49 |
50 | FormatNumberItem.propTypes = {
51 | itemsParams: PropTypes.object.isRequired,
52 | updateSelectedField: PropTypes.func.isRequired,
53 | menuParams: PropTypes.object.isRequired,
54 | };
55 |
56 | export default FormatNumberItem;
57 |
--------------------------------------------------------------------------------
/client/modules/core/components/core.import.styl:
--------------------------------------------------------------------------------
1 | .loading
2 | position absolute
3 | height 100%
4 | width calc(100% - 12px)
5 | background rgba(255, 255, 255, .4)
6 | top 0
7 | left 0
8 | margin 0 6px
9 | & > div
10 | top calc(50% - 50px)
11 | left calc(50% - 50px)
12 |
13 | .loading-centered
14 | margin 0
15 | width calc(100% - 20px)
16 | z-index 1
17 |
18 | .loading-preview
19 | width: calc(100% + 4px)
20 | margin: 0
21 | z-index: 1101
22 |
23 | .sidebar
24 | .logo
25 | margin 24px
26 | color #00BCD4
27 | .chart-modified-ago
28 | font-size 0.7em
29 | color grey
30 | margin-left 10px
31 |
32 | .focus-elements
33 | .blackout
34 | position absolute
35 | width 100%
36 | height 100%
37 | background-color rgba(0, 0, 0, .1)
38 | transition all 0.5s
39 | z-index 10000
40 | .backlightDrag
41 | z-index 10001
42 | position relative
43 | .backlight
44 | z-index 10001
45 | position relative
46 | background-color #fff
47 |
48 | .drop-area-container
49 | padding 7px
50 | height 100%
51 | .drop-area
52 | height 100%
53 | border 2px dashed #d6d6d6
54 | color #bdbdbd
55 | text-align center
56 | line-height 45px
57 | &.rotate
58 | width 64px
59 | height 274px
60 | .drop-area .label
61 | transform rotate(-90deg)
62 | position relative
63 | width 100px
64 | top 110px
65 | left -25px
66 |
67 | .app-bar-content
68 | width: calc(100% - 147px);
69 | padding-left: 35px;
70 | .left-content
71 | float left
72 | .right-content
73 | float right
74 |
75 | .format-number-item button
76 | width 32px !important
77 | height 32px !important
78 | padding 4px !important
--------------------------------------------------------------------------------
/client/modules/core/components/export_to_image/export_images_menu_item.jsx:
--------------------------------------------------------------------------------
1 | import React, { PropTypes } from 'react';
2 | import MenuItem from 'material-ui/MenuItem';
3 | import ImageIcon from 'material-ui/svg-icons/image/image';
4 | import CircularProgress from 'material-ui/CircularProgress';
5 | import { grey600 } from 'material-ui/styles/colors';
6 | import i18n from 'meteor/universe:i18n';
7 | import ImageRenderer from '/client/modules/core/containers/export_to_image/image_renderer';
8 |
9 | class ExportImageMenuItem extends React.Component {
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | isLoading: false,
14 | };
15 | }
16 |
17 | render() {
18 | const { iconStyle, dashboardName, chartsId } = this.props;
19 | return (
20 |