├── .editorconfig ├── .eslintrc.yml ├── .github └── FUNDING.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.md ├── Makefile ├── README.md ├── browserlist ├── demo ├── README.md ├── conf │ ├── config-ga.yml │ ├── config-github-stats.yml │ ├── config-github.yml │ ├── config-gitlab-labels-charts.yml │ ├── config-gitlab.yml │ ├── config-json.yml │ ├── config-time.yml │ ├── config-travis.yml │ └── config.yml ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── register_apis.js ├── server.js ├── src │ ├── index.js │ ├── register_extensions.js │ └── register_themes.js └── yarn.lock ├── lerna.json ├── package.json ├── packages ├── babel-preset │ ├── build │ │ └── use-lodash-es.js │ ├── index.js │ ├── package.json │ └── yarn.lock ├── server │ ├── .gitignore │ ├── .npmignore │ ├── .prettierrc │ ├── README.md │ ├── package.json │ ├── src │ │ ├── bus.ts │ │ ├── core_api.ts │ │ ├── index.ts │ │ ├── load_yaml.ts │ │ ├── logger.ts │ │ └── mozaik.ts │ ├── test │ │ ├── bus.test.ts │ │ ├── bus_add_client.test.ts │ │ ├── bus_process_api_call.test.ts │ │ ├── bus_register_api.test.ts │ │ ├── bus_remove_client.test.ts │ │ ├── bus_subscribe.test.ts │ │ ├── bus_unsubscribe.test.ts │ │ ├── fixtures │ │ │ └── config │ │ │ │ ├── invalid.yml │ │ │ │ └── valid.yml │ │ ├── load_yaml.test.ts │ │ └── logger.ts │ ├── tsconfig.json │ ├── tslint.json │ └── yarn.lock ├── themes │ ├── .babelrc │ ├── .gitignore │ ├── .npmignore │ ├── LICENSE.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── index.js │ │ ├── mini-kuro │ │ │ ├── charts.js │ │ │ ├── colors.js │ │ │ ├── index.js │ │ │ └── typography.js │ │ ├── mini │ │ │ ├── charts.js │ │ │ ├── colors.js │ │ │ ├── index.js │ │ │ └── typography.js │ │ ├── night-blue │ │ │ ├── charts.js │ │ │ ├── colors.js │ │ │ ├── index.js │ │ │ └── typography.js │ │ ├── snow │ │ │ ├── charts.js │ │ │ ├── colors.js │ │ │ ├── index.js │ │ │ └── typography.js │ │ ├── solarized-dark │ │ │ ├── charts.js │ │ │ ├── colors.js │ │ │ ├── index.js │ │ │ └── typography.js │ │ └── wine │ │ │ ├── charts.js │ │ │ ├── colors.js │ │ │ ├── index.js │ │ │ └── typography.js │ └── yarn.lock └── ui │ ├── .babelrc │ ├── .gitignore │ ├── .npmignore │ ├── .storybook │ ├── addons.js │ └── config.js │ ├── README.md │ ├── package.json │ ├── src │ ├── App.js │ ├── WidgetsRegistry.js │ ├── actions │ │ ├── apiActions.js │ │ ├── configurationActions.js │ │ ├── dashboardsActions.js │ │ ├── notificationsActions.js │ │ ├── themesActions.js │ │ └── wsActions.js │ ├── components │ │ ├── ConnectionStatus.js │ │ ├── ExternalLink.js │ │ ├── Inspector.js │ │ ├── Mozaik.js │ │ ├── Text.js │ │ ├── ThemeProvider.js │ │ ├── TrapApiError.js │ │ ├── dashboard │ │ │ ├── Dashboard.js │ │ │ ├── DashboardHeader.js │ │ │ ├── DashboardPlayer.js │ │ │ └── DashboardTitle.js │ │ ├── icons.js │ │ ├── notifications │ │ │ ├── Notifications.js │ │ │ └── NotificationsItem.js │ │ ├── settings │ │ │ ├── Settings.js │ │ │ └── ThemesSetting.js │ │ └── widget │ │ │ ├── UnknowWidgetTypeError.js │ │ │ ├── Widget.js │ │ │ ├── WidgetAvatar.js │ │ │ ├── WidgetBody.js │ │ │ ├── WidgetCounter.js │ │ │ ├── WidgetHeader.js │ │ │ ├── WidgetLabel.js │ │ │ ├── WidgetLoader.js │ │ │ ├── WidgetWrapper.js │ │ │ ├── list │ │ │ └── WidgetListItem.js │ │ │ ├── status │ │ │ ├── WidgetStatusBadge.js │ │ │ └── WidgetStatusChip.js │ │ │ └── table │ │ │ ├── WidgetTable.js │ │ │ ├── WidgetTableCell.js │ │ │ └── WidgetTableHeadCell.js │ ├── configureStore.js │ ├── constants │ │ ├── notificationsConstants.js │ │ └── wsConstants.js │ ├── containers │ │ ├── MozaikContainer.js │ │ ├── NotificationsContainer.js │ │ └── WidgetContainer.js │ ├── index.js │ ├── lib │ │ ├── WSHelper.js │ │ └── shallowEqual.js │ ├── reducers │ │ ├── apiReducer.js │ │ ├── configurationReducer.js │ │ ├── dashboardsReducer.js │ │ ├── index.js │ │ ├── notificationsReducer.js │ │ ├── themesReducer.js │ │ └── wsReducer.js │ └── theming │ │ ├── ThemeManager.js │ │ ├── defaultTheme.js │ │ └── typography.js │ ├── stories │ ├── decorators │ │ ├── MultiTheme.js │ │ ├── registerThemes.js │ │ ├── themed.js │ │ ├── withMultiTheme.js │ │ └── withWidget.js │ ├── text.js │ ├── widget_counter.js │ ├── widget_header.js │ ├── widget_label.js │ ├── widget_list_item.js │ └── widget_status_badge.js │ ├── test │ ├── .eslintrc.yml │ └── reducers │ │ └── apiReducer.test.js │ └── yarn.lock ├── preview.png ├── website ├── README.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── package.json ├── src │ ├── assets │ │ ├── extension-demo │ │ │ ├── github-extension-website.png │ │ │ ├── heroku-auto-deploy.png │ │ │ ├── heroku-connect-github.png │ │ │ ├── heroku-create-app-form.png │ │ │ ├── heroku-create-app.png │ │ │ ├── heroku-deploy-method.png │ │ │ ├── heroku-deploy.png │ │ │ └── heroku-vars.png │ │ ├── features │ │ │ ├── mozaik-icon-backend.png │ │ │ ├── mozaik-icon-extendable.png │ │ │ ├── mozaik-icon-positioning.png │ │ │ ├── mozaik-icon-scalable.png │ │ │ └── mozaik-icon-themes.png │ │ ├── mozaik-logo-white.png │ │ ├── mozaik-logo.png │ │ ├── mozaik-pattern.gif │ │ ├── mozaik-pattern.png │ │ ├── posts │ │ │ ├── mozaik-inspector-widget.png │ │ │ └── saucelabs-widgets.png │ │ └── refs │ │ │ ├── aptus-health.png │ │ │ ├── canalplus.png │ │ │ ├── icelandair.svg │ │ │ ├── jolicode.svg │ │ │ └── sc5.png │ ├── components │ │ ├── Container.js │ │ ├── Extensions.js │ │ ├── ExtensionsItem.js │ │ ├── ExternalPosts.js │ │ ├── Features.js │ │ ├── FeaturesItem.js │ │ ├── Footer.js │ │ ├── GeneratedContent.js │ │ ├── Global.js │ │ ├── Header.js │ │ ├── HeaderNav.js │ │ ├── HomeBanner.js │ │ ├── Pager.js │ │ ├── References.js │ │ ├── SecondaryNav.js │ │ ├── Share.js │ │ └── SubHeader.js │ ├── data │ │ ├── extensions.yml │ │ ├── references.yml │ │ └── useCases.yml │ ├── layouts │ │ ├── documentation.js │ │ ├── index.js │ │ └── post.js │ ├── pages │ │ ├── 404.js │ │ ├── architecture.js │ │ ├── docs │ │ │ ├── quick-start │ │ │ │ ├── configuration.md │ │ │ │ ├── grid-system.md │ │ │ │ ├── index.md │ │ │ │ └── using-extensions.md │ │ │ └── v1 │ │ │ │ ├── guides │ │ │ │ ├── client-poll-mode.md │ │ │ │ ├── client-push-mode.md │ │ │ │ ├── client.md │ │ │ │ ├── extension-demo.md │ │ │ │ └── index.md │ │ │ │ └── quick-start │ │ │ │ ├── configuration.md │ │ │ │ ├── grid-system.md │ │ │ │ ├── index.md │ │ │ │ ├── theming.md │ │ │ │ └── widgets.md │ │ ├── extensions │ │ │ └── index.js │ │ ├── faq.js │ │ ├── index.js │ │ └── posts │ │ │ ├── mozaik-1.0.10.md │ │ │ ├── mozaik-1.0.13.md │ │ │ ├── mozaik-1.2.0.md │ │ │ ├── mozaik-1.3.0.md │ │ │ ├── mozaik-saucelabs-extensions.md │ │ │ ├── mozaik-v2.md │ │ │ └── mozaik-website-launched.md │ ├── styles │ │ ├── base.css │ │ ├── index.css │ │ ├── media.js │ │ ├── prism.css │ │ └── theme.js │ └── templates │ │ ├── documentation.js │ │ └── post.js ├── static │ ├── CNAME │ └── favicon.ico └── yarn.lock └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,ts}] 2 | indent_style = space 3 | indent_size = 4 4 | max_line_length = 100 5 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parser: babel-eslint 2 | 3 | parserOptions: 4 | ecmaVersion: 6 5 | sourceType: module 6 | ecmaFeatures: 7 | jsx: true 8 | experimentalObjectRestSpread: true 9 | 10 | env: 11 | browser: true 12 | es6: true 13 | 14 | globals: 15 | global: true 16 | 17 | extends: 18 | - eslint:recommended 19 | - plugin:react/recommended 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [plouc] 4 | #open_collective: # Replace with a single Open Collective username 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log* 3 | node_modules 4 | .idea 5 | .DS_Store 6 | .nyc_output 7 | coverage 8 | .env 9 | *.lerna_backup 10 | 11 | # ignore extensions, this directory is just here 12 | # to ease extension authoring using lerna abilities 13 | /extensions 14 | 15 | /demo/build 16 | 17 | website/.cache 18 | website/public 19 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | .eslintrc 3 | .babelrc 4 | .gitignore 5 | .DS_Store 6 | /.nyc_output 7 | /coverage 8 | /test 9 | /src/ui 10 | preview.png 11 | *.log* 12 | yarn.lock 13 | webpack.config.js 14 | browserlist 15 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | packages/*/es 2 | packages/*/lib -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | printWidth: 100 2 | tabWidth: 4 3 | bracketSpacing: true 4 | semi: false 5 | trailingComma: es5 6 | singleQuote: true 7 | arrowParens: avoid -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '8' 4 | - '10' 5 | before_script: 6 | - make init 7 | script: 8 | - make test-all 9 | #- npm run test-cover 10 | #after_success: 11 | # - npm run coveralls 12 | 13 | 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) Raphaël Benitte 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![MOZAÏK][logo] 2 | 3 | [![License][license-image]][license-url] 4 | [![Travis CI][travis-image]][travis-url] 5 | [![NPM version][npm-image]][npm-url] 6 | [![mozaïk channel on discord](https://img.shields.io/badge/discord-mozaik-61dafb.svg?style=flat-square)](https://discord.gg/E6tQSsQ) 7 | 8 | Mozaïk is a tool based on nodejs / react / redux / d3 to easily craft beautiful dashboards. [See demo](http://mozaik.herokuapp.com/) 9 | 10 | ![preview](https://raw.githubusercontent.com/juhamust/mozaik/readme/preview.png) 11 | 12 | **Features:** 13 | 14 | - Scalable layout 15 | - Themes support 16 | - Extendable by modules 17 | - Grid positioning 18 | - Optimized backend communication 19 | - Rotation support (with smooth transition) 20 | 21 | ## Getting started 22 | 23 | Installation instructions: 24 | 25 | - [Mozaïk V1 (stable)](http://mozaik.rocks/v1/use/) 26 | - [Mozaïk V2 (latest)](http://mozaik.rocks/v2/use/) 27 | 28 | Visit the [website](http://mozaik.rocks/) for further information/doc. 29 | 30 | [logo]: https://raw.githubusercontent.com/wiki/plouc/mozaik/assets/mozaik-logo-v2.png 31 | [license-image]: https://img.shields.io/github/license/plouc/mozaik.svg?style=flat-square 32 | [license-url]: https://github.com/plouc/mozaik/blob/master/LICENSE.md 33 | [npm-image]: https://img.shields.io/npm/v/@mozaik/ui.svg?style=flat-square 34 | [npm-url]: https://www.npmjs.com/~mozaik 35 | [travis-image]: https://img.shields.io/travis/plouc/mozaik.svg?style=flat-square 36 | [travis-url]: https://travis-ci.org/plouc/mozaik 37 | -------------------------------------------------------------------------------- /browserlist: -------------------------------------------------------------------------------- 1 | # Supported Browsers by Mozaïk 2 | 3 | Chrome > 27 # so flex rules receive a -webkit- prefix 4 | > 1% 5 | -------------------------------------------------------------------------------- /demo/conf/config-ga.yml: -------------------------------------------------------------------------------- 1 | host: 0.0.0.0 2 | port: 5000 3 | 4 | # define duration between each dashboard rotation (ms) 5 | rotationDuration: 10000 6 | 7 | # This part of the config is neither watched nor 8 | # transmitted to the UI as it might contain sensible info 9 | apis: 10 | # define the global interval used by Mozaïk Bus to call registered APIs 11 | pollInterval: 100000 12 | 13 | extension: &extension analytics 14 | startDate: &startDate 300daysAgo 15 | endDate: &endDate 288daysAgo 16 | id: &id 96121643 17 | 18 | dashboards: 19 | - title: Google Analytics Demo 20 | columns: 3 21 | rows: 3 22 | widgets: 23 | - 24 | extension: *extension 25 | widget: Browser 26 | id: *id 27 | startDate: *startDate 28 | endDate: *endDate 29 | columns: 1 30 | rows: 1 31 | x: 0 32 | y: 0 33 | - 34 | extension: *extension 35 | widget: PageViews 36 | id: *id 37 | startDate: *startDate 38 | endDate: *endDate 39 | columns: 1 40 | rows: 1 41 | x: 1 42 | y: 0 43 | - 44 | extension: *extension 45 | widget: PageViewsLine 46 | id: *id 47 | startDate: *startDate 48 | endDate: *endDate 49 | columns: 1 50 | rows: 1 51 | x: 2 52 | y: 0 53 | - 54 | extension: *extension 55 | widget: TopPages 56 | id: *id 57 | startDate: *startDate 58 | endDate: *endDate 59 | columns: 1 60 | rows: 1 61 | x: 0 62 | y: 1 63 | - 64 | extension: *extension 65 | widget: TopPagesAvgTimeBar 66 | id: *id 67 | startDate: *startDate 68 | endDate: *endDate 69 | columns: 1 70 | rows: 1 71 | x: 1 72 | y: 1 73 | - 74 | extension: *extension 75 | widget: TopPagesViewsBar 76 | id: *id 77 | startDate: *startDate 78 | endDate: *endDate 79 | columns: 1 80 | rows: 1 81 | x: 2 82 | y: 1 83 | - 84 | extension: *extension 85 | widget: TopPagesViewsBubble 86 | id: *id 87 | startDate: *startDate 88 | endDate: *endDate 89 | columns: 1 90 | rows: 1 91 | x: 0 92 | y: 2 93 | - 94 | extension: *extension 95 | widget: TopPagesViewsLine 96 | id: *id 97 | startDate: *startDate 98 | endDate: *endDate 99 | columns: 1 100 | rows: 1 101 | x: 1 102 | y: 2 103 | - 104 | extension: *extension 105 | widget: TopPagesViewsTreeMap 106 | id: *id 107 | startDate: *startDate 108 | endDate: *endDate 109 | columns: 1 110 | rows: 1 111 | x: 2 112 | y: 2 -------------------------------------------------------------------------------- /demo/conf/config-github-stats.yml: -------------------------------------------------------------------------------- 1 | port: 5000 2 | 3 | # define duration between each dashboard rotation (seconds) 4 | rotationDuration: 10 5 | 6 | # This part of the config is neither watched nor 7 | # transmitted to the UI as it might contain sensible info 8 | apis: 9 | # define the global interval used by Mozaïk Bus to call registered APIs 10 | pollInterval: 100000 11 | 12 | extension: &extension github 13 | github_user: &github_user plouc 14 | github_org: &github_org ekino 15 | github_repo: &github_repo plouc/mozaik 16 | 17 | dashboards: 18 | - title: "GitHub Demo: traffic" 19 | columns: 2 20 | rows: 3 21 | widgets: 22 | - 23 | extension: *extension 24 | widget: RepoTrafficViewsHistogram 25 | repository: *github_repo 26 | rows: 1 27 | columns: 1 28 | x: 0 29 | y: 0 30 | - 31 | extension: *extension 32 | widget: RepoTrafficViewsLine 33 | repository: *github_repo 34 | rows: 1 35 | columns: 1 36 | x: 1 37 | y: 0 38 | - 39 | extension: *extension 40 | widget: RepoTrafficClonesHistogram 41 | repository: *github_repo 42 | rows: 1 43 | columns: 1 44 | x: 0 45 | y: 1 46 | - 47 | extension: *extension 48 | widget: RepoTrafficClonesLine 49 | repository: *github_repo 50 | rows: 1 51 | columns: 1 52 | x: 1 53 | y: 1 54 | - 55 | extension: *extension 56 | widget: RepoCommitActivityHistogram 57 | repository: *github_repo 58 | rows: 1 59 | columns: 1 60 | x: 0 61 | y: 2 62 | - 63 | extension: *extension 64 | widget: RepoCommitActivityLine 65 | repository: *github_repo 66 | rows: 1 67 | columns: 1 68 | x: 1 69 | y: 2 -------------------------------------------------------------------------------- /demo/conf/config-gitlab.yml: -------------------------------------------------------------------------------- 1 | host: 0.0.0.0 2 | port: 5000 3 | 4 | # define duration between each dashboard rotation (ms) 5 | rotationDuration: 8000 6 | 7 | # This part of the config is neither watched nor 8 | # transmitted to the UI as it might contain sensible info 9 | apis: 10 | # define the global interval used by Mozaïk Bus to call registered APIs 11 | pollInterval: 100000 12 | 13 | extension: &extension gitlab 14 | gitlabProject: &gitlabProject gitlab-org/gitlab-runner 15 | 16 | dashboards: 17 | - title: '@mozaik/ext-gitlab demo' 18 | columns: 4 19 | rows: 6 20 | widgets: 21 | - 22 | extension: *extension 23 | widget: Project 24 | project: *gitlabProject 25 | columns: 1 26 | rows: 2 27 | x: 0 28 | y: 0 29 | - 30 | extension: *extension 31 | widget: ProjectMembers 32 | project: *gitlabProject 33 | columns: 1 34 | rows: 2 35 | x: 1 36 | y: 3 37 | - 38 | extension: *extension 39 | widget: ProjectContributors 40 | project: *gitlabProject 41 | columns: 1 42 | rows: 2 43 | x: 2 44 | y: 3 45 | - 46 | extension: *extension 47 | widget: ProjectActivity 48 | project: *gitlabProject 49 | columns: 1 50 | rows: 4 51 | x: 0 52 | y: 2 53 | - 54 | extension: *extension 55 | widget: Branches 56 | project: *gitlabProject 57 | columns: 1 58 | rows: 4 59 | x: 3 60 | y: 2 61 | - 62 | extension: *extension 63 | widget: JobHistogram 64 | project: *gitlabProject 65 | columns: 2 66 | rows: 2 67 | x: 1 68 | y: 0 69 | - 70 | extension: mozaik 71 | widget: Inspector 72 | columns: 1 73 | rows: 2 74 | x: 3 75 | y: 0 76 | - 77 | extension: *extension 78 | widget: LatestProjectPipeline 79 | project: *gitlabProject 80 | gitRef: master 81 | hideCommitMessage: true 82 | columns: 2 83 | rows: 1 84 | x: 1 85 | y: 2 86 | - 87 | extension: *extension 88 | widget: LatestProjectPipeline 89 | project: *gitlabProject 90 | gitRef: runner-self-signed-docs 91 | hideCommitMessage: false 92 | columns: 2 93 | rows: 1 94 | x: 1 95 | y: 5 96 | -------------------------------------------------------------------------------- /demo/conf/config-json.yml: -------------------------------------------------------------------------------- 1 | # 2 | # @mozaik/ext-json demo dashboard 3 | # 4 | port: 5000 5 | 6 | # define duraton between each dashboard rotation (ms) 7 | rotationDuration: 4000 8 | 9 | # This part of the config is neither watched nor 10 | # transmitted to the UI as it might contain sensible info 11 | apis: 12 | # define the global interval used by Mozaïk Bus to call registered APIs 13 | pollInterval: 10000000 14 | 15 | dashboards: 16 | - 17 | columns: 4 18 | rows: 3 19 | title: '@mozaik/ext-json demo' 20 | widgets: 21 | - 22 | extension: json 23 | widget: JsonKeys 24 | title: Json keys 25 | url: https://jsonplaceholder.typicode.com/todos/1 26 | headers: 27 | X-Whatever: yay 28 | keys: 29 | - id 30 | - userId 31 | - title 32 | - completed 33 | columns: 1 34 | rows: 1 35 | x: 0 36 | y: 0 37 | - 38 | extension: json 39 | widget: CustomJson 40 | title: Custom json 41 | url: https://jsonplaceholder.typicode.com/todos/1 42 | headers: 43 | X-Whatever: yay 44 | template: | 45 | Using custom template to display json data:

46 |   The user ID is ${userId},
47 |   and the title is ${title}. 48 | columns: 1 49 | rows: 1 50 | x: 1 51 | y: 0 52 | - 53 | extension: mozaik 54 | widget: Inspector 55 | columns: 1 56 | rows: 1 57 | x: 2 58 | y: 0 59 | - 60 | extension: json 61 | widget: JsonStatus 62 | title: Json status (contains) 63 | url: https://jsonplaceholder.typicode.com/todos/1 64 | statuses: 65 | - assert: contains(title,delectus) 66 | status: success 67 | label: Task `title` contains 'delectus' 68 | columns: 1 69 | rows: 1 70 | x: 0 71 | y: 1 72 | - 73 | extension: json 74 | widget: JsonStatus 75 | title: Json status (falsy) 76 | url: https://jsonplaceholder.typicode.com/todos/1 77 | statuses: 78 | - assert: falsy(completed) 79 | status: warning 80 | label: Task `completed` is a falsy value 81 | columns: 1 82 | rows: 1 83 | x: 1 84 | y: 1 85 | - 86 | extension: json 87 | widget: JsonStatus 88 | title: Json status (matches) 89 | url: https://jsonplaceholder.typicode.com/todos/1 90 | statuses: 91 | - assert: matches(title, tem$) 92 | status: error 93 | label: Task `title` matches `/tem$/` 94 | columns: 1 95 | rows: 1 96 | x: 2 97 | y: 1 98 | -------------------------------------------------------------------------------- /demo/conf/config-time.yml: -------------------------------------------------------------------------------- 1 | port: 5000 2 | 3 | # define duration between each dashboard rotation (ms) 4 | rotationDuration: 10000 5 | 6 | # This part of the config is neither watched nor 7 | # transmitted to the UI as it might contain sensible info 8 | apis: 9 | # define the global interval used by Mozaïk Bus to call registered APIs 10 | pollInterval: 100000 11 | 12 | dashboards: 13 | - columns: 4 14 | rows: 3 15 | title: Mozaïk time extension dashboard 16 | widgets: 17 | - 18 | extension: time 19 | widget: Clock 20 | timezone: Europe/Paris 21 | info: date 22 | title: Paris 23 | columns: 1 24 | rows: 1 25 | x: 0 26 | y: 0 27 | - 28 | extension: time 29 | widget: Clock 30 | info: time 31 | timezone: Asia/Tokyo 32 | title: Tokyo 33 | columns: 1 34 | rows: 1 35 | x: 1 36 | y: 0 37 | - 38 | extension: time 39 | widget: Clock 40 | info: timezone 41 | timezone: America/Los_Angeles 42 | title: Los Angeles 43 | columns: 1 44 | rows: 1 45 | x: 2 46 | y: 0 47 | - 48 | extension: time 49 | widget: DigitalClock 50 | timezone: Europe/Paris 51 | displaySeconds: false 52 | columns: 1 53 | rows: 1 54 | x: 0 55 | y: 1 56 | - 57 | extension: time 58 | widget: DigitalClock 59 | timezone: Asia/Tokyo 60 | displayDate: false 61 | columns: 1 62 | rows: 1 63 | x: 1 64 | y: 1 65 | - 66 | extension: time 67 | widget: DigitalClock 68 | timezone: America/Los_Angeles 69 | columns: 1 70 | rows: 1 71 | x: 2 72 | y: 1 73 | - 74 | extension: time 75 | widget: Clock 76 | timezone: Europe/Paris 77 | columns: 1 78 | rows: 1 79 | x: 0 80 | y: 2 81 | - 82 | extension: time 83 | widget: Clock 84 | timezone: Asia/Tokyo 85 | columns: 1 86 | rows: 1 87 | x: 1 88 | y: 2 89 | - 90 | extension: time 91 | widget: Clock 92 | timezone: America/Los_Angeles 93 | columns: 1 94 | rows: 1 95 | x: 2 96 | y: 2 -------------------------------------------------------------------------------- /demo/conf/config-travis.yml: -------------------------------------------------------------------------------- 1 | port: 5000 2 | 3 | # define duration between each dashboard rotation (seconds) 4 | rotationDuration: 10 5 | 6 | # This part of the config is neither watched nor 7 | # transmitted to the UI as it might contain sensible info 8 | apis: 9 | # define the global interval used by Mozaïk Bus to call registered APIs 10 | pollInterval: 100000 11 | 12 | extension: &extension travis 13 | 14 | # change those values of you want to use another user/repo 15 | travisOwner: &travisOwner plouc 16 | travisRepo: &travisRepo mozaik 17 | 18 | dashboards: 19 | - columns: 4 20 | rows: 4 21 | title: '@mozaik/ext-travis demo' 22 | widgets: 23 | - 24 | extension: *extension 25 | widget: Repository 26 | owner: *travisOwner 27 | repository: *travisRepo 28 | columns: 1 29 | rows: 2 30 | x: 0 31 | y: 0 32 | - 33 | extension: *extension 34 | widget: BuildHistory 35 | owner: *travisOwner 36 | repository: *travisRepo 37 | hideHeader: false 38 | columns: 1 39 | rows: 4 40 | x: 3 41 | y: 0 42 | - 43 | extension: *extension 44 | widget: LatestRepositoryBuild 45 | owner: *travisOwner 46 | repository: *travisRepo 47 | hideHeader: false 48 | columns: 2 49 | rows: 2 50 | x: 1 51 | y: 0 52 | - 53 | extension: *extension 54 | widget: RepositoryBuildsStats 55 | owner: *travisOwner 56 | repository: *travisRepo 57 | columns: 1 58 | rows: 2 59 | x: 0 60 | y: 2 61 | - 62 | extension: *extension 63 | widget: BuildHistogram 64 | owner: *travisOwner 65 | repository: *travisRepo 66 | limit: 40 67 | columns: 2 68 | rows: 2 69 | x: 1 70 | y: 2 -------------------------------------------------------------------------------- /demo/conf/config.yml: -------------------------------------------------------------------------------- 1 | # 2 | # mozaik generic demo dashboard 3 | # 4 | # It's not required, but you should add this env var, 5 | # without it, you'll probably reach the API rate limit. 6 | # 7 | # GITHUB_API_TOKEN=xxxxx 8 | # 9 | port: 5000 10 | 11 | # define duraton between each dashboard rotation (ms) 12 | rotationDuration: 10000 13 | 14 | # This part of the config is neither watched nor 15 | # transmitted to the UI as it might contain sensible info 16 | apis: 17 | # define the global interval used by Mozaïk Bus to call registered APIs 18 | pollInterval: 100000 19 | 20 | dashboards: 21 | - columns: 4 22 | rows: 3 23 | title: Mozaïk demo dashboard 24 | widgets: 25 | - 26 | extension: github 27 | widget: UserBadge 28 | user: plouc 29 | columns: 1 30 | rows: 1 31 | x: 0 32 | y: 0 33 | - 34 | extension: github 35 | widget: OrgBadge 36 | organization: ekino 37 | columns: 1 38 | rows: 1 39 | x: 1 40 | y: 0 41 | - 42 | extension: github 43 | widget: RepoContributorsStats 44 | repository: plouc/mozaik 45 | columns: 1 46 | rows: 1 47 | x: 2 48 | y: 0 49 | - 50 | extension: github 51 | widget: RepoBadge 52 | repository: plouc/mozaik 53 | columns: 1 54 | rows: 1 55 | x: 3 56 | y: 0 57 | - 58 | extension: mozaik 59 | widget: Inspector 60 | columns: 1 61 | rows: 1 62 | x: 0 63 | y: 1 64 | - 65 | extension: github 66 | widget: Status 67 | columns: 1 68 | rows: 1 69 | x: 1 70 | y: 1 71 | - 72 | extension: github 73 | widget: Branches 74 | repository: plouc/mozaik 75 | columns: 1 76 | rows: 1 77 | x: 2 78 | y: 1 79 | - 80 | extension: github 81 | widget: PullRequests 82 | repository: plouc/mozaik 83 | columns: 1 84 | rows: 1 85 | x: 3 86 | y: 1 87 | - 88 | extension: travis 89 | widget: Repository 90 | owner: plouc 91 | repository: mozaik 92 | columns: 1 93 | rows: 1 94 | x: 0 95 | y: 2 96 | - 97 | extension: travis 98 | widget: BuildHistogram 99 | owner: plouc 100 | repository: mozaik 101 | columns: 2 102 | rows: 1 103 | x: 1 104 | y: 2 105 | - 106 | extension: travis 107 | widget: BuildHistory 108 | owner: plouc 109 | repository: mozaik 110 | columns: 1 111 | rows: 1 112 | x: 3 113 | y: 2 114 | -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mozaik-basic-example", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@mozaik/ext-github": "^2.0.0-alpha.10", 7 | "@mozaik/ext-gitlab": "^2.0.0-alpha.9", 8 | "@mozaik/ext-jira": "^0.5.0", 9 | "@mozaik/ext-json": "^0.4.0", 10 | "@mozaik/ext-time": "^2.0.0-alpha.11", 11 | "@mozaik/ext-travis": "^2.0.0-rc.0", 12 | "@mozaik/server": "^2.0.0-alpha.7", 13 | "@mozaik/themes": "^1.0.0-alpha.17", 14 | "@mozaik/ui": "^2.0.0-rc.2", 15 | "nivo": "^0.15.0", 16 | "react": "^16.4.0", 17 | "react-dom": "^16.4.0" 18 | }, 19 | "devDependencies": { 20 | "react-scripts": "1.1.4" 21 | }, 22 | "proxy": { 23 | "/socket": { 24 | "target": "ws://localhost:5000", 25 | "ws": true 26 | }, 27 | "/config": { 28 | "target": "http://localhost:5000" 29 | } 30 | }, 31 | "scripts": { 32 | "start": "react-scripts start", 33 | "build": "react-scripts build" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/demo/public/favicon.ico -------------------------------------------------------------------------------- /demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 22 | Mozaïk basic dashboard example 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /demo/register_apis.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = server => { 4 | server.registerApi('github', require('@mozaik/ext-github/client')) 5 | server.registerApi('travis', require('@mozaik/ext-travis/client')) 6 | server.registerApi('gitlab', require('@mozaik/ext-gitlab/client')) 7 | server.registerApi('json', require('@mozaik/ext-json/client')) 8 | server.registerApi('jira', require('@mozaik/ext-jira/client')) 9 | } 10 | -------------------------------------------------------------------------------- /demo/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | require('dotenv').load({ silent: true }) 4 | 5 | const path = require('path') 6 | const server = require('@mozaik/server').default 7 | 8 | let configFile = process.argv[2] || 'conf/config.yml' 9 | 10 | console.log(`> using config file: '${configFile}'\n`) 11 | 12 | server 13 | .configureFromFile(path.join(__dirname, configFile)) 14 | .then(() => { 15 | require('./register_apis')(server) 16 | server.start() 17 | }) 18 | .catch(err => { 19 | console.error(err) 20 | }) 21 | -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './register_themes' 4 | import './register_extensions' 5 | import Mozaik from '@mozaik/ui' 6 | 7 | ReactDOM.render(, document.getElementById('root')) 8 | -------------------------------------------------------------------------------- /demo/src/register_extensions.js: -------------------------------------------------------------------------------- 1 | import { Registry } from '@mozaik/ui' 2 | 3 | import github from '@mozaik/ext-github' 4 | import gitlab from '@mozaik/ext-gitlab' 5 | import jira from '@mozaik/ext-jira' 6 | import json from '@mozaik/ext-json' 7 | import time from '@mozaik/ext-time' 8 | import travis from '@mozaik/ext-travis' 9 | 10 | Registry.addExtensions({ 11 | github, 12 | gitlab, 13 | jira, 14 | json, 15 | time, 16 | travis, 17 | }) 18 | -------------------------------------------------------------------------------- /demo/src/register_themes.js: -------------------------------------------------------------------------------- 1 | import { ThemeManager } from '@mozaik/ui' 2 | 3 | import { 4 | miniTheme, 5 | miniKuroTheme, 6 | nightBlueTheme, 7 | snowTheme, 8 | solarizedDarkTheme, 9 | wineTheme, 10 | } from '@mozaik/themes' 11 | 12 | ThemeManager.add(miniTheme) 13 | ThemeManager.add(miniKuroTheme) 14 | ThemeManager.add(nightBlueTheme) 15 | ThemeManager.add(snowTheme) 16 | ThemeManager.add(solarizedDarkTheme) 17 | ThemeManager.add(wineTheme) 18 | 19 | ThemeManager.defaultTheme = solarizedDarkTheme.name 20 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "2.0.0-rc.5", 3 | "packages": [ 4 | "packages/*", 5 | "extensions/*", 6 | "demo" 7 | ], 8 | "version": "0.0.0", 9 | "npmClient": "yarn" 10 | } 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "engineStrict": true, 4 | "engines": { 5 | "node": ">=8.2.0", 6 | "npm": ">=3.0.0" 7 | }, 8 | "devDependencies": { 9 | "babel-eslint": "^8.2.3", 10 | "eslint": "^4.19.1", 11 | "eslint-plugin-react": "^7.9.1", 12 | "lerna": "^3.0.0-rc.0", 13 | "prettier": "^1.14.0", 14 | "raf": "^3.4.0", 15 | "rollup": "^0.60.1" 16 | }, 17 | "dependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /packages/babel-preset/build/use-lodash-es.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | return { 3 | visitor: { 4 | ImportDeclaration(path) { 5 | var source = path.node.source 6 | source.value = source.value.replace(/^lodash($|\/)/, 'lodash-es$1') 7 | }, 8 | }, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/babel-preset/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const plugins = [ 4 | [ 5 | require.resolve('babel-plugin-transform-es2015-template-literals'), 6 | { 7 | loose: true, 8 | }, 9 | ], 10 | require.resolve('babel-plugin-transform-es2015-literals'), 11 | require.resolve('babel-plugin-transform-es2015-function-name'), 12 | require.resolve('babel-plugin-transform-es2015-arrow-functions'), 13 | require.resolve('babel-plugin-transform-es2015-block-scoped-functions'), 14 | require.resolve('babel-plugin-transform-class-properties'), 15 | [ 16 | require.resolve('babel-plugin-transform-es2015-classes'), 17 | { 18 | loose: true, 19 | }, 20 | ], 21 | require.resolve('babel-plugin-transform-es2015-object-super'), 22 | require.resolve('babel-plugin-transform-es2015-shorthand-properties'), 23 | [ 24 | require.resolve('babel-plugin-transform-es2015-computed-properties'), 25 | { 26 | loose: true, 27 | }, 28 | ], 29 | require.resolve('babel-plugin-check-es2015-constants'), 30 | [ 31 | require.resolve('babel-plugin-transform-es2015-spread'), 32 | { 33 | loose: true, 34 | }, 35 | ], 36 | require.resolve('babel-plugin-transform-es2015-parameters'), 37 | [ 38 | require.resolve('babel-plugin-transform-es2015-destructuring'), 39 | { 40 | loose: true, 41 | }, 42 | ], 43 | require.resolve('babel-plugin-transform-es2015-block-scoping'), 44 | require.resolve('babel-plugin-transform-object-rest-spread'), 45 | require.resolve('babel-plugin-transform-react-jsx'), 46 | require.resolve('babel-plugin-syntax-jsx'), 47 | ] 48 | 49 | const env = process.env.BABEL_ENV || process.env.NODE_ENV 50 | 51 | if (env === 'commonjs') { 52 | plugins.push.apply(plugins, [ 53 | [ 54 | require.resolve('babel-plugin-transform-es2015-modules-commonjs'), 55 | { 56 | loose: true, 57 | }, 58 | ], 59 | ]) 60 | } 61 | 62 | if (env === 'es') { 63 | plugins.push.apply(plugins, [require.resolve('./build/use-lodash-es')]) 64 | } 65 | 66 | if (env === 'test') { 67 | plugins.push.apply(plugins, [ 68 | require.resolve('babel-plugin-istanbul'), 69 | [ 70 | require.resolve('babel-plugin-transform-es2015-modules-commonjs'), 71 | { 72 | loose: true, 73 | }, 74 | ], 75 | ]) 76 | } 77 | 78 | module.exports = { 79 | plugins, 80 | } 81 | -------------------------------------------------------------------------------- /packages/babel-preset/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mozaik/babel-preset", 3 | "version": "1.0.0-alpha.6", 4 | "description": "Mozaïk babel preset", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/plouc/mozaik" 8 | }, 9 | "author": { 10 | "name": "Raphaël Benitte", 11 | "url": "https://github.com/plouc" 12 | }, 13 | "license": "MIT", 14 | "dependencies": { 15 | "babel-cli": "^6.24.1", 16 | "babel-core": "^6.25.0", 17 | "babel-plugin-check-es2015-constants": "^6.22.0", 18 | "babel-plugin-external-helpers": "^6.22.0", 19 | "babel-plugin-istanbul": "^4.1.4", 20 | "babel-plugin-syntax-jsx": "^6.18.0", 21 | "babel-plugin-transform-class-properties": "^6.24.1", 22 | "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", 23 | "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", 24 | "babel-plugin-transform-es2015-block-scoping": "^6.24.1", 25 | "babel-plugin-transform-es2015-classes": "^6.24.1", 26 | "babel-plugin-transform-es2015-computed-properties": "^6.24.1", 27 | "babel-plugin-transform-es2015-destructuring": "^6.23.0", 28 | "babel-plugin-transform-es2015-function-name": "^6.24.1", 29 | "babel-plugin-transform-es2015-literals": "^6.22.0", 30 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", 31 | "babel-plugin-transform-es2015-object-super": "^6.24.1", 32 | "babel-plugin-transform-es2015-parameters": "^6.24.1", 33 | "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", 34 | "babel-plugin-transform-es2015-spread": "^6.22.0", 35 | "babel-plugin-transform-es2015-template-literals": "^6.22.0", 36 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 37 | "babel-plugin-transform-react-jsx": "^6.24.1" 38 | }, 39 | "scripts": { 40 | "version": "echo ${npm_package_version}" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/server/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/server/.npmignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .prettierrc 3 | tsconfig.json 4 | tslint.json 5 | .DS_Store 6 | /coverage 7 | /src 8 | *.log* 9 | -------------------------------------------------------------------------------- /packages/server/.prettierrc: -------------------------------------------------------------------------------- 1 | printWidth: 100 2 | tabWidth: 4 3 | bracketSpacing: true 4 | semi: false 5 | trailingComma: es5 6 | singleQuote: true 7 | arrowParens: avoid -------------------------------------------------------------------------------- /packages/server/README.md: -------------------------------------------------------------------------------- 1 | ![MOZAÏK][logo] 2 | 3 | [![License][license-image]][license-url] 4 | [![NPM version][npm-image]][npm-url] 5 | 6 | # @mozaik/server 7 | 8 | [logo]: https://raw.githubusercontent.com/wiki/plouc/mozaik/assets/mozaik-logo-v2.png 9 | [license-image]: https://img.shields.io/github/license/plouc/mozaik.svg?style=flat-square 10 | [license-url]: https://github.com/plouc/mozaik/blob/master/LICENSE.md 11 | [npm-image]: https://img.shields.io/npm/v/@mozaik/server.svg?style=flat-square 12 | [npm-url]: https://www.npmjs.com/package/@mozaik/server 13 | -------------------------------------------------------------------------------- /packages/server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mozaik/server", 3 | "version": "2.0.0-alpha.7", 4 | "description": "Mozaïk server", 5 | "main": "dist/index.js", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/plouc/mozaik" 10 | }, 11 | "author": { 12 | "name": "Raphaël Benitte", 13 | "url": "https://github.com/plouc" 14 | }, 15 | "devDependencies": { 16 | "@types/chokidar": "^1.7.5", 17 | "@types/cors": "^2.8.4", 18 | "@types/express": "^4.16.0", 19 | "@types/jest": "^23.3.1", 20 | "@types/js-yaml": "^3.11.2", 21 | "@types/lodash": "^4.14.115", 22 | "@types/request-promise-native": "^1.0.15", 23 | "@types/socket.io": "^1.4.36", 24 | "@types/winston": "^2.3.9", 25 | "jest": "^23.4.2", 26 | "prettier": "^1.14.0", 27 | "ts-jest": "^23.0.1", 28 | "tslint-config-prettier": "^1.14.0", 29 | "typescript": "^3.0.1" 30 | }, 31 | "scripts": { 32 | "test": "jest --verbose", 33 | "build": "tsc", 34 | "version": "echo ${npm_package_version}", 35 | "lint": "tslint 'src/**/*.ts' 'test/**/*.ts'", 36 | "fmt": "prettier --color --write \"{src,test}/**/*.{js,ts}\"", 37 | "fmt:check": "prettier --list-different \"{src,test}/**/*.{js,ts}\"", 38 | "prepublishOnly": "npm run build" 39 | }, 40 | "dependencies": { 41 | "chalk": "^2.4.1", 42 | "chokidar": "^1.7.0", 43 | "cors": "^2.8.4", 44 | "express": "^4.15.3", 45 | "js-yaml": "^3.9.0", 46 | "lodash": "^4.17.4", 47 | "request": "^2.87.0", 48 | "request-promise-native": "^1.0.5", 49 | "socket.io": "^2.1.1", 50 | "tslint": "^5.11.0", 51 | "winston": "^2.3.1" 52 | }, 53 | "jest": { 54 | "testURL": "http://localhost/", 55 | "transform": { 56 | "^.+\\.ts$": "ts-jest" 57 | }, 58 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 59 | "moduleFileExtensions": [ 60 | "ts", 61 | "js", 62 | "json", 63 | "node" 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /packages/server/src/core_api.ts: -------------------------------------------------------------------------------- 1 | import Bus from './bus' 2 | 3 | export default (bus: Bus) => () => ({ 4 | inspector() { 5 | return Promise.resolve({ 6 | apis: bus.listApis(), 7 | clientCount: bus.clientCount(), 8 | uptime: process.uptime(), 9 | }) 10 | }, 11 | }) 12 | -------------------------------------------------------------------------------- /packages/server/src/index.ts: -------------------------------------------------------------------------------- 1 | import Mozaik from './mozaik' 2 | 3 | export default new Mozaik() 4 | -------------------------------------------------------------------------------- /packages/server/src/load_yaml.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as yaml from 'js-yaml' 3 | 4 | /** 5 | * Loads and parse a yaml file. 6 | */ 7 | export default (path: string): any => 8 | new Promise((resolve, reject) => { 9 | fs.readFile(path, 'utf8', (err, data) => { 10 | if (err) return reject(err) 11 | 12 | try { 13 | const parsed = yaml.safeLoad(data) 14 | resolve(parsed) 15 | } catch (err) { 16 | return reject(err) 17 | } 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /packages/server/src/logger.ts: -------------------------------------------------------------------------------- 1 | import * as winston from 'winston' 2 | import { LeveledLogMethod } from 'winston' 3 | 4 | export interface Logger { 5 | error: LeveledLogMethod 6 | warn: LeveledLogMethod 7 | info: LeveledLogMethod 8 | debug: LeveledLogMethod 9 | } 10 | 11 | export default winston 12 | -------------------------------------------------------------------------------- /packages/server/test/bus.test.ts: -------------------------------------------------------------------------------- 1 | declare var jest, beforeAll, it, expect 2 | 3 | import chalk from 'chalk' 4 | import { Socket } from 'socket.io' 5 | import Bus from '../src/bus' 6 | import loggerMock from './logger' 7 | 8 | beforeAll(() => { 9 | chalk.enabled = false 10 | }) 11 | 12 | it('clientCount() should return the number of connected clients', () => { 13 | const logger = loggerMock() 14 | const bus = new Bus({ logger }) 15 | 16 | expect(bus.clientCount()).toBe(0) 17 | 18 | bus.addClient({ id: 'client_a' } as Socket) 19 | bus.addClient({ id: 'client_b' } as Socket) 20 | bus.addClient({ id: 'client_c' } as Socket) 21 | 22 | expect(bus.clientCount()).toBe(3) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/server/test/bus_add_client.test.ts: -------------------------------------------------------------------------------- 1 | declare var jest, beforeAll, it, expect 2 | 3 | import chalk from 'chalk' 4 | import { Socket } from 'socket.io' 5 | import Bus from '../src/bus' 6 | import loggerMock from './logger' 7 | 8 | beforeAll(() => { 9 | chalk.enabled = false 10 | }) 11 | 12 | it('should add a client to the current list', () => { 13 | const logger = loggerMock() 14 | const bus = new Bus({ logger }) 15 | 16 | bus.addClient({ id: 'test_client' } as Socket) 17 | 18 | expect(bus.listClients()).toHaveProperty('test_client') 19 | 20 | expect(logger.info).toHaveBeenCalled() 21 | expect(logger.info).toHaveBeenCalledWith('Client #test_client connected') 22 | }) 23 | 24 | it('should throw if a client with the same id already exists', () => { 25 | const logger = loggerMock() 26 | const bus = new Bus({ logger }) 27 | 28 | bus.addClient({ id: 'test_client' } as Socket) 29 | 30 | expect(() => { 31 | bus.addClient({ id: 'test_client' } as Socket) 32 | }).toThrow(`Client with id 'test_client' already exists`) 33 | 34 | expect(logger.error).toHaveBeenCalled() 35 | expect(logger.error).toHaveBeenCalledWith(`Client with id 'test_client' already exists`) 36 | }) 37 | -------------------------------------------------------------------------------- /packages/server/test/bus_register_api.test.ts: -------------------------------------------------------------------------------- 1 | declare var jest, it, expect, beforeAll 2 | 3 | import chalk from 'chalk' 4 | import Bus from '../src/bus' 5 | import loggerMock from './logger' 6 | 7 | beforeAll(() => { 8 | chalk.enabled = false 9 | }) 10 | 11 | it('should make the API available', () => { 12 | const logger = loggerMock() 13 | const bus = new Bus({ logger }) 14 | 15 | bus.registerApi('test_api', () => {}) 16 | 17 | expect(bus.listApis()).toEqual(['test_api']) 18 | expect(logger.info).toHaveBeenCalled() 19 | expect(logger.info).toHaveBeenCalledWith(`Registered API 'test_api' (mode: poll)`) 20 | }) 21 | 22 | it('should throw if the API was already registered', () => { 23 | const logger = loggerMock() 24 | const bus = new Bus({ logger }) 25 | 26 | bus.registerApi('test_api', () => {}) 27 | 28 | expect(logger.info).toHaveBeenCalled() 29 | expect(logger.info).toHaveBeenCalledWith(`Registered API 'test_api' (mode: poll)`) 30 | 31 | const expectedError = `API 'test_api' already registered` 32 | 33 | expect(() => { 34 | bus.registerApi('test_api', () => {}) 35 | }).toThrow(expectedError) 36 | 37 | expect(logger.error).toHaveBeenCalled() 38 | expect(logger.error).toHaveBeenCalledWith(expectedError) 39 | }) 40 | 41 | it(`should allow to set API mode to 'push'`, () => { 42 | const logger = loggerMock() 43 | const bus = new Bus({ logger }) 44 | 45 | bus.registerApi('test_api', () => {}, 'push') 46 | 47 | expect(bus.listApis()).toEqual(['test_api']) 48 | expect(logger.info).toHaveBeenCalled() 49 | expect(logger.info).toHaveBeenCalledWith(`Registered API 'test_api' (mode: push)`) 50 | }) 51 | 52 | it('should throw if we pass an invalid API mode', () => { 53 | const logger = loggerMock() 54 | const bus = new Bus({ logger }) 55 | 56 | const expectedError = `API mode 'invalid' is not a valid mode, must be one of 'poll' or 'push'` 57 | 58 | expect(() => { 59 | bus.registerApi('test_api', () => {}, 'invalid') 60 | }).toThrow(expectedError) 61 | 62 | expect(logger.error).toHaveBeenCalled() 63 | expect(logger.error).toHaveBeenCalledWith(expectedError) 64 | }) 65 | -------------------------------------------------------------------------------- /packages/server/test/bus_remove_client.test.ts: -------------------------------------------------------------------------------- 1 | declare var jest, it, expect, beforeAll 2 | 3 | import chalk from 'chalk' 4 | import { Socket } from 'socket.io' 5 | import Bus, { Subscription } from '../src/bus' 6 | import loggerMock from './logger' 7 | 8 | beforeAll(() => { 9 | chalk.enabled = false 10 | }) 11 | 12 | it('should remove a registered client from the current list', () => { 13 | const logger = loggerMock() 14 | const bus = new Bus({ logger }) 15 | 16 | bus.addClient({ id: 'test_client' } as Socket) 17 | expect(bus.listClients()).toHaveProperty('test_client') 18 | 19 | bus.removeClient('test_client') 20 | expect(bus.listClients()).not.toHaveProperty('test_client') 21 | 22 | expect(logger.info).toHaveBeenCalledTimes(2) 23 | expect(logger.info).toHaveBeenCalledWith('Client #test_client connected') 24 | expect(logger.info).toHaveBeenCalledWith('Client #test_client disconnected') 25 | }) 26 | 27 | it('should cleanup subscription and remove timer if no clients left', () => { 28 | const logger = loggerMock() 29 | const bus = new Bus({ logger }) 30 | 31 | bus.addClient({ 32 | id: 'test_client', 33 | emit: jest.fn(), 34 | } as Socket) 35 | expect(bus.listClients()).toHaveProperty('test_client') 36 | 37 | bus.registerApi('test_api', () => ({ 38 | test() {}, 39 | })) 40 | expect(bus.listApis()).toEqual(['test_api']) 41 | 42 | bus.subscribe('test_client', { id: 'test_api.test' } as Subscription) 43 | 44 | const subscriptions = bus.listSubscriptions() 45 | expect(subscriptions['test_api.test'].timer).not.toBeUndefined() 46 | expect(subscriptions['test_api.test']).toHaveProperty('clients') 47 | expect(subscriptions['test_api.test'].clients).toEqual(['test_client']) 48 | 49 | bus.removeClient('test_client') 50 | expect(subscriptions['test_api.test'].timer).toBeUndefined() 51 | expect(subscriptions['test_api.test'].clients).toEqual([]) 52 | }) 53 | -------------------------------------------------------------------------------- /packages/server/test/bus_unsubscribe.test.ts: -------------------------------------------------------------------------------- 1 | declare var beforeAll, it, expect 2 | 3 | import chalk from 'chalk' 4 | import { Socket } from 'socket.io' 5 | import Bus, { Subscription } from '../src/bus' 6 | import loggerMock from './logger' 7 | 8 | beforeAll(() => { 9 | chalk.enabled = false 10 | }) 11 | 12 | it('should warn if the client does not exist', () => { 13 | const logger = loggerMock() 14 | const bus = new Bus({ logger }) 15 | 16 | bus.unsubscribe('invalid', 'invalid') 17 | 18 | expect(logger.warn).toHaveBeenCalled() 19 | expect(logger.warn).toHaveBeenCalledWith( 20 | `unable to unsubscribe from 'invalid', client with id 'invalid' does not exist` 21 | ) 22 | }) 23 | 24 | it('should warn if the subscription does not exist', () => { 25 | const logger = loggerMock() 26 | const bus = new Bus({ logger }) 27 | 28 | bus.clients = { test_client: {} as Socket } 29 | bus.unsubscribe('test_client', 'invalid') 30 | 31 | expect(logger.warn).toHaveBeenCalled() 32 | expect(logger.warn).toHaveBeenCalledWith( 33 | `unable to unsubscribe from 'invalid', subscription does not exist` 34 | ) 35 | }) 36 | 37 | it('should remove client from subscription', () => { 38 | const logger = loggerMock() 39 | const bus = new Bus({ logger }) 40 | 41 | bus.clients = { test_client: {} as Socket } 42 | bus.subscriptions = { 43 | test_subscription: { 44 | clients: ['test_client', 'other_client'], 45 | } as Subscription, 46 | } 47 | bus.unsubscribe('test_client', 'test_subscription') 48 | 49 | const subscriptions = bus.listSubscriptions() 50 | expect(subscriptions).toHaveProperty('test_subscription') 51 | expect(subscriptions.test_subscription).toEqual({ 52 | clients: ['other_client'], 53 | }) 54 | }) 55 | 56 | it('should remove subscription if no more client left', () => { 57 | const logger = loggerMock() 58 | const bus = new Bus({ logger }) 59 | 60 | bus.clients = { test_client: {} as Socket } 61 | bus.subscriptions = { 62 | test_subscription: { 63 | clients: ['test_client'], 64 | timer: {} as NodeJS.Timer, 65 | } as Subscription, 66 | } 67 | 68 | bus.unsubscribe('test_client', 'test_subscription') 69 | 70 | const subscriptions = bus.listSubscriptions() 71 | expect(subscriptions).toEqual({}) 72 | }) 73 | -------------------------------------------------------------------------------- /packages/server/test/fixtures/config/invalid.yml: -------------------------------------------------------------------------------- 1 | - I'm an invalid yaml file 2 | for: testing purpose 3 | -------------------------------------------------------------------------------- /packages/server/test/fixtures/config/valid.yml: -------------------------------------------------------------------------------- 1 | I'm an valid yaml file for testing purpose 2 | -------------------------------------------------------------------------------- /packages/server/test/load_yaml.test.ts: -------------------------------------------------------------------------------- 1 | declare var jest, it, expect 2 | 3 | import * as path from 'path' 4 | import loadYaml from '../src/load_yaml' 5 | 6 | it('should reject when file does not exist', () => { 7 | expect.assertions(1) 8 | 9 | return loadYaml('noent.yml').catch(err => 10 | expect(err.message).toEqual(`ENOENT: no such file or directory, open 'noent.yml'`) 11 | ) 12 | }) 13 | 14 | it('should reject when file is not a valid yaml', () => { 15 | expect.assertions(1) 16 | 17 | return loadYaml(path.join(__dirname, 'fixtures', 'config', 'invalid.yml')).catch(err => 18 | expect(err.message).toContain( 19 | `end of the stream or a document separator is expected at line 2, column 2` 20 | ) 21 | ) 22 | }) 23 | 24 | it('should return parsed yaml', () => { 25 | expect.assertions(1) 26 | 27 | return loadYaml(path.join(__dirname, 'fixtures', 'config', 'valid.yml')).then(data => { 28 | expect(data).toEqual(`I'm an valid yaml file for testing purpose`) 29 | }) 30 | }) 31 | -------------------------------------------------------------------------------- /packages/server/test/logger.ts: -------------------------------------------------------------------------------- 1 | declare var jest 2 | 3 | import { LeveledLogMethod } from 'winston' 4 | 5 | export default () => ({ 6 | info: jest.fn() as LeveledLogMethod, 7 | warn: jest.fn() as LeveledLogMethod, 8 | error: jest.fn() as LeveledLogMethod, 9 | debug: jest.fn() as LeveledLogMethod, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": true, 6 | "moduleResolution": "node", 7 | "removeComments": true, 8 | "preserveConstEnums": true, 9 | "sourceMap": true, 10 | "outDir": "dist", 11 | "lib": [ 12 | "es6", 13 | "es2015", 14 | "es2016", 15 | "es2017" 16 | ], 17 | "baseUrl": ".", 18 | "paths": { 19 | "*": [ 20 | "node_modules/*", 21 | "src/types/*" 22 | ] 23 | } 24 | }, 25 | "include": [ 26 | "src/**/*" 27 | ] 28 | } -------------------------------------------------------------------------------- /packages/server/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "jsRules": {}, 4 | "rules": { 5 | "ordered-imports": false, 6 | "interface-name": false, 7 | "object-literal-sort-keys": false, 8 | "member-ordering": false, 9 | "curly": [true, "ignore-same-line"], 10 | "no-empty": [true, "allow-empty-functions"], 11 | "one-variable-per-declaration": false 12 | }, 13 | "rulesDirectory": [], 14 | "extends": [ 15 | "tslint:recommended", 16 | "tslint-config-prettier" 17 | ] 18 | } -------------------------------------------------------------------------------- /packages/themes/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@mozaik/babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/themes/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /es -------------------------------------------------------------------------------- /packages/themes/.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | .eslintrc 3 | .babelrc 4 | .gitignore 5 | .DS_Store 6 | /.nyc_output 7 | /coverage 8 | /test 9 | /build 10 | /src 11 | *.log* 12 | yarn.lock 13 | -------------------------------------------------------------------------------- /packages/themes/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) Raphaël Benitte 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /packages/themes/README.md: -------------------------------------------------------------------------------- 1 | ![MOZAÏK][logo] 2 | 3 | [![License][license-image]][license-url] 4 | [![NPM version][npm-image]][npm-url] 5 | ![themes count][themes-count-image] 6 | 7 | # Mozaïk themes 8 | 9 | Official themes for Mozaïk. 10 | 11 | **Themes:** 12 | 13 | - `night blue` 14 | - `snow` 15 | - `solarized dark` 16 | - `sunny` 17 | - `wine` 18 | 19 | [license-image]: https://img.shields.io/github/license/plouc/mozaik-themes.svg?style=flat-square 20 | [license-url]: https://github.com/plouc/mozaik-themes/blob/master/LICENSE.md 21 | [logo]: https://raw.githubusercontent.com/wiki/plouc/mozaik/assets/mozaik-logo-v2.png 22 | [themes-count-image]: https://img.shields.io/badge/themes-x5-green.svg?style=flat-square 23 | [npm-image]: https://img.shields.io/npm/v/mozaik-themes.svg?style=flat-square 24 | [npm-url]: https://www.npmjs.com/package/mozaik-themes 25 | -------------------------------------------------------------------------------- /packages/themes/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mozaik/themes", 3 | "version": "1.0.0-alpha.17", 4 | "description": "Mozaïk official themes", 5 | "repository": { 6 | "type": "git", 7 | "url": "git://github.com/plouc/mozaik" 8 | }, 9 | "author": { 10 | "name": "Raphaël Benitte", 11 | "url": "https://github.com/plouc" 12 | }, 13 | "license": "MIT", 14 | "main": "./lib/index.js", 15 | "module": "es/index.js", 16 | "jsnext:main": "es/index.js", 17 | "keywords": [ 18 | "mozaik", 19 | "theming", 20 | "themes", 21 | "components", 22 | "widget", 23 | "dashboard", 24 | "framework", 25 | "dataviz", 26 | "react" 27 | ], 28 | "devDependencies": { 29 | "@mozaik/babel-preset": "^1.0.0-alpha.6", 30 | "babel-cli": "^6.24.1", 31 | "cross-env": "^5.0.1" 32 | }, 33 | "scripts": { 34 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", 35 | "build:commonjs:watch": "npm run build:commonjs -- --watch", 36 | "build:es": "cross-env BABEL_ENV=es babel src --out-dir es", 37 | "build:es:watch": "npm run build:es -- --watch", 38 | "build": "npm run build:commonjs && npm run build:es", 39 | "version": "echo ${npm_package_version}", 40 | "prepublishOnly": "npm run build" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/themes/src/index.js: -------------------------------------------------------------------------------- 1 | export { default as miniTheme } from './mini' 2 | export { default as miniKuroTheme } from './mini-kuro' 3 | export { default as nightBlueTheme } from './night-blue' 4 | export { default as snowTheme } from './snow' 5 | export { default as solarizedDarkTheme } from './solarized-dark' 6 | export { default as wineTheme } from './wine' 7 | -------------------------------------------------------------------------------- /packages/themes/src/mini-kuro/charts.js: -------------------------------------------------------------------------------- 1 | export default { 2 | axis: { 3 | textColor: '#fff', 4 | tickColor: '#fff', 5 | legendColor: '#fff', 6 | }, 7 | grid: { 8 | stroke: '#222', 9 | }, 10 | colors: ['#66c2a5', '#fc8d62', '#8da0cb', '#e78ac3', '#a6d854'], 11 | tooltip: { 12 | background: '#000', 13 | color: '#fff', 14 | fontSize: '1.4vmin', 15 | borderRadius: 0, 16 | boxShadow: 'none', 17 | border: '1px solid #fff', 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /packages/themes/src/mini-kuro/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | text: '#fff', 3 | textHighlight: '#fff', 4 | icon: '#fff', 5 | unknown: '#c0ab7f', 6 | success: '#4eb6a3', 7 | warning: '#d1be65', 8 | failure: '#ff9176', 9 | } 10 | -------------------------------------------------------------------------------- /packages/themes/src/mini-kuro/typography.js: -------------------------------------------------------------------------------- 1 | export default { 2 | default: { 3 | default: { 4 | fontFamily: `'Roboto Mono', monospace`, 5 | fontSize: '1.8vmin', 6 | fontWeight: 400, 7 | lineHeight: '1.5em', 8 | }, 9 | strong: { 10 | fontWeight: 600, 11 | }, 12 | small: { 13 | fontSize: '1.6vmin', 14 | }, 15 | }, 16 | display: { 17 | default: { 18 | fontFamily: `'Roboto Mono', monospace`, 19 | fontSize: '1.8vmin', 20 | fontWeight: 700, 21 | lineHeight: '1.5em', 22 | }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /packages/themes/src/mini/charts.js: -------------------------------------------------------------------------------- 1 | export default { 2 | axis: { 3 | textColor: '#000', 4 | tickColor: '#000', 5 | legendColor: '#000', 6 | }, 7 | grid: { 8 | stroke: '#eee', 9 | }, 10 | colors: ['#b3e2cd', '#fdcdac', '#cbd5e8', '#f4cae4', '#e6f5c9'], 11 | tooltip: { 12 | background: '#fff', 13 | color: '#000', 14 | fontSize: '1.4vmin', 15 | borderRadius: 0, 16 | boxShadow: 'none', 17 | border: '1px solid #000', 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /packages/themes/src/mini/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | text: '#000', 3 | textHighlight: '#000', 4 | icon: '#000', 5 | unknown: '#c0ab7f', 6 | success: '#4eb6a3', 7 | warning: '#d1be65', 8 | failure: '#ff9176', 9 | } 10 | -------------------------------------------------------------------------------- /packages/themes/src/mini/typography.js: -------------------------------------------------------------------------------- 1 | export default { 2 | default: { 3 | default: { 4 | fontFamily: `'Roboto Mono', monospace`, 5 | fontSize: '1.8vmin', 6 | fontWeight: 400, 7 | lineHeight: '1.5em', 8 | }, 9 | strong: { 10 | fontWeight: 600, 11 | }, 12 | small: { 13 | fontSize: '1.6vmin', 14 | }, 15 | }, 16 | display: { 17 | default: { 18 | fontFamily: `'Roboto Mono', monospace`, 19 | fontSize: '1.8vmin', 20 | fontWeight: 700, 21 | lineHeight: '1.5em', 22 | }, 23 | }, 24 | } 25 | -------------------------------------------------------------------------------- /packages/themes/src/night-blue/charts.js: -------------------------------------------------------------------------------- 1 | import colors from './colors' 2 | 3 | export default { 4 | axis: { 5 | textColor: colors.text, 6 | tickColor: colors.text, 7 | legendColor: colors.text, 8 | }, 9 | grid: { 10 | stroke: '#25303d', 11 | }, 12 | colors: ['#FDECA4', '#cbb04a', '#D68649', '#648e9c', '#253445'], 13 | tooltip: { 14 | background: '#323f53', 15 | color: '#f6ecd0', 16 | fontSize: '1.4vmin', 17 | borderRadius: '2px', 18 | boxShadow: '0 1px 3px rgba(0, 0, 0, 0.5)', 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /packages/themes/src/night-blue/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | text: '#eedba5', 3 | textHighlight: '#f6ecd0', 4 | background: '#1e2430', 5 | icon: '#e0c671', 6 | unknown: '#495b71', 7 | success: '#4ec2b4', 8 | warning: '#d1be65', 9 | failure: '#de5029', 10 | } 11 | -------------------------------------------------------------------------------- /packages/themes/src/night-blue/typography.js: -------------------------------------------------------------------------------- 1 | export default { 2 | default: { 3 | default: { 4 | fontFamily: `'Raleway', sans-serif`, 5 | fontSize: '1.8vmin', 6 | fontWeight: 400, 7 | lineHeight: '1.5em', 8 | }, 9 | strong: { 10 | fontWeight: 600, 11 | }, 12 | small: { 13 | fontSize: '1.6vmin', 14 | }, 15 | }, 16 | display: { 17 | default: { 18 | fontFamily: `'Montserrat', sans-serif`, 19 | fontSize: '1.8vmin', 20 | lineHeight: '1.4em', 21 | }, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /packages/themes/src/snow/charts.js: -------------------------------------------------------------------------------- 1 | import colors from './colors' 2 | 3 | export default { 4 | axis: { 5 | textColor: colors.text, 6 | tickColor: '#bbb', 7 | legendColor: colors.text, 8 | }, 9 | grid: { 10 | stroke: '#eee', 11 | }, 12 | colors: ['#82c2de', '#ade0de', '#dad7a3', '#b3deff', '#baab89'], 13 | tooltip: { 14 | background: '#fff', 15 | color: colors.text, 16 | fontSize: '1.4vmin', 17 | borderRadius: '2px', 18 | boxShadow: '0 5px 9px rgba(0, 0, 0, 0.15)', 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /packages/themes/src/snow/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | text: '#333', 3 | background: '#ebf0f1', 4 | icon: '#0badc2', 5 | unknown: '#d3dfe8', 6 | success: '#8ddb8d', 7 | warning: '#d1be65', 8 | failure: '#e37856', 9 | } 10 | -------------------------------------------------------------------------------- /packages/themes/src/snow/typography.js: -------------------------------------------------------------------------------- 1 | export default { 2 | default: { 3 | default: { 4 | fontFamily: `'Open sans', sans-serif`, 5 | fontSize: '1.6vmin', 6 | fontWeight: 400, 7 | lineHeight: '3vmin', 8 | }, 9 | strong: { 10 | fontWeight: 600, 11 | }, 12 | small: { 13 | fontSize: '1.4vmin', 14 | }, 15 | }, 16 | display: { 17 | default: { 18 | fontFamily: `'Montserrat', sans-serif`, 19 | fontSize: '2vmin', 20 | }, 21 | }, 22 | } 23 | -------------------------------------------------------------------------------- /packages/themes/src/solarized-dark/charts.js: -------------------------------------------------------------------------------- 1 | import colors from './colors' 2 | 3 | export default { 4 | axis: { 5 | textColor: colors.text, 6 | tickColor: colors.text, 7 | legendColor: colors.text, 8 | }, 9 | grid: { 10 | stroke: '#073642', 11 | }, 12 | colors: ['#00b2b0', '#24dead', '#d9d356', '#f1c4c2', '#dfbbe8'], 13 | tooltip: { 14 | background: '#073c49', 15 | color: '#bed0d2', 16 | fontSize: '1.4vmin', 17 | borderRadius: 0, 18 | boxShadow: '0 1px 3px rgba(0, 0, 0, 0.5)', 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /packages/themes/src/solarized-dark/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | text: '#90a2a4', 3 | textHighlight: '#eee8d5', 4 | background: '#073642', 5 | icon: '#0badc2', 6 | unknown: '#495b71', 7 | success: '#859900', 8 | warning: '#b58900', 9 | failure: '#dc322f', 10 | } 11 | -------------------------------------------------------------------------------- /packages/themes/src/solarized-dark/index.js: -------------------------------------------------------------------------------- 1 | import colors from './colors' 2 | import charts from './charts' 3 | import typography from './typography' 4 | 5 | export default { 6 | name: 'solarized dark', 7 | typography, 8 | colors, 9 | root: { 10 | fontFamily: `'Space Mono', Consolas, monospace`, 11 | background: colors.background, 12 | fontSize: '1.6vmin', 13 | lineHeight: '2.8vmin', 14 | extend: ` 15 | @import url('https://fonts.googleapis.com/css?family=Space+Mono'); 16 | 17 | & a { 18 | text-decoration: underline; 19 | } 20 | `, 21 | }, 22 | dashboard: { 23 | header: { 24 | background: '#002b36', 25 | title: { 26 | fontSize: '1.8vmin', 27 | color: '#eee8d5', 28 | }, 29 | }, 30 | player: { 31 | slash: { 32 | color: '#0badc2', 33 | margin: '0 0.8vmin', 34 | }, 35 | }, 36 | }, 37 | widget: { 38 | background: '#002b36', 39 | wrapper: { 40 | padding: '0.6vmin', 41 | }, 42 | header: { 43 | height: '5vmin', 44 | subject: {}, 45 | count: { 46 | color: '#eee8d5', 47 | }, 48 | icon: { 49 | fontSize: '2vmin', 50 | color: '#0badc2', 51 | }, 52 | }, 53 | }, 54 | notifications: { 55 | item: { 56 | padding: '1.2vmin 2vmin', 57 | background: colors.background, 58 | color: '#b3c5c7', 59 | extend: ` 60 | box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25); 61 | `, 62 | }, 63 | }, 64 | label: { 65 | background: colors.background, 66 | extend: ` 67 | border-radius: 2px; 68 | `, 69 | main: { 70 | color: '#839496', 71 | }, 72 | addon: { 73 | background: '#00242f', 74 | color: '#93a1a1', 75 | }, 76 | }, 77 | list: { 78 | item: { 79 | hover: { 80 | background: '#002834', 81 | }, 82 | meta: { 83 | fontSize: '1.4vmin', 84 | }, 85 | }, 86 | }, 87 | charts, 88 | } 89 | -------------------------------------------------------------------------------- /packages/themes/src/solarized-dark/typography.js: -------------------------------------------------------------------------------- 1 | export default { 2 | default: { 3 | default: { 4 | fontFamily: `'Space Mono', Consolas, monospace`, 5 | fontSize: '1.8vmin', 6 | fontWeight: 400, 7 | lineHeight: '1.5em', 8 | }, 9 | strong: { 10 | fontWeight: 600, 11 | }, 12 | small: { 13 | fontSize: '1.6vmin', 14 | }, 15 | }, 16 | display: { 17 | default: { 18 | fontFamily: `'Space Mono', Consolas, monospace`, 19 | fontSize: '1.8vmin', 20 | lineHeight: '1.4em', 21 | }, 22 | }, 23 | mono: { 24 | default: { 25 | fontFamily: `'Space Mono', Consolas, monospace`, 26 | fontSize: '1.8vmin', 27 | lineHeight: '1.4em', 28 | }, 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /packages/themes/src/wine/charts.js: -------------------------------------------------------------------------------- 1 | import colors from './colors' 2 | 3 | export default { 4 | axis: { 5 | textColor: colors.text, 6 | tickColor: colors.text, 7 | legendColor: colors.text, 8 | }, 9 | grid: { 10 | stroke: '#321515', 11 | }, 12 | colors: ['#a52818', '#e46a55', '#bd532e', '#9b2c13', '#E43D24'], 13 | tooltip: { 14 | background: '#501919', 15 | color: 'hsl(10, 60%, 90%)', 16 | fontSize: '1.4vmin', 17 | borderRadius: 0, 18 | boxShadow: '0 2px 5px rgba(0, 0, 0, 0.6)', 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /packages/themes/src/wine/colors.js: -------------------------------------------------------------------------------- 1 | export default { 2 | text: 'hsl(6, 26%, 67%)', 3 | textHighlight: '#fff', 4 | background: '#281212', 5 | icon: 'hsl(0, 52%, 60%)', 6 | unknown: '#7e706d', 7 | success: '#50a3b2', 8 | warning: '#b87334', 9 | failure: '#a31c12', 10 | } 11 | -------------------------------------------------------------------------------- /packages/themes/src/wine/index.js: -------------------------------------------------------------------------------- 1 | import colors from './colors' 2 | import charts from './charts' 3 | import typography from './typography' 4 | 5 | export default { 6 | name: 'wine', 7 | typography, 8 | colors, 9 | root: { 10 | background: colors.background, 11 | extend: ` 12 | @import url("https://fonts.googleapis.com/css?family=Roboto+Slab:100,300|Open+Sans:400italic,400,300,600,700"); 13 | & a { 14 | color: hsl(10, 60%, 90%); 15 | } 16 | `, 17 | }, 18 | dashboard: { 19 | header: { 20 | background: 'rgb(69, 23, 23)', 21 | }, 22 | player: { 23 | slash: { 24 | color: 'hsl(0, 52%, 60%)', 25 | }, 26 | }, 27 | }, 28 | widget: { 29 | background: 'rgb(69, 23, 23)', 30 | wrapper: { 31 | padding: '0.3vmin', 32 | }, 33 | header: { 34 | fontSize: '2.2vmin', 35 | extend: ` 36 | border-bottom: 1px solid rgb(40, 18, 18); 37 | `, 38 | subject: {}, 39 | count: { 40 | color: 'hsl(0, 52%, 60%)', 41 | fontSize: '1.8vmin', 42 | extend: ` 43 | background-color: rgb(40, 18, 18); 44 | border-radius: 2px; 45 | padding: 0.6vmin 1vmin; 46 | `, 47 | }, 48 | icon: { 49 | fontSize: '2.6vmin', 50 | color: 'hsl(0, 52%, 60%)', 51 | }, 52 | }, 53 | }, 54 | notifications: { 55 | item: { 56 | background: 'rgb(87, 25, 25)', 57 | color: '#fff', 58 | extend: ` 59 | box-shadow: 0 1px 2px rgba(0, 0, 0, .75); 60 | font-size: 1.6vmin; 61 | `, 62 | }, 63 | }, 64 | label: { 65 | extend: ` 66 | border-radius: 2px; 67 | `, 68 | main: { 69 | background: '#561d1d', 70 | color: 'hsl(10, 60%, 90%)', 71 | }, 72 | addon: { 73 | background: 'rgb(55, 21, 21)', 74 | color: 'hsl(0, 60%, 63%)', 75 | }, 76 | }, 77 | list: { 78 | item: { 79 | extend: ` 80 | border-bottom: 1px solid rgb(52, 20, 20); 81 | &:last-child { 82 | border-bottom: 0; 83 | } 84 | `, 85 | hover: { 86 | background: 'rgb(82, 25, 25)', 87 | }, 88 | meta: { 89 | color: 'hsl(0, 52%, 60%)', 90 | fontSize: '1.6vmin', 91 | }, 92 | }, 93 | }, 94 | charts, 95 | } 96 | -------------------------------------------------------------------------------- /packages/themes/src/wine/typography.js: -------------------------------------------------------------------------------- 1 | export default { 2 | default: { 3 | default: { 4 | fontFamily: `'Open sans', sans-serif`, 5 | fontSize: '1.8vmin', 6 | fontWeight: 400, 7 | lineHeight: '1.5em', 8 | }, 9 | strong: { 10 | fontWeight: 600, 11 | }, 12 | small: { 13 | fontSize: '1.6vmin', 14 | }, 15 | }, 16 | display: { 17 | default: { 18 | fontFamily: `'Roboto Slab', sans-serif`, 19 | fontSize: '1.8vmin', 20 | lineHeight: '1.5em', 21 | }, 22 | }, 23 | mono: { 24 | default: { 25 | fontFamily: `'Space Mono', Consolas, monospace`, 26 | fontSize: '1.8vmin', 27 | lineHeight: '1.4em', 28 | }, 29 | }, 30 | } 31 | -------------------------------------------------------------------------------- /packages/ui/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@mozaik/babel-preset"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/ui/.gitignore: -------------------------------------------------------------------------------- 1 | /lib 2 | /es 3 | -------------------------------------------------------------------------------- /packages/ui/.npmignore: -------------------------------------------------------------------------------- 1 | .travis.yml 2 | .eslintrc 3 | .babelrc 4 | .gitignore 5 | .DS_Store 6 | /.nyc_output 7 | /coverage 8 | /test 9 | /build 10 | /src 11 | *.log* 12 | yarn.lock 13 | /stories 14 | /.storybook 15 | -------------------------------------------------------------------------------- /packages/ui/.storybook/addons.js: -------------------------------------------------------------------------------- 1 | import '@storybook/addon-knobs/register' 2 | import '@storybook/addon-options/register' 3 | -------------------------------------------------------------------------------- /packages/ui/.storybook/config.js: -------------------------------------------------------------------------------- 1 | import { configure, addDecorator } from '@storybook/react' 2 | import { withKnobs } from '@storybook/addon-knobs' 3 | import { setOptions } from '@storybook/addon-options' 4 | 5 | setOptions({ 6 | name: '@mozaik/ui', 7 | addonPanelInRight: true, 8 | }) 9 | 10 | addDecorator(withKnobs) 11 | 12 | function loadStories() { 13 | require('../stories/text.js') 14 | require('../stories/widget_header') 15 | require('../stories/widget_label') 16 | require('../stories/widget_list_item') 17 | require('../stories/widget_counter') 18 | require('../stories/widget_status_badge') 19 | } 20 | 21 | configure(loadStories, module) 22 | -------------------------------------------------------------------------------- /packages/ui/README.md: -------------------------------------------------------------------------------- 1 | ![MOZAÏK][logo] 2 | 3 | [![License][license-image]][license-url] 4 | [![NPM version][npm-image]][npm-url] 5 | 6 | # Mozaïk UI 7 | 8 | [logo]: https://raw.githubusercontent.com/wiki/plouc/mozaik/assets/mozaik-logo-v2.png 9 | [license-image]: https://img.shields.io/github/license/plouc/mozaik.svg?style=flat-square 10 | [license-url]: https://github.com/plouc/mozaik/blob/master/LICENSE.md 11 | [npm-image]: https://img.shields.io/npm/v/@mozaik/ui.svg?style=flat-square 12 | [npm-url]: https://www.npmjs.com/package/@mozaik/ui 13 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mozaik/ui", 3 | "description": "Mozaïk UI", 4 | "version": "2.0.0-rc.2", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/plouc/mozaik" 9 | }, 10 | "author": { 11 | "name": "Raphaël Benitte", 12 | "url": "https://github.com/plouc" 13 | }, 14 | "main": "./lib/index.js", 15 | "module": "es/index.js", 16 | "jsnext:main": "es/index.js", 17 | "dependencies": { 18 | "immutable": "^3.8.1", 19 | "lodash": "^4.17.4", 20 | "lodash-es": "^4.17.4", 21 | "prop-types": "^15.5.10", 22 | "react-feather": "^1.1.1", 23 | "react-redux": "^5.0.5", 24 | "react-svg-buttons": "^0.4.0", 25 | "redux": "^3.7.2", 26 | "redux-thunk": "^2.2.0", 27 | "socket.io-client": "^2.1.1", 28 | "styled-components": "^2.1.1" 29 | }, 30 | "peerDependencies": { 31 | "react": "^16.4.0" 32 | }, 33 | "devDependencies": { 34 | "@mozaik/babel-preset": "^1.0.0-alpha.6", 35 | "@mozaik/themes": "1.0.0-alpha.17", 36 | "@storybook/addon-knobs": "^3.4.8", 37 | "@storybook/addon-options": "^3.4.8", 38 | "@storybook/react": "^3.4.8", 39 | "babel-cli": "^6.24.1", 40 | "babel-core": "^6.26.3", 41 | "babel-jest": "^20.0.3", 42 | "cross-env": "^5.0.1", 43 | "jest": "^20.0.4", 44 | "react": "^16.4.0", 45 | "react-dom": "^16.4.0" 46 | }, 47 | "scripts": { 48 | "build:commonjs": "cross-env BABEL_ENV=commonjs babel src --out-dir lib", 49 | "build:commonjs:watch": "npm run build:commonjs -- --watch", 50 | "build:es": "cross-env BABEL_ENV=es babel src --out-dir es", 51 | "build:es:watch": "npm run build:es -- --watch", 52 | "build": "npm run build:commonjs && npm run build:es", 53 | "version": "echo ${npm_package_version}", 54 | "prepublishOnly": "npm run build", 55 | "test": "jest", 56 | "storybook": "start-storybook -p 9001 -c .storybook" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/ui/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Provider } from 'react-redux' 3 | import Mozaik from './containers/MozaikContainer' 4 | import configureStore from './configureStore' 5 | import ThemeProvider from './components/ThemeProvider' 6 | import ThemeManager from './theming/ThemeManager' 7 | 8 | const MozaikWrapper = () => { 9 | const store = configureStore({ 10 | themes: { 11 | themes: ThemeManager.listThemes(), 12 | current: ThemeManager.defaultTheme, 13 | }, 14 | }) 15 | 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | ) 23 | } 24 | 25 | export default MozaikWrapper 26 | -------------------------------------------------------------------------------- /packages/ui/src/WidgetsRegistry.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | 3 | const registry = {} 4 | 5 | const WidgetsRegistry = { 6 | /** 7 | * Register multiple extensions components. 8 | * 9 | * @param {Object} extensions 10 | */ 11 | addExtensions(extensions) { 12 | _.forOwn(extensions, (components, extensionId) => { 13 | WidgetsRegistry.addExtension(extensionId, components) 14 | }) 15 | 16 | return WidgetsRegistry 17 | }, 18 | 19 | /** 20 | * Register an extension components. 21 | * 22 | * @param {String} extensionId 23 | * @param {Object} components 24 | * @returns {WidgetsRegistry} 25 | */ 26 | addExtension(extension, components) { 27 | _.forOwn(components, (component, widget) => { 28 | WidgetsRegistry.add(extension, widget, component) 29 | }) 30 | 31 | return WidgetsRegistry 32 | }, 33 | 34 | /** 35 | * 36 | * @param {string} extension 37 | * @param {string} widget 38 | * @param {Function} component 39 | * @returns {WidgetsRegistry} 40 | */ 41 | add(extension, widget, component) { 42 | if (!registry.hasOwnProperty(extension)) { 43 | registry[extension] = {} 44 | } 45 | registry[extension][widget] = component 46 | 47 | return WidgetsRegistry 48 | }, 49 | 50 | /** 51 | * 52 | * @param {string} extension 53 | * @param {string} widget 54 | * @return {boolean} 55 | */ 56 | has(extension, widget) { 57 | return registry.hasOwnProperty(extension) && registry[extension].hasOwnProperty(widget) 58 | }, 59 | 60 | /** 61 | * @param {string} extension 62 | * @param {string} widget 63 | * @return {Function} 64 | */ 65 | getComponent(extension, widget) { 66 | if (!WidgetsRegistry.has(extension, widget)) { 67 | throw new Error(`No widget "${widget}" defined for extension "${extension}"`) 68 | } 69 | 70 | return registry[extension][widget] 71 | }, 72 | 73 | widgetsCount() { 74 | let count = 0 75 | for (let ext in registry) { 76 | count += Object.keys(registry[ext]).length 77 | } 78 | 79 | return count 80 | }, 81 | 82 | /** 83 | * @return {Object} 84 | */ 85 | list() { 86 | return registry 87 | }, 88 | } 89 | 90 | export default WidgetsRegistry 91 | -------------------------------------------------------------------------------- /packages/ui/src/actions/apiActions.js: -------------------------------------------------------------------------------- 1 | import { send } from './wsActions' 2 | 3 | export const API_SUBSCRIBE = 'API_SUBSCRIBE' 4 | export const API_SUBSCRIBED = 'API_SUBSCRIBED' 5 | export const API_UNSUBSCRIBE = 'API_UNSUBSCRIBE' 6 | export const API_ALL_UNSUBSCRIBED = 'API_ALL_UNSUBSCRIBED' 7 | export const API_DATA = 'API_DATA' 8 | export const API_FAILURE = 'API_FAILURE' 9 | 10 | export const subscribedToApi = subscription => ({ 11 | type: API_SUBSCRIBED, 12 | subscription, 13 | }) 14 | 15 | export const allSubscriptionsUnsubscribed = () => ({ 16 | type: API_ALL_UNSUBSCRIBED, 17 | }) 18 | 19 | export const subscribeToApi = subscription => { 20 | return (dispatch, getState) => { 21 | const { api, ws } = getState() 22 | 23 | if (!api.get('subscriptions').has(subscription.id)) { 24 | dispatch({ type: API_SUBSCRIBE, subscription }) 25 | 26 | if (ws.connected !== true) return 27 | 28 | dispatch(send('api.subscription', subscription)) 29 | dispatch(subscribedToApi(subscription)) 30 | } 31 | } 32 | } 33 | 34 | export const sendPendingSubscriptions = () => (dispatch, getState) => { 35 | const { api, ws } = getState() 36 | 37 | if (ws.connected !== true) { 38 | // eslint-disable-next-line no-console 39 | console.error(`Cannot send pending subscriptions as ws is disconnected!`) 40 | return 41 | } 42 | 43 | api.get('subscriptions') 44 | .filter(s => !s.get('hasSubscribed')) 45 | .forEach(sub => { 46 | const subscription = sub.toJS() 47 | dispatch(send('api.subscription', subscription)) 48 | dispatch(subscribedToApi(subscription)) 49 | }) 50 | } 51 | 52 | export const unsubscribeFromApi = id => { 53 | return (dispatch, getState) => { 54 | const { api } = getState() 55 | if (api.get('subscriptions').has(id)) { 56 | dispatch(send('api.unsubscription', { id })) 57 | } 58 | 59 | dispatch({ 60 | type: API_UNSUBSCRIBE, 61 | id, 62 | }) 63 | } 64 | } 65 | 66 | export const receiveApiData = ({ id, data }) => ({ 67 | type: API_DATA, 68 | id, 69 | data, 70 | }) 71 | 72 | export const apiFailure = ({ id, data }) => ({ 73 | type: API_FAILURE, 74 | id, 75 | data, 76 | }) 77 | -------------------------------------------------------------------------------- /packages/ui/src/actions/configurationActions.js: -------------------------------------------------------------------------------- 1 | import { connect } from './wsActions' 2 | import { setDashboards, play } from './dashboardsActions' 3 | import { notifySuccess, notifyError } from './notificationsActions' 4 | 5 | export const FETCH_CONFIGURATION = 'FETCH_CONFIGURATION' 6 | export const FETCH_CONFIGURATION_SUCCESS = 'FETCH_CONFIGURATION_SUCCESS' 7 | export const FETCH_CONFIGURATION_FAILURE = 'FETCH_CONFIGURATION_FAILURE' 8 | 9 | export const fetchConfigurationSuccess = configuration => ({ 10 | type: FETCH_CONFIGURATION_SUCCESS, 11 | configuration, 12 | }) 13 | 14 | const fetchConfigurationFailure = error => ({ 15 | type: FETCH_CONFIGURATION_FAILURE, 16 | error, 17 | }) 18 | 19 | export const fetchConfiguration = () => { 20 | return dispatch => { 21 | dispatch({ type: FETCH_CONFIGURATION }) 22 | 23 | return fetch('/config') 24 | .then(res => { 25 | if (res.status !== 200) { 26 | return Promise.reject( 27 | new Error( 28 | `Unable to fetch configuration: ${res.statusText} (${res.status})` 29 | ) 30 | ) 31 | } 32 | 33 | return res.json() 34 | }) 35 | .then(configuration => { 36 | dispatch(fetchConfigurationSuccess(configuration)) 37 | dispatch(connect(configuration)) 38 | dispatch( 39 | notifySuccess({ 40 | message: 'configuration loaded', 41 | ttl: 2000, 42 | }) 43 | ) 44 | dispatch(setDashboards(configuration.dashboards)) 45 | dispatch(play()) 46 | }) 47 | .catch(err => { 48 | dispatch( 49 | notifyError({ 50 | message: `An error occurred while fetching configuration: ${err.message}`, 51 | ttl: -1, 52 | }) 53 | ) 54 | dispatch(fetchConfigurationFailure(err.message)) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/ui/src/actions/themesActions.js: -------------------------------------------------------------------------------- 1 | export const THEME_SET = 'THEME_SET' 2 | 3 | export const setTheme = theme => ({ 4 | type: THEME_SET, 5 | theme, 6 | }) 7 | -------------------------------------------------------------------------------- /packages/ui/src/components/ExternalLink.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | export default class ExternalLink extends Component { 5 | static propTypes = { 6 | children: PropTypes.node.isRequired, 7 | } 8 | 9 | render() { 10 | const { children, ...rest } = this.props 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/ui/src/components/Text.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import typography from '../theming/typography' 5 | 6 | export default class Text extends Component { 7 | static propTypes = { 8 | tag: PropTypes.string.isRequired, 9 | type: PropTypes.oneOf(['default', 'display', 'mono']).isRequired, 10 | variant: PropTypes.oneOf(['strong', 'small']).isRequired, 11 | children: PropTypes.node.isRequired, 12 | } 13 | 14 | static defaultProps = { 15 | tag: 'span', 16 | type: 'default', 17 | variant: 'default', 18 | } 19 | 20 | render() { 21 | const { tag, type, variant, children, ...rest } = this.props 22 | 23 | const StyledText = styled(tag)` 24 | ${props => typography(props.theme, props.type, props.variant)}; 25 | ` 26 | 27 | return ( 28 | 29 | {children} 30 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/ui/src/components/ThemeProvider.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux' 4 | import { ThemeProvider as Provider } from 'styled-components' 5 | 6 | class ThemeProvider extends Component { 7 | static propTypes = { 8 | themes: PropTypes.object.isRequired, 9 | current: PropTypes.string.isRequired, 10 | children: PropTypes.element, 11 | } 12 | 13 | render() { 14 | const { themes, current } = this.props 15 | 16 | let theme = {} 17 | if (themes.hasOwnProperty(current)) { 18 | theme = themes[current] 19 | } 20 | 21 | return {this.props.children} 22 | } 23 | } 24 | 25 | const mapStateToProps = ({ themes: { themes, current } }) => { 26 | return { themes, current } 27 | } 28 | 29 | export default connect(mapStateToProps)(ThemeProvider) 30 | -------------------------------------------------------------------------------- /packages/ui/src/components/TrapApiError.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | export default class TrapApiError extends Component { 5 | static propTypes = { 6 | error: PropTypes.object, 7 | children: PropTypes.node, 8 | } 9 | 10 | render() { 11 | const { error, children } = this.props 12 | 13 | if (error) { 14 | return
{error.message}
15 | } 16 | 17 | return children 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/ui/src/components/dashboard/DashboardTitle.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { TransitionMotion, spring } from 'react-motion' 4 | import styled from 'styled-components' 5 | 6 | const Title = styled.div` 7 | height: 6vmin; 8 | position: absolute; 9 | top: 0; 10 | left: 0; 11 | display: flex; 12 | align-items: center; 13 | white-space: pre; 14 | font-family: ${props => props.theme.dashboard.header.title.fontFamily}; 15 | font-size: ${props => props.theme.dashboard.header.title.fontSize}; 16 | text-transform: ${props => props.theme.dashboard.header.title.textTransform}; 17 | color: ${props => props.theme.dashboard.header.title.color}; 18 | ${props => props.theme.dashboard.header.title.extend.trim()}; 19 | ` 20 | 21 | const willEnter = () => ({ x: 30, opacity: 0 }) 22 | const willLeave = () => ({ 23 | x: spring(0, { stiffness: 150, damping: 15 }), 24 | opacity: spring(0, { stiffness: 150, damping: 15 }), 25 | }) 26 | 27 | export default class DashboardTitle extends Component { 28 | static propTypes = { 29 | currentDashboardIndex: PropTypes.number.isRequired, 30 | title: PropTypes.string, 31 | } 32 | 33 | render() { 34 | const { currentDashboardIndex, title } = this.props 35 | 36 | const items = [{ key: currentDashboardIndex, title }] 37 | 38 | return ( 39 | ({ 43 | key: `${item.key}`, 44 | data: item.title, 45 | style: { 46 | x: spring(0, { stiffness: 60, damping: 15 }), 47 | opacity: spring(1, { stiffness: 60, damping: 15 }), 48 | }, 49 | }))} 50 | > 51 | {styles => ( 52 |
53 | {styles.map(({ key, data, style }) => { 54 | return ( 55 | 63 | {data} 64 | 65 | ) 66 | })} 67 |
68 | )} 69 |
70 | ) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /packages/ui/src/components/icons.js: -------------------------------------------------------------------------------- 1 | export { default as ActivityIcon } from 'react-feather/dist/icons/activity' 2 | export { default as GitlabIcon } from 'react-feather/dist/icons/gitlab' 3 | export { default as BarChartIcon } from 'react-feather/dist/icons/bar-chart' 4 | export { default as UsersIcon } from 'react-feather/dist/icons/users' 5 | export { default as GitBranchIcon } from 'react-feather/dist/icons/git-branch' 6 | export { default as ClockIcon } from 'react-feather/dist/icons/clock' 7 | export { default as LockIcon } from 'react-feather/dist/icons/lock' 8 | export { default as UnlockIcon } from 'react-feather/dist/icons/unlock' 9 | export { default as StarIcon } from 'react-feather/dist/icons/star' 10 | export { default as TagIcon } from 'react-feather/dist/icons/tag' 11 | export { default as GridIcon } from 'react-feather/dist/icons/grid' 12 | export { default as InfoIcon } from 'react-feather/dist/icons/info' 13 | export { default as PackageIcon } from 'react-feather/dist/icons/package' 14 | export { default as HelpIcon } from 'react-feather/dist/icons/help-circle' 15 | export { default as CheckCircleIcon } from 'react-feather/dist/icons/check-circle' 16 | export { default as AlertTriangleIcon } from 'react-feather/dist/icons/alert-triangle' 17 | export { default as AlertCircleIcon } from 'react-feather/dist/icons/alert-circle' 18 | export { default as SlidersIcon } from 'react-feather/dist/icons/sliders' 19 | export { default as FolderIcon } from 'react-feather/dist/icons/folder' 20 | export { default as LoaderIcon } from 'react-feather/dist/icons/loader' 21 | export { default as CalendarIcon } from 'react-feather/dist/icons/calendar' 22 | export { default as GitCommitIcon } from 'react-feather/dist/icons/git-commit' 23 | export { default as PauseCircleIcon } from 'react-feather/dist/icons/pause-circle' 24 | export { default as FastForwardIcon } from 'react-feather/dist/icons/fast-forward' 25 | export { default as GithubIcon } from 'react-feather/dist/icons/github' 26 | export { default as SlashIcon } from 'react-feather/dist/icons/slash' 27 | export { default as PlayCircleIcon } from 'react-feather/dist/icons/play-circle' 28 | export { default as CodeIcon } from 'react-feather/dist/icons/code' 29 | export { default as HashIcon } from 'react-feather/dist/icons/hash' 30 | export { default as PieChartIcon } from 'react-feather/dist/icons/pie-chart' 31 | -------------------------------------------------------------------------------- /packages/ui/src/components/notifications/Notifications.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import NotificationsItem from './NotificationsItem' 4 | import styled from 'styled-components' 5 | 6 | const Wrapper = styled.div` 7 | position: absolute; 8 | z-index: 100000; 9 | width: 25%; 10 | top: 8vmin; 11 | right: 2vmin; 12 | ` 13 | 14 | export default class Notifications extends Component { 15 | static propTypes = { 16 | notifications: PropTypes.array.isRequired, 17 | } 18 | 19 | render() { 20 | const { notifications } = this.props 21 | 22 | return ( 23 | 24 | {notifications.map(notification => ( 25 | 29 | ))} 30 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/ui/src/components/notifications/NotificationsItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import styled from 'styled-components' 5 | import typography from '../../theming/typography' 6 | 7 | const Item = styled.div` 8 | position: relative; 9 | margin-bottom: 1.4vmin; 10 | padding: ${props => props.theme.notifications.item.padding}; 11 | background: ${props => props.theme.notifications.item.background}; 12 | color: ${props => props.theme.notifications.item.color}; 13 | ${props => props.theme.notifications.item.extend.trim()}; 14 | ${props => typography(props.theme)}; 15 | ` 16 | 17 | export default class NotificationsItem extends Component { 18 | static propTypes = { 19 | notification: PropTypes.object.isRequired, 20 | } 21 | 22 | render() { 23 | const { notification } = this.props 24 | 25 | let content 26 | if (notification.component) { 27 | content = React.createElement( 28 | notification.component, 29 | _.assign({}, notification.props, { 30 | notificationId: notification.id, 31 | }) 32 | ) 33 | } else { 34 | content = notification.message 35 | } 36 | 37 | return {content} 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/ui/src/components/settings/ThemesSetting.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { withTheme } from 'styled-components' 4 | import { MorphIcon } from 'react-svg-buttons' 5 | import Widget from '../widget/Widget' 6 | import WidgetHeader from '../widget/WidgetHeader' 7 | import WidgetBody from '../widget/WidgetBody' 8 | import WidgetListItem from '../widget/list/WidgetListItem' 9 | import { SlidersIcon } from '../icons' 10 | 11 | class ThemeSetting extends Component { 12 | static propTypes = { 13 | themes: PropTypes.object.isRequired, 14 | theme: PropTypes.object.isRequired, 15 | currentTheme: PropTypes.string.isRequired, 16 | setTheme: PropTypes.func.isRequired, 17 | } 18 | 19 | shouldComponentUpdate(nextProps) { 20 | return this.props.currentTheme !== nextProps.currentTheme 21 | } 22 | 23 | render() { 24 | const { themes, currentTheme, setTheme, theme } = this.props 25 | 26 | const themeIds = Object.keys(themes) 27 | 28 | return ( 29 | 30 | 31 | 32 | {themeIds.map(t => { 33 | let icon = 'cross' 34 | if (t === currentTheme) { 35 | icon = 'check' 36 | } 37 | 38 | return ( 39 | { 42 | setTheme(t) 43 | }} 44 | title={t} 45 | style={{ cursor: 'pointer' }} 46 | pre={} 47 | /> 48 | ) 49 | })} 50 | 51 | 52 | ) 53 | } 54 | } 55 | 56 | export default withTheme(ThemeSetting) 57 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/UnknowWidgetTypeError.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import Widget from './Widget' 4 | import WidgetHeader from './WidgetHeader' 5 | import WidgetBody from './WidgetBody' 6 | 7 | export default class UnknowWidgetTypeError extends Component { 8 | static propTypes = { 9 | extension: PropTypes.string.isRequired, 10 | widget: PropTypes.string.isRequired, 11 | } 12 | 13 | static contextTypes = { 14 | theme: PropTypes.object.isRequired, 15 | } 16 | 17 | render() { 18 | const { extension, widget } = this.props 19 | return ( 20 | 21 | 22 | 23 |

24 | Unknown widget " 25 | {widget} 26 | " for extension " 27 | {extension} 28 | ". 29 |

30 |

31 | Please make sure you installed the corresponding package (should be 32 | "mozaik-ext- 33 | {extension} 34 | ") and the package provides a " 35 | {widget} 36 | " widget. 37 |

38 |
39 |
40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/Widget.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import typography from '../../theming/typography' 5 | 6 | const Container = styled.div` 7 | position: relative; 8 | width: 100%; 9 | height: 100%; 10 | padding: ${props => props.theme.widget.wrapper.padding}; 11 | ` 12 | 13 | const Inner = styled.div` 14 | position: relative; 15 | width: 100%; 16 | height: 100%; 17 | background: ${props => props.theme.widget.background}; 18 | ${props => typography(props.theme, 'default', 'default')}; 19 | ` 20 | 21 | export default class Widget extends Component { 22 | static propTypes = { 23 | style: PropTypes.object.isRequired, 24 | children: PropTypes.node.isRequired, 25 | } 26 | 27 | static defaultProps = { 28 | style: {}, 29 | } 30 | 31 | render() { 32 | const { children, style: _style } = this.props 33 | 34 | return ( 35 | 36 | {children} 37 | 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/WidgetAvatar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | 5 | const Avatar = styled.div` 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | border-radius: 50%; 10 | overflow: hidden; 11 | ` 12 | 13 | export default class WidgetAvatar extends Component { 14 | static propTypes = { 15 | children: PropTypes.node, 16 | size: PropTypes.oneOfType([ 17 | PropTypes.number, 18 | // supports string because of vmin, rem… 19 | // CSS calc() is used to compute relative sizes 20 | PropTypes.string, 21 | ]).isRequired, 22 | style: PropTypes.object, 23 | } 24 | 25 | static defaultProps = { 26 | size: 36, 27 | style: {}, 28 | } 29 | 30 | render() { 31 | const { children, size, style: _style } = this.props 32 | 33 | const style = { 34 | fontSize: `calc(${size} / 2)`, 35 | height: size, 36 | width: size, 37 | ..._style, 38 | } 39 | 40 | return {children} 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/WidgetBody.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | 5 | const Body = styled.div` 6 | position: absolute; 7 | top: ${props => (props.isHeaderless ? 0 : props.theme.widget.body.top)}; 8 | right: 0; 9 | bottom: 0; 10 | left: 0; 11 | overflow-x: hidden; 12 | overflow-y: auto; 13 | ${props => 14 | props.disablePadding ? '' : `padding: ${props.theme.widget.body.padding};`} ${props => 15 | props.theme.widget.body.extend.trim()}; 16 | ` 17 | 18 | export default class WidgetBody extends Component { 19 | static propTypes = { 20 | style: PropTypes.object, 21 | children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]) 22 | .isRequired, 23 | disablePadding: PropTypes.bool.isRequired, 24 | isHeaderless: PropTypes.bool.isRequired, 25 | } 26 | 27 | static defaultProps = { 28 | disablePadding: false, 29 | isHeaderless: false, 30 | style: {}, 31 | } 32 | 33 | render() { 34 | const { children, disablePadding, isHeaderless, style } = this.props 35 | 36 | return ( 37 | 38 | {children} 39 | 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/WidgetCounter.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import typography from '../../theming/typography' 5 | 6 | const Container = styled.div` 7 | width: 100%; 8 | height: 100%; 9 | overflow-x: hidden; 10 | overflow-y: auto; 11 | display: flex; 12 | flex-direction: column; 13 | ` 14 | 15 | const Count = styled.span` 16 | flex: 1; 17 | display: flex; 18 | justify-content: ${props => 19 | props.align === 'center' ? 'center' : props.align === 'left' ? 'flex-start' : 'flex-end'}; 20 | align-items: center; 21 | ` 22 | 23 | const CountInner = styled.span` 24 | white-space: pre; 25 | ` 26 | 27 | const CountText = styled.span` 28 | color: ${props => props.theme.colors.textHighlight}; 29 | ${props => typography(props.theme, 'display')} font-size: 9vmin; 30 | ` 31 | 32 | const Unit = styled.span` 33 | display: inline-block; 34 | margin-left: 1vmin; 35 | ${props => typography(props.theme, 'display')} font-size: 5vmin; 36 | ` 37 | 38 | const PreLabel = styled.div` 39 | margin-bottom: 2vmin; 40 | text-align: ${props => 41 | props.align === 'center' ? 'center' : props.align === 'left' ? 'left' : 'right'}; 42 | ${props => typography(props.theme)}; 43 | ` 44 | 45 | const PostLabel = styled.div` 46 | margin-top: 2vmin; 47 | text-align: ${props => 48 | props.align === 'center' ? 'center' : props.align === 'left' ? 'left' : 'right'}; 49 | ${props => typography(props.theme)}; 50 | ` 51 | 52 | export default class WidgetCounter extends Component { 53 | static propTypes = { 54 | count: PropTypes.number.isRequired, 55 | unit: PropTypes.string, 56 | preLabel: PropTypes.node, 57 | postLabel: PropTypes.node, 58 | align: PropTypes.oneOf(['left', 'center', 'right']).isRequired, 59 | } 60 | 61 | static defaultProps = { 62 | align: 'center', 63 | } 64 | 65 | render() { 66 | const { count, unit, preLabel, postLabel, align } = this.props 67 | 68 | return ( 69 | 70 | {preLabel && {preLabel}} 71 | 72 | 73 | {count} 74 | {unit && {unit}} 75 | 76 | 77 | {postLabel && {postLabel}} 78 | 79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/WidgetLabel.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | 5 | const Wrapper = styled.span` 6 | display: inline-flex; 7 | align-items: stretch; 8 | align-content: stretch; 9 | background: ${props => props.theme.label.background}; 10 | color: ${props => props.theme.label.color}; 11 | ${props => props.theme.label.extend.trim()}; 12 | ` 13 | 14 | const Label = styled.span` 15 | display: inline-flex; 16 | align-items: center; 17 | white-space: pre; 18 | flex-grow: 1; 19 | padding: ${props => props.theme.label.main.padding}; 20 | background: ${props => props.theme.label.main.background}; 21 | color: ${props => props.theme.label.main.color}; 22 | ${props => props.theme.label.main.extend.trim()}; 23 | ` 24 | 25 | const Addon = styled.span` 26 | display: inline-flex; 27 | align-items: center; 28 | white-space: pre; 29 | padding: ${props => props.theme.label.addon.padding}; 30 | background: ${props => props.theme.label.addon.background}; 31 | color: ${props => props.theme.label.addon.color}; 32 | ${props => props.theme.label.addon.extend.trim()}; 33 | ` 34 | 35 | export default class WidgetLabel extends Component { 36 | static propTypes = { 37 | prefix: PropTypes.node, 38 | label: PropTypes.node.isRequired, 39 | suffix: PropTypes.node, 40 | style: PropTypes.object.isRequired, 41 | } 42 | 43 | static defaultProps = { 44 | style: {}, 45 | } 46 | 47 | render() { 48 | const { label, prefix, suffix, style: style } = this.props 49 | 50 | let prefixNode = null 51 | if (prefix !== undefined) { 52 | prefixNode = {prefix} 53 | } 54 | 55 | let suffixNode = null 56 | if (suffix !== undefined) { 57 | suffixNode = {suffix} 58 | } 59 | 60 | return ( 61 | 62 | {prefixNode} 63 | 64 | {suffixNode} 65 | 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/WidgetLoader.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import PropTypes from 'prop-types' 4 | 5 | const WidgetLoader = ({ color }) => ( 6 | 7 | 8 | 9 | 10 | 11 | ) 12 | 13 | WidgetLoader.propTypes = { 14 | color: PropTypes.string, 15 | } 16 | 17 | const SpinnerContainer = styled.div` 18 | height: 100%; 19 | display: flex; 20 | justify-content: center; 21 | flex-direction: column; 22 | text-align: center; 23 | overflow: hidden; 24 | ` 25 | 26 | const StyledSpinner = styled.svg` 27 | animation: rotate 2s linear infinite; 28 | height: 50px; 29 | 30 | & .path { 31 | stroke: ${props => props.color}; 32 | stroke-linecap: round; 33 | animation: dash 1.5s ease-in-out infinite; 34 | } 35 | 36 | @keyframes rotate { 37 | 100% { 38 | transform: rotate(360deg); 39 | } 40 | } 41 | @keyframes dash { 42 | 0% { 43 | stroke-dasharray: 1, 150; 44 | stroke-dashoffset: 0; 45 | } 46 | 50% { 47 | stroke-dasharray: 90, 150; 48 | stroke-dashoffset: -35; 49 | } 50 | 100% { 51 | stroke-dasharray: 90, 150; 52 | stroke-dashoffset: -124; 53 | } 54 | } 55 | ` 56 | 57 | export default WidgetLoader 58 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/WidgetWrapper.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import _ from 'lodash' 4 | import UnknowWidgetTypeError from './UnknowWidgetTypeError' 5 | import shallowEqual from '../../lib/shallowEqual' 6 | import { is } from 'immutable' 7 | import { withTheme } from 'styled-components' 8 | 9 | const ignoreProps = ['extension', 'widget', 'registry', 'apiData', 'apiError'] 10 | 11 | class WidgetWrapper extends Component { 12 | static propTypes = { 13 | apiData: PropTypes.object, 14 | apiError: PropTypes.object, 15 | extension: PropTypes.string.isRequired, 16 | widget: PropTypes.string.isRequired, 17 | subscriptionId: PropTypes.string, 18 | theme: PropTypes.object.isRequired, 19 | registry: PropTypes.shape({ 20 | getComponent: PropTypes.func.isRequired, 21 | }).isRequired, 22 | } 23 | 24 | shouldComponentUpdate(nextProps) { 25 | return ( 26 | !shallowEqual(_.omit(this.props, ignoreProps), _.omit(nextProps, ignoreProps)) || 27 | !is(this.props.apiData, nextProps.apiData) || 28 | !is(this.props.apiError, nextProps.apiError) 29 | ) 30 | } 31 | 32 | render() { 33 | const { registry, extension, widget: type, apiData, apiError } = this.props 34 | 35 | //console.log(`=> ${extension}.${type}`) 36 | 37 | let content 38 | if (!registry.has(extension, type)) { 39 | content = 40 | } else { 41 | // Pick component from registry and instantiate with filtered props 42 | const component = registry.getComponent(extension, type) 43 | 44 | // Pass props to widget component without 'metadata 45 | const childProps = _.omit(this.props, ignoreProps) 46 | if (apiData) { 47 | //console.log('API_DATA', apiData) 48 | childProps.apiData = apiData //.toJS() 49 | 50 | childProps.apiData = apiData 51 | } 52 | if (apiError) { 53 | childProps.apiError = apiError //.toJS() 54 | } 55 | 56 | content = React.createElement(component, childProps) 57 | } 58 | 59 | return content 60 | } 61 | } 62 | 63 | export default withTheme(WidgetWrapper) 64 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/list/WidgetListItem.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | import Text from '../../Text' 5 | import typography from '../../../theming/typography' 6 | 7 | const Item = styled.div` 8 | position: relative; 9 | display: flex; 10 | align-items: ${props => 11 | props.align === 'top' ? 'flex-start' : props.align === 'center' ? 'center' : 'flex-end'}; 12 | padding: ${props => props.theme.list.item.padding}; 13 | background: ${props => props.theme.list.item.background}; 14 | ${props => props.theme.list.item.extend.trim()} &:hover { 15 | background: ${props => props.theme.list.item.hover.background}; 16 | } 17 | ` 18 | 19 | const Title = styled.div` 20 | color: ${props => props.theme.colors.textHighlight}; 21 | ${props => typography(props.theme, 'default', 'strong')}; 22 | ` 23 | 24 | const Pre = styled.div` 25 | margin-right: 2vmin; 26 | ` 27 | 28 | const Post = styled.div` 29 | margin-left: 2vmin; 30 | ` 31 | 32 | export default class WidgetListItem extends Component { 33 | static propTypes = { 34 | title: PropTypes.node.isRequired, 35 | pre: PropTypes.node, 36 | post: PropTypes.node, 37 | meta: PropTypes.node, 38 | style: PropTypes.object, 39 | onClick: PropTypes.func, 40 | align: PropTypes.oneOf(['top', 'center', 'bottom']).isRequired, 41 | } 42 | 43 | static defaultProps = { 44 | subjectPlacement: 'prepend', 45 | align: 'center', 46 | } 47 | 48 | render() { 49 | const { title, pre, post, meta, onClick, align, style } = this.props 50 | 51 | let metaNode = null 52 | if (meta !== undefined) { 53 | metaNode = ( 54 | 55 | {meta} 56 | 57 | ) 58 | } 59 | 60 | let preNode = null 61 | if (pre !== undefined) { 62 | preNode =
{pre}
63 | } 64 | 65 | let postNode = null 66 | if (post !== undefined) { 67 | postNode = {post} 68 | } 69 | 70 | return ( 71 | 72 | {preNode} 73 |
74 | {title} 75 | {metaNode} 76 |
77 | {postNode} 78 |
79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/status/WidgetStatusChip.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled, { withTheme } from 'styled-components' 4 | 5 | const Ship = styled.span` 6 | display: block; 7 | border-radius: 50%; 8 | ` 9 | 10 | const colorMapping = { 11 | success: ['success', 'passed', 'ok'], 12 | warning: ['warning'], 13 | failure: ['error', 'failed', 'ko'], 14 | } 15 | 16 | const getColorKey = status => { 17 | for (let c in colorMapping) { 18 | if (colorMapping[c].includes(status)) return c 19 | } 20 | 21 | return 'unknown' 22 | } 23 | 24 | class WidgetStatusChip extends Component { 25 | static propTypes = { 26 | size: PropTypes.number, 27 | status: PropTypes.string.isRequired, 28 | theme: PropTypes.object.isRequired, 29 | style: PropTypes.object.isRequired, 30 | } 31 | 32 | static defaultProps = { 33 | size: 12, 34 | } 35 | 36 | render() { 37 | const { status, size, theme, style: _style } = this.props 38 | 39 | const colorKey = getColorKey(status) 40 | 41 | const style = { 42 | height: size, 43 | width: size, 44 | background: theme.colors[colorKey], 45 | ..._style, 46 | } 47 | 48 | return 49 | } 50 | } 51 | 52 | export default withTheme(WidgetStatusChip) 53 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/table/WidgetTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import PropTypes from 'prop-types' 4 | 5 | const Table = styled.table` 6 | width: 100%; 7 | border-collapse: collapse; 8 | font-size: 1.6vmin; 9 | ` 10 | 11 | const WidgetTable = ({ children }) => {children}
12 | 13 | WidgetTable.propTypes = { 14 | children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired, 15 | } 16 | 17 | export default WidgetTable 18 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/table/WidgetTableCell.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import PropTypes from 'prop-types' 4 | 5 | const Cell = styled.td` 6 | padding: 1vmin 2vmin; 7 | ` 8 | 9 | const WidgetTableCell = ({ children }) => {children} 10 | 11 | WidgetTableCell.propTypes = { 12 | children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired, 13 | } 14 | 15 | export default WidgetTableCell 16 | -------------------------------------------------------------------------------- /packages/ui/src/components/widget/table/WidgetTableHeadCell.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import PropTypes from 'prop-types' 4 | 5 | const HeadCell = styled.th` 6 | padding: 1vmin 2vmin; 7 | text-align: left; 8 | font-weight: normal; 9 | ` 10 | 11 | const WidgetTableHeadCell = ({ children }) => {children} 12 | 13 | WidgetTableHeadCell.propTypes = { 14 | children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]).isRequired, 15 | } 16 | 17 | export default WidgetTableHeadCell 18 | -------------------------------------------------------------------------------- /packages/ui/src/configureStore.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux' 2 | import thunkMiddleware from 'redux-thunk' 3 | import reducers from './reducers' 4 | 5 | export default function configureStore(initialState) { 6 | const store = createStore( 7 | reducers, 8 | initialState, 9 | compose( 10 | applyMiddleware(thunkMiddleware), 11 | window.devToolsExtension ? window.devToolsExtension() : f => f 12 | ) 13 | ) 14 | 15 | return store 16 | } 17 | -------------------------------------------------------------------------------- /packages/ui/src/constants/notificationsConstants.js: -------------------------------------------------------------------------------- 1 | export const NOTIFICATION_STATUS_UNKNOWN = 'unknown' 2 | export const NOTIFICATION_STATUS_SUCCESS = 'success' 3 | export const NOTIFICATION_STATUS_WARNING = 'warning' 4 | export const NOTIFICATION_STATUS_ERROR = 'error' 5 | 6 | export const NOTIFICATION_DEFAULT_TTL = 5000 7 | -------------------------------------------------------------------------------- /packages/ui/src/constants/wsConstants.js: -------------------------------------------------------------------------------- 1 | export const WS_STATUS_CONNECTED = 'WS_STATUS_CONNECTED' 2 | export const WS_STATUS_DELAYING = 'WS_STATUS_DELAYING' 3 | export const WS_STATUS_FAILED = 'WS_STATUS_FAILED' 4 | 5 | export const WS_NOTIFICATION_ID = 'connection.status' 6 | 7 | export const WS_MAX_RETRIES = 10 8 | export const WS_RETRY_DELAY = 5000 9 | -------------------------------------------------------------------------------- /packages/ui/src/containers/MozaikContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import Mozaik from '../components/Mozaik' 3 | import { fetchConfiguration } from '../actions/configurationActions' 4 | import { play, pause, previous, next } from '../actions/dashboardsActions' 5 | import { setTheme } from '../actions/themesActions' 6 | 7 | const mapStateToProps = state => { 8 | const { 9 | configuration, 10 | dashboards: { dashboards, current, isPlaying }, 11 | themes: { themes, current: currentTheme }, 12 | } = state 13 | 14 | return { 15 | ...configuration, 16 | dashboards, 17 | currentDashboard: current, 18 | isPlaying, 19 | themes, 20 | currentTheme, 21 | } 22 | } 23 | 24 | const mapDispatchToProps = dispatch => ({ 25 | fetchConfiguration: () => { 26 | dispatch(fetchConfiguration()) 27 | }, 28 | play: () => { 29 | dispatch(play()) 30 | }, 31 | pause: () => { 32 | dispatch(pause()) 33 | }, 34 | previous: () => { 35 | dispatch(pause()) 36 | dispatch(previous()) 37 | }, 38 | next: () => { 39 | dispatch(pause()) 40 | dispatch(next()) 41 | }, 42 | setTheme: theme => { 43 | dispatch(setTheme(theme)) 44 | }, 45 | }) 46 | 47 | export default connect( 48 | mapStateToProps, 49 | mapDispatchToProps 50 | )(Mozaik) 51 | -------------------------------------------------------------------------------- /packages/ui/src/containers/NotificationsContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import Notifications from '../components/notifications/Notifications' 3 | 4 | const mapStateToProps = state => { 5 | const { 6 | notifications: { items }, 7 | themes: { current: themeId }, 8 | } = state 9 | 10 | return { 11 | notifications: items, 12 | // not used but needed to force refresh of context 13 | themeId, 14 | } 15 | } 16 | 17 | const mapDispatchToProps = () => ({}) 18 | 19 | export default connect( 20 | mapStateToProps, 21 | mapDispatchToProps 22 | )(Notifications) 23 | -------------------------------------------------------------------------------- /packages/ui/src/containers/WidgetContainer.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import WidgetWrapper from '../components/widget/WidgetWrapper' 3 | 4 | const mapStateToProps = (state, { subscriptionId: id }) => { 5 | let apiData 6 | let apiError 7 | 8 | if (id) { 9 | if (state.api.get('data').has(id)) { 10 | apiData = state.api.get('data').get(id) 11 | } 12 | if (state.api.get('errors').has(id)) { 13 | apiError = state.api.get('errors').get(id) 14 | } 15 | } 16 | 17 | return { 18 | // not used but needed to force refresh of context 19 | themeId: state.themes.current, 20 | apiData, 21 | apiError, 22 | } 23 | } 24 | 25 | export default connect(mapStateToProps)(WidgetWrapper) 26 | -------------------------------------------------------------------------------- /packages/ui/src/index.js: -------------------------------------------------------------------------------- 1 | import Registry from './WidgetsRegistry' 2 | import Inspector from './components/Inspector' 3 | import Mozaik from './App' 4 | 5 | Registry.add('mozaik', 'Inspector', Inspector) 6 | 7 | export { Registry } 8 | export { default as ThemeManager } from './theming/ThemeManager' 9 | export { default as defaultTheme } from './theming/defaultTheme' 10 | export { default as typography } from './theming/typography' 11 | export { default as TrapApiError } from './components/TrapApiError' 12 | export { default as Text } from './components/Text' 13 | export { default as Widget } from './components/widget/Widget' 14 | export { default as WidgetHeader } from './components/widget/WidgetHeader' 15 | export { default as WidgetBody } from './components/widget/WidgetBody' 16 | export { default as WidgetLoader } from './components/widget/WidgetLoader' 17 | export { default as WidgetListItem } from './components/widget/list/WidgetListItem' 18 | export { default as WidgetLabel } from './components/widget/WidgetLabel' 19 | export { default as WidgetTable } from './components/widget/table/WidgetTable' 20 | export { default as WidgetTableCell } from './components/widget/table/WidgetTableCell' 21 | export { default as WidgetTableHeadCell } from './components/widget/table/WidgetTableHeadCell' 22 | export { default as WidgetAvatar } from './components/widget/WidgetAvatar' 23 | export { default as WidgetStatusChip } from './components/widget/status/WidgetStatusChip' 24 | export { default as WidgetStatusBadge } from './components/widget/status/WidgetStatusBadge' 25 | export { default as WidgetCounter } from './components/widget/WidgetCounter' 26 | export { default as ExternalLink } from './components/ExternalLink' 27 | export * from './components/icons' 28 | 29 | export default Mozaik 30 | -------------------------------------------------------------------------------- /packages/ui/src/lib/WSHelper.js: -------------------------------------------------------------------------------- 1 | export const guessWSURL = (config = {}) => { 2 | if (!window || !window.document || !window.document.location) { 3 | throw new Error( 4 | `Unable to guess websocket URL because 'window.document.location' is not defined` 5 | ) 6 | } 7 | 8 | let proto = 'ws' 9 | if (config.useWssConnection === true || window.document.location.protocol === 'https:') { 10 | proto = 'wss' 11 | } 12 | 13 | let port = window.document.location.port 14 | if (config.wsPort !== undefined) { 15 | port = config.wsPort 16 | } 17 | 18 | let wsUrl = `${proto}://${window.document.location.hostname}` 19 | if (port && port !== '') { 20 | wsUrl = `${wsUrl}:${port}` 21 | } 22 | 23 | return wsUrl 24 | } 25 | -------------------------------------------------------------------------------- /packages/ui/src/lib/shallowEqual.js: -------------------------------------------------------------------------------- 1 | /** 2 | * from https://raw.githubusercontent.com/reactjs/react-redux/master/src/utils/shallowEqual.js 3 | */ 4 | 5 | const hasOwn = Object.prototype.hasOwnProperty 6 | 7 | export default function shallowEqual(a, b) { 8 | if (a === b) return true 9 | 10 | let countA = 0 11 | let countB = 0 12 | 13 | for (let key in a) { 14 | if (hasOwn.call(a, key) && a[key] !== b[key]) return false 15 | countA++ 16 | } 17 | 18 | for (let key in b) { 19 | if (hasOwn.call(b, key)) countB++ 20 | } 21 | 22 | return countA === countB 23 | } 24 | -------------------------------------------------------------------------------- /packages/ui/src/reducers/apiReducer.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable' 2 | 3 | import { 4 | API_SUBSCRIBE, 5 | API_SUBSCRIBED, 6 | API_ALL_UNSUBSCRIBED, 7 | API_UNSUBSCRIBE, 8 | API_DATA, 9 | API_FAILURE, 10 | } from '../actions/apiActions' 11 | 12 | const defaultState = Map({ 13 | subscriptions: Map(), 14 | data: Map(), 15 | errors: Map(), 16 | }) 17 | 18 | export default function configuration(state = defaultState, action) { 19 | switch (action.type) { 20 | case API_SUBSCRIBE: 21 | if (state.get('subscriptions').has(action.subscription.id)) { 22 | return state 23 | } 24 | 25 | return state.setIn( 26 | ['subscriptions', action.subscription.id], 27 | Map( 28 | Object.assign({}, action.subscription, { 29 | hasSubscribed: false, 30 | }) 31 | ) 32 | ) 33 | 34 | case API_SUBSCRIBED: 35 | if (!state.get('subscriptions').has(action.subscription.id)) { 36 | return state 37 | } 38 | 39 | return state.setIn(['subscriptions', action.subscription.id, 'hasSubscribed'], true) 40 | 41 | case API_ALL_UNSUBSCRIBED: 42 | return state.setIn( 43 | ['subscriptions'], 44 | state.get('subscriptions').map(subscription => { 45 | return subscription.setIn(['hasSubscribed'], false) 46 | }) 47 | ) 48 | 49 | case API_UNSUBSCRIBE: 50 | if (!state.get('subscriptions').has(action.id)) { 51 | return state 52 | } 53 | 54 | return state.deleteIn(['subscriptions', action.id]) 55 | 56 | case API_DATA: 57 | return state.deleteIn(['errors', action.id]).setIn(['data', action.id], action.data) 58 | 59 | case API_FAILURE: 60 | return state.mergeIn(['errors', action.id], action.data) 61 | 62 | default: 63 | return state 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/ui/src/reducers/configurationReducer.js: -------------------------------------------------------------------------------- 1 | import { FETCH_CONFIGURATION, FETCH_CONFIGURATION_SUCCESS } from '../actions/configurationActions' 2 | 3 | export default function configuration( 4 | state = { 5 | isLoading: true, 6 | configuration: null, 7 | }, 8 | action 9 | ) { 10 | switch (action.type) { 11 | case FETCH_CONFIGURATION: 12 | return { 13 | ...state, 14 | isLoading: true, 15 | } 16 | 17 | case FETCH_CONFIGURATION_SUCCESS: 18 | return { 19 | ...state, 20 | isLoading: false, 21 | configuration: action.configuration, 22 | } 23 | 24 | default: 25 | return state 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/ui/src/reducers/dashboardsReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | SET_DASHBOARDS, 3 | SET_CURRENT_DASHBOARD, 4 | DASHBOARDS_PLAY, 5 | DASHBOARDS_PAUSE, 6 | } from '../actions/dashboardsActions' 7 | 8 | export default function dashboards( 9 | state = { 10 | dashboards: [], 11 | current: 0, 12 | isPlaying: false, 13 | }, 14 | action 15 | ) { 16 | switch (action.type) { 17 | case SET_DASHBOARDS: 18 | return { 19 | ...state, 20 | dashboards: action.dashboards, 21 | } 22 | 23 | case SET_CURRENT_DASHBOARD: 24 | return { 25 | ...state, 26 | current: action.index, 27 | } 28 | 29 | case DASHBOARDS_PLAY: 30 | return { 31 | ...state, 32 | isPlaying: true, 33 | } 34 | 35 | case DASHBOARDS_PAUSE: 36 | return { 37 | ...state, 38 | isPlaying: false, 39 | } 40 | 41 | default: 42 | return state 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/ui/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import api from './apiReducer' 3 | import configuration from './configurationReducer' 4 | import dashboards from './dashboardsReducer' 5 | import notifications from './notificationsReducer' 6 | import ws from './wsReducer' 7 | import themes from './themesReducer' 8 | 9 | export default combineReducers({ 10 | api, 11 | configuration, 12 | dashboards, 13 | notifications, 14 | ws, 15 | themes, 16 | }) 17 | -------------------------------------------------------------------------------- /packages/ui/src/reducers/notificationsReducer.js: -------------------------------------------------------------------------------- 1 | import { NOTIFY, NOTIFICATION_UPDATE, NOTIFICATION_CLOSE } from '../actions/notificationsActions' 2 | 3 | export default function notifications( 4 | state = { 5 | items: [], 6 | }, 7 | action 8 | ) { 9 | switch (action.type) { 10 | case NOTIFY: 11 | return { 12 | ...state, 13 | items: [action.notification, ...state.items], 14 | } 15 | 16 | case NOTIFICATION_UPDATE: 17 | return { 18 | ...state, 19 | items: state.items.map(notification => { 20 | if (notification.id === action.id) { 21 | return { 22 | ...notification, 23 | ...action.notification, 24 | } 25 | } 26 | 27 | return notification 28 | }), 29 | } 30 | 31 | case NOTIFICATION_CLOSE: 32 | return { 33 | ...state, 34 | items: state.items.filter(({ id }) => id !== action.id), 35 | } 36 | 37 | default: 38 | return state 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/ui/src/reducers/themesReducer.js: -------------------------------------------------------------------------------- 1 | import { THEME_SET } from '../actions/themesActions' 2 | 3 | export default function themes( 4 | state = { 5 | current: null, 6 | themes: {}, 7 | }, 8 | action 9 | ) { 10 | switch (action.type) { 11 | case THEME_SET: 12 | return { 13 | ...state, 14 | current: action.theme, 15 | } 16 | 17 | default: 18 | return state 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/ui/src/reducers/wsReducer.js: -------------------------------------------------------------------------------- 1 | import { WS_CONNECT, WS_CONNECT_SUCCESS, WS_DISCONNECTED } from '../actions/wsActions' 2 | 3 | export default function ws( 4 | state = { 5 | connected: false, 6 | connecting: false, 7 | }, 8 | action 9 | ) { 10 | switch (action.type) { 11 | case WS_CONNECT: 12 | return { 13 | ...state, 14 | connecting: true, 15 | } 16 | 17 | case WS_CONNECT_SUCCESS: 18 | return { 19 | ...state, 20 | connected: true, 21 | connecting: false, 22 | } 23 | 24 | case WS_DISCONNECTED: 25 | return { 26 | ...state, 27 | connected: false, 28 | } 29 | 30 | default: 31 | return state 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/ui/src/theming/ThemeManager.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import defaultTheme from './defaultTheme' 3 | 4 | const themes = { 5 | [defaultTheme.name]: defaultTheme, 6 | } 7 | 8 | const ThemeManager = { 9 | add(theme) { 10 | themes[theme.name] = _.defaultsDeep(theme, defaultTheme) 11 | }, 12 | 13 | listThemes() { 14 | return themes 15 | }, 16 | 17 | get(name) { 18 | return themes[name] 19 | }, 20 | } 21 | 22 | ThemeManager.defaultTheme = defaultTheme.name 23 | 24 | export default ThemeManager 25 | -------------------------------------------------------------------------------- /packages/ui/src/theming/typography.js: -------------------------------------------------------------------------------- 1 | const cache = {} 2 | 3 | export default (theme, type = 'default', variant = 'default') => { 4 | const key = `${theme.name}.${type}.${variant}` 5 | 6 | const cached = cache[key] 7 | if (cached !== undefined) return cached 8 | 9 | let config = theme.typography[type].default 10 | if (variant !== 'default') { 11 | config = { 12 | ...config, 13 | ...theme.typography[type][variant], 14 | } 15 | } 16 | 17 | const rules = ` 18 | font-family: ${config.fontFamily}; 19 | font-size: ${config.fontSize}; 20 | line-height: ${config.lineHeight}; 21 | font-weight: ${config.fontWeight}; 22 | ` 23 | 24 | cache[key] = rules 25 | 26 | return rules 27 | } 28 | -------------------------------------------------------------------------------- /packages/ui/stories/decorators/MultiTheme.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import styled, { ThemeProvider } from 'styled-components' 3 | import { ThemeManager } from '../../src' 4 | 5 | const Grid = styled.div` 6 | display: grid; 7 | grid-template-columns: 1fr 1fr 1fr; 8 | ` 9 | 10 | const Container = styled.div` 11 | background: ${props => props.theme.root.background}; 12 | color: ${props => props.theme.colors.text}; 13 | padding: 2vmin; 14 | ` 15 | 16 | export default class MultiTheme extends Component { 17 | render() { 18 | const { children } = this.props 19 | 20 | return ( 21 | 22 | {Object.keys(ThemeManager.listThemes()).map(theme => { 23 | return ( 24 | 25 | {children} 26 | 27 | ) 28 | })} 29 | 30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/ui/stories/decorators/registerThemes.js: -------------------------------------------------------------------------------- 1 | import { 2 | solarizedDarkTheme, 3 | snowTheme, 4 | wineTheme, 5 | nightBlueTheme, 6 | miniTheme, 7 | miniKuroTheme, 8 | } from '@mozaik/themes' 9 | import { ThemeManager } from '../../src' 10 | 11 | ThemeManager.add(solarizedDarkTheme) 12 | ThemeManager.add(snowTheme) 13 | ThemeManager.add(wineTheme) 14 | ThemeManager.add(nightBlueTheme) 15 | ThemeManager.add(miniTheme) 16 | ThemeManager.add(miniKuroTheme) 17 | -------------------------------------------------------------------------------- /packages/ui/stories/decorators/themed.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { ThemeProvider } from 'styled-components' 3 | import { select } from '@storybook/addon-knobs' 4 | import './registerThemes' 5 | import { ThemeManager } from '../../src' 6 | 7 | const ThemeSelector = ({ theme, children }) => ( 8 | {children} 9 | ) 10 | 11 | const options = {} 12 | for (let name in ThemeManager.listThemes()) { 13 | options[name] = name 14 | } 15 | 16 | export default story => ( 17 | {story()} 18 | ) 19 | -------------------------------------------------------------------------------- /packages/ui/stories/decorators/withMultiTheme.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './registerThemes' 3 | import MultiTheme from './MultiTheme' 4 | 5 | export default story => {story()} 6 | -------------------------------------------------------------------------------- /packages/ui/stories/decorators/withWidget.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import { Widget } from '../../src' 4 | 5 | const Container = styled.div` 6 | position: fixed; 7 | top: 0; 8 | left: 0; 9 | bottom: 0; 10 | right: 0; 11 | overflow: auto; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | background: ${props => props.theme.root.background}; 16 | color: ${props => props.theme.root.color}; 17 | ` 18 | 19 | const widgetStyle = { 20 | width: 320, 21 | height: 320, 22 | } 23 | 24 | export default story => ( 25 | 26 | {story()} 27 | 28 | ) 29 | -------------------------------------------------------------------------------- /packages/ui/stories/text.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | import { Widget, Text, WidgetHeader, WidgetBody } from '../src' 4 | import withMultiTheme from './decorators/withMultiTheme' 5 | 6 | const stories = storiesOf('Text', module) 7 | 8 | stories.addDecorator(withMultiTheme) 9 | 10 | stories.add('default', () => ( 11 | 12 | 13 | 14 |
15 | default default variant span 16 |
17 |
18 | 19 | default default variant div 20 |
21 | with multiline text 22 |
23 |
24 |
25 | default strong variant 26 |
27 |
28 | 29 | default strong variant div 30 |
31 | with multiline text 32 |
33 |
34 |
35 | default small variant 36 |
37 |
38 | 39 | default small variant div 40 |
41 | with multiline text 42 |
43 |
44 |
45 |
46 | )) 47 | 48 | stories.add('display', () => ( 49 | 50 | 51 | 52 |
53 | display default variant 54 |
55 |
56 | 57 | display strong variant 58 | 59 |
60 |
61 | 62 | display small variant 63 | 64 |
65 |
66 |
67 | )) 68 | 69 | stories.add('mono', () => ( 70 | 71 | 72 | 73 |
74 | mono default variant 75 |
76 |
77 | 78 | mono strong variant 79 | 80 |
81 |
82 | 83 | mono small variant 84 | 85 |
86 |
87 |
88 | )) 89 | -------------------------------------------------------------------------------- /packages/ui/stories/widget_header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | import { Widget, WidgetHeader, WidgetBody } from '../src' 4 | import withMultiTheme from './decorators/withMultiTheme' 5 | 6 | const stories = storiesOf('WidgetHeader', module) 7 | 8 | stories.addDecorator(withMultiTheme) 9 | 10 | stories.add('default', () => ( 11 | 12 | 13 | widget body 14 | 15 | )) 16 | 17 | stories.add('with count', () => ( 18 | 19 | 20 | widget body 21 | 22 | )) 23 | 24 | stories.add('with subject', () => ( 25 | 26 | 27 | widget body 28 | 29 | )) 30 | -------------------------------------------------------------------------------- /packages/ui/stories/widget_label.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | import { Widget, WidgetHeader, WidgetBody, WidgetLabel } from '../src' 4 | import withMultiTheme from './decorators/withMultiTheme' 5 | 6 | storiesOf('WidgetLabel', module) 7 | .addDecorator(withMultiTheme) 8 | .add('default', () => ( 9 | 10 | 11 | 12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 |
26 | )) 27 | -------------------------------------------------------------------------------- /packages/ui/stories/widget_status_badge.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { storiesOf } from '@storybook/react' 3 | import { Widget, WidgetHeader, WidgetBody, WidgetStatusBadge, InfoIcon } from '../src' 4 | import withMultiTheme from './decorators/withMultiTheme' 5 | 6 | const stories = storiesOf('WidgetStatusBadge', module) 7 | 8 | stories.addDecorator(withMultiTheme) 9 | 10 | stories.add('default', () => ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | )) 18 | 19 | stories.add('success', () => ( 20 | 21 | 22 | 23 | 24 | 25 | 26 | )) 27 | 28 | stories.add('warning', () => ( 29 | 30 | 31 | 32 | 33 | 34 | 35 | )) 36 | 37 | stories.add('error', () => ( 38 | 39 | 40 | 41 | 42 | 43 | 44 | )) 45 | 46 | stories.add('with message', () => ( 47 | 48 | 49 | 50 | 51 | 52 | 53 | )) 54 | 55 | stories.add('with meta', () => ( 56 | 57 | 58 | 59 | 60 | 61 | 62 | )) 63 | 64 | stories.add('full featured', () => ( 65 | 66 | 67 | 68 | 69 | 70 | 71 | )) 72 | -------------------------------------------------------------------------------- /packages/ui/test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | jest: true 3 | -------------------------------------------------------------------------------- /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/preview.png -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Mozaïk website 2 | 3 | The mozaik.rocks source, based upon gatsby. -------------------------------------------------------------------------------- /website/gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it -------------------------------------------------------------------------------- /website/gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: 'Mozaïk', 4 | }, 5 | plugins: [ 6 | 'gatsby-transformer-yaml', 7 | { 8 | resolve: 'gatsby-source-filesystem', 9 | options: { 10 | name: 'src', 11 | path: `${__dirname}/src/`, 12 | }, 13 | }, 14 | { 15 | resolve: `gatsby-transformer-remark`, 16 | options: { 17 | plugins: [ 18 | 'gatsby-remark-prismjs', 19 | { 20 | resolve: `gatsby-remark-images`, 21 | options: { 22 | // It's important to specify the maxWidth (in pixels) of 23 | // the content container as this plugin uses this as the 24 | // base for generating different widths of each image. 25 | maxWidth: 590, 26 | }, 27 | }, 28 | ] 29 | }, 30 | }, 31 | 'gatsby-plugin-catch-links', 32 | 'gatsby-plugin-react-helmet', 33 | 'gatsby-plugin-styled-components', 34 | 'gatsby-remark-copy-linked-files', 35 | { 36 | resolve: 'gatsby-remark-images', 37 | options: { 38 | maxWidth: 1080, 39 | }, 40 | }, 41 | ], 42 | } 43 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mozaik-website", 3 | "description": "Mozaïk website", 4 | "version": "1.0.0", 5 | "dependencies": { 6 | "date-fns": "^1.29.0", 7 | "gatsby": "^1.9.247", 8 | "gatsby-link": "^1.6.40", 9 | "gatsby-plugin-catch-links": "^1.0.22", 10 | "gatsby-plugin-react-helmet": "^2.0.10", 11 | "gatsby-plugin-styled-components": "^2.0.11", 12 | "gatsby-remark-copy-linked-files": "^1.5.35", 13 | "gatsby-remark-images": "^1.5.66", 14 | "gatsby-remark-prismjs": "^2.0.3", 15 | "gatsby-source-filesystem": "^1.5.38", 16 | "gatsby-transformer-remark": "^1.7.42", 17 | "gatsby-transformer-yaml": "^1.5.17", 18 | "lodash": "^4.17.10", 19 | "prismjs": "^1.14.0", 20 | "prop-types": "^15.6.1", 21 | "react-helmet": "^5.2.0", 22 | "react-icons": "^2.2.7", 23 | "styled-components": "^3.3.2" 24 | }, 25 | "private": true 26 | } 27 | -------------------------------------------------------------------------------- /website/src/assets/extension-demo/github-extension-website.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/extension-demo/github-extension-website.png -------------------------------------------------------------------------------- /website/src/assets/extension-demo/heroku-auto-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/extension-demo/heroku-auto-deploy.png -------------------------------------------------------------------------------- /website/src/assets/extension-demo/heroku-connect-github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/extension-demo/heroku-connect-github.png -------------------------------------------------------------------------------- /website/src/assets/extension-demo/heroku-create-app-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/extension-demo/heroku-create-app-form.png -------------------------------------------------------------------------------- /website/src/assets/extension-demo/heroku-create-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/extension-demo/heroku-create-app.png -------------------------------------------------------------------------------- /website/src/assets/extension-demo/heroku-deploy-method.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/extension-demo/heroku-deploy-method.png -------------------------------------------------------------------------------- /website/src/assets/extension-demo/heroku-deploy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/extension-demo/heroku-deploy.png -------------------------------------------------------------------------------- /website/src/assets/extension-demo/heroku-vars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/extension-demo/heroku-vars.png -------------------------------------------------------------------------------- /website/src/assets/features/mozaik-icon-backend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/features/mozaik-icon-backend.png -------------------------------------------------------------------------------- /website/src/assets/features/mozaik-icon-extendable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/features/mozaik-icon-extendable.png -------------------------------------------------------------------------------- /website/src/assets/features/mozaik-icon-positioning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/features/mozaik-icon-positioning.png -------------------------------------------------------------------------------- /website/src/assets/features/mozaik-icon-scalable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/features/mozaik-icon-scalable.png -------------------------------------------------------------------------------- /website/src/assets/features/mozaik-icon-themes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/features/mozaik-icon-themes.png -------------------------------------------------------------------------------- /website/src/assets/mozaik-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/mozaik-logo-white.png -------------------------------------------------------------------------------- /website/src/assets/mozaik-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/mozaik-logo.png -------------------------------------------------------------------------------- /website/src/assets/mozaik-pattern.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/mozaik-pattern.gif -------------------------------------------------------------------------------- /website/src/assets/mozaik-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/mozaik-pattern.png -------------------------------------------------------------------------------- /website/src/assets/posts/mozaik-inspector-widget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/posts/mozaik-inspector-widget.png -------------------------------------------------------------------------------- /website/src/assets/posts/saucelabs-widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/posts/saucelabs-widgets.png -------------------------------------------------------------------------------- /website/src/assets/refs/aptus-health.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/refs/aptus-health.png -------------------------------------------------------------------------------- /website/src/assets/refs/canalplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/refs/canalplus.png -------------------------------------------------------------------------------- /website/src/assets/refs/sc5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/src/assets/refs/sc5.png -------------------------------------------------------------------------------- /website/src/components/Container.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | 5 | const Root = styled.div` 6 | width: 100%; 7 | &:before { 8 | content: ""; 9 | display: table; 10 | } 11 | &:after { 12 | clear: both; 13 | } 14 | ` 15 | 16 | const Inner = styled.div` 17 | max-width: ${props => props.size === 'large' ? 1200 : 900}px; 18 | margin: 0 auto; 19 | padding: 0 20px; 20 | ` 21 | 22 | const Container = ({ children, style, innerStyle, size, ...rest }) => ( 23 | 24 | 25 | {children} 26 | 27 | 28 | ) 29 | 30 | Container.propTypes = { 31 | style: PropTypes.object.isRequired, 32 | innerStyle: PropTypes.object.isRequired, 33 | size: PropTypes.oneOf(['medium', 'large']).isRequired, 34 | } 35 | 36 | Container.defaultProps = { 37 | style: {}, 38 | innerStyle: {}, 39 | size: 'medium' 40 | } 41 | 42 | export default Container -------------------------------------------------------------------------------- /website/src/components/Extensions.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import ExtensionsItem from './ExtensionsItem' 4 | import media from '../styles/media' 5 | 6 | const Root = styled.div` 7 | margin: 30px 0; 8 | display: grid; 9 | grid-column-gap: 30px; 10 | 11 | ${media.mobile` 12 | grid-template-columns: 1fr; 13 | `} 14 | 15 | ${media.tablet` 16 | grid-template-columns: 1fr; 17 | grid-row-gap: 20px; 18 | `} 19 | 20 | ${media.desktop` 21 | grid-template-columns: 1fr 1fr; 22 | grid-row-gap: 30px; 23 | `} 24 | ` 25 | 26 | const Extensions = ({ extensions }) => ( 27 | 28 | {extensions.map(extension => ( 29 | 33 | ))} 34 | 35 | ) 36 | 37 | export default Extensions 38 | 39 | -------------------------------------------------------------------------------- /website/src/components/ExternalPosts.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Container from './Container' 4 | import media from '../styles/media' 5 | 6 | const Root = styled(Container)` 7 | background: ${props => props.theme.contentBackgroundColor}; 8 | padding-bottom: 30px; 9 | ` 10 | 11 | const Title = styled.h2` 12 | font-weight: 700; 13 | font-size: 22px; 14 | margin: 30px 0 0; 15 | padding-bottom: 15px; 16 | text-transform: uppercase; 17 | border-bottom: 1px solid ${props => props.theme.borderColor}; 18 | 19 | ${media.mobile` 20 | & { 21 | text-align: center; 22 | } 23 | `} 24 | ` 25 | 26 | const Item = styled.a` 27 | text-decoration: none; 28 | color: ${props => props.theme.textColor} !important; 29 | display: block; 30 | padding: 10px 0; 31 | border-bottom: 1px solid ${props => props.theme.borderColor}; 32 | 33 | &:last-child { 34 | border-bottom: 0; 35 | } 36 | ` 37 | 38 | const ExternalPosts = ({ posts }) => ( 39 | 40 | Use cases/posts 41 | {posts.map(post => ( 42 | 43 | {post.title} 44 | 45 | ))} 46 | 47 | ) 48 | 49 | export default ExternalPosts 50 | -------------------------------------------------------------------------------- /website/src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from 'styled-components' 4 | 5 | const Root = styled.footer` 6 | color: #fff; 7 | position: relative; 8 | text-align: center; 9 | line-height: 1.4; 10 | //font-family: font-title 11 | 12 | @media print { 13 | display: none; 14 | } 15 | 16 | ${props => { 17 | if (props.isFullWidth) { 18 | return ` 19 | padding: 40px 0 40px 300px; 20 | ` 21 | } 22 | 23 | return ` 24 | padding: 40px 0; 25 | ` 26 | }} 27 | ` 28 | 29 | const Inner = styled.div` 30 | max-width: 1200px; 31 | margin: 0 auto; 32 | ` 33 | 34 | const Link = styled.a` 35 | color: inherit; 36 | text-decoration: none; 37 | transition: 0.2s; 38 | font-weight: bold; 39 | 40 | &:hover { 41 | color: #fff; 42 | } 43 | ` 44 | 45 | /* 46 | @media only screen and (min-width: 769px) { 47 | #footer { 48 | text-align: left; 49 | } 50 | 51 | #footer-copyright { 52 | float: left; 53 | } 54 | 55 | #footer-links { 56 | float: right; 57 | margin-top: 0; 58 | } 59 | } 60 | */ 61 | 62 | const Footer = ({ isFullWidth }) => ( 63 | 64 | 65 | 72 | 73 | 74 | ) 75 | 76 | Footer.propTypes = { 77 | isFullWidth: PropTypes.bool.isRequired, 78 | } 79 | 80 | Footer.defaultProps = { 81 | isFullWidth: false 82 | } 83 | 84 | export default Footer 85 | -------------------------------------------------------------------------------- /website/src/components/GeneratedContent.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export default styled.div` 4 | padding-bottom: 30px; 5 | 6 | a { 7 | color: ${props => props.theme.accentTextColor}; 8 | text-decoration: none; 9 | border-bottom: 1px solid ${props => props.theme.accentTextColor}; 10 | 11 | &:hover { 12 | background-color: ${props => props.theme.accentTextColor}; 13 | color: #ffffff; 14 | } 15 | } 16 | ` -------------------------------------------------------------------------------- /website/src/components/Global.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import PropTypes from 'prop-types' 4 | import Helmet from 'react-helmet' 5 | import { ThemeProvider } from 'styled-components' 6 | import theme from '../styles/theme' 7 | import '../styles/index.css' 8 | 9 | const Container = styled.div` 10 | color: ${props => props.theme.textColor}; 11 | font-family: ${props => props.theme.fontFamily}; 12 | font-size: ${props => props.theme.fontSize}; 13 | line-height: ${props => props.theme.lineHeight}; 14 | ` 15 | 16 | const Global = ({ children, siteTitle }) => ( 17 | 18 | 19 | 22 | {children} 23 | 24 | 25 | ) 26 | 27 | Global.propTypes = { 28 | children: PropTypes.node.isRequired, 29 | siteTitle: PropTypes.string.isRequired, 30 | } 31 | 32 | export default Global 33 | -------------------------------------------------------------------------------- /website/src/components/HeaderNav.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'gatsby-link' 3 | import styled, { css } from 'styled-components' 4 | import GitHubIcon from 'react-icons/lib/fa/github' 5 | import TwitterIcon from 'react-icons/lib/fa/twitter' 6 | import media from '../styles/media' 7 | 8 | const Root = styled.nav` 9 | height: ${props => props.theme.headerHeight}px; 10 | display: flex; 11 | align-items: center; 12 | 13 | ${media.mobile` 14 | display: none; 15 | `} 16 | ` 17 | 18 | const itemStyle = css` 19 | text-decoration: none; 20 | color: #fff; 21 | cursor: pointer; 22 | text-decoration: none; 23 | font-size: 13px; 24 | font-weight: 700; 25 | text-transform: uppercase; 26 | padding: 7px 10px; 27 | display: block; 28 | line-height: 1em; 29 | ` 30 | 31 | const StyledLink = styled(Link)` 32 | ${itemStyle} 33 | ` 34 | 35 | const StyledA = styled.a` 36 | ${itemStyle} 37 | ` 38 | 39 | const DemoLink = styled(StyledA)` 40 | background: #fff; 41 | color: #13aae3; 42 | border-radius: 2px; 43 | margin-left: 10px; 44 | 45 | &:hover { 46 | background: #13aae3; 47 | color: #fff; 48 | } 49 | ` 50 | 51 | const HeaderNav = () => ( 52 | 53 | 54 | Docs 55 | 56 | 57 | How it works 58 | 59 | 60 | Extensions 61 | 62 | 63 | News 64 | 65 | 66 | FAQ 67 | 68 | 73 | 74 | 75 | 80 | 81 | 82 | 86 | demo 87 | 88 | 89 | ) 90 | 91 | export default HeaderNav 92 | -------------------------------------------------------------------------------- /website/src/components/HomeBanner.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'gatsby-link' 3 | import styled, { css } from 'styled-components' 4 | import Container from './Container' 5 | import media from '../styles/media' 6 | 7 | const Root = styled(Container)` 8 | text-align: center; 9 | color: #fff; 10 | 11 | ${media.mobile` 12 | & { 13 | padding: 20px 0; 14 | } 15 | `} 16 | 17 | ${media.tablet` 18 | & { 19 | padding: 40px 0; 20 | } 21 | `} 22 | 23 | ${media.desktop` 24 | & { 25 | padding: 50px 0; 26 | } 27 | `} 28 | ` 29 | 30 | const Intro = styled.p` 31 | margin-bottom: 30px; 32 | font-size: 18px; 33 | font-weight: 600; 34 | text-shadow: 0 1px 1px rgba(0, 0, 0, 0.35); 35 | ` 36 | 37 | const buttonCss = css` 38 | display: inline-block; 39 | background: #13aae3; 40 | color: #fff; 41 | font-size: 15px; 42 | text-decoration: none; 43 | margin: 0; 44 | border: 2px solid #fff; 45 | transition: background 0.2s, color 0.2s; 46 | text-align: center; 47 | font-weight: 700; 48 | text-transform: uppercase; 49 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15); 50 | 51 | &:hover { 52 | background: #fff; 53 | color: #13aae3; 54 | } 55 | 56 | ${media.mobile` 57 | & { 58 | width: 100%; 59 | padding: 7px 16px; 60 | } 61 | 62 | &:first-child { 63 | margin-bottom: 12px; 64 | } 65 | `} 66 | 67 | ${media.tablet` 68 | & { 69 | width: 47%; 70 | padding: 12px 16px; 71 | } 72 | 73 | &:first-child { 74 | margin-right: 4%; 75 | } 76 | `} 77 | 78 | ${media.desktop` 79 | & { 80 | width: 18%; 81 | padding: 12px 16px; 82 | } 83 | 84 | &:first-child { 85 | margin-right: 2%; 86 | } 87 | `} 88 | ` 89 | 90 | const DemoButton = styled.a` 91 | ${buttonCss} 92 | ` 93 | 94 | const DocsButton = styled(Link)` 95 | ${buttonCss} 96 | ` 97 | 98 | const HomeBanner = () => ( 99 | 100 | 101 | Mozaïk is a tool based on nodejs / react / d3 / stylus to easily craft beautiful dashboards. 102 | 103 |
104 | 105 | demo 106 | 107 | 108 | get started 109 | 110 |
111 |
112 | ) 113 | 114 | export default HomeBanner 115 | -------------------------------------------------------------------------------- /website/src/components/References.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Container from './Container' 4 | import media from '../styles/media' 5 | 6 | const Root = styled(Container)` 7 | background: ${props => props.theme.contentBackgroundHighlightColor}; 8 | border-top: 1px solid ${props => props.theme.borderColor}; 9 | border-bottom: 1px solid ${props => props.theme.borderColor}; 10 | padding-bottom: 25px; 11 | ` 12 | 13 | const Title = styled.h2` 14 | font-weight: 700; 15 | font-size: 22px; 16 | margin: 30px 0 30px 0; 17 | text-transform: uppercase; 18 | 19 | ${media.mobile` 20 | & { 21 | text-align: center; 22 | } 23 | `} 24 | ` 25 | 26 | const Wrapper = styled.div` 27 | display: flex; 28 | justify-content: space-between; 29 | align-items: stretch; 30 | 31 | ${media.mobile` 32 | & { 33 | flex-wrap: wrap; 34 | } 35 | `} 36 | 37 | ${media.tablet` 38 | & { 39 | flex-wrap: wrap; 40 | } 41 | `} 42 | 43 | ${media.desktop` 44 | & { 45 | flex-wrap: nowrap; 46 | } 47 | `} 48 | ` 49 | 50 | const Item = styled.a` 51 | display: flex; 52 | justify-content: center; 53 | align-items: center; 54 | margin-bottom: 15px; 55 | padding: 0 15px; 56 | height: 60px; 57 | 58 | & img { 59 | max-width: 100%; 60 | max-height: 100%; 61 | } 62 | 63 | ${media.mobile` 64 | & { 65 | width: 48%; 66 | } 67 | `} 68 | 69 | ${media.tablet` 70 | & { 71 | width: 31%; 72 | } 73 | `} 74 | 75 | ${media.desktop` 76 | & { 77 | width: 24%; 78 | } 79 | `} 80 | ` 81 | 82 | const References = ({ references, images }) => ( 83 | 84 | Who's using it 85 | 86 | {references.map(reference => { 87 | const image = images.find(i => i.base === reference.image) 88 | 89 | return ( 90 | 95 | {reference.label} 99 | 100 | ) 101 | })} 102 | 103 | 104 | ) 105 | 106 | export default References 107 | 108 | -------------------------------------------------------------------------------- /website/src/components/SecondaryNav.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Link from 'gatsby-link' 3 | import styled from 'styled-components' 4 | 5 | const Root = styled.aside` 6 | position: fixed; 7 | top: ${props => props.theme.headerHeight}px; 8 | width: ${props => props.theme.sidebarWidth}px; 9 | background: ${props => props.theme.contentBackgroundColor}; 10 | height: calc(100vh - ${props => props.theme.headerHeight}px); 11 | overflow-y: auto; 12 | padding-bottom: 20px; 13 | border-right: 1px solid ${props => props.theme.borderColor}; 14 | z-index: 2; 15 | 16 | &::-webkit-scrollbar { 17 | width: 6px; 18 | height: 6px; 19 | } 20 | &::-webkit-scrollbar-track { 21 | background: #bdd7f4; 22 | } 23 | &::-webkit-scrollbar-thumb { 24 | background: #08a0db; 25 | } 26 | ` 27 | 28 | const Title = styled.h3` 29 | margin: 20px 0 0; 30 | padding: 8px 20px 2px; 31 | font-size: 16px; 32 | text-transform: uppercase; 33 | font-weight: 400; 34 | color: ${props => props.theme.accentTextColor}; 35 | ` 36 | 37 | const StyledLink = styled(Link)` 38 | display: block; 39 | font-weight: 500; 40 | padding: 3px 20px; 41 | text-decoration: none; 42 | color: ${props => props.theme.textColor}; 43 | 44 | &.isActive { 45 | background: ${props => props.theme.contentBackgroundHighlightColor}; 46 | color: ${props => props.theme.accentTextColor}; 47 | } 48 | ` 49 | 50 | const SecondaryNav = ({ items }) => ( 51 | 52 | 66 | 67 | ) 68 | 69 | export default SecondaryNav 70 | -------------------------------------------------------------------------------- /website/src/components/Share.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | /* 5 | @media only screen and (min-width: 769px) { 6 | .Share { 7 | display: block; 8 | } 9 | } 10 | */ 11 | 12 | const Root = styled.section` 13 | background: #fff; 14 | display: none; 15 | text-align: center; 16 | border-bottom: 1px solid #ddd; 17 | ` 18 | 19 | const Inner = styled.div` 20 | max-width: 1200px; 21 | margin: 0 auto; 22 | padding: 12px 0; 23 | height: 44px; 24 | ` 25 | 26 | const Share = () => ( 27 | 28 | 29 | {/* GitHub stars */} 30 |