123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392 |
- var ngrok = angular.module("ngrok", ["ngSanitize"]);
- var hexRepr = function(bytes) {
- var buf = [];
- var ascii = [];
- for (var i=0; i<bytes.length; ++i) {
- var b = bytes[i];
- if (!(i%8) && i!=0) {
- buf.push("\t");
- buf.push.apply(buf, ascii)
- buf.push('\n');
- ascii = [];
- }
- if (b < 16) {
- buf.push("0");
- }
- if (b < 0x20 || b > 0x7e) {
- ascii.push('.');
- } else {
- ascii.push(String.fromCharCode(b));
- }
- buf.push(b.toString(16));
- buf.push(" ");
- ascii.push(" ");
- }
- if (ascii.length > 0) {
- var charsLeft = 8 - (ascii.length / 2);
- for (i=0; i<charsLeft; ++i) {
- buf.push(" ");
- }
- buf.push("\t");
- buf.push.apply(buf, ascii);
- }
- return buf.join("");
- }
- ngrok.factory("txnSvc", function() {
- var processBody = function(body, binary) {
- body.binary = binary;
- body.isForm = body.ContentType == "application/x-www-form-urlencoded";
- body.exists = body.Length > 0;
- body.hasError = !!body.Error;
- var syntaxClass = {
- "text/xml": "xml",
- "application/xml": "xml",
- "text/html": "xml",
- "text/css": "css",
- "application/json": "json",
- "text/javascript": "javascript",
- "application/javascript": "javascript",
- }[body.ContentType];
- // decode body
- if (binary) {
- body.Text = "";
- } else {
- body.Text = Base64.decode(body.Text).text;
- }
- // prettify
- var transform = {
- "xml": "xml",
- "json": "json"
- }[syntaxClass];
- if (!body.hasError && !!transform) {
- try {
- // vkbeautify does poorly at formatting html
- if (body.ContentType != "text/html") {
- body.Text = vkbeautify[transform](body.Text);
- }
- } catch (e) {
- }
- }
- if (!!syntaxClass) {
- body.Text = hljs.highlight(syntaxClass, body.Text).value;
- } else {
- // highlight.js doesn't have a 'plaintext' syntax, so we'll just copy its escaping function.
- body.Text = body.Text.replace(/&/gm, '&').replace(/</gm, '<').replace(/>/gm, '>');
- }
- };
- var processReq = function(req) {
- if (!req.RawBytes) {
- var decoded = Base64.decode(req.Raw);
- req.RawBytes = hexRepr(decoded.bytes);
- if (!req.Binary) {
- req.RawText = decoded.text;
- }
- }
- processBody(req.Body, req.Binary);
- };
- var processResp = function(resp) {
- resp.statusClass = {
- '2': "text-info",
- '3': "muted",
- '4': "text-warning",
- '5': "text-error"
- }[resp.Status[0]];
- if (!resp.RawBytes) {
- var decoded = Base64.decode(resp.Raw);
- resp.RawBytes = hexRepr(decoded.bytes);
- if (!resp.Binary) {
- resp.RawText = decoded.text;
- }
- }
- processBody(resp.Body, resp.Binary);
- };
- var processTxn = function(txn) {
- processReq(txn.Req);
- processResp(txn.Resp);
- };
- var preprocessTxn = function(txn) {
- var toFixed = function(value, precision) {
- var power = Math.pow(10, precision || 0);
- return String(Math.round(value * power) / power);
- }
- // parse nanosecond count
- var ns = txn.Duration;
- var ms = ns / (1000 * 1000);
- txn.Duration = ms;
- if (ms > 1000) {
- txn.Duration = toFixed(ms / 1000, 2) + "s";
- } else {
- txn.Duration = toFixed(ms, 2) + "ms";
- }
- };
- var active;
- var txns = window.data.Txns;
- txns.forEach(function(t) {
- preprocessTxn(t);
- });
- var activate = function(txn) {
- if (!txn.processed) {
- processTxn(txn);
- txn.processed = true;
- }
- active = txn;
- }
- if (txns.length > 0) {
- activate(txns[0]);
- }
- return {
- add: function(txnData) {
- txns.unshift(JSON.parse(txnData));
- preprocessTxn(txns[0]);
- if (!active) {
- activate(txns[0]);
- }
- },
- all: function() {
- return txns;
- },
- active: function(txn) {
- if (!txn) {
- return active;
- } else {
- activate(txn);
- }
- },
- isActive: function(txn) {
- return !!active && txn.Id == active.Id;
- }
- };
- });
- ngrok.directive({
- "keyval": function() {
- return {
- scope: {
- title: "@",
- tuples: "=",
- },
- replace: true,
- restrict: "E",
- template: "" +
- '<div ng-show="hasKeys()">' +
- '<h6>{{title}}</h6>' +
- '<table class="table params">' +
- '<tr ng-repeat="(key, value) in tuples">' +
- '<th>{{ key }}</th>' +
- '<td>{{ value }}</td>' +
- '</tr>' +
- '</table>' +
- '</div>',
- link: function($scope) {
- $scope.hasKeys = function() {
- for (key in $scope.tuples) { return true; }
- return false;
- };
- }
- };
- },
- "tabs": function() {
- return {
- scope: {
- "tabs": "@",
- "btn": "@",
- "onbtnclick": "&"
- },
- replace: true,
- template: '' +
- '<ul class="nav nav-pills">' +
- '<li ng-repeat="tab in tabNames" ng-class="{\'active\': isTab(tab)}">' +
- '<a href="" ng-click="setTab(tab)">{{tab}}</a>' +
- '</li>' +
- '<li ng-show="!!btn" class="pull-right"> <button class="btn btn-primary" ng-click="onbtnclick()">{{btn}}</button></li>' +
- '</ul>',
- link: function postLink(scope, element, attrs) {
- scope.tabNames = attrs.tabs.split(",");
- scope.activeTab = scope.tabNames[0];
- scope.setTab = function(t) {
- scope.activeTab = t;
- };
- scope.$parent.isTab = scope.isTab = function(t) {
- return t == scope.activeTab;
- };
- },
- };
- },
- "body": function() {
- return {
- scope: {
- "body": "=",
- "binary": "="
- },
- template: '' +
- '<h6 ng-show="body.exists">' +
- '{{ body.Length }} bytes ' +
- '{{ body.RawContentType }}' +
- '</h6>' +
- '' +
- '<div ng-show="!body.isForm && !body.binary">' +
- '<pre ng-show="body.exists"><code ng-bind-html="body.Text"></code></pre>' +
- '</div>' +
- '' +
- '<div ng-show="body.isForm">' +
- '<keyval title="Form Params" tuples="body.Form">' +
- '</div>' +
- '<div ng-show="body.hasError" class="alert">' +
- '{{ body.Error }}' +
- '</div>',
- link: function($scope, $elem) {
- $scope.$watch(function() { return $scope.body; }, function() {
- if ($scope.body && $scope.body.ErrorOffset > -1) {
- var offset = $scope.body.ErrorOffset;
- function textNodes(node) {
- var textNodes = [];
- function getTextNodes(node) {
- if (node.nodeType == 3) {
- textNodes.push(node);
- } else {
- for (var i = 0, len = node.childNodes.length; i < len; ++i) {
- getTextNodes(node.childNodes[i]);
- }
- }
- }
- getTextNodes(node);
- return textNodes;
- }
- var tNodes = textNodes($elem.find("code").get(0));
- for (var i=0; i<tNodes.length; i++) {
- offset -= tNodes[i].nodeValue.length;
- if (offset < 0) {
- $(tNodes[i]).parent().css("background-color", "orange");
- break;
- }
- }
- }
- });
- }
- };
- }
- });
- ngrok.controller({
- "HttpTxns": function($scope, txnSvc) {
- $scope.tunnels = window.data.UiState.Tunnels;
- $scope.txns = txnSvc.all();
- if (!!window.WebSocket) {
- var ws = new WebSocket("ws://" + location.host + "/_ws");
- ws.onopen = function() {
- console.log("connected websocket for real-time updates");
- };
- ws.onmessage = function(message) {
- $scope.$apply(function() {
- txnSvc.add(message.data);
- });
- };
- ws.onerror = function(err) {
- console.log("Web socket error:")
- console.log(err);
- };
- ws.onclose = function(cls) {
- console.log("Web socket closed:" + cls);
- };
- }
- },
- "HttpRequest": function($scope, txnSvc) {
- $scope.replay = function() {
- $.ajax({
- type: "POST",
- url: "/http/in/replay",
- data: { txnid: txnSvc.active().Id }
- });
- }
- var setReq = function() {
- var txn = txnSvc.active();
- if (!!txn && txn.Req) {
- $scope.Req = txnSvc.active().Req;
- } else {
- $scope.Req = null;
- }
- };
- $scope.$watch(function() { return txnSvc.active() }, setReq);
- },
- "HttpResponse": function($scope, $element, txnSvc) {
- var setResp = function() {
- var txn = txnSvc.active();
- if (!!txn && txn.Resp) {
- $scope.Resp = txnSvc.active().Resp;
- } else {
- $scope.Resp = null;
- }
- };
- $scope.$watch(function() { return txnSvc.active() }, setResp);
- },
- "TxnNavItem": function($scope, txnSvc) {
- $scope.isActive = function() { return txnSvc.isActive($scope.txn); }
- $scope.makeActive = function() {
- txnSvc.active($scope.txn);
- };
- },
- "HttpTxn": function($scope, txnSvc, $timeout) {
- var setTxn = function() {
- $scope.Txn = txnSvc.active();
- };
- $scope.ISO8601 = function(ts) {
- if (!!ts) {
- return new Date(ts * 1000).toISOString();
- }
- };
- $scope.TimeFormat = function(ts) {
- if (!!ts) {
- return $.timeago($scope.ISO8601(ts));
- }
- };
- $scope.$watch(function() { return txnSvc.active() }, setTxn);
- // this causes angular to update the timestamps
- setInterval(function() { $scope.$apply(function() {}); }, 30000);
- },
- });
|