Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(desktop): visualize topic tree support #1779

Merged
merged 7 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mqttx-cli",
"version": "1.10.1",
"version": "1.11.0",
"description": "MQTTX Command Line Tools",
"keywords": [
"mqtt",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "MQTTX",
"version": "1.10.1",
"version": "1.11.0",
"description": "MQTT desktop client",
"author": "EMQX Team <[email protected]>",
"scripts": {
Expand Down
14 changes: 7 additions & 7 deletions src/assets/font/iconfont.css
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 1257443 */
src: url('iconfont.woff2?t=1711556341747') format('woff2'),
url('iconfont.woff?t=1711556341747') format('woff'),
url('iconfont.ttf?t=1711556341747') format('truetype');
src: url('iconfont.woff2?t=1729587706168') format('woff2'),
url('iconfont.woff?t=1729587706168') format('woff'),
url('iconfont.ttf?t=1729587706168') format('truetype');
}

.iconfont {
Expand All @@ -13,6 +13,10 @@
-moz-osx-font-smoothing: grayscale;
}

.icon-tree-view:before {
content: "\e803";
}

.icon-save:before {
content: "\e802";
}
Expand Down Expand Up @@ -49,10 +53,6 @@
content: "\e7ed";
}

.icon-viewer:before {
content: "\e7fc";
}

.icon-backup:before {
content: "\e7f1";
}
Expand Down
66 changes: 65 additions & 1 deletion src/assets/font/iconfont.js

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions src/assets/font/iconfont.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "42190193",
"name": "tree-view",
"font_class": "tree-view",
"unicode": "e803",
"unicode_decimal": 59395
},
{
"icon_id": "39719291",
"name": "save",
Expand Down Expand Up @@ -68,13 +75,6 @@
"unicode": "e7ed",
"unicode_decimal": 59373
},
{
"icon_id": "39113445",
"name": "viewer",
"font_class": "viewer",
"unicode": "e7fc",
"unicode_decimal": 59388
},
{
"icon_id": "39111040",
"name": "backup",
Expand Down
Binary file modified src/assets/font/iconfont.ttf
Binary file not shown.
Binary file modified src/assets/font/iconfont.woff
Binary file not shown.
Binary file modified src/assets/font/iconfont.woff2
Binary file not shown.
2 changes: 1 addition & 1 deletion src/components/Leftbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export default class Leftbar extends Vue {
private menuItems: MenuItem[] = [
{ path: '/recent_connections', icon: 'icon-connections', name: 'Connections' },
{ path: '/recent_connections/0', icon: 'icon-new', query: { oper: 'create' }, name: 'ConnectionDetails' },
{ path: '/viewer', icon: 'icon-viewer' },
{ path: '/viewer', icon: 'icon-tree-view' },
{ path: '/script', icon: 'icon-script' },
{ path: '/log', icon: 'icon-log' },
]
Expand Down
93 changes: 59 additions & 34 deletions src/components/charts/Tree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,62 +17,87 @@ export default class TreeChartComponent extends Vue {
@Prop({ required: true }) public id!: string
@Prop({ default: '400px' }) public height!: string
@Prop({ default: '100%' }) public width!: string
@Prop({ required: true }) public data!: TopicTreeNode[]
@Prop({ default: 'vertical' }) public layout!: 'horizontal' | 'vertical'
@Prop({ default: 7 }) public symbolSize!: number
@Prop({ required: true }) public data!: EChartsTreeNode
@Prop({ default: 8 }) public symbolSize!: number
@Prop({ default: 'transparent' }) public backgroundColor!: string
@Prop({ default: () => ({}) }) public tooltipFormatter!: (params: any) => string | Record<string, any>
@Prop({ default: 4 }) public defaultExpandLevel!: number

@Getter('currentTheme') private theme!: string

private chart: echarts.ECharts | null = null

@Watch('data', { deep: true })
@Watch('layout')
@Watch('symbolSize')
private onDataChanged() {
this.updateChart()
}

private generateChartOption(): any {
const treeData = this.data
console.log(treeData)
@Watch('defaultExpandLevel')
private onDefaultExpandLevelChanged() {
this.updateChart()
}

private generateChartOption() {
const isLightTheme = this.theme === 'light'
const textColor = isLightTheme ? '#616161' : '#e6e8f1'
const backgroundColor = isLightTheme ? '#fff' : this.theme === 'dark' ? '#262729' : '#292b33'

return {
backgroundColor: this.backgroundColor,
tooltip: {
trigger: 'item',
triggerOn: 'mousemove',
formatter: this.tooltipFormatter,
},
legend: {
data: treeData.map((tree) => ({
name: tree.label,
icon: 'rectangle',
})),
},
series: treeData.map((tree) => ({
type: 'tree',
data: [tree],
symbolSize: this.symbolSize,
label: {
position: 'left',
verticalAlign: 'middle',
align: 'right',
},
leaves: {
series: [
{
type: 'tree',
data: [this.data],
symbolSize: this.symbolSize,
initialTreeDepth: this.defaultExpandLevel,
itemStyle: {
color: '#53daa2',
borderColor: '#53daa2',
},
label: {
position: 'right',
color: textColor,
position: 'left',
verticalAlign: 'middle',
align: 'left',
align: 'right',
backgroundColor,
padding: [1, 1],
borderRadius: 3,
z: 100,
fontSize: 13,
},
leaves: {
label: {
position: 'right',
verticalAlign: 'middle',
align: 'left',
color: textColor,
fontSize: 13,
},
},
emphasis: {
focus: 'descendant',
itemStyle: {
color: '#34c388',
borderColor: '#34c388',
},
label: {
color: '#53daa2',
},
},
expandAndCollapse: true,
animationDuration: 550,
animationDurationUpdate: 750,
left: '130px',
right: '130px',
top: 0,
bottom: 0,
},
emphasis: {
focus: 'descendant',
},
expandAndCollapse: true,
animationDuration: 550,
animationDurationUpdate: 750,
})),
],
}
}

Expand All @@ -84,7 +109,7 @@ export default class TreeChartComponent extends Vue {
this.updateChart()
}

private updateChart() {
public updateChart() {
if (this.chart) {
const option = this.generateChartOption()
this.chart.setOption(option)
Expand Down
70 changes: 25 additions & 45 deletions src/components/widgets/TreeNodeInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,70 +6,50 @@
<div :key="`label-${index}`">{{ item.label }}</div>
<div :key="`value-${index}`" class="node-info-item">{{ item.value }}</div>
</template>
<div>{{ $t('connections.subTopics') }}</div>
<div class="mt-2">
<el-tag
type="info"
v-for="(topic, index) in getSubTopics(node)"
:key="`${topic}-${index}`"
size="small"
class="mr-2 mb-2"
>
{{ topic }}
</el-tag>
</div>
</div>
<!-- Topic node without payload -->
<div v-else-if="!node.message || (node.message && checkPayloadEmpty(node.message.payload))">
<!-- Full Topic (shown for all nodes except the base node) -->
<template v-else>
<div>{{ $t('connections.fullTopic') }}</div>
<el-tooltip
:effect="currentTheme !== 'light' ? 'light' : 'dark'"
:disabled="fullTopicPath.length < 20"
:disabled="fullTopicPath.length < 25"
placement="top"
:content="fullTopicPath"
:open-delay="500"
>
<div ref="topicPath" class="node-info-item ellipsis">{{ fullTopicPath }}</div>
</el-tooltip>
</template>
<!-- Topic node without payload -->
<div v-if="!node.message || (node.message && checkPayloadEmpty(node.message.payload))">
<template v-if="node.subTopicCount === 0 && node.messageCount > 0">
<el-alert class="no-payload-alert" type="warning" :closable="false">{{
$t('viewer.noPayloadFromTopicNode')
}}</el-alert>
</template>
<template v-else>
<div>{{ $t('connections.subTopics') }}</div>
<div class="mt-2">
<el-tag
v-for="(topic, index) in getSubTopics(node)"
type="info"
:key="`${topic}-${index}`"
size="small"
class="mr-2 mb-2"
>
{{ topic }}
</el-tag>
</div>
</template>
</div>
<!-- Sub topics section -->
<div v-if="node.subTopicCount > 0">
<div>{{ $t('connections.subTopics') }}</div>
<div class="mt-2">
<el-tag
v-for="(topic, index) in getSubTopics(node)"
type="info"
:key="`${topic}-${index}`"
size="small"
class="mr-2 mb-2"
>
{{ topic }}
</el-tag>
</div>
</div>
<!-- Topic node with payload -->
<div v-else>
<div>{{ $t('connections.fullTopic') }}</div>
<el-tooltip
:effect="currentTheme !== 'light' ? 'light' : 'dark'"
:disabled="fullTopicPath.length < 25"
placement="top"
:content="fullTopicPath"
:open-delay="500"
>
<div ref="topicPath" class="node-info-item ellipsis">{{ fullTopicPath }}</div>
</el-tooltip>
<div v-if="node.message && !checkPayloadEmpty(node.message.payload)">
<div>{{ $t('connections.receivedTime') }}</div>
<div v-if="node.message" class="node-info-item">{{ node.message.createAt }}</div>
<div class="node-info-item">{{ node.message.createAt }}</div>
<div class="flex justify-between">
<span
>Payload <el-tag v-if="node.message && node.message.retain" type="info" size="mini">Retained</el-tag></span
>
<span v-if="node.message">QoS: {{ node.message.qos }}</span>
<span>Payload <el-tag v-if="node.message.retain" type="info" size="mini">Retained</el-tag></span>
<span>QoS: {{ node.message.qos }}</span>
</div>
<pre
class="payload-container mt-2 mb-2"
Expand Down
Loading
Loading