工作環境有不少Web Farm主機,部署程式時需要將一或多個檔案同步複製到多台主機上,依上線流程需產生指令檔交給OP人員執行。這種需求用COPY、XCOPY或ROBOCOPY指令寫成批次檔是最直覺有效的做法,但以「將四個檔案複製到八台主機」為例,需寫成32條指令,沒營養又躲不掉的枯躁苦工,交給機器人才是王道。

輸入IP清單轉成一串指令的小工具,印象裡我寫過WinForm版也寫過WebForm版,最近有類似需求,卻不幸遇到記憶斷層,怎麼也想不起程式放哪… Orz 心一橫,索性拿它當成練功題材,花了點時間寫成純前端網頁版,順手分享出來。

操作介面如上圖,設好來源項目與目標IP按個鈕即展開成批次指令,亦提供複製到剪貼簿的功能。Script Template是產生指令的樣版字串,{0}代表來源清單的項目,{1}則是對象機器IP,另外有個{0:path}語法可以只取來源項目資料夾路徑,方便組裝COPY指令。主機清單通常是固定的,右上方我放了快捷鈕,可直接帶入預先定義的主機IP清單,IP資料由一個JavaScript物件提供,使用時請自行調整成實際在用的群組名稱與IP。

var groups = {
  "GRP1": "191.168.1.1\n192.168.1.2",
  "GRP2": "10.10.100.1\n10.10.100.2\n10.10.100.3\n10.10.100.4"
}   

程式靠Angular MVVM配合JavsScript輕鬆搞定,沒什麼技術含量,寫成網頁的好處是只需單一檔案,丟上網路人人可用,懂JavaScript就能修改,差不多是路邊奉茶的概念(笑)。

程式碼如下,同時我也放上JSBin了,有需要的朋友請自取。

<!DOCTYPE html>
<html ng-app="app">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>Copy Script Generator</title>
  <style>
    .deploy {
        width: 1024px;
    }
 
    legend {
        font-size: 9pt;
    }
 
    .list {
        width: 100%;
        margin: 6px 0;
    }
 
        .list td {
            padding-right: 6px;
        }
 
    textarea {
        max-width: 100%;
        width: 100%;
        height: 100px;
    }
 
    input {
        max-width: 100%;
    }
 
    .op {
        margin: 12px 0;
        background-color: #ccc;
        padding: 3px;
    }
 
    .result {
        height: 200px;
    }
 
    .show-hide {
        transition: all linear 0.5s;
    }
 
        .show-hide.ng-hide {
            opacity: 0;
        }
 
    .txt-button {
        color: blue;
        text-decoration: underline;
        cursor: pointer;
        margin-right: 3px;
    }    
  </style>
</head>
<body>
<div ng-controller="ctrl" class="deploy" ng-cloak afa-loading-indicator="vm.IsBusy">
    <h3>
        Copy Script Generator by darkthread
    </h3>
    <div>
        <table class="list">
            <tr>
                <td>Source Item</td>
                <td>
                    Source IPs
                    <span style="float: right">
                        IP Groups:
                        <span ng-repeat="(k,v) in vm.Groups" 
                          class="txt-button" ng-click="vm.SetDest(k)">{{k}}</span>
                    </span>
                </td>
            </tr>
            <tr>
                <td>
                    <textarea ng-model="vm.SrcList"></textarea>
                </td>
                <td>
                    <textarea ng-model="vm.DstList"></textarea>
                </td>
            </tr>
        </table>
    </div>
    <div class="op">
        <div>Script Template</div>
        <textarea ng-model="vm.ScriptTmpl" style="height: 50px"></textarea>
        <button ng-click="vm.GenScript()">Generate Script</button>
        <button ng-click="vm.CopyScript()">Copy to Clipboard</button>
        <span class="show-hide" ng-bind="vm.Msg" ng-show="vm.ShowMsg"></span>
    </div>
 
    <div>
        <fieldset>
            <legend>Scripts</legend>
            <textarea ng-model="vm.Script" class="result"></textarea>
        </fieldset>
    </div>
</div>  
  <script src="https://code.jquery.com/jquery-3.1.0.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js">
  </script>
  <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular-animate.min.js">
   </script>
  <script>
//REF: http://stackoverflow.com/a/30810322/4335757
function copyTextToClipboard(text) {
  var textArea = document.createElement("textarea");
  textArea.style.position = 'fixed';
  textArea.style.top = 0;
  textArea.style.left = 0;
  textArea.style.width = '2em';
  textArea.style.height = '2em';
  textArea.style.padding = 0;
  textArea.style.border = 'none';
  textArea.style.outline = 'none';
  textArea.style.boxShadow = 'none';
  textArea.style.background = 'transparent';
  textArea.value = text;
  document.body.appendChild(textArea);
  textArea.select();
  try {
    var successful = document.execCommand('copy');
    var msg = successful ? 'successful' : 'unsuccessful';
  }
  catch (err) {
    console.log('Oops, unable to copy');
  }
  document.body.removeChild(textArea);
}
 
var groups = {
  "GRP1": "191.168.1.1\n192.168.1.2",
  "GRP2": "10.10.100.1\n10.10.100.2\n10.10.100.3\n10.10.100.4"
}    
    
    
angular
    .module("app", ["ngAnimate"])
    .controller("ctrl", function($scope) {
        function myViewModel() {
            var self = this;
            self.IsBusy = false;
            self.SrcList = "scripts\\common.js\nscripts\\common.min.js\ncss\\style.css";
            self.DstList = "192.168.1.1\n192.168.1.2";
            self.ScriptTmpl = "copy d:\\WWW\\blah\\{0} \\\\{1}\\d$\\WWW\\blah\\{0:path} /Y";
            self.ShowMsg = false;
            self.GenScript = function() {
                var self = this;
                var srcAry = self.SrcList.split('\n');
                var dstAry = self.DstList.split('\n');
                var cmds = [];
                $.each(srcAry, function(i, src) {
                  var p = src.split('\\');
                  p.pop();
                  var srcPath = p.length ? p.join('\\') : src;
                  $.each(dstAry, function(j, dst) {
                        cmds.push(self.ScriptTmpl
                                  .replace(/[{]0[}]/g, src)
                                  .replace(/[{]0:path[}]/g, srcPath)
                                  .replace(/[{]1[}]/g, dst));
                    });
                });
                self.Script = cmds.join('\n') + "\n";
            };
            self.GenScript();
            self.CopyScript = function() {
                var self = this;
                copyTextToClipboard(self.Script);
                self.Msg = "Copied!";
                self.ShowMsg = true;
                setTimeout(function() {
                    self.Msg = "";
                    self.ShowMsg = false;
                    $scope.$digest();
                }, 2000);
            };
            self.Groups = groups;
            self.SetDest = function(grpName) {
                var self = this;
                self.DstList = groups[grpName];
            };
        }
        $scope.vm = new myViewModel();
    });    
  </script>
</body>
</html>

後記:小工具寫完沒幾天,NG2正式版就閃電問市惹… 所以,敬請期待「COPY指令產生器 in NG2」,Coming Soon~


Comments

# by Jenkins`

use jenkins maybe?

# by Jeffrey

to Jenkins`, 謝謝回饋,在一些專案我們有透過TFS Build Service實踐自動部署,但基於現行制度及環境限制仍需以這類方式執行。

Post a comment