From 0aabffdd6d8443ccbb30b59d2af41d275fad20e1 Mon Sep 17 00:00:00 2001 From: lixucheng Date: Wed, 10 May 2017 11:50:55 +0800 Subject: [PATCH] [CE-38] Add chain detail functions for user dashboard Include below functions: 1. Chain topology overview, include network latency show. 2. Chain logs tracking. 3. Chain blocks presentation and transactions reveal. 4. Chain APIs list. 5. Chaincode list, invoke and query. Change-Id: I138205b807d747fc8f7a65a5937e1777ab6f6abd Signed-off-by: lixucheng --- user-dashboard/modules/chaincode.js | 156 +++++++++ .../public/css/dashboard/chain-detail.css | 36 ++ .../public/js/app/dashboard/chain/detail.js | 28 ++ .../js/app/dashboard/chain/detail/api.js | 40 +++ .../js/app/dashboard/chain/detail/blocks.js | 309 +++++++++++++++++ .../js/app/dashboard/chain/detail/log.js | 186 +++++++++++ .../app/dashboard/chain/detail/scrollspy.js | 84 +++++ .../js/app/dashboard/chain/detail/topology.js | 312 ++++++++++++++++++ .../js/app/dashboard/chaincode/deploy.js | 209 ++++++++++++ .../js/app/dashboard/chaincode/invoke.js | 201 +++++++++++ .../public/js/app/dashboard/chaincode/list.js | 122 +++++++ .../js/app/dashboard/chaincode/query.js | 212 ++++++++++++ .../public/js/entry/chain/detail.js | 6 + .../dashboard/chain/detail/block.html | 3 + .../dashboard/chain/detail/blockdialog.html | 7 + .../dashboard/chain/detail/blocktitle.html | 9 + .../chain/detail/largeContainer.html | 4 + .../dashboard/chain/detail/overlay.html | 5 + .../dashboard/chain/detail/transaction.html | 22 ++ .../dashboard/chaincode/chaincode.html | 24 ++ .../dashboard/chaincode/deploydialog.html | 44 +++ .../dashboard/chaincode/invokedialog.html | 37 +++ .../dashboard/chaincode/querydialog.html | 38 +++ user-dashboard/routes/api.js | 89 +++++ user-dashboard/routes/dashboard/chain.js | 20 ++ .../views/dashboard/chain/detail.pug | 129 ++++++++ 26 files changed, 2332 insertions(+) create mode 100644 user-dashboard/modules/chaincode.js create mode 100644 user-dashboard/public/css/dashboard/chain-detail.css create mode 100644 user-dashboard/public/js/app/dashboard/chain/detail.js create mode 100644 user-dashboard/public/js/app/dashboard/chain/detail/api.js create mode 100644 user-dashboard/public/js/app/dashboard/chain/detail/blocks.js create mode 100644 user-dashboard/public/js/app/dashboard/chain/detail/log.js create mode 100644 user-dashboard/public/js/app/dashboard/chain/detail/scrollspy.js create mode 100644 user-dashboard/public/js/app/dashboard/chain/detail/topology.js create mode 100644 user-dashboard/public/js/app/dashboard/chaincode/deploy.js create mode 100644 user-dashboard/public/js/app/dashboard/chaincode/invoke.js create mode 100644 user-dashboard/public/js/app/dashboard/chaincode/list.js create mode 100644 user-dashboard/public/js/app/dashboard/chaincode/query.js create mode 100644 user-dashboard/public/js/entry/chain/detail.js create mode 100644 user-dashboard/public/js/resources/dashboard/chain/detail/block.html create mode 100644 user-dashboard/public/js/resources/dashboard/chain/detail/blockdialog.html create mode 100644 user-dashboard/public/js/resources/dashboard/chain/detail/blocktitle.html create mode 100644 user-dashboard/public/js/resources/dashboard/chain/detail/largeContainer.html create mode 100644 user-dashboard/public/js/resources/dashboard/chain/detail/overlay.html create mode 100644 user-dashboard/public/js/resources/dashboard/chain/detail/transaction.html create mode 100644 user-dashboard/public/js/resources/dashboard/chaincode/chaincode.html create mode 100644 user-dashboard/public/js/resources/dashboard/chaincode/deploydialog.html create mode 100644 user-dashboard/public/js/resources/dashboard/chaincode/invokedialog.html create mode 100644 user-dashboard/public/js/resources/dashboard/chaincode/querydialog.html create mode 100644 user-dashboard/views/dashboard/chain/detail.pug diff --git a/user-dashboard/modules/chaincode.js b/user-dashboard/modules/chaincode.js new file mode 100644 index 000000000..5ee4b75ec --- /dev/null +++ b/user-dashboard/modules/chaincode.js @@ -0,0 +1,156 @@ +/** + * Created by lixuc on 2017/5/10. + */ +var rp = require("request-promise"); +var config = require("./configuration"); +var Pagination = require("../kit/pagination"); +var dt = require("../kit/date-tool"); + +function chaincode(chainId) { + this.chainId = chainId; +} +chaincode.prototype = { + RESTfulURL: "http://" + config.RESTful_Server + config.RESTful_BaseURL, + PoolManagerURL: "http://" + config.PoolManager_Server + config.PoolManager_BaseURL, + list: function(page) { + return new Promise(function(resolve, reject) { + rp({ + uri: this.RESTfulURL + "cluster/chaincode/deployed?cluster_id=" + this.chainId, + json: true + }).then(function(response) { + if (response.success) { + var cc = []; + var chain = response.result.cluster_info; + var chaincodes = response.result.chaincodes; + var pageNo = page || 1; + var pg = new Pagination(chaincodes); + chaincodes = pg.gotoPage(pageNo); + for (var i in chaincodes) { + cc.push({ + id: chaincodes[i]["chaincode_id"], + name: chaincodes[i]["chaincode_name"], + contract: { + id: chaincodes[i]["contract_id"], + name: chaincodes[i]["contract_name"], + invoke: chaincodes[i]["contract_default_functions"]["invoke"] || [], + query: chaincodes[i]["contract_default_functions"]["query"] || [] + }, + deployTime: dt.diff2Now(chaincodes[i]["deploy_time"]) + }); + } + resolve({ + success: true, + chain: { + name: chain["cluster_name"], + description: chain["cluster_description"] + }, + chaincodes: cc, + totalNumber: pg.getTotalRow(), + totalPage: pg.getTotalPage() + }); + } else { + var e = new Error(response.message); + e.status = 503; + throw e; + } + }).catch(function(err) { + reject({ + success: false, + message: (err.status == 503 && err.message) || "System maintenance, please try again later!" + }); + }); + }.bind(this)); + }, + getAPIs: function() { + return new Promise(function(resolve, reject) { + rp({ + uri: this.PoolManagerURL + "cluster/" + this.chainId, + json: true + }).then(function(response) { + if (response.status.toLowerCase() == "ok") { + var serviceUrl = response.data.service_url; + delete serviceUrl["ecaa"]; + delete serviceUrl["ecap"]; + delete serviceUrl["tcaa"]; + delete serviceUrl["tcap"]; + delete serviceUrl["tlscaa"]; + delete serviceUrl["tlscap"]; + resolve({ + success: true, + serviceUrl: serviceUrl + }); + } else { + var e = new Error(response.error); + e.status = 503; + throw e; + } + }).catch(function(err) { + reject({ + success: false, + message: (err.status == 503 && err.message) || "System maintenance, please try again later!" + }); + }); + }.bind(this)); + }, + invoke: function(id, func, args) { + return new Promise(function(resolve, reject) { + rp({ + method: "POST", + uri: this.RESTfulURL + "cluster/chaincode/invoke", + body: { + cluster_id: this.chainId, + chaincode_id: id, + func: func, + args: JSON.parse(args) + }, + json: true + }).then(function(response) { + if (response.success) { + resolve(response); + } else { + var e = new Error(response.message); + e.status = 503; + throw e; + } + }).catch(function(err) { + reject({ + success: false, + message: (err.status == 503 && err.message) || "System maintenance, please try again later!" + }); + }); + }.bind(this)); + }, + query: function(id, func, args) { + return new Promise(function(resolve, reject) { + rp({ + method: "POST", + uri: this.RESTfulURL + "cluster/chaincode/query", + body: { + cluster_id: this.chainId, + chaincode_id: id, + func: func, + args: JSON.parse(args) + }, + json: true + }).then(function(response) { + if (response.success) { + var result = response.result.response; + resolve({ + success: true, + result: result + }); + } else { + var e = new Error(response.message); + e.status = 503; + throw e; + } + }).catch(function(err) { + reject({ + success: false, + message: (err.status == 503 && err.message) || "System maintenance, please try again later!" + }); + }); + }.bind(this)); + } +}; +module.exports = chaincode; \ No newline at end of file diff --git a/user-dashboard/public/css/dashboard/chain-detail.css b/user-dashboard/public/css/dashboard/chain-detail.css new file mode 100644 index 000000000..51012ba53 --- /dev/null +++ b/user-dashboard/public/css/dashboard/chain-detail.css @@ -0,0 +1,36 @@ +@CHARSET "UTF-8"; +.ds-chain-main-container{width:80%;margin-top:60px;font-family:"Helvetica Neue",Arial,sans-serif;} +.ds-chain-main-container h1{font-size:24px;color:#4178BE;margin-top:42px;border-bottom:none;} +.ds-chain-main-container h1>label{color:#ADADAD;font-size:14px;} +.ds-chain-main h1>a{color:#4178BE;} +.ds-chain-main h2{font-size:18px;color:#296fb6;margin-bottom:30px;border-bottom:2px solid #296fb6;} +.ds-chain-main th{font-size:16px;color:#4178BE;} +.ds-chain-main thead :hover{color:#FF5003;} +.expand-compress-icon{margin-top:10px;cursor:pointer;} +.latency-show{position:absolute;bottom:0;right:10px;font-size:10px;font-weight:500;} +.log-badge{position:absolute;bottom:0;left:0;} +.log-badge-normal{background-color:#f7f7f7;color:#666;font-weight:inherit;} +.log-show{position:absolute;bottom:0;right:15px;font-size:10px;font-weight:500;} +ul.listico{margin:30px auto 0;border-top:2px solid #4178BE;overflow:hidden;width:200px;} +ul.listico li{float:left;width:46px;padding:12px 8px 10px 12px;list-style:none;} +ul.listico li a{display:block;padding-top:42px;height:32px;line-height:32px;text-align:center;} +ul.listico li a.ico0{background:url(/images/icon-deploy.png) no-repeat center 0;color:#ff6523;} +ul.listico li a.ico1{background:url(/images/icon-invoke.png) no-repeat center 0;color:#ff6523;} +ul.listico li a.ico2{background:url(/images/icon-query.png) no-repeat center 0;color:#ff6523;} +ul.listico li a.ico3{background:url(/images/icon-topo.png) no-repeat center 0;} +ul.listico li a.ico4{background:url(/images/icon-log.png) no-repeat center 0;} +ul.listico li a.ico5{background:url(/images/icon-bc.png) no-repeat center 0;} +ul.listico li a.ico6{background:url(/images/icon-apis.png) no-repeat center 0;} +.img-dropdown{margin-right:12px;margin-left:-2px;} +td.no-result{color:#b2b2b2;text-align:center;line-height:80px;font-style:italic;font-size:24px;} +.panel-overlayer{position:absolute;top:0;left:0;width:100%;height:100%; +background:#fafafa;opacity:0.3;border-radius:4px;z-index:999;} +.iconrefresh{position:absolute;width:20px;height:20px;top:50%;left:50%;margin-top:-10px;margin-left:-10px;} +.nodeDetail{background:#333 none repeat scroll 0 0;border-radius:3px;box-sizing:border-box; +color:rgba(255, 255, 255, 0.7);font-size:12px;line-height:18px;padding:5px 8px;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);} +.block{width:60px;height:60px;border:1px solid rgba(0, 0, 0, 0.25);box-shadow:0 0 6px rgba(0, 0, 0, 0.5); +background-color:rgba(255, 255, 255, 0.4);text-align:center;} +.block:hover{box-shadow:0px 0px 12px rgba(0,0,0,0.5);border:1px solid rgba(0,0,0,0.75);} +.block .symbol{position:absolute;top:35px;left:0px;right:0px;font-size:60px;font-weight:bold;color:rgba(0,0,0,0.7); +text-shadow:0 0 10px rgba(255,255,255,0.95);} +.notify-message{background:#444 none repeat scroll 0 0;border:1px solid #444;color:#fff;} \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/chain/detail.js b/user-dashboard/public/js/app/dashboard/chain/detail.js new file mode 100644 index 000000000..c82c42986 --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/chain/detail.js @@ -0,0 +1,28 @@ +/** + * Created by lixuc on 2017/5/10. + */ +define([ + "jquery", + "app/dashboard/profile", + "app/dashboard/chain/detail/topology", + "app/dashboard/chain/detail/log", + "app/dashboard/chain/detail/blocks", + "app/dashboard/chain/detail/api", + "app/dashboard/chaincode/list", + "uikit" +], function($, Profile, Topology, Log, Blocks, API, Chaincodelist) { + $(function() { + new Profile(); + + var $chainId = $("#chainId"); + var topology = new Topology($chainId.val()); + var log = new Log($chainId.val()); + var blocks = new Blocks($chainId.val()); + new API(); + new Chaincodelist($chainId.val()); + + topology.load(); + log.load(); + blocks.load(); + }); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/chain/detail/api.js b/user-dashboard/public/js/app/dashboard/chain/detail/api.js new file mode 100644 index 000000000..1154c16e3 --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/chain/detail/api.js @@ -0,0 +1,40 @@ +/** + * Created by lixuc on 2017/5/10. + */ +define([ + "jquery", + "app/dashboard/chain/detail/scrollspy", + "plugin/text!resources/dashboard/chain/detail/largeContainer.html" +], function($, Scrollspy, largeContainer) { + var scrollspy = new Scrollspy(); + var $largeContainer; + var $expandCompressBtn; + var $apiPanel; + + function api() { + $expandCompressBtn = $("#api .expand-compress-icon"); + $apiPanel = $("#apiPanel"); + + $expandCompressBtn.on("click", function() { + $largeContainer = $(largeContainer); + $largeContainer.find(">div").append($apiPanel.detach().css("height", "450px")); + $largeContainer.on({ + "hide.uk.modal": function() { + $("#api").after($apiPanel.detach().css("height", "300px")); + $(this).remove(); + } + }); + $("body").append($largeContainer); + var $container = UIkit.modal($largeContainer); + $container.options.center = true; + $container.show(); + }); + $apiPanel.on("inview.uk.scrollspy", function() { + scrollspy.select("apis", true); + }); + $apiPanel.on("outview.uk.scrollspy", function() { + scrollspy.select("apis", false); + }); + } + return api; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/chain/detail/blocks.js b/user-dashboard/public/js/app/dashboard/chain/detail/blocks.js new file mode 100644 index 000000000..66d7987c5 --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/chain/detail/blocks.js @@ -0,0 +1,309 @@ +/** + * Created by lixuc on 2017/5/10. + */ +define([ + "jquery", + "app/kit/ui", + "app/dashboard/chain/detail/scrollspy", + "three", + "tween.min", + "plugin/text!resources/dashboard/chain/detail/block.html", + "plugin/text!resources/dashboard/chain/detail/blockdialog.html", + "plugin/text!resources/dashboard/chain/detail/blocktitle.html", + "plugin/text!resources/dashboard/chain/detail/transaction.html", + "plugin/text!resources/dashboard/chain/detail/overlay.html", + "plugin/text!resources/dashboard/chain/detail/largeContainer.html", + "CSS3DRenderer", + "TrackballControls" +], function($, UI, Scrollspy, THREE, TWEEN, block, blockdialog, blocktitle, transaction, overlay, largeContainer) { + var ui = new UI(); + var scrollspy = new Scrollspy(); + var $largeContainer; + var $expandCompressBtn; + var $blockdialog; + var $blockDetailDialog; + var $blockDetailPanel; + var $blockDetailTitle; + var $blockchain; + var width; + var height; + + var renderer; + var scene; + var camera; + var controls; + + var blocks = []; + var targets = []; + + var interval = 5000; + var timer; + + function render() { + renderer.render(scene, camera); + } + function calculateHelixCoord() { + targets = []; + var vector = new THREE.Vector3(); + for (var i=0; idiv").append($blockchain.detach().css("height", "450px")); + $largeContainer.on({ + "show.uk.modal": function() { + controls.reset(); + renderer.setSize($blockchain.width(), $blockchain.height()); + repaint(); + }, + "hide.uk.modal": function() { + $("#bc").after($blockchain.detach().css("height", "300px")); + $(this).remove(); + controls.reset(); + renderer.setSize($blockchain.width(), $blockchain.height()); + repaint(); + } + }); + $("body").append($largeContainer); + var $container = UIkit.modal($largeContainer); + $container.options.center = true; + $container.show(); + }); + $blockchain.on("inview.uk.scrollspy", function() { + _self.refresh(); + scrollspy.select("blocks", true); + }); + $blockchain.on("outview.uk.scrollspy", function() { + _self.stopRefresh(); + scrollspy.select("blocks", false); + }); + this.init = function() { + blocks = []; + $.ajax({ + type: "GET", + url: "/api/chain/" + this.chainId + "/blocks", + dataType: "json", + success: function(data) { + if (data.success) { + var blockInfo = _.template(block); + var chainHeight = data.height; + for (var i=0; i 0) { + var trans = _.template(transaction); + for (var i in transactions) { + $blockDetailPanel.append($(trans({ + uuid: transactions[i].uuid, + chaincodeId: base64 ? + transactions[i].chaincodeId : + atob(transactions[i].chaincodeId), + type: transactions[i].type, + payload: base64 ? + transactions[i].payload : + atob(transactions[i].payload), + timestamp: transactions[i].timestamp + }))); + } + } + } + var $overlay = $(overlay); + $blockDetailPanel.append($overlay); + $.ajax({ + type: "GET", + url: "/api/chain/" + _self.chainId + "/block/" + blockId, + dataType: "json", + success: function(data) { + $overlay.remove(); + $blockDetailDialog.options.bgclose = true; + if (data.success) { + $blockDetailTitle.append(_.template(blocktitle)({ + existTransaction: data.transactions.length > 0, + id: blockId, + timestamp: data.commitTime + })); + $blockDetailTitle.find("button").click(function() { + if ($(this).hasClass("uk-button-danger")) { + $(this).removeClass("uk-button-danger") + .addClass("uk-button-success").text("Conceal"); + injectTransaction(data.transactions, false); + } else { + $(this).removeClass("uk-button-success") + .addClass("uk-button-danger").text("Reveal"); + injectTransaction(data.transactions, true); + } + }); + injectTransaction(data.transactions, true); + } else { + ui.dialog.error(data.message, 2000); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + $overlay.remove(); + $blockDetailDialog.options.bgclose = true; + ui.dialog.error(errorThrown); + } + }); + }, + "hide.uk.modal": function() { + $(this).remove(); + } + }); + $("body").append($blockdialog); + $blockDetailDialog = UIkit.modal($blockdialog); + $blockDetailDialog.options.bgclose = false; + $blockDetailDialog.show(); + } + } + }); + this.init(); + animate(); + }, + refresh: function() { + var _self = this; + clearTimeout(timer); + $.ajax({ + type: "GET", + url: "/api/chain/" + this.chainId + "/blocks", + dataType: "json", + success: function(data) { + if (data.success) { + var chainHeight = data.height; + if (parseInt(chainHeight, 10) > blocks.length) { + var blockInfo = _.template(block); + for (var i=blocks.length; idiv").append($logPanel.detach().css("height", "450px")); + $largeContainer.on({ + "show.uk.modal": function() { + $logPanel.find(">div").css("height", "440px"); + }, + "hide.uk.modal": function() { + $("#log").after($logPanel.detach().css("height", "300px")); + $(this).remove(); + $logPanel.find(">div").css("height", "290px"); + } + }); + $("body").append($largeContainer); + var $container = UIkit.modal($largeContainer); + $container.options.center = true; + $container.show(); + }); + $logPanel.on("inview.uk.scrollspy", function() { + var $sel = $nodeList.find(".uk-badge-success"); + if ($sel && buffer[$sel.text()] && buffer[$sel.text()].length == 0) { + this.writeToBuffer($sel.data("type"), $sel.text()); + } + scrollspy.select("log", true); + }.bind(this)); + $logPanel.on("outview.uk.scrollspy", function() { + scrollspy.select("log", false); + }.bind(this)); + } + log.prototype = { + load: function() { + var _self = this; + var $overlay = $(overlay); + $logPanel.append($overlay); + $.ajax({ + type: "GET", + url: "/api/chain/" + this.chainId + "/log/nodes", + dataType: "json", + success: function(data) { + $overlay.remove(); + if (data.success) { + $nodeList.empty(); + $logPanel.find(">div").remove(); + for (var i in data.nodes) { + var badgeClass = (i == 0) ? "uk-badge-success" : "log-badge-normal"; + var badgeStyle = (i == 0) ? "border-radius:0 0 0 4px;cursor:pointer;" : + "border-radius:0;cursor:pointer;"; + $("
" + data.nodes[i].id + + "
").click(function() { + if (!$(this).hasClass("uk-badge-success")) { + var $sel = $nodeList.find(".uk-badge-success"); + $sel.removeClass("uk-badge-success").addClass("log-badge-normal"); + $logPanel.find("." + $sel.text()).hide(); + + $(this).removeClass("log-badge-normal").addClass("uk-badge-success"); + $logPanel.find("." + $(this).text()).show(); + + //刷新当前选择节点的日志 + if (buffer[$(this).text()].length == 0) { + _self.writeToBuffer($(this).data("type"), + $(this).text() == "chaincode" ? "vp0": $(this).text()); + } + } + }).appendTo($("
  • ").appendTo($nodeList)); + + var logContainerStyle = "font-size:11px;height:290px;overflow-x:hidden;overflow-y:auto;"; + if (i != 0) { + logContainerStyle += "display:none;"; + } + $logPanel.append("
    "); + //初始化log缓冲区 + buffer[data.nodes[i].id] = []; + //初始化每个节点的定时器 + timer[data.nodes[i].id] = null; + //初始化每个节点的刷新显示定时器 + clocker[data.nodes[i].id] = null; + //初始化时间戳 + timestamp[data.nodes[i].id] = ""; + } + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + $overlay.remove(); + ui.dialog.error(errorThrown); + } + }); + }, + writeToBuffer: function(type, nid) { + clearTimeout(timer[nid]); + var _self = this; + $.ajax({ + type: "GET", + url: "/api/chain/" + this.chainId + "/log", + data: { + type: type, + node: nid, + size: maxSize, + time: timestamp[nid] + }, + dataType: "json", + success: function(data) { + if (data.success) { + timestamp[nid] = data.latest_ts; + var logs = data.logs; + for (var i in logs) { + if (logs[i].log_data.trim() != "") { + buffer[nid].push("【" + logs[i].log_level + "】" + + "【" + logs[i].module + "】" + + logs[i].log_data); + } + } + _self.showFromBuffer(nid, buffer[nid].length); + timer[nid] = setTimeout(function() { _self.writeToBuffer(type, nid) }, interval); + } else { + ui.dialog.error("Log error: " + data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + ui.dialog.error(errorThrown, 2000); + } + }); + }, + /** + * 将buffer中的log显示出来 + */ + showFromBuffer: function(nid, size) { + clearInterval(clocker[nid]); + if (size > 0) { + var $logContainer = $logPanel.find("." + nid); + clocker[nid] = setInterval(function() { + if (buffer[nid].length > 0) { + $logContainer.append("
    "); + //最多保留500条log记录 + var len = $logContainer.children("label").length; + if (len > 500) { + $logContainer.children("label").slice(0, 1).remove(); + $logContainer.children("br").slice(0, 1).remove(); + } + //是否总是显示最新的log + if ($showLatestLogs.prop("checked")) { + $logContainer.scrollTop($logContainer[0].scrollHeight); + } + } + }, interval / size); + } + } + }; + return log; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/chain/detail/scrollspy.js b/user-dashboard/public/js/app/dashboard/chain/detail/scrollspy.js new file mode 100644 index 000000000..8914e24fc --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/chain/detail/scrollspy.js @@ -0,0 +1,84 @@ +define(["jquery"], function($) { + var $topology; + var $log; + var $blocks; + var $apis; + function scrollspy() { + $topology = $(".ico3"); + $log = $(".ico4"); + $blocks = $(".ico5"); + $apis = $(".ico6"); + + this.highlightTopo = function(highlight) { + if (highlight) { + $topology.css({ + color: "#00aaff", + background: "url(/images/icon-topo-highlight.png) no-repeat center 0" + }); + } else { + $topology.css({ + color: "#07d", + background: "url(/images/icon-topo.png) no-repeat center 0" + }); + } + } + this.highlightLog = function(highlight) { + if (highlight) { + $log.css({ + color: "#00aaff", + background: "url(/images/icon-log-highlight.png) no-repeat center 0" + }); + } else { + $log.css({ + color: "#07d", + background: "url(/images/icon-log.png) no-repeat center 0" + }); + } + } + this.highlightBlocks = function(highlight) { + if (highlight) { + $blocks.css({ + color: "#00aaff", + background: "url(/images/icon-bc-highlight.png) no-repeat center 0" + }); + } else { + $blocks.css({ + color: "#07d", + background: "url(/images/icon-bc.png) no-repeat center 0" + }); + } + } + this.highlightAPIs = function(highlight) { + if (highlight) { + $apis.css({ + color: "#00aaff", + background: "url(/images/icon-apis-highlight.png) no-repeat center 0" + }); + } else { + $apis.css({ + color: "#07d", + background: "url(/images/icon-apis.png) no-repeat center 0" + }); + } + } + } + scrollspy.prototype = { + select: function(item, show) { + switch (item) { + case "topology": + show ? this.highlightTopo(true) : this.highlightTopo(false); + break; + case "log": + show ? this.highlightLog(true) : this.highlightLog(false); + break; + case "blocks": + show ? this.highlightBlocks(true) : this.highlightBlocks(false); + break; + case "apis": + show ? this.highlightAPIs(true): this.highlightAPIs(false); + break; + } + } + }; + return scrollspy; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/chain/detail/topology.js b/user-dashboard/public/js/app/dashboard/chain/detail/topology.js new file mode 100644 index 000000000..160c134c8 --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/chain/detail/topology.js @@ -0,0 +1,312 @@ +/** + * Created by lixuc on 2017/5/10. + */ +define([ + "jquery", + "ol", + "app/kit/ui", + "app/kit/common", + "app/dashboard/chain/detail/scrollspy", + "plugin/text!resources/dashboard/chain/detail/overlay.html", + "plugin/text!resources/dashboard/chain/detail/largeContainer.html" +], function($, ol, UI, Common, Scrollspy, overlay, largeContainer) { + var ui = new UI(); + var common = new Common(); + var scrollspy = new Scrollspy(); + var $largeContainer; + var $expandCompressBtn; + var $map; + var $showLatency; + var map = null; + var interval = 5000; + var timer; + + function createLinkStyle(property, zoom, showLatency) { + var strokeColor = "#639E13"; + var strokeWidth = 1; + if (property.latency != "" && !isNaN(property.latency) && property.latency >= 0.1) { + strokeColor = "#e28327"; + strokeWidth = 2; + } + var lineDash = null; + if (property.type == "nvv") { + lineDash = [10]; + } + var strokeStyle = new ol.style.Stroke({ + color: strokeColor, + lineDash: lineDash, + width: strokeWidth + }); + if (showLatency) { + var label = property.latency; + if (property.latency != "" && !isNaN(property.latency)) { + label += " ms"; + } + var font = (zoom == 18 ? "30px" : (zoom == 17 ? "18px" : "9px")) + " Calibri,sans-serif"; + var textStyle = new ol.style.Text({ + text: label, + font: font, + fill: new ol.style.Fill({ + color: "#847574" + }), + stroke: new ol.style.Stroke({ + color: "#fff", + width: 3 + }) + }); + return [new ol.style.Style({ + stroke: strokeStyle, + text: textStyle + })]; + } else { + return [new ol.style.Style({ + stroke: strokeStyle + })]; + } + } + function createNodeStyle(property, zoom) { + var nodeRadius = 30; + var nodeFont = "25px"; + if (property.type == "v") { + nodeRadius = (zoom == 18 ? 40: (zoom == 17 ? 30 : 15)); + nodeFont = (zoom == 18 ? "35px" : (zoom == 17 ? "25px" : "12px")); + } else { + nodeRadius = (zoom == 18 ? 30 : (zoom == 17 ? 20 : 10)); + nodeFont = (zoom == 18 ? "25px" : (zoom == 17 ? "15px" : "8px")); + } + nodeFont += " Calibri,sans-serif"; + return [new ol.style.Style({ + image: new ol.style.Circle({ + radius: nodeRadius, + fill: new ol.style.Fill({ + color: "#F2FAE3" + }), + stroke: new ol.style.Stroke({ + color: "#659F13", + width: 1 + }) + }), + text: new ol.style.Text({ + text: property.id, + font: nodeFont, + fill: new ol.style.Fill({ + color: "#847574" + }), + stroke: new ol.style.Stroke({ + color: "#fff", + width: 3 + }) + }) + })]; + } + function topology(chainId) { + this.chainId = chainId; + + var _self = this; + $expandCompressBtn = $("#topo .expand-compress-icon"); + $map = $("#map"); + $showLatency = $("#showLatency"); + $showLatency.prop("checked", true); + + $expandCompressBtn.on("click", function() { + $largeContainer = $(largeContainer); + $largeContainer.find(">div").append($map.detach().css("height", "450px")); + $largeContainer.on({ + "show.uk.modal": function() { + //Set max zoom level to 18 + _self.load(18); + }, + "hide.uk.modal": function() { + $("#topo").after($map.detach().css("height", "300px")); + $(this).remove(); + _self.load(); + } + }); + $("body").append($largeContainer); + var $container = UIkit.modal($largeContainer); + $container.options.center = true; + $container.show(); + }); + this.getLink = function(from, to) { + if (map != null) { + var layers = map.getLayers().getArray(); + var vectorSource = layers[0].getSource(); + var features = vectorSource.getFeatures(); + for (var i in features) { + var property = features[i].getProperties(); + if ((property.from == from && property.to == to) || + (property.from == to && property.to == from)) { + return features[i]; + } + } + } + } + $map.on("inview.uk.scrollspy", function() { + if ($showLatency.prop("checked")) { + _self.refreshLinkLatency(); + } + scrollspy.select("topology", true); + }); + $map.on("outview.uk.scrollspy", function() { + if ($showLatency.prop("checked")) { + _self.stopRefresh(); + } + scrollspy.select("topology", false); + }); + $showLatency.on("change", function() { + if (map != null) { + var layers = map.getLayers().getArray(); + layers[0].getSource().changed(); + if ($(this).prop("checked")) { + _self.refreshLinkLatency(); + } else { + _self.stopRefresh(); + } + } + }); + } + topology.prototype = { + load: function(maxZoom) { + if (map != null) { + map.setTarget(null); + map = null; + } + if (!maxZoom) maxZoom = 17; + var $overlay = $(overlay); + $map.append($overlay); + var _self = this; + $.ajax({ + type: "GET", + url: "/api/chain/" + this.chainId + "/topology", + dataType: "json", + success: function(data) { + if (data.success) { + map = new ol.Map({ + target: "map", + layers: [ + new ol.layer.Vector({ + source: new ol.source.Vector({ + features: (new ol.format.GeoJSON()).readFeatures(data.links) + }), + style: function(feature, resolution) { + var property = feature.getProperties(); + var zoom = map.getView().getZoom(); + return createLinkStyle(property, zoom, $showLatency.prop("checked")); + } + }), + new ol.layer.Vector({ + source: new ol.source.Vector({ + features: (new ol.format.GeoJSON()).readFeatures(data.nodes) + }), + style: function(feature, resolution) { + var property = feature.getProperties(); + var zoom = map.getView().getZoom(); + return createNodeStyle(property, zoom); + } + }) + ], + controls: [], + interactions: ol.interaction.defaults({ + altShiftDragRotate: false, + dragPan: false, + keyboard: false, + shiftDragZoom: false, + pinchRotate: false, + pinchZoom: false + }).extend([new ol.interaction.DragPan()]), + view: new ol.View({ + center: [0, 0], + zoom: 17, + maxZoom: maxZoom, + minZoom: 16 + }) + }); + var drag = false; + var lastFea = null; + /** + * If map drag out view port, pan to view port again + */ + map.on("pointerdrag", function(evt) { + drag = true; + common.pan2Viewport(map); + }); + map.on("moveend", function(evt) { + drag = false; + common.pan2Viewport(map); + }); + /** + * Show detail info when mouse move on the topology + */ + map.on("pointermove", function(evt) { + if (!drag) { + var pixel = map.getEventPixel(evt.originalEvent); + var feature = map.forEachFeatureAtPixel(pixel, + function(feature, layer) { + return feature; + } + ); + if (feature) { + if (feature != lastFea) { + var geometry = feature.getGeometry(); + var property = feature.getProperties(); + if (geometry.getType() == "Point") { + $map.css("cursor", "pointer"); + common.showNodeDetail(map, geometry, property); + } else { + $map.css("cursor", ""); + common.hideNodeDetail(map); + } + lastFea = feature; + } + } else { + $map.css("cursor", ""); + common.hideNodeDetail(map); + lastFea = null; + } + } + }); + } else { + ui.dialog.error(data.message); + } + $overlay.remove(); + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + $overlay.remove(); + ui.dialog.error(errorThrown); + } + }); + }, + refreshLinkLatency: function() { + var _self = this; + $.ajax({ + type: "GET", + url: "/api/chain/" + this.chainId + "/topology/latency", + dataType: "json", + success: function(data) { + if (data.success) { + var links = data.links; + for (var i in links) { + var latency = links[i].latency; + if (latency != "" && !isNaN(latency)) { + var link = _self.getLink(links[i].from, links[i].to); + if (link) { + link.setProperties({ latency: latency }); + } + } + } + } + clearTimeout(timer); + timer = setTimeout(function() { _self.refreshLinkLatency(); }, interval); + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + ui.dialog.error(errorThrown); + clearTimeout(timer); + } + }); + }, + stopRefresh: function() { + clearTimeout(timer); + } + }; + return topology; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/chaincode/deploy.js b/user-dashboard/public/js/app/dashboard/chaincode/deploy.js new file mode 100644 index 000000000..47b046d3e --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/chaincode/deploy.js @@ -0,0 +1,209 @@ +/** + * Created by lixuc on 2017/5/10. + */ +define([ + "jquery", + "cookie", + "app/kit/ui", + "app/kit/common", + "app/kit/storage", + "plugin/text!resources/dashboard/chaincode/deploydialog.html", + "lodash" +], function($, Cookies, UI, Common, Storage, deploydialog) { + var ui = new UI(); + var common = new Common(); + var storage = new Storage(); + var $deploydialog; + var $deployChaincodeDialog; + var $deployContract; + var $instanceName; + var $deployFunction; + var $functionListDropdown; + var $functionList; + var $deployArgument; + var $deploySubmit; + var $deployCancel; + + function deploychaincode(list) { + function disableAllElement(disabled) { + if (disabled) { + $deployChaincodeDialog.options.bgclose = false; + } else { + $deployChaincodeDialog.options.bgclose = true; + } + $deployContract.attr("disabled", disabled); + $instanceName.attr("disabled", disabled); + $deployFunction.attr("disabled", disabled); + $deployArgument.attr("disabled", disabled); + $deploySubmit.attr("disabled", disabled); + $deployCancel.attr("disabled", disabled); + } + function injectFunction(contractId) { + //从本地存储中获取到函数名和参数 + var deploy = storage.get("deploy", contractId); + $functionList.empty(); + for (var i in deploy) { + $("
  • " + + "" + deploy[i].func + "" + + "
  • ").click(function() { + $deployFunction.val($(this).find("a").text()); + $deployArgument.val(JSON.stringify($(this).data("args"))); + }).appendTo($functionList); + } + } + function loadContract() { + var userInfo = Cookies.getJSON("BlockChainAccount"); + disableAllElement(true); + $.ajax({ + type: "GET", + url: "/api/" + userInfo.apikey + "/contract/list", + dataType: "json", + success: function(data) { + disableAllElement(false); + if (data.success) { + $deployContract.empty(); + if (data["public"]) { + for (var i in data["public"]) { + var contract = data["public"][i]; + $deployContract.append( + ""); + //将默认函数名和参数存储到本地 + storage.save("deploy", contract.id, contract.deploy); + } + } + if (data["private"]) { + for (var i in data["private"]) { + var contract = data["private"][i]; + $deployContract.append( + ""); + //将默认函数名和参数存储到本地 + storage.save("deploy", contract.id, contract.deploy); + } + } + injectFunction($deployContract.val()); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + disableAllElement(false); + ui.dialog.error(errorThrown); + } + }); + } + this.init = function() { + $deployContract = $("#deploy_chaincode_contract"); + $instanceName = $("#deploy_chaincode_instance_name"); + $deployFunction = $("#deploy_chaincode_function"); + $functionListDropdown = $("#chaincode_function_dropdown"); + $functionList = $("#chaincode_function_dropdown ul"); + $deployArgument = $("#deploy_chaincode_argument"); + $deploySubmit = $("#deploy_chaincode_submit"); + $deployCancel = $("#deploy_chaincode_cancel"); + + $deployContract.on("change", function() { + injectFunction($(this).val()); + $deployFunction.val(""); + $deployArgument.val(""); + }); + $deployFunction.on("focus blur", function(evt) { + if ($functionList.children().length > 0) { + if (evt.type == "focus") { + $functionListDropdown.show(); + } else if (evt.type == "blur") { + setTimeout(function() { + $functionListDropdown.hide(); + }, 200); + } + } + }); + $deploySubmit.on("click", function() { + if (!$deployContract.val()) { + ui.dialog.warning("Please select a smart contract."); + $deployContract.focus(); + return; + } else if ($instanceName.val() == "") { + ui.dialog.warning("Please input the instance name."); + $instanceName.focus(); + return; + } else if ($deployFunction.val() == "") { + ui.dialog.warning("Please input the function name."); + $deployFunction.focus(); + return; + } else if ($deployArgument.val() == "") { + ui.dialog.warning("Please input the function arguments."); + $deployArgument.focus(); + return; + } + var deployArgs = common.parseJSON($deployArgument.val()); + if (!deployArgs) { + ui.dialog.warning("Please input valid function arguments."); + $deployArgument.focus(); + return; + } + var $spinner = $(""); + disableAllElement(true); + $(this).append($spinner); + $.ajax({ + type: "POST", + url: "/api/contract/" + $deployContract.val() + "/deploy", + data: { + chain: list.chainId, + name: $instanceName.val(), + func: $deployFunction.val(), + args: $deployArgument.val() + }, + dataType: "json", + success: function(data) { + $spinner.remove(); + disableAllElement(false); + if (data.success) { + //本地存储用户输入的函数名和参数 + storage.set("deploy", $deployContract.val(), { + func: $deployFunction.val(), + args: deployArgs + }); + $deployChaincodeDialog.hide(); + ui.dialog.success("Deploy successfully.", 3000); + list.search(1); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + $spinner.remove(); + disableAllElement(false); + ui.dialog.error(errorThrown); + } + }); + }); + $deployCancel.on("click", function() { + $deployChaincodeDialog.hide(); + }); + loadContract(); + } + } + deploychaincode.prototype = { + show: function() { + var _self = this; + $deploydialog = $(deploydialog); + $deploydialog.on({ + "show.uk.modal": function() { + _self.init(); + }, + "hide.uk.modal": function() { + $(this).remove(); + } + }); + $("body").append($deploydialog); + $deployChaincodeDialog = UIkit.modal($deploydialog); + $deployChaincodeDialog.options.center = true; + $deployChaincodeDialog.show(); + } + }; + return deploychaincode; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/chaincode/invoke.js b/user-dashboard/public/js/app/dashboard/chaincode/invoke.js new file mode 100644 index 000000000..5da2c4e6b --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/chaincode/invoke.js @@ -0,0 +1,201 @@ +/** + * Created by lixuc on 2017/5/10. + */ +define([ + "jquery", + "app/kit/ui", + "app/kit/common", + "app/kit/storage", + "plugin/text!resources/dashboard/chaincode/invokedialog.html", + "lodash" +], function($, UI, Common, Storage, invokedialog) { + var ui = new UI(); + var common = new Common(); + var storage = new Storage(); + var $invokedialog; + var $invokeChaincodeDialog; + var $invokeInstanceView; + var $invokeInstance; + var $invokeFunction; + var $functionListDropdown; + var $functionList; + var $invokeArgument; + var $invokeSubmit; + var $invokeCancel; + + function invokechaincode(list) { + function disableAllElement(disabled) { + if (disabled) { + $invokeChaincodeDialog.options.bgclose = false; + } else { + $invokeChaincodeDialog.options.bgclose = true; + } + $invokeInstance.attr("disabled", disabled); + $invokeFunction.attr("disabled", disabled); + $invokeArgument.attr("disabled", disabled); + $invokeSubmit.attr("disabled", disabled); + $invokeCancel.attr("disabled", disabled); + } + function injectFunction(id) { + //从本地存储中获取到函数名和参数 + var invoke = storage.get("invoke", id); + $functionList.empty(); + for (var i in invoke) { + $("
  • " + + "" + invoke[i].func + "" + + "
  • ").click(function() { + $invokeFunction.val($(this).find("a").text()); + $invokeArgument.val(JSON.stringify($(this).data("args"))); + }).appendTo($functionList); + } + } + function loadInstance() { + disableAllElement(true); + $.ajax({ + type: "GET", + url: "/api/chain/" + list.chainId + "/chaincode/list", + data: { + page: -1 + }, + dataType: "json", + success: function(data) { + disableAllElement(false); + if (data.success) { + $invokeInstance.empty(); + for (var i in data.chaincodes) { + var chaincode = data.chaincodes[i]; + $invokeInstance.append( + ""); + //将默认函数名和参数存储到本地 + storage.save("invoke", list.chainId + "_" + chaincode.id, chaincode.contract.invoke); + } + injectFunction(list.chainId + "_" + $invokeInstance.val()); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + disableAllElement(false); + ui.dialog.error(errorThrown); + } + }); + } + this.init = function(id) { + $invokeInstanceView = $("#invoke_chaincode_instance_view"); + $invokeInstance = $("#invoke_chaincode_instance"); + $invokeFunction = $("#invoke_chaincode_function"); + $functionListDropdown = $("#invoke_function_dropdown"); + $functionList = $("#invoke_function_dropdown ul"); + $invokeArgument = $("#invoke_chaincode_argument"); + $invokeSubmit = $("#invoke_chaincode_submit"); + $invokeCancel = $("#invoke_chaincode_cancel"); + + if (id) { + $invokeInstanceView.hide(); + injectFunction(list.chainId + "_" + id); + } else { + loadInstance(); + $invokeInstance.on("change", function() { + injectFunction(list.chainId + "_" + $(this).val()); + $invokeFunction.val(""); + $invokeArgument.val(""); + }); + } + $invokeFunction.on("focus blur", function(evt) { + if ($functionList.children().length > 0) { + if (evt.type == "focus") { + $functionListDropdown.show(); + } else if (evt.type == "blur") { + setTimeout(function() { + $functionListDropdown.hide(); + }, 200); + } + } + }); + $invokeSubmit.on("click", function() { + if ($invokeInstanceView.css("display") != "none" && !$invokeInstance.val()) { + ui.dialog.warning("Please select a smart contract instance."); + $invokeInstance.focus(); + return; + } else if ($invokeFunction.val() == "") { + ui.dialog.warning("Please input the function name."); + $invokeFunction.focus(); + return; + } else if ($invokeArgument.val() == "") { + ui.dialog.warning("Please input the function arguments."); + $invokeArgument.focus(); + return; + } + var invokeArgs = common.parseJSON($invokeArgument.val()); + if (!invokeArgs) { + ui.dialog.warning("Please input valid function arguments."); + $invokeArgument.focus(); + return; + } + var $spinner = $(""); + disableAllElement(true); + $(this).append($spinner); + var chaincodeId; + if ($invokeInstanceView.css("display") != "none") { + chaincodeId = $invokeInstance.val(); + } else { + chaincodeId = id; + } + $.ajax({ + type: "POST", + url: "/api/chain/" + list.chainId + "/chaincode/invoke", + data: { + id: chaincodeId, + func: $invokeFunction.val(), + args: $invokeArgument.val() + }, + dataType: "json", + success: function(data) { + $spinner.remove(); + disableAllElement(false); + if (data.success) { + //本地存储用户输入的函数名和参数 + storage.set("invoke", list.chainId + "_" + chaincodeId, { + func: $invokeFunction.val(), + args: invokeArgs + }); + $invokeChaincodeDialog.hide(); + ui.dialog.success("Invoke successfully.", 3000); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + $spinner.remove(); + disableAllElement(false); + ui.dialog.error(errorThrown); + } + }); + }); + $invokeCancel.on("click", function() { + $invokeChaincodeDialog.hide(); + }); + } + } + invokechaincode.prototype = { + show: function(id) { + var _self = this; + $invokedialog = $(invokedialog); + $invokedialog.on({ + "show.uk.modal": function() { + _self.init(id); + }, + "hide.uk.modal": function() { + $(this).remove(); + } + }); + $("body").append($invokedialog); + $invokeChaincodeDialog = UIkit.modal($invokedialog); + $invokeChaincodeDialog.options.center = true; + $invokeChaincodeDialog.show(); + } + }; + return invokechaincode; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/chaincode/list.js b/user-dashboard/public/js/app/dashboard/chaincode/list.js new file mode 100644 index 000000000..9f2a663ac --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/chaincode/list.js @@ -0,0 +1,122 @@ +/** + * Created by lixuc on 2017/5/10. + */ +define([ + "jquery", + "app/kit/overlayer", + "app/kit/ui", + "app/kit/storage", + "app/dashboard/chaincode/deploy", + "app/dashboard/chaincode/invoke", + "app/dashboard/chaincode/query", + "plugin/text!resources/dashboard/chaincode/chaincode.html", + "lodash" +], function($, Overlayer, UI, Storage, DeployChaincode, InvokeChaincode, QueryChaincode, chaincode) { + var ui = new UI(); + var storage = new Storage(); + var $chaincodeContainer = $("#chaincodePanel tbody"); + var $chaincodeListPagination = $("#chaincodeListPagination"); + var $cache = $("#cache"); + var $deployBtn = $(".ico0"); + var $invokeBtn = $(".ico1"); + var $queryBtn = $(".ico2"); + + function chaincodelist(chainId) { + this.chainId = chainId; + + var deployChaincode = new DeployChaincode(this); + var invokeChaincode = new InvokeChaincode(this); + var queryChaincode = new QueryChaincode(this); + + function handler() { + $(this).parents(".uk-dropdown").hide(); + if ($(this).data("type") == "invoke") { + var chaincodeId = $(this).data("chaincodeId"); + invokeChaincode.show(chaincodeId); + } else if ($(this).data("type") == "query") { + var chaincodeId = $(this).data("chaincodeId"); + queryChaincode.show(chaincodeId); + } + } + $chaincodeContainer.on("click", "li>a", handler); + $chaincodeListPagination.on("select.uk.pagination", function(event, pageIndex) { + this.search(pageIndex + 1); + }.bind(this)); + $deployBtn.on("click", function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + deployChaincode.show(); + UIkit.Utils.scrollToElement(UIkit.$($("#sc-instance")[0])); + }); + $invokeBtn.on("click", function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + invokeChaincode.show(); + UIkit.Utils.scrollToElement(UIkit.$($("#sc-instance")[0])); + }); + $queryBtn.on("click", function(evt) { + evt.preventDefault(); + evt.stopPropagation(); + queryChaincode.show(); + UIkit.Utils.scrollToElement(UIkit.$($("#sc-instance")[0])); + }); + //将缓存中的默认函数名和参数存储到本地,清空缓存 + var cache = JSON.parse($cache.val()); + for(var i in cache) { + storage.save("invoke", cache[i].id, cache[i].invoke); + storage.save("query", cache[i].id, cache[i].query); + } + $cache.remove(); + } + chaincodelist.prototype = { + search: function(pageNo) { + var _self = this; + var overlayer = new Overlayer(); + overlayer.show(); + $.ajax({ + type: "GET", + url: "/api/chain/" + this.chainId + "/chaincode/list", + data: { + page: pageNo + }, + dataType: "json", + success: function(data) { + overlayer.hide(); + if (data.success) { + $chaincodeContainer.empty(); + if (data.chaincodes.length == 0) { + $chaincodeContainer.append("No result."); + $chaincodeListPagination.hide(); + } else { + var chaincodeInfo = _.template(chaincode); + for (var i in data.chaincodes) { + var _chaincode = data.chaincodes[i]; + $chaincodeContainer.append(chaincodeInfo({ + id: _chaincode.id, + name: _chaincode.name, + contract: _chaincode.contract.name, + deployTime: _chaincode.deployTime + })); + //将默认函数名和参数存储到本地 + storage.save("invoke", _self.chainId + "_" + _chaincode.id, _chaincode.contract.invoke); + storage.save("query", _self.chainId + "_" + _chaincode.id, _chaincode.contract.query); + } + $chaincodeListPagination.show(); + if (pageNo > data.totalPage) pageNo = data.totalPage; + var $pagination = UIkit.pagination("#chaincodeListPagination"); + $pagination.currentPage = pageNo - 1; + $pagination.render(data.totalPage); + } + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + overlayer.hide(); + ui.dialog.error(errorThrown); + } + }); + } + }; + return chaincodelist; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/app/dashboard/chaincode/query.js b/user-dashboard/public/js/app/dashboard/chaincode/query.js new file mode 100644 index 000000000..548134c5d --- /dev/null +++ b/user-dashboard/public/js/app/dashboard/chaincode/query.js @@ -0,0 +1,212 @@ +/** + * Created by lixuc on 2017/5/10. + */ +define([ + "jquery", + "app/kit/ui", + "app/kit/common", + "app/kit/storage", + "plugin/text!resources/dashboard/chaincode/querydialog.html", + "lodash" +], function($, UI, Common, Storage, querydialog) { + var ui = new UI(); + var common = new Common(); + var storage = new Storage(); + var $querydialog; + var $queryChaincodeDialog; + var $queryInstanceView; + var $queryInstance; + var $queryFunction; + var $functionListDropdown; + var $functionList; + var $queryArgument; + var $querySubmit; + var $queryCancel; + + function querychaincode(list) { + function disableAllElement(disabled) { + if (disabled) { + $queryChaincodeDialog.options.bgclose = false; + } else { + $queryChaincodeDialog.options.bgclose = true; + } + $queryInstance.attr("disabled", disabled); + $queryFunction.attr("disabled", disabled); + $queryArgument.attr("disabled", disabled); + $querySubmit.attr("disabled", disabled); + $queryCancel.attr("disabled", disabled); + } + function injectFunction(id) { + //从本地存储中获取到函数名和参数 + var query = storage.get("query", id); + $functionList.empty(); + for (var i in query) { + $("
  • " + + "" + query[i].func + "" + + "
  • ").click(function() { + $queryFunction.val($(this).find("a").text()); + $queryArgument.val(JSON.stringify($(this).data("args"))); + }).appendTo($functionList); + } + } + function loadInstance() { + disableAllElement(true); + $.ajax({ + type: "GET", + url: "/api/chain/" + list.chainId + "/chaincode/list", + data: { + page: -1 + }, + dataType: "json", + success: function(data) { + disableAllElement(false); + if (data.success) { + $queryInstance.empty(); + for (var i in data.chaincodes) { + var chaincode = data.chaincodes[i]; + $queryInstance.append( + ""); + //将默认函数名和参数存储到本地 + storage.save("query", list.chainId + "_" + chaincode.id, chaincode.contract.query); + } + injectFunction(list.chainId + "_" + $queryInstance.val()); + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + disableAllElement(false); + ui.dialog.error(errorThrown); + } + }); + } + this.init = function(id) { + $queryInstanceView = $("#query_chaincode_instance_view"); + $queryInstance = $("#query_chaincode_instance"); + $queryFunction = $("#query_chaincode_function"); + $functionListDropdown = $("#query_function_dropdown"); + $functionList = $("#query_function_dropdown ul"); + $queryArgument = $("#query_chaincode_argument"); + $querySubmit = $("#query_chaincode_submit"); + $queryCancel = $("#query_chaincode_cancel"); + + if (id) { + $queryInstanceView.hide(); + injectFunction(list.chainId + "_" + id); + } else { + loadInstance(); + $queryInstance.on("change", function() { + injectFunction(list.chainId + "_" + $(this).val()); + $queryFunction.val(""); + $queryArgument.val(""); + }); + } + $queryFunction.on("focus blur", function(evt) { + if ($functionList.children().length > 0) { + if (evt.type == "focus") { + $functionListDropdown.show(); + } else if (evt.type == "blur") { + setTimeout(function() { + $functionListDropdown.hide(); + }, 200); + } + } + }); + $querySubmit.on("click", function() { + if ($queryInstanceView.css("display") != "none" && !$queryInstance.val()) { + ui.dialog.warning("Please select a smart contract instance."); + $queryInstance.focus(); + return; + } else if ($queryFunction.val() == "") { + ui.dialog.warning("Please input the function name."); + $queryFunction.focus(); + return; + } else if ($queryArgument.val() == "") { + ui.dialog.warning("Please input the function arguments."); + $queryArgument.focus(); + return; + } + var queryArgs = common.parseJSON($queryArgument.val()); + if (!queryArgs) { + ui.dialog.warning("Please input valid function arguments."); + $queryArgument.focus(); + return; + } + var $spinner = $(""); + disableAllElement(true); + $(this).append($spinner); + var chaincodeId; + if ($queryInstanceView.css("display") != "none") { + chaincodeId = $queryInstance.val(); + } else { + chaincodeId = id; + } + $.ajax({ + type: "POST", + url: "/api/chain/" + list.chainId + "/chaincode/query", + data: { + id: chaincodeId, + func: $queryFunction.val(), + args: $queryArgument.val() + }, + dataType: "json", + success: function(data) { + $spinner.remove(); + disableAllElement(false); + if (data.success) { + //本地存储用户输入的函数名和参数 + storage.set("query", list.chainId + "_" + chaincodeId, { + func: $queryFunction.val(), + args: queryArgs + }); + var response = data.result; + $queryChaincodeDialog.hide(); + try { + ui.dialog.info("Response: " + + "
    " +
    +                                        JSON.stringify(JSON.parse(response.message), null, 4) +
    +                                    "
    ", 5000); + } catch (err) { + ui.dialog.info("Response: " + + "
    " +
    +                                        response.message +
    +                                    "
    ", 5000); + } + } else { + ui.dialog.error(data.message); + } + }, + error: function(XMLHttpRequest, textStatus, errorThrown) { + $spinner.remove(); + disableAllElement(false); + ui.dialog.error(errorThrown); + } + }); + }); + $queryCancel.on("click", function() { + $queryChaincodeDialog.hide(); + }); + } + } + querychaincode.prototype = { + show: function(id) { + var _self = this; + $querydialog = $(querydialog); + $querydialog.on({ + "show.uk.modal": function() { + _self.init(id); + }, + "hide.uk.modal": function() { + $(this).remove(); + } + }); + $("body").append($querydialog); + $queryChaincodeDialog = UIkit.modal($querydialog); + $queryChaincodeDialog.options.center = true; + $queryChaincodeDialog.show(); + } + }; + return querychaincode; +}); \ No newline at end of file diff --git a/user-dashboard/public/js/entry/chain/detail.js b/user-dashboard/public/js/entry/chain/detail.js new file mode 100644 index 000000000..3180b8db0 --- /dev/null +++ b/user-dashboard/public/js/entry/chain/detail.js @@ -0,0 +1,6 @@ +/** + * Created by lixuc on 2017/5/10. + */ +requirejs(["../../common"], function(common) { + requirejs(["app/dashboard/chain/detail"]); +}); \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chain/detail/block.html b/user-dashboard/public/js/resources/dashboard/chain/detail/block.html new file mode 100644 index 000000000..b4c5dd3a8 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chain/detail/block.html @@ -0,0 +1,3 @@ +
    + +
    \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chain/detail/blockdialog.html b/user-dashboard/public/js/resources/dashboard/chain/detail/blockdialog.html new file mode 100644 index 000000000..4dd948f4f --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chain/detail/blockdialog.html @@ -0,0 +1,7 @@ +
    +
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chain/detail/blocktitle.html b/user-dashboard/public/js/resources/dashboard/chain/detail/blocktitle.html new file mode 100644 index 000000000..7a0e27439 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chain/detail/blocktitle.html @@ -0,0 +1,9 @@ +Block [${id}] +<% if (existTransaction) { %> +
    +
    ${timestamp}
    + +
    +<% } else { %> +
    ${timestamp}
    +<% } %> \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chain/detail/largeContainer.html b/user-dashboard/public/js/resources/dashboard/chain/detail/largeContainer.html new file mode 100644 index 000000000..a0671fac9 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chain/detail/largeContainer.html @@ -0,0 +1,4 @@ +
    +
    +
    +
    \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chain/detail/overlay.html b/user-dashboard/public/js/resources/dashboard/chain/detail/overlay.html new file mode 100644 index 000000000..07a73c440 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chain/detail/overlay.html @@ -0,0 +1,5 @@ +
    +
    + +
    +
    \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chain/detail/transaction.html b/user-dashboard/public/js/resources/dashboard/chain/detail/transaction.html new file mode 100644 index 000000000..1c4ea84ff --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chain/detail/transaction.html @@ -0,0 +1,22 @@ +
    +
    + +
    ${uuid}
    +
    +
    + +
    ${chaincodeId}
    +
    +
    + + +
    +
    + +
    ${payload}
    +
    +
    + + +
    +
    \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chaincode/chaincode.html b/user-dashboard/public/js/resources/dashboard/chaincode/chaincode.html new file mode 100644 index 000000000..467315b05 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chaincode/chaincode.html @@ -0,0 +1,24 @@ + + + +
    +
    +
    + +
    +
    + + ${contract} + ${deployTime} + \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chaincode/deploydialog.html b/user-dashboard/public/js/resources/dashboard/chaincode/deploydialog.html new file mode 100644 index 000000000..0dee1731a --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chaincode/deploydialog.html @@ -0,0 +1,44 @@ +
    +
    +
    +
    Deploy
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
      +
      +
      +
      +
      + +
      + +
      +
      +
      + + +
      +
      +
      +
      +
      \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chaincode/invokedialog.html b/user-dashboard/public/js/resources/dashboard/chaincode/invokedialog.html new file mode 100644 index 000000000..622d07511 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chaincode/invokedialog.html @@ -0,0 +1,37 @@ +
      +
      +
      +
      Invoke
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
        +
        +
        +
        +
        + +
        + +
        +
        +
        + + +
        +
        +
        +
        +
        \ No newline at end of file diff --git a/user-dashboard/public/js/resources/dashboard/chaincode/querydialog.html b/user-dashboard/public/js/resources/dashboard/chaincode/querydialog.html new file mode 100644 index 000000000..cae2678a7 --- /dev/null +++ b/user-dashboard/public/js/resources/dashboard/chaincode/querydialog.html @@ -0,0 +1,38 @@ +
        +
        +
        +
        Query
        +
        +
        + +
        + +
        +
        +
        + +
        + +
        +
          +
          +
          +
          +
          + +
          + +
          +
          +
          + + +
          +
          +
          +
          +
          \ No newline at end of file diff --git a/user-dashboard/routes/api.js b/user-dashboard/routes/api.js index 9f2b318ac..a2dc948e4 100644 --- a/user-dashboard/routes/api.js +++ b/user-dashboard/routes/api.js @@ -8,6 +8,7 @@ var config = require("../modules/configuration"); var User = require("../modules/user"); var Profile = require("../modules/profile"); var Chain = require("../modules/chain"); +var Chaincode = require("../modules/chaincode"); var mongoClient = require("../modules/mongoclient"); var Contract = require("../modules/contract"); @@ -123,6 +124,94 @@ router.get("/chain/:id/operate", function(req, res) { res.json(err); }); }); +router.get("/chain/:id/topology", function(req, res) { + var topology = {}; + var chain = new Chain(); + chain.topologyNodes(req.params.id).then(function(result) { + topology["nodes"] = result["geoData"]; + return chain.topologyLinks(req.params.id); + }).then(function(result) { + topology["links"] = result["geoData"]; + res.json(Object.assign(topology, { success: true })); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:id/topology/latency", function(req, res) { + var chain = new Chain(); + chain.topologyLatency(req.params.id).then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:id/log/nodes", function(req, res) { + var chain = new Chain(); + chain.logNodes(req.params.id).then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:id/log", function(req, res) { + var chain = new Chain(); + chain.log(req.params.id, + req.query.type, + req.query.node, + req.query.size, + req.query.time) + .then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:id/blocks", function(req, res) { + var chain = new Chain(); + chain.blocks(req.params.id).then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:chainId/block/:blockId", function(req, res) { + var chain = new Chain(); + chain.block(req.params.chainId, req.params.blockId).then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.get("/chain/:id/chaincode/list", function(req, res) { + var chaincode = new Chaincode(req.params.id); + chaincode.list(req.query.page).then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.post("/chain/:id/chaincode/invoke", function(req, res) { + var chaincode = new Chaincode(req.params.id); + chaincode.invoke(req.body.id, + req.body.func, + req.body.args) + .then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); +router.post("/chain/:id/chaincode/query", function(req, res) { + var chaincode = new Chaincode(req.params.id); + chaincode.query(req.body.id, + req.body.func, + req.body.args) + .then(function(result) { + res.json(result); + }).catch(function(err) { + res.json(err); + }); +}); router.post("/:apikey/contract/upload", function(req, res) { mongoClient.getGridFS().then(function(gfs) { return new Promise(function(resolve, reject) { diff --git a/user-dashboard/routes/dashboard/chain.js b/user-dashboard/routes/dashboard/chain.js index f282f9a3b..4ed29a4af 100644 --- a/user-dashboard/routes/dashboard/chain.js +++ b/user-dashboard/routes/dashboard/chain.js @@ -4,6 +4,7 @@ var express = require("express"); var config = require("../../modules/configuration"); var Chain = require("../../modules/chain"); +var Chaincode = require("../../modules/chaincode"); var router = express.Router(); @@ -21,4 +22,23 @@ router.get("/chain", function(req, res, next) { next(e); }); }); +router.get("/chain/:id", function(req, res, next) { + var renderer = {}; + var chaincode = new Chaincode(req.params.id); + chaincode.list().then(function(result) { + renderer["chain"] = Object.assign(result["chain"], { id: req.params.id }); + renderer["chaincodes"] = { + list: result["chaincodes"], + pages: result["totalPage"] + }; + return chaincode.getAPIs(); + }).then(function(result) { + renderer["apis"] = result["serviceUrl"]; + res.render("dashboard/chain/detail", renderer); + }).catch(function(err) { + var e = new Error(err.message); + e.status = 500; + next(e); + }); +}); module.exports = router; \ No newline at end of file diff --git a/user-dashboard/views/dashboard/chain/detail.pug b/user-dashboard/views/dashboard/chain/detail.pug new file mode 100644 index 000000000..8d5cf3f74 --- /dev/null +++ b/user-dashboard/views/dashboard/chain/detail.pug @@ -0,0 +1,129 @@ +extends ../../layout + +block css + link(rel="stylesheet", href="/css/addon/sticky.almost-flat.min.css") + link(rel="stylesheet", href="/css/ol.css") + link(rel="stylesheet", href="/css/dashboard/overlayer.css") + link(rel="stylesheet", href="/css/dashboard/navbar.css") + link(rel="stylesheet", href="/css/dashboard/chain-detail.css") +block js + script(data-main="/js/entry/chain/detail" src="/js/lib/require.js") +block content + include ../includes/navbar.pug + +active("chain") + section.ds-chain-main-container.uk-container.uk-container-center + h1 + a(href="/dashboard/chain") My Chains + |  /  + span= chain.name + label.uk-margin-left= chain.description + .uk-grid + .ds-chain-main.uk-width-3-4 + h2#topo + img.uk-margin-right(src="/images/nav-topo.png") + | Overview + i.uk-icon-expand.uk-float-right.expand-compress-icon + #map.uk-panel.uk-panel-box( + style="padding:5px 5px 20px 5px;height:300px;" data-uk-scrollspy="{repeat:true}") + label.latency-show + input#showLatency(type="checkbox" checked) + | Show latency + h2#log(style="margin-top:80px;") + img.uk-margin-right(src="/images/nav-log.png") + | Log + i.uk-icon-expand.uk-float-right.expand-compress-icon + #logPanel.uk-panel.uk-panel-box( + style="height:300px;padding:5px 0px 10px 5px;" + data-uk-scrollspy="{cls:'uk-animation-fade', repeat:true}") + ul.uk-grid.uk-grid-collapse.log-badge + label.log-show + input#showLatestLogs(type="checkbox" checked) + | Always show the latest logs + h2#bc(style="margin-top:80px;") + i.uk-icon-th-large.uk-margin-right + | Blocks + i.uk-icon-expand.uk-float-right.expand-compress-icon + #blockchain.uk-panel.uk-panel-box( + style="padding:5px;height:300px;" data-uk-scrollspy="{cls:'uk-animation-fade', repeat:true}") + h2#api(style="margin-top:80px;") + i.uk-icon-exchange.uk-margin-right + | APIs + i.uk-icon-expand.uk-float-right.expand-compress-icon + #apiPanel.uk-panel.uk-panel-box( + style="padding:5px;height:300px;" data-uk-scrollspy="{cls:'uk-animation-fade', repeat:true}") + table.uk-table.uk-table-hover + thead + tr + th Type + th Service URL + tbody + each url, type in apis + tr + td= type + td= url + else + tr + td.no-result(colspan="2") No result. + h2#sc-instance(style="margin-top:80px;") + i.uk-icon-th-list.uk-margin-right + | Smart Contract Instances + #chaincodePanel.uk-panel.uk-panel-box( + style="min-height:50px;" data-uk-scrollspy="{cls:'uk-animation-fade', repeat:true}") + - var cache = []; + table.uk-table.uk-table-hover + thead + tr + th(style="width:150px;") Name + th Smart Contract + th Deployment Time + tbody + each chaincode in chaincodes.list + - + cache.push({ + id: chain.id + "_" + chaincode.id, + invoke: chaincode.contract.invoke, + query: chaincode.contract.query + }); + tr + td + label= chaincode.name + .uk-button-dropdown.uk-float-right.uk-margin-right(data-uk-dropdown) + div + i.uk-icon-chevron-circle-down(style="color:#ff5003;") + .uk-dropdown.uk-dropdown-small + ul.uk-nav.uk-nav-dropdown + li + a(data-type="invoke" data-chaincode-id=`${chaincode.id}`) + img.img-dropdown(src="/images/icon-invoke-small.svg") + | Invoke + li + a(data-type="query" data-chaincode-id=`${chaincode.id}`) + i.uk-icon-search.uk-margin-right + | Query + td= chaincode.contract.name + td= chaincode.deployTime + else + tr + td.no-result(colspan="3") No result. + ul#chaincodeListPagination.uk-pagination.uk-pagination-right( + data-uk-pagination=`{pages:${chaincodes.pages}}`)&attributes( + chaincodes.list.length ? {} : {"style": "display:none;"}) + input#cache(type="hidden" value=cache) + div(style="height:150px;") + .uk-width-1-4(data-uk-sticky) + ul.listico.uk-padding-remove + li(style="width:200px;") + a.ico3(href="#topo" data-uk-smooth-scroll) Overview + li(style="width:200px;") + a.ico4(href="#log" data-uk-smooth-scroll) Log + li(style="width:200px;") + a.ico5(href="#bc" data-uk-smooth-scroll) Blocks + li(style="width:200px;") + a.ico6(href="#api" data-uk-smooth-scroll) APIs + li + a.ico0(href="#sc-instance" data-uk-smooth-scroll) Deploy + li + a.ico1(href="#sc-instance" data-uk-smooth-scroll) Invoke + li + a.ico2(href="#sc-instance" data-uk-smooth-scroll) Query + input#chainId(type="hidden" value=chain.id) \ No newline at end of file