This commit is contained in:
Eugene Pankov
2021-08-25 01:25:49 +02:00
parent f0497a081a
commit e541269392
12 changed files with 123 additions and 14 deletions

22
README.md Normal file
View File

@@ -0,0 +1,22 @@
# Using Docker images
Tabby Web consists of two Docker images - `backend` and `frontend`. See an example set up in `docker-compose.yml`
## Environment variables
### Frontend
* `BACKEND_URL`
* `WEB_CONCURRENCY`
### Backend
* `DATABASE_URL` (required).
* `FRONTEND_URL`
* `APP_DIST_STORAGE`: a `file://`, `s3://`, or `gcs://` URL to store app distros in.
* `SOCIAL_AUTH_*_KEY` & `SOCIAL_AUTH_*_SECRET`: social login credentials, supported providers are `GITHUB`, `GITLAB`, `MICROSOFT_GRAPH` and `GOOGLE_OAUTH2`.
* `ENABLE_HOMEPAGE`: set to `False` to disable the homepage and always redirect to the app.
## Installing Tabby app versions
* `docker-compose run backend ./manage.py add_version 1.0.156-nightly.1`

0
app-dist/.gitkeep Normal file
View File

View File

@@ -81,6 +81,7 @@ class AppVersionViewSet(ListModelMixin, GenericViewSet):
return [ return [
self._get_version(x['name']) self._get_version(x['name'])
for x in fs.listdir(settings.APP_DIST_STORAGE) for x in fs.listdir(settings.APP_DIST_STORAGE)
if x['type'] == 'directory'
] ]
def _get_version(self, dir): def _get_version(self, dir):

View File

@@ -1,6 +1,8 @@
import fsspec import fsspec
import os
from fsspec.implementations.local import LocalFileSystem
from django.conf import settings from django.conf import settings
from django.http.response import HttpResponseRedirect from django.http.response import FileResponse, HttpResponseNotFound, HttpResponseRedirect
from django.views import static from django.views import static
from rest_framework.views import APIView from rest_framework.views import APIView
from urllib.parse import urlparse from urllib.parse import urlparse
@@ -24,4 +26,9 @@ class AppDistView(APIView):
def get(self, request, version=None, path=None, format=None): def get(self, request, version=None, path=None, format=None):
fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme) fs = fsspec.filesystem(urlparse(settings.APP_DIST_STORAGE).scheme)
url = f'{settings.APP_DIST_STORAGE}/{version}/{path}' url = f'{settings.APP_DIST_STORAGE}/{version}/{path}'
return HttpResponseRedirect(fs.url(url)) if isinstance(fs, LocalFileSystem):
if not fs.exists(url):
return HttpResponseNotFound()
return FileResponse(fs.open(url), filename=os.path.basename(url))
else:
return HttpResponseRedirect(fs.url(url))

View File

@@ -1,5 +1,6 @@
from django.conf import settings from django.conf import settings
from django.contrib import admin from django.contrib import admin
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.urls import path, include from django.urls import path, include
from django.views.static import serve from django.views.static import serve
from .app.urls import urlpatterns as app_urlpatterns from .app.urls import urlpatterns as app_urlpatterns
@@ -8,7 +9,7 @@ urlpatterns = [
path('', include(app_urlpatterns)), path('', include(app_urlpatterns)),
path('api/1/auth/social/', include('social_django.urls', namespace='social')), path('api/1/auth/social/', include('social_django.urls', namespace='social')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path(f'{settings.STATIC_URL.strip("/")}/<path:path>', serve, kwargs={ # path(f'{settings.STATIC_URL.strip("/")}/<path:path>', serve, kwargs={
'document_root': settings.STATIC_ROOT, # 'document_root': settings.STATIC_ROOT,
}), # }),
] ] + staticfiles_urlpatterns(settings.STATIC_URL)

21
docker-compose.yml Normal file
View File

@@ -0,0 +1,21 @@
services:
frontend:
build: frontend
ports:
- 9090:80
environment:
- PORT=80
- BACKEND_URL=http://localhost:9091
backend:
build: backend
ports:
- 9091:80
volumes:
- ./app-dist:/app-dist
environment:
- DATABASE_URL
- PORT=80
- FRONTEND_URL=http://localhost:9090
- ENABLE_HOMEPAGE=False
- DEBUG=False
- APP_DIST_STORAGE=file:///app-dist

View File

@@ -7,8 +7,6 @@ steps:
- '${_DOCKER_TAG}' - '${_DOCKER_TAG}'
- '--cache-from' - '--cache-from'
- '${_DOCKER_TAG}' - '${_DOCKER_TAG}'
- '--build-arg'
- 'BACKEND_URL=${_BACKEND_URL}'
- '.' - '.'
images: ['${_DOCKER_TAG}'] images: ['${_DOCKER_TAG}']

View File

@@ -21,6 +21,8 @@
<meta property="twitter:title" content="Tabby - a terminal for a more modern age"> <meta property="twitter:title" content="Tabby - a terminal for a more modern age">
<meta property="twitter:description" content="Tabby is a free and open source SSH, local and Telnet terminal with everything you'll ever need."> <meta property="twitter:description" content="Tabby is a free and open source SSH, local and Telnet terminal with everything you'll ever need.">
<meta property="twitter:image" content="https://user-images.githubusercontent.com/161476/126016449-a053012a-e322-48ed-a2ab-3ed4f3281465.png"> <meta property="twitter:image" content="https://user-images.githubusercontent.com/161476/126016449-a053012a-e322-48ed-a2ab-3ed4f3281465.png">
<meta property="x-tabby-web-backend-url" content="{{backendURL}}">
</head> </head>
<body> <body>
<app></app> <app></app>

View File

@@ -8,6 +8,7 @@ import { enableProdMode } from '@angular/core'
import { ngExpressEngine } from '@nguniversal/express-engine' import { ngExpressEngine } from '@nguniversal/express-engine'
import * as express from 'express' import * as express from 'express'
import { join } from 'path' import { join } from 'path'
@@ -42,7 +43,19 @@ function start () {
})) }))
app.get(['/', '/app', '/login'], (req, res) => { app.get(['/', '/app', '/login'], (req, res) => {
res.render('index', { req }) res.render(
'index',
{
req,
providers: [
{ provide: 'BACKEND_URL', useValue: process.env.BACKEND_URL ?? '' },
],
},
(err: Error, html: string) => {
html = html.replace('{{backendURL}}', process.env.BACKEND_URL ?? '')
res.status(err ? 500 : 200).send(html || err.message)
},
)
}) })
app.get(['/terminal'], (req, res) => { app.get(['/terminal'], (req, res) => {

View File

@@ -1,12 +1,23 @@
import { Injectable } from '@angular/core' import { Inject, Injectable, Optional } from '@angular/core'
declare const BACKEND_URL: any
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class CommonService { export class CommonService {
backendURL: string = BACKEND_URL backendURL: string
constructor () { constructor (@Inject('BACKEND_URL') @Optional() ssrBackendURL: string) {
const tag = (document.querySelector('meta[property=x-tabby-web-backend-url]') as HTMLMetaElement)
if (ssrBackendURL) {
this.backendURL = ssrBackendURL
tag.content = ssrBackendURL
} else {
if (tag.content && !tag.content.startsWith('{{')) {
this.backendURL = tag.content
} else {
this.backendURL = ''
}
}
console.log(this.backendURL)
if (this.backendURL.endsWith('/')) { if (this.backendURL.endsWith('/')) {
this.backendURL = this.backendURL.slice(0, -1) this.backendURL = this.backendURL.slice(0, -1)
} }

View File

@@ -1,6 +1,8 @@
import './terminal-styles.scss' import './terminal-styles.scss'
async function start () { async function start () {
window['__filename'] = ''
await new Promise<void>(resolve => { await new Promise<void>(resolve => {
window.addEventListener('message', event => { window.addEventListener('message', event => {
if (event.data === 'connector-ready') { if (event.data === 'connector-ready') {

View File

@@ -953,6 +953,11 @@ async-foreach@^0.1.3:
resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542"
integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=
async@~3.2.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.1.tgz#d3274ec66d107a47476a4c49136aacdb00665fc8"
integrity sha512-XdD5lRO/87udXCMC9meWdYiR+Nq6ZjUfXidViUZGu2F1MO4T3XwZ1et0hb2++BgLfhyJwy44BGB/yx80ABx8hg==
asynckit@^0.4.0: asynckit@^0.4.0:
version "0.4.0" version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
@@ -2898,6 +2903,13 @@ lru-cache@^6.0.0:
dependencies: dependencies:
yallist "^4.0.0" yallist "^4.0.0"
lru-cache@~5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
dependencies:
yallist "^3.0.2"
magic-string@^0.25.0: magic-string@^0.25.0:
version "0.25.7" version "0.25.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
@@ -3064,6 +3076,20 @@ ms@2.1.2:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
mustache-express@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/mustache-express/-/mustache-express-1.3.1.tgz#b5144513ab79a503c87aa8cb16fe019d2f61d9f9"
integrity sha512-RSSzrvM+CVAk9217dkWSNYyl6c2JnesNn6zaZ8+FvZSn8aLxY9l4kTnYqIoiE8GxdLyVQL2ak7XlMZS6t/l8YA==
dependencies:
async "~3.2.0"
lru-cache "~5.1.1"
mustache "^4.2.0"
mustache@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
nan@^2.13.2: nan@^2.13.2:
version "2.14.2" version "2.14.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
@@ -4950,6 +4976,11 @@ y18n@^5.0.5:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yallist@^4.0.0: yallist@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"