Add start of POC
This commit is contained in:
parent
7228f8b7c0
commit
25adf0f374
7 changed files with 1082 additions and 0 deletions
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
|||
module git.faercol.me/faercol/topology-map
|
||||
|
||||
go 1.23.1
|
212
main.go
Normal file
212
main.go
Normal file
|
@ -0,0 +1,212 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const apiKey = "4e75b8927940adc29e2e1eac042bf92bcddd57fe"
|
||||
const netboxBaseURL = "https://netbox.internal.faercol.me/api/"
|
||||
|
||||
type Device struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type Object struct {
|
||||
Device Device `json:"device"`
|
||||
}
|
||||
|
||||
type CableTermination struct {
|
||||
Object Object `json:"object"`
|
||||
}
|
||||
|
||||
type Cable struct {
|
||||
ID int `json:"id"`
|
||||
ATerminations []CableTermination `json:"a_terminations"`
|
||||
BTerminations []CableTermination `json:"b_terminations"`
|
||||
}
|
||||
|
||||
type VM struct {
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Device Device `json:"device"`
|
||||
}
|
||||
|
||||
type vmsResponse struct {
|
||||
Results []VM `json:"results"`
|
||||
}
|
||||
|
||||
type deviceResponse struct {
|
||||
Results []Device `json:"results"`
|
||||
}
|
||||
|
||||
type cableResponse struct {
|
||||
Results []Cable `json:"results"`
|
||||
}
|
||||
|
||||
type ElementData struct {
|
||||
ID string `json:"id"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Target string `json:"target,omitempty"`
|
||||
Parent string `json:"parent,omitempty"`
|
||||
}
|
||||
|
||||
type Element struct {
|
||||
Data ElementData `json:"data"`
|
||||
Classes []string `json:"classes"`
|
||||
}
|
||||
|
||||
func GetDevices() ([]Device, error) {
|
||||
query, err := http.NewRequest("GET", netboxBaseURL+"dcim/devices", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query.Header.Set("Authorization", "Token "+apiKey)
|
||||
|
||||
resp, err := http.DefaultClient.Do(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res deviceResponse
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(respBody, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Results, nil
|
||||
}
|
||||
|
||||
func GetCables() ([]Cable, error) {
|
||||
query, err := http.NewRequest("GET", netboxBaseURL+"dcim/cables", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query.Header.Set("Authorization", "Token "+apiKey)
|
||||
|
||||
resp, err := http.DefaultClient.Do(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res cableResponse
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(respBody, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Results, nil
|
||||
}
|
||||
|
||||
func GetVMs() ([]VM, error) {
|
||||
query, err := http.NewRequest("GET", netboxBaseURL+"virtualization/virtual-machines", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query.Header.Set("Authorization", "Token "+apiKey)
|
||||
|
||||
resp, err := http.DefaultClient.Do(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var res vmsResponse
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(respBody, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return res.Results, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
srv := http.NewServeMux()
|
||||
|
||||
srv.HandleFunc("/static/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Serving static file")
|
||||
fs := http.FileServer(http.Dir("./static"))
|
||||
http.StripPrefix("/static", fs).ServeHTTP(w, r)
|
||||
})
|
||||
srv.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Serving API route")
|
||||
devices, err := GetDevices()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to get devices: %s\n", err)
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
cables, err := GetCables()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to get cables: %s\n", err)
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
vms, err := GetVMs()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to get VMs: %s\n", err)
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
resp := []Element{}
|
||||
for _, d := range devices {
|
||||
resp = append(resp, Element{Data: ElementData{ID: d.Name}})
|
||||
}
|
||||
for _, c := range cables {
|
||||
resp = append(resp, Element{Data: ElementData{ID: "link-" + strconv.FormatInt(int64(c.ID), 10), Source: c.ATerminations[0].Object.Device.Name, Target: c.BTerminations[0].Object.Device.Name}})
|
||||
}
|
||||
for _, v := range vms {
|
||||
resp = append(resp, Element{Data: ElementData{ID: v.Name}}, Element{Data: ElementData{ID: "vm-" + strconv.FormatInt(int64(v.ID), 10), Source: v.Name, Target: v.Device.Name}, Classes: []string{"edge", "virtual-edge"}})
|
||||
}
|
||||
|
||||
respBody, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to serialize data: %s\n", err)
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(respBody)
|
||||
})
|
||||
srv.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Println("Serving main root")
|
||||
tpl, err := template.New("index.html").ParseFiles("templates/index.html")
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to read template: %s\n", err)
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
if err := tpl.Execute(w, nil); err != nil {
|
||||
fmt.Printf("Failed to execute template: %s\n", err)
|
||||
w.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
if err := http.ListenAndServe("127.0.0.1:5000", srv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
753
static/scripts/cytoscape-cola.js
Normal file
753
static/scripts/cytoscape-cola.js
Normal file
|
@ -0,0 +1,753 @@
|
|||
(function webpackUniversalModuleDefinition(root, factory) {
|
||||
if (typeof exports === 'object' && typeof module === 'object')
|
||||
module.exports = factory(require("webcola"));
|
||||
else if (typeof define === 'function' && define.amd)
|
||||
define(["webcola"], factory);
|
||||
else if (typeof exports === 'object')
|
||||
exports["cytoscapeCola"] = factory(require("webcola"));
|
||||
else
|
||||
root["cytoscapeCola"] = factory(root["webcola"]);
|
||||
})(this, function (__WEBPACK_EXTERNAL_MODULE_5__) {
|
||||
return /******/ (function (modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if (installedModules[moduleId]) {
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
/******/
|
||||
}
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/
|
||||
};
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/
|
||||
}
|
||||
/******/
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
/******/
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
/******/
|
||||
/******/ // identity function for calling harmony imports with the correct context
|
||||
/******/ __webpack_require__.i = function (value) { return value; };
|
||||
/******/
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function (exports, name, getter) {
|
||||
/******/ if (!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, {
|
||||
/******/ configurable: false,
|
||||
/******/ enumerable: true,
|
||||
/******/ get: getter
|
||||
/******/
|
||||
});
|
||||
/******/
|
||||
}
|
||||
/******/
|
||||
};
|
||||
/******/
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function (module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/
|
||||
};
|
||||
/******/
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function (object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
/******/
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "";
|
||||
/******/
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 3);
|
||||
/******/
|
||||
})
|
||||
/************************************************************************/
|
||||
/******/([
|
||||
/* 0 */
|
||||
/***/ (function (module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
||||
|
||||
var assign = __webpack_require__(1);
|
||||
var defaults = __webpack_require__(2);
|
||||
var cola = __webpack_require__(5) || (typeof window !== 'undefined' ? window.cola : null);
|
||||
var raf = __webpack_require__(4);
|
||||
var isString = function isString(o) {
|
||||
return (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === _typeof('');
|
||||
};
|
||||
var isNumber = function isNumber(o) {
|
||||
return (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === _typeof(0);
|
||||
};
|
||||
var isObject = function isObject(o) {
|
||||
return o != null && (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === _typeof({});
|
||||
};
|
||||
var isFunction = function isFunction(o) {
|
||||
return o != null && (typeof o === 'undefined' ? 'undefined' : _typeof(o)) === _typeof(function () { });
|
||||
};
|
||||
var nop = function nop() { };
|
||||
|
||||
var getOptVal = function getOptVal(val, ele) {
|
||||
if (isFunction(val)) {
|
||||
var fn = val;
|
||||
return fn.apply(ele, [ele]);
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
// constructor
|
||||
// options : object containing layout options
|
||||
function ColaLayout(options) {
|
||||
this.options = assign({}, defaults, options);
|
||||
}
|
||||
|
||||
// runs the layout
|
||||
ColaLayout.prototype.run = function () {
|
||||
var layout = this;
|
||||
var options = this.options;
|
||||
|
||||
layout.manuallyStopped = false;
|
||||
|
||||
var cy = options.cy; // cy is automatically populated for us in the constructor
|
||||
var eles = options.eles;
|
||||
var nodes = eles.nodes();
|
||||
var edges = eles.edges();
|
||||
var ready = false;
|
||||
|
||||
var isParent = function isParent(ele) {
|
||||
return ele.isParent();
|
||||
};
|
||||
|
||||
var parentNodes = nodes.filter(isParent);
|
||||
|
||||
var nonparentNodes = nodes.subtract(parentNodes);
|
||||
|
||||
var bb = options.boundingBox || { x1: 0, y1: 0, w: cy.width(), h: cy.height() };
|
||||
if (bb.x2 === undefined) {
|
||||
bb.x2 = bb.x1 + bb.w;
|
||||
}
|
||||
if (bb.w === undefined) {
|
||||
bb.w = bb.x2 - bb.x1;
|
||||
}
|
||||
if (bb.y2 === undefined) {
|
||||
bb.y2 = bb.y1 + bb.h;
|
||||
}
|
||||
if (bb.h === undefined) {
|
||||
bb.h = bb.y2 - bb.y1;
|
||||
}
|
||||
|
||||
var updateNodePositions = function updateNodePositions() {
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
var node = nodes[i];
|
||||
var dimensions = node.layoutDimensions(options);
|
||||
var scratch = node.scratch('cola');
|
||||
|
||||
// update node dims
|
||||
if (!scratch.updatedDims) {
|
||||
var padding = getOptVal(options.nodeSpacing, node);
|
||||
|
||||
scratch.width = dimensions.w + 2 * padding;
|
||||
scratch.height = dimensions.h + 2 * padding;
|
||||
}
|
||||
}
|
||||
|
||||
nodes.positions(function (node) {
|
||||
var scratch = node.scratch().cola;
|
||||
var retPos = void 0;
|
||||
|
||||
if (!node.grabbed() && nonparentNodes.contains(node)) {
|
||||
retPos = {
|
||||
x: bb.x1 + scratch.x,
|
||||
y: bb.y1 + scratch.y
|
||||
};
|
||||
|
||||
if (!isNumber(retPos.x) || !isNumber(retPos.y)) {
|
||||
retPos = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return retPos;
|
||||
});
|
||||
|
||||
nodes.updateCompoundBounds(); // because the way this layout sets positions is buggy for some reason; ref #878
|
||||
|
||||
if (!ready) {
|
||||
onReady();
|
||||
ready = true;
|
||||
}
|
||||
|
||||
if (options.fit) {
|
||||
cy.fit(options.padding);
|
||||
}
|
||||
};
|
||||
|
||||
var onDone = function onDone() {
|
||||
if (options.ungrabifyWhileSimulating) {
|
||||
grabbableNodes.grabify();
|
||||
}
|
||||
|
||||
cy.off('destroy', destroyHandler);
|
||||
|
||||
nodes.off('grab free position', grabHandler);
|
||||
nodes.off('lock unlock', lockHandler);
|
||||
|
||||
// trigger layoutstop when the layout stops (e.g. finishes)
|
||||
layout.one('layoutstop', options.stop);
|
||||
layout.trigger({ type: 'layoutstop', layout: layout });
|
||||
};
|
||||
|
||||
var onReady = function onReady() {
|
||||
// trigger layoutready when each node has had its position set at least once
|
||||
layout.one('layoutready', options.ready);
|
||||
layout.trigger({ type: 'layoutready', layout: layout });
|
||||
};
|
||||
|
||||
var ticksPerFrame = options.refresh;
|
||||
|
||||
if (options.refresh < 0) {
|
||||
ticksPerFrame = 1;
|
||||
} else {
|
||||
ticksPerFrame = Math.max(1, ticksPerFrame); // at least 1
|
||||
}
|
||||
|
||||
var adaptor = layout.adaptor = cola.adaptor({
|
||||
trigger: function trigger(e) {
|
||||
// on sim event
|
||||
var TICK = cola.EventType ? cola.EventType.tick : null;
|
||||
var END = cola.EventType ? cola.EventType.end : null;
|
||||
|
||||
switch (e.type) {
|
||||
case 'tick':
|
||||
case TICK:
|
||||
if (options.animate) {
|
||||
updateNodePositions();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'end':
|
||||
case END:
|
||||
updateNodePositions();
|
||||
if (!options.infinite) {
|
||||
onDone();
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
kick: function kick() {
|
||||
// kick off the simulation
|
||||
//let skip = 0;
|
||||
|
||||
var firstTick = true;
|
||||
|
||||
var inftick = function inftick() {
|
||||
if (layout.manuallyStopped) {
|
||||
onDone();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
var ret = adaptor.tick();
|
||||
|
||||
if (!options.infinite && !firstTick) {
|
||||
adaptor.convergenceThreshold(options.convergenceThreshold);
|
||||
}
|
||||
|
||||
firstTick = false;
|
||||
|
||||
if (ret && options.infinite) {
|
||||
// resume layout if done
|
||||
adaptor.resume(); // resume => new kick
|
||||
}
|
||||
|
||||
return ret; // allow regular finish b/c of new kick
|
||||
};
|
||||
|
||||
var multitick = function multitick() {
|
||||
// multiple ticks in a row
|
||||
var ret = void 0;
|
||||
|
||||
for (var i = 0; i < ticksPerFrame && !ret; i++) {
|
||||
ret = ret || inftick(); // pick up true ret vals => sim done
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
if (options.animate) {
|
||||
var frame = function frame() {
|
||||
if (multitick()) {
|
||||
return;
|
||||
}
|
||||
|
||||
raf(frame);
|
||||
};
|
||||
|
||||
raf(frame);
|
||||
} else {
|
||||
while (!inftick()) {
|
||||
// keep going...
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
on: nop, // dummy; not needed
|
||||
|
||||
drag: nop // not needed for our case
|
||||
});
|
||||
layout.adaptor = adaptor;
|
||||
|
||||
// if set no grabbing during layout
|
||||
var grabbableNodes = nodes.filter(':grabbable');
|
||||
if (options.ungrabifyWhileSimulating) {
|
||||
grabbableNodes.ungrabify();
|
||||
}
|
||||
|
||||
var destroyHandler = void 0;
|
||||
cy.one('destroy', destroyHandler = function destroyHandler() {
|
||||
layout.stop();
|
||||
});
|
||||
|
||||
// handle node dragging
|
||||
var grabHandler = void 0;
|
||||
nodes.on('grab free position', grabHandler = function grabHandler(e) {
|
||||
var node = this;
|
||||
var scrCola = node.scratch().cola;
|
||||
var pos = node.position();
|
||||
var nodeIsTarget = e.cyTarget === node || e.target === node;
|
||||
|
||||
if (!nodeIsTarget) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (e.type) {
|
||||
case 'grab':
|
||||
adaptor.dragstart(scrCola);
|
||||
break;
|
||||
case 'free':
|
||||
adaptor.dragend(scrCola);
|
||||
break;
|
||||
case 'position':
|
||||
// only update when different (i.e. manual .position() call or drag) so we don't loop needlessly
|
||||
if (scrCola.px !== pos.x - bb.x1 || scrCola.py !== pos.y - bb.y1) {
|
||||
scrCola.px = pos.x - bb.x1;
|
||||
scrCola.py = pos.y - bb.y1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
var lockHandler = void 0;
|
||||
nodes.on('lock unlock', lockHandler = function lockHandler() {
|
||||
var node = this;
|
||||
var scrCola = node.scratch().cola;
|
||||
|
||||
scrCola.fixed = node.locked();
|
||||
|
||||
if (node.locked()) {
|
||||
adaptor.dragstart(scrCola);
|
||||
} else {
|
||||
adaptor.dragend(scrCola);
|
||||
}
|
||||
});
|
||||
|
||||
// add nodes to cola
|
||||
adaptor.nodes(nonparentNodes.map(function (node, i) {
|
||||
var padding = getOptVal(options.nodeSpacing, node);
|
||||
var pos = node.position();
|
||||
var dimensions = node.layoutDimensions(options);
|
||||
|
||||
var struct = node.scratch().cola = {
|
||||
x: options.randomize && !node.locked() || pos.x === undefined ? Math.round(Math.random() * bb.w) : pos.x,
|
||||
y: options.randomize && !node.locked() || pos.y === undefined ? Math.round(Math.random() * bb.h) : pos.y,
|
||||
width: dimensions.w + 2 * padding,
|
||||
height: dimensions.h + 2 * padding,
|
||||
index: i,
|
||||
fixed: node.locked()
|
||||
};
|
||||
|
||||
return struct;
|
||||
}));
|
||||
|
||||
// the constraints to be added on nodes
|
||||
var constraints = [];
|
||||
|
||||
if (options.alignment) {
|
||||
// then set alignment constraints
|
||||
|
||||
if (options.alignment.vertical) {
|
||||
var verticalAlignments = options.alignment.vertical;
|
||||
verticalAlignments.forEach(function (alignment) {
|
||||
var offsetsX = [];
|
||||
alignment.forEach(function (nodeData) {
|
||||
var node = nodeData.node;
|
||||
var scrCola = node.scratch().cola;
|
||||
var index = scrCola.index;
|
||||
offsetsX.push({
|
||||
node: index,
|
||||
offset: nodeData.offset ? nodeData.offset : 0
|
||||
});
|
||||
});
|
||||
constraints.push({
|
||||
type: 'alignment',
|
||||
axis: 'x',
|
||||
offsets: offsetsX
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (options.alignment.horizontal) {
|
||||
var horizontalAlignments = options.alignment.horizontal;
|
||||
horizontalAlignments.forEach(function (alignment) {
|
||||
var offsetsY = [];
|
||||
alignment.forEach(function (nodeData) {
|
||||
var node = nodeData.node;
|
||||
var scrCola = node.scratch().cola;
|
||||
var index = scrCola.index;
|
||||
offsetsY.push({
|
||||
node: index,
|
||||
offset: nodeData.offset ? nodeData.offset : 0
|
||||
});
|
||||
});
|
||||
constraints.push({
|
||||
type: 'alignment',
|
||||
axis: 'y',
|
||||
offsets: offsetsY
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// if gapInequalities variable is set add each inequality constraint to list of constraints
|
||||
if (options.gapInequalities) {
|
||||
options.gapInequalities.forEach(function (inequality) {
|
||||
|
||||
// for the constraints to be passed to cola layout adaptor use indices of nodes,
|
||||
// not the nodes themselves
|
||||
var leftIndex = inequality.left.scratch().cola.index;
|
||||
var rightIndex = inequality.right.scratch().cola.index;
|
||||
|
||||
constraints.push({
|
||||
axis: inequality.axis,
|
||||
left: leftIndex,
|
||||
right: rightIndex,
|
||||
gap: inequality.gap,
|
||||
equality: inequality.equality
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// add constraints if any
|
||||
if (constraints.length > 0) {
|
||||
adaptor.constraints(constraints);
|
||||
}
|
||||
|
||||
// add compound nodes to cola
|
||||
adaptor.groups(parentNodes.map(function (node, i) {
|
||||
// add basic group incl leaf nodes
|
||||
var optPadding = getOptVal(options.nodeSpacing, node);
|
||||
var getPadding = function getPadding(d) {
|
||||
return parseFloat(node.style('padding-' + d));
|
||||
};
|
||||
|
||||
var pleft = getPadding('left') + optPadding;
|
||||
var pright = getPadding('right') + optPadding;
|
||||
var ptop = getPadding('top') + optPadding;
|
||||
var pbottom = getPadding('bottom') + optPadding;
|
||||
|
||||
node.scratch().cola = {
|
||||
index: i,
|
||||
|
||||
padding: Math.max(pleft, pright, ptop, pbottom),
|
||||
|
||||
// leaves should only contain direct descendants (children),
|
||||
// not the leaves of nested compound nodes or any nodes that are compounds themselves
|
||||
leaves: node.children().intersection(nonparentNodes).map(function (child) {
|
||||
return child[0].scratch().cola.index;
|
||||
}),
|
||||
|
||||
fixed: node.locked()
|
||||
};
|
||||
|
||||
return node;
|
||||
}).map(function (node) {
|
||||
// add subgroups
|
||||
node.scratch().cola.groups = node.children().intersection(parentNodes).map(function (child) {
|
||||
return child.scratch().cola.index;
|
||||
});
|
||||
|
||||
return node.scratch().cola;
|
||||
}));
|
||||
|
||||
// get the edge length setting mechanism
|
||||
var length = void 0;
|
||||
var lengthFnName = void 0;
|
||||
if (options.edgeLength != null) {
|
||||
length = options.edgeLength;
|
||||
lengthFnName = 'linkDistance';
|
||||
} else if (options.edgeSymDiffLength != null) {
|
||||
length = options.edgeSymDiffLength;
|
||||
lengthFnName = 'symmetricDiffLinkLengths';
|
||||
} else if (options.edgeJaccardLength != null) {
|
||||
length = options.edgeJaccardLength;
|
||||
lengthFnName = 'jaccardLinkLengths';
|
||||
} else {
|
||||
length = 100;
|
||||
lengthFnName = 'linkDistance';
|
||||
}
|
||||
|
||||
var lengthGetter = function lengthGetter(link) {
|
||||
return link.calcLength;
|
||||
};
|
||||
|
||||
// add the edges to cola
|
||||
adaptor.links(edges.stdFilter(function (edge) {
|
||||
return nonparentNodes.contains(edge.source()) && nonparentNodes.contains(edge.target());
|
||||
}).map(function (edge) {
|
||||
var c = edge.scratch().cola = {
|
||||
source: edge.source()[0].scratch().cola.index,
|
||||
target: edge.target()[0].scratch().cola.index
|
||||
};
|
||||
|
||||
if (length != null) {
|
||||
c.calcLength = getOptVal(length, edge);
|
||||
}
|
||||
|
||||
return c;
|
||||
}));
|
||||
|
||||
adaptor.size([bb.w, bb.h]);
|
||||
|
||||
if (length != null) {
|
||||
adaptor[lengthFnName](lengthGetter);
|
||||
}
|
||||
|
||||
// set the flow of cola
|
||||
if (options.flow) {
|
||||
var flow = void 0;
|
||||
var defAxis = 'y';
|
||||
var defMinSep = 50;
|
||||
|
||||
if (isString(options.flow)) {
|
||||
flow = {
|
||||
axis: options.flow,
|
||||
minSeparation: defMinSep
|
||||
};
|
||||
} else if (isNumber(options.flow)) {
|
||||
flow = {
|
||||
axis: defAxis,
|
||||
minSeparation: options.flow
|
||||
};
|
||||
} else if (isObject(options.flow)) {
|
||||
flow = options.flow;
|
||||
|
||||
flow.axis = flow.axis || defAxis;
|
||||
flow.minSeparation = flow.minSeparation != null ? flow.minSeparation : defMinSep;
|
||||
} else {
|
||||
// e.g. options.flow: true
|
||||
flow = {
|
||||
axis: defAxis,
|
||||
minSeparation: defMinSep
|
||||
};
|
||||
}
|
||||
|
||||
adaptor.flowLayout(flow.axis, flow.minSeparation);
|
||||
}
|
||||
|
||||
layout.trigger({ type: 'layoutstart', layout: layout });
|
||||
|
||||
adaptor.avoidOverlaps(options.avoidOverlap).handleDisconnected(options.handleDisconnected).start(options.unconstrIter, options.userConstIter, options.allConstIter, undefined, // gridSnapIterations = 0
|
||||
undefined, // keepRunning = true
|
||||
options.centerGraph);
|
||||
|
||||
if (!options.infinite) {
|
||||
setTimeout(function () {
|
||||
if (!layout.manuallyStopped) {
|
||||
adaptor.stop();
|
||||
}
|
||||
}, options.maxSimulationTime);
|
||||
}
|
||||
|
||||
return this; // chaining
|
||||
};
|
||||
|
||||
// called on continuous layouts to stop them before they finish
|
||||
ColaLayout.prototype.stop = function () {
|
||||
if (this.adaptor) {
|
||||
this.manuallyStopped = true;
|
||||
this.adaptor.stop();
|
||||
}
|
||||
|
||||
return this; // chaining
|
||||
};
|
||||
|
||||
module.exports = ColaLayout;
|
||||
|
||||
/***/
|
||||
}),
|
||||
/* 1 */
|
||||
/***/ (function (module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
// Simple, internal Object.assign() polyfill for options objects etc.
|
||||
|
||||
module.exports = Object.assign != null ? Object.assign.bind(Object) : function (tgt) {
|
||||
for (var _len = arguments.length, srcs = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
|
||||
srcs[_key - 1] = arguments[_key];
|
||||
}
|
||||
|
||||
srcs.filter(function (src) {
|
||||
return src != null;
|
||||
}).forEach(function (src) {
|
||||
Object.keys(src).forEach(function (k) {
|
||||
return tgt[k] = src[k];
|
||||
});
|
||||
});
|
||||
|
||||
return tgt;
|
||||
};
|
||||
|
||||
/***/
|
||||
}),
|
||||
/* 2 */
|
||||
/***/ (function (module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
// default layout options
|
||||
var defaults = {
|
||||
animate: true, // whether to show the layout as it's running
|
||||
refresh: 1, // number of ticks per frame; higher is faster but more jerky
|
||||
maxSimulationTime: 4000, // max length in ms to run the layout
|
||||
ungrabifyWhileSimulating: false, // so you can't drag nodes during layout
|
||||
fit: true, // on every layout reposition of nodes, fit the viewport
|
||||
padding: 30, // padding around the simulation
|
||||
boundingBox: undefined, // constrain layout bounds; { x1, y1, x2, y2 } or { x1, y1, w, h }
|
||||
nodeDimensionsIncludeLabels: false, // whether labels should be included in determining the space used by a node
|
||||
|
||||
// layout event callbacks
|
||||
ready: function ready() { }, // on layoutready
|
||||
stop: function stop() { }, // on layoutstop
|
||||
|
||||
// positioning options
|
||||
randomize: false, // use random node positions at beginning of layout
|
||||
avoidOverlap: true, // if true, prevents overlap of node bounding boxes
|
||||
handleDisconnected: true, // if true, avoids disconnected components from overlapping
|
||||
convergenceThreshold: 0.01, // when the alpha value (system energy) falls below this value, the layout stops
|
||||
nodeSpacing: function nodeSpacing(node) {
|
||||
return 10;
|
||||
}, // extra spacing around nodes
|
||||
flow: undefined, // use DAG/tree flow layout if specified, e.g. { axis: 'y', minSeparation: 30 }
|
||||
alignment: undefined, // relative alignment constraints on nodes, e.g. function( node ){ return { x: 0, y: 1 } }
|
||||
gapInequalities: undefined, // list of inequality constraints for the gap between the nodes, e.g. [{"axis":"y", "left":node1, "right":node2, "gap":25}]
|
||||
centerGraph: true, // adjusts the node positions initially to center the graph (pass false if you want to start the layout from the current position)
|
||||
|
||||
|
||||
// different methods of specifying edge length
|
||||
// each can be a constant numerical value or a function like `function( edge ){ return 2; }`
|
||||
edgeLength: undefined, // sets edge length directly in simulation
|
||||
edgeSymDiffLength: undefined, // symmetric diff edge length in simulation
|
||||
edgeJaccardLength: undefined, // jaccard edge length in simulation
|
||||
|
||||
// iterations of cola algorithm; uses default values on undefined
|
||||
unconstrIter: undefined, // unconstrained initial layout iterations
|
||||
userConstIter: undefined, // initial layout iterations with user-specified constraints
|
||||
allConstIter: undefined, // initial layout iterations with all constraints including non-overlap
|
||||
|
||||
// infinite layout options
|
||||
infinite: false // overrides all other options for a forces-all-the-time mode
|
||||
};
|
||||
|
||||
module.exports = defaults;
|
||||
|
||||
/***/
|
||||
}),
|
||||
/* 3 */
|
||||
/***/ (function (module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
var impl = __webpack_require__(0);
|
||||
|
||||
// registers the extension on a cytoscape lib ref
|
||||
var register = function register(cytoscape) {
|
||||
if (!cytoscape) {
|
||||
return;
|
||||
} // can't register if cytoscape unspecified
|
||||
|
||||
cytoscape('layout', 'cola', impl); // register with cytoscape.js
|
||||
};
|
||||
|
||||
if (typeof cytoscape !== 'undefined') {
|
||||
// expose to global cytoscape (i.e. window.cytoscape)
|
||||
register(cytoscape);
|
||||
}
|
||||
|
||||
module.exports = register;
|
||||
|
||||
/***/
|
||||
}),
|
||||
/* 4 */
|
||||
/***/ (function (module, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
|
||||
|
||||
var raf = void 0;
|
||||
|
||||
if ((typeof window === "undefined" ? "undefined" : _typeof(window)) !== (true ? "undefined" : _typeof(undefined))) {
|
||||
raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function (fn) {
|
||||
return setTimeout(fn, 16);
|
||||
};
|
||||
} else {
|
||||
// if not available, all you get is immediate calls
|
||||
raf = function raf(cb) {
|
||||
cb();
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = raf;
|
||||
|
||||
/***/
|
||||
}),
|
||||
/* 5 */
|
||||
/***/ (function (module, exports) {
|
||||
|
||||
module.exports = __WEBPACK_EXTERNAL_MODULE_5__;
|
||||
|
||||
/***/
|
||||
})
|
||||
/******/]);
|
||||
});
|
||||
|
77
static/scripts/index.js
Normal file
77
static/scripts/index.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
// import cola from 'cytoscape-cola';
|
||||
|
||||
async function getData() {
|
||||
try {
|
||||
const resp = await fetch("/api/data");
|
||||
if (!resp.ok) {
|
||||
throw new Error(`Response status: ${resp.status}`);
|
||||
}
|
||||
|
||||
const json = await resp.json();
|
||||
return json;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
function setupGraph() {
|
||||
|
||||
getData()
|
||||
.catch(function (err) { console.error(err) })
|
||||
.then(function (elements) {
|
||||
|
||||
var cy = window.cy = cytoscape({
|
||||
|
||||
container: document.getElementById('map-container'), // container to render in
|
||||
|
||||
elements: elements,
|
||||
// elements: [ // list of graph elements to start with
|
||||
// { // node a
|
||||
// data: { id: 'a' }
|
||||
// },
|
||||
// { // node b
|
||||
// data: { id: 'b' }
|
||||
// },
|
||||
// { // edge ab
|
||||
// data: { id: 'ab', source: 'a', target: 'b' }
|
||||
// }
|
||||
// ],
|
||||
|
||||
style: [ // the stylesheet for the graph
|
||||
{
|
||||
selector: 'node',
|
||||
style: {
|
||||
'background-color': '#666',
|
||||
'label': 'data(id)'
|
||||
}
|
||||
},
|
||||
|
||||
{
|
||||
selector: 'edge',
|
||||
style: {
|
||||
'width': 1,
|
||||
'line-color': '#1E88E5',
|
||||
'target-arrow-color': '#1E88E5',
|
||||
'target-arrow-shape': 'none',
|
||||
'curve-style': 'bezier'
|
||||
}
|
||||
},
|
||||
{
|
||||
selector: ".virtual-edge",
|
||||
style: {
|
||||
"line-color": "#BDBDBD",
|
||||
"line-style": "dashed",
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
layout: {
|
||||
name: 'cola',
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', setupGraph);
|
9
static/style/main.css
Normal file
9
static/style/main.css
Normal file
|
@ -0,0 +1,9 @@
|
|||
#map-container {
|
||||
width: 80%;
|
||||
height: 600px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.edge {
|
||||
color: pink;
|
||||
}
|
28
templates/index.html
Normal file
28
templates/index.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="/static/style/main.css">
|
||||
|
||||
<!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.30.2/cytoscape.min.js"
|
||||
integrity="sha512-EY3U1MWdgKx0P1dqTE4inlKz2cpXtWpsR1YUyD855Hs6RL/A0cyvrKh60EpE8wDZ814cTe1KgRK+sG0Rn792vQ=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script> -->
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.30.2/cytoscape.umd.js"
|
||||
integrity="sha512-j0/d+W9EL6BFFGcM3YGwI10MsaTihhvlrvZoTLff+plAOEC78IgiJZGx/0ZGMpjHqrlp5si4JpdNtcDMywep4A=="
|
||||
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="https://unpkg.com/webcola/WebCola/cola.min.js"></script>
|
||||
<script src="/static/scripts/cytoscape-cola.js"></script>
|
||||
<script src="/static/scripts/index.js"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Coucou</h1>
|
||||
|
||||
<div id="map-container"></div>
|
||||
</body>
|
||||
|
||||
</html>
|
BIN
topology-map
Executable file
BIN
topology-map
Executable file
Binary file not shown.
Loading…
Reference in a new issue