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.
Files changed (38) hide show
  1. {sysgraph-0.0.16/src/sysgraph.egg-info → sysgraph-0.0.17}/PKG-INFO +43 -29
  2. sysgraph-0.0.17/README.md +127 -0
  3. sysgraph-0.0.17/pyproject.toml +64 -0
  4. sysgraph-0.0.17/src/sysgraph/__init__.py +1 -0
  5. sysgraph-0.0.16/src/sysgraph/dist/assets/index-D4kwmDWy.css → sysgraph-0.0.17/src/sysgraph/dist/assets/index-BBuhcA5M.css +1 -1
  6. sysgraph-0.0.17/src/sysgraph/dist/assets/index-BlTIaOJH.js +737 -0
  7. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/dist/index.html +3 -3
  8. sysgraph-0.0.17/src/sysgraph/temp/congress_network/compute_vc.py +31 -0
  9. sysgraph-0.0.17/src/sysgraph/temp/congress_network/convert_to_graph_format.py +85 -0
  10. sysgraph-0.0.17/src/sysgraph/temp/congress_network/histogram_weights.py +36 -0
  11. sysgraph-0.0.17/src/sysgraph/temp/congress_network/viral_centrality.py +120 -0
  12. sysgraph-0.0.17/src/sysgraph/temp/voles_network/convert_voles_to_graph_format.py +89 -0
  13. sysgraph-0.0.17/src/sysgraph/tests/test_discovery.py +19 -0
  14. sysgraph-0.0.17/src/sysgraph/tests/test_graph.py +132 -0
  15. {sysgraph-0.0.16 → sysgraph-0.0.17/src/sysgraph.egg-info}/PKG-INFO +43 -29
  16. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph.egg-info/SOURCES.txt +9 -4
  17. sysgraph-0.0.17/src/sysgraph.egg-info/top_level.txt +2 -0
  18. sysgraph-0.0.16/README.md +0 -99
  19. sysgraph-0.0.16/pyproject.toml +0 -19
  20. sysgraph-0.0.16/setup.py +0 -91
  21. sysgraph-0.0.16/src/sysgraph/__init__.py +0 -1
  22. sysgraph-0.0.16/src/sysgraph/dist/assets/index-D5t1fx8g.js +0 -735
  23. sysgraph-0.0.16/src/sysgraph.egg-info/not-zip-safe +0 -1
  24. sysgraph-0.0.16/src/sysgraph.egg-info/top_level.txt +0 -1
  25. {sysgraph-0.0.16 → sysgraph-0.0.17}/LICENSE +0 -0
  26. {sysgraph-0.0.16 → sysgraph-0.0.17}/MANIFEST.in +0 -0
  27. {sysgraph-0.0.16 → sysgraph-0.0.17}/setup.cfg +0 -0
  28. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/__main__.py +0 -0
  29. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/app.py +0 -0
  30. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/constants.py +0 -0
  31. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/discovery.py +0 -0
  32. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/dist/assets/icon-TKtfQOgj.png +0 -0
  33. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/graph.py +0 -0
  34. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/main.py +0 -0
  35. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph/model.py +0 -0
  36. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph.egg-info/dependency_links.txt +0 -0
  37. {sysgraph-0.0.16 → sysgraph-0.0.17}/src/sysgraph.egg-info/entry_points.txt +0 -0
  38. {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.16
3
+ Version: 0.0.17
4
4
  Summary: Visualizer for processes and their interconnections
5
- Home-page: https://github.com/gubenkoved/sysgraph
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
- Real-time process-graph visualizer that discovers running OS processes, their inter-process communication channels (pipes, Unix domain sockets, TCP/UDP network connections), and renders them as an interactive force-directed graph in the browser.
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
  ![Python](https://img.shields.io/badge/python-%3E%3D3.12-blue)
49
38
  ![License](https://img.shields.io/badge/license-MIT-green)
50
39
 
51
40
  ## Features
52
41
 
53
- - **Process discovery** — enumerates all running OS processes and their parent-child relationships
54
- - **IPC visualization** — discovers pipes, Unix domain sockets, and TCP/UDP connections between processes
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
- - **Real-time** — fetch the latest process graph on demand via the web UI
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
- - **Export/Import** — save and load graph snapshots as JSON
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
- - Root/sudo recommended for full visibility into all processes
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 to see the interactive process graph.
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 **FastAPI backend** uses `psutil` and Linux-specific APIs (`/proc`, `ss`) to discover processes, pipes, Unix domain sockets, and network connections.
106
- 2. It builds a graph of processes (nodes) and their IPC channels (edges).
107
- 3. The **browser frontend** fetches the graph via `GET /api/graph` and renders it using [force-graph](https://github.com/vasturiano/force-graph) with d3 physics simulation.
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
+ ![Python](https://img.shields.io/badge/python-%3E%3D3.12-blue)
9
+ ![License](https://img.shields.io/badge/license-MIT-green)
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% - 20px);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}
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}