github.com/tickoalcantara12/micro/v3@v3.0.0-20221007104245-9d75b9bcbab9/client/web/html/template.go (about)

     1  package html
     2  
     3  var (
     4  	LoginTemplate = `
     5  	{{define "basic"}}
     6  		<html>
     7  		<head>
     8  			<style>
     9  				.inner {
    10  					position: absolute;
    11  					left: 50%;
    12  					top: 40%;
    13  					transform: translate(-50%, -50%);
    14  					max-width: 100vw;
    15  					width: 400px;
    16  				}
    17  
    18  				form  {
    19  					display: flex;
    20  					flex-direction: column;
    21  				}
    22  
    23  				input {
    24  					margin-top:  5px;
    25  					margin-bottom: 20px;
    26  					outline: none;
    27  					height: 25px;
    28  				}
    29  
    30  				input[type=submit] {
    31  					
    32  				}
    33  			</style>
    34  		</head>
    35  		<body>
    36  			<div class="error">{{ .error }}</div>
    37  			<div class='inner'>
    38  				<h1>Login</h1>
    39  				<form method='post'>
    40  					<label for='username'>Username</label>
    41  					<input type='username' name='username' required />
    42  
    43  					<label for='password'>Password</label>
    44  					<input type='password' name='password' required />
    45  
    46  					<input type='submit' value='Submit' />
    47  				</form>
    48  			</div>
    49  		</body>
    50  		</html>
    51  	{{end}}
    52  `
    53  
    54  	LayoutTemplate = `
    55  {{define "layout"}}
    56  <html>
    57  	<head>
    58  		<title>{{ template "title" . }} | Micro</title>
    59  		<meta name="viewport" content="width=device-width, initial-scale=1.0">
    60  		<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
    61  		<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro&display=swap" rel="stylesheet">
    62  		<style>
    63  		  html, body {
    64  		    font-size: 16px;
    65  		    font-family: 'Source Sans Pro', sans-serif;
    66  		  }
    67  		  html a { color: #333333; }
    68                    .navbar { margin-top: 50px; };
    69  		  .navbar-brand img { display: inline; }
    70  		  #navBar, .navbar-toggle { margin-top: 15px; }
    71  		  .icon-bar { background-color: #333333; }
    72  		  .nav>li>a:focus, .nav>li>a:hover { background-color: white; }
    73  		  .logo { font-size: 2em; }
    74  		 .search {
    75  		    position: relative;
    76  		    max-width: 600px;
    77  		    margin: 0 auto;
    78  		    border-radius: 0;
    79  		    border: 0;
    80  		    box-shadow: none;
    81  		    border-bottom: 1px solid #ccc;
    82  		 }
    83  		 .search:focus {
    84  		    border-color: transparent;
    85  		    outline: 0;
    86  		    box-shadow: none;
    87  		    border-bottom: 1px solid #ccc;
    88  	 	 }
    89  		 pre {
    90  		    background-color: #EEF0F3;
    91  		    border: 1px solid #ccc;
    92  		 }
    93  		 .user {
    94  		    padding: 15px;
    95  		 }
    96  		</style>
    97  		<style>
    98  		{{ template "style" . }}
    99  		</style>
   100  		{{ template "head" . }}
   101  	</head>
   102  	<body>
   103  	  <nav class="navbar">
   104  	    <div class="container">
   105                <div class="navbar-header">
   106                  <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navBar">
   107                    <span class="icon-bar"></span>
   108                    <span class="icon-bar"></span>
   109                    <span class="icon-bar"></span> 
   110                  </button>
   111                  <a class="navbar-brand logo" href="/">Micro</a>
   112                </div>
   113                <div class="collapse navbar-collapse" id="navBar">
   114  	        <ul class="nav navbar-nav navbar-right" id="dev">
   115  		  {{if gt (len .User) 0 }}
   116                      <span class="user small" style="position: absolute; top: -40px; right: 20px;">
   117                        Logged in as: {{.User}}
   118                      </span>
   119                    {{end}}
   120  	          <li><a href="/">Home</a></li>
   121  	          <li><a href="/client">Client</a></li>
   122  	          <li><a href="/services">Services</a></li>
   123  	          {{if .LoginURL}}<li><a href="{{.LoginURL}}" class="navbar-link">{{.LoginTitle}}</a></li>{{end}}
   124  	        </ul>
   125                </div>
   126  	    </div>
   127  	  </nav>
   128            <div class="container">
   129              <div class="row">
   130  	      <div class="col-sm-12">
   131                  {{ template "heading" . }}
   132                  {{ template "content" . }}
   133                </div>
   134              </div>
   135            </div>
   136  	  <script src="/assets/mu.js"></script>
   137  	  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
   138  	  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
   139  	  {{template "script" . }}
   140  	  <script type="text/javascript">
   141  		function toggle(e) {
   142  		      var ev = window.event? event : e
   143  		      if (ev.keyCode == 80 && ev.ctrlKey && ev.shiftKey) {
   144  			var el = document.getElementById("dev");
   145  			if (el.style.display == "none") {
   146  			  el.style.display = "block";
   147  			} else {
   148  			  el.style.display = "none";
   149  			}
   150  		    }
   151  		}
   152  
   153  		document.onkeydown = toggle;
   154  	  </script>
   155  	</body>
   156  </html>
   157  {{end}}
   158  {{ define "style" }}
   159  .service { border-radius: 100px; }
   160  {{end}}
   161  {{ define "head" }}{{end}}
   162  {{ define "script" }}{{end}}
   163  {{ define "title" }}Web{{end}}
   164  {{ define "heading" }}<h3>&nbsp;</h3>{{end}}
   165  `
   166  
   167  	IndexTemplate = `
   168  {{define "title"}}Home{{end}}
   169  {{define "heading"}}<h4><input class="form-control input-lg search" type=text placeholder="Search" autofocus></h4>{{end}}
   170  {{define "style" }}
   171  .search {
   172    border-radius: 0;
   173    border: 0;
   174    box-shadow: none;
   175    border-bottom: 1px solid #ccc;
   176  }
   177  .search:focus {
   178    border-color: transparent;
   179    outline: 0;
   180    box-shadow: none;
   181    border-bottom: 1px solid #ccc;
   182  }
   183  .service {
   184  	margin: 5px 3px 5px 3px;
   185  	padding: 20px;
   186  	text-align: center;
   187  	display: block;
   188  }
   189  .search { border-radius: 100px; }
   190  .apps {
   191    max-width: 600px;
   192    text-align: center;
   193    margin: 0 auto;
   194  }
   195  .icon {
   196    width: 60px;
   197    height: 60px;
   198    display: block;
   199    border-radius: 50px; 
   200    border: 2px solid whitesmoke;
   201    background-color: #fcfcfc;
   202    color: #AFACBE;
   203    font-size: 40px;
   204    font-weight: bold;
   205  }
   206  .icon:hover {
   207    color: #23527c;
   208    border: 2px solid #23527c;
   209  }
   210  .apps .service:hover {
   211    text-decoration: none;
   212    font-weight: bold;
   213  }
   214  @media only screen and (max-width: 500px) {
   215    .service {
   216      padding: 5px;
   217    }
   218  }
   219  {{end}}
   220  {{define "content"}}
   221  	{{if .Results.HasWebServices}}
   222  		<div class="apps">
   223  			{{range .Results.WebServices}}
   224  			<div style="display: inline-block; max-width: 150px; vertical-align: top;">
   225  			<a href="{{.Link}}" data-filter={{.Name}} class="service">
   226  			  <div style="padding: 5px; max-width: 80px; display: block; margin: 0 auto;">
   227  				{{if .Icon }}<img src="{{.Icon}}" style="width: 70px; height: auto;"/>{{else}}
   228  				<div class="icon">{{First .Name}}</div>
   229  				{{end}}
   230  			  </div>
   231  			  <div>{{Title .Name}}</div>
   232  			</a>
   233  			</div>
   234  			{{end}}
   235  		</div>
   236  	{{end}}
   237  {{end}}
   238  {{define "script"}}
   239  <script type="text/javascript">
   240  jQuery(function($, undefined) {
   241  	var refs = $('a[data-filter]');
   242  	$('.search').on('keyup', function() {
   243  		var val = $.trim(this.value);
   244  		refs.hide();
   245  		refs.filter(function() {
   246  			return $(this).data('filter').search(val) >= 0
   247  		}).show();
   248  	});
   249  
   250  	$('.search').on('keypress', function(e) {
   251  		if (e.which != 13) {
   252  			return;
   253  		}
   254  		$('.service').each(function() {
   255  			if ($(this).css('display') == "none") {
   256  				return;
   257  			}
   258  			window.location.href = $(this).attr('href');
   259  		})
   260  	});
   261  });
   262  
   263  </script>
   264  {{end}}
   265  `
   266  	CallTemplate = `
   267  {{define "title"}}Client{{end}}
   268  {{define "heading"}}<h3>Client</h3>{{end}}
   269  {{define "style"}}
   270  	pre {
   271  		word-wrap: break-word;
   272  		border: 0;
   273  	}
   274  	.form-control {
   275  		border: 1px solid #ccc;
   276  	}
   277  {{end}}
   278  {{define "content"}}
   279  <div class="row">
   280    <div class="panel">
   281      <div class="panel-body">
   282  	<div class="col-sm-5">
   283  		<form id="call-form" onsubmit="return call();">
   284  			<div class="form-group">
   285  				<label for="service">Service</label>
   286  				<ul class="list-group">
   287  					<select class="form-control" type=text name=service id=service> 
   288  					<option disabled selected> -- select a service -- </option>
   289  					{{range $key, $value := .Results}}
   290  					<option class = "list-group-item" value="{{$key}}">{{$key}}</option>
   291  					{{end}}
   292  					</select>
   293  				</ul>
   294  			</div>
   295  			<div class="form-group">
   296  				<label for="endpoint">Endpoint</label>
   297  				<ul class="list-group">
   298  					<select class="form-control" type=text name=endpoint id=endpoint>
   299  					<option disabled selected> -- select an endpoint -- </option>
   300  					</select>
   301  				</ul>
   302  			</div>
   303  			<div class="form-group other" style="display: none;">
   304  				<label for="otherendpoint">Other Endpoint</label>
   305  				<ul class="list-group">
   306  					<input class="form-control" type=text name=otherendpoint id=otherendpoint placeholder="Endpoint"/>
   307  				</ul>
   308  			</div>
   309  			<div class="form-group">
   310  			</div>
   311  			<div class="form-group">
   312  				<label for="metadata">Metadata</label>
   313  				<ul class="list-group">
   314  					<input class="form-control" type=text name=metadata id=metadata placeholder="Metadata" value="{}"/>
   315  				</ul>
   316  				<label for="request">Request</label>
   317  				<textarea class="form-control" name=request id=request rows=8>{}</textarea>
   318  			</div>
   319  			<div class="form-group">
   320  				<button class="btn btn-default">Call</button>
   321  			</div>
   322  		</form>
   323  	</div>
   324  	<div class="col-sm-7">
   325  		<p><b>Response</b><span class="pull-right"><a href="#" onclick="copyResponse()">Copy</a></p>
   326  		<pre id="response" style="min-height: 405px; max-height: 405px; overflow: scroll;">{}</pre>
   327  	</div>
   328      </div>
   329    </div>
   330  </div>
   331  {{end}}
   332  {{define "script"}}
   333  	<script>
   334  		function copyResponse() {
   335  			var copyText = document.getElementById("response");
   336  			const textArea = document.createElement('textarea');
   337  			textArea.textContent = copyText.innerText;
   338  			textArea.style = "position: absolute; left: -1000px; top: -1000px";	
   339  			document.body.append(textArea);
   340  			textArea.select();
   341  			textArea.setSelectionRange(0, 99999);
   342  			document.execCommand("copy");
   343  			document.body.removeChild(textArea);
   344  			return false;
   345  		}
   346  	</script>
   347  	<script>
   348  		$(document).ready(function(){
   349  			//Function executes on change of first select option field 
   350  			$("#service").change(function(){
   351  				var select = $("#service option:selected").val();
   352  				$("#endpoint").empty();
   353  				$("#endpoint").append("<option disabled selected> -- select an endpoint -- </option>");
   354  				var s_map = {};
   355  				{{ range $service, $endpoints := .Results }}
   356  				var m_list = [];
   357  				{{range $index, $element := $endpoints}}
   358  				m_list[{{$index}}] = {{$element.Name}}
   359  				{{end}}
   360  				s_map[{{$service}}] = m_list
   361  				{{ end }}
   362  				if (select in s_map) {
   363  					var serviceEndpoints = s_map[select]
   364  					var len = serviceEndpoints.length;
   365  					for(var i = 0; i < len; i++) {
   366  						$("#endpoint").append("<option value=\""+serviceEndpoints[i]+"\">"+serviceEndpoints[i]+"</option>");	
   367  					}
   368  				}
   369  				$("#endpoint").append("<option value=\"other\"> - Other</option>");
   370  			});
   371  
   372  			//Function executes on change of second select option field 
   373  			$("#endpoint").change(function(){
   374  				var select = $("#endpoint option:selected").val();
   375  				if (select == "other") {
   376  					$(".other").css('display', 'block');
   377  					$("#otherendpoint").attr("disabled", false);
   378  				} else {
   379  					$(".other").css('display', 'none');
   380  					$("#otherendpoint").attr("disabled", true);
   381  					$('#otherendpoint').val('');
   382  				}
   383  
   384  			});
   385  		});
   386  	</script>
   387  	<script>
   388  		function call() {
   389  			var req = new XMLHttpRequest()
   390  			req.onreadystatechange = function() {
   391  				if(req.readyState != 4) {
   392  					return
   393  				}
   394  				if (req.readyState == 4 && req.status == 200) {
   395  					document.getElementById("response").innerText = JSON.stringify(JSON.parse(req.responseText), null, 2);
   396  				} else if (req.responseText.slice(0, 1) == "{") {
   397  					document.getElementById("response").innerText = JSON.stringify(JSON.parse(req.responseText), null, 2);
   398  				} else if (req.responseText.length > 0) {
   399  					document.getElementById("response").innerText = req.responseText;
   400  				} else {
   401  					document.getElementById("response").innerText = "Request error " + req.status;
   402  				}
   403  				console.log(req.responseText);
   404  			}
   405  
   406  			var service = document.forms[0].elements["service"].value;
   407  			var endpoint = document.forms[0].elements["endpoint"].value
   408  			if (!($('#otherendpoint').prop('disabled'))) {
   409  				endpoint = document.forms[0].elements["otherendpoint"].value
   410  			}
   411  
   412  			var request;
   413  			var headers;
   414  
   415  			try {
   416  				var md = document.forms[0].elements["metadata"].value;
   417  				var rq = document.forms[0].elements["request"].value
   418  				if (md.length > 0) {
   419  					headers = JSON.parse(md);
   420  				}
   421  				if (rq.length > 0) {
   422  					request = JSON.parse(rq);
   423  				};
   424  			} catch(e) {
   425  				document.getElementById("response").innerText = "Invalid request: " + e.message;
   426  				return false;
   427  			}
   428  
   429  			endpoint = endpoint.replace(".", "/");
   430  
   431  			req.open("POST", "{{.ApiURL}}/" + service + "/" + endpoint, true);
   432  			req.setRequestHeader("Content-type","application/json");
   433  			req.setRequestHeader("Authorization", "Bearer {{.Token}}");
   434  			req.setRequestHeader("Micro-Namespace", {{.Namespace}});
   435  
   436  			if (headers != undefined) {
   437  				for (let [key, value] of Object.entries(headers)) {
   438  					req.setRequestHeader(key, value);
   439  				}
   440  			}
   441  
   442  			req.send(JSON.stringify(request));
   443  
   444  			return false;
   445  		};	
   446  	</script>
   447  {{end}}
   448  `
   449  	RegistryTemplate = `
   450  {{define "heading"}}<h4><input class="form-control input-lg search" type=text placeholder="Search" autofocus></h4>{{end}}
   451  {{define "title"}}Services{{end}}
   452  {{define "content"}}
   453  	<p style="margin: 0;">&nbsp;</p>
   454  	<div style="max-width: 600px; margin: 0 auto; min-height: 400px; height: calc(100vh - 400px); overflow: scroll;">
   455  	{{range .Results}}
   456  	<div style="margin: 5px 5px 5px 15px;">
   457  	    <a href="/service/{{.Name}}" data-filter={{.Name}} class="service">{{.Name}}</a>
   458  	</div>
   459  	{{end}}
   460          </div>
   461  {{end}}
   462  {{define "script"}}
   463  <script type="text/javascript">
   464  jQuery(function($, undefined) {
   465  	var refs = $('a[data-filter]');
   466  	$('.search').on('keyup', function() {
   467  		var val = $.trim(this.value);
   468  		refs.hide();
   469  		refs.filter(function() {
   470  			return $(this).data('filter').search(val) >= 0
   471  		}).show();
   472  	});
   473  });
   474  </script>
   475  {{end}}
   476  `
   477  
   478  	ServiceTemplate = `
   479  {{define "title"}}Service{{end}}
   480  {{define "heading"}}<h3>{{with $svc := index .Results 0}}{{Title $svc.Name}}{{end}}</h3>{{end}}
   481  {{define "style"}}
   482  .table>tbody>tr>th, .table>tbody>tr>td {
   483      border-top: none;
   484  }
   485  .endpoint {
   486    cursor: pointer;
   487  }
   488  .bold {
   489    font-weight: bold;
   490  }
   491  pre {padding: 20px;}
   492  {{end}}
   493  {{define "script"}}
   494  <script type="text/javascript">
   495    $('.endpoint').on('click', function() {
   496  	var val = $(this).parent().find("table");
   497  	var state = $(this).find(".state");
   498  	if (val.css('display') == 'none') {
   499  	  state.text("[-]");
   500  	  val.css('display', 'table');
   501  	} else {
   502  	  val.css('display', 'none');
   503  	  state.text("[+]");
   504  	}
   505    });
   506  </script>
   507  {{end}}
   508  {{define "content"}}
   509  	<hr>
   510  	<h4 class="bold">Nodes</h4>
   511  	{{range .Results}}
   512  	<h5>Version: {{.Version}}</h5>
   513  	<table class="table">
   514  		<thead>
   515  			<th>Id</th>
   516  			<th>Address</th>
   517  			<th>Metadata</th>
   518  		<thead>
   519  		<tbody>
   520  			{{range .Nodes}}
   521  			<tr>
   522  				<td>{{.Id}}</td>
   523  				<td>{{.Address}}</td>
   524  				<td>{{ range $key, $value := .Metadata }}{{$key}}={{$value}} {{end}}</td>
   525  			</tr>
   526  			{{end}}
   527  		</tbody>
   528  	</table>
   529  	{{end}}
   530  	{{with $svc := index .Results 0}}
   531  	{{if $svc.Endpoints}}
   532  	<h4 class="bold">Endpoints</h4>
   533  	<hr/>
   534  	{{end}}
   535  	{{range $svc.Endpoints}}
   536  	<div>
   537  		<h4 class="endpoint"><span class="state">[+]</span> {{.Name}}</h4>
   538  		<table class="table" style="display: none;">
   539  			<tbody>
   540  				{{if .Metadata}}
   541  				<tr>
   542  					<th class="col-sm-2" scope="row">Metadata</th>
   543  					<td>{{ range $key, $value := .Metadata }}{{$key}}={{$value}} {{end}}</td>
   544  				</tr>
   545  				{{end}}
   546  				<tr>
   547  					<th class="col-sm-2" scope="row">Request</th>
   548  					<td><pre>{{format .Request}}</pre></td>
   549  				</tr>
   550  				<tr>
   551  					<th class="col-sm-2" scope="row">Response</th>
   552  					<td><pre>{{format .Response}}</pre></td>
   553  				</tr>
   554  			</tbody>
   555  		</table>
   556  	</div>
   557  	{{end}}
   558  	{{end}}
   559  {{end}}
   560  
   561  `
   562  
   563  	WebTemplate = `
   564  {{define "title"}}{{Title .Name}}{{end}}
   565  {{define "heading"}}<h3>{{Title .Name}}</h3>{{end}}
   566  {{define "style"}}
   567  	pre {
   568  		word-wrap: break-word;
   569  		border: 0;
   570  	}
   571  	.form-control {
   572  		border: 1px solid #ccc;
   573  	}
   574  {{end}}
   575  {{define "content"}}
   576  <div class="row">
   577    <div class="panel">
   578      <div class="panel-body">
   579  	<div class="col-sm-2">
   580  	    {{ range $service, $endpoints := .Results }}
   581                {{ range $endpoint := $endpoints }}
   582                  <div><a id="{{$endpoint.Name}}" href="#{{$endpoint.Name}}" onclick="setEndpoint(this)">{{Split $endpoint.Name}}</a></div>
   583                {{end}}
   584  	    {{end}}
   585          </div>
   586  	<div class="col-sm-4">
   587  		<form id="call-form" onsubmit="return call();">
   588  			<input class="form-control" type=text name=service id=service style="display: none;">
   589  			<input class="form-control" type=text name=endpoint id=endpoint style="display: none;">
   590  			<input class="form-control" type=text name=request id=request style="display: none;">
   591  			<div class="form-group">
   592  				<p><b>Request</b></p>
   593  				<div id="inputs"></div>
   594  			</div>
   595  			<div class="form-group">
   596  				<button class="btn btn-default">Submit</button>
   597  			</div>
   598  		</form>
   599  	</div>
   600  	<div class="col-sm-6">
   601  		<p><b>Response</b><span class="pull-right"><a href="#" onclick="copyResponse()">Copy</a></p>
   602  		<pre id="response" style="min-height: 405px; max-height: 405px; overflow: scroll;">{}</pre>
   603  	</div>
   604      </div>
   605    </div>
   606  </div>
   607  {{end}}
   608  {{define "script"}}
   609  	<script>
   610  		function copyResponse() {
   611  			var copyText = document.getElementById("response");
   612  			const textArea = document.createElement('textarea');
   613  			textArea.textContent = copyText.innerText;
   614  			textArea.style = "position: absolute; left: -1000px; top: -1000px";	
   615  			document.body.append(textArea);
   616  			textArea.select();
   617  			textArea.setSelectionRange(0, 99999);
   618  			document.execCommand("copy");
   619  			document.body.removeChild(textArea);
   620  			return false;
   621  		}
   622  	</script>
   623  	<script>
   624  		$(document).ready(function(){
   625  			document.getElementById("service").value = "{{.Name}}";
   626  
   627  			//Function executes on change of first select option field 
   628  			{{ range $service, $endpoints := .Results }}
   629  				{{range $index, $element := $endpoints}}
   630  					{{ if eq $index 0 }}
   631  						var el = document.getElementById("{{$element.Name}}");
   632  						setEndpoint(el);
   633  					{{ end }}
   634  				{{end}}
   635  			{{ end }}
   636  		});
   637  	</script>
   638  	<script>
   639  		function setEndpoint(el) {
   640  			var id = el.id;
   641  			var map = {};
   642  			{{ range $service, $endpoints := .Results }}
   643  				{{range $index, $element := $endpoints}}
   644  					map[{{$element.Name}}] = [];
   645  					{{ range $value := $element.Request.Values }}
   646  						map[{{$element.Name}}].push({{$value.Name}});
   647  					{{end}}
   648  
   649  					// set all to unselected
   650  					document.getElementById("{{$element.Name}}").style.fontWeight = "normal";
   651  				{{end}}
   652  			{{end}}
   653  
   654  			var inputs = document.getElementById("inputs");
   655  			inputs.innerHTML = '';
   656  
   657  			// get values for the endpoint
   658  			var values = map[id];
   659  
   660  			values.forEach(function(value) {
   661  				var input = document.createElement('input')
   662  				input.className = 'form-control';
   663  				input.type = 'text';
   664  				input.name = 'value[]' + value;
   665  				input.id = 'value[]' + value;
   666  				input.placeholder = value;
   667  				input.autocomplete = 'off';
   668  				input.style = 'margin-bottom: 10px;';
   669  				inputs.appendChild(input);
   670  			});
   671  
   672  			// set the endpoint value
   673  			document.getElementById("endpoint").value = el.id;
   674  			// select the endpoint link
   675  			el.style.fontWeight = "bold";
   676  
   677  			return false;
   678  		};
   679  
   680  		function call() {
   681  			var req = new XMLHttpRequest()
   682  			req.onreadystatechange = function() {
   683  				if(req.readyState != 4) {
   684  					return
   685  				}
   686  				if (req.readyState == 4 && req.status == 200) {
   687  					document.getElementById("response").innerText = JSON.stringify(JSON.parse(req.responseText), null, 2);
   688  				} else if (req.responseText.slice(0, 1) == "{") {
   689  					document.getElementById("response").innerText = JSON.stringify(JSON.parse(req.responseText), null, 2);
   690  				} else if (req.responseText.length > 0) {
   691  					document.getElementById("response").innerText = req.responseText;
   692  				} else {
   693  					document.getElementById("response").innerText = "Request error " + req.status;
   694  				}
   695  				console.log(req.responseText);
   696  			}
   697  
   698  			var service = document.forms[0].elements["service"].value
   699  			var endpoint = document.forms[0].elements["endpoint"].value
   700  
   701  			var request;
   702  			var headers;
   703  
   704  			try {
   705  				var data = {};
   706  				var inputs = document.getElementById("call-form").elements;
   707  
   708  				for (i = 0; i < inputs.length; i++) {
   709  					var val = inputs[i];
   710  					if (val.id.startsWith("value[]")) {
   711  						var v = document.getElementById(val.id);
   712  						if (v.value.length > 0) {
   713  							data[val.name.replace('value[]','')] = v.value;
   714  						}
   715  					}
   716  				};
   717  				console.log(data);
   718  				request = data;
   719  			} catch(e) {
   720  				document.getElementById("response").innerText = "Invalid request: " + e.message;
   721  				return false;
   722  			}
   723  
   724  			endpoint = endpoint.replace(".", "/");
   725  
   726  			req.open("POST", "{{.ApiURL}}/" + service + "/" + endpoint, true);
   727  			req.setRequestHeader("Content-type","application/json");
   728  			req.setRequestHeader("Authorization", "Bearer {{.Token}}");
   729  			req.setRequestHeader("Micro-Namespace", {{.Namespace}});
   730  			if (headers != undefined) {
   731  				for (let [key, value] of Object.entries(headers)) {
   732  					req.setRequestHeader(key, value);
   733  				}
   734  			}
   735  
   736  			req.send(JSON.stringify(request));
   737  
   738  			return false;
   739  		};	
   740  	</script>
   741  {{end}}
   742  `
   743  
   744  	NotFoundTemplate = `
   745  {{define "title"}}404: Not Found{{end}}
   746  {{define "heading"}}<h3>404: Not Found</h3>{{end}}
   747  {{define "content"}}<p>The requested page could not be found</p>{{end}}`
   748  )