1
0
mirror of https://github.com/Eugeny/tabby-web.git synced 2025-07-22 10:58:03 +00:00
This commit is contained in:
Eugene Pankov
2021-07-25 14:00:22 +02:00
parent d9d019e15f
commit e11193b807
24 changed files with 961 additions and 193 deletions

13
.editorconfig Normal file

@@ -0,0 +1,13 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

123
.eslintrc.yml Normal file

@@ -0,0 +1,123 @@
parser: '@typescript-eslint/parser'
parserOptions:
project:
- tsconfig.json
- '*/tsconfig.typings.json'
extends:
- 'plugin:@typescript-eslint/all'
plugins:
- '@typescript-eslint'
env:
browser: true
es6: true
node: true
commonjs: true
rules:
'@typescript-eslint/semi':
- error
- never
'@typescript-eslint/indent':
- error
- 4
'@typescript-eslint/explicit-member-accessibility':
- error
- accessibility: no-public
overrides:
parameterProperties: explicit
'@typescript-eslint/no-require-imports': off
'@typescript-eslint/no-parameter-properties': off
'@typescript-eslint/explicit-function-return-type': off
'@typescript-eslint/no-explicit-any': off
'@typescript-eslint/no-magic-numbers': off
'@typescript-eslint/member-delimiter-style': off
'@typescript-eslint/promise-function-async': off
'@typescript-eslint/require-array-sort-compare': off
'@typescript-eslint/no-floating-promises': off
'@typescript-eslint/prefer-readonly': off
'@typescript-eslint/require-await': off
'@typescript-eslint/strict-boolean-expressions': off
'@typescript-eslint/no-misused-promises':
- error
- checksVoidReturn: false
'@typescript-eslint/typedef': off
'@typescript-eslint/consistent-type-imports': off
'@typescript-eslint/sort-type-union-intersection-members': off
'@typescript-eslint/no-use-before-define':
- error
- classes: false
no-duplicate-imports: error
array-bracket-spacing:
- error
- never
block-scoped-var: error
brace-style: off
'@typescript-eslint/brace-style':
- error
- 1tbs
- allowSingleLine: true
computed-property-spacing:
- error
- never
comma-dangle: off
'@typescript-eslint/comma-dangle':
- error
- always-multiline
curly: error
eol-last: error
eqeqeq:
- error
- smart
max-depth:
- 1
- 5
max-statements:
- 1
- 80
no-multiple-empty-lines: error
no-mixed-spaces-and-tabs: error
no-trailing-spaces: error
'@typescript-eslint/no-unused-vars':
- error
- vars: all
args: after-used
argsIgnorePattern: ^_
no-undef: error
no-var: error
object-curly-spacing: off
'@typescript-eslint/object-curly-spacing':
- error
- always
quote-props:
- warn
- as-needed
- keywords: true
numbers: true
quotes: off
'@typescript-eslint/quotes':
- error
- single
- allowTemplateLiterals: true
'@typescript-eslint/no-confusing-void-expression':
- error
- ignoreArrowShorthand: true
'@typescript-eslint/no-non-null-assertion': off
'@typescript-eslint/no-unnecessary-condition':
- error
- allowConstantLoopConditions: true
'@typescript-eslint/restrict-template-expressions': off
'@typescript-eslint/prefer-readonly-parameter-types': off
'@typescript-eslint/no-unsafe-member-access': off
'@typescript-eslint/no-unsafe-call': off
'@typescript-eslint/no-unsafe-return': off
'@typescript-eslint/no-unsafe-assignment': off
'@typescript-eslint/naming-convention': off
'@typescript-eslint/lines-between-class-members':
- error
- exceptAfterSingleLine: true
'@typescript-eslint/dot-notation': off
'@typescript-eslint/no-implicit-any-catch': off
'@typescript-eslint/member-ordering': off
'@typescript-eslint/no-var-requires': off
'@typescript-eslint/no-unsafe-argument': off
'@typescript-eslint/restrict-plus-operands': off
'@typescript-eslint/space-infix-ops': off

@@ -4,10 +4,11 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"build": "webpack --progress", "build": "webpack --progress",
"watch": "DEV=1 webpack --progress --watch", "watch": "BACKEND_URL=http://localhost:8001 DEV=1 webpack --progress --watch",
"build:server": "webpack --progress -c webpack.server.config.js", "build:server": "webpack --progress -c webpack.config.server.js",
"watch:server": "DEV=1 webpack --progress --watch -c webpack.server.config.js", "watch:server": "DEV=1 webpack --progress --watch -c webpack.config.server.js",
"start": "./manage.py runserver" "start": "./manage.py runserver 8001",
"start:server": "PORT=8000 node build-server/server.js"
}, },
"private": true, "private": true,
"devDependencies": { "devDependencies": {
@@ -32,6 +33,8 @@
"@ngtools/webpack": "^12.0.4", "@ngtools/webpack": "^12.0.4",
"@tabby-gang/to-string-loader": "^1.1.7-beta.1", "@tabby-gang/to-string-loader": "^1.1.7-beta.1",
"@types/node": "^11.9.5", "@types/node": "^11.9.5",
"@typescript-eslint/eslint-plugin": "^4.28.4",
"@typescript-eslint/parser": "^4.28.4",
"apply-loader": "^2.0.0", "apply-loader": "^2.0.0",
"awesome-typescript-loader": "^5.2.1", "awesome-typescript-loader": "^5.2.1",
"bootstrap": "^5.0.1", "bootstrap": "^5.0.1",
@@ -39,6 +42,7 @@
"core-js": "^3.14.0", "core-js": "^3.14.0",
"css-loader": "^2.1.0", "css-loader": "^2.1.0",
"deepmerge": "^4.2.2", "deepmerge": "^4.2.2",
"eslint": "^7.31.0",
"file-loader": "^1.1.11", "file-loader": "^1.1.11",
"html-loader": "^2.1.2", "html-loader": "^2.1.2",
"html-webpack-plugin": "^5.3.2", "html-webpack-plugin": "^5.3.2",
@@ -63,6 +67,7 @@
"webpack": "^5.38.1", "webpack": "^5.38.1",
"webpack-bundle-analyzer": "^4.4.2", "webpack-bundle-analyzer": "^4.4.2",
"webpack-cli": "^4.7.2", "webpack-cli": "^4.7.2",
"webpack-node-externals": "^3.0.0",
"zone.js": "^0.11.4" "zone.js": "^0.11.4"
}, },
"dependencies": { "dependencies": {

17
poetry.lock generated

@@ -237,6 +237,17 @@ sqlparse = ">=0.2.2"
argon2 = ["argon2-cffi (>=19.1.0)"] argon2 = ["argon2-cffi (>=19.1.0)"]
bcrypt = ["bcrypt"] bcrypt = ["bcrypt"]
[[package]]
name = "django-cors-headers"
version = "3.7.0"
description = "django-cors-headers is a Django application for handling the server headers required for Cross-Origin Resource Sharing (CORS)."
category = "main"
optional = false
python-versions = ">=3.6"
[package.dependencies]
Django = ">=2.2"
[[package]] [[package]]
name = "django-rest-framework" name = "django-rest-framework"
version = "0.1.0" version = "0.1.0"
@@ -852,7 +863,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.7" python-versions = "^3.7"
content-hash = "198155cefe870371fb615a4787e9117dd1f1b9ea25116df55741b1dcd4eb2c12" content-hash = "cc070c0414bb554395cbc518a5857a6f239ad17b1573c27012441cef86320b7a"
[metadata.files] [metadata.files]
appdirs = [ appdirs = [
@@ -984,6 +995,10 @@ django = [
{file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"}, {file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"},
{file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"}, {file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"},
] ]
django-cors-headers = [
{file = "django-cors-headers-3.7.0.tar.gz", hash = "sha256:96069c4aaacace786a34ee7894ff680780ec2644e4268b31181044410fecd12e"},
{file = "django_cors_headers-3.7.0-py3-none-any.whl", hash = "sha256:1ac2b1213de75a251e2ba04448da15f99bcfcbe164288ae6b5ff929dc49b372f"},
]
django-rest-framework = [ django-rest-framework = [
{file = "django-rest-framework-0.1.0.tar.gz", hash = "sha256:47a8f496fa69e3b6bd79f68dd7a1527d907d6b77f009e9db7cf9bb21cc565e4a"}, {file = "django-rest-framework-0.1.0.tar.gz", hash = "sha256:47a8f496fa69e3b6bd79f68dd7a1527d907d6b77f009e9db7cf9bb21cc565e4a"},
] ]

@@ -23,6 +23,7 @@ Twisted = "20.3.0"
semver = "^2.13.0" semver = "^2.13.0"
requests = "^2.25.1" requests = "^2.25.1"
pyga = "^2.6.2" pyga = "^2.6.2"
django-cors-headers = "^3.7.0"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
flake8 = "^3.9.2" flake8 = "^3.9.2"

@@ -5,10 +5,11 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { CommonModule } from '@angular/common' import { CommonModule } from '@angular/common'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { RouterModule } from '@angular/router' 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 { ClipboardModule } from '@angular/cdk/clipboard'
import { TransferHttpCacheModule } from '@nguniversal/common' import { TransferHttpCacheModule } from '@nguniversal/common'
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome' import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
import { UniversalInterceptor } from './interceptor'
import { AppComponent } from './components/app.component' import { AppComponent } from './components/app.component'
import { MainComponent } from './components/main.component' import { MainComponent } from './components/main.component'
import { ConfigModalComponent } from './components/configModal.component' import { ConfigModalComponent } from './components/configModal.component'
@@ -17,7 +18,7 @@ import { HomeComponent } from './components/home.component'
import { LoginComponent } from './components/login.component' import { LoginComponent } from './components/login.component'
import { InstanceInfoResolver } from './api' import { InstanceInfoResolver } from './api'
import '@fortawesome/fontawesome-svg-core/styles.css' // import '@fortawesome/fontawesome-svg-core/styles.css'
const ROUTES = [ const ROUTES = [
{ {
@@ -25,28 +26,28 @@ const ROUTES = [
component: HomeComponent, component: HomeComponent,
resolve: { resolve: {
instanceInfo: InstanceInfoResolver, instanceInfo: InstanceInfoResolver,
} },
}, },
{ {
path: 'app', path: 'app',
component: MainComponent, component: MainComponent,
resolve: { resolve: {
instanceInfo: InstanceInfoResolver, instanceInfo: InstanceInfoResolver,
} },
}, },
{ {
path: 'login', path: 'login',
component: LoginComponent, component: LoginComponent,
resolve: { resolve: {
instanceInfo: InstanceInfoResolver, instanceInfo: InstanceInfoResolver,
} },
}, },
] ]
@NgModule({ @NgModule({
imports: [ imports: [
BrowserModule.withServerTransition({ BrowserModule.withServerTransition({
appId: 'tabby' appId: 'tabby',
}), }),
TransferHttpCacheModule, TransferHttpCacheModule,
BrowserAnimationsModule, BrowserAnimationsModule,
@@ -60,6 +61,13 @@ const ROUTES = [
ClipboardModule, ClipboardModule,
RouterModule.forRoot(ROUTES), RouterModule.forRoot(ROUTES),
], ],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UniversalInterceptor,
multi: true,
},
],
declarations: [ declarations: [
AppComponent, AppComponent,
MainComponent, MainComponent,
@@ -68,6 +76,6 @@ const ROUTES = [
ConfigModalComponent, ConfigModalComponent,
SettingsModalComponent, SettingsModalComponent,
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent],
}) })
export class AppModule { } export class AppModule { }

@@ -1,9 +1,7 @@
import { HTTP_INTERCEPTORS } from '@angular/common/http'
import { NgModule } from '@angular/core' import { NgModule } from '@angular/core'
import { ServerModule, ServerTransferStateModule } from '@angular/platform-server' import { ServerModule, ServerTransferStateModule } from '@angular/platform-server'
import { AppModule } from './app.module' import { AppModule } from './app.module'
import { AppComponent } from './components/app.component' import { AppComponent } from './components/app.component'
import { UniversalInterceptor } from './ssr-interceptor'
@NgModule({ @NgModule({
imports: [ imports: [
@@ -13,13 +11,6 @@ import { UniversalInterceptor } from './ssr-interceptor'
ServerModule, ServerModule,
ServerTransferStateModule, ServerTransferStateModule,
], ],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: UniversalInterceptor,
multi: true
}
],
// Since the bootstrapped component is not inherited from your // Since the bootstrapped component is not inherited from your
// imported AppModule, it needs to be repeated here. // imported AppModule, it needs to be repeated here.
bootstrap: [AppComponent], bootstrap: [AppComponent],

@@ -6,11 +6,18 @@ import { InstanceInfo, Version } from '../api'
import { faCoffee, faDownload, faSignInAlt } from '@fortawesome/free-solid-svg-icons' import { faCoffee, faDownload, faSignInAlt } from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons' import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { ActivatedRoute } from '@angular/router' import { ActivatedRoute } from '@angular/router'
import { CommonService } from '../services/common.service'
class DemoConnector { class DemoConnector {
constructor (targetWindow: Window, private version: Version) { constructor (
targetWindow['tabbyWebDemoDataPath'] = `${this.getDistURL()}/${version.version}/tabby-web-demo/data` 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> { 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 { getAppVersion (): string {
return this.version.version return this.version.version
} }
getDistURL (): string { async getDistURL (): Promise<string> {
return '/app-dist' return await this.commonService.getBackendURL() + '/app-dist'
} }
getPluginsToLoad (): string[] { getPluginsToLoad (): string[] {
@@ -94,12 +101,14 @@ export class HomeComponent {
constructor ( constructor (
private http: HttpClient, private http: HttpClient,
private commonService: CommonService,
route: ActivatedRoute, route: ActivatedRoute,
) { ) {
window.addEventListener('message', this.connectorRequestHandler) window.addEventListener('message', this.connectorRequestHandler)
this.instanceInfo = route.snapshot.data.instanceInfo this.instanceInfo = route.snapshot.data.instanceInfo
} }
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
connectorRequestHandler = event => { connectorRequestHandler = event => {
if (event.data === 'request-connector') { if (event.data === 'request-connector') {
this.iframe.nativeElement.contentWindow['__connector__'] = this.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() const versions = await this.http.get('/api/1/versions').toPromise()
versions.sort((a, b) => -semverCompare(a.version, b.version)) versions.sort((a, b) => -semverCompare(a.version, b.version))
this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, versions[0]) this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, this.commonService, versions[0])
this.iframe.nativeElement.src = '/terminal' this.iframe.nativeElement.src = '/terminal.html'
} }
ngOnDestroy () { ngOnDestroy (): void {
window.removeEventListener('message', this.connectorRequestHandler) window.removeEventListener('message', this.connectorRequestHandler)
} }
} }

@@ -80,7 +80,7 @@ export class MainComponent {
async loadApp (config, version) { async loadApp (config, version) {
this.showApp = true this.showApp = true
this.iframe.nativeElement.src = '/terminal' this.iframe.nativeElement.src = '/terminal.html'
await this.http.patch(`/api/1/configs/${config.id}`, { await this.http.patch(`/api/1/configs/${config.id}`, {
last_used_with_version: version.version, last_used_with_version: version.version,
}).toPromise() }).toPromise()

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() require('source-map-support').install()
import 'zone.js/dist/zone-node';
import './ssr-polyfills'
import { enableProdMode } from '@angular/core';
// Express Engine // Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine'; import { ngExpressEngine } from '@nguniversal/express-engine';
import './ssr-polyfills'
import * as express from 'express' import * as express from 'express'
import { join } from 'path' import { join } from 'path'
@@ -37,15 +38,15 @@ app.use('/static', express.static(DIST_FOLDER, {
maxAge: '1y' 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 }); 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 // Start up the Node server
app.listen(PORT, () => { app.listen(PORT, () => {

@@ -2,9 +2,10 @@ import { Buffer } from 'buffer'
import { Subject } from 'rxjs' import { Subject } from 'rxjs'
import { debounceTime } from 'rxjs/operators' import { debounceTime } from 'rxjs/operators'
import { HttpClient } from '@angular/common/http' import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core' import { Injectable, Injector } from '@angular/core'
import { LoginService } from '../services/login.service'
import { Config, Gateway, Version } from '../api' import { Config, Gateway, Version } from '../api'
import { LoginService } from './login.service'
import { CommonService } from './common.service'
export class SocketProxy { export class SocketProxy {
connect$ = new Subject<void>() connect$ = new Subject<void>()
@@ -21,12 +22,21 @@ export class SocketProxy {
port: number 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.options = options
this.url = this.appConnector.loginService.user.custom_connection_gateway this.url = this.loginService.user.custom_connection_gateway
this.authToken = this.appConnector.loginService.user.custom_connection_gateway_token this.authToken = this.loginService.user.custom_connection_gateway_token
if (!this.url) { if (!this.url) {
try { try {
const gateway = await this.appConnector.chooseConnectionGateway() const gateway = await this.appConnector.chooseConnectionGateway()
@@ -120,9 +130,11 @@ export class AppConnectorService {
sockets: SocketProxy[] = [] sockets: SocketProxy[] = []
constructor ( constructor (
private injector: Injector,
private http: HttpClient, private http: HttpClient,
public loginService: LoginService, private commonService: CommonService,
) { ) {
this.configUpdate.pipe(debounceTime(1000)).subscribe(async content => { this.configUpdate.pipe(debounceTime(1000)).subscribe(async content => {
const result = await this.http.patch(`/api/1/configs/${this.config.id}`, { content }).toPromise() const result = await this.http.patch(`/api/1/configs/${this.config.id}`, { content }).toPromise()
Object.assign(this.config, result) Object.assign(this.config, result)
@@ -147,8 +159,8 @@ export class AppConnectorService {
return this.version.version return this.version.version
} }
getDistURL (): string { async getDistURL (): Promise<string> {
return '../app-dist' return await this.commonService.getBackendURL() + '/app-dist'
} }
getPluginsToLoad (): string[] { getPluginsToLoad (): string[] {
@@ -168,7 +180,7 @@ export class AppConnectorService {
} }
createSocket () { createSocket () {
const socket = new SocketProxy(this) const socket = new SocketProxy(this.injector)
this.sockets.push(socket) this.sockets.push(socket)
socket.close$.subscribe(() => { socket.close$.subscribe(() => {
this.sockets = this.sockets.filter(x => x !== socket) this.sockets = this.sockets.filter(x => x !== socket)

@@ -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 fs from 'fs';
import * as path from 'path'; 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); const win = domino.createWindow(template);
global['window'] = win; global['window'] = win;

@@ -31,7 +31,7 @@ async function start () {
await (await fetch(url)).text() await (await fetch(url)).text()
} }
const baseUrl = `${connector.getDistURL()}/${appVersion}` const baseUrl = `${await connector.getDistURL()}/${appVersion}`
const coreURLs = [ const coreURLs = [
`${baseUrl}/tabby-web-container/dist/preload.js`, `${baseUrl}/tabby-web-container/dist/preload.js`,
`${baseUrl}/tabby-web-container/dist/bundle.js`, `${baseUrl}/tabby-web-container/dist/bundle.js`,

@@ -16,9 +16,9 @@ urlpatterns = [
path('api/1/instance-info', api.InstanceInfoViewSet.as_view({'get': 'retrieve'})), path('api/1/instance-info', api.InstanceInfoViewSet.as_view({'get': 'retrieve'})),
path('api/1/gateways/choose', api.ChooseGatewayViewSet.as_view({'post': 'retrieve'})), path('api/1/gateways/choose', api.ChooseGatewayViewSet.as_view({'post': 'retrieve'})),
re_path('^(|login|app)$', views.IndexView.as_view()), # re_path('^(|login|app)$', views.IndexView.as_view()),
path('terminal', views.TerminalView.as_view()), # path('terminal', views.TerminalView.as_view()),
path('app-dist/<version>/<path:path>', views.AppDistView.as_view()), path('app-dist/<version>/<path:path>', views.AppDistView.as_view()),
path('', include(router.urls)), path('', include(router.urls)),
] ]

@@ -35,6 +35,7 @@ INSTALLED_APPS = [
'channels', 'channels',
'rest_framework', 'rest_framework',
'social_django', 'social_django',
'corsheaders',
'tabby.app', 'tabby.app',
] ]
@@ -46,6 +47,7 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'corsheaders.middleware.CorsMiddleware',
'tabby.middleware.TokenMiddleware', 'tabby.middleware.TokenMiddleware',
'tabby.middleware.GAMiddleware', 'tabby.middleware.GAMiddleware',
] ]
@@ -135,15 +137,7 @@ LOGGING = {
}, },
} }
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/' STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'static'
STATICFILES_DIRS = [BASE_DIR / 'build']
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
@@ -180,9 +174,11 @@ LOGIN_REDIRECT_URL = '/app'
APP_DIST_PATH = Path(os.getenv('APP_DIST_PATH', BASE_DIR / 'app-dist')) APP_DIST_PATH = Path(os.getenv('APP_DIST_PATH', BASE_DIR / 'app-dist'))
NPM_REGISTRY = os.getenv('NPM_REGISTRY', 'https://registry.npmjs.org').rstrip('/') NPM_REGISTRY = os.getenv('NPM_REGISTRY', 'https://registry.npmjs.org').rstrip('/')
FRONTEND_URL = None
GITHUB_ELIGIBLE_SPONSORSHIPS = None GITHUB_ELIGIBLE_SPONSORSHIPS = None
for key in [ for key in [
'FRONTEND_URL',
'SOCIAL_AUTH_GITHUB_KEY', 'SOCIAL_AUTH_GITHUB_KEY',
'SOCIAL_AUTH_GITHUB_SECRET', 'SOCIAL_AUTH_GITHUB_SECRET',
'SOCIAL_AUTH_GITLAB_KEY', 'SOCIAL_AUTH_GITLAB_KEY',
@@ -229,3 +225,7 @@ if GITHUB_ELIGIBLE_SPONSORSHIPS:
GITHUB_ELIGIBLE_SPONSORSHIPS = GITHUB_ELIGIBLE_SPONSORSHIPS.split(',') GITHUB_ELIGIBLE_SPONSORSHIPS = GITHUB_ELIGIBLE_SPONSORSHIPS.split(',')
else: else:
GITHUB_ELIGIBLE_SPONSORSHIPS = [] GITHUB_ELIGIBLE_SPONSORSHIPS = []
if FRONTEND_URL:
CORS_ALLOWED_ORIGINS = [FRONTEND_URL]

@@ -1,11 +1,14 @@
from django.conf import settings from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from .app.urls import urlpatterns from django.views.static import serve
from .app.urls import urlpatterns as app_urlpatterns
urlpatterns = [ urlpatterns = [
path('', include(urlpatterns)), path('', include(app_urlpatterns)),
path('api/1/auth/social/', include('social_django.urls', namespace='social')), path('api/1/auth/social/', include('social_django.urls', namespace='social')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) path(f'^{settings.STATIC_URL}<path:path>', serve, kwargs={
'document_root': settings.STATIC_ROOT,
}),
]

@@ -1,12 +1,6 @@
const webpack = require('webpack') const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require("mini-css-extract-plugin") const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const htmlPluginOptions = {
hash: true,
minify: false
}
module.exports = { module.exports = {
mode: process.env.DEV ? 'development' : 'production', mode: process.env.DEV ? 'development' : 'production',
context: __dirname, context: __dirname,
@@ -61,17 +55,5 @@ module.exports = {
}, },
plugins: [ plugins: [
new MiniCssExtractPlugin(), new MiniCssExtractPlugin(),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['index'],
...htmlPluginOptions,
}),
new HtmlWebpackPlugin({
template: './src/terminal.html',
filename: 'terminal.html',
chunks: ['terminal'],
...htmlPluginOptions,
}),
], ],
} }

@@ -1,7 +1,20 @@
const baseConfig = require('./webpack.base.config.js') const baseConfig = require('./webpack.config.base.js')
const fs = require('fs')
const path = require('path') const path = require('path')
const { AngularWebpackPlugin } = require('@ngtools/webpack') const { AngularWebpackPlugin } = require('@ngtools/webpack')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
const HtmlWebpackPlugin = require('html-webpack-plugin')
const htmlPluginOptions = {
hash: true,
minify: false
}
const outputPath = path.join(__dirname, 'build')
const backendURL = process.env.BACKEND_URL
if (!backendURL) {
throw new Error('BACKEND_URL env var is required')
}
module.exports = { module.exports = {
name: 'browser', name: 'browser',
@@ -18,11 +31,32 @@ module.exports = {
directTemplateLoading: false, directTemplateLoading: false,
skipCodeGeneration: false, skipCodeGeneration: false,
}), }),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
chunks: ['index'],
...htmlPluginOptions,
}),
new HtmlWebpackPlugin({
template: './src/terminal.html',
filename: 'terminal.html',
chunks: ['terminal'],
...htmlPluginOptions,
}),
{
apply: (compiler) => {
compiler.hooks.afterEmit.tap('AfterEmitPlugin', _ => {
fs.writeFileSync(path.join(outputPath, 'config.json'), JSON.stringify({
backendURL,
}))
})
},
},
], ],
output: { output: {
path: path.join(__dirname, 'build'), path: outputPath,
pathinfo: true, pathinfo: true,
publicPath: '/static/', publicPath: '/',
filename: '[name].js', filename: '[name].js',
chunkFilename: '[name].bundle.js', chunkFilename: '[name].bundle.js',
}, },

39
webpack.config.server.js Normal file

@@ -0,0 +1,39 @@
const baseConfig = require('./webpack.config.base.js')
const path = require('path')
const { AngularWebpackPlugin } = require('@ngtools/webpack')
const nodeExternals = require('webpack-node-externals')
const outputPath = path.join(__dirname, 'build-server')
module.exports = {
name: 'server',
target: 'node',
...baseConfig,
entry: {
// 'index.server': path.resolve(__dirname, 'src/index.server.ts'),
'server': path.resolve(__dirname, 'src/server.ts'),
},
resolve: {
...baseConfig.resolve,
mainFields: ['esm2015', 'module', 'main'],
},
plugins: [
...baseConfig.plugins,
new AngularWebpackPlugin({
entryModule: path.resolve(__dirname, 'src/app.server.module#AppServerModule'),
mainPath: path.resolve(__dirname, 'src/server.ts'),
tsconfig: 'tsconfig.json',
directTemplateLoading: false,
platform: 1,
skipCodeGeneration: false,
}),
],
output: {
// libraryTarget: 'commonjs',
path: outputPath,
pathinfo: true,
publicPath: '/',
filename: '[name].js',
chunkFilename: '[name].bundle.js',
},
}

@@ -1,32 +0,0 @@
const baseConfig = require('./webpack.base.config.js')
const path = require('path')
const { AngularWebpackPlugin } = require('@ngtools/webpack')
module.exports = {
name: 'server',
target: 'node',
...baseConfig,
entry: {
'index.server': path.resolve(__dirname, 'src/index.server.ts'),
'server': path.resolve(__dirname, 'src/server.ts'),
},
plugins: [
...baseConfig.plugins,
new AngularWebpackPlugin({
entryModule: path.resolve(__dirname, 'src/app/app.server.module`#AppServerModule'),
mainPath: path.resolve(__dirname, 'src/index.server.ts'),
tsconfig: 'tsconfig.json',
directTemplateLoading: false,
platform: 1,
skipCodeGeneration: false,
}),
],
output: {
libraryTarget: 'commonjs',
path: path.join(__dirname, 'build-server'),
pathinfo: true,
publicPath: '/static/',
filename: '[name].js',
chunkFilename: '[name].bundle.js',
},
}

605
yarn.lock

File diff suppressed because it is too large Load Diff