├── .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 | [](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 | 
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 }) =>
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 |
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 |
37 | {/* GitHub forks */}
38 |
45 |
46 |
47 | )
48 |
49 | export default Share
50 |
--------------------------------------------------------------------------------
/website/src/components/SubHeader.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 | box-shadow: 0 -1px 1px rgba(0,0,0,0.2) inset;
8 |
9 | ${media.mobile`
10 | & {
11 | padding: 30px 0 20px;
12 | }
13 | `}
14 |
15 | ${media.tablet`
16 | & {
17 | padding: 30px 0;
18 | }
19 | `}
20 |
21 | ${media.desktop`
22 | & {
23 | padding: 50px 0;
24 | }
25 | `}
26 | `
27 |
28 | const Title = styled.h1`
29 | color: #fff;
30 | font-family: 'Montserrat','Open Sans','Helvetica Neue',Helvetica,Arial,sans-serif;
31 | font-weight: 400;
32 | font-size: 28px;
33 | text-shadow: 0 1px 1px rgba(0,0,0,0.35);
34 | margin: 0;
35 | padding: 0;
36 | `
37 |
38 | const Description = styled.div`
39 | margin: 10px 0 0;
40 | color: #012939;
41 | `
42 |
43 | const SubHeader = ({ title, description, isDocPage }) => (
44 |
45 | {title}
46 | {description && {description}}
47 |
48 | )
49 |
50 | export default SubHeader
51 |
--------------------------------------------------------------------------------
/website/src/data/references.yml:
--------------------------------------------------------------------------------
1 | - image: jolicode.svg
2 | link: http://jolicode.com/
3 | label: JoliCode
4 |
5 | - image: canalplus.png
6 | link: http://canalplus.github.io/
7 | label: Canal+
8 |
9 | - image: aptus-health.png
10 | link: http://www.aptushealth.com/
11 | label: Aptus Health
12 |
13 | - image: sc5.png
14 | link: https://sc5.io/
15 | label: SC5
16 |
--------------------------------------------------------------------------------
/website/src/data/useCases.yml:
--------------------------------------------------------------------------------
1 | - title: Mozaïk meets Apache Spark, Rock-Paper-Scissors!
2 | link: https://developer.ibm.com/clouddataservices/2015/12/01/humans-vs-apache-spark-building-our-rock-paper-scissors-game/
3 |
4 | - title: JoliDashboard at Jolicode, serious geeks having fun with Mozaïk
5 | link: http://jolicode.com/blog/joliday-2015-avec-de-la-sciure-de-l-etain-et-des-lego
6 |
7 | - title: Six Open Source Dashboards to Organize Your Data
8 | link: http://www.astronomer.io/blog/six-open-source-dashboards
9 |
--------------------------------------------------------------------------------
/website/src/layouts/documentation.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Global from '../components/Global'
4 | import Header from '../components/Header'
5 | import Footer from '../components/Footer'
6 |
7 | const Layout = ({ children, data }) => (
8 |
9 |
13 | )
14 |
15 | Layout.propTypes = {
16 | children: PropTypes.func,
17 | }
18 |
19 | export default Layout
20 |
21 | export const query = graphql`
22 | query DocSiteTitleQuery {
23 | site {
24 | siteMetadata {
25 | title
26 | }
27 | }
28 | }
29 | `
30 |
--------------------------------------------------------------------------------
/website/src/layouts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Global from '../components/Global'
4 | import Header from '../components/Header'
5 | import Footer from '../components/Footer'
6 |
7 | const Layout = ({ children, data }) => (
8 |
9 |
10 | {children()}
11 |
12 |
13 | )
14 |
15 | Layout.propTypes = {
16 | children: PropTypes.func,
17 | }
18 |
19 | export default Layout
20 |
21 | export const query = graphql`
22 | query SiteTitleQuery {
23 | site {
24 | siteMetadata {
25 | title
26 | }
27 | }
28 | }
29 | `
30 |
31 |
--------------------------------------------------------------------------------
/website/src/layouts/post.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import Global from '../components/Global'
4 | import Header from '../components/Header'
5 | import Footer from '../components/Footer'
6 |
7 | const Layout = ({ children, data }) => (
8 |
9 |
13 | )
14 |
15 | Layout.propTypes = {
16 | children: PropTypes.func,
17 | }
18 |
19 | export default Layout
20 |
21 | export const query = graphql`
22 | query PostSiteTitleQuery {
23 | site {
24 | siteMetadata {
25 | title
26 | }
27 | }
28 | }
29 | `
30 |
--------------------------------------------------------------------------------
/website/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const NotFoundPage = () => (
4 |
5 |
NOT FOUND
6 |
You just hit a route that doesn't exist... the sadness.
7 |
8 | )
9 |
10 | export default NotFoundPage
11 |
--------------------------------------------------------------------------------
/website/src/pages/architecture.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import SubHeader from '../components/SubHeader'
3 |
4 | const Architecture = () => {
5 | return (
6 |
7 |
11 |
Architecture
12 |
13 | )
14 | }
15 |
16 | export default Architecture
17 |
--------------------------------------------------------------------------------
/website/src/pages/docs/quick-start/configuration.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Configuration
3 | description: Configure Mozaïk V2
4 | path: /docs/configuration
5 | position: 110
6 | ---
7 | **config.yml** is the main **Mozaïk** config, it's where you configure your dashboards.
8 | It should be located at the root of your main **Mozaïk** project directory.
9 |
10 | Let see how to define it:
11 |
12 | ``` yaml
13 | # ~/mozaik-demo/config.yml
14 |
15 | host: localhost # Mozaïk host
16 | port: 5000 # Mozaïk port
17 |
18 | # Mozaïk dashboard rotation interval (ms)
19 | rotationDuration: 8000
20 |
21 | dashboards:
22 | # first dashboard
23 | - columns: 4
24 | rows: 3
25 | widgets: []
26 |
27 | # second dashboard
28 | - columns: 3
29 | rows: 3
30 | widgets: []
31 | ```
32 |
--------------------------------------------------------------------------------
/website/src/pages/docs/quick-start/grid-system.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Grid system
3 | description: Configure Mozaïk V2 grid system
4 | path: /docs/grid-system
5 | position: 120
6 | ---
7 | As we saw previously in the [config.yml](/docs/configuration) section,
8 | **Mozaïk** provides a simple way to define dashboard layout.
9 | Let's see how it works.
10 |
11 |
12 | To configure a layout like this:
13 |
14 | ``` raw
15 | columns: 3
16 | +——————————————————+——————————————————+——————————————————+
17 | | | |
18 | | A -> x: 0 y: 0 | B -> x: 1 y: 0 |
19 | | columns: 1 | columns: 2 |
20 | | rows: 1 | rows: 1 |
21 | | | |
22 | +——————————————————+——————————————————+——————————————————+ rows: 2
23 | | | |
24 | | C -> x: 0 y: 1 | D -> x: 2 y: 1 |
25 | | columns: 2 | columns: 1 |
26 | | rows: 1 | rows: 1 |
27 | | | |
28 | +——————————————————+——————————————————+——————————————————+
29 |
30 | ```
31 |
32 | You should have the following config:
33 |
34 | ``` yaml
35 | # ~/mozaik-demo/config.yml
36 | dashboards:
37 | - columns: 3
38 | rows: 2
39 | widgets:
40 |
41 | # widget A
42 | - type: whatever
43 | columns: 1
44 | rows: 1
45 | x: 0
46 | y: 0
47 |
48 | # widget B
49 | - type: whatever
50 | columns: 2
51 | rows: 1
52 | x: 1
53 | y: 0
54 |
55 | # widget C
56 | - type: whatever
57 | columns: 2
58 | rows: 1
59 | x: 0
60 | y: 1
61 |
62 | # widget D
63 | - type: whatever
64 | columns: 1
65 | rows: 1
66 | x: 2
67 | y: 1
68 | ```
69 |
--------------------------------------------------------------------------------
/website/src/pages/docs/quick-start/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Getting started
3 | sectionTitle: Quick start
4 | description: Have Mozaïk up and running in minutes
5 | path: /docs
6 | position: 100
7 | ---
8 |
9 | This is the install guide for Mozaïk v2, be aware that this version
10 | is in **alpha stage** so you won't be able to use all extensions, you can check
11 | [extensions compatibility](/extensions).
12 |
13 | ## Sample Repo
14 |
15 | The easiest way to get started is by using the [demo dashboard](https://github.com/plouc/mozaik-demo).
16 | For now Mozaïk stable version is V1, in order to install V2 you must use the dedicated branch.
17 |
18 | ``` bash
19 | git clone git@github.com:plouc/mozaik-demo.git
20 | git checkout mozaik-2
21 | ```
22 |
23 | ### Install packages & publish assets
24 |
25 | ``` bash
26 | cd mozaik-demo
27 | # using npm
28 | npm install
29 | # or yarn
30 | yarn install
31 | ```
32 |
33 | If for some reason the install command fails, the asset generation should be skipped,
34 | you can re-run it with:
35 |
36 | ``` bash
37 | # using npm
38 | npm run build
39 | # or yarn
40 | yarn build
41 | ```
42 |
43 | ### Add github tokens in a `.env` file
44 |
45 | This step is optional, it's useful if you want to bypass github api rate limit.
46 | You have to generate a token for your dashboard from your GitHub account,
47 | and then set the appropriate var for `mozaik-ext-github` which is installed by default.
48 |
49 | ``` bash
50 | # ./.env
51 | GITHUB_API_TOKEN=xxxxx
52 | ```
53 |
54 | ### Run the app
55 |
56 | ```bash
57 | # using npm
58 | npm start
59 | # or yarn
60 | yarn start
61 | ```
62 |
63 | **Mozaïk** can be configured through a simple yaml config file `config.yml` located at the root folder.
64 | You should start from the default config file and
65 | [customize it to fit your needs](/docs/configuration).
66 |
--------------------------------------------------------------------------------
/website/src/pages/docs/v1/guides/client-poll-mode.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Extension client [poll mode]
3 | description: How to feed your hungry widgets by regularly fetching some external API data
4 | path: /docs/v1/guides/client-poll-mode
5 | position: 420
6 | ---
7 | If you didn't read the [intro](/docs/v1/guides/client/) on Mozaïk extension clients,
8 | then you should consider [reading it first](/docs/v1/guides/client/).
9 |
10 | When you register your extension's client to Mozaïk, you have the ability to pass a **mode**
11 | which determine how the data will be pulled from your external service to Mozaïk.
12 |
13 | `poll` mode is the default mode set when you register a client to Mozaïk.
14 |
15 | ## When using `poll` mode
16 |
17 | It's useful when you want your client to fetch data from an external service at a given interval.
18 |
19 | See [this page](/docs/v1/guides/client/) for usage.
20 |
--------------------------------------------------------------------------------
/website/src/pages/docs/v1/guides/client-push-mode.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Extension client [push mode]
3 | description: How to feed your hungry widgets in near real time
4 | path: /docs/v1/guides/client-push-mode
5 | position: 430
6 | ---
7 | If you didn't read the [intro](/docs/v1/guides/client/) on Mozaïk extension client,
8 | then you should consider [reading it first](/docs/v1/guides/client/).
9 |
10 | When you register your extension's client to Mozaïk, you have the ability to pass a **mode**
11 | which determine how the data will be pulled from your external service to Mozaïk.
12 |
13 | `push` must be specified when you register a client to Mozaïk.
14 |
15 | ## When using `push` mode
16 |
17 | It's useful when you communicate with an external service through websockets or if you want
18 | to connect Mozaïk to some sort of message queue.
19 |
20 | The main difference with `poll` mode is that Mozaïk is no more responsible for fetching the data
21 | at a given interval, your client acts as a producer and notify Mozaïk when it wants to push
22 | some fresh data to its bound widgets.
23 |
24 | ## How to use it
25 |
26 | {{< gist 5fc5d80aa74e4a6c3196a265602c509c >}}
--------------------------------------------------------------------------------
/website/src/pages/docs/v1/guides/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Anatomy of an extension
3 | sectionTitle: Guides (v1)
4 | description: What's the structure of a Mozaïk extension
5 | path: /docs/v1/extension-anatomy
6 | position: 400
7 | ---
8 | ## Directory layout
9 |
10 | You should use the following directory layout when developing a Mozaïk extension.
11 |
12 | ``` default
13 | mozaik-ext-awesome/ # root extension directory
14 | preview/ # put preview images in this directory, you can reference them in the README
15 | src/ # extension javascript code
16 | components/ # put all your extension's widgets in there
17 | WidgetA.jsx # a widget (React component)
18 | WidgetB.jsx # another one :)
19 | index.js # entry point to all your widgets
20 | client.js # extension's client
21 | config.js # define your extension's client configuration schema using convict
22 | styl/ # custom extension styles if you need some
23 | index.styl # this is the file that gets collected by Mozaïk
24 | test/ # of course :)
25 | .eslintrc # javascript linter configuration
26 | .babelrc # required for es6/jsx transiplation
27 | .npmignore # files to ignore when publishing your extension to npm
28 | .travis.yml # to automatically run your tests
29 | client.js # entry point to your extension's client
30 | package.json # dependencies, meta, scripts…
31 | README.md # extension description and documentation
32 | ```
33 |
34 |
--------------------------------------------------------------------------------
/website/src/pages/docs/v1/quick-start/configuration.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Configuration
3 | description: Configure Mozaïk
4 | path: /docs/v1/configuration
5 | position: 310
6 | ---
7 | **config.js** is the main **Mozaïk** config, it's where you configure your dashboards.
8 | It must be located at the root of your main **Mozaïk** project directory.
9 |
10 | Let see how to define it:
11 |
12 | ``` javascript
13 | // ~/mozaik-demo/config.js
14 |
15 | // because we use plain js file, the config object must be exported
16 | module.exports = {
17 | host: 'localhost', // Mozaïk host
18 | port: 5000, // Mozaïk port
19 |
20 | theme: 'night-blue', // Mozaïk theme to use
21 |
22 | rotationDuration: 8000, // Mozaïk dashboard rotation interval (ms)
23 |
24 | // Dashboards definition
25 | dashboards: [
26 | // first dashboard
27 | {
28 | // defines dashboard grid: 4 x 3
29 | columns: 4, rows: 3,
30 | },
31 |
32 | // a second dashboard
33 | {
34 | // add title
35 | title: 'My second Mozaïk dashboard',
36 | // defines dashboard grid: 5 x 4
37 | columns: 5, rows: 4,
38 | }
39 | ]
40 | };
41 | ```
42 |
--------------------------------------------------------------------------------
/website/src/pages/docs/v1/quick-start/grid-system.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Grid system
3 | description: Configure Mozaïk grid system
4 | path: /docs/v1/grid-system
5 | position: 320
6 | ---
7 | As we saw previously in the [config.js](/v1/use/config) section,
8 | **Mozaïk** provides a simple way to define dashboard layout. Let's see how it works.
9 |
10 |
11 | To configure a layout like this:
12 |
13 | ``` default
14 | columns: 3
15 | +——————————————————+——————————————————+——————————————————+
16 | | | |
17 | | A -> x: 0 y: 0 | B -> x: 1 y: 0 |
18 | | columns: 1 | columns: 2 |
19 | | rows: 1 | rows: 1 |
20 | | | |
21 | +——————————————————+——————————————————+——————————————————+ rows: 2
22 | | | |
23 | | C -> x: 0 y: 1 | D -> x: 2 y: 1 |
24 | | columns: 2 | columns: 1 |
25 | | rows: 1 | rows: 1 |
26 | | | |
27 | +——————————————————+——————————————————+——————————————————+
28 |
29 | ```
30 |
31 | You should have the following config:
32 |
33 | ``` javascript
34 | {
35 | //...
36 | dashboards: [
37 | {
38 | columns: 3, rows: 2,
39 | widgets: [
40 | {
41 | type: 'whatever',
42 | /* A */ columns: 1, rows: 1,
43 | x: 0, y: 0
44 | },
45 | {
46 | type: 'whatever',
47 | /* B */ columns: 2, rows: 1,
48 | x: 1, y: 0
49 | },
50 | {
51 | type: 'whatever',
52 | /* C */ columns: 2, rows: 1,
53 | x: 0, y: 1
54 | },
55 | {
56 | type: 'whatever',
57 | /* D */ columns: 1, rows: 1,
58 | x: 2, y: 1
59 | }
60 | ]
61 | }
62 | ]
63 | }
64 | ```
65 |
--------------------------------------------------------------------------------
/website/src/pages/docs/v1/quick-start/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Getting started
3 | sectionTitle: Quick start (v1)
4 | description: Have Mozaïk up and running in minutes
5 | path: /docs/v1
6 | position: 300
7 | ---
8 | ## Sample Repo
9 |
10 | The easiest way to get started is by using the [demo dashboard](https://github.com/plouc/mozaik-demo).
11 |
12 | ### Clone the repo
13 |
14 | ``` bash
15 | git clone git@github.com:plouc/mozaik-demo.git
16 | ```
17 |
18 | ### Install packages & publish assets
19 |
20 | ``` bash
21 | cd mozaik-demo
22 | npm install
23 | ```
24 |
25 | If for some reason the install command fails, the asset generation should be skipped,
26 | you can re-run it with:
27 |
28 | ``` bash
29 | npm run build
30 | ```
31 |
32 | ### Add github tokens in a `.env` file
33 |
34 | This step is optional, it's useful if you want to bypass github api rate limit.
35 | You have to generate a token for your dashboard from your GitHub account,
36 | and then set the appropriate var for `mozaik-ext-github` which is installed by default.
37 |
38 | ``` bash
39 | # ./.env
40 | GITHUB_API_TOKEN=xxxxx
41 | ```
42 |
43 | ### Run the app
44 |
45 | ``` bash
46 | node app.js
47 | ```
48 |
--------------------------------------------------------------------------------
/website/src/pages/docs/v1/quick-start/theming.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Theming
3 | description: Configure Mozaïk theme
4 | path: /docs/v1/theming
5 | position: 330
6 | ---
7 | ## Configuration
8 |
9 | You can set the theme you want with the `theme` key in the config file:
10 |
11 | ``` javascript
12 | {
13 | //…
14 | theme: 'yellow',
15 | //…
16 | }
17 | ```
18 | ### Available themes
19 |
20 | Mozaïk comes with 6 themes:
21 |
22 | - bordeau
23 | - light-grey
24 | - light-yellow
25 | - night-blue
26 | - snow
27 | - yellow
28 |
29 | Take a look at the theme gallery
30 |
31 | ## Creating a custom theme
32 |
33 | Mozaïk eases the creation of a custom theme by providing a bunch of customizable variables.
34 | To create your theme you'll have to follow this directory layout:
35 |
36 | ```
37 | your-mozaik-app/ # root Mozaïk app directory, if you used the demo repository, should be 'mozaik-demo'
38 | build/
39 | src/
40 | themes/ # custom themes directory
41 | my-theme/ # your theme, name must match config key
42 | _vars.styl # theme variables
43 | index.styl # theme overrides
44 | ```
45 |
46 | So, you have to create `themes` and `my-theme` directories and `_vars.styl`, `index.styl` files.
47 | Your theme can now be used by setting `theme` config key to **my-theme**.
48 |
49 | The theme compilation run in two phases:
50 |
51 | 1. loads Mozaïk core styles using the stylus variables defined in `_vars.styl`
52 | 2. loads overrides defined in `index.styl`
53 |
54 | You should take a look at [an existing theme](https://github.com/plouc/mozaik/tree/master/src/themes/night-blue) to see how it's built.
55 |
56 | ### _vars.styl
57 |
58 | As seen previously, this file contains variables availables in core Mozaïk styles, for an exhaustive list of them, see [this file](https://github.com/plouc/mozaik/blob/master/src/styl/__vars.styl).
59 | You don't have to set all variables, if one is not set, default value defined in [this file](https://github.com/plouc/mozaik/blob/master/src/styl/__vars.styl) will be used.
60 |
61 | ### index.styl
62 |
63 | This file contains overrides, it differs from `_vars.styl` because it's completely independent from Mozaïk core styles.
64 | If you want to customize a certain widget or if you don't find a variable which fit your needs, you should put your rules there.
65 |
--------------------------------------------------------------------------------
/website/src/pages/docs/v1/quick-start/widgets.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Widgets
3 | description: Configure Mozaïk widgets
4 | path: /docs/v1/widgets
5 | position: 340
6 | ---
7 | Widgets are the building blocks of a Mozaïk dashboard, a widget is in fact
8 | a react component which often communicates with an API.
9 | Mozaïk comes with just a few core widgets, others must be installed through extensions,
10 | see [available extensions](/extensions).
11 |
12 | Widgets share some common properties which are all required:
13 |
14 | | key | description |
15 | | ----------- | ----------------------------------- |
16 | | **type** | *the type of widget to instantiate* |
17 | | **x** | *x position* |
18 | | **y** | *y position* |
19 | | **columns** | *width expressed in columns* |
20 | | **rows** | *height expressed in rows* |
21 |
22 | Example:
23 |
24 | ``` javascript
25 | // ~/mozaik-demo/config.js
26 | {
27 | // …
28 | dashboards: [{
29 | columns: 3, rows: 2,
30 | widgets: [
31 | {
32 | type: 'ext.widget_type',
33 | columns: 1, rows: 1,
34 | x: 0, y: 0,
35 | },
36 | ],
37 | }],
38 | },
39 | ```
40 |
41 | *for properties related to position/size see [grid system]({{< relref "v1/use/grid.md" >}}).*
42 |
43 | ## Core widgets
44 |
45 | Mozaïk core extension provides an `inspector` widget, in order to use it,
46 | you should use this config:
47 |
48 | ``` javascript
49 | // ~/mozaik-demo/config.js
50 | {
51 | // …
52 | dashboards: [{
53 | columns: 3, rows: 2,
54 | widgets: [
55 | {
56 | type: 'mozaik.inspector',
57 | columns: 1, rows: 1,
58 | x: 0, y: 0,
59 | },
60 | ],
61 | }],
62 | },
63 | ```
64 |
--------------------------------------------------------------------------------
/website/src/pages/extensions/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import Container from '../../components/Container'
4 | import SubHeader from '../../components/SubHeader'
5 | import Extensions from '../../components/Extensions'
6 |
7 | const Root = styled(Container)`
8 | padding: 30px 0;
9 | background: ${props => props.theme.contentBackgroundColor};
10 | `
11 |
12 | const Intro = styled.div`
13 | text-align: left;
14 | font-weight: 700;
15 | padding-left: 20px;
16 | border-left: 3px solid #71cff2;
17 | `
18 |
19 | const ExtensionsPage = ({ data }) => {
20 | const extensions = data.allExtensionsYaml.edges.map(({ node }) => node)
21 |
22 | return (
23 |
24 |
28 |
29 |
30 | Mozaïk widgets are maintained as separate modules,
31 | available as mozaik-ext-EXT_NAME
npm packages.
32 |
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | export default ExtensionsPage
40 |
41 | export const query = graphql`
42 | query ExtensionsQuery {
43 | allExtensionsYaml {
44 | edges {
45 | node {
46 | name
47 | description
48 | link
49 | demo
50 | travis
51 | compat
52 | tags
53 | }
54 | }
55 | }
56 | }
57 | `
58 |
--------------------------------------------------------------------------------
/website/src/pages/faq.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import SubHeader from '../components/SubHeader'
3 |
4 | const FAQ = () => {
5 | return (
6 |
7 |
11 |
FAQ
12 |
13 | )
14 | }
15 |
16 | export default FAQ
17 |
--------------------------------------------------------------------------------
/website/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import HomeBanner from '../components/HomeBanner'
3 | import Share from '../components/Share'
4 | import Features from '../components/Features'
5 | import References from '../components/References'
6 | import ExternalPosts from '../components/ExternalPosts'
7 |
8 | const IndexPage = props => {
9 | const references = props.data.allReferencesYaml.edges.map(({ node }) => node)
10 | const refImages = props.data.refImages.edges.map(({ node }) => node)
11 | const externalPosts = props.data.allUseCasesYaml.edges.map(({ node }) => node)
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | export default IndexPage
25 |
26 | export const query = graphql`
27 | query IndexQuery {
28 | allReferencesYaml {
29 | edges {
30 | node {
31 | image
32 | label
33 | link
34 | }
35 | }
36 | }
37 |
38 | allUseCasesYaml {
39 | edges {
40 | node {
41 | title
42 | link
43 | }
44 | }
45 | }
46 |
47 | refImages: allFile(filter: { relativePath: { regex: "/assets/refs/*/" } }) {
48 | edges {
49 | node {
50 | base
51 | publicURL
52 | }
53 | }
54 | }
55 | }
56 | `
--------------------------------------------------------------------------------
/website/src/pages/posts/mozaik-1.0.10.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Mozaïk 1.0.10 Released
3 | date: 2016-01-15
4 | ---
5 | Mozaïk 1.0.10 was released, brings you small bug fixes for server port/host settings.
6 | You can check detail on [GitHub](https://github.com/plouc/mozaik/releases/tag/v1.0.10).
7 |
--------------------------------------------------------------------------------
/website/src/pages/posts/mozaik-1.0.13.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Mozaïk 1.0.13 Released
3 | date: 2016-04-02
4 | ---
5 | Mozaïk 1.0.13 was released, brings you small bug fixes plus ability to set an optional title for your dashboards.
6 | You can check all the details on [GitHub](https://github.com/plouc/mozaik/releases/tag/v1.0.13).
7 |
--------------------------------------------------------------------------------
/website/src/pages/posts/mozaik-1.2.0.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Mozaïk 1.2.0 Released
3 | date: 2016-04-06
4 | ---
5 | Mozaïk 1.2.0 was released, some internals changes and a brand new widget!
6 |
7 | So there's now a new `mozaik.inspector` widget:
8 |
9 | 
10 |
11 | To use it:
12 |
13 | ```javascript
14 | {
15 | type: 'mozaik.inspector',
16 | columns: 1, rows: 1,
17 | x: 0, y: 0
18 | }
19 | ```
20 |
21 | You can check detail on [GitHub](https://github.com/plouc/mozaik/releases/tag/v1.2.0).
22 |
--------------------------------------------------------------------------------
/website/src/pages/posts/mozaik-1.3.0.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Mozaïk 1.3.0 Released
3 | date: 2016-04-09
4 | ---
5 | Mozaïk **1.3.0** was released, shipping a **critical feature**:
6 | support for `push` mode, it solves [an issue](https://github.com/plouc/mozaik/issues/34)
7 | which have been there for far too long.
8 |
9 | Before, Mozaïk just provided a polling model, which worked nicely for most of the cases.
10 |
11 | However, some users wanted to push data from **live streams**, for example another WebSocket
12 | or a Message queue, but prior to this version, you can not decide when to send data to Mozaïk,
13 | you can only provide some endpoints to be called at a given interval.
14 |
15 | So, now you have the ability to **decide when to push data**,
16 | more details on this [here]({{< relref "v1/hack/client-push-mode.md" >}}).
17 |
18 | As usual, **release notes** are available on [GitHub](https://github.com/plouc/mozaik/releases/tag/v1.3.0).
19 |
--------------------------------------------------------------------------------
/website/src/pages/posts/mozaik-saucelabs-extensions.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Saucelabs widgets for Mozaïk
3 | description: Say Hi to the new SauceLabs extension for Mozaïk!
4 | date: 2016-03-19
5 | ---
6 | This one is from [iamfiscus](https://github.com/iamfiscus), it comes with 2 widgets at the time I'm writing,
7 | one to show the current SauceLabs status from [status.saucelabs.com](http://status.saucelabs.com/) and another one showing a list of your jobs with extra info.
8 |
9 | 
10 |
11 | Have a look at the [repo's README](https://github.com/iamfiscus/mozaik-ext-saucelabs/blob/master/README.md) for instructions/details.
12 |
13 | Thanks [@iamfiscus](https://github.com/iamfiscus)!
14 |
--------------------------------------------------------------------------------
/website/src/pages/posts/mozaik-v2.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Mozaïk v2
3 | date: 2016-12-12
4 | ---
5 | I'm currently working on Mozaïk v2, at the time of writing, it's in alpha stage.
6 | This release will contains a lot of breaking changes, but let's see why and how
7 | it will differ from Mozaïk v1.
8 |
9 | ## Improving DX
10 |
11 | When working with Mozaïk v1, some people legitimately complained about the constrain
12 | of having to restart the server each time they wanted to change the config file: widgets,
13 | theme… Mozaïk v2 now supports hot reloading and watcher on config file.
14 |
15 | You'll find the corresponding issue [here](https://github.com/plouc/mozaik/issues/97).
16 |
17 | ## Ease extension authoring
18 |
19 | Developing Mozaïk extensions was really painful, no guide, tricky workflow, thanks to
20 | hot reloading and migration to [redux](http://redux.js.org/), it's now easier to develop
21 | a custom extension and debug it.
22 |
23 | I'm also planing to add an in depth guide on how to craft a Mozaïk extension for v2,
24 | so stay tuned!
25 |
26 | The usage of React components mixin has been completely removed, which makes easier
27 | to code your component following the current React.js best practices and ease
28 | extensions testing.
29 |
30 | ## Modules upgrade/replacement
31 |
32 | [reflux](https://github.com/reflux/refluxjs) is a great library,
33 | but [redux](http://redux.js.org/) has became the de facto state management
34 | library for React, and its ecosystem is really complete.
35 |
36 | `react` and `react-dom` have also been upgraded, which means you should be
37 | able to use recent libraries and third party components.
38 |
39 | Some people had problem with [ws](https://github.com/websockets/ws) module,
40 | while it offers great performance and did the job, Mozaïk v2 uses
41 | [socket.io](http://socket.io/) which is widely adopted and has already been
42 | used for many projects, it also provide higher abstractions, meaning less
43 | code to maintain and a more robust implementation.
44 |
45 | ## Contributing
46 |
47 | If you're interested in contributing to Mozaïk v2, check the dedicated
48 | [milestone](https://github.com/plouc/mozaik/milestone/2) and don't hesitate
49 | to contact me.
50 |
51 | If you're an extension maintainer, you should have a look at the
52 | [migrated extensions](https://github.com/plouc/mozaik/issues/98)
53 | to understand how to make it works with Mozaïk v2.
54 |
55 |
--------------------------------------------------------------------------------
/website/src/pages/posts/mozaik-website-launched.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Mozaïk website launched
3 | date: 2015-08-24
4 | ---
5 | After some weeks of work, it's finally here!
6 | This website is built with hexo, a great static site generator written in nodejs.
7 |
--------------------------------------------------------------------------------
/website/src/styles/base.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: sans-serif;
3 | -ms-text-size-adjust: 100%;
4 | -webkit-text-size-adjust: 100%;
5 | text-rendering: optimizeLegibility;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | body {
11 | margin: 0;
12 | position: relative;
13 | background-color: #13aae3;
14 | background-image: url("../assets/mozaik-pattern.png");
15 | background-size: 255px 128px;
16 | padding-top: 50px;
17 | }
18 |
19 | *, *:before, *:after {
20 | box-sizing: border-box;
21 | }
22 |
--------------------------------------------------------------------------------
/website/src/styles/index.css:
--------------------------------------------------------------------------------
1 | @import "./base.css";
2 | @import "./prism.css";
3 |
--------------------------------------------------------------------------------
/website/src/styles/media.js:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components'
2 |
3 | export default {
4 | mobile: (...args) => css`
5 | @media screen and (max-width: 768px) {
6 | ${css(...args)}
7 | }
8 | `,
9 | tablet: (...args) => css`
10 | @media screen and (min-width: 480px) {
11 | ${css(...args)}
12 | }
13 | `,
14 | desktop: (...args) => css`
15 | @media screen and (min-width: 769px) {
16 | ${css(...args)}
17 | }
18 | `
19 | }
--------------------------------------------------------------------------------
/website/src/styles/prism.css:
--------------------------------------------------------------------------------
1 | /* PrismJS 1.14.0
2 | https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript */
3 | /**
4 | * prism.js tomorrow night eighties for JavaScript, CoffeeScript, CSS and HTML
5 | * Based on https://github.com/chriskempson/tomorrow-theme
6 | * @author Rose Pritchard
7 | */
8 |
9 | code[class*="language-"],
10 | pre[class*="language-"] {
11 | color: #ccc;
12 | background: none;
13 | font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
14 | text-align: left;
15 | white-space: pre;
16 | word-spacing: normal;
17 | word-break: normal;
18 | word-wrap: normal;
19 | font-size: 14px;
20 | line-height: 1.5;
21 |
22 | -moz-tab-size: 4;
23 | -o-tab-size: 4;
24 | tab-size: 4;
25 |
26 | -webkit-hyphens: none;
27 | -moz-hyphens: none;
28 | -ms-hyphens: none;
29 | hyphens: none;
30 |
31 | }
32 |
33 | /* Code blocks */
34 | pre[class*="language-"] {
35 | padding: 1em;
36 | margin: .5em 0;
37 | overflow: auto;
38 | }
39 |
40 | :not(pre) > code[class*="language-"],
41 | pre[class*="language-"] {
42 | background: #2d2d2d;
43 | }
44 |
45 | /* Inline code */
46 | :not(pre) > code[class*="language-"] {
47 | padding: .1em;
48 | border-radius: .3em;
49 | white-space: normal;
50 | }
51 |
52 | .token.comment,
53 | .token.block-comment,
54 | .token.prolog,
55 | .token.doctype,
56 | .token.cdata {
57 | color: #999;
58 | }
59 |
60 | .token.punctuation {
61 | color: #ccc;
62 | }
63 |
64 | .token.tag,
65 | .token.attr-name,
66 | .token.namespace,
67 | .token.deleted {
68 | color: #e2777a;
69 | }
70 |
71 | .token.function-name {
72 | color: #6196cc;
73 | }
74 |
75 | .token.boolean,
76 | .token.number,
77 | .token.function {
78 | color: #f08d49;
79 | }
80 |
81 | .token.property,
82 | .token.class-name,
83 | .token.constant,
84 | .token.symbol {
85 | color: #f8c555;
86 | }
87 |
88 | .token.selector,
89 | .token.important,
90 | .token.atrule,
91 | .token.keyword,
92 | .token.builtin {
93 | color: #cc99cd;
94 | }
95 |
96 | .token.string,
97 | .token.char,
98 | .token.attr-value,
99 | .token.regex,
100 | .token.variable {
101 | color: #7ec699;
102 | }
103 |
104 | .token.operator,
105 | .token.entity,
106 | .token.url {
107 | color: #67cdcc;
108 | }
109 |
110 | .token.important,
111 | .token.bold {
112 | font-weight: bold;
113 | }
114 | .token.italic {
115 | font-style: italic;
116 | }
117 |
118 | .token.entity {
119 | cursor: help;
120 | }
121 |
122 | .token.inserted {
123 | color: green;
124 | }
125 |
126 |
--------------------------------------------------------------------------------
/website/src/styles/theme.js:
--------------------------------------------------------------------------------
1 | export default {
2 | fontFamily: `-apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'`,
3 | fontSize: '16px',
4 | lineHeight: '1.8em',
5 |
6 | textColor: '#333333',
7 | accentTextColor: '#08a0db',
8 | contentBackgroundColor: '#f5f5f5',
9 | contentBackgroundHighlightColor: '#ffffff',
10 | borderColor: '#dddddd',
11 |
12 | // dimensions
13 | desktopMaxWidth: 1200,
14 | desktopDocMaxWidth: 900,
15 | headerHeight: 60,
16 | sidebarWidth: 300,
17 | }
--------------------------------------------------------------------------------
/website/src/templates/documentation.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Helmet from 'react-helmet'
3 | import styled from 'styled-components'
4 | import SubHeader from '../components/SubHeader'
5 | import SecondaryNav from '../components/SecondaryNav'
6 | import Pager from '../components/Pager'
7 | import Container from '../components/Container'
8 | import GeneratedContent from '../components/GeneratedContent'
9 |
10 | const Wrapper = styled.div`
11 | padding-left: ${props => props.theme.sidebarWidth}px;
12 | `
13 |
14 | const Background = styled(Container)`
15 | background: ${props => props.theme.contentBackgroundColor};
16 | display: flex;
17 | `
18 |
19 | const DocumentationPage = props => {
20 | const {
21 | data: {
22 | markdownRemark: {
23 | frontmatter,
24 | html
25 | }
26 | },
27 | pathContext: {
28 | navigation
29 | },
30 | pageResources: {
31 | page
32 | }
33 | } = props
34 |
35 | return (
36 |
37 |
46 |
47 |
52 |
53 |
56 |
57 |
61 |
62 |
63 |
64 | )
65 | }
66 |
67 | export default DocumentationPage
68 |
69 | export const pageQuery = graphql`
70 | query DocumentationPageByPath($path: String!) {
71 | markdownRemark(frontmatter: { path: { eq: $path } }) {
72 | html
73 | frontmatter {
74 | title
75 | description
76 | }
77 | }
78 | }
79 | `
--------------------------------------------------------------------------------
/website/src/templates/post.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Helmet from 'react-helmet'
3 | import styled from 'styled-components'
4 | import SubHeader from '../components/SubHeader'
5 | import SecondaryNav from '../components/SecondaryNav'
6 | import Pager from '../components/Pager'
7 | import Container from '../components/Container'
8 | import GeneratedContent from '../components/GeneratedContent'
9 |
10 | const Wrapper = styled.div`
11 | padding-left: ${props => props.theme.sidebarWidth}px;
12 | `
13 |
14 | const Background = styled(Container)`
15 | background: ${props => props.theme.contentBackgroundColor};
16 | display: flex;
17 | `
18 |
19 | const PostPage = props => {
20 | const {
21 | data: {
22 | markdownRemark: {
23 | frontmatter,
24 | html
25 | }
26 | },
27 | pathContext: {
28 | allPosts
29 | },
30 | pageResources: {
31 | page
32 | }
33 | } = props
34 |
35 | return (
36 |
37 |
46 |
47 |
52 |
53 |
56 |
57 |
61 |
62 |
63 |
64 | )
65 | }
66 |
67 | export default PostPage
68 |
69 | export const pageQuery = graphql`
70 | query PostPageByDate($date: String!) {
71 | markdownRemark(frontmatter: { date: { glob: $date } }) {
72 | html
73 | frontmatter {
74 | title
75 | description
76 | }
77 | }
78 | }
79 | `
--------------------------------------------------------------------------------
/website/static/CNAME:
--------------------------------------------------------------------------------
1 | mozaik.rocks
2 |
--------------------------------------------------------------------------------
/website/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/plouc/mozaik/5fc9070d9c3aeb1c53ef1719daa3a7239c23a31b/website/static/favicon.ico
--------------------------------------------------------------------------------