Переглянути джерело

feat(): adds webpack-based vue.js-app to stack

Nils Wisiol 5 роки тому
батько
коміт
c07c4ead45
39 змінених файлів з 638 додано та 2 видалено
  1. 4 0
      .gitignore
  2. 29 0
      docker-compose.dev.yml
  3. 14 0
      docker-compose.yml
  4. 2 0
      webapp/.browserslistrc
  5. 27 0
      webapp/.eslintrc.js
  6. 24 0
      webapp/.gitignore
  7. 34 0
      webapp/README.md
  8. 5 0
      webapp/babel.config.js
  9. 3 0
      webapp/cypress.json
  10. 35 0
      webapp/package.json
  11. 5 0
      webapp/postcss.config.js
  12. BIN
      webapp/public/favicon.ico
  13. 17 0
      webapp/public/index.html
  14. 32 0
      webapp/src/App.vue
  15. BIN
      webapp/src/assets/logo.png
  16. 62 0
      webapp/src/components/HelloWorld.vue
  17. 12 0
      webapp/src/main.js
  18. 29 0
      webapp/src/router/index.js
  19. 15 0
      webapp/src/store/index.js
  20. 5 0
      webapp/src/views/About.vue
  21. 18 0
      webapp/src/views/Home.vue
  22. 12 0
      webapp/tests/e2e/.eslintrc.js
  23. 24 0
      webapp/tests/e2e/plugins/index.js
  24. 8 0
      webapp/tests/e2e/specs/test.js
  25. 25 0
      webapp/tests/e2e/support/commands.js
  26. 20 0
      webapp/tests/e2e/support/index.js
  27. 5 0
      webapp/tests/unit/.eslintrc.js
  28. 13 0
      webapp/tests/unit/example.spec.js
  29. 11 0
      webapp/vue.config.js
  30. 18 0
      www/90-desec.static.dev.location
  31. 54 0
      www/conf/sites-available/85-redirects.conf.var
  32. 14 0
      www/conf/sites-available/90-desec.api.location
  33. 35 0
      www/conf/sites-available/90-desec.conf.var
  34. 8 0
      www/conf/sites-available/90-desec.static.location
  35. 17 1
      www/conf/sites-available/99-desec.conf.var
  36. 1 0
      www/conf/sites-enabled/85-redirects.conf
  37. 1 0
      www/conf/sites-enabled/90-desec.conf
  38. 0 1
      www/conf/sites-enabled/99-desec.conf
  39. 0 0
      www/html/app/.gitkeep

+ 4 - 0
.gitignore

@@ -12,3 +12,7 @@ api/desecapi.sqlite
 # local certificates
 /certs/
 /replication-certs/
+
+# Webapp development files
+node_modules
+package-lock.json

+ 29 - 0
docker-compose.dev.yml

@@ -3,6 +3,12 @@ version: '2.2'
 # mostly extending from main .yml
 services:
   www:
+    depends_on:
+    - webapp
+    networks:
+    - rearwebapp
+    volumes:
+    - ./www/90-desec.static.dev.location:/etc/nginx/sites-available/90-desec.static.location  # override webapp access
     logging:
       driver: "json-file"
 
@@ -50,3 +56,26 @@ services:
   celery:
     logging:
       driver: "json-file"
+
+  webapp:
+    image: node:latest
+    volumes:
+    - ./webapp/:/usr/src/app/
+    working_dir: /usr/src/app/
+    command: npm run serve --fix
+    environment:
+    - HOST=0.0.0.0
+    - PORT=443
+    networks:
+    - rearwebapp
+    logging:
+      driver: "json-file"
+
+networks:
+  rearwebapp:
+    driver: bridge
+    ipam:
+      driver: default
+      config:
+      - subnet: ${DESECSTACK_IPV4_REAR_PREFIX16}.100.0/24
+        gateway: ${DESECSTACK_IPV4_REAR_PREFIX16}.100.1

+ 14 - 0
docker-compose.yml

@@ -11,6 +11,7 @@ services:
     volumes:
     - ${DESECSTACK_WWW_CERTS}:/etc/ssl/private:ro
     - ./www/html:/usr/share/nginx/html:ro
+    - webapp_dist:/usr/share/nginx/html/app:ro
     environment:
     - DESECSTACK_DOMAIN
     - DESECSTACK_WWW_CERTS
@@ -180,6 +181,18 @@ services:
         tag: "desec/nsmaster"
     restart: unless-stopped
 
+  webapp:
+    image: node:latest
+    volumes:
+    - ./webapp/:/usr/src/app/
+    - webapp_dist:/usr/src/app/dist
+    working_dir: /usr/src/app/
+    command: bash -c "npm install && npm run build -- --no-clean"
+    logging:
+      driver: "syslog"
+      options:
+        tag: "desec/webapp"
+
   rabbitmq:
     image: rabbitmq:3.8-alpine
     init: true
@@ -236,6 +249,7 @@ volumes:
   dblord_mysql:
   dbmaster_mysql:
   rabbitmq_data:
+  webapp_dist:
 
 networks:
   # Note that it is required that the front network ranks lower (in lexical order)

+ 2 - 0
webapp/.browserslistrc

@@ -0,0 +1,2 @@
+> 1%
+last 2 versions

+ 27 - 0
webapp/.eslintrc.js

@@ -0,0 +1,27 @@
+module.exports = {
+  root: true,
+  env: {
+    node: true
+  },
+  'extends': [
+    'plugin:vue/essential',
+    'eslint:recommended'
+  ],
+  rules: {
+    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
+  },
+  parserOptions: {
+    parser: 'babel-eslint'
+  },
+  overrides: [
+    {
+      files: [
+        '**/__tests__/*.{j,t}s?(x)'
+      ],
+      env: {
+        mocha: true
+      }
+    }
+  ]
+}

+ 24 - 0
webapp/.gitignore

@@ -0,0 +1,24 @@
+.DS_Store
+node_modules
+/dist
+
+/tests/e2e/videos/
+/tests/e2e/screenshots/
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 34 - 0
webapp/README.md

@@ -0,0 +1,34 @@
+# webapp
+
+## Project setup
+```
+npm install
+```
+
+### Compiles and hot-reloads for development
+```
+npm run serve
+```
+
+### Compiles and minifies for production
+```
+npm run build
+```
+
+### Run your unit tests
+```
+npm run test:unit
+```
+
+### Run your end-to-end tests
+```
+npm run test:e2e
+```
+
+### Lints and fixes files
+```
+npm run lint
+```
+
+### Customize configuration
+See [Configuration Reference](https://cli.vuejs.org/config/).

+ 5 - 0
webapp/babel.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  presets: [
+    '@vue/cli-plugin-babel/preset'
+  ]
+}

+ 3 - 0
webapp/cypress.json

@@ -0,0 +1,3 @@
+{
+  "pluginsFile": "tests/e2e/plugins/index.js"
+}

+ 35 - 0
webapp/package.json

@@ -0,0 +1,35 @@
+{
+  "name": "webapp",
+  "version": "0.1.0",
+  "private": true,
+  "scripts": {
+    "serve": "vue-cli-service serve",
+    "build": "vue-cli-service build",
+    "test:unit": "vue-cli-service test:unit",
+    "test:e2e": "vue-cli-service test:e2e",
+    "lint": "vue-cli-service lint"
+  },
+  "dependencies": {
+    "core-js": "^3.3.2",
+    "vue": "^2.6.10",
+    "vue-router": "^3.1.3",
+    "vuex": "^3.0.1"
+  },
+  "devDependencies": {
+    "@vue/cli-plugin-babel": "^4.0.0",
+    "@vue/cli-plugin-e2e-cypress": "^4.0.0",
+    "@vue/cli-plugin-eslint": "^4.0.0",
+    "@vue/cli-plugin-router": "^4.0.0",
+    "@vue/cli-plugin-unit-mocha": "^4.0.0",
+    "@vue/cli-plugin-vuex": "^4.0.0",
+    "@vue/cli-service": "^4.0.0",
+    "@vue/test-utils": "1.0.0-beta.29",
+    "babel-eslint": "^10.0.3",
+    "chai": "^4.1.2",
+    "eslint": "^5.16.0",
+    "eslint-plugin-vue": "^5.0.0",
+    "sass": "^1.19.0",
+    "sass-loader": "^8.0.0",
+    "vue-template-compiler": "^2.6.10"
+  }
+}

+ 5 - 0
webapp/postcss.config.js

@@ -0,0 +1,5 @@
+module.exports = {
+  plugins: {
+    autoprefixer: {}
+  }
+}

BIN
webapp/public/favicon.ico


+ 17 - 0
webapp/public/index.html

@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width,initial-scale=1.0">
+    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
+    <title>webapp</title>
+  </head>
+  <body>
+    <noscript>
+      <strong>We're sorry but webapp doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+    </noscript>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 32 - 0
webapp/src/App.vue

@@ -0,0 +1,32 @@
+<template>
+  <div id="app">
+    <div id="nav">
+      <router-link to="/">Home</router-link> |
+      <router-link to="/about">About</router-link>
+    </div>
+    <router-view/>
+  </div>
+</template>
+
+<style lang="scss">
+#app {
+  font-family: 'Avenir', Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  text-align: center;
+  color: #2c3e50;
+}
+
+#nav {
+  padding: 30px;
+
+  a {
+    font-weight: bold;
+    color: #2c3e50;
+
+    &.router-link-exact-active {
+      color: #42b983;
+    }
+  }
+}
+</style>

BIN
webapp/src/assets/logo.png


+ 62 - 0
webapp/src/components/HelloWorld.vue

@@ -0,0 +1,62 @@
+<template>
+  <div class="hello">
+    <h1>{{ msg }}</h1>
+    <p>
+      For a guide and recipes on how to configure / customize this project,<br>
+      check out the
+      <a href="https://cli.vuejs.org" target="_blank" rel="noopener">vue-cli documentation</a>.
+    </p>
+    <h3>Installed CLI Plugins</h3>
+    <ul>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel" target="_blank" rel="noopener">babel</a></li>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router" target="_blank" rel="noopener">router</a></li>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex" target="_blank" rel="noopener">vuex</a></li>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint" target="_blank" rel="noopener">eslint</a></li>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-unit-mocha" target="_blank" rel="noopener">unit-mocha</a></li>
+      <li><a href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-e2e-cypress" target="_blank" rel="noopener">e2e-cypress</a></li>
+    </ul>
+    <h3>Essential Links</h3>
+    <ul>
+      <li><a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a></li>
+      <li><a href="https://forum.vuejs.org" target="_blank" rel="noopener">Forum</a></li>
+      <li><a href="https://chat.vuejs.org" target="_blank" rel="noopener">Community Chat</a></li>
+      <li><a href="https://twitter.com/vuejs" target="_blank" rel="noopener">Twitter</a></li>
+      <li><a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a></li>
+    </ul>
+    <h3>Ecosystem</h3>
+    <ul>
+      <li><a href="https://router.vuejs.org" target="_blank" rel="noopener">vue-router</a></li>
+      <li><a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a></li>
+      <li><a href="https://github.com/vuejs/vue-devtools#vue-devtools" target="_blank" rel="noopener">vue-devtools</a></li>
+      <li><a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener">vue-loader</a></li>
+      <li><a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">awesome-vue</a></li>
+    </ul>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'HelloWorld',
+  props: {
+    msg: String
+  }
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped lang="scss">
+h3 {
+  margin: 40px 0 0;
+}
+ul {
+  list-style-type: none;
+  padding: 0;
+}
+li {
+  display: inline-block;
+  margin: 0 10px;
+}
+a {
+  color: #42b983;
+}
+</style>

+ 12 - 0
webapp/src/main.js

@@ -0,0 +1,12 @@
+import Vue from 'vue'
+import App from './App.vue'
+import router from './router'
+import store from './store'
+
+Vue.config.productionTip = false
+
+new Vue({
+  router,
+  store,
+  render: h => h(App)
+}).$mount('#app')

+ 29 - 0
webapp/src/router/index.js

@@ -0,0 +1,29 @@
+import Vue from 'vue'
+import VueRouter from 'vue-router'
+import Home from '../views/Home.vue'
+
+Vue.use(VueRouter)
+
+const routes = [
+  {
+    path: '/',
+    name: 'home',
+    component: Home
+  },
+  {
+    path: '/about',
+    name: 'about',
+    // route level code-splitting
+    // this generates a separate chunk (about.[hash].js) for this route
+    // which is lazy-loaded when the route is visited.
+    component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
+  }
+]
+
+const router = new VueRouter({
+  mode: 'history',
+  base: process.env.BASE_URL,
+  routes
+})
+
+export default router

+ 15 - 0
webapp/src/store/index.js

@@ -0,0 +1,15 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+
+export default new Vuex.Store({
+  state: {
+  },
+  mutations: {
+  },
+  actions: {
+  },
+  modules: {
+  }
+})

+ 5 - 0
webapp/src/views/About.vue

@@ -0,0 +1,5 @@
+<template>
+  <div class="about">
+    <h1>This is an about page</h1>
+  </div>
+</template>

+ 18 - 0
webapp/src/views/Home.vue

@@ -0,0 +1,18 @@
+<template>
+  <div class="home">
+    <img alt="Vue logo" src="../assets/logo.png">
+    <HelloWorld msg="Welcome to Your Vue.js App"/>
+  </div>
+</template>
+
+<script>
+// @ is an alias to /src
+import HelloWorld from '@/components/HelloWorld.vue'
+
+export default {
+  name: 'home',
+  components: {
+    HelloWorld
+  }
+}
+</script>

+ 12 - 0
webapp/tests/e2e/.eslintrc.js

@@ -0,0 +1,12 @@
+module.exports = {
+  plugins: [
+    'cypress'
+  ],
+  env: {
+    mocha: true,
+    'cypress/globals': true
+  },
+  rules: {
+    strict: 'off'
+  }
+}

+ 24 - 0
webapp/tests/e2e/plugins/index.js

@@ -0,0 +1,24 @@
+// https://docs.cypress.io/guides/guides/plugins-guide.html
+
+// if you need a custom webpack configuration you can uncomment the following import
+// and then use the `file:preprocessor` event
+// as explained in the cypress docs
+// https://docs.cypress.io/api/plugins/preprocessors-api.html#Examples
+
+/* eslint-disable import/no-extraneous-dependencies, global-require, arrow-body-style */
+// const webpack = require('@cypress/webpack-preprocessor')
+
+module.exports = (on, config) => {
+  // on('file:preprocessor', webpack({
+  //  webpackOptions: require('@vue/cli-service/webpack.config'),
+  //  watchOptions: {}
+  // }))
+
+  return Object.assign({}, config, {
+    fixturesFolder: 'tests/e2e/fixtures',
+    integrationFolder: 'tests/e2e/specs',
+    screenshotsFolder: 'tests/e2e/screenshots',
+    videosFolder: 'tests/e2e/videos',
+    supportFile: 'tests/e2e/support/index.js'
+  })
+}

+ 8 - 0
webapp/tests/e2e/specs/test.js

@@ -0,0 +1,8 @@
+// https://docs.cypress.io/api/introduction/api.html
+
+describe('My First Test', () => {
+  it('Visits the app root url', () => {
+    cy.visit('/')
+    cy.contains('h1', 'Welcome to Your Vue.js App')
+  })
+})

+ 25 - 0
webapp/tests/e2e/support/commands.js

@@ -0,0 +1,25 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add("login", (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This is will overwrite an existing command --
+// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })

+ 20 - 0
webapp/tests/e2e/support/index.js

@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/index.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')

+ 5 - 0
webapp/tests/unit/.eslintrc.js

@@ -0,0 +1,5 @@
+module.exports = {
+  env: {
+    mocha: true
+  }
+}

+ 13 - 0
webapp/tests/unit/example.spec.js

@@ -0,0 +1,13 @@
+import { expect } from 'chai'
+import { shallowMount } from '@vue/test-utils'
+import HelloWorld from '@/components/HelloWorld.vue'
+
+describe('HelloWorld.vue', () => {
+  it('renders props.msg when passed', () => {
+    const msg = 'new message'
+    const wrapper = shallowMount(HelloWorld, {
+      propsData: { msg }
+    })
+    expect(wrapper.text()).to.include(msg)
+  })
+})

+ 11 - 0
webapp/vue.config.js

@@ -0,0 +1,11 @@
+module.exports = {
+  configureWebpack: {
+    devServer: {
+      disableHostCheck: true,
+      sockHost: 'desec.example.dedyn.io',    // TODO use env
+      public: 'https://desec.example.dedyn.io/app/',    // TODO use env
+      // sockPath: "/app/sockjs-node",
+    },
+  },
+  publicPath: '/app/',
+};

+ 18 - 0
www/90-desec.static.dev.location

@@ -0,0 +1,18 @@
+######
+# Serve webapp.
+# In dev mode, we reverse-proxy to the node dev server.
+######
+location / {
+    expires epoch;
+    etag off;
+    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+    proxy_set_header Host "0.0.0.0";
+
+    # enable WebSockets
+    proxy_http_version 1.1;
+    proxy_set_header Upgrade $http_upgrade;
+    proxy_set_header Connection "upgrade";
+
+    proxy_pass http://webapp:443; # FIXME Note that we use 443 for non-SSL communication, to make webpack dev tools use port 443, too. A vue-cli upgrade may help.
+}

+ 54 - 0
www/conf/sites-available/85-redirects.conf.var

@@ -0,0 +1,54 @@
+######
+# Redirect HTTP requests on (www.)desec.* to HTTPS
+######
+server {
+	listen 80;
+	listen [::]:80;
+	server_name www.desec.$DESECSTACK_DOMAIN
+	            desec.$DESECSTACK_DOMAIN
+	            get.desec.$DESECSTACK_DOMAIN;
+
+	include global.conf;
+
+	location / {
+		return 301 https://$host$request_uri;
+	}
+}
+
+######
+# Strip www. from HTTPS requests on www.desec.*
+######
+server {
+	listen 443 ssl http2;
+	listen [::]:443 ssl http2;
+	server_name www.desec.$DESECSTACK_DOMAIN;
+
+	ssl_certificate ${CERT_PATH}www.desec.${DESECSTACK_DOMAIN}.cer;
+	ssl_certificate_key ${CERT_PATH}www.desec.${DESECSTACK_DOMAIN}.key;
+
+	include global.conf;
+
+	location / {
+		add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
+		return 301 https://desec.$DESECSTACK_DOMAIN$request_uri;
+	}
+}
+
+######
+# For the "get" subdomain, we redirect to the main page for now
+######
+server {
+        listen 443 ssl http2;
+        listen [::]:443 ssl http2;
+        server_name get.desec.$DESECSTACK_DOMAIN;
+
+        ssl_certificate ${CERT_PATH}get.desec.${DESECSTACK_DOMAIN}.cer;
+        ssl_certificate_key ${CERT_PATH}get.desec.${DESECSTACK_DOMAIN}.key;
+
+        include global.conf;
+
+        location / {
+                add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
+                return 301 https://desec.$DESECSTACK_DOMAIN$request_uri;
+        }
+}

+ 14 - 0
www/conf/sites-available/90-desec.api.location

@@ -0,0 +1,14 @@
+######
+# Pass API requests to an API server
+######
+location /api/ {
+    # max .5r/s to the API per IP, but 10 at once is okay. This limit may need to be increased once
+    # client applications become more powerful
+    limit_req zone=perip-api burst=10 nodelay;
+
+    expires epoch;
+    etag off;
+    add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
+    include uwsgi_params;
+    uwsgi_pass desecapi;
+}

+ 35 - 0
www/conf/sites-available/90-desec.conf.var

@@ -0,0 +1,35 @@
+######
+# The website server
+######
+server {
+	listen 443 ssl http2;
+	listen [::]:443 ssl http2;
+	server_name desec.$DESECSTACK_DOMAIN;
+
+	access_log /var/log/nginx/access.log main;
+	error_log /var/log/nginx/error.log;
+
+	ssl_certificate ${CERT_PATH}desec.${DESECSTACK_DOMAIN}.cer;
+	ssl_certificate_key ${CERT_PATH}desec.${DESECSTACK_DOMAIN}.key;
+	add_header Strict-Transport-Security "max-age=31536000; includeSubdomains; preload" always;
+
+	include global.conf;
+
+	######
+	# Caching Policy
+	######
+	#expires 1M;
+	#etag on;
+	#location /index.html {
+	#	expires epoch;
+	#	etag on;
+	#}
+	#location /texts/ {
+	#	expires epoch;
+	#	etag on;
+	#}
+
+    # include api and static locations, these files may be overridden for debugging
+    include sites-available/90-desec.api.location;
+    include sites-available/90-desec.static.location;
+}

+ 8 - 0
www/conf/sites-available/90-desec.static.location

@@ -0,0 +1,8 @@
+#####
+# Static content for the web app
+#####
+location / {
+    root   /usr/share/nginx/html/;
+    index  index.html;
+    try_files $uri $uri/ /index.html =404;
+}

+ 17 - 1
www/conf/sites-available/99-desec.conf.var

@@ -83,6 +83,8 @@ server {
 	#	expires epoch;
 	#	etag on;
 	#}
+
+    include sites-available/99-desec.*.dev.location; # include dev locations, if any. Note that they have priority over locations below
 	
 	######
 	# Pass API requests to an API server
@@ -98,7 +100,21 @@ server {
 		include uwsgi_params;
 		uwsgi_pass desecapi;
 	}
-	
+
+
+    #####
+    # Static content for the web app
+    #####
+    location /app/static/ {
+        root   /usr/share/nginx/html/;
+    }
+
+    location /app/ {
+        root   /usr/share/nginx/html/;
+        index  index.html;
+        try_files $uri $uri/ /app/index.html =404;
+    }
+
 	######
 	# Pass static content requests
 	######

+ 1 - 0
www/conf/sites-enabled/85-redirects.conf

@@ -0,0 +1 @@
+../sites-available/85-redirects.conf

+ 1 - 0
www/conf/sites-enabled/90-desec.conf

@@ -0,0 +1 @@
+../sites-available/90-desec.conf

+ 0 - 1
www/conf/sites-enabled/99-desec.conf

@@ -1 +0,0 @@
-../sites-available/99-desec.conf

+ 0 - 0
www/html/app/.gitkeep