diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 1a07df6ca..ea5a4dc2f 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -10,4 +10,4 @@ 5. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. -This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) +This Code of Conduct is adapted from the [Contributor Covenant](https://contributor-covenant.org), version 1.0.0, available at [https://contributor-covenant.org/version/1/0/0/](https://contributor-covenant.org/version/1/0/0/) diff --git a/.github/CODE_OF_CONDUCT_CN.md b/.github/CODE_OF_CONDUCT_CN.md index b359dd81d..661fa833d 100644 --- a/.github/CODE_OF_CONDUCT_CN.md +++ b/.github/CODE_OF_CONDUCT_CN.md @@ -10,4 +10,4 @@ 5. 可以通过提出问题或联系一个或多个项目维护者来举报虐待,骚扰或其他不可接受的行为。 -本行为准则改编自 [Contributor Covenant](http://contributor-covenant.org)版本1.0.0,可在 [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/ )查看 +本行为准则改编自 [Contributor Covenant](https://contributor-covenant.org)版本1.0.0,可在 [https://contributor-covenant.org/version/1/0/0/](https://contributor-covenant.org/version/1/0/0/ )查看 diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index adc0cdf17..b1abb3c44 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -84,11 +84,11 @@ If you find a problem 🐛 or have a better idea 💡 during use, you can modify - ci: Continuous integration - build || chore: Changes in build tools or dependent packages -6. Submit a pull request to the `master` branch of the upstream repository, and we will review it. +6. Submit a pull request to the `main` branch of the upstream repository, and we will review it. - > Note: The master branch is an unstable code branch, new code will be merged to master, if you need to use stable code, you can switch to the tag. git checkout ${tag_name} + > Note: The main branch is an unstable code branch, new code will be merged to main, if you need to use stable code, you can switch to the tag. git checkout ${tag_name} -7. Release process, pull down the latest master branch `maste`, using this command line to generate a new commit, and finally use rebase merge to merge into The master branch `master`. +7. Release process, pull down the latest `main` branch, using this command line to generate a new commit, and finally use rebase merge to merge into the `main` branch. ```shell npm version [patch | minor | major] -m '${Commit message}' diff --git a/.github/CONTRIBUTING_CN.md b/.github/CONTRIBUTING_CN.md index 966f45e71..cca2915d0 100644 --- a/.github/CONTRIBUTING_CN.md +++ b/.github/CONTRIBUTING_CN.md @@ -83,11 +83,11 @@ - ci: 持续集成 - build || chore: 构建过程或辅助工具的变动 -6. 最后,向上游仓库中的 `master` 分支发起一个 pull request 请求,我们将对其进行认真的审查。 +6. 最后,向上游仓库中的 `main` 分支发起一个 pull request 请求,我们将对其进行认真的审查。 - > 注意:master 分支为不稳定代码分支,新的代码都会 merge 到 master,如果您需要使用稳定代码,可以切换到对应的 tag。git checkout ${tag_name} + > 注意:main 分支为不稳定代码分支,新的代码都会 merge 到 main,如果您需要使用稳定代码,可以切换到对应的 tag。git checkout ${tag_name} -7. 发版流程,拉下最新的主分支 `maste`,使用下面这个命令行产生一个新的 commit,最后使用 rebase merge 合并到主分支 `master`。 +7. 发版流程,拉下最新的主分支 `main`,使用下面这个命令行产生一个新的 commit,最后使用 rebase merge 合并到主分支 `main`。 ```shell npm version [patch | minor | major] -m '${Commit message}' diff --git a/.github/workflows/build_cli.yaml b/.github/workflows/build_cli.yaml index d15036541..df6a8c228 100644 --- a/.github/workflows/build_cli.yaml +++ b/.github/workflows/build_cli.yaml @@ -10,12 +10,12 @@ on: jobs: build_packages: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v1 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 with: - node-version: 16.14 + node-version-file: '.nvmrc' - name: build run: | @@ -44,9 +44,9 @@ jobs: publish_docker: if: github.event_name == 'release' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: docker meta id: meta uses: docker/metadata-action@v4 @@ -79,12 +79,12 @@ jobs: publish_npm: if: github.event_name == 'release' && !github.event.release.prerelease - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: 16.14 + node-version-file: '.nvmrc' registry-url: 'https://registry.npmjs.org' - name: build run: | diff --git a/.github/workflows/build_packages.yaml b/.github/workflows/build_packages.yaml index 7f76c3de9..313ab8c47 100644 --- a/.github/workflows/build_packages.yaml +++ b/.github/workflows/build_packages.yaml @@ -8,9 +8,9 @@ on: jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: build run: | docker run --rm -i \ diff --git a/.github/workflows/deploy_web.yaml b/.github/workflows/deploy_web.yaml index c5bc10cef..e158514a7 100644 --- a/.github/workflows/deploy_web.yaml +++ b/.github/workflows/deploy_web.yaml @@ -11,14 +11,14 @@ on: jobs: deploy_website: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: + - uses: actions/checkout@v4 - name: use node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: 16.14 + node-version-file: '.nvmrc' - - uses: actions/checkout@v3 - name: set env run: | cd web @@ -41,14 +41,14 @@ jobs: publish_docker: if: github.event_name != 'pull_request' - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 steps: + - uses: actions/checkout@v4 - name: use node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: 16.14 + node-version-file: '.nvmrc' - - uses: actions/checkout@v3 - name: set env run: | cd web diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..b6a7d89c6 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16 diff --git a/README-CN.md b/README-CN.md index 760e3a010..8f3aad73c 100644 --- a/README-CN.md +++ b/README-CN.md @@ -15,7 +15,7 @@ [MQTTX](https://mqttx.app/zh) 的用户界面借助聊天软件的形式简化了页面的操作逻辑,用户可以快速创建连接保存并同时建立多个连接客户端,方便用户快速测试 MQTT/TCP、MQTT/TLS、MQTT/WebSocket 的 **连接/发布/订阅** 功能及其他特性。 -> [MQTT](http://mqtt.org/faq) 全称为 Message Queuing Telemetry Transport(消息队列遥测传输)是一种基于 发布/订阅 范式的“轻量级”消息协议,旨在用于受限设备和低带宽,高延迟或不可靠的网络,由 IBM 发布。 +> [MQTT](https://mqtt.org/faq) 全称为 Message Queuing Telemetry Transport(消息队列遥测传输)是一种基于 发布/订阅 范式的“轻量级”消息协议,旨在用于受限设备和低带宽,高延迟或不可靠的网络,由 IBM 发布。 ## 功能预览 diff --git a/README-JP.md b/README-JP.md index 82adc1774..b4b1a76bc 100644 --- a/README-JP.md +++ b/README-JP.md @@ -15,7 +15,7 @@ [MQTTX](https://mqttx.app/)のユーザーインターフェイスは、チャットソフトウェアのような形でページの操作ロジックを簡素化します。ユーザーは、接続をすばやく作成し、複数の接続クライアントを同時に保存および確立できます。 MQTT/TCP、MQTT/TLS、MQTT/WebSocketの**接続/パブリッシュ/サブスクライブ**機能およびその他の機能をすばやくテストすることが便利です。 -> [MQTT](http://mqtt.org/faq)(Message Queuing Telemetry Transport)は、パブリッシュ/サブスクライブパラダイムに基づいた「軽量」メッセージングプロトコルです。制約のあるデバイスや、低帯域幅、高遅延、または信頼性の低いネットワークで使用するように設計されています。IBMによって公開されています。 +> [MQTT](https://mqtt.org/faq)(Message Queuing Telemetry Transport)は、パブリッシュ/サブスクライブパラダイムに基づいた「軽量」メッセージングプロトコルです。制約のあるデバイスや、低帯域幅、高遅延、または信頼性の低いネットワークで使用するように設計されています。IBMによって公開されています。 ## 機能プレビュー diff --git a/README.md b/README.md index d186a0658..b46f46030 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,18 @@ MQTTX Logo [![GitHub Release](https://img.shields.io/github/release/emqx/mqttx?color=brightgreen)](https://github.com/emqx/mqttx/releases) -![Support Platforms](https://camo.githubusercontent.com/a50c47295f350646d08f2e1ccd797ceca3840e52/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f706c6174666f726d2d6d61634f5325323025374325323057696e646f77732532302537432532304c696e75782d6c69676874677265792e737667) +![platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey) ![build packages](https://github.com/emqx/MQTTX/workflows/build%20packages/badge.svg) -[![Total Downloads](https://img.shields.io/github/downloads/emqx/mqttx/total.svg)](https://github.com/emqx/mqttx/releases) -[![Slack](https://img.shields.io/badge/Slack-EMQX-39AE85?logo=slack)](https://slack-invite.emqx.io/) [![Discord](https://img.shields.io/discord/931086341838622751?label=Discord&logo=discord)](https://discord.gg/xYGf3fQnES) + +[![GitHub Downloads](https://img.shields.io/github/downloads/emqx/MQTTX/total?label=GitHub%20Downloads)](https://mqttx.app/downloads) +[![Docker Web Pulls](https://img.shields.io/docker/pulls/emqx/mqttx-web?label=Docker%20Web%20Pulls)](https://hub.docker.com/r/emqx/mqttx-web) +[![Docker CLI Pulls](https://img.shields.io/docker/pulls/emqx/mqttx-cli?label=Docker%20CLI%20Pulls)](https://hub.docker.com/r/emqx/mqttx-cli) + [![Community](https://img.shields.io/badge/Community-MQTTX-yellow?logo=github)](https://github.com/emqx/MQTTX/discussions) +[![Slack](https://img.shields.io/badge/Slack-EMQX-39AE85?logo=slack)](https://slack-invite.emqx.io/) +[![Discord](https://img.shields.io/discord/931086341838622751?label=Discord&logo=discord)](https://discord.gg/xYGf3fQnES) [![YouTube](https://img.shields.io/badge/Subscribe-EMQ-FF0000?logo=youtube)](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q) -[![Twitter](https://img.shields.io/badge/Follow-EMQ-1DA1F2?logo=twitter)](https://twitter.com/EMQTech) +[![Twitter Follows](https://img.shields.io/twitter/follow/EMQTech?label=Twitter%20Follows)](https://twitter.com/EMQTech) --- @@ -15,7 +20,7 @@ [MQTTX](https://mqttx.app) simplifies test operation with the help of a familiar, chat-like interface. It’s easy and quick to create multiple, simultaneous online MQTT client connections, and can test the connection, publishing, and subscription functions of MQTT/TCP, MQTT/TLS, MQTT/WebSocket as well as other MQTT protocol features. -> [MQTT](http://mqtt.org/faq) stands for MQ Telemetry Transport. It is a publish/subscribe, extremely simple and lightweight messaging protocol, designed for constrained devices and low-bandwidth, high-latency or unreliable networks. +> [MQTT](https://mqtt.org/faq) stands for MQ Telemetry Transport. It is a publish/subscribe, extremely simple and lightweight messaging protocol, designed for constrained devices and low-bandwidth, high-latency or unreliable networks. ## Preview @@ -138,7 +143,7 @@ Please make sure to read the [Contributing Guide](https://github.com/emqx/MQTTX/ ## Resources -- [MQTT client programming](https://www.emqx.com/en/blog/tag/mqtt-client-programming) +- [MQTT Programming](https://www.emqx.com/en/blog/category/mqtt-programming) A series of blogs to help developers get started quickly with MQTT in PHP, Node.js, Python, Golang, and other programming languages. diff --git a/assets/mqttx-gif.gif b/assets/mqttx-gif.gif index 7f60370be..0f3b79a0e 100644 Binary files a/assets/mqttx-gif.gif and b/assets/mqttx-gif.gif differ diff --git a/assets/mqttx-preview.png b/assets/mqttx-preview.png index 4eb035c78..bbbe6b74a 100644 Binary files a/assets/mqttx-preview.png and b/assets/mqttx-preview.png differ diff --git a/assets/mqttx-web-preview.png b/assets/mqttx-web-preview.png index 344aff3a8..03d7601bc 100644 Binary files a/assets/mqttx-web-preview.png and b/assets/mqttx-web-preview.png differ diff --git a/babel.config.js b/babel.config.js index b31b3358c..1d3320337 100644 --- a/babel.config.js +++ b/babel.config.js @@ -9,7 +9,28 @@ const plugins = [ [ 'prismjs', { - languages: ['javascript'], + languages: [ + 'javascript', + 'json', + 'python', + 'java', + 'bash', + 'sql', + 'c', + 'csharp', + 'cpp', + 'go', + 'kotlin', + 'php', + 'ruby', + 'rust', + 'scala', + 'swift', + 'typescript', + 'yaml', + 'erlang', + 'dart', + ], // plugins: ['line-numbers'], // theme: 'funky', css: true, diff --git a/cli/README-CN.md b/cli/README-CN.md index dbdee7a01..5ef4d6e31 100644 --- a/cli/README-CN.md +++ b/cli/README-CN.md @@ -18,7 +18,7 @@ [MQTTX CLI](https://mqttx.app/zh/cli) 是一款开源的 MQTT 5.0 命令行客户端工具,也是命令行上的 MQTTX,旨在帮助开发者在不需要使用图形化界面的基础上,也能更快的开发和调试 MQTT 服务与应用。 -> [MQTT](http://mqtt.org/faq) 全称为 Message Queuing Telemetry Transport(消息队列遥测传输)是一种基于 发布/订阅 范式的“轻量级”消息协议,旨在用于受限设备和低带宽,高延迟或不可靠的网络,由 IBM 发布。 +> [MQTT](https://mqtt.org/faq) 全称为 Message Queuing Telemetry Transport(消息队列遥测传输)是一种基于 发布/订阅 范式的“轻量级”消息协议,旨在用于受限设备和低带宽,高延迟或不可靠的网络,由 IBM 发布。 ## 功能预览 diff --git a/cli/README.md b/cli/README.md index df7ab6c95..b955fc891 100644 --- a/cli/README.md +++ b/cli/README.md @@ -3,19 +3,24 @@ # MQTTX CLI [![GitHub Release](https://img.shields.io/github/release/emqx/mqttx?color=brightgreen)](https://github.com/emqx/mqttx/releases) +![platforms](https://img.shields.io/badge/platforms-Windows%20%7C%20Linux%20%7C%20macOS-lightgrey) ![build packages](https://github.com/emqx/MQTTX/workflows/build%20packages/badge.svg) -[![Total Downloads](https://img.shields.io/github/downloads/emqx/mqttx/total.svg)](https://github.com/emqx/mqttx/releases) -[![Docker](https://img.shields.io/docker/pulls/emqx/mqttx-cli)](https://hub.docker.com/r/emqx/mqttx-cli) -[![Slack](https://img.shields.io/badge/Slack-EMQX-39AE85?logo=slack)](https://slack-invite.emqx.io/) [![Discord](https://img.shields.io/discord/931086341838622751?label=Discord&logo=discord)](https://discord.gg/xYGf3fQnES) + +[![GitHub Downloads](https://img.shields.io/github/downloads/emqx/MQTTX/total?label=GitHub%20Downloads)](https://mqttx.app/downloads) +[![Docker Web Pulls](https://img.shields.io/docker/pulls/emqx/mqttx-web?label=Docker%20Web%20Pulls)](https://hub.docker.com/r/emqx/mqttx-web) +[![Docker CLI Pulls](https://img.shields.io/docker/pulls/emqx/mqttx-cli?label=Docker%20CLI%20Pulls)](https://hub.docker.com/r/emqx/mqttx-cli) + [![Community](https://img.shields.io/badge/Community-MQTTX-yellow?logo=github)](https://github.com/emqx/MQTTX/discussions) +[![Slack](https://img.shields.io/badge/Slack-EMQX-39AE85?logo=slack)](https://slack-invite.emqx.io/) +[![Discord](https://img.shields.io/discord/931086341838622751?label=Discord&logo=discord)](https://discord.gg/xYGf3fQnES) [![YouTube](https://img.shields.io/badge/Subscribe-EMQ-FF0000?logo=youtube)](https://www.youtube.com/channel/UC5FjR77ErAxvZENEWzQaO5Q) -[![Twitter](https://img.shields.io/badge/Follow-EMQ-1DA1F2?logo=twitter)](https://twitter.com/EMQTech) +[![Twitter Follows](https://img.shields.io/twitter/follow/EMQTech?label=Twitter%20Follows)](https://twitter.com/EMQTech) --- [MQTTX CLI](https://mqttx.app/cli) is an open source MQTT 5.0 CLI Client and MQTTX on the command line. Designed to help develop and debug MQTT services and applications faster without the need to use a graphical interface. -> [MQTT](http://mqtt.org/faq) stands for MQ Telemetry Transport. It is a publish/subscribe, extremely simple and lightweight messaging protocol, designed for constrained devices and low-bandwidth, high-latency or unreliable networks. +> [MQTT](https://mqtt.org/faq) stands for MQ Telemetry Transport. It is a publish/subscribe, extremely simple and lightweight messaging protocol, designed for constrained devices and low-bandwidth, high-latency or unreliable networks. ## Preview @@ -23,442 +28,35 @@ ## Documentation -For introduction, installation, and usage, see the [MQTTX CLI documentation](https://mqttx.app/docs/cli). Below is a quick start guide. +Explore the full range of features and learn how to get the most out of MQTTX CLI with our comprehensive [MQTTX CLI Documentation](https://mqttx.app/docs/cli). ## Installation -### macOS - -To install the latest MQTTX CLI stable release on **macOS** using **binary download**. - -> **Note**: Please note CPU architecture of the current system environment - -#### Intel Chip - -```shell -curl -LO https://www.emqx.com/en/downloads/MQTTX/v1.9.5/mqttx-cli-macos-x64 -sudo install ./mqttx-cli-macos-x64 /usr/local/bin/mqttx -``` - -#### Apple Silicon - -```shell -curl -LO https://www.emqx.com/en/downloads/MQTTX/v1.9.5/mqttx-cli-macos-arm64 -sudo install ./mqttx-cli-macos-arm64 /usr/local/bin/mqttx -``` - -#### Homebrew - -```shell -brew install emqx/mqttx/mqttx-cli -``` - -### Linux - -To install the latest MQTTX CLI stable release on **Linux** using **binary download**. - -> **Note**: Please note CPU architecture of the current system environment - -#### x86-64 - -```shell -curl -LO https://www.emqx.com/en/downloads/MQTTX/v1.9.5/mqttx-cli-linux-x64 -sudo install ./mqttx-cli-linux-x64 /usr/local/bin/mqttx -``` - -#### ARM64 - -```shell -curl -LO https://www.emqx.com/en/downloads/MQTTX/v1.9.5/mqttx-cli-linux-arm64 -sudo install ./mqttx-cli-linux-arm64 /usr/local/bin/mqttx -``` - -### Windows - -Windows users should go to the MQTTX [release page](https://github.com/emqx/MQTTX/releases) and find the `exe` package for the corresponding system architecture, download it manually and execute. - -### NPM - -```shell -npm install mqttx-cli -g -``` - -### Docker - -```shell -docker pull emqx/mqttx-cli - -docker run -it --rm emqx/mqttx-cli -``` - -### Other platforms - -Download packaged binaries from the [MQTTX releases page](https://github.com/emqx/MQTTX/releases). - -## Usage - -After installing it, run `mqttx` on the terminal - -### Quickstart - -Connect - -```shell -mqttx conn -h 'broker.emqx.io' -p 1883 -u 'admin' -P 'public' -``` - -Subscribe - -```shell -mqttx sub -t 'hello' -h 'broker.emqx.io' -p 1883 -``` - -Publish - -```shell -# Publish a single message -mqttx pub -t 'hello' -h 'broker.emqx.io' -p 1883 -m 'from MQTTX CLI' - -# Publish multiple messages (multiline) -mqttx pub -t 'hello' -h 'broker.emqx.io' -p 1883 -s -M -``` - -Benchmark - -```bash -# Connect Benchmark -mqttx bench conn -c 5000 +Get started by downloading MQTTX CLI from the [MQTTX Downloads Page](https://mqttx.app/downloads). Installation instructions are provided for different platforms to ensure a smooth setup. -# Subscribe Benchmark -mqttx bench sub -c 5000 -t bench/%i +## Quickstart -# Publish Benchmark -mqttx bench pub -c 5000 -t bench/%i -``` - -Simulate - -```bash -# Specify a built-in local scenario and start the simulation -mqttx simulate -sc tesla -c 10 - -# Specify a scenario file and start the simulation -mqttx simulate -f -c 10 - -# List the built-in scenarios -mqttx ls -sc -``` - -### Help - -```shell -mqttx --help -``` - -| Options | Description | -| ------------- | ------------------------- | -| -v, --version | Output the version number | -| -h, --help | Display help for command | - -| Command | Description | -| ------- | ---------------------------------------------- | -| check | Check for updates | -| conn | Create a connection and connect to MQTT Broker | -| pub | Publish a message to a topic | -| sub | Subscribes to one or multiple topics | -| bench | MQTT Benchmark in performance testing | -| simulate | Simulate publishing scenario-specific MQTT messages | - -### Connect - -```shell -mqttx conn --help -``` - -| Options | Description | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | -| -V, --mqtt-version <5/3.1.1/3.1> | the MQTT version (default: 5) | -| -h, --hostname | the broker host (default: "localhost") | -| -p, --port | the broker port | -| -i, --client-id | the client id | -| --no-clean | set the clean session flag to false (default: true) | -| -k, --keepalive | send a ping every SEC seconds (default: 30) | -| -u, --username | the username | -| -P, --password | the password | -| -l, --protocol | the protocol to use, mqtt, mqtts, ws, or wss (default: mqtt) | -| --path | the path of websocket (default: /mqtt) | -| --key | path to the key file | -| --cert | path to the cert file | -| --ca | path to the ca certificate | -| --insecure | do not verify the server certificate | -| --alpn | set one or multiple ALPN (Application Layer Protocol Negotiation) protocols | -| -rp, --reconnect-period | interval between two reconnections, disable auto reconnect by setting to 0 (default: 1000ms) | -| --maximum-reconnect-times | the maximum reconnect times (default: 10) | -| -up, --user-properties | the user properties of MQTT 5.0 (e.g. -up "name: mqttx cli") | -| -Wt, --will-topic | the will topic | -| -Wm, --will-message | the will message | -| -Wq, --will-qos <0/1/2> | the will qos | -| -Wr, --will-retain | send a will retained message (default: false) | -| -Wd, --will-delay-interval | the will delay interval in seconds | -| -Wpf, --will-payload-format-indicator | will message is UTF-8 encoded character data or not | -| -We, --will-message-expiry-interval | lifetime of the will message in seconds | -| -Wct, --will-content-type | description of the will message’s content | -| -Wrt, --will-response-topic | topic name for a response message | -| -Wcd, --will-correlation-data | correlation data for the response message | -| -Wup, --will-user-properties | the user properties of will message | -| -se, --session-expiry-interval | the session expiry interval in seconds | -| --rcv-max, --receive-maximum | the receive maximum value | -| --maximum-packet-size | the maximum packet size the client is willing to accept | -| --topic-alias-maximum | the topic alias maximum value | -| --req-response-info | the client requests response information from the server | -| --no-req-problem-info | the client requests problem information from the server | -| --save \[PATH\] | save the parameters to the local configuration file, which supports json and yaml format, default path is `./mqttx-cli-config.json` | -| --config \[PATH\] | load the parameters from the local configuration file, which supports json and yaml format, default path is `./mqttx-cli-config.json` | -| --help | display help for conn command | - -### Subscribe - -```shell -mqttx sub --help -``` - -| Options | Description | -| ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -| -V, --mqtt-version <5/3.1.1/3.1> | the MQTT version (default: 5) | -| -h, --hostname | the broker host (default: "localhost") | -| -p, --port | the broker port | -| -i, --client-id | the client id | -| -q, --qos <0/1/2> | the QoS of the message (default: 0) | -| --no-clean | set the clean session flag to false (default: true) | -| -t, --topic | the message topic | -| -k, --keepalive | send a ping every SEC seconds (default: 30) | -| -u, --username | the username | -| -P, --password | the password | -| -l, --protocol | the protocol to use, mqtt, mqtts, ws, or wss (default: mqtt) | -| --path | the path of websocket (default: /mqtt) | -| -nl, --no_local | the no local MQTT 5.0 flag | -| -rap, --retain-as-published | the retain as published MQTT 5.0 flag | -| -rh, --retain-handling <0/1/2> | the retain handling MQTT 5.0 | -| --key | path to the key file | -| --cert | path to the cert file | -| --ca | path to the ca certificate | -| --insecure | do not verify the server certificate | -| --alpn | set one or multiple ALPN (Application Layer Protocol Negotiation) protocols | -| -rp, --reconnect-period | interval between two reconnections, disable auto reconnect by setting to 0 (default: 1000ms) | -| --maximum-reconnect-times | the maximum reconnect times (default: 10) | -| -up, --user-properties | the user properties of MQTT 5.0 (e.g. -up "name: mqttx cli") | -| -f, --format | format the message body, support base64, json, hex | -| -v, --verbose | print the topic before the message | -| --output-mode | choose between the default and clean mode, which outputs the complete MQTT packet data, allowing users to pipe the output as they wish | -| -Wt, --will-topic | the will topic | -| -Wm, --will-message | the will message | -| -Wq, --will-qos <0/1/2> | the will qos | -| -Wr, --will-retain | send a will retained message (default: false) | -| -Wd, --will-delay-interval | the will delay interval in seconds | -| -Wpf, --will-payload-format-indicator | will message is UTF-8 encoded character data or not | -| -We, --will-message-expiry-interval | lifetime of the will message in seconds | -| -Wct, --will-content-type | description of the will message’s content | -| -Wrt, --will-response-topic | topic name for a response message | -| -Wcd, --will-correlation-data | correlation data for the response message | -| -Wup, --will-user-properties | the user properties of will message | -| -se, --session-expiry-interval | the session expiry interval in seconds | -| -si, --subscription-identifier | the identifier of the subscription | -| --rcv-max, --receive-maximum | the receive maximum value | -| --maximum-packet-size | the maximum packet size the client is willing to accept | -| --topic-alias-maximum | the topic alias maximum value | -| --req-response-info | the client requests response information from the server | -| --no-req-problem-info | the client requests problem information from the server | -| -Cup, --conn-user-properties | the connect user properties of MQTT 5.0 (e.g. -Cup "name: mqttx cli") | -| --save \[PATH\] | save the parameters to the local configuration file, which supports json and yaml format, default path is `./mqttx-cli-config.json` | -| --config \[PATH\] | load the parameters from the local configuration file, which supports json and yaml format, default path is `./mqttx-cli-config.json` | -| --help | display help for sub command | -| -Pp, --protobuf-path | the path to the .proto file that defines the message format for Protocol Buffers (protobuf) | -| -Pmn, --protobuf-message-name | the name of the protobuf message type (must exist in the .proto file) | - -### Publish - -```shell -mqttx pub --help -``` - -| Options | Description | -| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | -| -V, --mqtt-version <5/3.1.1/3.1> | the MQTT version (default: 5) | -| -h, --hostname | the broker host (default: "localhost") | -| -p, --port | the broker port | -| -i, --client-id | the client id | -| -q, --qos <0/1/2> | the QoS of the message (default: 0) | -| --no-clean | set the clean session flag to false (default: true) | -| -t, --topic | the message topic | -| -m, --message | the message body (default: "Hello From MQTTX CLI") | -| -r, --retain | send a retained message (default: false) | -| -s, --stdin | read the message body from stdin | -| -M, --multiline | read lines from stdin as multiple messages | -| -u, --username | the username | -| -P, --password | the password | -| -f, --format | the format type of the input message, support base64, json, hex | -| -l, --protocol | the protocol to use, mqtt, mqtts, ws, or wss (default: mqtt) | -| --path | the path of websocket (default: /mqtt) | -| --key | path to the key file | -| --cert | path to the cert file | -| --ca | path to the ca certificate | -| --insecure | do not verify the server certificate | -| --alpn | set one or multiple ALPN (Application Layer Protocol Negotiation) protocols | -| -rp, --reconnect-period | interval between two reconnections, disable auto reconnect by setting to 0 (default: 1000ms) | -| --maximum-reconnect-times | the maximum reconnect times (default: 10) | -| -up, --user-properties | the user properties of MQTT 5.0 (e.g. -up "name: mqttx cli") | -| -pf, --payload-format-indicator | the payload format indicator of the publish message | -| -e, --message-expiry-interval | the lifetime of the publish message in seconds | -| -ta, --topic-alias | value that is used to identify the topic instead of using the topic name | -| -rt, --response-topic | string which is used as the topic name for a response message | -| -cd, --correlation-data | used by the sender of the request message to identify which request the response message is for when it is received | -| -si, --subscription-identifier | the identifier of the subscription | -| -ct, --content-type | a description of the content of the publish message | -| -Wt, --will-topic | the will topic | -| -Wm, --will-message | the will message | -| -Wq, --will-qos <0/1/2> | the will qos | -| -Wr, --will-retain | send a will retained message (default: false) | -| -Wd, --will-delay-interval | the will delay interval in seconds | -| -Wpf, --will-payload-format-indicator | will message is UTF-8 encoded character data or not | -| -We, --will-message-expiry-interval | lifetime of the will message in seconds | -| -Wct, --will-content-type | description of the will message’s content | -| -Wrt, --will-response-topic | topic name for a response message | -| -Wcd, --will-correlation-data | correlation data for the response message | -| -Wup, --will-user-properties | the user properties of will message | -| -se, --session-expiry-interval | the session expiry interval in seconds | -| --rcv-max, --receive-maximum | the receive maximum value | -| --maximum-packet-size | the maximum packet size the client is willing to accept | -| --topic-alias-maximum | the topic alias maximum value | -| --req-response-info | the client requests response information from the server | -| --no-req-problem-info | the client requests problem information from the server | -| -Cup, --conn-user-properties | the connect user properties of MQTT 5.0 (e.g. -Cup "name: mqttx cli") | -| --save \[PATH\] | save the parameters to the local configuration file, which supports json and yaml format, default path is `./mqttx-cli-config.json` | -| --config \[PATH\] | load the parameters from the local configuration file, which supports json and yaml format, default path is `./mqttx-cli-config.json` | -| --help | display help for pub command | -| -Pp, --protobuf-path | the path to the .proto file that defines the message format for Protocol Buffers (protobuf) | -| -Pmn, --protobuf-message-name | the name of the protobuf message type (must exist in the .proto file) | - -### Benchmark - -The bench command is used to test the performance of the broker. It has basically the same as the normal command options, the following will only list the new or changed options. - -#### Connect Benchmark - -```shell -mqttx bench conn --help -``` - -| Options | Description | -| ----------------------------- | ---------------------------------------------------- | -| -c, --count | the number of connections (default: 1000) | -| -i, --interval | interval of connecting to the broker (default: 10ms) | -| -I, --client-id | the client id, support %i (index) variable | +Once you've installed MQTTX CLI, you can immediately begin exploring its capabilities. Start with these basic commands: -#### Subscribe Benchmark +- **Connect to a Broker** -```shell -mqttx bench sub --help -``` - -| Options | Description | -| ----------------------------- | ------------------------------------------------------------------------------ | -| -c, --count | the number of connections (default: 1000) | -| -i, --interval | interval of connecting to the broker (default: 10ms) | -| -I, --client-id | the client id, support %i (index) variable | -| -t, --topic | the message topic, support %u (username), %c (client id), %i (index) variables | -| -v, --verbose | print history received messages and rate | - -#### Publish Benchmark - -```shell -mqttx bench pub --help -``` - -| Options | Description | -| -------------------------------------- | ------------------------------------------------------------------------------ | -| -c, --count | the number of connections (default: 1000) | -| -i, --interval | interval of connecting to the broker (default: 10ms) | -| -im, --interval-message | interval of publishing message to the broker (default: 1000ms) | -| -I, --client-id | the client id, support %i (index) variable | -| -t, --topic | the message topic, support %u (username), %c (client id), %i (index) variables | -| -v, --verbose | print history received messages and rate | -| ~~-s, --stdin~~ | ~~read the message body from stdin~~ | -| ~~-M, --multiline~~ | ~~read lines from stdin as multiple messages~~ | + ```shell + mqttx conn -h 'broker.emqx.io' -p 1883 -u 'admin' -P 'public' + ``` -### Simulate - -For simulating MQTT publish message in specific scenarios. - -It has basically the same as the [Publish Benchmark](#publish-benchmark) command options, the following will only list the new or changed options. - -```shell -mqttx simulate --help -``` - -| Options | Description | -| ----------------------------- | -------------------------------- | -| -sc, --scenario | the name of the built-in scenario to simulate | -| -f, --file | file path of a local custom scenario script | -| -t, --topic | the message topic, optional, supports variables such as %u (username), %c (client id), %i (index), %sc (scenario). Default topic format is `mqttx/simulate/%sc/%c` | - -One of the `--scenario` and `--file` parameters must be specified, and if both are specified, the `--file` parameter is preferred. - -Custom IoT Data Simulation Script Example:: - -```js -/** - * MQTTX Scenario file example - * - * This script generates random temperature and humidity data. - */ -function generator (faker, options) { - return { - // If no topic is returned, use the topic in the command line parameters. - // Topic format: 'mqttx/simulate/myScenario/' + clientId, - message: JSON.stringify({ - temp: faker.number.int({ min: 20, max: 80 }), // Generate a random temperature between 20 and 80. - hum: faker.number.int({ min: 40, max: 90 }), // Generate a random humidity between 40 and 90. - }) - } -} -// Export the scenario module -module.exports = { - name: 'myScenario', // Name of the scenario - generator, // Generator function -} -``` +- **Subscribe to a Topic** -For more examples and detailed editing guides, please refer to the [scripts-example](https://github.com/emqx/MQTTX/tree/main/scripts-example/IoT-data-scenarios) in the MQTTX GitHub repository, or see how to use [faker.js](https://fakerjs.dev/) to generate various types of random data. + ```shell + mqttx sub -t 'hello' -h 'broker.emqx.io' -p 1883 + ``` -### List +- **Publish a Message** -The `list` command provides an overview of available resources. - -> Currently, it supports listing built-in scenarios. - -```shell -mqttx list --help -``` - -| Options | Description | -| ----------------------------- | -------------------------------- | -| -sc, --scenarios | list the built-in scenarios | - -#### Built-in Scenarios - -You can use the `--scenarios` option to display a list of built-in scenarios. - -```shell -mqttx list --scenarios -``` - -This command will output a table that shows the name and description of each built-in scenario. If you want to use one of them in the simulate command, simply specify the scenario name in the `--scenario` option: - -```shell -mqttx simulate --scenario -``` + ```shell + mqttx pub -t 'hello' -h 'broker.emqx.io' -p 1883 -m 'from MQTTX CLI' + ``` -More options and features will be added to the `list` command in the future. Stay tuned! +For additional information on usage and advanced features, please consult our [Getting Started Guide](https://mqttx.app/docs/cli/get-started). ## Better Together with EMQX diff --git a/cli/package.json b/cli/package.json index 85823b09a..c680ebf9b 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "mqttx-cli", - "version": "1.9.6", + "version": "1.9.9", "description": "MQTTX Command Line Tools", "keywords": [ "mqtt", @@ -17,8 +17,12 @@ "mqttx": "./bin/index.js" }, "license": "Apache-2.0", + "engines": { + "node": ">=16" + }, "dependencies": { "axios": "^0.27.2", + "cbor": "^9.0.1", "chalk": "~4.1.2", "cli-table3": "^0.6.3", "commander": "^9.3.0", @@ -26,6 +30,7 @@ "concat-stream": "^2.0.0", "core-js": "^3.26.0", "js-yaml": "^4.1.0", + "json-bigint": "^1.0.0", "mqtt": "^4.3.7", "protobufjs": "^7.2.3", "pump": "^3.0.0", @@ -36,7 +41,9 @@ "devDependencies": { "@faker-js/faker": "^8.1.0", "@types/concat-stream": "^2.0.0", + "@types/debug": "^4.1.12", "@types/js-yaml": "^4.0.5", + "@types/json-bigint": "^1.0.4", "@types/node": "^17.0.43", "@types/pump": "^1.1.1", "@types/readable-stream": "^2.3.13", diff --git a/cli/src/index.ts b/cli/src/index.ts index b400fa2c6..87c78c435 100755 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -104,6 +104,7 @@ export class Commander { '--config [PATH]', 'load the parameters from the local configuration file, which supports json and yaml format, default path is ./mqttx-cli-config.json', ) + .option('--debug', 'Enable debug mode for MQTT.js', false) .allowUnknownOption(false) .action(conn) @@ -141,7 +142,11 @@ export class Commander { .option('-V, --mqtt-version <5/3.1.1/3.1>', 'the MQTT version', parseMQTTVersion, 5) .option('-h, --hostname ', 'the broker host', 'localhost') .option('-p, --port ', 'the broker port', parseNumber) - .option('-f, --format ', 'the format type of the input message, support base64, json, hex', parseFormat) + .option( + '-f, --format ', + 'the format type of the input message, support base64, json, hex and cbor', + parseFormat, + ) .option('-i, --client-id ', 'the client id', getClientId()) .option('--no-clean', 'set the clean session flag to false', true) .option('-k, --keepalive ', 'send a ping every SEC seconds', parseNumber, 30) @@ -206,6 +211,7 @@ export class Commander { '-Pmn, --protobuf-message-name ', 'the name of the protobuf message type (must exist in the .proto file)', ) + .option('--debug', 'Enable debug mode for MQTT.js', false) .allowUnknownOption(false) .action(pub) @@ -228,7 +234,7 @@ export class Commander { 'the user properties of MQTT 5.0 (e.g. -up "name: mqttx cli")', parseUserProperties, ) - .option('-f, --format ', 'format the message body, support base64, json, hex', parseFormat) + .option('-f, --format ', 'format the message body, support base64, json, hex and cbor', parseFormat) .option('-v, --verbose', 'print the topic before the message') .option( '--output-mode ', @@ -304,6 +310,7 @@ export class Commander { '-Pmn, --protobuf-message-name ', 'the name of the protobuf message type (must exist in the .proto file)', ) + .option('--debug', 'Enable debug mode for MQTT.js', false) .allowUnknownOption(false) .action(sub) @@ -382,6 +389,12 @@ export class Commander { .option('-c, --count ', 'the number of connections', parseNumber, 1000) .option('-i, --interval ', 'interval of connecting to the broker', parseNumber, 10) .option('-im, --message-interval ', 'interval of publishing messages', parseNumber, 1000) + .option( + '-L, --limit ', + 'The maximum number of messages to publish. A value of 0 means no limit on the number of messages', + parseNumber, + 0, + ) .option( '-t, --topic ', 'the message topic, support %u (username), %c (client id), %i (index) variables', diff --git a/cli/src/lib/conn.ts b/cli/src/lib/conn.ts index d814e5c96..ab04ec9d1 100644 --- a/cli/src/lib/conn.ts +++ b/cli/src/lib/conn.ts @@ -3,14 +3,17 @@ import { Signale, signale, basicLog, benchLog } from '../utils/signale' import { parseConnectOptions } from '../utils/parse' import delay from '../utils/delay' import { saveConfig, loadConfig } from '../utils/config' +import * as Debug from 'debug' const conn = (options: ConnectOptions) => { - const { save, config } = options + const { debug, save, config } = options config && (options = loadConfig('conn', config)) save && saveConfig('conn', options) + debug && Debug.enable('mqttjs*') + const { maximumReconnectTimes } = options const connOpts = parseConnectOptions(options, 'conn') @@ -45,6 +48,10 @@ const conn = (options: ConnectOptions) => { client.on('close', () => { basicLog.close() }) + + client.on('disconnect', () => { + basicLog.disconnect() + }) } const benchConn = async (options: BenchConnectOptions) => { @@ -116,6 +123,10 @@ const benchConn = async (options: BenchConnectOptions) => { connectedCount > 0 && (connectedCount -= 1) benchLog.close(connectedCount, count, opts.clientId!) }) + + client.on('disconnect', () => { + basicLog.disconnect(opts.clientId!) + }) })(i, connOpts) await delay(interval) diff --git a/cli/src/lib/pub.ts b/cli/src/lib/pub.ts index 6b106209b..f0143fa18 100644 --- a/cli/src/lib/pub.ts +++ b/cli/src/lib/pub.ts @@ -11,12 +11,13 @@ import { saveConfig, loadConfig } from '../utils/config' import { loadSimulator } from '../utils/simulate' import { serializeProtobufToBuffer } from '../utils/protobuf' import convertPayload from '../utils/convertPayload' +import * as Debug from 'debug' const processPublishMessage = ( message: string | Buffer, - protobufPath: string | undefined, - protobufMessageName: string | undefined, - format: FormatType | undefined, + protobufPath?: string, + protobufMessageName?: string, + format?: FormatType, ): Buffer | string => { /* * Pipeline for processing outgoing messages in two potential stages: @@ -95,10 +96,13 @@ const multisend = ( const sender = new Writable({ objectMode: true, }) + let count = 0 sender._write = (line, _enc, cb) => { const { topic, opts, protobufPath, protobufMessageName, format } = pubOpts + count++ + let omitTopic = opts.properties?.topicAlias && count >= 2 const publishMessage = processPublishMessage(line.trim(), protobufPath, protobufMessageName, format) - client.publish(topic, publishMessage, opts, cb) + client.publish(omitTopic ? '' : topic, publishMessage, opts, cb) } client.on('connect', () => { @@ -137,15 +141,21 @@ const multisend = ( const { reconnectPeriod } = connOpts reconnectPeriod ? sender.cork() : process.exit(1) }) + + client.on('disconnect', () => { + basicLog.disconnect() + }) } const pub = (options: PublishOptions) => { - const { save, config } = options + const { debug, save, config } = options config && (options = loadConfig('pub', config)) save && saveConfig('pub', options) + debug && Debug.enable('mqttjs*') + checkTopicExists(options.topic, 'pub') const connOpts = parseConnectOptions(options, 'pub') @@ -184,8 +194,19 @@ const multiPub = async (commandType: CommandType, options: BenchPublishOptions | save && saveConfig('benchPub', options) } - const { count, interval, messageInterval, hostname, port, topic, clientId, message, verbose, maximumReconnectTimes } = - options + const { + count, + interval, + messageInterval, + limit, + hostname, + port, + topic, + clientId, + message, + verbose, + maximumReconnectTimes, + } = options checkTopicExists(topic, commandType) @@ -195,8 +216,12 @@ const multiPub = async (commandType: CommandType, options: BenchPublishOptions | const { username } = connOpts + let initialized = false + let connectedCount = 0 + let inFlightMessageCount = 0 + const isNewConnArray = Array(count).fill(true) const retryTimesArray = Array(count).fill(0) @@ -247,10 +272,18 @@ const multiPub = async (commandType: CommandType, options: BenchPublishOptions | if (isNewConnArray[i - 1]) { interactive.success('[%d/%d] - Connected', connectedCount, count) - setInterval(() => { - if (!client.connected) { + setInterval(async () => { + // If the number of messages sent exceeds the limit, exit the process. + if (limit > 0 && total >= limit) { + // Wait for the total number of sent messages to be printed, then exit the process. + await delay(1000) + process.exit(0) + } + // If not initialized or client is not connected or message count exceeds the limit, do not send messages. + if (!initialized || !client.connected || (limit > 0 && total + inFlightMessageCount >= limit)) { return } + inFlightMessageCount += 1 let publishTopic = topicName let publishMessage = message if (commandType === 'simulate') { @@ -262,6 +295,7 @@ const multiPub = async (commandType: CommandType, options: BenchPublishOptions | publishMessage = simulationResult.message } client.publish(publishTopic, publishMessage, pubOpts.opts, (err) => { + inFlightMessageCount -= 1 if (err) { signale.warn(err) } else { @@ -272,13 +306,12 @@ const multiPub = async (commandType: CommandType, options: BenchPublishOptions | }, messageInterval) if (connectedCount === count) { + initialized = true + const connEnd = Date.now() signale.info(`Created ${count} connections in ${(connEnd - connStart) / 1000}s`) - total = 0 - rate = 0 - if (!verbose) { setInterval(() => { simpleInteractive.info(`Published total: ${total}, message rate: ${rate}/s`) @@ -320,6 +353,10 @@ const multiPub = async (commandType: CommandType, options: BenchPublishOptions | connectedCount > 0 && (connectedCount -= 1) benchLog.close(connectedCount, count, opts.clientId!) }) + + client.on('disconnect', () => { + basicLog.disconnect(opts.clientId!) + }) })(i, connOpts) await delay(interval) diff --git a/cli/src/lib/sub.ts b/cli/src/lib/sub.ts index 8ee8250d5..cfb6977f6 100644 --- a/cli/src/lib/sub.ts +++ b/cli/src/lib/sub.ts @@ -5,12 +5,13 @@ import delay from '../utils/delay' import convertPayload from '../utils/convertPayload' import { saveConfig, loadConfig } from '../utils/config' import { deserializeBufferToProtobuf } from '../utils/protobuf' +import * as Debug from 'debug' const processReceivedMessage = ( payload: Buffer, - protobufPath: string | undefined, - protobufMessageName: string | undefined, - format: FormatType | undefined, + protobufPath?: string, + protobufMessageName?: string, + format?: FormatType, ): string => { let message: string | Buffer = payload /* @@ -36,12 +37,14 @@ const processReceivedMessage = ( } const sub = (options: SubscribeOptions) => { - const { save, config } = options + const { debug, save, config } = options config && (options = loadConfig('sub', config)) save && saveConfig('sub', options) + debug && Debug.enable('mqttjs*') + checkTopicExists(options.topic, 'sub') const connOpts = parseConnectOptions(options, 'sub') @@ -138,6 +141,10 @@ const sub = (options: SubscribeOptions) => { client.on('close', () => { !outputModeClean && basicLog.close() }) + + client.on('disconnect', () => { + !outputModeClean && basicLog.disconnect() + }) } const benchSub = async (options: BenchSubscribeOptions) => { @@ -275,6 +282,10 @@ const benchSub = async (options: BenchSubscribeOptions) => { connectedCount > 0 && (connectedCount -= 1) benchLog.close(connectedCount, count, opts.clientId!) }) + + client.on('disconnect', () => { + basicLog.disconnect(opts.clientId!) + }) })(i, connOpts) await delay(interval) diff --git a/cli/src/types/global.d.ts b/cli/src/types/global.d.ts index 4718a58dd..127dcf9cf 100644 --- a/cli/src/types/global.d.ts +++ b/cli/src/types/global.d.ts @@ -7,7 +7,7 @@ declare global { type QoS = 0 | 1 | 2 - type FormatType = 'base64' | 'json' | 'hex' + type FormatType = 'base64' | 'json' | 'hex' | 'cbor' type OutputMode = 'clean' | 'default' @@ -52,6 +52,7 @@ declare global { willUserProperties?: Record save?: boolean | string config?: boolean | string + debug?: boolean } interface PublishOptions extends ConnectOptions { @@ -92,24 +93,30 @@ declare global { protobufMessageName?: string } - interface BenchConnectOptions extends ConnectOptions { + type OmitConnectOptions = Omit + + interface BenchConnectOptions extends OmitConnectOptions { count: number interval: number } type OmitPublishOptions = Omit< PublishOptions, - 'stdin' | 'multiline' | 'protobufPath' | 'protobufMessageName' | 'format' + 'stdin' | 'multiline' | 'protobufPath' | 'protobufMessageName' | 'format' | 'debug' > interface BenchPublishOptions extends OmitPublishOptions { count: number interval: number messageInterval: number + limit: number verbose: boolean } - type OmitSubscribeOptions = Omit + type OmitSubscribeOptions = Omit< + SubscribeOptions, + 'format' | 'outputMode' | 'protobufPath' | 'protobufMessageName' | 'debug' + > interface BenchSubscribeOptions extends OmitSubscribeOptions { count: number diff --git a/cli/src/utils/convertPayload.ts b/cli/src/utils/convertPayload.ts index f1846818a..de0e49712 100644 --- a/cli/src/utils/convertPayload.ts +++ b/cli/src/utils/convertPayload.ts @@ -1,29 +1,68 @@ import chalk from 'chalk' +import { jsonParse, jsonStringify } from './jsonUtils' +import cbor from 'cbor' +import { basicLog } from './signale' -const convertJSON = (value: Buffer | string, action: 'encode' | 'decode') => { +type Action = 'encode' | 'decode' + +const handleError = (err: unknown, value: Buffer | string, action: Action) => { + basicLog.error(err as Error) + return action === 'decode' ? chalk.red(value.toString()) : process.exit(1) +} + +/** + * Converts a JSON payload to a Buffer or string based on the specified action. + * @param value - The JSON payload to convert. + * @param action - The action to perform on the payload ('decode' or 'encode'). + * @returns The converted payload. + */ +const convertJSON = (value: Buffer | string, action: Action) => { + try { + return action === 'decode' + ? jsonStringify(jsonParse(value.toString()), null, 2) + : Buffer.from(jsonStringify(jsonParse(value.toString()))) + } catch (err) { + return handleError(err, value, action) + } +} + +/** + * Converts a CBOR payload to JSON or vice versa. + * @param value - The CBOR payload to convert. + * @param action - The action to perform: 'decode' to convert CBOR to JSON, 'encode' to convert JSON to CBOR. + * @returns The converted payload. + */ +const convertCBOR = (value: Buffer | string, action: Action) => { try { - if (action === 'decode') { - return JSON.stringify(JSON.parse(value.toString()), null, 2) - } else { - return Buffer.from(JSON.stringify(JSON.parse(value.toString()))) - } + return action === 'decode' + ? jsonStringify(cbor.decodeFirstSync(value), null, 2) + : cbor.encodeOne(JSON.parse(value.toString())) } catch (err) { - return chalk.red(err) + return handleError(err, value, action) } } -const convertPayload = (payload: Buffer | string, format?: FormatType, action: 'encode' | 'decode' = 'decode') => { +/** + * Converts the payload based on the specified format and action. + * @param payload - The payload to be converted. + * @param format - The format in which the payload should be converted. (Optional) + * @param action - The action to be performed on the payload. (Default: 'decode') + * @returns The converted payload. + */ +const convertPayload = (payload: Buffer | string, format?: FormatType, action: Action = 'decode') => { const actions = { encode: { base64: () => Buffer.from(payload.toString(), 'base64'), json: () => convertJSON(payload, 'encode'), hex: () => Buffer.from(payload.toString().replace(/\s+/g, ''), 'hex'), + cbor: () => convertCBOR(payload, 'encode'), default: () => Buffer.from(payload.toString(), 'utf-8'), }, decode: { base64: () => payload.toString('base64'), json: () => convertJSON(payload, 'decode'), hex: () => payload.toString('hex').replace(/(.{4})/g, '$1 '), + cbor: () => convertCBOR(payload, 'decode'), default: () => payload.toString('utf-8'), }, } diff --git a/cli/src/utils/jsonUtils.ts b/cli/src/utils/jsonUtils.ts new file mode 100644 index 000000000..f00b0f18a --- /dev/null +++ b/cli/src/utils/jsonUtils.ts @@ -0,0 +1,29 @@ +const JSONBigNumber = require('json-bigint') +const JSONBigInt = require('json-bigint')({ useNativeBigInt: true }) + +export const jsonParse: typeof JSON.parse = (...args: any[]) => { + try { + // When JSON only contains integers, use the native bigint type. + return JSONBigInt.parse(...args) + } catch { + try { + // When JSON contains floating-point numbers, use the bignumber library. + // The numbers in the parsed JSON Object will be represented as BigNumber Objects. + return JSONBigNumber.parse(...args) + } catch { + // To verify the validity of the JSON string and throw an error if it's invalid. + return JSON.parse(args[0], args[1]) + } + } +} + +export const jsonStringify: typeof JSON.stringify = (...args: any[]) => { + try { + // When JSON only contains integers, use the native bigint type. + return JSONBigInt.stringify(...args) + } catch (_error) { + // When JSON contains floating-point numbers, use the bignumber library. + // Integers will be represented in scientific notation. + return JSONBigNumber.stringify(...args) + } +} diff --git a/cli/src/utils/parse.ts b/cli/src/utils/parse.ts index aa7cbbc48..76c30d4d9 100644 --- a/cli/src/utils/parse.ts +++ b/cli/src/utils/parse.ts @@ -98,7 +98,7 @@ const parsePubTopic = (value: string) => { } const parseFormat = (value: string) => { - if (!['base64', 'json', 'hex'].includes(value)) { + if (!['base64', 'json', 'hex', 'cbor'].includes(value)) { signale.error('Not a valid format type.') process.exit(1) } @@ -247,7 +247,7 @@ const parseConnectOptions = ( Object.entries(willProperties).filter(([_, v]) => v !== null && v !== undefined), )) } - + let optionsTempWorkAround if (mqttVersion === 3) { connectOptions.protocolId = 'MQIsdp' } else if (mqttVersion === 5) { @@ -274,9 +274,15 @@ const parseConnectOptions = ( connectOptions.properties = Object.fromEntries( Object.entries(properties).filter(([_, v]) => v !== null && v !== undefined), ) + // Map options.properties.topicAliasMaximum to options.topicAliasMaximum, as that is where MQTT.js looks for it. + // TODO: remove after bug fixed in MQTT.js v5. + optionsTempWorkAround = Object.assign( + { topicAliasMaximum: connectOptions.properties ? connectOptions.properties.topicAliasMaximum : undefined }, + connectOptions, + ) } - return connectOptions + return optionsTempWorkAround || connectOptions } const parsePublishOptions = (options: PublishOptions) => { diff --git a/cli/src/utils/signale.ts b/cli/src/utils/signale.ts index 3cb7257a4..de01d795c 100644 --- a/cli/src/utils/signale.ts +++ b/cli/src/utils/signale.ts @@ -48,6 +48,8 @@ const basicLog = { close: () => signale.error('Connection closed'), reconnecting: () => signale.await('Reconnecting...'), reconnectTimesLimit: () => signale.error('Exceed the maximum reconnect times limit, stop retry'), + disconnect: (clientId?: string) => + signale.warn(`${clientId ? `Client ID: ${clientId}, ` : ''}The Broker has actively disconnected`), } const benchLog = { diff --git a/cli/yarn.lock b/cli/yarn.lock index e835864ee..f66a477e1 100644 --- a/cli/yarn.lock +++ b/cli/yarn.lock @@ -72,11 +72,28 @@ dependencies: "@types/node" "*" +"@types/debug@^4.1.12": + version "4.1.12" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.12.tgz#a155f21690871953410df4b6b6f53187f0500917" + integrity sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ== + dependencies: + "@types/ms" "*" + "@types/js-yaml@^4.0.5": version "4.0.5" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" integrity sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA== +"@types/json-bigint@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@types/json-bigint/-/json-bigint-1.0.4.tgz#250d29e593375499d8ba6efaab22d094c3199ef3" + integrity sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag== + +"@types/ms@*": + version "0.7.34" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.34.tgz#10964ba0dee6ac4cd462e2795b6bebd407303433" + integrity sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g== + "@types/node@*", "@types/node@^17.0.43": version "17.0.43" resolved "https://registry.npmjs.org/@types/node/-/node-17.0.43.tgz" @@ -170,6 +187,11 @@ base64-js@^1.3.1: resolved "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== +bignumber.js@^9.0.0: + version "9.1.2" + resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" + integrity sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug== + bl@^4.0.2: version "4.1.0" resolved "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz" @@ -200,6 +222,13 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +cbor@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/cbor/-/cbor-9.0.1.tgz#b16e393d4948d44758cd54ac6151379d443b37ae" + integrity sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ== + dependencies: + nofilter "^3.1.0" + chalk@^2.3.2: version "2.4.2" resolved "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" @@ -356,9 +385,9 @@ find-up@^2.0.0: locate-path "^2.0.0" follow-redirects@^1.14.9: - version "1.15.2" - resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== + version "1.15.4" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.4.tgz#cdc7d308bf6493126b17ea2191ea0ccf3e535adf" + integrity sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw== form-data@^4.0.0: version "4.0.0" @@ -449,6 +478,13 @@ js-yaml@^4.1.0: dependencies: argparse "^2.0.1" +json-bigint@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1" + integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ== + dependencies: + bignumber.js "^9.0.0" + json-parse-better-errors@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz" @@ -550,6 +586,11 @@ ms@2.1.2: resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== +nofilter@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/nofilter/-/nofilter-3.1.0.tgz#c757ba68801d41ff930ba2ec55bab52ca184aa66" + integrity sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g== + number-allocator@^1.0.9: version "1.0.10" resolved "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.10.tgz" diff --git a/package.json b/package.json index 425c5d542..1aafa9186 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "MQTTX", - "version": "1.9.6", + "version": "1.9.9", "description": "MQTT desktop client", "author": "EMQX Team ", "scripts": { @@ -23,6 +23,7 @@ "db:log": "TS_NODE_PROJECT=tsconfig.commonjs.json ts-node ./node_modules/.bin/typeorm schema:log -f ormconfig.ts", "db:migration:show": "TS_NODE_PROJECT=tsconfig.commonjs.json ts-node ./node_modules/.bin/typeorm migration:show -f ormconfig.ts", "db:migration:generate": "TS_NODE_PROJECT=tsconfig.commonjs.json ts-node ./node_modules/.bin/typeorm migration:generate -f ormconfig.ts --pretty", + "db:migration:create": "TS_NODE_PROJECT=tsconfig.commonjs.json ts-node ./node_modules/.bin/typeorm migration:create -f ormconfig.ts", "db:migration:run": "TS_NODE_PROJECT=tsconfig.commonjs.json ts-node ./node_modules/.bin/typeorm migration:run -f ormconfig.ts", "db:migration:revert": "TS_NODE_PROJECT=tsconfig.commonjs.json ts-node ./node_modules/.bin/typeorm migration:revert -f ormconfig.ts", "db:migration:sync": "TS_NODE_PROJECT=tsconfig.commonjs.json ts-node ./node_modules/.bin/typeorm schema:sync -f ormconfig.ts", @@ -30,18 +31,24 @@ "db:diagram": "TS_NODE_PROJECT=tsconfig.commonjs.json ts-node ./node_modules/.bin/typeorm-uml ormconfig.ts -d ./src/database/database.png" }, "main": "background.js", + "engines": { + "node": ">=16" + }, "dependencies": { "appdata-path": "^1.0.0", "axios": "^0.21.2", - "chart.js": "^2.9.4", + "cbor": "^9.0.1", "compare-versions": "^6.0.0-rc.2", "core-js": "^2.6.11", + "crypto-js": "^4.2.0", "csvtojson": "^2.0.10", + "echarts": "^5.4.3", "electron-store": "^8.0.2", "electron-updater": "^5.0.5", "element-ui": "^2.15.5", "fs-extra": "^8.1.0", "js-yaml": "^4.1.0", + "json-bigint": "^1.0.0", "json2csv": "^5.0.3", "log4js": "^6.4.0", "moment": "^2.29.4", @@ -61,6 +68,7 @@ "vue-click-outside": "^1.1.0", "vue-clipboard2": "^0.3.1", "vue-i18n": "^8.11.2", + "vue-markdown": "^2.2.4", "vue-property-decorator": "^8.5.1", "vue-router": "^3.4.9", "vue-rx": "6.2.0", @@ -71,10 +79,10 @@ }, "devDependencies": { "@types/chai": "^4.1.0", - "@types/chart.js": "^2.9.28", "@types/electron-devtools-installer": "^2.2.0", "@types/fs-extra": "^8.0.0", "@types/js-yaml": "^4.0.5", + "@types/json-bigint": "^1.0.4", "@types/json2csv": "^5.0.3", "@types/lodash": "^4.14.142", "@types/mocha": "^5.2.7", diff --git a/src/App.vue b/src/App.vue index 3647a72f9..97ccccb3f 100644 --- a/src/App.vue +++ b/src/App.vue @@ -10,7 +10,7 @@ import { Component, Vue } from 'vue-property-decorator' @Component export default class App extends Vue { created() { - this.$log.info('APP init') + this.$log.info('MQTTX Desktop App init') } } diff --git a/src/assets/font/iconfont.css b/src/assets/font/iconfont.css index 0ef93a04c..a70ce4fee 100644 --- a/src/assets/font/iconfont.css +++ b/src/assets/font/iconfont.css @@ -1,8 +1,8 @@ @font-face { font-family: "iconfont"; /* Project id 1257443 */ - src: url('iconfont.woff2?t=1668757527838') format('woff2'), - url('iconfont.woff?t=1668757527838') format('woff'), - url('iconfont.ttf?t=1668757527838') format('truetype'); + src: url('iconfont.woff2?t=1706248596667') format('woff2'), + url('iconfont.woff?t=1706248596667') format('woff'), + url('iconfont.ttf?t=1706248596667') format('truetype'); } .iconfont { @@ -13,148 +13,192 @@ -moz-osx-font-smoothing: grayscale; } -.icon-mqtt:before { - content: "\e79a"; +.icon-create-new:before { + content: "\e7fa"; } -.icon-a-createnew:before { - content: "\e792"; +.icon-x:before { + content: "\e801"; } -.icon-connections:before { - content: "\e790"; +.icon-more:before { + content: "\e7e1"; } -.icon-help:before { - content: "\e78b"; +.icon-new:before { + content: "\e7e5"; } -.icon-github:before { - content: "\e75b"; +.icon-stop-timing:before { + content: "\e7ec"; } -.icon-website:before { - content: "\e75a"; +.icon-stop-script:before { + content: "\e7f6"; } -.icon-faq:before { - content: "\e759"; +.icon-script:before { + content: "\e7f3"; } -.icon-discord:before { - content: "\e66e"; +.icon-log:before { + content: "\e7ed"; } -.icon-cloud-logo:before { - content: "\e602"; +.icon-viewer:before { + content: "\e7fc"; } -.icon-download:before { - content: "\e6ab"; +.icon-backup:before { + content: "\e7f1"; } -.icon-language:before { - content: "\e61b"; +.icon-bytes-statistics:before { + content: "\e7f2"; } -.icon-youtube:before { - content: "\e612"; +.icon-import-data:before { + content: "\e7f4"; } -.icon-linkedin:before { - content: "\e601"; -} - -.icon-more:before { - content: "\e6e5"; +.icon-about:before { + content: "\e7f5"; } .icon-edit:before { - content: "\e6e2"; + content: "\e7f7"; } -.icon-a-stopscrip:before { - content: "\e6e0"; +.icon-copy:before { + content: "\e7f8"; } -.icon-a-clearhistory:before { - content: "\e6e4"; +.icon-connections:before { + content: "\e7f9"; } -.icon-a-stoptiming:before { - content: "\e6df"; +.icon-refresh:before { + content: "\e7fb"; } .icon-collapse:before { - content: "\e6e1"; + content: "\e7fd"; } -.icon-copy:before { - content: "\e6e3"; +.icon-chat:before { + content: "\e7fe"; } -.icon-a-bytesstatistics:before { - content: "\e6dd"; +.icon-disconnect:before { + content: "\e7ff"; } -.icon-delete:before { - content: "\e6de"; +.icon-hide-connections:before { + content: "\e800"; } -.icon-log:before { - content: "\e6d8"; +.icon-help:before { + content: "\e7e2"; } -.icon-script:before { - content: "\e6dc"; +.icon-timed-message:before { + content: "\e7e3"; } -.icon-new:before { - content: "\e6db"; +.icon-use-script:before { + content: "\e7e4"; } -.icon-a-timedmessage:before { - content: "\e6c6"; +.icon-new-window:before { + content: "\e7e6"; } -.icon-a-exportdata:before { - content: "\e6c9"; +.icon-delete:before { + content: "\e7e7"; } -.icon-a-importdata:before { - content: "\e6cb"; +.icon-send:before { + content: "\e7e8"; } -.icon-search:before { - content: "\e6cc"; +.icon-clear-history:before { + content: "\e7e9"; } -.icon-about:before { - content: "\e6ce"; +.icon-export-data:before { + content: "\e7ea"; +} + +.icon-frame:before { + content: "\e7eb"; +} + +.icon-down:before { + content: "\e7ee"; } .icon-right:before { - content: "\e6d1"; + content: "\e7ef"; } .icon-left:before { - content: "\e6d2"; + content: "\e7f0"; } -.icon-middle:before { - content: "\e6d3"; +.icon-run-script:before { + content: "\e7db"; } -.icon-a-newwindow:before { - content: "\e6d4"; +.icon-middle:before { + content: "\e7dc"; } .icon-settings:before { - content: "\e6d6"; + content: "\e7dd"; } -.icon-a-usescript:before { - content: "\e6d7"; +.icon-show-connections:before { + content: "\e7da"; +} + +.icon-website:before { + content: "\e7de"; +} + +.icon-search:before { + content: "\e7df"; +} + +.icon-mqtt:before { + content: "\e79a"; +} + +.icon-github:before { + content: "\e75b"; +} + +.icon-faq:before { + content: "\e759"; +} + +.icon-discord:before { + content: "\e66e"; +} + +.icon-cloud-logo:before { + content: "\e602"; +} + +.icon-language:before { + content: "\e61b"; +} + +.icon-youtube:before { + content: "\e612"; +} + +.icon-linkedin:before { + content: "\e601"; } .icon-fold:before { @@ -165,10 +209,6 @@ content: "\e6da"; } -.icon-triangle:before { - content: "\e8e3"; -} - .icon-qq:before { content: "\e615"; } @@ -177,19 +217,11 @@ content: "\e73a"; } -.icon-we-chat:before { +.icon-wechat:before { content: "\e70e"; } -.icon-ttww:before { - content: "\e6c7"; -} - .icon-slack:before { content: "\e641"; } -.icon-send:before { - content: "\e62f"; -} - diff --git a/src/assets/font/iconfont.js b/src/assets/font/iconfont.js index 6be3580ff..932c04b83 100644 --- a/src/assets/font/iconfont.js +++ b/src/assets/font/iconfont.js @@ -1,16 +1,16 @@ ;(window._iconfont_svg_string_1257443 = - ''), + ''), (function (t) { var c = (c = document.getElementsByTagName('script'))[c.length - 1], h = c.getAttribute('data-injectcss'), c = c.getAttribute('data-disable-injectsvg') if (!c) { - var a, + var o, l, - s, - o, + a, i, - d = function (c, h) { + s, + v = function (c, h) { h.parentNode.insertBefore(c, h) } if (h && !t.__iconfont__svg__cssinject__) { @@ -23,7 +23,7 @@ console && console.log(c) } } - ;(a = function () { + ;(o = function () { var c, h = document.createElement('div') ;(h.innerHTML = t._iconfont_svg_string_1257443), @@ -34,33 +34,33 @@ (h.style.height = 0), (h.style.overflow = 'hidden'), (h = h), - (c = document.body).firstChild ? d(h, c.firstChild) : c.appendChild(h)) + (c = document.body).firstChild ? v(h, c.firstChild) : c.appendChild(h)) }), document.addEventListener ? ~['complete', 'loaded', 'interactive'].indexOf(document.readyState) - ? setTimeout(a, 0) + ? setTimeout(o, 0) : ((l = function () { - document.removeEventListener('DOMContentLoaded', l, !1), a() + document.removeEventListener('DOMContentLoaded', l, !1), o() }), document.addEventListener('DOMContentLoaded', l, !1)) : document.attachEvent && - ((s = a), - (o = t.document), - (i = !1), + ((a = o), + (i = t.document), + (s = !1), e(), - (o.onreadystatechange = function () { - 'complete' == o.readyState && ((o.onreadystatechange = null), p()) + (i.onreadystatechange = function () { + 'complete' == i.readyState && ((i.onreadystatechange = null), d()) })) } - function p() { - i || ((i = !0), s()) + function d() { + s || ((s = !0), a()) } function e() { try { - o.documentElement.doScroll('left') + i.documentElement.doScroll('left') } catch (c) { return void setTimeout(e, 50) } - p() + d() } })(window) diff --git a/src/assets/font/iconfont.json b/src/assets/font/iconfont.json index 4f3fa34ad..70cf43b98 100644 --- a/src/assets/font/iconfont.json +++ b/src/assets/font/iconfont.json @@ -6,256 +6,333 @@ "description": "", "glyphs": [ { - "icon_id": "32933818", - "name": "mqtt", - "font_class": "mqtt", - "unicode": "e79a", - "unicode_decimal": 59290 - }, - { - "icon_id": "32681832", + "icon_id": "39123757", "name": "create new", - "font_class": "a-createnew", - "unicode": "e792", - "unicode_decimal": 59282 + "font_class": "create-new", + "unicode": "e7fa", + "unicode_decimal": 59386 }, { - "icon_id": "32675792", - "name": "connections", - "font_class": "connections", - "unicode": "e790", - "unicode_decimal": 59280 + "icon_id": "39123400", + "name": "x", + "font_class": "x", + "unicode": "e801", + "unicode_decimal": 59393 }, { - "icon_id": "32483181", - "name": "Help", - "font_class": "help", - "unicode": "e78b", - "unicode_decimal": 59275 + "icon_id": "39123307", + "name": "more", + "font_class": "more", + "unicode": "e7e1", + "unicode_decimal": 59361 }, { - "icon_id": "29692593", - "name": "github", - "font_class": "github", - "unicode": "e75b", - "unicode_decimal": 59227 + "icon_id": "39123305", + "name": "new", + "font_class": "new", + "unicode": "e7e5", + "unicode_decimal": 59365 }, { - "icon_id": "29241652", - "name": "website", - "font_class": "website", - "unicode": "e75a", - "unicode_decimal": 59226 + "icon_id": "39123308", + "name": "stop timing", + "font_class": "stop-timing", + "unicode": "e7ec", + "unicode_decimal": 59372 }, { - "icon_id": "29237198", - "name": "faq", - "font_class": "faq", - "unicode": "e759", - "unicode_decimal": 59225 + "icon_id": "39123306", + "name": "stop script", + "font_class": "stop-script", + "unicode": "e7f6", + "unicode_decimal": 59382 }, { - "icon_id": "3876491", - "name": "discord", - "font_class": "discord", - "unicode": "e66e", - "unicode_decimal": 58990 + "icon_id": "39114318", + "name": "script", + "font_class": "script", + "unicode": "e7f3", + "unicode_decimal": 59379 }, { - "icon_id": "25711546", - "name": "cloud-logo", - "font_class": "cloud-logo", - "unicode": "e602", - "unicode_decimal": 58882 + "icon_id": "39114218", + "name": "log", + "font_class": "log", + "unicode": "e7ed", + "unicode_decimal": 59373 }, { - "icon_id": "2238873", - "name": "下载", - "font_class": "download", - "unicode": "e6ab", - "unicode_decimal": 59051 + "icon_id": "39113445", + "name": "viewer", + "font_class": "viewer", + "unicode": "e7fc", + "unicode_decimal": 59388 }, { - "icon_id": "17686988", - "name": "地球", - "font_class": "language", - "unicode": "e61b", - "unicode_decimal": 58907 + "icon_id": "39111040", + "name": "backup", + "font_class": "backup", + "unicode": "e7f1", + "unicode_decimal": 59377 }, { - "icon_id": "2591074", - "name": "youtube", - "font_class": "youtube", - "unicode": "e612", - "unicode_decimal": 58898 + "icon_id": "39111045", + "name": "bytes statistics", + "font_class": "bytes-statistics", + "unicode": "e7f2", + "unicode_decimal": 59378 }, { - "icon_id": "12294078", - "name": "linkin", - "font_class": "linkedin", - "unicode": "e601", - "unicode_decimal": 58881 + "icon_id": "39111057", + "name": "import data", + "font_class": "import-data", + "unicode": "e7f4", + "unicode_decimal": 59380 }, { - "icon_id": "22444844", - "name": "more", - "font_class": "more", - "unicode": "e6e5", - "unicode_decimal": 59109 + "icon_id": "39111042", + "name": "about", + "font_class": "about", + "unicode": "e7f5", + "unicode_decimal": 59381 }, { - "icon_id": "22444281", + "icon_id": "39111054", "name": "edit", "font_class": "edit", - "unicode": "e6e2", - "unicode_decimal": 59106 + "unicode": "e7f7", + "unicode_decimal": 59383 }, { - "icon_id": "22420295", - "name": "stop scrip", - "font_class": "a-stopscrip", - "unicode": "e6e0", - "unicode_decimal": 59104 + "icon_id": "39111047", + "name": "copy", + "font_class": "copy", + "unicode": "e7f8", + "unicode_decimal": 59384 }, { - "icon_id": "22419005", - "name": "clear history", - "font_class": "a-clearhistory", - "unicode": "e6e4", - "unicode_decimal": 59108 + "icon_id": "39111044", + "name": "connections", + "font_class": "connections", + "unicode": "e7f9", + "unicode_decimal": 59385 }, { - "icon_id": "22418847", - "name": "stop timing", - "font_class": "a-stoptiming", - "unicode": "e6df", - "unicode_decimal": 59103 + "icon_id": "39111041", + "name": "refresh", + "font_class": "refresh", + "unicode": "e7fb", + "unicode_decimal": 59387 }, { - "icon_id": "22418849", + "icon_id": "39111043", "name": "collapse", "font_class": "collapse", - "unicode": "e6e1", - "unicode_decimal": 59105 + "unicode": "e7fd", + "unicode_decimal": 59389 }, { - "icon_id": "22418851", - "name": "copy", - "font_class": "copy", - "unicode": "e6e3", - "unicode_decimal": 59107 + "icon_id": "39111037", + "name": "chat", + "font_class": "chat", + "unicode": "e7fe", + "unicode_decimal": 59390 }, { - "icon_id": "22416556", - "name": "bytes statistics", - "font_class": "a-bytesstatistics", - "unicode": "e6dd", - "unicode_decimal": 59101 + "icon_id": "39111036", + "name": "disconnect", + "font_class": "disconnect", + "unicode": "e7ff", + "unicode_decimal": 59391 }, { - "icon_id": "22416557", - "name": "delete", - "font_class": "delete", - "unicode": "e6de", - "unicode_decimal": 59102 + "icon_id": "39111038", + "name": "hide connections", + "font_class": "hide-connections", + "unicode": "e800", + "unicode_decimal": 59392 }, { - "icon_id": "22414557", - "name": "log", - "font_class": "log", - "unicode": "e6d8", - "unicode_decimal": 59096 + "icon_id": "39111055", + "name": "help", + "font_class": "help", + "unicode": "e7e2", + "unicode_decimal": 59362 }, { - "icon_id": "22414310", - "name": "script", - "font_class": "script", - "unicode": "e6dc", - "unicode_decimal": 59100 + "icon_id": "39111067", + "name": "timed message", + "font_class": "timed-message", + "unicode": "e7e3", + "unicode_decimal": 59363 }, { - "icon_id": "22413795", - "name": "new", - "font_class": "new", - "unicode": "e6db", - "unicode_decimal": 59099 + "icon_id": "39111069", + "name": "use script", + "font_class": "use-script", + "unicode": "e7e4", + "unicode_decimal": 59364 }, { - "icon_id": "22406900", - "name": "timed message", - "font_class": "a-timedmessage", - "unicode": "e6c6", - "unicode_decimal": 59078 + "icon_id": "39111065", + "name": "new window", + "font_class": "new-window", + "unicode": "e7e6", + "unicode_decimal": 59366 }, { - "icon_id": "22406907", - "name": "export data", - "font_class": "a-exportdata", - "unicode": "e6c9", - "unicode_decimal": 59081 + "icon_id": "39111060", + "name": "delete", + "font_class": "delete", + "unicode": "e7e7", + "unicode_decimal": 59367 }, { - "icon_id": "22406911", - "name": "import data", - "font_class": "a-importdata", - "unicode": "e6cb", - "unicode_decimal": 59083 + "icon_id": "39111066", + "name": "send", + "font_class": "send", + "unicode": "e7e8", + "unicode_decimal": 59368 }, { - "icon_id": "22406912", - "name": "search", - "font_class": "search", - "unicode": "e6cc", - "unicode_decimal": 59084 + "icon_id": "39111048", + "name": "clear history", + "font_class": "clear-history", + "unicode": "e7e9", + "unicode_decimal": 59369 }, { - "icon_id": "22407879", - "name": "about", - "font_class": "about", - "unicode": "e6ce", - "unicode_decimal": 59086 + "icon_id": "39111051", + "name": "export data", + "font_class": "export-data", + "unicode": "e7ea", + "unicode_decimal": 59370 }, { - "icon_id": "22407882", + "icon_id": "39111059", + "name": "frame", + "font_class": "frame", + "unicode": "e7eb", + "unicode_decimal": 59371 + }, + { + "icon_id": "39111061", + "name": "down", + "font_class": "down", + "unicode": "e7ee", + "unicode_decimal": 59374 + }, + { + "icon_id": "39111063", "name": "right", "font_class": "right", - "unicode": "e6d1", - "unicode_decimal": 59089 + "unicode": "e7ef", + "unicode_decimal": 59375 }, { - "icon_id": "22407883", + "icon_id": "39111052", "name": "left", "font_class": "left", - "unicode": "e6d2", - "unicode_decimal": 59090 + "unicode": "e7f0", + "unicode_decimal": 59376 }, { - "icon_id": "22407884", - "name": "middle", - "font_class": "middle", - "unicode": "e6d3", - "unicode_decimal": 59091 + "icon_id": "39111064", + "name": "run script", + "font_class": "run-script", + "unicode": "e7db", + "unicode_decimal": 59355 }, { - "icon_id": "22407886", - "name": "new window", - "font_class": "a-newwindow", - "unicode": "e6d4", - "unicode_decimal": 59092 + "icon_id": "39111049", + "name": "middle", + "font_class": "middle", + "unicode": "e7dc", + "unicode_decimal": 59356 }, { - "icon_id": "22407889", + "icon_id": "39111074", "name": "settings", "font_class": "settings", - "unicode": "e6d6", - "unicode_decimal": 59094 + "unicode": "e7dd", + "unicode_decimal": 59357 }, { - "icon_id": "22407891", - "name": "use script", - "font_class": "a-usescript", - "unicode": "e6d7", - "unicode_decimal": 59095 + "icon_id": "39111039", + "name": "show connections", + "font_class": "show-connections", + "unicode": "e7da", + "unicode_decimal": 59354 + }, + { + "icon_id": "39111073", + "name": "website", + "font_class": "website", + "unicode": "e7de", + "unicode_decimal": 59358 + }, + { + "icon_id": "39111070", + "name": "search", + "font_class": "search", + "unicode": "e7df", + "unicode_decimal": 59359 + }, + { + "icon_id": "32933818", + "name": "mqtt", + "font_class": "mqtt", + "unicode": "e79a", + "unicode_decimal": 59290 + }, + { + "icon_id": "29692593", + "name": "github", + "font_class": "github", + "unicode": "e75b", + "unicode_decimal": 59227 + }, + { + "icon_id": "29237198", + "name": "faq", + "font_class": "faq", + "unicode": "e759", + "unicode_decimal": 59225 + }, + { + "icon_id": "3876491", + "name": "discord", + "font_class": "discord", + "unicode": "e66e", + "unicode_decimal": 58990 + }, + { + "icon_id": "25711546", + "name": "cloud-logo", + "font_class": "cloud-logo", + "unicode": "e602", + "unicode_decimal": 58882 + }, + { + "icon_id": "17686988", + "name": "language", + "font_class": "language", + "unicode": "e61b", + "unicode_decimal": 58907 + }, + { + "icon_id": "2591074", + "name": "youtube", + "font_class": "youtube", + "unicode": "e612", + "unicode_decimal": 58898 + }, + { + "icon_id": "12294078", + "name": "linkedin", + "font_class": "linkedin", + "unicode": "e601", + "unicode_decimal": 58881 }, { "icon_id": "22407905", @@ -271,13 +348,6 @@ "unicode": "e6da", "unicode_decimal": 59098 }, - { - "icon_id": "17752358", - "name": "down", - "font_class": "triangle", - "unicode": "e8e3", - "unicode_decimal": 59619 - }, { "icon_id": "1878149", "name": "qq", @@ -294,31 +364,17 @@ }, { "icon_id": "9277879", - "name": "we-chat", - "font_class": "we-chat", + "name": "wechat", + "font_class": "wechat", "unicode": "e70e", "unicode_decimal": 59150 }, - { - "icon_id": "12313365", - "name": "twitter", - "font_class": "ttww", - "unicode": "e6c7", - "unicode_decimal": 59079 - }, { "icon_id": "3876355", "name": "slack", "font_class": "slack", "unicode": "e641", "unicode_decimal": 58945 - }, - { - "icon_id": "9559673", - "name": "send", - "font_class": "send", - "unicode": "e62f", - "unicode_decimal": 58927 } ] } diff --git a/src/assets/font/iconfont.ttf b/src/assets/font/iconfont.ttf index 40002a566..0a1d4b19c 100644 Binary files a/src/assets/font/iconfont.ttf and b/src/assets/font/iconfont.ttf differ diff --git a/src/assets/font/iconfont.woff b/src/assets/font/iconfont.woff index 654d8d172..4a07448a4 100644 Binary files a/src/assets/font/iconfont.woff and b/src/assets/font/iconfont.woff differ diff --git a/src/assets/font/iconfont.woff2 b/src/assets/font/iconfont.woff2 index 566468f77..67d47e22a 100644 Binary files a/src/assets/font/iconfont.woff2 and b/src/assets/font/iconfont.woff2 differ diff --git a/src/assets/scss/base.scss b/src/assets/scss/base.scss index 033bcbe7e..d3b0e2f0c 100644 --- a/src/assets/scss/base.scss +++ b/src/assets/scss/base.scss @@ -140,3 +140,16 @@ textarea { padding: 16px 0; -webkit-app-region: drag; } + +#notify-copilot-button { + margin: 12px 0 4px 0; + height: 32px; + line-height: 32px; + font-size: 13px; + background-color: transparent; + border: 1px solid var(--color-main-green); + color: var(--color-main-green); + border-radius: 4px; + padding: 0 24px; + cursor: pointer; +} diff --git a/src/assets/scss/connections.scss b/src/assets/scss/connections.scss index b4702eea7..e4dbd450b 100644 --- a/src/assets/scss/connections.scss +++ b/src/assets/scss/connections.scss @@ -10,6 +10,7 @@ z-index: 1000; border-right: 1px solid var(--color-border-default); background-color: var(--color-bg-normal); + transition: all 0.3s ease-in-out; .no-data { text-align: center; @@ -20,9 +21,26 @@ } } +.connections { + height: 100%; + .connections-view { + height: 100%; + } +} + .connection-skeleton-page { margin-top: 30px; - margin-left: 370px; margin-right: 30px; overflow-x: hidden; } + +.slide-enter-active, +.slide-leave-active { + transition: all 0.3s ease; +} +.slide-enter { + transform: translateX(-100%); +} +.slide-leave-to { + transform: translateX(-100%); +} diff --git a/src/assets/scss/element/element-reset.scss b/src/assets/scss/element/element-reset.scss index ca8ef0b61..322688867 100644 --- a/src/assets/scss/element/element-reset.scss +++ b/src/assets/scss/element/element-reset.scss @@ -41,6 +41,9 @@ .el-card.is-hover-shadow:hover { box-shadow: 0 2px 12px 0 var(--color-shadow-card); } +.el-card__header { + border-bottom: 1px solid var(--color-border-default); +} /* Form */ .el-form { @@ -327,3 +330,12 @@ } } } + +/* Tag */ +.el-tag { + &.el-tag--info { + background-color: transparent; + border-color: var(--color-border-default); + color: var(--color-text-tips); + } +} diff --git a/src/assets/scss/theme/custom/prism-one-dark.scss b/src/assets/scss/theme/custom/prism-one-dark.scss index dfa5871cf..81b8b1c31 100644 --- a/src/assets/scss/theme/custom/prism-one-dark.scss +++ b/src/assets/scss/theme/custom/prism-one-dark.scss @@ -263,28 +263,73 @@ pre[class*='language-'] { /* Toolbar plugin overrides */ /* Space out all buttons and move them away from the right edge of the code block */ -div.code-toolbar > .toolbar.toolbar > .toolbar-item { - margin-right: 0.4em; +/* + Global Prism.js toolbar override: This CSS hides toolbar buttons in all code blocks site-wide. + Addressing the issue where Prism.js applies toolbar buttons globally without a native removal method + (referenced in https://github.com/PrismJS/prism/issues/459), this ensures a cleaner global appearance + by preventing buttons from showing up where they are not needed or intended. +*/ +div.code-toolbar { + .toolbar { + display: none; + } } +/* + Selective display within #copilot: Within this specific context, the toolbar buttons are explicitly + shown. This creates a controlled environment where the toolbar's enhanced features (like copy and insert + buttons) are available only in the #copilot area. This CSS-based 'namespace' isolation is a practical + solution to selectively enable toolbar functionality in desired areas, avoiding its impact elsewhere. +*/ +#copilot { + div.code-toolbar { + position: relative; + border-radius: 8px; + border: 1px solid transparent; + background-color: var(--color-main-grey); + transition: border-color 0.3s ease-in-out; + .toolbar { + position: absolute; + top: -10px; + right: 0; + transition: opacity 0.3s ease-in-out; + opacity: 0; + display: flex; + } + &:hover { + border-color: var(--color-border-default); + .toolbar { + opacity: 1; + } + } + } + div.code-toolbar > .toolbar.toolbar > .toolbar-item { + margin-right: 0.4em; + } -/* Styling the buttons */ -div.code-toolbar > .toolbar.toolbar > .toolbar-item > button, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > span { - background: hsl(220, 13%, 26%); - color: hsl(220, 9%, 55%); - padding: 0.1em 0.4em; - border-radius: 0.3em; -} + /* Styling the buttons */ + div.code-toolbar > .toolbar.toolbar > .toolbar-item > button, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > span { + cursor: pointer; + font-size: 12px; + background-color: var(--color-bg-normal); + border: 1px solid var(--color-border-default); + color: var(--color-text-default); + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + box-shadow: #00000014 0px 4px 12px; + } -div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { - background: hsl(220, 13%, 28%); - color: hsl(220, 14%, 71%); + div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { + border: 1px solid var(--color-main-green); + color: var(--color-main-green); + } } /* Line Highlight plugin overrides */ diff --git a/src/assets/scss/theme/custom/prism-one-light.scss b/src/assets/scss/theme/custom/prism-one-light.scss index 87930e8b7..fba0df995 100644 --- a/src/assets/scss/theme/custom/prism-one-light.scss +++ b/src/assets/scss/theme/custom/prism-one-light.scss @@ -251,28 +251,67 @@ pre[class*='language-'] { /* Toolbar plugin overrides */ /* Space out all buttons and move them away from the right edge of the code block */ -div.code-toolbar > .toolbar.toolbar > .toolbar-item { - margin-right: 0.4em; -} - -/* Styling the buttons */ -div.code-toolbar > .toolbar.toolbar > .toolbar-item > button, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > span { - background: hsl(230, 1%, 90%); - color: hsl(230, 6%, 44%); - padding: 0.1em 0.4em; - border-radius: 0.3em; -} - -div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, -div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { - background: hsl(230, 1%, 78%); /* custom: darken(--syntax-bg, 20%) */ - color: hsl(230, 8%, 24%); +/* + Global Prism.js toolbar override: This CSS hides toolbar buttons in all code blocks site-wide. + Addressing the issue where Prism.js applies toolbar buttons globally without a native removal method + (referenced in https://github.com/PrismJS/prism/issues/459), this ensures a cleaner global appearance + by preventing buttons from showing up where they are not needed or intended. +*/ +div.code-toolbar { + .toolbar { + display: none; + } +} +/* + Selective display within #copilot: Within this specific context, the toolbar buttons are explicitly + shown. This creates a controlled environment where the toolbar's enhanced features (like copy and insert + buttons) are available only in the #copilot area. This CSS-based 'namespace' isolation is a practical + solution to selectively enable toolbar functionality in desired areas, avoiding its impact elsewhere. +*/ +#copilot { + div.code-toolbar { + position: relative; + border-radius: 8px; + .toolbar { + position: absolute; + top: -10px; + right: 0; + transition: opacity 0.5s ease-in-out; + opacity: 0; + display: flex; + } + &:hover .toolbar { + opacity: 1; + } + } + div.code-toolbar > .toolbar.toolbar > .toolbar-item { + margin-right: 0.4em; + } + + /* Styling the buttons */ + div.code-toolbar > .toolbar.toolbar > .toolbar-item > button, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > span { + cursor: pointer; + font-size: 12px; + background-color: var(--color-bg-normal); + border: 1px solid var(--color-border-default); + color: var(--color-text-default); + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + box-shadow: #00000014 0px 4px 12px; + } + + div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, + div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { + border: 1px solid var(--color-main-green); + color: var(--color-main-green); + } } /* Line Highlight plugin overrides */ diff --git a/src/background.ts b/src/background.ts index 5667a96af..95a062fd2 100644 --- a/src/background.ts +++ b/src/background.ts @@ -89,6 +89,9 @@ function handleIpcMessages() { message: 'There are currently no updates available.', }) }) + ipcMain.on('insertCodeToEditor', (event, ...args) => { + event.sender.send('insertCodeToEditor', ...args) + }) } // handle event when APP quit @@ -123,6 +126,10 @@ async function createWindow() { syncOsTheme: setting.syncOsTheme, multiTopics: setting.multiTopics, jsonHighlight: setting.jsonHighlight, + enableCopilot: setting.enableCopilot, + openAIAPIKey: setting.openAIAPIKey, + model: setting.model, + logLevel: setting.logLevel, } } // Create the browser window. diff --git a/src/components/BytesStatistics.vue b/src/components/BytesStatistics.vue deleted file mode 100644 index 87a524e27..000000000 --- a/src/components/BytesStatistics.vue +++ /dev/null @@ -1,174 +0,0 @@ - - - - - diff --git a/src/components/ClearUpHistoryData.vue b/src/components/ClearUpHistoryData.vue index 03b94e5c5..f3bb625ce 100644 --- a/src/components/ClearUpHistoryData.vue +++ b/src/components/ClearUpHistoryData.vue @@ -43,7 +43,7 @@ export default class ClearUpHistoryData extends Vue { ]) this.$message.success(this.$tc('connections.cleanHistorySuccess')) - this.$log.info('Clear history successfully') + this.$log.info('History cleared successfully') this.resetData() } diff --git a/src/components/Copilot.vue b/src/components/Copilot.vue new file mode 100644 index 000000000..d9055afda --- /dev/null +++ b/src/components/Copilot.vue @@ -0,0 +1,564 @@ + + + + + diff --git a/src/components/Editor.vue b/src/components/Editor.vue index 7d898c2ed..3490bedaf 100644 --- a/src/components/Editor.vue +++ b/src/components/Editor.vue @@ -31,6 +31,7 @@ export default class Editor extends Vue { @Model('change', { type: String }) private readonly value!: string @Getter('currentTheme') private theme!: Theme + @Getter('showConnectionList') private showConnectionList!: boolean private editor: monaco.editor.IStandaloneCodeEditor | null = null @@ -66,6 +67,13 @@ export default class Editor extends Vue { } } + @Watch('showConnectionList') + private handleShowConnectionListChanged(val: boolean) { + setTimeout(() => { + this.editorLayout() + }, 500) + } + // init and register customer editor style private initCustomerLanguages() { this.registerLog() diff --git a/src/components/EmptyPage.vue b/src/components/EmptyPage.vue index d1848c3fc..c237bcc91 100644 --- a/src/components/EmptyPage.vue +++ b/src/components/EmptyPage.vue @@ -1,5 +1,10 @@