diff --git a/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/rudder.js b/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/rudder.js index 18abf01250c..4c5dd38f5a6 100644 --- a/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/rudder.js +++ b/webapp/sources/rudder/rudder-web/src/main/javascript/rudder/rudder.js @@ -95,24 +95,6 @@ function refuseEnter(event) } } -/* portlet */ - -$(function() { - - $(".portlet").addClass("ui-widget ui-widget-content ui-helper-clearfix arrondis") - .find(".portlet-header") - .addClass("ui-widget-header") - .end() - .find(".portlet-content"); - - $(".portlet-header .ui-icon").click(function() { - $(this).toggleClass("ui-icon-minusthick").toggleClass("ui-icon-plusthick"); - $(this).parents(".portlet:first").find(".portlet-content").toggle(); - }); - - }); - - /** * Check all checkbox named name according to the status of the checkbox with id id * @param id diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/ShowNodeDetailsFromNode.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/ShowNodeDetailsFromNode.scala index 2d738ae7238..6280f7675d4 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/ShowNodeDetailsFromNode.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/ShowNodeDetailsFromNode.scala @@ -58,10 +58,12 @@ import com.normation.rudder.web.ChooseTemplate import com.normation.rudder.web.model.JsNodeId import com.normation.rudder.web.services.CurrentUser import com.normation.rudder.web.services.DisplayNode +import com.normation.rudder.web.services.DisplayNode.removeNode import com.normation.rudder.web.services.DisplayNodeGroupTree import net.liftweb.common._ import net.liftweb.http.DispatchSnippet import net.liftweb.http.S +import net.liftweb.http.SHtml import net.liftweb.http.js.JE.JsRaw import net.liftweb.http.js.JsCmds._ import net.liftweb.http.js.JsExp @@ -261,12 +263,12 @@ class ShowNodeDetailsFromNode( val jsId = JsNodeId(nodeId, "") def htmlId(jsId: JsNodeId, prefix: String): String = prefix + jsId.toString val detailsId = htmlId(jsId, "details_") + configService.rudder_global_policy_mode().toBox match { case Full(globalMode) => bindNode(node, sm, withinPopup, globalMode) ++ Script( DisplayNode.jsInit(node.id, sm.node.softwareIds, "") & JsRaw(s""" - $$('#nodeHostname').html("${xml.Utility.escape(sm.node.main.hostname)}"); $$( "#${detailsId}" ).tabs({ active : ${tab} } ); $$('#nodeInventory .ui-tabs-vertical .ui-tabs-nav li a').on('click',function(){ var tab = $$(this).attr('href'); @@ -299,9 +301,18 @@ class ShowNodeDetailsFromNode( * Show the content of a node in the portlet * @return */ + private def bindNode(node: NodeInfo, inventory: FullInventory, withinPopup: Boolean, globalMode: GlobalPolicyMode): NodeSeq = { val id = JsNodeId(node.id) - ("#node_groupTree" #> + ("#nodeHeader" #> DisplayNode.showNodeHeader(inventory, Some(node)) & + "#confirmNodeDeletion" #> { + SHtml.ajaxButton( + "Confirm", + () => { removeNode(inventory.node.main) }, + ("class", "btn btn-danger") + ) + } & + "#node_groupTree" #>
& diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala index 5b792b87fa7..078fd604636 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DisplayNode.scala @@ -54,12 +54,10 @@ import com.normation.rudder.domain.reports.ComplianceLevelSerialisation import com.normation.rudder.domain.reports.NodeStatusReport import com.normation.rudder.facts.nodes.ChangeContext import com.normation.rudder.facts.nodes.SelectFacts -import com.normation.rudder.hooks.HookReturnCode import com.normation.rudder.services.reports.NoReportInInterval import com.normation.rudder.services.reports.Pending import com.normation.rudder.services.servers.DeleteMode import com.normation.rudder.web.model.JsNodeId -import com.normation.rudder.web.services.CurrentUser import com.normation.rudder.web.snippet.RegisterToasts import com.normation.rudder.web.snippet.ToastNotification import com.normation.utils.DateFormaterService @@ -93,16 +91,13 @@ import scala.xml.Utility.escape */ object DisplayNode extends Loggable { - private[this] val nodeFactRepository = RudderConfig.nodeFactRepository - private[this] val removeNodeService = RudderConfig.removeNodeService - private[this] val asyncDeploymentAgent = RudderConfig.asyncDeploymentAgent - private[this] val uuidGen = RudderConfig.stringUuidGenerator - private[this] val nodeInfoService = RudderConfig.nodeInfoService - private[this] val linkUtil = RudderConfig.linkUtil - private[this] val reportingService = RudderConfig.reportingService - private[this] val deleteNodePopupHtmlId = "deleteNodePopupHtmlId" - private[this] val errorPopupHtmlId = "errorPopupHtmlId" - private[this] val successPopupHtmlId = "successPopupHtmlId" + private[this] val nodeFactRepository = RudderConfig.nodeFactRepository + private[this] val removeNodeService = RudderConfig.removeNodeService + private[this] val asyncDeploymentAgent = RudderConfig.asyncDeploymentAgent + private[this] val uuidGen = RudderConfig.stringUuidGenerator + private[this] val nodeInfoService = RudderConfig.nodeInfoService + private[this] val linkUtil = RudderConfig.linkUtil + private[this] val reportingService = RudderConfig.reportingService private def escapeJs(in: String): JsExp = Str(escape(in)) private def escapeHTML(in: String): NodeSeq = Text(escape(in)) @@ -346,7 +341,7 @@ object DisplayNode extends Loggable { val tabId = htmlId(jsId, "node_details_")
- + {tabContent.flatten}
++ Script(OnLoad(JsRaw(s"$$('#${tabId}').tabs()"))) } @@ -363,20 +358,166 @@ object DisplayNode extends Loggable { ): NodeSeq = { val jsId = JsNodeId(sm.node.main.id, salt) val detailsId = htmlId(jsId, "details_") -
- -
- {showNodeDetails(sm, nodeAndGlobalMode, None, inventoryStatus, salt)} +
+
+ {showNodeHeader(sm)}
-
- {showInventoryVerticalMenu(sm, nodeAndGlobalMode.map(_._1))} +
+ +
+ {showNodeDetails(sm, nodeAndGlobalMode, None, inventoryStatus, salt)} +
+
+ {showInventoryVerticalMenu(sm, nodeAndGlobalMode.map(_._1))} +
} + def removeNode(node: NodeSummary): JsCmd = { + implicit val cc: ChangeContext = ChangeContext( + ModificationId(uuidGen.newUuid), + CurrentUser.actor, + DateTime.now(), + None, + S.request.map(_.remoteAddr).toOption + ) + + // only erase for Rudder 8.0 + removeNodeService.removeNodePure(node.id, DeleteMode.Erase).toBox match { + case Full(_) => + asyncDeploymentAgent ! AutomaticStartDeployment(cc.modId, cc.actor) + onSuccess(node) + case eb: EmptyBox => + val message = s"There was an error while deleting node '${node.hostname}' [${node.id.value}]" + val e = eb ?~! message + NodeLoggerPure.Delete.logEffect.error(e.messageChain) + onFailure(node, message) + } + } + + private[this] def onFailure( + node: NodeSummary, + message: String + ): JsCmd = { + RegisterToasts.register( + ToastNotification.Error( + s"An error happened when trying to delete node '${node.hostname}' [${node.id.value}]. " + + "Please contact your server admin to resolve the problem. " + + s"Error was: '${message}'" + ) + ) + RedirectTo("/secure/nodeManager/nodes") + } + + private[this] def onSuccess(node: NodeSummary): JsCmd = { + RegisterToasts.register(ToastNotification.Success(s"Node '${node.hostname}' [${node.id.value}] was correctly deleted")) + RedirectTo("/secure/nodeManager/nodes") + } + + private[this] def isRootNode(n: NodeId): Boolean = { + n.value.equals("root"); + } + + def showNodeHeader(sm: FullInventory, optNode: Option[NodeInfo] = None): NodeSeq = { + val machineTooltip: String = { + s""" + |

Machine details

+ |
+ |
    + |
  • Type: ${displayMachineType(sm.machine)}
  • + |
  • Total physical memory (RAM): ${xml.Utility.escape( + sm.node.ram.map(_.toStringMo).getOrElse("-") + )}
  • + |
  • Manufacturer: ${xml.Utility.escape( + sm.machine.flatMap(x => x.manufacturer).map(x => x.name).getOrElse("-") + )}
  • + |
  • Total swap space: ${xml.Utility.escape(sm.node.swap.map(_.toStringMo).getOrElse("-"))}
  • + |
  • System serial number: ${xml.Utility.escape( + sm.machine.flatMap(x => x.systemSerialNumber).getOrElse("-") + )}
  • + |
  • Time zone: ${xml.Utility.escape( + sm.node.timezone + .map(x => if (x.name.toLowerCase == "utc") "UTC" else s"${x.name} (UTC ${x.offset})") + .getOrElse("unknown") + )}
  • + |
  • ${sm.machine + .map(_.id.value) + .map(machineId => "Machine ID: " ++ { xml.Utility.escape(machineId) }) + .getOrElse("Machine Information are missing for that node")}
  • + |
+ |
+ |""".stripMargin.replaceAll("\n", " ") + } + + val deleteBtn = { + sm.node.main.status match { + case AcceptedInventory => + + { + if (!isRootNode(sm.node.main.id)) { + + } else { NodeSeq.Empty } + } + + case _ => NodeSeq.Empty + } + } + + val osTooltip: String = { + s""" + |

Operating system details

+ |
+ |
    + |
  • Type: ${xml.Utility.escape(sm.node.main.osDetails.os.kernelName)}
  • + |
  • Name: ${xml.Utility.escape(S.?("os.name." + sm.node.main.osDetails.os.name))}
  • + |
  • Version: ${xml.Utility.escape(sm.node.main.osDetails.version.value)}
  • + |
  • Service pack: ${xml.Utility.escape(sm.node.main.osDetails.servicePack.getOrElse("None"))}
  • + |
  • Architecture: ${xml.Utility.escape(sm.node.archDescription.getOrElse("None"))}
  • + |
  • Kernel version: ${xml.Utility.escape(sm.node.main.osDetails.kernelVersion.value)}
  • + |
+ |
""".stripMargin.replaceAll("\n", " ") + } + + val nodeStateIcon = (optNode + .map(n => ) + .getOrElse(NodeSeq.Empty)) + +
+ +

+
+ {nodeStateIcon} + {sm.node.main.hostname} + + {sm.node.main.osDetails.fullName} + {sm.node.ram.map(_.toStringMo).getOrElse("-")} + + +
+ +

+
+ {deleteBtn} +
+
++ Script(OnLoad(JsRaw(s"""new ClipboardJS('[data-clipboard-text]');"""))) + } + // mimic the content of server_details/ShowNodeDetailsFromNode def showNodeDetails( sm: FullInventory, @@ -407,101 +548,8 @@ object DisplayNode extends Loggable { None } - val deleteButton: NodeSeq = { - sm.node.main.status match { - case AcceptedInventory => -
-