Skip to content

Commit 78dac88

Browse files
committed
feat(assets-view): implement sorting by last input and enhance asset data structure
Signed-off-by: Manuel Abascal <mjabascal10@gmail.com>
1 parent f7bb56e commit 78dac88

File tree

3 files changed

+140
-115
lines changed

3 files changed

+140
-115
lines changed

frontend/src/app/assets-discover/assets-view/assets-view.component.html

Lines changed: 111 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -110,130 +110,135 @@ <h5 class="card-title mb-0 text-uppercase label-header">
110110
<th class="font-weight-semibold cursor-pointer">
111111
Types
112112
</th>
113-
<th class="font-weight-semibold cursor-pointer">
114-
Last input
113+
<th (sort)="onSortBy($event)"
114+
[isSortable]="true"
115+
[sortEvent]="sortEvent"
116+
[sortable]="'lastInput'"
117+
appColumnSortable
118+
class="font-weight-semibold cursor-pointer">
119+
Last Input
115120
</th>
116121
<th class="font-weight-semibold cursor-pointer" style="width: 20px">
117122
Action
118123
</th>
119124
</tr>
120125
</thead>
121-
<tbody *ngIf="assets$ | async as assets">
122-
<ng-container *ngFor="let asset of assets; trackBy: trackByFn">
123-
<tr class="cursor-pointer" (click)="viwAgentDetail($event, asset)">
124-
<td class="text-center">
125-
<div class="d-flex justify-content-between align-items-center h-100 w-100 small-md-icon">
126-
<div class="list-icons span-small-icon">
127-
<i (click)="addToSelected($event, asset)"
128-
[ngClass]="isSelected(asset)?'icon-checkbox-checked':'icon-checkbox-unchecked'"
129-
[ngbTooltip]="'Add to selected'"
130-
class="cursor-pointer"
131-
container="body"
132-
tooltipClass="utm-tooltip-top"></i>
133-
</div>
134-
<app-assets-apply-type (applyTypeEvent)="getAssets()" (focus)="stopInterval($event)"
135-
[assets]="[asset.id]" [typeFormat]="'icon'"
136-
[type]="asset.assetType" class="ml-2">
137-
</app-assets-apply-type>
126+
<tbody>
127+
<ng-container *ngFor="let asset of assets; trackBy: trackByFn">
128+
<tr class="cursor-pointer" (click)="viwAgentDetail($event, asset)">
129+
<td class="text-center">
130+
<div class="d-flex justify-content-between align-items-center h-100 w-100 small-md-icon">
131+
<div class="list-icons span-small-icon">
132+
<i (click)="addToSelected($event, asset)"
133+
[ngClass]="isSelected(asset)?'icon-checkbox-checked':'icon-checkbox-unchecked'"
134+
[ngbTooltip]="'Add to selected'"
135+
class="cursor-pointer"
136+
container="body"
137+
tooltipClass="utm-tooltip-top"></i>
138+
</div>
139+
<app-assets-apply-type (applyTypeEvent)="getAssets()" (focus)="stopInterval($event)"
140+
[assets]="[asset.id]" [typeFormat]="'icon'"
141+
[type]="asset.assetType" class="ml-2">
142+
</app-assets-apply-type>
138143

139-
<app-asset-group-add (focus)="stopInterval($event)"
140-
(applyGroupEvent)="getAssets()" [assets]="[asset.id]"
141-
[group]="asset.group"
142-
[typeFormat]="'icon'" class="ml-2"></app-asset-group-add>
144+
<app-asset-group-add (focus)="stopInterval($event)"
145+
(applyGroupEvent)="getAssets()" [assets]="[asset.id]"
146+
[group]="asset.group"
147+
[typeFormat]="'icon'" class="ml-2"></app-asset-group-add>
143148

144-
<app-assets-apply-note (focus)="stopInterval($event)" [asset]="asset"
145-
class="ml-2"></app-assets-apply-note>
146-
</div>
147-
</td>
148-
<td>
149-
<app-asset-status [asset]="asset"></app-asset-status>
150-
</td>
151-
<td>
152-
{{ asset.displayName }}
153-
</td>
154-
<td>
155-
<div class="d-flex justify-content-start align-items-start flex-wrap w-100 h-100"
156-
*ngIf="asset.dataInputList && asset.dataInputList.length > 0">
149+
<app-assets-apply-note (focus)="stopInterval($event)" [asset]="asset"
150+
class="ml-2"></app-assets-apply-note>
151+
</div>
152+
</td>
153+
<td>
154+
<app-asset-status [asset]="asset"></app-asset-status>
155+
</td>
156+
<td>
157+
{{ asset.displayName }}
158+
</td>
159+
<td>
160+
<div class="d-flex justify-content-start align-items-start flex-wrap w-100 h-100"
161+
*ngIf="asset.dataInputList && asset.dataInputList.length > 0">
157162

158-
<span *ngFor="let dat of asset.dataInputList; trackBy: trackByDataInputFn"
159-
[ngClass]="isSourceConnected(asset, dat) ? 'bg-success-300' : 'bg-warning-800'"
160-
[ngbTooltip]="dataTypeDetail"
161-
placement="auto"
162-
container="body"
163-
class="badge p-1 border-1 m-1">
164-
{{ dat.dataType }}
165-
<i class="ml-2 text-white cursor-pointer" (click)="deleteDataType($event, dat)"
166-
*ngIf="!isSourceConnected(asset, dat)"
167-
[ngClass]="deleting.includes(dat.id) ? 'icon-spinner2 spinner':'icon-cross font-size-sm'">
168-
</i>
169-
<ng-template #dataTypeDetail>
170-
<div>
171-
<span
172-
[ngClass]="isSourceConnected(asset, dat)?'text-success':'text-warning-800'"
173-
class="span-small-icon d-flex justify-content-start align-items-center mt-2">
174-
<i
175-
[ngClass]="'icon-circle2'"
176-
class="mr-2"></i>
177-
<span class="flex-wrap" style="max-width: 280px">
178-
{{ dat.dataType + ' is ' + (isSourceConnected(asset, dat) ? 'up' : 'taking longer than usual to send logs') }}
179-
</span>
180-
</span>
181-
<span class="span-small-icon d-flex justify-content-start align-items-center mt-2 mb-2">
182-
<i
183-
[ngClass]="'icon-sort-time-desc'"
184-
class="mr-2"></i>
185-
<span>
186-
Last input: {{ formatTimestampToDate(dat.timestamp) | date }}
163+
<span *ngFor="let dat of asset.dataInputList; trackBy: trackByDataInputFn"
164+
[ngClass]="isSourceConnected(asset, dat) ? 'bg-success-300' : 'bg-warning-800'"
165+
[ngbTooltip]="dataTypeDetail"
166+
placement="auto"
167+
container="body"
168+
class="badge p-1 border-1 m-1">
169+
{{ dat.dataType }}
170+
<i class="ml-2 text-white cursor-pointer" (click)="deleteDataType($event, dat)"
171+
*ngIf="!isSourceConnected(asset, dat)"
172+
[ngClass]="deleting.includes(dat.id) ? 'icon-spinner2 spinner':'icon-cross font-size-sm'">
173+
</i>
174+
<ng-template #dataTypeDetail>
175+
<div>
176+
<span
177+
[ngClass]="isSourceConnected(asset, dat)?'text-success':'text-warning-800'"
178+
class="span-small-icon d-flex justify-content-start align-items-center mt-2">
179+
<i
180+
[ngClass]="'icon-circle2'"
181+
class="mr-2"></i>
182+
<span class="flex-wrap" style="max-width: 280px">
183+
{{ dat.dataType + ' is ' + (isSourceConnected(asset, dat) ? 'up' : 'taking longer than usual to send logs') }}
187184
</span>
188-
</span>
189-
</div>
190-
</ng-template>
191-
</span>
185+
</span>
186+
<span class="span-small-icon d-flex justify-content-start align-items-center mt-2 mb-2">
187+
<i
188+
[ngClass]="'icon-sort-time-desc'"
189+
class="mr-2"></i>
190+
<span>
191+
Last input: {{ formatTimestampToDate(dat.timestamp) | date }}
192+
</span>
193+
</span>
194+
</div>
195+
</ng-template>
196+
</span>
192197

193-
</div>
194-
</td>
195-
<td>
196-
{{ getLastInput(asset) }}
197-
</td>
198+
</div>
199+
</td>
200+
<td>
201+
{{ asset.lastInput | date }}
202+
</td>
198203

199-
<td class="td-action">
200-
<div
201-
class="d-flex justify-content-end align-items-center medium-icon">
204+
<td class="td-action">
205+
<div
206+
class="d-flex justify-content-end align-items-center medium-icon">
202207

203-
<i (click)="viwAgentDetail($event, asset)" class="icon-display cursor-pointer ml-2"
204-
*ngIf="asset.agent"
205-
ngbTooltip="View agent detail"
206-
container="body"
207-
placement="left"></i>
208+
<i (click)="viwAgentDetail($event, asset)" class="icon-display cursor-pointer ml-2"
209+
*ngIf="asset.agent"
210+
ngbTooltip="View agent detail"
211+
container="body"
212+
placement="left"></i>
208213

209-
<i (click)="deleteAsset($event, asset)" class="icon-cross2 cursor-pointer ml-2"
210-
ngbTooltip="Delete source"
211-
container="body"
212-
placement="auto"></i>
213-
</div>
214-
</td>
215-
</tr>
216-
</ng-container>
214+
<i (click)="deleteAsset($event, asset)" class="icon-cross2 cursor-pointer ml-2"
215+
ngbTooltip="Delete source"
216+
container="body"
217+
placement="auto"></i>
218+
</div>
219+
</td>
220+
</tr>
221+
</ng-container>
217222
</tbody>
218223
<tbody *ngIf="noData && !loading">
219-
<tr>
220-
<td [attr.colspan]="assetsFields.length +1">
221-
<app-no-data-found></app-no-data-found>
222-
</td>
223-
</tr>
224+
<tr>
225+
<td [attr.colspan]="assetsFields.length +1">
226+
<app-no-data-found></app-no-data-found>
227+
</td>
228+
</tr>
224229
</tbody>
225230
<tbody *ngIf="loading">
226-
<tr>
227-
<td [attr.colspan]="assetsFields.length +1">
228-
<div class="p-5 d-flex justify-content-center align-items-center text-blue-800">
229-
<app-utm-spinner [height]="'35px'"
230-
[label]="'Loading...'"
231-
[loading]="loading"
232-
[width]="'35px'">
233-
</app-utm-spinner>
234-
</div>
235-
</td>
236-
</tr>
231+
<tr>
232+
<td [attr.colspan]="assetsFields.length +1">
233+
<div class="p-5 d-flex justify-content-center align-items-center text-blue-800">
234+
<app-utm-spinner [height]="'35px'"
235+
[label]="'Loading...'"
236+
[loading]="loading"
237+
[width]="'35px'">
238+
</app-utm-spinner>
239+
</div>
240+
</td>
241+
</tr>
237242
</tbody>
238243
</table>
239244
</div>

frontend/src/app/assets-discover/assets-view/assets-view.component.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
} from '../../shared/components/utm/util/modal-confirmation/modal-confirmation.component';
1515
import {ALERT_SENSOR_FIELD} from '../../shared/constants/alert/alert-field.constant';
1616
import {ITEMS_PER_PAGE} from '../../shared/constants/pagination.constants';
17+
import {SortDirection} from '../../shared/directives/sortable/type/sort-direction.type';
1718
import {SortEvent} from '../../shared/directives/sortable/type/sort-event';
1819
import {ChartValueSeparator} from '../../shared/enums/chart-value-separator';
1920
import {ElasticOperatorsEnum} from '../../shared/enums/elastic-operators.enum';
@@ -35,7 +36,6 @@ import {AssetFilterType} from '../shared/types/asset-filter.type';
3536
import {UtmDataInputStatus} from '../shared/types/data-source-input.type';
3637
import {NetScanType} from '../shared/types/net-scan.type';
3738
import {SourceDataTypeConfigComponent} from '../source-data-type-config/source-data-type-config.component';
38-
import {SortDirection} from "../../shared/directives/sortable/type/sort-direction.type";
3939

4040
@Component({
4141
selector: 'app-assets-view',
@@ -109,14 +109,13 @@ export class AssetsViewComponent implements OnInit, OnDestroy {
109109
};
110110
});
111111

112-
this.assets$ = this.utmNetScanService.onRefresh$
112+
this.utmNetScanService.onRefresh$
113113
.pipe(
114114
filter(refresh => !!refresh),
115115
switchMap(() => this.utmNetScanService.fetchData(this.requestParam)),
116116
tap((response: HttpResponse<NetScanType[]>) => {
117117
this.totalItems = Number(response.headers.get('X-Total-Count'));
118118
this.loading = false;
119-
this.assets = response.body;
120119
this.noData = response.body.length === 0;
121120
}),
122121
map((response) => {
@@ -127,6 +126,8 @@ export class AssetsViewComponent implements OnInit, OnDestroy {
127126
asset.dataInputList = [];
128127
}
129128

129+
asset.lastInput = this.getLastInput(asset);
130+
130131
const displayName = asset.assetName && asset.assetIp ? `${asset.assetName} (${asset.assetIp})`
131132
: asset.assetName ? asset.assetName : asset.assetIp ? asset.assetIp : 'Unknown source';
132133

@@ -135,10 +136,10 @@ export class AssetsViewComponent implements OnInit, OnDestroy {
135136
return { ...asset, displayName, sortKey };
136137
});
137138
})
138-
);
139+
).subscribe( assets => this.assets = assets );
139140

140141
this.utmNetScanService.notifyRefresh(true);
141-
//this.starInterval();
142+
// this.starInterval();
142143
}
143144

144145
setInitialWidth() {
@@ -180,12 +181,26 @@ export class AssetsViewComponent implements OnInit, OnDestroy {
180181
onSortBy($event: SortEvent) {
181182
if ($event.column === 'displayName') {
182183
this.sortAssets($event.direction);
184+
} else if ($event.column === 'lastInput') {
185+
this.sortLastInput($event.direction);
183186
} else {
184187
this.requestParam.sort = $event.column + ',' + $event.direction;
185188
this.getAssets();
186189
}
187190
}
188191

192+
sortLastInput(direction: SortDirection) {
193+
this.assets.sort((a, b) => {
194+
const t1 = a.lastInputTimestamp ? a.lastInputTimestamp : -Infinity;
195+
const t2 = b.lastInputTimestamp ? b.lastInputTimestamp : -Infinity;
196+
197+
return direction === 'asc'
198+
? t1 - t2
199+
: t2 - t1;
200+
});
201+
}
202+
203+
189204
sortAssets(direction: SortDirection) {
190205
this.assets.sort((a, b) => {
191206

@@ -357,13 +372,16 @@ export class AssetsViewComponent implements OnInit, OnDestroy {
357372

358373
getLastInput(asset: NetScanType) {
359374
if (asset.dataInputList.length > 0) {
360-
const lastInput = asset.dataInputList.sort((a, b) => a.timestamp > b.timestamp ? 1 : -1)[0].timestamp;
361-
return this.datePipe.transform(this.formatTimestampToDate(lastInput));
362-
} else {
363-
return 'Unknown';
375+
const lastInput = asset.dataInputList[asset.dataInputList.length - 1].timestamp;
376+
asset.lastInputTimestamp = lastInput;
377+
return this.formatTimestampToDate(lastInput);
364378
}
379+
380+
asset.lastInputTimestamp = null;
381+
return 'Unknown';
365382
}
366383

384+
367385
formatTimestampToDate(time: number) {
368386
return moment.unix(time).utc().toISOString();
369387
}

frontend/src/app/assets-discover/shared/types/net-scan.type.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,6 @@ export class NetScanType {
3737
assetOsMajorVersion?: string;
3838
displayName?: string;
3939
sortKey?: string;
40+
lastInput: string;
41+
lastInputTimestamp: number;
4042
}

0 commit comments

Comments
 (0)