mirror of
https://github.com/Eugeny/tabby-web.git
synced 2025-06-09 05:59:53 +00:00
.
This commit is contained in:
parent
b3bb9fdd40
commit
0484b4c8d7
@ -131,7 +131,7 @@ class UserSerializer(ModelSerializer):
|
||||
read_only_fields = ('id', 'username')
|
||||
|
||||
def get_is_pro(self, obj):
|
||||
return check_is_sponsor_cached(obj) or obj.force_pro
|
||||
return obj.force_pro or not settings.GITHUB_ELIGIBLE_SPONSORSHIPS or check_is_sponsor_cached(obj)
|
||||
|
||||
def get_is_sponsor(self, obj):
|
||||
return check_is_sponsor_cached(obj)
|
||||
|
@ -8,6 +8,8 @@ from urllib.parse import urlparse
|
||||
|
||||
class IndexView(APIView):
|
||||
def get(self, request, format=None):
|
||||
if settings.FRONTEND_URL:
|
||||
return HttpResponseRedirect(settings.FRONTEND_URL)
|
||||
return static.serve(request, 'index.html', document_root=str(settings.FRONTEND_BUILD_DIR))
|
||||
|
||||
|
||||
|
@ -100,6 +100,12 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
|
||||
AUTH_USER_MODEL = 'app.User'
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_RENDERER_CLASSES': (
|
||||
'rest_framework.renderers.JSONRenderer',
|
||||
)
|
||||
}
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.2/topics/i18n/
|
||||
|
||||
@ -174,10 +180,12 @@ APP_DIST_STORAGE = os.getenv('APP_DIST_STORAGE', 'file://' + str(BASE_DIR / 'app
|
||||
NPM_REGISTRY = os.getenv('NPM_REGISTRY', 'https://registry.npmjs.org').rstrip('/')
|
||||
|
||||
FRONTEND_URL = None
|
||||
BACKEND_URL = None
|
||||
GITHUB_ELIGIBLE_SPONSORSHIPS = None
|
||||
|
||||
for key in [
|
||||
'FRONTEND_URL',
|
||||
'BACKEND_URL',
|
||||
'SOCIAL_AUTH_GITHUB_KEY',
|
||||
'SOCIAL_AUTH_GITHUB_SECRET',
|
||||
'SOCIAL_AUTH_GITLAB_KEY',
|
||||
@ -243,6 +251,8 @@ if FRONTEND_URL:
|
||||
]
|
||||
frontend_domain = urlparse(FRONTEND_URL).hostname
|
||||
CSRF_TRUSTED_ORIGINS = [frontend_domain]
|
||||
if BACKEND_URL:
|
||||
CSRF_TRUSTED_ORIGINS.append(urlparse(BACKEND_URL).hostname)
|
||||
SESSION_COOKIE_DOMAIN = frontend_domain
|
||||
CSRF_COOKIE_DOMAIN = frontend_domain
|
||||
|
||||
|
@ -4,6 +4,7 @@ import { Resolve } from '@angular/router'
|
||||
import { Observable } from 'rxjs'
|
||||
|
||||
export interface User {
|
||||
id: number
|
||||
active_config: number
|
||||
active_version: string
|
||||
custom_connection_gateway: string|null
|
||||
@ -11,6 +12,7 @@ export interface User {
|
||||
config_sync_token: string
|
||||
github_username: string
|
||||
is_pro: boolean
|
||||
is_sponsor: boolean
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
|
@ -18,6 +18,8 @@ import { ConfigModalComponent } from './components/configModal.component'
|
||||
import { SettingsModalComponent } from './components/settingsModal.component'
|
||||
import { HomeComponent } from './components/home.component'
|
||||
import { LoginComponent } from './components/login.component'
|
||||
import { ConnectionListComponent } from './components/connectionList.component'
|
||||
import { UpgradeModalComponent } from './components/upgradeModal.component'
|
||||
import { InstanceInfoResolver } from './api'
|
||||
|
||||
import '@fortawesome/fontawesome-svg-core/styles.css'
|
||||
@ -75,6 +77,8 @@ const ROUTES = [
|
||||
LoginComponent,
|
||||
ConfigModalComponent,
|
||||
SettingsModalComponent,
|
||||
ConnectionListComponent,
|
||||
UpgradeModalComponent,
|
||||
],
|
||||
bootstrap: [AppComponent],
|
||||
})
|
||||
|
8
frontend/src/components/connectionList.component.pug
Normal file
8
frontend/src/components/connectionList.component.pug
Normal file
@ -0,0 +1,8 @@
|
||||
.list-group.list-group-light
|
||||
.list-group-item.d-flex(*ngFor='let socket of appConnector.sockets')
|
||||
fa-icon.text-success.me-2([icon]='_circleIcon', [fixedWidth]='true')
|
||||
.me-auto
|
||||
div {{socket.options.host}}:{{socket.options.port}}
|
||||
.text-muted via {{socket.url}}
|
||||
button.btn.btn-link((click)='closeSocket(socket)')
|
||||
fa-icon([icon]='_closeIcon', [fixedWidth]='true')
|
20
frontend/src/components/connectionList.component.ts
Normal file
20
frontend/src/components/connectionList.component.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { AppConnectorService, SocketProxy } from '../services/appConnector.service'
|
||||
import { faCircle, faTimes } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
@Component({
|
||||
selector: 'connection-list',
|
||||
templateUrl: './connectionList.component.pug',
|
||||
})
|
||||
export class ConnectionListComponent {
|
||||
_circleIcon = faCircle
|
||||
_closeIcon = faTimes
|
||||
|
||||
constructor (
|
||||
public appConnector: AppConnectorService,
|
||||
) { }
|
||||
|
||||
closeSocket (socket: SocketProxy) {
|
||||
socket.close(new Error('Connection closed by user'))
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
main(*ngIf='ready && loggedIn')
|
||||
.login-view(*ngIf='ready && !loggedIn')
|
||||
.login-view(*ngIf='ready')
|
||||
.buttons
|
||||
a.btn(
|
||||
*ngFor='let provider of providers',
|
||||
|
@ -4,7 +4,7 @@
|
||||
.modal-body
|
||||
.mb-3
|
||||
h5 GitHub account
|
||||
a.btn.btn-info(href='/api/1/auth/social/login/github', *ngIf='!user.github_username')
|
||||
a.btn.btn-info(href='{{commonService.backendURL}}/api/1/auth/social/login/github', *ngIf='!user.github_username')
|
||||
fa-icon([icon]='_githubIcon', [fixedWidth]='true')
|
||||
span Connect a GitHub account
|
||||
.alert.alert-success.d-flex(*ngIf='user.github_username')
|
||||
@ -61,13 +61,12 @@
|
||||
)
|
||||
label Gateway authentication token
|
||||
|
||||
div(*ngIf='appConnector.sockets.length')
|
||||
.mb-3.mt-4(*ngIf='appConnector.sockets.length')
|
||||
h5 Active connections
|
||||
.list-group.list-group-flush
|
||||
.list-group-item(*ngFor='let socket of appConnector.sockets')
|
||||
div {{socket.options.host}}:{{socket.options.port}}
|
||||
.text-muted via {{socket.url}}
|
||||
connection-list
|
||||
|
||||
.modal-footer
|
||||
.text-muted Account ID: {{user.id}}
|
||||
.ms-auto
|
||||
button.btn.btn-primary((click)='apply()') Apply
|
||||
button.btn.btn-secondary((click)='cancel()') Cancel
|
||||
|
@ -4,6 +4,7 @@ import { LoginService } from '../services/login.service'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { User } from '../api'
|
||||
import { AppConnectorService } from '../services/appConnector.service'
|
||||
import { CommonService } from '../services/common.service'
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faCheck, faCopy } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
@ -21,6 +22,7 @@ export class SettingsModalComponent {
|
||||
|
||||
constructor (
|
||||
public appConnector: AppConnectorService,
|
||||
public commonService: CommonService,
|
||||
private modalInstance: NgbActiveModal,
|
||||
private loginService: LoginService,
|
||||
) {
|
||||
|
28
frontend/src/components/upgradeModal.component.pug
Normal file
28
frontend/src/components/upgradeModal.component.pug
Normal file
@ -0,0 +1,28 @@
|
||||
.modal-header
|
||||
h1.modal-title Hey!
|
||||
|
||||
.modal-body
|
||||
h4 It looks like you're enjoying Tabby a lot!
|
||||
|
||||
p Tabby Web has a limit of {{appConnector.connectionLimit}} simultaneous connections due to the fact that I have to pay for hosting and traffic out of my own pocket.
|
||||
|
||||
p #[strong You can have unlimited parallel connections] if you support Tabby on GitHub with #[code $3]/month or more. It's cancellable anytime, there are no hidden costs and it helps me pay my bills.
|
||||
|
||||
a.btn.btn-primary.btn-lg.d-block.mb-3(href='https://github.com/sponsors/Eugeny', target='_blank')
|
||||
fa-icon.me-2([icon]='_loveIcon')
|
||||
span Support Tabby on GitHub
|
||||
|
||||
button.btn.btn-warning.d-block.w-100((click)='skipOnce()', *ngIf='canSkip')
|
||||
fa-icon.me-2([icon]='_giftIcon')
|
||||
span Skip - just this one time
|
||||
|
||||
p.mt-3 If you work in education, have already supported me on Ko-fi before, or your country isn't supported on GitHub Sponsors, just #[a(href='mailto:e@ajenti.org?subject=Help with Tabby Pro') let me know] and I'll hook you up.
|
||||
|
||||
.mb-3(*ngIf='!loginService.user.github_username')
|
||||
a.btn.btn-info(href='{{commonService.backendURL}}/api/1/auth/social/login/github')
|
||||
fa-icon([icon]='_githubIcon', [fixedWidth]='true')
|
||||
span Connect your GitHub account to link your sponsorship
|
||||
|
||||
.mt-4
|
||||
p You can also kill any active connection from the list below to free up a slot.
|
||||
connection-list
|
36
frontend/src/components/upgradeModal.component.ts
Normal file
36
frontend/src/components/upgradeModal.component.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faGift, faHeart } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
import { LoginService } from '../services/login.service'
|
||||
import { AppConnectorService } from '../services/appConnector.service'
|
||||
import { CommonService } from '../services/common.service'
|
||||
import { User } from '../api'
|
||||
|
||||
@Component({
|
||||
selector: 'upgrade-modal',
|
||||
templateUrl: './upgradeModal.component.pug',
|
||||
})
|
||||
export class UpgradeModalComponent {
|
||||
user: User
|
||||
_githubIcon = faGithub
|
||||
_loveIcon = faHeart
|
||||
_giftIcon = faGift
|
||||
canSkip = false
|
||||
|
||||
constructor (
|
||||
public appConnector: AppConnectorService,
|
||||
public commonService: CommonService,
|
||||
public loginService: LoginService,
|
||||
private modalInstance: NgbActiveModal,
|
||||
) {
|
||||
this.canSkip = !window.localStorage['upgrade-modal-skipped']
|
||||
}
|
||||
|
||||
skipOnce () {
|
||||
window.localStorage['upgrade-modal-skipped'] = true
|
||||
window.sessionStorage['upgrade-skip-active'] = true
|
||||
this.modalInstance.close(true)
|
||||
}
|
||||
}
|
@ -2,7 +2,9 @@ import { Buffer } from 'buffer'
|
||||
import { Subject } from 'rxjs'
|
||||
import { debounceTime } from 'rxjs/operators'
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { Injectable, Injector } from '@angular/core'
|
||||
import { Injectable, Injector, NgZone } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { UpgradeModalComponent } from '../components/upgradeModal.component'
|
||||
import { Config, Gateway, Version } from '../api'
|
||||
import { LoginService } from './login.service'
|
||||
import { CommonService } from './common.service'
|
||||
@ -24,16 +26,31 @@ export class SocketProxy {
|
||||
|
||||
private appConnector: AppConnectorService
|
||||
private loginService: LoginService
|
||||
private ngbModal: NgbModal
|
||||
private zone: NgZone
|
||||
|
||||
constructor (
|
||||
injector: Injector,
|
||||
) {
|
||||
this.appConnector = injector.get(AppConnectorService)
|
||||
this.loginService = injector.get(LoginService)
|
||||
this.ngbModal = injector.get(NgbModal)
|
||||
this.zone = injector.get(NgZone)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
async connect (options: any): Promise<void> {
|
||||
if (!this.loginService.user.is_pro && this.appConnector.sockets.length > this.appConnector.connectionLimit && !window.sessionStorage['upgrade-skip-active']) {
|
||||
let skipped = false
|
||||
try {
|
||||
skipped = await this.zone.run(() => this.ngbModal.open(UpgradeModalComponent)).result
|
||||
} catch { }
|
||||
if (!skipped) {
|
||||
this.close(new Error('Connection limit reached'))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.options = options
|
||||
this.url = this.loginService.user.custom_connection_gateway
|
||||
this.authToken = this.loginService.user.custom_connection_gateway_token
|
||||
@ -127,12 +144,14 @@ export class AppConnectorService {
|
||||
private configUpdate = new Subject<string>()
|
||||
private config: Config
|
||||
private version: Version
|
||||
connectionLimit = 3
|
||||
sockets: SocketProxy[] = []
|
||||
|
||||
constructor (
|
||||
private injector: Injector,
|
||||
private http: HttpClient,
|
||||
private commonService: CommonService,
|
||||
private zone: NgZone,
|
||||
) {
|
||||
|
||||
this.configUpdate.pipe(debounceTime(1000)).subscribe(async content => {
|
||||
@ -180,12 +199,14 @@ export class AppConnectorService {
|
||||
}
|
||||
|
||||
createSocket () {
|
||||
const socket = new SocketProxy(this.injector)
|
||||
this.sockets.push(socket)
|
||||
socket.close$.subscribe(() => {
|
||||
this.sockets = this.sockets.filter(x => x !== socket)
|
||||
return this.zone.run(() => {
|
||||
const socket = new SocketProxy(this.injector)
|
||||
this.sockets.push(socket)
|
||||
socket.close$.subscribe(() => {
|
||||
this.sockets = this.sockets.filter(x => x !== socket)
|
||||
})
|
||||
return socket
|
||||
})
|
||||
return socket
|
||||
}
|
||||
|
||||
async chooseConnectionGateway (): Promise<Gateway> {
|
||||
|
Loading…
x
Reference in New Issue
Block a user