From ccd5cf86e5ff66ac706ea554195cd01f6e7e4094 Mon Sep 17 00:00:00 2001 From: Rohan Vazarkar Date: Wed, 18 Jul 2018 10:28:49 -0400 Subject: [PATCH] More node editor updates + delete edge --- src/AppContainer.jsx | 2 + src/components/Float/NodeEditor.jsx | 111 +++++++++++++++--- src/components/Float/NodeEditorRow.jsx | 16 ++- src/components/Graph.jsx | 54 ++++++++- src/components/Modals/DeleteEdgeModal.jsx | 64 ++++++++++ src/components/edgeTooltip.html | 8 ++ .../{tooltip.html => nodeTooltip.html} | 0 src/css/styles.css | 5 + 8 files changed, 234 insertions(+), 26 deletions(-) create mode 100644 src/components/Modals/DeleteEdgeModal.jsx create mode 100644 src/components/edgeTooltip.html rename src/components/{tooltip.html => nodeTooltip.html} (100%) diff --git a/src/AppContainer.jsx b/src/AppContainer.jsx index c8a4433..677aea4 100644 --- a/src/AppContainer.jsx +++ b/src/AppContainer.jsx @@ -3,6 +3,7 @@ import GraphContainer from "./components/Graph"; import SearchContainer from "./components/SearchContainer/SearchContainer"; import SpotlightContainer from "./components/Spotlight/SpotlightContainer"; import LogoutModal from "./components/Modals/LogoutModal"; +import DeleteEdgeModal from "./components/Modals/DeleteEdgeModal"; import CancelUploadModal from "./components/Modals/CancelUploadModal"; import ClearWarnModal from "./components/Modals/ClearWarnModal"; import ClearConfirmModal from "./components/Modals/ClearConfirmModal"; @@ -46,6 +47,7 @@ export default class AppContainer extends Component { + diff --git a/src/components/Float/NodeEditor.jsx b/src/components/Float/NodeEditor.jsx index b2cd96e..a500413 100644 --- a/src/components/Float/NodeEditor.jsx +++ b/src/components/Float/NodeEditor.jsx @@ -22,9 +22,11 @@ export default class NodeEditor extends Component { }, handle: "#nodeEditOuter" }); + $(this.refs.outer).fadeToggle(false); } getNodeData(name, type) { + appStore.currentTooltip.close(); $(this.refs.outer).fadeIn(); let q = driver.session(); this.setState({ label: name, type: type }); @@ -42,6 +44,7 @@ export default class NodeEditor extends Component { let label = props.name; delete props.name; this.setState({ properties: props, label: label }); + q.close() }.bind(this) ); } @@ -50,8 +53,75 @@ export default class NodeEditor extends Component { $(this.refs.outer).fadeToggle(false); } + addAttrib(){ + let input = $(this.refs.newAttrName); + let typeinput = $(this.refs.newAttrType); + let val = input.val(); + let type = typeinput.val(); + if (val === ""){ + input.css("border", "3px solid red") + return; + } + + let newval; + if (type === "boolean"){ + newval = false; + }else if (type === "number"){ + newval = 0; + }else if (type === "string"){ + newval = "placeholder"; + }else{ + newval = []; + } + + let key; + if (this.state.type === "ou") { + key = "guid"; + } else { + key = "name"; + } + + let q = driver.session(); + let statement = "MATCH (n:{} {{}:{name}}) SET n.{}={newprop} RETURN n".format(this.state.type.toTitleCase(), key, val) + + q.run(statement, {name: this.state.label, newprop: newval}).then(result => { + let props = result.records[0]._fields[0].properties; + let label = props.name; + delete props.name; + this.setState({ properties: props, label: label }); + }); + + } + updateHandler(attrName, newVal) { - console.log(attrName, newVal) + let key; + if (this.state.type === "ou") { + key = "guid"; + } else { + key = "name"; + } + let statement; + if (attrName === "serviceprincipalnames" && this.state.type === "user"){ + if (newVal[0] === "" && newVal.length === 1){ + newVal = [] + } + + if (newVal.length > 0){ + statement = "MATCH (n:{} {{}:{name}}) SET n.{}={newprop}, n.hasspn=true RETURN n".format(this.state.type.toTitleCase(), key, attrName) + }else{ + statement = "MATCH (n:{} {{}:{name}}) SET n.{}={newprop}, n.hasspn=false RETURN n".format(this.state.type.toTitleCase(), key, attrName) + } + }else{ + statement = "MATCH (n:{} {{}:{name}}) SET n.{}={newprop} RETURN n".format(this.state.type.toTitleCase(), key, attrName) + } + + let q = driver.session(); + q.run(statement, {name: this.state.label, newprop: newVal}).then(result => { + let props = result.records[0]._fields[0].properties; + let label = props.name; + delete props.name; + this.setState({ properties: props, label: label }); + }); } deletePropHandler(attrName) { @@ -73,10 +143,16 @@ export default class NodeEditor extends Component { let label = props.name; delete props.name; this.setState({ properties: props, label: label }); + q.close(); }.bind(this) ); } + removeValidation(){ + let input = $(this.refs.newAttrName); + input.css("border", "") + } + render() { return (
@@ -126,21 +202,24 @@ export default class NodeEditor extends Component {
-
- - - + x.preventDefault()} className="form-inline pull-right"> +
+ + + +
diff --git a/src/components/Float/NodeEditorRow.jsx b/src/components/Float/NodeEditorRow.jsx index 869812f..0c7f6eb 100644 --- a/src/components/Float/NodeEditorRow.jsx +++ b/src/components/Float/NodeEditorRow.jsx @@ -34,7 +34,7 @@ export default class NodeEditorRow extends Component { } else if (input.is("textarea")){ let tempval = val.join("\n"); input.attr("disabled", ""); - input.val(tempval); + input.val(tempval); }else { input.attr("disabled", ""); } @@ -42,18 +42,24 @@ export default class NodeEditorRow extends Component { } saveEdit() { - console.log(this.state); let input = jQuery(this.refs.input); let val; if (input.is("div")) { val = input.html(); + input.removeAttr("contenteditable"); } else if (input.is("textarea")){ - val = input.val(); + let tempval = input.val(); + val = tempval.split("\n"); + input.attr("disabled", ""); } else { val = this.state.val; + input.attr("disabled", ""); } - - console.log(val); + if (this.state.valtype === "number"){ + val = parseInt(val); + } + this.setState({editing: false}); + this.props.updateHandler(this.props.attributeName, val) } enableEdit() { diff --git a/src/components/Graph.jsx b/src/components/Graph.jsx index 4f0a6bf..efe229b 100644 --- a/src/components/Graph.jsx +++ b/src/components/Graph.jsx @@ -21,15 +21,24 @@ export default class GraphContainer extends Component { design: null, dragged: false, firstDraw: true, - template: null, + nodeTemplate: null, + edgeTemplate: null, session: driver.session() }; $.ajax({ - url: "src/components/tooltip.html", + url: "src/components/nodeTooltip.html", type: "GET", success: function(response) { - this.setState({ template: response }); + this.setState({ nodeTemplate: response }); + }.bind(this) + }); + + $.ajax({ + url: "src/components/edgeTooltip.html", + type: "GET", + success: function(response) { + this.setState({ edgeTemplate: response }); }.bind(this) }); @@ -140,6 +149,7 @@ export default class GraphContainer extends Component { emitter.on("zoomOut", this.zoomOut.bind(this)); emitter.on("changeNodeLabels", this.changeNodeLabelMode.bind(this)); emitter.on("changeEdgeLabels", this.changeEdgeLabelMode.bind(this)); + emitter.on("deleteEdgeConfirm", this.deleteEdge.bind(this)); } componentDidMount() { @@ -164,6 +174,26 @@ export default class GraphContainer extends Component { } } + deleteEdge(id){ + let instance = this.state.sigmaInstance; + let edge = instance.graph.edges(id); + let sourcenode = instance.graph.nodes(edge.source); + let targetnode = instance.graph.nodes(edge.target); + + + let sourcekey = sourcenode.type === "OU" ? 'guid' : 'name'; + let targetkey = targetnode.type === "OU" ? 'guid' : 'name'; + + let statement = "MATCH (n:{} {{}:{sname}}) MATCH (m:{} {{}:{tname}}) MATCH (n)-[r:{}]->(m) DELETE r".format(sourcenode.type, sourcekey, targetnode.type, targetkey, edge.label); + + instance.graph.dropEdge(edge.id); + instance.refresh(); + + let q = driver.session(); + q.run(statement, {sname: sourcenode.label, tname: targetnode.label}).then(x => {q.close()}) + + } + reload(){ this.doQueryNative(this.state.currentQuery); } @@ -858,6 +888,9 @@ export default class GraphContainer extends Component { sigmaInstance.settings({ edgeColor: "default", + edgeHoverColor: "default", + defaultEdgeHoverColor: "#000", + enableEdgeHovering: true, nodeColor: "default", minEdgeSize: 1, maxEdgeSize: 2.5, @@ -988,7 +1021,7 @@ export default class GraphContainer extends Component { cssClass: "new-tooltip", autoadjust: true, renderer: function(node) { - var template = this.state.template; + var template = this.state.nodeTemplate; node.expand = false; node.collapse = false; if ( @@ -1008,7 +1041,18 @@ export default class GraphContainer extends Component { return Mustache.render(template, node); }.bind(this) } - ] + ], + edge: [ + { + show: "rightClickEdge", + cssClass: "new-tooltip", + autoadjust: true, + renderer: function(edge){ + var template = this.state.edgeTemplate; + return Mustache.render(template, edge); + }.bind(this) + } + ] } ); diff --git a/src/components/Modals/DeleteEdgeModal.jsx b/src/components/Modals/DeleteEdgeModal.jsx new file mode 100644 index 0000000..2d23c5b --- /dev/null +++ b/src/components/Modals/DeleteEdgeModal.jsx @@ -0,0 +1,64 @@ +import React, { Component } from "react"; +import { Modal } from "react-bootstrap"; + +export default class DeleteEdgeModal extends Component { + constructor() { + super(); + this.state = { + open: false + }; + } + + closeModal() { + this.setState({ open: false }); + } + + confirmDelete() { + this.closeModal(); + emitter.emit("deleteEdgeConfirm", this.state.id); + } + + openModal(id) { + appStore.currentTooltip.close(); + this.setState({ open: true, id: id }); + } + + componentDidMount() { + emitter.on("deleteEdge", this.openModal.bind(this)); + } + + render() { + return ( + + + Delete Edge + + + +

Are you sure you want to delete this edge?

+
+ + + + + +
+ ); + } +} diff --git a/src/components/edgeTooltip.html b/src/components/edgeTooltip.html new file mode 100644 index 0000000..de2f179 --- /dev/null +++ b/src/components/edgeTooltip.html @@ -0,0 +1,8 @@ +
+ {{label}} +
+
    +
  • + Delete Edge +
  • +
\ No newline at end of file diff --git a/src/components/tooltip.html b/src/components/nodeTooltip.html similarity index 100% rename from src/components/tooltip.html rename to src/components/nodeTooltip.html diff --git a/src/css/styles.css b/src/css/styles.css index 2ced714..c6b776a 100644 --- a/src/css/styles.css +++ b/src/css/styles.css @@ -179,6 +179,11 @@ div.tooltip-inner-custom { box-shadow: 0 12px 15px 0 rgba(0, 0, 0, 0.2); } +.formButtonFix:focus{ + border: 1px solid #ccc !important; + box-shadow: inset 0 1px 1px rgba(0,0,0,.075) !important; +} + .heighthack{ height: 1px; }