Added API keys. Minor modifications for other views.
This commit is contained in:
parent
b1bac758e3
commit
298d351e5d
@ -116,6 +116,7 @@
|
||||
(click)="deleteAction(action)">Delete</button>
|
||||
}
|
||||
<button mat-raised-button
|
||||
disabled="{{waitForResponse}}"
|
||||
(click)="dialogRef.close()">Cancel</button>
|
||||
<button mat-raised-button
|
||||
disabled="{{!formsDirty || !formsValidity || waitForResponse}}"
|
||||
|
@ -70,7 +70,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
}));
|
||||
|
||||
this.addSubscription(this.events.listen('login', () => {
|
||||
this.keyService.fetch(true)
|
||||
this.keyService.fetch()
|
||||
.pipe(timeout(3000), first())
|
||||
.subscribe(async (d: ApiKey[]) => {
|
||||
if (d.length > 0)
|
||||
|
@ -23,6 +23,7 @@ import PermissionResolver from './shared/resolvers/permission-resolver';
|
||||
import { ConnectionsComponent } from './connections/connections/connections.component';
|
||||
import ConnectionResolver from './shared/resolvers/connection-resolver';
|
||||
import { ConnectionCallbackComponent } from './connections/callback/callback.component';
|
||||
import { KeysComponent } from './keys/keys/keys.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
@ -80,6 +81,14 @@ export const routes: Routes = [
|
||||
permissions: PermissionResolver,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'keys',
|
||||
component: KeysComponent,
|
||||
canActivate: [AuthUserGuard],
|
||||
resolve: {
|
||||
keys: ApiKeyResolver,
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
component: LoginComponent,
|
||||
|
@ -52,7 +52,7 @@ export class ImpersonationComponent implements OnInit {
|
||||
.subscribe(async _ =>
|
||||
await setTimeout(async () =>
|
||||
await this.router.navigate([url.substring(1)]), 500));
|
||||
this.keyService.fetch(true)
|
||||
this.keyService.fetch()
|
||||
.pipe(timeout(3000), first())
|
||||
.subscribe(async (d: ApiKey[]) => {
|
||||
if (d.length > 0)
|
||||
|
@ -39,7 +39,7 @@ export class TtsLoginComponent implements OnInit, OnDestroy {
|
||||
this.subscriptions.push(this.events.listen('impersonation', _ => {
|
||||
this.selected_api_key = undefined;
|
||||
|
||||
this.keyService.fetch(true)
|
||||
this.keyService.fetch()
|
||||
.pipe(timeout(3000), first())
|
||||
.subscribe(d => this.api_keys = d);
|
||||
}));
|
||||
|
@ -45,10 +45,11 @@
|
||||
align="end">
|
||||
<button mat-raised-button
|
||||
class="warning"
|
||||
disabled="{{waitForResponse}}"
|
||||
(click)="dialogRef.close()">Cancel</button>
|
||||
<button mat-raised-button
|
||||
class="confirm"
|
||||
disabled="{{form.invalid || waitForResponse}}"
|
||||
disabled="{{!form.dirty || form.invalid || waitForResponse}}"
|
||||
(click)="submit()">Add</button>
|
||||
</mat-card-actions>
|
||||
|
||||
|
@ -7,8 +7,6 @@ import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { ActionItemEditComponent } from '../../actions/action-item-edit/action-item-edit.component';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
|
||||
@ -27,10 +25,10 @@ import { DOCUMENT } from '@angular/common';
|
||||
styleUrl: './connection-item-edit.component.scss'
|
||||
})
|
||||
export class ConnectionItemEditComponent {
|
||||
private readonly client = inject(HermesClientService);
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly data = inject<{ name: string }>(MAT_DIALOG_DATA);
|
||||
readonly dialogRef = inject(MatDialogRef<ConnectionItemEditComponent>);
|
||||
|
||||
readonly data = inject<{ name: string }>(MAT_DIALOG_DATA);
|
||||
readonly nameControl = new FormControl<string>('', [Validators.required]);
|
||||
readonly clientIdControl = new FormControl<string>('', [Validators.required]);
|
||||
readonly typeControl = new FormControl<string>('', [Validators.required]);
|
||||
@ -40,7 +38,6 @@ export class ConnectionItemEditComponent {
|
||||
type: this.typeControl,
|
||||
});
|
||||
|
||||
readonly dialogRef = inject(MatDialogRef<ActionItemEditComponent>);
|
||||
|
||||
responseError: string | undefined;
|
||||
waitForResponse = false;
|
||||
|
@ -5,13 +5,13 @@
|
||||
<article class="right">
|
||||
<button mat-button
|
||||
class="neutral"
|
||||
(click)="renew(connection())">
|
||||
(click)="renew()">
|
||||
<mat-icon>refresh</mat-icon>
|
||||
Renew
|
||||
</button>
|
||||
<button mat-button
|
||||
class="danger"
|
||||
(click)="delete(connection())">
|
||||
(click)="delete()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
Delete
|
||||
</button>
|
||||
|
@ -5,7 +5,6 @@ import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Router } from '@angular/router';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
|
||||
@ -21,19 +20,19 @@ import { HermesClientService } from '../../hermes-client.service';
|
||||
styleUrl: './connection-item.component.scss'
|
||||
})
|
||||
export class ConnectionItemComponent {
|
||||
router = inject(Router);
|
||||
http = inject(HttpClient);
|
||||
client = inject(HermesClientService);
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly client = inject(HermesClientService);
|
||||
|
||||
connection = input.required<Connection>();
|
||||
|
||||
constructor(@Inject(DOCUMENT) private document: Document) { }
|
||||
|
||||
delete(conn: Connection) {
|
||||
this.client.deleteConnection(conn.name);
|
||||
delete() {
|
||||
this.client.deleteConnection(this.connection().name);
|
||||
}
|
||||
|
||||
renew(conn: Connection) {
|
||||
renew() {
|
||||
const conn = this.connection();
|
||||
this.http.post('/api/auth/connections', {
|
||||
name: conn.name,
|
||||
type: conn.type,
|
||||
|
39
src/app/keys/key-item-edit/key-item-edit.component.html
Normal file
39
src/app/keys/key-item-edit/key-item-edit.component.html
Normal file
@ -0,0 +1,39 @@
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-title-group>
|
||||
<mat-card-title>Add API Key</mat-card-title>
|
||||
<mat-card-subtitle></mat-card-subtitle>
|
||||
</mat-card-title-group>
|
||||
</mat-card-header>
|
||||
|
||||
<mat-card-content>
|
||||
<mat-form-field>
|
||||
<mat-label>Key Label</mat-label>
|
||||
<input matInput
|
||||
[formControl]="labelControl" />
|
||||
@if (labelControl.invalid && (labelControl.dirty || labelControl.touched)) {
|
||||
@if (labelControl.hasError('required')) {
|
||||
<small class="error">This field is required.</small>
|
||||
}
|
||||
}
|
||||
</mat-form-field>
|
||||
</mat-card-content>
|
||||
|
||||
<mat-card-actions class="actions"
|
||||
align="end">
|
||||
<button mat-raised-button
|
||||
class="warning"
|
||||
disabled="{{waitForResponse}}"
|
||||
(click)="dialogRef.close()">Cancel</button>
|
||||
<button mat-raised-button
|
||||
class="confirm"
|
||||
disabled="{{!form.dirty || form.invalid || waitForResponse}}"
|
||||
(click)="submit()">Add</button>
|
||||
</mat-card-actions>
|
||||
|
||||
@if (responseError) {
|
||||
<mat-card-footer>
|
||||
<small class="error below">{{responseError}}</small>
|
||||
</mat-card-footer>
|
||||
}
|
||||
</mat-card>
|
23
src/app/keys/key-item-edit/key-item-edit.component.spec.ts
Normal file
23
src/app/keys/key-item-edit/key-item-edit.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { KeyItemEditComponent } from './key-item-edit.component';
|
||||
|
||||
describe('KeyItemEditComponent', () => {
|
||||
let component: KeyItemEditComponent;
|
||||
let fixture: ComponentFixture<KeyItemEditComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [KeyItemEditComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(KeyItemEditComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
67
src/app/keys/key-item-edit/key-item-edit.component.ts
Normal file
67
src/app/keys/key-item-edit/key-item-edit.component.ts
Normal file
@ -0,0 +1,67 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Component, inject } 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 { MatInputModule } from '@angular/material/input';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import EventService from '../../shared/services/EventService';
|
||||
|
||||
@Component({
|
||||
selector: 'key-item-edit',
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatIconModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
templateUrl: './key-item-edit.component.html',
|
||||
styleUrl: './key-item-edit.component.scss'
|
||||
})
|
||||
export class KeyItemEditComponent {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly events = inject(EventService);
|
||||
readonly data = inject<{ name: string }>(MAT_DIALOG_DATA);
|
||||
|
||||
readonly labelControl = new FormControl<string>('', [Validators.required]);
|
||||
readonly form = new FormGroup({
|
||||
name: this.labelControl,
|
||||
});
|
||||
|
||||
readonly dialogRef = inject(MatDialogRef<KeyItemEditComponent>);
|
||||
|
||||
responseError: string | undefined;
|
||||
waitForResponse = false;
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.labelControl.setValue(this.data.name);
|
||||
}
|
||||
|
||||
submit(): void {
|
||||
if (this.form.invalid || this.waitForResponse) {
|
||||
return;
|
||||
}
|
||||
|
||||
const label = this.labelControl.value;
|
||||
this.http.post('/api/keys', { label },
|
||||
{
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('jwt')
|
||||
}
|
||||
}).subscribe(async (d: any) => {
|
||||
this.events.emit('add_api_key', {
|
||||
id: d.key,
|
||||
label: d.label,
|
||||
});
|
||||
|
||||
this.dialogRef.close();
|
||||
});
|
||||
}
|
||||
}
|
20
src/app/keys/key-item/key-item.component.html
Normal file
20
src/app/keys/key-item/key-item.component.html
Normal file
@ -0,0 +1,20 @@
|
||||
<section>
|
||||
{{(isVisible ? key().id : key().label)}}
|
||||
|
||||
<article class="right">
|
||||
<button mat-button
|
||||
class="neutral"
|
||||
[disabled]="waitForResponse"
|
||||
(click)="isVisible = !isVisible">
|
||||
<mat-icon>{{(isVisible ? "visibility_off" : "visibility")}}</mat-icon>
|
||||
{{(isVisible ? "Hide" : "View")}} Key
|
||||
</button>
|
||||
<button mat-button
|
||||
class="danger"
|
||||
[disabled]="waitForResponse"
|
||||
(click)="delete()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
Delete
|
||||
</button>
|
||||
</article>
|
||||
</section>
|
11
src/app/keys/key-item/key-item.component.scss
Normal file
11
src/app/keys/key-item/key-item.component.scss
Normal file
@ -0,0 +1,11 @@
|
||||
section {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 0 0.5em;
|
||||
}
|
23
src/app/keys/key-item/key-item.component.spec.ts
Normal file
23
src/app/keys/key-item/key-item.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { KeyItemComponent } from './key-item.component';
|
||||
|
||||
describe('KeyItemComponent', () => {
|
||||
let component: KeyItemComponent;
|
||||
let fixture: ComponentFixture<KeyItemComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [KeyItemComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(KeyItemComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
48
src/app/keys/key-item/key-item.component.ts
Normal file
48
src/app/keys/key-item/key-item.component.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import { Component, inject, input } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import ApiKey from '../../shared/models/api-key';
|
||||
import EventService from '../../shared/services/EventService';
|
||||
|
||||
@Component({
|
||||
selector: 'key-item',
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatFormFieldModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
templateUrl: './key-item.component.html',
|
||||
styleUrl: './key-item.component.scss'
|
||||
})
|
||||
export class KeyItemComponent {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly events = inject(EventService);
|
||||
|
||||
key = input.required<ApiKey>();
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
28
src/app/keys/key-list/key-list.component.html
Normal file
28
src/app/keys/key-list/key-list.component.html
Normal file
@ -0,0 +1,28 @@
|
||||
<ul>
|
||||
<li class="header">
|
||||
<mat-form-field appearance="outline"
|
||||
subscriptSizing="dynamic">
|
||||
<mat-label>Label Filter</mat-label>
|
||||
<input matInput
|
||||
placeholder="Filter keys by label"
|
||||
[formControl]="searchControl" />
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-icon-button
|
||||
(click)="add()">
|
||||
<mat-icon>add</mat-icon>
|
||||
</button>
|
||||
</li>
|
||||
@for (key of keys; track key.id) {
|
||||
<li>
|
||||
<key-item [key]="key" />
|
||||
</li>
|
||||
}
|
||||
@if (!keys.length) {
|
||||
@if (searchControl.value) {
|
||||
<p class="notice">No API keys match the filter.</p>
|
||||
} @else {
|
||||
<p class="notice">No API keys available.</p>
|
||||
}
|
||||
}
|
||||
</ul>
|
38
src/app/keys/key-list/key-list.component.scss
Normal file
38
src/app/keys/key-list/key-list.component.scss
Normal file
@ -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;
|
||||
}
|
23
src/app/keys/key-list/key-list.component.spec.ts
Normal file
23
src/app/keys/key-list/key-list.component.spec.ts
Normal file
@ -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<KeyListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [KeyListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(KeyListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
61
src/app/keys/key-list/key-list.component.ts
Normal file
61
src/app/keys/key-list/key-list.component.ts
Normal file
@ -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<string>('');
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
12
src/app/keys/keys.module.ts
Normal file
12
src/app/keys/keys.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule
|
||||
]
|
||||
})
|
||||
export class KeysModule { }
|
2
src/app/keys/keys/keys.component.html
Normal file
2
src/app/keys/keys/keys.component.html
Normal file
@ -0,0 +1,2 @@
|
||||
<h3>API Keys</h3>
|
||||
<key-list [keys]="keys" />
|
0
src/app/keys/keys/keys.component.scss
Normal file
0
src/app/keys/keys/keys.component.scss
Normal file
23
src/app/keys/keys/keys.component.spec.ts
Normal file
23
src/app/keys/keys/keys.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { KeysComponent } from './keys.component';
|
||||
|
||||
describe('KeysComponent', () => {
|
||||
let component: KeysComponent;
|
||||
let fixture: ComponentFixture<KeysComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [KeysComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(KeysComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
39
src/app/keys/keys/keys.component.ts
Normal file
39
src/app/keys/keys/keys.component.ts
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -54,6 +54,12 @@
|
||||
Connections
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a routerLink="/keys"
|
||||
routerLinkActive="active">
|
||||
API Keys
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
|
||||
</ul>
|
||||
|
@ -24,10 +24,11 @@
|
||||
|
||||
<mat-card-actions class="actions">
|
||||
<button mat-raised-button
|
||||
disabled="{{waitForResponse}}"
|
||||
(click)="dialogRef.close()">Cancel</button>
|
||||
|
||||
<button mat-raised-button
|
||||
disabled="{{pathControl.invalid || waitForResponse}}"
|
||||
disabled="{{!pathControl.dirty || pathControl.invalid || waitForResponse}}"
|
||||
(click)="submit()">{{data.isNew ? "Add" : "Save"}}</button>
|
||||
</mat-card-actions>
|
||||
|
||||
|
@ -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<ApiKey[]>(environment.API_HOST + '/keys', {
|
||||
|
@ -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<TwitchRedemption[]>(environment.API_HOST + '/twitch/redemptions', {
|
||||
|
@ -16,9 +16,10 @@
|
||||
|
||||
<mat-card-actions class="actions">
|
||||
<button mat-raised-button
|
||||
disabled="{{waitForResponse}}"
|
||||
(click)="dialogRef.close()">Cancel</button>
|
||||
<button mat-raised-button
|
||||
disabled="{{usernameControl.invalid || waitForResponse}}"
|
||||
disabled="{{!usernameControl.dirty || usernameControl.invalid || waitForResponse}}"
|
||||
(click)="submit()">Add</button>
|
||||
</mat-card-actions>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user