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_listenerto 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.