();
+ isVisible: boolean = false;
+ waitForResponse = false;
+
+ delete() {
+ if (this.waitForResponse) {
+ return;
+ }
+
+ const key_id = this.key().id;
+ this.http.delete('/api/keys',
+ {
+ body: {
+ key: key_id,
+ },
+ headers: {
+ 'Authorization': 'Bearer ' + localStorage.getItem('jwt')
+ }
+ }).subscribe(async (d: any) => {
+ this.events.emit('delete_api_key', key_id);
+ this.waitForResponse = false;
+ });
+ }
+}
diff --git a/src/app/keys/key-list/key-list.component.html b/src/app/keys/key-list/key-list.component.html
new file mode 100644
index 0000000..cd5d798
--- /dev/null
+++ b/src/app/keys/key-list/key-list.component.html
@@ -0,0 +1,28 @@
+
\ No newline at end of file
diff --git a/src/app/keys/key-list/key-list.component.scss b/src/app/keys/key-list/key-list.component.scss
new file mode 100644
index 0000000..7d3fc2c
--- /dev/null
+++ b/src/app/keys/key-list/key-list.component.scss
@@ -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: 600px;
+ 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;
+}
\ No newline at end of file
diff --git a/src/app/keys/key-list/key-list.component.spec.ts b/src/app/keys/key-list/key-list.component.spec.ts
new file mode 100644
index 0000000..ab0b40f
--- /dev/null
+++ b/src/app/keys/key-list/key-list.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { KeyListComponent } from './key-list.component';
+
+describe('KeyListComponent', () => {
+ let component: KeyListComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [KeyListComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(KeyListComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/keys/key-list/key-list.component.ts b/src/app/keys/key-list/key-list.component.ts
new file mode 100644
index 0000000..de6ab30
--- /dev/null
+++ b/src/app/keys/key-list/key-list.component.ts
@@ -0,0 +1,61 @@
+import { Component, inject, Input } from '@angular/core';
+import { FormControl, ReactiveFormsModule } from '@angular/forms';
+import { MatButtonModule } from '@angular/material/button';
+import { MatCardModule } from '@angular/material/card';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatIconModule } from '@angular/material/icon';
+import { MatInputModule } from '@angular/material/input';
+import { MatDialog } from '@angular/material/dialog';
+import { MatSelectModule } from '@angular/material/select';
+import { containsLettersInOrder } from '../../shared/utils/string-compare';
+import { KeyItemComponent } from '../key-item/key-item.component';
+import { KeyItemEditComponent } from '../key-item-edit/key-item-edit.component';
+import ApiKey from '../../shared/models/api-key';
+
+@Component({
+ selector: 'key-list',
+ imports: [
+ MatButtonModule,
+ MatCardModule,
+ MatFormFieldModule,
+ MatIconModule,
+ MatInputModule,
+ MatSelectModule,
+ ReactiveFormsModule,
+ KeyItemComponent,
+ ],
+ templateUrl: './key-list.component.html',
+ styleUrl: './key-list.component.scss'
+})
+export class KeyListComponent {
+ private readonly _dialog = inject(MatDialog);
+
+ private _keys: ApiKey[] = [];
+
+ readonly searchControl = new FormControl('');
+
+ opened = false;
+
+
+ get keys() {
+ return this._keys.filter(c => containsLettersInOrder(c.label, this.searchControl.value));
+ }
+
+ @Input({ required: true })
+ set keys(value: ApiKey[]) {
+ this._keys = value;
+ }
+
+ add() {
+ if (this.opened)
+ return;
+
+ this.opened = true;
+
+ const dialogRef = this._dialog.open(KeyItemEditComponent, {
+ data: { name: this.searchControl.value },
+ });
+
+ dialogRef.afterClosed().subscribe((_: any) => this.opened = false);
+ }
+}
diff --git a/src/app/keys/keys.module.ts b/src/app/keys/keys.module.ts
new file mode 100644
index 0000000..56fd02c
--- /dev/null
+++ b/src/app/keys/keys.module.ts
@@ -0,0 +1,12 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+
+
+
+@NgModule({
+ declarations: [],
+ imports: [
+ CommonModule
+ ]
+})
+export class KeysModule { }
diff --git a/src/app/keys/keys/keys.component.html b/src/app/keys/keys/keys.component.html
new file mode 100644
index 0000000..2f0593a
--- /dev/null
+++ b/src/app/keys/keys/keys.component.html
@@ -0,0 +1,2 @@
+API Keys
+
\ No newline at end of file
diff --git a/src/app/keys/keys/keys.component.scss b/src/app/keys/keys/keys.component.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/app/keys/keys/keys.component.spec.ts b/src/app/keys/keys/keys.component.spec.ts
new file mode 100644
index 0000000..b7938ed
--- /dev/null
+++ b/src/app/keys/keys/keys.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { KeysComponent } from './keys.component';
+
+describe('KeysComponent', () => {
+ let component: KeysComponent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [KeysComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(KeysComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/keys/keys/keys.component.ts b/src/app/keys/keys/keys.component.ts
new file mode 100644
index 0000000..8ff4c51
--- /dev/null
+++ b/src/app/keys/keys/keys.component.ts
@@ -0,0 +1,39 @@
+import { Component, inject, OnDestroy } from '@angular/core';
+import { ActivatedRoute } from '@angular/router';
+import { Subscription } from 'rxjs';
+import { KeyListComponent } from '../key-list/key-list.component';
+import ApiKey from '../../shared/models/api-key';
+import EventService from '../../shared/services/EventService';
+import { ApiKeyService } from '../../shared/services/api/api-key.service';
+
+@Component({
+ selector: 'keys',
+ imports: [KeyListComponent],
+ templateUrl: './keys.component.html',
+ styleUrl: './keys.component.scss'
+})
+export class KeysComponent implements OnDestroy {
+ private readonly route = inject(ActivatedRoute);
+ private readonly events = inject(EventService);
+ private readonly keyService = inject(ApiKeyService);
+
+ subscriptions: (Subscription | undefined)[] = [];
+ keys: ApiKey[] = [];
+
+
+ constructor() {
+ this.route.data.subscribe(payload => {
+ this.keys = payload['keys'] ?? [];
+ });
+
+ this.subscriptions.push(this.events.listen('add_api_key', _ => this.keyService.fetch().subscribe(keys => this.keys = keys)));
+ this.subscriptions.push(this.events.listen('delete_api_key', _ => this.keyService.fetch().subscribe(keys => this.keys = keys)));
+ }
+
+ ngOnDestroy(): void {
+ for (let subscription of this.subscriptions) {
+ if (subscription)
+ subscription.unsubscribe();
+ }
+ }
+}
diff --git a/src/app/navigation/navigation.component.html b/src/app/navigation/navigation.component.html
index e0251ea..83ce2c5 100644
--- a/src/app/navigation/navigation.component.html
+++ b/src/app/navigation/navigation.component.html
@@ -54,6 +54,12 @@
Connections
+
+
+ API Keys
+
+
}
diff --git a/src/app/permissions/permission-item-edit/permission-item-edit.component.html b/src/app/permissions/permission-item-edit/permission-item-edit.component.html
index fff6e30..7f4e9bc 100644
--- a/src/app/permissions/permission-item-edit/permission-item-edit.component.html
+++ b/src/app/permissions/permission-item-edit/permission-item-edit.component.html
@@ -24,10 +24,11 @@
diff --git a/src/app/shared/services/api/api-key.service.ts b/src/app/shared/services/api/api-key.service.ts
index 96a4c0d..d87a07e 100644
--- a/src/app/shared/services/api/api-key.service.ts
+++ b/src/app/shared/services/api/api-key.service.ts
@@ -20,10 +20,13 @@ export class ApiKeyService {
this.keys = [];
this.loaded = false;
});
+
+ this.events.listen('delete_api_key', payload => this.keys = this.keys.filter(k => k.id != payload));
+ this.events.listen('add_api_key', payload => this.keys.push(payload));
}
- fetch(force: boolean = false) {
- if (!force && this.loaded)
+ fetch() {
+ if (this.loaded)
return of(this.keys);
const $ = this.http.get(environment.API_HOST + '/keys', {
diff --git a/src/app/shared/services/twitch-redemption.service.ts b/src/app/shared/services/twitch-redemption.service.ts
index 567b175..7e542c7 100644
--- a/src/app/shared/services/twitch-redemption.service.ts
+++ b/src/app/shared/services/twitch-redemption.service.ts
@@ -22,8 +22,8 @@ export default class TwitchRedemptionService {
});
}
- fetch(force: boolean = false) {
- if (!force && this.loaded)
+ fetch() {
+ if (this.loaded)
return of(this.twitchRedemptions);
const $ = this.http.get(environment.API_HOST + '/twitch/redemptions', {
diff --git a/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.html b/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.html
index 373b52e..b900a99 100644
--- a/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.html
+++ b/src/app/twitch-users/twitch-user-item-add/twitch-user-item-add.component.html
@@ -16,9 +16,10 @@