Skip to content

Commit 13cc1b9

Browse files
committed
chore: init demo project
0 parents  commit 13cc1b9

13 files changed

+6041
-0
lines changed

.gitignore

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
node_modules
2+
*.log*
3+
.nuxt
4+
.nitro
5+
.cache
6+
.output
7+
.env
8+
dist
9+
.DS_Store
10+
.fleet
11+
.idea

.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
shamefully-hoist=true

README.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Wireshark based on HTML and WASM
2+
3+
Demo with Packet Analyzer powered by Wireshark compiled for WebAssembly
4+
5+
## Setup
6+
7+
Make sure to install the dependencies:
8+
9+
```bash
10+
# yarn
11+
yarn install
12+
13+
# npm
14+
npm install
15+
16+
# pnpm
17+
pnpm install
18+
```
19+
20+
## Development Server
21+
22+
Start the development server on `http://localhost:3000`
23+
24+
```bash
25+
npm run dev
26+
```
27+
28+
## Production
29+
30+
Build the application for production:
31+
32+
```bash
33+
npm run build
34+
```
35+
36+
Locally preview production build:
37+
38+
```bash
39+
npm run preview
40+
```
41+
42+
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.

app.vue

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<div class="p-10px">
3+
<NuxtPage />
4+
</div>
5+
</template>

nuxt.config.ts

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { nodePolyfills } from 'vite-plugin-node-polyfills'
2+
// https://nuxt.com/docs/api/configuration/nuxt-config
3+
export default defineNuxtConfig({
4+
modules: [
5+
'nuxt-windicss',
6+
],
7+
plugins: [
8+
'./plugins/antd.ts',
9+
],
10+
ssr: false,
11+
vite: {
12+
plugins: [
13+
nodePolyfills()
14+
]
15+
}
16+
})

package.json

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "wireshark",
3+
"scripts": {
4+
"build": "nuxt build",
5+
"dev": "nuxt dev",
6+
"generate": "nuxt generate",
7+
"preview": "nuxt preview",
8+
"postinstall": "nuxt prepare"
9+
},
10+
"devDependencies": {
11+
"@types/lodash": "^4.14.195",
12+
"@types/node": "^18",
13+
"@types/pako": "^2.0.0",
14+
"nuxt": "^3.5.2"
15+
},
16+
"dependencies": {
17+
"@goodtools/wiregasm": "^1.2.0",
18+
"ant-design-vue": "^3.2.20",
19+
"lodash": "^4.17.21",
20+
"nuxt-windicss": "^2.6.1",
21+
"pako": "^2.1.0",
22+
"sass": "^1.62.1",
23+
"vite-plugin-node-polyfills": "^0.8.2",
24+
"ws": "^8.13.0"
25+
}
26+
}

pages/index.vue

+215
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
<template>
2+
<a-alert message="WebAssembly Wireshark" type="info" show-icon />
3+
<input type="file" @change="fileChangeHandler">
4+
<div v-if="processed" class="my-10px">
5+
<ATag color="green">总包数: {{ summary.total_packets }}</ATag>
6+
<ATag color="green">开始时间: {{ start_time }}</ATag>
7+
<ATag color="green">结束时间: {{ stop_time }}</ATag>
8+
</div>
9+
<AInput v-model:value="filter_input" placeholder="display filter, example: tcp" />
10+
<ATable
11+
:loading="loading"
12+
:columns="columns"
13+
:data-source="data_source"
14+
:pagination="pagination"
15+
size="small"
16+
:customRow="customRowHandler"
17+
:scroll="{ y: 240 }"
18+
class="packet-table"
19+
@change="tableChangeHandler"
20+
/>
21+
<AAlert v-if="(typeof filter_ret !== 'boolean')">
22+
{{ filter_ret }}
23+
</AAlert>
24+
<div class="flex">
25+
<ATree
26+
:tree-data="selected_packet_tree"
27+
:fieldNames="{ children:'tree', title:'label', key:'id' }"
28+
block-node
29+
/>
30+
31+
</div>
32+
</template>
33+
<script lang="ts" setup>
34+
import { get, map, reduce } from 'lodash'
35+
import { MessageData } from '~/utils/wireshark.worker'
36+
37+
const selected_row_idx = ref(0)
38+
const selected_packet_tree = ref([] as Tree[])
39+
const customRowHandler = ({ raw }: any, idx: number) => {
40+
const bg = raw.number === selected_row_idx.value ? 'blue' : `#${Number(raw.bg).toString(16)}`
41+
const fg = raw.number === selected_row_idx.value ? 'white' : `#${Number(raw.fg).toString(16)}`
42+
return {
43+
style: `background: ${bg}; color: ${fg};cursor: pointer;`,
44+
onClick: () => selected_row_idx.value = raw.number
45+
}
46+
}
47+
48+
const initialized = ref(false)
49+
const processed = ref(false)
50+
const loading = ref(true)
51+
const summary = ref({
52+
start_time: 0,
53+
stop_time: 0,
54+
total_packets: 0,
55+
})
56+
const start_time = computed(() => {
57+
return new Date(summary.value.start_time * 1000)
58+
})
59+
const stop_time = computed(() => {
60+
return new Date(summary.value.stop_time * 1000)
61+
})
62+
63+
const columns = ref([] as Record<string, any>)
64+
const column_width = {
65+
'No.': 60,
66+
'Source': 150,
67+
'Destination': 150,
68+
default: 100,
69+
} as Record<string, number>
70+
const data_source = ref([] as Record<string, any>)
71+
const worker = new Worker(new URL('~/utils/wireshark.worker.ts', import.meta.url), {
72+
type: 'module',
73+
})
74+
75+
const fileChangeHandler = async (ev: Event) => {
76+
const f = (ev.target as HTMLInputElement).files?.[0]
77+
if (!f) return window.alert('文件不存在')
78+
processed.value = false
79+
const buf = await f?.arrayBuffer()
80+
worker.postMessage({
81+
type: 'process:buffer',
82+
data: buf,
83+
}, [buf])
84+
}
85+
86+
const page_index = ref(1)
87+
const page_size = ref(10)
88+
const pagination = computed(() => ({
89+
total: summary.value.total_packets,
90+
current: page_index.value,
91+
pageSize: page_size.value,
92+
}))
93+
const fetchTableData = async () => {
94+
loading.value = true
95+
const { port1, port2 } = new MessageChannel()
96+
port1.onmessage = ev => {
97+
const { data } = ev.data
98+
data_source.value = map(data?.frames, (f: any) => {
99+
return reduce(columns.value, (acc: Record<string, any>, col: Record<string, any>, idx: any) => {
100+
acc[col.dataIndex] = get(f, ['columns', idx])
101+
return acc
102+
}, {
103+
raw: f,
104+
})
105+
})
106+
port1.onmessage = null
107+
port1.close()
108+
port2.close()
109+
loading.value = false
110+
}
111+
worker.postMessage({
112+
type: 'select-frames',
113+
skip: (page_index.value - 1) * page_size.value,
114+
limit: page_size.value,
115+
filter: filter_input.value,
116+
}, [port2])
117+
}
118+
const tableChangeHandler = (page: { pageSize: number, current: number }) => {
119+
page_index.value = page.current
120+
page_size.value = page.pageSize
121+
fetchTableData()
122+
}
123+
124+
async function fetchPacketDetail() {
125+
if (!processed.value) return
126+
worker.postMessage({
127+
type: 'select',
128+
number: selected_row_idx.value,
129+
})
130+
}
131+
watchEffect(() => fetchPacketDetail())
132+
133+
const filter_input = ref('')
134+
const filter_ret = ref(true)
135+
const filterChangeHandler = () => {
136+
if (!processed.value) return
137+
const { port1, port2 } = new MessageChannel()
138+
port1.onmessage = ev => {
139+
filter_ret.value = ev.data.data
140+
if (filter_ret.value === true) fetchTableData()
141+
}
142+
worker.postMessage({
143+
type: 'check-filter',
144+
data: filter_input.value,
145+
}, [port2])
146+
}
147+
watchEffect(() => filterChangeHandler())
148+
149+
interface Tree {
150+
label: string;
151+
[key: string]: any;
152+
}
153+
const processPacketTree = (tree: Tree[], id_prefix = ''): Tree[] => map(tree, (t: Tree, idx: number) => {
154+
const id = `${id_prefix}${idx}`
155+
return {
156+
...t,
157+
id,
158+
tree: processPacketTree(t.tree, `${id}-`)
159+
}
160+
})
161+
162+
const MESSAGE_STRATEGIES = {
163+
init: ev => {
164+
initialized.value = true
165+
loading.value = false
166+
worker.postMessage({ type: 'columns' })
167+
},
168+
columns: ev => {
169+
const cols = ev.data.data
170+
const end = cols.length - 1
171+
columns.value = map(cols, (c: string, idx: number) => {
172+
return {
173+
title: c,
174+
dataIndex: c,
175+
width: idx < end ? (column_width[c] || column_width['default']) : undefined,
176+
}
177+
})
178+
},
179+
processed: ev => {
180+
const { summary: _summary } = ev.data.data
181+
summary.value = {
182+
start_time: _summary.start_time,
183+
stop_time: _summary.stop_time,
184+
total_packets: _summary.packet_count,
185+
}
186+
processed.value = true
187+
fetchTableData()
188+
selected_row_idx.value = 1
189+
},
190+
selected: ev => {
191+
selected_packet_tree.value = processPacketTree(ev.data.data?.tree)
192+
}
193+
} as Record<string, (ev: MessageEvent<MessageData>) => void>
194+
195+
worker.addEventListener('message', (ev: MessageEvent) => {
196+
const type = ev.data.type as string
197+
MESSAGE_STRATEGIES[type]?.(ev)
198+
})
199+
</script>
200+
<style lang="scss" scoped>
201+
.packet-table {
202+
:deep(.ant-table-tbody > tr > td.ant-table-cell-row-hover),
203+
:deep(.ant-table-tbody > tr.ant-table-row:hover > td) {
204+
background: transparent;
205+
}
206+
207+
:deep(.ant-table.ant-table-small .ant-table-tbody > tr > td) {
208+
padding: 0;
209+
}
210+
211+
:deep(.ant-table-pagination.ant-pagination) {
212+
margin: 5px 0;
213+
}
214+
}
215+
</style>

plugins/antd.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { defineNuxtPlugin } from "#app";
2+
import 'ant-design-vue/dist/antd.css';
3+
import Antd from 'ant-design-vue';
4+
5+
export default defineNuxtPlugin((nuxtApp) => {
6+
nuxtApp.vueApp.use(Antd)
7+
})

0 commit comments

Comments
 (0)