From 7beebdddd960e5f1c14e9467e0ad0756f1967209 Mon Sep 17 00:00:00 2001
From: Alain Takoudjou <talino@tiolive.com>
Date: Tue, 26 Nov 2013 14:37:32 +0100
Subject: [PATCH] Update log management, add custom log file...

---
 slapos/runner/static/css/styles.css           |  19 +-
 slapos/runner/static/images/plus.png          | Bin 0 -> 266 bytes
 slapos/runner/static/js/scripts/process.js    |  90 ++++--
 .../static/js/scripts/softwareFolder.js       |   2 +-
 slapos/runner/static/js/scripts/viewlog.js    | 290 ++++++++++++++++--
 slapos/runner/templates/layout.html           |  12 +-
 slapos/runner/templates/viewLog.html          |  85 +++--
 slapos/runner/views.py                        |  54 ++--
 8 files changed, 444 insertions(+), 108 deletions(-)
 create mode 100644 slapos/runner/static/images/plus.png

diff --git a/slapos/runner/static/css/styles.css b/slapos/runner/static/css/styles.css
index 1228338..f570114 100644
--- a/slapos/runner/static/css/styles.css
+++ b/slapos/runner/static/css/styles.css
@@ -94,7 +94,7 @@ body {
   font-size: 1.08em;
   color: #2D73B1;
 }
-#running p.instance{
+#running p.software{
   border-left: 4px solid #1E73BD;
 }
 #running img{
@@ -472,12 +472,11 @@ padding: 10px;height: 80px;padding-bottom:15px;}
 }
 .log_info_box h2{
   font-weight: bold;
-  padding: 5px;
+  padding: 2px;
   padding-left: 20px;
   text-align: left;
   color: #fff;
   display: block;
-  height: 20px;
   font-size: 16px;
   margin-top: 20px;
 }
@@ -485,20 +484,26 @@ padding: 10px;height: 80px;padding-bottom:15px;}
   margin-top: 0;
 }
 
-.log_info_box h2.software{
+.log_info_box h2.software, h2.boxtitle{
   background: #1E73BD;
 }
 .log_info_box h2.instance{
   background: #C753C4;
 }
+.log_info_box ul li{padding: 5px; font-size: 14px;color: #777B7C; font-weight: bold; margin: 3px 0; cursor: pointer;border: 1px solid #fff}
+.log_info_box ul li:hover{background: #EBEBEB}
+.log_info_box ul li.checked{border: 1px solid #DADADA}
+.add_btn{background: url(../images/plus.png) center left no-repeat; display: block; margin: 2px; opacity: 0.6; min-width:22px; height: 22px;
+    text-indent: 30px; font-weight: bold; padding-top: 7px;}
+.add_btn:hover{opacity: 1;}
 
-#sr_run_state, #instance_run_state{
+#software_run_state, #instance_run_state{
   color: #000;
   font-weight: bold;
   margin-bottom: 10px;
 }
-#sr_run_state p, #instance_run_state p{
-  padding-top: 3px;
+#software_run_state p, #instance_run_state p{
+  padding-top: 2px;
   font-size: 16px;
 
 }
diff --git a/slapos/runner/static/images/plus.png b/slapos/runner/static/images/plus.png
new file mode 100644
index 0000000000000000000000000000000000000000..4660895e488907bc3dc74b07399c39ce60afd1bb
GIT binary patch
literal 266
zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fEa{HEjtmSN`?>!lvI6;>1s;*b
z3=Dh+L6~vJ#O${~L8%hgh?3y^w370~qEv=}#LT=BJwMkFg)(D3Q$0gN_s>q|Kviv?
zE{-7{-pLXI7w+HJzhGZ@sgYAyU6$?9Vuu>8j`NHk?N$hVI<UlZvWvRktUvPHb*!OI
zauTKzd~E9v{uNd?dvM@x1P|-O^$}MN8D5ECW7Cn+JQR@Z&bzGr;Eg1)E=_OI9S1U|
zwKWGe2QqB9S3Jugu}OBqHnvO8YHMrN9JVorNU<j*FfdGLOAxTjIa&jB5re0zpUXO@
GgeCy=Hdf03

literal 0
HcmV?d00001

diff --git a/slapos/runner/static/js/scripts/process.js b/slapos/runner/static/js/scripts/process.js
index 30c8cf2..3dafe38 100644
--- a/slapos/runner/static/js/scripts/process.js
+++ b/slapos/runner/static/js/scripts/process.js
@@ -10,10 +10,10 @@
 var url = $SCRIPT_ROOT + "/slapgridResult";
 var currentState = false;
 var running = true;
-var $current;
 var processType = "";
 var currentProcess;
 var sendStop = false;
+var forcedStop = false;
 var processState = "Checking"; //define slapgrid running state
 var openedlogpage = ""; //content software or instance if the current page is software or instance log, otherwise nothing
 var logReadingPosition = 0;
@@ -56,7 +56,7 @@ function getRunningState() {
         log_info = "",
         param = {
             position: logReadingPosition,
-            log: (processState !== "Checking" && openedlogpage === processType.toLowerCase()) ? openedlogpage : ""
+            log: (processState !== "Checking" && openedlogpage !== "") ? processType.toLowerCase() : ""
         },
         jqxhr = $.post(url, param, function (data) {
             setRunningState(data);
@@ -75,6 +75,8 @@ function getRunningState() {
             if (running && processState === "Checking" && openedlogpage !== "") {
                 $("#salpgridLog").show();
                 $("#manualLog").hide();
+                $("#slapstate").show();
+                $("#openloglist").hide();
             }
             processState = running ? "Running" : "Stopped";
             currentLogSize += parseInt(size, 10);
@@ -107,7 +109,7 @@ function stopProcess() {
         var urlfor = $SCRIPT_ROOT + "stopSlapgrid",
             type = "slapgrid-sr";
 
-        if ($("#instrun").text() === "Stop instance") {
+        if (processType === "Instance") {
             type = "slapgrid-cp";
         }
         $.post(urlfor, {type: type}, function (data) {
@@ -121,6 +123,7 @@ function stopProcess() {
             .complete(function () {
                 sendStop = false;
                 processState = "Stopped";
+                forcedStop = true;
             });
     }
 }
@@ -128,29 +131,50 @@ function stopProcess() {
 function bindRun() {
     "use strict";
     $("#softrun").click(function () {
-        if ($("#softrun").text() === "Stop software") {
+        if ($(this).hasClass('slapos_stop')) {
             stopProcess();
         } else {
             if (!isRunning()) {
                 setCookie("slapgridCMD", "Software");
-                window.location.href = $SCRIPT_ROOT + "/viewSoftwareLog";
+                window.location.href = $SCRIPT_ROOT + "/viewLog";
             }
         }
         return false;
     });
     $("#instrun").click(function () {
-        if ($("#instrun").text() === "Stop instance") {
+        if ($("#softrun").hasClass('slapos_stop')) {
             stopProcess();
         } else {
             if (!isRunning()) {
                 setCookie("slapgridCMD", "Instance");
-                window.location.href = $SCRIPT_ROOT + "/viewInstanceLog";
+                window.location.href = $SCRIPT_ROOT + "/viewLog";
             }
         }
         return false;
     });
 }
 
+function updateStatus(elt, val) {
+  "use strict";
+  var src = '#' + elt + '_run_state', value = 'state_' + val;
+  $(src).removeClass();
+  $(src).addClass(value);
+  switch (val) {
+    case "waiting":
+      $(src).children('p').text("Waiting for starting");
+      break;
+    case "stopped":
+      $(src).children('p').text("Stopped by user");
+      break;
+    case "terminated":
+      $(src).children('p').text("Complete");
+      break;
+    case "running":
+      $(src).children('p').text("Processing");
+      break;
+  }
+}
+
 function setRunningState(data) {
     "use strict";
     if (data.result) {
@@ -159,31 +183,45 @@ function setRunningState(data) {
             running = true;
             //change run menu title and style
             if (data.software) {
-                $("#softrun").empty();
-                $("#softrun").append("Stop software");
-                $("#softrun").css("color", "#0271BF");
-                $current = $("#softrun");
+                if ( $("#running").children('span').length === 0 ) {
+                  $("#softrun").removeClass('slapos_run');
+                  $("#softrun").addClass('slapos_stop');
+                  $("#running img").before('<p id="running_info" class="software">Building software...</p>');
+                }
                 processType = "Software";
             }
             if (data.instance) {
-                $("#instrun").empty();
-                $("#instrun").append("Stop instance");
-                $("#instrun").css("color", "#0271BF");
-                $current = $("#instrun");
+              ///Draft!!
+                if ( $("#running").children('span').length === 0 ) {
+                  $("#softrun").removeClass('slapos_run');
+                  $("#softrun").addClass('slapos_stop');
+                  $("#running img").before('<p id="running_info" class="instance">Running instance...</p>');
+                }
                 processType = "Instance";
             }
         }
     } else {
+        if ( $("#running").is(":visible") ) {
+          $("#error").Popup("Slapgrid finished running your " + processType + " Profile", {type: 'info', duration: 3000});
+          if ( forcedStop ) {
+            updateStatus('instance', 'stopped');
+            updateStatus('software', 'stopped');
+          }
+          else {
+            updateStatus(processType.toLowerCase(), 'terminated');
+          }
+          //Update window!!!
+          $("#slapswitch").attr('rel', 'opend');
+          $("#slapswitch").text('Access application');
+        }
         $("#running").hide();
         running = false; //nothing is currently running
-        if ($current !== undefined) {
-            $current.empty();
-            $current.append("Run " + processType.toLowerCase());
-            $current.css("color", "#275777");
-            $current = undefined;
-            currentState = false;
-            $("#error").Popup("Slapgrid finished running your " + processType + " Profile", {type: 'info', duration: 3000});
+        $("#softrun").removeClass('slapos_stop');
+        $("#softrun").addClass('slapos_run');
+        if ( $("#running").children('span').length > 0 ) {
+          $("#running").children('p').remove();
         }
+        currentState = false;
     }
     currentState = data.result;
 }
@@ -197,6 +235,9 @@ function runProcess(urlfor, data) {
             .error(function () {
                 $("#error").Popup("Failled to run Slapgrid", {type: 'error', duration: 3000});
             });
+        if ( $("#running_info").children('span').length > 0 ) {
+          $("#running_info").children('p').remove();
+        }
         setRunningState(data);
         setTimeout(getRunningState, 6000);
     }
@@ -208,14 +249,19 @@ function checkSavedCmd() {
     if (!result) {
         return false;
     }
+    forcedStop = false;
     if (result === "Software") {
         running = false;
         runProcess(($SCRIPT_ROOT + "/runSoftwareProfile"),
                    {result: true, instance: false, software: true});
+        updateStatus('software', 'running');
+        updateStatus('instance', 'waiting');
     } else if (result === "Instance") {
         running = false;
         runProcess(($SCRIPT_ROOT + "/runInstanceProfile"),
                    {result: true, instance: true, software: false});
+        updateStatus('software', 'terminated');
+        updateStatus('instance', 'running');
     }
     deleteCookie("slapgridCMD");
     return (result !== null);
diff --git a/slapos/runner/static/js/scripts/softwareFolder.js b/slapos/runner/static/js/scripts/softwareFolder.js
index 1af61c7..815391c 100644
--- a/slapos/runner/static/js/scripts/softwareFolder.js
+++ b/slapos/runner/static/js/scripts/softwareFolder.js
@@ -551,7 +551,7 @@ $(document).ready(function () {
 
     function addToFavourite(filepath){
       var i = favourite_list.length,
-          filename = filepath.replace(/^.*(\\|\/|\:)/, '');;
+          filename = filepath.replace(/^.*(\\|\/|\:)/, '');
       if (i === 0){
         $("#tooltip-filelist ul").empty();
       }
diff --git a/slapos/runner/static/js/scripts/viewlog.js b/slapos/runner/static/js/scripts/viewlog.js
index 110660f..0dbd933 100644
--- a/slapos/runner/static/js/scripts/viewlog.js
+++ b/slapos/runner/static/js/scripts/viewlog.js
@@ -6,6 +6,28 @@
 $(document).ready(function () {
     "use strict";
 
+    // Current_log is not used for auto displaying mode, only for manual reload of log file!!!
+    var current_log = 'instance.log',
+        sending,
+        state,
+        selectedFile = "",
+        logfilelist = "instance_root/.log_list";
+
+    var getCurrentLogFile = function () {
+      if ( $("#manual").is(":checked") ) {
+        return "";
+      }
+      if (current_log === "software.log") {
+        return "software";
+      }
+      else if (current_log === "instance.log") {
+        return "instance";
+      }
+      else {
+        return "";
+      }
+    };
+
     function setupBox() {
         var state = $("#logconfigbox").css("display");
         if (state === "none") {
@@ -23,59 +45,281 @@ $(document).ready(function () {
         if (processState === "Stopped" || processState === "Checking" || $("#manual").is(":checked")) {
             $("#salpgridLog").hide();
             $("#manualLog").show();
-            $("#manualLog")
-                .scrollTop($("#manualLog")[0].scrollHeight - $("#manualLog").height());
+            $("#slapstate").hide();
+            $("#openloglist").show();
+            loadLog(current_log);
+            $("#salpgridLog").empty();
         } else {
             $("#salpgridLog").show();
             $("#manualLog").hide();
+            $("#slapstate").show();
+            $("#openloglist").hide();
         }
     }
 
-    openedlogpage = $("input#type").val();
+    function loadLog(filepath, $elt) {
+      if (sending){
+        return;
+      }
+      sending = true;
+      var info = $("#logheader").html();
+      $("#logheader").html("LOADING FILE... <img src='"+$SCRIPT_ROOT+"/static/images/loading.gif' />");
+      var jqxhr = $.ajax({
+          type: "POST",
+          url: $SCRIPT_ROOT + '/getFileLog',
+          data: {filename: filepath, truncate: 1500}
+          })
+        .done(function(data) {
+          if (data.code === 0) {
+            $("#error").Popup(data.result, {type: 'alert', duration: 5000});
+          } else {
+            $("#manualLog").empty();
+            $("#manualLog").html(data.result);
+            $("#logheader").empty();
+            info = "Log from file " + filepath;
+            $("#manualLog")
+                .scrollTop($("#manualLog")[0].scrollHeight - $("#manualLog").height());
+            current_log = filepath;
+            if ($elt) {
+              $("#openloglist ul li").each(function () {
+                if ($(this).hasClass("checked")){
+                  $(this).removeClass("checked");
+                  return;
+                }
+              });
+              $elt.addClass('checked');
+            }
+          }
+        })
+        .fail(function(jqXHR, exception) {
+          if (jqXHR.status == 404) {
+              $("#error").Popup("Requested page not found. [404]", {type: 'error'});
+          } else if (jqXHR.status == 500) {
+              $("#error").Popup("Internal Error. Cannot respond to your request, please check your parameters", {type: 'error'});
+          } else {
+              $("#error").Popup("An Error occured: \n" + jqXHR.responseText, {type: 'error'});
+          }
+        })
+        .always(function() {
+          sending = false;
+          $("#logheader").html(info);
+        });
+    }
+
+    function init() {
+      openedlogpage = getCurrentLogFile();
+      state = getCookie("autoUpdate");
+      if (state) {
+        $("#" + state).attr('checked', true);
+        //updatelogBox();
+        if (state === "manual") {
+            openedlogpage = "";
+            setSpeed(0);
+        } else {
+            setSpeed((state === "live") ? 100 : 2500);
+        }
+      } else {
+          $("#slow").attr('checked', true);
+      }
+      $("#inlineViewer").colorbox({inline:true, width: "600px",
+                      title: "Please select your log file",
+                      onComplete:function () {
+                        selectedFile = "";
+                      }
+      });
+      //Load Custom file list if exist!
+      $.ajax({
+          type: "POST",
+          url: $SCRIPT_ROOT + '/getFileContent',
+          data: {file: logfilelist}
+          })
+        .done(function(data) {
+          if (data.code === 0) {
+            // nothing
+          } else {
+            var list = data.result.split('#'), i, filename;
+            for (i = 0; i < list.length; i += 1) {
+              if ( list[i] === "" ) {
+                continue;
+              }
+              filename = list[i].replace(/^.*(\\|\/|\:)/, '');
+              $("#openloglist ul").append("<li rel='" + list[i] + "'>" +
+                    "<span class='bt_close' title='Remove this element!'" +
+                    "style='display:none'>×</span>" + filename + "</li>");
+            }
+          }
+        })
+        .fail(function(jqXHR, exception) {
+          // nothing
+        })
+        .always(function() {
+          $("#openloglist ul li").click(function () {
+            var logfile = $(this).attr('rel');
+            if (current_log === logfile){
+              return false;
+            }
+            loadLog(logfile, $(this));
+            return false;
+          });
+        });
+    }
+
+    function initTree(tree, path, key) {
+      if (!key){
+        key = '0';
+      }
+      $(tree).fancytree({
+        activate: function(event, data) {
+          var node = data.node;
+        },
+        click: function(event, data) {
+          if (!data.node.isFolder()){
+            selectedFile = data.node.data.path;
+          }
+        },
+        source: {
+          url: $SCRIPT_ROOT + "/fileBrowser",
+          data:{opt: 20, dir: path, key: key, listfiles: 'yes'},
+          cache: false
+        },
+        lazyload: function(event, data) {
+          var node = data.node;
+          data.result = {
+            url: $SCRIPT_ROOT + "/fileBrowser",
+            data: {opt: 20, dir: node.data.path , key: node.key, listfiles: 'yes'}
+          }
+        },
+      });
+    }
+
+    init();
     updatelogBox();
-    var state = getCookie("autoUpdate");
+    initTree('#fileTree', "instance_root");
 
     $("#logheader").click(function () {
         setupBox();
     });
 
     $("#manual").change(function () {
+      if ( $(this).is(':checked') ) {
         setCookie("autoUpdate", "manual");
+        updatelogBox();
+        openedlogpage = "";
+      }
+        /*
         if ($("input#type").val() === "instance") {
             window.location.href = $SCRIPT_ROOT + "/viewInstanceLog";
         } else {
             window.location.href = $SCRIPT_ROOT + "/viewSoftwareLog";
-        }
+        }*/
     });
 
     $("#live").change(function () {
+      if ( $(this).is(':checked') ) {
         updatelogBox();
-        $("#logconfigbox").find("input:radio").attr('checked', false);
-        $("#live").attr('checked', true);
         setSpeed(100);
         setCookie("autoUpdate", "live");
-        openedlogpage = $("input#type").val();
+      }
     });
 
     $("#slow").change(function () {
+      if ( $(this).is(':checked') ) {
         updatelogBox();
-        $("#logconfigbox").find("input:radio").attr('checked', false);
-        $("#slow").attr('checked', true);
         setSpeed(2500);
         setCookie("autoUpdate", "slow");
-        openedlogpage = $("input#type").val();
+      }
+    });
+
+    $("#refreshlog").click(function () {
+      loadLog(current_log);
+      return false;
+    });
+
+    $("#addfile").click(function () {
+      var filename;
+      $.colorbox.close();
+      if (selectedFile !== "") {
+        $.ajax({
+          type: "POST",
+          url: $SCRIPT_ROOT + '/checkFileType',
+          data: {path: selectedFile}
+          })
+        .done(function(data) {
+          if (data.code === 0) {
+            $("#error").Popup(data.result, {type: 'alert', duration: 5000});
+          } else {
+            filename = selectedFile.replace(/^.*(\\|\/|\:)/, '');
+            $("#openloglist ul").append("<li rel='" + selectedFile + "'>" +
+                    "<span class='bt_close' title='Remove this element!'" +
+                    ">×</span>" + filename + "</li>");
+          }
+        })
+        .fail(function(jqXHR, exception) {
+          $("#error").Popup("An Error occured: \n" + jqXHR.responseText, {type: 'error'});
+        });
+      }
+      else{
+        $("#error").Popup("No log file selected!", {type: 'alert', duration: 3000});
+      }
+    });
+
+    $("#logEdit").click(function () {
+      if ( $(this).text() === "Save list") {
+        $(this).text("Edit list");
+        $("#addbox").hide();
+        $("#openloglist ul li").click(function () {
+          var logfile = $(this).attr('rel');
+          if (current_log === logfile){
+            return false;
+          }
+          loadLog(logfile, $(this));
+          return false;
+        });
+        var savelist = "";
+        $("#openloglist ul li").each(function () {
+          $(this).children( "span" ).hide();
+          $(this).children( "span" ).unbind('click');
+          if ( $(this).children('span').length > 0 ) {
+            savelist += $(this).attr('rel') + "#"
+          }
+        });
+        //Send result to be saved!!
+        $.ajax({
+          type: "POST",
+          url: $SCRIPT_ROOT + '/saveFileContent',
+          data: {file: logfilelist, content: savelist}
+          })
+        .done(function(data) {
+          if (data.code === 0) {
+            $("#error").Popup(data.result, {type: 'error', duration: 5000});
+          } else {
+            $("#error").Popup("Done! Your log file list has been saved.", {type: 'confirm', duration: 5000});
+          }
+        })
+        .fail(function(jqXHR, exception) {
+          $("#error").Popup("An Error occured: \n" + jqXHR.responseText, {type: 'error'});
+        });
+      }
+      else {
+        $(this).text("Save list");
+        $("#openloglist ul li").unbind('click');
+        $("#addbox").show();
+        $("#openloglist ul li").each(function () {
+          $(this).children( "span" ).show();
+          $(this).children( "span" ).click(function () {
+            $(this).parent().remove();
+          });
+        });
+      }
+    });
+
+    $("#slapswitch").click( function () {
+      if ( $(this).attr('rel') === 'opend' ) {
+        window.location.href = $SCRIPT_ROOT + "/inspectInstance";
+      }
+      else {
+        $("#softrun").click();
+      }
     });
 
-    if (state) {
-        $("#" + state).attr('checked', true);
-        updatelogBox();
-        if (state === "manual") {
-            openedlogpage = "";
-            setSpeed(0);
-        } else {
-            setSpeed((state === "live") ? 100 : 2500);
-        }
-    } else {
-        $("#slow").attr('checked', true);
-    }
 });
diff --git a/slapos/runner/templates/layout.html b/slapos/runner/templates/layout.html
index b183b1c..8050d7d 100644
--- a/slapos/runner/templates/layout.html
+++ b/slapos/runner/templates/layout.html
@@ -70,9 +70,7 @@
 	        <div class="line"></div>
           <h2 class="info">{% block title %}{% endblock %} - {{session.title}}</h2>
     	    <div class="run">
-            <div id="running">
-              <p id="running_info" class='software'>Building software...</p>
-              <!--<p id="running_info" class='instance'>Running instance...</p>-->
+            <div id="running" style="display:none">
               <img alt="" src="{{ url_for('static', filename='images/ajax_roller.gif') }}" height='26' title="slapgrid is currently running"/>
               <div class="clear"></div>
             </div>
@@ -82,8 +80,8 @@
           <ul class="sf-menu">
             <li><a href="{{ url_for('editCurrentProject') }}">Editor</a></li>
             <li><a href="{{ url_for('inspectInstance') }}">Services</a></li>
-            <li><a href="{{ url_for('viewInstanceLog') }}">Logs</a></li>
-            <li><a href="{{ url_for('viewSoftwareLog') }}">Terminal</a></li>
+            <li><a href="{{ url_for('viewLog') }}">Logs</a></li>
+            <li><a href="{{ url_for('viewLog') }}">Terminal</a></li>
             <li><a href="{{ url_for('manageRepository')}}#tab2">Git</a></li>
             <li class='right_menu main_menu'><a href="#"></a>
               <ul>
@@ -93,7 +91,7 @@
                 <li><a href="{{ url_for('openProject', method='new')}}">Create Software Release</a></li>
                 <li class='sep'></li>
                 <li><a href="{{ url_for('removeInstance') }}" id="removeIst">Destroy All Services</a></li>
-                <li><a href="{{ url_for('runInstanceProfile') }}">Redeploy Services</a></li>
+                <li><a href="{{ url_for('runInstanceProfile') }}" id="instrun">Redeploy Services</a></li>
                 <li class='sep'></li>
                 <li><a href="{{ url_for('browseWorkspace') }}">Browse Workspace</a></li>
                 <li><a href="{{ url_for('inspectSoftware') }}">My Softwares Releases</a></li>
@@ -101,7 +99,7 @@
                 <li><a href="{{ url_for('dologout') }}">Log out</a></li>
              </ul>
             </li>
-            <li class='right_menu slapos_run'><a href="{{ url_for('runSoftwareProfile') }}" id="instrun"></a>
+            <li class='right_menu slapos_run' id="softrun"><a href="{{ url_for('runSoftwareProfile') }}"></a>
             <!--<li class='right_menu slapos_top'><a href="{{ url_for('runInstanceProfile') }}" id="instrun"></a>-->
             </li>
           </ul>
diff --git a/slapos/runner/templates/viewLog.html b/slapos/runner/templates/viewLog.html
index 95a1a8d..052dfaf 100644
--- a/slapos/runner/templates/viewLog.html
+++ b/slapos/runner/templates/viewLog.html
@@ -1,12 +1,17 @@
 {% extends "layout.html" %}
-{% block title %}View {{ type }} log{% endblock %}
+{% block title %}View log{% endblock %}
 {% block head %}
   {{ super() }}
   <script src="{{ url_for('static', filename='js/scripts/viewlog.js') }}" type="text/javascript" charset="utf-8"></script>
+  <link href="{{ url_for('static', filename='css/ui.fancytree.css', _external=False) }}" rel="stylesheet" type="text/css" media="screen" />
+  <script src="{{ url_for('static', filename='js/jquery/jquery.fancytree.min.js') }}" type="text/javascript" charset="utf-8"></script>
+  <link href="{{ url_for('static', filename='css/colorbox.css', _external=False) }}" rel="stylesheet" type="text/css" media="screen" />
+  <script src="{{ url_for('static', filename='js/jquery/jquery.colorbox-min.js') }}" type="text/javascript" charset="utf-8"></script>
 {% endblock %}
 {% block body %}
-<input type="hidden" name="type" value="{{type}}" id="type" />
-<h2 class="hight hide" id="logheader">Slapgrid result for {{ type }}</h2>
+
+
+<h2 class="hight hide" id="logheader">Inspecting current slapgrid log - Click for more options</h2>
 <div class="log_btn" id="logconfigbox" style="display:none">
 <span style="margin-left:15px; font-size: 18px;"> Update parameters </span>
 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
@@ -20,37 +25,67 @@
 <div class='space'></div>
 <div class="log_content">
   <div class="log" id="salpgridLog"></div>
-  <textarea class="log" readonly id="manualLog" style="display:none">{{result}}</textarea>
+  <div class="log" id="manualLog" style="display:none"></div>
 </div>
 <div class="log_information">
   <div class="log_info_box">
-    <h2 class="software">Building State</h2>
-    <div class="log_content_box">
-      <div id="sr_run_state" class="state_running">
-        <span class="legend"></span>
-        <p>Processing</p>
-        <div class="clear"></div>
+    <div id="slapstate">
+      <h2 class="software">Building State</h2>
+      <div class="log_content_box">
+        <div id="software_run_state" class="state_running">
+          <span class="legend"></span>
+          <p>Processing</p>
+          <div class="clear"></div>
+        </div>
+        <p>SlapOS rebuild your software from source, allowing you to easily patch or add any free software. <a href="#">Lean how!</a></p>
       </div>
-      <p>SlapOS rebuild your software from source, allowing you to easily patch or add any free software. <a href="#">Lean how!</a></p>
-    </div>
 
-    <h2 class="instance">Running State</h2>
-    <div class="log_content_box">
-      <!--<div id="instance_run_state" class="state_terminated">-->
-      <!--<div id="instance_run_state" class="state_stopped">-->
-      <div id="instance_run_state" class="state_waiting">
-        <span class="legend"></span>
-        <p>Waiting for starting</p>
-        <div class="clear"></div>
+      <h2 class="instance">Running State</h2>
+      <div class="log_content_box">
+        <!--<div id="instance_run_state" class="state_terminated">-->
+        <!--<div id="instance_run_state" class="state_stopped">-->
+        <div id="instance_run_state" class="state_waiting">
+          <span class="legend"></span>
+          <p>Waiting for starting</p>
+          <div class="clear"></div>
+        </div>
+        <p>SlapOS configure your running environment to match your needs. <a href="#">Lean how!</a></p>
       </div>
-      <p>SlapOS configure your running environment to match your needs. <a href="#">Lean how!</a></p>
-    </div>
 
-    <div class="separator"></div>
-    <div style="height: 38px; text-align: center; padding-top: 25px;">
-      <a href="#" id="switch" class="lshare" rel="stop">STOP PROCESS</a>
+      <div class="separator"></div>
+      <div style="height: 38px; text-align: center; padding-top: 25px;">
+        <a href="#" id="slapswitch" class="lshare" rel="stop">STOP PROCESS</a>
+      </div>
+    </div>
+    <div id="openloglist">
+      <h2 class="boxtitle">Select log file to view</h2>
+      <div style="height: 340px; margin-bottom: 10px;">
+        <div id='addbox' style="display:none">
+          <a href="#inline_content" id="inlineViewer" class="add_btn">ADD FILE</a>
+          <div class="separator"></div>
+        </div>
+        <ul>
+          <li rel='software.log'>Software.log</li>
+          <li rel='instance.log' class='checked'>Instance.log</li>
+        </ul>
+      </div>
+      <div class="separator"></div>
+      <div style="height: 38px; text-align: left; padding: 20px 0 0 15px;">
+        <a id="logEdit" class="lshare no-right-border" style="float:left">Edit list</a>
+        <a id="refreshlog" class="lshare" style="float:left">Refresh log</a>
+      </div>
     </div>
   </div>
 </div>
 <div class='clear'></div>
+
+<!-- This contains the hidden content for inline calls -->
+<div style='display:none'>
+  <div id='inline_content' style='padding:10px; background:#fff;'>
+    <div style='border: solid 1px #678dad; height: 300px; overflow: auto;'>
+      <div id="fileTree"></div>
+    </div>
+    <input type=submit value="Add file" id="addfile" class="button">
+  </div>
+</div>
 {% endblock %}
diff --git a/slapos/runner/views.py b/slapos/runner/views.py
index cca28cf..38dcd14 100755
--- a/slapos/runner/views.py
+++ b/slapos/runner/views.py
@@ -160,16 +160,6 @@ def runSoftwareProfile():
     return jsonify(result=False)
 
 
-@login_required()
-def viewSoftwareLog():
-  if os.path.exists(app.config['software_log']):
-    result = tail(open(app.config['software_log']), lines=1500)
-  else:
-    result = 'Not found yet'
-  return render_template('viewLog.html', type='software',
-      result=result.encode("utf-8"))
-
-
 # instance views
 @login_required()
 def editInstanceProfile():
@@ -240,13 +230,30 @@ def runInstanceProfile():
 
 
 @login_required()
-def viewInstanceLog():
-  if os.path.exists(app.config['instance_log']):
-    result = open(app.config['instance_log']).read()
+def viewLog():
+  return render_template('viewLog.html')
+
+@login_required()
+def getFileLog():
+  logfile = request.form.get('filename', '').encode('utf-8')
+  if logfile == "instance.log":
+    file_path = app.config['instance_log']
+  elif logfile == "software.log":
+    file_path = app.config['software_log']
   else:
-    result = 'Not found yet'
-  return render_template('viewLog.html', type='instance',
-      result=result.encode("utf-8"))
+    file_path = realpath(app.config, logfile)
+  try:
+    if not isText(file_path):
+      return jsonify(code=0,
+            result="Can not open binary file, please select a text file!")
+    if 'truncate' in request.form:
+      content = tail(open(file_path), int(request.form['truncate']))
+      return jsonify(code=1, result=content)
+    else:
+      with open(file_path) as f:
+        return jsonify(code=1, result=f.read())
+  except:
+    return jsonify(code=0, result="Warning: Log file doesn't exist yet or empty log!!")
 
 
 @login_required()
@@ -388,12 +395,13 @@ def getFileContent():
 
 @login_required()
 def saveFileContent():
-  file_path = realpath(app.config, request.form['file'])
+  file_path = realpath(app.config, request.form['file'], False)
   if file_path:
-    open(file_path, 'w').write(request.form['content'].encode("utf-8"))
+    with open(file_path, 'w') as f:
+      f.write(request.form['content'].encode("utf-8"))
     return jsonify(code=1, result="")
   else:
-    return jsonify(code=0, result="Error: No such file!")
+    return jsonify(code=0, result="Rejected!! Cannot access to this location.")
 
 
 @login_required()
@@ -705,8 +713,6 @@ app.add_url_rule('/inspectSoftware', 'inspectSoftware', inspectSoftware)
 app.add_url_rule('/removeSoftware', 'removeSoftware', removeSoftware)
 app.add_url_rule('/runSoftwareProfile', 'runSoftwareProfile',
                  runSoftwareProfile, methods=['GET', 'POST'])
-app.add_url_rule('/viewSoftwareLog', 'viewSoftwareLog',
-                 viewSoftwareLog, methods=['GET'])
 app.add_url_rule('/editInstanceProfile', 'editInstanceProfile',
                  editInstanceProfile)
 app.add_url_rule('/inspectInstance', 'inspectInstance',
@@ -716,8 +722,10 @@ app.add_url_rule('/supervisordStatus', 'supervisordStatus',
 app.add_url_rule('/runInstanceProfile', 'runInstanceProfile',
                  runInstanceProfile, methods=['GET', 'POST'])
 app.add_url_rule('/removeInstance', 'removeInstance', removeInstance)
-app.add_url_rule('/viewInstanceLog', 'viewInstanceLog',
-                 viewInstanceLog, methods=['GET'])
+app.add_url_rule('/viewLog', 'viewLog',
+                 viewLog, methods=['GET'])
+app.add_url_rule("/getFileLog", 'getFileLog', getFileLog,
+                 methods=['POST'])
 app.add_url_rule('/stopAllPartition', 'stopAllPartition',
                  stopAllPartition, methods=['GET'])
 app.add_url_rule('/tailProcess/name/<process>', 'tailProcess',
-- 
2.30.9