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
15d1fe4a46
commit
0b55657200
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
149
poetry.lock
generated
149
poetry.lock
generated
@ -136,6 +136,14 @@ twisted = {version = ">=18.7", extras = ["tls"]}
|
||||
[package.extras]
|
||||
tests = ["hypothesis (==4.23)", "pytest (>=3.10,<4.0)", "pytest-asyncio (>=0.8,<1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "defusedxml"
|
||||
version = "0.7.1"
|
||||
description = "XML bomb protection for Python stdlib modules"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
|
||||
[[package]]
|
||||
name = "django"
|
||||
version = "3.2.3"
|
||||
@ -256,6 +264,19 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.1.1"
|
||||
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
rsa = ["cryptography (>=3.0.0,<4)"]
|
||||
signals = ["blinker (>=1.4.0)"]
|
||||
signedtoken = ["cryptography (>=3.0.0,<4)", "pyjwt (>=2.0.0,<3)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyasn1"
|
||||
version = "0.4.8"
|
||||
@ -299,6 +320,20 @@ category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "2.1.0"
|
||||
description = "JSON Web Token implementation in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
crypto = ["cryptography (>=3.3.1,<4.0.0)"]
|
||||
dev = ["sphinx", "sphinx-rtd-theme", "zope.interface", "cryptography (>=3.3.1,<4.0.0)", "pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)", "mypy", "pre-commit"]
|
||||
docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"]
|
||||
tests = ["pytest (>=6.0.0,<7.0.0)", "coverage[toml] (==5.0.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyopenssl"
|
||||
version = "20.0.1"
|
||||
@ -315,6 +350,21 @@ six = ">=1.5.2"
|
||||
docs = ["sphinx", "sphinx-rtd-theme"]
|
||||
test = ["flaky", "pretend", "pytest (>=3.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "python3-openid"
|
||||
version = "3.2.0"
|
||||
description = "OpenID support for modern servers and consumers."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
defusedxml = "*"
|
||||
|
||||
[package.extras]
|
||||
mysql = ["mysql-connector-python"]
|
||||
postgresql = ["psycopg2"]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2021.1"
|
||||
@ -323,6 +373,33 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "requests"
|
||||
version = "2.15.1"
|
||||
description = "Python HTTP for Humans."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"]
|
||||
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
|
||||
|
||||
[[package]]
|
||||
name = "requests-oauthlib"
|
||||
version = "1.3.0"
|
||||
description = "OAuthlib authentication support for Requests."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[package.dependencies]
|
||||
oauthlib = ">=3.0.0"
|
||||
requests = ">=2.0.0"
|
||||
|
||||
[package.extras]
|
||||
rsa = ["oauthlib[signedtoken] (>=3.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "service-identity"
|
||||
version = "21.1.0"
|
||||
@ -352,6 +429,42 @@ category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "social-auth-app-django"
|
||||
version = "4.0.0"
|
||||
description = "Python Social Authentication, Django integration."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
six = "*"
|
||||
social-auth-core = ">=3.3.0"
|
||||
|
||||
[[package]]
|
||||
name = "social-auth-core"
|
||||
version = "4.1.0"
|
||||
description = "Python social authentication made simple."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=1.4"
|
||||
defusedxml = ">=0.5.0rc1"
|
||||
oauthlib = ">=1.0.3"
|
||||
PyJWT = ">=2.0.0"
|
||||
python3-openid = ">=3.0.10"
|
||||
requests = ">=2.9.1"
|
||||
requests-oauthlib = ">=0.6.1"
|
||||
|
||||
[package.extras]
|
||||
all = ["python-jose (>=3.0.0)", "python3-saml (>=1.2.1)", "cryptography (>=2.1.1)"]
|
||||
allpy3 = ["python-jose (>=3.0.0)", "python3-saml (>=1.2.1)", "cryptography (>=2.1.1)"]
|
||||
azuread = ["cryptography (>=2.1.1)"]
|
||||
openidconnect = ["python-jose (>=3.0.0)"]
|
||||
saml = ["python3-saml (>=1.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "sqlparse"
|
||||
version = "0.4.1"
|
||||
@ -464,7 +577,7 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.7"
|
||||
content-hash = "3922460ced69b5e33ce174cf478542b18bf7bc793d32e262fe46a4b7d9a2a047"
|
||||
content-hash = "ddf1bd292c78d2b6919ec1a9eb9b76b48ff4d97e7a20a3a46783dcdbf5ec1163"
|
||||
|
||||
[metadata.files]
|
||||
asgiref = [
|
||||
@ -560,6 +673,10 @@ daphne = [
|
||||
{file = "daphne-3.0.2-py3-none-any.whl", hash = "sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"},
|
||||
{file = "daphne-3.0.2.tar.gz", hash = "sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f"},
|
||||
]
|
||||
defusedxml = [
|
||||
{file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"},
|
||||
{file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"},
|
||||
]
|
||||
django = [
|
||||
{file = "Django-3.2.3-py3-none-any.whl", hash = "sha256:7e0a1393d18c16b503663752a8b6790880c5084412618990ce8a81cc908b4962"},
|
||||
{file = "Django-3.2.3.tar.gz", hash = "sha256:13ac78dbfd189532cad8f383a27e58e18b3d33f80009ceb476d7fcbfc5dcebd8"},
|
||||
@ -599,6 +716,10 @@ mccabe = [
|
||||
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
|
||||
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
|
||||
]
|
||||
oauthlib = [
|
||||
{file = "oauthlib-3.1.1-py2.py3-none-any.whl", hash = "sha256:42bf6354c2ed8c6acb54d971fce6f88193d97297e18602a3a886603f9d7730cc"},
|
||||
{file = "oauthlib-3.1.1.tar.gz", hash = "sha256:8f0215fcc533dd8dd1bee6f4c412d4f0cd7297307d43ac61666389e3bc3198a3"},
|
||||
]
|
||||
pyasn1 = [
|
||||
{file = "pyasn1-0.4.8-py2.4.egg", hash = "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"},
|
||||
{file = "pyasn1-0.4.8-py2.5.egg", hash = "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf"},
|
||||
@ -641,14 +762,31 @@ pyflakes = [
|
||||
{file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"},
|
||||
{file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"},
|
||||
]
|
||||
pyjwt = [
|
||||
{file = "PyJWT-2.1.0-py3-none-any.whl", hash = "sha256:934d73fbba91b0483d3857d1aff50e96b2a892384ee2c17417ed3203f173fca1"},
|
||||
{file = "PyJWT-2.1.0.tar.gz", hash = "sha256:fba44e7898bbca160a2b2b501f492824fc8382485d3a6f11ba5d0c1937ce6130"},
|
||||
]
|
||||
pyopenssl = [
|
||||
{file = "pyOpenSSL-20.0.1-py2.py3-none-any.whl", hash = "sha256:818ae18e06922c066f777a33f1fca45786d85edfe71cd043de6379337a7f274b"},
|
||||
{file = "pyOpenSSL-20.0.1.tar.gz", hash = "sha256:4c231c759543ba02560fcd2480c48dcec4dae34c9da7d3747c508227e0624b51"},
|
||||
]
|
||||
python3-openid = [
|
||||
{file = "python3-openid-3.2.0.tar.gz", hash = "sha256:33fbf6928f401e0b790151ed2b5290b02545e8775f982485205a066f874aaeaf"},
|
||||
{file = "python3_openid-3.2.0-py3-none-any.whl", hash = "sha256:6626f771e0417486701e0b4daff762e7212e820ca5b29fcc0d05f6f8736dfa6b"},
|
||||
]
|
||||
pytz = [
|
||||
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
|
||||
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
|
||||
]
|
||||
requests = [
|
||||
{file = "requests-2.15.1-py2.py3-none-any.whl", hash = "sha256:ff753b2196cd18b1bbeddc9dcd5c864056599f7a7d9a4fb5677e723efa2b7fb9"},
|
||||
{file = "requests-2.15.1.tar.gz", hash = "sha256:e5659b9315a0610505e050bb7190bf6fa2ccee1ac295f2b760ef9d8a03ebbb2e"},
|
||||
]
|
||||
requests-oauthlib = [
|
||||
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
|
||||
{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"},
|
||||
]
|
||||
service-identity = [
|
||||
{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"},
|
||||
@ -657,6 +795,15 @@ six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
social-auth-app-django = [
|
||||
{file = "social-auth-app-django-4.0.0.tar.gz", hash = "sha256:2c69e57df0b30c9c1823519c5f1992cbe4f3f98fdc7d95c840e091a752708840"},
|
||||
{file = "social_auth_app_django-4.0.0-py2-none-any.whl", hash = "sha256:df5212370bd250108987c4748419a1a1d0cec750878856c2644c36aaa0fd3e58"},
|
||||
{file = "social_auth_app_django-4.0.0-py3-none-any.whl", hash = "sha256:567ad0e028311541d7dfed51d3bf2c60440a6fd236d5d4d06c5a618b3d6c57c5"},
|
||||
]
|
||||
social-auth-core = [
|
||||
{file = "social-auth-core-4.1.0.tar.gz", hash = "sha256:5ab43b3b15dce5f059db69cc3082c216574739f0edbc98629c8c6e8769c67eb4"},
|
||||
{file = "social_auth_core-4.1.0-py3-none-any.whl", hash = "sha256:983b53167ac56e7ba4909db555602a6e7a98c97ca47183bb222eb85ba627bf2b"},
|
||||
]
|
||||
sqlparse = [
|
||||
{file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"},
|
||||
{file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"},
|
||||
|
@ -11,6 +11,7 @@ django-rest-framework = "^0.1.0"
|
||||
channels = "^3.0.3"
|
||||
uvloop = "^0.15.2"
|
||||
djangorestframework-dataclasses = "^0.9"
|
||||
social-auth-app-django = "^4.0.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
flake8 = "^3.9.2"
|
||||
|
@ -5,6 +5,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { HttpClientModule, HttpClientXsrfModule } from '@angular/common/http'
|
||||
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'
|
||||
import { AppComponent } from './components/app.component'
|
||||
import { MainComponent } from './components/main.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -17,6 +18,7 @@ import { AppComponent } from './components/app.component'
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
MainComponent,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
@ -1,55 +1,2 @@
|
||||
.sidebar
|
||||
img.logo(src='{{_logo}}')
|
||||
|
||||
div(ngbDropdown, placement='bottom-right')
|
||||
button.btn.btn-secondary(ngbDropdownToggle)
|
||||
fa-icon([icon]='_cogIcon', [fixedWidth]='true')
|
||||
|
||||
.config-menu(ngbDropdownMenu)
|
||||
.header(*ngIf='getActiveConfig()')
|
||||
.dropdown-header Active config
|
||||
.title {{getActiveConfig().modified_at}}
|
||||
div(*ngIf='activeVersion')
|
||||
div App version:
|
||||
div(ngbDropdown)
|
||||
button.btn.btn-secondary(ngbDropdownToggle) {{activeVersion.version}}
|
||||
div(ngbDropdownMenu)
|
||||
a(
|
||||
*ngFor='let version of versions',
|
||||
ngbDropdownItem,
|
||||
[class.active]='version == activeVersion',
|
||||
(click)='selectVersion(version)'
|
||||
) {{version.version}}
|
||||
|
||||
.btn-toolbar
|
||||
button.btn.btn-light.w-50((click)='duplicateConfig()')
|
||||
fa-icon([icon]='_copyIcon', [fixedWidth]='true')
|
||||
span Duplicate
|
||||
|
||||
button.btn.btn-light.w-50((click)='deleteConfig()')
|
||||
fa-icon([icon]='_deleteIcon', [fixedWidth]='true')
|
||||
span Delete
|
||||
|
||||
div(*ngIf='configs.length > 1')
|
||||
.dropdown-header All configs
|
||||
|
||||
ng-container(*ngFor='let config of configs')
|
||||
a(
|
||||
*ngIf='config !== getActiveConfig()',
|
||||
ngbDropdownItem,
|
||||
(click)='selectConfig(config)'
|
||||
) Config modified at {{config.modified_at}}
|
||||
|
||||
button.btn.btn-light.w-100((click)='createNewConfig()')
|
||||
fa-icon([icon]='_addIcon', [fixedWidth]='true')
|
||||
span New config
|
||||
|
||||
div(ngbDropdown, placement='bottom-right')
|
||||
button.btn.btn-secondary(ngbDropdownToggle)
|
||||
fa-icon([icon]='_userIcon', [fixedWidth]='true')
|
||||
|
||||
div(ngbDropdownMenu)
|
||||
a(ngbDropdownItem, (click)='logout()') Logout
|
||||
|
||||
.terminal([hidden]='!activeVersion')
|
||||
iframe(#iframe)
|
||||
main(*ngIf='ready && user')
|
||||
login(*ngIf='ready && !user')
|
||||
|
@ -1,41 +0,0 @@
|
||||
:host {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 64px;
|
||||
flex: none;
|
||||
|
||||
.logo {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
}
|
||||
|
||||
.terminal {
|
||||
flex: 1 1 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
iframe {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
.config-menu {
|
||||
.header {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
}
|
@ -1,9 +1,5 @@
|
||||
import * as semverGT from 'semver/functions/gt'
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core'
|
||||
import { Component } from '@angular/core'
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { AppConnectorService } from '../services/appConnector.service'
|
||||
|
||||
import { faCog, faUser, faCopy, faTrash, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
@Component({
|
||||
selector: 'app',
|
||||
@ -11,95 +7,18 @@ import { faCog, faUser, faCopy, faTrash, faPlus } from '@fortawesome/free-solid-
|
||||
styleUrls: ['./app.component.scss'],
|
||||
})
|
||||
export class AppComponent {
|
||||
_logo = require('../assets/logo.svg')
|
||||
_cogIcon = faCog
|
||||
_userIcon = faUser
|
||||
_copyIcon = faCopy
|
||||
_addIcon = faPlus
|
||||
_deleteIcon = faTrash
|
||||
|
||||
configs: any[] = []
|
||||
versions: any[] = []
|
||||
activeVersion?: any
|
||||
@ViewChild('iframe') iframe: ElementRef
|
||||
user: any
|
||||
ready = false
|
||||
|
||||
constructor (
|
||||
private appConnector: AppConnectorService,
|
||||
private http: HttpClient,
|
||||
) {
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data === 'request-connector') {
|
||||
this.iframe.nativeElement.contentWindow['__connector__'] = this.appConnector
|
||||
this.iframe.nativeElement.contentWindow.postMessage('connector-ready', '*')
|
||||
}
|
||||
})
|
||||
}
|
||||
) { }
|
||||
|
||||
async ngAfterViewInit () {
|
||||
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()
|
||||
async ngOnInit () {
|
||||
const user = await this.http.get('/api/1/user').toPromise()
|
||||
if (user.id) {
|
||||
this.user = user
|
||||
}
|
||||
|
||||
this.selectConfig(this.configs[0])
|
||||
}
|
||||
|
||||
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 () {
|
||||
delete this.activeVersion
|
||||
this.iframe.nativeElement.src = 'about:blank'
|
||||
}
|
||||
|
||||
loadApp (version) {
|
||||
this.iframe.nativeElement.src = `/terminal?${version.version}`
|
||||
this.activeVersion = version
|
||||
}
|
||||
|
||||
getActiveConfig () {
|
||||
return this.appConnector.config
|
||||
}
|
||||
|
||||
selectVersion (version: any) {
|
||||
// TODO check config incompatibility
|
||||
this.unloadApp()
|
||||
setTimeout(() => {
|
||||
this.loadApp(version)
|
||||
})
|
||||
}
|
||||
|
||||
async selectConfig (config: any) {
|
||||
let matchingVersion = this.versions.find(x => x.version === config.last_used_with_version)
|
||||
if (!matchingVersion) {
|
||||
// TODO ask to upgrade
|
||||
matchingVersion = this.versions[0]
|
||||
}
|
||||
|
||||
this.appConnector.config = config
|
||||
|
||||
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 () {
|
||||
await this.http.post('/api/1/auth/logout', null).toPromise()
|
||||
this.ready = true
|
||||
}
|
||||
}
|
||||
|
57
src/components/main.component.pug
Normal file
57
src/components/main.component.pug
Normal file
@ -0,0 +1,57 @@
|
||||
.sidebar
|
||||
img.logo(src='{{_logo}}')
|
||||
|
||||
div(ngbDropdown, placement='bottom-right')
|
||||
button.btn(ngbDropdownToggle)
|
||||
fa-icon([icon]='_cogIcon', [fixedWidth]='true')
|
||||
|
||||
.config-menu(ngbDropdownMenu)
|
||||
.header(*ngIf='getActiveConfig()')
|
||||
.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()')
|
||||
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='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]='_userIcon', [fixedWidth]='true')
|
||||
|
||||
div(ngbDropdownMenu)
|
||||
a(ngbDropdownItem, (click)='logout()', href='#') Logout
|
||||
|
||||
.terminal([hidden]='!activeVersion')
|
||||
iframe(#iframe)
|
61
src/components/main.component.scss
Normal file
61
src/components/main.component.scss
Normal file
@ -0,0 +1,61 @@
|
||||
@import "../theme/vars";
|
||||
|
||||
:host {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 64px;
|
||||
flex: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
||||
.logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
align-self: center;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
>button, >[ngbdropdown] > button {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.terminal {
|
||||
flex: 1 1 0;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
iframe {
|
||||
background: $body-bg;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
.config-menu {
|
||||
.header {
|
||||
border-bottom: 1px solid black;
|
||||
}
|
||||
}
|
106
src/components/main.component.ts
Normal file
106
src/components/main.component.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import * as semverGT from 'semver/functions/gt'
|
||||
import { Component, ElementRef, ViewChild } from '@angular/core'
|
||||
import { HttpClient } from '@angular/common/http'
|
||||
import { AppConnectorService } from '../services/appConnector.service'
|
||||
|
||||
import { faCog, faUser, faCopy, faTrash, faPlus } from '@fortawesome/free-solid-svg-icons'
|
||||
|
||||
@Component({
|
||||
selector: 'main',
|
||||
templateUrl: './main.component.pug',
|
||||
styleUrls: ['./main.component.scss'],
|
||||
})
|
||||
export class MainComponent {
|
||||
_logo = require('../assets/logo.svg')
|
||||
_cogIcon = faCog
|
||||
_userIcon = faUser
|
||||
_copyIcon = faCopy
|
||||
_addIcon = faPlus
|
||||
_deleteIcon = faTrash
|
||||
|
||||
configs: any[] = []
|
||||
versions: any[] = []
|
||||
activeVersion?: any
|
||||
@ViewChild('iframe') iframe: ElementRef
|
||||
|
||||
constructor (
|
||||
private appConnector: AppConnectorService,
|
||||
private http: HttpClient,
|
||||
) {
|
||||
window.addEventListener('message', event => {
|
||||
if (event.data === 'request-connector') {
|
||||
this.iframe.nativeElement.contentWindow['__connector__'] = this.appConnector
|
||||
this.iframe.nativeElement.contentWindow.postMessage('connector-ready', '*')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async ngAfterViewInit () {
|
||||
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.selectConfig(this.configs[0])
|
||||
}
|
||||
|
||||
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 () {
|
||||
delete this.activeVersion
|
||||
this.iframe.nativeElement.src = 'about:blank'
|
||||
}
|
||||
|
||||
loadApp (version) {
|
||||
this.iframe.nativeElement.src = `/terminal?${version.version}`
|
||||
this.activeVersion = version
|
||||
}
|
||||
|
||||
getActiveConfig () {
|
||||
return this.appConnector.config
|
||||
}
|
||||
|
||||
selectVersion (version: any) {
|
||||
// TODO check config incompatibility
|
||||
this.unloadApp()
|
||||
setTimeout(() => {
|
||||
this.loadApp(version)
|
||||
})
|
||||
}
|
||||
|
||||
async selectConfig (config: any) {
|
||||
let matchingVersion = this.versions.find(x => x.version === config.last_used_with_version)
|
||||
if (!matchingVersion) {
|
||||
// TODO ask to upgrade
|
||||
matchingVersion = this.versions[0]
|
||||
}
|
||||
|
||||
this.appConnector.config = config
|
||||
|
||||
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 () {
|
||||
await this.http.post('/api/1/auth/logout', null).toPromise()
|
||||
location.href = '/'
|
||||
}
|
||||
}
|
@ -6,4 +6,4 @@ body {
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
@import "./theme/index.scss"
|
||||
|
64
src/theme/index.scss
Normal file
64
src/theme/index.scss
Normal file
@ -0,0 +1,64 @@
|
||||
@import "vars";
|
||||
|
||||
@import "~bootstrap/scss/functions";
|
||||
@import "~bootstrap/scss/variables";
|
||||
@import "~bootstrap/scss/mixins";
|
||||
@import "~bootstrap/scss/utilities";
|
||||
|
||||
@import "~bootstrap/scss/root";
|
||||
@import "~bootstrap/scss/reboot";
|
||||
@import "~bootstrap/scss/type";
|
||||
// @import "~bootstrap/scss/images";
|
||||
@import "~bootstrap/scss/containers";
|
||||
@import "~bootstrap/scss/grid";
|
||||
// @import "~bootstrap/scss/tables";
|
||||
@import "~bootstrap/scss/forms";
|
||||
@import "~bootstrap/scss/buttons";
|
||||
@import "~bootstrap/scss/transitions";
|
||||
@import "~bootstrap/scss/dropdown";
|
||||
@import "~bootstrap/scss/button-group";
|
||||
// @import "~bootstrap/scss/nav";
|
||||
// @import "~bootstrap/scss/navbar";
|
||||
// @import "~bootstrap/scss/card";
|
||||
// @import "~bootstrap/scss/accordion";
|
||||
// @import "~bootstrap/scss/breadcrum/b";
|
||||
// @import "~bootstrap/scss/pagination";
|
||||
@import "~bootstrap/scss/badge";
|
||||
// @import "~bootstrap/scss/alert";
|
||||
// @import "~bootstrap/scss/progress";
|
||||
@import "~bootstrap/scss/list-group";
|
||||
// @import "~bootstrap/scss/close";
|
||||
// @import "~bootstrap/scss/toasts";
|
||||
// @import "~bootstrap/scss/modal";
|
||||
// @import "~bootstrap/scss/tooltip";
|
||||
// @import "~bootstrap/scss/popover";
|
||||
// @import "~bootstrap/scss/carousel";
|
||||
// @import "~bootstrap/scss/spinners";
|
||||
// @import "~bootstrap/scss/offcanvas";
|
||||
|
||||
// Helpers
|
||||
@import "~bootstrap/scss/helpers";
|
||||
@import "~bootstrap/scss/utilities/api";
|
||||
|
||||
::-webkit-scrollbar-track
|
||||
{
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
|
||||
background-color: $gray-900;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar
|
||||
{
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
background-color: #F5F5F5;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb
|
||||
{
|
||||
background-color: $gray-700;
|
||||
}
|
||||
|
||||
|
||||
.dropdown-menu {
|
||||
box-shadow: $dropdown-box-shadow;
|
||||
}
|
187
src/theme/vars.scss
Normal file
187
src/theme/vars.scss
Normal file
@ -0,0 +1,187 @@
|
||||
$white: #fff;
|
||||
$gray-100: #f8f9fa;
|
||||
$gray-200: #e9ecef;
|
||||
$gray-300: #dee2e6;
|
||||
$gray-400: #ced4da;
|
||||
$gray-500: #adb5bd;
|
||||
$gray-600: #6c757d;
|
||||
$gray-700: #495057;
|
||||
$gray-800: #343a40;
|
||||
$gray-900: #212529;
|
||||
$black: #000;
|
||||
|
||||
|
||||
$red: #d9534f !default;
|
||||
$orange: #f0ad4e !default;
|
||||
$yellow: #ffd500 !default;
|
||||
$green: #5cb85c !default;
|
||||
$blue: #0275d8 !default;
|
||||
$teal: #5bc0de !default;
|
||||
$pink: #ff5b77 !default;
|
||||
$purple: #613d7c !default;
|
||||
$semi: rgba(0,0,0, .5);
|
||||
|
||||
|
||||
@import "~bootstrap/scss/functions";
|
||||
|
||||
$table-bg: rgba(255,255,255,.05);
|
||||
$table-bg-hover: rgba(255,255,255,.1);
|
||||
$table-border-color: rgba(255,255,255,.1);
|
||||
|
||||
$theme-colors: (
|
||||
primary: $blue,
|
||||
secondary: #38434e,
|
||||
success: $green,
|
||||
info: $blue,
|
||||
warning: $orange,
|
||||
danger: $red,
|
||||
light: $gray-300,
|
||||
dark: #0e151d,
|
||||
rare: $purple,
|
||||
semi: $semi
|
||||
);
|
||||
|
||||
$body-color: #ccc;
|
||||
$body-bg: #0c131b;
|
||||
|
||||
$font-family-sans-serif: "Source Sans Pro";
|
||||
$font-family-monospace: "Source Code Pro";
|
||||
$font-size-base: 14rem / 16;
|
||||
$font-size-lg: 1.28rem;
|
||||
$font-size-sm: .85rem;
|
||||
|
||||
$line-height-base: 1.6;
|
||||
|
||||
$border-radius: .4rem;
|
||||
$border-radius-lg: .6rem;
|
||||
$border-radius-sm: .2rem;
|
||||
|
||||
$box-shadow: 0 .5rem 1rem rgba($black, .5) !default;
|
||||
|
||||
// -----
|
||||
|
||||
$headings-color: #ced9e2;
|
||||
$headings-font-weight: lighter;
|
||||
|
||||
$input-btn-padding-y: .3rem;
|
||||
$input-btn-padding-x: .9rem;
|
||||
$input-btn-line-height: 1.6;
|
||||
$input-btn-line-height-sm: 1.8;
|
||||
$input-btn-line-height-lg: 1.8;
|
||||
$btn-focus-width: 1px;
|
||||
|
||||
$h4-font-size: 18px;
|
||||
|
||||
$link-color: $gray-400;
|
||||
$link-hover-color: $white;
|
||||
$link-hover-decoration: none;
|
||||
|
||||
$component-active-color: $white;
|
||||
$component-active-bg: #2f3a42;
|
||||
|
||||
$list-group-bg: $table-bg;
|
||||
$list-group-border-color: $table-border-color;
|
||||
|
||||
$list-group-item-padding-y: 0.8rem;
|
||||
$list-group-item-padding-x: 1rem;
|
||||
|
||||
$list-group-hover-bg: $table-bg-hover;
|
||||
$list-group-active-bg: rgba(255,255,255,.2);
|
||||
$list-group-active-color: $component-active-color;
|
||||
$list-group-active-border-color: translate;
|
||||
|
||||
$list-group-action-color: $body-color;
|
||||
$list-group-action-hover-color: white;
|
||||
|
||||
$list-group-action-active-color: $component-active-color;
|
||||
$list-group-action-active-bg: $list-group-active-bg;
|
||||
|
||||
$alert-padding-y: 0.9rem;
|
||||
$alert-padding-x: 1.25rem;
|
||||
|
||||
$transition-base: all .15s ease-in-out;
|
||||
$transition-fade: opacity .1s linear;
|
||||
$transition-collapse: height .35s ease;
|
||||
$btn-transition: all .15s ease-in-out;
|
||||
|
||||
$popover-bg: $body-bg;
|
||||
$popover-body-color: $body-color;
|
||||
$popover-header-bg: $table-bg-hover;
|
||||
$popover-header-color: $headings-color;
|
||||
$popover-arrow-color: $popover-bg;
|
||||
$popover-max-width: 360px;
|
||||
|
||||
$btn-border-width: 2px;
|
||||
|
||||
$input-bg: #181e23;
|
||||
$input-disabled-bg: #2e3235;
|
||||
|
||||
$input-color: #ddd;
|
||||
$input-border-color: $input-bg;
|
||||
$input-border-width: 2px;
|
||||
|
||||
$input-focus-bg: $input-bg;
|
||||
$input-focus-border-color: rgba(171, 171, 171, 0.61);
|
||||
$input-focus-color: $input-color;
|
||||
|
||||
$input-group-addon-color: $input-color;
|
||||
$input-group-addon-bg: $input-bg;
|
||||
$input-group-addon-border-color: transparent;
|
||||
$input-group-btn-border-color: $input-bg;
|
||||
|
||||
$nav-tabs-border-radius: 0;
|
||||
$nav-tabs-border-color: transparent;
|
||||
$nav-tabs-border-width: 2px;
|
||||
$nav-tabs-link-hover-border-color: transparent;
|
||||
$nav-tabs-link-active-color: #eee;
|
||||
$nav-tabs-link-active-bg: transparent;
|
||||
$nav-tabs-link-active-border-color: #eee;
|
||||
|
||||
$navbar-padding-y: 0;
|
||||
$navbar-padding-x: 0;
|
||||
|
||||
$dropdown-bg: $body-bg;
|
||||
$dropdown-color: $body-color;
|
||||
$dropdown-border-width: 1px;
|
||||
$dropdown-header-color: $gray-500;
|
||||
|
||||
$dropdown-link-color: $body-color;
|
||||
$dropdown-link-hover-color: #eee;
|
||||
$dropdown-link-hover-bg: rgba(255,255,255,.04);
|
||||
$dropdown-link-active-color: white;
|
||||
$dropdown-link-active-bg: rgba(0, 0, 0, .2);
|
||||
$dropdown-item-padding-y: 0.5rem;
|
||||
$dropdown-item-padding-x: 1.5rem;
|
||||
|
||||
|
||||
$code-color: $orange;
|
||||
$code-bg: rgba(0, 0, 0, .25);
|
||||
$code-padding-y: 3px;
|
||||
$code-padding-x: 5px;
|
||||
$pre-bg: $dropdown-bg;
|
||||
$pre-color: $dropdown-link-color;
|
||||
|
||||
$badge-font-size: 0.75rem;
|
||||
$badge-font-weight: bold;
|
||||
$badge-padding-y: 4px;
|
||||
$badge-padding-x: 6px;
|
||||
|
||||
|
||||
$custom-control-indicator-size: 1.2rem;
|
||||
$custom-control-indicator-bg: $body-bg;
|
||||
$custom-control-indicator-border-color: lighten($body-bg, 25%);
|
||||
$custom-control-indicator-checked-bg: theme-color("primary");
|
||||
$custom-control-indicator-checked-color: $body-bg;
|
||||
$custom-control-indicator-checked-border-color: transparent;
|
||||
$custom-control-indicator-active-bg: rgba(255, 255, 0, 0.5);
|
||||
|
||||
|
||||
$modal-content-bg: $body-bg;
|
||||
$modal-content-border-color: $body-bg;
|
||||
$modal-header-border-width: 0;
|
||||
$modal-footer-border-color: #222;
|
||||
$modal-footer-border-width: 1px;
|
||||
$modal-content-border-width: 0;
|
||||
|
||||
$progress-bg: $table-bg;
|
||||
$progress-height: 3px;
|
@ -2,15 +2,16 @@ import os
|
||||
from dataclasses import dataclass
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import logout
|
||||
from rest_framework import fields
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.mixins import ListModelMixin, RetrieveModelMixin
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.viewsets import GenericViewSet, ModelViewSet
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from rest_framework.serializers import ModelSerializer, Field
|
||||
from rest_framework_dataclasses.serializers import DataclassSerializer
|
||||
|
||||
from .models import Config
|
||||
from .models import Config, User
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -61,6 +62,26 @@ class AppVersionViewSet(ListModelMixin, GenericViewSet):
|
||||
).data)
|
||||
|
||||
|
||||
class UserSerializer(ModelSerializer):
|
||||
id = fields.IntegerField()
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ('id', 'username', 'active_config')
|
||||
read_only_fields = ('id', 'username')
|
||||
|
||||
|
||||
class UserViewSet(RetrieveModelMixin, GenericViewSet):
|
||||
queryset = User.objects.all()
|
||||
serializer_class = UserSerializer
|
||||
|
||||
def get_object(self):
|
||||
if self.request.user.is_authenticated:
|
||||
return self.request.user
|
||||
return None
|
||||
|
||||
|
||||
class LogoutView(APIView):
|
||||
def post(self, request, format=None):
|
||||
logout(request)
|
||||
return Response(None)
|
||||
|
@ -12,6 +12,7 @@ router.register('api/1/versions', api.AppVersionViewSet, basename='app-versions'
|
||||
|
||||
urlpatterns = [
|
||||
path('api/1/auth/logout', api.LogoutView.as_view()),
|
||||
path('api/1/user', api.UserViewSet.as_view({'get': 'retrieve'})),
|
||||
|
||||
path('', views.IndexView.as_view()),
|
||||
path('terminal', views.TerminalView.as_view()),
|
||||
|
@ -39,6 +39,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.staticfiles',
|
||||
'channels',
|
||||
'rest_framework',
|
||||
'social_django',
|
||||
'terminus.app',
|
||||
]
|
||||
|
||||
|
@ -19,5 +19,6 @@ from .app.urls import urlpatterns
|
||||
|
||||
urlpatterns = [
|
||||
path('', include(urlpatterns)),
|
||||
path('', include('social_django.urls', namespace='social')),
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user