mirror of
https://github.com/thomasnordquist/MQTT-Explorer.git
synced 2026-09-11 17:13:53 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21ba23502d |
+7
-13
@@ -1,11 +1,4 @@
|
||||
{
|
||||
"import": [
|
||||
"@cspell/dict-typescript/cspell-ext.json"
|
||||
],
|
||||
"ignoreRegExpList": [
|
||||
"import(?:(?:(?:[ \\n\\t]+([^ *\\n\\t\\{\\},]+)[ \\n\\t]*(?:,|[ \\n\\t]+))?([ \\n\\t]*\\{(?:[ \\n\\t]*[^ \\n\\t\"'\\{\\}]+[ \\n\\t]*,?)+\\})?[ \\n\\t]*)|[ \n\\n\\t]*\\*[ \\n\\t]*as[ \\n\\t]+([^ \\n\\t\\{\\}]+)[ \\n\\t]+)from[ \\n\\t]*(?:['\"])([^'\"\\n]+)(['\"])\n",
|
||||
"^import\\s+(['\"]).*\\1$"
|
||||
],
|
||||
"language": "en",
|
||||
"words": [
|
||||
"Bbreak",
|
||||
@@ -15,13 +8,15 @@
|
||||
"nowrap",
|
||||
"subheader",
|
||||
"basepath",
|
||||
"webdriverio",
|
||||
"repo",
|
||||
"hexagonalize",
|
||||
"pixelize",
|
||||
"Transistions",
|
||||
"squashfs",
|
||||
"squashfs",
|
||||
"provisionprofile",
|
||||
"Nsis",
|
||||
"webdriverio",
|
||||
"Appx",
|
||||
"Hashable",
|
||||
"clickaway",
|
||||
@@ -31,6 +26,8 @@
|
||||
"Monokai",
|
||||
"plottable",
|
||||
"snackbar",
|
||||
"webdriverio",
|
||||
"prismjs",
|
||||
"Nordquist",
|
||||
"debounced",
|
||||
"mosquitto",
|
||||
@@ -50,9 +47,6 @@
|
||||
"mixins",
|
||||
"Explorerdmg",
|
||||
"heapsnapshot",
|
||||
"noconflict",
|
||||
"sparkplugb",
|
||||
"protojson",
|
||||
"typesafe"
|
||||
"noconflict"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
package.ts @thomasnordquist
|
||||
.github @thomasnordquist
|
||||
scripts @thomasnordquist
|
||||
@@ -1,29 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- release
|
||||
- beta
|
||||
paths:
|
||||
- Dockerfile
|
||||
- .github
|
||||
|
||||
jobs:
|
||||
create-image:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ghcr.io/thomasnordquist/mqtt-explorer-ui-tests:latest
|
||||
@@ -1,53 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- release
|
||||
- beta
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
build:
|
||||
- os: ubuntu-latest
|
||||
task: linux
|
||||
- os: windows-latest
|
||||
task: win
|
||||
- os: macos-latest
|
||||
task: mac
|
||||
runs-on: ${{ matrix.build.os }}
|
||||
steps:
|
||||
- if: matrix.build.os == 'ubuntu-latest'
|
||||
run: sudo snap install snapcraft --classic
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: npm install -g yarn
|
||||
- run: yarn
|
||||
- id: create_token # get ReleaseBot access token
|
||||
uses: tibdex/github-app-token@v2
|
||||
with:
|
||||
app_id: ${{ secrets.RELEASE_BOT_APP_ID }}
|
||||
private_key: ${{ secrets.RELEASE_BOT_PRIVATE_KEY }}
|
||||
- name: Semantic Release
|
||||
uses: cycjimmy/semantic-release-action@v4
|
||||
id: semantic # Need an `id` for output variables
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.create_token.outputs.token }}
|
||||
- run: yarn build
|
||||
if: steps.semantic.outputs.new_release_published == 'true'
|
||||
- run: yarn prepare-release
|
||||
if: steps.semantic.outputs.new_release_published == 'true'
|
||||
- run: yarn package ${{ matrix.build.task }}
|
||||
if: steps.semantic.outputs.new_release_published == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
on:
|
||||
pull_request_target: # Use pull_request_target
|
||||
branches: [master, beta, release]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ghcr.io/thomasnordquist/mqtt-explorer-ui-tests:latest
|
||||
volumes:
|
||||
- ./:/app
|
||||
options: --user root
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Packages
|
||||
run: yarn install --frozen-lockfile
|
||||
- name: Build
|
||||
run: yarn build
|
||||
- name: Test
|
||||
run: yarn test
|
||||
- name: UI-Test
|
||||
run: yarn ui-test
|
||||
- name: Post-processing
|
||||
run: ./scripts/prepareVideo.sh
|
||||
- uses: hkusu/s3-upload-action@v2
|
||||
id: upload # specify some ID for use in subsequent steps
|
||||
with:
|
||||
aws-access-key-id: ${{ vars.AWS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: 'eu-central-1'
|
||||
aws-bucket: ${{ vars.AWS_BUCKET }}
|
||||
file-path: './ui-test.gif'
|
||||
content-type: image/gif
|
||||
output-file-url: 'true'
|
||||
- name: Show URL
|
||||
run: echo '${{ steps.upload.outputs.file-url }}'
|
||||
id: artifact-upload-step
|
||||
- run: echo '' >> $GITHUB_STEP_SUMMARY
|
||||
@@ -1,17 +0,0 @@
|
||||
name: Update Website
|
||||
|
||||
on: [release, workflow_dispatch]
|
||||
|
||||
jobs:
|
||||
update-website:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: gh-pages
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
- run: npm install
|
||||
- run: npm run readme
|
||||
- uses: stefanzweifel/git-auto-commit-action@v5
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"branches": [
|
||||
"release",
|
||||
{
|
||||
"name": "beta",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
repositoryUrl: "git@github.com:thomasnordquist/MQTT-Explorer.git",
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"semantic-release-export-data",
|
||||
"@semantic-release/changelog",
|
||||
[
|
||||
"@semantic-release/npm",
|
||||
{
|
||||
"npmPublish": false
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"package.json",
|
||||
"yarn.lock"
|
||||
]
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
language: node_js
|
||||
|
||||
services:
|
||||
- xvfb
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
- $HOME/.npm/_prebuilds
|
||||
|
||||
node_js:
|
||||
- "10"
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
|
||||
osx_image: xcode10.2
|
||||
|
||||
dist: bionic
|
||||
|
||||
services:
|
||||
- docker
|
||||
|
||||
install:
|
||||
- yarn install
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update && sudo apt-get -y install snap squashfs-tools && sudo snap install snapcraft --classic; fi;
|
||||
|
||||
script:
|
||||
- yarn run build
|
||||
- yarn lint
|
||||
- yarn test
|
||||
- export TRAVIS_BUILD_NUMBER="" # Override travis build number since it is uses for tagging the binary version https://github.com/electron-userland/electron-builder/issues/3730
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then docker run -e GH_TOKEN=$GH_TOKEN -e GIT_TAG=$TRAVIS_TAG --rm -v `pwd`:/app thomasnordquist/ui-test-recording-env sh -c "cd app && docker/testMounted.sh"; fi
|
||||
- if [[ "$TRAVIS_TAG" != "" ]]; then yarn run prepare-release; fi
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$TRAVIS_TAG" != "" ]]; then
|
||||
openssl aes-256-cbc -d -in res/snapstore-credentials.enc -out credentials -k $SNAPSTORE_CREDENTIALS_DECRYPTION_KEY;
|
||||
snapcraft login --with credentials;
|
||||
rm credentials;
|
||||
yarn run package linux;
|
||||
fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [[ "$TRAVIS_TAG" != "" ]]; then yarn run package -- mac; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]] && [[ "$TRAVIS_TAG" != "" ]]; then unset CSC_LINK; yarn run package -- win; fi
|
||||
@@ -6,12 +6,11 @@
|
||||
[](https://ci.appveyor.com/project/thomasnordquist/mqtt-explorer/branch/master)
|
||||
[](https://app.codacy.com/app/thomasnordquist/MQTT-Explorer?utm_source=github.com&utm_medium=referral&utm_content=thomasnordquist/MQTT-Explorer&utm_campaign=Badge_Grade_Dashboard)
|
||||
|
||||
| | | |
|
||||
| :---------------------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------------------: |
|
||||
| [](https://mqtt-explorer.com/img/screen-composite.png) | [](https://mqtt-explorer.com/img/screen2.png) | [](https://mqtt-explorer.com/img/screen3.png) |
|
||||
| | | |
|
||||
|:---:|:---:|:---:|
|
||||
|[](https://mqtt-explorer.com/img/screen-composite.png)|[](https://mqtt-explorer.com/img/screen2.png)|[](https://mqtt-explorer.com/img/screen3.png)|
|
||||
|
||||
# The App has moved to [mqtt-explorer.com](https://mqtt-explorer.com)
|
||||
|
||||
MQTT Explorer is a comprehensive and easy-to-use MQTT Client.
|
||||
Downloads can be found at the link above.
|
||||
|
||||
@@ -30,7 +29,6 @@ yarn start
|
||||
## Develop
|
||||
|
||||
Launch Application
|
||||
|
||||
```bash
|
||||
npm install -g yarn
|
||||
yarn
|
||||
@@ -65,20 +63,12 @@ npm run build
|
||||
node dist/src/spec/webdriverio.js
|
||||
```
|
||||
|
||||
## Create a release
|
||||
|
||||
Create a PR to `release` branch.
|
||||
There needs to be a "feat: some new feature" or "fix: some bugfix" commit for a new release to be created
|
||||
|
||||
## Create a beta release
|
||||
|
||||
Create a PR to `beta` branch. A "feat" or "fix" commit is necessary to create a new version.
|
||||
|
||||
## Write docs
|
||||
|
||||
```
|
||||
git clone --single-branch -b gh-pages https://github.com/thomasnordquist/MQTT-Explorer.git mqtt-explorer-pages
|
||||
git clone https://github.com/thomasnordquist/MQTT-Explorer.git mqtt-explorer-pages
|
||||
cd mqtt-explorer-pages
|
||||
git checkout gh-pages
|
||||
bundle install
|
||||
bundle exec jekyll serve --incremental
|
||||
```
|
||||
|
||||
+152
-168
@@ -1,197 +1,181 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" />
|
||||
<title>MQTT Explorer</title>
|
||||
<script src="./bugtracking.bundle.js"></script>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no" />
|
||||
<title>MQTT Explorer</title>
|
||||
<script src="./bugtracking.bundle.js"></script>
|
||||
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[tabindex] {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@keyframes updateDark {
|
||||
0% {
|
||||
background-color: none;
|
||||
<style>
|
||||
body,
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
25% {
|
||||
background-color: #595585;
|
||||
[tabindex] {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: #595585;
|
||||
@keyframes updateDark {
|
||||
0% {
|
||||
background-color: none;
|
||||
}
|
||||
25% {
|
||||
background-color: #595585;
|
||||
}
|
||||
50% {
|
||||
background-color: #595585;
|
||||
}
|
||||
100% {
|
||||
background-color: none;
|
||||
}
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes updateLight {
|
||||
0% {
|
||||
background-color: none;
|
||||
color: inherit;
|
||||
@keyframes updateLight {
|
||||
0% {
|
||||
background-color: none;
|
||||
color: inherit;
|
||||
}
|
||||
25% {
|
||||
background-color: #c0c8c0;
|
||||
}
|
||||
50% {
|
||||
background-color: #c0c8c0;
|
||||
}
|
||||
100% {
|
||||
background-color: none;
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
25% {
|
||||
background-color: #c0c8c0;
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
50% {
|
||||
background-color: #c0c8c0;
|
||||
::-webkit-scrollbar-corner {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
100% {
|
||||
background-color: none;
|
||||
color: inherit;
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(60, 60, 60, 0.5);
|
||||
background-color: rgba(140, 140, 140, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(140, 140, 140, 0.8);
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.Resizer {
|
||||
background: rgba(200, 200, 200, 0);
|
||||
z-index: 10;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-background-clip: padding;
|
||||
-webkit-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
.Resizer:hover {
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(60, 60, 60, 0.5);
|
||||
background-color: rgba(140, 140, 140, 0.1);
|
||||
}
|
||||
.Resizer.horizontal {
|
||||
height: 10px;
|
||||
margin: -10px 0 0 0;
|
||||
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: row-resize;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: rgba(140, 140, 140, 0.8);
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.Resizer {
|
||||
background: rgba(200, 200, 200, 0);
|
||||
z-index: 10;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-background-clip: padding;
|
||||
-webkit-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
.Resizer.horizontal::before {
|
||||
content: '•••';
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: -22px;
|
||||
color: #aaa;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.Resizer:hover {
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
.Resizer.horizontal:hover {
|
||||
border-top: 5px solid rgba(120, 120, 120, 0.3);
|
||||
border-bottom: 5px solid rgba(120, 120, 120, 0.3);
|
||||
}
|
||||
|
||||
.Resizer.horizontal {
|
||||
height: 10px;
|
||||
margin: -10px 0 0 0;
|
||||
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: row-resize;
|
||||
width: 100%;
|
||||
}
|
||||
.Resizer.vertical {
|
||||
width: 2px;
|
||||
margin: 0px -8px 0px 0px;
|
||||
border-left: 0px solid rgba(128, 128, 128, 0);
|
||||
border-right: 8px solid rgba(128, 128, 128, 0);
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.Resizer.horizontal::before {
|
||||
content: '•••';
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
margin-top: -22px;
|
||||
color: #aaa;
|
||||
opacity: 1;
|
||||
}
|
||||
.Resizer.vertical::before {
|
||||
content: '•••';
|
||||
margin-left: -11px;
|
||||
height: 3em;
|
||||
margin-top: calc(50vh - 32px);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
opacity: 1;
|
||||
writing-mode: vertical-lr;
|
||||
text-orientation: sideways;
|
||||
}
|
||||
|
||||
.Resizer.horizontal:hover {
|
||||
border-top: 5px solid rgba(120, 120, 120, 0.3);
|
||||
border-bottom: 5px solid rgba(120, 120, 120, 0.3);
|
||||
}
|
||||
.Resizer.vertical:hover {
|
||||
border-left: 0px solid rgba(130, 130, 130, 0.3);
|
||||
border-right: 8px solid rgba(140, 140, 140, 0.3);
|
||||
}
|
||||
|
||||
.Resizer.vertical {
|
||||
width: 2px;
|
||||
margin: 0px -8px 0px 0px;
|
||||
border-left: 0px solid rgba(128, 128, 128, 0);
|
||||
border-right: 8px solid rgba(128, 128, 128, 0);
|
||||
cursor: col-resize;
|
||||
}
|
||||
.Resizer.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.Resizer.disabled:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.Resizer.vertical::before {
|
||||
content: '•••';
|
||||
margin-left: -11px;
|
||||
height: 3em;
|
||||
margin-top: calc(50vh - 32px);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
opacity: 1;
|
||||
writing-mode: vertical-lr;
|
||||
text-orientation: sideways;
|
||||
}
|
||||
.example-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
.example-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity 300ms ease-in;
|
||||
}
|
||||
.example-exit {
|
||||
opacity: 1;
|
||||
}
|
||||
.example-exit-active {
|
||||
opacity: 0;
|
||||
transition: opacity 300ms ease-in;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" style="font: -webkit-control;"></div>
|
||||
<script>
|
||||
function loadScript(path) {
|
||||
var script = document.createElement('script')
|
||||
script.src = path
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
|
||||
.Resizer.vertical:hover {
|
||||
border-left: 0px solid rgba(130, 130, 130, 0.3);
|
||||
border-right: 8px solid rgba(140, 140, 140, 0.3);
|
||||
}
|
||||
|
||||
.Resizer.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.Resizer.disabled:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.example-enter {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.example-enter-active {
|
||||
opacity: 1;
|
||||
transition: opacity 300ms ease-in;
|
||||
}
|
||||
|
||||
.example-exit {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.example-exit-active {
|
||||
opacity: 0;
|
||||
transition: opacity 300ms ease-in;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
global = globalThis //<- this should be enough
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app" style="font: -webkit-control;"></div>
|
||||
<script>
|
||||
function loadScript(path) {
|
||||
var script = document.createElement('script')
|
||||
script.src = path
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', onLoad(), false)
|
||||
function onLoad() {
|
||||
// <% _.forEach(htmlWebpackPlugin.files.js, function(file) { %>loadScript("<%- file %>");<% }); %>
|
||||
// loadScript("<%= JSON.stringify(htmlWebpackPlugin) %>")
|
||||
}
|
||||
</script>
|
||||
<% _.forEach(htmlWebpackPlugin.files.js, function(file) { %>
|
||||
document.addEventListener('DOMContentLoaded', onLoad(), false)
|
||||
function onLoad() {
|
||||
// <% _.forEach(htmlWebpackPlugin.files.js, function(file) { %>loadScript("<%- file %>");<% }); %>
|
||||
// loadScript("<%= JSON.stringify(htmlWebpackPlugin) %>")
|
||||
}
|
||||
</script>
|
||||
<% _.forEach(htmlWebpackPlugin.files.js, function(file) { %>
|
||||
<script src="<%- file %>"></script>
|
||||
<% }); %>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+21
-22
@@ -4,30 +4,30 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --mode production",
|
||||
"build": "yarn rebuild && webpack --mode production",
|
||||
"dev": "node_modules/.bin/webpack-dev-server --mode development --progress",
|
||||
"rebuild": "cd node_modules/heapdump && node-gyp rebuild --target=7.1.1 --arch=x64 --dist-url=https://atom.io/download/electron || echo Could not build heapdump; cd -",
|
||||
"test": "cross-env TS_NODE_PROJECT=test/tsconfig.json yarn mochatest",
|
||||
"mochatest": "mocha --require ts-node/register --require source-map-support/register --recursive src/*/**/*.spec.ts"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
"mochatest": "mocha --require ts-node/register src/**/*.spec.ts"
|
||||
},
|
||||
"author": "",
|
||||
"license": "CC-BY-ND-4.0",
|
||||
"dependencies": {
|
||||
"@material-ui/core": "4.12",
|
||||
"@material-ui/core": "^4",
|
||||
"@material-ui/icons": "^4",
|
||||
"@material-ui/lab": "^4.0.0-alpha",
|
||||
"@material-ui/styles": "4.11",
|
||||
"@material-ui/styles": "^4",
|
||||
"@types/react-transition-group": "^4",
|
||||
"ace-builds": "^1.4.11",
|
||||
"axios": "^0.28.0",
|
||||
"axios": "^0.19.0",
|
||||
"compare-versions": "^3.5.0",
|
||||
"copy-text-to-clipboard": "^2.1.0",
|
||||
"d3": "^5.9.7",
|
||||
"d3-shape": "^1.3.5",
|
||||
"diff": "^4.0.1",
|
||||
"dot-prop": "^5.0.0",
|
||||
"electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist",
|
||||
"file-loader": "6",
|
||||
"get-value": "^3.0.1",
|
||||
"immutable": "^4.0.0-rc.12",
|
||||
"in-viewport": "^3.6.0",
|
||||
@@ -35,6 +35,7 @@
|
||||
"json-to-ast": "^2.1.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"moment": "^2.29.1",
|
||||
"moving-average": "^1.0.0",
|
||||
"number-abbreviate": "^2.0.0",
|
||||
"parse-duration": "^0.1.1",
|
||||
@@ -55,7 +56,6 @@
|
||||
"uuid": "7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@types/d3": "^5.7.2",
|
||||
"@types/diff": "^4.0.1",
|
||||
"@types/get-value": "^3.0.1",
|
||||
@@ -69,25 +69,24 @@
|
||||
"@types/socket.io-client": "^1.4.32",
|
||||
"@types/uuid": "^7.0.2",
|
||||
"@types/vis": "^4.21.9",
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"chai": "^4.2.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"css-loader": "^3.0.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"html-webpack-plugin": "^5.5.0",
|
||||
"lodash": "^4.17.21",
|
||||
"mocha": "^10.4.0",
|
||||
"moment": "^2.29.1",
|
||||
"hard-source-webpack-plugin": "^0.13.1",
|
||||
"heapdump": "^0.3.12",
|
||||
"html-webpack-plugin": "^4.0.0-beta.5",
|
||||
"mocha": "^7.1.1",
|
||||
"node-loader": "^0.6.0",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"style-loader": "^1",
|
||||
"ts-loader": "^9.2.6",
|
||||
"typescript": "^4.5.5",
|
||||
"webpack": "^5.69.1",
|
||||
"webpack-bundle-analyzer": "^4.5.0",
|
||||
"webpack-cli": "^4.9.2",
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
"typescript": "^3.6.3",
|
||||
"webpack": "^4.28.2",
|
||||
"webpack-bundle-analyzer": "^3.0.3",
|
||||
"webpack-cli": "^3.3.6",
|
||||
"webpack-dev-server": "^3.1.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"electron": "^29"
|
||||
"electron": "^5.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+53
-45
@@ -60,55 +60,63 @@ export const saveCharts = () => async (dispatch: Dispatch<any>, getState: () =>
|
||||
}
|
||||
}
|
||||
|
||||
export const addChart =
|
||||
(chartParameters: ChartParameters) => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
const chartExists = Boolean(
|
||||
getState()
|
||||
.charts.get('charts')
|
||||
.find(chart => chart.topic === chartParameters.topic && chart.dotPath === chartParameters.dotPath)
|
||||
)
|
||||
if (chartExists) {
|
||||
dispatch(showNotification('Already added'))
|
||||
return
|
||||
}
|
||||
|
||||
dispatch({
|
||||
type: ActionTypes.CHARTS_ADD,
|
||||
chart: chartParameters,
|
||||
})
|
||||
dispatch(saveCharts())
|
||||
dispatch(showNotification('Added to chart panel'))
|
||||
export const addChart = (chartParameters: ChartParameters) => async (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
) => {
|
||||
const chartExists = Boolean(
|
||||
getState()
|
||||
.charts.get('charts')
|
||||
.find(chart => chart.topic === chartParameters.topic && chart.dotPath === chartParameters.dotPath)
|
||||
)
|
||||
if (chartExists) {
|
||||
dispatch(showNotification('Already added'))
|
||||
return
|
||||
}
|
||||
|
||||
export const updateChart =
|
||||
(chartParameters: ChartParameters) => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
dispatch({
|
||||
type: ActionTypes.CHARTS_UPDATE,
|
||||
topic: chartParameters.topic,
|
||||
dotPath: chartParameters.dotPath,
|
||||
parameters: chartParameters,
|
||||
})
|
||||
dispatch(saveCharts())
|
||||
}
|
||||
dispatch({
|
||||
type: ActionTypes.CHARTS_ADD,
|
||||
chart: chartParameters,
|
||||
})
|
||||
dispatch(saveCharts())
|
||||
dispatch(showNotification('Added to chart panel'))
|
||||
}
|
||||
|
||||
export const removeChart =
|
||||
(chartParameters: ChartParameters) => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
dispatch({
|
||||
chart: chartParameters,
|
||||
type: ActionTypes.CHARTS_REMOVE,
|
||||
})
|
||||
dispatch(saveCharts())
|
||||
}
|
||||
export const updateChart = (chartParameters: ChartParameters) => async (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
) => {
|
||||
dispatch({
|
||||
type: ActionTypes.CHARTS_UPDATE,
|
||||
topic: chartParameters.topic,
|
||||
dotPath: chartParameters.dotPath,
|
||||
parameters: chartParameters,
|
||||
})
|
||||
dispatch(saveCharts())
|
||||
}
|
||||
|
||||
export const moveChartUp =
|
||||
(parameters: { topic: string; dotPath?: string }) => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
dispatch({
|
||||
topic: parameters.topic,
|
||||
dotPath: parameters.dotPath,
|
||||
type: ActionTypes.CHARTS_MOVE_UP,
|
||||
})
|
||||
dispatch(saveCharts())
|
||||
}
|
||||
export const removeChart = (chartParameters: ChartParameters) => async (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
) => {
|
||||
dispatch({
|
||||
chart: chartParameters,
|
||||
type: ActionTypes.CHARTS_REMOVE,
|
||||
})
|
||||
dispatch(saveCharts())
|
||||
}
|
||||
|
||||
export const moveChartUp = (parameters: { topic: string; dotPath?: string }) => async (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
) => {
|
||||
dispatch({
|
||||
topic: parameters.topic,
|
||||
dotPath: parameters.dotPath,
|
||||
type: ActionTypes.CHARTS_MOVE_UP,
|
||||
})
|
||||
dispatch(saveCharts())
|
||||
}
|
||||
|
||||
export const setCharts = (charts: Array<ChartParameters>): Action => {
|
||||
return {
|
||||
|
||||
@@ -11,29 +11,31 @@ import { showError } from './Global'
|
||||
import { TopicViewModel } from '../model/TopicViewModel'
|
||||
import { addMqttConnectionEvent, makeConnectionStateEvent, removeConnection, rendererEvents } from '../../../events'
|
||||
|
||||
export const connect =
|
||||
(options: MqttOptions, connectionId: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
dispatch(connecting(connectionId))
|
||||
rendererEvents.emit(addMqttConnectionEvent, { options, id: connectionId })
|
||||
const event = makeConnectionStateEvent(connectionId)
|
||||
const host = url.parse(options.url).hostname
|
||||
export const connect = (options: MqttOptions, connectionId: string) => (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
) => {
|
||||
dispatch(connecting(connectionId))
|
||||
rendererEvents.emit(addMqttConnectionEvent, { options, id: connectionId })
|
||||
const event = makeConnectionStateEvent(connectionId)
|
||||
const host = url.parse(options.url).hostname
|
||||
|
||||
rendererEvents.subscribe(event, dataSourceState => {
|
||||
if (dataSourceState.connected) {
|
||||
const didReconnect = Boolean(getState().connection.tree)
|
||||
if (!didReconnect) {
|
||||
const tree = new q.Tree<TopicViewModel>()
|
||||
tree.updateWithConnection(rendererEvents, connectionId)
|
||||
dispatch(showTree(tree))
|
||||
dispatch(connected(tree, host!))
|
||||
}
|
||||
} else if (dataSourceState.error) {
|
||||
dispatch(showError(dataSourceState.error))
|
||||
dispatch(disconnect())
|
||||
rendererEvents.subscribe(event, dataSourceState => {
|
||||
if (dataSourceState.connected) {
|
||||
const didReconnect = Boolean(getState().connection.tree)
|
||||
if (!didReconnect) {
|
||||
const tree = new q.Tree<TopicViewModel>()
|
||||
tree.updateWithConnection(rendererEvents, connectionId)
|
||||
dispatch(showTree(tree))
|
||||
dispatch(connected(tree, host!))
|
||||
}
|
||||
dispatch(updateHealth(dataSourceState))
|
||||
})
|
||||
}
|
||||
} else if (dataSourceState.error) {
|
||||
dispatch(showError(dataSourceState.error))
|
||||
dispatch(disconnect())
|
||||
}
|
||||
dispatch(updateHealth(dataSourceState))
|
||||
})
|
||||
}
|
||||
|
||||
const updateHealth = (dataSourceState: DataSourceState) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
let state
|
||||
|
||||
@@ -9,13 +9,12 @@ import {
|
||||
import { default as persistentStorage, StorageIdentifier } from '../utils/PersistentStorage'
|
||||
import { Dispatch } from 'redux'
|
||||
import { showError } from './Global'
|
||||
import { remote } from 'electron'
|
||||
import { promises as fsPromise } from 'fs'
|
||||
import * as path from 'path'
|
||||
import { ActionTypes, Action } from '../reducers/ConnectionManager'
|
||||
import { Subscription } from '../../../backend/src/DataSource/MqttSource'
|
||||
import { connectionsMigrator } from './migrations/Connection'
|
||||
import { rendererRpc } from '../../../events'
|
||||
import { makeOpenDialogRpc } from '../../../events/OpenDialogRequest'
|
||||
|
||||
export interface ConnectionDictionary {
|
||||
[s: string]: ConnectionOptions
|
||||
@@ -51,19 +50,21 @@ export const loadConnectionSettings = () => async (dispatch: Dispatch<any>, getS
|
||||
}
|
||||
|
||||
export type CertificateTypes = 'selfSignedCertificate' | 'clientCertificate' | 'clientKey'
|
||||
export const selectCertificate =
|
||||
(type: CertificateTypes, connectionId: string) => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
try {
|
||||
const certificate = await openCertificate()
|
||||
dispatch(
|
||||
updateConnection(connectionId, {
|
||||
[type]: certificate,
|
||||
})
|
||||
)
|
||||
} catch (error) {
|
||||
dispatch(showError(error))
|
||||
}
|
||||
export const selectCertificate = (type: CertificateTypes, connectionId: string) => async (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
) => {
|
||||
try {
|
||||
const certificate = await openCertificate()
|
||||
dispatch(
|
||||
updateConnection(connectionId, {
|
||||
[type]: certificate,
|
||||
})
|
||||
)
|
||||
} catch (error) {
|
||||
dispatch(showError(error))
|
||||
}
|
||||
}
|
||||
|
||||
async function openCertificate(): Promise<CertificateParameters> {
|
||||
const rejectReasons = {
|
||||
@@ -71,7 +72,7 @@ async function openCertificate(): Promise<CertificateParameters> {
|
||||
certificateSizeDoesNotMatch: 'Certificate size larger/smaller then expected.',
|
||||
}
|
||||
|
||||
const openDialogReturnValue = await rendererRpc.call(makeOpenDialogRpc(), {
|
||||
const openDialogReturnValue = await remote.dialog.showOpenDialog(remote.getCurrentWindow(), {
|
||||
properties: ['openFile'],
|
||||
securityScopedBookmarks: true,
|
||||
})
|
||||
@@ -82,7 +83,7 @@ async function openCertificate(): Promise<CertificateParameters> {
|
||||
}
|
||||
|
||||
const data = await fsPromise.readFile(selectedFile)
|
||||
if (data.length > 16_384 || data.length < 64) {
|
||||
if (data.length > 16_384 || data.length < 128) {
|
||||
throw rejectReasons.certificateSizeDoesNotMatch
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ActionTypes, ConfirmationRequest } from '../reducers/Global'
|
||||
import { Dispatch } from 'redux'
|
||||
|
||||
export const showError = (error?: string | unknown) => ({
|
||||
export const showError = (error?: string) => ({
|
||||
error,
|
||||
type: ActionTypes.showError,
|
||||
})
|
||||
|
||||
+13
-17
@@ -43,14 +43,12 @@ export const storeSettings = () => async (dispatch: Dispatch<any>, getState: ()
|
||||
}
|
||||
}
|
||||
|
||||
export const setAutoExpandLimit =
|
||||
(autoExpandLimit: number = 0) =>
|
||||
(dispatch: Dispatch<any>) => {
|
||||
dispatch({
|
||||
autoExpandLimit,
|
||||
type: ActionTypes.SETTINGS_SET_AUTO_EXPAND_LIMIT,
|
||||
})
|
||||
}
|
||||
export const setAutoExpandLimit = (autoExpandLimit: number = 0) => (dispatch: Dispatch<any>) => {
|
||||
dispatch({
|
||||
autoExpandLimit,
|
||||
type: ActionTypes.SETTINGS_SET_AUTO_EXPAND_LIMIT,
|
||||
})
|
||||
}
|
||||
|
||||
export const setTimeLocale = (timeLocale: string) => (dispatch: Dispatch<any>) => {
|
||||
dispatch({
|
||||
@@ -83,15 +81,13 @@ export const toggleHighlightTopicUpdates = () => (dispatch: Dispatch<any>) => {
|
||||
dispatch(storeSettings())
|
||||
}
|
||||
|
||||
export const setTopicOrder =
|
||||
(topicOrder: TopicOrder = TopicOrder.none) =>
|
||||
(dispatch: Dispatch<any>) => {
|
||||
dispatch({
|
||||
topicOrder,
|
||||
type: ActionTypes.SETTINGS_SET_TOPIC_ORDER,
|
||||
})
|
||||
dispatch(storeSettings())
|
||||
}
|
||||
export const setTopicOrder = (topicOrder: TopicOrder = TopicOrder.none) => (dispatch: Dispatch<any>) => {
|
||||
dispatch({
|
||||
topicOrder,
|
||||
type: ActionTypes.SETTINGS_SET_TOPIC_ORDER,
|
||||
})
|
||||
dispatch(storeSettings())
|
||||
}
|
||||
|
||||
export const filterTopics = (filterStr: string) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
const { tree } = getState().connection
|
||||
|
||||
+22
-21
@@ -12,10 +12,12 @@ export { clearTopic } from './clearTopic'
|
||||
|
||||
export { moveSelectionUpOrDownwards, moveInward, moveOutward } from './visibleTreeTraversal'
|
||||
|
||||
export const selectTopic =
|
||||
(topic: q.TreeNode<TopicViewModel>) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
debouncedSelectTopic(topic, dispatch, getState)
|
||||
}
|
||||
export const selectTopic = (topic: q.TreeNode<TopicViewModel>) => (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
) => {
|
||||
debouncedSelectTopic(topic, dispatch, getState)
|
||||
}
|
||||
|
||||
const debouncedSelectTopic = debounce(
|
||||
(topic: q.TreeNode<TopicViewModel>, dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
@@ -72,26 +74,25 @@ function destroyUnreferencedTree(state: AppState) {
|
||||
}
|
||||
}
|
||||
|
||||
export const resetStore =
|
||||
() =>
|
||||
(dispatch: Dispatch<any>, getState: () => AppState): AnyAction => {
|
||||
destroyUnreferencedTree(getState())
|
||||
export const resetStore = () => (dispatch: Dispatch<any>, getState: () => AppState): AnyAction => {
|
||||
destroyUnreferencedTree(getState())
|
||||
|
||||
return dispatch({
|
||||
type: ActionTypes.TREE_RESET_STORE,
|
||||
})
|
||||
}
|
||||
return dispatch({
|
||||
type: ActionTypes.TREE_RESET_STORE,
|
||||
})
|
||||
}
|
||||
|
||||
export const showTree =
|
||||
(tree: q.Tree<TopicViewModel> | undefined) =>
|
||||
(dispatch: Dispatch<any>, getState: () => AppState): AnyAction => {
|
||||
destroyUnreferencedTree(getState())
|
||||
export const showTree = (tree: q.Tree<TopicViewModel> | undefined) => (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
): AnyAction => {
|
||||
destroyUnreferencedTree(getState())
|
||||
|
||||
return dispatch({
|
||||
tree,
|
||||
type: ActionTypes.TREE_SHOW_TREE,
|
||||
})
|
||||
}
|
||||
return dispatch({
|
||||
tree,
|
||||
type: ActionTypes.TREE_SHOW_TREE,
|
||||
})
|
||||
}
|
||||
|
||||
export const togglePause = (tree?: q.Tree<TopicViewModel>) => (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
const paused = getState().tree.get('paused')
|
||||
|
||||
@@ -5,50 +5,52 @@ import { makePublishEvent, rendererEvents } from '../../../events'
|
||||
import { moveSelectionUpOrDownwards } from './visibleTreeTraversal'
|
||||
import { globalActions } from '.'
|
||||
|
||||
export const clearTopic =
|
||||
(topic: q.TreeNode<any>, recursive: boolean) => async (dispatch: Dispatch<any>, getState: () => AppState) => {
|
||||
const topicsForPurging = recursive ? [topic, ...topic.childTopics()] : [topic]
|
||||
export const clearTopic = (topic: q.TreeNode<any>, recursive: boolean) => async (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
) => {
|
||||
const topicsForPurging = recursive ? [topic, ...topic.childTopics()] : [topic]
|
||||
|
||||
if (recursive) {
|
||||
const topicCount = topic.childTopicCount()
|
||||
if (recursive) {
|
||||
const topicCount = topic.childTopicCount()
|
||||
|
||||
const topicDelta = topic.hasMessage() ? -1 : 0
|
||||
const childTopicsMessage =
|
||||
topicCount + topicDelta > 0
|
||||
? ` and ${topicCount + topicDelta} child ${topicCount + topicDelta === 1 ? 'topic' : 'topics'}`
|
||||
: ''
|
||||
const topicDelta = topic.hasMessage() ? -1 : 0
|
||||
const childTopicsMessage =
|
||||
topicCount + topicDelta > 0
|
||||
? ` and ${topicCount + topicDelta} child ${topicCount + topicDelta === 1 ? 'topic' : 'topics'}`
|
||||
: ''
|
||||
|
||||
const confirmed = await dispatch(
|
||||
globalActions.requestConfirmation(
|
||||
'Confirm delete',
|
||||
`Do you want to clear "${topic.path()}"${childTopicsMessage}?\n\nThis function will send an empty payload (QoS 0, retain) to this and every subtopic, clearing retained topics in the process. Only use this function if you know what you are doing.`
|
||||
)
|
||||
const confirmed = await dispatch(
|
||||
globalActions.requestConfirmation(
|
||||
'Confirm delete',
|
||||
`Do you want to clear "${topic.path()}"${childTopicsMessage}?\n\nThis function will send an empty payload (QoS 0, retain) to this and every subtopic, clearing retained topics in the process. Only use this function if you know what you are doing.`
|
||||
)
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(moveSelectionUpOrDownwards('next'))
|
||||
|
||||
const { connectionId } = getState().connection
|
||||
if (!connectionId) {
|
||||
)
|
||||
if (!confirmed) {
|
||||
return
|
||||
}
|
||||
const publishEvent = makePublishEvent(connectionId)
|
||||
|
||||
topicsForPurging
|
||||
.filter(t => t.path() !== '' && t.hasMessage())
|
||||
.map(t => t.path())
|
||||
.forEach((path, idx) => {
|
||||
const mqttMessage = {
|
||||
topic: path,
|
||||
payload: null,
|
||||
retain: true,
|
||||
qos: 0 as 0,
|
||||
messageId: undefined,
|
||||
}
|
||||
// Rate limit deletion
|
||||
setTimeout(() => rendererEvents.emit(publishEvent, mqttMessage), 20 * idx)
|
||||
})
|
||||
}
|
||||
|
||||
dispatch(moveSelectionUpOrDownwards('next'))
|
||||
|
||||
const { connectionId } = getState().connection
|
||||
if (!connectionId) {
|
||||
return
|
||||
}
|
||||
const publishEvent = makePublishEvent(connectionId)
|
||||
|
||||
topicsForPurging
|
||||
.filter(t => t.path() !== '' && t.hasMessage())
|
||||
.map(t => t.path())
|
||||
.forEach((path, idx) => {
|
||||
const mqttMessage = {
|
||||
topic: path,
|
||||
payload: null,
|
||||
retain: true,
|
||||
qos: 0 as 0,
|
||||
messageId: undefined,
|
||||
}
|
||||
// Rate limit deletion
|
||||
setTimeout(() => rendererEvents.emit(publishEvent, mqttMessage), 20 * idx)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,56 +6,53 @@ import { SettingsState } from '../reducers/Settings'
|
||||
import { sortedNodes } from '../sortedNodes'
|
||||
import { TopicViewModel } from '../model/TopicViewModel'
|
||||
|
||||
export const moveSelectionUpOrDownwards =
|
||||
(direction: 'next' | 'previous') =>
|
||||
(dispatch: Dispatch<any>, getState: () => AppState): any => {
|
||||
const state = getState()
|
||||
const selected = state.tree.get('selectedTopic')
|
||||
const tree = state.tree.get('tree')
|
||||
export const moveSelectionUpOrDownwards = (direction: 'next' | 'previous') => (
|
||||
dispatch: Dispatch<any>,
|
||||
getState: () => AppState
|
||||
): any => {
|
||||
const state = getState()
|
||||
const selected = state.tree.get('selectedTopic')
|
||||
const tree = state.tree.get('tree')
|
||||
|
||||
if (!selected || !tree) {
|
||||
if (tree) {
|
||||
dispatch(selectTopic(tree))
|
||||
}
|
||||
return
|
||||
}
|
||||
const nextTreeNode = nextVisibleElementInTree(state.settings, tree, selected, direction)
|
||||
if (nextTreeNode && nextTreeNode.viewModel) {
|
||||
dispatch(selectTopic(nextTreeNode))
|
||||
if (!selected || !tree) {
|
||||
if (tree) {
|
||||
dispatch(selectTopic(tree))
|
||||
}
|
||||
return
|
||||
}
|
||||
const nextTreeNode = nextVisibleElementInTree(state.settings, tree, selected, direction)
|
||||
if (nextTreeNode && nextTreeNode.viewModel) {
|
||||
dispatch(selectTopic(nextTreeNode))
|
||||
}
|
||||
}
|
||||
|
||||
export const moveInward = () => (dispatch: Dispatch<any>, getState: () => AppState): any => {
|
||||
const state = getState()
|
||||
const selected = state.tree.get('selectedTopic')
|
||||
if (!selected || !selected.viewModel) {
|
||||
return
|
||||
}
|
||||
|
||||
export const moveInward =
|
||||
() =>
|
||||
(dispatch: Dispatch<any>, getState: () => AppState): any => {
|
||||
const state = getState()
|
||||
const selected = state.tree.get('selectedTopic')
|
||||
if (!selected || !selected.viewModel) {
|
||||
return
|
||||
}
|
||||
if (!selected.viewModel.isExpanded() && selected.edgeCount() > 0) {
|
||||
selected.viewModel.setExpanded(true, true)
|
||||
} else {
|
||||
dispatch(moveSelectionUpOrDownwards('next'))
|
||||
}
|
||||
}
|
||||
|
||||
if (!selected.viewModel.isExpanded() && selected.edgeCount() > 0) {
|
||||
selected.viewModel.setExpanded(true, true)
|
||||
} else {
|
||||
dispatch(moveSelectionUpOrDownwards('next'))
|
||||
}
|
||||
export const moveOutward = () => (dispatch: Dispatch<any>, getState: () => AppState): any => {
|
||||
const state = getState()
|
||||
const selected = state.tree.get('selectedTopic')
|
||||
if (!selected || !selected.viewModel) {
|
||||
return
|
||||
}
|
||||
|
||||
export const moveOutward =
|
||||
() =>
|
||||
(dispatch: Dispatch<any>, getState: () => AppState): any => {
|
||||
const state = getState()
|
||||
const selected = state.tree.get('selectedTopic')
|
||||
if (!selected || !selected.viewModel) {
|
||||
return
|
||||
}
|
||||
|
||||
if (selected.viewModel.isExpanded() && selected.edgeCount() > 0) {
|
||||
selected.viewModel.setExpanded(false, true)
|
||||
} else {
|
||||
dispatch(moveSelectionUpOrDownwards('previous'))
|
||||
}
|
||||
if (selected.viewModel.isExpanded() && selected.edgeCount() > 0) {
|
||||
selected.viewModel.setExpanded(false, true)
|
||||
} else {
|
||||
dispatch(moveSelectionUpOrDownwards('previous'))
|
||||
}
|
||||
}
|
||||
|
||||
function isTreeNodeVisible(treeNode: q.TreeNode<any>) {
|
||||
return Boolean(treeNode.viewModel)
|
||||
|
||||
@@ -12,7 +12,6 @@ import { ConfirmationRequest } from '../reducers/Global'
|
||||
import { connect } from 'react-redux'
|
||||
import { globalActions, settingsActions } from '../actions'
|
||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||
;(window as any).global = window
|
||||
|
||||
const Settings = React.lazy(() => import('./SettingsDrawer/Settings'))
|
||||
const ContentView = React.lazy(() => import('./Layout/ContentView'))
|
||||
@@ -67,8 +66,6 @@ class App extends React.PureComponent<Props, {}> {
|
||||
return null
|
||||
}
|
||||
|
||||
const anyProps: any = {}
|
||||
|
||||
return (
|
||||
<div className={centerContent}>
|
||||
<CssBaseline />
|
||||
@@ -76,7 +73,7 @@ class App extends React.PureComponent<Props, {}> {
|
||||
<ConfirmationDialog confirmationRequests={this.props.confirmationRequests} />
|
||||
{this.renderNotification()}
|
||||
<React.Suspense fallback={<div></div>}>
|
||||
<Settings {...anyProps} />
|
||||
<Settings />
|
||||
</React.Suspense>
|
||||
<div className={centerContent}>
|
||||
<div className={`${settingsVisible ? contentShift : content}`}>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import * as React from 'react'
|
||||
import Add from '@material-ui/icons/Add'
|
||||
import ClearAdornment from '../helper/ClearAdornment'
|
||||
import Delete from '@material-ui/icons/Delete'
|
||||
import Lock from '@material-ui/icons/Lock'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { Button, Theme, Tooltip, Typography } from '@material-ui/core'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react'
|
||||
import { Theme, withStyles } from '@material-ui/core'
|
||||
const cursor = require('./cursor.png')
|
||||
import cursor from './cursor.png'
|
||||
|
||||
interface State {
|
||||
enabled: boolean
|
||||
|
||||
@@ -5,7 +5,7 @@ let heapdump: any
|
||||
|
||||
function writeHeapdump(path?: string) {
|
||||
if (!heapdump) {
|
||||
//<heapdump = require('heapdump')
|
||||
heapdump = require('heapdump')
|
||||
}
|
||||
|
||||
heapdump.writeSnapshot(path || `${Date.now()}.heapsnapshot`)
|
||||
|
||||
@@ -2,6 +2,7 @@ import * as React from 'react'
|
||||
import PersistentStorage from '../utils/PersistentStorage'
|
||||
import SentimentDissatisfied from '@material-ui/icons/SentimentDissatisfied'
|
||||
import Warning from '@material-ui/icons/Warning'
|
||||
import { electronRendererTelemetry } from 'electron-telemetry'
|
||||
import { Theme, withStyles } from '@material-ui/core/styles'
|
||||
import { Button, Modal, Paper, Toolbar, Typography } from '@material-ui/core'
|
||||
|
||||
@@ -32,7 +33,7 @@ class ErrorBoundary extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
public componentDidCatch(error: Error, errorInfo: any) {
|
||||
// electronRendererTelemetry.trackError(error)
|
||||
electronRendererTelemetry.trackError(error)
|
||||
console.log('did catch', error)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,17 +3,10 @@ import DateFormatter from '../helper/DateFormatter'
|
||||
import { AppState } from '../../reducers'
|
||||
import { bindActionCreators } from 'redux'
|
||||
import { connect } from 'react-redux'
|
||||
import { Input, InputLabel, MenuItem, Select, Theme } from '@material-ui/core'
|
||||
import { Input, InputLabel, MenuItem, Select, StyleRulesCallback, Theme } from '@material-ui/core'
|
||||
import { settingsActions } from '../../actions'
|
||||
import { withStyles } from '@material-ui/styles'
|
||||
|
||||
function importAll(r: any) {
|
||||
r.keys().forEach(r)
|
||||
}
|
||||
// @ts-expect-error -- webpack require
|
||||
importAll(require.context('moment/locale', true, /\.js$/))
|
||||
|
||||
const moment = require('moment')
|
||||
const moment = require('moment/min/moment-with-locales')
|
||||
|
||||
interface Props {
|
||||
actions: {
|
||||
|
||||
@@ -43,9 +43,9 @@ class CodeDiff extends React.PureComponent<Props, State> {
|
||||
private plottableLiteralsIndexedWithLineNumbers() {
|
||||
const allLiterals = this.isValidJson(this.props.current) ? literalsMappedByLines(this.props.current) || [] : []
|
||||
|
||||
return allLiterals.map((l: JsonPropertyLocation) =>
|
||||
isPlottable(l.value) ? l : undefined
|
||||
) as Array<JsonPropertyLocation>
|
||||
return allLiterals.map((l: JsonPropertyLocation) => (isPlottable(l.value) ? l : undefined)) as Array<
|
||||
JsonPropertyLocation
|
||||
>
|
||||
}
|
||||
|
||||
private renderStyledCodeLines(changes: Array<Diff.Change>) {
|
||||
|
||||
@@ -30,5 +30,4 @@ const Panel = (props: {
|
||||
)
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
export default withStyles(styles)(Panel)
|
||||
|
||||
@@ -122,7 +122,7 @@ const EditorMode = memo(function EditorMode(props: {
|
||||
const str = JSON.stringify(JSON.parse(props.payload), undefined, ' ')
|
||||
updatePayload(str)
|
||||
} catch (error) {
|
||||
props.globalActions.showError(`Format error: ${(error as Error)?.message}`)
|
||||
props.globalActions.showError(`Format error: ${error.message}`)
|
||||
}
|
||||
}
|
||||
}, [props.payload])
|
||||
|
||||
@@ -40,7 +40,7 @@ function ActionButtons(props: {
|
||||
</Tooltip>
|
||||
</ToggleButton>
|
||||
<ToggleButton className={props.classes.toggleButton} value="raw" id="valueRendererDisplayMode-raw">
|
||||
<Tooltip title="Raw / formatted JSON / formatted sparkplugb protojson">
|
||||
<Tooltip title="Raw / formatted JSON">
|
||||
<span>
|
||||
<Reorder className={props.classes.toggleButtonIcon} />
|
||||
</span>
|
||||
|
||||
@@ -128,5 +128,4 @@ const styles = (theme: Theme) => ({
|
||||
},
|
||||
})
|
||||
|
||||
// @ts-ignore
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(ValuePanel))
|
||||
|
||||
@@ -4,9 +4,9 @@ import CodeDiff from '../CodeDiff'
|
||||
import { AppState } from '../../../reducers'
|
||||
import { Base64Message } from '../../../../../backend/src/Model/Base64Message'
|
||||
import { connect } from 'react-redux'
|
||||
import { default as ReactResizeDetector } from 'react-resize-detector'
|
||||
import { ValueRendererDisplayMode } from '../../../reducers/Settings'
|
||||
import { Fade } from '@material-ui/core'
|
||||
import { Decoder } from '../../../../../backend/src/Model/Decoder'
|
||||
import { Typography, Fade, Grow } from '@material-ui/core'
|
||||
|
||||
interface Props {
|
||||
message: q.Message
|
||||
@@ -83,12 +83,7 @@ class ValueRenderer extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
public render() {
|
||||
return (
|
||||
<div style={{ padding: '0px 0px 8px 0px', width: '100%' }}>
|
||||
{this.props.message?.payload?.decoder === Decoder.SPARKPLUG && 'Decoded SparkplugB'}
|
||||
{this.renderValue()}
|
||||
</div>
|
||||
)
|
||||
return <div style={{ padding: '0px 0px 8px 0px', width: '100%' }}>{this.renderValue()}</div>
|
||||
}
|
||||
|
||||
public renderValue() {
|
||||
|
||||
@@ -37,7 +37,7 @@ function nodeToHistory(startTime: number | undefined, history: q.MessageHistory)
|
||||
function nodeDotPathToHistory(startTime: number | undefined, history: q.MessageHistory, dotPath: string) {
|
||||
return filterUsingTimeRange(startTime, history.toArray())
|
||||
.map((message: q.Message) => {
|
||||
let json: any = {}
|
||||
let json = {}
|
||||
try {
|
||||
json = message.payload ? JSON.parse(Base64Message.toUnicodeString(message.payload)) : {}
|
||||
} catch (ignore) {}
|
||||
|
||||
@@ -19,7 +19,7 @@ class TreeNodeTitle extends React.PureComponent<TreeNodeProps, {}> {
|
||||
const name = this.props.name || (this.props.treeNode.sourceEdge && this.props.treeNode.sourceEdge.name)
|
||||
|
||||
return (
|
||||
<span key="edge" className={this.props.classes.sourceEdge} data-test-topic={name}>
|
||||
<span key="edge" className={this.props.classes.sourceEdge}>
|
||||
{name}
|
||||
</span>
|
||||
)
|
||||
|
||||
@@ -91,26 +91,23 @@ class TreeComponent extends React.PureComponent<Props, State> {
|
||||
const updateInterval = Math.max(expectedRenderTime * 7, 300)
|
||||
const timeUntilNextUpdate = updateInterval - (performance.now() - this.renderTime)
|
||||
|
||||
this.updateTimer = setTimeout(
|
||||
() => {
|
||||
window.requestIdleCallback(
|
||||
() => {
|
||||
this.updateTimer && clearTimeout(this.updateTimer)
|
||||
this.updateTimer = undefined
|
||||
this.renderTime = performance.now()
|
||||
this.updateTimer = setTimeout(() => {
|
||||
window.requestIdleCallback(
|
||||
() => {
|
||||
this.updateTimer && clearTimeout(this.updateTimer)
|
||||
this.updateTimer = undefined
|
||||
this.renderTime = performance.now()
|
||||
|
||||
window.requestIdleCallback(
|
||||
() => {
|
||||
this.setState({ lastUpdate: this.renderTime })
|
||||
},
|
||||
{ timeout: 100 }
|
||||
)
|
||||
},
|
||||
{ timeout: 500 }
|
||||
)
|
||||
},
|
||||
Math.max(0, timeUntilNextUpdate)
|
||||
)
|
||||
window.requestIdleCallback(
|
||||
() => {
|
||||
this.setState({ lastUpdate: this.renderTime })
|
||||
},
|
||||
{ timeout: 100 }
|
||||
)
|
||||
},
|
||||
{ timeout: 500 }
|
||||
)
|
||||
}, Math.max(0, timeUntilNextUpdate))
|
||||
}
|
||||
|
||||
public componentWillUpdate() {
|
||||
|
||||
@@ -13,7 +13,6 @@ import { Theme, withStyles } from '@material-ui/core/styles'
|
||||
import { updateNotifierActions } from '../actions'
|
||||
|
||||
import { Button, IconButton, Modal, Paper, Snackbar, SnackbarContent, Typography } from '@material-ui/core'
|
||||
import { rendererRpc, getAppVersion } from '../../../events'
|
||||
|
||||
interface Props {
|
||||
showUpdateNotification: boolean
|
||||
@@ -51,21 +50,18 @@ class UpdateNotifier extends React.PureComponent<Props, State> {
|
||||
super(props)
|
||||
this.state = { newerVersions: [] }
|
||||
|
||||
this.checkForUpdates()
|
||||
}
|
||||
const ownVersion = electron.remote.app.getVersion()
|
||||
this.fetchReleases().then(releases => {
|
||||
const newerVersions = releases
|
||||
.filter(release => this.allowPrereleaseIfOwnVersionIsBeta(release, ownVersion))
|
||||
.filter(release => compareVersions(release.tag_name, ownVersion) > 0)
|
||||
.sort((a, b) => compareVersions(b.tag_name, a.tag_name))
|
||||
|
||||
private async checkForUpdates() {
|
||||
const ownVersion = await rendererRpc.call(getAppVersion, undefined, 10000)
|
||||
const releases = await this.fetchReleases()
|
||||
const newerVersions = releases
|
||||
.filter(release => this.allowPrereleaseIfOwnVersionIsBeta(release, ownVersion))
|
||||
.filter(release => compareVersions(release.tag_name, ownVersion) > 0)
|
||||
.sort((a, b) => compareVersions(b.tag_name, a.tag_name))
|
||||
|
||||
if (newerVersions.length > 0) {
|
||||
this.setState({ newerVersions })
|
||||
this.props.actions.showUpdateNotification(true)
|
||||
}
|
||||
if (newerVersions.length > 0) {
|
||||
this.setState({ newerVersions })
|
||||
this.props.actions.showUpdateNotification(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private allowPrereleaseIfOwnVersionIsBeta(release: GithubRelease, ownVersion: string) {
|
||||
|
||||
@@ -45,11 +45,9 @@ const initialStateFactory = Record<TreeStateModel>({
|
||||
filter: undefined,
|
||||
})
|
||||
|
||||
const setPaused =
|
||||
(pause: boolean) =>
|
||||
(state: TreeState, action: ShowTree): TreeState => {
|
||||
return state.set('paused', pause)
|
||||
}
|
||||
const setPaused = (pause: boolean) => (state: TreeState, action: ShowTree): TreeState => {
|
||||
return state.set('paused', pause)
|
||||
}
|
||||
|
||||
const actions: {
|
||||
[s: string]: (state: TreeState, action: ReduxAction) => TreeState
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { rendererRpc } from '../../../events'
|
||||
import { rendererEvents } from '../../../events'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
import { storageStoreEvent, storageLoadEvent, storageClearEvent } from '../../../events/StorageEvents'
|
||||
import {
|
||||
storageStoreEvent,
|
||||
makeStorageResponseEvent,
|
||||
storageLoadEvent,
|
||||
storageClearEvent,
|
||||
makeStorageAcknowledgementEvent,
|
||||
} from '../../../events/StorageEvents'
|
||||
|
||||
export interface StorageIdentifier<Model> {
|
||||
id: string
|
||||
@@ -13,27 +20,71 @@ export interface PersistentStorage {
|
||||
}
|
||||
|
||||
class RemoteStorage implements PersistentStorage {
|
||||
public store<Model>(identifier: StorageIdentifier<Model>, data: Model): Promise<void> {
|
||||
return rendererRpc.call(storageStoreEvent, {
|
||||
data,
|
||||
store: identifier.id,
|
||||
private timeoutCallback(event: any, callback: any, reject: any) {
|
||||
setTimeout(() => {
|
||||
reject('remote storage timeout')
|
||||
rendererEvents.unsubscribe(event, callback)
|
||||
}, 10000)
|
||||
}
|
||||
|
||||
private expectAck(transactionId: string): Promise<void> {
|
||||
const ack = makeStorageAcknowledgementEvent(transactionId)
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const callback = (msg: any) => {
|
||||
if (msg && msg.error) {
|
||||
reject(msg.error)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
rendererEvents.unsubscribe(ack, callback)
|
||||
}
|
||||
rendererEvents.subscribe(ack, callback)
|
||||
this.timeoutCallback(ack, callback, reject)
|
||||
})
|
||||
}
|
||||
|
||||
public async load<Model>(identifier: StorageIdentifier<Model>): Promise<Model | undefined> {
|
||||
const result = await rendererRpc.call(
|
||||
storageLoadEvent,
|
||||
{
|
||||
store: identifier.id,
|
||||
},
|
||||
10000
|
||||
)
|
||||
public store<Model>(identifier: StorageIdentifier<Model>, data: Model): Promise<void> {
|
||||
const transactionId = v4()
|
||||
const expectation = this.expectAck(transactionId)
|
||||
rendererEvents.emit(storageStoreEvent, {
|
||||
data,
|
||||
transactionId,
|
||||
store: identifier.id,
|
||||
})
|
||||
return expectation
|
||||
}
|
||||
|
||||
return (result as any).data
|
||||
public load<Model>(identifier: StorageIdentifier<Model>): Promise<Model | undefined> {
|
||||
const transactionId = v4()
|
||||
const responseEvent = makeStorageResponseEvent(transactionId)
|
||||
|
||||
const promise = new Promise<Model>((resolve, reject) => {
|
||||
const callback = (msg: any) => {
|
||||
if (msg.error) {
|
||||
reject(msg.error)
|
||||
} else {
|
||||
resolve(msg.data)
|
||||
}
|
||||
rendererEvents.unsubscribe(responseEvent, callback)
|
||||
}
|
||||
rendererEvents.subscribe(responseEvent, callback)
|
||||
this.timeoutCallback(responseEvent, callback, reject)
|
||||
})
|
||||
|
||||
rendererEvents.emit(storageLoadEvent, {
|
||||
transactionId,
|
||||
store: identifier.id,
|
||||
})
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
public clear(): Promise<void> {
|
||||
return rendererRpc.call(storageClearEvent, undefined, 10000)
|
||||
const transactionId = v4()
|
||||
const expectation = this.expectAck(transactionId)
|
||||
|
||||
rendererEvents.emit(storageClearEvent, { transactionId })
|
||||
return expectation
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// import { electronRendererTelemetry } from 'electron-telemetry'
|
||||
import { electronRendererTelemetry } from 'electron-telemetry'
|
||||
|
||||
// const telemetry = electronRendererTelemetry
|
||||
// electronRendererTelemetry.registerErrorHandler()
|
||||
const telemetry = electronRendererTelemetry
|
||||
electronRendererTelemetry.registerErrorHandler()
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
export const selectTextWithCtrlA =
|
||||
(options?: { targetSelector: string }) => (e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
const isCtrlA = (e.metaKey || e.ctrlKey) && e.key === 'a'
|
||||
export const selectTextWithCtrlA = (options?: { targetSelector: string }) => (
|
||||
e: React.KeyboardEvent<HTMLDivElement>
|
||||
) => {
|
||||
const isCtrlA = (e.metaKey || e.ctrlKey) && e.key === 'a'
|
||||
|
||||
if (isCtrlA && window.getSelection) {
|
||||
e.persist()
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const selection = window.getSelection()
|
||||
const range = document.createRange()
|
||||
const eventTarget = e.target as HTMLElement
|
||||
const target: HTMLElement | null = options ? eventTarget.querySelector(options.targetSelector) : eventTarget
|
||||
if (isCtrlA && window.getSelection) {
|
||||
e.persist()
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
const selection = window.getSelection()
|
||||
const range = document.createRange()
|
||||
const eventTarget = e.target as HTMLElement
|
||||
const target: HTMLElement | null = options ? eventTarget.querySelector(options.targetSelector) : eventTarget
|
||||
|
||||
if (!target) {
|
||||
console.error('Could not find matching target for Ctrl+A Event')
|
||||
}
|
||||
if (selection && target) {
|
||||
range.selectNodeContents(target)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
if (!target) {
|
||||
console.error('Could not find matching target for Ctrl+A Event')
|
||||
}
|
||||
if (selection && target) {
|
||||
range.selectNodeContents(target)
|
||||
selection.removeAllRanges()
|
||||
selection.addRange(range)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
-23
@@ -1,27 +1,27 @@
|
||||
// import { electronRendererTelemetry } from 'electron-telemetry'
|
||||
import { electronRendererTelemetry } from 'electron-telemetry'
|
||||
|
||||
// Used to determine long-time-stability and memory leaks
|
||||
// function trackProcessStatistics() {
|
||||
// setInterval(() => {
|
||||
// try {
|
||||
// electronRendererTelemetry.trackCustomEvent({
|
||||
// name: 'heapStatistics',
|
||||
// payload: process.getHeapStatistics(),
|
||||
// })
|
||||
// electronRendererTelemetry.trackCustomEvent({
|
||||
// name: 'cpuUsage',
|
||||
// payload: process.getCPUUsage(),
|
||||
// })
|
||||
// electronRendererTelemetry.trackCustomEvent({
|
||||
// name: 'runningSince',
|
||||
// payload: performance.now(),
|
||||
// })
|
||||
// } catch (error) {
|
||||
// console.error(error)
|
||||
// }
|
||||
// }, 60 * 1000)
|
||||
// }
|
||||
// trackProcessStatistics()
|
||||
function trackProcessStatistics() {
|
||||
setInterval(() => {
|
||||
try {
|
||||
electronRendererTelemetry.trackCustomEvent({
|
||||
name: 'heapStatistics',
|
||||
payload: process.getHeapStatistics(),
|
||||
})
|
||||
electronRendererTelemetry.trackCustomEvent({
|
||||
name: 'cpuUsage',
|
||||
payload: process.getCPUUsage(),
|
||||
})
|
||||
electronRendererTelemetry.trackCustomEvent({
|
||||
name: 'runningSince',
|
||||
payload: performance.now(),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}, 60 * 1000)
|
||||
}
|
||||
trackProcessStatistics()
|
||||
|
||||
// Log reducer event names to determine what functionality is used and how to reproduce reported errors
|
||||
export function trackEvent(name: string) {
|
||||
@@ -30,6 +30,6 @@ export function trackEvent(name: string) {
|
||||
}
|
||||
const blacklist = ['CONNECTION_SET_HEALTH']
|
||||
if (blacklist.indexOf(name) === -1) {
|
||||
// electronRendererTelemetry.trackEvent(name)
|
||||
electronRendererTelemetry.trackEvent(name)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
--require ts-node/register
|
||||
--require source-map-support/register
|
||||
--recursive ./src/**/*.spec.ts
|
||||
+6
-23
@@ -4,38 +4,21 @@
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom"
|
||||
],
|
||||
"lib": ["es2017", "dom"],
|
||||
"moduleResolution": "node",
|
||||
"outDir": "./build/",
|
||||
"sourceMap": true,
|
||||
"module": "esnext",
|
||||
"target": "es2017",
|
||||
"jsx": "react",
|
||||
"paths": {
|
||||
"react": [
|
||||
"./node_modules/@types/react"
|
||||
]
|
||||
},
|
||||
"types": [
|
||||
"react"
|
||||
],
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"skipLibCheck": true
|
||||
"types": ["react"],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.d.ts",
|
||||
".src/**/*.png",
|
||||
"./node_modules"
|
||||
],
|
||||
"include": ["./src/**/*"],
|
||||
"exclude": ["**/*.d.ts", ".src/**/*.png"],
|
||||
"awesomeTypescriptLoaderOptions": {
|
||||
"useCache": true,
|
||||
"transpileModule": true,
|
||||
"errorsAsWarnings": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
-22
@@ -1,6 +1,7 @@
|
||||
// const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const webpack = require('webpack')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const webpack = require('webpack');
|
||||
const HardSourceWebpackPlugin = require('hard-source-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
@@ -17,11 +18,12 @@ module.exports = {
|
||||
splitChunks: {
|
||||
chunks: 'all',
|
||||
minSize: 30000,
|
||||
maxSize: 0,
|
||||
minChunks: 1,
|
||||
maxAsyncRequests: 5,
|
||||
maxInitialRequests: 3,
|
||||
automaticNameDelimiter: '~',
|
||||
// name: true,
|
||||
name: true,
|
||||
cacheGroups: {
|
||||
vendors: {
|
||||
test: /[\\/]node_modules[\\/](react|react-dom|@material-ui|popper\.js|react|react-redux|prop-types|jss|redux|scheduler|react-transition-group)[\\/]/,
|
||||
@@ -30,7 +32,6 @@ module.exports = {
|
||||
priority: -10,
|
||||
},
|
||||
default: {
|
||||
name: 'default',
|
||||
minChunks: 2,
|
||||
priority: -20,
|
||||
reuseExistingChunk: true,
|
||||
@@ -54,7 +55,13 @@ module.exports = {
|
||||
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'ts-loader',
|
||||
loader: 'awesome-typescript-loader',
|
||||
options: {
|
||||
reportFiles: [
|
||||
"src/**/*.{ts,tsx}",
|
||||
"../backend/src/**/*.{ts,tsx}"
|
||||
]
|
||||
}
|
||||
},
|
||||
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
|
||||
{ enforce: 'pre', test: /\.js$/, loader: 'source-map-loader' },
|
||||
@@ -63,29 +70,35 @@ module.exports = {
|
||||
use: ['style-loader', 'css-loader'],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif)$/i,
|
||||
type: 'asset/resource',
|
||||
test: /\.(png|jpg|gif)$/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.node$/,
|
||||
use: {
|
||||
loader: 'node-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
// {
|
||||
// test: /\.node$/,
|
||||
// use: {
|
||||
// loader: 'node-loader',
|
||||
// options: {
|
||||
// modules: true,
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
],
|
||||
},
|
||||
// node: { global: true },
|
||||
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({ template: './index.html', file: './build/index.html', inject: false }),
|
||||
// new BundleAnalyzerPlugin(),
|
||||
new HardSourceWebpackPlugin(),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
// new webpack.IgnorePlugin({
|
||||
// resourceRegExp: /\.\/build\/Debug\/addon/,
|
||||
// contextRegExp: /heapdump$/
|
||||
// }),
|
||||
new webpack.IgnorePlugin({
|
||||
resourceRegExp: /\.\/build\/Debug\/addon/,
|
||||
contextRegExp: /heapdump$/
|
||||
}),
|
||||
],
|
||||
|
||||
// When importing a module whose path matches one of the following, just
|
||||
@@ -96,4 +109,4 @@ module.exports = {
|
||||
// "react": "React",
|
||||
// "react-dom": "ReactDOM"
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
+5038
-2691
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -7,10 +7,10 @@ cache:
|
||||
- '%LOCALAPPDATA%\electron-builder\cache'
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 19
|
||||
- ps: Install-Product node 10
|
||||
|
||||
build_script:
|
||||
- yarn install --frozen-lockfile
|
||||
- yarn
|
||||
- yarn build
|
||||
- yarn prepare-release
|
||||
- yarn package appx
|
||||
|
||||
+5
-10
@@ -4,16 +4,13 @@
|
||||
"description": "",
|
||||
"main": "build/index.js",
|
||||
"scripts": {
|
||||
"test": "mocha --require ts-node/register --require source-map-support/register --recursive ./src/*/**/*.spec.ts",
|
||||
"test": "mocha",
|
||||
"build": "tsc",
|
||||
"test-inspect": "mocha --inspect-brk --require ts-node/register --require source-map-support/register --recursive ./src/*/**/*.spec.ts",
|
||||
"coverage": "nyc mocha --require ts-node/register --require source-map-support/register --recursive ./src/*/**/*.spec.ts",
|
||||
"test-inspect": "mocha --inspect-brk",
|
||||
"coverage": "nyc mocha",
|
||||
"debug": "ts-node --inspect ./src/index.ts",
|
||||
"postinstall": "yarn build"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"author": "",
|
||||
"license": "CC-BY-ND-4.0",
|
||||
"nyc": {
|
||||
@@ -41,9 +38,7 @@
|
||||
"peerDependencies": {
|
||||
"fs-extra": "^8.0.1",
|
||||
"js-base64": "^2.5.1",
|
||||
"long": "^4.0.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"mqtt": "^3.0.0",
|
||||
"protobufjs": "^6.11.4"
|
||||
"mqtt": "^3.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,14 @@ import * as FileAsync from 'lowdb/adapters/FileAsync'
|
||||
import * as fs from 'fs-extra'
|
||||
import * as lowdb from 'lowdb'
|
||||
import * as path from 'path'
|
||||
import { backendRpc } from '../../events'
|
||||
import { storageClearEvent, storageLoadEvent, storageStoreEvent } from '../../events/StorageEvents'
|
||||
import { backendEvents } from '../../events'
|
||||
import {
|
||||
makeStorageAcknowledgementEvent,
|
||||
makeStorageResponseEvent,
|
||||
storageClearEvent,
|
||||
storageLoadEvent,
|
||||
storageStoreEvent,
|
||||
} from '../../events/StorageEvents'
|
||||
|
||||
export default class ConfigStorage {
|
||||
private file: string
|
||||
@@ -26,26 +32,56 @@ export default class ConfigStorage {
|
||||
}
|
||||
|
||||
public async init() {
|
||||
backendRpc.on(storageStoreEvent, async event => {
|
||||
const db = await this.getDb()
|
||||
await db.set(event.store, event.data).write()
|
||||
return
|
||||
})
|
||||
|
||||
backendRpc.on(storageLoadEvent, async event => {
|
||||
const db = await this.getDb()
|
||||
const data = await db.get(event.store).value()
|
||||
return {
|
||||
data,
|
||||
store: event.store,
|
||||
backendEvents.subscribe(storageStoreEvent, async event => {
|
||||
const ack = makeStorageAcknowledgementEvent(event.transactionId)
|
||||
try {
|
||||
const db = await this.getDb()
|
||||
await db.set(event.store, event.data).write()
|
||||
backendEvents.emit(ack, undefined)
|
||||
} catch (error) {
|
||||
backendEvents.emit(ack, {
|
||||
error,
|
||||
transactionId: event.transactionId,
|
||||
store: event.store,
|
||||
})
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
backendRpc.on(storageClearEvent, async event => {
|
||||
const db = await this.getDb()
|
||||
const keys = await db.keys().value()
|
||||
for (const key of keys) {
|
||||
await db.unset(key).write()
|
||||
backendEvents.subscribe(storageLoadEvent, async event => {
|
||||
const responseEvent = makeStorageResponseEvent(event.transactionId)
|
||||
try {
|
||||
const db = await this.getDb()
|
||||
const data = await db.get(event.store).value()
|
||||
backendEvents.emit(responseEvent, {
|
||||
data,
|
||||
transactionId: event.transactionId,
|
||||
store: event.store,
|
||||
})
|
||||
} catch (error) {
|
||||
backendEvents.emit(responseEvent, {
|
||||
error,
|
||||
transactionId: event.transactionId,
|
||||
store: event.store,
|
||||
})
|
||||
throw error
|
||||
}
|
||||
})
|
||||
|
||||
backendEvents.subscribe(storageClearEvent, async event => {
|
||||
try {
|
||||
const db = await this.getDb()
|
||||
const keys = await db.keys().value()
|
||||
for (const key of keys) {
|
||||
await db.unset(key).write()
|
||||
}
|
||||
backendEvents.emit(makeStorageAcknowledgementEvent(event.transactionId), undefined)
|
||||
} catch (error) {
|
||||
backendEvents.emit(makeStorageAcknowledgementEvent(event.transactionId), {
|
||||
error,
|
||||
transactionId: event.transactionId,
|
||||
})
|
||||
throw error
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { URL } from 'url'
|
||||
import * as Url from 'url'
|
||||
|
||||
import { Client, connect as mqttConnect } from 'mqtt'
|
||||
import { DataSource, DataSourceStateMachine } from './'
|
||||
@@ -41,13 +41,13 @@ export class MqttSource implements DataSource<MqttOptions> {
|
||||
const urlStr = options.tls ? options.url.replace(/^(mqtt|ws):/, '$1s:') : options.url
|
||||
let url
|
||||
try {
|
||||
url = new URL(urlStr)
|
||||
url = Url.parse(urlStr)
|
||||
} catch (error) {
|
||||
this.stateMachine.setError(error as Error)
|
||||
this.stateMachine.setError(error)
|
||||
throw error
|
||||
}
|
||||
|
||||
const client = mqttConnect(url.toString(), {
|
||||
const client = mqttConnect(url, {
|
||||
resubscribe: false,
|
||||
rejectUnauthorized: options.certValidation,
|
||||
username: options.username,
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Base64 } from 'js-base64'
|
||||
import { Decoder } from './Decoder'
|
||||
const { Base64 } = require('js-base64')
|
||||
|
||||
export class Base64Message {
|
||||
private base64Message: string
|
||||
private unicodeValue: string
|
||||
public decoder: Decoder
|
||||
|
||||
public length: number
|
||||
|
||||
private constructor(base64Str: string) {
|
||||
this.base64Message = base64Str
|
||||
this.unicodeValue = Base64.decode(base64Str)
|
||||
this.length = base64Str.length
|
||||
this.decoder = Decoder.NONE
|
||||
}
|
||||
|
||||
public static toUnicodeString(message: Base64Message) {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
export enum Decoder {
|
||||
NONE,
|
||||
SPARKPLUG,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ChangeBuffer } from './ChangeBuffer'
|
||||
import { Destroyable } from './Destroyable'
|
||||
import { EventDispatcher, makeConnectionMessageEvent, MqttMessage, EventBusInterface } from '../../../events'
|
||||
import { EventBusInterface, EventDispatcher, makeConnectionMessageEvent, MqttMessage } from '../../../events'
|
||||
import { TreeNode } from './'
|
||||
import { TreeNodeFactory } from './TreeNodeFactory'
|
||||
|
||||
|
||||
@@ -1,204 +0,0 @@
|
||||
/* spell-checker: disable */
|
||||
|
||||
const protocol = `
|
||||
syntax = "proto2";
|
||||
|
||||
//
|
||||
// To compile:
|
||||
// cd client_libraries/java
|
||||
// protoc --proto_path=../../ --java_out=src/main/java ../../sparkplug_b.proto
|
||||
//
|
||||
package com.cirruslink.sparkplug.protobuf;
|
||||
|
||||
option java_package = "com.cirruslink.sparkplug.protobuf";
|
||||
option java_outer_classname = "SparkplugBProto";
|
||||
|
||||
message Payload {
|
||||
/*
|
||||
// Indexes of Data Types
|
||||
|
||||
// Unknown placeholder for future expansion.
|
||||
Unknown = 0;
|
||||
|
||||
// Basic Types
|
||||
Int8 = 1;
|
||||
Int16 = 2;
|
||||
Int32 = 3;
|
||||
Int64 = 4;
|
||||
UInt8 = 5;
|
||||
UInt16 = 6;
|
||||
UInt32 = 7;
|
||||
UInt64 = 8;
|
||||
Float = 9;
|
||||
Double = 10;
|
||||
Boolean = 11;
|
||||
String = 12;
|
||||
DateTime = 13;
|
||||
Text = 14;
|
||||
|
||||
// Additional Metric Types
|
||||
UUID = 15;
|
||||
DataSet = 16;
|
||||
Bytes = 17;
|
||||
File = 18;
|
||||
Template = 19;
|
||||
|
||||
// Additional PropertyValue Types
|
||||
PropertySet = 20;
|
||||
PropertySetList = 21;
|
||||
|
||||
*/
|
||||
|
||||
message Template {
|
||||
|
||||
message Parameter {
|
||||
optional string name = 1;
|
||||
optional uint32 type = 2;
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 3;
|
||||
uint64 long_value = 4;
|
||||
float float_value = 5;
|
||||
double double_value = 6;
|
||||
bool boolean_value = 7;
|
||||
string string_value = 8;
|
||||
ParameterValueExtension extension_value = 9;
|
||||
}
|
||||
|
||||
message ParameterValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
optional string version = 1; // The version of the Template to prevent mismatches
|
||||
repeated Metric metrics = 2; // Each metric is the name of the metric and the datatype of the member but does not contain a value
|
||||
repeated Parameter parameters = 3;
|
||||
optional string template_ref = 4; // Reference to a template if this is extending a Template or an instance - must exist if an instance
|
||||
optional bool is_definition = 5;
|
||||
extensions 6 to max;
|
||||
}
|
||||
|
||||
message DataSet {
|
||||
|
||||
message DataSetValue {
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 1;
|
||||
uint64 long_value = 2;
|
||||
float float_value = 3;
|
||||
double double_value = 4;
|
||||
bool boolean_value = 5;
|
||||
string string_value = 6;
|
||||
DataSetValueExtension extension_value = 7;
|
||||
}
|
||||
|
||||
message DataSetValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
message Row {
|
||||
repeated DataSetValue elements = 1;
|
||||
extensions 2 to max; // For third party extensions
|
||||
}
|
||||
|
||||
optional uint64 num_of_columns = 1;
|
||||
repeated string columns = 2;
|
||||
repeated uint32 types = 3;
|
||||
repeated Row rows = 4;
|
||||
extensions 5 to max; // For third party extensions
|
||||
}
|
||||
|
||||
message PropertyValue {
|
||||
|
||||
optional uint32 type = 1;
|
||||
optional bool is_null = 2;
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 3;
|
||||
uint64 long_value = 4;
|
||||
float float_value = 5;
|
||||
double double_value = 6;
|
||||
bool boolean_value = 7;
|
||||
string string_value = 8;
|
||||
PropertySet propertyset_value = 9;
|
||||
PropertySetList propertysets_value = 10; // List of Property Values
|
||||
PropertyValueExtension extension_value = 11;
|
||||
}
|
||||
|
||||
message PropertyValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
message PropertySet {
|
||||
repeated string keys = 1; // Names of the properties
|
||||
repeated PropertyValue values = 2;
|
||||
extensions 3 to max;
|
||||
}
|
||||
|
||||
message PropertySetList {
|
||||
repeated PropertySet propertyset = 1;
|
||||
extensions 2 to max;
|
||||
}
|
||||
|
||||
message MetaData {
|
||||
// Bytes specific metadata
|
||||
optional bool is_multi_part = 1;
|
||||
|
||||
// General metadata
|
||||
optional string content_type = 2; // Content/Media type
|
||||
optional uint64 size = 3; // File size, String size, Multi-part size, etc
|
||||
optional uint64 seq = 4; // Sequence number for multi-part messages
|
||||
|
||||
// File metadata
|
||||
optional string file_name = 5; // File name
|
||||
optional string file_type = 6; // File type (i.e. xml, json, txt, cpp, etc)
|
||||
optional string md5 = 7; // md5 of data
|
||||
|
||||
// Catchalls and future expansion
|
||||
optional string description = 8; // Could be anything such as json or xml of custom properties
|
||||
extensions 9 to max;
|
||||
}
|
||||
|
||||
message Metric {
|
||||
|
||||
optional string name = 1; // Metric name - should only be included on birth
|
||||
optional uint64 alias = 2; // Metric alias - tied to name on birth and included in all later DATA messages
|
||||
optional uint64 timestamp = 3; // Timestamp associated with data acquisition time
|
||||
optional uint32 datatype = 4; // DataType of the metric/tag value
|
||||
optional bool is_historical = 5; // If this is historical data and should not update real time tag
|
||||
optional bool is_transient = 6; // Tells consuming clients such as MQTT Engine to not store this as a tag
|
||||
optional bool is_null = 7; // If this is null - explicitly say so rather than using -1, false, etc for some datatypes.
|
||||
optional MetaData metadata = 8; // Metadata for the payload
|
||||
optional PropertySet properties = 9;
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 10;
|
||||
uint64 long_value = 11;
|
||||
float float_value = 12;
|
||||
double double_value = 13;
|
||||
bool boolean_value = 14;
|
||||
string string_value = 15;
|
||||
bytes bytes_value = 16; // Bytes, File
|
||||
DataSet dataset_value = 17;
|
||||
Template template_value = 18;
|
||||
MetricValueExtension extension_value = 19;
|
||||
}
|
||||
|
||||
message MetricValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
optional uint64 timestamp = 1; // Timestamp at message sending time
|
||||
repeated Metric metrics = 2; // Repeated forever - no limit in Google Protobufs
|
||||
optional uint64 seq = 3; // Sequence number
|
||||
optional string uuid = 4; // UUID to track message type in terms of schema definitions
|
||||
optional bytes body = 5; // To optionally bypass the whole definition above
|
||||
extensions 6 to max; // For third party extensions
|
||||
}
|
||||
`
|
||||
|
||||
/* spell-checker: enable */
|
||||
export default protocol
|
||||
@@ -1,25 +0,0 @@
|
||||
// cSpell:words protobuf
|
||||
import * as protobuf from 'protobufjs'
|
||||
import protocol from './sparkplugb.proto'
|
||||
import { Base64Message } from './Base64Message'
|
||||
import { Decoder } from './Decoder'
|
||||
|
||||
const root = protobuf.parse(protocol).root
|
||||
/* cspell:disable-next-line */
|
||||
export let SparkplugPayload = root.lookupType('com.cirruslink.sparkplug.protobuf.Payload')
|
||||
|
||||
export const SparkplugDecoder = {
|
||||
decode(input: Buffer): Base64Message {
|
||||
try {
|
||||
const message = Base64Message.fromString(
|
||||
JSON.stringify(SparkplugPayload.toObject(SparkplugPayload.decode(new Uint8Array(input))))
|
||||
)
|
||||
message.decoder = Decoder.SPARKPLUG
|
||||
return message
|
||||
} catch {
|
||||
const message = Base64Message.fromString("Failed to decode sparkplugb payload")
|
||||
message.decoder = Decoder.NONE
|
||||
return message
|
||||
}
|
||||
},
|
||||
}
|
||||
+1
-10
@@ -10,7 +10,6 @@ import {
|
||||
makePublishEvent,
|
||||
removeConnection,
|
||||
} from '../../events'
|
||||
import { SparkplugDecoder } from './Model/sparkplugb'
|
||||
|
||||
export class ConnectionManager {
|
||||
private connections: { [s: string]: DataSource<any> } = {}
|
||||
@@ -47,17 +46,9 @@ export class ConnectionManager {
|
||||
buffer = buffer.slice(0, 20000)
|
||||
}
|
||||
|
||||
let decoded_payload = null
|
||||
// spell-checker: disable-next-line
|
||||
if (topic.match(/^spBv1\.0\/[^/]+\/[ND](DATA|CMD|DEATH|BIRTH)\/[^/]+(\/[^/]+)?$/u)) {
|
||||
decoded_payload = SparkplugDecoder.decode(buffer)
|
||||
} else {
|
||||
decoded_payload = Base64Message.fromBuffer(buffer)
|
||||
}
|
||||
|
||||
backendEvents.emit(messageEvent, {
|
||||
topic,
|
||||
payload: decoded_payload,
|
||||
payload: Base64Message.fromBuffer(buffer),
|
||||
qos: packet.qos,
|
||||
retain: packet.retain,
|
||||
messageId: packet.messageId,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
--require ts-node/register
|
||||
--require source-map-support/register
|
||||
--recursive ./src/**/*.spec.ts
|
||||
@@ -1,15 +1,11 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"outDir": "./build",
|
||||
"strict": true,
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom"
|
||||
],
|
||||
"lib": ["es2017", "dom"],
|
||||
"sourceMap": true
|
||||
},
|
||||
"includes": [
|
||||
@@ -22,4 +18,4 @@
|
||||
"**/*.d.ts",
|
||||
"typings"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+523
-82
@@ -2,96 +2,537 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
|
||||
integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==
|
||||
async-limiter@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8"
|
||||
integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==
|
||||
|
||||
"@protobufjs/base64@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735"
|
||||
integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
|
||||
|
||||
"@protobufjs/codegen@^2.0.4":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb"
|
||||
integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==
|
||||
base64-js@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3"
|
||||
integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw==
|
||||
|
||||
"@protobufjs/eventemitter@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70"
|
||||
integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==
|
||||
|
||||
"@protobufjs/fetch@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45"
|
||||
integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==
|
||||
bl@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||
integrity sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.1"
|
||||
"@protobufjs/inquire" "^1.1.0"
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
"@protobufjs/float@^1.0.2":
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
|
||||
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
|
||||
|
||||
callback-stream@^1.0.2:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/callback-stream/-/callback-stream-1.1.0.tgz#4701a51266f06e06eaa71fc17233822d875f4908"
|
||||
integrity sha1-RwGlEmbwbgbqpx/BcjOCLYdfSQg=
|
||||
dependencies:
|
||||
inherits "^2.0.1"
|
||||
readable-stream "> 1.0.0 < 3.0.0"
|
||||
|
||||
commist@^1.0.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/commist/-/commist-1.1.0.tgz#17811ec6978f6c15ee4de80c45c9beb77cee35d5"
|
||||
integrity sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==
|
||||
dependencies:
|
||||
leven "^2.1.0"
|
||||
minimist "^1.1.0"
|
||||
|
||||
concat-map@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
|
||||
|
||||
concat-stream@^1.6.2:
|
||||
version "1.6.2"
|
||||
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
|
||||
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
inherits "^2.0.3"
|
||||
readable-stream "^2.2.2"
|
||||
typedarray "^0.0.6"
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1"
|
||||
integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
||||
"@protobufjs/inquire@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089"
|
||||
integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==
|
||||
|
||||
"@protobufjs/path@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d"
|
||||
integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==
|
||||
|
||||
"@protobufjs/pool@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54"
|
||||
integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==
|
||||
|
||||
"@protobufjs/utf8@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
|
||||
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
|
||||
|
||||
"@types/long@^4.0.1":
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
|
||||
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==
|
||||
|
||||
"@types/node@>=13.7.0":
|
||||
version "20.12.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.2.tgz#9facdd11102f38b21b4ebedd9d7999663343d72e"
|
||||
integrity sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==
|
||||
d@1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a"
|
||||
integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
es5-ext "^0.10.50"
|
||||
type "^1.0.1"
|
||||
|
||||
long@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28"
|
||||
integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==
|
||||
|
||||
protobufjs@^6.11.4:
|
||||
version "6.11.4"
|
||||
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa"
|
||||
integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==
|
||||
duplexify@^3.5.1, duplexify@^3.6.0:
|
||||
version "3.7.1"
|
||||
resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
|
||||
integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
|
||||
dependencies:
|
||||
"@protobufjs/aspromise" "^1.1.2"
|
||||
"@protobufjs/base64" "^1.1.2"
|
||||
"@protobufjs/codegen" "^2.0.4"
|
||||
"@protobufjs/eventemitter" "^1.1.0"
|
||||
"@protobufjs/fetch" "^1.1.0"
|
||||
"@protobufjs/float" "^1.0.2"
|
||||
"@protobufjs/inquire" "^1.1.0"
|
||||
"@protobufjs/path" "^1.1.2"
|
||||
"@protobufjs/pool" "^1.1.0"
|
||||
"@protobufjs/utf8" "^1.1.0"
|
||||
"@types/long" "^4.0.1"
|
||||
"@types/node" ">=13.7.0"
|
||||
long "^4.0.0"
|
||||
end-of-stream "^1.0.0"
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^2.0.0"
|
||||
stream-shift "^1.0.0"
|
||||
|
||||
undici-types@~5.26.4:
|
||||
version "5.26.5"
|
||||
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
|
||||
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
|
||||
end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
||||
integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@~0.10.14:
|
||||
version "0.10.50"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.50.tgz#6d0e23a0abdb27018e5ac4fd09b412bc5517a778"
|
||||
integrity sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==
|
||||
dependencies:
|
||||
es6-iterator "~2.0.3"
|
||||
es6-symbol "~3.1.1"
|
||||
next-tick "^1.0.0"
|
||||
|
||||
es6-iterator@~2.0.1, es6-iterator@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7"
|
||||
integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c=
|
||||
dependencies:
|
||||
d "1"
|
||||
es5-ext "^0.10.35"
|
||||
es6-symbol "^3.1.1"
|
||||
|
||||
es6-map@^0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0"
|
||||
integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=
|
||||
dependencies:
|
||||
d "1"
|
||||
es5-ext "~0.10.14"
|
||||
es6-iterator "~2.0.1"
|
||||
es6-set "~0.1.5"
|
||||
es6-symbol "~3.1.1"
|
||||
event-emitter "~0.3.5"
|
||||
|
||||
es6-set@~0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1"
|
||||
integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=
|
||||
dependencies:
|
||||
d "1"
|
||||
es5-ext "~0.10.14"
|
||||
es6-iterator "~2.0.1"
|
||||
es6-symbol "3.1.1"
|
||||
event-emitter "~0.3.5"
|
||||
|
||||
es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77"
|
||||
integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=
|
||||
dependencies:
|
||||
d "1"
|
||||
es5-ext "~0.10.14"
|
||||
|
||||
event-emitter@~0.3.5:
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
|
||||
integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=
|
||||
dependencies:
|
||||
d "1"
|
||||
es5-ext "~0.10.14"
|
||||
|
||||
extend@^3.0.0:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||
integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
|
||||
|
||||
glob-parent@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
|
||||
integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=
|
||||
dependencies:
|
||||
is-glob "^3.1.0"
|
||||
path-dirname "^1.0.0"
|
||||
|
||||
glob-stream@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4"
|
||||
integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=
|
||||
dependencies:
|
||||
extend "^3.0.0"
|
||||
glob "^7.1.1"
|
||||
glob-parent "^3.1.0"
|
||||
is-negated-glob "^1.0.0"
|
||||
ordered-read-streams "^1.0.0"
|
||||
pumpify "^1.3.5"
|
||||
readable-stream "^2.1.5"
|
||||
remove-trailing-separator "^1.0.1"
|
||||
to-absolute-glob "^2.0.0"
|
||||
unique-stream "^2.0.2"
|
||||
|
||||
glob@^7.1.1:
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
|
||||
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
help-me@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/help-me/-/help-me-1.1.0.tgz#8f2d508d0600b4a456da2f086556e7e5c056a3c6"
|
||||
integrity sha1-jy1QjQYAtKRW2i8IZVbn5cBWo8Y=
|
||||
dependencies:
|
||||
callback-stream "^1.0.2"
|
||||
glob-stream "^6.1.0"
|
||||
through2 "^2.0.1"
|
||||
xtend "^4.0.0"
|
||||
|
||||
inflight@^1.0.4:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||
integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
|
||||
dependencies:
|
||||
once "^1.3.0"
|
||||
wrappy "1"
|
||||
|
||||
inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
|
||||
|
||||
is-absolute@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576"
|
||||
integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==
|
||||
dependencies:
|
||||
is-relative "^1.0.0"
|
||||
is-windows "^1.0.1"
|
||||
|
||||
is-extglob@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
|
||||
integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
|
||||
|
||||
is-glob@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
|
||||
integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=
|
||||
dependencies:
|
||||
is-extglob "^2.1.0"
|
||||
|
||||
is-negated-glob@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
|
||||
integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=
|
||||
|
||||
is-relative@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
|
||||
integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==
|
||||
dependencies:
|
||||
is-unc-path "^1.0.0"
|
||||
|
||||
is-unc-path@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d"
|
||||
integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==
|
||||
dependencies:
|
||||
unc-path-regex "^0.1.2"
|
||||
|
||||
is-windows@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
|
||||
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
|
||||
|
||||
isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
|
||||
|
||||
json-stable-stringify-without-jsonify@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
|
||||
integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=
|
||||
|
||||
leven@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
|
||||
integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA=
|
||||
|
||||
minimatch@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.1.0, minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
||||
|
||||
mqtt-packet@^6.0.0:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/mqtt-packet/-/mqtt-packet-6.1.2.tgz#239493d185c9209011a5a22ebce30e098c731f81"
|
||||
integrity sha512-yVG5PoS3wJ8TLzfS8pQMsDVLAf/EipnBAG5XQE9X/9L0EMxuduI9J2WnlRvJT497K1CUT4VJWjoP08+CKiKt1Q==
|
||||
dependencies:
|
||||
bl "^1.2.2"
|
||||
inherits "^2.0.3"
|
||||
process-nextick-args "^2.0.0"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
mqtt@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/mqtt/-/mqtt-3.0.0.tgz#7961e5f61efba3eec37d5aa9c4cbcdeb6f841380"
|
||||
integrity sha512-0nKV6MAc1ibKZwaZQUTb3iIdT4NVpj541BsYrqrGBcQdQ7Jd0MnZD1/6/nj1UFdGTboK9ZEUXvkCu2nPCugHFA==
|
||||
dependencies:
|
||||
base64-js "^1.3.0"
|
||||
commist "^1.0.0"
|
||||
concat-stream "^1.6.2"
|
||||
end-of-stream "^1.4.1"
|
||||
es6-map "^0.1.5"
|
||||
help-me "^1.0.1"
|
||||
inherits "^2.0.3"
|
||||
minimist "^1.2.0"
|
||||
mqtt-packet "^6.0.0"
|
||||
pump "^3.0.0"
|
||||
readable-stream "^2.3.6"
|
||||
reinterval "^1.1.0"
|
||||
split2 "^3.1.0"
|
||||
websocket-stream "^5.1.2"
|
||||
xtend "^4.0.1"
|
||||
|
||||
next-tick@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
|
||||
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
|
||||
|
||||
once@^1.3.0, once@^1.3.1, once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
ordered-read-streams@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e"
|
||||
integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=
|
||||
dependencies:
|
||||
readable-stream "^2.0.1"
|
||||
|
||||
path-dirname@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0"
|
||||
integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=
|
||||
|
||||
path-is-absolute@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
|
||||
|
||||
process-nextick-args@^2.0.0, process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||
integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
|
||||
|
||||
pump@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909"
|
||||
integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
pump@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
|
||||
integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==
|
||||
dependencies:
|
||||
end-of-stream "^1.1.0"
|
||||
once "^1.3.1"
|
||||
|
||||
pumpify@^1.3.5:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
|
||||
integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
|
||||
dependencies:
|
||||
duplexify "^3.6.0"
|
||||
inherits "^2.0.3"
|
||||
pump "^2.0.0"
|
||||
|
||||
"readable-stream@> 1.0.0 < 3.0.0", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
readable-stream@^3.0.0:
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
|
||||
integrity sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
reinterval@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/reinterval/-/reinterval-1.1.0.tgz#3361ecfa3ca6c18283380dd0bb9546f390f5ece7"
|
||||
integrity sha1-M2Hs+jymwYKDOA3Qu5VG85D17Oc=
|
||||
|
||||
remove-trailing-separator@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||
integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8=
|
||||
|
||||
safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
|
||||
|
||||
split2@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/split2/-/split2-3.1.1.tgz#c51f18f3e06a8c4469aaab487687d8d956160bb6"
|
||||
integrity sha512-emNzr1s7ruq4N+1993yht631/JH+jaj0NYBosuKmLcq+JkGQ9MmTw1RB1fGaTCzUuseRIClrlSLHRNYGwWQ58Q==
|
||||
dependencies:
|
||||
readable-stream "^3.0.0"
|
||||
|
||||
stream-shift@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
|
||||
integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d"
|
||||
integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
through2-filter@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
|
||||
integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==
|
||||
dependencies:
|
||||
through2 "~2.0.0"
|
||||
xtend "~4.0.0"
|
||||
|
||||
through2@^2.0.1, through2@~2.0.0:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
|
||||
integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
|
||||
dependencies:
|
||||
readable-stream "~2.3.6"
|
||||
xtend "~4.0.1"
|
||||
|
||||
to-absolute-glob@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b"
|
||||
integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=
|
||||
dependencies:
|
||||
is-absolute "^1.0.0"
|
||||
is-negated-glob "^1.0.0"
|
||||
|
||||
type@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/type/-/type-1.0.1.tgz#084c9a17fcc9151a2cdb1459905c2e45e4bb7d61"
|
||||
integrity sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw==
|
||||
|
||||
typedarray@^0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
|
||||
|
||||
ultron@~1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
|
||||
integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==
|
||||
|
||||
unc-path-regex@^0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
|
||||
integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo=
|
||||
|
||||
unique-stream@^2.0.2:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac"
|
||||
integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==
|
||||
dependencies:
|
||||
json-stable-stringify-without-jsonify "^1.0.1"
|
||||
through2-filter "^3.0.0"
|
||||
|
||||
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
websocket-stream@^5.1.2:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/websocket-stream/-/websocket-stream-5.5.0.tgz#9827f2846fc0d2b4dca7aab8f92980b2548b868e"
|
||||
integrity sha512-EXy/zXb9kNHI07TIMz1oIUIrPZxQRA8aeJ5XYg5ihV8K4kD1DuA+FY6R96HfdIHzlSzS8HiISAfrm+vVQkZBug==
|
||||
dependencies:
|
||||
duplexify "^3.5.1"
|
||||
inherits "^2.0.1"
|
||||
readable-stream "^2.3.3"
|
||||
safe-buffer "^5.1.2"
|
||||
ws "^3.2.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
ws@^3.2.0:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2"
|
||||
integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
safe-buffer "~5.1.0"
|
||||
ultron "~1.1.0"
|
||||
|
||||
xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM node:20
|
||||
FROM node:11-stretch
|
||||
|
||||
RUN DEBIAN_FRONTEND="noninteractive" apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates nano ffmpeg xvfb git-core tmux locales mosquitto x11vnc
|
||||
&& apt-get install -y --no-install-recommends nano ffmpeg xvfb git-core tmux locales mosquitto x11vnc
|
||||
RUN apt-get install -yq --no-install-recommends libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 libnss3
|
||||
|
||||
# Generate locales for TMUX
|
||||
@@ -12,5 +12,6 @@ ENV LC_ALL en_US.UTF-8
|
||||
|
||||
CMD /bin/bash
|
||||
|
||||
COPY cloneBuildAndTest.sh ./
|
||||
VOLUME /app
|
||||
EXPOSE 5900
|
||||
@@ -1,7 +1,11 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
yarn install --frozen-lockfile
|
||||
git clone https://github.com/thomasnordquist/MQTT-Explorer.git /app
|
||||
cd /app
|
||||
git checkout travis-ui-tests
|
||||
|
||||
yarn
|
||||
yarn build
|
||||
|
||||
yarn ui-test
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import { IpcMain } from 'electron'
|
||||
import { Event } from '../Events'
|
||||
import { EventBusInterface } from './EventBusInterface'
|
||||
import { IpcMain, ipcMain, ipcRenderer } from 'electron'
|
||||
|
||||
export class IpcMainEventBus implements EventBusInterface {
|
||||
import { Event } from './Events'
|
||||
import { IpcRendererEventBus } from './IpcRendererEventBus'
|
||||
|
||||
export interface EventBusInterface {
|
||||
subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void): void
|
||||
unsubscribeAll<MessageType>(event: Event<MessageType>): void
|
||||
emit<MessageType>(event: Event<MessageType>, msg: MessageType): void
|
||||
unsubscribe<MessageType>(event: Event<MessageType>, callback: any): void
|
||||
}
|
||||
|
||||
export interface CallbackStore {
|
||||
wrappedCallback: any
|
||||
callback: any
|
||||
}
|
||||
|
||||
class IpcMainEventBus implements EventBusInterface {
|
||||
private ipc: IpcMain
|
||||
private client: any
|
||||
constructor(ipc: IpcMain) {
|
||||
@@ -32,3 +45,6 @@ export class IpcMainEventBus implements EventBusInterface {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const rendererEvents = new IpcRendererEventBus(ipcRenderer)
|
||||
export const backendEvents = new IpcMainEventBus(ipcMain)
|
||||
@@ -1,4 +0,0 @@
|
||||
export interface CallbackStore {
|
||||
wrappedCallback: any
|
||||
callback: any
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import { ipcMain, ipcRenderer } from 'electron'
|
||||
|
||||
import { IpcMainEventBus } from './IpcMainEventBus'
|
||||
import { IpcRendererEventBus } from './IpcRendererEventBus'
|
||||
import { Rpc } from './Rpc'
|
||||
|
||||
export const rendererEvents = new IpcRendererEventBus(ipcRenderer)
|
||||
export const backendEvents = new IpcMainEventBus(ipcMain)
|
||||
|
||||
// Preferred way to communicate typesafe
|
||||
export const rendererRpc = new Rpc(rendererEvents)
|
||||
export const backendRpc = new Rpc(backendEvents)
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Event } from '../Events'
|
||||
|
||||
export interface EventBusInterface {
|
||||
subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void): void
|
||||
unsubscribeAll<MessageType>(event: Event<MessageType>): void
|
||||
emit<MessageType>(event: Event<MessageType>, msg: MessageType): void
|
||||
unsubscribe<MessageType>(event: Event<MessageType>, callback: any): void
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
import { Event } from '../Events'
|
||||
import { EventBusInterface } from './EventBusInterface'
|
||||
import { v4 } from 'uuid'
|
||||
|
||||
export type RpcEvent<RequestType, ResponseType> = {
|
||||
topic: string
|
||||
}
|
||||
|
||||
export class Rpc {
|
||||
constructor(private participant: EventBusInterface) {}
|
||||
|
||||
// tslint:disable-next-line:member-access
|
||||
async call<RpcRequest, RpcResponse>(
|
||||
event: RpcEvent<RpcRequest, RpcResponse>,
|
||||
request: RpcRequest,
|
||||
timeout: number = 0
|
||||
): Promise<RpcResponse> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const id = v4()
|
||||
|
||||
const responseEvent: Event<any> = { topic: `${event.topic}/response/${id}` }
|
||||
const requestEvent: Event<any> = { topic: `${event.topic}/request` }
|
||||
const callback = (result: { id: string; payload: RpcResponse; error: unknown }) => {
|
||||
this.participant.unsubscribe(responseEvent as any, callback)
|
||||
if (result.error) {
|
||||
reject(result.error)
|
||||
} else {
|
||||
resolve(result.payload)
|
||||
}
|
||||
console.log('received', result)
|
||||
}
|
||||
this.participant.subscribe(responseEvent, callback)
|
||||
this.participant.emit(requestEvent, { id, payload: request })
|
||||
|
||||
if (timeout > 0) {
|
||||
setTimeout(() => {
|
||||
reject(new Error(`Did not respond to ${event.topic} within ${timeout}ms`))
|
||||
this.participant.unsubscribe(responseEvent as any, callback)
|
||||
}, 10000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:member-access
|
||||
async on<RpcRequest, RpcResponse>(
|
||||
event: RpcEvent<RpcRequest, RpcResponse>,
|
||||
handler: (request: RpcRequest) => Promise<RpcResponse>
|
||||
) {
|
||||
this.participant.subscribe<RpcRequest>({ topic: `${event.topic}/request` }, async request => {
|
||||
let payload
|
||||
let error
|
||||
try {
|
||||
payload = await handler((request as any).payload)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
const id = (request as any).id
|
||||
console.log(`${event.topic}/response/${id}`, payload, error)
|
||||
this.participant.emit({ topic: `${event.topic}/response/${id}` }, { id, payload, error })
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
-6
@@ -1,9 +1,8 @@
|
||||
import { Base64Message } from '../backend/src/Model/Base64Message'
|
||||
import { DataSourceState, MqttOptions } from '../backend/src/DataSource'
|
||||
import { UpdateInfo } from 'builder-util-runtime'
|
||||
import { RpcEvent } from './EventSystem/Rpc'
|
||||
|
||||
export type Event<MessageType> = {
|
||||
export interface Event<MessageType> {
|
||||
topic: string
|
||||
}
|
||||
|
||||
@@ -50,7 +49,3 @@ export function makeConnectionMessageEvent(connectionId: string): Event<MqttMess
|
||||
topic: `conn/${connectionId}`,
|
||||
}
|
||||
}
|
||||
|
||||
export const getAppVersion: RpcEvent<void, string> = {
|
||||
topic: 'getAppVersion',
|
||||
}
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
import { CallbackStore } from './CallbackStore'
|
||||
import { EventBusInterface } from './EventBusInterface'
|
||||
import { Event } from '../Events'
|
||||
import { CallbackStore, EventBusInterface } from './EventBus'
|
||||
import { Event } from './Events'
|
||||
import { IpcRenderer } from 'electron'
|
||||
|
||||
export class IpcRendererEventBus implements EventBusInterface {
|
||||
private ipc: IpcRenderer
|
||||
private callbacks: Array<CallbackStore> = []
|
||||
|
||||
constructor(ipc: IpcRenderer) {
|
||||
this.ipc = ipc
|
||||
}
|
||||
|
||||
public subscribe<MessageType>(event: Event<MessageType>, callback: (msg: MessageType) => void) {
|
||||
const wrappedCallback = (_: any, arg: any) => {
|
||||
callback(arg)
|
||||
}
|
||||
console.log('subscribing', event.topic)
|
||||
this.ipc.on(event.topic, wrappedCallback)
|
||||
this.callbacks.push({
|
||||
callback,
|
||||
wrappedCallback,
|
||||
})
|
||||
}
|
||||
|
||||
public unsubscribeAll<MessageType>(event: Event<MessageType>) {
|
||||
this.ipc.removeAllListeners(event.topic)
|
||||
}
|
||||
|
||||
public unsubscribe<MessageType>(event: Event<MessageType>, callback: any) {
|
||||
const item = this.callbacks.find(store => store.callback === callback)
|
||||
if (!item) {
|
||||
@@ -35,7 +29,6 @@ export class IpcRendererEventBus implements EventBusInterface {
|
||||
this.ipc.removeListener(event.topic, item.wrappedCallback)
|
||||
this.callbacks = this.callbacks.filter(a => a !== item)
|
||||
}
|
||||
|
||||
public emit<MessageType>(event: Event<MessageType>, msg: MessageType) {
|
||||
this.ipc.send(event.topic, msg)
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { OpenDialogOptions, OpenDialogReturnValue } from 'electron'
|
||||
import { RpcEvent } from './EventSystem/Rpc'
|
||||
|
||||
export function makeOpenDialogRpc(): RpcEvent<OpenDialogOptions, OpenDialogReturnValue> {
|
||||
return {
|
||||
topic: 'openDialog',
|
||||
}
|
||||
}
|
||||
+25
-8
@@ -1,22 +1,39 @@
|
||||
import { RpcEvent } from './EventSystem/Rpc'
|
||||
import { Event } from './'
|
||||
|
||||
export interface StoreCommand {
|
||||
store: string
|
||||
data: any
|
||||
interface StorageEvent {
|
||||
transactionId: string
|
||||
}
|
||||
|
||||
export interface LoadCommand {
|
||||
export interface StoreCommand extends StorageEvent {
|
||||
store?: string
|
||||
data?: any
|
||||
error?: any
|
||||
}
|
||||
|
||||
export interface LoadCommand extends StorageEvent {
|
||||
store: string
|
||||
}
|
||||
|
||||
export const storageStoreEvent: RpcEvent<StoreCommand, void> = {
|
||||
export const storageStoreEvent: Event<StoreCommand> = {
|
||||
topic: 'storage/store',
|
||||
}
|
||||
|
||||
export const storageLoadEvent: RpcEvent<LoadCommand, StoreCommand> = {
|
||||
export const storageLoadEvent: Event<LoadCommand> = {
|
||||
topic: 'storage/load',
|
||||
}
|
||||
|
||||
export const storageClearEvent: RpcEvent<void, void> = {
|
||||
export function makeStorageAcknowledgementEvent(transactionId: string): Event<StoreCommand> {
|
||||
return {
|
||||
topic: `storage/ack/${transactionId}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function makeStorageResponseEvent(transactionId: string): Event<StoreCommand> {
|
||||
return {
|
||||
topic: `storage/response/${transactionId}`,
|
||||
}
|
||||
}
|
||||
|
||||
export const storageClearEvent: Event<StorageEvent> = {
|
||||
topic: 'storage/clear',
|
||||
}
|
||||
|
||||
+2
-3
@@ -1,4 +1,3 @@
|
||||
export * from './Events'
|
||||
export * from './EventSystem/EventDispatcher'
|
||||
export * from './EventSystem/EventBus'
|
||||
export * from './EventSystem/EventBusInterface'
|
||||
export * from './EventDispatcher'
|
||||
export * from './EventBus'
|
||||
|
||||
+26
-38
@@ -1,12 +1,8 @@
|
||||
{
|
||||
"name": "MQTT-Explorer",
|
||||
"version": "0.4.0-beta.5",
|
||||
"version": "0.4.0-beta1",
|
||||
"description": "Explore your message queues",
|
||||
"main": "dist/src/electron.js",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"private": "true",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"test": "yarn test:app && yarn test:backend",
|
||||
@@ -17,11 +13,8 @@
|
||||
"dev:app": "cd app && npm run dev",
|
||||
"dev:electron": "tsc && electron . --development",
|
||||
"lint": "npm-run-all --parallel lint:prettier lint:tslint lint:spellcheck",
|
||||
"lint:fix": "npm-run-all lint:tslint:fix lint:prettier:fix",
|
||||
"lint:prettier": "prettier --check \"**/*.ts{x,}\"",
|
||||
"lint:prettier:fix": "prettier --write \"**/*.ts{x,}\"",
|
||||
"lint:tslint": "tslint -p ./",
|
||||
"lint:tslint:fix": "tslint -p ./ --fix",
|
||||
"lint:spellcheck": "cspell -e ./build -e \"node_modules\" \"**/*.ts{x,}\"",
|
||||
"build": "tsc && cd app && yarn run build && cd ..",
|
||||
"prepare-release": "ts-node scripts/prepare-release.ts",
|
||||
@@ -69,19 +62,14 @@
|
||||
"buildResources": "res",
|
||||
"output": "build"
|
||||
},
|
||||
"afterPack": "./dist/scripts/afterPack.js"
|
||||
"afterPack": "./dist/scripts/afterPack.js",
|
||||
"afterSign": "./scripts/afterSign.js"
|
||||
},
|
||||
"author": "Thomas Nordquist",
|
||||
"email": "xxnerowingerxx@gmail.com",
|
||||
"homepage": "https://github.com",
|
||||
"license": "CC-BY-ND-4.0",
|
||||
"devDependencies": {
|
||||
"@babel/runtime": "^7.17.2",
|
||||
"@cspell/dict-typescript": "^3.1.2",
|
||||
"@electron/notarize": "^2.3.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/commit-analyzer": "^12.0.0",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/fs-extra": "8",
|
||||
"@types/lowdb": "^1.0.6",
|
||||
@@ -91,44 +79,44 @@
|
||||
"@types/node": "^12.6.8",
|
||||
"@types/semver": "7",
|
||||
"@types/sha1": "^1.1.1",
|
||||
"@types/uuid": "^8.3.4",
|
||||
"builder-util-runtime": "^9",
|
||||
"builder-util-runtime": "^8.2.5",
|
||||
"chai": "^4.2.0",
|
||||
"cspell": "^8.6.1",
|
||||
"electron": "29.2.0",
|
||||
"electron-builder": "^24.13.3",
|
||||
"mocha": "^10.4.0",
|
||||
"cspell": "^4.0.28",
|
||||
"electron": "8.2.3",
|
||||
"electron-builder": "22.5.1",
|
||||
"electron-notarize": "^0.3.0",
|
||||
"mocha": "7.1",
|
||||
"mustache": "4",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "15",
|
||||
"playwright": "^1.43.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier": "2",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"semantic-release": "^23.0.8",
|
||||
"semantic-release-export-data": "^1.0.1",
|
||||
"source-map-support": "^0.5.9",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-airbnb": "^5.11.2",
|
||||
"tslint-react": "^5.0.0",
|
||||
"spectron": "10",
|
||||
"ts-node": "^8.2.0",
|
||||
"tslint": "6",
|
||||
"tslint-config-airbnb": "^5.11.1",
|
||||
"tslint-react": "^4.0.0",
|
||||
"tslint-react-recommended": "^1.0.15",
|
||||
"typescript": "^4.5.5"
|
||||
"tslint-strict-null-checks": "^1.0.1",
|
||||
"typescript": "^3.2.2",
|
||||
"webdriverio": "6"
|
||||
},
|
||||
"dependencies": {
|
||||
"about-window": "^1.12.1",
|
||||
"axios": "^0.28.0",
|
||||
"axios": "^0.19.0",
|
||||
"dot-prop": "^5.0.0",
|
||||
"electron-log": "4.4.6",
|
||||
"electron-updater": "^4.6",
|
||||
"electron-log": "4.1.1",
|
||||
"electron-telemetry": "git+https://github.com/thomasnordquist/electron-telemetry.git#dist",
|
||||
"electron-updater": "^4.0.6",
|
||||
"fs-extra": "9",
|
||||
"js-base64": "^3.7.2",
|
||||
"js-base64": "^2.5.1",
|
||||
"json-to-ast": "^2.1.0",
|
||||
"lowdb": "^1.0.0",
|
||||
"mime": "^2.4.4",
|
||||
"mqtt": "^4.3.6",
|
||||
"protobufjs": "^6.11.4",
|
||||
"mqtt": "^3.0.0",
|
||||
"sha1": "^1.1.1",
|
||||
"uuid": "^8.3.2",
|
||||
"yarn-run-all": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"optionalDependencies": {}
|
||||
}
|
||||
|
||||
+11
-21
@@ -5,9 +5,9 @@ import * as dotProp from 'dot-prop'
|
||||
|
||||
const linuxAppImage: builder.CliOptions = {
|
||||
x64: true,
|
||||
ia32: false,
|
||||
ia32: true,
|
||||
armv7l: true,
|
||||
arm64: true,
|
||||
arm64: false,
|
||||
projectDir: './build/clean',
|
||||
publish: 'always',
|
||||
}
|
||||
@@ -15,24 +15,15 @@ const linuxAppImage: builder.CliOptions = {
|
||||
const linuxSnap: builder.CliOptions = {
|
||||
x64: true,
|
||||
ia32: false,
|
||||
armv7l: false, // not supported to build on x64
|
||||
arm64: false, // not supported to build on x64
|
||||
projectDir: './build/clean',
|
||||
publish: 'always',
|
||||
}
|
||||
|
||||
const linuxDeb: builder.CliOptions = {
|
||||
x64: true,
|
||||
ia32: false,
|
||||
armv7l: true,
|
||||
arm64: true,
|
||||
armv7l: false,
|
||||
arm64: false,
|
||||
projectDir: './build/clean',
|
||||
publish: 'always',
|
||||
}
|
||||
|
||||
const winPortable: builder.CliOptions = {
|
||||
x64: true,
|
||||
ia32: false,
|
||||
ia32: true,
|
||||
armv7l: false,
|
||||
arm64: false,
|
||||
projectDir: './build/clean',
|
||||
@@ -41,7 +32,7 @@ const winPortable: builder.CliOptions = {
|
||||
|
||||
const winNsis: builder.CliOptions = {
|
||||
x64: true,
|
||||
ia32: false,
|
||||
ia32: true,
|
||||
armv7l: false,
|
||||
arm64: false,
|
||||
projectDir: './build/clean',
|
||||
@@ -50,7 +41,7 @@ const winNsis: builder.CliOptions = {
|
||||
|
||||
const winAppx: builder.CliOptions = {
|
||||
x64: true,
|
||||
ia32: false,
|
||||
ia32: true,
|
||||
armv7l: false,
|
||||
arm64: false,
|
||||
projectDir: './build/clean',
|
||||
@@ -59,9 +50,9 @@ const winAppx: builder.CliOptions = {
|
||||
|
||||
const mac: builder.CliOptions = {
|
||||
x64: true,
|
||||
ia32: false,
|
||||
ia32: true,
|
||||
armv7l: false,
|
||||
arm64: true,
|
||||
arm64: false,
|
||||
projectDir: './build/clean',
|
||||
publish: 'always',
|
||||
}
|
||||
@@ -78,12 +69,11 @@ async function executeBuild() {
|
||||
case 'linux':
|
||||
await buildWithOptions(linuxAppImage, { platform: 'linux', package: 'AppImage' })
|
||||
await buildWithOptions(linuxSnap, { platform: 'linux', package: 'snap' })
|
||||
await buildWithOptions(linuxDeb, { platform: 'linux', package: 'deb' })
|
||||
break
|
||||
case 'mac':
|
||||
await buildWithOptions(mac, { platform: 'mac', package: 'dmg' })
|
||||
// await buildWithOptions(mac, { platform: 'mac', package: 'mas' })
|
||||
// await buildWithOptions(mac, { platform: 'mac', package: 'zip' })
|
||||
await buildWithOptions(mac, { platform: 'mac', package: 'mas' })
|
||||
await buildWithOptions(mac, { platform: 'mac', package: 'zip' })
|
||||
break
|
||||
default:
|
||||
await buildWithOptions({ ...mac, projectDir: '' }, { platform: 'mac', package: 'mas-dev' })
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
syntax = "proto2";
|
||||
|
||||
//
|
||||
// To compile:
|
||||
// cd client_libraries/java
|
||||
// protoc --proto_path=../../ --java_out=src/main/java ../../sparkplug_b.proto
|
||||
//
|
||||
package com.cirruslink.sparkplug.protobuf;
|
||||
|
||||
option java_package = "com.cirruslink.sparkplug.protobuf";
|
||||
option java_outer_classname = "SparkplugBProto";
|
||||
|
||||
message Payload {
|
||||
/*
|
||||
// Indexes of Data Types
|
||||
|
||||
// Unknown placeholder for future expansion.
|
||||
Unknown = 0;
|
||||
|
||||
// Basic Types
|
||||
Int8 = 1;
|
||||
Int16 = 2;
|
||||
Int32 = 3;
|
||||
Int64 = 4;
|
||||
UInt8 = 5;
|
||||
UInt16 = 6;
|
||||
UInt32 = 7;
|
||||
UInt64 = 8;
|
||||
Float = 9;
|
||||
Double = 10;
|
||||
Boolean = 11;
|
||||
String = 12;
|
||||
DateTime = 13;
|
||||
Text = 14;
|
||||
|
||||
// Additional Metric Types
|
||||
UUID = 15;
|
||||
DataSet = 16;
|
||||
Bytes = 17;
|
||||
File = 18;
|
||||
Template = 19;
|
||||
|
||||
// Additional PropertyValue Types
|
||||
PropertySet = 20;
|
||||
PropertySetList = 21;
|
||||
|
||||
*/
|
||||
|
||||
message Template {
|
||||
|
||||
message Parameter {
|
||||
optional string name = 1;
|
||||
optional uint32 type = 2;
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 3;
|
||||
uint64 long_value = 4;
|
||||
float float_value = 5;
|
||||
double double_value = 6;
|
||||
bool boolean_value = 7;
|
||||
string string_value = 8;
|
||||
ParameterValueExtension extension_value = 9;
|
||||
}
|
||||
|
||||
message ParameterValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
optional string version = 1; // The version of the Template to prevent mismatches
|
||||
repeated Metric metrics = 2; // Each metric is the name of the metric and the datatype of the member but does not contain a value
|
||||
repeated Parameter parameters = 3;
|
||||
optional string template_ref = 4; // Reference to a template if this is extending a Template or an instance - must exist if an instance
|
||||
optional bool is_definition = 5;
|
||||
extensions 6 to max;
|
||||
}
|
||||
|
||||
message DataSet {
|
||||
|
||||
message DataSetValue {
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 1;
|
||||
uint64 long_value = 2;
|
||||
float float_value = 3;
|
||||
double double_value = 4;
|
||||
bool boolean_value = 5;
|
||||
string string_value = 6;
|
||||
DataSetValueExtension extension_value = 7;
|
||||
}
|
||||
|
||||
message DataSetValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
message Row {
|
||||
repeated DataSetValue elements = 1;
|
||||
extensions 2 to max; // For third party extensions
|
||||
}
|
||||
|
||||
optional uint64 num_of_columns = 1;
|
||||
repeated string columns = 2;
|
||||
repeated uint32 types = 3;
|
||||
repeated Row rows = 4;
|
||||
extensions 5 to max; // For third party extensions
|
||||
}
|
||||
|
||||
message PropertyValue {
|
||||
|
||||
optional uint32 type = 1;
|
||||
optional bool is_null = 2;
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 3;
|
||||
uint64 long_value = 4;
|
||||
float float_value = 5;
|
||||
double double_value = 6;
|
||||
bool boolean_value = 7;
|
||||
string string_value = 8;
|
||||
PropertySet propertyset_value = 9;
|
||||
PropertySetList propertysets_value = 10; // List of Property Values
|
||||
PropertyValueExtension extension_value = 11;
|
||||
}
|
||||
|
||||
message PropertyValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
message PropertySet {
|
||||
repeated string keys = 1; // Names of the properties
|
||||
repeated PropertyValue values = 2;
|
||||
extensions 3 to max;
|
||||
}
|
||||
|
||||
message PropertySetList {
|
||||
repeated PropertySet propertyset = 1;
|
||||
extensions 2 to max;
|
||||
}
|
||||
|
||||
message MetaData {
|
||||
// Bytes specific metadata
|
||||
optional bool is_multi_part = 1;
|
||||
|
||||
// General metadata
|
||||
optional string content_type = 2; // Content/Media type
|
||||
optional uint64 size = 3; // File size, String size, Multi-part size, etc
|
||||
optional uint64 seq = 4; // Sequence number for multi-part messages
|
||||
|
||||
// File metadata
|
||||
optional string file_name = 5; // File name
|
||||
optional string file_type = 6; // File type (i.e. xml, json, txt, cpp, etc)
|
||||
optional string md5 = 7; // md5 of data
|
||||
|
||||
// Catchalls and future expansion
|
||||
optional string description = 8; // Could be anything such as json or xml of custom properties
|
||||
extensions 9 to max;
|
||||
}
|
||||
|
||||
message Metric {
|
||||
|
||||
optional string name = 1; // Metric name - should only be included on birth
|
||||
optional uint64 alias = 2; // Metric alias - tied to name on birth and included in all later DATA messages
|
||||
optional uint64 timestamp = 3; // Timestamp associated with data acquisition time
|
||||
optional uint32 datatype = 4; // DataType of the metric/tag value
|
||||
optional bool is_historical = 5; // If this is historical data and should not update real time tag
|
||||
optional bool is_transient = 6; // Tells consuming clients such as MQTT Engine to not store this as a tag
|
||||
optional bool is_null = 7; // If this is null - explicitly say so rather than using -1, false, etc for some datatypes.
|
||||
optional MetaData metadata = 8; // Metadata for the payload
|
||||
optional PropertySet properties = 9;
|
||||
|
||||
oneof value {
|
||||
uint32 int_value = 10;
|
||||
uint64 long_value = 11;
|
||||
float float_value = 12;
|
||||
double double_value = 13;
|
||||
bool boolean_value = 14;
|
||||
string string_value = 15;
|
||||
bytes bytes_value = 16; // Bytes, File
|
||||
DataSet dataset_value = 17;
|
||||
Template template_value = 18;
|
||||
MetricValueExtension extension_value = 19;
|
||||
}
|
||||
|
||||
message MetricValueExtension {
|
||||
extensions 1 to max;
|
||||
}
|
||||
}
|
||||
|
||||
optional uint64 timestamp = 1; // Timestamp at message sending time
|
||||
repeated Metric metrics = 2; // Repeated forever - no limit in Google Protobufs
|
||||
optional uint64 seq = 3; // Sequence number
|
||||
optional string uuid = 4; // UUID to track message type in terms of schema definitions
|
||||
optional bytes body = 5; // To optionally bypass the whole definition above
|
||||
extensions 6 to max; // For third party extensions
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import * as fs from 'fs-extra'
|
||||
import * as path from 'path'
|
||||
import { chdir } from 'process'
|
||||
import { exec } from './util'
|
||||
|
||||
|
||||
@@ -17,18 +17,18 @@ async function prepareRelease() {
|
||||
|
||||
// Install app dependencies
|
||||
chdir('app')
|
||||
await exec('yarn', ['install', '--frozen-lockfile'])
|
||||
await exec('yarn')
|
||||
chdir('..')
|
||||
|
||||
// Install electron dependencies
|
||||
await exec('yarn', ['install', '--frozen-lockfile'])
|
||||
await exec('yarn')
|
||||
|
||||
// Build App and Electron backend
|
||||
await exec('yarn', ['build'])
|
||||
|
||||
// Clean up
|
||||
await fs.remove('node_modules')
|
||||
await exec('yarn', ['install', '--production', '--frozen-lockfile']) // Do not clean up, electron version detection will fail otherwise
|
||||
await exec('yarn', ['install', '--production']) // Do not clean up, electron version detection will fail otherwise
|
||||
await fs.remove(path.join('app', 'node_modules'))
|
||||
|
||||
chdir(originalDir)
|
||||
|
||||
+17
-26
@@ -1,25 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
function finish {
|
||||
set +e
|
||||
echo "Exiting, cleaning up.."
|
||||
|
||||
echo "Stopping TMUX session (record).."
|
||||
tmux kill-session -t record || echo "Already stopped"
|
||||
|
||||
if [[ ! -z "$PID_MOSQUITTO" ]]; then
|
||||
echo "Stopping mosquitto ($PID_MOSQUITTO).."
|
||||
kill "$PID_MOSQUITTO" || echo "Already stopped"
|
||||
fi
|
||||
|
||||
if [[ ! -z "$PID_VNC" ]]; then
|
||||
echo "Stopping VNC ($PID_VNC).."
|
||||
kill "$PID_VNC" || echo "Already stopped"
|
||||
fi
|
||||
|
||||
if [[ ! -z "$PID_XVFB" ]]; then
|
||||
echo "Stopping XVFB ($PID_XVFB).."
|
||||
kill "$PID_XVFB" || echo "Already stopped"
|
||||
fi
|
||||
echo "Exiting, cleaning up"
|
||||
tmux send-keys -t record q || echo "No tmux was running"
|
||||
#echo kill $PID_XVFB $PID_CHROMEDRIVER $PID_MOSQUITTO
|
||||
#kill $PID_XVFB $PID_CHROMEDRIVER $PID_MOSQUITTO
|
||||
}
|
||||
|
||||
trap finish EXIT
|
||||
@@ -33,25 +18,28 @@ export PID_XVFB=$!
|
||||
sleep 2
|
||||
|
||||
# Debug with VNC
|
||||
x11vnc -localhost -rfbport 5900 -passwd "bierbier" -display :$SCR &
|
||||
while [ "$TEST_EXIT_CODE" = "" ]; do x11vnc -localhost -passwd "bierbier" -display :$SCR; done &
|
||||
export PID_VNC=$!
|
||||
|
||||
# Start mqtt broker
|
||||
mosquitto &
|
||||
export PID_MOSQUITTO=$!
|
||||
|
||||
DISPLAY=:$SCR ./node_modules/.bin/chromedriver --url-base=wd/hub --port=9515 &
|
||||
export PID_CHROMEDRIVER=$!
|
||||
sleep 2
|
||||
|
||||
# Delete old video
|
||||
rm -f ./app*.mp4
|
||||
rm -f ./qrawvideorgb24.yuv
|
||||
rm ./app.mp4 || echo no need to delete ./app.mp4
|
||||
|
||||
# Start recoring in tmux
|
||||
# tmux new-session -d -s record ffmpeg -f x11grab -draw_mouse 0 -video_size $DIMENSIONS -i :$SCR -codec:v libx264 -r 20 ./app.mp4
|
||||
#tmux new-session -d -s record ffmpeg -f x11grab -draw_mouse 0 -video_size $DIMENSIONS -i :$SCR -codec:v libx264 -r 20 ./app.mp4
|
||||
tmux new-session -d -s record ffmpeg -f x11grab -draw_mouse 0 -video_size $DIMENSIONS -i :$SCR -r 20 -vcodec rawvideo -pix_fmt yuv420p qrawvideorgb24.yuv
|
||||
|
||||
# Start tests
|
||||
DISPLAY=:$SCR node dist/src/spec/demoVideo.js
|
||||
node dist/src/spec/demoVideo.js
|
||||
TEST_EXIT_CODE=$?
|
||||
echo "Test script exited with $TEST_EXIT_CODE"
|
||||
echo "Webriver exitet with $TEST_EXIT_CODE"
|
||||
|
||||
# Stop recording
|
||||
tmux send-keys -t record q
|
||||
@@ -59,4 +47,7 @@ tmux send-keys -t record q
|
||||
# Ensure video is written
|
||||
sleep 5
|
||||
|
||||
# Process the video
|
||||
./scripts/prepareVideo.sh
|
||||
|
||||
exit $TEST_EXIT_CODE
|
||||
|
||||
@@ -27,7 +27,7 @@ async function createDraft(tag: string) {
|
||||
draft: true,
|
||||
},
|
||||
})
|
||||
// @ts-ignore
|
||||
|
||||
return cleanUploadUrl(response.data.upload_url)
|
||||
}
|
||||
|
||||
@@ -53,12 +53,12 @@ async function uploadAsset() {
|
||||
try {
|
||||
uploadUrl = await createDraft(tag)
|
||||
} catch (error) {
|
||||
console.error('failed to create draft', (error as Error).stack)
|
||||
console.error('failed to create draft', error.stack)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('failed to find tag release', (error as Error).stack)
|
||||
console.error('failed to find tag release', error.stack)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
@@ -76,7 +76,6 @@ async function uploadFile(uploadUrl: string, file: string) {
|
||||
const data = fs.readFileSync(file)
|
||||
const mimeType = mime.getType(path.extname(file))
|
||||
|
||||
// @ts-ignore
|
||||
return axios({
|
||||
data,
|
||||
method: 'post',
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ function redirectOutputFor(child: ChildProcess) {
|
||||
}
|
||||
|
||||
async function waitFor(child: ChildProcess) {
|
||||
return new Promise<void>(resolve => {
|
||||
return new Promise(resolve => {
|
||||
child.once('close', () => resolve())
|
||||
})
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import { autoUpdater, UpdateInfo } from 'electron-updater'
|
||||
// import { BuildInfo } from 'electron-telemetry/build/Model'
|
||||
import { BuildInfo } from 'electron-telemetry/build/Model'
|
||||
|
||||
export function shouldAutoUpdate(build: any) {
|
||||
export function shouldAutoUpdate(build: BuildInfo) {
|
||||
return (
|
||||
build.package !== 'portable' &&
|
||||
build.package !== 'appx' &&
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
// import { BuildInfo } from 'electron-telemetry/build/Model'
|
||||
import { BuildInfo } from 'electron-telemetry/build/Model'
|
||||
|
||||
let buildOptions: any = {
|
||||
let buildOptions: BuildInfo = {
|
||||
platform: process.platform,
|
||||
package: 'unpacked',
|
||||
} as any
|
||||
|
||||
@@ -17,12 +17,10 @@ export async function waitForDevServer() {
|
||||
}
|
||||
|
||||
export function loadDevTools() {
|
||||
/* spell-checker: disable */
|
||||
// Redux
|
||||
// BrowserWindow.addDevToolsExtension(
|
||||
// path.join(os.homedir(), '/Library/Application Support/Google/Chrome/Default/Extensions/lmhkpmbekcpmknklioeibfkpmmfibljd/2.17.0_0/')
|
||||
// )
|
||||
/* spell-checker: enable */
|
||||
}
|
||||
|
||||
export function isDev() {
|
||||
|
||||
+6
-16
@@ -1,31 +1,23 @@
|
||||
import * as log from 'electron-log'
|
||||
import * as path from 'path'
|
||||
import ConfigStorage from '../backend/src/ConfigStorage'
|
||||
import { app, BrowserWindow, Menu, dialog } from 'electron'
|
||||
import { app, BrowserWindow, Menu } from 'electron'
|
||||
import { autoUpdater } from 'electron-updater'
|
||||
import { ConnectionManager } from '../backend/src/index'
|
||||
// import { electronTelemetryFactory } from 'electron-telemetry'
|
||||
import { electronTelemetryFactory } from 'electron-telemetry'
|
||||
import { menuTemplate } from './MenuTemplate'
|
||||
import buildOptions from './buildOptions'
|
||||
import { waitForDevServer, isDev, runningUiTestOnCi, loadDevTools } from './development'
|
||||
import { shouldAutoUpdate, handleAutoUpdate } from './autoUpdater'
|
||||
import { registerCrashReporter } from './registerCrashReporter'
|
||||
import { makeOpenDialogRpc } from '../events/OpenDialogRequest'
|
||||
import { backendRpc, getAppVersion } from '../events'
|
||||
|
||||
registerCrashReporter()
|
||||
|
||||
// if (!isDev() && !runningUiTestOnCi()) {
|
||||
// const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
|
||||
// }
|
||||
if (!isDev() && !runningUiTestOnCi()) {
|
||||
const electronTelemetry = electronTelemetryFactory('9b0c8ca04a361eb8160d98c5', buildOptions)
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch('--no-sandbox')
|
||||
app.whenReady().then(() => {
|
||||
backendRpc.on(makeOpenDialogRpc(), async request => {
|
||||
return dialog.showOpenDialog(BrowserWindow.getFocusedWindow() ?? BrowserWindow.getAllWindows()[0], request)
|
||||
})
|
||||
backendRpc.on(getAppVersion, async () => app.getVersion())
|
||||
})
|
||||
|
||||
autoUpdater.logger = log
|
||||
log.info('App starting...')
|
||||
@@ -33,7 +25,7 @@ log.info('App starting...')
|
||||
const connectionManager = new ConnectionManager()
|
||||
connectionManager.manageConnections()
|
||||
|
||||
const configStorage = new ConfigStorage(path.join(app.getPath('userData'), 'settings.json'))
|
||||
const configStorage = new ConfigStorage(path.join(app.getPath('appData'), app.name, 'settings.json'))
|
||||
configStorage.init()
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
@@ -53,8 +45,6 @@ async function createWindow() {
|
||||
height: 720,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
...({ enableRemoteModule: true } as any),
|
||||
contextIsolation: false,
|
||||
nodeIntegration: true,
|
||||
devTools: true,
|
||||
sandbox: false,
|
||||
|
||||
+64
-71
@@ -1,9 +1,6 @@
|
||||
import * as fs from 'fs'
|
||||
import * as os from 'os'
|
||||
import * as path from 'path'
|
||||
|
||||
import { ElectronApplication, _electron as electron } from 'playwright'
|
||||
|
||||
import * as webdriverio from 'webdriverio'
|
||||
import mockMqtt, { stop as stopMqtt } from './mock-mqtt'
|
||||
import { clearOldTopics } from './scenarios/clearOldTopics'
|
||||
import { clearSearch, searchTree } from './scenarios/searchTree'
|
||||
@@ -13,7 +10,7 @@ import { copyTopicToClipboard } from './scenarios/copyTopicToClipboard'
|
||||
import { copyValueToClipboard } from './scenarios/copyValueToClipboard'
|
||||
import { disconnect } from './scenarios/disconnect'
|
||||
import { publishTopic } from './scenarios/publishTopic'
|
||||
import { Scene, SceneBuilder } from './SceneBuilder'
|
||||
import { SceneBuilder } from './SceneBuilder'
|
||||
import { showAdvancedConnectionSettings } from './scenarios/showAdvancedConnectionSettings'
|
||||
import { showJsonPreview } from './scenarios/showJsonPreview'
|
||||
import { showMenu } from './scenarios/showMenu'
|
||||
@@ -21,15 +18,6 @@ import { showNumericPlot } from './scenarios/showNumericPlot'
|
||||
import { showOffDiffCapability } from './scenarios/showOffDiffCapability'
|
||||
import { showZoomLevel } from './scenarios/showZoomLevel'
|
||||
|
||||
/**
|
||||
* A convenience method that handles gracefully cleaning up the test run.
|
||||
*/
|
||||
const cleanUp = async (scenes: SceneBuilder, electronApp: ElectronApplication) => {
|
||||
// Exit app.
|
||||
fs.writeFileSync('scenes.json', JSON.stringify(scenes.scenes, undefined, ' '))
|
||||
await electronApp.close()
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', (error: Error | any) => {
|
||||
console.error('unhandledRejection', error.message, error.stack)
|
||||
process.exit(1)
|
||||
@@ -37,114 +25,119 @@ process.on('unhandledRejection', (error: Error | any) => {
|
||||
|
||||
const runningUiTestOnCi = os.platform() === 'darwin' ? [] : ['--runningUiTestOnCi']
|
||||
|
||||
const options = {
|
||||
host: '127.0.0.1', // Use localhost as chrome driver server
|
||||
port: 9515, // "9515" is the port opened by chrome driver.
|
||||
path: '/wd/hub',
|
||||
capabilities: {
|
||||
browserName: 'chrome',
|
||||
'goog:chromeOptions': {
|
||||
binary: `${__dirname}/../../../node_modules/.bin/electron`,
|
||||
args: [
|
||||
`--app=${__dirname}/../../..`,
|
||||
'--force-device-scale-factor=1',
|
||||
'--no-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-extensions',
|
||||
].concat(runningUiTestOnCi),
|
||||
windowTypes: ['app', 'webview'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
async function doStuff() {
|
||||
console.log('Waiting for MQTT Broker on port 1880 (no auth)')
|
||||
await mockMqtt()
|
||||
console.log('start webdriver')
|
||||
|
||||
console.log('Starting playwright/electron')
|
||||
|
||||
// Launch Electron app.
|
||||
const electronApp: ElectronApplication = await electron.launch({
|
||||
args: [`${__dirname}/../../..`, ...runningUiTestOnCi],
|
||||
})
|
||||
|
||||
console.log('Playwright started')
|
||||
// Get the first window that the app opens, wait if necessary.
|
||||
const page = await electronApp.firstWindow({ timeout: 3000 })
|
||||
// Print the title.
|
||||
console.log(await page.title())
|
||||
// Capture a screenshot.
|
||||
await page.screenshot({ path: 'intro.png' })
|
||||
// Direct Electron console to Node terminal.
|
||||
page.on('console', console.log)
|
||||
const browser = await webdriverio.remote(options)
|
||||
await createFakeMousePointer(browser)
|
||||
|
||||
// Wait for Username input to be visible
|
||||
await page.locator('//label[contains(text(), "Username")]/..//input')
|
||||
await browser.$('//label[contains(text(), "Username")]/..//input')
|
||||
|
||||
const scenes = new SceneBuilder()
|
||||
await scenes.record('connect', async () => {
|
||||
await connectTo('127.0.0.1', page)
|
||||
await connectTo('127.0.0.1', browser)
|
||||
await sleep(1000)
|
||||
})
|
||||
|
||||
await scenes.record('numeric_plots', async () => {
|
||||
await showText('Plot topic history', 1500, page)
|
||||
await showNumericPlot(page)
|
||||
await showText('Plot topic history', 1500, browser)
|
||||
await showNumericPlot(browser)
|
||||
await sleep(2000)
|
||||
})
|
||||
|
||||
await scenes.record('json-formatting', async () => {
|
||||
await showJsonPreview(page)
|
||||
await showText('Formatted messages', 1500, page, 'top')
|
||||
await showJsonPreview(browser)
|
||||
await showText('Formatted messages', 1500, browser, 'top')
|
||||
await sleep(1500)
|
||||
})
|
||||
|
||||
await scenes.record('diffs', async () => {
|
||||
await showOffDiffCapability(page)
|
||||
await hideText(page)
|
||||
await showOffDiffCapability(browser)
|
||||
await hideText(browser)
|
||||
})
|
||||
|
||||
// disable this scenario for now until expandTopic is sorted out
|
||||
// await scenes.record('publish_topic', async () => {
|
||||
// await showText('Publish topics', 1500, page, 'top')
|
||||
// await clickOnHistory(page)
|
||||
// await publishTopic(page)
|
||||
// await sleep(1000)
|
||||
// })
|
||||
await scenes.record('publish_topic', async () => {
|
||||
await showText('Publish topics', 1500, browser, 'top')
|
||||
await clickOnHistory(browser)
|
||||
await publishTopic(browser)
|
||||
await sleep(1000)
|
||||
})
|
||||
|
||||
await scenes.record('clipboard', async () => {
|
||||
await showText('Copy to Clipboard', 1500, page)
|
||||
await copyTopicToClipboard(page)
|
||||
await hideText(page)
|
||||
await copyValueToClipboard(page)
|
||||
await showText('Copy to Clipboard', 1500, browser)
|
||||
await copyTopicToClipboard(browser)
|
||||
await hideText(browser)
|
||||
await copyValueToClipboard(browser)
|
||||
await sleep(1000)
|
||||
})
|
||||
|
||||
await scenes.record('topic_filter', async () => {
|
||||
await showText('Search topic hierarchy', 0, page, 'middle')
|
||||
await searchTree('temp', page)
|
||||
await hideText(page)
|
||||
await showText('Topics containing "temp"', 1500, page)
|
||||
await showText('Search topic hierarchy', 0, browser, 'middle')
|
||||
await searchTree('temp', browser)
|
||||
await hideText(browser)
|
||||
await showText('Topics containing "temp"', 1500, browser)
|
||||
await sleep(1500)
|
||||
await clearSearch(page)
|
||||
await clearSearch(browser)
|
||||
await sleep(1000)
|
||||
})
|
||||
|
||||
// disable this scenario for now until expandTopic is sorted out
|
||||
// await scenes.record('delete_retained_topics', async () => {
|
||||
// await hideText(page)
|
||||
// await showText('Delete retained topics', 5000, page)
|
||||
// await clearOldTopics(page)
|
||||
// await hideText(page)
|
||||
// })
|
||||
await scenes.record('delete_retained_topics', async () => {
|
||||
await hideText(browser)
|
||||
await showText('Delete retained topics', 5000, browser)
|
||||
await clearOldTopics(browser)
|
||||
await hideText(browser)
|
||||
})
|
||||
|
||||
await scenes.record('settings', async () => {
|
||||
await showText('Settings', 1500, page)
|
||||
await showMenu(page)
|
||||
await showText('Settings', 1500, browser)
|
||||
await showMenu(browser)
|
||||
})
|
||||
|
||||
await scenes.record('customize_subscriptions', async () => {
|
||||
await sleep(2000)
|
||||
await disconnect(page)
|
||||
await showText('Customize Subscriptions', 1500, page, 'top')
|
||||
await showAdvancedConnectionSettings(page)
|
||||
await disconnect(browser)
|
||||
await showText('Customize Subscriptions', 1500, browser, 'top')
|
||||
await showAdvancedConnectionSettings(browser)
|
||||
})
|
||||
|
||||
await scenes.record('keyboard_shortcuts', async () => {
|
||||
await showText('Keyboard shortcuts', 1500, page, 'middle')
|
||||
await showText('Keyboard shortcuts', 1500, browser, 'middle')
|
||||
await sleep(1750)
|
||||
await showZoomLevel(page)
|
||||
await showZoomLevel(browser)
|
||||
})
|
||||
|
||||
await scenes.record('end', async () => {
|
||||
await showText('The End', 3000, page, 'middle')
|
||||
await showText('The End', 3000, browser, 'middle')
|
||||
await sleep(3000)
|
||||
})
|
||||
|
||||
browser.closeWindow()
|
||||
stopMqtt()
|
||||
console.log('Stopped mqtt')
|
||||
|
||||
cleanUp(scenes, electronApp)
|
||||
fs.writeFileSync('scenes.json', JSON.stringify(scenes.scenes, undefined, ' '))
|
||||
}
|
||||
|
||||
doStuff()
|
||||
|
||||
+38
-20
@@ -1,6 +1,5 @@
|
||||
import * as os from 'os'
|
||||
import { ElectronApplication, _electron as electron } from 'playwright'
|
||||
|
||||
import * as webdriverio from 'webdriverio'
|
||||
import mockMqtt, { stopUpdates as stopMqttUpdates } from './mock-mqtt'
|
||||
import { ClassNameMapping, countInstancesOf, createFakeMousePointer, getHeapDump, setFast, sleep } from './util'
|
||||
import { clearSearch, searchTree } from './scenarios/searchTree'
|
||||
@@ -14,53 +13,67 @@ process.on('unhandledRejection', (error: Error | any) => {
|
||||
|
||||
const runningUiTestOnCi = os.platform() === 'darwin' ? [] : ['--runningUiTestOnCi']
|
||||
|
||||
const options = {
|
||||
host: '127.0.0.1', // Use localhost as chrome driver server
|
||||
port: 9515, // "9515" is the port opened by chrome driver.
|
||||
capabilities: {
|
||||
browserName: 'chrome',
|
||||
'goog:chromeOptions': {
|
||||
binary: `${__dirname}/../../../node_modules/.bin/electron`,
|
||||
args: [
|
||||
`--app=${__dirname}/../../..`,
|
||||
'--force-device-scale-factor=1',
|
||||
'--no-sandbox',
|
||||
'--disable-dev-shm-usage',
|
||||
'--disable-extensions',
|
||||
].concat(runningUiTestOnCi),
|
||||
windowTypes: ['app', 'webview'],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
async function doStuff() {
|
||||
console.log('Waiting for MQTT Broker on port 1880 (no auth)')
|
||||
await mockMqtt()
|
||||
console.log('start webdriver')
|
||||
|
||||
console.log('Starting playwright/electron')
|
||||
|
||||
// Launch Electron app.
|
||||
const electronApp: ElectronApplication = await electron.launch({
|
||||
args: [`${__dirname}/../../..`, ...runningUiTestOnCi],
|
||||
})
|
||||
|
||||
console.log('Playwright started')
|
||||
// Get the first window that the app opens, wait if necessary.
|
||||
const browser = await electronApp.firstWindow({ timeout: 3000 })
|
||||
// Print the title.
|
||||
console.log(await browser.title())
|
||||
// Capture a screenshot.
|
||||
await browser.screenshot({ path: 'intro.png' })
|
||||
// Direct Electron console to Node terminal.
|
||||
browser.on('console', console.log)
|
||||
|
||||
const browser = await webdriverio.remote(options)
|
||||
setFast()
|
||||
await createFakeMousePointer(browser)
|
||||
|
||||
// Wait for Username input to be visible
|
||||
await browser.locator('//label[contains(text(), "Username")]/..//input')
|
||||
await browser.$('//label[contains(text(), "Username")]/..//input')
|
||||
await connectTo('127.0.0.1', browser)
|
||||
stopMqttUpdates()
|
||||
await sleep(1000, true)
|
||||
|
||||
const heapDump = await getHeapDump(browser)
|
||||
const initialTreeOccurrences = await countInstancesOf(heapDump, ClassNameMapping.Tree)
|
||||
const initialNodeOccurrences = await countInstancesOf(heapDump, ClassNameMapping.TreeNode)
|
||||
console.log(initialTreeOccurrences, initialNodeOccurrences)
|
||||
|
||||
await doX(3, async () => {
|
||||
await reconnect(browser)
|
||||
})
|
||||
|
||||
await sleep(1000, true)
|
||||
|
||||
await doX(15, async () => {
|
||||
await searchTree('temp', browser)
|
||||
await reconnect(browser)
|
||||
})
|
||||
|
||||
await searchTree('ab', browser)
|
||||
await clearSearch(browser)
|
||||
|
||||
await searchTree('temp', browser)
|
||||
await clearSearch(browser)
|
||||
|
||||
await sleep(1000, true)
|
||||
|
||||
await waitForGarbageCollectorToDetermineLeak(browser, initialTreeOccurrences, initialNodeOccurrences)
|
||||
}
|
||||
|
||||
async function waitForGarbageCollectorToDetermineLeak(
|
||||
browser: any,
|
||||
initialTreeOccurrences: number,
|
||||
@@ -77,6 +90,7 @@ async function waitForGarbageCollectorToDetermineLeak(
|
||||
const heapDump = await getHeapDump(browser)
|
||||
const currentTreeOccurrences = await countInstancesOf(heapDump, ClassNameMapping.Tree)
|
||||
const currentNodeOccurrences = await countInstancesOf(heapDump, ClassNameMapping.TreeNode)
|
||||
|
||||
// Temporary "leaks" are expected due to React Fibers memoization
|
||||
if (
|
||||
Math.abs(initialTreeOccurrences - currentTreeOccurrences) > 1 ||
|
||||
@@ -93,17 +107,21 @@ async function waitForGarbageCollectorToDetermineLeak(
|
||||
} else {
|
||||
leak = false
|
||||
}
|
||||
|
||||
const treeDelta = lastTreeOccurrences >= 0 ? currentTreeOccurrences - lastTreeOccurrences : -1
|
||||
const nodeDelta = lastTreeOccurrences >= 0 ? currentNodeOccurrences - lastNodeOccurrences : -1
|
||||
delta = treeDelta + nodeDelta
|
||||
|
||||
lastTreeOccurrences = currentTreeOccurrences
|
||||
lastNodeOccurrences = currentNodeOccurrences
|
||||
}
|
||||
|
||||
if (leak) {
|
||||
console.error('leak')
|
||||
process.exit(100)
|
||||
}
|
||||
}
|
||||
|
||||
async function doX(x: number, action: () => Promise<any>) {
|
||||
for (let i = 0; i < x; i += 1) {
|
||||
await action()
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import { clickOn, expandTopic, moveToCenterOfElement, sleep, writeText } from '../util'
|
||||
|
||||
export async function clearOldTopics(browser: Page) {
|
||||
export async function clearOldTopics(browser: Browser) {
|
||||
const topics = ['hello', 'test 123']
|
||||
for (const topic of topics) {
|
||||
await expandTopic(topic, browser)
|
||||
await sleep(1000)
|
||||
|
||||
const deleteButton = await browser.locator('//button[contains(@title, "Delete retained topic")]')
|
||||
await moveToCenterOfElement(deleteButton)
|
||||
await clickOn(deleteButton)
|
||||
const deleteButton = await browser.$('//button[contains(@title, "Delete retained topic")]')
|
||||
await moveToCenterOfElement(deleteButton, browser)
|
||||
await clickOn(deleteButton, browser)
|
||||
await sleep(700)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import { clickOn, setTextInInput } from '../util'
|
||||
import { Page, Locator } from 'playwright'
|
||||
export async function connectTo(host: string, browser: Page) {
|
||||
|
||||
export async function connectTo(host: string, browser: Browser) {
|
||||
await setTextInInput('Host', host, browser)
|
||||
|
||||
await browser.screenshot({ path: 'screen1.png' })
|
||||
await browser.saveScreenshot('screen1.png')
|
||||
|
||||
const connectButton = await browser.locator('//button/span[contains(text(),"Connect")]')
|
||||
await clickOn(connectButton)
|
||||
const connectButton = await browser.$('//button/span[contains(text(),"Connect")]')
|
||||
await clickOn(connectButton, browser)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser } from 'webdriverio'
|
||||
import { clickOn } from '../util'
|
||||
|
||||
export async function copyTopicToClipboard(browser: Page) {
|
||||
const copyButton = await browser.locator('//span[contains(text(), "Topic")]//button[1]')
|
||||
await clickOn(copyButton, 1)
|
||||
export async function copyTopicToClipboard(browser: Browser) {
|
||||
const copyButton = await browser.$('//span[contains(text(), "Topic")]//button')
|
||||
await clickOn(copyButton, browser, 1)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser } from 'webdriverio'
|
||||
import { clickOn } from '../util'
|
||||
|
||||
export async function copyValueToClipboard(browser: Page) {
|
||||
const copyButton = await browser.locator('//span[contains(text(), "Value")]//button')
|
||||
await clickOn(copyButton, 1)
|
||||
export async function copyValueToClipboard(browser: Browser) {
|
||||
const copyButton = await browser.$('//span[contains(text(), "Value")]//button')
|
||||
await clickOn(copyButton, browser, 1)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import { clickOn } from '../util'
|
||||
|
||||
export async function disconnect(browser: Page) {
|
||||
const disconnectButton = await browser.locator('//button/span[contains(text(),"Disconnect")]')
|
||||
await clickOn(disconnectButton)
|
||||
export async function disconnect(browser: Browser) {
|
||||
const disconnectButton = await browser.$('//button/span[contains(text(),"Disconnect")]')
|
||||
await clickOn(disconnectButton, browser)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Page, Locator } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import {
|
||||
clickOn,
|
||||
sleep,
|
||||
@@ -9,30 +9,31 @@ import {
|
||||
showText,
|
||||
} from '../util'
|
||||
|
||||
export async function publishTopic(browser: Page) {
|
||||
export async function publishTopic(browser: Browser) {
|
||||
await expandTopic('kitchen/lamp/state', browser)
|
||||
const topicInput = await browser.locator('//input[contains(@value,"kitchen/lamp/state")][1]')
|
||||
await clickOn(topicInput)
|
||||
await deleteTextWithBackspaces(topicInput, 120, 5)
|
||||
await writeText('set', topicInput, 300)
|
||||
const topicInput = await browser.$('//input[contains(@value,"kitchen/lamp/state")][1]')
|
||||
await clickOn(topicInput, browser)
|
||||
await deleteTextWithBackspaces(topicInput, browser, 120, 5)
|
||||
await writeText('set', browser, 300)
|
||||
|
||||
const payloadInput = await browser.$('//*[contains(@class, "ace_text-input")]')
|
||||
await writeTextPayload(payloadInput, '{"action": "setState", "state": "on" }')
|
||||
|
||||
const payloadInput = await browser.locator('//*[contains(@class, "ace_text-input")]')
|
||||
await writeTextPayload(payloadInput, 'off')
|
||||
await sleep(500)
|
||||
const formatJsonButton = await browser.locator('#sidebar-publish-format-json')
|
||||
await clickOn(formatJsonButton)
|
||||
const formatJsonButton = await browser.$('#sidebar-publish-format-json')
|
||||
await clickOn(formatJsonButton, browser)
|
||||
|
||||
const publishButton = await browser.locator('#publish-button')
|
||||
await moveToCenterOfElement(publishButton)
|
||||
const publishButton = await browser.$('#publish-button')
|
||||
await moveToCenterOfElement(publishButton, browser)
|
||||
await showText('Lamp turns on', 1000, browser, 'top')
|
||||
await sleep(500)
|
||||
|
||||
await clickOn(publishButton)
|
||||
await clickOn(publishButton, browser)
|
||||
|
||||
const sidebarDrawer = await browser.locator('#Sidebar')
|
||||
await sidebarDrawer.scrollIntoViewIfNeeded()
|
||||
const sidebarDrawer = await browser.$('#Sidebar')
|
||||
await sidebarDrawer.scrollIntoView()
|
||||
}
|
||||
|
||||
async function writeTextPayload(payloadInput: Locator, text: string) {
|
||||
await payloadInput.fill(text)
|
||||
async function writeTextPayload(payloadInput: any, text: string) {
|
||||
await payloadInput.setValue(text)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import { clickOn } from '../util'
|
||||
|
||||
export async function reconnect(browser: Page) {
|
||||
const disconnectButton = await browser.locator('//button/span[contains(text(),"Disconnect")]')
|
||||
await clickOn(disconnectButton)
|
||||
const connectButton = await browser.locator('//button/span[contains(text(),"Connect")]')
|
||||
await clickOn(connectButton)
|
||||
export async function reconnect(browser: Browser) {
|
||||
const disconnectButton = await browser.$('//button/span[contains(text(),"Disconnect")]')
|
||||
await clickOn(disconnectButton, browser)
|
||||
const connectButton = await browser.$('//button/span[contains(text(),"Connect")]')
|
||||
await clickOn(connectButton, browser)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import { clickOn, deleteTextWithBackspaces, showText, sleep, writeText } from '../util'
|
||||
|
||||
export async function searchTree(text: string, browser: Page) {
|
||||
const searchField = await browser.locator('//input[contains(@placeholder, "Search")]')
|
||||
await clickOn(searchField, 1)
|
||||
await writeText(text, searchField, 100)
|
||||
export async function searchTree(text: string, browser: Browser) {
|
||||
const searchField = await browser.$('//input[contains(@placeholder, "Search")]')
|
||||
await clickOn(searchField, browser, 1)
|
||||
await writeText(text, browser, 100)
|
||||
await sleep(1500)
|
||||
}
|
||||
|
||||
export async function clearSearch(browser: Page) {
|
||||
const searchField = await browser.locator('//input[contains(@placeholder, "Search")]')
|
||||
await clickOn(searchField, 1)
|
||||
await deleteTextWithBackspaces(searchField, 100)
|
||||
export async function clearSearch(browser: Browser) {
|
||||
const searchField = await browser.$('//input[contains(@placeholder, "Search")]')
|
||||
await clickOn(searchField, browser, 1)
|
||||
await deleteTextWithBackspaces(searchField, browser, 100)
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser } from 'webdriverio'
|
||||
import { clickOn, sleep, setInputText } from '../util'
|
||||
|
||||
export async function showAdvancedConnectionSettings(browser: Page) {
|
||||
const advancedSettingsButton = await browser.locator('//button/span[contains(text(),"Advanced")]')
|
||||
const addButton = await browser.locator('//button/span[contains(text(),"Add")]')
|
||||
const topicInput = await browser.locator('//*[contains(@class, "advanced-connection-settings-topic-input")]//input')
|
||||
export async function showAdvancedConnectionSettings(browser: Browser) {
|
||||
const advancedSettingsButton = await browser.$('//button/span[contains(text(),"Advanced")]')
|
||||
const addButton = await browser.$('//button/span[contains(text(),"Add")]')
|
||||
const topicInput = await browser.$('//*[contains(@class, "advanced-connection-settings-topic-input")]//input')
|
||||
|
||||
await clickOn(advancedSettingsButton)
|
||||
await clickOn(advancedSettingsButton, browser)
|
||||
await setInputText(topicInput, 'garden/#', browser)
|
||||
await clickOn(addButton)
|
||||
await clickOn(addButton, browser)
|
||||
|
||||
await setInputText(topicInput, 'livingroom/#', browser)
|
||||
await clickOn(addButton)
|
||||
await clickOn(addButton, browser)
|
||||
|
||||
await deleteFirstSubscribedTopic(browser)
|
||||
await deleteFirstSubscribedTopic(browser)
|
||||
await sleep(1000)
|
||||
|
||||
const backButton = await browser.locator('//button/span[contains(text(),"Back")]').first()
|
||||
await clickOn(backButton)
|
||||
const backButton = await browser.$('//button/span[contains(text(),"Back")]')
|
||||
await clickOn(backButton, browser)
|
||||
|
||||
const connectButton = await browser.locator('//button/span[contains(text(),"Connect")]')
|
||||
await clickOn(connectButton)
|
||||
const connectButton = await browser.$('//button/span[contains(text(),"Connect")]')
|
||||
await clickOn(connectButton, browser)
|
||||
}
|
||||
|
||||
async function deleteFirstSubscribedTopic(browser: Page) {
|
||||
const deleteButton = await browser.locator('.advanced-connection-settings-topic-list button').first()
|
||||
await clickOn(deleteButton)
|
||||
async function deleteFirstSubscribedTopic(browser: Browser) {
|
||||
const deleteButton = await browser.$('.advanced-connection-settings-topic-list button')
|
||||
await clickOn(deleteButton, browser)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Page, Locator } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import { expandTopic, sleep } from '../util'
|
||||
|
||||
export async function showJsonPreview(browser: Page) {
|
||||
export async function showJsonPreview(browser: Browser) {
|
||||
await expandTopic('actuality/showcase', browser)
|
||||
await browser.screenshot({ path: 'screen3.png' })
|
||||
await browser.saveScreenshot('screen3.png')
|
||||
await sleep(1000)
|
||||
}
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser } from 'webdriverio'
|
||||
import { clickOn, showText, sleep } from '../util'
|
||||
|
||||
export async function showMenu(browser: Page) {
|
||||
const menuButton = await browser.locator('//button[contains(@aria-label, "Menu")]')
|
||||
await clickOn(menuButton)
|
||||
export async function showMenu(browser: Browser) {
|
||||
const menuButton = await browser.$('//button[contains(@aria-label, "Menu")]')
|
||||
await clickOn(menuButton, browser)
|
||||
|
||||
// const brokerStatistics = await browser.$('//div[contains(@class, "BrokerStatistics")]/div[1]')
|
||||
// moveToCenterOfElement(brokerStatistics, browser)
|
||||
await sleep(2000)
|
||||
|
||||
await browser.screenshot({ path: 'screen4.png' })
|
||||
await browser.saveScreenshot('screen4.png')
|
||||
|
||||
const topicOrder = await browser.locator('//input[@name="node-order"]/../div')
|
||||
await clickOn(topicOrder)
|
||||
const topicOrder = await browser.$('//input[@name="node-order"]/../div')
|
||||
await clickOn(topicOrder, browser)
|
||||
await sleep(1000)
|
||||
|
||||
const alphabetically = await browser.locator('//li[contains(@data-value, "abc")]')
|
||||
await clickOn(alphabetically)
|
||||
const alphabetically = await browser.$('//li[contains(@data-value, "abc")]')
|
||||
await clickOn(alphabetically, browser)
|
||||
await sleep(2000)
|
||||
|
||||
await showText('Dark Mode', 1500, browser, 'top')
|
||||
await sleep(1500)
|
||||
const themeSwitch = await browser.locator('//*[contains(text(), "Dark Mode")]/..//input')
|
||||
await clickOn(themeSwitch)
|
||||
const themeSwitch = await browser.$('//*[contains(text(), "Dark Mode")]/..//input')
|
||||
await clickOn(themeSwitch, browser)
|
||||
await sleep(3000)
|
||||
await browser.screenshot({ path: 'screen_dark_mode.png' })
|
||||
await clickOn(themeSwitch)
|
||||
await browser.saveScreenshot('screen_dark_mode.png')
|
||||
await clickOn(themeSwitch, browser)
|
||||
|
||||
await clickOn(menuButton)
|
||||
await clickOn(menuButton, browser)
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import { moveToCenterOfElement, clickOn, clickOnHistory, expandTopic, sleep, writeText } from '../util'
|
||||
|
||||
export async function showNumericPlot(browser: Page) {
|
||||
export async function showNumericPlot(browser: Browser) {
|
||||
await expandTopic('kitchen/coffee_maker', browser)
|
||||
let heater = await valuePreviewGuttersShowChartIcon('heater', browser)
|
||||
await moveToCenterOfElement(heater)
|
||||
await moveToCenterOfElement(heater, browser)
|
||||
await sleep(1000)
|
||||
// Refocus and click
|
||||
heater = await valuePreviewGuttersShowChartIcon('heater', browser)
|
||||
@@ -12,7 +12,7 @@ export async function showNumericPlot(browser: Page) {
|
||||
|
||||
await sleep(1000)
|
||||
let temperature = await valuePreviewGuttersShowChartIcon('temperature', browser)
|
||||
await moveToCenterOfElement(temperature)
|
||||
await moveToCenterOfElement(temperature, browser)
|
||||
await sleep(1000)
|
||||
// Refocus and click
|
||||
temperature = await valuePreviewGuttersShowChartIcon('temperature', browser)
|
||||
@@ -30,7 +30,7 @@ export async function showNumericPlot(browser: Page) {
|
||||
await clickAway('temperature', browser)
|
||||
await sleep(2500)
|
||||
|
||||
await browser.screenshot({ path: 'screen_chart_panel.png' })
|
||||
await browser.saveScreenshot('screen_chart_panel.png')
|
||||
|
||||
await removeChart('heater', browser)
|
||||
await sleep(750)
|
||||
@@ -42,38 +42,34 @@ export async function showNumericPlot(browser: Page) {
|
||||
await clickOnHistory(browser)
|
||||
}
|
||||
|
||||
async function valuePreviewGuttersShowChartIcon(name: string, browser: Page) {
|
||||
async function valuePreviewGuttersShowChartIcon(name: string, browser: Browser) {
|
||||
for (let retries = 0; retries < 2; retries += 1) {
|
||||
try {
|
||||
return await browser.locator(`//*[contains(@data-test-type, "ShowChart")][contains(@data-test, "${name}")]`)
|
||||
return await browser.$(`//*[contains(@data-test-type, "ShowChart")][contains(@data-test, "${name}")]`)
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return browser.locator(`//*[contains(@data-test-type, "ShowChart")][contains(@data-test, "${name}")]`)
|
||||
return browser.$(`//*[contains(@data-test-type, "ShowChart")][contains(@data-test, "${name}")]`)
|
||||
}
|
||||
|
||||
async function chartSettings(name: string, browser: Page) {
|
||||
const settings = await browser.locator(
|
||||
`//*[contains(@data-test-type, "ChartSettings")][contains(@data-test, "${name}")]`
|
||||
)
|
||||
return clickOn(settings)
|
||||
async function chartSettings(name: string, browser: Browser) {
|
||||
const settings = await browser.$(`//*[contains(@data-test-type, "ChartSettings")][contains(@data-test, "${name}")]`)
|
||||
return clickOn(settings, browser)
|
||||
}
|
||||
|
||||
async function clickAway(name: string, browser: Page) {
|
||||
const settings = await browser.locator(
|
||||
`//*[contains(@data-test-type, "ChartPaper")][contains(@data-test, "${name}")]`
|
||||
)
|
||||
await moveToCenterOfElement(settings)
|
||||
await settings.press('Escape')
|
||||
async function clickAway(name: string, browser: Browser) {
|
||||
const settings = await browser.$(`//*[contains(@data-test-type, "ChartPaper")][contains(@data-test, "${name}")]`)
|
||||
await moveToCenterOfElement(settings, browser)
|
||||
await browser.keys(['Escape'])
|
||||
}
|
||||
|
||||
async function removeChart(name: string, browser: Page) {
|
||||
const remove = await browser.locator(`//*[contains(@data-test-type, "RemoveChart")][contains(@data-test, "${name}")]`)
|
||||
return clickOn(remove)
|
||||
async function removeChart(name: string, browser: Browser) {
|
||||
const remove = await browser.$(`//*[contains(@data-test-type, "RemoveChart")][contains(@data-test, "${name}")]`)
|
||||
return clickOn(remove, browser)
|
||||
}
|
||||
|
||||
async function clickOnMenuPoint(name: string, browser: Page) {
|
||||
const item = await browser.locator(`//li/span[contains(text(), "${name}")]`)
|
||||
return clickOn(item)
|
||||
async function clickOnMenuPoint(name: string, browser: Browser) {
|
||||
const item = await browser.$(`//li/span[contains(text(), "${name}")]`)
|
||||
return clickOn(item, browser)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { Page, Locator } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import { clickOn, showText, sleep } from '../util'
|
||||
|
||||
// Expects a topic with at least two messages to be selected
|
||||
export async function showOffDiffCapability(browser: Page) {
|
||||
export async function showOffDiffCapability(browser: Browser) {
|
||||
await showText('Compare messages', 2000, browser, 'top')
|
||||
|
||||
await showText('Show raw message', 2000, browser, 'bottom')
|
||||
const rawMessage = await browser.locator('#valueRendererDisplayMode-raw')
|
||||
await clickOn(rawMessage)
|
||||
const rawMessage = await browser.$('#valueRendererDisplayMode-raw')
|
||||
await clickOn(rawMessage, browser)
|
||||
await sleep(1000)
|
||||
|
||||
await showText('Compare with others', 2000, browser, 'bottom')
|
||||
const diffMessages = await browser.locator('#valueRendererDisplayMode-diff')
|
||||
await clickOn(diffMessages)
|
||||
const diffMessages = await browser.$('#valueRendererDisplayMode-diff')
|
||||
await clickOn(diffMessages, browser)
|
||||
|
||||
// // const firstEntry = await browser.$('//span[contains(text(), "History")]/../../div/div[1]/div')
|
||||
// const secondEntry = await browser.$('//span[contains(text(), "History")]/../../div/div[2]/div')
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Page } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
import { showKeys, showText, sleep } from '../util'
|
||||
|
||||
export async function showZoomLevel(browser: Page) {
|
||||
export async function showZoomLevel(browser: Browser) {
|
||||
await showKeys('Zoom in', 2000, browser, 'top', ['Ctrl', '+'])
|
||||
await sleep(2000)
|
||||
await showKeys('Zoom out', 2000, browser, 'middle', ['Ctrl', '-'])
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { clickOn } from './'
|
||||
import { Page } from 'playwright'
|
||||
import { Browser, Element } from 'webdriverio'
|
||||
|
||||
export async function expandTopic(path: string, browser: Page) {
|
||||
export async function expandTopic(path: string, browser: Browser) {
|
||||
const originalTopics = path.split('/')
|
||||
console.log('expandTopic', path)
|
||||
let topics = path.split('/')
|
||||
while (topics.length > 0 && !(await topicMatches(topics, browser))) {
|
||||
topics = topics.slice(0, topics.length - 1)
|
||||
@@ -12,27 +11,19 @@ export async function expandTopic(path: string, browser: Page) {
|
||||
throw Error('could not expand topics, no match found')
|
||||
}
|
||||
|
||||
console.log('found topics', topics, originalTopics)
|
||||
|
||||
for (const topic of topics) {
|
||||
const match = await browser.locator(topicSelector([topic]))
|
||||
await clickOn(match.first())
|
||||
while (topics.length <= originalTopics.length) {
|
||||
const match = await browser.$(topicSelector(topics))
|
||||
await clickOn(match, browser)
|
||||
topics.push(originalTopics[topics.length])
|
||||
}
|
||||
// while (topics.length <= originalTopics.length) {
|
||||
// const match = await browser.locator(topicSelector(topics))
|
||||
// console.log('click', match)
|
||||
// await clickOn(match)
|
||||
// topics.push(originalTopics[topics.length])
|
||||
// }
|
||||
}
|
||||
|
||||
async function topicMatches(topics: Array<string>, browser: Page) {
|
||||
const result = await browser.locator(topicSelector(topics))
|
||||
console.log('topic matches', topics, result)
|
||||
return true
|
||||
async function topicMatches(topics: Array<string>, browser: Browser) {
|
||||
const result = await browser.$(topicSelector(topics))
|
||||
return result.isExisting()
|
||||
}
|
||||
|
||||
function topicSelector(topics: Array<string>) {
|
||||
const selectors = topics.map(v => `span[data-test-topic='${v}']`)
|
||||
return selectors.join(' ')
|
||||
const suffix = topics.map(topic => `*[contains(text(), "${topic}")]`).join('/../..//')
|
||||
return `//${suffix}`
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user