Browse Source

Refactoring server for multiple session contexts

luis 7 years ago
parent
commit
0d914ebff5

+ 21 - 1
browser/vue-flow/src/App.vue

@@ -5,8 +5,27 @@
 </template>
 
 <script>
+import Vue from 'vue'
+import FlowService from './services/flowservice'
 export default {
-  name: 'App'
+  name: 'App',
+  created () {
+    console.log(this.$route)
+    let ctx = this.$route.params.context
+    console.log('Context:', ctx)
+
+    let urlPath = [
+      window.location.host,
+      ctx,
+      'conn'
+    ]
+
+    let targetws = 'ws://' + urlPath.join('/')
+    if (window.location.protocol === 'https:') {
+      targetws = 'wss://' + urlPath.join('/')
+    }
+    Vue.use(FlowService, {location: targetws})
+  }
 }
 </script>
 
@@ -14,6 +33,7 @@ export default {
 * {
   box-sizing:border-box;
 }
+
 #app {
   position:relative;
   padding:0;

+ 2 - 2
browser/vue-flow/src/components/flow/manager.vue

@@ -308,12 +308,12 @@ export default {
           // find Parent
 
           var curTarget = ev.target
-          for (;curTarget = curTarget.parentNode; curTarget !== document.body) {
+          for (; curTarget.hasAttribute !== undefined && curTarget !== document.body; curTarget = curTarget.parentNode) {
             if (curTarget.hasAttribute('data-nodeid')) {
               break
             }
           }
-          if (curTarget === document.body) {
+          if (!curTarget.hasAttribute || curTarget === document.body) {
             console.error('LINK: target is not a socket')
             return
           }

+ 3 - 2
browser/vue-flow/src/components/main.vue

@@ -228,7 +228,7 @@ export default {
     // Handle incoming things
     this.$flowService.on('sessionJoin', (v) => {
       if (v.id !== this.$route.params.sessId) {
-        this.$router.push('/' + v.id) // Swap to ID
+        this.$router.push('/' + this.$route.params.context + '/' + v.id) // Swap to ID
       }
     })
     this.$flowService.on('registry', (v) => {
@@ -317,7 +317,8 @@ export default {
   flex-direction: column;
 }
 
-.flow-main .app-flow-container {
+.app-flow-container {
+  width:100%;
   position:relative;
   flex:1;
 }

+ 5 - 2
browser/vue-flow/src/components/panel-inspector.vue

@@ -101,6 +101,7 @@ export default {
 .flow-inspector {
   font-size:12px;
   flex:1;
+  width:100%;
   overflow:hidden;
   height: available;
   display:flex;
@@ -112,10 +113,10 @@ export default {
   padding:10px;
   display:flex;
   flex-flow:column;
-  white-space: nowrap;
   width:100%;
   flex-basis:100%;
   transition: all var(--transition-speed);
+  overflow-x:hidden;
   overflow-y:auto;
 }
 
@@ -124,7 +125,7 @@ export default {
   color: var(--normal);
 }
 
-.flow-inspector__area {
+.flow-inspector__area{
   flex:1;
 }
 
@@ -150,6 +151,8 @@ export default {
 
 .flow-inspector__area .property {
   white-space: normal;
+  word-wrap: break-word;
+
 }
 
 .flow-inspector--properties-error {

+ 4 - 3
browser/vue-flow/src/components/shared/hx-contextmenu.vue

@@ -21,8 +21,8 @@ module.exports = {
   },
   computed: {
     style () {
-      let x = this.x - 20
-      let y = this.y - 10
+      let x = this.x
+      let y = this.y
 
       if (!this.$el || !this.$parent || !this.$parent.$el) { return }
       const parentRect = this.$parent.$el.getBoundingClientRect()
@@ -69,7 +69,8 @@ module.exports = {
   background: var(--background-secondary);
   color: var(--normal);
   transition: opacity var(--transition-speed);
-  border: solid 1px rgba(150,150,150,0.1)
+  border: solid 1px rgba(150,150,150,0.1);
+  box-shadow: 0 1px 4px rgba(0,0,0,0.5);
 }
 
 .hx-context-menu.visible{

+ 2 - 0
browser/vue-flow/src/components/shared/hx-split.vue

@@ -79,6 +79,7 @@ export default {
 .split {
   display: flex;
   flex: 1;
+  max-width:100%;
   height: 100%;
 }
 
@@ -109,6 +110,7 @@ export default {
 .split.resizeable.vertical > .splitter {
   cursor: row-resize;
 }
+
 .split.resizeable.horizontal > .splitter {
   cursor: col-resize;
 }

+ 0 - 8
browser/vue-flow/src/main.js

@@ -1,16 +1,8 @@
 import Vue from 'vue'
 import App from './App.vue'
-import FlowService from './services/flowservice'
 
 import router from './router'
 
-let targetws = 'ws://' + window.location.host + '/conn'
-if (window.location.protocol === 'https:') {
-  targetws = 'wss://' + window.location.host + '/conn'
-}
-
-Vue.use(FlowService, {location: targetws})
-
 window.app = new Vue({
   el: '#app',
   router,

+ 2 - 1
browser/vue-flow/src/router/index.js

@@ -8,7 +8,8 @@ export default new Router({
   mode: 'history',
   routes: [
     { path: '/', component: FlowMain },
-    { path: '/:sessId', component: FlowMain }
+    { path: '/:context', component: FlowMain },
+    { path: '/:context/:sessId', component: FlowMain }
   ]
 
 })

+ 1 - 1
browser/vue-flow/webpack.config.js

@@ -122,7 +122,7 @@ if (process.env.NODE_ENV === 'development') {
     output: {
       path: path.resolve(__dirname, './docs'),
       // publicPath: '/vue-edi-table/',
-      publicPath: '/',
+      publicPath: '',
       filename: 'index.js'
     }
   })

+ 140 - 0
go/src/flow/cmd/demo1/devops/devops.go

@@ -0,0 +1,140 @@
+package devops
+
+import (
+	"bufio"
+	"flow/registry"
+	"io"
+	"math/rand"
+	"strings"
+	"time"
+)
+
+// New create a new devops Registry
+func New() *registry.R {
+
+	r := registry.New()
+
+	r.Add(
+		dockerNew,
+		setWriter,
+		dockerTest,
+	).Tags("build")
+
+	return r
+}
+
+//////////////////////
+// DevOps SIM
+////////
+
+// DockerHandler example
+type DockerHandler struct {
+	out io.Writer
+}
+
+func dockerNew() DockerHandler {
+	return DockerHandler{}
+}
+
+func setWriter(out io.Writer, o DockerHandler) DockerHandler {
+	o.out = out
+	return o
+}
+
+//
+func dockerTest(handler DockerHandler, imageName string) DockerHandler {
+	sampleData := `
+	make: Entering directory '/home/stdio/coding/Projects/Flow'
+make -C go test
+make[1]: Entering directory '/home/stdio/coding/Projects/Flow/go'
+gocov test -race ./src/... | gocov report
+ok  	flow	1.020s	coverage: 84.1% of statements
+?   	flow/cmd/buildops	[no test files]
+?   	flow/cmd/demo1	[no test files]
+?   	flow/flowserver	[no test files]
+?   	flow/flowserver/flowmsg	[no test files]
+?   	flow/internal/assert	[no test files]
+ok  	flow/registry	1.011s	coverage: 100.0% of statements
+
+flow/flow.go		 Flow.String			 100.00% (11/11)
+flow/utils.go		 RandString			 100.00% (10/10)
+flow/flow.go		 @290:21			 100.00% (8/8)
+flow/flow.go		 Flow.MarshalJSON		 100.00% (8/8)
+flow/flow.go		 Flow.Const			 100.00% (7/7)
+flow/flow.go		 Flow.Var			 100.00% (6/6)
+flow/flow.go		 @315:21			 100.00% (6/6)
+flow/hook.go		 Hooks.Attach			 100.00% (3/3)
+flow/flow.go		 Flow.Run			 100.00% (2/2)
+flow/flow.go		 Flow.SetRegistry		 100.00% (2/2)
+flow/operation.go	 @107:12			 100.00% (2/2)
+flow/operation.go	 operation.ID			 100.00% (1/1)
+flow/operation.go	 opFunc				 100.00% (1/1)
+flow/operation.go	 opVar				 100.00% (1/1)
+flow/operation.go	 opConst			 100.00% (1/1)
+flow/flow.go		 Flow.In			 100.00% (1/1)
+flow/operation.go	 @220:12			 100.00% (1/1)
+flow/flow.go		 Flow.Res			 100.00% (1/1)
+flow/operation.go	 @221:12			 100.00% (1/1)
+flow/operation.go	 opIn				 100.00% (1/1)
+flow/flow.go		 Flow.Hook			 100.00% (1/1)
+flow/operation.go	 operation.Set			 100.00% (1/1)
+flow/hook.go		 Hooks.wait			 100.00% (1/1)
+flow/flow.go		 Flow.SetIDGen			 100.00% (1/1)
+flow/hook.go		 Hooks.finish			 100.00% (1/1)
+flow/operation.go	 operation.processWithCtx	 100.00% (1/1)
+flow/flow.go		 @49:13				 100.00% (1/1)
+flow/operation.go	 newOpCtx			 100.00% (1/1)
+flow/operation.go	 operation.Process		 100.00% (1/1)
+flow/hook.go		 Hooks.start			 100.00% (1/1)
+flow/flow.go		 New				 100.00% (1/1)
+flow/flow.go		 Flow.DefOp			 92.31% (12/13)
+flow/flow.go		 Flow.Op			 88.89% (16/18)
+flow/flow.go		 @240:21			 84.21% (16/19)
+flow/flow.go		 Flow.run			 84.21% (16/19)
+flow/operation.go	 @166:8				 80.00% (4/5)
+flow/hook.go		 Hooks.Trigger			 78.57% (11/14)
+flow/flow.go		 Flow.Analyse			 75.00% (3/4)
+flow/flow.go		 Flow.getOp			 75.00% (3/4)
+flow/flow.go		 Flow.Must			 66.67% (2/3)
+flow/operation.go	 @93:12				 66.67% (2/3)
+flow/operation.go	 @121:12			 65.22% (30/46)
+flow/operation.go	 @123:10			 33.33% (1/3)
+flow/hook.go		 Hooks.error			 0.00% (0/1)
+flow/operation.go	 opNil				 0.00% (0/1)
+flow/operation.go	 @229:12			 0.00% (0/1)
+flow/operation.go	 dumbSet			 0.00% (0/0)
+flow			 ------------------------	 84.10% (201/239)
+
+flow/registry/entry.go		 NewEntry		 100.00% (18/18)
+flow/registry/registry.go	 R.Get			 100.00% (12/12)
+flow/registry/registry.go	 R.Add			 100.00% (8/8)
+flow/registry/entry.go		 Entry.DescInputs	 100.00% (8/8)
+flow/registry/batch.go		 Batch			 100.00% (6/6)
+flow/registry/registry.go	 R.Register		 100.00% (5/5)
+flow/registry/entry.go		 Entry.Extra		 100.00% (4/4)
+flow/registry/registry.go	 R.Entry		 100.00% (4/4)
+flow/registry/registry.go	 R.Clone		 100.00% (4/4)
+flow/registry/entry.go		 Entry.Tags		 100.00% (4/4)
+flow/registry/entry.go		 Entry.DescOutput	 100.00% (4/4)
+flow/registry/registry.go	 R.Descriptions		 100.00% (4/4)
+flow/registry/batch.go		 EntryBatch.Extra	 100.00% (3/3)
+flow/registry/batch.go		 EntryBatch.DescOutput	 100.00% (3/3)
+flow/registry/batch.go		 EntryBatch.DescInputs	 100.00% (3/3)
+flow/registry/batch.go		 EntryBatch.Tags	 100.00% (3/3)
+flow/registry/entry.go		 Entry.Err		 100.00% (1/1)
+flow/registry/registry.go	 New			 100.00% (1/1)
+flow/registry			 ---------------------	 100.00% (95/95)
+
+Total Coverage: 88.62% (296/334)
+make[1]: Leaving directory '/home/stdio/coding/Projects/Flow/go'
+make: Leaving directory '/home/stdio/coding/Projects/Flow'`
+
+	scanner := bufio.NewScanner(strings.NewReader(sampleData))
+
+	for scanner.Scan() {
+		time.Sleep(time.Duration(rand.Intn(3000)) * time.Millisecond)
+		handler.out.Write([]byte(scanner.Text()))
+	}
+
+	return handler
+}

+ 20 - 114
go/src/flow/cmd/demo1/main.go

@@ -1,20 +1,22 @@
 package main
 
 import (
-	"bufio"
 	"errors"
 	"flow"
+	"flow/cmd/demo1/devops"
 	"flow/flowserver"
 	"flow/registry"
 	"fmt"
-	"io"
 	"log"
 	"math"
 	"math/rand"
+	"net/http"
 	"strings"
 	"time"
 
 	"github.com/gohxs/prettylog"
+	"github.com/gohxs/webu"
+	"github.com/gohxs/webu/chain"
 )
 
 func main() {
@@ -22,9 +24,9 @@ func main() {
 	log.Println("Running version:", flowserver.Version)
 
 	// String functions
-	registry.Batch(
-		registry.Add(strings.Split).DescInputs("string", "separator"),
-		registry.Add(strings.Join).DescInputs("", "sep"),
+	registry.Describer(
+		registry.Add(strings.Split).Inputs("string", "separator"),
+		registry.Add(strings.Join).Inputs("", "sep"),
 		registry.Add(strings.Compare, strings.Contains),
 		registry.Register("Cat", func(a, b string) string { return a + " " + b }),
 		registry.Register("ToString", func(a interface{}) string { return fmt.Sprint(a) }),
@@ -35,8 +37,7 @@ func main() {
 		math.Abs, math.Cos, math.Sin, math.Exp, math.Exp2, math.Tanh, math.Max, math.Min,
 	).Tags("math").Extra("style", registry.M{"color": "#386"})
 
-	registry.Add(registry.Add(DockerPull)).Tags("devops") // Rand functions
-	registry.Batch(
+	registry.Describer(
 		registry.Add(rand.Int, rand.Intn, rand.Float64),
 		registry.Register("Perm", func(n int) []int {
 			if n > 10 { // Limiter for safety
@@ -50,7 +51,7 @@ func main() {
 	registry.Add(testErrorPanic, testErrorDelayed, testRandomError).
 		Tags("testing-errors")
 
-	registry.Batch(
+	registry.Describer(
 		registry.Register("wait", wait),
 		registry.Register("waitRandom", waitRandom),
 	).Tags("testing-time").Extra("style", map[string]string{"color": "#8a5"})
@@ -58,8 +59,17 @@ func main() {
 	addr := ":2015"
 	log.Println("Starting server  at:", addr)
 
-	f := flowserver.FlowServer{}
-	f.ListenAndServe(addr)
+	c := chain.New(webu.ChainLogger(prettylog.New("req")))
+
+	mux := http.NewServeMux()
+
+	mux.Handle("/", c.Build(http.RedirectHandler("/default/", 302).ServeHTTP))
+	mux.Handle("/default/", c.Build(flowserver.New(registry.Global, "default").ServeHTTP))
+
+	mux.Handle("/devops/", c.Build(flowserver.New(devops.New(), "devops").ServeHTTP))
+
+	// Context registry
+	http.ListenAndServe(addr, mux)
 }
 
 func wait(data flow.Data, n int) flow.Data {
@@ -96,107 +106,3 @@ func testRandomError(d flow.Data) (flow.Data, error) {
 	}
 	return d, nil
 }
-
-//////////////////////
-// DevOps SIM
-////////
-//
-type DockerImage struct{}
-
-//
-func DockerPull(w io.Writer, imageName string) DockerImage {
-	sampleData := `
-	make: Entering directory '/home/stdio/coding/Projects/Flow'
-make -C go test
-make[1]: Entering directory '/home/stdio/coding/Projects/Flow/go'
-gocov test -race ./src/... | gocov report
-ok  	flow	1.020s	coverage: 84.1% of statements
-?   	flow/cmd/buildops	[no test files]
-?   	flow/cmd/demo1	[no test files]
-?   	flow/flowserver	[no test files]
-?   	flow/flowserver/flowmsg	[no test files]
-?   	flow/internal/assert	[no test files]
-ok  	flow/registry	1.011s	coverage: 100.0% of statements
-
-flow/flow.go		 Flow.String			 100.00% (11/11)
-flow/utils.go		 RandString			 100.00% (10/10)
-flow/flow.go		 @290:21			 100.00% (8/8)
-flow/flow.go		 Flow.MarshalJSON		 100.00% (8/8)
-flow/flow.go		 Flow.Const			 100.00% (7/7)
-flow/flow.go		 Flow.Var			 100.00% (6/6)
-flow/flow.go		 @315:21			 100.00% (6/6)
-flow/hook.go		 Hooks.Attach			 100.00% (3/3)
-flow/flow.go		 Flow.Run			 100.00% (2/2)
-flow/flow.go		 Flow.SetRegistry		 100.00% (2/2)
-flow/operation.go	 @107:12			 100.00% (2/2)
-flow/operation.go	 operation.ID			 100.00% (1/1)
-flow/operation.go	 opFunc				 100.00% (1/1)
-flow/operation.go	 opVar				 100.00% (1/1)
-flow/operation.go	 opConst			 100.00% (1/1)
-flow/flow.go		 Flow.In			 100.00% (1/1)
-flow/operation.go	 @220:12			 100.00% (1/1)
-flow/flow.go		 Flow.Res			 100.00% (1/1)
-flow/operation.go	 @221:12			 100.00% (1/1)
-flow/operation.go	 opIn				 100.00% (1/1)
-flow/flow.go		 Flow.Hook			 100.00% (1/1)
-flow/operation.go	 operation.Set			 100.00% (1/1)
-flow/hook.go		 Hooks.wait			 100.00% (1/1)
-flow/flow.go		 Flow.SetIDGen			 100.00% (1/1)
-flow/hook.go		 Hooks.finish			 100.00% (1/1)
-flow/operation.go	 operation.processWithCtx	 100.00% (1/1)
-flow/flow.go		 @49:13				 100.00% (1/1)
-flow/operation.go	 newOpCtx			 100.00% (1/1)
-flow/operation.go	 operation.Process		 100.00% (1/1)
-flow/hook.go		 Hooks.start			 100.00% (1/1)
-flow/flow.go		 New				 100.00% (1/1)
-flow/flow.go		 Flow.DefOp			 92.31% (12/13)
-flow/flow.go		 Flow.Op			 88.89% (16/18)
-flow/flow.go		 @240:21			 84.21% (16/19)
-flow/flow.go		 Flow.run			 84.21% (16/19)
-flow/operation.go	 @166:8				 80.00% (4/5)
-flow/hook.go		 Hooks.Trigger			 78.57% (11/14)
-flow/flow.go		 Flow.Analyse			 75.00% (3/4)
-flow/flow.go		 Flow.getOp			 75.00% (3/4)
-flow/flow.go		 Flow.Must			 66.67% (2/3)
-flow/operation.go	 @93:12				 66.67% (2/3)
-flow/operation.go	 @121:12			 65.22% (30/46)
-flow/operation.go	 @123:10			 33.33% (1/3)
-flow/hook.go		 Hooks.error			 0.00% (0/1)
-flow/operation.go	 opNil				 0.00% (0/1)
-flow/operation.go	 @229:12			 0.00% (0/1)
-flow/operation.go	 dumbSet			 0.00% (0/0)
-flow			 ------------------------	 84.10% (201/239)
-
-flow/registry/entry.go		 NewEntry		 100.00% (18/18)
-flow/registry/registry.go	 R.Get			 100.00% (12/12)
-flow/registry/registry.go	 R.Add			 100.00% (8/8)
-flow/registry/entry.go		 Entry.DescInputs	 100.00% (8/8)
-flow/registry/batch.go		 Batch			 100.00% (6/6)
-flow/registry/registry.go	 R.Register		 100.00% (5/5)
-flow/registry/entry.go		 Entry.Extra		 100.00% (4/4)
-flow/registry/registry.go	 R.Entry		 100.00% (4/4)
-flow/registry/registry.go	 R.Clone		 100.00% (4/4)
-flow/registry/entry.go		 Entry.Tags		 100.00% (4/4)
-flow/registry/entry.go		 Entry.DescOutput	 100.00% (4/4)
-flow/registry/registry.go	 R.Descriptions		 100.00% (4/4)
-flow/registry/batch.go		 EntryBatch.Extra	 100.00% (3/3)
-flow/registry/batch.go		 EntryBatch.DescOutput	 100.00% (3/3)
-flow/registry/batch.go		 EntryBatch.DescInputs	 100.00% (3/3)
-flow/registry/batch.go		 EntryBatch.Tags	 100.00% (3/3)
-flow/registry/entry.go		 Entry.Err		 100.00% (1/1)
-flow/registry/registry.go	 New			 100.00% (1/1)
-flow/registry			 ---------------------	 100.00% (95/95)
-
-Total Coverage: 88.62% (296/334)
-make[1]: Leaving directory '/home/stdio/coding/Projects/Flow/go'
-make: Leaving directory '/home/stdio/coding/Projects/Flow'`
-
-	scanner := bufio.NewScanner(strings.NewReader(sampleData))
-
-	for scanner.Scan() {
-		time.Sleep(time.Duration(rand.Intn(4)) * time.Second)
-		w.Write([]byte(scanner.Text()))
-	}
-
-	return DockerImage{}
-}

+ 12 - 7
go/src/flow/flowserver/flowbuilder.go

@@ -6,6 +6,7 @@ import (
 	"flow/registry"
 	"log"
 	"reflect"
+	"strings"
 )
 
 // Node that will contain registry src
@@ -29,7 +30,7 @@ type FlowDocument struct {
 	Links []Link `json:"links"`
 }
 
-// FlowBuild build a flowGraph
+// FlowBuild build a flowGraph from incoming web ui json
 func FlowBuild(rawData []byte, r *registry.R) (*flow.Flow, error) {
 	doc := FlowDocument{[]Node{}, []Link{}}
 	err := json.Unmarshal(rawData, &doc)
@@ -78,17 +79,21 @@ func FlowBuild(rawData []byte, r *registry.R) (*flow.Flow, error) {
 				param[l.In] = f.Var(from.ID, from.Prop["init"])
 			case "Const":
 				// XXX: Automate this in a func
-				newVal := reflect.New(entry.Inputs[l.In])
 				raw := from.Label
 				//raw := from.Prop["value"]
-				if _, ok := newVal.Interface().(*string); ok {
-					log.Println("Trying to unmarshal a string")
-					raw = "\"" + raw + "\""
+				raw = strings.TrimSpace(raw)
+				if raw[0] == '"' { // its a string
+					log.Println("Unmashal string")
+					var val string
+					json.Unmarshal([]byte(raw), &val)
+					param[l.In] = val
+					continue
 				}
-				log.Println("Will unmarshal raw:", raw)
+				log.Println("Will unmarshal to input:", raw)
+				newVal := reflect.New(entry.Inputs[l.In])
 				err := json.Unmarshal([]byte(raw), newVal.Interface())
 				if err != nil {
-					// ignore error
+					// ignore error?
 					log.Println("unmarshalling Error", err)
 					//param[l.In] = nil
 					//continue

+ 30 - 33
go/src/flow/flowserver/flowserver.go

@@ -1,15 +1,15 @@
 package flowserver
 
 import (
+	"flow/registry"
 	"log"
 	"net/http"
 	"net/http/httputil"
 	"net/url"
 	"os"
+	"strings"
 
-	"github.com/gohxs/prettylog"
 	"github.com/gohxs/webu"
-	"github.com/gohxs/webu/chain"
 )
 
 //go:generate go get github.com/gohxs/genversion
@@ -17,47 +17,44 @@ import (
 //
 
 // FlowServer structure
-type FlowServer struct{}
-
-// ListenAndServe starts the httpserver
-// It will listen on default port 2015 and increase if port is in use
-func (f *FlowServer) ListenAndServe(addr string) error {
-	fsm := NewFlowSessionManager()
+type FlowServer struct {
+	mux *http.ServeMux
+}
 
-	c := chain.New(webu.ChainLogger(prettylog.New("req")))
+// New creates a New flow server
+func New(r *registry.R, store string) *FlowServer {
+	if r == nil {
+		r = registry.Global
+	}
 
 	mux := http.NewServeMux()
-	mux.Handle("/conn", c.Build(fsm.ServeHTTP))
+	mux.Handle("/conn", NewFlowSessionManager(r, store))
 
 	if os.Getenv("DEBUG") == "1" {
 		log.Println("DEBUG MODE: reverse proxy localhost:8081")
 		proxyURL, err := url.Parse("http://localhost:8081")
 		if err != nil {
-			return err
+			return nil
 		}
-		mux.Handle("/", c.Build(httputil.NewSingleHostReverseProxy(proxyURL).ServeHTTP))
+		mux.Handle("/", httputil.NewSingleHostReverseProxy(proxyURL))
 	} else {
-		mux.Handle("/", c.Build(webu.StaticHandler("web", "index.html")))
+		mux.Handle("/", webu.StaticHandler("web", "index.html"))
 	}
 
-	return http.ListenAndServe(addr, mux)
-
-	////////////////////
-	// Server starter
-	/////
-	//port := 2015
-	//for {
-	//addr := fmt.Sprintf(":%d", port)
-	//s, err := net.Listen("tcp", addr)
-	//if err != nil {
-	//log.Println("Listen error:", err)
-	//port++
-	//continue
-	//}
-	//log.Println("Listening at:", addr)
-	//err = http.Serve(s, mux)
-	//if err != nil {
-	//log.Fatal(err)
-	//}
-	//}
+	return &FlowServer{mux}
+}
+
+func (f *FlowServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	// Manual routing here
+	// Grab last part of path
+	urlParts := strings.Split(r.URL.Path, "/")
+	prefixToRemove := strings.Join(urlParts[:len(urlParts)-1], "/")
+	http.StripPrefix(prefixToRemove, f.mux).ServeHTTP(w, r)
+
+}
+
+// ListenAndServe starts the httpserver
+// It will listen on default port 2015 and increase if port is in use
+func (f *FlowServer) ListenAndServe(addr string) error {
+	return http.ListenAndServe(addr, f.mux)
 }

+ 7 - 26
go/src/flow/flowserver/session.go

@@ -5,23 +5,17 @@ import (
 	"errors"
 	"flow"
 	"flow/flowserver/flowmsg"
-	"flow/registry"
 	"fmt"
 	"io"
 	"io/ioutil"
 	"log"
 	"os"
-	"path/filepath"
 	"sync"
 	"time"
 
 	"github.com/gorilla/websocket"
 )
 
-const (
-	storePath = "store"
-)
-
 // NodeActivity when nodes are processing
 type NodeActivity struct {
 	Status    string    `json:"status"` // nodeStatus, Running, error, result
@@ -34,7 +28,7 @@ type NodeActivity struct {
 // FlowSession Create a session and link clients
 type FlowSession struct {
 	sync.Mutex
-	mgr *FlowSessionManager
+	manager *FlowSessionManager
 
 	ID string // Random handle for sessionID
 	// List of clients on this session
@@ -48,11 +42,11 @@ type FlowSession struct {
 }
 
 //NewSession creates and initializes a NewSession
-func NewSession(mgr *FlowSessionManager, ID string) *FlowSession {
+func NewSession(fsm *FlowSessionManager, ID string) *FlowSession {
 	// Or load
 	//
 	//
-	fpath, err := pathFor(ID)
+	fpath, err := fsm.pathFor(ID)
 	if err != nil {
 		log.Println("Error fetching filepath", err)
 	}
@@ -67,7 +61,7 @@ func NewSession(mgr *FlowSessionManager, ID string) *FlowSession {
 
 	s := &FlowSession{
 		Mutex:        sync.Mutex{},
-		mgr:          mgr,
+		manager:      fsm,
 		ID:           ID,
 		clients:      []*websocket.Conn{},
 		Chat:         ChatRoom{},
@@ -87,7 +81,7 @@ func (s *FlowSession) ClientAdd(c *websocket.Conn) error {
 	if err != nil {
 		return err
 	}
-	desc, err := registry.Descriptions()
+	desc, err := s.manager.registry.Descriptions()
 	if err != nil {
 		return err
 	}
@@ -149,7 +143,7 @@ func (s *FlowSession) DocumentSave() error {
 	s.Lock()
 	defer s.Unlock()
 
-	fpath, err := pathFor(s.ID)
+	fpath, err := s.manager.pathFor(s.ID)
 	if err != nil {
 		log.Println("path error", err)
 		return err
@@ -188,7 +182,7 @@ func (s *FlowSession) NodeRun(c *websocket.Conn, data []byte) error {
 	go func() {
 		log.Printf("Building flow from '%s'\n", string(s.RawDoc))
 
-		localr := registry.Global.Clone()
+		localr := s.manager.registry.Clone()
 		//Add our log func that is not in global registry
 		localr.Register("Notify", func(v flow.Data, msg string) flow.Data {
 			s.Notify(msg)
@@ -287,16 +281,3 @@ func (s *FlowSession) broadcast(c *websocket.Conn, v interface{}) error {
 	return nil
 
 }
-
-func pathFor(ID string) (string, error) {
-	{
-		err := os.MkdirAll(storePath, 0755)
-		if err != nil {
-			return "", err
-		}
-	}
-	fpath := filepath.Clean(ID)
-	_, fpath = filepath.Split(fpath)
-	fpath = filepath.Join(storePath, fpath)
-	return fpath, nil
-}

+ 30 - 1
go/src/flow/flowserver/sessionmgr.go

@@ -5,8 +5,10 @@ import (
 	"errors"
 	"flow"
 	"flow/flowserver/flowmsg"
+	"flow/registry"
 	"log"
 	"net/http"
+	"os"
 	"path/filepath"
 	"runtime"
 	"sync"
@@ -16,6 +18,9 @@ import (
 
 //FlowSessionManager or FlowServerCore
 type FlowSessionManager struct {
+	name     string
+	registry *registry.R
+	store    string
 	// List of flow sessions?
 	sessions map[string]*FlowSession
 	chats    map[string]*ChatRoom
@@ -24,8 +29,10 @@ type FlowSessionManager struct {
 }
 
 //NewFlowSessionManager creates a New initialized FlowSessionManager
-func NewFlowSessionManager() *FlowSessionManager {
+func NewFlowSessionManager(r *registry.R, store string) *FlowSessionManager {
 	return &FlowSessionManager{
+		registry: r,
+		store:    store,
 		sessions: map[string]*FlowSession{},
 	}
 }
@@ -65,6 +72,7 @@ var upgrader = websocket.Upgrader{}
 
 func (fsm *FlowSessionManager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 
+	// Contextual flowsession
 	c, err := upgrader.Upgrade(w, r, nil)
 	if err != nil {
 		log.Println("upgrade:", err)
@@ -188,6 +196,27 @@ func (fsm *FlowSessionManager) ServeHTTP(w http.ResponseWriter, r *http.Request)
 	log.Println("ws Is disconnecting", r.RemoteAddr)
 }
 
+const (
+	storePath = "store"
+)
+
+func (fsm *FlowSessionManager) pathFor(ID string) (string, error) {
+	{
+		err := os.MkdirAll(storePath, 0755)
+		if err != nil {
+			return "", err
+		}
+		err = os.MkdirAll(filepath.Join(storePath, fsm.store), 0755)
+		if err != nil {
+			return "", err
+		}
+	}
+	fpath := filepath.Clean(ID)
+	_, fpath = filepath.Split(fpath)
+	fpath = filepath.Join(storePath, fsm.store, fpath)
+	return fpath, nil
+}
+
 func e(err error) bool {
 	if err == nil {
 		return false

+ 0 - 51
go/src/flow/registry/batch.go

@@ -1,51 +0,0 @@
-package registry
-
-//EntryBatch helper to batch set properties
-type EntryBatch []*Entry
-
-// Batch returns a batch of entries for easy manipulation
-func Batch(params ...interface{}) EntryBatch {
-	ret := EntryBatch{}
-	for _, el := range params {
-		switch v := el.(type) {
-		case EntryBatch:
-			ret = append(ret, v...)
-		case *Entry:
-			ret = append(ret, v)
-		}
-	}
-	return ret
-}
-
-//Tags set categories of the group
-func (eg EntryBatch) Tags(cat ...string) EntryBatch {
-	for _, e := range eg {
-		e.Tags(cat...)
-	}
-	return eg
-}
-
-// DescInputs describe inputs
-func (eg EntryBatch) DescInputs(input ...string) EntryBatch {
-	for _, e := range eg {
-		e.DescInputs(input...)
-	}
-	return eg
-
-}
-
-// DescOutput describe inputs
-func (eg EntryBatch) DescOutput(v string) EntryBatch {
-	for _, e := range eg {
-		e.DescOutput(v)
-	}
-	return eg
-}
-
-// Extra set extras of the group
-func (eg EntryBatch) Extra(name string, value interface{}) EntryBatch {
-	for _, e := range eg {
-		e.Extra(name, value)
-	}
-	return eg
-}

+ 95 - 0
go/src/flow/registry/describer.go

@@ -0,0 +1,95 @@
+package registry
+
+// Description of an entry
+type Description struct {
+	Name string   `json:"name"`
+	Desc string   `json:"description"`
+	Tags []string `json:"categories"`
+
+	//InputType
+	Inputs []DescType `json:"inputs"`
+	Output DescType   `json:"output"`
+
+	Extra map[string]interface{} `json:"extra"`
+}
+
+//EDescriber helper to batch set properties
+type EDescriber []*Entry
+
+// Describer returns a batch of entries for easy manipulation
+func Describer(params ...interface{}) EDescriber {
+	ret := EDescriber{}
+	for _, el := range params {
+		switch v := el.(type) {
+		case EDescriber:
+			ret = append(ret, v...)
+		case *Entry:
+			ret = append(ret, v)
+		}
+	}
+	return ret
+}
+
+// Description set node description
+func (d EDescriber) Description(m string) EDescriber {
+	for _, e := range d {
+		if e.Description == nil {
+			continue
+		}
+
+		e.Description.Desc = m
+	}
+	return d
+
+}
+
+//Tags set categories of the group
+func (d EDescriber) Tags(tags ...string) EDescriber {
+	for _, e := range d {
+		if e.Description == nil {
+			continue
+		}
+		e.Description.Tags = tags
+	}
+	return d
+}
+
+// Inputs describe inputs
+func (d EDescriber) Inputs(inputs ...string) EDescriber {
+	for _, e := range d {
+		if e.Description == nil {
+			continue
+		}
+
+		for i, dstr := range inputs {
+			if i >= len(e.Description.Inputs) { // do nothing
+				break // next entry
+			}
+			curDesc := e.Description.Inputs[i]
+			e.Description.Inputs[i] = DescType{curDesc.Type, dstr}
+		}
+	}
+	return d
+}
+
+// Output describe the output
+func (d EDescriber) Output(output string) EDescriber {
+	for _, e := range d {
+		if e.Description == nil {
+			continue
+		}
+		e.Description.Output = DescType{e.Description.Output.Type, output}
+	}
+	return d
+}
+
+// Extra set extras of the group
+func (d EDescriber) Extra(name string, value interface{}) EDescriber {
+	for _, e := range d {
+		if e.Description == nil {
+			continue
+		}
+		e.Description.Extra[name] = value
+	}
+	return d
+}

+ 3 - 3
go/src/flow/registry/batch_test.go

@@ -11,15 +11,15 @@ func TestMakeBatch(t *testing.T) {
 	a := assert.A(t)
 	r := registry.New()
 
-	b := registry.Batch(
+	b := registry.Describer(
 		r.Add(strings.Split, strings.Join),
 		r.Add(strings.Compare),
 		r.Register("named", strings.Compare),
 	)
 	a.Eq(len(b), 4, "should have 3 entries in batch")
 
-	b.DescInputs("str")
-	b.DescOutput("result")
+	b.Inputs("str")
+	b.Output("result")
 
 	for _, en := range b {
 		a.Eq(en.Description.Inputs[0].Name, "str", "first input should be string")

+ 5 - 61
go/src/flow/registry/entry.go

@@ -11,18 +11,6 @@ type DescType struct {
 	Name string `json:"name"`
 }
 
-// Description of an entry
-type Description struct {
-	Name string   `json:"name"`
-	Tags []string `json:"categories"`
-
-	//InputType
-	Inputs []DescType `json:"inputs"`
-	Output DescType   `json:"output"`
-
-	Extra map[string]interface{} `json:"extra"`
-}
-
 // Entry contains a function description params etc
 type Entry struct {
 	registry    *R
@@ -66,56 +54,12 @@ func NewEntry(r *R, fn interface{}) *Entry {
 	return e
 }
 
+// Describer return a description builder
+func (e *Entry) Describer() EDescriber {
+	return Describer(e)
+}
+
 // Err returns error of the entry if any
 func (e *Entry) Err() error {
 	return e.err
 }
-
-/////////////
-// Descriptions
-/////
-
-//Tags of the entry
-func (e *Entry) Tags(cat ...string) *Entry {
-	if e.err != nil {
-		return e
-	}
-	e.Description.Tags = cat
-	return e
-}
-
-// DescInputs description for Inputs
-func (e *Entry) DescInputs(desc ...string) *Entry {
-	if e.err != nil {
-		return e
-	}
-	for i, d := range desc {
-		if i >= len(e.Description.Inputs) { // do nothing
-			return e
-		}
-		curDesc := e.Description.Inputs[i]
-		e.Description.Inputs[i] = DescType{curDesc.Type, d}
-	}
-	return e
-}
-
-// DescOutput description for Input
-func (e *Entry) DescOutput(desc string) *Entry {
-	if e.err != nil {
-		return e
-	}
-	e.Description.Output = DescType{
-		e.Description.Output.Type,
-		desc,
-	}
-	return e
-}
-
-// Extra information on entry
-func (e *Entry) Extra(name string, extra interface{}) *Entry {
-	if e.err != nil {
-		return e
-	}
-	e.Description.Extra[name] = extra
-	return e
-}

+ 11 - 10
go/src/flow/registry/entry_test.go

@@ -24,7 +24,7 @@ func TestDescription(t *testing.T) {
 	r := registry.New()
 
 	e := registry.NewEntry(r, func(a int) int { return 0 })
-	e.Tags("a", "b")
+	e.Describer().Tags("a", "b")
 	a.Eq(e.Err(), nil, "should not fail setting categories")
 	a.Eq(len(e.Description.Tags), 2, "should have 2 categories")
 	a.Eq(len(e.Description.Inputs), 1, "Should have 2 input description")
@@ -32,18 +32,18 @@ func TestDescription(t *testing.T) {
 	e2 := registry.NewEntry(r, func(a, b int) int { return 0 })
 	a.Eq(len(e2.Description.Inputs), 2, "Should have 2 input description")
 
-	e.DescInputs("input")
+	e.Describer().Inputs("input")
 	a.Eq(e.Err(), nil, "should not fail setting input name")
 	a.Eq(e.Description.Inputs[0].Name, "input", "should have the input description")
 
-	e.DescInputs("input", "2", "3")
+	e.Describer().Inputs("input", "2", "3")
 	a.Eq(len(e.Description.Inputs), 1, "should have only one input")
 
-	e.DescOutput("output name")
+	e.Describer().Output("output name")
 	a.Eq(e.Err(), nil, "should not fail setting input description")
 	a.Eq(e.Description.Output.Name, "output name", "output description should be the same")
 
-	e.Extra("test", 123)
+	e.Describer().Extra("test", 123)
 	a.Eq(e.Err(), nil, "should not fail setting extra doc")
 	a.Eq(e.Description.Extra["test"], 123, "extra text should be as expected")
 }
@@ -52,14 +52,15 @@ func TestDescriptionError(t *testing.T) {
 	a := assert.A(t)
 	r := registry.New()
 	e := registry.NewEntry(r, "string")
+
 	a.Eq(e.Err(), registry.ErrNotAFunc, "entry is not a function")
-	e.Tags("a", "b")
+	e.Describer().Tags("a", "b")
 	a.Eq(e.Err(), registry.ErrNotAFunc, "entry is not a function")
-	e.DescInputs("input")
+	e.Describer().Inputs("input")
 	a.Eq(e.Err(), registry.ErrNotAFunc, "entry is not a function")
-	e.DescOutput("output")
+	e.Describer().Output("output")
 	a.Eq(e.Err(), registry.ErrNotAFunc, "entry is not a function")
-	e.Extra("test", 123)
+	e.Describer().Extra("test", 123)
 	a.Eq(e.Err(), registry.ErrNotAFunc, "entry is not a function")
 
 }
@@ -68,7 +69,7 @@ func TestEntryBatch(t *testing.T) {
 	a := assert.A(t)
 	r := registry.New()
 
-	b := registry.Batch(
+	b := registry.Describer(
 		registry.NewEntry(r, func() int { return 0 }),
 		registry.NewEntry(r, func() int { return 0 }),
 		registry.NewEntry(r, func() int { return 0 }),

+ 2 - 2
go/src/flow/registry/registry.go

@@ -38,9 +38,9 @@ func (r *R) Clone() *R {
 }
 
 // Add unnamed function
-func (r *R) Add(fns ...interface{}) EntryBatch {
+func (r *R) Add(fns ...interface{}) EDescriber {
 
-	b := EntryBatch{}
+	b := EDescriber{}
 	for _, fn := range fns {
 		if reflect.TypeOf(fn).Kind() != reflect.Func {
 			return nil

+ 21 - 0
test.txt

@@ -0,0 +1,21 @@
+Using default tag: latest
+latest: Pulling from base/archlinux
+6bf09137c659: Pulling fs layer
+e412e19e1798: Pulling fs layer
+dc224f9fe847: Pulling fs layer
+e1aef29dab31: Pulling fs layer
+e1aef29dab31: Waiting
+e412e19e1798: Verifying Checksum
+e412e19e1798: Download complete
+6bf09137c659: Verifying Checksum
+6bf09137c659: Download complete
+dc224f9fe847: Verifying Checksum
+dc224f9fe847: Download complete
+e1aef29dab31: Verifying Checksum
+e1aef29dab31: Download complete
+6bf09137c659: Pull complete
+e412e19e1798: Pull complete
+dc224f9fe847: Pull complete
+e1aef29dab31: Pull complete
+Digest: sha256:ea17a7802b4effc6c5239d01829336ee66fb37d842c0cd7d6e7067616cd0c994
+Status: Downloaded newer image for base/archlinux:latest