Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions etc/docker/environment/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2022-2025, The OTNS Authors.
# Copyright (c) 2022-2026, The OTNS Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
Expand All @@ -24,15 +24,18 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# Builds the OTNS2 web development environment Docker (nodejs, npm, gRPC).
# Builds the OTNS Go + web development environment Docker (nodejs, npm, gRPC).
# All build file results are kept.
# The .dockerignore file from the repo root is used during build.
# An older OS is used to avoid breaking the web build which heavily relies
# on specific versions of tools/libraries.
# The starting point is the official Node 22 Docker and Go is installed on top
# of this.

FROM golang:1.23

RUN apt-get update && apt-get install -y python3 python3-pip sudo unzip nodejs npm git && rm -rf /var/lib/apt/lists/*
FROM node:22-bookworm
COPY --from=golang:1.26-bookworm /usr/local/go /usr/local/go
Comment thread
EskoDijk marked this conversation as resolved.
ENV PATH="/usr/local/go/bin:${PATH}"
RUN apt-get update \
&& apt-get install -y python3 python3-pip sudo unzip git curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY . /otns
WORKDIR /otns
RUN ./script/install-deps
Expand Down
5 changes: 3 additions & 2 deletions pylibs/unittests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1087,8 +1087,9 @@ def testSimulationId(self):
ns2.add('router')
ns2.add('router')
self.assertEqual(3, len(ns2.nodes()))
ns2.go(30)
self.assertEqual(30.0, ns2.time)
ns2.go(150)
ns2.go(10)
self.assertEqual(160.0, ns2.time)
self.assertEqual(3, len(ns2.nodes()))

# check number of partitions
Expand Down
4 changes: 1 addition & 3 deletions script/pack-web
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,7 @@ main()
npm install
fi
npm version
npx webpack ./js/visualize.js -o ./static/js/visualize.js
npx webpack ./js/energyViewer.js -o ./static/js/energyViewer.js
npx webpack ./js/statsViewer.js -o ./static/js/statsViewer.js
npm run build

go-bindata -pkg web_site -o _bindata.go templates/... static/...
head -26 bindata.go >_merge_bindata.go
Expand Down
1 change: 1 addition & 0 deletions web/site/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
22
6 changes: 3 additions & 3 deletions web/site/bindata.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions web/site/js/vis/ActionBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

import * as PIXI from "pixi.js-legacy";
import * as PIXI from "pixi.js";
import VObject from "./VObject";
import Button from "./Button";
import {
Expand All @@ -44,7 +44,7 @@ export default class ActionBar extends VObject {
super();

this.root = new PIXI.Container();
this.root.interactive = true;
this.root.eventMode = 'static';
this.root.hitArea = new PIXI.Rectangle(0, 0, 0, 0);

this._buttons = [];
Expand Down
13 changes: 6 additions & 7 deletions web/site/js/vis/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

import * as PIXI from "pixi.js-legacy";
import * as PIXI from "pixi.js";
import VObject from "./VObject";
import {BUTTON_LABEL_FONT_FAMILY} from "./consts";

Expand All @@ -36,14 +36,14 @@ export default class Button extends VObject {
this._onRefresh = onRefresh;
this._minWidth = 16;

let label = new PIXI.Text(text, {fontFamily: BUTTON_LABEL_FONT_FAMILY, fontSize: 16});
let label = new PIXI.Text({text, style: {fontFamily: BUTTON_LABEL_FONT_FAMILY, fontSize: 16}});
this._label = label;
label.anchor.set(0.5, 0.5);

let graphics = new PIXI.Graphics();
this.root = graphics;
this.root.addChild(label);
this.root.interactive = true;
this.root.eventMode = 'static';

let button = this;
this.setOnTouchStart((e) => {
Expand Down Expand Up @@ -126,10 +126,9 @@ export default class Button extends VObject {
width = this.minWidth

graphics.clear();
graphics.beginFill(0xeeeeee);
graphics.lineStyle(2, 0x424242);
graphics.drawRoundedRect(-width / 2, -height / 2, width, height, 7);
graphics.endFill();
graphics.roundRect(-width / 2, -height / 2, width, height, 7);
graphics.fill(0xeeeeee);
graphics.stroke({width: 2, color: 0x424242});

if (sprite) {
sprite.position.set(-width / 2 + spriteWidth / 2 + 8, 0)
Expand Down
125 changes: 87 additions & 38 deletions web/site/js/vis/LogWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

import * as PIXI from "pixi.js-legacy";
import * as PIXI from "pixi.js";
import VObject from "./VObject";
import {Scrollbox} from 'pixi-scrollbox'
import {
LOG_WINDOW_FONT_FAMILY, LOG_WINDOW_FONT_SIZE, LOG_WINDOW_FONT_COLOR
} from "./consts";
Expand All @@ -44,64 +43,114 @@ const LOG_TEXT_LINE_HEIGHT = 15;
const LOG_WINDOW_MAX_SIZE = 100;
export const LOG_WINDOW_WIDTH = 400;
const LOG_WINDOW_BOTTOM_PADDING = 100;
const WHEEL_LINES_PER_NOTCH = 3;

// A scrollable, bottom-tailing log view. Replaces the former pixi-scrollbox
// dependency with a masked container scrolled via the mouse wheel.
export default class LogWindow extends VObject {
constructor() {
super();

let height = window.innerHeight - LOG_WINDOW_BOTTOM_PADDING;
this.logIndex = 0;
this.boxHeight = window.innerHeight - LOG_WINDOW_BOTTOM_PADDING;
this.loglist = [];
// While true, new lines keep the view pinned to the bottom (tail mode).
// The user scrolling up with the wheel temporarily disables it.
this._stickToBottom = true;

this._root = new Scrollbox({
boxWidth: LOG_WINDOW_WIDTH,
boxHeight: height,
fade: true,
overflowX: "auto",
overflowY: "auto"
});
this.logContainer = new PIXI.Container();
this._root = new PIXI.Container();
this._root.eventMode = 'static';

// White highlight bar drawn just below the most recent line.
this.lastline = new PIXI.Graphics();
this.lastline.clear();
this.lastline.beginFill(0xFFFFFF);
// this.lastline.lineStyle(0);
this.lastline.drawRect(0, 0, LOG_WINDOW_WIDTH, LOG_TEXT_LINE_HEIGHT);
this.lastline.endFill();
this.lastline.rect(0, 0, LOG_WINDOW_WIDTH, LOG_TEXT_LINE_HEIGHT);
this.lastline.fill(0xFFFFFF);

this.logContainer = new PIXI.Container();
this.logContainer.addChild(this.lastline);
this._root.addChild(this.logContainer);

this._root.content.addChild(this.logContainer);
this._root.update();
this.loglist = [];
// Clip the log lines to the box bounds.
this._mask = new PIXI.Graphics();
this._root.addChild(this._mask);
this.logContainer.mask = this._mask;

this._root.on('wheel', (e) => this._onWheel(e));

this._applyBoxSize();
}

_disposeLogItem(log) {
this.logContainer.removeChild(log);
// explicit GPU texture clear: one unique texture per log line
log.destroy({ texture: true, textureSource: true });
}

addLog(text, color = LOG_WINDOW_FONT_COLOR) {
if (this.loglist.length === LOG_WINDOW_MAX_SIZE) {
let rm = this.loglist.shift();
this.logContainer.removeChild(rm)
if (this.loglist.length >= LOG_WINDOW_MAX_SIZE) {
let rmLog = this.loglist.shift();
this._disposeLogItem(rmLog);
}

LOG_TEXT_STYLE.fill = color;
let log = new PIXI.Text(text, LOG_TEXT_STYLE);
log.position.set(3, 3 + this.logIndex * LOG_TEXT_LINE_HEIGHT);
this.logIndex++;
let log = new PIXI.Text({text, style: Object.assign({}, LOG_TEXT_STYLE, {fill: color})});
this.logContainer.addChild(log);
this.loglist.push(log);

this.logContainer.position.set(0, -this.loglist[0].position.y);
this.lastline.position.set(0, log.y + LOG_TEXT_LINE_HEIGHT);

this._root.resize({boxWidth: LOG_WINDOW_WIDTH, boxHeight: this._root.boxHeight});
this._root.ensureVisible(0, log.y + this.logContainer.y, LOG_WINDOW_WIDTH, log.height);
this._relayout();
}

clear() {
for (const log of this.loglist) {
this._disposeLogItem(log);
}
Comment thread
EskoDijk marked this conversation as resolved.
this.loglist = [];
this.logContainer.removeChildren();
this.logContainer.position.set(0, 0);
this.logIndex = 0;
this._root.resize({boxWidth: LOG_WINDOW_WIDTH, boxHeight: this._root.boxHeight});
this._stickToBottom = true;
this._relayout();
}

resetLayout(width, height) {
this._root.resize({boxWidth: LOG_WINDOW_WIDTH, boxHeight: height - LOG_WINDOW_BOTTOM_PADDING})
this.boxHeight = height - LOG_WINDOW_BOTTOM_PADDING;
this._applyBoxSize();
}

_applyBoxSize() {
this._mask.clear();
this._mask.rect(0, 0, LOG_WINDOW_WIDTH, this.boxHeight);
this._mask.fill(0xFFFFFF);
this._root.hitArea = new PIXI.Rectangle(0, 0, LOG_WINDOW_WIDTH, this.boxHeight);
this._relayout();
}

_relayout() {
for (let i = 0; i < this.loglist.length; i++) {
this.loglist[i].position.set(3, 3 + i * LOG_TEXT_LINE_HEIGHT)
}
this.lastline.position.set(0, 3 + this.loglist.length * LOG_TEXT_LINE_HEIGHT);

if (this._stickToBottom) {
this.logContainer.y = this._minScrollY();
} else {
this._clampScroll();
}
}

_contentHeight() {
// log lines + the trailing highlight bar + top padding
return (this.loglist.length + 1) * LOG_TEXT_LINE_HEIGHT + 3;
}

// Most-negative allowed logContainer.y (i.e. scrolled fully to the bottom).
_minScrollY() {
return Math.min(0, this.boxHeight - this._contentHeight());
}

_clampScroll() {
this.logContainer.y = Math.max(this._minScrollY(), Math.min(0, this.logContainer.y))
}

_onWheel(e) {
let dir = e.deltaY > 0 ? 1 : -1;
this.logContainer.y -= dir * WHEEL_LINES_PER_NOTCH * LOG_TEXT_LINE_HEIGHT;
this._clampScroll();
// Re-enable tailing once the user scrolls back to the bottom.
this._stickToBottom = this.logContainer.y <= this._minScrollY() + 1;
}
}
13 changes: 6 additions & 7 deletions web/site/js/vis/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

import * as PIXI from "pixi.js-legacy";
import * as PIXI from "pixi.js";
import VObject from "./VObject";
import {NodeMode, OtDeviceRole} from '../proto/visualize_grpc_pb'
import {Visualizer} from "./PixiVisualizer";
Expand Down Expand Up @@ -85,7 +85,7 @@ export default class Node extends VObject {
this._statusSprite = statusSprite;

let node = this;
this._root.interactive = true;
this._root.eventMode = 'static';
this._root.hitArea = new PIXI.Circle(0, 0, radius);
this.setOnTouchStart((e) => {
this.vis.setSelectedNode(node.id);
Expand All @@ -107,7 +107,7 @@ export default class Node extends VObject {

this._updateSize();

let label = new PIXI.Text("", {fontFamily: NODE_LABEL_FONT_FAMILY, fontSize: NODE_LABEL_FONT_SIZE, align: 'left'});
let label = new PIXI.Text({text: "", style: {fontFamily: NODE_LABEL_FONT_FAMILY, fontSize: NODE_LABEL_FONT_SIZE, align: 'left'}});
label.position.set(11, 11);
this._root.addChild(label);
this.label = label;
Expand Down Expand Up @@ -339,10 +339,9 @@ export default class Node extends VObject {

const rangeCircleSize = this.radioRange;
let rangeCircle = new PIXI.Graphics();
rangeCircle.beginFill(0x98ee99, 0.2);
rangeCircle.lineStyle({width: 1, color: 0x338a3e, alpha: 0.7});
rangeCircle.drawCircle(0, 0, rangeCircleSize);
rangeCircle.endFill();
rangeCircle.circle(0, 0, rangeCircleSize);
rangeCircle.fill({color: 0x98ee99, alpha: 0.2});
rangeCircle.stroke({width: 1, color: 0x338a3e, alpha: 0.7});
this.root.addChildAt(rangeCircle, 0);
this._rangeCircle = rangeCircle;
}
Expand Down
2 changes: 1 addition & 1 deletion web/site/js/vis/NodeWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

import * as PIXI from "pixi.js-legacy";
import * as PIXI from "pixi.js";
import VObject from "./VObject";
import * as fmt from "./format_text"
import {OtDeviceRole} from "../proto/visualize_grpc_pb";
Expand Down
Loading
Loading