Improved the code for handling policies.

This commit is contained in:
Tom 2025-04-07 18:49:44 +00:00
parent f2c5178e82
commit f4511157a5
9 changed files with 98 additions and 121 deletions

View File

@ -37,7 +37,8 @@
[groups]="groups" [groups]="groups"
[policies]="policies" [policies]="policies"
[group]="group?.id" /> [group]="group?.id" />
<policy-table [policies]="policies" /> <policy-table [policies]="policies"
[groups]="groups"/>
</mat-expansion-panel> </mat-expansion-panel>
<mat-expansion-panel> <mat-expansion-panel>

View File

@ -1,4 +1,4 @@
import { Component, EventEmitter, inject, Input, Output } from '@angular/core'; import { Component, inject, input } from '@angular/core';
import { Policy } from '../../shared/models/policy'; import { Policy } from '../../shared/models/policy';
import { PolicyItemEditComponent } from '../policy-item-edit/policy-item-edit.component'; import { PolicyItemEditComponent } from '../policy-item-edit/policy-item-edit.component';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -17,28 +17,21 @@ import { Group } from '../../shared/models/group';
}) })
export class PolicyAddButtonComponent { export class PolicyAddButtonComponent {
private readonly dialog = inject(MatDialog); private readonly dialog = inject(MatDialog);
@Input({ required: true }) policies: Policy[] = [];
@Input({ required: true }) groups: Group[] = []; policies = input.required<Policy[]>();
@Input() group: string | undefined = undefined; groups = input.required<Group[]>();
@Output() policy = new EventEmitter<Policy>(); group = input<string | undefined>();
openDialog(): void { openDialog(): void {
const dialogRef = this.dialog.open(PolicyItemEditComponent, { this.dialog.open(PolicyItemEditComponent, {
data: { data: {
policies: this.policies, policies: this.policies(),
groups: this.groups, groups: this.groups(),
group_id: this.group, group_id: this.group(),
groupDisabled: !!this.group, groupDisabled: !!this.group(),
isNew: true, isNew: true,
} }
}); });
dialogRef.afterClosed().subscribe((result: Policy) => {
if (!result)
return;
this.policy.emit(result);
});
} }
} }

View File

@ -7,8 +7,11 @@
[formControl]="policyControl" [formControl]="policyControl"
[matAutocomplete]="auto" /> [matAutocomplete]="auto" />
<mat-autocomplete #auto="matAutocomplete"> <mat-autocomplete #auto="matAutocomplete">
@for (option of filteredPolicies | async; track option) { @for (option of filteredPolicies | async; track option.path) {
<mat-option [value]="option">{{option}}</mat-option> <mat-option [value]="option.path">
<p class="path">{{option.path}}</p>
<p class="description muted">{{option.description}}</p>
</mat-option>
} }
</mat-autocomplete> </mat-autocomplete>
@if (policyControl.invalid && (policyControl.dirty || policyControl.touched)) { @if (policyControl.invalid && (policyControl.dirty || policyControl.touched)) {

View File

@ -0,0 +1,12 @@
p {
margin: 0;
padding: 0;
}
.description {
font-size: smaller;
}
.muted {
color: #999999
}

View File

@ -4,9 +4,14 @@ import { FormControl, FormsModule, ReactiveFormsModule, Validators } from '@angu
import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { MatInputModule } from '@angular/material/input'; import { MatInputModule } from '@angular/material/input';
import { map, Observable, startWith } from 'rxjs'; import { EMPTY, map, Observable, startWith } from 'rxjs';
const Policies = [ interface PolicyData {
path: string;
description: string;
}
const Policies: PolicyData[] = [
{ path: "tts", description: "Anything to do with TTS" }, { path: "tts", description: "Anything to do with TTS" },
{ path: "tts.chat", description: "Anything to do with chat" }, { path: "tts.chat", description: "Anything to do with chat" },
{ path: "tts.chat.bits.read", description: "To read chat messages with bits via TTS" }, { path: "tts.chat.bits.read", description: "To read chat messages with bits via TTS" },
@ -43,14 +48,7 @@ const Policies = [
export class PolicyDropdownComponent { export class PolicyDropdownComponent {
@Input() policy: string | null = ''; @Input() policy: string | null = '';
@Input({ alias: 'control' }) policyControl = new FormControl('', [Validators.required]); @Input({ alias: 'control' }) policyControl = new FormControl('', [Validators.required]);
filteredPolicies: Observable<string[]>; filteredPolicies: Observable<PolicyData[]> = EMPTY;
constructor() {
this.filteredPolicies = this.policyControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value || '')),
);
}
ngOnInit() { ngOnInit() {
this.policyControl.setValue(this.policy); this.policyControl.setValue(this.policy);
@ -60,13 +58,12 @@ export class PolicyDropdownComponent {
); );
} }
private _filter(value: string): string[] { private _filter(value: string): PolicyData[] {
const filterValue = value.toLowerCase(); const filterValue = value.toLowerCase();
const names = Policies.map(p => p.path); if (Policies.map(p => p.path).includes(filterValue)) {
if (names.includes(filterValue)) { return Policies;
return names;
} }
return names.filter(option => option.toLowerCase().includes(filterValue)); return Policies.filter(option => option.path.toLowerCase().includes(filterValue));
} }
} }

View File

@ -1,5 +1,5 @@
<table mat-table <table mat-table
[dataSource]="policies" [dataSource]="policies()"
class="mat-elevation-z8"> class="mat-elevation-z8">
<ng-container matColumnDef="path"> <ng-container matColumnDef="path">
<th mat-header-cell <th mat-header-cell

View File

@ -1,79 +1,48 @@
import { AfterViewInit, Component, inject, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewChild } from '@angular/core'; import { Component, inject, input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatTable, MatTableModule } from '@angular/material/table'; import { MatTable, MatTableModule } from '@angular/material/table';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import EventService from '../../shared/services/EventService';
import { Policy } from '../../shared/models/policy'; import { Policy } from '../../shared/models/policy';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { HermesClientService } from '../../hermes-client.service'; import { HermesClientService } from '../../hermes-client.service';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
import { ActivatedRoute } from '@angular/router';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { PolicyItemEditComponent } from '../policy-item-edit/policy-item-edit.component'; import { PolicyItemEditComponent } from '../policy-item-edit/policy-item-edit.component';
import { Group } from '../../shared/models/group'; import { Group } from '../../shared/models/group';
import PolicyService from '../../shared/services/policy.service';
@Component({ @Component({
selector: 'policy-table', selector: 'policy-table',
imports: [FormsModule, MatButtonModule, MatTableModule, MatIconModule], imports: [
FormsModule,
MatButtonModule,
MatTableModule,
MatIconModule,
],
templateUrl: './policy-table.component.html', templateUrl: './policy-table.component.html',
styleUrl: './policy-table.component.scss' styleUrl: './policy-table.component.scss'
}) })
export class PolicyTableComponent implements OnInit, OnDestroy, AfterViewInit { export class PolicyTableComponent implements OnInit, OnDestroy {
private readonly route = inject(ActivatedRoute); private readonly client = inject(HermesClientService);
private readonly hermes = inject(HermesClientService); private readonly policyService = inject(PolicyService);
private readonly events = inject(EventService);
private readonly dialog = inject(MatDialog); private readonly dialog = inject(MatDialog);
@Input() policies: Policy[] = []; policies = input.required<Policy[]>();
groups = input.required<Group[]>();
@ViewChild(MatTable) table: MatTable<Policy>; @ViewChild(MatTable) table: MatTable<Policy>;
readonly displayedColumns = ['path', 'group', 'usage', 'span', 'actions']; readonly displayedColumns = ['path', 'group', 'usage', 'span', 'actions'];
private readonly _subscriptions: any[] = []; private readonly _subscriptions: any[] = [];
groups: Group[] = [];
constructor() { constructor() {
this.table = {} as MatTable<Policy>; this.table = {} as MatTable<Policy>;
} }
ngOnInit(): void { ngOnInit(): void {
this.route.data.subscribe(r => { this._subscriptions.push(this.policyService.create$?.subscribe(_ => this.table.renderRows()));
this.groups = [...r['groups']]; this._subscriptions.push(this.policyService.update$?.subscribe(_ => this.table.renderRows()));
}); this._subscriptions.push(this.policyService.delete$?.subscribe(_ => this.table.renderRows()));
this._subscriptions.push(this.events.listen('addPolicy', (payload) => {
if (!payload || this.policies.map(p => p.path).includes(payload))
return;
this.policies.push(payload);
this.table.renderRows();
}));
this._subscriptions.push(this.hermes.subscribeToRequests('create_policy', response => {
const policy = this.policies.find(p => p.path == response.data.path);
if (policy == null) {
this.policies.push(response.data);
}
this.table.renderRows();
}));
this._subscriptions.push(this.hermes.subscribeToRequests('update_policy', response => {
const policy = this.policies.find(p => p.id == response.data.id);
if (policy != null) {
policy.id = response.data.id;
policy.group_id = response.data.group_id;
}
this.table.renderRows();
}));
this._subscriptions.push(this.hermes.subscribeToRequests('delete_policy', response => {
this.policies = this.policies.filter(p => p.id != response.request.data.id);
this.table.renderRows();
}));
}
ngAfterViewInit(): void {
this.table.renderRows();
} }
ngOnDestroy(): void { ngOnDestroy(): void {
@ -81,33 +50,23 @@ export class PolicyTableComponent implements OnInit, OnDestroy, AfterViewInit {
} }
delete(policy: Policy) { delete(policy: Policy) {
this.hermes.deletePolicy(policy.id); this.client.deletePolicy(policy.id);
} }
edit(policy: Policy) { edit(policy: Policy) {
const dialogRef = this.dialog.open(PolicyItemEditComponent, { this.dialog.open(PolicyItemEditComponent, {
data: { data: {
policies: this.policies, policies: this.policies(),
groups: this.groups, groups: this.groups(),
policy_id: policy.id, policy_id: policy.id,
group_id: policy.group_id, group_id: policy.group_id,
groupDisabled: true, groupDisabled: true,
isNew: false, isNew: false,
} }
}); });
dialogRef.afterClosed().subscribe((result: Policy) => {
if (!result)
return;
policy.group_id = result.group_id;
policy.path = result.path;
policy.usage = result.usage;
policy.span = result.span;
});
} }
getGroupById(group_id: string) { getGroupById(group_id: string) {
return this.groups.find((g: Group) => g.id == group_id); return this.groups().find((g: Group) => g.id == group_id);
} }
} }

View File

@ -1,10 +1,10 @@
<h4>Policies</h4> <h4>Policies</h4>
<div class="add"> <div class="add">
<policy-add-button [policies]="policies" <policy-add-button [policies]="policies"
[groups]="groups" [groups]="groups" />
(policy)="addPolicy($event)" />
</div> </div>
<div> <div>
<policy-table [policies]="policies" /> <policy-table [policies]="policies"
[groups]="groups" />
</div> </div>

View File

@ -1,4 +1,4 @@
import { Component, inject } from '@angular/core'; import { Component, inject, OnDestroy } from '@angular/core';
import { PolicyTableComponent } from "../policy-table/policy-table.component"; import { PolicyTableComponent } from "../policy-table/policy-table.component";
import { Policy } from '../../shared/models/policy'; import { Policy } from '../../shared/models/policy';
import { ActivatedRoute, RouterModule } from '@angular/router'; import { ActivatedRoute, RouterModule } from '@angular/router';
@ -6,43 +6,55 @@ import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { PolicyAddButtonComponent } from "../policy-add-button/policy-add-button.component"; import { PolicyAddButtonComponent } from "../policy-add-button/policy-add-button.component";
import { Group } from '../../shared/models/group'; import { Group } from '../../shared/models/group';
import PolicyService from '../../shared/services/policy.service';
import GroupService from '../../shared/services/group.service';
@Component({ @Component({
selector: 'policy', selector: 'policy',
imports: [MatButtonModule, MatIconModule, PolicyTableComponent, RouterModule, PolicyAddButtonComponent], imports: [
MatButtonModule,
MatIconModule,
PolicyTableComponent,
RouterModule,
PolicyAddButtonComponent,
],
templateUrl: './policy.component.html', templateUrl: './policy.component.html',
styleUrl: './policy.component.scss' styleUrl: './policy.component.scss'
}) })
export class PolicyComponent { export class PolicyComponent implements OnDestroy {
private readonly route = inject(ActivatedRoute); private readonly route = inject(ActivatedRoute);
private readonly policyService = inject(PolicyService);
private readonly groupService = inject(GroupService);
private readonly _subscriptions: any[] = [];
private _policies: Policy[] = []; private _policies: Policy[] = [];
groups: Group[] = []; private _groups: Group[] = [];
constructor() { constructor() {
this.route.data.subscribe((data) => { this.route.data.subscribe((data) => {
const policies = [...data['policies']]; this._policies = data['policies'];
policies.sort(this.compare); this._groups = data['groups'];
this._policies = policies;
this.groups = [...data['groups']];
}); });
this._subscriptions.push(this.policyService.delete$?.subscribe(_ =>
this.policyService.fetch().subscribe(p => this._policies = p)));
this._subscriptions.push(this.groupService.deleteGroup$?.subscribe(_ =>
this.groupService.fetch().subscribe(g => this._groups = g.groups)));
}
ngOnDestroy(): void {
this._subscriptions.filter(s => !!s).forEach(s => s.unsubscribe());
} }
get policies() { get policies() {
return this._policies; return this._policies;
} }
addPolicy(policy: Policy) { get groups() {
let index = -1; return this._groups;
for (let i = 0; i < this._policies.length; i++) {
const comp = this.compare(policy, this._policies[i]);
if (comp < 0) {
index = i;
break;
}
}
this._policies.splice(index >= 0 ? index : this._policies.length, 0, policy);
} }
compare(a: Policy, b: Policy) { compare(a: Policy, b: Policy) {