mirror of
https://github.com/Eugeny/tabby-web.git
synced 2026-08-17 23:06:04 +01:00
init
This commit is contained in:
25
frontend/src/common/index.ts
Normal file
25
frontend/src/common/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/* eslint-disable @typescript-eslint/no-extraneous-class */
|
||||
import { ModuleWithProviders, NgModule } from '@angular/core'
|
||||
import { HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'
|
||||
import { BackendXsrfInterceptor, UniversalInterceptor } from './interceptor'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
HttpClientXsrfModule,
|
||||
],
|
||||
})
|
||||
export class CommonAppModule {
|
||||
static forRoot (): ModuleWithProviders<CommonAppModule> {
|
||||
return {
|
||||
ngModule: CommonAppModule,
|
||||
providers: [
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: UniversalInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: BackendXsrfInterceptor, multi: true },
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { LoginService } from './services/login.service'
|
||||
export { ConfigService } from './services/config.service'
|
||||
export { CommonService } from './services/common.service'
|
||||
38
frontend/src/common/interceptor.ts
Normal file
38
frontend/src/common/interceptor.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpXsrfTokenExtractor } from '@angular/common/http'
|
||||
import { Observable } from 'rxjs'
|
||||
import { CommonService } from './services/common.service'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
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('/')) {
|
||||
const endpoint = request.url
|
||||
request = request.clone({
|
||||
url: `${this.commonService.backendURL}${endpoint}`,
|
||||
withCredentials: true,
|
||||
})
|
||||
}
|
||||
return next.handle(request)
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class BackendXsrfInterceptor implements HttpInterceptor {
|
||||
constructor (
|
||||
private commonService: CommonService,
|
||||
private tokenExtractor: HttpXsrfTokenExtractor,
|
||||
) { }
|
||||
|
||||
intercept (req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||
if (this.commonService.backendURL && req.url.startsWith(this.commonService.backendURL)) {
|
||||
const token = this.tokenExtractor.getToken()
|
||||
if (token !== null) {
|
||||
req = req.clone({ setHeaders: { 'X-XSRF-TOKEN': token } })
|
||||
}
|
||||
}
|
||||
return next.handle(req)
|
||||
}
|
||||
}
|
||||
26
frontend/src/common/services/common.service.ts
Normal file
26
frontend/src/common/services/common.service.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Inject, Injectable, Optional } from '@angular/core'
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class CommonService {
|
||||
backendURL: string
|
||||
|
||||
constructor (@Inject('BACKEND_URL') @Optional() ssrBackendURL: string) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||
const tag = document.querySelector('meta[property=x-tabby-web-backend-url]')! as HTMLMetaElement
|
||||
if (ssrBackendURL) {
|
||||
this.backendURL = ssrBackendURL
|
||||
tag.content = ssrBackendURL
|
||||
} else {
|
||||
if (tag.content && !tag.content.startsWith('{{')) {
|
||||
this.backendURL = tag.content
|
||||
} else {
|
||||
this.backendURL = ''
|
||||
}
|
||||
}
|
||||
|
||||
console.log(this.backendURL)
|
||||
if (this.backendURL.endsWith('/')) {
|
||||
this.backendURL = this.backendURL.slice(0, -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
122
frontend/src/common/services/config.service.ts
Normal file
122
frontend/src/common/services/config.service.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
import * as semverCompare from 'semver/functions/compare-loose'
|
||||
import { AsyncSubject, Subject } from 'rxjs'
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Config, User, Version } from '../../api'
|
||||
import { LoginService } from './login.service'
|
||||
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class ConfigService {
|
||||
activeConfig$ = new Subject<Config>()
|
||||
activeVersion$ = new Subject<Version>()
|
||||
user: User
|
||||
|
||||
configs: Config[] = []
|
||||
versions: Version[] = []
|
||||
ready$ = new AsyncSubject<void>()
|
||||
|
||||
get activeConfig (): Config | null { return this._activeConfig }
|
||||
get activeVersion (): Version | null { return this._activeVersion }
|
||||
|
||||
private _activeConfig: Config|null = null
|
||||
private _activeVersion: Version|null = null
|
||||
|
||||
constructor (
|
||||
private http: HttpClient,
|
||||
private loginService: LoginService,
|
||||
) {
|
||||
this.init()
|
||||
}
|
||||
|
||||
async updateUser (): Promise<void> {
|
||||
if (!this.loginService.user) {
|
||||
return
|
||||
}
|
||||
await this.http.put('/api/1/user', this.user).toPromise()
|
||||
}
|
||||
|
||||
async createNewConfig (): Promise<Config> {
|
||||
const configData = {
|
||||
content: '{}',
|
||||
last_used_with_version: this._activeVersion?.version ?? this.getLatestStableVersion().version,
|
||||
}
|
||||
if (!this.loginService.user) {
|
||||
const config = {
|
||||
id: Date.now(),
|
||||
name: `Temporary config at ${new Date()}`,
|
||||
created_at: new Date(),
|
||||
modified_at: new Date(),
|
||||
...configData,
|
||||
}
|
||||
this.configs.push(config)
|
||||
return config
|
||||
}
|
||||
const config = (await this.http.post('/api/1/configs', configData).toPromise()) as Config
|
||||
this.configs.push(config)
|
||||
return config
|
||||
}
|
||||
|
||||
getLatestStableVersion (): Version {
|
||||
return this.versions[0]
|
||||
}
|
||||
|
||||
async duplicateActiveConfig (): Promise<void> {
|
||||
let copy: any = { ...this._activeConfig, id: undefined }
|
||||
if (this.loginService.user) {
|
||||
copy = (await this.http.post('/api/1/configs', copy).toPromise()) as Config
|
||||
}
|
||||
this.configs.push(copy)
|
||||
}
|
||||
|
||||
async selectVersion (version: Version): Promise<void> {
|
||||
this._activeVersion = version
|
||||
this.activeVersion$.next(version)
|
||||
}
|
||||
|
||||
async selectConfig (config: Config): Promise<void> {
|
||||
let matchingVersion = this.versions.find(x => x.version === config.last_used_with_version)
|
||||
if (!matchingVersion) {
|
||||
// TODO ask to upgrade
|
||||
matchingVersion = this.versions[0]
|
||||
}
|
||||
|
||||
this._activeConfig = config
|
||||
this.activeConfig$.next(config)
|
||||
this.selectVersion(matchingVersion)
|
||||
if (this.loginService.user) {
|
||||
this.loginService.user.active_config = config.id
|
||||
await this.loginService.updateUser()
|
||||
}
|
||||
}
|
||||
|
||||
async selectDefaultConfig (): Promise<void> {
|
||||
await this.ready$.toPromise()
|
||||
await this.loginService.ready$.toPromise()
|
||||
this.selectConfig(this.configs.find(c => c.id === this.loginService.user?.active_config) ?? this.configs[0])
|
||||
}
|
||||
|
||||
async deleteConfig (config: Config): Promise<void> {
|
||||
if (this.loginService.user) {
|
||||
await this.http.delete(`/api/1/configs/${config.id}`).toPromise()
|
||||
}
|
||||
this.configs = this.configs.filter(x => x.id !== config.id)
|
||||
}
|
||||
|
||||
private async init () {
|
||||
await this.loginService.ready$.toPromise()
|
||||
|
||||
if (this.loginService.user) {
|
||||
this.configs = (await this.http.get('/api/1/configs').toPromise()) as Config[]
|
||||
}
|
||||
this.versions = (await this.http.get('/api/1/versions').toPromise()) as Version[]
|
||||
this.versions.sort((a, b) => -semverCompare(a.version, b.version))
|
||||
|
||||
if (!this.configs.length) {
|
||||
await this.createNewConfig()
|
||||
}
|
||||
|
||||
this.ready$.next()
|
||||
this.ready$.complete()
|
||||
}
|
||||
}
|
||||
33
frontend/src/common/services/login.service.ts
Normal file
33
frontend/src/common/services/login.service.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { AsyncSubject } from 'rxjs'
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { User } from '../../api'
|
||||
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class LoginService {
|
||||
user: User | null
|
||||
ready$ = new AsyncSubject<void>()
|
||||
|
||||
constructor (private http: HttpClient) {
|
||||
this.init()
|
||||
}
|
||||
|
||||
async updateUser (): Promise<void> {
|
||||
if (!this.user) {
|
||||
return
|
||||
}
|
||||
await this.http.put('/api/1/user', this.user).toPromise()
|
||||
}
|
||||
|
||||
private async init () {
|
||||
try {
|
||||
this.user = (await this.http.get('/api/1/user').toPromise()) as User
|
||||
} catch {
|
||||
this.user = null
|
||||
}
|
||||
|
||||
this.ready$.next()
|
||||
this.ready$.complete()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user