Skip to content
Snippets Groups Projects
Commit d2691d83 authored by Alex Complojer's avatar Alex Complojer
Browse files

graph poc: fixes & first usable, monolithic thing that works

parent e88d2d13
No related branches found
No related tags found
1 merge request!2Draft: Preview Version / POC: A4F Graph Analyizer
Pipeline #11725 failed
<template>
<v-sheet>
<v-card>
<v-card-title>{{ startVertex }}</v-card-title>
<v-card-text>
<v-row>
<v-sheet class="pa-4" style="position: relative">
<div
class="legend"
style="
position: absolute;
bottom: 10px;
right: 10px;
border: 1px solid black;
padding: 20px;
font-weight: bold;
"
>
<div v-for="(color, key) in colors">
<v-icon :color="color" class="mr-2">mdi-circle</v-icon> {{ color_alias[key] }}
</div>
</div>
<v-row no-gutters>
<v-col cols="12">
<v-row vertical-align="center">
<v-col cols="2">
<v-select
v-model="architectures.current"
:items="architectures.items"
label="Arch. / Target machine"
clearable
disabled
>
</v-select>
</v-col>
<v-col
cols="6"
v-for="(selection, key) in selections"
v-if="filter_alias[selection.label]"
:key="key"
......@@ -12,27 +38,49 @@
<v-combobox
v-model="selection.current"
:items="selection.items"
chips
:label="filter_alias[selection.label]"
multiple
></v-combobox>
@blur="jumpToNode"
clearable
:disabled="Object.keys(selections['local_srcfile'].current).length > 0"
>
</v-combobox>
</v-col>
<v-col cols="2">
<v-checkbox
v-model="options.drawIncludes"
label="Draw includes"
@change="resetGraph"
></v-checkbox>
</v-col>
<v-spacer></v-spacer>
<v-col
><v-btn @click="resetHoverNode">reset selection</v-btn
><v-btn @click="jumpToNode">jump to</v-btn
><v-btn @click="draw">redraw graph</v-btn></v-col
>
<v-col cols="1" class="d-flex">
<v-spacer />
<v-btn
fab
class="mt-2 push-right"
small
@click="
overlay = true
resetGraph()
"
><v-icon>mdi-filter-remove</v-icon></v-btn
>
<v-btn fab class="mt-2 push-right ml-2" small @click="resetHoverNode"
><v-icon>mdi-eye</v-icon></v-btn
>
</v-col>
</v-row>
<div></div>
<div id="sigma-container" class="full-height" style="height: 800px; width: 100%">
<div id="sigma" class="full-height" style="height: 800px; width: 100%"></div>
</v-col>
<v-col cols="12">
<div id="sigma-container" class="full-height" style="height: 800px">
<div id="sigma" class="full-height" style="height: 800px; width: 60%; float: left"></div>
</div>
</v-card-text>
</v-col>
<v-overlay absolute :value="overlay">
<v-progress-circular indeterminate size="64"></v-progress-circular>
</v-overlay>
</v-card>
</v-row>
</v-sheet>
</template>
......@@ -53,8 +101,8 @@ export default {
compliance: false
},
filter_alias: {
local_binfile: "Filter Startvertex (local bins)",
local_srcfile: "Filter includes by source"
// local_binfile: "Filter Startvertex (local bins)",
local_srcfile: "Dig deeper into sources..."
},
graphstate: {},
selections: {},
......@@ -65,24 +113,39 @@ export default {
graph: false,
sigma: false,
vertexes: {},
detail_vertexes: {},
done: [],
edges: {},
graph: "test",
options: {
drawImplicitNodes: false
drawImplicitNodes: false,
drawIncludes: true
},
// fake it till you make it
architectures: {
items: ["qemu86_64"],
current: "qemu86_64"
},
colors: {
startnode: "#00edfc00",
binary: "#fcef5e00",
sourcefile: "#6714b000",
startnode: "#00edfc",
//binary: "#fcef5e",
sourcefile: "#6714b0",
upstrfile: "#fcef5e",
patch: "#00FF00",
include: "#90ce4400",
//patch: "#00FF00",
//include: "#90ce44",
elf: "#ff8800",
license: "#DDDDDD",
package: "#af6e3d",
//license: "#DDDDDD",
//package: "#af6e3d",
implicit: "#EEEEEE"
},
color_alias: {
startnode: "Start vertices (local_bin)",
sourcefile: "Local Sourcefiles",
upstrfile: "Upstream Sources",
patch: "",
elf: "Image elf data",
implicit: "Implicit nodes"
},
startVertex: ""
}
},
......@@ -112,6 +175,13 @@ export default {
this.draw()
},
methods: {
resetGraph: function() {
this.resetHoverNode()
this.edges["binary_matches"] = []
this.selections = {}
this.detail_vertexes = {}
this.draw()
},
async fetchGraph(db, name = "test", stream = false) {
const graph = db.graph(name)
......@@ -129,10 +199,7 @@ export default {
}
}
},
show(item) {
this.startVertex = item.name + "@" + item.version
this.draw()
},
show(item) {},
async fetchCollection(db = this.db, collection, edges = false) {
try {
const entries = await db.query(aql`
......@@ -159,13 +226,17 @@ export default {
if (!this.graph.hasNode(line._from) && this.options.drawImplicitNodes)
this.graph.addNode(line._from, {
nodeType: deactivate ? "implicit" : fromtype,
label: line[label]
label: line[label],
x: 0,
y: 0
})
if (!this.graph.hasNode(line._to) && this.options.drawImplicitNodes)
this.graph.addNode(line._to, {
nodeType: deactivate ? "implicit" : totype,
label: line[label]
label: line[label],
x: 0,
y: 0
})
if (
......@@ -189,63 +260,58 @@ export default {
}
},
drawVertices: function(name, type, label) {
this.vertexes[name].forEach(vertex => {
// TBD, just as showcase
if (name == "local_binfile") this.matchBangerBinary(vertex)
// fill selections
if (typeof this.selections[name] == "undefined")
this.selections[name] = { items: [], current: [] }
this.selections[name].items.push(vertex[label])
this.selections[name].label = name
// filter or draw vertices
var skip = false
for (var a = 0; a < this.selections[name].current.length; a++) {
console.log(this.selections[name].current)
if (!this.selections[name].current.includes(vertex[label])) {
console.log("skip " + vertex[label])
skip = true
break
var vertices =
Object.keys(this.detail_vertexes).length > 0 ? this.detail_vertexes : this.vertexes
if (vertices[name])
vertices[name].forEach(vertex => {
// TBD, just as showcase
if (name == "local_binfile") this.matchBangerBinary(vertex)
// fill selections
if (typeof this.selections[name] == "undefined")
this.selections[name] = { items: [], current: [] }
this.selections[name].items.push(vertex[label])
this.selections[name].label = name
// filter or draw vertices
var skip = false
for (var a = 0; a < this.selections[name].current.length; a++) {
if (!this.selections[name].current.includes(vertex[label])) {
console.log("skip " + vertex[label])
skip = true
break
}
}
}
if (skip) return
if (!this.graph.hasNode(vertex._id))
this.graph.addNode(vertex._id, {
nodeType: type,
label:
vertex[label].indexOf("/unpacked.gpt-partition1.part-0x00000000-squashfs-1") != -1
? vertex[label].replace("/unpacked.gpt-partition1.part-0x00000000-squashfs-1", "")
: vertex[label]
})
})
if (skip) return
var mylabel = !vertex[label] ? vertex["label"] : vertex[label]
if (!this.graph.hasNode(vertex._id))
this.graph.addNode(vertex._id, {
nodeType: type,
collection: name,
label:
mylabel.indexOf("/unpacked.gpt-partition1.part-0x00000000-squashfs-1") != -1
? mylabel.replace("/unpacked.gpt-partition1.part-0x00000000-squashfs-1", "")
: mylabel
})
})
},
draw: async function() {
console.log(this.vertexes)
console.log(this.edges)
this.refreshGraph()
this.overlay = true
this.edges["binary_matches"] = []
if (!!this.graph) {
this.graph.clear()
}
if (!!this.graph) this.graph.clear()
if (!!this.renderer) {
this.renderer.clear()
} else this.graph = new Graph()
if (!!this.renderer) this.renderer.clear()
else this.graph = new Graph()
// aggregate root, later on all paths will be created starting from this vertices
this.drawVertices("local_binfile", "startnode", "label")
// banger spdx feeder data
// this.drawVertices("package", "package", "name")
// this.drawVertices("license", "license", "name")
// this.drawVertices("file", "sourcefile", "name")
// aliens4friends data
this.drawVertices("local_srcfile", "sourcefile", "label")
this.drawVertices("upstr_srcfile", "upstrfile", "label")
......@@ -254,7 +320,7 @@ export default {
this.drawEdges("patch_applied_to", "patch", "sourcefile")
this.drawEdges("modified_from", "sourcefile", "upstrfile")
this.drawEdges("copy_of", "sourcefile", "upstrfile")
this.drawEdges("includes", "sourcefile", "sourcefile")
if (this.options.drawIncludes) this.drawEdges("includes", "sourcefile", "sourcefile")
// banger data
this.drawVertices("elf", "elf", "name")
......@@ -263,6 +329,11 @@ export default {
// this.drawEdges("compiledfrom", "elf", "package")
// this.drawEdges("containsfile", "package", "file")
// banger spdx feeder data
// this.drawVertices("package", "package", "name")
// this.drawVertices("license", "license", "name")
// this.drawVertices("file", "sourcefile", "name")
// calculated/mapped edges data: can be easily saved to arangodb...
this.drawEdges("binary_matches", "elf", "binary")
......@@ -275,14 +346,24 @@ export default {
const minDegree = Math.min(...degrees)
const maxDegree = Math.max(...degrees)
const minSize = 5,
maxSize = 20
maxSize = 50
this.graph.forEachNode(node => {
const degree = this.graph.degree(node)
this.graph.setNodeAttribute(
node,
"size",
minSize + ((degree - minDegree) / (maxDegree - minDegree)) * (maxSize - minSize)
)
const atts = this.graph.getNodeAttributes(node)
let size = minSize + ((degree - minDegree) / (maxDegree - minDegree)) * (maxSize - minSize)
if (atts["label"].indexOf(".patch") != -1) {
size = size + 10
this.graph.setNodeAttribute(node, "patch", true)
}
if (atts["collection"] == "local_binfile") {
size = size + 10
}
this.graph.setNodeAttribute(node, "size", size)
})
circular.assign(this.graph)
......@@ -324,12 +405,11 @@ export default {
renderer.setSetting("edgeReducer", (edge, data) => {
const res = { ...data }
if (
this.graphstate.hoveredNode &&
!this.graph.hasExtremity(edge, this.graphstate.hoveredNode)
) {
//res.hidden = true
res.hidden = true
}
if (
......@@ -362,6 +442,7 @@ export default {
resetHoverNode() {
this.graphstate.hoveredNode = undefined
this.graphstate.hoveredNeighbors = undefined
this.graphstate.suggestions = undefined
this.renderer.refresh()
},
refreshGraph() {
......@@ -375,9 +456,23 @@ export default {
p.appendChild(c)
},
getAllNeighbors(node, depth = 0) {
if (depth > 1) return []
var isPatch = this.graph.getNodeAttribute(node, "patch")
var type = this.graph.getNodeAttribute(node, "nodeType")
var dep = type == "startnode" || type == "elf" ? 3 : 1
if (depth > dep && !isPatch) return []
this.done.push(node)
console.log(depth + " " + node)
var nodeinfo = this.graph.getNodeAttribute(node, "collection")
if (typeof this.detail_vertexes[nodeinfo] == "undefined") this.detail_vertexes[nodeinfo] = []
this.detail_vertexes[nodeinfo].push({
...this.graph.getNodeAttributes(node),
_id: node,
x: 0,
y: 0
})
var neighbors = this.graph.neighbors(node)
neighbors.forEach(val => {
if (this.done.includes(val)) return
......@@ -387,8 +482,11 @@ export default {
return neighbors
},
jumpToNode() {
if (this.selections["local_srcfile"].current.length > 0) {
var needle = this.selections["local_srcfile"].current[0]
this.resetHoverNode()
this.detail_vertexes = {}
this.selections["local_srcfile"].current.forEach(obj => {
var needle = obj
if (needle.length == 0) return
......@@ -397,19 +495,15 @@ export default {
.map(n => ({ id: n, label: this.graph.getNodeAttribute(n, "label") }))
.filter(({ label }) => label.toLowerCase().includes(needle.toLowerCase()))
console.log("JUMP")
console.log(node[0].id)
if (node.length > 0) {
this.graphstate.hoveredNeighbors = new Set(this.getAllNeighbors(node[0].id))
this.graphstate.suggestions = this.graphstate.hoveredNeighbors
this.selections = {}
this.draw()
}
this.graphstate.hoveredNode = node[0].id
this.graphstate.hoveredNeighbors = new Set(this.getAllNeighbors(node[0].id))
console.log(this.graphstate.hoveredNeighbors)
this.graphstate.suggestions = new Set(this.getAllNeighbors(node[0].id))
this.renderer.refresh()
const nodePosition = this.renderer.getNodeDisplayData(node[0].id)
this.renderer.getCamera().animate(nodePosition, {
duration: 500
})
}
})
}
}
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment