Skip to content

Commit

Permalink
UI: add status component for each edge (#1162)
Browse files Browse the repository at this point in the history
* with right restrictions (at least admin)
  • Loading branch information
fabianfnc authored Jun 18, 2020
1 parent f67c2c4 commit 135ae7b
Show file tree
Hide file tree
Showing 12 changed files with 384 additions and 100 deletions.
26 changes: 22 additions & 4 deletions ui/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,28 @@
<ion-title class="ion-hide-md-down" slot="end">
</ion-title>
<ng-container *ngIf="service.currentEdge | async as edge">
<ion-buttons slot="end" *ngIf="env.debugMode && edge.roleIsAtLeast('owner')">
<ion-button (click)="isSystemLogEnabled = !isSystemLogEnabled">
<ion-icon name="bug-outline"></ion-icon>
</ion-button>
<ion-buttons slot="end">
<ng-container *ngIf="edge.roleIsAtLeast('admin')">
<ng-container *ngIf="edge.currentData | async as currentData">
<ng-container *ngIf="currentData.summary.system as sum">
<ion-button (click)="presentSingleStatusModal()"
*ngIf="currentPage == 'IndexLive' || currentPage == 'IndexHistory' || currentPage == 'EdgeSettings'"
color="light" fill="solid" shape="round">
<ng-container [ngSwitch]="sum.state">
<ion-icon *ngSwitchCase="0" color="success" name="checkmark-circle-outline"></ion-icon>
<ion-icon *ngSwitchCase="1" color="success" name="information-outline"></ion-icon>
<ion-icon *ngSwitchCase="2" color="warning" name="alert-outline"></ion-icon>
<ion-icon *ngSwitchCase="3" color="danger" name="alert-outline"></ion-icon>
</ng-container>
</ion-button>
</ng-container>
</ng-container>
<ng-container *ngIf="env.debugMode && edge.roleIsAtLeast('owner')">
<ion-button (click)="isSystemLogEnabled = !isSystemLogEnabled">
<ion-icon name="bug-outline"></ion-icon>
</ion-button>
</ng-container>
</ng-container>
</ion-buttons>
</ng-container>
</ion-toolbar>
Expand Down
48 changes: 36 additions & 12 deletions ui/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Component, ChangeDetectorRef } from '@angular/core';
import { Edge, Service, Websocket, ChannelAddress } from './shared/shared';
import { environment } from '../environments';
import { filter, takeUntil } from 'rxjs/operators';
import { LanguageTag } from './shared/translate/language';
import { MenuController, Platform, ToastController, ModalController } from '@ionic/angular';
import { NavigationEnd, Router } from '@angular/router';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { MenuController, Platform, ToastController } from '@ionic/angular';
import { StatusSingleComponent } from './shared/status/single/status.component';
import { Subject } from 'rxjs';
import { filter, takeUntil } from 'rxjs/operators';
import { environment } from '../environments';
import { Edge, Service, Websocket } from './shared/shared';
import { LanguageTag } from './shared/translate/language';

@Component({
selector: 'app-root',
Expand All @@ -18,20 +19,21 @@ export class AppComponent {
public env = environment;
public backUrl: string | boolean = '/';
public enableSideMenu: boolean;
public currentPage: 'Other' | 'IndexLive' | 'IndexHistory' = 'Other';
public currentPage: 'EdgeSettings' | 'Other' | 'IndexLive' | 'IndexHistory' = 'Other';
public isSystemLogEnabled: boolean = false;
private ngUnsubscribe: Subject<void> = new Subject<void>();

constructor(
private cdRef: ChangeDetectorRef,
private platform: Platform,
private splashScreen: SplashScreen,
private statusBar: StatusBar,
public websocket: Websocket,
public service: Service,
public menu: MenuController,
public modalCtrl: ModalController,
public router: Router,
public service: Service,
public toastController: ToastController,
public menu: MenuController,
private cdRef: ChangeDetectorRef
public websocket: Websocket,
) {
// this.initializeApp();
service.setLang(LanguageTag.DE);
Expand Down Expand Up @@ -68,6 +70,15 @@ export class AppComponent {
).subscribe(event => {
this.updateUrl((<NavigationEnd>event).urlAfterRedirects);
})

// subscribe for single status component
this.service.currentEdge.pipe(takeUntil(this.ngUnsubscribe)).subscribe(edge => {
if (edge != null) {
edge.subscribeChannels(this.websocket, '', [
new ChannelAddress('_sum', 'State'),
]);
}
})
}

// used to prevent 'Expression has changed after it was checked' error
Expand Down Expand Up @@ -142,15 +153,21 @@ export class AppComponent {
updateCurrentPage(url: string) {
let urlArray = url.split('/');
let file = urlArray.pop();

if (urlArray.length >= 4) {
file = urlArray[3];
}
console.log("file", file, "urlArray", urlArray)
// Enable Segment Navigation for Edge-Index-Page
if ((file == 'history' || file == 'live') && urlArray.length == 3) {
if (file == 'history') {
this.currentPage = 'IndexHistory';
} else {
this.currentPage = 'IndexLive';
}
} else {
} else if (file == 'settings' && urlArray.length > 1) {
this.currentPage = 'EdgeSettings';
}
else {
this.currentPage = 'Other';
}
}
Expand All @@ -164,6 +181,13 @@ export class AppComponent {
}
}

async presentSingleStatusModal() {
const modal = await this.modalCtrl.create({
component: StatusSingleComponent,
});
return await modal.present();
}

ngOnDestroy() {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
Expand Down
2 changes: 2 additions & 0 deletions ui/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { SettingsModule as EdgeSettingsModule } from './edge/settings/settings.m
import { SharedModule } from './shared/shared.module';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';
import { StatusSingleComponent } from './shared/status/single/status.component';
import { SystemLogComponent } from './edge/settings/systemlog/systemlog.component';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import localDE from '@angular/common/locales/de';
Expand All @@ -34,6 +35,7 @@ import localDE from '@angular/common/locales/de';
InputTypeComponent,
PickDatePopoverComponent,
RepeatTypeComponent,
StatusSingleComponent,
SystemLogComponent,
],
entryComponents: [
Expand Down
9 changes: 6 additions & 3 deletions ui/src/app/shared/edge/currentdata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ export class CurrentData {
system: {
totalPower: null,
autarchy: null,
selfConsumption: null
selfConsumption: null,
state: null
}, storage: {
soc: null,
activePowerL1: null,
Expand Down Expand Up @@ -211,8 +212,8 @@ export class CurrentData {

{
/*
* Total
*/
* Total
*/
result.system.totalPower = Math.max(
// Productions
result.grid.buyActivePower
Expand All @@ -227,6 +228,8 @@ export class CurrentData {
);
result.system.autarchy = CurrentData.calculateAutarchy(result.grid.buyActivePower, result.consumption.activePower);
result.system.selfConsumption = CurrentData.calculateSelfConsumption(result.grid.sellActivePower, result.production.activePower, result.storage.effectiveDischargePower);
// State
result.system.state = c['_sum/State'];
}
return result;
}
Expand Down
4 changes: 3 additions & 1 deletion ui/src/app/shared/service/defaulttypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export module DefaultTypes {
// autarchy in percent
autarchy: number,
// self consumption in percent
selfConsumption: number
selfConsumption: number,
// state 0: Ok, 1: Info, 2: Warning, 3: Fault
state: number
}, storage: {
soc: number,
activePowerL1: number,
Expand Down
114 changes: 114 additions & 0 deletions ui/src/app/shared/status/single/status.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<ion-header>
<ion-toolbar class="ion-justify-content-center" color="primary">
<ion-title translate>General.systemState</ion-title>
<ion-buttons slot="end">
<ion-button (click)="modalCtrl.dismiss()">
<ion-icon name="close"></ion-icon>
</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ng-container *ngIf="edge && components">
<ng-container *ngIf="edge.currentData | async as currentData">
<ion-content>
<ion-card-content>
<ng-container *ngIf="currentData.summary.system as sum">
<ion-item lines="full">
<ion-icon color="primary" name="analytics-outline"></ion-icon>
<ion-label class="ion-padding-start" translate>
General.totalState
</ion-label>
<ng-container [ngSwitch]="sum.state">
<ion-icon *ngSwitchCase="0" color="success" name="checkmark-circle-outline"></ion-icon>
<ion-icon *ngSwitchCase="1" color="success" name="information-outline"></ion-icon>
<ion-icon *ngSwitchCase="2" color="warning" name="alert-outline"></ion-icon>
<ion-icon *ngSwitchCase="3" color="danger" name="alert-outline"></ion-icon>
</ng-container>
</ion-item>
</ng-container>
<ion-list>
<ng-container *ngFor="let category of components">
<ion-item-group>
<ion-item lines="inset">
<ion-icon name="{{ category.category.icon }}"></ion-icon>
<ion-label class="ion-padding-start">
{{ category.category.title }}
</ion-label>
</ion-item>
</ion-item-group>
<ion-item lines="none" *ngFor="let item of category.components">
<ion-label class="ion-text-wrap">
<ion-text color="primary">
<h3>{{ item.alias }}
<span *ngIf="item.id != item.alias"> ({{ item.id }})</span>
<small *ngIf="!item.isEnabled"> (<span
translate>General.componentInactive</span>) </small>
</h3>
</ion-text>
<ng-container *ngIf="config.factories[item.factoryId] as factory">
<p>{{ factory.name }}</p>
</ng-container>
<ng-container *ngIf="item['showProperties']">
<ng-container *ngFor="let address of subscribedInfoChannels">
<ng-container
*ngIf="address.componentId == item.id && address.channelId != 'State'">
<ng-container *ngIf="currentData.channel[address.toString()] == 1">
<ion-label *ngIf="item.channels[address.channelId]['text'].length != 0">
<ng-container
[ngSwitch]="item.channels[address.channelId]['level']">
<ion-text *ngSwitchCase="'INFO'" color="success" translate>
General.info&nbsp;
</ion-text>
<ion-text *ngSwitchCase="'WARNING'" color="warning" translate>
General.warning&nbsp;
</ion-text>
<ion-text *ngSwitchCase="'FAULT'" color="danger" translate>
General.fault&nbsp;
</ion-text>
</ng-container>
{{ item.channels[address.channelId]['text'] }}
</ion-label>
<ion-label *ngIf="item.channels[address.channelId]['text'].length == 0">
<ng-container
[ngSwitch]="item.channels[address.channelId]['level']">
<ion-text *ngSwitchCase="'INFO'" color="success" translate>
General.info&nbsp;
</ion-text>
<ion-text *ngSwitchCase="'WARNING'" color="warning" translate>
General.warning&nbsp;
</ion-text>
<ion-text *ngSwitchCase="'FAULT'" color="danger" translate>
General.fault&nbsp;
</ion-text>
</ng-container>
{{ address.channelId.toString() }}
</ion-label>
</ng-container>
</ng-container>
</ng-container>
</ng-container>
</ion-label>
<ion-icon style="cursor: pointer"
*ngIf="currentData.channel[item.id+'/State'] != null && currentData.channel[item.id+'/State'] > 0 && !item['showProperties']"
name="arrow-down-circle-outline" color="primary"
(click)="item['showProperties'] = !item['showProperties']; subscribeInfoChannels(item)">
</ion-icon>

<ion-icon style="cursor: pointer" *ngIf="item['showProperties'] == true"
name="arrow-up-circle-outline" color="primary"
(click)="item['showProperties'] = !item['showProperties']; unsubscribeInfoChannels(item)">
</ion-icon>

<ng-container [ngSwitch]="currentData.channel[item.id+'/State']">
<ion-icon *ngSwitchCase="0" color="success" name="checkmark-circle-outline"></ion-icon>
<ion-icon *ngSwitchCase="1" color="success" name="information-outline"></ion-icon>
<ion-icon *ngSwitchCase="2" color="warning" name="alert-outline"></ion-icon>
<ion-icon *ngSwitchCase="3" color="danger" name="alert-outline"></ion-icon>
</ng-container>
</ion-item>
</ng-container>
</ion-list>
</ion-card-content>
</ion-content>
</ng-container>
</ng-container>
90 changes: 90 additions & 0 deletions ui/src/app/shared/status/single/status.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { ActivatedRoute } from '@angular/router';
import { Component } from '@angular/core';
import { Edge, Service, Websocket, ChannelAddress } from '../../../shared/shared';
import { ModalController } from '@ionic/angular';
import { environment } from 'src/environments';
import { CategorizedComponents, EdgeConfig } from '../../edge/edgeconfig';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';

@Component({
selector: StatusSingleComponent.SELECTOR,
templateUrl: './status.component.html'
})
export class StatusSingleComponent {

private stopOnDestroy: Subject<void> = new Subject<void>();

public edge: Edge;
public config: EdgeConfig;
public components: CategorizedComponents[];
public subscribedInfoChannels: ChannelAddress[] = [];
public onInfoChannels: ChannelAddress[] = [];
public env = environment;


private static readonly SELECTOR = "statussingle";


constructor(
private route: ActivatedRoute,
public modalCtrl: ModalController,
public service: Service,
private websocket: Websocket,
) { }

ngOnInit() {
this.service.getConfig().then(config => {
this.config = config;
let categorizedComponentIds: string[] = []
this.components = config.listActiveComponents(categorizedComponentIds);
this.components.forEach(categorizedComponent => {
categorizedComponent.components.forEach(component => {
// sets all arrow buttons to standard position (folded)
component['showProperties'] = false;
this.subscribedInfoChannels.push(
new ChannelAddress(component.id, 'State')
)
})
})
//need to subscribe on currentedge because component is opened by app.component
this.service.currentEdge.pipe(takeUntil(this.stopOnDestroy)).subscribe(edge => {
this.edge = edge;
edge.subscribeChannels(this.websocket, StatusSingleComponent.SELECTOR, this.subscribedInfoChannels);
})
})
}

public subscribeInfoChannels(component: EdgeConfig.Component) {
Object.keys(component.channels).forEach(channel => {
if (component.channels[channel]['level']) {
this.subscribedInfoChannels.push(new ChannelAddress(component.id, channel))
this.onInfoChannels.push(new ChannelAddress(component.id, channel))
}
});
if (this.edge) {
this.edge.subscribeChannels(this.websocket, StatusSingleComponent.SELECTOR, this.subscribedInfoChannels);
}
}

public unsubscribeInfoChannels(component: EdgeConfig.Component) {
//removes unsubscribed elements from subscribedInfoChannels array
this.onInfoChannels.forEach(onInfoChannel => {
this.subscribedInfoChannels.forEach((subChannel, index) => {
if (onInfoChannel.channelId == subChannel.channelId && component.id == subChannel.componentId) {
this.subscribedInfoChannels.splice(index, 1);
}
});
})
//clear onInfoChannels Array
this.onInfoChannels = this.onInfoChannels.filter((channel) => channel.componentId != component.id)
if (this.edge) {
this.edge.subscribeChannels(this.websocket, StatusSingleComponent.SELECTOR, this.subscribedInfoChannels);
}
}

ngOnDestroy() {
this.stopOnDestroy.next();
this.stopOnDestroy.complete();
}
}
Loading

0 comments on commit 135ae7b

Please sign in to comment.