mirror of
https://github.com/Eugeny/tabby-web.git
synced 2026-08-17 23:06:04 +01:00
wip
This commit is contained in:
@@ -5,10 +5,11 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { RouterModule } from '@angular/router'
|
||||
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
|
||||
import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'
|
||||
import { ClipboardModule } from '@angular/cdk/clipboard'
|
||||
import { TransferHttpCacheModule } from '@nguniversal/common'
|
||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
|
||||
import { UniversalInterceptor } from './interceptor'
|
||||
import { AppComponent } from './components/app.component'
|
||||
import { MainComponent } from './components/main.component'
|
||||
import { ConfigModalComponent } from './components/configModal.component'
|
||||
@@ -17,7 +18,7 @@ import { HomeComponent } from './components/home.component'
|
||||
import { LoginComponent } from './components/login.component'
|
||||
import { InstanceInfoResolver } from './api'
|
||||
|
||||
import '@fortawesome/fontawesome-svg-core/styles.css'
|
||||
// import '@fortawesome/fontawesome-svg-core/styles.css'
|
||||
|
||||
const ROUTES = [
|
||||
{
|
||||
@@ -25,28 +26,28 @@ const ROUTES = [
|
||||
component: HomeComponent,
|
||||
resolve: {
|
||||
instanceInfo: InstanceInfoResolver,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'app',
|
||||
component: MainComponent,
|
||||
resolve: {
|
||||
instanceInfo: InstanceInfoResolver,
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
component: LoginComponent,
|
||||
resolve: {
|
||||
instanceInfo: InstanceInfoResolver,
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule.withServerTransition({
|
||||
appId: 'tabby'
|
||||
appId: 'tabby',
|
||||
}),
|
||||
TransferHttpCacheModule,
|
||||
BrowserAnimationsModule,
|
||||
@@ -60,6 +61,13 @@ const ROUTES = [
|
||||
ClipboardModule,
|
||||
RouterModule.forRoot(ROUTES),
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: UniversalInterceptor,
|
||||
multi: true,
|
||||
},
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
MainComponent,
|
||||
@@ -68,6 +76,6 @@ const ROUTES = [
|
||||
ConfigModalComponent,
|
||||
SettingsModalComponent,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
export class AppModule { }
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http'
|
||||
import { NgModule } from '@angular/core'
|
||||
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server'
|
||||
import { AppModule } from './app.module'
|
||||
import { AppComponent } from './components/app.component'
|
||||
import { UniversalInterceptor } from './ssr-interceptor'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -13,13 +11,6 @@ import { UniversalInterceptor } from './ssr-interceptor'
|
||||
ServerModule,
|
||||
ServerTransferStateModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: UniversalInterceptor,
|
||||
multi: true
|
||||
}
|
||||
],
|
||||
// Since the bootstrapped component is not inherited from your
|
||||
// imported AppModule, it needs to be repeated here.
|
||||
bootstrap: [AppComponent],
|
||||
|
||||
@@ -6,11 +6,18 @@ import { InstanceInfo, Version } from '../api'
|
||||
import { faCoffee, faDownload, faSignInAlt } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { CommonService } from '../services/common.service'
|
||||
|
||||
|
||||
class DemoConnector {
|
||||
constructor (targetWindow: Window, private version: Version) {
|
||||
targetWindow['tabbyWebDemoDataPath'] = `${this.getDistURL()}/${version.version}/tabby-web-demo/data`
|
||||
constructor (
|
||||
targetWindow: Window,
|
||||
private commonService: CommonService,
|
||||
private version: Version,
|
||||
) {
|
||||
this.getDistURL().then(distURL => {
|
||||
targetWindow['tabbyWebDemoDataPath'] = `${distURL}/${version.version}/tabby-web-demo/data`
|
||||
})
|
||||
}
|
||||
|
||||
async loadConfig (): Promise<string> {
|
||||
@@ -22,15 +29,15 @@ class DemoConnector {
|
||||
}`
|
||||
}
|
||||
|
||||
async saveConfig (content: string): Promise<void> {
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
async saveConfig (_content: string): Promise<void> { }
|
||||
|
||||
getAppVersion (): string {
|
||||
return this.version.version
|
||||
}
|
||||
|
||||
getDistURL (): string {
|
||||
return '/app-dist'
|
||||
async getDistURL (): Promise<string> {
|
||||
return await this.commonService.getBackendURL() + '/app-dist'
|
||||
}
|
||||
|
||||
getPluginsToLoad (): string[] {
|
||||
@@ -94,12 +101,14 @@ export class HomeComponent {
|
||||
|
||||
constructor (
|
||||
private http: HttpClient,
|
||||
private commonService: CommonService,
|
||||
route: ActivatedRoute,
|
||||
) {
|
||||
window.addEventListener('message', this.connectorRequestHandler)
|
||||
this.instanceInfo = route.snapshot.data.instanceInfo
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
connectorRequestHandler = event => {
|
||||
if (event.data === 'request-connector') {
|
||||
this.iframe.nativeElement.contentWindow['__connector__'] = this.connector
|
||||
@@ -107,14 +116,14 @@ export class HomeComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async ngAfterViewInit () {
|
||||
async ngAfterViewInit (): Promise<void> {
|
||||
const versions = await this.http.get('/api/1/versions').toPromise()
|
||||
versions.sort((a, b) => -semverCompare(a.version, b.version))
|
||||
this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, versions[0])
|
||||
this.iframe.nativeElement.src = '/terminal'
|
||||
this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, this.commonService, versions[0])
|
||||
this.iframe.nativeElement.src = '/terminal.html'
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
ngOnDestroy (): void {
|
||||
window.removeEventListener('message', this.connectorRequestHandler)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ export class MainComponent {
|
||||
|
||||
async loadApp (config, version) {
|
||||
this.showApp = true
|
||||
this.iframe.nativeElement.src = '/terminal'
|
||||
this.iframe.nativeElement.src = '/terminal.html'
|
||||
await this.http.patch(`/api/1/configs/${config.id}`, {
|
||||
last_used_with_version: version.version,
|
||||
}).toPromise()
|
||||
|
||||
24
src/interceptor.ts
Normal file
24
src/interceptor.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'
|
||||
import { Observable, from } from 'rxjs'
|
||||
import { switchMap } from 'rxjs/operators'
|
||||
import { CommonService } from './services/common.service'
|
||||
|
||||
@Injectable()
|
||||
export class UniversalInterceptor implements HttpInterceptor {
|
||||
constructor (private commonService: CommonService) { }
|
||||
|
||||
intercept (request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
if (!request.url.startsWith('//') && request.url.startsWith('/')) {
|
||||
return from(this.commonService.getBackendURL()).pipe(switchMap((baseUrl: string) => {
|
||||
const endpoint = request.url
|
||||
|
||||
request = request.clone({
|
||||
url: `${baseUrl}${endpoint}`,
|
||||
})
|
||||
return next.handle(request)
|
||||
}))
|
||||
}
|
||||
return next.handle(request)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
import 'zone.js/dist/zone-node';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
|
||||
require('source-map-support').install()
|
||||
|
||||
import 'zone.js/dist/zone-node';
|
||||
import './ssr-polyfills'
|
||||
|
||||
import { enableProdMode } from '@angular/core';
|
||||
|
||||
// Express Engine
|
||||
import { ngExpressEngine } from '@nguniversal/express-engine';
|
||||
|
||||
import './ssr-polyfills'
|
||||
|
||||
import * as express from 'express'
|
||||
import { join } from 'path'
|
||||
@@ -37,15 +38,15 @@ app.use('/static', express.static(DIST_FOLDER, {
|
||||
maxAge: '1y'
|
||||
}));
|
||||
|
||||
var proxy = require('express-http-proxy');
|
||||
// var proxy = require('express-http-proxy');
|
||||
|
||||
app.get(['/', '/login'], (req, res) => {
|
||||
app.get('*', (req, res) => {
|
||||
res.render('index', { req });
|
||||
});
|
||||
|
||||
|
||||
app.use('/', proxy('http://tabby.local:8000/api/', {
|
||||
}))
|
||||
// app.use('/', proxy('http://tabby.local:8000/api/', {
|
||||
// }))
|
||||
|
||||
// Start up the Node server
|
||||
app.listen(PORT, () => {
|
||||
|
||||
@@ -2,9 +2,10 @@ import { Buffer } from 'buffer'
|
||||
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'
|
||||
import { Injectable, Injector } from '@angular/core'
|
||||
import { Config, Gateway, Version } from '../api'
|
||||
import { LoginService } from './login.service'
|
||||
import { CommonService } from './common.service'
|
||||
|
||||
export class SocketProxy {
|
||||
connect$ = new Subject<void>()
|
||||
@@ -21,12 +22,21 @@ export class SocketProxy {
|
||||
port: number
|
||||
}
|
||||
|
||||
constructor (private appConnector: AppConnectorService) { }
|
||||
private appConnector: AppConnectorService
|
||||
private loginService: LoginService
|
||||
|
||||
async connect (options) {
|
||||
constructor (
|
||||
injector: Injector,
|
||||
) {
|
||||
this.appConnector = injector.get(AppConnectorService)
|
||||
this.loginService = injector.get(LoginService)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async connect (options: any): Promise<void> {
|
||||
this.options = options
|
||||
this.url = this.appConnector.loginService.user.custom_connection_gateway
|
||||
this.authToken = this.appConnector.loginService.user.custom_connection_gateway_token
|
||||
this.url = this.loginService.user.custom_connection_gateway
|
||||
this.authToken = this.loginService.user.custom_connection_gateway_token
|
||||
if (!this.url) {
|
||||
try {
|
||||
const gateway = await this.appConnector.chooseConnectionGateway()
|
||||
@@ -120,9 +130,11 @@ export class AppConnectorService {
|
||||
sockets: SocketProxy[] = []
|
||||
|
||||
constructor (
|
||||
private injector: Injector,
|
||||
private http: HttpClient,
|
||||
public loginService: LoginService,
|
||||
private commonService: CommonService,
|
||||
) {
|
||||
|
||||
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)
|
||||
@@ -147,8 +159,8 @@ export class AppConnectorService {
|
||||
return this.version.version
|
||||
}
|
||||
|
||||
getDistURL (): string {
|
||||
return '../app-dist'
|
||||
async getDistURL (): Promise<string> {
|
||||
return await this.commonService.getBackendURL() + '/app-dist'
|
||||
}
|
||||
|
||||
getPluginsToLoad (): string[] {
|
||||
@@ -168,7 +180,7 @@ export class AppConnectorService {
|
||||
}
|
||||
|
||||
createSocket () {
|
||||
const socket = new SocketProxy(this)
|
||||
const socket = new SocketProxy(this.injector)
|
||||
this.sockets.push(socket)
|
||||
socket.close$.subscribe(() => {
|
||||
this.sockets = this.sockets.filter(x => x !== socket)
|
||||
|
||||
18
src/services/common.service.ts
Normal file
18
src/services/common.service.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CommonService {
|
||||
private backendURL?: string
|
||||
|
||||
async getBackendURL (): Promise<string> {
|
||||
if (!this.backendURL) {
|
||||
const config = await (await fetch('/config.json')).json()
|
||||
this.backendURL = config.backendURL
|
||||
if (this.backendURL.endsWith('/')) {
|
||||
this.backendURL = this.backendURL.slice(0, -1)
|
||||
}
|
||||
}
|
||||
return this.backendURL
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* This interceptor ensures that the app makes requests
|
||||
* with relative paths correctly server-side.
|
||||
* Requests which start with a dot (ex. ./assets/...)
|
||||
* or relative ones ( ex. /assets/...) will be converted
|
||||
* to absolute paths
|
||||
*/
|
||||
import { Inject, Injectable, Injector, PLATFORM_ID } from '@angular/core';
|
||||
import { isPlatformServer } from '@angular/common';
|
||||
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
|
||||
import { REQUEST } from '@nguniversal/express-engine/tokens';
|
||||
|
||||
import { Request } from 'express';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class UniversalInterceptor implements HttpInterceptor {
|
||||
constructor(
|
||||
private readonly injector: Injector,
|
||||
@Inject(PLATFORM_ID) private readonly platformId: any) {
|
||||
}
|
||||
|
||||
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
const isServer = isPlatformServer(this.platformId);
|
||||
|
||||
if (isServer && !request.url.startsWith('//') && (request.url.startsWith('./') || request.url.startsWith('/'))) {
|
||||
const serverRequest = this.injector.get(REQUEST) as Request;
|
||||
console.log(serverRequest)
|
||||
const baseUrl = `${serverRequest.protocol}://${serverRequest.get('Host')}`;
|
||||
let endpoint = request.url;
|
||||
/**
|
||||
* ISSUE https://github.com/angular/angular/issues/19224
|
||||
* HttpClient doesn't support relative requests server-side
|
||||
*/
|
||||
if (endpoint.startsWith('.')) {
|
||||
endpoint = endpoint.substring(1);
|
||||
}
|
||||
// Now the endpoint starts with '/'
|
||||
request = request.clone({
|
||||
url: `${baseUrl}${endpoint}`
|
||||
});
|
||||
}
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import * as domino from 'domino';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
const template = fs.readFileSync(path.join(process.cwd(), 'build-server', 'index.html')).toString();
|
||||
const template = fs.readFileSync(path.join(process.cwd(), 'build', 'index.html')).toString();
|
||||
const win = domino.createWindow(template);
|
||||
|
||||
global['window'] = win;
|
||||
|
||||
@@ -31,7 +31,7 @@ async function start () {
|
||||
await (await fetch(url)).text()
|
||||
}
|
||||
|
||||
const baseUrl = `${connector.getDistURL()}/${appVersion}`
|
||||
const baseUrl = `${await connector.getDistURL()}/${appVersion}`
|
||||
const coreURLs = [
|
||||
`${baseUrl}/tabby-web-container/dist/preload.js`,
|
||||
`${baseUrl}/tabby-web-container/dist/bundle.js`,
|
||||
|
||||
Reference in New Issue
Block a user