mirror of
https://github.com/Eugeny/tabby-web.git
synced 2025-06-09 14:09:57 +00:00
wip
This commit is contained in:
parent
663615fe06
commit
0689c984ff
74
poetry.lock
generated
74
poetry.lock
generated
@ -210,6 +210,41 @@ mccabe = ">=0.6.0,<0.7.0"
|
|||||||
pycodestyle = ">=2.7.0,<2.8.0"
|
pycodestyle = ">=2.7.0,<2.8.0"
|
||||||
pyflakes = ">=2.3.0,<2.4.0"
|
pyflakes = ">=2.3.0,<2.4.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gql"
|
||||||
|
version = "2.0.0"
|
||||||
|
description = "GraphQL client for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
graphql-core = ">=2.3.2,<3"
|
||||||
|
promise = ">=2.3,<3"
|
||||||
|
requests = ">=2.12,<3"
|
||||||
|
six = ">=1.10.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["flake8 (==3.8.1)", "isort (==4.3.21)", "black (==19.10b0)", "mypy (==0.770)", "check-manifest (>=0.42,<1)", "pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "coveralls (==2.0.0)"]
|
||||||
|
test = ["pytest (==5.4.2)", "pytest-asyncio (==0.11.0)", "pytest-cov (==2.8.1)", "mock (==4.0.2)", "vcrpy (==4.0.2)", "coveralls (==2.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "graphql-core"
|
||||||
|
version = "2.3.2"
|
||||||
|
description = "GraphQL implementation for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
promise = ">=2.3,<3"
|
||||||
|
rx = ">=1.6,<2"
|
||||||
|
six = ">=1.10.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
gevent = ["gevent (>=1.1)"]
|
||||||
|
test = ["six (==1.14.0)", "pyannotate (==1.2.0)", "pytest (==4.6.10)", "pytest-django (==3.9.0)", "pytest-cov (==2.8.1)", "coveralls (==1.11.1)", "cython (==0.29.17)", "gevent (==1.5.0)", "pytest-benchmark (==3.2.3)", "pytest-mock (==2.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyperlink"
|
name = "hyperlink"
|
||||||
version = "21.0.0"
|
version = "21.0.0"
|
||||||
@ -277,6 +312,20 @@ rsa = ["cryptography (>=3.0.0,<4)"]
|
|||||||
signals = ["blinker (>=1.4.0)"]
|
signals = ["blinker (>=1.4.0)"]
|
||||||
signedtoken = ["cryptography (>=3.0.0,<4)", "pyjwt (>=2.0.0,<3)"]
|
signedtoken = ["cryptography (>=3.0.0,<4)", "pyjwt (>=2.0.0,<3)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "promise"
|
||||||
|
version = "2.3"
|
||||||
|
description = "Promises/A+ implementation for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
test = ["pytest (>=2.7.3)", "pytest-cov", "coveralls", "futures", "pytest-benchmark", "mock"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyasn1"
|
name = "pyasn1"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
@ -411,6 +460,14 @@ requests = ">=2.0.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rx"
|
||||||
|
version = "1.6.1"
|
||||||
|
description = "Reactive Extensions (Rx) for Python"
|
||||||
|
category = "main"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "service-identity"
|
name = "service-identity"
|
||||||
version = "21.1.0"
|
version = "21.1.0"
|
||||||
@ -596,7 +653,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 = "8e1e4f30b4d5f3cba3dc4b594a872f149b9aa3eeb6d3b5b721f5847c6aa2fd84"
|
content-hash = "7ddd4096097eb58dc20601d3334a56507c893607ff156155070c4c865108c563"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
asgiref = [
|
asgiref = [
|
||||||
@ -715,6 +772,14 @@ flake8 = [
|
|||||||
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
|
{file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"},
|
||||||
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
|
{file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"},
|
||||||
]
|
]
|
||||||
|
gql = [
|
||||||
|
{file = "gql-2.0.0-py2.py3-none-any.whl", hash = "sha256:35032ddd4bfe6b8f3169f806b022168932385d751eacc5c5f7122e0b3f4d6b88"},
|
||||||
|
{file = "gql-2.0.0.tar.gz", hash = "sha256:fe8d3a08047f77362ddfcfddba7cae377da2dd66f5e61c59820419c9283d4fb5"},
|
||||||
|
]
|
||||||
|
graphql-core = [
|
||||||
|
{file = "graphql-core-2.3.2.tar.gz", hash = "sha256:aac46a9ac524c9855910c14c48fc5d60474def7f99fd10245e76608eba7af746"},
|
||||||
|
{file = "graphql_core-2.3.2-py2.py3-none-any.whl", hash = "sha256:44c9bac4514e5e30c5a595fac8e3c76c1975cae14db215e8174c7fe995825bad"},
|
||||||
|
]
|
||||||
hyperlink = [
|
hyperlink = [
|
||||||
{file = "hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"},
|
{file = "hyperlink-21.0.0-py2.py3-none-any.whl", hash = "sha256:e6b14c37ecb73e89c77d78cdb4c2cc8f3fb59a885c5b3f819ff4ed80f25af1b4"},
|
||||||
{file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"},
|
{file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"},
|
||||||
@ -739,6 +804,9 @@ oauthlib = [
|
|||||||
{file = "oauthlib-3.1.1-py2.py3-none-any.whl", hash = "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc"},
|
{file = "oauthlib-3.1.1-py2.py3-none-any.whl", hash = "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc"},
|
||||||
{file = "oauthlib-3.1.1.tar.gz", hash = "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"},
|
{file = "oauthlib-3.1.1.tar.gz", hash = "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"},
|
||||||
]
|
]
|
||||||
|
promise = [
|
||||||
|
{file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"},
|
||||||
|
]
|
||||||
pyasn1 = [
|
pyasn1 = [
|
||||||
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
|
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
|
||||||
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
|
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
|
||||||
@ -810,6 +878,10 @@ requests-oauthlib = [
|
|||||||
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
|
{file = "requests_oauthlib-1.3.0-py2.py3-none-any.whl", hash = "sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d"},
|
||||||
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
|
{file = "requests_oauthlib-1.3.0-py3.7.egg", hash = "sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc"},
|
||||||
]
|
]
|
||||||
|
rx = [
|
||||||
|
{file = "Rx-1.6.1-py2.py3-none-any.whl", hash = "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"},
|
||||||
|
{file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"},
|
||||||
|
]
|
||||||
service-identity = [
|
service-identity = [
|
||||||
{file = "service-identity-21.1.0.tar.gz", hash = "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34"},
|
{file = "service-identity-21.1.0.tar.gz", hash = "sha256:6e6c6086ca271dc11b033d17c3a8bea9f24ebff920c587da090afc9519419d34"},
|
||||||
{file = "service_identity-21.1.0-py2.py3-none-any.whl", hash = "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db"},
|
{file = "service_identity-21.1.0-py2.py3-none-any.whl", hash = "sha256:f0b0caac3d40627c3c04d7a51b6e06721857a0e10a8775f2d1d7e72901b3a7db"},
|
||||||
|
@ -14,6 +14,7 @@ djangorestframework-dataclasses = "^0.9"
|
|||||||
social-auth-app-django = "^4.0.0"
|
social-auth-app-django = "^4.0.0"
|
||||||
python-dotenv = "^0.17.1"
|
python-dotenv = "^0.17.1"
|
||||||
websockets = "^9.1"
|
websockets = "^9.1"
|
||||||
|
gql = "^2.0.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
flake8 = "^3.9.2"
|
flake8 = "^3.9.2"
|
||||||
|
19
src/api.ts
Normal file
19
src/api.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export interface User {
|
||||||
|
active_config: number
|
||||||
|
active_version: string
|
||||||
|
custom_connection_gateway: string|null
|
||||||
|
custom_connection_gateway_token: string|null
|
||||||
|
is_pro: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Config {
|
||||||
|
id: number
|
||||||
|
content: string
|
||||||
|
last_used_with_version: string
|
||||||
|
created_at: Date
|
||||||
|
modified_at: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Version {
|
||||||
|
version: string
|
||||||
|
}
|
@ -1,24 +1,33 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core'
|
||||||
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'
|
import { NgbDropdownModule, NgbModalModule } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser'
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
||||||
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
|
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
|
||||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
|
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
|
||||||
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 { SettingsModalComponent } from './components/settingsModal.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
HttpClientXsrfModule,
|
HttpClientXsrfModule,
|
||||||
NgbDropdownModule,
|
NgbDropdownModule,
|
||||||
|
NgbModalModule,
|
||||||
FontAwesomeModule,
|
FontAwesomeModule,
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
AppComponent,
|
AppComponent,
|
||||||
MainComponent,
|
MainComponent,
|
||||||
|
ConfigModalComponent,
|
||||||
|
SettingsModalComponent,
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
})
|
})
|
||||||
|
@ -21,7 +21,9 @@ export class AppComponent {
|
|||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private loginService: LoginService,
|
private loginService: LoginService,
|
||||||
) { }
|
) {
|
||||||
|
this.providers = [this.providers[0]] // only keep GH for now
|
||||||
|
}
|
||||||
|
|
||||||
async ngOnInit () {
|
async ngOnInit () {
|
||||||
await this.loginService.ready$.toPromise()
|
await this.loginService.ready$.toPromise()
|
||||||
|
40
src/components/configModal.component.pug
Normal file
40
src/components/configModal.component.pug
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
.modal-body
|
||||||
|
.header(*ngIf='configService.activeConfig')
|
||||||
|
.d-flex.align-items-center.py-2.px-4
|
||||||
|
.me-auto
|
||||||
|
label Active config
|
||||||
|
.title {{configService.activeConfig.modified_at}}
|
||||||
|
|
||||||
|
button.btn.btn-semi.me-2((click)='configService.duplicateConfig()')
|
||||||
|
fa-icon([icon]='_copyIcon', [fixedWidth]='true')
|
||||||
|
|
||||||
|
button.btn.btn-semi((click)='deleteConfig()')
|
||||||
|
fa-icon([icon]='_deleteIcon', [fixedWidth]='true')
|
||||||
|
|
||||||
|
.d-flex.align-items-center.py-2.px-4(*ngIf='configService.activeVersion')
|
||||||
|
.me-auto App version:
|
||||||
|
div(ngbDropdown)
|
||||||
|
button.btn.btn-semi(ngbDropdownToggle) {{configService.activeVersion.version}}
|
||||||
|
div(ngbDropdownMenu)
|
||||||
|
a(
|
||||||
|
*ngFor='let version of versions',
|
||||||
|
ngbDropdownItem,
|
||||||
|
[class.active]='version == configService.activeVersion',
|
||||||
|
(click)='selectVersion(version)'
|
||||||
|
) {{version.version}}
|
||||||
|
|
||||||
|
div(*ngIf='configService.configs.length > 1')
|
||||||
|
.dropdown-header All configs
|
||||||
|
|
||||||
|
ng-container(*ngFor='let config of configService.configs')
|
||||||
|
a(
|
||||||
|
*ngIf='config !== configService.activeConfig',
|
||||||
|
ngbDropdownItem,
|
||||||
|
(click)='selectConfig(config)',
|
||||||
|
href='#'
|
||||||
|
) Config modified at {{config.modified_at}}
|
||||||
|
|
||||||
|
.p-3
|
||||||
|
button.btn.btn-semi.w-100((click)='configService.createNewConfig()')
|
||||||
|
fa-icon([icon]='_addIcon', [fixedWidth]='true')
|
||||||
|
span New config
|
29
src/components/configModal.component.ts
Normal file
29
src/components/configModal.component.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { AppConnectorService } from '../services/appConnector.service'
|
||||||
|
import { ConfigService } from '../services/config.service'
|
||||||
|
import { faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'config-modal',
|
||||||
|
templateUrl: './configModal.component.pug',
|
||||||
|
// styleUrls: ['./settingsModal.component.scss'],
|
||||||
|
})
|
||||||
|
export class ConfigModalComponent {
|
||||||
|
_addIcon = faPlus
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private modalInstance: NgbActiveModal,
|
||||||
|
public appConnector: AppConnectorService,
|
||||||
|
public configService: ConfigService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit () {
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel () {
|
||||||
|
this.modalInstance.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,66 +1,14 @@
|
|||||||
.sidebar
|
.sidebar
|
||||||
img.logo(src='{{_logo}}')
|
img.logo(src='{{_logo}}')
|
||||||
|
|
||||||
div(ngbDropdown, placement='bottom-right')
|
button.btn((click)='openConfig()')
|
||||||
button.btn(ngbDropdownToggle)
|
fa-icon([icon]='_cogIcon', [fixedWidth]='true')
|
||||||
fa-icon([icon]='_cogIcon', [fixedWidth]='true')
|
|
||||||
|
|
||||||
.config-menu(ngbDropdownMenu)
|
button.btn((click)='openSettings()')
|
||||||
.header(*ngIf='getActiveConfig()')
|
fa-icon([icon]='_settingsIcon', [fixedWidth]='true')
|
||||||
.d-flex.align-items-center.py-2.px-4
|
|
||||||
.me-auto
|
|
||||||
label Active config
|
|
||||||
.title {{getActiveConfig().modified_at}}
|
|
||||||
|
|
||||||
button.btn.btn-semi.me-2((click)='duplicateConfig()')
|
button.btn.mt-auto((click)='logout()')
|
||||||
fa-icon([icon]='_copyIcon', [fixedWidth]='true')
|
fa-icon([icon]='_logoutIcon', [fixedWidth]='true')
|
||||||
|
|
||||||
button.btn.btn-semi((click)='deleteConfig()')
|
.terminal([hidden]='!showApp')
|
||||||
fa-icon([icon]='_deleteIcon', [fixedWidth]='true')
|
|
||||||
|
|
||||||
.d-flex.align-items-center.py-2.px-4(*ngIf='activeVersion')
|
|
||||||
.me-auto App version:
|
|
||||||
div(ngbDropdown)
|
|
||||||
button.btn.btn-semi(ngbDropdownToggle) {{activeVersion.version}}
|
|
||||||
div(ngbDropdownMenu)
|
|
||||||
a(
|
|
||||||
*ngFor='let version of versions',
|
|
||||||
ngbDropdownItem,
|
|
||||||
[class.active]='version == activeVersion',
|
|
||||||
(click)='selectVersion(version)'
|
|
||||||
) {{version.version}}
|
|
||||||
|
|
||||||
div(*ngIf='configs.length > 1')
|
|
||||||
.dropdown-header All configs
|
|
||||||
|
|
||||||
ng-container(*ngFor='let config of configs')
|
|
||||||
a(
|
|
||||||
*ngIf='config !== getActiveConfig()',
|
|
||||||
ngbDropdownItem,
|
|
||||||
(click)='selectConfig(config)',
|
|
||||||
href='#'
|
|
||||||
) Config modified at {{config.modified_at}}
|
|
||||||
|
|
||||||
.p-3
|
|
||||||
button.btn.btn-semi.w-100((click)='createNewConfig()')
|
|
||||||
fa-icon([icon]='_addIcon', [fixedWidth]='true')
|
|
||||||
span New config
|
|
||||||
|
|
||||||
div(ngbDropdown, placement='bottom-right')
|
|
||||||
button.btn(ngbDropdownToggle)
|
|
||||||
fa-icon([icon]='_connectionIcon', [fixedWidth]='true')
|
|
||||||
|
|
||||||
div(ngbDropdownMenu)
|
|
||||||
.form-check.form-switch
|
|
||||||
input.form-check-input(type='checkbox', ngModel='!!loginService.user.custom_connection_gateway')
|
|
||||||
label(class='form-check-label') Use custom connection gateway
|
|
||||||
|
|
||||||
div(ngbDropdown, placement='bottom-right')
|
|
||||||
button.btn(ngbDropdownToggle)
|
|
||||||
fa-icon([icon]='_userIcon', [fixedWidth]='true')
|
|
||||||
|
|
||||||
div(ngbDropdownMenu)
|
|
||||||
a(ngbDropdownItem, (click)='logout()', href='#') Logout
|
|
||||||
|
|
||||||
.terminal([hidden]='!activeVersion')
|
|
||||||
iframe(#iframe)
|
iframe(#iframe)
|
||||||
|
@ -1,10 +1,15 @@
|
|||||||
import * as semverGT from 'semver/functions/gt'
|
|
||||||
import { Component, ElementRef, ViewChild } from '@angular/core'
|
import { Component, ElementRef, ViewChild } from '@angular/core'
|
||||||
import { HttpClient } from '@angular/common/http'
|
import { HttpClient } from '@angular/common/http'
|
||||||
import { AppConnectorService } from '../services/appConnector.service'
|
import { AppConnectorService } from '../services/appConnector.service'
|
||||||
|
|
||||||
import { faCog, faUser, faCopy, faTrash, faPlus, faPlug } from '@fortawesome/free-solid-svg-icons'
|
import { faCog, faCopy, faTrash, faPlus, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { LoginService } from '../services/login.service'
|
import { LoginService } from '../services/login.service'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { SettingsModalComponent } from './settingsModal.component'
|
||||||
|
import { ConfigModalComponent } from './configModal.component'
|
||||||
|
import { ConfigService } from '../services/config.service'
|
||||||
|
import { combineLatest } from 'rxjs'
|
||||||
|
import { Config, Version } from '../api'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'main',
|
selector: 'main',
|
||||||
@ -14,21 +19,22 @@ import { LoginService } from '../services/login.service'
|
|||||||
export class MainComponent {
|
export class MainComponent {
|
||||||
_logo = require('../assets/logo.svg')
|
_logo = require('../assets/logo.svg')
|
||||||
_cogIcon = faCog
|
_cogIcon = faCog
|
||||||
_userIcon = faUser
|
_settingsIcon = faCog
|
||||||
|
_logoutIcon = faSignOutAlt
|
||||||
_copyIcon = faCopy
|
_copyIcon = faCopy
|
||||||
_addIcon = faPlus
|
_addIcon = faPlus
|
||||||
_deleteIcon = faTrash
|
_deleteIcon = faTrash
|
||||||
_connectionIcon = faPlug
|
|
||||||
|
|
||||||
configs: any[] = []
|
showApp = false
|
||||||
versions: any[] = []
|
|
||||||
activeVersion?: any
|
|
||||||
@ViewChild('iframe') iframe: ElementRef
|
@ViewChild('iframe') iframe: ElementRef
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private appConnector: AppConnectorService,
|
public appConnector: AppConnectorService,
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
public loginService: LoginService,
|
public loginService: LoginService,
|
||||||
|
private ngbModal: NgbModal,
|
||||||
|
private config: ConfigService,
|
||||||
) {
|
) {
|
||||||
window.addEventListener('message', event => {
|
window.addEventListener('message', event => {
|
||||||
if (event.data === 'request-connector') {
|
if (event.data === 'request-connector') {
|
||||||
@ -39,68 +45,47 @@ export class MainComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async ngAfterViewInit () {
|
async ngAfterViewInit () {
|
||||||
this.configs = await this.http.get('/api/1/configs').toPromise()
|
combineLatest(
|
||||||
this.versions = await this.http.get('/api/1/versions').toPromise()
|
this.config.activeConfig$,
|
||||||
|
this.config.activeVersion$
|
||||||
this.versions.sort((a, b) => semverGT(a, b))
|
).subscribe(([config, version]) => {
|
||||||
|
if (config && version) {
|
||||||
if (!this.configs.length) {
|
this.reloadApp(config, version)
|
||||||
await this.createNewConfig()
|
}
|
||||||
}
|
})
|
||||||
|
this.config
|
||||||
this.selectConfig(this.configs[0])
|
await this.config.ready$.toPromise()
|
||||||
}
|
await this.config.selectDefaultConfig()
|
||||||
|
|
||||||
async createNewConfig () {
|
|
||||||
this.configs.push(await this.http.post('/api/1/configs', {
|
|
||||||
content: '{}',
|
|
||||||
last_used_with_version: this.versions[0].version,
|
|
||||||
}).toPromise())
|
|
||||||
}
|
|
||||||
|
|
||||||
async duplicateConfig () {
|
|
||||||
const copy = {...this.appConnector.config, pk: undefined}
|
|
||||||
this.configs.push(await this.http.post('/api/1/configs', copy).toPromise())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
unloadApp () {
|
unloadApp () {
|
||||||
delete this.activeVersion
|
this.showApp = false
|
||||||
this.iframe.nativeElement.src = 'about:blank'
|
this.iframe.nativeElement.src = 'about:blank'
|
||||||
}
|
}
|
||||||
|
|
||||||
loadApp (version) {
|
async loadApp (config, version) {
|
||||||
|
this.showApp = true
|
||||||
this.iframe.nativeElement.src = '/terminal'
|
this.iframe.nativeElement.src = '/terminal'
|
||||||
this.activeVersion = version
|
await this.http.patch(`/api/1/configs/${config.id}`, {
|
||||||
|
last_used_with_version: version.version,
|
||||||
|
}).toPromise()
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveConfig () {
|
reloadApp (config: Config, version: Version) {
|
||||||
return this.appConnector.config
|
|
||||||
}
|
|
||||||
|
|
||||||
selectVersion (version: any) {
|
|
||||||
// TODO check config incompatibility
|
// TODO check config incompatibility
|
||||||
this.unloadApp()
|
this.unloadApp()
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.appConnector.version = version
|
this.appConnector.setState(config, version)
|
||||||
this.loadApp(version)
|
this.loadApp(config, version)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async selectConfig (config: any) {
|
async openConfig () {
|
||||||
let matchingVersion = this.versions.find(x => x.version === config.last_used_with_version)
|
await this.ngbModal.open(ConfigModalComponent).result
|
||||||
if (!matchingVersion) {
|
}
|
||||||
// TODO ask to upgrade
|
|
||||||
matchingVersion = this.versions[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
this.appConnector.config = config
|
async openSettings () {
|
||||||
|
await this.ngbModal.open(SettingsModalComponent).result
|
||||||
const result = await this.http.patch(`/api/1/configs/${config.id}`, {
|
|
||||||
last_used_with_version: matchingVersion.version,
|
|
||||||
}).toPromise()
|
|
||||||
Object.assign(config, result)
|
|
||||||
|
|
||||||
this.selectVersion(matchingVersion)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async logout () {
|
async logout () {
|
||||||
|
32
src/components/settingsModal.component.pug
Normal file
32
src/components/settingsModal.component.pug
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
.modal-header
|
||||||
|
h5.modal-title Settings
|
||||||
|
.modal-body
|
||||||
|
.mb-3
|
||||||
|
.form-check.form-switch
|
||||||
|
input.form-check-input(
|
||||||
|
type='checkbox',
|
||||||
|
[(ngModel)]='customGatewayEnabled'
|
||||||
|
)
|
||||||
|
label(class='form-check-label') Use custom connection gateway
|
||||||
|
|
||||||
|
.mb-3(*ngIf='customGatewayEnabled')
|
||||||
|
.form-floating
|
||||||
|
input.form-control(
|
||||||
|
type='text',
|
||||||
|
[(ngModel)]='user.custom_connection_gateway',
|
||||||
|
placeholder='wss://1.2.3.4'
|
||||||
|
)
|
||||||
|
label Gateway address
|
||||||
|
|
||||||
|
.mb-3(*ngIf='customGatewayEnabled')
|
||||||
|
.form-floating
|
||||||
|
input.form-control(
|
||||||
|
type='password',
|
||||||
|
[(ngModel)]='user.custom_connection_gateway_token',
|
||||||
|
placeholder='123'
|
||||||
|
)
|
||||||
|
label Gateway authentication token
|
||||||
|
|
||||||
|
.modal-footer
|
||||||
|
button.btn.btn-primary((click)='apply()') Apply
|
||||||
|
button.btn.btn-secondary((click)='cancel()') Cancel
|
36
src/components/settingsModal.component.ts
Normal file
36
src/components/settingsModal.component.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Component } from '@angular/core'
|
||||||
|
import { LoginService } from '../services/login.service'
|
||||||
|
|
||||||
|
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
import { User } from '../api'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'settings-modal',
|
||||||
|
templateUrl: './settingsModal.component.pug',
|
||||||
|
// styleUrls: ['./settingsModal.component.scss'],
|
||||||
|
})
|
||||||
|
export class SettingsModalComponent {
|
||||||
|
user: User
|
||||||
|
customGatewayEnabled = false
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private modalInstance: NgbActiveModal,
|
||||||
|
private loginService: LoginService,
|
||||||
|
) {
|
||||||
|
this.user = { ...loginService.user }
|
||||||
|
this.customGatewayEnabled = !!this.user.custom_connection_gateway
|
||||||
|
}
|
||||||
|
|
||||||
|
async ngOnInit () {
|
||||||
|
}
|
||||||
|
|
||||||
|
async apply () {
|
||||||
|
Object.assign(this.loginService.user, this.user)
|
||||||
|
this.modalInstance.close()
|
||||||
|
await this.loginService.updateUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel () {
|
||||||
|
this.modalInstance.dismiss()
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import { debounceTime } from 'rxjs/operators'
|
|||||||
import { HttpClient } from '@angular/common/http'
|
import { HttpClient } from '@angular/common/http'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
import { LoginService } from '../services/login.service'
|
import { LoginService } from '../services/login.service'
|
||||||
|
import { Config, Version } from '../api'
|
||||||
|
|
||||||
export class SocketProxy {
|
export class SocketProxy {
|
||||||
connect$ = new Subject<void>()
|
connect$ = new Subject<void>()
|
||||||
@ -86,10 +87,9 @@ export class SocketProxy {
|
|||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class AppConnectorService {
|
export class AppConnectorService {
|
||||||
config: any
|
|
||||||
version: any
|
|
||||||
user: any
|
|
||||||
private configUpdate = new Subject<string>()
|
private configUpdate = new Subject<string>()
|
||||||
|
private config: Config
|
||||||
|
private version: Version
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
@ -101,6 +101,11 @@ export class AppConnectorService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setState (config: Config, version: Version) {
|
||||||
|
this.config = config
|
||||||
|
this.version = version
|
||||||
|
}
|
||||||
|
|
||||||
async loadConfig (): Promise<string> {
|
async loadConfig (): Promise<string> {
|
||||||
return this.config.content
|
return this.config.content
|
||||||
}
|
}
|
||||||
|
83
src/services/config.service.ts
Normal file
83
src/services/config.service.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import * as semverGT from 'semver/functions/gt'
|
||||||
|
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 { return this._activeConfig }
|
||||||
|
get activeVersion (): Version { return this._activeVersion }
|
||||||
|
|
||||||
|
private _activeConfig: Config|null = null
|
||||||
|
private _activeVersion: Version|null = null
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
private http: HttpClient,
|
||||||
|
private loginService: LoginService,
|
||||||
|
) {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateUser () {
|
||||||
|
await this.http.put('/api/1/user', this.user).toPromise()
|
||||||
|
}
|
||||||
|
|
||||||
|
async createNewConfig () {
|
||||||
|
this.configs.push(await this.http.post('/api/1/configs', {
|
||||||
|
content: '{}',
|
||||||
|
last_used_with_version: this._activeVersion.version,
|
||||||
|
}).toPromise())
|
||||||
|
}
|
||||||
|
|
||||||
|
async duplicateActiveConfig () {
|
||||||
|
const copy = {...this._activeConfig, pk: undefined}
|
||||||
|
this.configs.push(await this.http.post('/api/1/configs', copy).toPromise())
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectVersion (version: Version) {
|
||||||
|
this._activeVersion = version
|
||||||
|
this.activeVersion$.next(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectConfig (config: Config) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
async selectDefaultConfig () {
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
|
||||||
|
private async init () {
|
||||||
|
this.configs = await this.http.get('/api/1/configs').toPromise()
|
||||||
|
this.versions = await this.http.get('/api/1/versions').toPromise()
|
||||||
|
this.versions.sort((a, b) => semverGT(a, b))
|
||||||
|
|
||||||
|
if (!this.configs.length) {
|
||||||
|
await this.createNewConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.ready$.next()
|
||||||
|
this.ready$.complete()
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,29 @@
|
|||||||
import { AsyncSubject } from 'rxjs'
|
import { AsyncSubject } from 'rxjs'
|
||||||
import { HttpClient } from '@angular/common/http'
|
import { HttpClient } from '@angular/common/http'
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { User } from '../api'
|
||||||
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class LoginService {
|
export class LoginService {
|
||||||
user: any
|
user: User
|
||||||
ready$ = new AsyncSubject<void>()
|
ready$ = new AsyncSubject<void>()
|
||||||
|
|
||||||
constructor (private http: HttpClient) {
|
constructor (private http: HttpClient) {
|
||||||
this.init()
|
this.init()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateUser () {
|
||||||
|
await this.http.put('/api/1/user', this.user).toPromise()
|
||||||
|
}
|
||||||
|
|
||||||
private async init () {
|
private async init () {
|
||||||
const user = await this.http.get('/api/1/user').toPromise()
|
try {
|
||||||
this.user = user
|
this.user = await this.http.get('/api/1/user').toPromise()
|
||||||
|
} catch {
|
||||||
|
this.user = null
|
||||||
|
}
|
||||||
|
|
||||||
this.ready$.next()
|
this.ready$.next()
|
||||||
this.ready$.complete()
|
this.ready$.complete()
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,6 @@
|
|||||||
import './terminal-styles.scss'
|
import './terminal-styles.scss'
|
||||||
|
|
||||||
|
|
||||||
Object.assign(window, {
|
|
||||||
// Buffer,
|
|
||||||
process: {
|
|
||||||
env: { },
|
|
||||||
argv: ['terminus'],
|
|
||||||
platform: 'darwin',
|
|
||||||
on: () => null,
|
|
||||||
stdout: {},
|
|
||||||
stderr: {},
|
|
||||||
resourcesPath: 'resources',
|
|
||||||
version: '14.0.0',
|
|
||||||
nextTick: (f, ...args) => setTimeout(() => f(...args)),
|
|
||||||
// cwd: () => '/',
|
|
||||||
},
|
|
||||||
global: window,
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
async function start () {
|
async function start () {
|
||||||
const modules = { }
|
|
||||||
|
|
||||||
Object.assign(window, {
|
|
||||||
require: (path) => {
|
|
||||||
if (modules[path]) {
|
|
||||||
return modules[path]
|
|
||||||
}
|
|
||||||
console.error('requiring real module', path)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>(resolve => {
|
||||||
window.addEventListener('message', event => {
|
window.addEventListener('message', event => {
|
||||||
if (event.data === 'connector-ready') {
|
if (event.data === 'connector-ready') {
|
||||||
@ -40,10 +10,11 @@ async function start () {
|
|||||||
window.parent.postMessage('request-connector', '*')
|
window.parent.postMessage('request-connector', '*')
|
||||||
})
|
})
|
||||||
|
|
||||||
const appVersion = window['__connector__'].getAppVersion()
|
const connector = window['__connector__']
|
||||||
|
|
||||||
async function loadPlugin (name, file = 'index.js') {
|
const appVersion = connector.getAppVersion()
|
||||||
const url = `../app-dist/${appVersion}/${name}/dist/${file}`
|
|
||||||
|
async function webRequire (url) {
|
||||||
console.log(`Loading ${url}`)
|
console.log(`Loading ${url}`)
|
||||||
const e = document.createElement('script')
|
const e = document.createElement('script')
|
||||||
window['module'] = { exports: {} } as any
|
window['module'] = { exports: {} } as any
|
||||||
@ -56,8 +27,11 @@ async function start () {
|
|||||||
return window['module'].exports
|
return window['module'].exports
|
||||||
}
|
}
|
||||||
|
|
||||||
await loadPlugin('web', 'preload.js')
|
const baseUrl = `../app-dist/${appVersion}`
|
||||||
await loadPlugin('web', 'bundle.js')
|
await webRequire(`${baseUrl}/web/dist/preload.js`)
|
||||||
|
await webRequire(`${baseUrl}/web/dist/bundle.js`)
|
||||||
|
|
||||||
|
const terminus = window['Terminus']
|
||||||
|
|
||||||
const pluginModules = []
|
const pluginModules = []
|
||||||
for (const plugin of [
|
for (const plugin of [
|
||||||
@ -68,15 +42,13 @@ async function start () {
|
|||||||
'terminus-community-color-schemes',
|
'terminus-community-color-schemes',
|
||||||
'terminus-web',
|
'terminus-web',
|
||||||
]) {
|
]) {
|
||||||
const mod = await loadPlugin(plugin)
|
pluginModules.push(await terminus.loadPlugin(`${baseUrl}/${plugin}`))
|
||||||
modules[`resources/builtin-plugins/${plugin}`] = modules[plugin] = mod
|
|
||||||
pluginModules.push(mod)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelector('app-root')['style'].display = 'flex'
|
document.querySelector('app-root')['style'].display = 'flex'
|
||||||
|
|
||||||
const config = window['__connector__'].loadConfig()
|
const config = connector.loadConfig()
|
||||||
window['bootstrapTerminus'](pluginModules, {
|
terminus.bootstrap(pluginModules, {
|
||||||
config,
|
config,
|
||||||
executable: 'web',
|
executable: 'web',
|
||||||
isFirstWindow: true,
|
isFirstWindow: true,
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
@import "~bootstrap/scss/list-group";
|
@import "~bootstrap/scss/list-group";
|
||||||
// @import "~bootstrap/scss/close";
|
// @import "~bootstrap/scss/close";
|
||||||
// @import "~bootstrap/scss/toasts";
|
// @import "~bootstrap/scss/toasts";
|
||||||
// @import "~bootstrap/scss/modal";
|
@import "~bootstrap/scss/modal";
|
||||||
// @import "~bootstrap/scss/tooltip";
|
// @import "~bootstrap/scss/tooltip";
|
||||||
// @import "~bootstrap/scss/popover";
|
// @import "~bootstrap/scss/popover";
|
||||||
// @import "~bootstrap/scss/carousel";
|
// @import "~bootstrap/scss/carousel";
|
||||||
@ -62,3 +62,7 @@
|
|||||||
.dropdown-menu {
|
.dropdown-menu {
|
||||||
box-shadow: $dropdown-box-shadow;
|
box-shadow: $dropdown-box-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
background: #00000030;
|
||||||
|
}
|
||||||
|
@ -77,7 +77,7 @@ $link-hover-color: $white;
|
|||||||
$link-hover-decoration: none;
|
$link-hover-decoration: none;
|
||||||
|
|
||||||
$component-active-color: $white;
|
$component-active-color: $white;
|
||||||
$component-active-bg: #2f3a42;
|
$component-active-bg: $blue;
|
||||||
|
|
||||||
$list-group-bg: $table-bg;
|
$list-group-bg: $table-bg;
|
||||||
$list-group-border-color: $table-border-color;
|
$list-group-border-color: $table-border-color;
|
||||||
@ -113,7 +113,7 @@ $popover-max-width: 360px;
|
|||||||
|
|
||||||
$btn-border-width: 2px;
|
$btn-border-width: 2px;
|
||||||
|
|
||||||
$input-bg: #181e23;
|
$input-bg: $black;
|
||||||
$input-disabled-bg: #2e3235;
|
$input-disabled-bg: #2e3235;
|
||||||
|
|
||||||
$input-color: #ddd;
|
$input-color: #ddd;
|
||||||
@ -129,6 +129,8 @@ $input-group-addon-bg: $input-bg;
|
|||||||
$input-group-addon-border-color: transparent;
|
$input-group-addon-border-color: transparent;
|
||||||
$input-group-btn-border-color: $input-bg;
|
$input-group-btn-border-color: $input-bg;
|
||||||
|
|
||||||
|
$form-switch-color: rgba(255,255,255, .25);
|
||||||
|
|
||||||
$nav-tabs-border-radius: 0;
|
$nav-tabs-border-radius: 0;
|
||||||
$nav-tabs-border-color: transparent;
|
$nav-tabs-border-color: transparent;
|
||||||
$nav-tabs-border-width: 2px;
|
$nav-tabs-border-width: 2px;
|
||||||
@ -179,8 +181,7 @@ $custom-control-indicator-active-bg: rgba(255, 255, 0, 0.5);
|
|||||||
$modal-content-bg: $body-bg;
|
$modal-content-bg: $body-bg;
|
||||||
$modal-content-border-color: $body-bg;
|
$modal-content-border-color: $body-bg;
|
||||||
$modal-header-border-width: 0;
|
$modal-header-border-width: 0;
|
||||||
$modal-footer-border-color: #222;
|
$modal-footer-border-width: 0;
|
||||||
$modal-footer-border-width: 1px;
|
|
||||||
$modal-content-border-width: 0;
|
$modal-content-border-width: 0;
|
||||||
|
|
||||||
$progress-bg: $table-bg;
|
$progress-bg: $table-bg;
|
||||||
|
@ -3,12 +3,13 @@ from dataclasses import dataclass
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import logout
|
from django.contrib.auth import logout
|
||||||
from rest_framework import fields
|
from rest_framework import fields
|
||||||
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.permissions import IsAuthenticated
|
from rest_framework.permissions import IsAuthenticated
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
|
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||||
from rest_framework.serializers import ModelSerializer, Field
|
from rest_framework.serializers import ModelSerializer
|
||||||
from rest_framework_dataclasses.serializers import DataclassSerializer
|
from rest_framework_dataclasses.serializers import DataclassSerializer
|
||||||
|
|
||||||
from .models import Config, User
|
from .models import Config, User
|
||||||
@ -64,21 +65,25 @@ class AppVersionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
|
|
||||||
class UserSerializer(ModelSerializer):
|
class UserSerializer(ModelSerializer):
|
||||||
id = fields.IntegerField()
|
id = fields.IntegerField()
|
||||||
|
is_pro = fields.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('id', 'username', 'active_config', 'custom_connection_gateway', 'custom_connection_gateway_token')
|
fields = ('id', 'username', 'active_config', 'custom_connection_gateway', 'custom_connection_gateway_token', 'is_pro')
|
||||||
read_only_fields = ('id', 'username')
|
read_only_fields = ('id', 'username')
|
||||||
|
|
||||||
|
def get_is_pro(self, obj):
|
||||||
|
return False
|
||||||
|
|
||||||
class UserViewSet(RetrieveModelMixin, GenericViewSet):
|
|
||||||
|
class UserViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
|
||||||
queryset = User.objects.all()
|
queryset = User.objects.all()
|
||||||
serializer_class = UserSerializer
|
serializer_class = UserSerializer
|
||||||
|
|
||||||
def get_object(self):
|
def get_object(self):
|
||||||
if self.request.user.is_authenticated:
|
if self.request.user.is_authenticated:
|
||||||
return self.request.user
|
return self.request.user
|
||||||
return None
|
raise PermissionDenied()
|
||||||
|
|
||||||
|
|
||||||
class LogoutView(APIView):
|
class LogoutView(APIView):
|
||||||
|
63
terminus/app/sponsors.py
Normal file
63
terminus/app/sponsors.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from gql import Client, gql
|
||||||
|
from gql.transport.requests import RequestsHTTPTransport
|
||||||
|
|
||||||
|
|
||||||
|
GQL_ENDPOINT = 'https://api.github.com/graphql'
|
||||||
|
|
||||||
|
|
||||||
|
def get_sponsor_usernames():
|
||||||
|
client = Client(
|
||||||
|
transport=RequestsHTTPTransport(
|
||||||
|
url=GQL_ENDPOINT,
|
||||||
|
use_json=True,
|
||||||
|
headers={
|
||||||
|
'Authorization': f'Bearer {settings.GITHUB_TOKEN}',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
|
||||||
|
after = None
|
||||||
|
|
||||||
|
while True:
|
||||||
|
params = 'first: 1'
|
||||||
|
if after:
|
||||||
|
params += f', after:"{after}"'
|
||||||
|
|
||||||
|
query = '''
|
||||||
|
query {
|
||||||
|
user (login: "eugeny") {
|
||||||
|
sponsorshipsAsMaintainer(%s, includePrivate: true) {
|
||||||
|
pageInfo {
|
||||||
|
startCursor
|
||||||
|
hasNextPage
|
||||||
|
endCursor
|
||||||
|
}
|
||||||
|
nodes {
|
||||||
|
createdAt
|
||||||
|
tier {
|
||||||
|
monthlyPriceInDollars
|
||||||
|
}
|
||||||
|
sponsor{
|
||||||
|
... on User {
|
||||||
|
login
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''' % (params,)
|
||||||
|
|
||||||
|
response = client.execute(gql(query))
|
||||||
|
after = response['user']['sponsorshipsAsMaintainer']['pageInfo']['endCursor']
|
||||||
|
nodes = response['user']['sponsorshipsAsMaintainer']['nodes']
|
||||||
|
if not len(nodes):
|
||||||
|
break
|
||||||
|
for node in nodes:
|
||||||
|
if node['tier']['monthlyPriceInDollars'] >= settings.GITHUB_SPONSORS_MIN_PAYMENT:
|
||||||
|
result.append(node['sponsor']['login'])
|
||||||
|
|
||||||
|
return result
|
@ -12,7 +12,7 @@ router.register('api/1/versions', api.AppVersionViewSet, basename='app-versions'
|
|||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('api/1/auth/logout', api.LogoutView.as_view()),
|
path('api/1/auth/logout', api.LogoutView.as_view()),
|
||||||
path('api/1/user', api.UserViewSet.as_view({'get': 'retrieve'})),
|
path('api/1/user', api.UserViewSet.as_view({'get': 'retrieve', 'put': 'update'})),
|
||||||
|
|
||||||
path('', views.IndexView.as_view()),
|
path('', views.IndexView.as_view()),
|
||||||
path('terminal', views.TerminalView.as_view()),
|
path('terminal', views.TerminalView.as_view()),
|
||||||
|
@ -136,6 +136,8 @@ AUTHENTICATION_BACKENDS = (
|
|||||||
'django.contrib.auth.backends.ModelBackend',
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SOCIAL_AUTH_GITHUB_SCOPE = ['read:user', 'user:email']
|
||||||
|
|
||||||
LOGIN_REDIRECT_URL = '/'
|
LOGIN_REDIRECT_URL = '/'
|
||||||
|
|
||||||
APP_DIST_PATH = BASE_DIR / 'app-dist'
|
APP_DIST_PATH = BASE_DIR / 'app-dist'
|
||||||
@ -152,9 +154,19 @@ for key in [
|
|||||||
'CONNECTION_GATEWAY_AUTH_CA',
|
'CONNECTION_GATEWAY_AUTH_CA',
|
||||||
'CONNECTION_GATEWAY_AUTH_CERTIFICATE',
|
'CONNECTION_GATEWAY_AUTH_CERTIFICATE',
|
||||||
'CONNECTION_GATEWAY_AUTH_KEY',
|
'CONNECTION_GATEWAY_AUTH_KEY',
|
||||||
|
'GITHUB_SPONSORS_USER',
|
||||||
|
'GITHUB_SPONSORS_MIN_PAYMENT',
|
||||||
|
'GITHUB_TOKEN',
|
||||||
]:
|
]:
|
||||||
globals()[key] = os.getenv(key)
|
globals()[key] = os.getenv(key)
|
||||||
|
|
||||||
|
|
||||||
|
for key in [
|
||||||
|
'GITHUB_SPONSORS_MIN_PAYMENT',
|
||||||
|
]:
|
||||||
|
globals()[key] = int(globals()[key]) if globals()[key] else None
|
||||||
|
|
||||||
|
|
||||||
for key in [
|
for key in [
|
||||||
'CONNECTION_GATEWAY_AUTH_CA',
|
'CONNECTION_GATEWAY_AUTH_CA',
|
||||||
'CONNECTION_GATEWAY_AUTH_CERTIFICATE',
|
'CONNECTION_GATEWAY_AUTH_CERTIFICATE',
|
||||||
|
@ -1,4 +1,69 @@
|
|||||||
module.exports = [
|
const path = require('path')
|
||||||
require('./webpack.main.config'),
|
const webpack = require('webpack')
|
||||||
require('./webpack.terminal.config'),
|
const { AngularWebpackPlugin } = require('@ngtools/webpack')
|
||||||
]
|
|
||||||
|
module.exports = {
|
||||||
|
target: 'web',
|
||||||
|
entry: {
|
||||||
|
'index.ignore': 'file-loader?name=index.html!pug-html-loader!' + path.resolve(__dirname, './src/index.pug'),
|
||||||
|
index: path.resolve(__dirname, 'src/index.ts'),
|
||||||
|
'terminal.ignore': 'file-loader?name=terminal.html!pug-html-loader!' + path.resolve(__dirname, './src/terminal.pug'),
|
||||||
|
terminal: path.resolve(__dirname, 'src/terminal.ts'),
|
||||||
|
},
|
||||||
|
mode: process.env.DEV ? 'development' : 'production',
|
||||||
|
context: __dirname,
|
||||||
|
devtool: 'cheap-module-source-map',
|
||||||
|
output: {
|
||||||
|
path: path.join(__dirname, 'build'),
|
||||||
|
pathinfo: true,
|
||||||
|
filename: '[name].js',
|
||||||
|
chunkFilename: '[name].bundle.js',
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
modules: [
|
||||||
|
'src/',
|
||||||
|
'node_modules/',
|
||||||
|
],
|
||||||
|
extensions: ['.ts', '.js'],
|
||||||
|
},
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.[jt]sx?$/,
|
||||||
|
loader: '@ngtools/webpack',
|
||||||
|
},
|
||||||
|
{ test: /terminus\/app\/dist/, use: ['script-loader'] },
|
||||||
|
{
|
||||||
|
test: /\.pug$/,
|
||||||
|
use: ['apply-loader', 'pug-loader'],
|
||||||
|
include: /component\.pug/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: ['@terminus-term/to-string-loader', 'css-loader', 'sass-loader'],
|
||||||
|
include: /component\.scss/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.scss$/,
|
||||||
|
use: ['style-loader', 'css-loader', 'sass-loader'],
|
||||||
|
exclude: /component\.scss/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(ttf|eot|otf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
||||||
|
type: 'asset/resource',
|
||||||
|
},
|
||||||
|
{ test: /\.css$/, use: ['css-loader', 'sass-loader'] },
|
||||||
|
{
|
||||||
|
test: /\.(jpeg|png|svg)?$/,
|
||||||
|
type: 'asset/resource',
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [
|
||||||
|
new AngularWebpackPlugin({
|
||||||
|
tsconfig: 'tsconfig.main.json',
|
||||||
|
directTemplateLoading: false,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
@ -1,67 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const webpack = require('webpack')
|
|
||||||
const { AngularWebpackPlugin } = require('@ngtools/webpack')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
target: 'web',
|
|
||||||
entry: {
|
|
||||||
'index.ignore': 'file-loader?name=index.html!pug-html-loader!' + path.resolve(__dirname, './src/index.pug'),
|
|
||||||
index: path.resolve(__dirname, 'src/index.ts'),
|
|
||||||
},
|
|
||||||
mode: process.env.DEV ? 'development' : 'production',
|
|
||||||
context: __dirname,
|
|
||||||
devtool: 'cheap-module-source-map',
|
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, 'build'),
|
|
||||||
pathinfo: true,
|
|
||||||
filename: '[name].js',
|
|
||||||
chunkFilename: '[name].bundle.js',
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
modules: [
|
|
||||||
'src/',
|
|
||||||
'node_modules/',
|
|
||||||
],
|
|
||||||
extensions: ['.ts', '.js'],
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.[jt]sx?$/,
|
|
||||||
loader: '@ngtools/webpack',
|
|
||||||
},
|
|
||||||
{ test: /terminus\/app\/dist/, use: ['script-loader'] },
|
|
||||||
{
|
|
||||||
test: /\.pug$/,
|
|
||||||
use: ['apply-loader', 'pug-loader'],
|
|
||||||
include: /component\.pug/
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: ['@terminus-term/to-string-loader', 'css-loader', 'sass-loader'],
|
|
||||||
include: /component\.scss/
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
|
||||||
exclude: /component\.scss/
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(ttf|eot|otf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
|
||||||
type: 'asset/resource',
|
|
||||||
},
|
|
||||||
{ test: /\.css$/, use: ['css-loader', 'sass-loader'] },
|
|
||||||
{
|
|
||||||
test: /\.(jpeg|png|svg)?$/,
|
|
||||||
type: 'asset/resource',
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
|
|
||||||
plugins: [
|
|
||||||
new AngularWebpackPlugin({
|
|
||||||
tsconfig: 'tsconfig.main.json',
|
|
||||||
directTemplateLoading: false,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}
|
|
@ -1,72 +0,0 @@
|
|||||||
const path = require('path')
|
|
||||||
const webpack = require('webpack')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
name: 'web-container-terminal',
|
|
||||||
target: 'web',
|
|
||||||
entry: {
|
|
||||||
'terminal.ignore': 'file-loader?name=terminal.html!pug-html-loader!' + path.resolve(__dirname, './src/terminal.pug'),
|
|
||||||
terminal: path.resolve(__dirname, 'src/terminal.ts'),
|
|
||||||
},
|
|
||||||
mode: process.env.DEV ? 'development' : 'production',
|
|
||||||
context: __dirname,
|
|
||||||
devtool: 'cheap-module-source-map',
|
|
||||||
output: {
|
|
||||||
path: path.join(__dirname, 'build'),
|
|
||||||
pathinfo: true,
|
|
||||||
filename: '[name].js',
|
|
||||||
chunkFilename: '[name].bundle.js',
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
modules: [
|
|
||||||
...[
|
|
||||||
// '../terminus/terminus-core/node_modules/',
|
|
||||||
// '../terminus/terminus-settings/node_modules/',
|
|
||||||
// '../terminus/terminus-terminal/node_modules/',
|
|
||||||
// '../terminus/node_modules',
|
|
||||||
// '../terminus/app/node_modules',
|
|
||||||
// '../terminus/app/assets/',
|
|
||||||
'src',
|
|
||||||
].map(x => path.join(__dirname, x)),
|
|
||||||
'node_modules/',
|
|
||||||
],
|
|
||||||
extensions: ['.ts', '.js'],
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.ts$/,
|
|
||||||
use: {
|
|
||||||
loader: 'awesome-typescript-loader',
|
|
||||||
options: {
|
|
||||||
configFileName: path.resolve(__dirname, 'tsconfig.container.json'),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// { test: /terminus\/app\/dist/, use: ['script-loader'] },
|
|
||||||
{
|
|
||||||
test: /\.scss$/,
|
|
||||||
use: ['style-loader', 'css-loader', 'sass-loader'],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(ttf|eot|otf|woff|woff2)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
|
|
||||||
use: {
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: {
|
|
||||||
name: 'fonts/[name].[ext]',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{ test: /\.css$/, use: ['css-loader', 'sass-loader'] },
|
|
||||||
{
|
|
||||||
test: /\.(jpeg|png|svg)?$/,
|
|
||||||
use: {
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: {
|
|
||||||
name: '[name].[ext]'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user