diff --git a/src/components/app.component.pug b/src/components/app.component.pug index 63f69d5..314c3e1 100644 --- a/src/components/app.component.pug +++ b/src/components/app.component.pug @@ -1,5 +1,5 @@ -main(*ngIf='ready && user') -.login-view(*ngIf='ready && !user') +main(*ngIf='ready && loggedIn') +.login-view(*ngIf='ready && !loggedIn') .buttons a.btn( *ngFor='let provider of providers', diff --git a/src/components/app.component.ts b/src/components/app.component.ts index e0d0e93..264c2c7 100644 --- a/src/components/app.component.ts +++ b/src/components/app.component.ts @@ -1,5 +1,5 @@ import { Component } from '@angular/core' -import { HttpClient } from '@angular/common/http' +import { LoginService } from '../services/login.service' import { faGithub, faGitlab, faGoogle, faMicrosoft } from '@fortawesome/free-brands-svg-icons' @@ -9,7 +9,7 @@ import { faGithub, faGitlab, faGoogle, faMicrosoft } from '@fortawesome/free-bra styleUrls: ['./app.component.scss'], }) export class AppComponent { - user: any + loggedIn: any ready = false providers = [ @@ -20,14 +20,12 @@ export class AppComponent { ] constructor ( - private http: HttpClient, + private loginService: LoginService, ) { } async ngOnInit () { - const user = await this.http.get('/api/1/user').toPromise() - if (user.id) { - this.user = user - } + await this.loginService.ready$.toPromise() + this.loggedIn = !!this.loginService.user this.ready = true } } diff --git a/src/components/main.component.pug b/src/components/main.component.pug index 99d90e9..bd4a88e 100644 --- a/src/components/main.component.pug +++ b/src/components/main.component.pug @@ -46,6 +46,15 @@ fa-icon([icon]='_addIcon', [fixedWidth]='true') span New config + div(ngbDropdown, placement='bottom-right') + button.btn(ngbDropdownToggle) + fa-icon([icon]='_connectionIcon', [fixedWidth]='true') + + div(ngbDropdownMenu) + .form-check.form-switch + input.form-check-input(type='checkbox', ngModel='!!loginService.user.custom_connection_gateway') + label(class='form-check-label') Use custom connection gateway + div(ngbDropdown, placement='bottom-right') button.btn(ngbDropdownToggle) fa-icon([icon]='_userIcon', [fixedWidth]='true') diff --git a/src/components/main.component.ts b/src/components/main.component.ts index d934794..76300f6 100644 --- a/src/components/main.component.ts +++ b/src/components/main.component.ts @@ -3,7 +3,8 @@ import { Component, ElementRef, ViewChild } from '@angular/core' import { HttpClient } from '@angular/common/http' import { AppConnectorService } from '../services/appConnector.service' -import { faCog, faUser, faCopy, faTrash, faPlus } from '@fortawesome/free-solid-svg-icons' +import { faCog, faUser, faCopy, faTrash, faPlus, faPlug } from '@fortawesome/free-solid-svg-icons' +import { LoginService } from '../services/login.service' @Component({ selector: 'main', @@ -17,6 +18,7 @@ export class MainComponent { _copyIcon = faCopy _addIcon = faPlus _deleteIcon = faTrash + _connectionIcon = faPlug configs: any[] = [] versions: any[] = [] @@ -26,6 +28,7 @@ export class MainComponent { constructor ( private appConnector: AppConnectorService, private http: HttpClient, + public loginService: LoginService, ) { window.addEventListener('message', event => { if (event.data === 'request-connector') { diff --git a/src/services/appConnector.service.ts b/src/services/appConnector.service.ts index a82d418..69d7a02 100644 --- a/src/services/appConnector.service.ts +++ b/src/services/appConnector.service.ts @@ -3,30 +3,67 @@ import { Subject } from 'rxjs' import { debounceTime } from 'rxjs/operators' import { HttpClient } from '@angular/common/http' import { Injectable } from '@angular/core' +import { LoginService } from '../services/login.service' export class SocketProxy { connect$ = new Subject() data$ = new Subject() + error$ = new Subject() webSocket: WebSocket initialBuffer: Buffer + options: { + host: string + port: number + } - constructor () { + constructor (private appConnector: AppConnectorService) { this.initialBuffer = Buffer.from('') } connect (options) { - console.log('ws connect', options) - this.webSocket = new WebSocket(`ws://${location.host}/api/1/gateway/tcp/${options.host}:${options.port}`) - this.webSocket.onopen = event => { + this.options = options + this.webSocket = new WebSocket( + this.appConnector.loginService.user.custom_connection_gateway || + `ws://${location.host}/api/1/gateway/tcp` + ) + this.webSocket.onmessage = async event => { + if (typeof(event.data) === 'string') { + this.handleServiceMessage(JSON.parse(event.data)) + } else { + this.data$.next(Buffer.from(await event.data.arrayBuffer())) + } + } + } + + handleServiceMessage (msg) { + if (msg._ === 'hello') { + this.sendServiceMessage({ + _: 'hello', + version: 1, + auth_token: this.appConnector.loginService.user.custom_connection_gateway_token, + }) + } else if (msg._ === 'ready') { + this.sendServiceMessage({ + _: 'connect', + host: this.options.host, + port: this.options.port, + }) + } else if (msg._ === 'connected') { this.connect$.next() this.connect$.complete() this.webSocket.send(this.initialBuffer) this.initialBuffer = Buffer.from('') + } else if (msg._ === 'error') { + console.error('Connection gateway error', msg) + this.close(new Error(msg.details)) + } else { + console.warn('Unknown service message', msg) } - this.webSocket.onmessage = async event => { - this.data$.next(Buffer.from(await event.data.arrayBuffer())) - } + } + + sendServiceMessage (msg) { + this.webSocket.send(JSON.stringify(msg)) } write (chunk: Buffer): void { @@ -38,7 +75,12 @@ export class SocketProxy { } close (error: Error): void { - console.warn('socket destroy', error) + if (error) { + this.error$.next(error) + } + this.connect$.complete() + this.data$.complete() + this.error$.complete() } } @@ -46,10 +88,13 @@ export class SocketProxy { export class AppConnectorService { config: any version: any - Socket = SocketProxy + user: any private configUpdate = new Subject() - constructor (private http: HttpClient) { + constructor ( + private http: HttpClient, + public loginService: LoginService, + ) { this.configUpdate.pipe(debounceTime(1000)).subscribe(async content => { const result = await this.http.patch(`/api/1/configs/${this.config.id}`, { content }).toPromise() Object.assign(this.config, result) @@ -68,4 +113,8 @@ export class AppConnectorService { getAppVersion (): string { return this.version.version } + + createSocket () { + return new SocketProxy(this) + } } diff --git a/src/services/login.service.ts b/src/services/login.service.ts new file mode 100644 index 0000000..8507800 --- /dev/null +++ b/src/services/login.service.ts @@ -0,0 +1,21 @@ +import { AsyncSubject } from 'rxjs' +import { HttpClient } from '@angular/common/http' +import { Injectable } from '@angular/core' + + +@Injectable({ providedIn: 'root' }) +export class LoginService { + user: any + ready$ = new AsyncSubject() + + constructor (private http: HttpClient) { + this.init() + } + + private async init () { + const user = await this.http.get('/api/1/user').toPromise() + this.user = user + this.ready$.next() + this.ready$.complete() + } +} diff --git a/terminus/app/admin.py b/terminus/app/admin.py index 43e9afb..5f89b0d 100644 --- a/terminus/app/admin.py +++ b/terminus/app/admin.py @@ -2,5 +2,10 @@ from django.contrib import admin from django.contrib.auth.admin import UserAdmin from .models import User, Config -admin.site.register(User, UserAdmin) +class CustomUserAdmin(UserAdmin): + fieldsets = UserAdmin.fieldsets + ( + (None, {'fields': ('custom_connection_gateway', 'custom_connection_gateway_token')}), + ) + +admin.site.register(User, CustomUserAdmin) admin.site.register(Config) diff --git a/terminus/app/api.py b/terminus/app/api.py index fe214a5..988826d 100644 --- a/terminus/app/api.py +++ b/terminus/app/api.py @@ -67,7 +67,7 @@ class UserSerializer(ModelSerializer): class Meta: model = User - fields = ('id', 'username', 'active_config') + fields = ('id', 'username', 'active_config', 'custom_connection_gateway', 'custom_connection_gateway_token') read_only_fields = ('id', 'username') diff --git a/terminus/app/migrations/0003_user_custom_connection_gateway.py b/terminus/app/migrations/0003_user_custom_connection_gateway.py new file mode 100644 index 0000000..ac0b451 --- /dev/null +++ b/terminus/app/migrations/0003_user_custom_connection_gateway.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.3 on 2021-06-20 20:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0002_auto_20210605_2137'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='custom_connection_gateway', + field=models.CharField(max_length=255, null=True), + ), + ] diff --git a/terminus/app/migrations/0004_user_custom_connection_gateway_token.py b/terminus/app/migrations/0004_user_custom_connection_gateway_token.py new file mode 100644 index 0000000..d62fe60 --- /dev/null +++ b/terminus/app/migrations/0004_user_custom_connection_gateway_token.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.3 on 2021-06-20 20:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('app', '0003_user_custom_connection_gateway'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='custom_connection_gateway_token', + field=models.CharField(max_length=255, null=True), + ), + ] diff --git a/terminus/app/models.py b/terminus/app/models.py index 9cfdf07..751f878 100644 --- a/terminus/app/models.py +++ b/terminus/app/models.py @@ -16,6 +16,8 @@ class Config(models.Model): class User(AbstractUser): active_config = models.ForeignKey(Config, null=True, on_delete=models.CASCADE, related_name='+') active_version = models.CharField(max_length=32, null=True) + custom_connection_gateway = models.CharField(max_length=255, null=True) + custom_connection_gateway_token = models.CharField(max_length=255, null=True) created_at = models.DateTimeField(auto_now_add=True) modified_at = models.DateTimeField(auto_now=True)