sysgraph 0.0.16__tar.gz → 0.0.17__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sysgraph-0.0.16/src/sysgraph.egg-info → sysgraph-0.0.17}/PKG-INFO +43 -29
- sysgraph-0.0.17/README.md +127 -0
- sysgraph-0.0.17/pyproject.toml +64 -0
- sysgraph-0.0.17/src/sysgraph/__init__.py +1 -0
- sysgraph-0.0.16/src/sysgraph/dist/assets/index-D4kwmDWy.css → sysgraph-0.0.17/src/sysgraph/dist/assets/index-BBuhcA5M.css +1 -1
- sysgraph-0.0.17/src/sysgraph/dist/assets/index-BlTIaOJH.js +737 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/dist/index.html +3 -3
- sysgraph-0.0.17/src/sysgraph/temp/congress_network/compute_vc.py +31 -0
- sysgraph-0.0.17/src/sysgraph/temp/congress_network/convert_to_graph_format.py +85 -0
- sysgraph-0.0.17/src/sysgraph/temp/congress_network/histogram_weights.py +36 -0
- sysgraph-0.0.17/src/sysgraph/temp/congress_network/viral_centrality.py +120 -0
- sysgraph-0.0.17/src/sysgraph/temp/voles_network/convert_voles_to_graph_format.py +89 -0
- sysgraph-0.0.17/src/sysgraph/tests/test_discovery.py +19 -0
- sysgraph-0.0.17/src/sysgraph/tests/test_graph.py +132 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17/src/sysgraph.egg-info}/PKG-INFO +43 -29
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph.egg-info/SOURCES.txt +9 -4
- sysgraph-0.0.17/src/sysgraph.egg-info/top_level.txt +2 -0
- sysgraph-0.0.16/README.md +0 -99
- sysgraph-0.0.16/pyproject.toml +0 -19
- sysgraph-0.0.16/setup.py +0 -91
- sysgraph-0.0.16/src/sysgraph/__init__.py +0 -1
- sysgraph-0.0.16/src/sysgraph/dist/assets/index-D5t1fx8g.js +0 -735
- sysgraph-0.0.16/src/sysgraph.egg-info/not-zip-safe +0 -1
- sysgraph-0.0.16/src/sysgraph.egg-info/top_level.txt +0 -1
- {sysgraph-0.0.16 → sysgraph-0.0.17}/LICENSE +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/MANIFEST.in +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/setup.cfg +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/__main__.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/app.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/constants.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/discovery.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/dist/assets/icon-TKtfQOgj.png +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/graph.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/main.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/model.py +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph.egg-info/dependency_links.txt +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph.egg-info/entry_points.txt +0 -0
- {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph.egg-info/requires.txt +0 -0
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sysgraph
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.17
|
|
4
4
|
Summary: Visualizer for processes and their interconnections
|
|
5
|
-
|
|
6
|
-
Author: Eugene Gubenkov
|
|
7
|
-
Author-email: gubenkoved@gmail.com
|
|
5
|
+
Author-email: Eugene Gubenkov <gubenkoved@gmail.com>
|
|
8
6
|
License: MIT
|
|
9
7
|
Project-URL: Source, https://github.com/gubenkoved/sysgraph
|
|
10
8
|
Project-URL: Issues, https://github.com/gubenkoved/sysgraph/issues
|
|
@@ -27,43 +25,39 @@ Requires-Dist: psutil
|
|
|
27
25
|
Requires-Dist: coloredlogs
|
|
28
26
|
Requires-Dist: fastapi>=0.95
|
|
29
27
|
Requires-Dist: uvicorn[standard]>=0.20
|
|
30
|
-
Dynamic: author
|
|
31
|
-
Dynamic: author-email
|
|
32
|
-
Dynamic: classifier
|
|
33
|
-
Dynamic: description
|
|
34
|
-
Dynamic: description-content-type
|
|
35
|
-
Dynamic: home-page
|
|
36
|
-
Dynamic: keywords
|
|
37
|
-
Dynamic: license
|
|
38
28
|
Dynamic: license-file
|
|
39
|
-
Dynamic: project-url
|
|
40
|
-
Dynamic: requires-dist
|
|
41
|
-
Dynamic: requires-python
|
|
42
|
-
Dynamic: summary
|
|
43
29
|
|
|
44
30
|
# sysgraph
|
|
45
31
|
|
|
46
|
-
|
|
32
|
+
An interactive force-directed **network graph visualizer** for the browser — with two modes:
|
|
33
|
+
|
|
34
|
+
- **Import any graph** — load any JSON graph (nodes + edges) to explore and visualize it interactively, no Linux or special privileges required
|
|
35
|
+
- **Live process graph** — discover running OS processes and their inter-process communication channels (pipes, Unix domain sockets, TCP/UDP connections) in real time (Linux only)
|
|
47
36
|
|
|
48
37
|

|
|
49
38
|

|
|
50
39
|
|
|
51
40
|
## Features
|
|
52
41
|
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
42
|
+
- **Import any graph** — load a JSON file with nodes and edges to visualize any network, social graph, dependency tree, or dataset
|
|
43
|
+
- **Export/Import** — save and reload graph snapshots as JSON; use the sample at [`data/simplest-graph.json`](data/simplest-graph.json) as a format reference
|
|
55
44
|
- **Interactive graph** — force-directed graph rendered in the browser with zoom, pan, drag, and search
|
|
56
|
-
- **
|
|
57
|
-
- **Fuzzy search** — find processes by name, PID, command line, or any property
|
|
45
|
+
- **Fuzzy search** — find nodes by any property
|
|
58
46
|
- **Adjacency filtering** — right-click a node to show only its neighbors
|
|
59
|
-
- **Configurable** — tune d3 force parameters, colors, and filters via the settings panel
|
|
60
|
-
- **
|
|
47
|
+
- **Configurable** — tune d3 force parameters, colors, and type filters via the settings panel
|
|
48
|
+
- **Process discovery** *(Linux only)* — enumerates running OS processes and their parent-child relationships
|
|
49
|
+
- **IPC visualization** *(Linux only)* — discovers pipes, Unix domain sockets, and TCP/UDP connections between processes
|
|
50
|
+
- **Real-time** *(Linux only)* — fetch the latest process graph on demand via the web UI
|
|
51
|
+
|
|
52
|
+
## Demo
|
|
53
|
+
|
|
54
|
+
[Demo](https://github.com/user-attachments/assets/7d19daca-042c-43f1-bedd-4d74344e1e89)
|
|
61
55
|
|
|
62
56
|
## Requirements
|
|
63
57
|
|
|
64
|
-
- **Linux** (relies on `/proc` filesystem and the `ss` command)
|
|
65
58
|
- **Python ≥ 3.12**
|
|
66
|
-
-
|
|
59
|
+
- **Linux** is required only for live process-graph discovery (relies on `/proc` and `ss`); importing and visualizing your own graphs works on any platform
|
|
60
|
+
- Root/sudo recommended for full process visibility (Linux only)
|
|
67
61
|
|
|
68
62
|
## Installation
|
|
69
63
|
|
|
@@ -84,7 +78,27 @@ sysgraph --port 9000
|
|
|
84
78
|
python -m sysgraph
|
|
85
79
|
```
|
|
86
80
|
|
|
87
|
-
Open your browser to the displayed URL
|
|
81
|
+
Open your browser to the displayed URL.
|
|
82
|
+
|
|
83
|
+
### Visualize your own graph
|
|
84
|
+
|
|
85
|
+
Use the **Import** button in the UI to load any JSON file in the following format:
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"nodes": [
|
|
90
|
+
{"id": "1", "type": "person", "properties": {"name": "Alice"}},
|
|
91
|
+
{"id": "2", "type": "person", "properties": {"name": "Bob"}}
|
|
92
|
+
],
|
|
93
|
+
"edges": [
|
|
94
|
+
{"source_id": "1", "target_id": "2", "type": "knows", "properties": {}}
|
|
95
|
+
]
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
See [`data/simplest-graph.json`](data/simplest-graph.json) for a minimal example.
|
|
100
|
+
|
|
101
|
+
### Live process graph (Linux only)
|
|
88
102
|
|
|
89
103
|
For full visibility into all processes and their connections, run with elevated privileges:
|
|
90
104
|
|
|
@@ -102,9 +116,9 @@ The `--pid=host` and `--net=host` flags allow the container to see host processe
|
|
|
102
116
|
|
|
103
117
|
## How It Works
|
|
104
118
|
|
|
105
|
-
1. The **
|
|
106
|
-
2.
|
|
107
|
-
3. The **
|
|
119
|
+
1. The **browser frontend** renders interactive force-directed graphs using [force-graph](https://github.com/vasturiano/force-graph) with d3 physics simulation.
|
|
120
|
+
2. Graphs can be **imported from JSON** directly in the browser, or fetched live from the backend.
|
|
121
|
+
3. The **FastAPI backend** uses `psutil` and Linux-specific APIs (`/proc`, `ss`) to discover processes, pipes, Unix domain sockets, and network connections, building a graph served via `GET /api/graph`.
|
|
108
122
|
|
|
109
123
|
## Development
|
|
110
124
|
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# sysgraph
|
|
2
|
+
|
|
3
|
+
An interactive force-directed **network graph visualizer** for the browser — with two modes:
|
|
4
|
+
|
|
5
|
+
- **Import any graph** — load any JSON graph (nodes + edges) to explore and visualize it interactively, no Linux or special privileges required
|
|
6
|
+
- **Live process graph** — discover running OS processes and their inter-process communication channels (pipes, Unix domain sockets, TCP/UDP connections) in real time (Linux only)
|
|
7
|
+
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Import any graph** — load a JSON file with nodes and edges to visualize any network, social graph, dependency tree, or dataset
|
|
14
|
+
- **Export/Import** — save and reload graph snapshots as JSON; use the sample at [`data/simplest-graph.json`](data/simplest-graph.json) as a format reference
|
|
15
|
+
- **Interactive graph** — force-directed graph rendered in the browser with zoom, pan, drag, and search
|
|
16
|
+
- **Fuzzy search** — find nodes by any property
|
|
17
|
+
- **Adjacency filtering** — right-click a node to show only its neighbors
|
|
18
|
+
- **Configurable** — tune d3 force parameters, colors, and type filters via the settings panel
|
|
19
|
+
- **Process discovery** *(Linux only)* — enumerates running OS processes and their parent-child relationships
|
|
20
|
+
- **IPC visualization** *(Linux only)* — discovers pipes, Unix domain sockets, and TCP/UDP connections between processes
|
|
21
|
+
- **Real-time** *(Linux only)* — fetch the latest process graph on demand via the web UI
|
|
22
|
+
|
|
23
|
+
## Demo
|
|
24
|
+
|
|
25
|
+
[Demo](https://github.com/user-attachments/assets/7d19daca-042c-43f1-bedd-4d74344e1e89)
|
|
26
|
+
|
|
27
|
+
## Requirements
|
|
28
|
+
|
|
29
|
+
- **Python ≥ 3.12**
|
|
30
|
+
- **Linux** is required only for live process-graph discovery (relies on `/proc` and `ss`); importing and visualizing your own graphs works on any platform
|
|
31
|
+
- Root/sudo recommended for full process visibility (Linux only)
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install sysgraph
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Start the web server (default: http://localhost:8000)
|
|
43
|
+
sysgraph
|
|
44
|
+
|
|
45
|
+
# Specify a custom port
|
|
46
|
+
sysgraph --port 9000
|
|
47
|
+
|
|
48
|
+
# Or run as a module
|
|
49
|
+
python -m sysgraph
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Open your browser to the displayed URL.
|
|
53
|
+
|
|
54
|
+
### Visualize your own graph
|
|
55
|
+
|
|
56
|
+
Use the **Import** button in the UI to load any JSON file in the following format:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"nodes": [
|
|
61
|
+
{"id": "1", "type": "person", "properties": {"name": "Alice"}},
|
|
62
|
+
{"id": "2", "type": "person", "properties": {"name": "Bob"}}
|
|
63
|
+
],
|
|
64
|
+
"edges": [
|
|
65
|
+
{"source_id": "1", "target_id": "2", "type": "knows", "properties": {}}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
See [`data/simplest-graph.json`](data/simplest-graph.json) for a minimal example.
|
|
71
|
+
|
|
72
|
+
### Live process graph (Linux only)
|
|
73
|
+
|
|
74
|
+
For full visibility into all processes and their connections, run with elevated privileges:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
sudo sysgraph
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Docker
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
docker run --rm -it --pid=host --net=host gubenkoved/sysgraph
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The `--pid=host` and `--net=host` flags allow the container to see host processes and network connections.
|
|
87
|
+
|
|
88
|
+
## How It Works
|
|
89
|
+
|
|
90
|
+
1. The **browser frontend** renders interactive force-directed graphs using [force-graph](https://github.com/vasturiano/force-graph) with d3 physics simulation.
|
|
91
|
+
2. Graphs can be **imported from JSON** directly in the browser, or fetched live from the backend.
|
|
92
|
+
3. The **FastAPI backend** uses `psutil` and Linux-specific APIs (`/proc`, `ss`) to discover processes, pipes, Unix domain sockets, and network connections, building a graph served via `GET /api/graph`.
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
### Prerequisites
|
|
97
|
+
- Python ≥ 3.12, Linux, Docker
|
|
98
|
+
- Node.js 22 runs inside Docker; no host installation required
|
|
99
|
+
|
|
100
|
+
### Backend
|
|
101
|
+
```bash
|
|
102
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
103
|
+
pip install -e . && pip install -r requirements-dev.in
|
|
104
|
+
python src/sysgraph/app.py # → http://localhost:8000
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Frontend (TypeScript + Vite)
|
|
108
|
+
```bash
|
|
109
|
+
./scripts/build-ui.sh # production build → src/sysgraph/dist/
|
|
110
|
+
./scripts/dev-ui.sh # Vite dev server with HMR on :5173
|
|
111
|
+
./scripts/typecheck-ui.sh # TypeScript type checking
|
|
112
|
+
./scripts/lint-ui.sh # Biome linter (pass --fix to auto-fix)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Tests
|
|
116
|
+
```bash
|
|
117
|
+
pytest src/sysgraph/tests/ # requires Linux /proc
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Python linting
|
|
121
|
+
```bash
|
|
122
|
+
./scripts/lint.sh # ruff check + ruff format + isort
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "sysgraph"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Visualizer for processes and their interconnections"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Eugene Gubenkov", email = "gubenkoved@gmail.com" }]
|
|
12
|
+
requires-python = ">=3.12"
|
|
13
|
+
keywords = ["process", "visualization", "graph", "ipc", "system", "monitoring", "linux", "procfs"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.12",
|
|
17
|
+
"Programming Language :: Python :: 3.13",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: POSIX :: Linux",
|
|
20
|
+
"Topic :: System :: Monitoring",
|
|
21
|
+
"Topic :: System :: Systems Administration",
|
|
22
|
+
"Environment :: Web Environment",
|
|
23
|
+
"Framework :: FastAPI",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Intended Audience :: System Administrators",
|
|
26
|
+
]
|
|
27
|
+
dependencies = [
|
|
28
|
+
"psutil",
|
|
29
|
+
"coloredlogs",
|
|
30
|
+
"fastapi>=0.95",
|
|
31
|
+
"uvicorn[standard]>=0.20",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Source = "https://github.com/gubenkoved/sysgraph"
|
|
36
|
+
Issues = "https://github.com/gubenkoved/sysgraph/issues"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
sysgraph = "sysgraph.app:main"
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.dynamic]
|
|
42
|
+
version = { attr = "sysgraph.__version__" }
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["src"]
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.package-data]
|
|
48
|
+
sysgraph = ["dist/*", "dist/**/*"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
line-length = 79
|
|
52
|
+
target-version = "py312"
|
|
53
|
+
# Tune ruff rules as needed. Ruff supports many checks and can also format (`ruff format`).
|
|
54
|
+
# See https://beta.ruff.rs/docs/configuration/ for config options.
|
|
55
|
+
exclude = [".venv", "build", "dist", "__pycache__"]
|
|
56
|
+
|
|
57
|
+
[tool.isort]
|
|
58
|
+
profile = "black"
|
|
59
|
+
multi_line_output = 3
|
|
60
|
+
include_trailing_comma = true
|
|
61
|
+
force_grid_wrap = 0
|
|
62
|
+
use_parentheses = true
|
|
63
|
+
ensure_newline_before_comments = true
|
|
64
|
+
line_length = 88
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.17"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{--z-toolbar: 100;--z-search-help: 150;--z-context-menu: 200;--z-details-panel: 50;--z-selection-overlay: 10;--z-version-badge: 10;--z-zoom-indicator: 10;--bg-toolbar: rgba(255, 255, 255, .95);--bg-panel: rgba(255, 255, 255, .97);--border-subtle: rgba(0, 0, 0, .08);--border-light: rgba(0, 0, 0, .1);--border-medium: rgba(0, 0, 0, .12);--shadow-xs: rgba(0, 0, 0, .08);--shadow-sm: rgba(0, 0, 0, .14);--shadow-md: rgba(0, 0, 0, .15);--accent-primary: #1a56db;--accent-info: #1a73e8;--accent-danger: #dc2626;--toolbar-height: 48px;--spacing-xs: 4px;--spacing-sm: 6px;--spacing-md: 8px;--spacing-lg: 16px;--font-family: "Ubuntu", "Segoe UI", "Arial", sans-serif;--font-size-xs: 11px;--font-size-sm: 12px;--font-size-md: 13px;--radius-sm: 4px;--radius-md: 8px;--radius-lg: 12px;--button-height: 28px;--icon-button-size: 24px;--tp-base-background-color: hsla(230, 5%, 90%, 1);--tp-base-shadow-color: hsla(0, 0%, 0%, .1);--tp-button-background-color: hsla(230, 7%, 75%, 1);--tp-button-background-color-active: hsla(230, 7%, 60%, 1);--tp-button-background-color-focus: hsla(230, 7%, 65%, 1);--tp-button-background-color-hover: hsla(230, 7%, 70%, 1);--tp-button-foreground-color: hsla(230, 10%, 30%, 1);--tp-container-background-color: hsla(230, 15%, 30%, .2);--tp-container-background-color-active: hsla(230, 15%, 30%, .32);--tp-container-background-color-focus: hsla(230, 15%, 30%, .28);--tp-container-background-color-hover: hsla(230, 15%, 30%, .24);--tp-container-foreground-color: hsla(230, 10%, 30%, 1);--tp-groove-foreground-color: hsla(230, 15%, 30%, .1);--tp-input-background-color: hsla(230, 15%, 30%, .1);--tp-input-background-color-active: hsla(230, 15%, 30%, .22);--tp-input-background-color-focus: hsla(230, 15%, 30%, .18);--tp-input-background-color-hover: hsla(230, 15%, 30%, .14);--tp-input-foreground-color: hsla(230, 10%, 30%, 1);--tp-label-foreground-color: hsla(230, 10%, 30%, .7);--tp-monitor-background-color: hsla(230, 15%, 30%, .1);--tp-monitor-foreground-color: hsla(230, 10%, 30%, .5)}html,body{height:100%;margin:0;overflow:hidden;font-family:var(--font-family)}#toolbar{position:fixed;top:0;left:0;right:0;height:var(--toolbar-height);z-index:var(--z-toolbar);background:var(--bg-toolbar);border-bottom:1px solid var(--border-light);display:flex;align-items:center;padding:0 var(--spacing-lg);gap:var(--spacing-sm);box-shadow:0 1px 3px var(--shadow-xs)}#toolbar md-outlined-button{--md-outlined-button-container-height: var(--button-height);--md-outlined-button-label-text-size: var(--font-size-sm);--md-outlined-button-label-text-weight: 500;--md-outlined-button-icon-size: 16px;--md-outlined-button-leading-space: 10px;--md-outlined-button-trailing-space: 12px;--md-outlined-button-icon-offset: -2px;--md-outlined-button-container-shape: var(--radius-sm)}md-outlined-button.active{--md-outlined-button-container-color: #d0e1ff;--md-outlined-button-label-text-color: var(--accent-primary);--md-outlined-button-outline-color: var(--accent-primary);--md-outlined-button-icon-color: var(--accent-primary)}md-outlined-button.danger{--md-outlined-button-label-text-color: var(--accent-danger);--md-outlined-button-outline-color: var(--accent-danger);--md-outlined-button-icon-color: var(--accent-danger)}.toolbar-separator{width:1px;height:var(--icon-button-size);background:var(--border-light)}#searchInput{width:240px;height:var(--button-height);--md-outlined-text-field-container-shape: var(--radius-sm);--md-outlined-text-field-top-space: var(--spacing-xs);--md-outlined-text-field-bottom-space: var(--spacing-xs);--md-outlined-text-field-input-text-size: var(--font-size-sm);--md-outlined-text-field-label-text-size: var(--font-size-sm)}#searchHelpTrigger{--md-icon-button-icon-size: 18px;--md-icon-button-state-layer-height: var(--button-height);--md-icon-button-state-layer-width: var(--button-height);height:var(--button-height);width:var(--button-height)}.search-help-anchor{position:relative;display:inline-flex}#searchHelp{display:none;position:absolute;top:100%;left:0;margin-top:var(--spacing-xs);z-index:var(--z-search-help);border:1px solid var(--border-medium);border-radius:var(--radius-md);padding:10px 14px;box-shadow:0 4px 12px var(--shadow-md);font-size:var(--font-size-sm);line-height:1.5;background:#1e293b;color:#fff}#searchHelp.open{display:block}#searchHelp code{background:#ffffff1f;padding:1px var(--spacing-xs);border-radius:3px}#content{position:fixed;top:var(--toolbar-height);left:0;right:0;bottom:0}#graph{width:100%;height:100%;position:relative}.details-panel{position:absolute;top:var(--spacing-md);left:var(--spacing-md);width:fit-content;max-width:420px;height:auto;min-width:200px;min-height:120px;max-height:min(350px,calc(100% - var(--spacing-lg)));z-index:var(--z-details-panel);background:var(--bg-panel);box-shadow:0 2px 12px var(--shadow-sm);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);display:none;flex-direction:column;overflow:hidden;resize:both}.details-panel.open{display:flex}.details-panel .panel-header{display:flex;align-items:center;height:var(--button-height);padding:0 2px 0 10px;border-bottom:1px solid var(--border-subtle);flex-shrink:0;cursor:grab;-webkit-user-select:none;user-select:none}.details-panel .panel-header:active{cursor:grabbing}.details-panel .panel-title{font-size:var(--font-size-sm);font-weight:500;color:#333;flex:1}.details-panel md-icon-button{--md-icon-button-icon-size: 16px;--md-icon-button-state-layer-height: var(--icon-button-size);--md-icon-button-state-layer-width: var(--icon-button-size);height:var(--icon-button-size);width:var(--icon-button-size)}.details-panel .panel-body{flex:1;overflow-y:auto;min-height:0;padding:var(--spacing-md);font-family:var(--font-family)}#detailsPanel{border-left:3px solid var(--accent-info)}#detailsPanel .panel-header{background:#1a73e80f}#contextMenu{display:none;position:fixed;z-index:var(--z-context-menu);background:var(--bg-panel);border:1px solid var(--border-medium);border-radius:var(--spacing-sm);padding:var(--spacing-xs) 0;box-shadow:0 4px 12px var(--shadow-md);min-width:160px;font-size:var(--font-size-md)}.context-menu-item{padding:var(--spacing-sm) 14px;cursor:pointer;-webkit-user-select:none;user-select:none}.context-menu-item:hover{background:#0000000f}.context-menu-divider{height:1px;margin:var(--spacing-xs) 0;background:var(--border-medium)}.tp-rotv{font-family:var(--font-family)!important;opacity:.9}#settingsPane{max-height:calc(100% -
|
|
1
|
+
:root{--z-toolbar: 100;--z-search-help: 150;--z-context-menu: 200;--z-details-panel: 50;--z-selection-overlay: 10;--z-version-badge: 10;--z-zoom-indicator: 10;--bg-toolbar: rgba(255, 255, 255, .95);--bg-panel: rgba(255, 255, 255, .97);--border-subtle: rgba(0, 0, 0, .08);--border-light: rgba(0, 0, 0, .1);--border-medium: rgba(0, 0, 0, .12);--shadow-xs: rgba(0, 0, 0, .08);--shadow-sm: rgba(0, 0, 0, .14);--shadow-md: rgba(0, 0, 0, .15);--accent-primary: #1a56db;--accent-info: #1a73e8;--accent-danger: #dc2626;--toolbar-height: 48px;--spacing-xs: 4px;--spacing-sm: 6px;--spacing-md: 8px;--spacing-lg: 16px;--font-family: "Ubuntu", "Segoe UI", "Arial", sans-serif;--font-size-xs: 11px;--font-size-sm: 12px;--font-size-md: 13px;--radius-sm: 4px;--radius-md: 8px;--radius-lg: 12px;--button-height: 28px;--icon-button-size: 24px;--tp-base-background-color: hsla(230, 5%, 90%, 1);--tp-base-shadow-color: hsla(0, 0%, 0%, .1);--tp-button-background-color: hsla(230, 7%, 75%, 1);--tp-button-background-color-active: hsla(230, 7%, 60%, 1);--tp-button-background-color-focus: hsla(230, 7%, 65%, 1);--tp-button-background-color-hover: hsla(230, 7%, 70%, 1);--tp-button-foreground-color: hsla(230, 10%, 30%, 1);--tp-container-background-color: hsla(230, 15%, 30%, .2);--tp-container-background-color-active: hsla(230, 15%, 30%, .32);--tp-container-background-color-focus: hsla(230, 15%, 30%, .28);--tp-container-background-color-hover: hsla(230, 15%, 30%, .24);--tp-container-foreground-color: hsla(230, 10%, 30%, 1);--tp-groove-foreground-color: hsla(230, 15%, 30%, .1);--tp-input-background-color: hsla(230, 15%, 30%, .1);--tp-input-background-color-active: hsla(230, 15%, 30%, .22);--tp-input-background-color-focus: hsla(230, 15%, 30%, .18);--tp-input-background-color-hover: hsla(230, 15%, 30%, .14);--tp-input-foreground-color: hsla(230, 10%, 30%, 1);--tp-label-foreground-color: hsla(230, 10%, 30%, .7);--tp-monitor-background-color: hsla(230, 15%, 30%, .1);--tp-monitor-foreground-color: hsla(230, 10%, 30%, .5)}html,body{height:100%;margin:0;overflow:hidden;font-family:var(--font-family)}#toolbar{position:fixed;top:0;left:0;right:0;height:var(--toolbar-height);z-index:var(--z-toolbar);background:var(--bg-toolbar);border-bottom:1px solid var(--border-light);display:flex;align-items:center;padding:0 var(--spacing-lg);gap:var(--spacing-sm);box-shadow:0 1px 3px var(--shadow-xs)}#toolbar md-outlined-button{--md-outlined-button-container-height: var(--button-height);--md-outlined-button-label-text-size: var(--font-size-sm);--md-outlined-button-label-text-weight: 500;--md-outlined-button-icon-size: 16px;--md-outlined-button-leading-space: 10px;--md-outlined-button-trailing-space: 12px;--md-outlined-button-icon-offset: -2px;--md-outlined-button-container-shape: var(--radius-sm)}md-outlined-button.active{--md-outlined-button-container-color: #d0e1ff;--md-outlined-button-label-text-color: var(--accent-primary);--md-outlined-button-outline-color: var(--accent-primary);--md-outlined-button-icon-color: var(--accent-primary)}md-outlined-button.danger{--md-outlined-button-label-text-color: var(--accent-danger);--md-outlined-button-outline-color: var(--accent-danger);--md-outlined-button-icon-color: var(--accent-danger)}.toolbar-separator{width:1px;height:var(--icon-button-size);background:var(--border-light)}#searchInput{width:240px;height:var(--button-height);--md-outlined-text-field-container-shape: var(--radius-sm);--md-outlined-text-field-top-space: var(--spacing-xs);--md-outlined-text-field-bottom-space: var(--spacing-xs);--md-outlined-text-field-input-text-size: var(--font-size-sm);--md-outlined-text-field-label-text-size: var(--font-size-sm)}#searchHelpTrigger{--md-icon-button-icon-size: 18px;--md-icon-button-state-layer-height: var(--button-height);--md-icon-button-state-layer-width: var(--button-height);height:var(--button-height);width:var(--button-height)}.search-help-anchor{position:relative;display:inline-flex}#searchHelp{display:none;position:absolute;top:100%;left:0;margin-top:var(--spacing-xs);z-index:var(--z-search-help);border:1px solid var(--border-medium);border-radius:var(--radius-md);padding:10px 14px;box-shadow:0 4px 12px var(--shadow-md);font-size:var(--font-size-sm);line-height:1.5;background:#1e293b;color:#fff}#searchHelp.open{display:block}#searchHelp code{background:#ffffff1f;padding:1px var(--spacing-xs);border-radius:3px}#content{position:fixed;top:var(--toolbar-height);left:0;right:0;bottom:0}#graph{width:100%;height:100%;position:relative}.details-panel{position:absolute;top:var(--spacing-md);left:var(--spacing-md);width:fit-content;max-width:420px;height:auto;min-width:200px;min-height:120px;max-height:min(350px,calc(100% - var(--spacing-lg)));z-index:var(--z-details-panel);background:var(--bg-panel);box-shadow:0 2px 12px var(--shadow-sm);border:1px solid var(--border-subtle);border-radius:var(--radius-lg);display:none;flex-direction:column;overflow:hidden;resize:both}.details-panel.open{display:flex}.details-panel .panel-header{display:flex;align-items:center;height:var(--button-height);padding:0 2px 0 10px;border-bottom:1px solid var(--border-subtle);flex-shrink:0;cursor:grab;-webkit-user-select:none;user-select:none}.details-panel .panel-header:active{cursor:grabbing}.details-panel .panel-title{font-size:var(--font-size-sm);font-weight:500;color:#333;flex:1}.details-panel md-icon-button{--md-icon-button-icon-size: 16px;--md-icon-button-state-layer-height: var(--icon-button-size);--md-icon-button-state-layer-width: var(--icon-button-size);height:var(--icon-button-size);width:var(--icon-button-size)}.details-panel .panel-body{flex:1;overflow-y:auto;min-height:0;padding:var(--spacing-md);font-family:var(--font-family)}#detailsPanel{border-left:3px solid var(--accent-info)}#detailsPanel .panel-header{background:#1a73e80f}#contextMenu{display:none;position:fixed;z-index:var(--z-context-menu);background:var(--bg-panel);border:1px solid var(--border-medium);border-radius:var(--spacing-sm);padding:var(--spacing-xs) 0;box-shadow:0 4px 12px var(--shadow-md);min-width:160px;font-size:var(--font-size-md)}.context-menu-item{padding:var(--spacing-sm) 14px;cursor:pointer;-webkit-user-select:none;user-select:none}.context-menu-item:hover{background:#0000000f}.context-menu-divider{height:1px;margin:var(--spacing-xs) 0;background:var(--border-medium)}.tp-rotv{font-family:var(--font-family)!important;opacity:.9}#settingsPane{max-height:calc(100% - 30px);position:absolute;overflow-y:auto;right:10px;top:10px}#version-badge{position:fixed;bottom:var(--spacing-sm);right:var(--spacing-md);font-size:var(--font-size-xs);color:#0000004d;pointer-events:none;z-index:var(--z-version-badge);font-family:var(--font-family)}#zoom-indicator{position:fixed;bottom:var(--spacing-md);left:var(--spacing-md);z-index:var(--z-zoom-indicator);display:flex;align-items:center;gap:0;background:var(--bg-toolbar);border:1px solid var(--border-subtle);border-radius:var(--radius-md);box-shadow:0 1px 4px var(--shadow-xs);padding:0 var(--spacing-xs)}#zoom-indicator md-icon-button{--md-icon-button-icon-size: 16px;--md-icon-button-state-layer-height: var(--button-height);--md-icon-button-state-layer-width: var(--button-height);height:var(--button-height);width:var(--button-height)}#zoomLevel{font-size:var(--font-size-xs);font-family:var(--font-family);color:#555;min-width:34px;text-align:center;-webkit-user-select:none;user-select:none;cursor:pointer;border-radius:var(--radius-sm);padding:2px var(--spacing-xs);transition:background .1s}#zoomLevel:hover{background:#0000000f}.muted{color:#666}hr{border:none;border-top:1px solid var(--border-subtle);margin:var(--spacing-sm) 0}
|