diff --git a/.nx/cache/18.3.4-nx.win32-x64-msvc.node b/.nx/cache/18.3.4-nx.win32-x64-msvc.node new file mode 100644 index 0000000..4981b20 Binary files /dev/null and b/.nx/cache/18.3.4-nx.win32-x64-msvc.node differ diff --git a/angular.json b/angular.json index e59acb9..5104365 100644 --- a/angular.json +++ b/angular.json @@ -43,6 +43,13 @@ }, "configurations": { "production": { + "optimization": false, + "sourceMap": true, + "namedChunks": false, + "aot": false, + "extractLicenses": false, + "vendorChunk": false, + "buildOptimizer": false, "budgets": [ { "type": "initial", diff --git a/src/app/components/controls/custom-table/custom-table.component.html b/src/app/components/controls/custom-table/custom-table.component.html index 2f5040a..06f7564 100644 --- a/src/app/components/controls/custom-table/custom-table.component.html +++ b/src/app/components/controls/custom-table/custom-table.component.html @@ -17,7 +17,7 @@ class="this-table" > @@ -37,16 +37,16 @@ - {{ element[item] }} + {{ element[getKeyBy(item)] }} - + val === value)?.[0] : value; + } selectedRowIndex: number = -1; // -1 is selected nothings @Output() rowClick: EventEmitter = new EventEmitter(); diff --git a/src/app/components/controls/flexible-chart/flexible-chart.component.html b/src/app/components/controls/flexible-chart/flexible-chart.component.html index bca392e..ac0b411 100644 --- a/src/app/components/controls/flexible-chart/flexible-chart.component.html +++ b/src/app/components/controls/flexible-chart/flexible-chart.component.html @@ -1,10 +1,12 @@ -
- + + #canvas + >
diff --git a/src/app/components/controls/flexible-chart/flexible-chart.component.ts b/src/app/components/controls/flexible-chart/flexible-chart.component.ts index be9eb4d..9d093da 100644 --- a/src/app/components/controls/flexible-chart/flexible-chart.component.ts +++ b/src/app/components/controls/flexible-chart/flexible-chart.component.ts @@ -25,13 +25,16 @@ export interface ChartData { }) export class FlexibleChartComponent implements OnInit, AfterViewInit, OnDestroy { exampleData: ChartData[] = []; + @Input() globalMinMax: boolean = false mousePosition: any = { inside: false, mousedown: false, x: 0, y: 0 }; + @Input() fullToolTip = false; @Input() isRange = true; + @Input() startXAxisNumber = 0; @Input() options: any = { axisX: false, axisY: false @@ -89,7 +92,7 @@ export class FlexibleChartComponent implements OnInit, AfterViewInit, OnDestroy // const { layerX = 0, layerY = 0 }:any = {}; const { offsetWidth, offsetHeight, offsetLeft, offsetTop } = this.canvas.nativeElement; const dX = layerX - offsetLeft; - const dY = layerX - offsetTop; + const dY = layerX - offsetTop; switch (event.type) { case 'mousemove': @@ -152,10 +155,10 @@ export class FlexibleChartComponent implements OnInit, AfterViewInit, OnDestroy const index = Math.ceil((this.mousePosition.x) / stepX); return index; } - tooltip(ctx: any, text: string) { + tooltip(ctx: any, text: string, index = 0) { const padding = 4; ctx.font = `14px monospace`; - const textElement = ctx.measureText(text); + const textElement = ctx.measureText(text.split('\n')[0]); const w = textElement.width; const x = Math.max(0, Math.min( ctx.canvas.clientWidth - w - padding * 2, @@ -165,10 +168,13 @@ export class FlexibleChartComponent implements OnInit, AfterViewInit, OnDestroy ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; ctx.fillStyle = 'black'; - this.drawText(ctx, text, { - x, y, - bgColor: 'rgba(255, 255, 255, 0.8)', - color: 'black' + text.split('\n').forEach((line, k) => { + + this.drawText(ctx, line, { + x, y: y + k * 20, + bgColor: 'rgba(255, 255, 255, 0.8)', + color: 'black' + }) }) } @@ -206,18 +212,38 @@ export class FlexibleChartComponent implements OnInit, AfterViewInit, OnDestroy ctx.lineWidth = 1; } draw(ctx: any) { + + let minData = Number.MAX_VALUE, maxData = Number.MIN_VALUE; + if (this.globalMinMax) { + this.exampleData.forEach(({ data }: any) => { + minData = Math.min(...data, minData, 0); + maxData = Math.max(...data, maxData); + }); + } this.exampleData.forEach(({ data, color, typeOfChart }: any) => { - this.drawChart(ctx, data, color, typeOfChart); + this.drawChart(ctx, data, color, typeOfChart, [minData, maxData]); }) if (this.isRange) { this.drawRange(ctx); if (this.mousePosition.inside) { this.drawTarget(ctx); - this.tooltip(ctx, JSON.stringify( - !this.mousePosition.mousedown ? - this.getIndexOfData() : - this.сalcRangeByData())); + if (this.fullToolTip) { + + const idx = this.getIndexOfData() - 1; + console.log({ exampleData: this.exampleData }); + this.tooltip(ctx, + + this.exampleData.map(i => i.data[idx] ? `${i.name}: ${i.data[idx]}` : '').filter(i => !!i).join('\n') + + ) + } else { + + this.tooltip(ctx, JSON.stringify( + !this.mousePosition.mousedown ? + this.getIndexOfData() : + this.сalcRangeByData())); + } } } @@ -282,7 +308,7 @@ export class FlexibleChartComponent implements OnInit, AfterViewInit, OnDestroy ctx.moveTo(x, y - 5); ctx.lineTo(x, y + 3); ctx.stroke(); - this.drawText(ctx, `${(data.length * (i + 1) / L).toFixed(0)}`, { + this.drawText(ctx, `${(this.startXAxisNumber + data.length * (i + 1) / L).toFixed(0)}`, { x: x - 10, y: y, bgColor: 'rgba(255, 255, 255, 0)', @@ -307,11 +333,21 @@ export class FlexibleChartComponent implements OnInit, AfterViewInit, OnDestroy ctx.stroke(); } } - drawChart(ctx: any, data: any[], color = 'rgba(0,255,255,0.5)', type: TypeOfChart = this.typeOfChart) { + drawChart(ctx: any, data: any[], color = 'rgba(0,255,255,0.5)', type: TypeOfChart = this.typeOfChart, [min, max]: any) { data = Functions.cloneObject(data); const pudding = { x: 10, y: this.options.axisX ? 40 : 0 }; // px - const minY = Math.min(...data, 0); - const maxY = Math.max(...data); + let minY; + let maxY; + if (this.globalMinMax) { + minY = min; + maxY = max; + + } else { + + minY = Math.min(...data, 0); + maxY = Math.max(...data); + } + let stepX = ((ctx.canvas.offsetWidth - pudding.x * 2) / (data.length)); const stepY = ((ctx.canvas.offsetHeight - pudding.y * 2) / maxY); @@ -380,7 +416,7 @@ export class FlexibleChartComponent implements OnInit, AfterViewInit, OnDestroy const c = this.canvas.nativeElement; c.width = `${c.offsetWidth}`; - c.height = `${c.offsetHeight}`; + c.height = `${c.offsetHeight - 5}`; if (c.getContext) { var ctx = c.getContext('2d'); diff --git a/src/app/components/controls/tap/tap-rtp-streams/stream-detail/stream-detail.component.html b/src/app/components/controls/tap/tap-rtp-streams/stream-detail/stream-detail.component.html new file mode 100644 index 0000000..adc1b6c --- /dev/null +++ b/src/app/components/controls/tap/tap-rtp-streams/stream-detail/stream-detail.component.html @@ -0,0 +1,161 @@ + +
+ +
+
+ CurrentTime: + {{ rec.player?.getCurrentTime() || 0 | number : "1.3-3" }}s +
+
Duration: {{ rec.player?.getDuration() || 0 }}
+
PlaybackRate: {{ rec.player?.getPlaybackRate() || 0 }}
+
+
+
+

Audio file is empty (size : 0b)

+
+
+ + + + + + + +
+
+
+ + + + + + + +
+
+ +
+
+ {{ item.title }} +
+
+
+ + Line + Bar + Area + + + +
+
+
+ + + + +
+
diff --git a/src/app/components/controls/tap/tap-rtp-streams/stream-detail/stream-detail.component.scss b/src/app/components/controls/tap/tap-rtp-streams/stream-detail/stream-detail.component.scss new file mode 100644 index 0000000..489506b --- /dev/null +++ b/src/app/components/controls/tap/tap-rtp-streams/stream-detail/stream-detail.component.scss @@ -0,0 +1,18 @@ +.player-card-data { + display: flex; + & > div { + padding: 0.5rem; + } +} +.top-container { + display: flex; + margin-bottom: 3rem; + flex-direction: column; + // position: sticky; + top: 0; + z-index: 1; +} + +.labels ::ng-deep .mdc-checkbox { + display: none !important; +} diff --git a/src/app/components/controls/tap/tap-rtp-streams/stream-detail/stream-detail.component.ts b/src/app/components/controls/tap/tap-rtp-streams/stream-detail/stream-detail.component.ts new file mode 100644 index 0000000..e791cee --- /dev/null +++ b/src/app/components/controls/tap/tap-rtp-streams/stream-detail/stream-detail.component.ts @@ -0,0 +1,349 @@ +import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { WebSharkDataService } from '@app/services/web-shark-data.service'; +import { hash } from '@app/helper/functions'; +import WaveSurfer from 'wavesurfer.js'; +import TimelinePlugin from 'wavesurfer.js/dist/plugins/timeline'; +import { TypeOfChart } from '@app/components/controls/flexible-chart/flexible-chart.component'; + +declare const transcode: Function; +const DATA_TYPE = 'application/octet-stream'; + +@Component({ + selector: 'stream-detail', + templateUrl: './stream-detail.component.html', + styleUrls: ['./stream-detail.component.scss'] +}) +export class StreamDetailComponent implements OnInit { + + // constructor() { } + + // ngOnInit() { + // } + onClose() { + console.log('StreamDetailComponent::onClose'); + this.close.emit({}) + } + + lastRange: any; + _data: any; + columns: any; + streams: any[] = []; + selectedStreams: any; + selectedColumns: any; + + players: any[] = []; + optionsAudioContainer = { + // waveColor: '#D2EDD4', + normalize: true, + progressColor: 'green', + responsive: true, + minPxPerSec: 100, + + } + @Input() rec: any; + + @Input() set data(val: any) { + this._data = val; + this.streams = val.streams || []; + const [stream] = this.streams || []; + this.columns = Object.keys(stream || []); + } + @Output() close: EventEmitter = new EventEmitter(); + get data(): any { + return this._data; + } + typeOfChartRadio:TypeOfChart = 'bar' + isReady = false; + progressMessage = ['Initialization']; + chartData: any[] = []; + rangeChartData: any[] = []; + audioStreamsBlobURL: any[] = []; + columnDictionary: any = { + bw: 'IP BW (kbps)', + d: 'Delta', + f: 'Packet (Time)', + j: 'Jitter', + mark: 'Marker', + o: 'o', + s: 'Status', + sk: 'Skew (ms)', + sn: 'Sequence', + t: 't', + }; + + chartFilter = Object.entries(this.columnDictionary).map(([key, val]: any) => ({ + title: val, + index: key, + color: '#' + hash(key, 3), + value: true + })) + columnsDictionary: any = Object.values(this.columnDictionary); + constructor( + private webSharkDataService: WebSharkDataService, + private cdr: ChangeDetectorRef + ) { } + get titleId() { + const [tap] = this.rec.rowData.taps; + // console.log( + // tap + // ) + const [sip, sport, dip, dport, ssrc] = (tap.tap || '').split(/[_]+/g); + return `${ssrc} - ${sip}:${sport} -> ${dip}:${dport}` + } + get captureFile() { + return this.webSharkDataService.getCapture(); + } + async getFileArrayOfUint8Array(filename: string) { + const href = encodeURIComponent('/' + filename); + const url = `/webshark/json?method=download&capture=${href}&token=self`; + const { body } = await fetch(url); + const reader = body?.getReader(); + if (!reader) { + console.error(new Error('Could not get file')); + return new Blob([], { type: DATA_TYPE }); + } + let isDone = false; + const collectArrayData = []; + + while (!isDone) { + const { value, done } = await reader.read(); + if (value) { + collectArrayData.push(value); + } + isDone = done; + } + + // console.log({ collectArrayData }); + return new Blob(collectArrayData, { type: DATA_TYPE }); + + } + // blobSaveAsFile(blobUrl: string, filename: string) { + // var link = document.createElement("a"); // Or maybe get it from the current document + // link.href = blobUrl; + // link.download = filename; + // link.innerHTML = "Click here to download the file"; + // document.body.appendChild(link); + // } + /** + * parse PCAP to rtp-strems binnary data + * FFMPEG + */ + async ffmpegDecoder() { + this.progressMessage.push(`Reading data from ${this.captureFile} file`); + this.cdr.detectChanges(); + const blobData: Blob = await this.getFileArrayOfUint8Array(this.captureFile); + // console.log(this.captureFile, { blobData }); + // const blobUrl = await transcode(blobData); + // console.log(blobUrl) + + this.progressMessage.push('Separate PCAP to frames'); + this.cdr.detectChanges(); + + const index = await this.webSharkDataService.getFrames(0); + let offset = 24; + const arrOffset = index.map((i: any, k: number, arr: any[]) => { + if (k > 0) { + offset += +arr[k - 1].c[5]; + } + offset += 16; + return [offset, ...i.c, blobData.slice(offset, offset + +i.c[5], DATA_TYPE)]; + }) + // console.log(arrOffset); + this.progressMessage.push('Collect payload binary to streams') + this.cdr.detectChanges(); + // rtp-streams + const { taps: [{ streams: rtpStreams }] } = await this.webSharkDataService.getTapJson('rtp-streams'); + // console.log(rtpStreams); + + const arr = rtpStreams.map((streamData: any) => { + return { + ssrc: streamData.ssrc, + data: streamData, + blob: new Blob(arrOffset + .filter((frame: any) => frame[7].toUpperCase().includes(`SSRC=${streamData.ssrc.toUpperCase()}`)) + .map((i: any) => (i[8] as Blob).slice(54)), { type: DATA_TYPE }) + }; + }) + // console.log({ arr }) + const codecDictionary: any = { + 'g711a': 'alaw', + 'g711u': 'mulaw', + 'g722': 'g722', + }; + const out: any[] = []; + for (let item of arr) { + // console.log('<>>>>', item.data); + + const codec = codecDictionary[(item.data.payload + '').toLowerCase()] || 'g722'; + // console.log('<>>>>', {codec}); + + // const i = arr[0]; + this.progressMessage.push(`FFmpeg:: converting ${item.ssrc} stream to audio (mp3)`); + this.cdr.detectChanges(); + const blobUrl = await transcode(item.blob, codec, `audio-${item.ssrc}.mp3`); + // console.log(blobUrl) + + out.push({ ssrc: item.ssrc, blobUrl }); + // this.blobSaveAsFile(blobUrl, `audio-${item.ssrc}.mp3`); + } + + return out; + } + getPlayer(rec: any) { + /** + * rec: { id, mp3, player } + */ + if (!rec.player) { + try { + // rec.mp3 = 'http://localhost:8003/assets/we__will_rock_you.mp3' + const player = WaveSurfer.create({ + ...this.optionsAudioContainer, + container: '#' + (rec.id || 'audio-player'), + plugins: [TimelinePlugin.create({ + /** The duration of the timeline in seconds, defaults to wavesurfer's duration */ + // duration: 1, + /** Interval between ticks in seconds */ + timeInterval: 0.1, + /** Interval between numeric labels in seconds */ + primaryLabelInterval: 1, + /** Interval between secondary numeric labels in seconds */ + // secondaryLabelInterval: 8, + })] + + }); + if (rec.mp3) { + // this.webSharkDataService.getBLOB(rec.mp3).subscribe((data: any) => { + // const data = {} + // console.log({ data }) + // if (!data || data.size === 0) { + // rec.noData = true; + // this.cdr.detectChanges(); + // } else { + player.load(rec.mp3); + player.on('ready', () => { + player.zoom(1); + player.on('audioprocess', (event: any) => { + /** + * needs to sync the playback with table stream data + */ + // console.log(event); + this.cdr.detectChanges(); + }); + this.cdr.detectChanges(); + }); + // } + // }); + } else { + rec.noData = true; + this.cdr.detectChanges(); + } + rec.player = player; + + + } catch (err) { + console.log(err, rec); + } + } + return rec; + + } + async ngOnInit() { + + this.audioStreamsBlobURL = await this.ffmpegDecoder(); + this.progressMessage.push('done.'); + this.cdr.detectChanges(); + requestAnimationFrame(() => { + + this.isReady = true; + this.setRecActive(); + this.cdr.detectChanges(); + }) + + } + + setRecActive() { + + console.log('setRecActive()', this.rec); + // this.players.forEach(p => p.isActive = p.id === id); + let playerElement = this.rec; + const [tap]: any = playerElement.rowData?.taps || []; + this.selectedStreams = tap?.items || []; + const [item] = this.selectedStreams; + this.selectedColumns = ["bw", "d", "f", "j", "mark", "o", "s", "sk", "sn", "t"]; // Object.keys(item); + this.chartData = this.getChartData(); + this.onRange([0, 0]); + this.cdr.detectChanges(); + } + playItemClick(event: any) { + console.log({ event }); + } + onZoomAudio(event: any, player: any) { + // console.log(event.wheelDelta); + if (!player._myZoom) { + player._myZoom = 1; + } + player._myZoom += event.wheelDelta / 12 + player._myZoom = Math.max(1, player._myZoom); + player.zoom(player._myZoom); + this.cdr.detectChanges(); + + } + onRange([a, b]: any) { + const start = Math.min(a, b); + const end = Math.max(a, b); + + console.log({ start, end }); + this.lastRange = { start, end }; + this.rangeChartData = []; + + this.chartData.forEach(item => { + const out = Object.assign({}, item); + if (start >= 0 && end >= 0 && start != end) { + out.data = out.data.slice(start, end); + } else { + out.data = out.data; + } + this.rangeChartData.push(out) + + }) + } + getChartData() { + /** + * format have to be like this: + * [ + { data: [1, 3, 2, 4, 5, 4, 3, 2] }, + { data: [2, 4, 5, 4, 3, 2], typeOfChart: 'area', color: 'red' }, + . . . + { data: [5, 4, 3, 2, 1, 4] } + ] + */ + // this.columnDictionary + const outData: any[] = []; + this.selectedColumns.forEach((column: string) => { + if (this.chartFilter.find(i => i.index == column)?.value) { + + const d = { + color: '#' + hash(column, 3), + name: this.columnDictionary[column], + data: this.selectedStreams.map((stream: any) => stream[column]) + }; + outData.push(d) + } + }) + console.log({ selectedStreams: this.selectedStreams }); + return outData; + + } + onFilterChart() { + console.log({ f: 'onFilterChart', d: this.chartFilter }) + this.chartData = this.getChartData(); + if (this.lastRange) { + const { start, end } = this.lastRange; + this.onRange([start, end]); + } else { + this.onRange([]); + } + this.cdr.detectChanges(); + } + +} diff --git a/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.html b/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.html index ba96da5..d9000df 100644 --- a/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.html +++ b/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.html @@ -1,16 +1,38 @@ - - - - - +
+
+
+ +
+
+ +

{{ col }}:

+ {{ item[col] }} +
+
+
+ + + + + +
  • {{ item }} diff --git a/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.scss b/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.scss index cb21db5..a5f87df 100644 --- a/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.scss +++ b/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.scss @@ -1,16 +1,46 @@ -@use 'sass:map'; -@use '@angular/material' as mat; +@use "sass:map"; +@use "@angular/material" as mat; @import "/src/theme.scss"; $primary: map.get($my-theme, primary); - +$bgColor: rgba(black, 0.12); :host { + .table-row-wrapper { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + // justify-content: space-between; + margin: 5px; + /* border: 2px solid red; */ + border-radius: 0.5rem; + box-shadow: 1px 1px 8px -3px #000; + &:hover { + background-color: $bgColor; + cursor: pointer; + } + .table-row { + width: 200px; + min-width: 200px; + padding: 0.5rem; + background: rgba(0, 0, 0, 0.05); + margin: 4px; + border-radius: 4px; + // border: 1px solid #999; + > h3 { + display: inline-block; + font-weight: 500; + line-height: initial; + letter-spacing: inherit; + margin: 0; + } + } + } .is-data-wrapper { width: 100%; display: block; } .player-card-data { display: flex; - &>div{ + & > div { padding: 0.5rem; } } @@ -50,7 +80,7 @@ $primary: map.get($my-theme, primary); opacity: 0; transition: opacity 0.3s; z-index: 10; - >* { + > * { margin: 0.3rem; } } diff --git a/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.ts b/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.ts index 101465d..ec656ad 100644 --- a/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.ts +++ b/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.component.ts @@ -51,6 +51,9 @@ export class TapRtpStreamsComponent implements OnInit { get captureFile() { return this.webSharkDataService.getCapture(); } + getKeys(obj: any) { + return Object.keys(obj).filter(i => i !== '__selected'); + } async getFileArrayOfUint8Array(filename: string) { const href = encodeURIComponent('/' + filename); const url = `/webshark/json?method=download&capture=${href}&token=self`; @@ -75,13 +78,13 @@ export class TapRtpStreamsComponent implements OnInit { return new Blob(collectArrayData, { type: DATA_TYPE }); } - blobSaveAsFile(blobUrl: string, filename: string) { - var link = document.createElement("a"); // Or maybe get it from the current document - link.href = blobUrl; - link.download = filename; - link.innerHTML = "Click here to download the file"; - document.body.appendChild(link); - } + // blobSaveAsFile(blobUrl: string, filename: string) { + // var link = document.createElement("a"); // Or maybe get it from the current document + // link.href = blobUrl; + // link.download = filename; + // link.innerHTML = "Click here to download the file"; + // document.body.appendChild(link); + // } /** * parse PCAP to rtp-strems binnary data * FFMPEG @@ -142,7 +145,7 @@ export class TapRtpStreamsComponent implements OnInit { // console.log(blobUrl) out.push({ ssrc: item.ssrc, blobUrl }); - this.blobSaveAsFile(blobUrl, `audio-${item.ssrc}.mp3`); + // this.blobSaveAsFile(blobUrl, `audio-${item.ssrc}.mp3`); } return out; @@ -153,7 +156,6 @@ export class TapRtpStreamsComponent implements OnInit { */ if (!rec.player) { try { - // rec.mp3 = 'http://localhost:8003/assets/we__will_rock_you.mp3' const player = WaveSurfer.create({ ...this.optionsAudioContainer, container: '#' + (rec.id || 'audio-player'), @@ -217,8 +219,15 @@ export class TapRtpStreamsComponent implements OnInit { }) } + onClosePlayer(idx: any) { + // console.log({idx}) + this.players = this.players.filter((i, k) => k !== idx); + this.cdr.detectChanges(); + } async rowClick({ row }: any) { - // console.log({ row }); + // this.streams + console.log(this.streams, { row }); + const id = `player-${hash(JSON.stringify(row))}`; let playerElement = this.players.find(p => p.id === id); if (!playerElement) { @@ -233,10 +242,7 @@ export class TapRtpStreamsComponent implements OnInit { rowData, }; - - - - setTimeout(() => { + requestAnimationFrame(() => { playerElement = this.getPlayer(playerElement); this.cdr.detectChanges(); }); diff --git a/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.module.ts b/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.module.ts index 696632b..3120e49 100644 --- a/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.module.ts +++ b/src/app/components/controls/tap/tap-rtp-streams/tap-rtp-streams.module.ts @@ -8,9 +8,18 @@ import { TapRtpStreamsComponent } from './tap-rtp-streams.component'; import { CustomTableModule } from '../../custom-table/custom-table.module'; import { ScrollingModule } from '@angular/cdk/scrolling'; import { NoDataModule } from '../../no-data/no-data.module'; +import { StreamDetailComponent } from './stream-detail/stream-detail.component'; +import { ModalResizableModule } from '../../modal-resizable/modal-resizable.module'; +import { MatTabsModule } from '@angular/material/tabs'; +import { FlexibleChartModule } from '../../flexible-chart/flexible-chart.module'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import {MatRadioModule} from '@angular/material/radio'; @NgModule({ imports: [ + FormsModule, + ReactiveFormsModule, CommonModule, CustomTableModule, AngularSplitModule, @@ -18,9 +27,18 @@ import { NoDataModule } from '../../no-data/no-data.module'; MatIconModule, MatButtonModule, ScrollingModule, - NoDataModule + NoDataModule, + ModalResizableModule, + MatTabsModule, + FlexibleChartModule, + MatCheckboxModule, + MatRadioModule + ], + declarations: [ + // ModalResizableComponent, + StreamDetailComponent, + TapRtpStreamsComponent ], - declarations: [TapRtpStreamsComponent], exports: [TapRtpStreamsComponent] }) export class TapRtpStreamsModule { } diff --git a/src/app/components/controls/webshark/webshark.component.html b/src/app/components/controls/webshark/webshark.component.html index 7787a9a..723381d 100644 --- a/src/app/components/controls/webshark/webshark.component.html +++ b/src/app/components/controls/webshark/webshark.component.html @@ -1,6 +1,9 @@
    -
    +
    @@ -22,7 +25,8 @@

    Files

    font-size: 40px; width: 40px; height: 40px; - ">notenote
    {{ note.name }} @@ -38,60 +42,69 @@

    Files

    *ngIf="!(!(dataTree.length > 0) && !(detailsTable.length > 0))" [noDataIf]="!(dataTree.length > 0) && !(detailsTable.length > 0)" > - - - -
    - - -
    -
    - - - - -

    - No Data -

    -
    - - - -
    -
    -
    +
    + + + +
    + + +
    +
    + + + + +

    + No Data +

    +
    + + + +
    +
    +
    +