Skip to content

Commit

Permalink
timescale-rules: add templates
Browse files Browse the repository at this point in the history
  • Loading branch information
franzmueller committed Oct 25, 2023
1 parent 6287143 commit 2344cde
Show file tree
Hide file tree
Showing 8 changed files with 384 additions and 18 deletions.
8 changes: 7 additions & 1 deletion src/app/modules/admin/admin.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ import {
import {MatPaginatorModule} from '@angular/material/paginator';
import { BudgetComponent } from './budget/budget.component';
import { BudgetCreateEditComponent } from './budget/budget-create-edit/budget-create-edit.component';
import {
TimescaleRulesCreateEditTemplateComponent
} from "./timescale-rules/timescale-rules-create-edit-template/timescale-rules-create-edit-template.component";
import {MatExpansionModule} from "@angular/material/expansion";

const listRules: Route[] = [
{
Expand Down Expand Up @@ -61,6 +65,7 @@ const listRules: Route[] = [
PermissionsDialogImportComponent,
TimescaleRulesComponent,
TimescaleRulesCreateEditComponent,
TimescaleRulesCreateEditTemplateComponent,
BudgetComponent,
BudgetCreateEditComponent
],
Expand Down Expand Up @@ -89,7 +94,8 @@ const listRules: Route[] = [
InfiniteScrollModule,
WidgetModule,
MatPaginatorModule,
FlexLayoutModule
FlexLayoutModule,
MatExpansionModule
]
})
export class AdminModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,16 @@ export interface TimescaleRuleModel {
delete_template: string;
errors?: string[];
completed_run: boolean;
type: string;
template?: string;
}

export interface TimescaleRuleTemplateModel {
name: string;
description: string;
priority: number;
group: string;
command_template: string;
delete_template: string;
}

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
import {Injectable} from '@angular/core';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {ErrorHandlerService} from '../../../../core/services/error-handler.service';
import {TimescaleRuleModel} from './timescale-rule.model';
import {TimescaleRuleModel, TimescaleRuleTemplateModel} from './timescale-rule.model';
import {catchError, map} from 'rxjs/operators';
import {Observable} from 'rxjs';
import {environment} from '../../../../../environments/environment';
Expand Down Expand Up @@ -50,13 +50,43 @@ export class TimescaleRulesService {
);
}

createRuleFromTemplate(r: TimescaleRuleModel): Observable<TimescaleRuleModel | null> {
return this.http.post<TimescaleRuleModel>(environment.timescaleRuleManagerUrl + '/template-rules', r).pipe(
catchError((error: HttpErrorResponse) => this.errorHandlerService.handleErrorWithSnackBar('Error creating rule: ' + this.formatErr(error.error), TimescaleRulesService.name, 'createRule()', null)(error)),
);
}

updateRule(r: TimescaleRuleModel): Observable<boolean> {
return this.http.put(environment.timescaleRuleManagerUrl + '/rules/' + r.id, r).pipe(
map((_) => true),
catchError((error: HttpErrorResponse) => this.errorHandlerService.handleErrorWithSnackBar('Error updating rule: ' + this.formatErr(error.error), TimescaleRulesService.name, 'updateRule()', false)(error)),
);
}

updateRuleFromTemplate(r: TimescaleRuleModel): Observable<boolean> {
return this.http.put(environment.timescaleRuleManagerUrl + '/template-rules/' + r.id, r).pipe(
map((_) => true),
catchError((error: HttpErrorResponse) => this.errorHandlerService.handleErrorWithSnackBar('Error updating rule: ' + this.formatErr(error.error), TimescaleRulesService.name, 'updateRule()', false)(error)),
);
}

getTemplates(): Observable<TimescaleRuleTemplateModel[]> {
return this.http.get<any>(environment.timescaleRuleManagerUrl + '/templates').pipe(
map((resp) => {
const arr: TimescaleRuleTemplateModel[] = [];
Object.keys(resp).forEach((k) => {
const v = resp[k];
if (v !== undefined) {
v.name = k;
arr.push(v);
}
});
return arr;
}),
catchError(this.errorHandlerService.handleErrorWithSnackBar('Error loading templates', TimescaleRulesService.name, 'getTemplates()', [])),
);
}

private formatErr(err: any): string {
if (typeof err === 'string' || err instanceof String) {
return err as string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2023 InfAI (CC SES)
*
* 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-mdc-dialog-content {
padding-top: 8px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<!--
~
~ Copyright 2023 InfAI (CC SES)
~
~ 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.
~
-->
<h2 mat-dialog-title>Timescale Rule</h2>

<mat-dialog-content>


<form [formGroup]="form" fxLayout="column">
<div>
<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>ID</mat-label>
<input matInput formControlName="id">
</mat-form-field>
</div>

<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>Template</mat-label>
<senergy-select-search [options]="templates" formControlName="template" useOptionViewProperty="name"
useOptionValueProperty="name" [required]="true"></senergy-select-search>
</mat-form-field>

<div>
<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>Description</mat-label>
<input matInput formControlName="description" required>
</mat-form-field>
</div>

<div>
<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>Target</mat-label>
<mat-select formControlName="target" placeholder="Target" required>
<mat-option [value]="'device'">
Devices
</mat-option>
<mat-option [value]="'export'">
Exports
</mat-option>
</mat-select>
</mat-form-field>
</div>

<div>
<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>Users</mat-label>
<senergy-select-search [options]="users" [multiple]="true" formControlName="users"
useOptionViewProperty="username"
useOptionValueProperty="id"></senergy-select-search>
</mat-form-field>
</div>

<div>
<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>Roles</mat-label>
<senergy-select-search [options]="roles" [multiple]="true"
formControlName="roles"></senergy-select-search>
</mat-form-field>
</div>

<div *ngIf="rule?.errors !== null && rule?.errors !== undefined && (rule?.errors?.length || 0) > 0">
<mat-form-field color="accent" appearance="outline" fxFill disabled="">
<mat-label>Errors</mat-label>
<textarea matInput [value]="(rule?.errors || []).join('\n')" cdkTextareaAutosize cdkAutosizeMinRows="1"
disabled></textarea>
</mat-form-field>
</div>

<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-title fxFlex>Details</mat-panel-title>
</mat-expansion-panel-header>
<ng-template matExpansionPanelContent>

<div>
<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>Group</mat-label>
<input matInput formControlName="group" required>
</mat-form-field>
</div>

<div>
<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>Priority</mat-label>
<input matInput formControlName="priority" required type="number">
</mat-form-field>
</div>

<div>
<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>Command Template</mat-label>
<textarea matInput formControlName="command_template" required cdkTextareaAutosize
cdkAutosizeMinRows="5"></textarea>
</mat-form-field>
</div>

<div>
<mat-form-field color="accent" appearance="outline" fxFill>
<mat-label>Delete Template</mat-label>
<textarea matInput formControlName="delete_template" cdkTextareaAutosize
cdkAutosizeMinRows="1"></textarea>
</mat-form-field>
</div>
</ng-template>
</mat-expansion-panel>
</form>
</mat-dialog-content>


<mat-dialog-actions fxLayoutAlign="end center">
<button mat-raised-button color="primary" (click)="close()">Cancel</button>
<button mat-raised-button color="accent" (click)="save()" [disabled]="form.invalid" *ngIf="editable">Save</button>
</mat-dialog-actions>
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright 2023 InfAI (CC SES)
*
* 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.
*/

import {Component, Inject} from '@angular/core';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {TimescaleRuleModel, TimescaleRuleTemplateModel} from '../shared/timescale-rule.model';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import {TimescaleRulesService} from '../shared/timescale-rules.service';

@Component({
selector: 'senergy-timescale-rules-create-edit-template',
templateUrl: './timescale-rules-create-edit-template.component.html',
styleUrls: ['./timescale-rules-create-edit-template.component.css']
})
export class TimescaleRulesCreateEditTemplateComponent {

rule?: TimescaleRuleModel;
editable = false;
roles: string[] = [];
users: any[] = [];
form: FormGroup = this.fb.group({
id: {value: '', disabled: true},
description: [{value: '', disabled: true}, Validators.required],
priority: [{value: 0, disabled: true}, Validators.required],
group: [{value: '', disabled: true}, Validators.required],
target: ['', Validators.required],
users: [],
roles: [],
command_template: [{value: '', disabled: true}, Validators.required],
delete_template: {value: '', disabled: true},
errors: {value: null, disabled: true},
template: ['', Validators.required],
});
create = false;
templates: TimescaleRuleTemplateModel[] = [];

constructor(
private dialogRef: MatDialogRef<TimescaleRulesCreateEditTemplateComponent>,
private fb: FormBuilder,
@Inject(MAT_DIALOG_DATA) data: {
rule?: TimescaleRuleModel;
editable: boolean;
roles: string[];
users: any[];
templates: TimescaleRuleTemplateModel[];
},
private timescaleRuleService: TimescaleRulesService,
) {
this.rule = data.rule;
this.roles = data.roles;
this.users = data.users;
this.editable = data.editable;
this.templates = data.templates;
if (this.rule !== undefined) {
this.form.patchValue(this.rule);
}
if (!this.editable) {
this.form.disable();
}
this.create = data.rule === undefined;
this.form.get('template')?.valueChanges.subscribe(name => {
const template = this.templates.find(t => t.name === name);
if (template === undefined) {
return;
}
this.form.patchValue({
description: template.description,
priority: template.priority,
group: template.group,
command_template: template.command_template,
delete_template: template.delete_template,
});
});
}

save() {
this.rule = this.form.getRawValue() as TimescaleRuleModel;
this.rule.errors = undefined;
this.rule.completed_run = false;
if (this.create) {
this.timescaleRuleService.createRuleFromTemplate(this.rule).subscribe(rule => {
if (rule !== null) {
this.dialogRef.close(rule);
}
});
} else {
this.timescaleRuleService.updateRuleFromTemplate(this.rule).subscribe(t => {
if (t) {
this.dialogRef.close(this.rule);
}
});
}
}

close() {
this.dialogRef.close();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,17 @@
<td mat-cell *matCellDef="let m">{{m.priority}}</td>
</ng-container>

<ng-container matColumnDef="users">
<th mat-header-cell *matHeaderCellDef>Users</th>
<td mat-cell *matCellDef="let m">{{usernames(m.users)}}</td>
</ng-container>

<ng-container matColumnDef="roles">
<th mat-header-cell *matHeaderCellDef>Roles</th>
<td mat-cell *matCellDef="let m">{{m.roles}}</td>
</ng-container>


<ng-container matColumnDef="errors">
<th mat-header-cell *matHeaderCellDef>Errors</th>
<td mat-cell *matCellDef="let m">
Expand Down Expand Up @@ -86,13 +97,18 @@


<tr mat-header-row
*matHeaderRowDef="['description', 'group', 'priority', 'errors', 'details', 'edit', 'delete']; sticky: true"></tr>
*matHeaderRowDef="['description', 'group', 'priority', 'users', 'roles', 'errors', 'details', 'edit', 'delete']; sticky: true"></tr>
<tr mat-row
*matRowDef="let m; columns: ['description', 'group', 'priority', 'errors', 'details', 'edit', 'delete'];"></tr>
*matRowDef="let m; columns: ['description', 'group', 'priority', 'users', 'roles', 'errors', 'details', 'edit', 'delete'];"></tr>
</table>
</div>

<button mat-fab class="fab" (click)="add()" matTooltip="Add new import type">
<button mat-fab class="fab" (click)="addFromTemplate()" matTooltip="Add new rule from template">
<mat-icon>add</mat-icon>
</button>
<!--
<button mat-fab class="fab" (click)="add()" matTooltip="Add new custom rule">
<mat-icon>add</mat-icon>
</button>
-->
</div>
Loading

0 comments on commit 2344cde

Please sign in to comment.