Browse Source

Initial commit

Luis Figueiredo 8 years ago
commit
5418905d4d
47 changed files with 3056 additions and 0 deletions
  1. 66 0
      SKETCH.md
  2. 6 0
      TODO.md
  3. 82 0
      frontend/static-web/index.html
  4. 185 0
      frontend/static-web/lib/hLayout/HLayout.css
  5. 262 0
      frontend/static-web/lib/hLayout/HLayout.js
  6. BIN
      frontend/static-web/lib/hLayout/OpenSans-Regular.ttf
  7. 4 0
      frontend/static-web/lib/jquery-2.2.3.min.js
  8. 139 0
      frontend/static-web/lib/prism/prism.css
  9. 7 0
      frontend/static-web/lib/prism/prism.js
  10. 184 0
      frontend/static-web/res/css/style.css
  11. 187 0
      frontend/static-web/res/main.js
  12. 14 0
      frontend/vue-web/.babelrc
  13. 9 0
      frontend/vue-web/.editorconfig
  14. 2 0
      frontend/vue-web/.eslintignore
  15. 32 0
      frontend/vue-web/.eslintrc.js
  16. 6 0
      frontend/vue-web/.gitignore
  17. 8 0
      frontend/vue-web/.postcssrc.js
  18. 21 0
      frontend/vue-web/README.md
  19. 35 0
      frontend/vue-web/build/build.js
  20. 45 0
      frontend/vue-web/build/check-versions.js
  21. 9 0
      frontend/vue-web/build/dev-client.js
  22. 89 0
      frontend/vue-web/build/dev-server.js
  23. 71 0
      frontend/vue-web/build/utils.js
  24. 12 0
      frontend/vue-web/build/vue-loader.conf.js
  25. 67 0
      frontend/vue-web/build/webpack.base.conf.js
  26. 35 0
      frontend/vue-web/build/webpack.dev.conf.js
  27. 120 0
      frontend/vue-web/build/webpack.prod.conf.js
  28. 6 0
      frontend/vue-web/config/dev.env.js
  29. 38 0
      frontend/vue-web/config/index.js
  30. 3 0
      frontend/vue-web/config/prod.env.js
  31. 15 0
      frontend/vue-web/index.html
  32. 71 0
      frontend/vue-web/package.json
  33. 60 0
      frontend/vue-web/src/App.vue
  34. BIN
      frontend/vue-web/src/assets/logo.png
  35. 45 0
      frontend/vue-web/src/components/Output.vue
  36. 56 0
      frontend/vue-web/src/components/Requester.vue
  37. 17 0
      frontend/vue-web/src/components/hLayout/Pane.vue
  38. 13 0
      frontend/vue-web/src/components/hLayout/Splitter.vue
  39. 15 0
      frontend/vue-web/src/main.js
  40. 14 0
      frontend/vue-web/src/router/index.js
  41. 3 0
      frontend/vue-web/src/store/console.js
  42. 0 0
      frontend/vue-web/static/.gitkeep
  43. 183 0
      frontend/vue-web/static/hLayout/HLayout.css
  44. 494 0
      frontend/vue-web/static/hLayout/HLayout.js
  45. BIN
      frontend/vue-web/static/hLayout/OpenSans-Regular.ttf
  46. 137 0
      frontend/vue-web/static/prism.css
  47. 189 0
      frontend/vue-web/static/style.css

+ 66 - 0
SKETCH.md

@@ -0,0 +1,66 @@
+restui
+============
+
+RestStudio (name in progress)
+
+Introduction:
+-------------
+
+* Create user free token based session, where we can setup history store Rest calls 
+* While saving the configurations we generate new versions instead of replacing content
+* Any user can access and setup endpoints/history
+* Provide a test output area and code editor to write json body 
+
+Server:
+  golang will provide data for session, store sessions
+
+Database:
+	mongodb or nosql database, sqllite (for easy setup in case this goes opensource)
+
+Post translator:
+	Provide a way to setup golang text templates to translate request/responses
+
+endpoint store:
+	while creating end points we can allow user to REST the server i.e:
+
+```
++-------+                                       +-------------+
+| User  |                                       | RestStudio  |
++-------+                                       +-------------+
+    |                                                  |
+    | Create GET call named 'hello'                    |
+    |------------------------------------------------->|
+    |                                                  |
+    | Save and generate ID                             |
+    |------------------------------------------------->|
+    |                                                  |
+    |                          SessionId: "djEIsjaLdj" |
+    |<-------------------------------------------------|
+    |                                                  |
+    | "curl http://reststudio/djElsjaLdj/hello"        |
+    |------------------------------------------------->|
+    |                                                  | --------------------------------------\
+    |                                                  |-| Perform the configured call 'hello' |
+    |                                                  | |-------------------------------------|
+    |                                                  |
+    |        Response from GET configured with 'hello' |
+    |<-------------------------------------------------|
+    |                                                  |
+```
+
+
+Model to store requests
+
+```go
+type Request struct { // common request model
+	Method string
+	URL string
+	Headers map[string]string
+	QueryParam map[string]string // It will be append to URL as http://url?key=value
+	Body string
+}
+```
+
+
+
+

+ 6 - 0
TODO.md

@@ -0,0 +1,6 @@
+TODO for restui
+------------------
+
+* Remote jquery dependency from hLayout - nearly done
+* create history based datastore
+

+ 82 - 0
frontend/static-web/index.html

@@ -0,0 +1,82 @@
+<html>
+<head>
+
+<script src='lib/jquery-2.2.3.min.js'></script>
+
+<script src='lib/prism/prism.js'></script>
+<link rel="stylesheet" type="text/css" href='lib/prism/prism.css'>
+
+<script src='lib/hLayout/HLayout.js'></script>
+<link rel="stylesheet" type="text/css" href='lib/hLayout/HLayout.css'>
+
+<link rel="stylesheet" type="text/css" href="res/css/style.css">
+
+<script src='res/main.js'></script>
+<title>Focal Gateway tester</title>
+
+
+</head>
+
+<body>
+	<div class="layout-container">
+		<section class="vertical">
+			<section class="horizontal" style="flex-basis:60%">
+				<div class="hpane requester">
+					<header>Requester</header>
+					<div class="hcontent">
+						<div class='flex-column'>
+							<input type='text' id='request-input' />
+							<div class='flex-row'>
+							<button data-method='GET' class='request'>GET</button>
+							<button data-method='POST' class='request'>POST</button>
+							<button data-method='DELETE' class='request'>DELETE</button>
+							<button data-method='PUT' class='request'>PUT</button>
+							</div>
+
+
+						</div>
+					</div>
+				</div>
+				<div class="hpane" style="flex-basis: fill">
+					<header>Output <button class='output-clear'>Clear</button></header>
+					<div class="hcontent">
+						<div class="flex-column">
+							<div class='output'></div>
+						</div>
+					</div>
+				</div>
+			</section>
+			<section class="horizontal">
+				<div id="header-list" class="hpane">
+					<header>Headers</header>
+					<div class="hcontent">
+						<div class="flex-column">
+							<button class='entry-new'>+</button>
+							<ul class='entry-list'>
+							
+							</ul>
+						</div>
+					</div>
+	
+				</div>
+				<div id="params-list" class="hpane">
+					<header>Params</header>
+					<div class="hcontent">
+						<div class="flex-column">
+							<button class='entry-new'>+</button>
+							
+							<ul class='entry-list'>
+							</ul>
+						</div>
+					</div>
+	
+				</div>
+				
+			</section>
+
+
+		</section>
+	</div>
+</body>
+
+</html>

+ 185 - 0
frontend/static-web/lib/hLayout/HLayout.css

@@ -0,0 +1,185 @@
+@font-face {
+  font-family: 'Open Sans';
+  src: url('OpenSans-Regular.ttf')  format('truetype') /* Safari, Android, iOS */
+}
+
+body,html {
+	position:relative;
+	width:100%;
+	height:100%;
+	margin:0px;
+	padding:0px;
+	
+}
+* {
+	box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+}
+
+.layout-container {
+	box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+	
+	
+	
+	padding:5px;
+	position:absolute;
+	top:0px;
+	bottom:0px;
+	left:0px;
+	right:0px;
+	
+	
+	
+	display:flex;
+	background: #333;
+	
+}
+
+
+section {
+	
+	display:flex;
+	flex:1 0 auto;
+	background:transparent;
+	
+}
+
+section.vertical {
+	flex-direction: row;
+}
+section.horizontal {
+	flex-direction: column;
+}
+
+
+section.horizontal > .splitter {
+	cursor:s-resize;
+}
+section.vertical > .splitter {
+	cursor:e-resize;
+}
+
+.splitter {
+	background: transparent;
+	flex-basis:6px;
+	flex-grow: 0;
+	min-width:6px;
+	min-height:6px;
+
+}
+
+
+/* AKA PAne */
+
+.hpane {
+	/*box-shadow: 0px 0px 10px #000;*/
+	position:relative;
+	overflow:hidden;
+	flex: 1 1 auto;
+	flex-direction: column;
+	/* Theme part */
+	
+	background: #EEE;
+	color: #333;
+	
+	transition: border 1s ease;
+	opacity:1;
+}
+
+.hpane.discover {
+	box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+	
+	gborder: solid 40px #999;
+	transition: border 1s ease;
+}
+
+.hpane.detached {
+	position:Fixed;
+	/*width:20%;*/
+	/*height:50%;*/
+	z-index:99;
+	 
+	box-shadow: 0px 0px 10px #000;
+	
+	
+}
+
+.hpane.detached.moving {
+	opacity: 0.5;
+	pointer-events: none;
+	
+}
+
+
+
+.hpane.preview {
+	background: #555;
+	border: dashed 1px #FFF;
+}
+
+.hpane >header {
+	
+	padding:10px;
+	background: #555;
+	color:#FFF;
+	font-family: 'Open Sans';
+	text-transform:uppercase;
+	font-weight: 700;
+	
+	-webkit-touch-callout: none; /* iOS Safari */
+	-webkit-user-select: none;   /* Chrome/Safari/Opera */
+	-khtml-user-select: none;    /* Konqueror */
+	-moz-user-select: none;      /* Firefox */
+	-ms-user-select: none;       /* IE/Edge */
+	user-select: none;
+	
+}
+.hpane .hcontent {
+	overflow:auto;
+	position:absolute;
+	top:40px;
+	bottom:0px;
+	left:0px;
+	right:0px;
+	
+	background: #EEE;
+	color:#333;
+	font-family: 'Open Sans';
+	-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+	-moz-box-sizing: border-box;    /* Firefox, other Gecko */
+	box-sizing: border-box;         /* Opera/IE 8+ */
+}
+
+
+.attach-preview {
+	position:fixed;
+	background: rgba(0,0,0,0.2);
+	border: dashed 1px #777;
+	z-index:90;
+	pointer-events:none;
+	transition: all 0.2s;
+}
+
+section.horizontal {
+	cursor:s-resize;
+}
+section.vertical {
+	cursor:e-resize;
+}
+section.horizontal > *:first-child {
+	rborder: solid 1px #F00;
+	margin-bottom:7px;
+}
+section.vertical > *:first-child {
+	rborder: solid 1px #F00;
+	margin-right:7px;
+}
+	section > * {
+		cursor:initial;
+	}
+

+ 262 - 0
frontend/static-web/lib/hLayout/HLayout.js

@@ -0,0 +1,262 @@
+
+var md;
+
+function startHLayoutEvents() {
+	// Global mouse event;
+	$(document).on('mousemove',function(e) {
+		if(md == undefined) md = {x:e.clientX,y:e.clientY};
+		md.deltaX = e.clientX - md.x;
+		md.deltaY = e.clientY - md.y;
+		md.x = e.clientX;
+		md.y = e.clientY;
+	});
+	
+	var $grab = undefined;
+	$(document).on('mousedown','.hpane > header',function(e){
+		if(e.which != 1) return;
+		if(!$(e.target).is(".hpane > header")) return;
+		
+		e.preventDefault();
+		e.stopPropagation();
+		
+		$grab = $(this).parent();
+		$parent = $grab.parent();
+		var next = $grab.next();
+		var origW = $grab.width();
+		var origH = $grab.height();
+		
+		// Check if we are root?
+		
+		var dim = {w: $grab.width(), h: $grab.height()};
+		var pos = $grab.position();
+	
+		$($grab).detach().appendTo('body').addClass("detached").addClass("moving").css({width:dim.w,height:dim.h});
+		
+		$('.hpane:not(.detached)').addClass("discover");
+		
+		// All childs to pointer event none
+		if($parent.is('section')) {
+			// We aren't
+
+			$child = $parent.children('.hpane, section');
+			$child.attr('style','');
+			$child.css('flex-basis',$parent.css('flex-basis'));
+			$child.detach();
+			$parent.replaceWith($child);
+		}
+	})
+	
+	var $sectionResize = null;
+	$(document).on('mousedown','section',function(e) {
+		
+		var $target = $(e.target);
+		if(!$target.is("section"))return;
+		e.preventDefault();
+		$sectionResize = $target;
+		$sectionResize.addClass("resizing");
+	});
+	$(document).on('mousemove',function(e) {
+		if($sectionResize == null) return;
+		var $first = $sectionResize.children(':nth-child(1)');
+		var $second = $sectionResize.children(':nth-child(2)');
+		
+		
+		var common;
+		var common2;
+		if( $sectionResize.is(".horizontal")) {common = "height",common2 = "deltaY"};
+		if( $sectionResize.is(".vertical")) {common = "width", common2 = "deltaX"};
+
+		var parentValue = $sectionResize[common]();
+		var hFirst = $first[common]();
+		var hSec = $second[common]();
+	
+		var newVal1 = (hFirst + md[common2]) / parentValue * 100;
+		var newVal2 = (hSec -md[common2]) / parentValue * 100;
+			
+		$first.css('flex-basis',newVal1 + "%");
+		$second.css('flex-basis',newVal2 + "%");
+			
+	});
+	$(document).on('mouseup',function(e) {
+		if($sectionResize) {
+			$sectionResize.removeClass("resizing");
+		}
+
+		$sectionResize = null;
+	});
+	
+	
+	$(document).on('mousemove',function(e) {
+		if($grab == undefined) return;
+		var pos = $grab.position();
+		
+		var nx = pos.left + md.deltaX;
+		var ny = pos.top + md.deltaY;
+		var offX = $grab.width() / 2;
+		var offY = 0;
+		
+		$grab.css({left: e.clientX - offX, top: e.clientY -offY});
+		
+		// Check target
+		var $target = $(e.target);
+		
+		if(!$target.is(".hpane")) {
+			$target = $target.parents(".hpane");
+		}
+		
+		
+		if($target.is(".hpane:not(.preview)")){
+			var placement = checkAttach($target,e);
+			if(placement != 0 ) {
+				previewPanel($grab,$target,placement);
+			} else {
+				removePreviews();
+			}
+		
+		} else {
+			removePreviews();
+			
+		}
+		
+	});
+	
+	$(document).on('mouseup',function(e) {
+		if($grab == undefined) return;
+		
+		removePreviews();
+
+		$grab.removeClass("moving");
+		$('.hpane').removeClass("discover");
+
+		$target = $(e.target);
+		if(!$target.is(".hpane")) $target = $target.parents('.hpane');
+		
+		if($target.is(".hpane")){
+			var placement = checkAttach($target,e);
+			if(placement != 0 ) {
+				$grab.removeClass("detached");
+				$grab.attr('style','');
+				attachPanel($grab,$target,placement);
+			}
+		}
+		
+		$grab = undefined;
+		return;
+	});
+	
+}
+
+
+	
+	function recalcFlex() {
+		$('section.horizontal > section, section.horizontal > .hpane').each(function() {
+			var parentH = $(this).parent().height();
+			var curH = $(this).height();
+			var flex = 100 * curH / parentH;
+			$(this).css({'flex-basis':flex + "%"});
+		});
+		$('section.vertical > section, section.vertical > .hpane').each(function() {
+			var parentW = $(this).parent().width();
+			var curW = $(this).width();
+			var flex = 100 * curW / parentW;
+			$(this).css({'flex-basis':flex + "%"});
+		});
+	}	
+	recalcFlex();
+	
+	function checkAttach($target,e) {
+		
+		var tW = $target.width()/3;
+		var tH = $target.height()/3;
+		
+		var targetPos = $target.offset();
+		var dim = { w: $target.width(), h: $target.height()};
+		var rPos = { x: e.pageX - targetPos.left,  y: e.pageY - targetPos.top};
+		//Calc dists and check the closest one
+		
+		
+		if(rPos.x > tW && rPos.x < dim.w - tW && rPos.y < tH) {  					// 1 Top
+			return 1;
+		} else if(rPos.x > tW && rPos.x < dim.w - tW && rPos.y > dim.h - tH){ 		// 3 Bottom
+			return 3;
+		} else if(rPos.x < tW) {													// 2 Left
+			return 2;
+		} else if( rPos.x > dim.w - tW){											// 4 Right
+			return 4;
+		}
+		
+		return 0;
+	}
+	function removePreviews() {
+		$('.attach-preview').hide();
+	}
+	function previewPanel($grab,$target,attach) {
+		
+		$previewDiv = $(".attach-preview");
+		if($previewDiv.length == 0 ) {
+			$previewDiv = $('<div class="attach-preview" style="display:none;position:fixed"></div>').appendTo('body');
+		}
+		
+		$previewDiv.show();
+		var dim = {w:$target.width(),h:$target.height()};
+		var pos = $target.offset();
+		
+		if((attach % 2) == 1) {
+			$previewDiv.css('height',dim.h/3);
+			$previewDiv.css("width",dim.w);
+			$previewDiv.css("left",pos.left);
+			if(attach == 1) {
+				$previewDiv.css('top',pos.top);
+			} else {
+				$previewDiv.css('top',pos.top  + dim.h - dim.h/3);
+			}
+		} else if((attach % 2) == 0) {
+			$previewDiv.css("height",dim.h);
+			$previewDiv.css('width',dim.w/3);
+			$previewDiv.css("top",pos.top);
+			if(attach == 2) {
+				$previewDiv.css('left',pos.left);
+			} else {
+				$previewDiv.css('left',pos.left + dim.w - dim.w/3);
+			}
+
+		}
+			
+	}
+	function attachPanel(what,target, attach) {
+		if(attach){
+			var type
+			if((attach % 2) == 1)type = "horizontal";
+			if((attach % 2) == 0)type = "vertical";
+			$target = $(target);
+			$parent = $(target).parent();
+			
+			$sib = $(target);
+			if($sib.next().is("section, .hpane,.splitter")) {
+				$section = $("<section class='" + type +"' style='flex-basis:" + $target.css('flex-basis') + "'></section>").prependTo($parent);
+			} else {
+				$section = $("<section class='" + type + "' style='flex-basis:" + $target.css('flex-basis') +" '></section>").appendTo($parent);
+			}
+			$sib.detach();
+			what.css('flex-basis','33%');
+			$sib.css('flex-basis','66%');
+			if(attach < 3) {
+				$section.append(what);
+				//$section.append("<div class='splitter'></div>");
+				$section.append($sib);
+			} else {
+				$section.append($sib);
+				//$section.append("<div class='splitter'></div>");
+				$section.append(what);
+			}
+			
+		}
+	
+	}
+	
+
+$(function() {
+	
+	startHLayoutEvents();
+	
+});

BIN
frontend/static-web/lib/hLayout/OpenSans-Regular.ttf


File diff suppressed because it is too large
+ 4 - 0
frontend/static-web/lib/jquery-2.2.3.min.js


+ 139 - 0
frontend/static-web/lib/prism/prism.css

@@ -0,0 +1,139 @@
+/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+json */
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+	color: black;
+	background: none;
+	text-shadow: 0 1px white;
+	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+	text-align: left;
+	white-space: pre;
+	word-spacing: normal;
+	word-break: normal;
+	word-wrap: normal;
+	line-height: 1.5;
+
+	-moz-tab-size: 4;
+	-o-tab-size: 4;
+	tab-size: 4;
+
+	-webkit-hyphens: none;
+	-moz-hyphens: none;
+	-ms-hyphens: none;
+	hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
+	text-shadow: none;
+	background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
+code[class*="language-"]::selection, code[class*="language-"] ::selection {
+	text-shadow: none;
+	background: #b3d4fc;
+}
+
+@media print {
+	code[class*="language-"],
+	pre[class*="language-"] {
+		text-shadow: none;
+	}
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+	padding: 1em;
+	margin: .5em 0;
+	overflow: auto;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+	background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+	padding: .1em;
+	border-radius: .3em;
+	white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+	color: slategray;
+}
+
+.token.punctuation {
+	color: #999;
+}
+
+.namespace {
+	opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+	color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+	color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+	color: #a67f59;
+	background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+	color: #07a;
+}
+
+.token.function {
+	color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+	color: #e90;
+}
+
+.token.important,
+.token.bold {
+	font-weight: bold;
+}
+.token.italic {
+	font-style: italic;
+}
+
+.token.entity {
+	cursor: help;
+}
+

File diff suppressed because it is too large
+ 7 - 0
frontend/static-web/lib/prism/prism.js


+ 184 - 0
frontend/static-web/res/css/style.css

@@ -0,0 +1,184 @@
+* {
+		box-sizing: border-box;
+		-webkit-box-sizing: border-box;
+	}
+html,body {
+	font-family: 'Open Sans';
+	position:Relative;
+	
+	width:100%;
+	height:100%;
+	padding:0px;
+	margin:0px;
+}
+.flex-column {
+	width:100%;
+	height:100%;
+	display:flex;
+	flex-direction:column;
+}
+.flex-column > * {
+	padding:10px;
+	margin:10px;
+}
+
+.flex-row {
+	
+	width:100%;
+	
+	display:flex;
+	flex-direction:row;
+	align-items:center;
+	justify-content:center;
+	padding:0px;
+	margin:0px;
+}
+.flex-row > *{
+	flex-grow:1;
+	
+}
+
+.output {
+	height:100%;
+	border: solid 1px rgba(0,0,0,0.1);
+	overflow-y:auto;
+	
+}
+.output p {
+	margin: 2px 2px 2px 2px;
+}
+.output p.error {
+	color: #F55;
+}
+button {
+	margin: 10px 10px 10px 10px;
+	padding: 10px 20px 10px 20px;
+	background: #555;
+	color: #FFF;
+	border:none;
+	font-size: 16px;
+	font-weight:bold;
+}
+button:hover {
+	background: #888;
+}
+
+.request {
+	
+} 
+
+.layout-container {
+	background: #EEE;
+}
+
+.hpane {
+	background: #FFF;
+	box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.3);
+}
+.hpane >header {
+	background: #EEE;
+	color:#333;
+	
+}
+.hpane .hcontent {
+	background: #FFF;
+}
+section.horizontal > *:first-child {
+	margin-bottom:9px;
+}
+section.vertical > *:first-child {
+	margin-right:9px;
+}
+
+
+
+ul.entry-list {
+	list-style-type: none;
+	display:flex;
+	padding:0px;
+	margin:0px;
+	flex-direction: column;
+}
+.entry-list > li {
+	rborder: solid 1px #F00;
+	display:flex;
+	flex-direction:row;
+	
+	
+	height:48px;
+	
+	align-items: center;
+    justify-content: center;
+    margin:10px;
+  	
+  	
+}
+.entry-list > li > * {
+	margin:0px;
+	padding:0px;
+	display:flex;
+	align-items:center;
+	justify-content:center;
+	width:100%;
+	height:100%;
+	
+}
+
+.entry-list > li > input[type=text] {
+	
+	/*border:none;
+	border-bottom: solid 1px #555;*/
+}
+
+.entry-list > li > .entry-name {
+	
+	font-weight: bold;
+	
+	
+	align-items:right;
+	padding:10px;
+	margin-right:10px;
+	flex:auto;
+	text-align:right;
+}
+.entry-list > li> .entry-content {
+	
+	text-align:center;
+	padding:10px;
+	margin-right:10px;
+	
+	
+	
+}
+.entry-list > li > .entry-remove
+{
+	padding:0px;
+	margin:0px;
+	width:47px;
+	min-width:47px;
+	
+	
+
+	
+}
+
+
+.hpane.requester {
+	flex-grow:0.2;
+	min-height: fit-content;
+	min-height: 160px;
+	
+}
+.output-clear {
+	background:Transparent;
+	color:#333;
+	margin:0px;
+	padding:2px;
+	float:right;
+}
+.output-clear:hover {
+	background:Transparent;
+	color:#777;
+}
+}
+

+ 187 - 0
frontend/static-web/res/main.js

@@ -0,0 +1,187 @@
+
+// Start headers
+var requester = {
+		headers: {
+			'x-sgw':12345,
+			'othertest':1234
+		},
+		params: {
+			
+		}
+};
+
+class MLog {
+	constructor(jElem,listobj) {
+		this.jElem = jElem;
+		this.count = 0;
+	}
+	raw(msg) {
+		var $output = $(this.jElem);
+		
+		$output.each(function() {
+			var $this = $(this); // Cache element
+			var tolerance = 100;
+			
+			
+			var doscroll = false;
+			if($this.scrollTop() + $this.innerHeight() >= $this.prop('scrollHeight')-tolerance){
+				doscroll = true;
+			
+			}
+		
+			$this.append( msg );
+		
+			if(doscroll) {
+				$this.scrollTop($this.prop('scrollHeight'));
+			}
+		});
+		this.count++;
+	}
+	info(msg) {
+
+		this.raw('<p>[' + this.count + '] ' + msg + '</p>');
+	}
+	error(msg) {
+		this.raw('<p class="error">[' + this.count + '] ' + msg + '</p>');
+	}
+	clear() {
+		$(this.jElem).html('');
+	}
+}
+
+var mlog = new MLog('.output');
+
+
+class ListController {
+	constructor(targetPane,targetObject) {
+		this.targetPane = targetPane;
+		this.targetObject = targetObject;
+	}
+	setupInteraction() {
+		var self = this;
+		$(document).on('click', this.targetPane + ' .entry-remove',function() {
+			$(this).parent().remove() 
+		});
+		$(document).on('click', this.targetPane + ' .entry-new',function() {
+			self.add(); 
+		});
+		$(this.targetPane + ' .entry-input').on('keypress', function(e) {
+			if(e.which != 13) return;
+			
+			var content = $(this).val();
+			var arr = content.split(':');
+			if(arr.length != 2) {
+				mlog.error('Invalid format');
+				return;
+			}
+			var headerKey = arr[0].trim();
+			var headerContent = arr[1].trim();
+			mlog.info('Header:' + headerKey + ' as "' + headerContent +'"');
+			
+			self.targetObject[arr[0].trim()] = arr[1].trim();
+			self.update();
+			$(this).val('');
+			
+		});
+		
+	}
+	update() {
+		console.log("HEaders: %o", this.targetObject);
+		
+		for( var k in this.targetObject) {
+			this.add(k,this.targetObject[k]);
+		}
+	}
+	add(key,value) {
+		var k = key || "";
+		var v = value || "";
+		
+		var $list = $(this.targetPane + ' .entry-list');
+		$list.append('<li>'
+				+ '<input type="text" class="entry-name" value="' + k + '">' 
+				+ '<input type="text" class="entry-content" value="' + v + '">'
+				+ '<button class="entry-remove">X</button>'
+				+ '</li>');
+		
+	}
+	values() {
+		var ret = {};
+		$(this.targetPane + ' .entry-list > li').each(function() {
+			
+			var k = $(this).find('.entry-name').val();
+			var v = $(this).find('.entry-content').val();
+			ret[k] = v
+			
+		});
+		return ret;
+	}
+	
+}
+
+
+	
+$(function() {
+		
+	$('.output-clear').on('click',function(e) {
+		e.preventDefault();
+		e.stopPropagation();
+		mlog.clear();
+	})
+		var headerList = new ListController('#header-list',requester.headers);
+	
+		headerList.setupInteraction();
+		headerList.update();
+		
+		var paramList = new ListController('#params-list',requester.params);
+		paramList.setupInteraction();
+		paramList.update();
+		
+		$('#request-input').val('/api/endpoint/1234/named1/method1');
+		
+		$('.request').on('click',function() {
+			var req = $('#request-input').val();
+			var method= $(this).attr('data-method');
+			
+			mlog.info(method.toUpperCase() + ": " + req + " with var: " + Prism.highlight(JSON.stringify(requester.params),Prism.languages.json)); 
+			var params = paramList.values();
+			$.ajax({
+				url: req,
+				method: method,
+				data: params,
+				beforeSend: function(xhr) {
+					var headers = headerList.values();
+					for( var k in headers) {
+						xhr.setRequestHeader(k,headers[k]);	
+					}
+					 
+				},
+				success: function(data) {
+					var result = data;
+					try {
+						if(typeof(data) === "string") {
+							data = JSON.parse(data);
+						}
+						var json = JSON.stringify(data,null,'    ');
+						result = Prism.highlight(json,Prism.languages.json);
+					} catch(e) {
+						mlog.error(e.message);
+					}
+		
+					mlog.raw('<pre><code>'+ result + '</code></pre>');
+					
+				},
+				error: function(xhr, err, thrown) {
+				
+					mlog.error('[' + xhr.status + '] ' +thrown + '</font>');
+				}
+			})
+		});
+		
+		
+		// Header handler area
+		
+	
+		
+		
+	});
+	

+ 14 - 0
frontend/vue-web/.babelrc

@@ -0,0 +1,14 @@
+{
+  "presets": [
+    ["env", { "modules": false }],
+    "stage-2"
+  ],
+  "plugins": ["transform-runtime"],
+  "comments": false,
+  "env": {
+    "test": {
+      "presets": ["env", "stage-2"],
+      "plugins": [ "istanbul" ]
+    }
+  }
+}

+ 9 - 0
frontend/vue-web/.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 2 - 0
frontend/vue-web/.eslintignore

@@ -0,0 +1,2 @@
+build/*.js
+config/*.js

+ 32 - 0
frontend/vue-web/.eslintrc.js

@@ -0,0 +1,32 @@
+// http://eslint.org/docs/user-guide/configuring
+
+module.exports = {
+  root: true,
+  parser: 'babel-eslint',
+  parserOptions: {
+    sourceType: 'module'
+  },
+  env: {
+    browser: true,
+  },
+  // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
+  extends: 'standard',
+  // required to lint *.vue files
+  plugins: [
+    'html'
+  ],
+  // add your custom rules here
+  'rules': {
+		'quotes':[1,'double'],
+		'no-tabs':0,
+		'indent':[1,'tab'],
+		'space-before-function-paren':0,
+		'disallowTabs':0,
+    // allow paren-less arrow functions
+    'arrow-parens': 0,
+    // allow async-await
+    'generator-star-spacing': 0,
+    // allow debugger during development
+    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
+  }
+}

+ 6 - 0
frontend/vue-web/.gitignore

@@ -0,0 +1,6 @@
+.DS_Store
+node_modules/
+dist/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*

+ 8 - 0
frontend/vue-web/.postcssrc.js

@@ -0,0 +1,8 @@
+// https://github.com/michael-ciniawsky/postcss-load-config
+
+module.exports = {
+  "plugins": {
+    // to edit target browsers: use "browserlist" field in package.json
+    "autoprefixer": {}
+  }
+}

+ 21 - 0
frontend/vue-web/README.md

@@ -0,0 +1,21 @@
+# restcli
+
+> Interface to test rest
+
+## Build Setup
+
+``` bash
+# install dependencies
+npm install
+
+# serve with hot reload at localhost:8080
+npm run dev
+
+# build for production with minification
+npm run build
+
+# build for production and view the bundle analyzer report
+npm run build --report
+```
+
+For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader).

+ 35 - 0
frontend/vue-web/build/build.js

@@ -0,0 +1,35 @@
+require('./check-versions')()
+
+process.env.NODE_ENV = 'production'
+
+var ora = require('ora')
+var rm = require('rimraf')
+var path = require('path')
+var chalk = require('chalk')
+var webpack = require('webpack')
+var config = require('../config')
+var webpackConfig = require('./webpack.prod.conf')
+
+var spinner = ora('building for production...')
+spinner.start()
+
+rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
+  if (err) throw err
+  webpack(webpackConfig, function (err, stats) {
+    spinner.stop()
+    if (err) throw err
+    process.stdout.write(stats.toString({
+      colors: true,
+      modules: false,
+      children: false,
+      chunks: false,
+      chunkModules: false
+    }) + '\n\n')
+
+    console.log(chalk.cyan('  Build complete.\n'))
+    console.log(chalk.yellow(
+      '  Tip: built files are meant to be served over an HTTP server.\n' +
+      '  Opening index.html over file:// won\'t work.\n'
+    ))
+  })
+})

+ 45 - 0
frontend/vue-web/build/check-versions.js

@@ -0,0 +1,45 @@
+var chalk = require('chalk')
+var semver = require('semver')
+var packageConfig = require('../package.json')
+
+function exec (cmd) {
+  return require('child_process').execSync(cmd).toString().trim()
+}
+
+var versionRequirements = [
+  {
+    name: 'node',
+    currentVersion: semver.clean(process.version),
+    versionRequirement: packageConfig.engines.node
+  },
+  {
+    name: 'npm',
+    currentVersion: exec('npm --version'),
+    versionRequirement: packageConfig.engines.npm
+  }
+]
+
+module.exports = function () {
+  var warnings = []
+  for (var i = 0; i < versionRequirements.length; i++) {
+    var mod = versionRequirements[i]
+    if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
+      warnings.push(mod.name + ': ' +
+        chalk.red(mod.currentVersion) + ' should be ' +
+        chalk.green(mod.versionRequirement)
+      )
+    }
+  }
+
+  if (warnings.length) {
+    console.log('')
+    console.log(chalk.yellow('To use this template, you must update following to modules:'))
+    console.log()
+    for (var i = 0; i < warnings.length; i++) {
+      var warning = warnings[i]
+      console.log('  ' + warning)
+    }
+    console.log()
+    process.exit(1)
+  }
+}

+ 9 - 0
frontend/vue-web/build/dev-client.js

@@ -0,0 +1,9 @@
+/* eslint-disable */
+require('eventsource-polyfill')
+var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
+
+hotClient.subscribe(function (event) {
+  if (event.action === 'reload') {
+    window.location.reload()
+  }
+})

+ 89 - 0
frontend/vue-web/build/dev-server.js

@@ -0,0 +1,89 @@
+require('./check-versions')()
+
+var config = require('../config')
+if (!process.env.NODE_ENV) {
+  process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
+}
+
+var opn = require('opn')
+var path = require('path')
+var express = require('express')
+var webpack = require('webpack')
+var proxyMiddleware = require('http-proxy-middleware')
+var webpackConfig = require('./webpack.dev.conf')
+
+// default port where dev server listens for incoming traffic
+var port = process.env.PORT || config.dev.port
+// automatically open browser, if not set will be false
+var autoOpenBrowser = !!config.dev.autoOpenBrowser
+// Define HTTP proxies to your custom API backend
+// https://github.com/chimurai/http-proxy-middleware
+var proxyTable = config.dev.proxyTable
+
+var app = express()
+var compiler = webpack(webpackConfig)
+
+var devMiddleware = require('webpack-dev-middleware')(compiler, {
+  publicPath: webpackConfig.output.publicPath,
+  quiet: true
+})
+
+var hotMiddleware = require('webpack-hot-middleware')(compiler, {
+  log: () => {}
+})
+// force page reload when html-webpack-plugin template changes
+compiler.plugin('compilation', function (compilation) {
+  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
+    hotMiddleware.publish({ action: 'reload' })
+    cb()
+  })
+})
+
+// proxy api requests
+Object.keys(proxyTable).forEach(function (context) {
+  var options = proxyTable[context]
+  if (typeof options === 'string') {
+    options = { target: options }
+  }
+  app.use(proxyMiddleware(options.filter || context, options))
+})
+
+// handle fallback for HTML5 history API
+app.use(require('connect-history-api-fallback')())
+
+// serve webpack bundle output
+app.use(devMiddleware)
+
+// enable hot-reload and state-preserving
+// compilation error display
+app.use(hotMiddleware)
+
+// serve pure static assets
+var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
+app.use(staticPath, express.static('./static'))
+
+var uri = 'http://0.0.0.0:' + port
+
+var _resolve
+var readyPromise = new Promise(resolve => {
+  _resolve = resolve
+})
+
+console.log('> Starting dev server...')
+devMiddleware.waitUntilValid(() => {
+  console.log('> Listening at ' + uri + '\n')
+  // when env is testing, don't need open it
+  if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') {
+    opn(uri)
+  }
+  _resolve()
+})
+
+var server = app.listen(port)
+
+module.exports = {
+  ready: readyPromise,
+  close: () => {
+    server.close()
+  }
+}

+ 71 - 0
frontend/vue-web/build/utils.js

@@ -0,0 +1,71 @@
+var path = require('path')
+var config = require('../config')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+
+exports.assetsPath = function (_path) {
+  var assetsSubDirectory = process.env.NODE_ENV === 'production'
+    ? config.build.assetsSubDirectory
+    : config.dev.assetsSubDirectory
+  return path.posix.join(assetsSubDirectory, _path)
+}
+
+exports.cssLoaders = function (options) {
+  options = options || {}
+
+  var cssLoader = {
+    loader: 'css-loader',
+    options: {
+      minimize: process.env.NODE_ENV === 'production',
+      sourceMap: options.sourceMap
+    }
+  }
+
+  // generate loader string to be used with extract text plugin
+  function generateLoaders (loader, loaderOptions) {
+    var loaders = [cssLoader]
+    if (loader) {
+      loaders.push({
+        loader: loader + '-loader',
+        options: Object.assign({}, loaderOptions, {
+          sourceMap: options.sourceMap
+        })
+      })
+    }
+
+    // Extract CSS when that option is specified
+    // (which is the case during production build)
+    if (options.extract) {
+      return ExtractTextPlugin.extract({
+        use: loaders,
+        fallback: 'vue-style-loader'
+      })
+    } else {
+      return ['vue-style-loader'].concat(loaders)
+    }
+  }
+
+  // https://vue-loader.vuejs.org/en/configurations/extract-css.html
+  return {
+    css: generateLoaders(),
+    postcss: generateLoaders(),
+    less: generateLoaders('less'),
+    sass: generateLoaders('sass', { indentedSyntax: true }),
+    scss: generateLoaders('sass'),
+    stylus: generateLoaders('stylus'),
+    styl: generateLoaders('stylus')
+  }
+}
+
+// Generate loaders for standalone style files (outside of .vue)
+exports.styleLoaders = function (options) {
+  var output = []
+  var loaders = exports.cssLoaders(options)
+  for (var extension in loaders) {
+    var loader = loaders[extension]
+    output.push({
+      test: new RegExp('\\.' + extension + '$'),
+      use: loader
+    })
+  }
+  return output
+}

+ 12 - 0
frontend/vue-web/build/vue-loader.conf.js

@@ -0,0 +1,12 @@
+var utils = require('./utils')
+var config = require('../config')
+var isProduction = process.env.NODE_ENV === 'production'
+
+module.exports = {
+  loaders: utils.cssLoaders({
+    sourceMap: isProduction
+      ? config.build.productionSourceMap
+      : config.dev.cssSourceMap,
+    extract: isProduction
+  })
+}

+ 67 - 0
frontend/vue-web/build/webpack.base.conf.js

@@ -0,0 +1,67 @@
+var path = require('path')
+var utils = require('./utils')
+var config = require('../config')
+var vueLoaderConfig = require('./vue-loader.conf')
+
+function resolve (dir) {
+  return path.join(__dirname, '..', dir)
+}
+
+module.exports = {
+  entry: {
+    app: './src/main.js'
+  },
+  output: {
+    path: config.build.assetsRoot,
+    filename: '[name].js',
+    publicPath: process.env.NODE_ENV === 'production'
+      ? config.build.assetsPublicPath
+      : config.dev.assetsPublicPath
+  },
+  resolve: {
+    extensions: ['.js', '.vue', '.json'],
+    alias: {
+      'vue$': 'vue/dist/vue.esm.js',
+      '@': resolve('src')
+    }
+  },
+  module: {
+    rules: [
+      {
+        test: /\.(js|vue)$/,
+        loader: 'eslint-loader',
+        enforce: 'pre',
+        include: [resolve('src'), resolve('test')],
+        options: {
+          formatter: require('eslint-friendly-formatter')
+        }
+      },
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+        options: vueLoaderConfig
+      },
+      {
+        test: /\.js$/,
+        loader: 'babel-loader',
+        include: [resolve('src'), resolve('test')]
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('img/[name].[hash:7].[ext]')
+        }
+      },
+      {
+        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
+        loader: 'url-loader',
+        options: {
+          limit: 10000,
+          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
+        }
+      }
+    ]
+  }
+}

+ 35 - 0
frontend/vue-web/build/webpack.dev.conf.js

@@ -0,0 +1,35 @@
+var utils = require('./utils')
+var webpack = require('webpack')
+var config = require('../config')
+var merge = require('webpack-merge')
+var baseWebpackConfig = require('./webpack.base.conf')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
+var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
+
+// add hot-reload related code to entry chunks
+Object.keys(baseWebpackConfig.entry).forEach(function (name) {
+  baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
+})
+
+module.exports = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
+  },
+  // cheap-module-eval-source-map is faster for development
+  devtool: '#cheap-module-eval-source-map',
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env': config.dev.env
+    }),
+    // https://github.com/glenjamin/webpack-hot-middleware#installation--usage
+    new webpack.HotModuleReplacementPlugin(),
+    new webpack.NoEmitOnErrorsPlugin(),
+    // https://github.com/ampedandwired/html-webpack-plugin
+    new HtmlWebpackPlugin({
+      filename: 'index.html',
+      template: 'index.html',
+      inject: true
+    }),
+    new FriendlyErrorsPlugin()
+  ]
+})

+ 120 - 0
frontend/vue-web/build/webpack.prod.conf.js

@@ -0,0 +1,120 @@
+var path = require('path')
+var utils = require('./utils')
+var webpack = require('webpack')
+var config = require('../config')
+var merge = require('webpack-merge')
+var baseWebpackConfig = require('./webpack.base.conf')
+var CopyWebpackPlugin = require('copy-webpack-plugin')
+var HtmlWebpackPlugin = require('html-webpack-plugin')
+var ExtractTextPlugin = require('extract-text-webpack-plugin')
+var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
+
+var env = config.build.env
+
+var webpackConfig = merge(baseWebpackConfig, {
+  module: {
+    rules: utils.styleLoaders({
+      sourceMap: config.build.productionSourceMap,
+      extract: true
+    })
+  },
+  devtool: config.build.productionSourceMap ? '#source-map' : false,
+  output: {
+    path: config.build.assetsRoot,
+    filename: utils.assetsPath('js/[name].[chunkhash].js'),
+    chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
+  },
+  plugins: [
+    // http://vuejs.github.io/vue-loader/en/workflow/production.html
+    new webpack.DefinePlugin({
+      'process.env': env
+    }),
+    new webpack.optimize.UglifyJsPlugin({
+      compress: {
+        warnings: false
+      },
+      sourceMap: true
+    }),
+    // extract css into its own file
+    new ExtractTextPlugin({
+      filename: utils.assetsPath('css/[name].[contenthash].css')
+    }),
+    // Compress extracted CSS. We are using this plugin so that possible
+    // duplicated CSS from different components can be deduped.
+    new OptimizeCSSPlugin({
+      cssProcessorOptions: {
+        safe: true
+      }
+    }),
+    // generate dist index.html with correct asset hash for caching.
+    // you can customize output by editing /index.html
+    // see https://github.com/ampedandwired/html-webpack-plugin
+    new HtmlWebpackPlugin({
+      filename: config.build.index,
+      template: 'index.html',
+      inject: true,
+      minify: {
+        removeComments: true,
+        collapseWhitespace: true,
+        removeAttributeQuotes: true
+        // more options:
+        // https://github.com/kangax/html-minifier#options-quick-reference
+      },
+      // necessary to consistently work with multiple chunks via CommonsChunkPlugin
+      chunksSortMode: 'dependency'
+    }),
+    // split vendor js into its own file
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'vendor',
+      minChunks: function (module, count) {
+        // any required modules inside node_modules are extracted to vendor
+        return (
+          module.resource &&
+          /\.js$/.test(module.resource) &&
+          module.resource.indexOf(
+            path.join(__dirname, '../node_modules')
+          ) === 0
+        )
+      }
+    }),
+    // extract webpack runtime and module manifest to its own file in order to
+    // prevent vendor hash from being updated whenever app bundle is updated
+    new webpack.optimize.CommonsChunkPlugin({
+      name: 'manifest',
+      chunks: ['vendor']
+    }),
+    // copy custom static assets
+    new CopyWebpackPlugin([
+      {
+        from: path.resolve(__dirname, '../static'),
+        to: config.build.assetsSubDirectory,
+        ignore: ['.*']
+      }
+    ])
+  ]
+})
+
+if (config.build.productionGzip) {
+  var CompressionWebpackPlugin = require('compression-webpack-plugin')
+
+  webpackConfig.plugins.push(
+    new CompressionWebpackPlugin({
+      asset: '[path].gz[query]',
+      algorithm: 'gzip',
+      test: new RegExp(
+        '\\.(' +
+        config.build.productionGzipExtensions.join('|') +
+        ')$'
+      ),
+      threshold: 10240,
+      minRatio: 0.8
+    })
+  )
+}
+
+if (config.build.bundleAnalyzerReport) {
+  var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
+  webpackConfig.plugins.push(new BundleAnalyzerPlugin())
+}
+
+module.exports = webpackConfig

+ 6 - 0
frontend/vue-web/config/dev.env.js

@@ -0,0 +1,6 @@
+var merge = require('webpack-merge')
+var prodEnv = require('./prod.env')
+
+module.exports = merge(prodEnv, {
+  NODE_ENV: '"development"'
+})

+ 38 - 0
frontend/vue-web/config/index.js

@@ -0,0 +1,38 @@
+// see http://vuejs-templates.github.io/webpack for documentation.
+var path = require('path')
+
+module.exports = {
+  build: {
+    env: require('./prod.env'),
+    index: path.resolve(__dirname, '../dist/index.html'),
+    assetsRoot: path.resolve(__dirname, '../dist'),
+    assetsSubDirectory: 'static',
+    assetsPublicPath: '/',
+    productionSourceMap: true,
+    // Gzip off by default as many popular static hosts such as
+    // Surge or Netlify already gzip all static assets for you.
+    // Before setting to `true`, make sure to:
+    // npm install --save-dev compression-webpack-plugin
+    productionGzip: false,
+    productionGzipExtensions: ['js', 'css'],
+    // Run the build command with an extra argument to
+    // View the bundle analyzer report after build finishes:
+    // `npm run build --report`
+    // Set to `true` or `false` to always turn it on or off
+    bundleAnalyzerReport: process.env.npm_config_report
+  },
+  dev: {
+    env: require('./dev.env'),
+    port: 8080,
+    autoOpenBrowser: true,
+    assetsSubDirectory: 'static',
+    assetsPublicPath: '/',
+    proxyTable: {},
+    // CSS Sourcemaps off by default because relative paths are "buggy"
+    // with this option, according to the CSS-Loader README
+    // (https://github.com/webpack/css-loader#sourcemaps)
+    // In our experience, they generally work as expected,
+    // just be aware of this issue when enabling this option.
+    cssSourceMap: false
+  }
+}

+ 3 - 0
frontend/vue-web/config/prod.env.js

@@ -0,0 +1,3 @@
+module.exports = {
+  NODE_ENV: '"production"'
+}

+ 15 - 0
frontend/vue-web/index.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>restcli</title>
+		<script src="static/hLayout/HLayout.js"></script>
+		<link rel="stylesheet" type="text/css" href="static/hLayout/HLayout.css">
+		<link rel="stylesheet" type="text/css" href="static/prism.css">
+		<link rel="stylesheet" type="text/css" href="static/style.css">
+  </head>
+  <body>
+    <div id="app"></div>
+    <!-- built files will be auto injected -->
+  </body>
+</html>

+ 71 - 0
frontend/vue-web/package.json

@@ -0,0 +1,71 @@
+{
+  "name": "restcli",
+  "version": "1.0.0",
+  "description": "Interface to test rest",
+  "author": "Luis Figueiredo",
+  "private": true,
+  "scripts": {
+    "dev": "node build/dev-server.js",
+    "build": "node build/build.js",
+    "lint": "eslint --ext .js,.vue src"
+  },
+  "dependencies": {
+    "vue": "^2.2.2",
+    "vue-router": "^2.2.0"
+  },
+  "devDependencies": {
+    "autoprefixer": "^6.7.2",
+    "babel-core": "^6.22.1",
+    "babel-eslint": "^7.1.1",
+    "babel-loader": "^6.2.10",
+    "babel-plugin-transform-runtime": "^6.22.0",
+    "babel-preset-env": "^1.2.1",
+    "babel-preset-stage-2": "^6.22.0",
+    "babel-register": "^6.22.0",
+    "chalk": "^1.1.3",
+    "connect-history-api-fallback": "^1.3.0",
+    "copy-webpack-plugin": "^4.0.1",
+    "css-loader": "^0.26.1",
+    "eslint": "^3.14.1",
+    "eslint-config-standard": "^6.2.1",
+    "eslint-friendly-formatter": "^2.0.7",
+    "eslint-loader": "^1.6.1",
+    "eslint-plugin-html": "^2.0.0",
+    "eslint-plugin-promise": "^3.4.0",
+    "eslint-plugin-standard": "^2.0.1",
+    "eventsource-polyfill": "^0.9.6",
+    "express": "^4.14.1",
+    "extract-text-webpack-plugin": "^2.0.0",
+    "file-loader": "^0.10.0",
+    "friendly-errors-webpack-plugin": "^1.1.3",
+    "function-bind": "^1.1.0",
+    "html-webpack-plugin": "^2.28.0",
+    "http-proxy-middleware": "^0.17.3",
+    "opn": "^4.0.2",
+    "optimize-css-assets-webpack-plugin": "^1.3.0",
+    "ora": "^1.1.0",
+    "prismjs": "^1.6.0",
+    "rimraf": "^2.6.0",
+    "semver": "^5.3.0",
+    "url-loader": "^0.5.7",
+    "vue-loader": "^11.1.4",
+    "vue-style-loader": "^2.0.0",
+    "vue-template-compiler": "^2.2.4",
+    "vue2-ace": "^1.0.5",
+    "vuex": "^2.2.1",
+    "webpack": "^2.2.1",
+    "webpack-bundle-analyzer": "^2.2.1",
+    "webpack-dev-middleware": "^1.10.0",
+    "webpack-hot-middleware": "^2.16.1",
+    "webpack-merge": "^2.6.1"
+  },
+  "engines": {
+    "node": ">= 4.0.0",
+    "npm": ">= 3.0.0"
+  },
+  "browserslist": [
+    "> 1%",
+    "last 2 versions",
+    "not ie <= 8"
+  ]
+}

+ 60 - 0
frontend/vue-web/src/App.vue

@@ -0,0 +1,60 @@
+<template>
+  <div id="app">
+		<div class="layout-container">
+		<!--<router-view></router-view>-->
+		<splitter type="vertical">
+			<pane title="Configured" basis="20%">
+				<div class="flex-column">
+					<button class="btn">GET http//something.com</button>
+					<button class="btn">POST http//something.com</button>
+					<button class="btn">GET http//something.com</button>
+				</div>
+			</pane>
+			<splitter type="horizontal">
+				<pane title="Requester" basis="210px">
+					<requester></requester>
+				</pane>
+				<splitter type="horizontal">
+					<pane title="Output">
+						<houtput></houtput>
+					</pane>
+				</splitter>
+
+			</splitter>
+		</splitter>
+		</div>
+  </div>
+</template>
+
+<script>
+import Pane from "@/components/hLayout/Pane.vue"
+import Splitter from "@/components/hLayout/Splitter.vue"
+import Requester from "@/components/Requester.vue"
+import Output from "@/components/Output.vue"
+
+export default {
+	name: "app",
+	data() {
+		return {
+			request: {
+				body: "{\n\t\"test\":\"test\"\n}"
+			}
+		}
+	},
+	components: {
+		Pane,
+		Splitter,
+		Requester,
+		houtput: Output
+	}
+}
+</script>
+
+<style>
+#app {
+  font-family: 'Avenir', Helvetica, Arial, sans-serif;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  color: #2c3e50;
+}
+</style>

BIN
frontend/vue-web/src/assets/logo.png


+ 45 - 0
frontend/vue-web/src/components/Output.vue

@@ -0,0 +1,45 @@
+<template>
+	<div class="output">
+		<pre><code v-html="msg">
+		</code>
+		</pre>	
+	</div>
+</template>
+
+<script>
+import Vue from "vue"
+import editor from "vue2-ace"
+import { globalEvt } from "../store/console"
+
+export default {
+	mounted() {
+		globalEvt.$on("log", this.dolog)
+	},
+	data: () => {
+		return {
+			msg: ""
+		}
+	},
+	methods: {
+		dolog (...msg) {
+			var doscroll = false
+			var tolerance = 10
+			var innerHeight = parseInt(getComputedStyle(this.$el).getPropertyValue("height"))
+			console.log("Element:", this.$el.scrollTop, " hieght:", this.$el.scrollHeight, "innerHeight:", innerHeight)
+
+			if (this.$el.scrollTop + innerHeight >= this.$el.scrollHeight - tolerance) {
+				doscroll = true
+			}
+
+			this.msg += msg.join(" ") + "\n"
+
+			if (doscroll) {
+				Vue.nextTick(() => { // This should be in the next tick
+					this.$el.scrollTop = this.$el.scrollHeight
+				})
+			}
+		}
+	},
+	components: {editor}
+}
+</script>

+ 56 - 0
frontend/vue-web/src/components/Requester.vue

@@ -0,0 +1,56 @@
+<template>
+	<!-- build Request component here -->
+	<div class="flex-column">
+		<input type='text' id='request-input' placeholder="URL"/>
+		<div class='flex-row'>
+			<button data-method='GET' @click="methodBtn($event)" class='request'>GET</button>
+			<button data-method='POST' @click="methodBtn($event)" class='request'>POST</button>
+			<button data-method='DELETE' @click="methodBtn($event)" class='request'>DELETE</button>
+			<button data-method='PUT' @click="methodBtn($event)" class='request'>PUT</button>
+		</div>
+		<div>Request body:</div>
+		<editor style="margin-top:0px" lang="json" :content.sync="body" height="100%"></editor>
+	</div>
+
+</template>
+<script>
+import editor from "vue2-ace"
+import "brace/mode/json"
+import "brace/theme/chrome"
+import Prism from "prismjs/prism"
+
+import { globalEvt } from "../store/console"
+
+export default {
+	name: "requester",
+	data() {
+		return {
+			method: "",
+			url: "",
+			body: "{\n\t\"test\":\"test\"\n}"
+		}
+	},
+	methods: {
+		methodBtn: function(el) {
+			var method = el.target.getAttribute("data-method")
+
+			var simObj = {
+				method: this.method,
+				url: this.url,
+				body: this.body
+			}
+			console.log("Prism:", Prism.languages)
+			// var highlighted = JSON.stringify(simObj, " ", " ") // Prism.highlight(JSON.stringify(simObj), Prism.languages)
+			var highlighted = Prism.highlight(JSON.stringify(simObj, null, "  "), Prism.languages.js)
+			this.log(method + "-\n" + highlighted)
+		},
+		// Mixin future or global thingy
+		log: function(...msg) {
+			globalEvt.$emit("log", "method:", ...msg)
+		}
+	},
+	components: {
+		editor
+	}
+}
+</script>

+ 17 - 0
frontend/vue-web/src/components/hLayout/Pane.vue

@@ -0,0 +1,17 @@
+ <template :title="title" :basis="basis">
+	<div class="hpane" :style="'flex-basis:' + basis">
+		<header>{{title}}</header>
+		<div class="hcontent restrict">
+			<slot></slot>
+		</div>
+	</div>
+</template>
+
+<script>
+export default {
+	props: {
+		title: {default: "Hello"},
+		basis: {default: "100%"}
+	}
+}
+</script>

+ 13 - 0
frontend/vue-web/src/components/hLayout/Splitter.vue

@@ -0,0 +1,13 @@
+<template :type="type">
+	<section :class="type">
+		<slot></slot>	
+	</section>
+</template>
+
+<script>
+export default {
+	props: {
+		type: { default: "vertical" }
+	}
+}
+</script>

+ 15 - 0
frontend/vue-web/src/main.js

@@ -0,0 +1,15 @@
+// The Vue build version to load with the `import` command
+// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
+import Vue from "vue"
+import App from "./App"
+import router from "./router"
+
+Vue.config.productionTip = false
+
+/* eslint-disable no-new */
+new Vue({
+	el: "#app",
+	router,
+	template: "<App/>",
+	components: { App }
+})

+ 14 - 0
frontend/vue-web/src/router/index.js

@@ -0,0 +1,14 @@
+import Vue from "vue"
+import Router from "vue-router"
+
+Vue.use(Router)
+
+export default new Router({
+/*  routes: [
+    {
+      path: '/',
+      name: 'Hello',
+      component: Hello
+    }
+  ] */
+})

+ 3 - 0
frontend/vue-web/src/store/console.js

@@ -0,0 +1,3 @@
+import Vue from "vue"
+
+export const globalEvt = new Vue()

+ 0 - 0
frontend/vue-web/static/.gitkeep


+ 183 - 0
frontend/vue-web/static/hLayout/HLayout.css

@@ -0,0 +1,183 @@
+@font-face {
+  font-family: 'Open Sans';
+  src: url('OpenSans-Regular.ttf')  format('truetype') /* Safari, Android, iOS */
+}
+
+body,html {
+	position:relative;
+	width:100%;
+	height:100%;
+	margin:0px;
+	padding:0px;
+	
+}
+* {
+	box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+}
+.layout-container {
+	box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+	
+	padding:5px;
+	position:absolute;
+	top:0px;
+	bottom:0px;
+	left:0px;
+	right:0px;
+	
+	
+	
+	display:flex;
+	background: #333;
+	
+}
+
+
+section {
+	display:flex;
+	flex:1 0 auto;
+	background:transparent;
+	
+}
+/* Hide any 3rd component on a splitter*/
+section > *:nth-child(n+3) {
+	display:none;
+}
+section.vertical {
+	flex-direction: row;
+}
+section.horizontal {
+	flex-direction: column;
+}
+
+/*
+section.horizontal > .splitter {
+	cursor:s-resize;
+}
+section.vertical > .splitter {
+	cursor:e-resize;
+}
+
+.splitter {
+	background: transparent;
+	flex-basis:6px;
+	flex-grow: 0;
+	min-width:6px;
+	min-height:6px;
+
+}*/
+
+
+/* AKA PAne */
+.hpane {
+	/*box-shadow: 0px 0px 10px #000;*/
+	position:relative;
+	overflow:hidden;
+	flex: 1 1 auto;
+	flex-direction: column;
+	/* Theme part */
+	
+	background: #EEE;
+	color: #333;
+	
+	transition: border 1s ease;
+	opacity:1;
+}
+
+.hpane.discover {
+	box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    -webkit-box-sizing: border-box;
+	
+	gborder: solid 40px #999;
+	transition: border 1s ease;
+}
+
+.hpane.detached {
+	position:Fixed;
+	/*width:20%;*/
+	/*height:50%;*/
+	z-index:99;
+	 
+	box-shadow: 0px 0px 10px #000;
+	
+	
+}
+
+.hpane.detached.moving {
+	opacity: 0.5;
+	pointer-events: none;
+}
+
+
+
+.hpane.preview {
+	background: #555;
+	border: dashed 1px #FFF;
+}
+
+.hpane >header {
+	padding:10px;
+	background: #555;
+	color:#FFF;
+	font-family: 'Open Sans';
+	text-transform:uppercase;
+	font-weight: 700;
+	
+	-webkit-touch-callout: none; /* iOS Safari */
+	-webkit-user-select: none;   /* Chrome/Safari/Opera */
+	-khtml-user-select: none;    /* Konqueror */
+	-moz-user-select: none;      /* Firefox */
+	-ms-user-select: none;       /* IE/Edge */
+	user-select: none;
+	
+}
+.hpane .hcontent {
+	overflow:auto;
+	position:absolute;
+	top:40px;
+	bottom:0px;
+	left:0px;
+	right:0px;
+	padding:5px;
+	
+	background: #EEE;
+	color:#333;
+	font-family: 'Open Sans';
+	-webkit-box-sizing: border-box; /* Safari/Chrome, other WebKit */
+	-moz-box-sizing: border-box;    /* Firefox, other Gecko */
+	box-sizing: border-box;         /* Opera/IE 8+ */
+}
+
+
+
+.attach-preview {
+	position:fixed;
+	background: rgba(0,0,0,0.2);
+	border: dashed 1px #777;
+	z-index:90;
+	pointer-events:none;
+	transition: all 0.2s;
+}
+
+section.horizontal {
+	cursor:s-resize;
+}
+section.vertical {
+	cursor:e-resize;
+}
+section.horizontal > *:first-child {
+	rborder: solid 1px #F00;
+	margin-bottom:7px;
+}
+section.vertical > *:first-child {
+	rborder: solid 1px #F00;
+	margin-right:7px;
+}
+	section > * {
+		cursor:initial;
+	}
+

+ 494 - 0
frontend/vue-web/static/hLayout/HLayout.js

@@ -0,0 +1,494 @@
+(function(window) {
+	function startHLayout() {
+		var md
+		var grabEl
+		var sectionResizeEl
+
+		// Global mouse update
+		document.addEventListener("mousemove", function(e) {
+			if (md === undefined) md = {x: e.clientX, y: e.clientY}
+			md.deltaX = e.clientX - md.x
+			md.deltaY = e.clientY - md.y
+			md.x = e.clientX
+			md.y = e.clientY
+		})
+		// Handle dragstart
+		document.addEventListener("mousedown", function(e) {
+			// Check for section resize
+			if (e.target.matches("section")) {
+				e.preventDefault()
+				e.stopPropagation()
+
+				sectionResizeEl = e.target
+				sectionResizeEl.classList.add("resizing")
+				return
+			}
+			// Check if element is our
+			if (!e.target.matches(".hpane > header")) {
+				return
+			}
+			e.preventDefault()
+			e.stopPropagation()
+			grabEl = e.target.parentElement
+
+			var parent = grabEl.parentElement
+
+			var elDim = grabEl.getBoundingClientRect()
+			document.body.appendChild(grabEl)
+			grabEl.classList.add("detached", "moving")
+			grabEl.style.width = elDim.width + "px"
+			grabEl.style.height = elDim.height + "px"
+
+			// Add discover to other panes
+			// $('.hpane:not(.detached)').addClass("discover");
+			// All childs to pointer event none
+			if (parent.matches("section")) {
+				var child = parent.querySelector(".hpane, section") // hpane or section
+				child.removeAttribute("style")
+				child.style["flex-basis"] = parent.style["flex-basis"]
+				parent.parentElement.insertBefore(child, parent)
+				parent.parentElement.removeChild(parent)
+			}
+		})
+
+		// Handle drag
+		document.addEventListener("mousemove", function(e) {
+			if (sectionResizeEl != null) {
+				var firstEl = sectionResizeEl.children[0]
+				var secondEl = sectionResizeEl.children[1]
+				// Default vertical
+				var	tDim = "offsetWidth"
+				var	tDelta = "deltaX"
+				if (sectionResizeEl.matches(".horizontal")) {
+					tDim = "offsetHeight"
+					tDelta = "deltaY"
+				}
+				var parentValue = sectionResizeEl[tDim]
+
+				var firstNewDim = (firstEl[tDim] + md[tDelta]) / parentValue * 100
+				var secondNewDim = (secondEl[tDim] - md[tDelta]) / parentValue * 100
+
+				firstEl.style["flex-basis"] = firstNewDim + "%"
+				secondEl.style["flex-basis"] = secondNewDim + "%"
+				return
+			}
+
+			// console.log("Drag:",e)
+			// if (!e.target.matches(".hpane > header")) { // we only deal with header
+			// 	return
+			// }
+			if (grabEl === undefined) return
+			// Get Absolute position somehow
+			// var pos = $grab.position();
+			var elDim = grabEl.getBoundingClientRect()
+			// var nx = elDim.left + md.deltaX
+			// var ny = elDim.top + md.deltaY
+			var offX = elDim.width / 2
+			var offY = 0
+			grabEl.style.left = e.clientX - offX + "px"
+			grabEl.style.top = e.clientY - offY + "px"
+			var targetEl = e.target
+			// if(!targetEl.matches(".hpane")) {
+			for (;targetEl !== null && !targetEl.matches(".hpane"); targetEl = targetEl.parentElement); // find Parent ".hpane"
+
+			if (targetEl != null && targetEl.matches(".hpane:not(.preview)")) {
+				// check attachment
+				var placement = checkAttach(targetEl, e)
+				if (placement !== 0) {
+					previewPanel(grabEl, targetEl, placement)
+				} else {
+					removePreviews()
+				}
+			} else {
+				removePreviews()
+			}
+		})
+		document.addEventListener("mouseup", function(e) {
+			if (sectionResizeEl) {
+				sectionResizeEl.classList.remove("resizing")
+				sectionResizeEl = null
+				return
+			}
+			// Drag
+			if (grabEl === undefined) return
+			removePreviews()
+			grabEl.classList.remove("moving")
+
+			var targetEl = e.target
+			for (;targetEl !== null && !targetEl.matches(".hpane"); targetEl = targetEl.parentElement); // find Parent ".hpane"
+
+			if (targetEl !== null) {
+				var placement = checkAttach(targetEl, e)
+				if (placement !== 0) {
+					grabEl.classList.remove("detached")
+					grabEl.removeAttribute("style")
+					attachPanel(grabEl, targetEl, placement)
+				}
+			}
+			grabEl = undefined
+		})
+	}
+
+	function attachPanel(grabEl, targetEl, placement) {
+		if (!placement) {
+			return
+		}
+		var type
+		if ((placement % 2) === 1)type = "horizontal" // else
+		if ((placement % 2) === 0)type = "vertical"
+
+		var parentEl = targetEl.parentElement
+		var sectionEl
+		var sibEl = targetEl // it will be new sibling
+
+		var newSectionEl = document.createElement("section")
+		newSectionEl.classList.add(type)
+		newSectionEl.style["flex-basis"] = targetEl.style["flex-basis"]
+		if (sibEl.nextElementSibling !== null && sibEl.nextElementSibling.matches("section, .hpane")) {
+			parentEl.insertBefore(newSectionEl, parentEl.children[0])
+		} else {
+			parentEl.appendChild(newSectionEl)
+		}
+		// parentEl.removeChild(sibEl)
+
+		grabEl.style["flex-basis"] = "33%"
+		sibEl.style["flex-basis"] = "66%"
+		if (placement < 3) {
+			newSectionEl.appendChild(grabEl)
+			newSectionEl.appendChild(sibEl) // will this work?
+			// $section.append(what);
+			// $section.append("<div class='splitter'></div>");
+			// $section.append($sib);
+		} else {
+			newSectionEl.appendChild(sibEl)
+			newSectionEl.appendChild(grabEl) // will this work?
+			// $section.append($sib);
+			// $section.append("<div class='splitter'></div>");
+			// $section.append(what);
+		}
+	}
+
+	function removePreviews() {
+		var previewEls = document.querySelectorAll(".attach-preview")
+		if (previewEls === null) { // not sure if it is needed
+			return
+		}
+		for (var i = previewEls.length; i--; i >= 0) {
+			previewEls[i].style.display = "none"
+		}
+	}
+	function checkAttach(targetEl, e) {
+		var targetDim = targetEl.getBoundingClientRect()
+
+		var tW = targetDim.width / 3
+		var tH = targetDim.height / 3
+		var rPos = {x: e.pageX - targetDim.left, y: e.pageY - targetDim.top}
+		// Calc dists and check the closest one
+		// Counter clock wise 1 TOP 2 RIGHT 3 BOTTOM 4 LEFT
+		if (rPos.x > tW && rPos.x < targetDim.width - tW && rPos.y < tH) {  													// 1 Top
+			return 1
+		} else if (rPos.x > tW && rPos.x < targetDim.width - tW && rPos.y > targetDim.height - tH) { 	// 3 Bottom
+			return 3
+		} else if (rPos.x < tW) {																																			// 2 Left
+			return 2
+		} else if (rPos.x > targetDim.width - tW) {			   																						// 4 Right
+			return 4
+		}
+		return 0
+	}
+
+	function previewPanel(grabEl, targetEl, placement) {
+		var previewEl = document.querySelector(".attach-preview")
+		if (previewEl === undefined || previewEl === null) {
+			previewEl = document.createElement("div")
+			previewEl.setAttribute("class", "attach-preview")
+			previewEl.setAttribute("style", "display:none;position:fixed")
+			document.body.appendChild(previewEl)
+		}
+		previewEl.style.display = "block" // Show element
+		var targetDim = targetEl.getBoundingClientRect()
+		if ((placement % 2) === 1) { // Odd is top or bottom
+			previewEl.style.height = (targetDim.height / 3) + "px"
+			previewEl.style.width = targetDim.width + "px"
+			previewEl.style.left = targetDim.left + "px"
+			if (placement === 1) {
+				previewEl.style.top = targetDim.top + "px"
+			} else {
+				previewEl.style.top = (targetDim.top + targetDim.height - (targetDim.height / 3)) + "px"
+			}
+		} else if ((placement % 2) === 0) {
+			previewEl.style.height = targetDim.height + "px"
+			previewEl.style.width = (targetDim.width / 3) + "px"
+			previewEl.style.top = targetDim.top + "px"
+			if (placement === 2) {
+				previewEl.style.left = targetDim.left + "px"
+			} else {
+				previewEl.style.left = (targetDim.left + targetDim.width - (targetDim.width / 3)) + "px"
+			}
+		}
+	}
+	window.onload = startHLayout
+})(window)
+
+/*
+var md
+function startHLayoutEvents() {
+	document.addEventListener("mousemove", function(e) {
+		if (md === undefined) md = {x: e.clientX, y: e.clientY}
+		md.deltaX = e.clientX - md.x
+		md.deltaY = e.clientY - md.y
+		md.x = e.clientX
+		md.y = e.clientY
+	})
+	// Global mouse event;
+	
+	var $grab = undefined;
+	// Start drag
+	$(document).on('mousedown','.hpane > header',function(e){
+		if(e.which != 1) return;
+		if(!$(e.target).is(".hpane > header")) return;
+		
+		e.preventDefault();
+		e.stopPropagation();
+		
+		$grab = $(this).parent();
+		$parent = $grab.parent();
+		var next = $grab.next();
+		var origW = $grab.width();
+		var origH = $grab.height();
+		
+		// Check if we are root?
+		
+		var dim = {w: $grab.width(), h: $grab.height()};
+		var pos = $grab.position();
+	
+		$($grab).detach().appendTo('body').addClass("detached").addClass("moving").css({width:dim.w,height:dim.h});
+		
+		$('.hpane:not(.detached)').addClass("discover");
+		
+		// All childs to pointer event none
+		if($parent.is('section')) {
+			// We aren't
+
+			$child = $parent.children('.hpane, section');
+			$child.attr('style','');
+			$child.css('flex-basis',$parent.css('flex-basis'));
+			$child.detach();
+			$parent.replaceWith($child);
+		}
+	})
+	
+	var $sectionResize = null;
+	$(document).on('mousedown','section',function(e) {
+		
+		var $target = $(e.target);
+		if(!$target.is("section"))return;
+		e.preventDefault();
+		$sectionResize = $target;
+		$sectionResize.addClass("resizing");
+	});
+	$(document).on('mousemove',function(e) {
+		if($sectionResize == null) return;
+		var $first = $sectionResize.children(':nth-child(1)');
+		var $second = $sectionResize.children(':nth-child(2)');
+		
+		
+		var common;
+		var common2;
+		if( $sectionResize.is(".horizontal")) {common = "height",common2 = "deltaY"};
+		if( $sectionResize.is(".vertical")) {common = "width", common2 = "deltaX"};
+
+		var parentValue = $sectionResize[common]();
+		var hFirst = $first[common]();
+		var hSec = $second[common]();
+	
+		var newVal1 = (hFirst + md[common2]) / parentValue * 100;
+		var newVal2 = (hSec -md[common2]) / parentValue * 100;
+			
+		$first.css('flex-basis',newVal1 + "%");
+		$second.css('flex-basis',newVal2 + "%");
+			
+	});
+	$(document).on('mouseup',function(e) {
+		if($sectionResize) {
+			$sectionResize.removeClass("resizing");
+		}
+
+		$sectionResize = null;
+	});
+	
+	
+	$(document).on('mousemove',function(e) {
+		if($grab == undefined) return;
+		var pos = $grab.position();
+		
+		var nx = pos.left + md.deltaX;
+		var ny = pos.top + md.deltaY;
+		var offX = $grab.width() / 2;
+		var offY = 0;
+		
+		$grab.css({left: e.clientX - offX, top: e.clientY -offY});
+		
+		// Check target
+		var $target = $(e.target);
+		
+		if(!$target.is(".hpane")) {
+			$target = $target.parents(".hpane");
+		}
+		
+		
+		if($target.is(".hpane:not(.preview)")){
+			var placement = checkAttach($target,e);
+			if(placement != 0 ) {
+				previewPanel($grab,$target,placement);
+			} else {
+				removePreviews();
+			}
+		
+		} else {
+			removePreviews();
+			
+		}
+		
+	});
+	
+	$(document).on('mouseup',function(e) {
+		if($grab == undefined) return;
+		
+		removePreviews();
+
+		$grab.removeClass("moving");
+		$('.hpane').removeClass("discover");
+
+		$target = $(e.target);
+		if(!$target.is(".hpane")) $target = $target.parents('.hpane');
+		
+		if($target.is(".hpane")){
+			var placement = checkAttach($target,e);
+			if(placement != 0 ) {
+				$grab.removeClass("detached");
+				$grab.attr('style','');
+				attachPanel($grab,$target,placement);
+			}
+		}
+		
+		$grab = undefined;
+		return;
+	});
+	
+}
+
+
+	
+	function recalcFlex() {
+		$('section.horizontal > section, section.horizontal > .hpane').each(function() {
+			var parentH = $(this).parent().height();
+			var curH = $(this).height();
+			var flex = 100 * curH / parentH;
+			$(this).css({'flex-basis':flex + "%"});
+		});
+		$('section.vertical > section, section.vertical > .hpane').each(function() {
+			var parentW = $(this).parent().width();
+			var curW = $(this).width();
+			var flex = 100 * curW / parentW;
+			$(this).css({'flex-basis':flex + "%"});
+		});
+	}	
+	recalcFlex();
+	
+	function checkAttach($target,e) {
+		
+		var tW = $target.width()/3;
+		var tH = $target.height()/3;
+		
+		var targetPos = $target.offset();
+		var dim = { w: $target.width(), h: $target.height()};
+		var rPos = { x: e.pageX - targetPos.left,  y: e.pageY - targetPos.top};
+		//Calc dists and check the closest one
+		
+		
+		if(rPos.x > tW && rPos.x < dim.w - tW && rPos.y < tH) {  					// 1 Top
+			return 1;
+		} else if(rPos.x > tW && rPos.x < dim.w - tW && rPos.y > dim.h - tH){ 		// 3 Bottom
+			return 3;
+		} else if(rPos.x < tW) {													// 2 Left
+			return 2;
+		} else if( rPos.x > dim.w - tW){											// 4 Right
+			return 4;
+		}
+		
+		return 0;
+	}
+	function removePreviews() {
+		$('.attach-preview').hide();
+	}
+	function previewPanel($grab,$target,attach) {
+		
+		$previewDiv = $(".attach-preview");
+		if($previewDiv.length == 0 ) {
+			$previewDiv = $('<div class="attach-preview" style="display:none;position:fixed"></div>').appendTo('body');
+		}
+		
+		$previewDiv.show();
+		var dim = {w:$target.width(),h:$target.height()};
+		var pos = $target.offset();
+		
+		if((attach % 2) == 1) {
+			$previewDiv.css('height',dim.h/3);
+			$previewDiv.css("width",dim.w);
+			$previewDiv.css("left",pos.left);
+			if(attach == 1) {
+				$previewDiv.css('top',pos.top);
+			} else {
+				$previewDiv.css('top',pos.top  + dim.h - dim.h/3);
+			}
+		} else if((attach % 2) == 0) {
+			$previewDiv.css("height",dim.h);
+			$previewDiv.css('width',dim.w/3);
+			$previewDiv.css("top",pos.top);
+			if(attach == 2) {
+				$previewDiv.css('left',pos.left);
+			} else {
+				$previewDiv.css('left',pos.left + dim.w - dim.w/3);
+			}
+
+		}
+			
+	}
+	function attachPanel(what,target, attach) {
+		if(attach){
+			var type
+			if((attach % 2) == 1)type = "horizontal";
+			if((attach % 2) == 0)type = "vertical";
+			$target = $(target);
+			$parent = $(target).parent();
+			
+			$sib = $(target);
+			if($sib.next().is("section, .hpane,.splitter")) {
+				$section = $("<section class='" + type +"' style='flex-basis:" + $target.css('flex-basis') + "'></section>").prependTo($parent);
+			} else {
+				$section = $("<section class='" + type + "' style='flex-basis:" + $target.css('flex-basis') +" '></section>").appendTo($parent);
+			}
+			$sib.detach();
+			what.css('flex-basis','33%');
+			$sib.css('flex-basis','66%');
+			if(attach < 3) {
+				$section.append(what);
+				//$section.append("<div class='splitter'></div>");
+				$section.append($sib);
+			} else {
+				$section.append($sib);
+				//$section.append("<div class='splitter'></div>");
+				$section.append(what);
+			}
+			
+		}
+	
+	}
+	
+
+$(function() {
+	
+	startHLayoutEvents();
+	
+});*/

BIN
frontend/vue-web/static/hLayout/OpenSans-Regular.ttf


+ 137 - 0
frontend/vue-web/static/prism.css

@@ -0,0 +1,137 @@
+/**
+ * prism.js default theme for JavaScript, CSS and HTML
+ * Based on dabblet (http://dabblet.com)
+ * @author Lea Verou
+ */
+
+code[class*="language-"],
+pre[class*="language-"] {
+	color: black;
+	background: none;
+	text-shadow: 0 1px white;
+	font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
+	text-align: left;
+	white-space: pre;
+	word-spacing: normal;
+	word-break: normal;
+	word-wrap: normal;
+	line-height: 1.5;
+
+	-moz-tab-size: 4;
+	-o-tab-size: 4;
+	tab-size: 4;
+
+	-webkit-hyphens: none;
+	-moz-hyphens: none;
+	-ms-hyphens: none;
+	hyphens: none;
+}
+
+pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
+code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
+	text-shadow: none;
+	background: #b3d4fc;
+}
+
+pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
+code[class*="language-"]::selection, code[class*="language-"] ::selection {
+	text-shadow: none;
+	background: #b3d4fc;
+}
+
+@media print {
+	code[class*="language-"],
+	pre[class*="language-"] {
+		text-shadow: none;
+	}
+}
+
+/* Code blocks */
+pre[class*="language-"] {
+	padding: 1em;
+	margin: .5em 0;
+	overflow: auto;
+}
+
+:not(pre) > code[class*="language-"],
+pre[class*="language-"] {
+	background: #f5f2f0;
+}
+
+/* Inline code */
+:not(pre) > code[class*="language-"] {
+	padding: .1em;
+	border-radius: .3em;
+	white-space: normal;
+}
+
+.token.comment,
+.token.prolog,
+.token.doctype,
+.token.cdata {
+	color: slategray;
+}
+
+.token.punctuation {
+	color: #999;
+}
+
+.namespace {
+	opacity: .7;
+}
+
+.token.property,
+.token.tag,
+.token.boolean,
+.token.number,
+.token.constant,
+.token.symbol,
+.token.deleted {
+	color: #905;
+}
+
+.token.selector,
+.token.attr-name,
+.token.string,
+.token.char,
+.token.builtin,
+.token.inserted {
+	color: #690;
+}
+
+.token.operator,
+.token.entity,
+.token.url,
+.language-css .token.string,
+.style .token.string {
+	color: #a67f59;
+	background: hsla(0, 0%, 100%, .5);
+}
+
+.token.atrule,
+.token.attr-value,
+.token.keyword {
+	color: #07a;
+}
+
+.token.function {
+	color: #DD4A68;
+}
+
+.token.regex,
+.token.important,
+.token.variable {
+	color: #e90;
+}
+
+.token.important,
+.token.bold {
+	font-weight: bold;
+}
+.token.italic {
+	font-style: italic;
+}
+
+.token.entity {
+	cursor: help;
+}

+ 189 - 0
frontend/vue-web/static/style.css

@@ -0,0 +1,189 @@
+* {
+		box-sizing: border-box;
+		-webkit-box-sizing: border-box;
+	}
+html,body {
+	font-family: 'Open Sans';
+	position:Relative;
+	
+	width:100%;
+	height:100%;
+	padding:0px;
+	margin:0px;
+}
+input {
+	padding:8px;
+}
+.flex-column {
+	width:100%;
+	height:100%;
+	display:flex;
+	flex-direction:column;
+}
+
+
+.flex-column > *:not(:first-child){
+	margin-top: 20px;
+}
+
+.flex-row > *:not(:first-child){
+	margin-left: 10px;
+}
+.flex-row {
+	width:100%;
+	
+	display:flex;
+	flex-direction:row;
+	align-items:center;
+	justify-content:center;
+	padding:0px;
+	margin:0px;
+}
+.flex-row > *{
+	flex-grow:1;
+}
+
+.output {
+	height:100%;
+	border: solid 1px rgba(0,0,0,0.1);
+	overflow-y:auto;
+	padding:10px;
+	
+}
+.output p {
+	margin: 2px 2px 2px 2px;
+}
+.output p.error {
+	color: #F55;
+}
+button {
+	/*margin: 10px 10px 10px 10px;*/
+	padding: 10px 20px 10px 20px;
+	background: #555;
+	color: #FFF;
+	border:none;
+	font-size: 16px;
+	font-weight:bold;
+}
+button:hover {
+	background: #888;
+}
+
+.request {
+	
+} 
+
+.layout-container {
+	background: #EEE;
+}
+
+.hpane {
+	background: #FFF;
+	box-shadow: 0px 1px 3px 0px rgba(0,0,0,0.3);
+}
+.hpane >header {
+	background: #EEE;
+	color:#333;
+	text-align:center;
+	
+}
+.hpane .hcontent {
+	background: #FFF;
+}
+section.horizontal > *:first-child {
+	margin-bottom:5px;
+}
+section.vertical > *:first-child {
+	margin-right:5px;
+}
+
+
+
+ul.entry-list {
+	list-style-type: none;
+	display:flex;
+	padding:0px;
+	margin:0px;
+	flex-direction: column;
+}
+.entry-list > li {
+	rborder: solid 1px #F00;
+	display:flex;
+	flex-direction:row;
+	
+	
+	height:48px;
+	
+	align-items: center;
+    justify-content: center;
+    margin:10px;
+  	
+  	
+}
+.entry-list > li > * {
+	margin:0px;
+	padding:0px;
+	display:flex;
+	align-items:center;
+	justify-content:center;
+	width:100%;
+	height:100%;
+	
+}
+
+.entry-list > li > input[type=text] {
+	
+	/*border:none;
+	border-bottom: solid 1px #555;*/
+}
+
+.entry-list > li > .entry-name {
+	
+	font-weight: bold;
+	
+	align-items:right;
+	padding:10px;
+	margin-right:10px;
+	flex:auto;
+	text-align:right;
+}
+.entry-list > li> .entry-content {
+	
+	text-align:center;
+	padding:10px;
+	margin-right:10px;
+	
+	
+	
+}
+.entry-list > li > .entry-remove
+{
+	padding:0px;
+	margin:0px;
+	width:47px;
+	min-width:47px;
+	
+	
+
+	
+}
+
+
+.requester {
+	flex-grow:0.2;
+	/*min-height: 160px;*/
+	
+}
+.output-clear {
+	background:Transparent;
+	color:#333;
+	margin:0px;
+	padding:2px;
+	float:right;
+}
+.output-clear:hover {
+	background:Transparent;
+	color:#777;
+}
+}
+