1
0
mirror of https://github.com/gusaul/grpcox.git synced 2024-12-25 09:50:10 +00:00

first commit

This commit is contained in:
gusaul 2018-11-05 02:56:06 +07:00
commit 54ab742f5c
35 changed files with 964 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
grpcox

93
core/grpcox.go Normal file
View File

@ -0,0 +1,93 @@
package core
import (
"context"
"time"
"github.com/fullstorydev/grpcurl"
"github.com/jhump/protoreflect/grpcreflect"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
reflectpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
)
// GrpCox - main object
type GrpCox struct {
KeepAlive float64
PlainText bool
// TODO : utilize below args
headers []string
reflectHeaders []string
authority string
insecure bool
cacert string
cert string
key string
serverName string
isUnixSocket func() bool
}
// GetResource - open resource to targeted grpc server
func (g *GrpCox) GetResource(ctx context.Context, target string) (*Resource, error) {
var err error
r := new(Resource)
h := append(g.headers, g.reflectHeaders...)
md := grpcurl.MetadataFromHeaders(h)
refCtx := metadata.NewOutgoingContext(ctx, md)
r.clientConn, err = g.dial(ctx, target)
if err != nil {
return nil, err
}
r.refClient = grpcreflect.NewClient(refCtx, reflectpb.NewServerReflectionClient(r.clientConn))
r.descSource = grpcurl.DescriptorSourceFromServer(ctx, r.refClient)
r.headers = h
return r, nil
}
func (g *GrpCox) dial(ctx context.Context, target string) (*grpc.ClientConn, error) {
dialTime := 10 * time.Second
ctx, cancel := context.WithTimeout(ctx, dialTime)
defer cancel()
var opts []grpc.DialOption
// keep alive
if g.KeepAlive > 0 {
timeout := time.Duration(g.KeepAlive * float64(time.Second))
opts = append(opts, grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: timeout,
Timeout: timeout,
}))
}
if g.authority != "" {
opts = append(opts, grpc.WithAuthority(g.authority))
}
var creds credentials.TransportCredentials
if !g.PlainText {
var err error
creds, err = grpcurl.ClientTransportCredentials(g.insecure, g.cacert, g.cert, g.key)
if err != nil {
return nil, err
}
if g.serverName != "" {
if err := creds.OverrideServerName(g.serverName); err != nil {
return nil, err
}
}
}
network := "tcp"
if g.isUnixSocket != nil && g.isUnixSocket() {
network = "unix"
}
cc, err := grpcurl.BlockingDial(ctx, network, target, creds, opts...)
if err != nil {
return nil, err
}
return cc, nil
}

183
core/resource.go Normal file
View File

@ -0,0 +1,183 @@
package core
import (
"bytes"
"context"
"fmt"
"io"
"log"
"os"
"time"
"github.com/fullstorydev/grpcurl"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/grpcreflect"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
// Resource - hold 3 main function (List, Describe, and Invoke)
type Resource struct {
clientConn *grpc.ClientConn
descSource grpcurl.DescriptorSource
refClient *grpcreflect.Client
headers []string
}
// List - To list all services exposed by a server
// symbol can be "" to list all available services
// symbol also can be service name to list all available method
func (r *Resource) List(symbol string) ([]string, error) {
var result []string
if symbol == "" {
svcs, err := grpcurl.ListServices(r.descSource)
if err != nil {
return result, err
}
if len(svcs) == 0 {
return result, fmt.Errorf("No Services")
}
for _, svc := range svcs {
result = append(result, fmt.Sprintf("%s\n", svc))
}
} else {
methods, err := grpcurl.ListMethods(r.descSource, symbol)
if err != nil {
return result, err
}
if len(methods) == 0 {
return result, fmt.Errorf("No Function") // probably unlikely
}
for _, m := range methods {
result = append(result, fmt.Sprintf("%s\n", m))
}
}
return result, nil
}
// Describe - The "describe" verb will print the type of any symbol that the server knows about
// or that is found in a given protoset file.
// It also prints a description of that symbol, in the form of snippets of proto source.
// It won't necessarily be the original source that defined the element, but it will be equivalent.
func (r *Resource) Describe(symbol string) (string, string, error) {
var result, template string
var symbols []string
if symbol != "" {
symbols = []string{symbol}
} else {
// if no symbol given, describe all exposed services
svcs, err := r.descSource.ListServices()
if err != nil {
return "", "", err
}
if len(svcs) == 0 {
log.Println("Server returned an empty list of exposed services")
}
symbols = svcs
}
for _, s := range symbols {
if s[0] == '.' {
s = s[1:]
}
dsc, err := r.descSource.FindSymbol(s)
if err != nil {
return "", "", err
}
txt, err := grpcurl.GetDescriptorText(dsc, r.descSource)
if err != nil {
return "", "", err
}
result = txt
if dsc, ok := dsc.(*desc.MessageDescriptor); ok {
// for messages, also show a template in JSON, to make it easier to
// create a request to invoke an RPC
tmpl := grpcurl.MakeTemplate(dsc)
_, formatter, err := grpcurl.RequestParserAndFormatterFor(grpcurl.Format("json"), r.descSource, true, false, nil)
if err != nil {
return "", "", err
}
str, err := formatter(tmpl)
if err != nil {
return "", "", err
}
template = str
}
}
return result, template, nil
}
// Invoke - invoking gRPC function
func (r *Resource) Invoke(ctx context.Context, symbol string, in io.Reader) (string, time.Duration, error) {
// because of grpcurl directlu fmt.Printf on their invoke function
// so we stub the Stdout using os.Pipe
backUpStdout := os.Stdout
defer func() {
os.Stdout = backUpStdout
}()
f, w, err := os.Pipe()
if err != nil {
return "", 0, err
}
os.Stdout = w
rf, formatter, err := grpcurl.RequestParserAndFormatterFor(grpcurl.Format("json"), r.descSource, false, true, in)
if err != nil {
return "", 0, err
}
h := grpcurl.NewDefaultEventHandler(os.Stdout, r.descSource, formatter, false)
start := time.Now()
err = grpcurl.InvokeRPC(ctx, r.descSource, r.clientConn, symbol, r.headers, h, rf.Next)
end := time.Now().Sub(start) / time.Millisecond
if err != nil {
return "", end, err
}
if h.Status.Code() != codes.OK {
log.Printf("ERROR:\n Code: %s\n Message: %s\n", h.Status.Code().String(), h.Status.Message())
r.exit(1)
}
// copy the output in a separate goroutine so printing can't block indefinitely
outC := make(chan string)
go func() {
var buf bytes.Buffer
io.Copy(&buf, f)
outC <- buf.String()
}()
w.Close()
out := <-outC
return out, end, nil
}
// Close - to close all resources that was opened before
func (r *Resource) Close() {
if r.refClient != nil {
r.refClient.Reset()
r.refClient = nil
}
if r.clientConn != nil {
r.clientConn.Close()
r.clientConn = nil
}
}
func (r *Resource) exit(code int) {
// to force reset before os exit
r.Close()
os.Exit(code)
}

26
grpcox.go Normal file
View File

@ -0,0 +1,26 @@
package main
import (
"fmt"
"log"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/gusaul/grpcox/handler"
)
func main() {
port := ":6969"
muxRouter := mux.NewRouter()
handler.Init(muxRouter)
srv := &http.Server{
Handler: muxRouter,
Addr: "127.0.0.1" + port,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
fmt.Println("Service started on", port)
log.Fatal(srv.ListenAndServe())
}

153
handler/handler.go Normal file
View File

@ -0,0 +1,153 @@
package handler
import (
"bytes"
"context"
"fmt"
"net/http"
"strconv"
"github.com/gorilla/mux"
"github.com/gusaul/grpcox/core"
)
func index(w http.ResponseWriter, r *http.Request) {
body := new(bytes.Buffer)
err := indexHTML.Execute(body, make(map[string]string))
if err != nil {
writeError(w, err)
return
}
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write(body.Bytes())
}
func getLists(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
host := vars["host"]
if host == "" {
writeError(w, fmt.Errorf("Invalid Host"))
return
}
service := vars["serv_name"]
g := new(core.GrpCox)
useTLS, _ := strconv.ParseBool(r.Header.Get("use_tls"))
g.PlainText = !useTLS
res, err := g.GetResource(context.Background(), host)
if err != nil {
writeError(w, err)
return
}
defer res.Close()
result, err := res.List(service)
if err != nil {
writeError(w, err)
return
}
response(w, result)
}
func describeFunction(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
host := vars["host"]
if host == "" {
writeError(w, fmt.Errorf("Invalid Host"))
return
}
funcName := vars["func_name"]
if host == "" {
writeError(w, fmt.Errorf("Invalid Func Name"))
return
}
g := new(core.GrpCox)
useTLS, _ := strconv.ParseBool(r.Header.Get("use_tls"))
g.PlainText = !useTLS
res, err := g.GetResource(context.Background(), host)
if err != nil {
writeError(w, err)
return
}
defer res.Close()
// get param
result, _, err := res.Describe(funcName)
if err != nil {
writeError(w, err)
return
}
match := reGetFuncArg.FindStringSubmatch(result)
if len(match) < 2 {
writeError(w, fmt.Errorf("Invalid Func Type"))
return
}
// describe func
result, template, err := res.Describe(match[1])
if err != nil {
writeError(w, err)
return
}
type desc struct {
Schema string `json:"schema"`
Template string `json:"template"`
}
response(w, desc{
Schema: result,
Template: template,
})
}
func invokeFunction(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
host := vars["host"]
if host == "" {
writeError(w, fmt.Errorf("Invalid Host"))
return
}
funcName := vars["func_name"]
if host == "" {
writeError(w, fmt.Errorf("Invalid Func Name"))
return
}
g := new(core.GrpCox)
useTLS, _ := strconv.ParseBool(r.Header.Get("use_tls"))
g.PlainText = !useTLS
res, err := g.GetResource(context.Background(), host)
if err != nil {
writeError(w, err)
return
}
defer res.Close()
// get param
result, timer, err := res.Invoke(context.Background(), funcName, r.Body)
if err != nil {
writeError(w, err)
return
}
type invRes struct {
Time string `json:"timer"`
Result string `json:"result"`
}
response(w, invRes{
Time: timer.String(),
Result: result,
})
}

36
handler/routes.go Normal file
View File

@ -0,0 +1,36 @@
package handler
import (
"net/http"
"github.com/gorilla/mux"
)
// Init - routes initialization
func Init(router *mux.Router) {
router.HandleFunc("/", index)
ajaxRoute := router.PathPrefix("/server/{host}").Subrouter()
ajaxRoute.HandleFunc("/services", corsHandler(getLists)).Methods(http.MethodGet, http.MethodOptions)
ajaxRoute.HandleFunc("/service/{serv_name}/functions", corsHandler(getLists)).Methods(http.MethodGet, http.MethodOptions)
ajaxRoute.HandleFunc("/function/{func_name}/describe", corsHandler(describeFunction)).Methods(http.MethodGet, http.MethodOptions)
ajaxRoute.HandleFunc("/function/{func_name}/invoke", corsHandler(invokeFunction)).Methods(http.MethodPost, http.MethodOptions)
assetsPath := "index"
router.PathPrefix("/css/").Handler(http.StripPrefix("/css/", http.FileServer(http.Dir(assetsPath+"/css/"))))
router.PathPrefix("/js/").Handler(http.StripPrefix("/js/", http.FileServer(http.Dir(assetsPath+"/js/"))))
router.PathPrefix("/font/").Handler(http.StripPrefix("/font/", http.FileServer(http.Dir(assetsPath+"/font/"))))
}
func corsHandler(h http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
if r.Method == http.MethodOptions {
w.Header().Set("Access-Control-Allow-Headers", "use_tls")
return
}
h.ServeHTTP(w, r)
}
}

44
handler/types.go Normal file
View File

@ -0,0 +1,44 @@
package handler
import (
"encoding/json"
"html/template"
"net/http"
"regexp"
)
var (
reGetFuncArg *regexp.Regexp
indexHTML *template.Template
)
func init() {
reGetFuncArg = regexp.MustCompile("\\( (.*) \\) returns")
indexHTML = template.Must(template.New("index.html").Delims("{[", "]}").ParseFiles("index/index.html"))
}
// Response - Standar ajax Response
type Response struct {
Error string `json:"error,omitempty"`
Data interface{} `json:"data"`
}
func writeError(w http.ResponseWriter, err error) {
e, _ := json.Marshal(Response{
Error: err.Error(),
})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(e)
}
func response(w http.ResponseWriter, data interface{}) {
e, _ := json.Marshal(Response{
Data: data,
})
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(e)
}

6
index/css/bootstrap.min.css vendored Executable file

File diff suppressed because one or more lines are too long

44
index/css/mdb.min.css vendored Executable file

File diff suppressed because one or more lines are too long

63
index/css/style.css Normal file
View File

@ -0,0 +1,63 @@
body {
padding-bottom: 50px;
}
.mt-10 {
margin-top:10px;
}
.pt-50 {
padding-top:50px;
}
.custom-pretty {
border:none!important;
}
.custom-control-label:hover {
cursor: pointer;
}
.loading-overlay {
position: absolute;
top:0;
display: grid;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,.5);
z-index: 99;
}
.loading-overlay div {
margin:auto;
}
#choose-service, #choose-function, #body-request {
margin-top: 40px;
}
.schema-body {
height: 250px;
overflow-y: scroll;
}
#response {
margin-top: 30px;
}
/* github corner */
.github-corner:hover .octo-arm{
animation:octocat-wave 560ms ease-in-out
}
@keyframes octocat-wave{
0%,100%{
transform:rotate(0)
}
20%,60%{
transform:rotate(-25deg)
}
40%,80%{
transform:rotate(10deg)
}
}
@media (max-width:500px){
.github-corner:hover .octo-arm{
animation:none
}
.github-corner .octo-arm{
animation:octocat-wave 560ms ease-in-out
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

124
index/index.html Normal file
View File

@ -0,0 +1,124 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>gRPCox - gRPC Testing Environment</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link href="css/bootstrap.min.css" rel="stylesheet">
<link href="css/mdb.min.css" rel="stylesheet">
<link href="css/style.css" rel="stylesheet">
</head>
<body>
<div class="container pt-50">
<div class="row animated fadeIn">
<div class="col">
<div class="md-form input-group">
<input type="text" class="form-control" id="server-target">
<label for="server-target">gRPC Server Target</label>
<div class="input-group-append">
<button id="get-services" class="btn btn-mdb-color waves-effect m-0" type="button"><i class="fa fa-plug"></i></button>
</div>
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="use-tls">
<label class="custom-control-label" for="use-tls">Use TLS</label>
</div>
<div class="other-elem" id="choose-service" style="display: none">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text btn-dark" for="select-service"><i class="fa fa-television"></i>&nbsp;&nbsp;Services</span>
</div>
<select class="browser-default custom-select" id="select-service"></select>
</div>
</div>
<div class="other-elem" id="choose-function" style="display: none">
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text btn-dark" for="select-function"><i class="fa fa-rocket"></i>&nbsp;&nbsp;Methods</span>
</div>
<select class="browser-default custom-select" id="select-function"></select>
</div>
</div>
<div class="row other-elem" id="body-request" style="display: none">
<div class="col-md-7">
<div class="card">
<div class="card-body schema-body">
<pre id="editor"></pre>
</div>
</div>
<button class="btn btn-primary waves-effect mt-10" id="invoke-func" type="button"><i class="fa fa-play"></i>&nbsp;&nbsp;Submit</button>
</div>
<div class="col-md-5">
<div class="card">
<div class="card-body schema-body">
<h4 class="card-title"><a>Schema Input</a></h4>
<pre class="prettyprint custom-pretty" id="schema-proto"></pre>
</div>
</div>
</div>
</div>
<div class="row other-elem" id="response" style="display: none">
<div class="col">
<div class="card">
<div class="card-body">
<small class="pull-right" id="timer-resp">Time : <span></span></small>
<h4 class="card-title"><a>Response:</a></h4>
<p class="card-text">
<pre class="prettyprint custom-pretty" id="json-response"></pre>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<a target="_blank" href="https://github.com/gusaul" class="github-corner" aria-label="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;"
aria-hidden="true">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2"
fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z"
fill="currentColor" class="octo-body"></path>
</svg>
</a>
<div class="loading-overlay" style="display: none">
<div><svg width="200px" height="200px" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid"
class="lds-flat-ball" style="background: none;">
<defs>
<mask ng-attr-id="{{config.mid}}" id="lds-flat-ball-mask-e49120e4825">
<circle cx="50" cy="50" r="45" fill="#fff"></circle>
</mask>
</defs>
<circle cx="50" cy="50" r="45" ng-attr-fill="{{config.base}}" fill="#85a2b6"></circle>
<path ng-attr-fill="{{config.dark}}" mask="url(#lds-flat-ball-mask-e49120e4825)" fill="rgb(107, 131, 147)" d="M 37.2721 55.3778 L 62.7279 29.922 L 162.728 129.922 L 137.272 155.378 Z">
<animate attributeName="d" calcMode="spline" values="M 37.27207793864214 40.72792206135786 L 62.72792206135786 15.272077938642143 L 162.72792206135784 115.27207793864214 L 137.27207793864216 140.72792206135784 Z;M 37.27207793864214 84.72792206135786 L 62.72792206135786 59.27207793864214 L 162.72792206135784 159.27207793864216 L 137.27207793864216 184.72792206135784 Z;M 37.27207793864214 40.72792206135786 L 62.72792206135786 15.272077938642143 L 162.72792206135784 115.27207793864214 L 137.27207793864216 140.72792206135784 Z"
keyTimes="0;0.5;1" dur="1" keySplines="0.45 0 0.9 0.55;0 0.45 0.55 0.9" begin="0s" repeatCount="indefinite"></animate>
</path>
<circle cx="50" ng-attr-cy="{{config.cy}}" ng-attr-r="{{config.radius}}" ng-attr-fill="{{config.color}}" cy="42.6499"
r="18" fill="#fdfdfd">
<animate attributeName="cy" calcMode="spline" values="28;72;28" keyTimes="0;0.5;1" dur="1" keySplines="0.45 0 0.9 0.55;0 0.45 0.55 0.9"
begin="0s" repeatCount="indefinite"></animate>
</circle>
</svg></div>
</div>
<script type="text/javascript" src="js/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="js/bootstrap.min.js"></script>
<script type="text/javascript" src="js/mdb.min.js"></script>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.1/ace.js"></script>
<script type="text/javascript" src="js/style.js"></script>
</body>
</html>

7
index/js/bootstrap.min.js vendored Executable file

File diff suppressed because one or more lines are too long

2
index/js/jquery-3.3.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
index/js/mdb.min.js vendored Normal file

File diff suppressed because one or more lines are too long

181
index/js/style.js Normal file
View File

@ -0,0 +1,181 @@
var target, use_tls, editor;
$('#get-services').click(function(){
var t = get_valid_target();
if (target != t) {
target = t;
use_tls = $('#use-tls').is(":checked");
} else {
return false;
}
$('.other-elem').hide();
var button = $(this).html();
$.ajax({
url: "server/"+target+"/services",
global: true,
method: "GET",
success: function(res){
if (res.error) {
target = "";
use_tls = "";
alert(res.error);
return;
}
$("#select-service").html(new Option("Choose Service", ""));
$.each(res.data, (_, item) => $("#select-service").append(new Option(item, item)));
$('#choose-service').show();
},
error: function(_, _, errorThrown) {
target = "";
use_tls = "";
alert(errorThrown);
},
beforeSend: function(xhr){
xhr.setRequestHeader('use_tls', use_tls);
$(this).html("Loading...");
},
complete: function(){
$(this).html(button);
}
});
});
$('#select-service').change(function(){
var selected = $(this).val();
if (selected == "") {
return false;
}
$('#body-request').hide();
$('#response').hide();
$.ajax({
url: "server/"+target+"/service/"+selected+"/functions",
global: true,
method: "GET",
success: function(res){
if (res.error) {
alert(res.error);
return;
}
$("#select-function").html(new Option("Choose Methods", ""));
$.each(res.data, (_, item) => $("#select-function").append(new Option(item.substr(selected.length) , item)));
$('#choose-function').show();
},
error: err,
beforeSend: function(xhr){
xhr.setRequestHeader('use_tls', use_tls);
show_loading();
},
complete: function(){
hide_loading();
}
});
});
$('#select-function').change(function(){
var selected = $(this).val();
if (selected == "") {
return false;
}
$('#response').hide();
$.ajax({
url: "server/"+target+"/function/"+selected+"/describe",
global: true,
method: "GET",
success: function(res){
if (res.error) {
alert(res.error);
return;
}
generate_editor(res.data.template);
$("#schema-proto").html(PR.prettyPrintOne(res.data.schema));
$('#body-request').show();
},
error: err,
beforeSend: function(xhr){
xhr.setRequestHeader('use_tls', use_tls);
show_loading();
},
complete: function(){
hide_loading();
}
});
});
$('#invoke-func').click(function(){
var func = $('#select-function').val();
if (func == "") {
return false;
}
var body = editor.getValue();
var button = $(this).html();
$.ajax({
url: "server/"+target+"/function/"+func+"/invoke",
global: true,
method: "POST",
data: body,
dataType: "json",
success: function(res){
if (res.error) {
alert(res.error);
return;
}
$("#json-response").html(PR.prettyPrintOne(res.data.result));
$("#timer-resp span").html(res.data.timer);
$('#response').show();
},
error: err,
beforeSend: function(xhr){
xhr.setRequestHeader('use_tls', use_tls);
$(this).html("Loading...");
},
complete: function(){
$(this).html(button);
}
});
});
function generate_editor(content) {
if(editor) {
editor.setValue(content);
return true;
}
$("#editor").html(content);
editor = ace.edit("editor");
editor.setOptions({
maxLines: Infinity
});
editor.renderer.setScrollMargin(10, 10, 10, 10);
editor.setTheme("ace/theme/github");
editor.session.setMode("ace/mode/json");
editor.renderer.setShowGutter(false);
}
function get_valid_target() {
t = $('#server-target').val().trim();
if (t == "") {
return target;
}
ts = t.split("://");
if (ts.length > 1) {
$('#server-target').val(ts[1]);
return ts[1];
}
return ts[0];
}
function err(_, _, errorThrown) {
alert(errorThrown);
}
function show_loading() {
$('.loading-overlay').show();
}
function hide_loading() {
$('.loading-overlay').hide();
}