Add status widget to data explorer (#3226)

diff --git a/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.html b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.html
new file mode 100644
index 0000000..518f3fc
--- /dev/null
+++ b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.html
@@ -0,0 +1,158 @@
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You 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.
+  ~
+  -->
+
+<sp-visualization-config-outer
+    [configurationValid]="
+        currentlyConfiguredWidget.visualizationConfig.configurationValid
+    "
+>
+    <sp-configuration-box title="Settings">
+        <div fxLayout="column" fxLayoutGap="10px">
+            <div
+                fxLayout="row"
+                fxLayoutGap="10px"
+                fxLayoutAlign="start center"
+                fxFlex="100"
+            >
+                <small fxFlex="30">Select Value Type</small>
+                <mat-radio-group
+                    [(ngModel)]="
+                        currentlyConfiguredWidget.visualizationConfig
+                            .selectedDataType
+                    "
+                    (change)="selectDataType($event.source.value)"
+                >
+                    <mat-radio-button [value]="'number'"
+                        >Numeric Value</mat-radio-button
+                    >
+                    <mat-radio-button [value]="'boolean'"
+                        >Boolean Value</mat-radio-button
+                    >
+                </mat-radio-group>
+            </div>
+
+            <div
+                *ngIf="
+                    currentlyConfiguredWidget.visualizationConfig
+                        .selectedDataType === 'number'
+                "
+                fxLayout="column"
+                fxLayoutGap="10px"
+            >
+                <div
+                    fxLayout="row"
+                    fxLayoutGap="10px"
+                    fxLayoutAlign="start center"
+                    fxFlex="100"
+                >
+                    <small
+                        fxFlex="30"
+                        matTooltip="Interval in seconds in which an event must arrive"
+                        matTooltipPosition="above"
+                        >Interval [sec]</small
+                    >
+                    <mat-form-field color="accent" appearance="outline" fxFlex>
+                        <input
+                            type="number"
+                            [(ngModel)]="
+                                currentlyConfiguredWidget.visualizationConfig
+                                    .selectedInterval
+                            "
+                            matInput
+                            (input)="selectInterval($event.target.value)"
+                            min="0"
+                        />
+                    </mat-form-field>
+                </div>
+                <div
+                    fxLayout="row"
+                    fxLayoutGap="10px"
+                    fxLayoutAlign="start center"
+                    fxFlex="100"
+                >
+                    <small>Show Last Seen Timestamp</small>
+                    <mat-checkbox
+                        color="accent"
+                        [(ngModel)]="
+                            currentlyConfiguredWidget.visualizationConfig
+                                .selectedLastSeen
+                        "
+                        (ngModelChange)="showLastSeen($event)"
+                    >
+                    </mat-checkbox>
+                </div>
+            </div>
+
+            <div
+                *ngIf="
+                    currentlyConfiguredWidget.visualizationConfig
+                        .selectedDataType === 'boolean'
+                "
+                fxLayout="column"
+                fxLayoutGap="10px"
+            >
+                <div
+                    fxLayout="row"
+                    fxLayoutGap="10px"
+                    fxLayoutAlign="start center"
+                    fxFlex="100"
+                >
+                    <small fxFlex="30">Field</small>
+                    <sp-select-property
+                        [availableProperties]="fieldProvider.booleanFields"
+                        [selectedProperty]="
+                            currentlyConfiguredWidget.visualizationConfig
+                                .selectedBooleanFieldToObserve
+                        "
+                        (changeSelectedProperty)="
+                            selectBooleanFieldToObserve($event)
+                        "
+                        fxFlex
+                    ></sp-select-property>
+                </div>
+
+                <div
+                    fxLayout="row"
+                    fxLayoutGap="10px"
+                    fxLayoutAlign="start center"
+                    fxFlex="100"
+                >
+                    <small fxFlex="30">Select Mapping</small>
+                    <mat-radio-group
+                        [(ngModel)]="
+                            currentlyConfiguredWidget.visualizationConfig
+                                .selectedMappingGreenTrue
+                        "
+                        (change)="selectMappingGreenTrue($event.source.value)"
+                    >
+                        <mat-radio-button [value]="true">
+                            <span class="color-box green-box"></span> True
+                            <span class="spacing"></span>
+                            <span class="color-box red-box"></span> False
+                        </mat-radio-button>
+                        <mat-radio-button [value]="false">
+                            <span class="color-box red-box"></span> True
+                            <span class="spacing"></span>
+                            <span class="color-box green-box"></span> False
+                        </mat-radio-button>
+                    </mat-radio-group>
+                </div>
+            </div>
+        </div>
+    </sp-configuration-box>
+</sp-visualization-config-outer>
diff --git a/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.scss b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.scss
new file mode 100644
index 0000000..c3105ba
--- /dev/null
+++ b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.scss
@@ -0,0 +1,38 @@
+/*
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements.  See the NOTICE file distributed with
+ *   this work for additional information regarding copyright ownership.
+ *   The ASF licenses this file to You 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.
+ */
+
+.color-box {
+    display: inline-block;
+    width: 12px;
+    height: 12px;
+    margin-right: 5px;
+    border-radius: 50%;
+}
+.spacing {
+    margin-left: 15px;
+}
+.green-box {
+    background-color: green;
+}
+
+.red-box {
+    background-color: red;
+}
+
+.checkbox-container {
+    margin-top: 20px;
+}
diff --git a/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.ts b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.ts
new file mode 100644
index 0000000..f671616
--- /dev/null
+++ b/ui/src/app/data-explorer/components/widgets/status/config/status-widget-config.component.ts
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ *
+ */
+
+import { Component } from '@angular/core';
+import { BaseWidgetConfig } from '../../base/base-widget-config';
+import { WidgetConfigurationService } from '../../../../services/widget-configuration.service';
+import {
+    StatusWidgetModel,
+    StatusVisConfig,
+} from '../model/status-widget.model';
+import { DataExplorerFieldProviderService } from '../../../../services/data-explorer-field-provider-service';
+import { DataExplorerField } from '@streampipes/platform-services';
+
+@Component({
+    selector: 'sp-data-explorer-status-widget-config',
+    templateUrl: './status-widget-config.component.html',
+    styleUrls: ['./status-widget-config.component.scss'],
+})
+export class StatusWidgetConfigComponent extends BaseWidgetConfig<
+    StatusWidgetModel,
+    StatusVisConfig
+> {
+    constructor(
+        widgetConfigurationService: WidgetConfigurationService,
+        fieldService: DataExplorerFieldProviderService,
+    ) {
+        super(widgetConfigurationService, fieldService);
+    }
+
+    selectDataType(selectedDataType: string): void {
+        this.currentlyConfiguredWidget.visualizationConfig.selectedDataType =
+            selectedDataType;
+        this.triggerViewRefresh();
+    }
+
+    selectInterval(selectedInterval: number): void {
+        this.currentlyConfiguredWidget.visualizationConfig.selectedInterval =
+            selectedInterval;
+        this.triggerViewRefresh();
+    }
+
+    showLastSeen(selectedLastSeen: boolean): void {
+        this.currentlyConfiguredWidget.visualizationConfig.showLastSeen =
+            selectedLastSeen;
+        this.triggerViewRefresh();
+    }
+
+    selectBooleanFieldToObserve(
+        selectedBooleanFieldToObserve: DataExplorerField,
+    ): void {
+        this.currentlyConfiguredWidget.visualizationConfig.selectedBooleanFieldToObserve =
+            selectedBooleanFieldToObserve;
+        this.triggerViewRefresh();
+    }
+
+    selectMappingGreenTrue(selectedMappingGreenTrue: boolean): void {
+        this.currentlyConfiguredWidget.visualizationConfig.selectedMappingGreenTrue =
+            selectedMappingGreenTrue;
+        this.triggerViewRefresh();
+    }
+
+    protected applyWidgetConfig(config: StatusVisConfig): void {
+        config.selectedBooleanFieldToObserve =
+            this.fieldService.getSelectedField(
+                config.selectedBooleanFieldToObserve,
+                this.fieldProvider.allFields,
+                () => this.fieldProvider.allFields[0],
+            );
+        this.currentlyConfiguredWidget.visualizationConfig.selectedInterval ??= 5;
+        this.currentlyConfiguredWidget.visualizationConfig.selectedMappingGreenTrue ??=
+            true;
+    }
+    protected requiredFieldsForChartPresent(): boolean {
+        return true;
+    }
+}
diff --git a/ui/src/app/data-explorer/components/widgets/status/model/status-widget.model.ts b/ui/src/app/data-explorer/components/widgets/status/model/status-widget.model.ts
new file mode 100644
index 0000000..73bb53e
--- /dev/null
+++ b/ui/src/app/data-explorer/components/widgets/status/model/status-widget.model.ts
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ *
+ */
+
+import {
+    DataExplorerDataConfig,
+    DataExplorerWidgetModel,
+    DataExplorerField,
+} from '@streampipes/platform-services';
+import { DataExplorerVisConfig } from '../../../../models/dataview-dashboard.model';
+
+export interface StatusVisConfig extends DataExplorerVisConfig {
+    selectedDataType: string;
+    selectedInterval: number;
+    showLastSeen: boolean;
+    selectedBooleanFieldToObserve: DataExplorerField;
+    selectedMappingGreenTrue: boolean;
+}
+
+export interface StatusWidgetModel extends DataExplorerWidgetModel {
+    dataConfig: DataExplorerDataConfig;
+    visualizationConfig: StatusVisConfig;
+}
diff --git a/ui/src/app/data-explorer/components/widgets/status/status-widget.component.html b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.html
new file mode 100644
index 0000000..ad01626
--- /dev/null
+++ b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.html
@@ -0,0 +1,60 @@
+<!--
+  ~   Licensed to the Apache Software Foundation (ASF) under one or more
+  ~   contributor license agreements.  See the NOTICE file distributed with
+  ~   this work for additional information regarding copyright ownership.
+  ~   The ASF licenses this file to You 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.
+  -->
+
+<div
+    fxFlex="100"
+    fxLayoutAlign="center center"
+    fxLayout="column"
+    class="main-panel"
+    [ngStyle]="{
+        background: dataExplorerWidget.baseAppearanceConfig.backgroundColor,
+        color: dataExplorerWidget.baseAppearanceConfig.textColor,
+        overflowX: 'auto'
+    }"
+>
+    <sp-no-data-in-date-range
+        *ngIf="showNoDataInDateRange"
+        [viewDateRange]="timeSettings"
+        class="h-50"
+    >
+    </sp-no-data-in-date-range>
+
+    <div fxLayoutAlign="center center">
+        <div
+            class="tl-container"
+            [ngStyle]="{
+                width: containerWidth + 'px',
+                height: containerHeight + 'px'
+            }"
+        >
+            <div
+                class="light"
+                [ngClass]="{
+                    'light-red': !active,
+                    'light-green': active
+                }"
+                [ngStyle]="{
+                    width: lightWidth + 'px',
+                    height: lightHeight + 'px'
+                }"
+            ></div>
+        </div>
+    </div>
+    <div *ngIf="selectedDataType === 'number' && showLastSeen">
+        <h5>Last seen: {{ lastTimestamp | date: 'short' }}</h5>
+    </div>
+</div>
diff --git a/ui/src/app/data-explorer/components/widgets/status/status-widget.component.scss b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.scss
new file mode 100644
index 0000000..ac65666
--- /dev/null
+++ b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.scss
@@ -0,0 +1,57 @@
+/*
+ *   Licensed to the Apache Software Foundation (ASF) under one or more
+ *   contributor license agreements.  See the NOTICE file distributed with
+ *   this work for additional information regarding copyright ownership.
+ *   The ASF licenses this file to You 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.
+ */
+
+.h-100 {
+    height: 100%;
+}
+
+.tl-container {
+    background-color: #222;
+    display: flex;
+    align-items: center;
+    flex-direction: column;
+    padding: 20px;
+    border-radius: 10px;
+}
+
+.light {
+    border-radius: 50%;
+    background-color: #3d3535;
+    background: repeating-linear-gradient(#333, #443 5px);
+}
+
+.light-red,
+.light-green {
+    box-shadow: 0 0 40px;
+    z-index: 1;
+}
+
+.light-red {
+    background: repeating-linear-gradient(#f00, #e00 5px);
+    box-shadow: 0 0 40px #f00;
+}
+
+.light-green {
+    background: repeating-linear-gradient(#0d0, #0c0 5px);
+    box-shadow: 0 0 40px #0d0;
+}
+
+.title-panel {
+    font-size: 20px;
+    height: 30px;
+    margin: 10px 0;
+}
diff --git a/ui/src/app/data-explorer/components/widgets/status/status-widget.component.ts b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.ts
new file mode 100644
index 0000000..abb41e5
--- /dev/null
+++ b/ui/src/app/data-explorer/components/widgets/status/status-widget.component.ts
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ *
+ */
+
+import { Component, OnInit } from '@angular/core';
+import { BaseDataExplorerWidgetDirective } from '../base/base-data-explorer-widget.directive';
+import { StatusWidgetModel } from './model/status-widget.model';
+import {
+    DataExplorerField,
+    SpQueryResult,
+} from '@streampipes/platform-services';
+
+@Component({
+    selector: 'sp-data-explorer-status-widget',
+    templateUrl: './status-widget.component.html',
+    styleUrls: ['./status-widget.component.scss'],
+})
+export class StatusWidgetComponent
+    extends BaseDataExplorerWidgetDirective<StatusWidgetModel>
+    implements OnInit
+{
+    width: number;
+    height: number;
+
+    row: any[][];
+    header: string[];
+    fieldIndex = -1;
+
+    containerWidth: number;
+    containerHeight: number;
+
+    lightWidth: number;
+    lightHeight: number;
+
+    selectedDataType: string;
+    selectedInterval: number;
+    showLastSeen: boolean;
+    selectedBooleanFieldToObserve: DataExplorerField;
+    selectedMappingGreenTrue: boolean;
+
+    lastTimestamp = 0;
+    active: boolean;
+
+    ngOnInit(): void {
+        super.ngOnInit();
+        this.onResize(
+            this.gridsterItemComponent.width - this.widthOffset,
+            this.gridsterItemComponent.height - this.heightOffset,
+        );
+        this.updateSettings();
+    }
+
+    updateSettings(): void {
+        this.selectedDataType =
+            this.dataExplorerWidget.visualizationConfig.selectedDataType;
+        this.selectedInterval =
+            this.dataExplorerWidget.visualizationConfig.selectedInterval;
+        this.showLastSeen =
+            this.dataExplorerWidget.visualizationConfig.showLastSeen;
+        this.selectedBooleanFieldToObserve =
+            this.dataExplorerWidget.visualizationConfig.selectedBooleanFieldToObserve;
+        this.selectedMappingGreenTrue =
+            this.dataExplorerWidget.visualizationConfig.selectedMappingGreenTrue;
+    }
+
+    getNumericalStatus(): void {
+        const timestamp = new Date().getTime();
+        this.active =
+            this.lastTimestamp >= timestamp - this.selectedInterval * 1000;
+    }
+
+    getBooleanStatus(): void {
+        if (this.selectedMappingGreenTrue) {
+            this.active = this.row[0][this.fieldIndex];
+        } else {
+            this.active = !this.row[0][this.fieldIndex];
+        }
+    }
+
+    booleanFieldToObserve(): void {
+        this.fieldIndex = this.header.indexOf(
+            this.selectedBooleanFieldToObserve.runtimeName,
+        );
+    }
+
+    refreshView(): void {
+        this.updateSettings();
+        if (this.row !== undefined && this.row.length > 0) {
+            if (this.selectedDataType == 'boolean') {
+                this.getBooleanStatus();
+            } else {
+                this.getNumericalStatus();
+            }
+        }
+    }
+
+    beforeDataFetched(): void {
+        this.setShownComponents(false, false, true, false);
+    }
+
+    onDataReceived(spQueryResult: SpQueryResult[]): void {
+        if (
+            spQueryResult.length > 0 &&
+            spQueryResult[0].allDataSeries.length > 0
+        ) {
+            this.header = spQueryResult[0].allDataSeries[0].headers;
+            this.row = spQueryResult[0].allDataSeries[0].rows;
+            this.lastTimestamp = spQueryResult[0].allDataSeries[0].rows[0][0];
+
+            if (this.selectedDataType == 'number') {
+                this.getNumericalStatus();
+            } else if (this.selectedDataType == 'boolean') {
+                this.booleanFieldToObserve();
+                this.getBooleanStatus();
+            }
+            this.setShownComponents(false, true, false, false);
+        } else {
+            this.setShownComponents(true, false, false, false);
+        }
+    }
+
+    onResize(width: number, heigth: number): void {
+        this.containerHeight = heigth * 0.3;
+        this.containerWidth = this.containerHeight;
+        this.lightWidth = this.containerHeight;
+        this.lightHeight = this.lightWidth;
+    }
+
+    handleUpdatedFields(
+        addedFields: DataExplorerField[],
+        removedFields: DataExplorerField[],
+    ) {
+        const updatedFields = this.fieldUpdateService.updateFieldSelection(
+            [
+                this.dataExplorerWidget.visualizationConfig
+                    .selectedBooleanFieldToObserve,
+            ],
+            {
+                addedFields,
+                removedFields,
+                fieldProvider: this.fieldProvider,
+            },
+            () => true,
+        );
+
+        this.selectedBooleanFieldToObserve = updatedFields[0];
+        this.booleanFieldToObserve();
+        this.refreshView();
+    }
+}
diff --git a/ui/src/app/data-explorer/components/widgets/traffic-light/traffic-light-widget.component.ts b/ui/src/app/data-explorer/components/widgets/traffic-light/traffic-light-widget.component.ts
index 80e3490..99df13f 100644
--- a/ui/src/app/data-explorer/components/widgets/traffic-light/traffic-light-widget.component.ts
+++ b/ui/src/app/data-explorer/components/widgets/traffic-light/traffic-light-widget.component.ts
@@ -110,8 +110,6 @@
                             (this.selectedWarningRange / 100)
                 );
             } else {
-                console.log(value);
-
                 return (
                     value <=
                     this.selectedThreshold +
@@ -125,13 +123,13 @@
         return !this.exceedsThreshold(value) && !this.isInWarningRange(value);
     }
 
-    public refreshView(): void {
+    refreshView(): void {
         this.updateSettings();
         this.fieldToObserve();
         this.getTrafficLightColor();
     }
 
-    public beforeDataFetched(): void {
+    beforeDataFetched(): void {
         this.setShownComponents(false, false, true, false);
     }
 
@@ -141,12 +139,19 @@
         );
     }
 
-    public onDataReceived(spQueryResult: SpQueryResult[]): void {
-        this.header = spQueryResult[0].allDataSeries[0].headers;
-        this.row = spQueryResult[0].allDataSeries[0].rows;
-        this.fieldToObserve();
-        this.getTrafficLightColor();
-        this.setShownComponents(false, true, false, false);
+    onDataReceived(spQueryResult: SpQueryResult[]): void {
+        if (
+            spQueryResult.length > 0 &&
+            spQueryResult[0].allDataSeries.length > 0
+        ) {
+            this.header = spQueryResult[0].allDataSeries[0].headers;
+            this.row = spQueryResult[0].allDataSeries[0].rows;
+            this.fieldToObserve();
+            this.getTrafficLightColor();
+            this.setShownComponents(false, true, false, false);
+        } else {
+            this.setShownComponents(true, false, false, false);
+        }
     }
 
     onResize(width: number, heigth: number) {
diff --git a/ui/src/app/data-explorer/data-explorer.module.ts b/ui/src/app/data-explorer/data-explorer.module.ts
index 09b8f33..c3070b4 100644
--- a/ui/src/app/data-explorer/data-explorer.module.ts
+++ b/ui/src/app/data-explorer/data-explorer.module.ts
@@ -48,6 +48,8 @@
 import { ImageWidgetComponent } from './components/widgets/image/image-widget.component';
 import { TrafficLightWidgetComponent } from './components/widgets/traffic-light/traffic-light-widget.component';
 import { TrafficLightWidgetConfigComponent } from './components/widgets/traffic-light/config/traffic-light-widget-config.component';
+import { StatusWidgetComponent } from './components/widgets/status/status-widget.component';
+import { StatusWidgetConfigComponent } from './components/widgets/status/config/status-widget-config.component';
 import { TableWidgetComponent } from './components/widgets/table/table-widget.component';
 import { AggregateConfigurationComponent } from './components/widgets/utils/aggregate-configuration/aggregate-configuration.component';
 import { LoadDataSpinnerComponent } from './components/widgets/utils/load-data-spinner/load-data-spinner.component';
@@ -251,6 +253,8 @@
         TableWidgetConfigComponent,
         TrafficLightWidgetComponent,
         TrafficLightWidgetConfigComponent,
+        StatusWidgetComponent,
+        StatusWidgetConfigComponent,
         MapWidgetConfigComponent,
         MapWidgetComponent,
         HeatmapWidgetConfigComponent,
diff --git a/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts b/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts
index c2d486b..efb221b 100644
--- a/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts
+++ b/ui/src/app/data-explorer/registry/data-explorer-widget-registry.ts
@@ -54,6 +54,8 @@
 import { GaugeWidgetModel } from '../components/widgets/gauge/model/gauge-widget.model';
 import { TrafficLightWidgetConfigComponent } from '../components/widgets/traffic-light/config/traffic-light-widget-config.component';
 import { TrafficLightWidgetComponent } from '../components/widgets/traffic-light/traffic-light-widget.component';
+import { StatusWidgetConfigComponent } from '../components/widgets/status/config/status-widget-config.component';
+import { StatusWidgetComponent } from '../components/widgets/status/status-widget.component';
 
 @Injectable({ providedIn: 'root' })
 export class DataExplorerWidgetRegistry {
@@ -93,6 +95,12 @@
                 widgetComponent: TrafficLightWidgetComponent,
             },
             {
+                id: 'status',
+                label: 'Status',
+                widgetConfigurationComponent: StatusWidgetConfigComponent,
+                widgetComponent: StatusWidgetComponent,
+            },
+            {
                 id: 'map',
                 label: 'Map',
                 widgetConfigurationComponent: MapWidgetConfigComponent,