| <!-- |
| Copyright 2020 Google LLC |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| --> |
| |
| <mat-spinner class="loading-spinner" color="accent" *ngIf="isLoading"></mat-spinner> |
| |
| <mat-toolbar class="device-list-toolbar"> |
| <mat-toolbar-row fxLayoutAlign="space-between center"> |
| <h1>Devices</h1> |
| <mat-form-field appearance="outline" class="mat-select-filter" fxFlex="300px" subscriptSizing="dynamic"> |
| <mat-select [(ngModel)]="selectedLab" (selectionChange)="load()"> |
| <mat-select-trigger> Lab: {{ selectedLab }} </mat-select-trigger> |
| <mat-option [value]="allOptionsValue"> |
| {{ allOptionsValue }} |
| </mat-option> |
| <mat-option *ngFor="let lab of labs" [value]="lab"> |
| {{ lab }} |
| </mat-option> |
| </mat-select> |
| </mat-form-field> |
| </mat-toolbar-row> |
| </mat-toolbar> |
| |
| <div class="filter"> |
| <form [formGroup]="formGroup"> |
| <mat-form-field class="form-field" fxFlex> |
| <mat-label>Filter</mat-label> |
| <mat-icon matPrefix class="filter-icon">filter_list</mat-icon> |
| <input |
| id="filterbar-input" |
| matInput |
| #valueInput |
| formControlName="valueControl" |
| [matAutocomplete]="auto" |
| (keydown.tab)="toggleByKeyPressed($event)" |
| (keydown.enter)="submit($event)" |
| type="text" |
| tabindex="0" |
| /> |
| <button |
| *ngIf="hasFilter" |
| matSuffix |
| mat-icon-button |
| aria-label="clear filter" |
| (click)="resetSelection()" |
| type="button" |
| > |
| <mat-icon>close</mat-icon> |
| </button> |
| <mat-autocomplete #auto="matAutocomplete" (closed)="renderSelectedOptionsAndResetFilterbar()"> |
| <div> |
| <mat-option [value]="back" *ngIf="filterBarUtility.selectedColumn"> |
| <div class="back" (click)="backToRoot($event)"> |
| <mat-icon matPrefix class="back-button-icon">arrow_back</mat-icon> |
| Back |
| </div> |
| </mat-option> |
| <mat-option |
| [value]="allOptionsValue" |
| *ngIf="filterBarUtility.selectedColumn && filteredOptions.length; else noMatch" |
| > |
| <div (click)="toggleSelectAll($event)"> |
| <mat-checkbox [checked]="isAllSelected"> |
| Select {{ filteredOptions?.length }} filtered results |
| </mat-checkbox> |
| </div> |
| </mat-option> |
| <ng-template #noMatch> |
| <div |
| *ngIf=" |
| filterBarUtility.selectedColumn && |
| !filteredOptions.length && |
| filterBarUtility.selectedColumn !== deviceSerialDisplay && |
| filterBarUtility.selectedColumn !== extraInfoDisplay |
| " |
| class="no-match" |
| > |
| No matches for <b>{{ inputValue }}</b> |
| </div> |
| </ng-template> |
| </div> |
| <mat-option *ngFor="let option of filteredOptions" [value]="option.value"> |
| <div (click)="toggleSelection($event, option)"> |
| <mat-checkbox *ngIf="option.showCheckbox" [checked]="option.selected"> </mat-checkbox> |
| {{ option.value }} |
| </div> |
| </mat-option> |
| <mat-option |
| *ngIf=" |
| filterBarUtility.selectedColumn === extraInfoDisplay || |
| filterBarUtility.selectedColumn === deviceSerialDisplay |
| " |
| [value]="inputValue" |
| > |
| <div (click)="addInputOption(inputValue, filterBarUtility.selectedColumn)"> |
| Search on {{ filterBarUtility.selectedColumn }}: "<b>{{ inputValue }}</b |
| >" |
| </div> |
| </mat-option> |
| </mat-autocomplete> |
| </mat-form-field> |
| |
| <button |
| mat-icon-button |
| class="refresh-button" |
| matTooltip="Refresh the data" |
| aria-label="Refresh the data" |
| (click)="load(0, true)" |
| > |
| <mat-icon>refresh</mat-icon> |
| </button> |
| |
| <mat-divider vertical></mat-divider> |
| |
| <button |
| mat-icon-button |
| id="view-columns-btn" |
| class="view-columns" |
| aria-label="View columns" |
| matTooltip="View columns" |
| [matMenuTriggerFor]="view_column_menu" |
| type="button" |
| (click)="startViewDevicesColumnsHats()" |
| > |
| <mat-icon>view_column</mat-icon> |
| </button> |
| <mat-menu #view_column_menu="matMenu"> |
| <ng-container *ngFor="let column of columns; let i = index"> |
| <ng-container *ngIf="column.removable"> |
| <button |
| [attr.id]="column.fieldName + '-menu-btn'" |
| (click)="toggleDisplayColumn($event, column.show, i)" |
| mat-menu-item |
| type="button" |
| > |
| <mat-icon>{{ column.show ? "check_box" : "check_box_outline_blank" }}</mat-icon> |
| <span class="menu-item">{{ column.displayName }}</span> |
| </button> |
| </ng-container> |
| </ng-container> |
| </mat-menu> |
| </form> |
| </div> |
| |
| <div class="action-row" fxLayout="row wrap" fxLayoutAlign="start center" *ngIf="notesEnabled"> |
| <add-notes-button |
| buttonStyle="Stroked" |
| [labName]="selectedLab" |
| [ids]="tableRowsSelectManager?.selection.selected" |
| [deviceHostMap]="deviceHostMap" |
| [disabled]="!(selectedLab | permissionCheck)" |
| noteType="DEVICE" |
| (notesUpdated)="loadDeviceNotes($event)" |
| (click)="startBatchAddDevicesNotesHats()" |
| ></add-notes-button> |
| </div> |
| <div [class.loading-mask]="isLoading"> |
| <mat-table |
| #table |
| [dataSource]="dataSource" |
| (scroll)="checkTableScrolled()" |
| [class.scrolled]="isTableScrolled" |
| aria-label="Device list" |
| tableRowsSelectManager |
| [(initialSelection)]="selectedSerials" |
| (selectionChange)="updateSelectedDeviceSerials($event)" |
| > |
| <!-- Checkbox Column --> |
| <ng-container matColumnDef="select"> |
| <mat-header-cell *matHeaderCellDef fxFlex="100px"> |
| <mat-checkbox |
| aria-label="Select all devices" |
| tableRowsSelectCheckbox |
| #tableRowsSelectCheckbox="tableRowsSelectCheckbox" |
| [checked]="tableRowsSelectCheckbox.isAllRowsSelected" |
| [indeterminate]="tableRowsSelectCheckbox.isPartialRowsSelected" |
| [disabled]="!selectEnabled" |
| >({{selectedSerials.length}}/{{dataSource.length}}) |
| </mat-checkbox> |
| </mat-header-cell> |
| <mat-cell *matCellDef="let row; let rowIndex = index" fxFlex="100px"> |
| <mat-checkbox |
| [aria-label]="'Device' + row.device_serial" |
| tableRowSelectCheckbox |
| #tableRowSelectCheckbox="tableRowSelectCheckbox" |
| [rowIdFieldValue]="row.device_serial" |
| [rowIndex]="rowIndex" |
| [checked]="tableRowSelectCheckbox.isSelected" |
| [disabled]="!selectEnabled" |
| > |
| </mat-checkbox> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Device Serial Number --> |
| <ng-container matColumnDef="device_serial"> |
| <mat-header-cell *matHeaderCellDef fxFlex="210px"> Serial </mat-header-cell> |
| <mat-cell *matCellDef="let device" [matTooltip]="getDeviceSerialForDisplay(device)" fxFlex="210px"> |
| <div class="device-serial"> |
| <a (click)="openDeviceDetails(device.device_serial)"> |
| {{ getDeviceSerialForDisplay(device) }} |
| </a> |
| </div> |
| <a |
| mat-icon-button |
| [href]="getDeviceDetailsUrl(device.device_serial)" |
| (click)="storeDeviceSerialsInLocalStorage()" |
| target="_blank" |
| > |
| <mat-icon>launch</mat-icon> |
| </a> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Sponge --> |
| <ng-container matColumnDef="sponge"> |
| <mat-header-cell *matHeaderCellDef fxFlex="120px"> Sponge </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="120px"> |
| <a [href]="getLogUrl(device)" target="_blank">sponge</a> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Host Name --> |
| <ng-container matColumnDef="hostname"> |
| <mat-header-cell *matHeaderCellDef fxFlex="200px"> |
| Hostname |
| </mat-header-cell> |
| <mat-cell *matCellDef="let device" [matTooltip]="device.hostname" fxFlex="200px"> |
| <div class="host-name"> |
| <a [routerLink]="['/hosts', device.hostname]">{{ device.hostname }}</a> |
| </div> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Host Group --> |
| <ng-container matColumnDef="host_group"> |
| <mat-header-cell *matHeaderCellDef fxFlex="300px"> |
| Host Group |
| </mat-header-cell> |
| <mat-cell *matCellDef="let device" [matTooltip]="device.host_group" fxFlex="300px"> |
| <div class="host-group">{{ device.host_group }}</div> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Pools --> |
| <ng-container matColumnDef="pools"> |
| <mat-header-cell *matHeaderCellDef fxFlex="220px"> Pools </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="220px"> |
| <ng-container *ngIf="device.pools"> |
| <overflow-list [data]="device.pools" [overflowListType]="overflowListType.BUTTON"> |
| </overflow-list> |
| </ng-container> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Device State --> |
| <ng-container matColumnDef="state"> |
| <mat-header-cell *matHeaderCellDef fxFlex="150px"> State </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="150px"> |
| <status-button [state]="device.state"></status-button> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Product --> |
| <ng-container matColumnDef="product"> |
| <mat-header-cell *matHeaderCellDef fxFlex="180px"> Product </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="180px"> {{ device.product }} </mat-cell> |
| </ng-container> |
| |
| <!-- Variant --> |
| <ng-container matColumnDef="variant"> |
| <mat-header-cell *matHeaderCellDef fxFlex="180px"> Variant </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="180px"> {{ device.product_variant }} </mat-cell> |
| </ng-container> |
| |
| <!-- Run Targets --> |
| <ng-container matColumnDef="run_target"> |
| <mat-header-cell *matHeaderCellDef fxFlex="220px"> Run Targets </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="220px"> |
| {{ device.run_target }} |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Build Id --> |
| <ng-container matColumnDef="build_id"> |
| <mat-header-cell *matHeaderCellDef fxFlex="180px"> Build Id </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="180px"> {{ device.build_id }} </mat-cell> |
| </ng-container> |
| |
| <!-- SIM State --> |
| <ng-container matColumnDef="sim_state"> |
| <mat-header-cell *matHeaderCellDef fxFlex="180px"> SIM State </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="180px"> {{ device.sim_state }} </mat-cell> |
| </ng-container> |
| |
| <!-- Battery --> |
| <ng-container matColumnDef="battery_level"> |
| <mat-header-cell *matHeaderCellDef fxFlex="150px"> Battery </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="150px"> |
| {{ device.extraInfo.battery_level | percent: "1.0" }} |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Test Harness --> |
| <ng-container matColumnDef="testHarness"> |
| <mat-header-cell *matHeaderCellDef fxFlex="120px"> |
| Test Harness |
| </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="120px"> {{ device.test_harness }} </mat-cell> |
| </ng-container> |
| |
| <!-- Notes Update Time --> |
| <ng-container matColumnDef="notesUpdateTime"> |
| <mat-header-cell *matHeaderCellDef fxFlex="140px"> Notes Update Time </mat-header-cell> |
| <mat-cell |
| *matCellDef="let device" |
| [matTooltip]="device.note?.timestamp | utc | date: 'MM/dd/yyyy h:mma'" |
| fxFlex="140px" |
| > |
| <ng-container *ngIf="device.note"> |
| {{ device.note.timestamp | utc | fromNow }} |
| </ng-container> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Notes Update Timestamp --> |
| <ng-container matColumnDef="notesUpdateTimestamp"> |
| <mat-header-cell *matHeaderCellDef fxFlex="140px"> Notes Update Timestamp </mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="140px"> |
| <ng-container *ngIf="device.note"> |
| {{ device.note.timestamp | utc | date: "MM/dd/yyyy h:mma" }} |
| </ng-container> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Offline Reason --> |
| <ng-container matColumnDef="offline_reason"> |
| <mat-header-cell *matHeaderCellDef fxFlex="160px"> Offline Reason </mat-header-cell> |
| <mat-cell *matCellDef="let device" [matTooltip]="device.note?.offline_reason" fxFlex="160px"> |
| <ng-container *ngIf="device.note"> |
| <div class="notes-info">{{ device.note.offline_reason }}</div> |
| </ng-container> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Recovery Action --> |
| <ng-container matColumnDef="recovery_action"> |
| <mat-header-cell *matHeaderCellDef fxFlex="160px"> Recovery Action </mat-header-cell> |
| <mat-cell *matCellDef="let device" [matTooltip]="device.note?.recovery_action" fxFlex="160px"> |
| <ng-container *ngIf="device.note"> |
| <div class="notes-info">{{ device.note.recovery_action }}</div> |
| </ng-container> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Note --> |
| <ng-container matColumnDef="note"> |
| <mat-header-cell *matHeaderCellDef fxFlex="160px"> Note </mat-header-cell> |
| <mat-cell *matCellDef="let device" [matTooltip]="device.note?.message" fxFlex="160px"> |
| <ng-container *ngIf="device.note"> |
| <div class="notes-info">{{ device.note.message }}</div> |
| </ng-container> |
| </mat-cell> |
| </ng-container> |
| |
| <!-- Actions: Mark as fixed, Remove for a single device --> |
| <ng-container matColumnDef="actions" stickyEnd *ngIf="notesEnabled"> |
| <mat-header-cell *matHeaderCellDef fxFlex="130px"></mat-header-cell> |
| <mat-cell *matCellDef="let device" fxFlex="130px" fxLayout="row" fxFlexAlign="start center"> |
| <button |
| fxFlexOffset="10px" |
| mat-icon-button |
| [matTooltip]=" |
| device.recovery_state === recoveryState.FIXED |
| ? 'Click to undo fixed' |
| : 'Click to mark as fixed' |
| " |
| aria-label="Mark as fixed" |
| [disabled]="!(device.lab_name | permissionCheck)" |
| [color]="device.recovery_state === recoveryState.FIXED ? 'accent' : 'primary'" |
| (click)="toggleDeviceFixedState(device, $event)" |
| > |
| <mat-icon [class.lighter]="!(device.recovery_state === recoveryState.FIXED)" |
| >done</mat-icon |
| > |
| </button> |
| <button |
| *ngIf=" |
| device?.state.toUpperCase() === 'GONE' |
| " |
| mat-button |
| color="accent" |
| type="button" |
| aria-label="Remove device" |
| [disabled]="!(device.lab_name | permissionCheck)" |
| (click)="removeDevice(device.device_serial, device.hostname)" |
| > |
| Remove |
| </button> |
| </mat-cell> |
| </ng-container> |
| |
| <mat-header-row *matHeaderRowDef="displayColumns"></mat-header-row> |
| <mat-row |
| *matRowDef="let row; columns: displayColumns; let rowIndex = index" |
| tableRowSelect |
| [rowIdFieldValue]="row.device_serial" |
| [rowIndex]="rowIndex" |
| ></mat-row> |
| </mat-table> |
| |
| <paginator |
| [pageSizeOptions]="pageSizeOptions" |
| (sizeChange)="resetPageTokenAndReload()" |
| (previous)="load(-1)" |
| (next)="load(1)" |
| ></paginator> |
| |
| <div class="empty" *ngIf="!dataSource.length"> |
| No devices found. |
| </div> |
| </div> |