mirror of
https://github.com/Eugeny/tabby-web.git
synced 2025-06-08 05:29:52 +00:00
wip
This commit is contained in:
parent
d9d019e15f
commit
e11193b807
13
.editorconfig
Normal file
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
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
|
13
package.json
13
package.json
@ -4,10 +4,11 @@
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --progress",
|
||||
"watch": "DEV=1 webpack --progress --watch",
|
||||
"build:server": "webpack --progress -c webpack.server.config.js",
|
||||
"watch:server": "DEV=1 webpack --progress --watch -c webpack.server.config.js",
|
||||
"start": "./manage.py runserver"
|
||||
"watch": "BACKEND_URL=http://localhost:8001 DEV=1 webpack --progress --watch",
|
||||
"build:server": "webpack --progress -c webpack.config.server.js",
|
||||
"watch:server": "DEV=1 webpack --progress --watch -c webpack.config.server.js",
|
||||
"start": "./manage.py runserver 8001",
|
||||
"start:server": "PORT=8000 node build-server/server.js"
|
||||
},
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
@ -32,6 +33,8 @@
|
||||
"@ngtools/webpack": "^12.0.4",
|
||||
"@tabby-gang/to-string-loader": "^1.1.7-beta.1",
|
||||
"@types/node": "^11.9.5",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.4",
|
||||
"@typescript-eslint/parser": "^4.28.4",
|
||||
"apply-loader": "^2.0.0",
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"bootstrap": "^5.0.1",
|
||||
@ -39,6 +42,7 @@
|
||||
"core-js": "^3.14.0",
|
||||
"css-loader": "^2.1.0",
|
||||
"deepmerge": "^4.2.2",
|
||||
"eslint": "^7.31.0",
|
||||
"file-loader": "^1.1.11",
|
||||
"html-loader": "^2.1.2",
|
||||
"html-webpack-plugin": "^5.3.2",
|
||||
@ -63,6 +67,7 @@
|
||||
"webpack": "^5.38.1",
|
||||
"webpack-bundle-analyzer": "^4.4.2",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-node-externals": "^3.0.0",
|
||||
"zone.js": "^0.11.4"
|
||||
},
|
||||
"dependencies": {
|
||||
|
17
poetry.lock
generated
17
poetry.lock
generated
@ -237,6 +237,17 @@ sqlparse = ">=0.2.2"
|
||||
argon2 = ["argon2-cffi (>=19.1.0)"]
|
||||
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]]
|
||||
name = "django-rest-framework"
|
||||
version = "0.1.0"
|
||||
@ -852,7 +863,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "198155cefe870371fb615a4787e9117dd1f1b9ea25116df55741b1dcd4eb2c12"
|
||||
content-hash = "cc070c0414bb554395cbc518a5857a6f239ad17b1573c27012441cef86320b7a"
|
||||
|
||||
[metadata.files]
|
||||
appdirs = [
|
||||
@ -984,6 +995,10 @@ django = [
|
||||
{file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"},
|
||||
{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 = [
|
||||
{file = "django-rest-framework-0.1.0.tar.gz", hash = "sha256:47a8f496fa69e3b6bd79f68dd7a1527d907d6b77f009e9db7cf9bb21cc565e4a"},
|
||||
]
|
||||
|
@ -23,6 +23,7 @@ Twisted = "20.3.0"
|
||||
semver = "^2.13.0"
|
||||
requests = "^2.25.1"
|
||||
pyga = "^2.6.2"
|
||||
django-cors-headers = "^3.7.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
flake8 = "^3.9.2"
|
||||
|
@ -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`,
|
||||
|
@ -16,9 +16,9 @@ urlpatterns = [
|
||||
path('api/1/instance-info', api.InstanceInfoViewSet.as_view({'get': '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('', include(router.urls)),
|
||||
]
|
||||
|
@ -35,6 +35,7 @@ INSTALLED_APPS = [
|
||||
'channels',
|
||||
'rest_framework',
|
||||
'social_django',
|
||||
'corsheaders',
|
||||
'tabby.app',
|
||||
]
|
||||
|
||||
@ -46,6 +47,7 @@ MIDDLEWARE = [
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'tabby.middleware.TokenMiddleware',
|
||||
'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_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'
|
||||
|
||||
@ -180,9 +174,11 @@ LOGIN_REDIRECT_URL = '/app'
|
||||
APP_DIST_PATH = Path(os.getenv('APP_DIST_PATH', BASE_DIR / 'app-dist'))
|
||||
NPM_REGISTRY = os.getenv('NPM_REGISTRY', 'https://registry.npmjs.org').rstrip('/')
|
||||
|
||||
FRONTEND_URL = None
|
||||
GITHUB_ELIGIBLE_SPONSORSHIPS = None
|
||||
|
||||
for key in [
|
||||
'FRONTEND_URL',
|
||||
'SOCIAL_AUTH_GITHUB_KEY',
|
||||
'SOCIAL_AUTH_GITHUB_SECRET',
|
||||
'SOCIAL_AUTH_GITLAB_KEY',
|
||||
@ -229,3 +225,7 @@ if GITHUB_ELIGIBLE_SPONSORSHIPS:
|
||||
GITHUB_ELIGIBLE_SPONSORSHIPS = GITHUB_ELIGIBLE_SPONSORSHIPS.split(',')
|
||||
else:
|
||||
GITHUB_ELIGIBLE_SPONSORSHIPS = []
|
||||
|
||||
|
||||
if FRONTEND_URL:
|
||||
CORS_ALLOWED_ORIGINS = [FRONTEND_URL]
|
||||
|
@ -1,11 +1,14 @@
|
||||
from django.conf import settings
|
||||
from django.conf.urls.static import static
|
||||
from django.contrib import admin
|
||||
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 = [
|
||||
path('', include(urlpatterns)),
|
||||
path('', include(app_urlpatterns)),
|
||||
path('api/1/auth/social/', include('social_django.urls', namespace='social')),
|
||||
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 HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
|
||||
|
||||
const htmlPluginOptions = {
|
||||
hash: true,
|
||||
minify: false
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
mode: process.env.DEV ? 'development' : 'production',
|
||||
context: __dirname,
|
||||
@ -61,17 +55,5 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
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,33 +1,67 @@
|
||||
const baseConfig = require('./webpack.base.config.js')
|
||||
const baseConfig = require('./webpack.config.base.js')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { AngularWebpackPlugin } = require('@ngtools/webpack')
|
||||
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 = {
|
||||
name: 'browser',
|
||||
target: 'web',
|
||||
...baseConfig,
|
||||
entry: {
|
||||
name: 'browser',
|
||||
target: 'web',
|
||||
...baseConfig,
|
||||
entry: {
|
||||
index: path.resolve(__dirname, 'src/index.ts'),
|
||||
terminal: path.resolve(__dirname, 'src/terminal.ts'),
|
||||
},
|
||||
plugins: [
|
||||
},
|
||||
plugins: [
|
||||
...baseConfig.plugins,
|
||||
new AngularWebpackPlugin({
|
||||
tsconfig: 'tsconfig.json',
|
||||
directTemplateLoading: false,
|
||||
skipCodeGeneration: false,
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
path: path.join(__dirname, 'build'),
|
||||
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: {
|
||||
path: outputPath,
|
||||
pathinfo: true,
|
||||
publicPath: '/static/',
|
||||
publicPath: '/',
|
||||
filename: '[name].js',
|
||||
chunkFilename: '[name].bundle.js',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (process.env.BUNDLE_ANALYZER) {
|
||||
module.exports[0].plugins.push(new BundleAnalyzerPlugin())
|
||||
module.exports[0].plugins.push(new BundleAnalyzerPlugin())
|
||||
}
|
||||
|
39
webpack.config.server.js
Normal file
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',
|
||||
},
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user