├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── .stylintrc ├── LICENSE ├── README.md ├── babel.config.js ├── docs ├── IntegrationExample.vue └── integrationExampleNative.html ├── index.js ├── misc ├── .gitkeep ├── logo.png └── screenshot.png ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── grid-9px-light.png ├── icons │ ├── apple-icon-152x152.png │ ├── apple-touch-icon-114x114.png │ ├── apple-touch-icon-120x120.png │ ├── apple-touch-icon-144x144.png │ ├── apple-touch-icon-152x152.png │ ├── apple-touch-icon-57x57.png │ ├── apple-touch-icon-60x60.png │ ├── apple-touch-icon-72x72.png │ ├── apple-touch-icon-76x76.png │ ├── favicon-128x128.png │ ├── favicon-16x16.png │ ├── favicon-196x196.png │ ├── favicon-32x32.png │ ├── favicon-96x96.png │ ├── icon-128x128.png │ ├── icon-192x192.png │ ├── icon-256x256.png │ ├── icon-384x384.png │ ├── icon-512x512.png │ ├── ms-icon-144x144.png │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ └── mstile-70x70.png └── mqtttiles-logo.png ├── quasar.conf.js ├── src-pwa ├── custom-service-worker.js ├── pwa-flag.d.ts └── register-service-worker.js └── src ├── App.vue ├── assets └── sad.svg ├── boot ├── .gitkeep ├── async-mqtt.js ├── i18n.js ├── icomoon.js ├── integrationBus.js └── prism.js ├── components ├── .gitkeep ├── Board.vue ├── BoardInfoDialog.vue ├── BoardSettings.vue ├── BoardVariable.vue ├── Boards.vue ├── ClientSettings.vue ├── CopyReplaceDialog.vue ├── Dash.vue ├── ImportExportModal.vue ├── Integration.vue ├── RemoteBoards.vue ├── ShareWizard.vue ├── global │ ├── register.js │ └── typed-input.vue ├── selectors │ ├── Selectors.vue │ ├── async.js │ └── config.json └── widgets │ ├── JsonTree.vue │ ├── Markdown.vue │ ├── Settings.vue │ ├── TextView.vue │ ├── Topic.vue │ ├── VariablesHelper.vue │ ├── WidgetWrapper.vue │ ├── calculator │ ├── Schema.vue │ ├── View.vue │ └── getTopics.js │ ├── clicker │ ├── Schema.vue │ ├── View.vue │ ├── getTopics.js │ └── migrations.js │ ├── color │ ├── Schema.vue │ ├── View.vue │ ├── constants.js │ └── getTopics.js │ ├── complex │ ├── AddMenu.vue │ ├── Knob.vue │ ├── Preview.vue │ ├── Progress.vue │ ├── Schema.vue │ ├── Text.vue │ ├── View.vue │ └── getTopics.js │ ├── frame │ ├── Schema.vue │ ├── View.vue │ ├── constants.js │ ├── getTopics.js │ └── migrations.js │ ├── getTopics.js │ ├── informer │ ├── Schema.vue │ ├── View.vue │ └── getTopics.js │ ├── linear │ ├── LinearGauge.vue │ ├── Schema.vue │ ├── View.vue │ └── getTopics.js │ ├── mapDevices │ ├── Schema.vue │ ├── View.vue │ ├── getTopics.js │ ├── messagesProcessing.js │ └── migrations.js │ ├── mapLocation │ ├── Schema.vue │ ├── View.vue │ ├── getTopics.js │ └── migrations.js │ ├── mapRoute │ ├── Schema.vue │ ├── View.vue │ ├── getTopics.js │ └── migrations.js │ ├── messagesProcessing.js │ ├── migrations.js │ ├── multiInformer │ ├── Schema.vue │ ├── View.vue │ └── getTopics.js │ ├── multiplier │ ├── Preview.vue │ ├── Schema.vue │ ├── View.vue │ ├── constants.js │ ├── getActionTopics.js │ ├── getTopics.js │ ├── messagesProcessing.js │ └── migration.js │ ├── radial │ ├── RadialGauge.vue │ ├── Schema.vue │ ├── View.vue │ └── getTopics.js │ ├── scheme │ ├── Preview.vue │ ├── Schema.vue │ ├── StaticTextSchema.vue │ ├── StaticTextView.vue │ ├── StatusSchema.vue │ ├── StatusView.vue │ ├── TextSchema.vue │ ├── TextView.vue │ ├── ToggleSchema.vue │ ├── ToggleView.vue │ ├── View.vue │ ├── activeMixin.js │ ├── constants.js │ ├── getTopics.js │ └── migrations.js │ ├── singleselect │ ├── Schema.vue │ ├── View.vue │ ├── constants.js │ └── getTopics.js │ ├── slider │ ├── Schema.vue │ ├── View.vue │ └── getTopics.js │ ├── staticInformer │ ├── Schema.vue │ └── View.vue │ ├── statusIndicator │ ├── Schema.vue │ ├── View.vue │ ├── constants.js │ ├── getActionTopics.js │ └── getTopics.js │ ├── switcher │ ├── Schema.vue │ ├── View.vue │ ├── constants.js │ ├── getTopics.js │ └── migrations.js │ └── textSender │ ├── Schema.vue │ ├── View.vue │ └── getTopics.js ├── constants ├── defaultes.js ├── index.js └── variablesPresets.js ├── css ├── app.styl ├── fonts │ ├── icomoon.eot │ ├── icomoon.svg │ ├── icomoon.ttf │ └── icomoon.woff ├── icomoon.css └── quasar.variables.styl ├── i18n ├── en-us │ └── index.js └── index.js ├── index.template.html ├── layouts └── MyLayout.vue ├── mixins ├── boardsMigrations.js ├── formatValue.js ├── getValueByTopic.js ├── timestamp.js └── validateTopic.js ├── pages ├── Error404.vue └── Index.vue └── router ├── index.js └── routes.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /src-bex/www 3 | /src-capacitor 4 | /src-cordova 5 | /.quasar 6 | /node_modules 7 | 8 | .eslintrc.js 9 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy 3 | // This option interrupts the configuration hierarchy at this file 4 | // Remove this if you have an higher level ESLint config file (it usually happens into a monorepos) 5 | root: true, 6 | 7 | parserOptions: { 8 | parser: 'babel-eslint', 9 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features 10 | sourceType: 'module' // Allows for the use of imports 11 | }, 12 | 13 | env: { 14 | browser: true 15 | }, 16 | 17 | // Rules order is important, please avoid shuffling them 18 | extends: [ 19 | // Base ESLint recommended rules 20 | // 'eslint:recommended', 21 | 22 | 23 | // Uncomment any of the lines below to choose desired strictness, 24 | // but leave only one uncommented! 25 | // See https://eslint.vuejs.org/rules/#available-rules 26 | 'plugin:vue/essential', // Priority A: Essential (Error Prevention) 27 | // 'plugin:vue/strongly-recommended', // Priority B: Strongly Recommended (Improving Readability) 28 | // 'plugin:vue/recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) 29 | 30 | // https://github.com/prettier/eslint-config-prettier#installation 31 | // usage with Prettier, provided by 'eslint-config-prettier'. 32 | 'prettier' 33 | ], 34 | 35 | plugins: [ 36 | // https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-file 37 | // required to lint *.vue files 38 | 'vue', 39 | 40 | // https://github.com/typescript-eslint/typescript-eslint/issues/389#issuecomment-509292674 41 | // Prettier has not been included as plugin to avoid performance impact 42 | // add it as an extension for your IDE 43 | ], 44 | 45 | globals: { 46 | ga: 'readonly', // Google Analytics 47 | cordova: 'readonly', 48 | __statics: 'readonly', 49 | process: 'readonly', 50 | Capacitor: 'readonly', 51 | chrome: 'readonly' 52 | }, 53 | 54 | // add your custom rules here 55 | rules: { 56 | 'prefer-promise-reject-errors': 'off', 57 | 58 | 59 | // allow debugger during development only 60 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .quasar 2 | .DS_Store 3 | .thumbs.db 4 | node_modules 5 | /dist 6 | /src-cordova/node_modules 7 | /src-cordova/platforms 8 | /src-cordova/plugins 9 | /src-cordova/www 10 | /src-bex/www 11 | /src-bex/js/core 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | -------------------------------------------------------------------------------- /.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | plugins: [ 5 | // to edit target browsers: use "browserslist" field in package.json 6 | require('autoprefixer') 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /.stylintrc: -------------------------------------------------------------------------------- 1 | { 2 | "blocks": "never", 3 | "brackets": "never", 4 | "colons": "never", 5 | "colors": "always", 6 | "commaSpace": "always", 7 | "commentSpace": "always", 8 | "cssLiteral": "never", 9 | "depthLimit": false, 10 | "duplicates": true, 11 | "efficient": "always", 12 | "extendPref": false, 13 | "globalDupe": true, 14 | "indentPref": 2, 15 | "leadingZero": "never", 16 | "maxErrors": false, 17 | "maxWarnings": false, 18 | "mixed": false, 19 | "namingConvention": false, 20 | "namingConventionStrict": false, 21 | "none": "never", 22 | "noImportant": false, 23 | "parenSpace": "never", 24 | "placeholder": false, 25 | "prefixVarsWithDollar": "always", 26 | "quotePref": "single", 27 | "semicolons": "never", 28 | "sortOrder": false, 29 | "stackedProperties": "never", 30 | "trailingWhitespace": "never", 31 | "universal": "never", 32 | "valid": true, 33 | "zeroUnits": "never", 34 | "zIndexNormalize": false 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 flespi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MQTTTiles 2 | ![Logo](/misc/logo.png?raw=true "MQTTTiles logo") 3 | > Dashboard based on MQTT. Supports MQTT 5.0 and 3.1.X protocols, connections to multiple brokers, multiple subscribe widgets. Saves configuration in browser's local cache or in retain message on broker. 4 | 5 | > Live version available here: [MQTTTiles](https://mqtttiles.flespi.io). 6 | 7 | ![Screenshot](/misc/screenshot.png?raw=true "MQTTTiles") 8 | 9 | ## Features 10 | * ES6 Javascript 11 | * Vue.js ([Quasar](http://quasar-framework.org)) 12 | * Writing .vue files 13 | * Vuex 14 | * Webpack 15 | * Responsive layout 16 | * NPM ecosystems 17 | * Material theme 18 | * Dev Hot Reload 19 | * and many more! 20 | 21 | ## Prerequisites: 22 | 23 | - [Node.js](https://nodejs.org/en/) (>=6.x) 24 | - [Quasar](http://quasar-framework.org) (>=1.5.x) 25 | - npm version 3+ and [Git](https://git-scm.com/). 26 | 27 | ## Build Setup 28 | 29 | ``` bash 30 | # clone the repo 31 | $ git clone https://github.com/flespi-software/mqtttiles.git mqtttiles 32 | 33 | # go into app's directory and install dependencies 34 | $ cd mqtttiles 35 | $ npm install 36 | 37 | # serve with hot reload at localhost:8080 38 | $ quasar dev 39 | 40 | # build for production with minification for flespi.io 41 | $ quasar build 42 | ``` 43 | 44 | ## Use like component 45 | You must have a Quasar-based app. 46 | ```bash 47 | # install like repo 48 | $ npm install git+https://git.gurtam.net/flespi/frontend/MQTTTiles.git 49 | ``` 50 | in quasar.conf.js 51 | ```js 52 | framework: { 53 | components: [ 54 | 'QIcon', 55 | 'QToolbar', 56 | 'QToolbarTitle', 57 | 'QModal', 58 | 'QModalLayout', 59 | 'QList', 60 | 'QListHeader', 61 | 'QItem', 62 | 'QItemMain', 63 | 'QItemSide', 64 | 'QItemTile', 65 | 'QItemSeparator', 66 | 'QBtn', 67 | 'QInput', 68 | 'QSelect', 69 | 'QCheckbox', 70 | 'QTooltip', 71 | 'QPopover', 72 | 'QCard', 73 | 'QCardTitle', 74 | 'QCardMain', 75 | 'QCardSeparator', 76 | 'QCardActions', 77 | 'QCardMedia', 78 | 'QToggle', 79 | 'QChip', 80 | 'QBtnToggle', 81 | 'QChipsInput', 82 | 'QField', 83 | 'QResizeObservable', 84 | 'QCollapsible', 85 | 'QRadio', 86 | 'QDialog', 87 | 'QProgress', 88 | 'QKnob', 89 | 'QSlider', 90 | 'QColorPicker', 91 | 'QBtnDropdown', 92 | 'QPagination' 93 | ], 94 | directives: [ 95 | 'Ripple', 96 | 'CloseOverlay' 97 | ], 98 | plugins: [ 99 | 'Notify', 100 | 'LocalStorage', 101 | 'SessionStorage', 102 | 'Dialog', 103 | 'Loading' 104 | ] 105 | } 106 | ``` 107 | ```js 108 | import Dash from 'mqtttiles' 109 | 110 | export default { 111 | components: { Dash } 112 | } 113 | ``` 114 | ```html 115 | 122 | ``` 123 | Props: 124 | 125 | | Name | Description | Default | 126 | |---|---|---| 127 | | clientSettings | Connection client settings | undefined | 128 | | initBoards | Your saved boards | undefined | 129 | 130 | clientSettings structure: 131 | ```js 132 | let clientSettings = { 133 | clientName: 'New client', 134 | clientId: `mqtt-tiles-${Math.random().toString(16).substr(2, 8)}`, 135 | host: 'wss://mqtt.flespi.io', 136 | keepalive: 60, 137 | protocolVersion: 5, 138 | clean: true, 139 | username: 'FlespiToken XXXXXXXXXXXXXXXXXXX', 140 | password: '', 141 | properties: { 142 | sessionExpiryInterval: undefined 143 | }, 144 | syncToRetain: false, 145 | syncNamespace: 'xflespifront/mqtttiles/boards' 146 | } 147 | ``` 148 | 149 | Events: 150 | 151 | | Name | Description | Payload | 152 | |---|---|---| 153 | | change:status | connect client status changes | status: | 154 | | share | share board | | 155 | | change:title | generated title | `{active-board-name} - MQTT Tiles`: | 156 | | update:boards | updated boards configuration | boards collection: | 157 | 158 | share model structure: 159 | ```js 160 | let shareModel = { 161 | boardId: "XXXXXXX-XXXXX-XXXX-XXXX-XXXXXXXXXX" 162 | token: "FlespiToken XXXXXXXXXXXXXXXXXXXXXXXXXXXX" 163 | topic: "topic/to/sync/data", 164 | share: [] 165 | } 166 | ``` 167 | 168 | ## License 169 | [MIT](https://github.com/flespi-software/mqtttiles/blob/master/LICENSE) license. 170 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@quasar/babel-preset-app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /docs/IntegrationExample.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 84 | -------------------------------------------------------------------------------- /docs/integrationExampleNative.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Example 7 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import Dash from './src/components/Dash.vue' 2 | import Integration from './src/components/Integration.vue' 3 | 4 | export { 5 | Integration, 6 | Dash 7 | } 8 | 9 | export default Dash 10 | -------------------------------------------------------------------------------- /misc/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/misc/.gitkeep -------------------------------------------------------------------------------- /misc/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/misc/logo.png -------------------------------------------------------------------------------- /misc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/misc/screenshot.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mqtttiles", 3 | "version": "1.17.0", 4 | "description": "MQTT-based IoT dashboard visualization tool", 5 | "productName": "MQTT Tiles", 6 | "capacitorId": "", 7 | "author": "Sergey Buntsevich ", 8 | "private": true, 9 | "main": "index.js", 10 | "scripts": { 11 | "deploy": "quasar build -m pwa && mkdir -p deploy && cp -R dist/pwa/* misc LICENSE package.json README.md deploy && node_modules/git-directory-deploy/bin/git-directory-deploy.sh -ddeploy -bgh-pages -rgit@git.gurtam.net:flespi/frontend/MQTTTiles.git && rm -rf deploy && git push github gh-pages" 12 | }, 13 | "dependencies": { 14 | "@quasar/extras": "^1.10.7", 15 | "@radial-color-picker/vue-color-picker": "^4.0.0", 16 | "babel-runtime": "^6.26.0", 17 | "canvas-gauges": "^2.1.5", 18 | "compare-versions": "^3.6.0", 19 | "core-js": "^3.14.0", 20 | "fitty": "^2.3.3", 21 | "flespi-io-js": "github:flespi-software/flespi-io-js", 22 | "js-base64": "^3.6.1", 23 | "jsonpath": "^1.1.1", 24 | "lodash": "^4.17.21", 25 | "mathjs": "^9.4.2", 26 | "mqtt": "^4.2.6", 27 | "prismjs": "^1.23.0", 28 | "quasar": "^1.15.21", 29 | "vue-grid-layout": "^2.3.11", 30 | "vue-i18n": "^8.24.4", 31 | "vue-markdown": "^2.2.4", 32 | "vue-virtual-scroll-list": "^1.4.6", 33 | "workbox-webpack-plugin": "^7.0.0", 34 | "xss": "^1.0.9" 35 | }, 36 | "devDependencies": { 37 | "@quasar/app": "^2.2.10", 38 | "babel-eslint": "^10.0.1", 39 | "eslint": "^7.21.0", 40 | "eslint-config-prettier": "^8.1.0", 41 | "eslint-plugin-vue": "^7.7.0", 42 | "eslint-webpack-plugin": "^2.4.0", 43 | "git-directory-deploy": "^1.5.1" 44 | }, 45 | "browserslist": [ 46 | "last 10 Chrome versions", 47 | "last 10 Firefox versions", 48 | "last 4 Edge versions", 49 | "last 7 Safari versions", 50 | "last 8 Android versions", 51 | "last 8 ChromeAndroid versions", 52 | "last 8 FirefoxAndroid versions", 53 | "last 10 iOS versions", 54 | "last 5 Opera versions" 55 | ], 56 | "engines": { 57 | "node": ">= 10.18.1", 58 | "npm": ">= 6.13.4", 59 | "yarn": ">= 1.21.1" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/favicon.ico -------------------------------------------------------------------------------- /public/grid-9px-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/grid-9px-light.png -------------------------------------------------------------------------------- /public/icons/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/apple-touch-icon-114x114.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/apple-touch-icon-120x120.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/apple-touch-icon-144x144.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/apple-touch-icon-152x152.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/apple-touch-icon-57x57.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/apple-touch-icon-60x60.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/apple-touch-icon-72x72.png -------------------------------------------------------------------------------- /public/icons/apple-touch-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/apple-touch-icon-76x76.png -------------------------------------------------------------------------------- /public/icons/favicon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/favicon-128x128.png -------------------------------------------------------------------------------- /public/icons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/favicon-16x16.png -------------------------------------------------------------------------------- /public/icons/favicon-196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/favicon-196x196.png -------------------------------------------------------------------------------- /public/icons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/favicon-32x32.png -------------------------------------------------------------------------------- /public/icons/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/favicon-96x96.png -------------------------------------------------------------------------------- /public/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/icon-128x128.png -------------------------------------------------------------------------------- /public/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/icon-192x192.png -------------------------------------------------------------------------------- /public/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/icon-256x256.png -------------------------------------------------------------------------------- /public/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/icon-384x384.png -------------------------------------------------------------------------------- /public/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/icon-512x512.png -------------------------------------------------------------------------------- /public/icons/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/icons/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/mstile-144x144.png -------------------------------------------------------------------------------- /public/icons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/mstile-150x150.png -------------------------------------------------------------------------------- /public/icons/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/mstile-310x150.png -------------------------------------------------------------------------------- /public/icons/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/mstile-310x310.png -------------------------------------------------------------------------------- /public/icons/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/icons/mstile-70x70.png -------------------------------------------------------------------------------- /public/mqtttiles-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/public/mqtttiles-logo.png -------------------------------------------------------------------------------- /src-pwa/custom-service-worker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file (which will be your service worker) 3 | * is picked up by the build system ONLY if 4 | * quasar.conf > pwa > workboxPluginMode is set to "InjectManifest" 5 | */ 6 | -------------------------------------------------------------------------------- /src-pwa/pwa-flag.d.ts: -------------------------------------------------------------------------------- 1 | // THIS FEATURE-FLAG FILE IS AUTOGENERATED, 2 | // REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING 3 | import "quasar/dist/types/feature-flag"; 4 | 5 | declare module "quasar/dist/types/feature-flag" { 6 | interface QuasarFeatureFlags { 7 | pwa: true; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src-pwa/register-service-worker.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is picked up by the build system only 3 | * when building for PRODUCTION 4 | */ 5 | 6 | import { register } from 'register-service-worker' 7 | 8 | register(process.env.SERVICE_WORKER_FILE, { 9 | ready () { 10 | console.log('App is being served from cache by a service worker.') 11 | }, 12 | registered (registration) { // registration -> a ServiceWorkerRegistration instance 13 | console.log('Service worker has been registered.') 14 | }, 15 | cached (registration) { // registration -> a ServiceWorkerRegistration instance 16 | console.log('Content has been cached for offline use.') 17 | }, 18 | updatefound (registration) { // registration -> a ServiceWorkerRegistration instance 19 | console.log('New content is downloading.') 20 | }, 21 | updated (registration) { // registration -> a ServiceWorkerRegistration instance 22 | let notification = document.createElement('div') 23 | notification.id = 'sw-notification' 24 | notification.innerHTML = `
25 |
26 |
27 |
28 | 29 |
30 |
31 |
The new version of MQQT Tiles is available. Refresh the page to update?
32 |
33 |
34 |
35 | 41 |
42 |
43 | 49 |
50 |
51 |
52 |
53 |
` 54 | let buttons = notification.getElementsByTagName('button'), 55 | body = document.getElementsByTagName('body')[0] 56 | function reload () { 57 | if ('serviceWorker' in navigator) { 58 | navigator.serviceWorker.getRegistration().then((reg) => { 59 | if (reg) { 60 | reg.unregister().then(() => { window.location.reload(true) }) 61 | } else { 62 | window.location.reload(true) 63 | } 64 | }) 65 | } else { 66 | window.location.reload(true) 67 | } 68 | setTimeout(() => { window.location.reload(true) }, 1000) 69 | } 70 | buttons[0].addEventListener('click', (ev) => { reload() }) 71 | buttons[1].addEventListener('click', (ev) => { notification.remove() }) 72 | body.appendChild(notification) 73 | }, 74 | offline () { 75 | console.log('No internet connection found. App is running in offline mode.') 76 | }, 77 | error (err) { 78 | console.error('Error during service worker registration:', err) 79 | } 80 | }) 81 | 82 | // ServiceWorkerRegistration: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration 83 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /src/boot/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/src/boot/.gitkeep -------------------------------------------------------------------------------- /src/boot/async-mqtt.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | import mqtt from 'mqtt' 4 | 5 | class AsyncClient { 6 | constructor (client) { 7 | this._client = client 8 | } 9 | 10 | set handleMessage (newHandler) { 11 | this._client.handleMessage = newHandler 12 | } 13 | 14 | get handleMessage () { 15 | return this._client.handleMessage 16 | } 17 | 18 | get connected () { 19 | return this._client.connected 20 | } 21 | 22 | get reconnecting () { 23 | return this._client.reconnecting 24 | } 25 | 26 | publish (...args) { 27 | return new Promise((resolve, reject) => { 28 | this._client.publish(...args, (err, result) => { 29 | if (err) reject(err) 30 | else resolve(result) 31 | }) 32 | }) 33 | } 34 | 35 | subscribe (...args) { 36 | return new Promise((resolve, reject) => { 37 | this._client.subscribe(...args, (err, result) => { 38 | if (err) reject(err) 39 | else resolve(result) 40 | }) 41 | }) 42 | } 43 | 44 | unsubscribe (...args) { 45 | return new Promise((resolve, reject) => { 46 | this._client.unsubscribe(...args, (err, result) => { 47 | if (err) reject(err) 48 | else resolve(result) 49 | }) 50 | }) 51 | } 52 | 53 | end (...args) { 54 | return new Promise((resolve, reject) => { 55 | this._client.end(...args, (err, result) => { 56 | if (err) reject(err) 57 | else resolve(result) 58 | }) 59 | }) 60 | } 61 | 62 | reconnect (...args) { 63 | return this._client.reconnect(...args) 64 | } 65 | 66 | addListener (...args) { 67 | return this._client.addListener(...args) 68 | } 69 | 70 | emit (...args) { 71 | return this._client.emit(...args) 72 | } 73 | 74 | eventNames (...args) { 75 | return this._client.eventNames(...args) 76 | } 77 | 78 | getLastMessageId (...args) { 79 | return this._client.getLastMessageId(...args) 80 | } 81 | 82 | getMaxListeners (...args) { 83 | return this._client.getMaxListeners(...args) 84 | } 85 | 86 | listenerCount (...args) { 87 | return this._client.listenerCount(...args) 88 | } 89 | 90 | listeners (...args) { 91 | return this._client.listeners(...args) 92 | } 93 | 94 | off (...args) { 95 | return this._client.off(...args) 96 | } 97 | 98 | on (...args) { 99 | return this._client.on(...args) 100 | } 101 | 102 | once (...args) { 103 | return this._client.once(...args) 104 | } 105 | 106 | prependListener (...args) { 107 | return this._client.prependListener(...args) 108 | } 109 | 110 | prependOnceListener (...args) { 111 | return this._client.prependOnceListener(...args) 112 | } 113 | 114 | rawListeners (...args) { 115 | return this._client.rawListeners(...args) 116 | } 117 | 118 | removeAllListeners (...args) { 119 | return this._client.removeAllListeners(...args) 120 | } 121 | 122 | removeListener (...args) { 123 | return this._client.removeListener(...args) 124 | } 125 | 126 | removeOutgoingMessage (...args) { 127 | return this._client.removeOutgoingMessage(...args) 128 | } 129 | 130 | setMaxListeners (...args) { 131 | return this._client.setMaxListeners(...args) 132 | } 133 | } 134 | export default { 135 | connect (brokerURL, opts) { 136 | const client = mqtt.connect(brokerURL, opts) 137 | const asyncClient = new AsyncClient(client) 138 | return asyncClient 139 | }, 140 | connectAsync (brokerURL, opts, allowRetries = true) { 141 | const client = mqtt.connect(brokerURL, opts) 142 | const asyncClient = new AsyncClient(client) 143 | 144 | return new Promise((resolve, reject) => { 145 | // Listeners added to client to trigger promise resolution 146 | const promiseResolutionListeners = { 147 | connect: (connack) => { 148 | removePromiseResolutionListeners() 149 | resolve(asyncClient) // Resolve on connect 150 | }, 151 | end: () => { 152 | removePromiseResolutionListeners() 153 | resolve(asyncClient) // Resolve on end 154 | }, 155 | error: (err) => { 156 | removePromiseResolutionListeners() 157 | client.end() 158 | reject(err) // Reject on error 159 | } 160 | } 161 | 162 | // If retries are not allowed, reject on close 163 | if (allowRetries === false) { 164 | promiseResolutionListeners.close = () => { 165 | promiseResolutionListeners.error("Couldn't connect to server") 166 | } 167 | } 168 | 169 | // Remove listeners added to client by this promise 170 | const removePromiseResolutionListeners = () => { 171 | Object.keys(promiseResolutionListeners).forEach((eventName) => { 172 | client.removeListener(eventName, promiseResolutionListeners[eventName]) 173 | }) 174 | } 175 | 176 | // Add listeners to client 177 | Object.keys(promiseResolutionListeners).forEach((eventName) => { 178 | client.on(eventName, promiseResolutionListeners[eventName]) 179 | }) 180 | }) 181 | }, 182 | AsyncClient 183 | } 184 | -------------------------------------------------------------------------------- /src/boot/i18n.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueI18n from 'vue-i18n' 3 | import messages from 'src/i18n' 4 | 5 | Vue.use(VueI18n) 6 | 7 | const i18n = new VueI18n({ 8 | locale: 'en-us', 9 | fallbackLocale: 'en-us', 10 | messages 11 | }) 12 | 13 | export default ({ app }) => { 14 | // Set i18n instance on app 15 | app.i18n = i18n 16 | } 17 | 18 | export { i18n } 19 | -------------------------------------------------------------------------------- /src/boot/icomoon.js: -------------------------------------------------------------------------------- 1 | import '../css/icomoon.css' 2 | 3 | export default () => { 4 | } 5 | -------------------------------------------------------------------------------- /src/boot/integrationBus.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | /* 3 | => SetFlespiLogin({token, region}) Message format: `MQTTTiles|${postkey}|${commandName}=>${payload}` 4 | => SetBoards({...board}) 5 | => SetActiveBoard({boardId, [params]}) 6 | => AddWidget({boardId, widgetConfig}) 7 | => SetTheme(theme) 8 | => CreateBoard() 9 | <= @ready() - mounted on Tiles 10 | <= @connected() - connection established 11 | <= @saveBoards({...boards}) 12 | <= @activeBoard(id) 13 | <= @boardCreated(boardId) 14 | <= @widgetCreated(widgetId) 15 | */ 16 | class IntegrationBus { 17 | constructor () { 18 | this.bus = new Vue() 19 | this.postkey = window.name 20 | window.addEventListener('message', (event) => { 21 | let cmd = '', 22 | payload = null 23 | if (typeof event.data === 'string' && event.data.indexOf('MQTTTiles|') === 0) { 24 | let data = event.data.split('|') 25 | data = data[this.postkey ? 2 : 1].split('=>') 26 | cmd = data[0] 27 | try { 28 | payload = JSON.parse(data[1]) 29 | } catch (e) { 30 | payload = data[1] 31 | } 32 | } 33 | if (cmd) { 34 | this.bus.$emit(cmd, payload) 35 | } 36 | }) 37 | } 38 | 39 | on () { 40 | this.bus.$on(...arguments) 41 | } 42 | 43 | send (cmd, payload) { 44 | cmd = `MQTTTiles${this.postkey ? `|${this.postkey}` : ''}|${cmd}${payload ? `=>${JSON.stringify(payload)}` : ''}` 45 | window.parent && window.parent !== window && window.parent.postMessage(cmd, '*') 46 | window.opener && window.opener.postMessage(cmd, '*') 47 | } 48 | } 49 | export default ({ Vue }) => { 50 | const bus = new IntegrationBus() 51 | Vue.prototype.$integrationBus = bus 52 | } 53 | -------------------------------------------------------------------------------- /src/boot/prism.js: -------------------------------------------------------------------------------- 1 | import Prism from 'prismjs' 2 | import 'prismjs/themes/prism.css' 3 | 4 | export default ({ Vue }) => { 5 | Vue.prototype.$prism = Prism 6 | } 7 | -------------------------------------------------------------------------------- /src/components/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flespi-software/MQTT-Tiles/dc3c9141073c9145d5c5dec72656bd8ab5cc2ab3/src/components/.gitkeep -------------------------------------------------------------------------------- /src/components/BoardVariable.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 74 | -------------------------------------------------------------------------------- /src/components/CopyReplaceDialog.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 82 | -------------------------------------------------------------------------------- /src/components/Integration.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 72 | -------------------------------------------------------------------------------- /src/components/global/register.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import TypedInput from './typed-input.vue' 3 | 4 | Vue.component('typed-input', TypedInput) 5 | -------------------------------------------------------------------------------- /src/components/global/typed-input.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 47 | -------------------------------------------------------------------------------- /src/components/selectors/async.js: -------------------------------------------------------------------------------- 1 | import RestConnector from 'flespi-io-js/dist/rest' 2 | import get from 'lodash/get' 3 | 4 | class Connector { 5 | constructor (config) { 6 | this.bus = new RestConnector(config) 7 | } 8 | 9 | get settings () { return this.bus.settings } 10 | set settings (settings) { this.bus.settings = settings } 11 | 12 | get token () { return this.bus.token } 13 | set token (token) { this.bus.token = token } 14 | 15 | async getDevices (relatedCalcs) { 16 | let deviceIds = 'all' 17 | if (relatedCalcs && relatedCalcs !== 'all') { 18 | const calcsIds = relatedCalcs.join(',') 19 | try { 20 | const tasksData = await this.bus.gw.getCalcsDevices(calcsIds, 'all', { fileds: 'device_id' }) 21 | const tasks = get(tasksData, 'data.result', []) 22 | const relatedDevicesIds = tasks.reduce((devices, task) => { 23 | devices[task.device_id] = true 24 | return devices 25 | }, {}) 26 | deviceIds = Object.keys(relatedDevicesIds).join(',') 27 | } catch (e) { 28 | return [] 29 | } 30 | } 31 | const devicesData = await this.bus.gw.getDevices(deviceIds, { fields: 'id,name' }) 32 | let devices = get(devicesData, 'data.result', []) 33 | devices = devices.map(device => ({ label: device.name || `#${device.id}`, value: `${device.id}` })) 34 | return devices 35 | } 36 | 37 | async getChannels () { 38 | const calcsData = await this.bus.gw.getChannels('all', { fileds: 'id,name' }) 39 | let calcs = get(calcsData, 'data.result', []) 40 | calcs = calcs.map(calc => ({ label: calc.name || `#${calc.id}`, value: `${calc.id}` })) 41 | return calcs 42 | } 43 | 44 | async getCalcs (relatedDevices) { 45 | let calcsIds = 'all' 46 | if (relatedDevices && relatedDevices !== 'all') { 47 | try { 48 | const devicesIds = relatedDevices.join(',') 49 | const tasksData = await this.bus.gw.getCalcsDevices('all', devicesIds, { fields: 'calc_id' }) 50 | const tasks = get(tasksData, 'data.result', []) 51 | const relatedCalcsIds = tasks.reduce((calcs, task) => { 52 | calcs[task.calc_id] = true 53 | return calcs 54 | }, {}) 55 | calcsIds = Object.keys(relatedCalcsIds).join(',') 56 | } catch (e) { 57 | return [] 58 | } 59 | } 60 | const channelsData = await this.bus.gw.getCalcs(calcsIds, { fileds: 'id,name' }) 61 | let channels = get(channelsData, 'data.result', []) 62 | channels = channels.map(channel => ({ label: channel.name || `#${channel.id}`, value: `${channel.id}` })) 63 | return channels 64 | } 65 | 66 | async getDevicesTelemetry (id) { 67 | const telemetryData = await this.bus.gw.getDevicesTelemetry(id, 'all') 68 | let telemetry = get(telemetryData, 'data.result', []) 69 | telemetry = telemetry.reduce((telemetry, device) => { 70 | const deviceTelemetry = device.telemetry 71 | if (deviceTelemetry) { 72 | telemetry = { ...telemetry, ...deviceTelemetry } 73 | } 74 | return telemetry 75 | }, {}) 76 | telemetry = Object.keys(telemetry).sort().map(param => ({ label: param, value: param })) 77 | return telemetry 78 | } 79 | 80 | async getDevicesSettings (id) { 81 | const settingsData = await this.bus.gw.getDevicesSettings(id, 'all', { fields: 'name,schema' }) 82 | let settings = get(settingsData, 'data.result', []) 83 | settings = settings.reduce((settings, setting) => { 84 | const name = setting.name, 85 | label = setting.schema.title 86 | if (!settings[name]) { settings[name] = {} } 87 | settings[name][label] = true 88 | return settings 89 | }, {}) 90 | settings = Object.keys(settings).map(settingName => ({ label: Object.keys(settings[settingName]).join(', '), value: settingName })) 91 | return settings 92 | } 93 | } 94 | export default Connector 95 | -------------------------------------------------------------------------------- /src/components/widgets/JsonTree.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 72 | 73 | 78 | -------------------------------------------------------------------------------- /src/components/widgets/Markdown.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 35 | 36 | 67 | -------------------------------------------------------------------------------- /src/components/widgets/TextView.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 88 | -------------------------------------------------------------------------------- /src/components/widgets/Topic.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 84 | -------------------------------------------------------------------------------- /src/components/widgets/VariablesHelper.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /src/components/widgets/WidgetWrapper.vue: -------------------------------------------------------------------------------- 1 | 45 | 46 | 67 | 68 | 88 | -------------------------------------------------------------------------------- /src/components/widgets/calculator/View.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 88 | -------------------------------------------------------------------------------- /src/components/widgets/calculator/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/clicker/Schema.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 95 | 96 | 103 | -------------------------------------------------------------------------------- /src/components/widgets/clicker/View.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 45 | 46 | 66 | -------------------------------------------------------------------------------- /src/components/widgets/clicker/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | publish: widget.settings.topics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/clicker/migrations.js: -------------------------------------------------------------------------------- 1 | import { getTopicModel } from '../../../constants/defaultes' 2 | export default { 3 | '1.5.3': (widget) => { 4 | widget.settings.minWidth = 1 5 | return widget 6 | }, 7 | '1.5.4': (widget) => { 8 | widget.settings.icon = '' 9 | return widget 10 | }, 11 | '1.10.6': (widget) => { 12 | const settings = widget.settings 13 | if (typeof settings.topic === 'string') { 14 | settings.topics = [ 15 | getTopicModel( 16 | { 17 | topicTemplate: settings.topic, 18 | topicFilter: settings.topic 19 | } 20 | ) 21 | ] 22 | } 23 | return widget 24 | }, 25 | '1.10.7': (widget) => { 26 | const settings = widget.settings 27 | if (typeof settings.payload === 'string') { 28 | settings.topics.forEach(topic => { topic.payload = settings.payload }) 29 | } 30 | return widget 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/widgets/color/Schema.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 129 | -------------------------------------------------------------------------------- /src/components/widgets/color/constants.js: -------------------------------------------------------------------------------- 1 | const COLOR_FORMAT_HEX = 'hexa', 2 | COLOR_FORMAT_RGB = 'rgba', 3 | COLOR_FORMAT_HSV = 'hsva', 4 | COLOR_MODE_SIMPLE = 0, 5 | COLOR_MODE_FULL = 1 6 | 7 | export { 8 | COLOR_FORMAT_HEX, 9 | COLOR_FORMAT_RGB, 10 | COLOR_FORMAT_HSV, 11 | COLOR_MODE_SIMPLE, 12 | COLOR_MODE_FULL 13 | } 14 | -------------------------------------------------------------------------------- /src/components/widgets/color/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | const topics = widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 3 | return { 4 | subscribe: [...topics], 5 | publish: [...topics] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/components/widgets/complex/AddMenu.vue: -------------------------------------------------------------------------------- 1 | 27 | 33 | -------------------------------------------------------------------------------- /src/components/widgets/complex/Knob.vue: -------------------------------------------------------------------------------- 1 | 12 | 18 | -------------------------------------------------------------------------------- /src/components/widgets/complex/Preview.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 104 | -------------------------------------------------------------------------------- /src/components/widgets/complex/Progress.vue: -------------------------------------------------------------------------------- 1 | 11 | 23 | -------------------------------------------------------------------------------- /src/components/widgets/complex/Text.vue: -------------------------------------------------------------------------------- 1 | 10 | 24 | -------------------------------------------------------------------------------- /src/components/widgets/complex/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/frame/constants.js: -------------------------------------------------------------------------------- 1 | const IFRAME_MODE_INTEGRATION = 0 2 | const IFRAME_MODE_SHOW = 1 3 | 4 | export { 5 | IFRAME_MODE_INTEGRATION, 6 | IFRAME_MODE_SHOW 7 | } 8 | -------------------------------------------------------------------------------- /src/components/widgets/frame/getTopics.js: -------------------------------------------------------------------------------- 1 | import { IFRAME_MODE_INTEGRATION, IFRAME_MODE_SHOW } from './constants' 2 | export default function getTopics (widget) { 3 | const mode = widget.settings.mode 4 | let subscribe, publish 5 | if (mode === IFRAME_MODE_INTEGRATION) { 6 | publish = widget.settings.items.map(item => { 7 | const topic = item.topic 8 | return (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+') 9 | }) 10 | } else if (mode === IFRAME_MODE_SHOW) { 11 | subscribe = widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 12 | } 13 | return { 14 | subscribe, 15 | publish 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/components/widgets/frame/migrations.js: -------------------------------------------------------------------------------- 1 | import { IFRAME_MODE_INTEGRATION, IFRAME_MODE_SHOW } from './constants' 2 | import cloneDeep from 'lodash/cloneDeep' 3 | export default { 4 | '1.5.4': (widget) => { 5 | const settings = widget.settings 6 | if (settings.mode === undefined) { settings.mode = IFRAME_MODE_SHOW } 7 | if (settings.mode === IFRAME_MODE_SHOW) { 8 | settings.items = [] 9 | settings.topics = [] 10 | } 11 | if (settings.mode === IFRAME_MODE_INTEGRATION) { 12 | const topic = cloneDeep(widget.dataTopics[0]) 13 | settings.maxTopicsLength = 0 14 | settings.items = [ 15 | { 16 | label: 'New item', 17 | template: settings.template, 18 | topic 19 | } 20 | ] 21 | settings.topics = [topic] 22 | widget.dataTopics = [] 23 | delete settings.template 24 | } 25 | return widget 26 | }, 27 | '1.10.1': (widget) => { 28 | const settings = widget.settings 29 | settings.readyMessage = '' 30 | return widget 31 | }, 32 | '1.10.3': (widget) => { 33 | const settings = widget.settings 34 | settings.initMessage = '' 35 | return widget 36 | }, 37 | '1.12.13': (widget) => { 38 | const settings = widget.settings 39 | if (!settings.aclTopics) { 40 | settings.aclTopics = [] 41 | } 42 | return widget 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/widgets/getTopics.js: -------------------------------------------------------------------------------- 1 | import uniq from 'lodash/uniq' 2 | import clicker from './clicker/getTopics.js' 3 | import singleselect from './singleselect/getTopics' 4 | import switcher from './switcher/getTopics' 5 | import textSender from './textSender/getTopics' 6 | import calculator from './calculator/getTopics' 7 | import color from './color/getTopics' 8 | import complex from './complex/getTopics' 9 | import frame from './frame/getTopics' 10 | import informer from './informer/getTopics' 11 | import multiInformer from './multiInformer/getTopics' 12 | import multiplier from './multiplier/getTopics' 13 | import statusIndicator from './statusIndicator/getTopics' 14 | import linear from './linear/getTopics' 15 | import radial from './radial/getTopics' 16 | import slider from './slider/getTopics' 17 | import mapLocation from './mapLocation/getTopics' 18 | import mapDevices from './mapDevices/getTopics' 19 | import mapRoute from './mapRoute/getTopics' 20 | const handlers = { 21 | clicker, 22 | singleselect, 23 | switcher, 24 | 'text-sender': textSender, 25 | calculator, 26 | color, 27 | complex, 28 | frame, 29 | informer, 30 | 'multi-informer': multiInformer, 31 | multiplier, 32 | 'status-indicator': statusIndicator, 33 | linear, 34 | radial, 35 | slider, 36 | 'map-devices': mapDevices, 37 | 'map-location': mapLocation, 38 | 'map-route': mapRoute 39 | } 40 | export default function getTopics (board, widgets) { 41 | let subscribeTopics = [`xflespifront/mqtttiles/boards/${board.id}`] 42 | if (board.settings.variables) { 43 | subscribeTopics = [...subscribeTopics, ...board.settings.variables.map(variable => variable.topic.topicTemplate)] 44 | } 45 | const result = widgets.reduce((topics, widget) => { 46 | const handler = handlers[widget.type] || (() => ({})) 47 | const widgetTopics = handler(widget) 48 | if (widgetTopics.subscribe) { 49 | topics.subscribe = [...topics.subscribe, ...widgetTopics.subscribe] 50 | } 51 | if (widgetTopics.publish) { 52 | topics.publish = [...topics.publish, ...widgetTopics.publish] 53 | } 54 | return topics 55 | }, 56 | { subscribe: subscribeTopics, publish: [] }) 57 | if (result.subscribe) { 58 | result.subscribe = uniq(result.subscribe).sort() 59 | } 60 | if (result.publish) { 61 | result.publish = uniq(result.publish).sort() 62 | } 63 | return result 64 | } 65 | -------------------------------------------------------------------------------- /src/components/widgets/informer/Schema.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 83 | -------------------------------------------------------------------------------- /src/components/widgets/informer/View.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 39 | 40 | 80 | -------------------------------------------------------------------------------- /src/components/widgets/informer/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/linear/LinearGauge.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 53 | -------------------------------------------------------------------------------- /src/components/widgets/linear/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: [ 4 | ...widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')), 5 | ...widget.settings.topics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 6 | ] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/widgets/mapDevices/View.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 81 | -------------------------------------------------------------------------------- /src/components/widgets/mapDevices/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: widget.settings.topics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/mapDevices/messagesProcessing.js: -------------------------------------------------------------------------------- 1 | import setWith from 'lodash/setWith' 2 | import get from 'lodash/get' 3 | import cloneDeep from 'lodash/cloneDeep' 4 | import Vue from 'vue' 5 | 6 | function getMapDevicesValueByPacket (packet, initValue, subscriptionTopic, widget) { 7 | const path = subscriptionTopic.split('/') 8 | const currentPath = packet.topic.split('/') 9 | let value = initValue 10 | const setPath = [] 11 | // path.forEach((pathItem, index) => { 12 | // setPath.push(currentPath[index]) 13 | // }) 14 | setPath.push(currentPath[4]) 15 | setPath.push(currentPath[6]) 16 | if (packet.payload.length) { 17 | let payload = packet 18 | // try { 19 | // payload = JSON.parse(packet.payload.toString()) 20 | // } catch (e) { 21 | // payload = packet.payload.toString() 22 | // } 23 | // const timestamp = get(packet, 'properties.userProperties.timestamp') 24 | // payload = { timestamp, payload } 25 | if (setPath.length) { 26 | setWith(value, setPath, payload, Object) 27 | } else { 28 | value = payload 29 | } 30 | } else { 31 | if (setPath.length) { 32 | const path = setPath.slice(0, -1).join('.') 33 | const name = setPath.slice(-1)[0] 34 | const obj = path ? get(value, path, null) : value 35 | obj && Vue.delete(obj, name) 36 | } else { 37 | value = null 38 | } 39 | } 40 | return value 41 | } 42 | 43 | 44 | export default function getMapDevicesValue (packets, initValue, subscriptionTopic, widget) { 45 | if (!packets || subscriptionTopic !== widget.topics[0]) { 46 | let res = null 47 | if (packets && packets.length) { 48 | res = packets.slice(-1)[0] 49 | } 50 | return res 51 | } 52 | let value = initValue ? cloneDeep(initValue) : {} 53 | packets.forEach((packet) => { 54 | value = getMapDevicesValueByPacket(packet, value, subscriptionTopic, widget) 55 | }) 56 | return value 57 | } 58 | -------------------------------------------------------------------------------- /src/components/widgets/mapDevices/migrations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | // '1.11.4': (widget) => { 3 | // const settings = widget.settings 4 | // settings.items = [ 5 | // { 6 | // name: 'Item 1', 7 | // lat: settings.topics[0], 8 | // lon: settings.topics[1] 9 | // } 10 | // ] 11 | // return widget 12 | // } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/widgets/mapLocation/View.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 70 | -------------------------------------------------------------------------------- /src/components/widgets/mapLocation/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: widget.settings.topics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/mapLocation/migrations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '1.11.4': (widget) => { 3 | const settings = widget.settings 4 | settings.items = [ 5 | { 6 | name: 'Item 1', 7 | lat: settings.topics[0], 8 | lon: settings.topics[1] 9 | } 10 | ] 11 | return widget 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/components/widgets/mapRoute/View.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 69 | -------------------------------------------------------------------------------- /src/components/widgets/mapRoute/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: widget.settings.topics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/mapRoute/migrations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '1.11.4': (widget) => { 3 | const settings = widget.settings 4 | settings.items = [ 5 | { 6 | name: 'Item 1', 7 | route: settings.topics[0] 8 | } 9 | ] 10 | return widget 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/components/widgets/messagesProcessing.js: -------------------------------------------------------------------------------- 1 | import multiplierHandler from './multiplier/messagesProcessing' 2 | import mapDevicesHandler from './mapDevices/messagesProcessing' 3 | function defaultProcessing (packets, dest, subTopic, widget) { 4 | return (packets && ((packets.length && packets.slice(-1)[0]) || dest)) || null 5 | } 6 | 7 | const customHandlersByType = { 8 | multiplier: multiplierHandler, 9 | "map-devices": mapDevicesHandler 10 | } 11 | 12 | export default function (type) { 13 | return customHandlersByType[type] || defaultProcessing 14 | } 15 | -------------------------------------------------------------------------------- /src/components/widgets/migrations.js: -------------------------------------------------------------------------------- 1 | import compareVersions from 'compare-versions' 2 | import clicker from './clicker/migrations' 3 | import frame from './frame/migrations' 4 | import switcher from './switcher/migrations' 5 | import mapLocation from './mapLocation/migrations' 6 | import mapDevices from './mapDevices/migrations' 7 | import mapRoute from './mapRoute/migrations' 8 | import scheme from './scheme/migrations' 9 | /* widgetType: {[version]: handler} */ 10 | const migrateHandlers = { 11 | clicker, 12 | frame, 13 | switcher, 14 | 'map-devices': mapDevices, 15 | 'map-location': mapLocation, 16 | 'map-route': mapRoute, 17 | scheme 18 | } 19 | function migrateWidgets (widgets, fromVersion, toVersion) { 20 | return widgets.reduce((newWidgets, widget) => { 21 | const migrations = migrateHandlers[widget.type] || {} 22 | const versions = Object.keys(migrations) 23 | versions.forEach((version) => { 24 | if (compareVersions.compare(version, fromVersion, '>') && compareVersions.compare(version, toVersion, '<=')) { 25 | try { 26 | migrations[version](widget) 27 | } catch (e) { 28 | console.log(e) 29 | } 30 | } 31 | }) 32 | newWidgets.push(widget) 33 | return newWidgets 34 | }, []) 35 | } 36 | 37 | export default migrateWidgets 38 | -------------------------------------------------------------------------------- /src/components/widgets/multiInformer/View.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 28 | 29 | 70 | -------------------------------------------------------------------------------- /src/components/widgets/multiInformer/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: widget.settings.topics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/multiplier/Preview.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 43 | 44 | 122 | -------------------------------------------------------------------------------- /src/components/widgets/multiplier/Schema.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 139 | -------------------------------------------------------------------------------- /src/components/widgets/multiplier/constants.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_MODE = 0 2 | const COMMAND_MODE = 1 3 | const ACCUMULATE_AND_MODE = 0 4 | const ACCUMULATE_OR_MODE = 1 5 | 6 | export { 7 | DEFAULT_MODE, 8 | COMMAND_MODE, 9 | ACCUMULATE_AND_MODE, 10 | ACCUMULATE_OR_MODE 11 | } 12 | -------------------------------------------------------------------------------- /src/components/widgets/multiplier/getActionTopics.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_MODE, COMMAND_MODE } from './constants.js' 2 | export default function getActionTopics (widget) { 3 | if (widget.settings.mode === COMMAND_MODE) { 4 | return [widget.settings.falseActionTopic, widget.settings.trueActionTopic] 5 | } else if (widget.topics.length === 1) { 6 | return [widget.topics[0]] 7 | } else if (widget.settings.mode === DEFAULT_MODE) { 8 | return [widget.settings.actionTopic] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/widgets/multiplier/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/multiplier/messagesProcessing.js: -------------------------------------------------------------------------------- 1 | import setWith from 'lodash/setWith' 2 | import get from 'lodash/get' 3 | import cloneDeep from 'lodash/cloneDeep' 4 | import Vue from 'vue' 5 | 6 | function getMultiplierValueByPacket (packet, initValue, subscriptionTopic, widget) { 7 | const path = subscriptionTopic.split('/') 8 | const currentPath = packet.topic.split('/') 9 | let value = initValue 10 | const setPath = [] 11 | path.forEach((pathItem, index) => { 12 | setPath.push(currentPath[index]) 13 | }) 14 | if (packet.payload.length) { 15 | let payload = packet 16 | // try { 17 | // payload = JSON.parse(packet.payload.toString()) 18 | // } catch (e) { 19 | // payload = packet.payload.toString() 20 | // } 21 | // const timestamp = get(packet, 'properties.userProperties.timestamp') 22 | // payload = { timestamp, payload } 23 | if (setPath.length) { 24 | setWith(value, setPath, payload, Object) 25 | } else { 26 | value = payload 27 | } 28 | } else { 29 | if (setPath.length) { 30 | const path = setPath.slice(0, -1).join('.') 31 | const name = setPath.slice(-1)[0] 32 | const obj = path ? get(value, path, null) : value 33 | obj && Vue.delete(obj, name) 34 | } else { 35 | value = null 36 | } 37 | } 38 | return value 39 | } 40 | export default function getMultiplierValue (packets, initValue, subscriptionTopic, widget) { 41 | if (!packets || subscriptionTopic !== widget.dataTopics[0].topicFilter) { 42 | let res = null 43 | if (packets && packets.length) { 44 | res = packets.slice(-1)[0] 45 | } 46 | return res 47 | } 48 | let value = initValue ? cloneDeep(initValue) : {} 49 | packets.forEach((packet) => { 50 | value = getMultiplierValueByPacket(packet, value, subscriptionTopic, widget) 51 | }) 52 | return value 53 | } 54 | -------------------------------------------------------------------------------- /src/components/widgets/multiplier/migration.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '1.15.0': (widget) => { 3 | const settings = widget.settings 4 | if (settings.type === 'map-location' || settings.type === 'map-route') { 5 | settings.type = 'informer' 6 | } 7 | return widget 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/components/widgets/radial/RadialGauge.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 55 | -------------------------------------------------------------------------------- /src/components/widgets/radial/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: [ 4 | ...widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')), 5 | ...widget.settings.topics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 6 | ] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/StaticTextSchema.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 63 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/StaticTextView.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 58 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/StatusView.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 89 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/TextSchema.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 106 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/TextView.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 114 | 115 | 124 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/ToggleSchema.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 85 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/ToggleView.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 109 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/View.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 39 | 40 | 97 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/activeMixin.js: -------------------------------------------------------------------------------- 1 | import get from 'lodash/get' 2 | import validateTopic from '../../../mixins/validateTopic.js' 3 | import TextSchema from './TextSchema' 4 | import ToggleSchema from './ToggleSchema' 5 | import StatusSchema from './StatusSchema' 6 | import StaticTextSchema from './StaticTextSchema' 7 | const componentsByType = { 8 | text: TextSchema, 9 | toggle: ToggleSchema, 10 | status: StatusSchema, 11 | 'static-text': StaticTextSchema 12 | } 13 | export default { 14 | data () { 15 | return { 16 | activeItemIndex: undefined 17 | } 18 | }, 19 | mixins: [validateTopic], 20 | computed: { 21 | activeItem () { 22 | let item 23 | if (typeof this.activeItemIndex !== 'undefined') { 24 | item = this.currentSettings.items[this.activeItemIndex] 25 | } 26 | return item 27 | }, 28 | errorsByItems () { 29 | return this.currentSettings.items.reduce((errors, item, index, items) => { 30 | const hasTopic = item.topic === undefined || (!!item.topic && this.validateTopic(item.topic.topicFilter)) 31 | const validator = get(componentsByType[item.type], 'methods.validate', () => true) 32 | const isValid = hasTopic && validator(item) 33 | if (!isValid) { errors[index] = true } 34 | return errors 35 | }, {}) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/constants.js: -------------------------------------------------------------------------------- 1 | export const WIDGET_ITEM_TYPE_TEXT = 'text' 2 | export const WIDGET_ITEM_TYPE_TOGGLE = 'toggle' 3 | export const WIDGET_ITEM_TYPE_STATUS = 'status' 4 | export const WIDGET_ITEM_TYPE_STATIC_TEXT = 'static-text' 5 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/getTopics.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_MODE = 0, 2 | COMMAND_MODE = 1 3 | export default function getTopics (widget) { 4 | const subscribe = [], 5 | publish = [] 6 | widget.settings.items.forEach(item => { 7 | if (item.topic) { 8 | const topic = (item.topic.topicTemplate || item.topic.topicFilter).replace(/<%.*%>/g, '+') 9 | subscribe.push(topic) 10 | if (item.type === 'toggle') { 11 | if (item.settings.mode === COMMAND_MODE) { 12 | publish.push(item.settings.falseActionTopic) 13 | publish.push(item.settings.trueActionTopic) 14 | } else if (widget.settings.mode === DEFAULT_MODE) { 15 | publish.push(topic) 16 | } 17 | } 18 | } 19 | }) 20 | return { 21 | subscribe, 22 | publish 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/widgets/scheme/migrations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '1.16.2': (widget) => { 3 | const settings = widget.settings 4 | settings.items.forEach(item => { 5 | item.settings.style = '' 6 | item.settings.autoresize = true 7 | }) 8 | return widget 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/widgets/singleselect/View.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 104 | -------------------------------------------------------------------------------- /src/components/widgets/singleselect/constants.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_MODE = 0 2 | const COMMAND_MODE = 1 3 | 4 | export { 5 | DEFAULT_MODE, 6 | COMMAND_MODE 7 | } 8 | -------------------------------------------------------------------------------- /src/components/widgets/singleselect/getTopics.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_MODE } from './constants.js' 2 | export default function getTopics (widget) { 3 | let publish 4 | if (widget.settings.mode === DEFAULT_MODE) { 5 | const topic = widget.dataTopics[0] 6 | publish = [topic.topicTemplate || topic.topicFilter] 7 | } else { 8 | publish = widget.settings.items.map(item => item.actionTopic) 9 | } 10 | return { 11 | publish, 12 | subscribe: widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/widgets/slider/View.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 38 | 39 | 136 | -------------------------------------------------------------------------------- /src/components/widgets/slider/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | const mainTopics = widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 3 | return { 4 | subscribe: [ 5 | ...mainTopics, 6 | ...widget.settings.topics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 7 | ], 8 | publish: [...mainTopics] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/components/widgets/staticInformer/Schema.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 58 | -------------------------------------------------------------------------------- /src/components/widgets/staticInformer/View.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 40 | -------------------------------------------------------------------------------- /src/components/widgets/statusIndicator/constants.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_MODE = 0 2 | const COMMAND_MODE = 1 3 | 4 | export { 5 | DEFAULT_MODE, 6 | COMMAND_MODE 7 | } 8 | -------------------------------------------------------------------------------- /src/components/widgets/statusIndicator/getActionTopics.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_MODE } from './constants.js' 2 | export default function getActionTopics (widget) { 3 | if (widget.settings.mode === DEFAULT_MODE) { 4 | return [widget.dataTopics[0].topicFilter] 5 | } else { 6 | return widget.settings.items.map(item => item.actionTopic) 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/components/widgets/statusIndicator/getTopics.js: -------------------------------------------------------------------------------- 1 | export default function getTopics (widget) { 2 | return { 3 | subscribe: widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/components/widgets/switcher/constants.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_MODE = 0 2 | const COMMAND_MODE = 1 3 | const ACCUMULATE_AND_MODE = 0 4 | const ACCUMULATE_OR_MODE = 1 5 | 6 | export { 7 | DEFAULT_MODE, 8 | COMMAND_MODE, 9 | ACCUMULATE_AND_MODE, 10 | ACCUMULATE_OR_MODE 11 | } 12 | -------------------------------------------------------------------------------- /src/components/widgets/switcher/getTopics.js: -------------------------------------------------------------------------------- 1 | import { DEFAULT_MODE, COMMAND_MODE } from './constants.js' 2 | export default function getTopics (widget) { 3 | let publish 4 | if (widget.settings.mode === COMMAND_MODE) { 5 | publish = [widget.settings.falseActionTopic, widget.settings.trueActionTopic] 6 | } else if (widget.dataTopics.length === 1) { 7 | const topic = widget.dataTopics[0] 8 | publish = [(topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')] 9 | } else if (widget.settings.mode === DEFAULT_MODE) { 10 | publish = [widget.settings.actionTopic] 11 | } 12 | return { 13 | publish, 14 | subscribe: widget.dataTopics.map(topic => (topic.topicTemplate || topic.topicFilter).replace(/<%.*%>/g, '+')) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/widgets/switcher/migrations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | '1.5.4': (widget) => { 3 | widget.settings.trueIcon = '' 4 | widget.settings.falseIcon = '' 5 | return widget 6 | }, 7 | '1.14.1': (widget) => { 8 | const colors = { 9 | grey: '#757575', 10 | red: '#e53935', 11 | green: '#43a047', 12 | orange: '#fb8c00', 13 | blue: '#1e88e5', 14 | 'light-blue': '#039be5', 15 | purple: '#8e24aa', 16 | 'deep-orange': '#f4511e', 17 | cyan: '#00acc1', 18 | brown: '#6d4c41', 19 | 'blue-grey': '#546e7a' 20 | } 21 | widget.settings.trueColor = colors[widget.color] 22 | widget.settings.falseColor = '#333' 23 | return widget 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/widgets/textSender/Schema.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 57 | -------------------------------------------------------------------------------- /src/components/widgets/textSender/View.vue: -------------------------------------------------------------------------------- 1 |