mirror of
https://github.com/AlbrechtL/openwrt-docker.git
synced 2026-10-14 08:01:25 +00:00
Add API calls to backend
This commit is contained in:
@@ -4,6 +4,15 @@ import { provideRouter } from '@angular/router';
|
||||
import { routes } from './app.routes';
|
||||
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
|
||||
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
import { BackendCommunicationService } from './backend-communication.service';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({ eventCoalescing: true }), provideRouter(routes), provideClientHydration(withEventReplay())]
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideClientHydration(withEventReplay()),
|
||||
provideHttpClient(),
|
||||
BackendCommunicationService
|
||||
]
|
||||
};
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BackendCommunicationService } from './backend-communication.service';
|
||||
|
||||
describe('BackendCommunicationService', () => {
|
||||
let service: BackendCommunicationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(BackendCommunicationService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable, tap, map } from 'rxjs';
|
||||
|
||||
interface OpenWrtInfo {
|
||||
version: string;
|
||||
kernelVersion: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class BackendCommunicationService {
|
||||
//urlPrefix: string = 'http://localhost:8006'; // Just for development
|
||||
urlPrefix: string = '';
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
getOpenWrtInfo(): Observable<OpenWrtInfo> {
|
||||
return this.http.get<any>(this.urlPrefix + '/api/get_openwrt_info').pipe(
|
||||
tap(response => console.log('Fetched OpenWrt data:', response)),
|
||||
map(response => {
|
||||
let tmpVersion = 'Unknown';
|
||||
let tmpKernelVersion = 'Unknown';
|
||||
if (response['combined_output'] !== '\n') {
|
||||
try {
|
||||
response['combined_output'] = JSON.parse(response['combined_output']);
|
||||
|
||||
tmpVersion = response['combined_output']['return']['pretty-name'];
|
||||
tmpKernelVersion = response['combined_output']['return']['kernel-release'];
|
||||
} catch (error) {
|
||||
//console.error('Error parsing combined_output:', error);
|
||||
}
|
||||
}
|
||||
return {
|
||||
version: tmpVersion,
|
||||
kernelVersion: tmpKernelVersion
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
pollOpenWrtInfo(intervalMs: number = 1000): Observable<OpenWrtInfo> {
|
||||
return new Observable(observer => {
|
||||
const poll = () => {
|
||||
this.getOpenWrtInfo().subscribe({
|
||||
next: (response) => {
|
||||
if (response.version !== 'Unknown') {
|
||||
observer.next(response);
|
||||
observer.complete();
|
||||
} else {
|
||||
setTimeout(poll, intervalMs);
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
observer.error(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
poll();
|
||||
});
|
||||
}
|
||||
|
||||
gracefulReboot(): Observable<any> {
|
||||
return this.http.get(this.urlPrefix + '/api/reboot');
|
||||
}
|
||||
|
||||
getContainerInfo(): Observable<any> {
|
||||
return this.http.get<any>(this.urlPrefix + '/api/get_container_info').pipe(
|
||||
map(response => {
|
||||
return response['combined_output'];
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button mat-stroked-button (click)="startClient()">Reconnect</button>
|
||||
<div class="row">
|
||||
<button mat-stroked-button (click)="startClient()">Reconnect console</button>
|
||||
<button mat-stroked-button (click)="rebootOpenWrt()">Reboot OpenWrt</button>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
.row {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.mat-mdc-button-base {
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Component, ElementRef, AfterViewInit, Renderer2 } from '@angular/core';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
// @ts-expect-error no types
|
||||
import RFB from '@novnc/novnc/lib/rfb';
|
||||
|
||||
import { BackendCommunicationService } from '../backend-communication.service';
|
||||
|
||||
|
||||
// TODO, see following issue
|
||||
// https://github.com/novnc/noVNC/pull/1944
|
||||
// https://github.com/novnc/noVNC/issues/1943
|
||||
@@ -18,7 +21,7 @@ export class ConsoleComponent implements AfterViewInit {
|
||||
|
||||
public rfb: any;
|
||||
|
||||
constructor(private el: ElementRef, private renderer: Renderer2) {}
|
||||
constructor(private el: ElementRef, private renderer: Renderer2, private service: BackendCommunicationService) { }
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
@@ -71,4 +74,8 @@ export class ConsoleComponent implements AfterViewInit {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rebootOpenWrt() {
|
||||
this.service.gracefulReboot().subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,26 @@
|
||||
<span>status works!</span>
|
||||
<div class="responsive-grid">
|
||||
<mat-card class="status-card" appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>OpenWrt image</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
@if (openwrtVersion) {
|
||||
<ul>
|
||||
<li>Version: {{ openwrtVersion }}</li>
|
||||
<li>Kernel: {{ openWrtKernel }}</li>
|
||||
</ul>
|
||||
} @else {
|
||||
<mat-spinner [diameter]="50"></mat-spinner>
|
||||
}
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
<mat-card class="status-card" appearance="outlined">
|
||||
<mat-card-header>
|
||||
<mat-card-title>Attached hardware</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
.status-card {
|
||||
max-width: 400px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.responsive-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
|
||||
//gap: 24px;
|
||||
}
|
||||
|
||||
mat-spinner {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { BackendCommunicationService } from '../backend-communication.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-status',
|
||||
imports: [],
|
||||
imports: [MatCardModule, MatButtonModule, MatProgressSpinnerModule],
|
||||
templateUrl: './status.component.html',
|
||||
styleUrl: './status.component.scss'
|
||||
styleUrl: './status.component.scss',
|
||||
})
|
||||
export class StatusComponent {
|
||||
openwrtVersion?: string;
|
||||
openWrtKernel?: string;
|
||||
|
||||
constructor(private service: BackendCommunicationService) {
|
||||
this.service.pollOpenWrtInfo().subscribe(response => {
|
||||
this.openwrtVersion = response.version;
|
||||
this.openWrtKernel = response.kernelVersion;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
import { DataSource } from '@angular/cdk/collections';
|
||||
import { MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Observable, of as observableOf, merge } from 'rxjs';
|
||||
|
||||
// TODO: Replace this with your own data model type
|
||||
export interface SystemInformationItem {
|
||||
name: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
// TODO: replace this with real data from your application
|
||||
const EXAMPLE_DATA: SystemInformationItem[] = [
|
||||
{id: 1, name: 'Hydrogen'},
|
||||
{id: 2, name: 'Helium'},
|
||||
{id: 3, name: 'Lithium'},
|
||||
{id: 4, name: 'Beryllium'},
|
||||
{id: 5, name: 'Boron'},
|
||||
{id: 6, name: 'Carbon'},
|
||||
{id: 7, name: 'Nitrogen'},
|
||||
{id: 8, name: 'Oxygen'},
|
||||
{id: 9, name: 'Fluorine'},
|
||||
{id: 10, name: 'Neon'},
|
||||
{id: 11, name: 'Sodium'},
|
||||
{id: 12, name: 'Magnesium'},
|
||||
{id: 13, name: 'Aluminum'},
|
||||
{id: 14, name: 'Silicon'},
|
||||
{id: 15, name: 'Phosphorus'},
|
||||
{id: 16, name: 'Sulfur'},
|
||||
{id: 17, name: 'Chlorine'},
|
||||
{id: 18, name: 'Argon'},
|
||||
{id: 19, name: 'Potassium'},
|
||||
{id: 20, name: 'Calcium'},
|
||||
];
|
||||
|
||||
/**
|
||||
* Data source for the SystemInformation view. This class should
|
||||
* encapsulate all logic for fetching and manipulating the displayed data
|
||||
* (including sorting, pagination, and filtering).
|
||||
*/
|
||||
export class SystemInformationDataSource extends DataSource<SystemInformationItem> {
|
||||
data: SystemInformationItem[] = EXAMPLE_DATA;
|
||||
paginator: MatPaginator | undefined;
|
||||
sort: MatSort | undefined;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect this data source to the table. The table will only update when
|
||||
* the returned stream emits new items.
|
||||
* @returns A stream of the items to be rendered.
|
||||
*/
|
||||
connect(): Observable<SystemInformationItem[]> {
|
||||
if (this.paginator && this.sort) {
|
||||
// Combine everything that affects the rendered data into one update
|
||||
// stream for the data-table to consume.
|
||||
return merge(observableOf(this.data), this.paginator.page, this.sort.sortChange)
|
||||
.pipe(map(() => {
|
||||
return this.getPagedData(this.getSortedData([...this.data ]));
|
||||
}));
|
||||
} else {
|
||||
throw Error('Please set the paginator and sort on the data source before connecting.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the table is being destroyed. Use this function, to clean up
|
||||
* any open connections or free any held resources that were set up during connect.
|
||||
*/
|
||||
disconnect(): void {}
|
||||
|
||||
/**
|
||||
* Paginate the data (client-side). If you're using server-side pagination,
|
||||
* this would be replaced by requesting the appropriate data from the server.
|
||||
*/
|
||||
private getPagedData(data: SystemInformationItem[]): SystemInformationItem[] {
|
||||
if (this.paginator) {
|
||||
const startIndex = this.paginator.pageIndex * this.paginator.pageSize;
|
||||
return data.splice(startIndex, this.paginator.pageSize);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the data (client-side). If you're using server-side sorting,
|
||||
* this would be replaced by requesting the appropriate data from the server.
|
||||
*/
|
||||
private getSortedData(data: SystemInformationItem[]): SystemInformationItem[] {
|
||||
if (!this.sort || !this.sort.active || this.sort.direction === '') {
|
||||
return data;
|
||||
}
|
||||
|
||||
return data.sort((a, b) => {
|
||||
const isAsc = this.sort?.direction === 'asc';
|
||||
switch (this.sort?.active) {
|
||||
case 'name': return compare(a.name, b.name, isAsc);
|
||||
case 'id': return compare(+a.id, +b.id, isAsc);
|
||||
default: return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** Simple sort comparator for example ID/Name columns (for client-side sorting). */
|
||||
function compare(a: string | number, b: string | number, isAsc: boolean): number {
|
||||
return (a < b ? -1 : 1) * (isAsc ? 1 : -1);
|
||||
}
|
||||
@@ -1,26 +1,3 @@
|
||||
<div class="mat-elevation-z2">
|
||||
<table mat-table class="full-width-table" matSort aria-label="Elements">
|
||||
<!-- Id Column -->
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Id</th>
|
||||
<td mat-cell *matCellDef="let row">{{row.id}}</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Name Column -->
|
||||
<ng-container matColumnDef="name">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
|
||||
<td mat-cell *matCellDef="let row">{{row.name}}</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator #paginator
|
||||
[length]="dataSource.data.length"
|
||||
[pageIndex]="0"
|
||||
[pageSize]="10"
|
||||
[pageSizeOptions]="[5, 10, 20]"
|
||||
aria-label="Select page">
|
||||
</mat-paginator>
|
||||
<div>
|
||||
<pre>{{ containerInfo }}</pre>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
.full-width-table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||
|
||||
import { SystemInformationComponent } from './system-information.component';
|
||||
|
||||
@@ -9,7 +8,6 @@ describe('SystemInformationComponent', () => {
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [NoopAnimationsModule]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
import { AfterViewInit, Component, ViewChild } from '@angular/core';
|
||||
import { MatTableModule, MatTable } from '@angular/material/table';
|
||||
import { MatPaginatorModule, MatPaginator } from '@angular/material/paginator';
|
||||
import { MatSortModule, MatSort } from '@angular/material/sort';
|
||||
import { SystemInformationDataSource, SystemInformationItem } from './system-information-datasource';
|
||||
import { Component } from '@angular/core';
|
||||
import { BackendCommunicationService } from '../backend-communication.service';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-system-information',
|
||||
templateUrl: './system-information.component.html',
|
||||
styleUrl: './system-information.component.scss',
|
||||
standalone: true,
|
||||
imports: [MatTableModule, MatPaginatorModule, MatSortModule]
|
||||
imports: []
|
||||
})
|
||||
export class SystemInformationComponent implements AfterViewInit {
|
||||
@ViewChild(MatPaginator) paginator!: MatPaginator;
|
||||
@ViewChild(MatSort) sort!: MatSort;
|
||||
@ViewChild(MatTable) table!: MatTable<SystemInformationItem>;
|
||||
dataSource = new SystemInformationDataSource();
|
||||
export class SystemInformationComponent {
|
||||
containerInfo?: string;
|
||||
|
||||
/** Columns displayed in the table. Columns IDs can be added, removed, or reordered. */
|
||||
displayedColumns = ['id', 'name'];
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
this.dataSource.sort = this.sort;
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.table.dataSource = this.dataSource;
|
||||
constructor(private service: BackendCommunicationService) {
|
||||
this.service.getContainerInfo().subscribe(response => {
|
||||
this.containerInfo = response;
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user