This commit is contained in:
Eugene Pankov 2021-07-28 22:14:34 +02:00
parent b3bb9fdd40
commit 0484b4c8d7
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
13 changed files with 146 additions and 15 deletions

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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 {

View File

@ -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],
})

View 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')

View 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'))
}
}

View File

@ -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',

View File

@ -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

View File

@ -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,
) {

View 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

View 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)
}
}

View File

@ -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> {