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> </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;"> </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 )