Added group permissions. Added some global styles. Made groups rely on services' data.
This commit is contained in:
parent
d19c5445d6
commit
9de4424736
@ -1,5 +1,5 @@
|
||||
<main>
|
||||
@for (action of actions; track $index) {
|
||||
@for (action of actions; track action.name) {
|
||||
<button type="button"
|
||||
class="container"
|
||||
(click)="modify(action)">
|
||||
|
@ -10,7 +10,7 @@
|
||||
<mat-select-trigger>
|
||||
<mat-icon matPrefix>filter_list</mat-icon> {{filter.name}}
|
||||
</mat-select-trigger>
|
||||
@for (item of filters; track $index) {
|
||||
@for (item of filters; track item.name) {
|
||||
<mat-option value="{{$index}}">{{item.name}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
|
@ -19,6 +19,7 @@ import PolicyResolver from './shared/resolvers/policy-resolver';
|
||||
import { GroupsComponent } from './groups/groups/groups.component';
|
||||
import { GroupPageComponent } from './groups/group-page/group-page.component';
|
||||
import GroupChatterResolver from './shared/resolvers/group-chatter-resolver';
|
||||
import PermissionResolver from './shared/resolvers/permission-resolver';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
@ -27,7 +28,6 @@ export const routes: Routes = [
|
||||
canActivate: [AuthUserGuard],
|
||||
resolve: {
|
||||
groups: GroupResolver,
|
||||
chatters: GroupChatterResolver,
|
||||
policies: PolicyResolver,
|
||||
}
|
||||
},
|
||||
@ -39,6 +39,7 @@ export const routes: Routes = [
|
||||
groups: GroupResolver,
|
||||
chatters: GroupChatterResolver,
|
||||
policies: PolicyResolver,
|
||||
permissions: PermissionResolver,
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -49,6 +50,7 @@ export const routes: Routes = [
|
||||
groups: GroupResolver,
|
||||
chatters: GroupChatterResolver,
|
||||
policies: PolicyResolver,
|
||||
permissions: PermissionResolver,
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1,24 +1,28 @@
|
||||
<article>
|
||||
<section class="title">{{item().group.name}}
|
||||
<section class="title">{{group().name}}
|
||||
@if (special) {
|
||||
<small class="tag">auto-generated</small>
|
||||
}
|
||||
</section>
|
||||
<section class="">
|
||||
{{item().group.priority}}
|
||||
{{group().priority}}
|
||||
<small class="muted block">priority</small>
|
||||
</section>
|
||||
<section>
|
||||
@if (special) {
|
||||
<p class="muted">Unknown</p>
|
||||
} @else {
|
||||
{{item().chatters.length}}
|
||||
<small class="muted block">user{{item().chatters.length == 1 ? '' : 's'}}</small>
|
||||
{{chatters().length}}
|
||||
<small class="muted block">user{{chatters().length == 1 ? '' : 's'}}</small>
|
||||
}
|
||||
</section>
|
||||
<section>
|
||||
{{item().policies.length}}
|
||||
<small class="muted block">polic{{item().chatters.length == 1 ? 'y' : 'ies'}}</small>
|
||||
{{permissions().length}}
|
||||
<small class="muted block">permission{{permissions().length == 1 ? '' : 's'}}</small>
|
||||
</section>
|
||||
<section>
|
||||
{{policies().length}}
|
||||
<small class="muted block">polic{{policies().length == 1 ? 'y' : 'ies'}}</small>
|
||||
</section>
|
||||
<section>
|
||||
<button mat-button
|
||||
|
@ -7,6 +7,7 @@ import { Policy } from '../../shared/models/policy';
|
||||
import { Router } from '@angular/router';
|
||||
import { GroupChatter } from '../../shared/models/group-chatter';
|
||||
import { SpecialGroups } from '../../shared/utils/groups';
|
||||
import { Permission } from '../../shared/models/permission';
|
||||
|
||||
@Component({
|
||||
selector: 'group-item',
|
||||
@ -21,12 +22,15 @@ import { SpecialGroups } from '../../shared/utils/groups';
|
||||
})
|
||||
export class GroupItemComponent implements OnInit {
|
||||
readonly router = inject(Router);
|
||||
item = input.required<{ group: Group, chatters: GroupChatter[], policies: Policy[] }>();
|
||||
group = input.required<Group>();
|
||||
chatters = input.required<GroupChatter[]>();
|
||||
permissions = input.required<Permission[]>();
|
||||
policies = input.required<Policy[]>();
|
||||
link: string = '';
|
||||
special: boolean = true;
|
||||
|
||||
ngOnInit() {
|
||||
this.special = SpecialGroups.includes(this.item().group.name);
|
||||
this.link = 'groups/' + this.item().group.id;
|
||||
this.special = SpecialGroups.includes(this.group().name);
|
||||
this.link = 'groups/' + this.group().id;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
<ul>
|
||||
@for (group of groups; track $index) {
|
||||
@for (group of groups; track group.id) {
|
||||
<li>
|
||||
<group-item [item]="group" />
|
||||
<group-item [group]="group"
|
||||
[chatters]="getChattersByGroup(group.id)"
|
||||
[permissions]="getPermissionsByGroup(group.id)"
|
||||
[policies]="getPoliciesByGroup(group.id)" />
|
||||
</li>
|
||||
}
|
||||
</ul>
|
@ -1,8 +1,9 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, input, Input } from '@angular/core';
|
||||
import { Group } from '../../shared/models/group';
|
||||
import { GroupItemComponent } from "../group-item/group-item.component";
|
||||
import { Policy } from '../../shared/models/policy';
|
||||
import { GroupChatter } from '../../shared/models/group-chatter';
|
||||
import { Permission } from '../../shared/models/permission';
|
||||
|
||||
@Component({
|
||||
selector: 'group-list',
|
||||
@ -12,25 +13,35 @@ import { GroupChatter } from '../../shared/models/group-chatter';
|
||||
styleUrl: './group-list.component.scss'
|
||||
})
|
||||
export class GroupListComponent {
|
||||
private _groups: { group: Group, chatters: GroupChatter[], policies: Policy[] }[] = [];
|
||||
private _filter: (item: { group: Group, chatters: GroupChatter[], policies: Policy[] }) => boolean = _ => true;
|
||||
_groups = input.required<Group[]>({ alias: 'groups' });
|
||||
chatters = input.required<GroupChatter[]>();
|
||||
permissions = input.required<Permission[]>();
|
||||
policies = input.required<Policy[]>();
|
||||
private _filter: (item: Group) => boolean = _ => true;
|
||||
|
||||
|
||||
get filter(): (item: { group: Group, chatters: GroupChatter[], policies: Policy[] }) => boolean {
|
||||
get filter(): (group: Group) => boolean {
|
||||
return this._filter;
|
||||
}
|
||||
|
||||
@Input({ alias: 'filter', required: false })
|
||||
set filter(value: (item: { group: Group, chatters: GroupChatter[], policies: Policy[] }) => boolean) {
|
||||
set filter(value: (item: Group) => boolean) {
|
||||
this._filter = value;
|
||||
}
|
||||
|
||||
get groups() {
|
||||
return this._groups.filter(this._filter);
|
||||
return this._groups().filter(g => this._filter(g));
|
||||
}
|
||||
|
||||
@Input({ alias: 'groups', required: true })
|
||||
set groups(value: { group: Group, chatters: GroupChatter[], policies: Policy[] }[]) {
|
||||
this._groups = value;
|
||||
getChattersByGroup(groupId: string) {
|
||||
return this.chatters().filter(c => c.group_id == groupId);
|
||||
}
|
||||
|
||||
getPermissionsByGroup(groupId: string) {
|
||||
return this.permissions().filter(c => c.group_id == groupId);
|
||||
}
|
||||
|
||||
getPoliciesByGroup(groupId: string) {
|
||||
return this.policies().filter(c => c.group_id == groupId);
|
||||
}
|
||||
}
|
@ -14,6 +14,18 @@
|
||||
</mat-expansion-panel>
|
||||
}
|
||||
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>Permissions</mat-panel-title>
|
||||
<mat-panel-description class="muted">
|
||||
{{permissions.length}} permission{{permissions.length == 1 ? '' : 's'}}
|
||||
</mat-panel-description>
|
||||
</mat-expansion-panel-header>
|
||||
<permission-list [permissions]="permissions"
|
||||
[groups]="groups"
|
||||
[group]="group" />
|
||||
</mat-expansion-panel>
|
||||
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>Policies</mat-panel-title>
|
||||
@ -43,7 +55,7 @@
|
||||
</article>
|
||||
<article class="right">
|
||||
<button mat-raised-button
|
||||
class="delete"
|
||||
class="danger"
|
||||
(click)="delete()">
|
||||
<mat-icon>delete</mat-icon>Delete this group.
|
||||
</button>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { Component, inject, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Group } from '../../shared/models/group';
|
||||
import { Policy } from '../../shared/models/policy';
|
||||
@ -15,6 +15,12 @@ import { HermesClientService } from '../../hermes-client.service';
|
||||
import { GroupChatter } from '../../shared/models/group-chatter';
|
||||
import { TwitchUsersModule } from "../../twitch-users/twitch-users.module";
|
||||
import { SpecialGroups } from '../../shared/utils/groups';
|
||||
import { PermissionListComponent } from "../../permissions/permission-list/permission-list.component";
|
||||
import { Permission } from '../../shared/models/permission';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { PermissionService } from '../../shared/services/permission.service';
|
||||
import GroupService from '../../shared/services/group.service';
|
||||
import PolicyService from '../../shared/services/policy.service';
|
||||
|
||||
@Component({
|
||||
imports: [
|
||||
@ -29,30 +35,42 @@ import { SpecialGroups } from '../../shared/utils/groups';
|
||||
PolicyTableComponent,
|
||||
PolicyTableComponent,
|
||||
TwitchUsersModule,
|
||||
PermissionListComponent
|
||||
],
|
||||
templateUrl: './group-page.component.html',
|
||||
styleUrl: './group-page.component.scss'
|
||||
})
|
||||
export class GroupPageComponent {
|
||||
export class GroupPageComponent implements OnDestroy {
|
||||
private readonly _router = inject(Router);
|
||||
private readonly _route = inject(ActivatedRoute);
|
||||
private readonly _groupService = inject(GroupService);
|
||||
private readonly _permissionService = inject(PermissionService);
|
||||
private readonly _policyService = inject(PolicyService);
|
||||
private readonly _client = inject(HermesClientService);
|
||||
private _group: Group | undefined;
|
||||
private _chatters: GroupChatter[];
|
||||
private _policies: Policy[];
|
||||
private _permissions: Permission[];
|
||||
|
||||
isSpecialGroup: boolean;
|
||||
_groups: Group[];
|
||||
|
||||
private readonly subscriptions: (Subscription | undefined)[] = [];
|
||||
|
||||
isSpecialGroup = false;
|
||||
groups: Group[] = [];
|
||||
|
||||
constructor() {
|
||||
this.isSpecialGroup = false
|
||||
this._groups = [];
|
||||
this._chatters = [];
|
||||
this._permissions = [];
|
||||
this._policies = [];
|
||||
|
||||
this._route.params.subscribe((p: any) => {
|
||||
const group_id = p.id;
|
||||
this._route.params.subscribe((params: any) => {
|
||||
// Fetch the group id from the query params.
|
||||
const group_id = params['id'];
|
||||
|
||||
this._route.data.subscribe(async (data: any) => {
|
||||
this.groups = [...data['groups']];
|
||||
this._groups = data['groups'];
|
||||
const group = this.groups.find((g: Group) => g.id == group_id);
|
||||
|
||||
if (!group) {
|
||||
@ -62,29 +80,88 @@ export class GroupPageComponent {
|
||||
|
||||
this._group = group;
|
||||
this.isSpecialGroup = SpecialGroups.includes(this.group!.name);
|
||||
this._chatters = [...data['chatters'].filter((c: GroupChatter) => c.group_id == group_id)];
|
||||
this._policies = [...data['policies'].filter((p: Policy) => p.group_id == group_id)];
|
||||
this._chatters = data['chatters'];
|
||||
this._permissions = data['permissions'];
|
||||
this._policies = data['policies'];
|
||||
});
|
||||
});
|
||||
|
||||
this.subscriptions.push(this._permissionService.delete$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._permissionService.fetch().subscribe(permissions => this._permissions = permissions);
|
||||
}));
|
||||
|
||||
this.subscriptions.push(this._groupService.deleteGroup$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._groupService.fetch().subscribe(data => this._groups = data.groups);
|
||||
}));
|
||||
|
||||
this.subscriptions.push(this._groupService.deleteChatter$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._groupService.fetch().subscribe(data => this._chatters = data.chatters);
|
||||
}));
|
||||
|
||||
this.subscriptions.push(this._policyService.delete$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._policyService.fetch().subscribe(policies => this._policies = policies);
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscriptions) {
|
||||
for (let subscription of this.subscriptions) {
|
||||
if (subscription)
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get group() {
|
||||
return this._group;
|
||||
}
|
||||
|
||||
get groups() {
|
||||
return this._groups;
|
||||
}
|
||||
|
||||
get chatters() {
|
||||
return this._chatters;
|
||||
if (!this._group) {
|
||||
return [];
|
||||
}
|
||||
return this._chatters.filter((c: GroupChatter) => c.group_id == this._group!.id);
|
||||
}
|
||||
|
||||
get permissions() {
|
||||
if (!this._group) {
|
||||
return [];
|
||||
}
|
||||
return this._permissions.filter((p: Permission) => p.group_id == this._group!.id);
|
||||
}
|
||||
|
||||
get policies() {
|
||||
return this._policies;
|
||||
if (!this._group) {
|
||||
return [];
|
||||
}
|
||||
return this._policies.filter((p: Policy) => p.group_id == this._group!.id);
|
||||
}
|
||||
|
||||
delete() {
|
||||
if (!this.group)
|
||||
return;
|
||||
|
||||
this._client.first(d => d.d.request.type == 'delete_group' && d.d.request.data.group == this.group!.id)
|
||||
this._client.first(d => d.d.request.type == 'delete_group' && d.d.request.data.id == this.group!.id)
|
||||
.subscribe(async () => await this._router.navigate(['groups']));
|
||||
this._client.deleteGroup(this.group.id);
|
||||
}
|
||||
|
@ -14,4 +14,7 @@
|
||||
}
|
||||
</mat-menu>
|
||||
<group-list class="groups"
|
||||
[groups]="items" />
|
||||
[groups]="groups"
|
||||
[chatters]="chatters"
|
||||
[permissions]="permissions"
|
||||
[policies]="policies" />
|
@ -1,4 +1,4 @@
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Component, inject, OnDestroy } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { ActivatedRoute, RouterModule } from '@angular/router';
|
||||
@ -13,6 +13,10 @@ import { MatMenuModule } from '@angular/material/menu';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import { GroupChatter } from '../../shared/models/group-chatter';
|
||||
import { SpecialGroups } from '../../shared/utils/groups';
|
||||
import { Permission } from '../../shared/models/permission';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { PermissionService } from '../../shared/services/permission.service';
|
||||
import PolicyService from '../../shared/services/policy.service';
|
||||
|
||||
@Component({
|
||||
selector: 'groups',
|
||||
@ -27,100 +31,108 @@ import { SpecialGroups } from '../../shared/utils/groups';
|
||||
templateUrl: './groups.component.html',
|
||||
styleUrl: './groups.component.scss'
|
||||
})
|
||||
export class GroupsComponent {
|
||||
private readonly _groupService = inject(GroupService);
|
||||
export class GroupsComponent implements OnDestroy {
|
||||
private readonly _client = inject(HermesClientService);
|
||||
private readonly _route = inject(ActivatedRoute);
|
||||
private readonly _dialog = inject(MatDialog);
|
||||
|
||||
private readonly _groupService = inject(GroupService);
|
||||
private readonly _permissionService = inject(PermissionService);
|
||||
private readonly _policyService = inject(PolicyService);
|
||||
private readonly subscriptions: (Subscription | undefined)[] = [];
|
||||
|
||||
readonly specialGroups = SpecialGroups;
|
||||
|
||||
items: { group: Group, chatters: GroupChatter[], policies: Policy[] }[] = [];
|
||||
private _groups: Group[] = [];
|
||||
private _chatters: GroupChatter[] = [];
|
||||
private _permissions: Permission[] = [];
|
||||
private _policies: Policy[] = [];
|
||||
|
||||
opened = false;
|
||||
|
||||
constructor() {
|
||||
this._route.data.subscribe(payload => {
|
||||
const groups = payload['groups'];
|
||||
const chatters = payload['chatters'];
|
||||
const policies = payload['policies'];
|
||||
const elements: { group: Group, chatters: GroupChatter[], policies: Policy[] }[] = [];
|
||||
this._groups = payload['groups'];
|
||||
this._chatters = payload['chatters'];
|
||||
this._permissions = payload['permissions'];
|
||||
this._policies = payload['policies'];
|
||||
});
|
||||
|
||||
for (let group of groups) {
|
||||
elements.push({
|
||||
group: group,
|
||||
chatters: chatters.filter((c: GroupChatter) => c.group_id == group.id),
|
||||
policies: policies.filter((p: Policy) => p.group_id == group.id),
|
||||
});
|
||||
this.subscriptions.push(this._permissionService.delete$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.items = elements;
|
||||
});
|
||||
this._permissionService.fetch().subscribe(permissions => this._permissions = permissions);
|
||||
}));
|
||||
|
||||
this._groupService.createGroup$?.subscribe(d => {
|
||||
if (d.error || !d.data || d.request.nounce != null && d.request.nounce.startsWith(this._client.session_id))
|
||||
this.subscriptions.push(this._groupService.deleteGroup$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
|
||||
let index = -1;
|
||||
for (let i = 0; i < this.items.length; i++) {
|
||||
const comp = this.compare(d.data, this.items[i].group);
|
||||
if (comp < 0) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.items.splice(index >= 0 ? index : this.items.length, 0, { group: d.data, chatters: [], policies: [] });
|
||||
});
|
||||
|
||||
this._groupService.updateGroup$?.subscribe(d => {
|
||||
if (d.error || !d.data || d.request.nounce != null && d.request.nounce.startsWith(this._client.session_id))
|
||||
this._groupService.fetch().subscribe(data => this._groups = data.groups);
|
||||
}));
|
||||
|
||||
this.subscriptions.push(this._groupService.deleteChatter$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
|
||||
const group = this.items.find(r => r.group.id = d.data.id)?.group;
|
||||
if (group) {
|
||||
group.id = d.data.id;
|
||||
group.name = d.data.name;
|
||||
group.priority = d.data.priority;
|
||||
}
|
||||
});
|
||||
|
||||
this._groupService.deleteGroup$?.subscribe(d => {
|
||||
if (d.error || d.request.nounce != null && d.request.nounce.startsWith(this._client.session_id))
|
||||
this._groupService.fetch().subscribe(data => this._chatters = data.chatters);
|
||||
}));
|
||||
|
||||
this.subscriptions.push(this._policyService.delete$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.items = this.items.filter(r => r.group.id != d.request.data.id);
|
||||
});
|
||||
this._policyService.fetch().subscribe(policies => this._policies = policies);
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscriptions) {
|
||||
for (let subscription of this.subscriptions) {
|
||||
if (subscription)
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get groups() {
|
||||
return this._groups;
|
||||
}
|
||||
|
||||
get chatters() {
|
||||
return this._chatters;
|
||||
}
|
||||
|
||||
get permissions() {
|
||||
return this._permissions;
|
||||
}
|
||||
|
||||
get policies() {
|
||||
return this._policies;
|
||||
}
|
||||
|
||||
openDialog(groupName: string): void {
|
||||
const group = { id: '', user_id: '', name: groupName, priority: 0 };
|
||||
if (this.opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.opened = true;
|
||||
const dialogRef = this._dialog.open(GroupItemEditComponent, {
|
||||
data: { group, isSpecial: groupName.length > 0 },
|
||||
data: { group: { id: '', user_id: '', name: groupName, priority: 0 }, isSpecial: groupName.length > 0 },
|
||||
});
|
||||
|
||||
const isNewGroup = group.id.length <= 0;
|
||||
dialogRef.afterClosed().subscribe((result: Group | undefined) => {
|
||||
if (!result)
|
||||
return;
|
||||
|
||||
|
||||
if (isNewGroup) {
|
||||
this.items.push({ group: result, chatters: [], policies: [] });
|
||||
} else {
|
||||
const same = this.items.find(i => i.group.id == group.id);
|
||||
if (same == null)
|
||||
return;
|
||||
|
||||
same.group.name = result.name;
|
||||
same.group.priority = result.priority;
|
||||
}
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((result: Group | undefined) => this.opened = false);
|
||||
}
|
||||
|
||||
compare(a: Group, b: Group) {
|
||||
return a.name.localeCompare(b.name);
|
||||
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
||||
}
|
||||
|
||||
exists(groupName: string) {
|
||||
return this.items.some(g => g.group.name == groupName);
|
||||
return this._groups.some(g => g.name == groupName);
|
||||
}
|
||||
}
|
@ -112,6 +112,17 @@ export class HermesClientService {
|
||||
});
|
||||
}
|
||||
|
||||
public createGroupPermission(groupId: string, path: string, allow: boolean | null) {
|
||||
if (!this.logged_in)
|
||||
return;
|
||||
|
||||
this.send(3, {
|
||||
request_id: null,
|
||||
type: "create_group_permission",
|
||||
data: { group: groupId, path, allow },
|
||||
});
|
||||
}
|
||||
|
||||
public createPolicy(groupId: string, path: string, usage: number, timespan: number) {
|
||||
if (!this.logged_in)
|
||||
return;
|
||||
@ -119,9 +130,7 @@ export class HermesClientService {
|
||||
this.send(3, {
|
||||
request_id: null,
|
||||
type: "create_policy",
|
||||
data: {
|
||||
groupId, path, count: usage, span: timespan
|
||||
},
|
||||
data: { groupId, path, count: usage, span: timespan },
|
||||
});
|
||||
}
|
||||
|
||||
@ -183,6 +192,17 @@ export class HermesClientService {
|
||||
});
|
||||
}
|
||||
|
||||
public deleteGroupPermission(id: string) {
|
||||
if (!this.logged_in)
|
||||
return;
|
||||
|
||||
this.send(3, {
|
||||
request_id: null,
|
||||
type: "delete_group_permission",
|
||||
data: { id },
|
||||
});
|
||||
}
|
||||
|
||||
public deletePolicy(id: string) {
|
||||
if (!this.logged_in)
|
||||
return;
|
||||
@ -252,13 +272,13 @@ export class HermesClientService {
|
||||
});
|
||||
}
|
||||
|
||||
public fetchPermissionsAndGroups() {
|
||||
public fetchPermissions() {
|
||||
if (!this.logged_in)
|
||||
return;
|
||||
|
||||
this.send(3, {
|
||||
request_id: null,
|
||||
type: "get_permissions",
|
||||
type: "get_group_permissions",
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
@ -331,6 +351,17 @@ export class HermesClientService {
|
||||
});
|
||||
}
|
||||
|
||||
public updateGroupPermission(id: string, groupId: string, path: string, allow: boolean | null) {
|
||||
if (!this.logged_in)
|
||||
return;
|
||||
|
||||
this.send(3, {
|
||||
request_id: null,
|
||||
type: "update_group_permission",
|
||||
data: { id, group: groupId, path, allow },
|
||||
});
|
||||
}
|
||||
|
||||
public updatePolicy(id: string, groupId: string, path: string, usage: number, timespan: number) {
|
||||
if (!this.logged_in)
|
||||
return;
|
||||
@ -338,9 +369,7 @@ export class HermesClientService {
|
||||
this.send(3, {
|
||||
request_id: null,
|
||||
type: "update_policy",
|
||||
data: {
|
||||
id, groupId, path, count: usage, span: timespan
|
||||
},
|
||||
data: { id, groupId, path, count: usage, span: timespan },
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title-group>
|
||||
<mat-card-title>{{data.isNew ? "Add" : "Edit"}} Group Permission</mat-card-title>
|
||||
@if (data.group) {
|
||||
<mat-card-subtitle>in {{data.group.name}}</mat-card-subtitle>
|
||||
}
|
||||
</mat-card-title-group>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<policy-dropdown [control]="pathControl" />
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Permission</mat-label>
|
||||
<mat-select matInput
|
||||
[formControl]="stateControl">
|
||||
@for (item of states; track $index) {
|
||||
<mat-option [value]="item.value">{{item.label}}</mat-option>
|
||||
}
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions class="actions">
|
||||
<button mat-raised-button
|
||||
(click)="dialogRef.close()">Cancel</button>
|
||||
|
||||
<button mat-raised-button
|
||||
disabled="{{pathControl.invalid || waitForResponse}}"
|
||||
(click)="submit()">{{data.isNew ? "Add" : "Save"}}</button>
|
||||
</mat-card-actions>
|
||||
|
||||
@if (responseError) {
|
||||
<mat-card-footer>
|
||||
<small class="error below">{{responseError}}</small>
|
||||
</mat-card-footer>
|
||||
}
|
||||
</mat-card>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PermissionItemEditComponent } from './permission-item-edit.component';
|
||||
|
||||
describe('PermissionItemEditComponent', () => {
|
||||
let component: PermissionItemEditComponent;
|
||||
let fixture: ComponentFixture<PermissionItemEditComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PermissionItemEditComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PermissionItemEditComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,99 @@
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import { Group } from '../../shared/models/group';
|
||||
import { Permission } from '../../shared/models/permission';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { PolicyDropdownComponent } from "../../policies/policy-dropdown/policy-dropdown.component";
|
||||
|
||||
@Component({
|
||||
selector: 'permission-item-edit',
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
PolicyDropdownComponent,
|
||||
],
|
||||
templateUrl: './permission-item-edit.component.html',
|
||||
styleUrl: './permission-item-edit.component.scss'
|
||||
})
|
||||
export class PermissionItemEditComponent implements OnInit {
|
||||
private readonly client = inject(HermesClientService);
|
||||
readonly data = inject<{ permission: Permission, group: Group, groups: Group[], isNew: boolean }>(MAT_DIALOG_DATA);
|
||||
readonly dialogRef = inject(MatDialogRef<PermissionItemEditComponent>);
|
||||
|
||||
readonly pathControl = new FormControl('', [Validators.required]);
|
||||
readonly stateControl = new FormControl<boolean | null>(null, [Validators.required]);
|
||||
private form = new FormGroup({
|
||||
path: this.pathControl,
|
||||
state: this.stateControl,
|
||||
});
|
||||
|
||||
readonly states: { label: string, value: boolean | null }[] = [
|
||||
{
|
||||
label: 'Allow', value: true
|
||||
},
|
||||
{
|
||||
label: 'Deny', value: false
|
||||
},
|
||||
]
|
||||
|
||||
responseError: string | undefined;
|
||||
waitForResponse = false;
|
||||
|
||||
|
||||
ngOnInit() {
|
||||
this.pathControl.setValue(this.data.permission.path);
|
||||
this.stateControl.setValue(this.data.permission.allow);
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (this.form.invalid || this.waitForResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.waitForResponse = true;
|
||||
this.responseError = undefined;
|
||||
|
||||
if (this.data.isNew) {
|
||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'create_group_permission' && d.d.request.data.path == this.pathControl.value)
|
||||
.subscribe({
|
||||
next: (d) => {
|
||||
console.log('sdifhsdiofs data', d);
|
||||
if (d.d.error) {
|
||||
this.responseError = d.d.error;
|
||||
} else {
|
||||
this.dialogRef.close(d.d.data);
|
||||
}
|
||||
},
|
||||
error: () => this.responseError = 'Something went wrong.',
|
||||
complete: () => this.waitForResponse = false,
|
||||
});
|
||||
this.client.createGroupPermission(this.data.group.id, this.pathControl.value!, this.stateControl.value);
|
||||
} else {
|
||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'update_group_permission' && d.d.request.data.id == this.data.permission.id)
|
||||
.subscribe({
|
||||
next: (d) => {
|
||||
if (d.d.error) {
|
||||
this.responseError = d.d.error;
|
||||
} else {
|
||||
this.dialogRef.close(d.d.data);
|
||||
}
|
||||
},
|
||||
error: () => this.responseError = 'Something went wrong.',
|
||||
complete: () => this.waitForResponse = false,
|
||||
});
|
||||
this.client.updateGroupPermission(this.data.permission.id, this.data.group.id, this.pathControl.value!, this.stateControl.value);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<section [class.enabled]="permission.allow == true"
|
||||
[class.disabled]="permission.allow == false"
|
||||
[class.inherited]="permission.allow == null">
|
||||
<p>{{permission.path}}</p>
|
||||
<div class="right">
|
||||
<button mat-button
|
||||
class="neutral"
|
||||
[disabled]="opened || waitForResponse"
|
||||
(click)="edit()">
|
||||
<mat-icon>edit</mat-icon>Edit
|
||||
</button>
|
||||
|
||||
<button mat-button
|
||||
class="danger"
|
||||
[disabled]="opened || waitForResponse"
|
||||
(click)="delete()">
|
||||
<mat-icon>delete</mat-icon>Delete
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
@ -0,0 +1,39 @@
|
||||
section {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.enabled {
|
||||
border: 0.5em solid rgb(41, 255, 41);
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
border: 0.5em solid rgb(255, 41, 41);
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.inherited {
|
||||
border: 0.5em solid rgb(255, 255, 255);
|
||||
border-top: 0;
|
||||
border-right: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-left: 0.5em;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
display: inline;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.right .mat-mdc-button {
|
||||
align-items: center;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PermissionItemComponent } from './permission-item.component';
|
||||
|
||||
describe('PermissionItemComponent', () => {
|
||||
let component: PermissionItemComponent;
|
||||
let fixture: ComponentFixture<PermissionItemComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PermissionItemComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PermissionItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,79 @@
|
||||
import { Component, inject, Input } from '@angular/core';
|
||||
import { Permission } from '../../shared/models/permission';
|
||||
import { Group } from '../../shared/models/group';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import EventService from '../../shared/services/EventService';
|
||||
import { PermissionItemEditComponent } from '../permission-item-edit/permission-item-edit.component';
|
||||
|
||||
@Component({
|
||||
selector: 'permission-item',
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
],
|
||||
templateUrl: './permission-item.component.html',
|
||||
styleUrl: './permission-item.component.scss'
|
||||
})
|
||||
export class PermissionItemComponent {
|
||||
@Input({ required: true }) permission: Permission = { id: '', group_id: '', user_id: '', path: '', allow: null };
|
||||
@Input({ required: true }) group: Group | undefined;
|
||||
|
||||
readonly dialog = inject(MatDialog);
|
||||
readonly client = inject(HermesClientService);
|
||||
readonly events = inject(EventService);
|
||||
|
||||
responseError: string | undefined;
|
||||
waitForResponse = false;
|
||||
opened = false;
|
||||
|
||||
|
||||
delete() {
|
||||
if (this.opened || this.waitForResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.waitForResponse = true;
|
||||
this.responseError = undefined;
|
||||
|
||||
const permissionId = this.permission.id;
|
||||
|
||||
this.client.first((d: any) => d.op == 4 && d.d.request.type == 'delete_group_permission' && d.d.request.data.id == permissionId)
|
||||
.subscribe({
|
||||
next: (d) => {
|
||||
if (d.d.error) {
|
||||
this.responseError = d.d.error;
|
||||
} else {
|
||||
this.events.emit('delete_group_permission', permissionId);
|
||||
}
|
||||
},
|
||||
error: () => this.responseError = 'Something went wrong.',
|
||||
complete: () => this.waitForResponse = false,
|
||||
});
|
||||
this.client.deleteGroupPermission(this.permission.id);
|
||||
}
|
||||
|
||||
edit() {
|
||||
if (this.opened || this.waitForResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.opened = true;
|
||||
|
||||
const dialogRef = this.dialog.open(PermissionItemEditComponent, {
|
||||
data: { permission: this.permission, group: this.group, groups: [], isNew: false },
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((permission: Permission) => {
|
||||
this.opened = false;
|
||||
if (!permission)
|
||||
return;
|
||||
|
||||
this.permission.group_id = permission.group_id;
|
||||
this.permission.path = permission.path;
|
||||
this.permission.allow = permission.allow;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
<ul>
|
||||
<li class="header">
|
||||
<mat-form-field appearance="outline"
|
||||
subscriptSizing="dynamic">
|
||||
<mat-label>Filter</mat-label>
|
||||
<input matInput
|
||||
placeholder="Filter group permissions"
|
||||
[formControl]="searchControl" />
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-icon-button
|
||||
(click)="add()">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
@for (permission of permissions; track permission.id) {
|
||||
<li>
|
||||
<permission-item [permission]="permission"
|
||||
[group]="group || getGroupById(permission.group_id)" />
|
||||
</li>
|
||||
}
|
||||
@if (!permissions.length) {
|
||||
@if (searchControl.value) {
|
||||
<p class="notice">No permission matches the filter.</p>
|
||||
} @else {
|
||||
<p class="notice">This group has no permissions. Cannot do anything.</p>
|
||||
}
|
||||
}
|
||||
</ul>
|
@ -0,0 +1,38 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
ul {
|
||||
@include mat.all-component-densities(-5);
|
||||
|
||||
@include mat.form-field-overrides((
|
||||
outlined-outline-color: rgb(167, 88, 199),
|
||||
outlined-focus-label-text-color: rgb(155, 57, 194),
|
||||
outlined-focus-outline-color: rgb(155, 57, 194),
|
||||
));
|
||||
|
||||
background-color: rgb(202, 68, 255);
|
||||
border-radius: 15px;
|
||||
margin: 0 0;
|
||||
padding: 0;
|
||||
max-width: 500px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
background-color: rgb(240, 165, 255);
|
||||
}
|
||||
|
||||
ul li.header {
|
||||
background-color: rgb(215, 115, 255);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
flex-direction: row;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
ul .notice {
|
||||
text-align: center;
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PermissionListComponent } from './permission-list.component';
|
||||
|
||||
describe('PermissionListComponent', () => {
|
||||
let component: PermissionListComponent;
|
||||
let fixture: ComponentFixture<PermissionListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PermissionListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PermissionListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,65 @@
|
||||
import { Component, inject, Input } from '@angular/core';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { containsLettersInOrder } from '../../shared/utils/string-compare';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import { Group } from '../../shared/models/group';
|
||||
import EventService from '../../shared/services/EventService';
|
||||
import { PermissionItemComponent } from '../permission-item/permission-item.component';
|
||||
import { Permission } from '../../shared/models/permission';
|
||||
import { PermissionItemEditComponent } from '../permission-item-edit/permission-item-edit.component';
|
||||
|
||||
@Component({
|
||||
selector: 'permission-list',
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatFormFieldModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule,
|
||||
PermissionItemComponent,
|
||||
],
|
||||
templateUrl: './permission-list.component.html',
|
||||
styleUrl: './permission-list.component.scss'
|
||||
})
|
||||
export class PermissionListComponent {
|
||||
@Input({ required: true }) groups: Group[] = [];
|
||||
@Input({ alias: 'permissions', required: true }) _permissions: Permission[] = [];
|
||||
@Input() group: Group | undefined;
|
||||
|
||||
readonly dialog = inject(MatDialog);
|
||||
readonly client = inject(HermesClientService);
|
||||
readonly events = inject(EventService);
|
||||
readonly searchControl = new FormControl<string>('');
|
||||
|
||||
opened = false;
|
||||
|
||||
|
||||
add() {
|
||||
if (!this.group || this.opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.opened = true;
|
||||
|
||||
const groupId = this.group.id;
|
||||
|
||||
const dialogRef = this.dialog.open(PermissionItemEditComponent, {
|
||||
data: { permission: { id: '', user_id: '', group_id: groupId, path: '', allow: null }, group: this.group, groups: [], isNew: true },
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((permission: Permission) => this.opened = false);
|
||||
}
|
||||
|
||||
get permissions() {
|
||||
return this._permissions.filter(p => containsLettersInOrder(p.path, this.searchControl.value));
|
||||
}
|
||||
|
||||
getGroupById(groupId: string) {
|
||||
return this.groups.find(g => g.id == groupId);
|
||||
}
|
||||
}
|
12
src/app/permissions/permissions.module.ts
Normal file
12
src/app/permissions/permissions.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule
|
||||
]
|
||||
})
|
||||
export class PermissionsModule { }
|
@ -25,7 +25,7 @@ const Policies = [
|
||||
{ path: "tts.commands.version", description: "To use !version command" },
|
||||
{ path: "tts.commands.voice", description: "To use !voice command" },
|
||||
{ path: "tts.commands.voice.admin", description: "To use !voice command on others" },
|
||||
]
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'policy-dropdown',
|
||||
@ -42,7 +42,7 @@ const Policies = [
|
||||
})
|
||||
export class PolicyDropdownComponent {
|
||||
@Input() policy: string | null = '';
|
||||
policyControl = new FormControl('', [Validators.required]);
|
||||
@Input({ alias: 'control' }) policyControl = new FormControl('', [Validators.required]);
|
||||
filteredPolicies: Observable<string[]>;
|
||||
|
||||
constructor() {
|
||||
|
@ -45,9 +45,10 @@
|
||||
<td mat-cell
|
||||
*matCellDef="let policy">
|
||||
<button mat-button
|
||||
class="neutral"
|
||||
(click)="edit(policy)"><mat-icon>edit</mat-icon>Edit</button>
|
||||
<button mat-button
|
||||
class="delete"
|
||||
class="danger"
|
||||
(click)="delete(policy)"><mat-icon>delete</mat-icon>Delete</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
7
src/app/shared/models/permission.ts
Normal file
7
src/app/shared/models/permission.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface Permission {
|
||||
id: string;
|
||||
user_id: string;
|
||||
group_id: string;
|
||||
path: string;
|
||||
allow: boolean | null;
|
||||
}
|
14
src/app/shared/resolvers/permission-resolver.ts
Normal file
14
src/app/shared/resolvers/permission-resolver.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Permission } from '../models/permission';
|
||||
import { PermissionService } from '../services/permission.service';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export default class PermissionResolver implements Resolve<Permission[]> {
|
||||
constructor(private service: PermissionService) { }
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<Permission[]> {
|
||||
return this.service.fetch();
|
||||
}
|
||||
}
|
@ -51,7 +51,7 @@ export default class GroupService {
|
||||
chatter.group_id = d.data.group_id;
|
||||
}
|
||||
});
|
||||
this.deleteChatter$?.subscribe(d => this.chatters = this.chatters.filter(r => r.group_id != d.request.data.group_id && r.chatter_id != d.request.data.chatter_id));
|
||||
this.deleteChatter$?.subscribe(d => this.chatters = this.chatters.filter(r => r.group_id != d.request.data.group && r.chatter_id != d.request.data.chatter));
|
||||
|
||||
this.events.listen('tts_logoff', () => {
|
||||
this.groups = [];
|
||||
|
16
src/app/shared/services/permission.service.spec.ts
Normal file
16
src/app/shared/services/permission.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PermissionService } from './permission.service';
|
||||
|
||||
describe('PermissionService', () => {
|
||||
let service: PermissionService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(PermissionService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
72
src/app/shared/services/permission.service.ts
Normal file
72
src/app/shared/services/permission.service.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import EventService from './EventService';
|
||||
import { Permission } from '../models/permission';
|
||||
import { map, Observable, of } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class PermissionService {
|
||||
private readonly client = inject(HermesClientService);
|
||||
private readonly events = inject(EventService);
|
||||
private data: Permission[] = [];
|
||||
private loaded = false;
|
||||
create$: Observable<any> | undefined;
|
||||
update$: Observable<any> | undefined;
|
||||
delete$: Observable<any> | undefined;
|
||||
|
||||
constructor() {
|
||||
this.create$ = this.client.filterByRequestType('create_group_permission');
|
||||
this.update$ = this.client.filterByRequestType('update_group_permission');
|
||||
this.delete$ = this.client.filterByRequestType('delete_group_permission');
|
||||
|
||||
this.create$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.data.push(d.data);
|
||||
});
|
||||
this.update$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const permission = this.data.find(p => p.id == d.data.id);
|
||||
if (permission) {
|
||||
permission.group_id = d.data.group_id;
|
||||
permission.path = d.data.path;
|
||||
permission.allow = d.data.allow;
|
||||
permission.user_id = d.data.user_id;
|
||||
}
|
||||
});
|
||||
this.delete$?.subscribe(d => {
|
||||
if (d.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.data = this.data.filter(r => r.id != d.request.data.id);
|
||||
});
|
||||
|
||||
this.events.listen('tts_logoff', () => {
|
||||
this.data = [];
|
||||
this.loaded = false;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
fetch() {
|
||||
if (this.loaded) {
|
||||
return of(this.data);
|
||||
}
|
||||
|
||||
const $ = this.client.first(d => d.d.request.type == 'get_group_permissions')!.pipe(map(d => d.d.data));
|
||||
$.subscribe(d => {
|
||||
this.data = d;
|
||||
this.loaded = true;
|
||||
});
|
||||
this.client.fetchPermissions();
|
||||
return $;
|
||||
}
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
|
||||
|
||||
export function containsLettersInOrder(value: string, inside: string): boolean {
|
||||
export function containsLettersInOrder(value: string | null, inside: string | null): boolean {
|
||||
if (!inside)
|
||||
return true;
|
||||
if (!value)
|
||||
return false;
|
||||
return containsLettersInOrderInternal(value, inside, 0, 0);
|
||||
}
|
||||
|
||||
@ -11,7 +15,7 @@ function containsLettersInOrderInternal(value: string, inside: string, indexValu
|
||||
if (indexValue >= value.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const match = value.at(indexValue) == inside.at(indexInside);
|
||||
return containsLettersInOrderInternal(value, inside, indexValue + 1, indexInside + (match ? 1 : 0));
|
||||
}
|
@ -74,7 +74,7 @@ export class TwitchUserItemAddComponent implements OnInit {
|
||||
error: () => this.responseError = 'Something went wrong.',
|
||||
complete: () => this.waitForResponse = false,
|
||||
});
|
||||
this.client.createGroupChatter(this.data.group.id, response.user.id, response.user.login)
|
||||
this.client.createGroupChatter(this.data.group.id, response.user.id, response.user.login);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div>
|
||||
<button mat-icon-button
|
||||
[disabled]="waitForResponse"
|
||||
(click)="delete()">
|
||||
<mat-icon>remove</mat-icon>
|
||||
</button>
|
||||
|
@ -21,18 +21,28 @@ export class TwitchUserItemComponent {
|
||||
|
||||
private readonly _client = inject(HermesClientService);
|
||||
private readonly _events = inject(EventService);
|
||||
private _deleted = false;
|
||||
|
||||
waitForResponse = false;
|
||||
responseError: string | undefined;
|
||||
|
||||
delete() {
|
||||
if (this._deleted)
|
||||
if (this.waitForResponse)
|
||||
return;
|
||||
|
||||
this._deleted = true;
|
||||
this.waitForResponse = true;
|
||||
this.responseError = undefined;
|
||||
|
||||
this._client.first(d => d.d.request.type == 'delete_group_chatter' && d.d.request.data.group == this.user.group_id && d.d.request.data.chatter == this.user.chatter_id)
|
||||
.subscribe(async (response) => {
|
||||
console.log('delete group chatter', response)
|
||||
this._events.emit('delete_group_chatter', this.user);
|
||||
.subscribe({
|
||||
next: (d) => {
|
||||
if (d.d.error) {
|
||||
this.responseError = d.d.error;
|
||||
} else {
|
||||
this._events.emit('delete_group_chatter', this.user);
|
||||
}
|
||||
},
|
||||
error: () => this.responseError = 'Something went wrong.',
|
||||
complete: () => this.waitForResponse = false,
|
||||
});
|
||||
this._client.deleteGroupChatter(this.user.group_id, this.user.chatter_id.toString());
|
||||
}
|
||||
|
@ -8,10 +8,8 @@ import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { containsLettersInOrder } from '../../shared/utils/string-compare';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import { Group } from '../../shared/models/group';
|
||||
import { TwitchUserItemAddComponent } from '../twitch-user-item-add/twitch-user-item-add.component';
|
||||
import EventService from '../../shared/services/EventService';
|
||||
|
||||
@Component({
|
||||
selector: 'twitch-user-list',
|
||||
@ -31,17 +29,10 @@ export class TwitchUserListComponent {
|
||||
@Input() group: Group | undefined;
|
||||
|
||||
readonly dialog = inject(MatDialog);
|
||||
readonly client = inject(HermesClientService);
|
||||
readonly events = inject(EventService);
|
||||
readonly searchControl: FormControl = new FormControl('');
|
||||
|
||||
opened = false;
|
||||
|
||||
constructor() {
|
||||
this.events.listen('delete_group_chatter', (chatter: GroupChatter) => {
|
||||
this.twitchUsers.splice(this.twitchUsers.findIndex(c => c.group_id == chatter.group_id && c.chatter_id == chatter.chatter_id), 1);
|
||||
});
|
||||
}
|
||||
|
||||
get users(): GroupChatter[] {
|
||||
return this.twitchUsers.filter(u => containsLettersInOrder(u.chatter_label, this.searchControl.value));
|
||||
@ -57,12 +48,6 @@ export class TwitchUserListComponent {
|
||||
data: { username: this.searchControl.value, group: this.group },
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((chatter: GroupChatter) => {
|
||||
this.opened = false;
|
||||
if (!chatter)
|
||||
return;
|
||||
|
||||
this.twitchUsers.push(chatter);
|
||||
});
|
||||
dialogRef.afterClosed().subscribe((chatter: GroupChatter) => this.opened = false);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
@ -34,4 +36,45 @@ body {
|
||||
/* Handle on hover */
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: rgb(122, 122, 122);
|
||||
}
|
||||
|
||||
.mat-small {
|
||||
@include mat.all-component-densities(-5);
|
||||
}
|
||||
|
||||
.mat-large {
|
||||
@include mat.all-component-densities(5);
|
||||
}
|
||||
|
||||
.confirm {
|
||||
@include mat.button-overrides((text-state-layer-color: rgb(52, 255, 62),
|
||||
text-label-text-color: rgb(71, 218, 78),
|
||||
text-disabled-label-text-color: rgb(71, 218, 78),
|
||||
));
|
||||
}
|
||||
|
||||
.neutral {
|
||||
@include mat.button-overrides((text-state-layer-color: rgb(64, 141, 255),
|
||||
text-label-text-color: rgb(52, 106, 255),
|
||||
text-disabled-label-text-color: rgb(52, 106, 255),
|
||||
));
|
||||
}
|
||||
|
||||
.warning {
|
||||
@include mat.button-overrides((text-state-layer-color: rgb(255, 172, 63),
|
||||
text-label-text-color: rgb(255, 145, 19),
|
||||
text-disabled-label-text-color: rgb(255, 145, 19),
|
||||
));
|
||||
}
|
||||
|
||||
.danger {
|
||||
@include mat.button-overrides((text-state-layer-color: rgb(255, 48, 48),
|
||||
text-label-text-color: rgb(255, 52, 52),
|
||||
text-disabled-label-text-color: rgb(255, 52, 52),
|
||||
filled-label-text-color: rgb(255, 52, 52),
|
||||
outlined-label-text-color: rgb(255, 52, 52),
|
||||
protected-label-text-color: rgb(255, 52, 52),
|
||||
protected-state-layer-color: rgb(255, 75, 75),
|
||||
protected-ripple-color: rgb(255, 154, 154),
|
||||
));
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user