This commit is contained in:
Eugene Pankov 2021-07-11 16:13:18 +02:00
parent 29a3b34dd2
commit 74fb173d80
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
30 changed files with 559 additions and 161 deletions

View File

@ -1,2 +0,0 @@
errorlog="-"
workers=4

250
poetry.lock generated
View File

@ -1,3 +1,11 @@
[[package]]
name = "appdirs"
version = "1.4.4"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "asgiref" name = "asgiref"
version = "3.3.4" version = "3.3.4"
@ -66,6 +74,38 @@ six = "*"
[package.extras] [package.extras]
visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"] visualize = ["graphviz (>0.5.1)", "Twisted (>=16.1.1)"]
[[package]]
name = "black"
version = "21.6b0"
description = "The uncompromising code formatter."
category = "dev"
optional = false
python-versions = ">=3.6.2"
[package.dependencies]
appdirs = "*"
click = ">=7.1.2"
mypy-extensions = ">=0.4.3"
pathspec = ">=0.8.1,<1"
regex = ">=2020.1.8"
toml = ">=0.10.1"
typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\""}
typing-extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.6.0)", "aiohttp-cors (>=0.4.0)"]
python2 = ["typed-ast (>=1.4.2)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "certifi"
version = "2021.5.30"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "cffi" name = "cffi"
version = "1.14.5" version = "1.14.5"
@ -93,6 +133,14 @@ Django = ">=2.2"
[package.extras] [package.extras]
tests = ["pytest", "pytest-django", "pytest-asyncio", "async-generator", "async-timeout", "coverage (>=4.5,<5.0)"] tests = ["pytest", "pytest-django", "pytest-asyncio", "async-generator", "async-timeout", "coverage (>=4.5,<5.0)"]
[[package]]
name = "chardet"
version = "4.0.0"
description = "Universal encoding detector for Python 2 and 3"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]] [[package]]
name = "click" name = "click"
version = "8.0.1" version = "8.0.1"
@ -308,11 +356,11 @@ idna = ">=2.5"
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.2" version = "2.10"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "importlib-metadata" name = "importlib-metadata"
@ -349,6 +397,14 @@ category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "mysqlclient" name = "mysqlclient"
version = "2.0.3" version = "2.0.3"
@ -370,6 +426,14 @@ 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 = "pathspec"
version = "0.8.1"
description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]] [[package]]
name = "promise" name = "promise"
version = "2.3" version = "2.3"
@ -500,15 +564,29 @@ optional = false
python-versions = "*" python-versions = "*"
[[package]] [[package]]
name = "requests" name = "regex"
version = "2.15.1" version = "2021.7.6"
description = "Python HTTP for Humans." description = "Alternative regular expression module, to replace re."
category = "main" category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "requests"
version = "2.25.1"
description = "Python HTTP for Humans."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[package.dependencies]
certifi = ">=2017.4.17"
chardet = ">=3.0.2,<5"
idna = ">=2.5,<3"
urllib3 = ">=1.21.1,<1.27"
[package.extras] [package.extras]
security = ["cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)"] security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
[[package]] [[package]]
@ -534,6 +612,14 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "semver"
version = "2.13.0"
description = "Python helper for Semantic Versioning (http://semver.org/)"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "service-identity" name = "service-identity"
version = "21.1.0" version = "21.1.0"
@ -607,6 +693,14 @@ category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
[[package]]
name = "toml"
version = "0.10.2"
description = "Python Library for Tom's Obvious, Minimal Language"
category = "dev"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]] [[package]]
name = "twisted" name = "twisted"
version = "20.3.0" version = "20.3.0"
@ -652,6 +746,14 @@ all = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)"]
dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (==1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"] dev = ["wheel", "pytest (>=2.6.4)", "pytest-cov (>=1.8.1)", "pep8 (>=1.6.2)", "sphinx (>=1.2.3)", "pyenchant (>=1.6.6)", "sphinxcontrib-spelling (>=2.1.2)", "sphinx-rtd-theme (>=0.1.9)", "tox (>=2.1.1)", "mock (==1.3.0)", "twine (>=1.6.5)", "tox-gh-actions (>=2.2.0)"]
twisted = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)"] twisted = ["zope.interface (>=5.2.0)", "twisted (>=20.3.0)"]
[[package]]
name = "typed-ast"
version = "1.4.3"
description = "a fork of Python 2 and 3 ast modules with type comment support"
category = "dev"
optional = false
python-versions = "*"
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "3.10.0.0" version = "3.10.0.0"
@ -660,6 +762,19 @@ category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
[[package]]
name = "urllib3"
version = "1.26.6"
description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
[package.extras]
brotli = ["brotlipy (>=0.6.0)"]
secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[[package]] [[package]]
name = "uvicorn" name = "uvicorn"
version = "0.14.0" version = "0.14.0"
@ -726,9 +841,13 @@ 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 = "894aba6b24869dde338f2fdcb2b4ed6f62e6cfbc9a2f934c4ee73cf61b88c36c" content-hash = "e15a4f9c7fdbf146c22cdd14ac7e67c7fa975a1db04f10c34a6b4c8aa8c9c91e"
[metadata.files] [metadata.files]
appdirs = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
asgiref = [ asgiref = [
{file = "asgiref-3.3.4-py3-none-any.whl", hash = "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee"}, {file = "asgiref-3.3.4-py3-none-any.whl", hash = "sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee"},
{file = "asgiref-3.3.4.tar.gz", hash = "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"}, {file = "asgiref-3.3.4.tar.gz", hash = "sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"},
@ -745,6 +864,14 @@ automat = [
{file = "Automat-20.2.0-py2.py3-none-any.whl", hash = "sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111"}, {file = "Automat-20.2.0-py2.py3-none-any.whl", hash = "sha256:b6feb6455337df834f6c9962d6ccf771515b7d939bca142b29c20c2376bc6111"},
{file = "Automat-20.2.0.tar.gz", hash = "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33"}, {file = "Automat-20.2.0.tar.gz", hash = "sha256:7979803c74610e11ef0c0d68a2942b152df52da55336e0c9d58daf1831cbdf33"},
] ]
black = [
{file = "black-21.6b0-py3-none-any.whl", hash = "sha256:dfb8c5a069012b2ab1e972e7b908f5fb42b6bbabcba0a788b86dc05067c7d9c7"},
{file = "black-21.6b0.tar.gz", hash = "sha256:dc132348a88d103016726fe360cb9ede02cecf99b76e3660ce6c596be132ce04"},
]
certifi = [
{file = "certifi-2021.5.30-py2.py3-none-any.whl", hash = "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"},
{file = "certifi-2021.5.30.tar.gz", hash = "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee"},
]
cffi = [ cffi = [
{file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"}, {file = "cffi-1.14.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:bb89f306e5da99f4d922728ddcd6f7fcebb3241fc40edebcb7284d7514741991"},
{file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"}, {file = "cffi-1.14.5-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:34eff4b97f3d982fb93e2831e6750127d1355a923ebaeeb565407b3d2f8d41a1"},
@ -800,6 +927,10 @@ channels = [
{file = "channels-3.0.3-py3-none-any.whl", hash = "sha256:3f15bdd2138bb4796e76ea588a0a344b12a7964ea9b2e456f992fddb988a4317"}, {file = "channels-3.0.3-py3-none-any.whl", hash = "sha256:3f15bdd2138bb4796e76ea588a0a344b12a7964ea9b2e456f992fddb988a4317"},
{file = "channels-3.0.3.tar.gz", hash = "sha256:056b72e51080a517a0f33a0a30003e03833b551d75394d6636c885d4edb8188f"}, {file = "channels-3.0.3.tar.gz", hash = "sha256:056b72e51080a517a0f33a0a30003e03833b551d75394d6636c885d4edb8188f"},
] ]
chardet = [
{file = "chardet-4.0.0-py2.py3-none-any.whl", hash = "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"},
{file = "chardet-4.0.0.tar.gz", hash = "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa"},
]
click = [ click = [
{file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"}, {file = "click-8.0.1-py3-none-any.whl", hash = "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6"},
{file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"}, {file = "click-8.0.1.tar.gz", hash = "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a"},
@ -878,8 +1009,8 @@ hyperlink = [
{file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"}, {file = "hyperlink-21.0.0.tar.gz", hash = "sha256:427af957daa58bc909471c6c40f74c5450fa123dd093fc53efd2e91d2705a56b"},
] ]
idna = [ idna = [
{file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a"}, {file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
{file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3"}, {file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
] ]
importlib-metadata = [ importlib-metadata = [
{file = "importlib_metadata-4.4.0-py3-none-any.whl", hash = "sha256:960d52ba7c21377c990412aca380bf3642d734c2eaab78a2c39319f67c6a5786"}, {file = "importlib_metadata-4.4.0-py3-none-any.whl", hash = "sha256:960d52ba7c21377c990412aca380bf3642d734c2eaab78a2c39319f67c6a5786"},
@ -893,6 +1024,10 @@ mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
] ]
mypy-extensions = [
{file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"},
{file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
]
mysqlclient = [ mysqlclient = [
{file = "mysqlclient-2.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:3381ca1a4f37ff1155fcfde20836b46416d66531add8843f6aa6d968982731c3"}, {file = "mysqlclient-2.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:3381ca1a4f37ff1155fcfde20836b46416d66531add8843f6aa6d968982731c3"},
{file = "mysqlclient-2.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0ac0dd759c4ca02c35a9fedc24bc982cf75171651e8187c2495ec957a87dfff7"}, {file = "mysqlclient-2.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:0ac0dd759c4ca02c35a9fedc24bc982cf75171651e8187c2495ec957a87dfff7"},
@ -904,6 +1039,10 @@ 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"},
] ]
pathspec = [
{file = "pathspec-0.8.1-py2.py3-none-any.whl", hash = "sha256:aa0cb481c4041bf52ffa7b0d8fa6cd3e88a2ca4879c533c9153882ee2556790d"},
{file = "pathspec-0.8.1.tar.gz", hash = "sha256:86379d6b86d75816baba717e64b1a3a3469deb93bb76d613c9ce79edc5cb68fd"},
]
promise = [ promise = [
{file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"}, {file = "promise-2.3.tar.gz", hash = "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0"},
] ]
@ -973,9 +1112,52 @@ pytz = [
{file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"}, {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
{file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"}, {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
] ]
regex = [
{file = "regex-2021.7.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e6a1e5ca97d411a461041d057348e578dc344ecd2add3555aedba3b408c9f874"},
{file = "regex-2021.7.6-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:6afe6a627888c9a6cfbb603d1d017ce204cebd589d66e0703309b8048c3b0854"},
{file = "regex-2021.7.6-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:ccb3d2190476d00414aab36cca453e4596e8f70a206e2aa8db3d495a109153d2"},
{file = "regex-2021.7.6-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:ed693137a9187052fc46eedfafdcb74e09917166362af4cc4fddc3b31560e93d"},
{file = "regex-2021.7.6-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:99d8ab206a5270c1002bfcf25c51bf329ca951e5a169f3b43214fdda1f0b5f0d"},
{file = "regex-2021.7.6-cp36-cp36m-manylinux2014_i686.whl", hash = "sha256:b85ac458354165405c8a84725de7bbd07b00d9f72c31a60ffbf96bb38d3e25fa"},
{file = "regex-2021.7.6-cp36-cp36m-manylinux2014_x86_64.whl", hash = "sha256:3f5716923d3d0bfb27048242a6e0f14eecdb2e2a7fac47eda1d055288595f222"},
{file = "regex-2021.7.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5983c19d0beb6af88cb4d47afb92d96751fb3fa1784d8785b1cdf14c6519407"},
{file = "regex-2021.7.6-cp36-cp36m-win32.whl", hash = "sha256:c92831dac113a6e0ab28bc98f33781383fe294df1a2c3dfd1e850114da35fd5b"},
{file = "regex-2021.7.6-cp36-cp36m-win_amd64.whl", hash = "sha256:791aa1b300e5b6e5d597c37c346fb4d66422178566bbb426dd87eaae475053fb"},
{file = "regex-2021.7.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:59506c6e8bd9306cd8a41511e32d16d5d1194110b8cfe5a11d102d8b63cf945d"},
{file = "regex-2021.7.6-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:564a4c8a29435d1f2256ba247a0315325ea63335508ad8ed938a4f14c4116a5d"},
{file = "regex-2021.7.6-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:59c00bb8dd8775473cbfb967925ad2c3ecc8886b3b2d0c90a8e2707e06c743f0"},
{file = "regex-2021.7.6-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:9a854b916806c7e3b40e6616ac9e85d3cdb7649d9e6590653deb5b341a736cec"},
{file = "regex-2021.7.6-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:db2b7df831c3187a37f3bb80ec095f249fa276dbe09abd3d35297fc250385694"},
{file = "regex-2021.7.6-cp37-cp37m-manylinux2014_i686.whl", hash = "sha256:173bc44ff95bc1e96398c38f3629d86fa72e539c79900283afa895694229fe6a"},
{file = "regex-2021.7.6-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:15dddb19823f5147e7517bb12635b3c82e6f2a3a6b696cc3e321522e8b9308ad"},
{file = "regex-2021.7.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ddeabc7652024803666ea09f32dd1ed40a0579b6fbb2a213eba590683025895"},
{file = "regex-2021.7.6-cp37-cp37m-win32.whl", hash = "sha256:f080248b3e029d052bf74a897b9d74cfb7643537fbde97fe8225a6467fb559b5"},
{file = "regex-2021.7.6-cp37-cp37m-win_amd64.whl", hash = "sha256:d8bbce0c96462dbceaa7ac4a7dfbbee92745b801b24bce10a98d2f2b1ea9432f"},
{file = "regex-2021.7.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edd1a68f79b89b0c57339bce297ad5d5ffcc6ae7e1afdb10f1947706ed066c9c"},
{file = "regex-2021.7.6-cp38-cp38-manylinux1_i686.whl", hash = "sha256:422dec1e7cbb2efbbe50e3f1de36b82906def93ed48da12d1714cabcd993d7f0"},
{file = "regex-2021.7.6-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cbe23b323988a04c3e5b0c387fe3f8f363bf06c0680daf775875d979e376bd26"},
{file = "regex-2021.7.6-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:0eb2c6e0fcec5e0f1d3bcc1133556563222a2ffd2211945d7b1480c1b1a42a6f"},
{file = "regex-2021.7.6-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:1c78780bf46d620ff4fff40728f98b8afd8b8e35c3efd638c7df67be2d5cddbf"},
{file = "regex-2021.7.6-cp38-cp38-manylinux2014_i686.whl", hash = "sha256:bc84fb254a875a9f66616ed4538542fb7965db6356f3df571d783f7c8d256edd"},
{file = "regex-2021.7.6-cp38-cp38-manylinux2014_x86_64.whl", hash = "sha256:598c0a79b4b851b922f504f9f39a863d83ebdfff787261a5ed061c21e67dd761"},
{file = "regex-2021.7.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:875c355360d0f8d3d827e462b29ea7682bf52327d500a4f837e934e9e4656068"},
{file = "regex-2021.7.6-cp38-cp38-win32.whl", hash = "sha256:e586f448df2bbc37dfadccdb7ccd125c62b4348cb90c10840d695592aa1b29e0"},
{file = "regex-2021.7.6-cp38-cp38-win_amd64.whl", hash = "sha256:2fe5e71e11a54e3355fa272137d521a40aace5d937d08b494bed4529964c19c4"},
{file = "regex-2021.7.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6110bab7eab6566492618540c70edd4d2a18f40ca1d51d704f1d81c52d245026"},
{file = "regex-2021.7.6-cp39-cp39-manylinux1_i686.whl", hash = "sha256:4f64fc59fd5b10557f6cd0937e1597af022ad9b27d454e182485f1db3008f417"},
{file = "regex-2021.7.6-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:89e5528803566af4df368df2d6f503c84fbfb8249e6631c7b025fe23e6bd0cde"},
{file = "regex-2021.7.6-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:2366fe0479ca0e9afa534174faa2beae87847d208d457d200183f28c74eaea59"},
{file = "regex-2021.7.6-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:f9392a4555f3e4cb45310a65b403d86b589adc773898c25a39184b1ba4db8985"},
{file = "regex-2021.7.6-cp39-cp39-manylinux2014_i686.whl", hash = "sha256:2bceeb491b38225b1fee4517107b8491ba54fba77cf22a12e996d96a3c55613d"},
{file = "regex-2021.7.6-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:f98dc35ab9a749276f1a4a38ab3e0e2ba1662ce710f6530f5b0a6656f1c32b58"},
{file = "regex-2021.7.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:319eb2a8d0888fa6f1d9177705f341bc9455a2c8aca130016e52c7fe8d6c37a3"},
{file = "regex-2021.7.6-cp39-cp39-win32.whl", hash = "sha256:eaf58b9e30e0e546cdc3ac06cf9165a1ca5b3de8221e9df679416ca667972035"},
{file = "regex-2021.7.6-cp39-cp39-win_amd64.whl", hash = "sha256:4c9c3155fe74269f61e27617529b7f09552fbb12e44b1189cebbdb24294e6e1c"},
{file = "regex-2021.7.6.tar.gz", hash = "sha256:8394e266005f2d8c6f0bc6780001f7afa3ef81a7a2111fa35058ded6fce79e4d"},
]
requests = [ requests = [
{file = "requests-2.15.1-py2.py3-none-any.whl", hash = "sha256:ff753b2196cd18b1bbeddc9dcd5c864056599f7a7d9a4fb5677e723efa2b7fb9"}, {file = "requests-2.25.1-py2.py3-none-any.whl", hash = "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"},
{file = "requests-2.15.1.tar.gz", hash = "sha256:e5659b9315a0610505e050bb7190bf6fa2ccee1ac295f2b760ef9d8a03ebbb2e"}, {file = "requests-2.25.1.tar.gz", hash = "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804"},
] ]
requests-oauthlib = [ requests-oauthlib = [
{file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"}, {file = "requests-oauthlib-1.3.0.tar.gz", hash = "sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"},
@ -986,6 +1168,10 @@ rx = [
{file = "Rx-1.6.1-py2.py3-none-any.whl", hash = "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"}, {file = "Rx-1.6.1-py2.py3-none-any.whl", hash = "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105"},
{file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"}, {file = "Rx-1.6.1.tar.gz", hash = "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23"},
] ]
semver = [
{file = "semver-2.13.0-py2.py3-none-any.whl", hash = "sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4"},
{file = "semver-2.13.0.tar.gz", hash = "sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"},
]
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"},
@ -1007,6 +1193,10 @@ sqlparse = [
{file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"}, {file = "sqlparse-0.4.1-py3-none-any.whl", hash = "sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0"},
{file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"}, {file = "sqlparse-0.4.1.tar.gz", hash = "sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"},
] ]
toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
]
twisted = [ twisted = [
{file = "Twisted-20.3.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:cdbc4c7f0cd7a2218b575844e970f05a1be1861c607b0e048c9bceca0c4d42f7"}, {file = "Twisted-20.3.0-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:cdbc4c7f0cd7a2218b575844e970f05a1be1861c607b0e048c9bceca0c4d42f7"},
{file = "Twisted-20.3.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d267125cc0f1e8a0eed6319ba4ac7477da9b78a535601c49ecd20c875576433a"}, {file = "Twisted-20.3.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:d267125cc0f1e8a0eed6319ba4ac7477da9b78a535601c49ecd20c875576433a"},
@ -1036,11 +1226,47 @@ txaio = [
{file = "txaio-21.2.1-py2.py3-none-any.whl", hash = "sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"}, {file = "txaio-21.2.1-py2.py3-none-any.whl", hash = "sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"},
{file = "txaio-21.2.1.tar.gz", hash = "sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8"}, {file = "txaio-21.2.1.tar.gz", hash = "sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8"},
] ]
typed-ast = [
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"},
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"},
{file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"},
{file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"},
{file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"},
{file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"},
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"},
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"},
{file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"},
{file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"},
{file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"},
{file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"},
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"},
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"},
{file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"},
{file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"},
{file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"},
{file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"},
{file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"},
{file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"},
{file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"},
{file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"},
{file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"},
{file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"},
{file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"},
{file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"},
{file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"},
{file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"},
{file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"},
{file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"},
]
typing-extensions = [ typing-extensions = [
{file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"}, {file = "typing_extensions-3.10.0.0-py2-none-any.whl", hash = "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497"},
{file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"}, {file = "typing_extensions-3.10.0.0-py3-none-any.whl", hash = "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"},
{file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"}, {file = "typing_extensions-3.10.0.0.tar.gz", hash = "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342"},
] ]
urllib3 = [
{file = "urllib3-1.26.6-py2.py3-none-any.whl", hash = "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4"},
{file = "urllib3-1.26.6.tar.gz", hash = "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"},
]
uvicorn = [ uvicorn = [
{file = "uvicorn-0.14.0-py3-none-any.whl", hash = "sha256:2a76bb359171a504b3d1c853409af3adbfa5cef374a4a59e5881945a97a93eae"}, {file = "uvicorn-0.14.0-py3-none-any.whl", hash = "sha256:2a76bb359171a504b3d1c853409af3adbfa5cef374a4a59e5881945a97a93eae"},
{file = "uvicorn-0.14.0.tar.gz", hash = "sha256:45ad7dfaaa7d55cab4cd1e85e03f27e9d60bc067ddc59db52a2b0aeca8870292"}, {file = "uvicorn-0.14.0.tar.gz", hash = "sha256:45ad7dfaaa7d55cab4cd1e85e03f27e9d60bc067ddc59db52a2b0aeca8870292"},

View File

@ -20,9 +20,12 @@ mysqlclient = "^2.0.3"
uvicorn = "^0.14.0" uvicorn = "^0.14.0"
gunicorn = "^20.1.0" gunicorn = "^20.1.0"
Twisted = "20.3.0" Twisted = "20.3.0"
semver = "^2.13.0"
requests = "^2.25.1"
[tool.poetry.dev-dependencies] [tool.poetry.dev-dependencies]
flake8 = "^3.9.2" flake8 = "^3.9.2"
black = {version = "^21.6b0", allow-prereleases = true}
[build-system] [build-system]
requires = ["poetry-core>=1.0.0"] requires = ["poetry-core>=1.0.0"]

View File

@ -27,6 +27,12 @@ export interface InstanceInfo {
login_enabled: boolean login_enabled: boolean
} }
export interface Gateway {
host: string
port: number
url: string
}
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class InstanceInfoResolver implements Resolve<Observable<InstanceInfo>> { export class InstanceInfoResolver implements Resolve<Observable<InstanceInfo>> {
constructor (private http: HttpClient) { } constructor (private http: HttpClient) { }

View File

@ -3,9 +3,11 @@
.d-flex.align-items-center.py-2.px-4 .d-flex.align-items-center.py-2.px-4
.me-auto .me-auto
label Active config label Active config
.title {{configService.activeConfig.modified_at}} .title
fa-icon([icon]='_configIcon')
span.ms-2 {{configService.activeConfig.created_at|date:"medium"}}
button.btn.btn-semi.me-2((click)='configService.duplicateConfig()') button.btn.btn-semi.me-2((click)='configService.duplicateActiveConfig()')
fa-icon([icon]='_copyIcon', [fixedWidth]='true') fa-icon([icon]='_copyIcon', [fixedWidth]='true')
button.btn.btn-semi((click)='deleteConfig()') button.btn.btn-semi((click)='deleteConfig()')
@ -16,25 +18,26 @@
div(ngbDropdown) div(ngbDropdown)
button.btn.btn-semi(ngbDropdownToggle) {{configService.activeVersion.version}} button.btn.btn-semi(ngbDropdownToggle) {{configService.activeVersion.version}}
div(ngbDropdownMenu) div(ngbDropdownMenu)
a( button(
*ngFor='let version of versions', *ngFor='let version of configService.versions',
ngbDropdownItem, ngbDropdownItem,
[class.active]='version == configService.activeVersion', [class.active]='version == configService.activeVersion',
(click)='selectVersion(version)' (click)='selectVersion(version)'
) {{version.version}} ) {{version.version}}
div(*ngIf='configService.configs.length > 1') .px-4.pt-3(*ngIf='configService.configs.length > 1')
.dropdown-header All configs h5 Other configs
.list-group.list-group-light
ng-container(*ngFor='let config of configService.configs') ng-container(*ngFor='let config of configService.configs')
a( button.list-group-item.list-group-item-action(
*ngIf='config !== configService.activeConfig', *ngIf='config !== configService.activeConfig',
ngbDropdownItem, (click)='selectConfig(config)'
(click)='selectConfig(config)', )
href='#' fa-icon([icon]='_configIcon')
) Config modified at {{config.modified_at}} span Config created at {{config.created_at|date:"medium"}}
.p-3 .py-3.px-4
button.btn.btn-semi.w-100((click)='configService.createNewConfig()') button.btn.btn-semi.w-100((click)='createNewConfig()')
fa-icon([icon]='_addIcon', [fixedWidth]='true') fa-icon([icon]='_addIcon', [fixedWidth]='true')
span New config span New config

View File

@ -2,7 +2,8 @@ import { Component } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { AppConnectorService } from '../services/appConnector.service' import { AppConnectorService } from '../services/appConnector.service'
import { ConfigService } from '../services/config.service' import { ConfigService } from '../services/config.service'
import { faPlus } from '@fortawesome/free-solid-svg-icons' import { faCopy, faFile, faPlus, faTrash } from '@fortawesome/free-solid-svg-icons'
import { Config, Version } from '../api'
@Component({ @Component({
selector: 'config-modal', selector: 'config-modal',
@ -11,6 +12,9 @@ import { faPlus } from '@fortawesome/free-solid-svg-icons'
}) })
export class ConfigModalComponent { export class ConfigModalComponent {
_addIcon = faPlus _addIcon = faPlus
_copyIcon = faCopy
_deleteIcon = faTrash
_configIcon = faFile
constructor ( constructor (
private modalInstance: NgbActiveModal, private modalInstance: NgbActiveModal,
@ -26,4 +30,20 @@ export class ConfigModalComponent {
this.modalInstance.dismiss() this.modalInstance.dismiss()
} }
async createNewConfig () {
const config = await this.configService.createNewConfig()
await this.configService.selectConfig(config)
this.modalInstance.dismiss()
}
async selectConfig (config: Config) {
await this.configService.selectConfig(config)
this.modalInstance.dismiss()
}
async selectVersion (version: Version) {
await this.configService.selectVersion(version)
this.modalInstance.dismiss()
}
} }

View File

@ -16,8 +16,8 @@
.intro .intro
h1 Hey. h1 Hey.
div My name is Eugene and I've built a nice terminal app, #[em.ms-1 just for you]. div My name is Eugene and I've built a nice terminal app, #[em.ms-1 just for you].
div Crossplatform, local, SSH, serial - it's all there. div Crossplatform, local, SSH, serial, Telnet - it's all there.
div Go on, try it out 👇 div Here's a demo 👇
iframe(#iframe) iframe(#iframe)

View File

@ -103,7 +103,7 @@ export class HomeComponent {
async ngAfterViewInit () { async ngAfterViewInit () {
const versions = await this.http.get('/api/1/versions').toPromise() const versions = await this.http.get('/api/1/versions').toPromise()
versions.sort((a, b) => semverGT(a, b)) versions.sort((a, b) => semverGT(a.version, b.version))
this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, versions[0]) this.connector = new DemoConnector(this.iframe.nativeElement.contentWindow, versions[0])
this.iframe.nativeElement.src = '/terminal' this.iframe.nativeElement.src = '/terminal'
} }

View File

@ -1,13 +1,13 @@
.sidebar .sidebar
img.logo(src='{{_logo}}') img.logo(src='{{_logo}}')
button.btn((click)='openConfig()') button.btn.mt-auto((click)='openConfig()')
fa-icon([icon]='_cogIcon', [fixedWidth]='true') fa-icon([icon]='_configIcon', [fixedWidth]='true')
button.btn((click)='openSettings()') button.btn((click)='openSettings()')
fa-icon([icon]='_settingsIcon', [fixedWidth]='true') fa-icon([icon]='_settingsIcon', [fixedWidth]='true')
button.btn.mt-auto((click)='logout()') button.btn.mt-3((click)='logout()')
fa-icon([icon]='_logoutIcon', [fixedWidth]='true') fa-icon([icon]='_logoutIcon', [fixedWidth]='true')
.terminal([hidden]='!showApp') .terminal([hidden]='!showApp')

View File

@ -2,7 +2,7 @@ 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, faCopy, faTrash, faPlus, faSignOutAlt } from '@fortawesome/free-solid-svg-icons' import { faCog, faFile, 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 { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { SettingsModalComponent } from './settingsModal.component' import { SettingsModalComponent } from './settingsModal.component'
@ -19,12 +19,10 @@ import { Router } from '@angular/router'
}) })
export class MainComponent { export class MainComponent {
_logo = require('../assets/logo.svg') _logo = require('../assets/logo.svg')
_cogIcon = faCog
_settingsIcon = faCog _settingsIcon = faCog
_logoutIcon = faSignOutAlt _logoutIcon = faSignOutAlt
_copyIcon = faCopy
_addIcon = faPlus _addIcon = faPlus
_deleteIcon = faTrash _configIcon = faFile
showApp = false showApp = false

View File

@ -1,5 +1,6 @@
.modal-header .modal-header
h5.modal-title Settings h5.modal-title Settings
.modal-body .modal-body
.mb-3 .mb-3
.form-check.form-switch .form-check.form-switch
@ -27,6 +28,13 @@
) )
label Gateway authentication token label Gateway authentication token
div(*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}}
.modal-footer .modal-footer
button.btn.btn-primary((click)='apply()') Apply button.btn.btn-primary((click)='apply()') Apply
button.btn.btn-secondary((click)='cancel()') Cancel button.btn.btn-secondary((click)='cancel()') Cancel

View File

@ -3,6 +3,7 @@ import { LoginService } from '../services/login.service'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { User } from '../api' import { User } from '../api'
import { AppConnectorService } from '../services/appConnector.service'
@Component({ @Component({
selector: 'settings-modal', selector: 'settings-modal',
@ -14,6 +15,7 @@ export class SettingsModalComponent {
customGatewayEnabled = false customGatewayEnabled = false
constructor ( constructor (
public appConnector: AppConnectorService,
private modalInstance: NgbActiveModal, private modalInstance: NgbActiveModal,
private loginService: LoginService, private loginService: LoginService,
) { ) {

View File

@ -4,13 +4,15 @@ 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' import { Config, Gateway, Version } from '../api'
export class SocketProxy { export class SocketProxy {
connect$ = new Subject<void>() connect$ = new Subject<void>()
data$ = new Subject<Buffer>() data$ = new Subject<Buffer>()
error$ = new Subject<Buffer>() error$ = new Subject<Buffer>()
close$ = new Subject<Buffer>()
url: string
webSocket: WebSocket webSocket: WebSocket
initialBuffer: Buffer initialBuffer: Buffer
options: { options: {
@ -22,12 +24,18 @@ export class SocketProxy {
this.initialBuffer = Buffer.from('') this.initialBuffer = Buffer.from('')
} }
connect (options) { async connect (options) {
this.options = options this.options = options
this.webSocket = new WebSocket( this.url = this.appConnector.loginService.user.custom_connection_gateway
this.appConnector.loginService.user.custom_connection_gateway || if (!this.url) {
`ws://${location.host}/api/1/gateway/tcp` try {
) this.url = (await this.appConnector.chooseConnectionGateway()).url
} catch (err) {
this.error$.next(err)
return
}
}
this.webSocket = new WebSocket(this.url)
this.webSocket.onmessage = async event => { this.webSocket.onmessage = async event => {
if (typeof(event.data) === 'string') { if (typeof(event.data) === 'string') {
this.handleServiceMessage(JSON.parse(event.data)) this.handleServiceMessage(JSON.parse(event.data))
@ -35,6 +43,9 @@ export class SocketProxy {
this.data$.next(Buffer.from(await event.data.arrayBuffer())) this.data$.next(Buffer.from(await event.data.arrayBuffer()))
} }
} }
this.webSocket.onclose = () => {
this.close()
}
} }
handleServiceMessage (msg) { handleServiceMessage (msg) {
@ -68,20 +79,23 @@ export class SocketProxy {
} }
write (chunk: Buffer): void { write (chunk: Buffer): void {
if (!this.webSocket.readyState) { if (!this.webSocket?.readyState) {
this.initialBuffer = Buffer.concat([this.initialBuffer, chunk]) this.initialBuffer = Buffer.concat([this.initialBuffer, chunk])
} else { } else {
this.webSocket.send(chunk) this.webSocket.send(chunk)
} }
} }
close (error: Error): void { close (error?: Error): void {
this.webSocket.close()
if (error) { if (error) {
this.error$.next(error) this.error$.next(error)
} }
this.connect$.complete() this.connect$.complete()
this.data$.complete() this.data$.complete()
this.error$.complete() this.error$.complete()
this.close$.next()
this.close$.complete()
} }
} }
@ -90,6 +104,7 @@ export class AppConnectorService {
private configUpdate = new Subject<string>() private configUpdate = new Subject<string>()
private config: Config private config: Config
private version: Version private version: Version
sockets: SocketProxy[] = []
constructor ( constructor (
private http: HttpClient, private http: HttpClient,
@ -135,6 +150,15 @@ export class AppConnectorService {
} }
createSocket () { createSocket () {
return new SocketProxy(this) const socket = new SocketProxy(this)
this.sockets.push(socket)
socket.close$.subscribe(() => {
this.sockets = this.sockets.filter(x => x !== socket)
})
return socket
}
async chooseConnectionGateway (): Promise<Gateway> {
return await this.http.post('/api/1/gateways/choose', {}).toPromise()
} }
} }

View File

@ -33,11 +33,17 @@ export class ConfigService {
await this.http.put('/api/1/user', this.user).toPromise() await this.http.put('/api/1/user', this.user).toPromise()
} }
async createNewConfig () { async createNewConfig (): Promise<Config> {
this.configs.push(await this.http.post('/api/1/configs', { const config = await this.http.post('/api/1/configs', {
content: '{}', content: '{}',
last_used_with_version: this._activeVersion.version, last_used_with_version: this._activeVersion?.version ?? this.getLatestStableVersion().version,
}).toPromise()) }).toPromise()
this.configs.push(config)
return config
}
getLatestStableVersion () {
return this.versions[0]
} }
async duplicateActiveConfig () { async duplicateActiveConfig () {
@ -60,6 +66,8 @@ export class ConfigService {
this._activeConfig = config this._activeConfig = config
this.activeConfig$.next(config) this.activeConfig$.next(config)
this.selectVersion(matchingVersion) this.selectVersion(matchingVersion)
this.loginService.user.active_config = config.id
await this.loginService.updateUser()
} }
async selectDefaultConfig () { async selectDefaultConfig () {
@ -71,7 +79,7 @@ export class ConfigService {
private async init () { private async init () {
this.configs = await this.http.get('/api/1/configs').toPromise() this.configs = await this.http.get('/api/1/configs').toPromise()
this.versions = await this.http.get('/api/1/versions').toPromise() this.versions = await this.http.get('/api/1/versions').toPromise()
this.versions.sort((a, b) => semverGT(a, b)) this.versions.sort((a, b) => semverGT(a.version, b.version))
if (!this.configs.length) { if (!this.configs.length) {
await this.createNewConfig() await this.createNewConfig()

View File

@ -27,16 +27,26 @@ async function start () {
return window['module'].exports return window['module'].exports
} }
async function prefetchURL (url) {
await (await fetch(url)).text()
}
const baseUrl = `${connector.getDistURL()}/${appVersion}` const baseUrl = `${connector.getDistURL()}/${appVersion}`
await webRequire(`${baseUrl}/tabby-web-container/dist/preload.js`) const coreURLs = [
await webRequire(`${baseUrl}/tabby-web-container/dist/bundle.js`) `${baseUrl}/tabby-web-container/dist/preload.js`,
`${baseUrl}/tabby-web-container/dist/bundle.js`,
]
await Promise.all(coreURLs.map(prefetchURL))
for (const url of coreURLs) {
await webRequire(url)
}
const tabby = window['Tabby'] const tabby = window['Tabby']
const pluginModules = [] const pluginURLs = connector.getPluginsToLoad().map(x => `${baseUrl}/${x}`)
for (const plugin of connector.getPluginsToLoad()) { const pluginModules = await tabby.loadPlugins(pluginURLs)
pluginModules.push(await tabby.loadPlugin(`${baseUrl}/${plugin}`))
}
document.querySelector('app-root')['style'].display = 'flex' document.querySelector('app-root')['style'].display = 'flex'

View File

@ -52,8 +52,8 @@ $font-size-sm: .85rem;
$line-height-base: 1.6; $line-height-base: 1.6;
$border-radius: .4rem; $border-radius: .35rem;
$border-radius-lg: .6rem; $border-radius-lg: .35rem;
$border-radius-sm: .2rem; $border-radius-sm: .2rem;
$box-shadow: 0 .5rem 1rem rgba($black, .5) !default; $box-shadow: 0 .5rem 1rem rgba($black, .5) !default;
@ -79,6 +79,7 @@ $link-hover-decoration: none;
$component-active-color: $white; $component-active-color: $white;
$component-active-bg: $blue; $component-active-bg: $blue;
$list-group-color: $body-color;
$list-group-bg: $table-bg; $list-group-bg: $table-bg;
$list-group-border-color: $table-border-color; $list-group-border-color: $table-border-color;
@ -145,6 +146,7 @@ $navbar-padding-x: 0;
$dropdown-bg: $body-bg; $dropdown-bg: $body-bg;
$dropdown-color: $body-color; $dropdown-color: $body-color;
$dropdown-border-width: 1px; $dropdown-border-width: 1px;
$dropdown-border-color: #ffffff24;
$dropdown-header-color: $gray-500; $dropdown-header-color: $gray-500;
$dropdown-link-color: $body-color; $dropdown-link-color: $body-color;
@ -182,7 +184,10 @@ $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-width: 0; $modal-footer-border-width: 0;
$modal-content-border-width: 0;
$modal-content-border-color: #ffffff24;
$modal-content-border-width: 1px;
$progress-bg: $table-bg; $progress-bg: $table-bg;
$progress-height: 3px; $progress-height: 3px;

View File

@ -1,11 +1,14 @@
from django.contrib import admin from django.contrib import admin
from django.contrib.auth.admin import UserAdmin from django.contrib.auth.admin import UserAdmin
from .models import User, Config from .models import Gateway, User, Config
class CustomUserAdmin(UserAdmin): class CustomUserAdmin(UserAdmin):
fieldsets = UserAdmin.fieldsets + ( fieldsets = UserAdmin.fieldsets + (
(None, {'fields': ('custom_connection_gateway', 'custom_connection_gateway_token')}), (None, {'fields': ('custom_connection_gateway', 'custom_connection_gateway_token')}),
) )
admin.site.register(User, CustomUserAdmin) admin.site.register(User, CustomUserAdmin)
admin.site.register(Config) admin.site.register(Config)
admin.site.register(Gateway)

View File

@ -1,9 +1,10 @@
import os import os
import random
from dataclasses import dataclass 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.exceptions import PermissionDenied, NotFound
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, UpdateModelMixin from rest_framework.mixins import ListModelMixin, RetrieveModelMixin, UpdateModelMixin
@ -12,7 +13,7 @@ from rest_framework.viewsets import GenericViewSet, ModelViewSet
from rest_framework.serializers import ModelSerializer, Serializer from rest_framework.serializers import ModelSerializer, Serializer
from rest_framework_dataclasses.serializers import DataclassSerializer from rest_framework_dataclasses.serializers import DataclassSerializer
from .models import Config, User from .models import Config, Gateway, User
@dataclass @dataclass
@ -25,6 +26,17 @@ class AppVersionSerializer(DataclassSerializer):
dataclass = AppVersion dataclass = AppVersion
class GatewaySerializer(ModelSerializer):
url = fields.SerializerMethodField()
class Meta:
fields = '__all__'
model = Gateway
def get_url(self, gw):
return f'{"wss" if gw.secure else "ws"}://{gw.host}:{gw.port}/'
class ConfigSerializer(ModelSerializer): class ConfigSerializer(ModelSerializer):
class Meta: class Meta:
model = Config model = Config
@ -103,3 +115,14 @@ class InstanceInfoViewSet(RetrieveModelMixin, GenericViewSet):
return { return {
'login_enabled': settings.ENABLE_LOGIN, 'login_enabled': settings.ENABLE_LOGIN,
} }
class ChooseGatewayViewSet(RetrieveModelMixin, GenericViewSet):
queryset = Gateway.objects.filter(enabled=True)
serializer_class = GatewaySerializer
def get_object(self):
gateways = list(self.queryset)
if not len(gateways):
raise NotFound()
return random.choice(gateways)

View File

View File

@ -0,0 +1,56 @@
import logging
import requests
import shutil
import subprocess
import tempfile
from django.core.management.base import BaseCommand
from django.conf import settings
from pathlib import Path
class Command(BaseCommand):
help = 'Downloads a new app version'
def add_arguments(self, parser):
parser.add_argument('version', type=str)
def handle(self, *args, **options):
version = options['version']
target: Path = settings.APP_DIST_PATH / version
plugin_list = [
'tabby-web-container',
'tabby-core',
'tabby-settings',
'tabby-terminal',
'tabby-ssh',
'tabby-community-color-schemes',
'tabby-web',
'tabby-web-demo',
]
with tempfile.TemporaryDirectory() as tempdir:
tempdir = Path(tempdir)
for plugin in plugin_list:
logging.info(f'Resolving {plugin}@{version}')
response = requests.get(f'{settings.NPM_REGISTRY}/{plugin}/{version}')
response.raise_for_status()
info = response.json()
url = info['dist']['tarball']
logging.info(f'Downloading {plugin}@{version} from {url}')
response = requests.get(url)
with tempfile.NamedTemporaryFile('wb') as f:
f.write(response.content)
plugin_final_target = Path(tempdir) / plugin
with tempfile.TemporaryDirectory() as extraction_tmp:
subprocess.check_call(
['tar', '-xzf', f.name, '-C', str(extraction_tmp)]
)
shutil.move(Path(extraction_tmp) / 'package', plugin_final_target)
if target.exists():
shutil.rmtree(target)
shutil.copytree(tempdir, target)

View File

@ -1,8 +1,10 @@
# Generated by Django 3.2.3 on 2021-06-05 21:23 # Generated by Django 3.2.3 on 2021-07-08 17:43
from django.conf import settings
import django.contrib.auth.models import django.contrib.auth.models
import django.contrib.auth.validators import django.contrib.auth.validators
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
@ -29,8 +31,11 @@ class Migration(migrations.Migration):
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups')), ('active_version', models.CharField(max_length=32, null=True)),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions')), ('custom_connection_gateway', models.CharField(max_length=255, null=True)),
('custom_connection_gateway_token', models.CharField(max_length=255, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
], ],
options={ options={
'verbose_name': 'user', 'verbose_name': 'user',
@ -41,4 +46,30 @@ class Migration(migrations.Migration):
('objects', django.contrib.auth.models.UserManager()), ('objects', django.contrib.auth.models.UserManager()),
], ],
), ),
migrations.CreateModel(
name='Config',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField(default='{}')),
('last_used_with_version', models.CharField(max_length=32, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='configs', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='user',
name='active_config',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='app.config'),
),
migrations.AddField(
model_name='user',
name='groups',
field=models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.Group', verbose_name='groups'),
),
migrations.AddField(
model_name='user',
name='user_permissions',
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'),
),
] ]

View File

@ -1,48 +0,0 @@
# Generated by Django 3.2.3 on 2021-06-05 21:37
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('app', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='active_version',
field=models.CharField(max_length=32, null=True),
),
migrations.AddField(
model_name='user',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now),
preserve_default=False,
),
migrations.AddField(
model_name='user',
name='modified_at',
field=models.DateTimeField(auto_now=True),
),
migrations.CreateModel(
name='Config',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('content', models.TextField(default='{}')),
('last_used_with_version', models.CharField(max_length=32, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('modified_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='configs', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='user',
name='active_config',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='+', to='app.config'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.3 on 2021-07-08 20:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Gateway',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('host', models.CharField(max_length=255)),
('port', models.IntegerField(default=1234)),
('enabled', models.BooleanField(default=True)),
('secure', models.BooleanField(default=True)),
],
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.3 on 2021-06-20 20:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0002_auto_20210605_2137'),
]
operations = [
migrations.AddField(
model_name='user',
name='custom_connection_gateway',
field=models.CharField(max_length=255, null=True),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.3 on 2021-06-20 20:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('app', '0003_user_custom_connection_gateway'),
]
operations = [
migrations.AddField(
model_name='user',
name='custom_connection_gateway_token',
field=models.CharField(max_length=255, null=True),
),
]

View File

@ -22,7 +22,11 @@ class User(AbstractUser):
modified_at = models.DateTimeField(auto_now=True) modified_at = models.DateTimeField(auto_now=True)
# @receiver(user_logged_in) class Gateway(models.Model):
# def post_login(sender, user, request, **kwargs): host = models.CharField(max_length=255)
# if not user.active_config: port = models.IntegerField(default=1234)
# user.active_config = Config.objects.filter() enabled = models.BooleanField(default=True)
secure = models.BooleanField(default=True)
def __str__(self):
return f'{self.host}:{self.port}'

View File

@ -14,6 +14,7 @@ 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', 'put': 'update'})), path('api/1/user', api.UserViewSet.as_view({'get': 'retrieve', 'put': 'update'})),
path('api/1/instance-info', api.InstanceInfoViewSet.as_view({'get': 'retrieve'})), path('api/1/instance-info', api.InstanceInfoViewSet.as_view({'get': 'retrieve'})),
path('api/1/gateways/choose', api.ChooseGatewayViewSet.as_view({'post': 'retrieve'})),
re_path('^(|login|app)$', views.IndexView.as_view()), re_path('^(|login|app)$', views.IndexView.as_view()),

View File

@ -21,6 +21,6 @@ class AppDistView(APIView):
return static.serve(request, os.path.join(version, path), document_root=str(settings.APP_DIST_PATH)) return static.serve(request, os.path.join(version, path), document_root=str(settings.APP_DIST_PATH))
class BuildView(APIView): # class BuildView(APIView):
def get(self, request, path=None, format=None): # def get(self, request, path=None, format=None):
return static.serve(request, path, document_root=str(settings.BASE_DIR / 'build')) # return static.serve(request, path, document_root=str(settings.BASE_DIR / 'build'))

View File

@ -109,6 +109,29 @@ USE_L10N = True
USE_TZ = True USE_TZ = True
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'simple': {
'format': '%(levelname)s %(message)s'
},
},
'handlers': {
'console': {
'level': 'INFO',
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
},
'loggers': {
'': {
'handlers': ['console'],
'propagate': False,
'level': 'INFO',
},
},
}
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/ # https://docs.djangoproject.com/en/3.2/howto/static-files/
@ -140,7 +163,8 @@ SOCIAL_AUTH_GITHUB_SCOPE = ['read:user', 'user:email']
LOGIN_REDIRECT_URL = '/app' LOGIN_REDIRECT_URL = '/app'
APP_DIST_PATH = os.getenv('APP_DIST_PATH', BASE_DIR / 'app-dist') APP_DIST_PATH = Path(os.getenv('APP_DIST_PATH', BASE_DIR / 'app-dist'))
NPM_REGISTRY = os.getenv('NPM_REGISTRY', 'https://registry.npmjs.org').rstrip('/')
for key in [ for key in [
'SOCIAL_AUTH_GITHUB_KEY', 'SOCIAL_AUTH_GITHUB_KEY',
@ -168,6 +192,12 @@ for key in [
globals()[key] = int(globals()[key]) if globals()[key] else None globals()[key] = int(globals()[key]) if globals()[key] else None
for key in [
'ENABLE_LOGIN',
]:
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',