зеркало из
1
0
Форкнуть 0

add cloud shell in device simulation (#354)

* add cloud shell in device simulation

* address comments

* update test
This commit is contained in:
YingXue 2020-09-10 11:58:15 -07:00 коммит произвёл GitHub
Родитель 2e725e8226
Коммит 1e22027bb1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
19 изменённых файлов: 1143 добавлений и 19 удалений

Двоичные данные
images/launchcloudshell.png Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 1.4 KiB

447
package-lock.json сгенерированный
Просмотреть файл

@ -837,6 +837,49 @@
"resolved": "https://registry.npmjs.org/@microsoft/load-themed-styles/-/load-themed-styles-1.10.39.tgz",
"integrity": "sha512-KKeQSM0FMTR7VtvkVF+D488zUmO3YoImxJ3O2xyLDEu9GqxqthF3Zvv1GddC2Jyoj2vuAJ0RGGgrI/T+MxLLDg=="
},
"@nodelib/fs.scandir": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz",
"integrity": "sha512-eGmwYQn3gxo4r7jdQnkrrN6bY478C3P+a/y72IJukF8LjB6ZHeB3c+Ehacj3sYeSmUXGlnA67/PmbM9CVwL7Dw==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "2.0.3",
"run-parallel": "^1.1.9"
}
},
"@nodelib/fs.stat": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.3.tgz",
"integrity": "sha512-bQBFruR2TAwoevBEd/NWMoAAtNGzTRgdrqnYCc7dhzfoNvqPzLyqlEQnzZ3kVnNrSp25iyxE00/3h2fqGAGArA==",
"dev": true
},
"@nodelib/fs.walk": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.4.tgz",
"integrity": "sha512-1V9XOY4rDW0rehzbrcqAmHnz8e7SKvX27gh8Gt2WgB0+pdzdiLV83p72kZPU+jvMbS1qU5mauP2iOvO8rhmurQ==",
"dev": true,
"requires": {
"@nodelib/fs.scandir": "2.1.3",
"fastq": "^1.6.0"
}
},
"@npmcli/move-file": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz",
"integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==",
"dev": true,
"requires": {
"mkdirp": "^1.0.4"
},
"dependencies": {
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
}
}
},
"@redux-saga/core": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.1.3.tgz",
@ -1737,6 +1780,24 @@
}
}
},
"aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
"integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
"dev": true,
"requires": {
"clean-stack": "^2.0.0",
"indent-string": "^4.0.0"
},
"dependencies": {
"indent-string": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
"integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
"dev": true
}
}
},
"airbnb-prop-types": {
"version": "2.15.0",
"resolved": "https://registry.npmjs.org/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz",
@ -3539,6 +3600,12 @@
"source-map": "~0.6.0"
}
},
"clean-stack": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
"integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
"dev": true
},
"cli-boxes": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.0.tgz",
@ -4044,6 +4111,206 @@
"integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=",
"dev": true
},
"copy-webpack-plugin": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.1.0.tgz",
"integrity": "sha512-aWjIuLt1OVQxaDVffnt3bnGmLA8zGgAJaFwPA+a+QYVPh1vhIKjVfh3SbOFLV0kRPvGBITbw17n5CsmiBS4LQQ==",
"dev": true,
"requires": {
"cacache": "^15.0.5",
"fast-glob": "^3.2.4",
"find-cache-dir": "^3.3.1",
"glob-parent": "^5.1.1",
"globby": "^11.0.1",
"loader-utils": "^2.0.0",
"normalize-path": "^3.0.0",
"p-limit": "^3.0.2",
"schema-utils": "^2.7.1",
"serialize-javascript": "^4.0.0",
"webpack-sources": "^1.4.3"
},
"dependencies": {
"@types/json-schema": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz",
"integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==",
"dev": true
},
"ajv": {
"version": "6.12.4",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz",
"integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==",
"dev": true,
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"ajv-keywords": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
},
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
},
"cacache": {
"version": "15.0.5",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz",
"integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==",
"dev": true,
"requires": {
"@npmcli/move-file": "^1.0.1",
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"glob": "^7.1.4",
"infer-owner": "^1.0.4",
"lru-cache": "^6.0.0",
"minipass": "^3.1.1",
"minipass-collect": "^1.0.2",
"minipass-flush": "^1.0.5",
"minipass-pipeline": "^1.2.2",
"mkdirp": "^1.0.3",
"p-map": "^4.0.0",
"promise-inflight": "^1.0.1",
"rimraf": "^3.0.2",
"ssri": "^8.0.0",
"tar": "^6.0.2",
"unique-filename": "^1.1.1"
}
},
"chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"dev": true
},
"find-cache-dir": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz",
"integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==",
"dev": true,
"requires": {
"commondir": "^1.0.1",
"make-dir": "^3.0.2",
"pkg-dir": "^4.1.0"
}
},
"globby": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.0.1.tgz",
"integrity": "sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ==",
"dev": true,
"requires": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.1.1",
"ignore": "^5.1.4",
"merge2": "^1.3.0",
"slash": "^3.0.0"
}
},
"json5": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
"integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
"dev": true,
"requires": {
"minimist": "^1.2.5"
}
},
"loader-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
"emojis-list": "^3.0.0",
"json5": "^2.1.2"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"dev": true
},
"p-limit": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz",
"integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==",
"dev": true,
"requires": {
"p-try": "^2.0.0"
}
},
"p-map": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
"integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
"dev": true,
"requires": {
"aggregate-error": "^3.0.0"
}
},
"schema-utils": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz",
"integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.5",
"ajv": "^6.12.4",
"ajv-keywords": "^3.5.2"
}
},
"ssri": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz",
"integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==",
"dev": true,
"requires": {
"minipass": "^3.1.1"
}
},
"tar": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz",
"integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==",
"dev": true,
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@ -4940,6 +5207,23 @@
"randombytes": "^2.0.0"
}
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"requires": {
"path-type": "^4.0.0"
},
"dependencies": {
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
}
}
},
"discontinuous-range": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
@ -6115,6 +6399,65 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
"integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA=="
},
"fast-glob": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.4.tgz",
"integrity": "sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.0",
"merge2": "^1.3.0",
"micromatch": "^4.0.2",
"picomatch": "^2.2.1"
},
"dependencies": {
"braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"dev": true,
"requires": {
"fill-range": "^7.0.1"
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"dev": true,
"requires": {
"to-regex-range": "^5.0.1"
}
},
"is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true
},
"micromatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz",
"integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==",
"dev": true,
"requires": {
"braces": "^3.0.1",
"picomatch": "^2.0.5"
}
},
"to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"requires": {
"is-number": "^7.0.0"
}
}
}
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@ -6132,6 +6475,15 @@
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true
},
"fastq": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz",
"integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==",
"dev": true,
"requires": {
"reusify": "^1.0.4"
}
},
"faye-websocket": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
@ -6389,6 +6741,15 @@
"universalify": "^0.1.0"
}
},
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"fs-write-stream-atomic": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz",
@ -7253,6 +7614,12 @@
"integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=",
"dev": true
},
"ignore": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
"integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
"dev": true
},
"ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
@ -9598,6 +9965,12 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@ -9732,6 +10105,68 @@
"integrity": "sha1-Z9ZgFLZqaoqqDAg8X9WN9OTpdgI=",
"dev": true
},
"minipass": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
"integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
},
"dependencies": {
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"minipass-collect": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
"integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-flush": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
"integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minipass-pipeline": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
"integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
"dev": true,
"requires": {
"minipass": "^3.0.0"
}
},
"minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dev": true,
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"dependencies": {
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
}
}
},
"mississippi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@ -14655,6 +15090,12 @@
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
"dev": true
},
"reusify": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true
},
"rgb-regex": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz",
@ -14735,6 +15176,12 @@
"integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==",
"dev": true
},
"run-parallel": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz",
"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==",
"dev": true
},
"run-queue": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz",

Просмотреть файл

@ -124,6 +124,7 @@
"@types/webpack-merge": "4.1.5",
"awesome-typescript-loader": "5.2.1",
"concurrently": "4.1.0",
"copy-webpack-plugin": "6.1.0",
"css-loader": "1.0.0",
"electron": "7.2.4",
"electron-builder": "22.8.0",

Просмотреть файл

@ -5,7 +5,7 @@
@import 'themes';
.collapsible-section {
padding-bottom: 20px;
padding-bottom: 5px;
@include themify($themes) {
border: 1px solid themed('borderColor')
}
@ -13,4 +13,7 @@
float: left;
margin-right: 5px;
}
.collapsible-section-children {
margin: 0px 10px;
}
}

Просмотреть файл

@ -5,8 +5,6 @@
@import 'themes';
.sas-token-section {
margin: 0px 10px;
.ms-Dropdown-title {
@include themify($themes){
background-color: themed('inputComponentBackgroundColor');

Просмотреть файл

@ -39,6 +39,15 @@ exports[`deviceEvents deviceEvents in non-pnp context matches snapshot in electr
"name": "deviceEvents.command.clearEvents",
"onClick": [Function],
},
Object {
"ariaLabel": "deviceEvents.command.clearEvents",
"iconProps": Object {
"iconName": "Code",
},
"key": "deviceEvents.command.simulate",
"name": "deviceEvents.command.simulate",
"onClick": [Function],
},
]
}
/>
@ -66,6 +75,10 @@ exports[`deviceEvents deviceEvents in non-pnp context matches snapshot in electr
onChange={[Function]}
onText="deviceEvents.toggleUseDefaultEventHub.on"
/>
<Component
onToggleSimulationPanel={[Function]}
showSimulationPanel={false}
/>
<div
className="device-events-container"
>
@ -136,6 +149,15 @@ exports[`deviceEvents deviceEvents in non-pnp context matches snapshot in hosted
"name": "deviceEvents.command.clearEvents",
"onClick": [Function],
},
Object {
"ariaLabel": "deviceEvents.command.clearEvents",
"iconProps": Object {
"iconName": "Code",
},
"key": "deviceEvents.command.simulate",
"name": "deviceEvents.command.simulate",
"onClick": [Function],
},
]
}
/>
@ -163,6 +185,10 @@ exports[`deviceEvents deviceEvents in non-pnp context matches snapshot in hosted
onChange={[Function]}
onText="deviceEvents.toggleUseDefaultEventHub.on"
/>
<Component
onToggleSimulationPanel={[Function]}
showSimulationPanel={false}
/>
<div
className="device-events-container"
>
@ -233,6 +259,15 @@ exports[`deviceEvents deviceEvents in pnp context matches snapshot while interfa
"name": "deviceEvents.command.clearEvents",
"onClick": [Function],
},
Object {
"ariaLabel": "deviceEvents.command.clearEvents",
"iconProps": Object {
"iconName": "Code",
},
"key": "deviceEvents.command.simulate",
"name": "deviceEvents.command.simulate",
"onClick": [Function],
},
]
}
/>
@ -260,6 +295,10 @@ exports[`deviceEvents deviceEvents in pnp context matches snapshot while interfa
onChange={[Function]}
onText="deviceEvents.toggleUseDefaultEventHub.on"
/>
<Component
onToggleSimulationPanel={[Function]}
showSimulationPanel={false}
/>
<div
className="device-events-container"
>
@ -309,6 +348,15 @@ exports[`deviceEvents deviceEvents in pnp context matches snapshot while interfa
"name": "deviceEvents.command.clearEvents",
"onClick": [Function],
},
Object {
"ariaLabel": "deviceEvents.command.clearEvents",
"iconProps": Object {
"iconName": "Code",
},
"key": "deviceEvents.command.simulate",
"name": "deviceEvents.command.simulate",
"onClick": [Function],
},
]
}
/>
@ -336,6 +384,10 @@ exports[`deviceEvents deviceEvents in pnp context matches snapshot while interfa
onChange={[Function]}
onText="deviceEvents.toggleUseDefaultEventHub.on"
/>
<Component
onToggleSimulationPanel={[Function]}
showSimulationPanel={false}
/>
<div
className="device-events-container"
>
@ -385,6 +437,15 @@ exports[`deviceEvents deviceEvents in pnp context matches snapshot while interfa
"name": "deviceEvents.command.clearEvents",
"onClick": [Function],
},
Object {
"ariaLabel": "deviceEvents.command.clearEvents",
"iconProps": Object {
"iconName": "Code",
},
"key": "deviceEvents.command.simulate",
"name": "deviceEvents.command.simulate",
"onClick": [Function],
},
]
}
/>
@ -412,6 +473,10 @@ exports[`deviceEvents deviceEvents in pnp context matches snapshot while interfa
onChange={[Function]}
onText="deviceEvents.toggleUseDefaultEventHub.on"
/>
<Component
onToggleSimulationPanel={[Function]}
showSimulationPanel={false}
/>
<div
className="device-events-container"
>

Просмотреть файл

@ -0,0 +1,176 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`deviceSimulationPanel matches snapshot 1`] = `
<StyledPanelBase
closeButtonAriaLabel="common.close"
headerText="deviceEvents.simulation.header"
isBlocking={false}
isOpen={true}
onDismiss={[MockFunction]}
type={3}
>
<a
onClick={[Function]}
target="_blank"
>
<img
alt="deviceEvents.simulation.cloudShell.imageDescription"
className="cloudShellButton"
src="images/launchcloudshell.png"
/>
</a>
<StyledLabelBase>
deviceEvents.simulation.cloudShell.textDescription
</StyledLabelBase>
<Component
expanded={true}
label="deviceEvents.simulation.prerequisite.label"
tooltipText="deviceEvents.simulation.prerequisite.tooltiop"
>
<span>
deviceEvents.simulation.prerequisite.instruction
</span>
</Component>
<Component
expanded={true}
label="deviceEvents.simulation.basic.label"
tooltipText="deviceEvents.simulation.basic.tooltiop"
>
<span>
deviceEvents.simulation.basic.instruction
</span>
<Component
allowMask={false}
ariaLabel="deviceEvents.simulation.basic.copyLabel"
label="deviceEvents.simulation.basic.copyLabel"
readOnly={true}
value="az iot device simulate --device-id device1 --login \\"\\""
/>
</Component>
<Component
expanded={false}
label="deviceEvents.simulation.advanced.label"
tooltipText="deviceEvents.simulation.advanced.tooltiop"
>
<span>
deviceEvents.simulation.advanced.instruction
</span>
<Component
tooltipText="deviceEvents.simulation.advanced.body.tooltip"
>
deviceEvents.simulation.advanced.body.label
</Component>
<StyledTextFieldBase
multiline={true}
onChange={[Function]}
rows={5}
/>
<StyledCommandBarBase
className="properties-section-command-bar"
items={
Array [
Object {
"ariaLabel": "deviceEvents.simulation.advanced.properties.addProperty",
"iconProps": Object {
"iconName": "CircleAddition",
},
"key": "deviceEvents.simulation.advanced.properties.addProperty",
"name": "deviceEvents.simulation.advanced.properties.addProperty",
"onClick": [Function],
},
Object {
"ariaLabel": "deviceEvents.simulation.advanced.properties.delete",
"disabled": true,
"iconProps": Object {
"iconName": "Delete",
},
"key": "deviceEvents.simulation.advanced.properties.delete",
"name": "deviceEvents.simulation.advanced.properties.delete",
"onClick": [Function],
},
]
}
/>
<StyledMarqueeSelectionBase
selection={
Selection {
"_anchoredIndex": 0,
"_canSelectItem": [Function],
"_changeEventSuppressionCount": 0,
"_exemptedCount": 0,
"_exemptedIndices": Object {},
"_getKey": [Function],
"_isModal": false,
"_items": Array [],
"_keyToIndexMap": Object {},
"_onSelectionChanged": [Function],
"_selectedItems": null,
"_unselectableCount": 0,
"_unselectableIndices": Object {},
"count": 0,
"mode": 2,
}
}
>
<StyledWithViewportComponent
ariaLabelForSelectAllCheckbox="deviceEvents.simulation.advanced.properties.selectAllCheckboxAriaLabel"
ariaLabelForSelectionColumn="deviceEvents.simulation.advanced.properties.toggleSelectionColumnAriaLabel"
checkButtonAriaLabel="deviceEvents.simulation.advanced.properties.rowCheckBoxAriaLabel"
columns={
Array [
Object {
"isResizable": true,
"key": "key",
"maxWidth": 300,
"minWidth": 150,
"name": "deviceEvents.simulation.advanced.properties.key",
},
Object {
"isResizable": true,
"key": "value",
"minWidth": 150,
"name": "deviceEvents.simulation.advanced.properties.value",
},
]
}
items={
Array [
Object {
"index": 0,
"keyName": "",
"value": "",
},
]
}
onRenderItemColumn={[Function]}
selection={
Selection {
"_anchoredIndex": 0,
"_canSelectItem": [Function],
"_changeEventSuppressionCount": 0,
"_exemptedCount": 0,
"_exemptedIndices": Object {},
"_getKey": [Function],
"_isModal": false,
"_items": Array [],
"_keyToIndexMap": Object {},
"_onSelectionChanged": [Function],
"_selectedItems": null,
"_unselectableCount": 0,
"_unselectableIndices": Object {},
"count": 0,
"mode": 2,
}
}
/>
</StyledMarqueeSelectionBase>
<Component
allowMask={false}
ariaLabel="deviceEvents.simulation.advanced.copyLabel"
label="deviceEvents.simulation.advanced.copyLabel"
readOnly={true}
value="az iot device simulate --device-id device1 --login \\"\\""
/>
</Component>
</StyledPanelBase>
`;

Просмотреть файл

@ -81,7 +81,7 @@ describe('deviceEvents', () => {
const wrapper = mount(<DeviceEvents/>);
const commandBar = wrapper.find(CommandBar).first();
// tslint:disable-next-line: no-magic-numbers
expect(commandBar.props().items.length).toEqual(3);
expect(commandBar.props().items.length).toEqual(4);
// click the start button
const startEventsMonitoringSpy = jest.spyOn(startEventsMonitoringAction, 'started');

Просмотреть файл

@ -14,7 +14,7 @@ import { Label } from 'office-ui-fabric-react/lib/components/Label';
import { ResourceKeys } from '../../../../localization/resourceKeys';
import { Message, MESSAGE_SYSTEM_PROPERTIES, MESSAGE_PROPERTIES } from '../../../api/models/messages';
import { parseDateTimeString } from '../../../api/dataTransforms/transformHelper';
import { CLEAR, CHECKED_CHECKBOX, EMPTY_CHECKBOX, START, STOP, NAVIGATE_BACK, REFRESH, REMOVE } from '../../../constants/iconNames';
import { CLEAR, CHECKED_CHECKBOX, EMPTY_CHECKBOX, START, STOP, NAVIGATE_BACK, REFRESH, REMOVE, CODE } from '../../../constants/iconNames';
import { getDeviceIdFromQueryString, getComponentNameFromQueryString, getInterfaceIdFromQueryString } from '../../../shared/utils/queryStringHelper';
import { SynchronizationStatus } from '../../../api/models/synchronizationStatus';
import { MonitorEventsParameters } from '../../../api/parameters/deviceParameters';
@ -40,6 +40,7 @@ import { getSchemaValidationErrors } from '../../../shared/utils/jsonSchemaAdapt
import { ParsedJsonSchema } from '../../../api/models/interfaceJsonParserOutput';
import { TelemetryContent } from '../../../api/models/modelDefinition';
import { getLocalizedData } from '../../../api/dataTransforms/modelDefinitionTransform';
import { DeviceSimulationPanel } from './deviceSimulationPanel';
import './deviceEvents.scss';
const JSON_SPACES = 2;
@ -78,6 +79,9 @@ export const DeviceEvents: React.FC = () => {
const telemetrySchema = React.useMemo(() => getDeviceTelemetry(modelDefinition), [modelDefinition]);
const [ showPnpModeledEvents, setShowPnpModeledEvents ] = React.useState(false);
// simulation specific
const [ showSimulationPanel, setShowSimulationPanel ] = React.useState(false);
React.useEffect(() => {
return () => {
stopMonitoring();
@ -123,7 +127,8 @@ export const DeviceEvents: React.FC = () => {
else {
return [createStartMonitoringCommandItem(),
createSystemPropertiesCommandItem(),
createClearCommandItem()
createClearCommandItem(),
createSimulationCommandItem()
];
}
};
@ -197,6 +202,18 @@ export const DeviceEvents: React.FC = () => {
}
};
const createSimulationCommandItem = (): ICommandBarItemProps => {
return {
ariaLabel: t(ResourceKeys.deviceEvents.command.clearEvents),
iconProps: {
iconName: CODE
},
key: t(ResourceKeys.deviceEvents.command.simulate),
name: t(ResourceKeys.deviceEvents.command.simulate),
onClick: onToggleSimulationPanel
};
};
const renderConsumerGroup = () => {
const renderConsumerGroupLabel = (textFieldProps: ITextFieldProps) => (
<LabelWithTooltip
@ -632,6 +649,10 @@ export const DeviceEvents: React.FC = () => {
setShowPnpModeledEvents(!showPnpModeledEvents);
};
const onToggleSimulationPanel = () => {
setShowSimulationPanel(!showSimulationPanel);
};
if (isLoading) {
return <MultiLineShimmer/>;
}
@ -639,6 +660,7 @@ export const DeviceEvents: React.FC = () => {
const className = componentName ?
'scrollable-pnp-telemetry' + (!useBuiltInEventHub ? ' scrollable-pnp-telemetry-custom' : '') :
'scrollable-telemetry' + (!useBuiltInEventHub ? ' scrollable-telemetry-custom' : '');
return (
<div className="device-events" key="device-events">
<CommandBar
@ -652,6 +674,10 @@ export const DeviceEvents: React.FC = () => {
/>
{renderConsumerGroup()}
{renderCustomEventHub()}
<DeviceSimulationPanel
showSimulationPanel={showSimulationPanel}
onToggleSimulationPanel={onToggleSimulationPanel}
/>
<div className="device-events-container">
{renderLoader()}
<div className={className}>

Просмотреть файл

@ -0,0 +1,8 @@
.ms-Panel-content {
.cloudShellButton {
cursor: pointer;
}
.collapsible-section {
margin-top: 30px;
}
}

Просмотреть файл

@ -0,0 +1,22 @@
/***********************************************************
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License
**********************************************************/
import 'jest';
import * as React from 'react';
import { shallow } from 'enzyme';
import { DeviceSimulationPanel } from './deviceSimulationPanel';
jest.mock('react-router-dom', () => ({
useLocation: () => ({ search: `?deviceId=device1` })
}));
describe('deviceSimulationPanel', () => {
it('matches snapshot ', () => {
expect(shallow(
<DeviceSimulationPanel
showSimulationPanel={true}
onToggleSimulationPanel={jest.fn()}
/>)).toMatchSnapshot();
});
});

Просмотреть файл

@ -0,0 +1,279 @@
/***********************************************************
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License
**********************************************************/
import * as React from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';
import { TextField } from 'office-ui-fabric-react/lib/components/TextField';
import { Panel, PanelType } from 'office-ui-fabric-react/lib/components/Panel';
import { Label } from 'office-ui-fabric-react/lib/components/Label';
import { DetailsList, IColumn } from 'office-ui-fabric-react/lib/components/DetailsList';
import { MarqueeSelection } from 'office-ui-fabric-react/lib/components/MarqueeSelection/MarqueeSelection';
import { Selection } from 'office-ui-fabric-react/lib/utilities/selection/Selection';
import { CommandBar } from 'office-ui-fabric-react/lib/components/CommandBar';
import { ResourceKeys } from '../../../../localization/resourceKeys';
import { getDeviceIdFromQueryString } from '../../../shared/utils/queryStringHelper';
import { LabelWithTooltip } from '../../../shared/components/labelWithTooltip';
import { CollapsibleSection } from '../../../shared/components/collapsibleSection';
import { MaskedCopyableTextField } from '../../../shared/components/maskedCopyableTextField';
import { getHubInformationFromLocalStorage } from '../../../shared/hooks/localStorageInformationRetriever';
import { CIRCLE_ADD, ArrayOperation } from '../../../constants/iconNames';
import { MEDIUM_COLUMN_WIDTH } from '../../../constants/columnWidth';
import './deviceSimulationPanel.scss';
export interface DeviceSimulationPanelProps {
showSimulationPanel: boolean;
onToggleSimulationPanel: () => void;
}
interface PropertyItem {
index: number;
keyName: string;
value: string;
}
export const DeviceSimulationPanel: React.FC<DeviceSimulationPanelProps> = props => {
const { t } = useTranslation();
const { search } = useLocation();
const deviceId = getDeviceIdFromQueryString(search);
const [ hubConnectionString, ] = getHubInformationFromLocalStorage();
const [ simulationBody, setSimulationBody ] = React.useState<string>('');
const [ propertyIndex, setPropertyIndex ] = React.useState<number>(0);
const [ selectedIndices, setSelectedIndices ] = React.useState<Set<number>>(new Set());
const [ properties, setProperties ] = React.useState<PropertyItem[]>([{index: 0, keyName: '', value: ''}]);
const selection = new Selection({
onSelectionChanged: () => onSelectionChanged()
});
const renderSimulationPanel = () => {
return (
<Panel
isOpen={props.showSimulationPanel}
type={PanelType.medium}
isBlocking={false}
onDismiss={props.onToggleSimulationPanel}
closeButtonAriaLabel={t(ResourceKeys.common.close)}
headerText={t(ResourceKeys.deviceEvents.simulation.header)}
>
<a onClick={onclick} target="_blank">
<img className="cloudShellButton" alt={t(ResourceKeys.deviceEvents.simulation.cloudShell.imageDescription)} src="images/launchcloudshell.png" />
</a>
<Label>{t(ResourceKeys.deviceEvents.simulation.cloudShell.textDescription)}</Label>
<CollapsibleSection
expanded={true}
label={t(ResourceKeys.deviceEvents.simulation.prerequisite.label)}
tooltipText={t(ResourceKeys.deviceEvents.simulation.prerequisite.tooltiop)}
>
<span>{t(ResourceKeys.deviceEvents.simulation.prerequisite.instruction)}</span>
</CollapsibleSection>
<CollapsibleSection
expanded={true}
label={t(ResourceKeys.deviceEvents.simulation.basic.label)}
tooltipText={t(ResourceKeys.deviceEvents.simulation.basic.tooltiop)}
>
<span>{t(ResourceKeys.deviceEvents.simulation.basic.instruction)}</span>
<MaskedCopyableTextField
ariaLabel={t(ResourceKeys.deviceEvents.simulation.basic.copyLabel, {deviceId})}
label={t(ResourceKeys.deviceEvents.simulation.basic.copyLabel, {deviceId})}
value={`az iot device simulate --device-id ${deviceId} --login \"${hubConnectionString}\"`}
allowMask={false}
readOnly={true}
/>
</CollapsibleSection>
<CollapsibleSection
expanded={false}
label={t(ResourceKeys.deviceEvents.simulation.advanced.label)}
tooltipText={t(ResourceKeys.deviceEvents.simulation.advanced.tooltiop)}
>
<span>{t(ResourceKeys.deviceEvents.simulation.advanced.instruction)}</span>
{renderMessageBodySection()}
{renderPropertiesList()}
<MaskedCopyableTextField
ariaLabel={t(ResourceKeys.deviceEvents.simulation.advanced.copyLabel, {deviceId})}
label={t(ResourceKeys.deviceEvents.simulation.advanced.copyLabel, {deviceId})}
value={convertToCliCommand()}
allowMask={false}
readOnly={true}
/>
</CollapsibleSection>
</Panel>
);
};
const renderMessageBodySection = () => {
const textFieldRows = 5;
return (
<>
<LabelWithTooltip
tooltipText={t(ResourceKeys.deviceEvents.simulation.advanced.body.tooltip)}
>
{t(ResourceKeys.deviceEvents.simulation.advanced.body.label)}
</LabelWithTooltip>
<TextField multiline={true} rows={textFieldRows} onChange={onTextFieldChange}/>
</>
);
};
const renderPropertiesList = () => {
return (
<>
<CommandBar
className="properties-section-command-bar"
items={[
{
ariaLabel: t(ResourceKeys.deviceEvents.simulation.advanced.properties.addProperty),
iconProps: {
iconName: CIRCLE_ADD
},
key: t(ResourceKeys.deviceEvents.simulation.advanced.properties.addProperty),
name: t(ResourceKeys.deviceEvents.simulation.advanced.properties.addProperty),
onClick: handleAddProperty
},
{
ariaLabel: t(ResourceKeys.deviceEvents.simulation.advanced.properties.delete),
disabled: selectedIndices.size === 0,
iconProps: {
iconName: ArrayOperation.REMOVE
},
key: t(ResourceKeys.deviceEvents.simulation.advanced.properties.delete),
name: t(ResourceKeys.deviceEvents.simulation.advanced.properties.delete),
onClick: handleDelete
},
]}
/>
<MarqueeSelection selection={selection}>
<DetailsList
items={properties}
columns={getColumns()}
onRenderItemColumn={renderItemColumn()}
ariaLabelForSelectionColumn={t(ResourceKeys.deviceEvents.simulation.advanced.properties.toggleSelectionColumnAriaLabel)}
ariaLabelForSelectAllCheckbox={t(ResourceKeys.deviceEvents.simulation.advanced.properties.selectAllCheckboxAriaLabel)}
checkButtonAriaLabel={t(ResourceKeys.deviceEvents.simulation.advanced.properties.rowCheckBoxAriaLabel)}
selection={selection}
/>
</MarqueeSelection>
</>
);
};
const renderItemColumn = () => (item: PropertyItem, index: number, column: IColumn) => {
const handleEditCustomPropertyKey = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
const items = [...properties];
items[index] = {...items[index], keyName: newValue};
setProperties(items);
};
switch (column.key) {
case 'key':
const hasDuplicateKey = (keyName: string) => keyName && properties.filter(property => property.keyName === keyName).length > 1;
return (
<TextField
ariaLabel={t(ResourceKeys.deviceEvents.simulation.advanced.properties.key)}
errorMessage={hasDuplicateKey(item.keyName) && t(ResourceKeys.deviceEvents.simulation.advanced.properties.keyDup)}
value={item.keyName}
onChange={handleEditCustomPropertyKey}
/>);
case 'value':
return renderItemValueColumn(item, column);
default:
return;
}
};
const findMatchingItemIndex = (property: PropertyItem): number => {
let indexFound = -1;
properties.forEach((element, index) => {
if (element.index === property.index) {
indexFound = index;
}
});
return indexFound;
};
const renderItemValueColumn = (item: PropertyItem, column: IColumn) => {
const index = findMatchingItemIndex(item);
const handleEditPropertyValue = (event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newValue?: string) => {
const items = [...properties];
items[index] = {...items[index], value: newValue};
setProperties(items);
};
return (
<TextField
ariaLabel={t(ResourceKeys.deviceEvents.simulation.advanced.properties.value)}
value={item.value}
onChange={handleEditPropertyValue}
/>);
};
const getColumns = (): IColumn[] => {
return [
{
isResizable: true,
key: 'key',
maxWidth: MEDIUM_COLUMN_WIDTH,
minWidth: 150,
name: t(ResourceKeys.deviceEvents.simulation.advanced.properties.key),
},
{
isResizable: true,
key: 'value',
minWidth: 150,
name: t(ResourceKeys.deviceEvents.simulation.advanced.properties.value),
}
];
};
const onSelectionChanged = () => {
setSelectedIndices(new Set(selection.getSelectedIndices()));
};
const handleAddProperty = () => {
const newIndex = propertyIndex + 1;
const newProperties = [...properties, {index: newIndex, keyName: '', value: ''}];
setProperties(newProperties);
setPropertyIndex(newIndex);
};
const handleDelete = () => {
const updatedProperties = [];
for (let i = 0; i < properties.length; i++) {
if (!selectedIndices.has(i)) {
updatedProperties.push(properties[i]);
}
}
setProperties(updatedProperties);
};
const onTextFieldChange = (ev: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>, newText: string) => {
setSimulationBody(newText);
};
const convertToCliPropertyFormat = () => {
let returnValue = '';
properties.forEach(item => { if (item.keyName && item.value) {returnValue += `${item.keyName}=${item.value}`; }});
return returnValue;
};
const convertToCliCommand = () => {
let returnValue = `az iot device simulate --device-id ${deviceId} --login \"${hubConnectionString}\"`;
if (simulationBody) {
returnValue += ` --data \"${simulationBody}\"`;
}
const commandProperties = convertToCliPropertyFormat();
if (commandProperties !== '') {
returnValue += ` --properties \"${commandProperties}\"`;
}
return returnValue;
};
const onclick = () => {
window.open('https://shell.azure.com');
};
return renderSimulationPanel();
};

Просмотреть файл

@ -20,5 +20,8 @@ exports[`collapsibleSection matches snapshot 1`] = `
>
Label
</Component>
<div
className="collapsible-section-children"
/>
</div>
`;

Просмотреть файл

@ -7,20 +7,12 @@ import { useTranslation } from 'react-i18next';
import { Route, NavLink, useLocation, useRouteMatch } from 'react-router-dom';
import { ResourceKeys } from '../../../localization/resourceKeys';
import { ROUTE_PARTS, ROUTE_PARAMS } from '../../constants/routes';
import { CONNECTION_STRING_NAME_LIST } from '../../constants/browserStorage';
import { getConnectionInfoFromConnectionString } from '../../api/shared/utils';
import { getDeviceIdFromQueryString, getComponentNameFromQueryString } from '../utils/queryStringHelper';
import { getHubInformationFromLocalStorage } from '../hooks/localStorageInformationRetriever';
import '../../css/_breadcrumb.scss';
export const Breadcrumb: React.FC = () => {
const [ hostName, setHostName ] = React.useState<string>('');
const connectionStrings = localStorage.getItem(CONNECTION_STRING_NAME_LIST);
const connectionString = connectionStrings && connectionStrings.split(',')[0];
React.useEffect(() => {
const host = getConnectionInfoFromConnectionString(connectionString).hostName;
setHostName(host);
}, [connectionString]);
const [ , hostName] = getHubInformationFromLocalStorage();
const renderBreadcrumbItem = () => <BreadcrumbItem hostName={hostName}/>;

Просмотреть файл

@ -43,7 +43,9 @@ export const CollapsibleSection: React.FC<CollapsibleSectionProps> = (props: Col
<LabelWithTooltip tooltipText={tooltipText}>
{label}
</LabelWithTooltip>
{expanded && children}
<div className="collapsible-section-children">
{expanded && children}
</div>
</div>
);
};

Просмотреть файл

@ -0,0 +1,15 @@
import * as React from 'react';
import { CONNECTION_STRING_NAME_LIST } from '../../constants/browserStorage';
import { getConnectionInfoFromConnectionString } from '../../api/shared/utils';
export const getHubInformationFromLocalStorage = () => {
const [ hubConnectionString, setHubConnectionString ] = React.useState<string>('');
const [ hostName, setHostName ] = React.useState<string>('');
const connectionStrings = localStorage.getItem(CONNECTION_STRING_NAME_LIST);
const connectionString = connectionStrings && connectionStrings.split(',')[0];
React.useEffect(() => {
setHubConnectionString(connectionString);
setHostName(getConnectionInfoFromConnectionString(connectionString).hostName);
}, [connectionString]);
return [hubConnectionString, hostName];
};

Просмотреть файл

@ -567,7 +567,8 @@
"start": "Start",
"stop": "Stop",
"fetch": "Retrieve events",
"close": "Back"
"close": "Back",
"simulate": "Simulation device"
},
"consumerGroups": {
"label": "Consumer group",
@ -625,7 +626,45 @@
"on": "Yes",
"off": "No"
},
"error": "Failed to monitor events. {{error}}"
"error": "Failed to monitor events. {{error}}",
"simulation": {
"header": "Simulate a device using CLI in Azure Cloud Shell",
"cloudShell": {
"imageDescription": "Launch Azure Cloud Shell",
"textDescription": "Click Launch Cloud Shell, paste the commands below to simulate a device sending telemetry. You will be able to start monitoring events from the built-in event hub."
},
"prerequisite": {
"label": "Prerequisite",
"tooltiop": "If you have not used Azure Cloud Shell before",
"instruction": "Follow the instructions in Azure Cloud Shell to pick a subscription and set up a storage if you haven't used Cloud Shell before. If you have encountered error 'az iot: 'device' is not in the 'az iot' command group', use command 'az extension add --name azure-iot' to install Azure IoT CLI extension."
},
"basic": {
"label": "Basic scenario",
"tooltiop": "Zero input basic scenario",
"instruction": "Simply copy paste the following command to Azure Cloud Shell. It will start simulating device {{deviceId}} as it's sending messages to IoT Hub. You can click 'Start' button from the Telemetry page to start monitoring the events.",
"copyLabel": "Simulating device {{deviceId}}"
},
"advanced": {
"label": "Advance scenario",
"tooltiop": "For advanced users to simulate events' body or properties",
"instruction": "Enter the desired message body and/or properties, and copy paste the generated command to Azure Cloud Shell. It will start simulating device {{deviceId}} as it's sending messages to IoT Hub. You can click 'Start' button from the Telemetry page to start monitoring the events.",
"copyLabel": "Simulating device {{deviceId}}",
"body": {
"label": "Provide event body",
"tooltip": "If left empty, the default value would be: 'Ping from Az CLI IoT Extension'."
},
"properties": {
"key": "Key",
"keyDup": "Key must be unique",
"value": "Value",
"addProperty": "Add a property",
"delete": "Delete",
"toggleSelectionColumnAriaLabel": "Toggle selection",
"selectAllCheckboxAriaLabel": "Toggle selection for all",
"rowCheckBoxAriaLabel": "Row checkbox"
}
}
}
},
"directMethod": {
"headerText": "Direct method",

Просмотреть файл

@ -291,6 +291,7 @@ export class ResourceKeys {
fetch : "deviceEvents.command.fetch",
refresh : "deviceEvents.command.refresh",
showSystemProperties : "deviceEvents.command.showSystemProperties",
simulate : "deviceEvents.command.simulate",
start : "deviceEvents.command.start",
stop : "deviceEvents.command.stop",
},
@ -323,6 +324,44 @@ export class ResourceKeys {
placeHolder : "deviceEvents.interfaceDropDown.placeHolder",
},
noEvent : "deviceEvents.noEvent",
simulation : {
advanced : {
body : {
label : "deviceEvents.simulation.advanced.body.label",
tooltip : "deviceEvents.simulation.advanced.body.tooltip",
},
copyLabel : "deviceEvents.simulation.advanced.copyLabel",
instruction : "deviceEvents.simulation.advanced.instruction",
label : "deviceEvents.simulation.advanced.label",
properties : {
addProperty : "deviceEvents.simulation.advanced.properties.addProperty",
delete : "deviceEvents.simulation.advanced.properties.delete",
key : "deviceEvents.simulation.advanced.properties.key",
keyDup : "deviceEvents.simulation.advanced.properties.keyDup",
rowCheckBoxAriaLabel : "deviceEvents.simulation.advanced.properties.rowCheckBoxAriaLabel",
selectAllCheckboxAriaLabel : "deviceEvents.simulation.advanced.properties.selectAllCheckboxAriaLabel",
toggleSelectionColumnAriaLabel : "deviceEvents.simulation.advanced.properties.toggleSelectionColumnAriaLabel",
value : "deviceEvents.simulation.advanced.properties.value",
},
tooltiop : "deviceEvents.simulation.advanced.tooltiop",
},
basic : {
copyLabel : "deviceEvents.simulation.basic.copyLabel",
instruction : "deviceEvents.simulation.basic.instruction",
label : "deviceEvents.simulation.basic.label",
tooltiop : "deviceEvents.simulation.basic.tooltiop",
},
cloudShell : {
imageDescription : "deviceEvents.simulation.cloudShell.imageDescription",
textDescription : "deviceEvents.simulation.cloudShell.textDescription",
},
header : "deviceEvents.simulation.header",
prerequisite : {
instruction : "deviceEvents.simulation.prerequisite.instruction",
label : "deviceEvents.simulation.prerequisite.label",
tooltiop : "deviceEvents.simulation.prerequisite.tooltiop",
},
},
toggleShowRawData : {
label : "deviceEvents.toggleShowRawData.label",
off : "deviceEvents.toggleShowRawData.off",

Просмотреть файл

@ -2,6 +2,7 @@ import * as webpack from 'webpack';
import * as path from 'path';
const HtmlWebpackPlugin = require('html-webpack-plugin'); // tslint:disable-line: no-var-requires
const MiniCssExtractPlugin = require('mini-css-extract-plugin'); // tslint:disable-line: no-var-requires
const CopyPlugin = require('copy-webpack-plugin'); // tslint:disable-line: no-var-requires
const config: webpack.Configuration = {
entry: {
@ -53,6 +54,14 @@ const config: webpack.Configuration = {
template: path.resolve(__dirname, '.', 'src', 'index.html')
}),
// new BundleAnalyzerPlugin(),
new CopyPlugin({
patterns: [
{
from: 'images',
to: 'images',
}
]
})
],
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.