From 7af0cc8aa843c6a108644214cb06a143fc0cbf48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Gauthier?= Date: Fri, 10 Feb 2017 14:47:37 +0100 Subject: [PATCH] Work in progress --- .../rudder/web/services/DisplayNode.scala | 26 ++- .../javascript/rudder/angular/apiAccount.js | 2 +- .../rudder/angular/nodeProperties.js | 158 ++++++++++++++++++ .../secure/nodeManager/searchNodes.html | 3 + .../main/webapp/style/rudder/rudder-menu.css | 132 +++++++++++++-- .../src/main/webapp/style/rudder/rudder.css | 1 + .../components/ComponentNodeProperties.html | 70 ++++++++ 7 files changed, 366 insertions(+), 26 deletions(-) create mode 100644 rudder-web/src/main/webapp/javascript/rudder/angular/nodeProperties.js create mode 100644 rudder-web/src/main/webapp/templates-hidden/components/ComponentNodeProperties.html diff --git a/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala b/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala index 4d6b5453981..0c45250d302 100644 --- a/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala +++ b/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala @@ -655,21 +655,31 @@ object DisplayNode extends Loggable { } private def displayTabVariable(jsId:JsNodeId,sm:FullInventory) : NodeSeq = { - val title = sm.node.inventoryDate.map(date => "Environment variable status on %s".format(DateFormaterService.getFormatedDate(date))) - displayTabGrid(jsId)("var", Full(sm.node.environmentVariables),title){ + val title = sm.node.inventoryDate.map(date => "Environment variable status on %s".format(DateFormaterService.getFormatedDate(date))) + displayTabGrid(jsId)("var", Full(sm.node.environmentVariables),title){ ("Name", {x:EnvironmentVariable => Text(x.name)}) :: ("Value", {x:EnvironmentVariable => Text(x.value.getOrElse("Unspecified"))}) :: Nil - } + } } private def displayTabProperties(jsId:JsNodeId, node: NodeInfo) : NodeSeq = { + import com.normation.rudder.domain.nodes.JsonSerialisation._ import net.liftweb.json._ - displayTabGrid(jsId)("props", Full(node.properties)){ - ("Name", {x:NodeProperty => Text(x.name)}) :: - ("Value", {x:NodeProperty =>
{prettyRender(x.value)}
}) :: - Nil - } + val nodeId = node.id.value + val jsonProperties = compactRender(node.properties.toApiJson()) + def tabProperties = ChooseTemplate(List("templates-hidden", "components", "ComponentNodeProperties") , "nodeproperties-tab") + + val css: CssSel = "#tabPropsId [id]" #> htmlId(jsId,"sd_props_") + css(tabProperties) ++ Script(OnLoad(JsRaw(s""" + angular.bootstrap('#nodeProp', ['nodeProperties']); + var scope = angular.element($$("#nodeProp")).scope(); + var props = ${jsonProperties}; + var nodeId = "${nodeId}" + scope.$$apply(function(){ + scope.init(props,nodeId); + }); + """))) } private def displayTabProcess(jsId:JsNodeId,sm:FullInventory) : NodeSeq = { diff --git a/rudder-web/src/main/webapp/javascript/rudder/angular/apiAccount.js b/rudder-web/src/main/webapp/javascript/rudder/angular/apiAccount.js index ed9a2b5d38f..4e73b8930ec 100644 --- a/rudder-web/src/main/webapp/javascript/rudder/angular/apiAccount.js +++ b/rudder-web/src/main/webapp/javascript/rudder/angular/apiAccount.js @@ -219,7 +219,7 @@ $scope.popupCreation = function(account,index) { $scope.myNewAccount.index = index; $("#newAccountName").focus(); }); - $('#newAccountPopup').bsModal('show'); + $('#newAccountPopup').bsModal('show'); return account; }; diff --git a/rudder-web/src/main/webapp/javascript/rudder/angular/nodeProperties.js b/rudder-web/src/main/webapp/javascript/rudder/angular/nodeProperties.js new file mode 100644 index 00000000000..8257cad2705 --- /dev/null +++ b/rudder-web/src/main/webapp/javascript/rudder/angular/nodeProperties.js @@ -0,0 +1,158 @@ +/* +************************************************************************************* +* Copyright 2017 Normation SAS +************************************************************************************* +* +* This file is part of Rudder. +* +* Rudder is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 3 of the License, or +* (at your option) any later version. +* +* In accordance with the terms of section 7 (7. Additional Terms.) of +* the GNU General Public License version 3, the copyright holders add +* the following Additional permissions: +* Notwithstanding to the terms of section 5 (5. Conveying Modified Source +* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General +* Public License version 3, when you create a Related Module, this +* Related Module is not considered as a part of the work and may be +* distributed under the license agreement of your choice. +* A "Related Module" means a set of sources files including their +* documentation that, without modification of the Source Code, enables +* supplementary functions or services in addition to those offered by +* the Software. +* +* Rudder is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with Rudder. If not, see . + +* +************************************************************************************* +*/ + +var app = angular.module('nodeProperties', ['DataTables', 'monospaced.elastic']); + +app.controller('nodePropertiesCtrl', function ($scope, $http, $compile) { + //DataTable + $scope.columnDefs = [ + {"aTargets":[0], "mDataProp": "name" , "sWidth": "250px", "sTitle" : "Name" } + , { + "aTargets":[1], + "mDataProp": "value", + "sTitle" :"Value", + "fnCreatedCell" : function (nTd, sData, oData, iRow, iCol) { + var pre = $("
");
+      var content = sData != null && typeof sData === 'object' ? JSON.stringify(sData) : sData;
+      $(nTd).empty();
+      pre.html(content)
+      $(nTd).prepend(pre);
+     }
+   }
+  , {
+    "aTargets":[2],
+    "sWidth": "50px",
+    "mDataProp": "name",
+    "sTitle" :"",
+    "orderable": false,
+    "fnCreatedCell" : function (nTd, sData, oData, iRow, iCol) {
+      var deleteButton = $('');
+      $(nTd).addClass('text-center delete-action');
+      $(nTd).empty();
+      $(nTd).prepend(deleteButton);
+      $compile(deleteButton)($scope);
+    }
+  }];
+
+  $scope.overrideOptions = {
+      "bFilter" : true
+    , "bPaginate" : true
+    , "bLengthChange": true
+    , "sPaginationType": "full_numbers"
+    , "oLanguage": {
+        "sSearch": ""
+      }
+    , "aaSorting": [[ 0, "asc" ]]
+    , "sDom": '<"dataTables_wrapper_top"fl>rt<"dataTables_wrapper_bottom"ip>'
+  };
+
+
+  //Initialize scope
+  $scope.properties;
+  $scope.newProperty = {'name':"", 'value':""}
+  $scope.alreadyUsed = false;
+  $scope.errorSaving = false;
+  $scope.selectedProperty = {'name':"", 'index':""};
+  $scope.urlAPI = contextPath + '/secure/api/latest/nodes/';
+  $scope.nodeId;
+  $scope.resetNewProperty = function(){
+    $scope.newProperty = {'name':"", 'value':""};
+  }
+
+  $scope.init = function(properties, nodeId){
+    //Get current node properties
+	$scope.properties = properties;
+	$scope.urlAPI = contextPath + '/secure/api/latest/nodes/'+ nodeId;
+    $("#nodePropertiesTab").dataTable().fnAddData($scope.properties);
+  }
+
+  $scope.addProperty = function(){
+    function getIndex(property, index, array) {
+      return property.name == $scope.newProperty.name;
+    }
+    var data = {"properties":[
+      $scope.newProperty
+    ]};
+    $http.post($scope.urlAPI, data).then(function successCallback(response) {
+      $scope.errorSaving = false;
+      //Check if new property's name is already used or not.
+      $scope.alreadyUsed = $scope.properties.some(getIndex);
+      if(!$scope.alreadyUsed){
+        $scope.properties.push(angular.copy($scope.newProperty));
+        $scope.resetNewProperty();
+        $('#newPropPopup').bsModal('hide');
+        $scope.newPropForm.$setPristine();
+      }
+    }, function errorCallback(response) {
+      return response.status==200;
+      $scope.errorSaving = true;
+    });
+  };
+  $scope.popupCreation = function() {
+    $('#newPropPopup').bsModal('show');
+  };
+  $scope.popupDeletion = function(prop,index) {
+    $scope.selectedProperty.name = prop;
+    $scope.selectedProperty.index = index;
+    $('#deletePropPopup').bsModal('show');
+  };
+  $scope.deleteProperty = function(){
+    var data = {"properties":[{"name":$scope.selectedProperty.name, "value":""}]};
+    $http.post($scope.urlAPI, data).then(function successCallback(response) {
+      $('#deletePropPopup').bsModal('hide');
+      $scope.properties.splice($scope.selectedProperty.index, 1);
+      $('#deletePropPopup').bsModal('hide');
+    }, function errorCallback(response) {
+      return response.status==200;
+      $('#deletePropPopup').bsModal('hide');
+    });
+  }
+  $scope.cancelPopupCreation = function() {
+    $('#newPropPopup').bsModal('hide');
+    $scope.resetNewProperty();
+    $scope.newPropForm.$setPristine();
+    $scope.errorSaving = false;
+    $scope.alreadyUsed = false;
+  };
+});
+
+app.config(function($locationProvider) {
+  $locationProvider.html5Mode({
+    enabled: true,
+    requireBase: false
+  });
+})
\ No newline at end of file
diff --git a/rudder-web/src/main/webapp/secure/nodeManager/searchNodes.html b/rudder-web/src/main/webapp/secure/nodeManager/searchNodes.html
index c99b042c77d..5200720f2aa 100644
--- a/rudder-web/src/main/webapp/secure/nodeManager/searchNodes.html
+++ b/rudder-web/src/main/webapp/secure/nodeManager/searchNodes.html
@@ -2,7 +2,10 @@
 
 
     Rudder - Search Nodes
+    
+    
     
+    
 
 
 
diff --git a/rudder-web/src/main/webapp/style/rudder/rudder-menu.css b/rudder-web/src/main/webapp/style/rudder/rudder-menu.css index 6436f52c3cc..60579dfee8c 100644 --- a/rudder-web/src/main/webapp/style/rudder/rudder-menu.css +++ b/rudder-web/src/main/webapp/style/rudder/rudder-menu.css @@ -84,23 +84,26 @@ tbody.toggle-color > tr > td{ tbody.toggle-color > tr.color1 > td,tbody.toggle-color > tr.color2 > td{ padding-bottom:4px; } -pre.json-beautify{ - cursor:pointer; - height:40px; - transition-duration:.2s; - overflow:hidden; - -moz-box-shadow: inset 0 -10px 10px -10px #bdbdbd; - -webkit-box-shadow: inset 0 -10px 10px -10px #bdbdbd; - box-shadow: inset 0 -10px 10px -10px #bdbdbd; - position:relative; +pre.json-beautify, .tw-bs pre.json-beautify{ + border: none; + background-color: transparent; + border-radius: 0; + cursor: pointer; + height: 37px; + transition-duration: .2s; + overflow: hidden; + position: relative; + margin-bottom: 0; + white-space: initial; + padding-right: 25px; } -pre.json-beautify.toggle{ +pre.json-beautify.toggle, .tw-bs pre.json-beautify.toggle{ height:auto; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } -pre.json-beautify:after{ +pre.json-beautify:after,.tw-bs pre.json-beautify:after{ position:absolute; right:10px; top : 10px; @@ -113,7 +116,7 @@ pre.json-beautify:after{ -webkit-font-smoothing: antialiased; transition-duration:.2s; } -pre.json-beautify.toggle:after{ +pre.json-beautify.toggle:after, .tw-bs pre.json-beautify.toggle:after{ transform:rotate(90deg); } /* - MODAL 4.0 - */ @@ -122,8 +125,7 @@ pre.json-beautify.toggle:after{ border: none; } .tw-bs .modal .modal-header { - border-top-left-radius: 4px; - border-top-right-radius: 4px; + border-radius: 0; border-bottom: 1px solid #f08004; background: #f08004; } @@ -637,7 +639,7 @@ a.sidebar-toggle{ @media (max-width: 767px){ .tw-bs .navbar-nav .open .dropdown-menu { position: absolute; - right: 42px; + right: 42px;td float: none; width: 280px; background-color: rgb(255, 255, 255); @@ -1028,6 +1030,65 @@ label.rule-info + div{ } /* --- NODE PAGE --- */ +#addPropTable{ + width:100%; +} +#addPropTable tbody > tr{ + padding:0; +} +#addPropTable tbody > tr > td{ + border-collapse: collapse; + padding:0; + vertical-align:top; +} +#addPropTable tbody > tr > td > .form-control{ + min-height:33px; +} +#addPropTable tbody > tr > td:first-child{ + width:235px; +} +#addPropTable tbody > tr > td:first-child > input.form-control{ + border-top-right-radius:0; + border-bottom-right-radius:0; +} +#addPropTable tbody > tr > td:nth-child(2),#addPropTable tbody > tr > td:last-child{ + width: 33px; +} +#addPropTable tbody > tr > td:nth-child(2) > span{ + border-left:none; + border-right:none; + border-radius:0; + height: 32px; +} +#addPropTable tbody > tr > td:nth-child(3) > .form-control{ + border-left-width:1px; + border-radius:0; +} +#addPropTable tbody > tr > td:nth-child(2),#addPropTable tbody > tr > td:last-child > .btn{ + height: 33px; + border-top-left-radius:0; + border-bottom-left-radius:0; +} +#nodePropertiesTab_info { + position: relative; + top: 3px; + padding-left: 3px; +} +#nodeProp .btn-success.new-icon{ + margin: 5px 0 7px 0; +} +#nodeProp .addon-json + textarea{ + min-height:initial; +} +#nodePropertiesTab tbody > tr > td:nth-child(2){ + padding:0; +} +#nodePropertiesTab thead > tr > th:last-child{ + border-left:none; +} +#nodeProp table .dataTables_empty{ + display:none; +} tbody.toggle-color > tr:nth-child(odd){ background-color: #eee; } @@ -1044,7 +1105,35 @@ table.tablewidth{ padding:0; border:none; } - +table > tbody > tr > td.action{ + width:80px; +} +.tw-bs #nodePropertiesTab_wrapper label { + margin-bottom: 0; + font-weight: normal; + font-size: 13.2px; +} +.tw-bs .dataTables_length label.text-fit{ + font-size:13.2px; + margin-bottom:0; +} +.tw-bs .dataTables_filter label{ + margin-bottom:0; +} +.tw-bs .dataTables_filter label input{ + font-size:13.2px; + margin-bottom:0; + line-height:normal; + font-weight:normal; +} +#nodePropertiesTab > tbody > tr > td.delete-action > .fa{ + display:none; + cursor:pointer; + font-size:18px; +} +#nodePropertiesTab > tbody > tr:hover > td.delete-action > .fa{ + display:inline-block; +} /* --- DIRECTIVE PAGE --- */ .editTech{ margin-top:4px; @@ -1556,6 +1645,15 @@ form .tooltip-content p { /* *----------------------------------------- */ +.form-group.has-error label { + color: #A94442; +} +.form-group.has-error .form-control { + border-color: #A94442; +} +.no-margin-bottom{ + margin-bottom:0 !important; +} .no-padding{ padding: 0 !important; } @@ -2364,7 +2462,7 @@ ul > li.rudder-form > .input-group.disabled *{ .box-tools > .btn > .fa,.box-tools > .btn > .glyphicon,.box-tools > .btn > .ion{ margin-left: 6px; } -.box-tools > .btn.new-icon:after{ +.btn.new-icon:after{ content: "\f055"; font: normal normal normal 14px/1 FontAwesome; font-size: 1.2em; diff --git a/rudder-web/src/main/webapp/style/rudder/rudder.css b/rudder-web/src/main/webapp/style/rudder/rudder.css index 8d17e430724..f841956a41d 100644 --- a/rudder-web/src/main/webapp/style/rudder/rudder.css +++ b/rudder-web/src/main/webapp/style/rudder/rudder.css @@ -2672,6 +2672,7 @@ label span.text-fit{ } #serverDetails .sInventory, #nodeInventory, #node_inventory{ border : none !important; + overflow-x: hidden; } #nodeInventory .ui-tabs-nav{ margin-top: 5px; diff --git a/rudder-web/src/main/webapp/templates-hidden/components/ComponentNodeProperties.html b/rudder-web/src/main/webapp/templates-hidden/components/ComponentNodeProperties.html new file mode 100644 index 00000000000..b572584aa00 --- /dev/null +++ b/rudder-web/src/main/webapp/templates-hidden/components/ComponentNodeProperties.html @@ -0,0 +1,70 @@ + + + \ No newline at end of file