Browse Source

fix: Adding compose files and frontend files

Matthias Ladkau 4 years ago
parent
commit
f33d23f912
25 changed files with 848 additions and 32 deletions
  1. 2 0
      .gitignore
  2. 5 4
      README.md
  3. 25 11
      examples/data-mining/build.sh
  4. 49 9
      examples/data-mining/doc/data-mining.md
  5. BIN
      examples/data-mining/doc/data-mining_chart.png
  6. BIN
      examples/data-mining/doc/eliasdb_graphiql.png
  7. BIN
      examples/data-mining/doc/eliasdb_term.png
  8. 46 0
      examples/data-mining/docker-compose.yml
  9. 1 1
      examples/data-mining/docker-images/collector/Dockerfile
  10. 58 7
      examples/data-mining/docker-images/collector/app/main.py
  11. 2 0
      examples/data-mining/docker-images/eliasdb/docker-compose.yml
  12. 5 0
      examples/data-mining/docker-images/frontend/Dockerfile
  13. 11 0
      examples/data-mining/docker-images/frontend/app/dist/frontend.js
  14. 15 0
      examples/data-mining/docker-images/frontend/app/index.html
  15. 33 0
      examples/data-mining/docker-images/frontend/app/package.json
  16. BIN
      examples/data-mining/docker-images/frontend/app/pig.gif
  17. 28 0
      examples/data-mining/docker-images/frontend/app/src/component/LineChart.vue
  18. 102 0
      examples/data-mining/docker-images/frontend/app/src/component/LineResult.vue
  19. 21 0
      examples/data-mining/docker-images/frontend/app/src/index.ts
  20. 231 0
      examples/data-mining/docker-images/frontend/app/src/lib/eliasdb-graphql.ts
  21. 4 0
      examples/data-mining/docker-images/frontend/app/src/vue-shims.d.ts
  22. 66 0
      examples/data-mining/docker-images/frontend/app/tsconfig.json
  23. 104 0
      examples/data-mining/docker-images/frontend/app/webpack.config.js
  24. 15 0
      examples/data-mining/docker-images/frontend/build.sh
  25. 25 0
      examples/data-mining/docker-images/frontend/etc/default.conf

+ 2 - 0
.gitignore

@@ -11,3 +11,5 @@
 /examples/tutorial/run/
 /examples/chat/run/
 /examples/data-mining/docker-images/eliasdb/eliasdb
+/examples/data-mining/docker-images/frontend/app/node_modules
+/examples/data-mining/docker-images/frontend/app/graphiql

+ 5 - 4
README.md

@@ -58,9 +58,9 @@ docker run --rm --network="host" -it -v $PWD:/data --user $(id -u):$(id -g) -v $
 
 ### Tutorial:
 
-To get an idea of what EliasDB is about have a look at the [tutorial](https://devt.de/krotik/eliasdb/src/master/examples/tutorial/doc/tutorial.md). This tutorial will cover the basics of EQL and show how data is organized.
+To get an idea of what EliasDB is about have a look at the [tutorial](examples/tutorial/doc/tutorial.md). This tutorial will cover the basics of EQL and show how data is organized.
 
-There is a separate [tutorial](https://devt.de/krotik/eliasdb/src/master/examples/tutorial/doc/tutorial_graphql.md) on using ELiasDB with GraphQL.
+There is a separate [tutorial](examples/tutorial/doc/tutorial_graphql.md) on using ELiasDB with GraphQL.
 
 ### REST API:
 
@@ -68,7 +68,7 @@ The terminal uses a REST API to communicate with the backend. The REST API can b
 
 ### Clustering:
 
-EliasDB supports to be run in a cluster by joining multiple instances of EliasDB together. You can read more about it [here](https://devt.de/krotik/eliasdb/src/master/cluster.md).
+EliasDB supports to be run in a cluster by joining multiple instances of EliasDB together. You can read more about it [here](cluster.md).
 
 ### Command line options
 The main EliasDB executable has two main tools:
@@ -216,7 +216,8 @@ docker build --tag krotik/eliasdb .
 
 Example Applications
 --------------------
-[Chat](https://devt.de/krotik/eliasdb/src/master/examples/chat/doc/chat.md) - A simple chat application showing user management and subscriptions.
+- [Chat](examples/chat/doc/chat.md) - A simple chat application showing user /management and subscriptions.
+- [Data-mining](examples/data-mining/doc/data-mining.md) - A more complex application which uses the cluster feature of EliasDB and GraphQL for data queries.
 
 
 Further Reading

+ 25 - 11
examples/data-mining/build.sh

@@ -2,17 +2,31 @@
 cd "$(dirname "$0")"
 export ROOT_PATH=`pwd`
 
-cd ../..
-docker build --tag data-mining/eliasdb .
+# This build script should build the following images in the local Docker registry:
+#
+# data-mining/frontend
+# data-mining/eliasdb1
+# data-mining/eliasdb2
+# data-mining/eliasdb3
+# data-mining/collector
+
+echo Building Collector
+echo ==================
+cd ./docker-images/collector
+./build.sh
+cd $ROOT_PATH
+
+echo
+echo Building Eliasdb Cluster
+echo ========================
+cd docker-images/eliasdb
+./build.sh
 cd $ROOT_PATH
 
-pwd
+echo
+echo Building Frontend
+echo =================
+cd docker-images/frontend
+./build.sh
+cd $ROOT_PATH
 
-#if ! [ -d "run" ]; then
-#  mkdir -p run/web
-#  cp -fR res/chat/* run/web
-#  cp -fR res/eliasdb.config.json run
-#  cp -fR res/access.db run
-#fi
-#cd run
-#../../../eliasdb server

+ 49 - 9
examples/data-mining/doc/data-mining.md

@@ -1,26 +1,66 @@
 EliasDB Data Mining Example
 ==
-This example demonstrates a more complex application which uses the cluster feature and EQL and GraphQL for data queries.
+This example demonstrates a more complex application which uses the cluster
+feature of EliasDB and GraphQL for data queries.
 
-The idea of the application is to provide a platform for datamining with 3 components for presentation, collection and storage of data.
+The idea of the application is to provide a platform for data mining with 3 components for presentation, collection and storage of data. The data which is being collected are request response times of the domain `devt.de`.
 
 The tutorial assumes you have downloaded EliasDB, extracted and build it.
 It also assumes that you have a running docker environment with the `docker` and `docker-compose` commands being available. This tutorial will only work in unix-like environments.
 
-For this tutorial please execute "build.sh" in the subdirectory: examples/data-mining.
+For this tutorial please execute `build.sh` in the subdirectory: examples/data-mining and run `docker-compose up` in the same directory.
 
+After running build.sh you should see the following docker images in the local docker registry:
 
+```
+> docker images
+
+REPOSITORY                   TAG                 IMAGE ID            CREATED             SIZE
+data-mining/collector        latest              3a159822c9e6        6 minutes ago       174MB
+data-mining/frontend         latest              c412dbd46dce        16 hours ago        22.7MB
+data-mining/eliasdb3         latest              c079c1ad876e        17 hours ago        20.9MB
+data-mining/eliasdb2         latest              b53ec5dfdcfb        17 hours ago        20.9MB
+data-mining/eliasdb1         latest              83fddb8783df        17 hours ago        20.9MB
+```
+
+After running `docker-compose up` you should see 5 containers starting with the collector container continuously gathering ping results and storing it into the running EliasDB cluster.
+
+You can query the state of the database by pointing a browser at:
+```
+http://localhost:4040/db/term.html
+```
+You can query for `PingResult` nodes:
 
+![](eliasdb_term.png)
 
-After starting EliasDB point your browser to:
+You can also use a GraphiQL interface by pointing a browser at:
 ```
-https://localhost:9090
+http://localhost:4040/graphiql/
+```
+You can also here query for `PingResult` nodes:
+
+![](eliasdb_graphiql.png)
+
+You can log into a running EliasDB container and query its disk usage:
+```
+> docker exec -it eliasdb1 sh
+
+/data # du -h
+2.2M	./db
+48.0K	./web/db
+56.0K	./web
+12.0K	./ssl
+2.3M	.
+
+/data # df -h
+Filesystem                Size      Used Available Use% Mounted on
+overlay                 240.1G     43.3G    184.5G  19% /
+...
 ```
 
-The generated default key and certificate for https are self-signed which should give a security warning in the browser. After accepting you should see a login prompt. Enter the credentials for the default user elias:
+Finally you can see a graph of the collected data by navigating to:
 ```
-Username: elias
-Password: elias
+http://localhost:4040/
 ```
 
-The browser should display the chat application after clicking `Login`. Open a second window and write some chat messages. You can see that both windows update immediately. This is done with GraphQL subscriptions.
+![](data-mining_chart.png)

BIN
examples/data-mining/doc/data-mining_chart.png


BIN
examples/data-mining/doc/eliasdb_graphiql.png


BIN
examples/data-mining/doc/eliasdb_term.png


+ 46 - 0
examples/data-mining/docker-compose.yml

@@ -0,0 +1,46 @@
+version: "3"
+
+services:
+  frontend:
+    image: data-mining/frontend:latest
+    container_name: frontend
+    ports:
+      - 4040:80
+    # Uncomment to directly edit the frontend
+    # volumes:
+    #   - ./docker-images/frontend/app:/usr/share/nginx/html
+    networks:
+      - back-tier
+
+  eliasdb1:
+    image: data-mining/eliasdb1:latest
+    container_name: eliasdb1
+    ports:
+      - 4041:9090
+    networks:
+      - back-tier
+
+  eliasdb2:
+    image: data-mining/eliasdb2:latest
+    container_name: eliasdb2
+    ports:
+      - 4042:9090
+    networks:
+      - back-tier
+
+  eliasdb3:
+    image: data-mining/eliasdb3:latest
+    container_name: eliasdb3
+    ports:
+      - 4043:9090
+    networks:
+      - back-tier
+
+  collector:
+    image: data-mining/collector:latest
+    container_name: collector
+    networks:
+      - back-tier
+
+networks:
+  back-tier:

+ 1 - 1
examples/data-mining/docker-images/collector/Dockerfile

@@ -1,7 +1,7 @@
 FROM python:alpine
 RUN apk update && apk add --no-cache supervisor
 
-RUN pip install schedule request
+RUN pip install schedule requests
 
 ADD etc/supervisord.conf /etc/supervisord.conf
 ADD app/main.py /app/main.py

+ 58 - 7
examples/data-mining/docker-images/collector/app/main.py

@@ -1,19 +1,70 @@
+#!/usr/bin/env python3
+#
+# EliasDB - Data mining collector example
+#
+# Copyright 2020 Matthias Ladkau. All rights reserved.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
 import schedule
 import time
 import requests
+import json
+
+ELIASDB_URL = "eliasdb1:9090"
+
+requests.packages.urllib3.disable_warnings()
 
 def job():
     global counter
 
-    now = int(time.time())
-    print("Running requests - timestamp: %s (%s)" %
-        (now, time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime(now))))
+    url = "https://devt.de"
+
+    try:
+
+        now = int(time.time())
+        print("Running request for %s - timestamp: %s (%s)" %
+            (url, now, time.strftime("%d-%m-%Y %H:%M:%S", time.gmtime(now))))
+
+        r = requests.get(url)
+        res_time = r.elapsed
+
+        print ("    %s -> %s" % (url, res_time))
+
+        result = {
+            "key"     : str(now),
+            "kind"    : "PingResult",
+            "url"     : url,
+            "success" : True,
+            "result"  : str(res_time),
+        }
+
+    except Exception as e:
+        print("Error: %s", e)
+
+        result = {
+            "key"     : str(now),
+            "kind"    : "PingResult",
+            "url"     : url,
+            "success" : False,
+            "result"  : str(e),
+        }
+
+    try:
+        r = requests.post('https://%s/db/v1/graph/main/n' % ELIASDB_URL,
+            json.dumps([result]),  verify=False)
+
+        if r.status_code != 200:
+            print("Could not store result: %s", r.text)
+
+    except Exception as e:
+        print("Error storing result: %s", e)
 
-    r = requests.get('https://devt.de')
-    res_time = r.elapsed
-    print ("  https://devt.de -> ", res_time)
 
-schedule.every(2).seconds.do(job)
+schedule.every(5).seconds.do(job)
 
 while True:
     schedule.run_pending()

+ 2 - 0
examples/data-mining/docker-images/eliasdb/docker-compose.yml

@@ -1,5 +1,7 @@
 version: "3"
 
+# Sample file to just spin up the eliasdb cluster
+
 services:
   eliasdb1:
     image: data-mining/eliasdb1:latest

+ 5 - 0
examples/data-mining/docker-images/frontend/Dockerfile

@@ -0,0 +1,5 @@
+FROM nginx:alpine
+
+COPY etc/default.conf /etc/nginx/conf.d/default.conf
+COPY app/ /usr/share/nginx/html
+

File diff suppressed because it is too large
+ 11 - 0
examples/data-mining/docker-images/frontend/app/dist/frontend.js


+ 15 - 0
examples/data-mining/docker-images/frontend/app/index.html

@@ -0,0 +1,15 @@
+<!doctype html>
+<html>
+<head>
+ <meta charset="utf-8"> 
+    <link rel="shortcut icon" href="./pig.gif">
+    
+    <title>Data Mining</title> 
+</head>
+
+<body>
+    <div id="app"></div>
+</body>
+<script src="./dist/frontend.js"></script>
+
+</html>

+ 33 - 0
examples/data-mining/docker-images/frontend/app/package.json

@@ -0,0 +1,33 @@
+{
+  "name": "data-mining-frontend",
+  "version": "1.0.0",
+  "description": "The frontend of the data-mining example",
+  "main": "bundle.js",
+  "scripts": {
+    "build": "webpack",
+    "watch": "webpack --watch",
+    "pretty": "tsc --noEmit && eslint 'src/**/*.{js,ts,tsx}' --quiet --fix"
+  },
+  "author": "Matthias Ladkau",
+  "license": "ISC",
+  "devDependencies": {
+    "@typescript-eslint/eslint-plugin": "^1.13.0",
+    "@typescript-eslint/parser": "^1.13.0",
+    "css-loader": "^3.1.0",
+    "eslint": "^6.1.0",
+    "eslint-config-prettier": "^6.0.0",
+    "eslint-plugin-prettier": "^3.1.0",
+    "prettier": "^1.18.2",
+    "ts-loader": "^6.0.4",
+    "typescript": "^3.5.3",
+    "vue": "^2.6.10",
+    "vue-loader": "^15.7.1",
+    "vue-template-compiler": "^2.6.10",
+    "webpack": "^4.39.1",
+    "webpack-cli": "^3.3.6"
+  },
+  "dependencies": {
+    "chart.js": "^2.9.3",
+    "vue-chartjs": "^3.5.0"
+  }
+}

BIN
examples/data-mining/docker-images/frontend/app/pig.gif


+ 28 - 0
examples/data-mining/docker-images/frontend/app/src/component/LineChart.vue

@@ -0,0 +1,28 @@
+<!--
+*
+* EliasDB - Data mining frontend example
+*
+* Copyright 2020 Matthias Ladkau. All rights reserved.
+*
+* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*
+
+Simple line chart using vue-chartjs.
+-->
+<script>
+import { Line, mixins } from "vue-chartjs";
+
+export default {
+  extends: Line,
+  mixins: [mixins.reactiveProp],
+  props: ["options"],
+  mounted() {
+    this.renderChart(this.chartData, this.options);
+  }
+};
+</script>
+
+<style>
+</style>

+ 102 - 0
examples/data-mining/docker-images/frontend/app/src/component/LineResult.vue

@@ -0,0 +1,102 @@
+<!--
+*
+* EliasDB - Data mining frontend example
+*
+* Copyright 2020 Matthias Ladkau. All rights reserved.
+*
+* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/.
+*
+
+Graph which renders a result set as a line chart.
+-->
+<template>
+  <div class="small">
+    <line-chart :chart-data="linechartdata" :options="linechartoptions"></line-chart>
+    <div class="chat-msg-window" v-for="msg in messages" v-bind:key="msg.key">
+      <div>{{msg.result}}</div>
+    </div>
+  </div>
+</template>
+
+<script lang="ts">
+import Vue from "vue";
+import LineChart from "./LineChart.vue";
+import { EliasDBGraphQLClient } from "../lib/eliasdb-graphql";
+
+interface Message {
+  key: string;
+  result: string;
+}
+
+export default Vue.extend({
+  props: ["name", "last"],
+  data() {
+    return {
+      client: new EliasDBGraphQLClient(),
+      messages: [] as Message[],
+      linechartdata: null as any,
+      linechartoptions: {
+        responsive: true,
+        maintainAspectRatio: false
+      }
+    };
+  },
+  mounted: async function() {
+    // Ensure channel node exists
+
+    let results: any[] = [];
+
+    try {
+      const response = await this.client.req(`
+{
+  PingResult(ascending: "key", last:${this.last}) {
+    key
+    result
+    success
+    url
+  }
+}`);
+      results = JSON.parse(response).data.PingResult;
+      console.log("Results:", results);
+    } catch (e) {
+      console.error("Could not query results:", e);
+    }
+
+    let labels: string[] = [];
+    let data: number[] = [];
+
+    results.forEach(r => {
+      const timestamp = new Date();
+      timestamp.setTime(parseInt(r["key"]) * 1000);
+      const secs = parseFloat("0." + r["result"].split(".")[1]);
+      if (!isNaN(secs)) {
+        labels.push(timestamp.toISOString());
+        data.push(secs);
+      }
+    });
+
+    this.linechartdata = {
+      labels: labels,
+      datasets: [
+        {
+          label: this.name,
+          backgroundColor: "#f87979",
+          data: data,
+          fill: false
+        }
+      ]
+    };
+  },
+  components: {
+    LineChart
+  }
+});
+</script>
+
+<style>
+.small {
+  margin: 50px auto;
+}
+</style>

+ 21 - 0
examples/data-mining/docker-images/frontend/app/src/index.ts

@@ -0,0 +1,21 @@
+import Vue from 'vue';
+import LineResult from './component/LineResult.vue';
+
+let v = new Vue({
+    el: '#app',
+    template: `
+    <div>
+        <h1>Ping results for devt.de</h1>
+        <line-result :name="name" :last="last" />
+    </div>
+    `,
+    data() {
+        return {
+            name: 'Ping Result',
+            last: '50',
+        };
+    },
+    components: {
+        LineResult,
+    },
+});

+ 231 - 0
examples/data-mining/docker-images/frontend/app/src/lib/eliasdb-graphql.ts

@@ -0,0 +1,231 @@
+/**
+ * EliasDB - JavaScript GraphQL client library (http version)
+ *
+ * Copyright 2019 Matthias Ladkau. All rights reserved.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+export enum RequestMetod {
+    Post = 'post',
+    Get = 'get',
+}
+
+export class EliasDBGraphQLClient {
+    /**
+     * Host this client is connected to.
+     */
+    protected host: string;
+
+    /**
+     * Partition this client is working on.
+     */
+    protected partition: string;
+
+    /**
+     * Websocket over which we can handle subscriptions.
+     */
+    private ws?: WebSocket;
+
+    /**
+     * EliasDB GraphQL endpoints.
+     */
+    private graphQLEndpoint: string;
+    private graphQLReadOnlyEndpoint: string;
+
+    /**
+     * List of operations to execute once the websocket connection is established.
+     */
+    private delayedOperations: {(): void}[] = [];
+
+    /**
+     * Queue of subscriptions which await an id;
+     */
+    private subscriptionQueue: {(data: any): void}[] = [];
+
+    /**
+     * Map of active subscriptions.
+     */
+    private subscriptionCallbacks: {[id: string]: {(data: any): void}} = {};
+
+    /**
+     * Createa a new EliasDB GraphQL Client.
+     *
+     * @param host Host to connect to.
+     * @param partition Partition to query.
+     */
+    public constructor(
+        host: string = window.location.host,
+        partition: string = 'main',
+    ) {
+        this.host = host;
+        this.partition = partition;
+        this.graphQLEndpoint = `http://${host}/db/v1/graphql/${partition}`;
+        this.graphQLReadOnlyEndpoint = `http://${host}/db/v1/graphql-query/${partition}`;
+    }
+
+    /**
+     * Initialize a websocket to support subscriptions.
+     */
+    private initWebsocket() {
+        const url = `ws://${this.host}/db/v1/graphql-subscriptions/${this.partition}`;
+        this.ws = new WebSocket(url);
+        this.ws.onmessage = this.message.bind(this);
+
+        this.ws.onopen = () => {
+            if (this.ws) {
+                this.ws.send(
+                    JSON.stringify({
+                        type: 'init',
+                        payload: {},
+                    }),
+                );
+            }
+        };
+    }
+
+    /**
+     * Run a GraphQL query or mutation and return the response.
+     *
+     * @param query Query to run.
+     * @param variables List of variable values. The query must define these
+     *                  variables.
+     * @param operationName Name of the named operation to run. The query must
+     *                      specify this named operation.
+     * @param method  Request method to use. Get requests cannot run mutations.
+     */
+    public req(
+        query: string,
+        variables: {[key: string]: any} = {},
+        operationName: string = '',
+        method: RequestMetod = RequestMetod.Post,
+    ): Promise<any> {
+        const http = new XMLHttpRequest();
+
+        const toSend: {[key: string]: any} = {
+            operationName,
+            variables,
+            query,
+        };
+
+        // Send an async ajax call
+
+        if (method === RequestMetod.Post) {
+            http.open(method, this.graphQLEndpoint, true);
+        } else {
+            const params = Object.keys(toSend)
+                .map(key => {
+                    const val =
+                        key !== 'variables'
+                            ? toSend[key]
+                            : JSON.stringify(toSend[key]);
+                    return `${key}=${encodeURIComponent(val)}`;
+                })
+                .join('&');
+            const url = `${this.graphQLReadOnlyEndpoint}?${params}`;
+
+            http.open(method, url, true);
+        }
+
+        http.setRequestHeader('content-type', 'application/json');
+
+        return new Promise(function(resolve, reject) {
+            http.onload = function() {
+                try {
+                    if (http.status === 200) {
+                        resolve(http.response);
+                    } else {
+                        let err: string;
+                        try {
+                            err = JSON.parse(http.responseText)['errors'];
+                        } catch {
+                            err = http.responseText.trim();
+                        }
+                        reject(err);
+                    }
+                } catch (e) {
+                    reject(e);
+                }
+            };
+
+            if (method === RequestMetod.Post) {
+                http.send(JSON.stringify(toSend));
+            } else {
+                http.send();
+            }
+        });
+    }
+
+    /**
+     * Run a GraphQL subscription and receive updates if the data changes.
+     *
+     * @param query Query to run.
+     * @param update Update callback.
+     */
+    public subscribe(
+        query: string,
+        update: (data: any) => void,
+        variables: any = null,
+    ) {
+        if (!this.ws) {
+            this.initWebsocket();
+        }
+
+        if (this.ws) {
+            const that = this;
+            const subscribeCall = function() {
+                if (that.ws) {
+                    that.ws.send(
+                        JSON.stringify({
+                            id: that.subscriptionQueue.length,
+                            query,
+                            type: 'subscription_start',
+                            variables: null,
+                        }),
+                    );
+                    that.subscriptionQueue.push(update);
+                }
+            };
+
+            if (this.ws.readyState !== WebSocket.OPEN) {
+                this.delayedOperations.push(subscribeCall);
+            } else {
+                subscribeCall();
+            }
+        }
+    }
+
+    /**
+     * Process a new websocket message.
+     *
+     * @param msg New message.
+     */
+    protected message(msg: MessageEvent) {
+        const pmsg = JSON.parse(msg.data);
+
+        if (pmsg.type == 'init_success') {
+            // Execute the delayed operations
+
+            this.delayedOperations.forEach(c => c());
+            this.delayedOperations = [];
+        } else if (pmsg.type == 'subscription_success') {
+            const callback = this.subscriptionQueue.shift();
+            if (callback) {
+                const id = pmsg.id;
+                this.subscriptionCallbacks[id] = callback;
+            }
+        } else if (pmsg.type == 'subscription_data') {
+            const callback = this.subscriptionCallbacks[pmsg.id];
+            if (callback) {
+                callback(pmsg.payload);
+            }
+        } else if (pmsg.type == 'subscription_fail') {
+            console.error(
+                'Subscription failed: ',
+                pmsg.payload.errors.join('; '),
+            );
+        }
+    }
+}

+ 4 - 0
examples/data-mining/docker-images/frontend/app/src/vue-shims.d.ts

@@ -0,0 +1,4 @@
+declare module '*.vue' {
+    import Vue from 'vue';
+    export default Vue;
+}

+ 66 - 0
examples/data-mining/docker-images/frontend/app/tsconfig.json

@@ -0,0 +1,66 @@
+{
+  "compilerOptions": {
+    /* Basic Options */
+    // "incremental": true,                   /* Enable incremental compilation */
+    "target": "es2015",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
+    "module": "es2015",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
+    // "lib": [],                             /* Specify library files to be included in the compilation. */
+    // "allowJs": true,                       /* Allow javascript files to be compiled. */
+    // "checkJs": true,                       /* Report errors in .js files. */
+    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
+    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
+    // "declarationMap": true,                /* Generates a sourcemap for each corresponding '.d.ts' file. */
+    "sourceMap": true,                     /* Generates corresponding '.map' file. */
+    // "outFile": "./",                       /* Concatenate and emit output to single file. */
+    "outDir": "./build/",                        /* Redirect output structure to the directory. */
+    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
+    // "composite": true,                     /* Enable project compilation */
+    // "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */
+    // "removeComments": true,                /* Do not emit comments to output. */
+    // "noEmit": true,                        /* Do not emit outputs. */
+    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
+    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
+    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
+
+    /* Strict Type-Checking Options */
+    "strict": true,                           /* Enable all strict type-checking options. */
+    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
+    // "strictNullChecks": true,              /* Enable strict null checks. */
+    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
+    // "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
+    // "strictPropertyInitialization": true,  /* Enable strict checking of property initialization in classes. */
+    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
+    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */
+
+    /* Additional Checks */
+    // "noUnusedLocals": true,                /* Report errors on unused locals. */
+    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
+    "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
+    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */
+
+    /* Module Resolution Options */
+    "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
+    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
+    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
+    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
+    // "typeRoots": [],                       /* List of folders to include type definitions from. */
+    // "types": [],                           /* Type declaration files to be included in compilation. */
+    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
+    "esModuleInterop": true                   /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
+    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */
+    // "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */
+
+    /* Source Map Options */
+    // "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */
+    // "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */
+    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
+    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
+
+    /* Experimental Options */
+    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
+    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
+  },
+  "include": [
+    "./src/**/*"
+  ]
+}

+ 104 - 0
examples/data-mining/docker-images/frontend/app/webpack.config.js

@@ -0,0 +1,104 @@
+var path = require('path')
+var webpack = require('webpack')
+const VueLoaderPlugin = require('vue-loader/lib/plugin')
+
+module.exports = {
+  entry: './src/index.ts',
+  output: {
+    path: path.resolve(__dirname, './dist'),
+    publicPath: '/dist/',
+    filename: 'frontend.js'
+  },
+  module: {
+    rules: [
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: {
+          loaders: {
+            // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
+            // the "scss" and "sass" values for the lang attribute to the right configs here.
+            // other preprocessors should work out of the box, no loader config like this necessary.
+            'scss': 'vue-style-loader!css-loader!sass-loader',
+            'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
+          }
+          // other vue-loader options go here
+        }
+      },
+      {
+        test: /\.tsx?$/,
+        loader: 'ts-loader',
+        exclude: /node_modules/,
+        options: {
+          appendTsSuffixTo: [/\.vue$/],
+        }
+      },
+      {
+        test: /\.(png|jpg|gif|svg)$/,
+        loader: 'file-loader',
+        options: {
+          name: '[name].[ext]?[hash]'
+        }
+      },
+      {
+        test: /\.css$/,
+        use: [
+          'vue-style-loader',
+          'css-loader'
+        ]
+      }
+    ]
+  },
+  resolve: {
+    extensions: ['.ts', '.js', '.vue', '.json'],
+    alias: {
+      'vue$': 'vue/dist/vue.esm.js'
+    }
+  },
+  devServer: {
+    historyApiFallback: true,
+    noInfo: true
+  },
+  performance: {
+    hints: false
+  },
+  devtool: '#eval-source-map',
+  plugins: [
+    // make sure to include the plugin for the magic
+    new VueLoaderPlugin(),
+    // add a nice banner
+    new webpack.BannerPlugin({ 
+        banner: `
+ EliasDB - Data mining frontend example
+
+ Copyright 2020 Matthias Ladkau. All rights reserved.
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+`, 
+        entryOnly: true 
+    })
+  ]
+}
+
+if (process.env.NODE_ENV === 'production') {
+  module.exports.devtool = '#source-map'
+  // http://vue-loader.vuejs.org/en/workflow/production.html
+  module.exports.plugins = (module.exports.plugins || []).concat([
+    new webpack.DefinePlugin({
+      'process.env': {
+        NODE_ENV: '"production"'
+      }
+    }),
+    new webpack.optimize.UglifyJsPlugin({
+      sourceMap: true,
+      compress: {
+        warnings: false
+      }
+    }),
+    new webpack.LoaderOptionsPlugin({
+      minimize: true
+    })
+  ])
+}

+ 15 - 0
examples/data-mining/docker-images/frontend/build.sh

@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# Copy graphiql from examples
+
+cp -fR ../../../tutorial/res/graphiql ./app
+
+# Build the collector component
+
+docker build --tag data-mining/frontend .
+
+# Run container
+# docker run -p 8080:80 data-mining/frontend
+
+# Run an interactive shell on the build image with:
+# docker run -it data-mining/frontend sh

+ 25 - 0
examples/data-mining/docker-images/frontend/etc/default.conf

@@ -0,0 +1,25 @@
+server {
+    listen       80;
+    server_name  localhost;
+
+    #charset koi8-r;
+    #access_log  /var/log/nginx/host.access.log  main;
+
+    location / {
+        root   /usr/share/nginx/html;
+        index  index.html index.htm;
+    }
+
+    #error_page  404              /404.html;
+
+    # redirect server error pages to the static page /50x.html
+    #
+    error_page   500 502 503 504  /50x.html;
+    location = /50x.html {
+        root   /usr/share/nginx/html;
+    }
+
+    location /db/ {
+        proxy_pass   https://eliasdb1:9090/db/;
+    }
+}