xref: /aosp_15_r20/tools/netsim/ui/ts/packet-info.ts (revision cf78ab8cffb8fc9207af348f23af247fb04370a6)
1import {css, html, LitElement} from 'lit';
2import {customElement, property} from 'lit/decorators.js';
3
4import {Device, Notifiable, SimulationInfo, simulationState,} from './device-observer.js';
5import {Capture} from './netsim/model.js';
6
7@customElement('ns-packet-info')
8export class PacketInformation extends LitElement implements Notifiable {
9  /**
10   * List of captures currently on the netsim.
11   */
12  @property() captureData: Capture[] = [];
13
14  /**
15   * List of devices currently on the netsim.
16   */
17  @property() deviceData: Device[] = [];
18
19  static styles = css`
20    :host {
21      display: flex;
22      justify-content: center;
23      align-items: flex-start;
24      height: 100vh;
25    }
26
27    .panel {
28      cursor: pointer;
29      display: grid;
30      place-content: center;
31      color: black;
32      font-size: 25px;
33      font-family: 'Lato', sans-serif;
34      border: 5px solid black;
35      border-radius: 12px;
36      margin: 10px;
37      padding: 10px;
38      background-color: #ffffff;
39      max-width: max-content;
40      float: left;
41    }
42
43    .title {
44      font-weight: bold;
45      text-transform: uppercase;
46      text-align: center;
47      margin-bottom: 10px;
48    }
49
50    .label {
51      text-align: left;
52    }
53
54    .styled-table {
55      border-collapse: collapse;
56      margin: 25px 0;
57      font-size: 20px;
58      font-family: sans-serif;
59      width: 100%;
60      box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
61    }
62
63    .styled-table thead tr {
64      background-color: #009879;
65      color: #ffffff;
66      text-align: left;
67    }
68
69    .styled-table th,
70    .styled-table td {
71      padding: 12px 15px;
72      text-align: left;
73    }
74
75    .styled-table tbody tr {
76      border-bottom: 1px solid #dddddd;
77    }
78
79    .styled-table tbody tr:nth-of-type(even) {
80      background-color: #cac0c0;
81    }
82
83    input[type='button'] {
84      height: 2rem;
85      font-size: inherit;
86    }
87
88    input[type='checkbox'].switch_1 {
89      font-size: 30px;
90      -webkit-appearance: none;
91      -moz-appearance: none;
92      appearance: none;
93      width: 3.5em;
94      height: 1.5em;
95      background: #ddd;
96      border-radius: 3em;
97      position: relative;
98      cursor: pointer;
99      outline: none;
100      -webkit-transition: all 0.2s ease-in-out;
101      transition: all 0.2s ease-in-out;
102    }
103
104    input[type='checkbox'].switch_1:checked {
105      background: #0ebeff;
106    }
107
108    input[type='checkbox'].switch_1:after {
109      position: absolute;
110      content: '';
111      width: 1.5em;
112      height: 1.5em;
113      border-radius: 50%;
114      background: #fff;
115      -webkit-box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.3);
116      box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.3);
117      -webkit-transform: scale(0.7);
118      transform: scale(0.7);
119      left: 0;
120      -webkit-transition: all 0.2s ease-in-out;
121      transition: all 0.2s ease-in-out;
122    }
123
124    input[type='checkbox'].switch_1:checked:after {
125      left: calc(100% - 1.5em);
126    }
127
128    button {
129      display: inline-block;
130      padding: 12px 24px;
131      background-color: #4CAF50;
132      color: #FFFFFF;
133      font-size: 18px;
134      font-weight: bold;
135      text-align: center;
136      text-decoration: none;
137      border: none;
138      cursor: pointer;
139      transition: background-color 0.3s ease;
140    }
141
142    button:hover {
143      background-color: #45a049;
144      transition: 0.5s;
145    }
146  `;
147
148  connectedCallback() {
149    super.connectedCallback();  // eslint-disable-line
150    simulationState.registerObserver(this);
151  }
152
153  disconnectedCallback() {
154    simulationState.removeObserver(this);
155    super.disconnectedCallback();  // eslint-disable-line
156  }
157
158  onNotify(data: SimulationInfo) {
159    this.captureData = data.captures;
160    this.deviceData = data.devices;
161    this.requestUpdate();
162  }
163
164  toggleCapture(capture: Capture) {
165    let id = capture.id.toString();
166    let state = capture.state ? '0' : '1';
167    simulationState.patchCapture(id, state);
168  }
169
170  private handleGetChips(device: Device) {
171    let btTable = html``;
172    let uwbTable = html``;
173    let wifiTable = html``;
174    if ('chips' in device && device.chips) {
175      for (const chip of device.chips) {
176        if ('bt' in chip && chip.bt) {
177          let bleTable = html``;
178          let bclassicTable = html``;
179          if ('lowEnergy' in chip.bt && chip.bt.lowEnergy) {
180            bleTable = html`
181              <tr>
182                <td>BLE</td>
183                <td>${chip.bt.lowEnergy.rxCount ?? 0}</td>
184                <td>${chip.bt.lowEnergy.txCount ?? 0}</td>
185              </tr>
186            `;
187          }
188          if ('classic' in chip.bt && chip.bt.classic) {
189            bclassicTable = html`
190              <tr>
191                <td>Bluetooth Classic</td>
192                <td>${chip.bt.classic.rxCount ?? 0}</td>
193                <td>${chip.bt.classic.txCount ?? 0}</td>
194              </tr>
195            `;
196          }
197          btTable = html`${bleTable} ${bclassicTable}`;
198        }
199        if ('uwb' in chip && chip.uwb) {
200          uwbTable = html`
201            <tr>
202              <td>UWB</td>
203              <td>${chip.uwb.rxCount ?? 0}</td>
204              <td>${chip.uwb.txCount ?? 0}</td>
205            </tr>
206          `;
207        }
208        if ('wifi' in chip && chip.wifi) {
209          wifiTable = html`
210            <tr>
211              <td>WIFI</td>
212              <td>${chip.wifi.rxCount ?? 0}</td>
213              <td>${chip.wifi.txCount ?? 0}</td>
214            </tr>
215          `;
216        }
217      }
218    }
219    return html`
220      ${btTable}
221      ${uwbTable}
222      ${wifiTable}
223    `;
224  }
225
226  private handleListCaptures(capture: Capture) {
227    return html`
228      <tr>
229        <td>${capture.deviceName}</td>
230        <td>${capture.chipKind}</td>
231        <td>${capture.size}</td>
232        <td>${capture.records}</td>
233        <td>
234        <input
235                type="checkbox"
236                class="switch_1"
237                .checked=${capture.state}
238                @click=${() => {
239      this.toggleCapture(capture);
240    }}
241              />
242        </td>
243        <td>
244          <a
245            href="./v1/captures/${capture.id}"
246            target="_blank"
247            type="application/vnd.tcpdump.pcap"
248            ><button>Download</button></a
249          >
250        </td>
251      </tr>
252    `
253  }
254
255  render() {
256    return html`
257      <div class="panel">
258        <div class="title">Packet Info</div>
259        ${this.deviceData.map(device => html`
260              <div class="label">${device.name}</div>
261              <table class="styled-table">
262                <tr>
263                  <th>Radio</th>
264                  <th>RX Count</th>
265                  <th>TX Count</th>
266                </tr>
267                ${this.handleGetChips(device)}
268              </table>
269            `)}
270      </div>
271      <div class="panel">
272        <div class="title">Packet Capture</div>
273        <table class="styled-table">
274          <tr>
275            <th>Device Name</th>
276            <th>Chip Kind</th>
277            <th>Bytes</th>
278            <th>Records</th>
279            <th>Capture State</th>
280            <th>Download Pcap</th>
281          </tr>
282          ${this.captureData.map(capture => this.handleListCaptures(capture))}
283        </table>
284      </div>
285    `;
286  }
287}
288