...
 
Commits (4)
  • Konrad Mohrfeldt's avatar
    add environment configuration support · da1ce8f3
    Konrad Mohrfeldt authored
    This adds limited support for environment configuration. Available
    options are outlined in the `Custom Enviroments` section in the readme.
    
    For now the only configurable setting is the `apiOrigin` making it
    possible to switch the origin of the thekno API without creating a
    separate build.
    
    Environment configuration is provided by the `/thekno-env.json`
    endpoint.
    da1ce8f3
  • Konrad Mohrfeldt's avatar
    don’t proxy /media/ path · 34a4dab6
    Konrad Mohrfeldt authored
    This path is handled by the lohrothek-api django app.
    34a4dab6
  • Konrad Mohrfeldt's avatar
    fix lint error · 73d3e96b
    Konrad Mohrfeldt authored
    73d3e96b
  • Konrad Mohrfeldt's avatar
    improve support for external APIs in development · edfab4e5
    Konrad Mohrfeldt authored
    The current production deployment of the lohrothek-api project is picky
    when it comes to being a PROXY_TARGET and because the production
    deployment of thekno will use an external origin anyway it’s a good idea
    to support it during development.
    
    Setting VUE_APP_APIORIGIN as an environment variable allows users to set
    the origin of an external API that should be used for data retrieval.
    edfab4e5
......@@ -10,6 +10,8 @@ RUN npm run build
# stage-2: copy static files to nginx image
FROM nginx:alpine
EXPOSE 5000
ARG env_file=system-files/env-prod.json
COPY system-files/nginx-defaultsite.conf /etc/nginx/conf.d/thekno.conf
COPY system-files/nginx-spa.conf /etc/nginx/snippets/thekno-spa.include
COPY --from=build /usr/src/app/build/dist /usr/share/thekno/html
COPY $env_file /usr/share/thekno/html/thekno-env.json
......@@ -12,23 +12,49 @@ The project uses a number of external dependencies, that you can install with th
*thekno* ships with fixtures that do not require a backend server, but in case you do want to work with the [Lohrothek REST API](https://git.hack-hro.de/lohro/lohrothek/lohrothek-api) you can do that. The development server started by `npm run serve` will proxy all requests to the `/api/` endpoint to `http://localhost:8000/` by default. This matches the default configuration for the Django webserver. In case you want to use a different backend server or have started Django on a non-standard port you can provide the `PROXY_TARGET` environment variable to `npm run serve`. A development server with the production-backend available on [thek.lohro.de](https://thek.lohro.de) can be started like this:
```sh
PROXY_TARGET="https://thek.lohro.de/" npm run serve
PROXY_TARGET=http://localhost:5000 npm run serve
```
Alternatively you can also directly use another API by passing the `VUE_APP_APIORIGIN` environment variable.
```sh
VUE_APP_APIORIGIN=https://thek.lohro.de npm run serve
```
You may also use the included docker script `scripts/docker.sh`. It will build and start *thekno* in an environment similar to those in production. Providing a [custom environment](#custom-environment) is possible with the `--build-arg` option like this:
```sh
scripts/docker.sh --build-arg env_file=path/to/my/environment-file.json
```
## Contributions
Contributions are always welcome. Before you push your commits or create *merge requests* make sure that `npm run lint` and `npm run test` do not report any errors (that did not exist before 😅).
## Builds & Deployment
VueJS provides a build target out of the box. You can create a build with `npm run build` and deploy the contents of the `build/dist` directory afterwards. There is also a `Dockerfile` that can be used to create docker images (in fact this `Dockerfile` is used to automatically deploy *thekno* to our [staging server](https://lohro-lohrothek-thekno-staging.lohrothek.git-k8s.hack-hro.de/)).
VueJS provides a build target out of the box. You can create a build with `npm run build` and deploy the contents of the `build/dist` directory afterwards. There is also a `Dockerfile` that can be used to create docker images (in fact it’s used to automatically deploy *thekno* to our [staging server](https://lohro-lohrothek-thekno-staging.lohrothek.git-k8s.hack-hro.de/)).
There will also be a deb package for Debian at a later point.
See [Custom Environments](#custom-environments) for a way to alter the default behaviour of *thekno*.
## Custom Environments
*thekno* supports some environment configuration. You may put the following options in a JSON file and serve it under the `/thekno-env.json` endpoint.
apiOrigin:
Origin (`protocol://host:port`, e.g. `http://localhost:8000` or `https://thek.lohro.de`) of the [*thekno* API](https://git.hack-hro.de/lohro/lohrothek/lohrothek-api).
There will also be a deb package for Debian at a later point.
## System Requirements
This projects requires fairly recent versions of *NodeJS* (v8+) and *npm* (v5.8+). Both are bundled with the default distribution available on [nodejs.org](https://nodejs.org/en/download/).
### Debian Stretch
If you are using Debian Stretch you’ll notice that the distributed `nodejs` package is fairly old and that npm is missing entirely.
......@@ -45,10 +71,12 @@ After that you will be able to install `nodejs` and `npm` with the following com
apt update && apt install -t stretch-backports nodejs npm
```
### Debian Buster
Debian Buster ships with the required versions so that you can just install the `nodejs` and `npm` packages.
### Other Distributions & Windows
For all other distributions that do not contain the required *NodeJS* and *npm* packages visit the [download section](https://nodejs.org/en/download/) on the NodeJS page.
......
......@@ -7,7 +7,7 @@
"build": "vue-cli-service build --modern",
"test": "npm run lint",
"lint": "vue-cli-service lint",
"docker": "docker build . -t thekno && docker run -d -p 5000:5000 thekno"
"docker": "scripts/docker.sh"
},
"dependencies": {
"axios": "^0.18.0",
......
#!/bin/sh
set -eu
docker build . -t thekno "$@" && docker run -d -p 5000:5000 thekno
import { TheknoNoApiError } from './error'
const { location, fetch } = window
const defaultOrigin = location.origin
const apiOrigin = process.env.VUE_APP_APIORIGIN || defaultOrigin
function retryAPI (origin = apiOrigin) {
return fetch(`${origin}/api/v1/`)
.then(res => res.json())
.then(
() => ({ apiOrigin: origin }),
() => { throw new TheknoNoApiError() })
}
function handleEarlyResponseFailure (e) {
if (/Failed to fetch/.test(e.message)) {
// Failures before a preliminary response is available indicate a CORS issue
return retryAPI()
}
throw e
}
function handleResponseProcessingFailure (e) {
if (/^Unexpected token/.test(e.message)) {
// Looks like the response could not be parsed as JSON.
// Maybe the default route handler kicked in returning the index.html.
// Try the current origin path for the API and if that fails give up.
return retryAPI()
}
// We’re out of ideas what might have gone wrong.
// Rethrow and let others handle this.
throw e
}
export function loadEnvironment () {
return fetch(`${defaultOrigin}/thekno-env.json`)
.then(res => res.json(), handleEarlyResponseFailure)
.catch(handleResponseProcessingFailure)
}
export class TheknoError extends Error {
toString () {
let baseMessage = `${this.message}\nCode: ${this.code}`
if (this.instructions) {
baseMessage += '\n\n' + this.instructions
}
return baseMessage
}
}
export class TheknoNoApiError extends TheknoError {
constructor (message = 'Unable to determine API endpoint') {
super(message)
this.code = 'THEKNO_NOAPI'
this.instructions =
'Thekno was unable to find the API endpoint used to fetch data. ' +
'Either you configure one by delivering a config through the `/thekno-env.json`-endpoint (see docs) ' +
'or enable API access through the `/api/v1/` endpoint on this origin.'
}
}
export function errorToHtml (e, title) {
return `
<div style="max-width: 600px; margin: 5rem auto; color: crimson; font-family: sans-serif">
<h1 style="margin-bottom: .5em">${title}</h1>
<pre style="white-space: pre-wrap">${String(e)}</pre>
</div>`
}
......@@ -3,9 +3,11 @@ import VueFeather from 'vue-feather'
import './plugins/vuetify'
import App from './App.vue'
import router from './router'
import store from './store'
import createStore from './store'
import i18n from './i18n'
import Player from './player'
import { errorToHtml } from './error'
import { loadEnvironment } from './bootstrap'
Vue.config.productionTip = false
Vue.config.keyCodes = {
......@@ -18,9 +20,19 @@ Vue.config.keyCodes = {
Vue.use(Player)
Vue.use(VueFeather)
new Vue({
router,
store,
i18n,
render: h => h(App)
}).$mount('#app')
function init (env) {
const store = createStore(env)
new Vue({
router,
store,
i18n,
render: h => h(App)
}).$mount('#app')
}
loadEnvironment()
.then(init, e => {
document.querySelector('#app').innerHTML =
errorToHtml(e, 'Could not initialize thekno app')
})
import Vapi from 'vuex-rest-api'
export default ({ axios }) => {
export default ({ axios, apiOrigin }) => {
const storeConfig = new Vapi({
baseURL: '/api/v1/broadcasts',
baseURL: `${apiOrigin}/api/v1/broadcasts`,
axios,
state: {
broadcasts: []
......
import Vapi from 'vuex-rest-api'
export default ({ axios }) => {
export default ({ axios, apiOrigin }) => {
const storeConfig = new Vapi({
baseURL: '/api/v1/recordings',
baseURL: `${apiOrigin}/api/v1/recordings`,
axios,
state: {
recordings: []
......
import Vapi from 'vuex-rest-api'
export default ({ axios }) => {
export default ({ axios, apiOrigin }) => {
const storeConfig = new Vapi({
baseURL: '/api/v1/series',
baseURL: `${apiOrigin}/api/v1/series`,
axios,
state: {
series: []
......
......@@ -6,15 +6,17 @@ import createApiStore from './api'
Vue.use(Vuex)
const store = new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
modules: {
api: createApiStore({ axios })
}
})
export default function createStore (env) {
const store = new Vuex.Store({
strict: process.env.NODE_ENV !== 'production',
modules: {
api: createApiStore({ axios, ...env })
}
})
store.dispatch('api/getBroadcasts')
store.dispatch('api/getRecordings')
store.dispatch('api/getSeries')
store.dispatch('api/getBroadcasts')
store.dispatch('api/getRecordings')
store.dispatch('api/getSeries')
export default store
return store
}
{
"apiOrigin": "https://thek.lohro.de"
}
# nginx default site for Docker image
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
......@@ -8,14 +7,7 @@ map $http_upgrade $connection_upgrade {
server {
listen 5000 default_server;
listen [::]:5000 default_server;
root /usr/share/thekno/html;
location /api/ {
proxy_pass https://thek.lohro.de;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
root /usr/share/thekno/html;
include /etc/nginx/snippets/thekno-spa.include;
}
const proxyTarget = process.env.PROXY_TARGET || 'http://localhost:8000/'
const proxyTarget = process.env.PROXY_TARGET || (process.env.VUE_APP_APIORIGIN ? 'disable' : 'http://localhost:8000/')
const proxyOptions = {
target: proxyTarget,
changeOrigin: true,
toProxy: true,
protocolRewrite: true,
autoRewrite: true
}
module.exports = {
integrity: true,
outputDir: 'build/dist',
devServer: {
proxy: {
'/api/': {
target: proxyTarget
}
}
proxy: proxyTarget !== 'disable' ? {
'/api/': proxyOptions,
'/media/': proxyOptions
} : null
},
pluginOptions: {
i18n: {
......@@ -39,7 +45,7 @@ module.exports = {
source: 'src',
img: 'src',
image: 'xlink:href',
'v-img': 'src',
'v-img': 'src'
}
return options
......