Skip to content

Skadi User Guide

Installation

Skadi is open source software released under the MIT License. For installation instructions and to view the source code, see https://codeberg.org/visual-topology/skadi.

Introduction

Skadi

Skadi is a front end (HTML/CSS/JS) web application which manages the construction of topologies (graphs containing nodes and links).

Skadi is used as the topology editor component in Telesto - where topologies (implemented in Python or Javascript) are stored on and executed on the server.

Skadi also embeds Hyrrokkin's Javascript Engine and can host the storage of and execution of topologies (implemented in Javascript) within the browser (no server required). This usage of skadi is termed standalone mode.

For more information on topologies, packages, nodes and configurations - please see the Hyrrokkin documentation (https://visualtopology.org/docs/hyrrokkin)

Skadi extends the definition of nodes and configurations to include extra information:

  • how nodes and links should be displayed in the topology designer application
  • define client web-pages that can be used to interact with nodes or configurations within a topology

Main components

Skadi has three main web-based interface components:

  • A directory component, listing which topologies are stored, and allowing a user to open them in a designer application.
  • A designer component, allowing a user to modify and run topologies. Changes are automatically saved.
  • An application component, allowing a user to interact with a running topology via selected node pages, whilst hiding the topology structure. This is provided as a working template which can be customised.

Running skadi in standalone mode

Deploy skadi in standalone mode using the distribution in the dist folder.

dist/
    skadi-directory.html     ...open this page for the directory web-app
    skadi-designer.html      ...open this page for the designer web-app
    skadi-application.html   ...use this as a template for building skadi applications - web-apps based on topologies
    skadi/                   ...skadi and dependencies - source code - js and css files         
    example-packages/        ...any example packages
    topologies/              ...zip files containing pre-prepared topologies
    options.json             ...skadi's configuration file 
    serve.py                 ...a convenience python script to serve skadi as a standalone service   

The options.json file is used to configure skadi, and is loaded by the designer, directory and application web-apps.

{
    "canvas_height": 2000,
    "canvas_width": 4000,
    "designer_title": "Topology Designer",
    "directory_title": "Topology Directory",
    "package_urls": ["example-packages/textgraph"],
    "workspace_id": "skadi_textgraph",
    "templates": {
        "demo": {
            "url": "topologies/abc.zip",
            "name": "Demo",
            "description": "Demo Topology ABC"
        }
    },
    "directory_url": "skadi-directory.html",
    "designer_url": "skadi-designer.html",
    "designer_splash": {
        "title": "Skadi Topology Designer",
        "image_url": "skadi/images/skadi.svg"
    },
    "directory_splash": {
        "title": "Skadi Topology Directory",
        "image_url": "skadi/images/skadi.svg"
    },
    "applications": {
        "application_dev": {
            "url": "skadi-application.html",
            "metadata": {
                "name": "Skadi Application Development Page",
                "description": "Generic test page for skadi applications"
            }
        }
    }
}
option description example
canvas_height set the height of the canvas upon which nodes are placed (the designer app shows a window on this canvas) 2000
canvas_width set the width of the canvas upon which nodes are placed 4000
designer_title text that is displayed as a title in the designer application "Topology Designer"
directory_title text that is displayed as a title in the directory application "Topology Directory"
package_urls A list of URLs which point to folders containing Hyrrokkin packages (each folder should contain a schema.json) ["example-packages/textgraph"]
workspace_id a unique string identifying a workspace - a separate area in which topologies are stored in the browser's IndexedDB "skadi_textgraph"
templates specify one or more zip files containing pre-packaged topologies { "demo": { "url": "topologies/abc.zip", "name": "Demo", "description": "Demo Topology ABC"} }
directory_url the name of the directory app's HTML file. Provide this if you rename the skadi-directory.html file to something else "skadi-directory.html"
designer_url the name of the designer app's HTML file. Provide this if you rename the skadi-designer.html file to something else "skadi-designer.html"
designer_splash configure the splash screen displayed when starting the designer app with title text and an image { "title": "Skadi Topology Designer", "image_url": "skadi/images/skadi.svg" }
directory_splash configure the splash screen displayed when starting the directory app with title text and an image { "title": "Skadi Topology Directory", "image_url": "skadi/images/skadi.svg" }
applications register web pages that implement skadi applications, this will be linked in the directory app { "application_dev": { "url": "skadi-application.html", "metadata": { "name": "Skadi Application Development Page", "description": "Generic test page for skadi applications" } } }
in_page whether to run the javascript execution engine within the page or in a web worker true

Extending the hyrrokkin schema to describe a package's UI

Textgraph is a simple example package of nodes which defines functionality for analysing text. Hyrrokkin defines a schema for this package which describes how the nodes are implemented in python and javascript.

Skadi supports extensions to this schema to associate nodes and configurations with web-pages, using the ui-overlay.json:

{
    "id": "textgraph",
    "metadata": {
        "name": "{{package_name}}",
        "description": "{{package_description}}"
    },
    "configuration": {
        "clients": {
            "default": {
                "button_label": "Configure...",
                "title": "{{textgraph_configuration}}",
                "url": "html/textgraph_configuration.html"
            }
        }
    },
    "l10n": {
        "languages": {
            "en": {
                "name": "English",
                "bundle_url": "l10n/en.json"
            },
            "de": {
                "name": "Deutsch",
                "bundle_url": "l10n/de.json"
            }
        },
        "default_language": "en"
    },
    "node_types": {
        "text_input_node": {
            "metadata": {
                "name": "{{text_input}}",
                "description": "{{text_input_description}}"
            },
            "display": {
                "corners": 0,
                "icon": "icons/text_input_node.svg"
            },
            "clients": {
                "default": {
                    "menu_label":"{{open}}...",
                    "title": "{{text_input}}",
                    "url": "html/text_input_node.html",
                    "window_width": 500,
                    "window_height": 300
                }
            }
        },
        "word_frequency_node": {
            "metadata": {
                "name": "{{word_frequency}}",
                "description": "{{word_frequency_description}}"
            },
            "display": {
                "corners": 8,
                "icon": "icons/word_frequency_node.svg"
            },
            "clients": {
                "default": {
                    "menu_label":"{{open}}...",
                    "title": "{{word_frequency}}",
                    "url": "html/word_frequency_node.html",
                    "window_width": 500,
                    "window_height": 300
                }
            }
        },
        "merge_frequencies_node": {
            "metadata": {
                "name": "{{merge_frequencies}}",
                "description": "{{merge_frequencies_description}}"
            },
            "display": {
                "corners": 8,
                "icon": "icons/merge_frequencies_node.svg"
            },
            "clients": {
                "default": {
                    "menu_label":"{{open}}...",
                    "title": "{{merge_frequencies}}",
                    "url": "html/merge_frequencies_node.html",
                    "window_width": 500,
                    "window_height": 300
                }
            }
        },
        "table_display_node": {
            "metadata": {
                "name": "{{table_display}}",
                "description": "{{table_display_description}}"
            },
            "display": {
                "corners": 8,
                "icon": "icons/table_display_node.svg"
            },
            "clients": {
                "default": {
                    "menu_label":"{{open}}...",
                    "title": "{{table_display}}",
                    "url": "html/table_display_node.html",
                    "window_width": 500,
                    "window_height": 300
                }
            }
        }
    },
    "link_types": {
        "text": {
            "metadata": {
                "name": "{{link_type_text}}",
                "description": "{{link_type_text_description}}"
            },
            "display": {
                "colour": "orange"
            }
        },
        "table": {
            "metadata": {
                "name": "{{link_type_table}}",
                "description": "{{link_type_table_description}}"
            },
            "display": {
                "colour": "purple"
            }
        }
    }
}

In the ui-overlay.json file listed above, consider for example the key clients defining a web-page associated with the node text_input_node.

In this example, the page is used to receive input text from the user and pass it to the node. Skadi provides a simple API that can be used in these pages to communicate with the node.

<!DOCTYPE html>
<html style="height:100%; margin:0px;">
<head>
    <meta charset="UTF-8">
    <script src="../../../skadi/skadi-page.js" type="text/javascript"></script>
    <script>
        window.addEventListener("load",() => {
            let text_input = document.getElementById("text_input");
            skadi.page.set_message_handler((value) => {
               text_input.value = value;
            });
            let update_btn = document.getElementById("update");
            update_btn.addEventListener("click",(evt) => {
                skadi.page.send_message(text_input.value);
            });
            skadi.page.set_connection_handler(() => {
                skadi.page.localise_body();
                document.body.style.visibility = "visible";
            });
        });
    </script>
</head>
<body style="height:100%;margin:0px;visibility:hidden;">
    <div style="display: flex; flex-direction: column; height: 100%;">
        <h3>{{text_input}}</h3><div>
        <input id="update" type="button" value="Update"></div>
        <textarea id="text_input" style="width:100%;height:100%;box-sizing: border-box;">AAAAAAAA</textarea>
    </div>
</body>
</html>

This page does the following:

  • includes the script skadi-page.js
  • calls skadi.page.set_message_listener to set up a handler that receives messages from the text_input_node.
  • After the page is opened, the node will send a message to the page with the current string value of the text stored in the node.
  • The page loads the received text into a Textarea control.
  • The user may modify or replace the text. When they click on the update button, the updated text is sent to the node by calling skadi.page.send_message.

The javascript implementation of text_input_node.js conforms to the Hyrrokkin API (https://visualtopology.org/docs/hyrrokkin) and interacts with the page:

//   Hyrrokkin - a library for building and running executable graphs
//
//   MIT License - Copyright (C) 2022-2025  Visual Topology Ltd

var textgraph = textgraph || {};

textgraph.TextInputNode = class {

    constructor(services) {
        this.services = services;
        this.clients = new Set();
        this.text = "";
    }

    async load() {
        let data = await this.services.get_data("value");
        if (data === null) {
            this.text = "";
        } else {
            this.text = (new TextDecoder()).decode(data);
        }
    }

    async open_client(client) {
        this.clients.add(client);
        client.set_message_handler(async (...msg) => await this.handle_message(client, ...msg));
        client.send_message(this.text);
    }

    async close_client(client) {
        this.clients.delete(client);
    }

    async handle_message(from_client, value) {
        if (value !== this.text) {
            this.text = value;
            await this.services.set_data("value", (new TextEncoder()).encode(this.text).buffer);
            await this.services.request_run();
            this.clients.forEach((other_client) => {
                if (other_client !== from_client) {
                    other_client.send_message(this.text);
                }
            });
        }
    }

    async run(inputs) {
        if (this.text) {
            return {"data_out": this.text}
        } else {
            return {};
        }
    }
}

In the page, the object skadi.page is an instance which implements the page interface.