From da431d21bbf47b847534b2ba27a3c983b8f1eb9d Mon Sep 17 00:00:00 2001 From: Flosch62 Date: Wed, 27 May 2026 09:40:23 +0200 Subject: [PATCH 1/8] Add React Flow gNMI map --- .gitignore | 3 + README.md | 17 +- index.html | 12 + package-lock.json | 2044 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 21 + src/App.jsx | 371 ++++++++ src/gnmiMap.js | 556 ++++++++++++ src/main.jsx | 10 + src/styles.css | 498 +++++++++++ 9 files changed, 3531 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/App.jsx create mode 100644 src/gnmiMap.js create mode 100644 src/main.jsx create mode 100644 src/styles.css diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3bdd52e --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +.DS_Store diff --git a/README.md b/README.md index bb0a81a..14bed92 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,25 @@ gNMI Map makes it easy to understand the composition of the gNMI service as well

+## React Flow map + +This repository now includes a React Flow recreation of the gNMI 0.7.0 PDF map. It keeps the original PDF as a reference while adding an interactive canvas with search, field-level links, a minimap, source/documentation links, and an extension-edge toggle. + +```bash +npm install +npm run dev +``` + +Open the dev-server URL printed by Vite. For a production build: + +```bash +npm run build +``` + ## Usage The map can be downloaded from this repository or viewed right in a browser. The maps for the following gNMI service versions have been created so far: * **gNMI 0.7.0** - [view](https://gitlab.com/rdodin/pics/-/wikis/uploads/d275425d2b66601be213c6722dadd4d6/gnmi_0.7.0_map.pdf) / [download](https://github.com/hellt/gnmi-map/raw/master/gnmi_0.7.0_map.pdf) ## OS X Preview app issue -Mac OS X default PDF reader app - Preview - messes with the link fragments (`http://url.com/page#fragment`), therefore the links won't work in this app (see [1](https://discussions.apple.com/thread/251041261), [2](https://discussions.apple.com/thread/250919338)). \ No newline at end of file +Mac OS X default PDF reader app - Preview - messes with the link fragments (`http://url.com/page#fragment`), therefore the links won't work in this app (see [1](https://discussions.apple.com/thread/251041261), [2](https://discussions.apple.com/thread/250919338)). diff --git a/index.html b/index.html new file mode 100644 index 0000000..82ec82e --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + gNMI React Flow Map + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5d9fa02 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2044 @@ +{ + "name": "gnmi-map", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gnmi-map", + "version": "0.1.0", + "dependencies": { + "@xyflow/react": "^12.8.4", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.0.7" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz", + "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz", + "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz", + "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz", + "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz", + "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz", + "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz", + "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz", + "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz", + "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz", + "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz", + "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz", + "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz", + "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz", + "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz", + "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz", + "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz", + "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz", + "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz", + "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz", + "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz", + "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz", + "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz", + "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz", + "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz", + "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@xyflow/react": { + "version": "12.10.2", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.2.tgz", + "integrity": "sha512-CgIi6HwlcHXwlkTpr0fxLv/0sRVNZ8IdwKLzzeCscaYBwpvfcH1QFOCeaTCuEn1FQEs/B8CjnTSjhs8udgmBgQ==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.76", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.76", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.76.tgz", + "integrity": "sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.32", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", + "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "peer": true, + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.362", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.362.tgz", + "integrity": "sha512-PUY2DrLvkjkUuWqq+KPL2iWshrJsZOcIojzRQ7eXFacc9dWga7MGMJAa15VbiejSZB1PAXaRLAiKgruHP8LB1w==", + "dev": true, + "license": "ISC" + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.46", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz", + "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.60.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", + "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.4", + "@rollup/rollup-android-arm64": "4.60.4", + "@rollup/rollup-darwin-arm64": "4.60.4", + "@rollup/rollup-darwin-x64": "4.60.4", + "@rollup/rollup-freebsd-arm64": "4.60.4", + "@rollup/rollup-freebsd-x64": "4.60.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.4", + "@rollup/rollup-linux-arm-musleabihf": "4.60.4", + "@rollup/rollup-linux-arm64-gnu": "4.60.4", + "@rollup/rollup-linux-arm64-musl": "4.60.4", + "@rollup/rollup-linux-loong64-gnu": "4.60.4", + "@rollup/rollup-linux-loong64-musl": "4.60.4", + "@rollup/rollup-linux-ppc64-gnu": "4.60.4", + "@rollup/rollup-linux-ppc64-musl": "4.60.4", + "@rollup/rollup-linux-riscv64-gnu": "4.60.4", + "@rollup/rollup-linux-riscv64-musl": "4.60.4", + "@rollup/rollup-linux-s390x-gnu": "4.60.4", + "@rollup/rollup-linux-x64-gnu": "4.60.4", + "@rollup/rollup-linux-x64-musl": "4.60.4", + "@rollup/rollup-openbsd-x64": "4.60.4", + "@rollup/rollup-openharmony-arm64": "4.60.4", + "@rollup/rollup-win32-arm64-msvc": "4.60.4", + "@rollup/rollup-win32-ia32-msvc": "4.60.4", + "@rollup/rollup-win32-x64-gnu": "4.60.4", + "@rollup/rollup-win32-x64-msvc": "4.60.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..d260f03 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "gnmi-map", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 0.0.0.0", + "build": "vite build", + "preview": "vite preview --host 0.0.0.0" + }, + "dependencies": { + "@xyflow/react": "^12.8.4", + "lucide-react": "^0.468.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "@vitejs/plugin-react": "^4.3.4", + "vite": "^6.0.7" + } +} diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 0000000..5605399 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,371 @@ +import React, { useCallback, useMemo, useState } from 'react'; +import { + Background, + Controls, + Handle, + MarkerType, + MiniMap, + Position, + ReactFlow, + ReactFlowProvider, + useReactFlow, +} from '@xyflow/react'; +import '@xyflow/react/dist/style.css'; +import { + BookOpen, + ExternalLink, + FileCode2, + FileDown, + Focus, + GitBranch, + Search, +} from 'lucide-react'; +import { mapBounds, mapEdges, mapNodes } from './gnmiMap.js'; + +const edgeStyleByKind = { + rpc: { stroke: '#0b4b8f', strokeWidth: 2.2 }, + field: { stroke: '#5b708a', strokeWidth: 1.6 }, + extension: { + stroke: '#8a6a1f', + strokeWidth: 1.4, + strokeDasharray: '7 6', + }, + 'extension-detail': { stroke: '#b47a18', strokeWidth: 1.5 }, +}; + +const nodeTypes = { + schema: SchemaNode, +}; + +function searchableText(node) { + const fieldText = node.data.fields + ?.map((field) => `${field.type} ${field.name} ${field.group ?? ''} ${field.badge ?? ''}`) + .join(' '); + + return `${node.data.kind} ${node.data.label} ${fieldText ?? ''}`.toLowerCase(); +} + +function fieldMatches(field, query) { + if (!query) { + return false; + } + + return `${field.type} ${field.name} ${field.group ?? ''} ${field.badge ?? ''}` + .toLowerCase() + .includes(query); +} + +function AppShell() { + const { fitView } = useReactFlow(); + const [queryValue, setQueryValue] = useState(''); + const [showExtensions, setShowExtensions] = useState(false); + const [selectedId, setSelectedId] = useState(null); + + const query = queryValue.trim().toLowerCase(); + + const nodeMatches = useMemo(() => { + if (!query) { + return new Set(); + } + + return new Set( + mapNodes + .filter((currentNode) => searchableText(currentNode).includes(query)) + .map((currentNode) => currentNode.id), + ); + }, [query]); + + const nodes = useMemo( + () => + mapNodes.map((currentNode) => { + const active = !query || nodeMatches.has(currentNode.id); + return { + ...currentNode, + data: { + ...currentNode.data, + active, + query, + showExtensions, + }, + }; + }), + [nodeMatches, query, showExtensions], + ); + + const edges = useMemo( + () => + mapEdges + .filter((edge) => { + if (showExtensions) { + return true; + } + + return edge.kind !== 'extension'; + }) + .map((edge) => { + const connectedToMatch = + !query || nodeMatches.has(edge.source) || nodeMatches.has(edge.target); + const style = edgeStyleByKind[edge.kind] ?? edgeStyleByKind.field; + + return { + ...edge, + type: 'smoothstep', + className: `flow-edge flow-edge-${edge.kind}`, + markerEnd: { type: MarkerType.ArrowClosed, color: style.stroke }, + animated: query ? connectedToMatch : edge.kind === 'rpc', + style: { + ...style, + opacity: connectedToMatch ? 1 : 0.12, + }, + }; + }), + [nodeMatches, query, showExtensions], + ); + + const selectedNode = useMemo( + () => nodes.find((currentNode) => currentNode.id === selectedId), + [nodes, selectedId], + ); + + const fit = useCallback(() => { + fitView({ padding: 0.12, duration: 450 }); + }, [fitView]); + + return ( +
+
+
+ gNMI 0.7.0 +

React Flow Map

+
+ +
+ + + + + + + + +
+
+ +
+ setSelectedId(node.id)} + onPaneClick={() => setSelectedId(null)} + proOptions={{ hideAttribution: true }} + > + + + minimapColor(node.data.kind)} + maskColor="rgba(15, 23, 42, 0.08)" + /> + + + +
+
+ ); +} + +function SchemaNode({ data, selected }) { + const fields = data.fields ?? []; + const dimmed = data.active === false; + const className = [ + 'schema-node', + `kind-${data.kind}`, + selected ? 'is-selected' : '', + dimmed ? 'is-dimmed' : '', + ] + .filter(Boolean) + .join(' '); + + return ( +
+ + +
+ {data.kind} + {data.label} +
+ {data.protoUrl ? ( + + + ) : null} + {data.specUrl ? ( + + + ) : null} +
+
+ + {data.badges?.length ? ( +
+ {data.badges.map((badge) => ( + {badge} + ))} +
+ ) : null} + +
+ {fields.length ? ( + fields.map((field) => ( + + )) + ) : ( +
empty message
+ )} +
+
+ ); +} + +function FieldRow({ field, highlighted, showExtensions }) { + const isExtension = field.ref === 'extension'; + const visibleExtensionHandle = !isExtension || showExtensions; + + return ( +
+ {field.type} + {field.name} + {field.group ? {field.group} : null} + {field.badge ? {field.badge} : null} + {field.ref && visibleExtensionHandle ? ( + ${field.ref}`} + /> + ) : null} +
+ ); +} + +function Inspector({ node, totalNodes, totalEdges }) { + if (!node) { + return ( + + ); + } + + return ( + + ); +} + +function minimapColor(kind) { + if (kind === 'service') { + return '#0b4b8f'; + } + if (kind === 'rpc') { + return '#1d6eb8'; + } + if (kind === 'enum') { + return '#ab5d00'; + } + if (kind === 'external') { + return '#485161'; + } + if (kind === 'legend') { + return '#748295'; + } + return '#1f7a72'; +} + +export default function App() { + return ( + + + + ); +} diff --git a/src/gnmiMap.js b/src/gnmiMap.js new file mode 100644 index 0000000..7cc4b10 --- /dev/null +++ b/src/gnmiMap.js @@ -0,0 +1,556 @@ +const GNMIBASE = + 'https://github.com/openconfig/gnmi/blob/d19cebf5e7be48e7a6fa9fbdff668d18ad87be9d/proto/gnmi/gnmi.proto'; +const EXTBBASE = + 'https://github.com/openconfig/gnmi/blob/d19cebf5e7be48e7a6fa9fbdff668d18ad87be9d/proto/gnmi_ext/gnmi_ext.proto'; +const SPECBASE = + 'https://github.com/openconfig/reference/blob/638fba23f697d67a0f8b6b683d492b8a1254817d/rpc/gnmi/gnmi-specification.md'; + +const proto = (line) => `${GNMIBASE}#L${line}`; +const extProto = (line) => `${EXTBBASE}#L${line}`; +const spec = (anchor) => `${SPECBASE}#${anchor}`; + +const node = (id, kind, label, x, y, width, data = {}) => ({ + id, + type: 'schema', + position: { x, y }, + style: { width }, + data: { + id, + kind, + label, + ...data, + }, +}); + +const f = (id, type, name, ref, extra = {}) => ({ + id, + type, + name, + ref, + ...extra, +}); + +const e = (source, sourceHandle, target, kind = 'field') => ({ + id: `${source}:${sourceHandle}->${target}`, + source, + sourceHandle, + target, + kind, +}); + +export const mapNodes = [ + node('service-gnmi', 'service', 'service gNMI 0.7.0', 1240, 40, 330, { + protoUrl: proto(44), + specUrl: spec('grpc-network-management-interface-gnmi'), + fields: [ + f('capabilities', 'rpc', 'Capabilities', 'rpc-capabilities'), + f('get', 'rpc', 'Get', 'rpc-get'), + f('set', 'rpc', 'Set', 'rpc-set'), + f('subscribe', 'rpc', 'Subscribe', 'rpc-subscribe', { badge: 'stream' }), + ], + }), + + node('rpc-set', 'rpc', 'rpc Set', 80, 230, 250, { + protoUrl: proto(62), + specUrl: spec('34-modifying-state'), + fields: [ + f('takes', 'takes', 'SetRequest', 'set-request'), + f('returns', 'returns', 'SetResponse', 'set-response'), + ], + }), + node('rpc-subscribe', 'rpc', 'rpc Subscribe', 780, 230, 290, { + protoUrl: proto(68), + specUrl: spec('35-subscribing-to-telemetry-updates'), + fields: [ + f('takes', 'takes stream', 'SubscribeRequest', 'subscribe-request'), + f('returns', 'returns stream', 'SubscribeResponse', 'subscribe-response'), + ], + }), + node('rpc-get', 'rpc', 'rpc Get', 1520, 230, 250, { + protoUrl: proto(57), + specUrl: spec('33-retrieving-snapshots-of-state-information'), + fields: [ + f('takes', 'takes', 'GetRequest', 'get-request'), + f('returns', 'returns', 'GetResponse', 'get-response'), + ], + }), + node('rpc-capabilities', 'rpc', 'rpc Capabilities', 2200, 230, 300, { + protoUrl: proto(51), + specUrl: spec('32-capability-discovery'), + fields: [ + f('takes', 'takes', 'CapabilityRequest', 'capability-request'), + f('returns', 'returns', 'CapabilityResponse', 'capability-response'), + ], + }), + + node('set-request', 'message', 'SetRequest', 20, 450, 340, { + protoUrl: proto(339), + specUrl: spec('341-the-setrequest-message'), + fields: [ + f('prefix', 'Path', 'prefix', 'path'), + f('delete', 'repeated Path', 'delete', 'path'), + f('replace', 'repeated Update', 'replace', 'update'), + f('update', 'repeated Update', 'update', 'update'), + f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { + badge: 'optional', + }), + ], + }), + node('set-response', 'message', 'SetResponse', 390, 450, 360, { + protoUrl: proto(356), + specUrl: spec('342-the-setresponse-message'), + fields: [ + f('prefix', 'Path', 'prefix', 'path'), + f('response', 'repeated UpdateResult', 'response', 'update-result'), + f('message', 'Error', 'message', 'error', { badge: 'deprecated' }), + f('timestamp', 'int64', 'timestamp'), + f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { + badge: 'optional', + }), + ], + }), + node('subscribe-request', 'message', 'SubscribeRequest', 780, 450, 360, { + protoUrl: proto(208), + specUrl: spec('3511-the-subscriberequest-message'), + fields: [ + f('subscribe', 'SubscriptionList', 'subscribe', 'subscription-list', { + group: 'oneof request', + }), + f('poll', 'Poll', 'poll', 'poll', { group: 'oneof request' }), + f('aliases', 'AliasList', 'aliases', 'alias-list', { + group: 'oneof request', + }), + f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { + badge: 'optional', + }), + ], + }), + node('subscribe-response', 'message', 'SubscribeResponse', 1180, 450, 360, { + protoUrl: proto(232), + specUrl: spec('3514-the-subscriberesponse-message'), + fields: [ + f('update', 'Notification', 'update', 'notification', { + group: 'oneof response', + }), + f('sync-response', 'bool', 'sync_response', null, { + group: 'oneof response', + }), + f('error', 'Error', 'error', 'error', { + badge: 'deprecated', + group: 'oneof response', + }), + f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { + badge: 'optional', + }), + ], + }), + node('get-request', 'message', 'GetRequest', 1570, 450, 350, { + protoUrl: proto(395), + specUrl: spec('331-the-getrequest-message'), + fields: [ + f('prefix', 'Path', 'prefix', 'path'), + f('path', 'repeated Path', 'path', 'path'), + f('type', 'DataType', 'type', 'data-type'), + f('encoding', 'Encoding', 'encoding', 'encoding'), + f('use-models', 'repeated ModelData', 'use_models', 'model-data'), + f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { + badge: 'optional', + }), + ], + }), + node('get-response', 'message', 'GetResponse', 1960, 450, 350, { + protoUrl: proto(420), + specUrl: spec('332-the-getresponse-message'), + fields: [ + f('notification', 'repeated Notification', 'notification', 'notification'), + f('error', 'Error', 'error', 'error', { badge: 'deprecated' }), + f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { + badge: 'optional', + }), + ], + }), + node('capability-request', 'message', 'CapabilityRequest', 2350, 450, 340, { + protoUrl: proto(431), + specUrl: spec('321-the-capabilityrequest-message'), + fields: [ + f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { + badge: 'optional', + }), + ], + }), + node('capability-response', 'message', 'CapabilityResponse', 2730, 450, 370, { + protoUrl: proto(440), + specUrl: spec('322-the-capabilityresponse-message'), + fields: [ + f('supported-models', 'repeated ModelData', 'supported_models', 'model-data'), + f('supported-encodings', 'repeated Encoding', 'supported_encodings', 'encoding'), + f('version', 'string', 'gNMI_version'), + f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { + badge: 'optional', + }), + ], + }), + + node('error', 'message', 'Error', 120, 850, 300, { + protoUrl: proto(179), + specUrl: spec('23-structured-data-types'), + badges: ['deprecated'], + fields: [ + f('code', 'uint32', 'code'), + f('message', 'string', 'message'), + f('data', 'google.protobuf.Any', 'data', 'any'), + ], + }), + node('update-result', 'message', 'UpdateResult', 450, 820, 330, { + protoUrl: proto(371), + specUrl: spec('342-the-setresponse-message'), + fields: [ + f('timestamp', 'int64', 'timestamp', null, { badge: 'deprecated' }), + f('path', 'Path', 'path', 'path'), + f('message', 'Error', 'message', 'error', { badge: 'deprecated' }), + f('op', 'Operation', 'op', 'operation'), + ], + }), + node('operation', 'enum', 'enum Operation', 430, 1120, 260, { + protoUrl: proto(373), + fields: [ + f('invalid', '0', 'INVALID'), + f('delete', '1', 'DELETE'), + f('replace', '2', 'REPLACE'), + f('update', '3', 'UPDATE'), + ], + }), + + node('poll', 'message', 'Poll', 800, 780, 180, { + protoUrl: proto(223), + specUrl: spec('35153-poll-subscriptions'), + fields: [], + }), + node('alias-list', 'message', 'AliasList', 720, 940, 300, { + protoUrl: proto(328), + specUrl: spec('3516-client-defined-aliases-within-a-subscription'), + fields: [f('alias', 'repeated Alias', 'alias', 'alias')], + }), + node('alias', 'message', 'Alias', 710, 1160, 280, { + protoUrl: proto(320), + specUrl: spec('242-path-aliases'), + fields: [ + f('path', 'Path', 'path', 'path'), + f('alias', 'string', 'alias'), + ], + }), + node('subscription-list', 'message', 'SubscriptionList', 1050, 750, 390, { + protoUrl: proto(251), + specUrl: spec('3512-the-subscriptionlist-message'), + fields: [ + f('prefix', 'Path', 'prefix', 'path'), + f('subscription', 'repeated Subscription', 'subscription', 'subscription'), + f('use-aliases', 'bool', 'use_aliases'), + f('qos', 'QOSMarking', 'qos', 'qos-marking'), + f('mode', 'Mode', 'mode', 'mode'), + f('allow-aggregation', 'bool', 'allow_aggregation'), + f('use-models', 'repeated ModelData', 'use_models', 'model-data'), + f('encoding', 'Encoding', 'encoding', 'encoding'), + f('updates-only', 'bool', 'updates_only'), + ], + }), + node('qos-marking', 'message', 'QOSMarking', 990, 1210, 270, { + protoUrl: proto(311), + specUrl: spec('3512-the-subscriptionlist-message'), + fields: [f('marking', 'uint32', 'marking')], + }), + node('mode', 'enum', 'enum Mode', 990, 1390, 240, { + protoUrl: proto(258), + specUrl: spec('3512-the-subscriptionlist-message'), + fields: [ + f('stream', '0', 'STREAM'), + f('once', '1', 'ONCE'), + f('poll', '2', 'POLL'), + ], + }), + node('subscription', 'message', 'Subscription', 1460, 920, 360, { + protoUrl: proto(286), + specUrl: spec('3513-the-subscription-message'), + fields: [ + f('path', 'Path', 'path', 'path'), + f('mode', 'SubscriptionMode', 'mode', 'subscription-mode'), + f('sample-interval', 'uint64', 'sample_interval'), + f('suppress-redundant', 'bool', 'suppress_redundant'), + f('heartbeat-interval', 'uint64', 'heartbeat_interval'), + ], + }), + node('subscription-mode', 'enum', 'enum SubscriptionMode', 1460, 1250, 310, { + protoUrl: proto(302), + specUrl: spec('35152-stream-subscriptions'), + fields: [ + f('target-defined', '0', 'TARGET_DEFINED'), + f('on-change', '1', 'ON_CHANGE'), + f('sample', '2', 'SAMPLE'), + ], + }), + + node('data-type', 'enum', 'enum DataType', 1660, 720, 280, { + protoUrl: proto(399), + specUrl: spec('331-the-getrequest-message'), + fields: [ + f('all', '0', 'ALL'), + f('config', '1', 'CONFIG'), + f('state', '2', 'STATE'), + f('operational', '3', 'OPERATIONAL'), + ], + }), + node('model-data', 'message', 'ModelData', 2410, 790, 300, { + protoUrl: proto(454), + specUrl: spec('261-the-modeldata-message'), + fields: [ + f('name', 'string', 'name'), + f('organization', 'string', 'organization'), + f('version', 'string', 'version'), + ], + }), + node('encoding', 'enum', 'enum Encoding', 2380, 1120, 260, { + protoUrl: proto(167), + specUrl: spec('23-structured-data-types'), + fields: [ + f('json', '0', 'JSON'), + f('bytes', '1', 'BYTES'), + f('proto', '2', 'PROTO'), + f('ascii', '3', 'ASCII'), + f('json-ietf', '4', 'JSON_IETF'), + ], + }), + + node('notification', 'message', 'Notification', 1740, 1160, 350, { + protoUrl: proto(79), + specUrl: spec('21-reusable-notification-message-format'), + fields: [ + f('timestamp', 'int64', 'timestamp'), + f('prefix', 'Path', 'prefix', 'path'), + f('alias', 'string', 'alias'), + f('update', 'repeated Update', 'update', 'update'), + f('delete', 'repeated Path', 'delete', 'path'), + f('atomic', 'bool', 'atomic'), + ], + }), + node('update', 'message', 'Update', 1320, 1460, 330, { + protoUrl: proto(95), + specUrl: spec('21-reusable-notification-message-format'), + fields: [ + f('path', 'Path', 'path', 'path'), + f('value', 'Value', 'value', 'value', { badge: 'deprecated' }), + f('val', 'TypedValue', 'val', 'typed-value'), + f('duplicates', 'uint32', 'duplicates'), + ], + }), + node('path', 'message', 'Path', 1780, 1530, 360, { + protoUrl: proto(135), + specUrl: spec('222-paths'), + fields: [ + f('element', 'repeated string', 'element', null, { badge: 'deprecated' }), + f('origin', 'string', 'origin'), + f('elem', 'repeated PathElem', 'elem', 'path-elem'), + f('target', 'string', 'target'), + ], + }), + node('path-elem', 'message', 'PathElem', 2210, 1600, 330, { + protoUrl: proto(148), + specUrl: spec('222-paths'), + fields: [ + f('name', 'string', 'name'), + f('key', 'map', 'key'), + ], + }), + node('value', 'message', 'Value', 700, 1570, 300, { + protoUrl: proto(156), + specUrl: spec('223-node-values'), + badges: ['deprecated'], + fields: [ + f('value', 'bytes', 'value'), + f('type', 'Encoding', 'type', 'encoding'), + ], + }), + node('typed-value', 'message', 'TypedValue', 980, 1780, 390, { + protoUrl: proto(104), + fields: [ + f('string-val', 'string', 'string_val', null, { group: 'oneof value' }), + f('int-val', 'int64', 'int_val', null, { group: 'oneof value' }), + f('uint-val', 'uint64', 'uint_val', null, { group: 'oneof value' }), + f('bool-val', 'bool', 'bool_val', null, { group: 'oneof value' }), + f('bytes-val', 'bytes', 'bytes_val', null, { group: 'oneof value' }), + f('float-val', 'float', 'float_val', null, { group: 'oneof value' }), + f('decimal-val', 'Decimal64', 'decimal_val', 'decimal64', { + group: 'oneof value', + }), + f('leaflist-val', 'ScalarArray', 'leaflist_val', 'scalar-array', { + group: 'oneof value', + }), + f('any-val', 'google.protobuf.Any', 'any_val', 'any', { + group: 'oneof value', + }), + f('json-val', 'bytes', 'json_val', null, { group: 'oneof value' }), + f('json-ietf-val', 'bytes', 'json_ietf_val', null, { + group: 'oneof value', + }), + f('ascii-val', 'string', 'ascii_val', null, { group: 'oneof value' }), + f('proto-bytes', 'bytes', 'proto_bytes', null, { group: 'oneof value' }), + ], + }), + node('decimal64', 'message', 'Decimal64', 1430, 1780, 280, { + protoUrl: proto(189), + fields: [ + f('digits', 'int64', 'digits'), + f('precision', 'uint32', 'precision'), + ], + }), + node('scalar-array', 'message', 'ScalarArray', 1430, 1980, 330, { + protoUrl: proto(195), + fields: [f('element', 'repeated TypedValue', 'element', 'typed-value')], + }), + node('any', 'external', 'google.protobuf.Any', 550, 1870, 330, { + protoUrl: 'https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto', + fields: [ + f('type-url', 'string', 'type_url'), + f('value', 'bytes', 'value'), + ], + }), + + node('extension', 'message', 'gnmi_ext.Extension', 2020, 820, 360, { + protoUrl: extProto(27), + specUrl: spec('27-extensions-to-gnmi'), + fields: [ + f('registered-ext', 'RegisteredExtension', 'registered_ext', 'registered-extension', { + group: 'oneof ext', + }), + f('master-arbitration', 'MasterArbitration', 'master_arbitration', 'master-arbitration', { + group: 'oneof ext', + }), + ], + }), + node('registered-extension', 'message', 'gnmi_ext.RegisteredExtension', 2510, 760, 390, { + protoUrl: extProto(37), + fields: [ + f('id', 'ExtensionID', 'id', 'extension-id'), + f('msg', 'bytes', 'msg'), + ], + }), + node('extension-id', 'enum', 'enum gnmi_ext.ExtensionID', 2940, 700, 310, { + protoUrl: extProto(44), + fields: [ + f('unset', '0', 'EID_UNSET'), + f('experimental', '999', 'EID_EXPERIMENTAL'), + ], + }), + node('master-arbitration', 'message', 'gnmi_ext.MasterArbitration', 2510, 1010, 390, { + protoUrl: extProto(59), + fields: [ + f('role', 'Role', 'role', 'role'), + f('election-id', 'Uint128', 'election_id', 'uint128'), + ], + }), + node('uint128', 'message', 'gnmi_ext.Uint128', 2940, 1040, 260, { + protoUrl: extProto(65), + fields: [ + f('high', 'uint64', 'high'), + f('low', 'uint64', 'low'), + ], + }), + node('role', 'message', 'gnmi_ext.Role', 2940, 1210, 260, { + protoUrl: extProto(71), + fields: [f('id', 'string', 'id')], + }), + + node('legend', 'legend', 'Legend', 2720, 1470, 410, { + fields: [ + f('proto', 'file icon', 'proto definition link'), + f('docs', 'book icon', 'documentation link'), + f('extension-note', 'toggle', 'extension relationship edges'), + f('pdf', 'reference', 'gnmi_0.7.0_map.pdf'), + ], + }), +]; + +export const mapEdges = [ + e('service-gnmi', 'set', 'rpc-set', 'rpc'), + e('service-gnmi', 'subscribe', 'rpc-subscribe', 'rpc'), + e('service-gnmi', 'get', 'rpc-get', 'rpc'), + e('service-gnmi', 'capabilities', 'rpc-capabilities', 'rpc'), + + e('rpc-set', 'takes', 'set-request', 'rpc'), + e('rpc-set', 'returns', 'set-response', 'rpc'), + e('rpc-subscribe', 'takes', 'subscribe-request', 'rpc'), + e('rpc-subscribe', 'returns', 'subscribe-response', 'rpc'), + e('rpc-get', 'takes', 'get-request', 'rpc'), + e('rpc-get', 'returns', 'get-response', 'rpc'), + e('rpc-capabilities', 'takes', 'capability-request', 'rpc'), + e('rpc-capabilities', 'returns', 'capability-response', 'rpc'), + + e('set-request', 'prefix', 'path'), + e('set-request', 'delete', 'path'), + e('set-request', 'replace', 'update'), + e('set-request', 'update', 'update'), + e('set-request', 'extension', 'extension', 'extension'), + e('set-response', 'prefix', 'path'), + e('set-response', 'response', 'update-result'), + e('set-response', 'message', 'error'), + e('set-response', 'extension', 'extension', 'extension'), + e('update-result', 'path', 'path'), + e('update-result', 'message', 'error'), + e('update-result', 'op', 'operation'), + + e('subscribe-request', 'subscribe', 'subscription-list'), + e('subscribe-request', 'poll', 'poll'), + e('subscribe-request', 'aliases', 'alias-list'), + e('subscribe-request', 'extension', 'extension', 'extension'), + e('subscribe-response', 'update', 'notification'), + e('subscribe-response', 'error', 'error'), + e('subscribe-response', 'extension', 'extension', 'extension'), + e('alias-list', 'alias', 'alias'), + e('alias', 'path', 'path'), + e('subscription-list', 'prefix', 'path'), + e('subscription-list', 'subscription', 'subscription'), + e('subscription-list', 'qos', 'qos-marking'), + e('subscription-list', 'mode', 'mode'), + e('subscription-list', 'use-models', 'model-data'), + e('subscription-list', 'encoding', 'encoding'), + e('subscription', 'path', 'path'), + e('subscription', 'mode', 'subscription-mode'), + + e('get-request', 'prefix', 'path'), + e('get-request', 'path', 'path'), + e('get-request', 'type', 'data-type'), + e('get-request', 'encoding', 'encoding'), + e('get-request', 'use-models', 'model-data'), + e('get-request', 'extension', 'extension', 'extension'), + e('get-response', 'notification', 'notification'), + e('get-response', 'error', 'error'), + e('get-response', 'extension', 'extension', 'extension'), + e('capability-request', 'extension', 'extension', 'extension'), + e('capability-response', 'supported-models', 'model-data'), + e('capability-response', 'supported-encodings', 'encoding'), + e('capability-response', 'extension', 'extension', 'extension'), + + e('notification', 'prefix', 'path'), + e('notification', 'update', 'update'), + e('notification', 'delete', 'path'), + e('update', 'path', 'path'), + e('update', 'value', 'value'), + e('update', 'val', 'typed-value'), + e('path', 'elem', 'path-elem'), + e('value', 'type', 'encoding'), + e('typed-value', 'decimal-val', 'decimal64'), + e('typed-value', 'leaflist-val', 'scalar-array'), + e('typed-value', 'any-val', 'any'), + e('scalar-array', 'element', 'typed-value'), + e('error', 'data', 'any'), + + e('extension', 'registered-ext', 'registered-extension', 'extension-detail'), + e('extension', 'master-arbitration', 'master-arbitration', 'extension-detail'), + e('registered-extension', 'id', 'extension-id', 'extension-detail'), + e('master-arbitration', 'role', 'role', 'extension-detail'), + e('master-arbitration', 'election-id', 'uint128', 'extension-detail'), +]; + +export const mapBounds = { + width: 3300, + height: 2220, +}; diff --git a/src/main.jsx b/src/main.jsx new file mode 100644 index 0000000..a853e23 --- /dev/null +++ b/src/main.jsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import App from './App.jsx'; +import './styles.css'; + +createRoot(document.getElementById('root')).render( + + + , +); diff --git a/src/styles.css b/src/styles.css new file mode 100644 index 0000000..f471489 --- /dev/null +++ b/src/styles.css @@ -0,0 +1,498 @@ +:root { + --page-bg: #f4f6f8; + --panel: #ffffff; + --panel-soft: #f8fafc; + --ink: #172033; + --muted: #677486; + --line: #cfd8e3; + --blue: #0b4b8f; + --blue-soft: #d9eaf9; + --teal: #16736b; + --teal-soft: #dcefeb; + --amber: #a15c03; + --amber-soft: #fbebd3; + --gray: #4d5a6b; + --focus: #2474c9; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + height: 100%; + margin: 0; +} + +body { + background: var(--page-bg); + color: var(--ink); +} + +button, +input { + font: inherit; +} + +a { + color: inherit; +} + +.app-shell { + display: grid; + grid-template-rows: auto 1fr; + height: 100%; + min-width: 0; +} + +.topbar { + z-index: 5; + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + min-height: 76px; + padding: 12px 18px; + border-bottom: 1px solid var(--line); + background: rgba(255, 255, 255, 0.94); + backdrop-filter: blur(10px); +} + +.brand { + min-width: 178px; +} + +.brand-kicker, +.inspector-kicker { + display: block; + color: var(--muted); + font-size: 11px; + font-weight: 700; + line-height: 1.2; + text-transform: uppercase; +} + +.brand h1, +.inspector h2 { + margin: 2px 0 0; + font-size: 21px; + line-height: 1.2; +} + +.toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: flex-end; + gap: 8px; + min-width: 0; +} + +.search-box { + display: flex; + align-items: center; + gap: 8px; + width: min(360px, 44vw); + min-width: 210px; + height: 38px; + padding: 0 11px; + border: 1px solid var(--line); + border-radius: 7px; + background: var(--panel); + color: var(--muted); +} + +.search-box input { + min-width: 0; + width: 100%; + border: 0; + outline: 0; + background: transparent; + color: var(--ink); +} + +.tool-button { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 7px; + height: 38px; + padding: 0 12px; + border: 1px solid var(--line); + border-radius: 7px; + background: var(--panel); + color: var(--ink); + cursor: pointer; + font-size: 14px; + font-weight: 700; + text-decoration: none; +} + +.tool-button:hover, +.tool-button:focus-visible, +.tool-button.is-active { + border-color: var(--focus); + color: var(--focus); + outline: none; +} + +.map-stage { + position: relative; + min-height: 0; +} + +.react-flow { + background: + linear-gradient(90deg, rgba(11, 75, 143, 0.04), transparent 36%), + linear-gradient(0deg, rgba(22, 115, 107, 0.04), transparent 42%), + var(--page-bg); +} + +.react-flow__node { + font-family: inherit; +} + +.schema-node { + overflow: hidden; + border: 1px solid #b8c5d3; + border-radius: 8px; + background: var(--panel); + box-shadow: 0 8px 22px rgba(31, 41, 55, 0.09); + color: var(--ink); + transition: + box-shadow 160ms ease, + opacity 160ms ease, + transform 160ms ease; +} + +.schema-node.is-selected { + border-color: var(--focus); + box-shadow: + 0 0 0 3px rgba(36, 116, 201, 0.16), + 0 10px 28px rgba(31, 41, 55, 0.16); +} + +.schema-node.is-dimmed { + opacity: 0.22; +} + +.node-header { + display: grid; + grid-template-columns: auto minmax(0, 1fr) auto; + align-items: center; + gap: 8px; + min-height: 36px; + padding: 8px 10px; + background: var(--teal); + color: #fff; +} + +.kind-service .node-header { + background: var(--blue); +} + +.kind-rpc .node-header { + background: #1c62a0; +} + +.kind-enum .node-header { + background: var(--amber); +} + +.kind-external .node-header, +.kind-legend .node-header { + background: var(--gray); +} + +.node-kind { + padding: 2px 6px; + border-radius: 999px; + background: rgba(255, 255, 255, 0.16); + color: rgba(255, 255, 255, 0.9); + font-size: 10px; + font-weight: 800; + line-height: 1.2; + text-transform: uppercase; +} + +.node-header strong { + overflow: hidden; + font-size: 14px; + line-height: 1.2; + text-overflow: ellipsis; + white-space: nowrap; +} + +.node-links { + display: inline-flex; + align-items: center; + gap: 5px; +} + +.node-links a { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border-radius: 6px; + background: rgba(255, 255, 255, 0.14); +} + +.node-links a:hover, +.node-links a:focus-visible { + background: rgba(255, 255, 255, 0.25); + outline: none; +} + +.node-badges { + display: flex; + gap: 6px; + padding: 7px 10px 0; +} + +.node-badges span, +.field-badge, +.field-group { + display: inline-flex; + align-items: center; + min-height: 18px; + padding: 2px 6px; + border-radius: 999px; + font-size: 10px; + font-weight: 800; + line-height: 1; + text-transform: uppercase; +} + +.node-badges span, +.badge-deprecated { + background: #fee2df; + color: #9b1c15; +} + +.badge-optional { + background: var(--amber-soft); + color: #744200; +} + +.field-group { + background: var(--blue-soft); + color: #164675; +} + +.node-body { + padding: 8px; +} + +.field-row, +.empty-field { + position: relative; + display: grid; + grid-template-columns: minmax(86px, 0.9fr) minmax(84px, 1fr); + align-items: center; + column-gap: 8px; + row-gap: 5px; + min-height: 30px; + padding: 6px 9px; + border-radius: 6px; + font-size: 12px; + line-height: 1.25; +} + +.field-row:nth-child(odd) { + background: var(--panel-soft); +} + +.field-row.has-ref { + padding-right: 18px; +} + +.field-row.is-highlighted { + background: #fff5c7; + box-shadow: inset 0 0 0 1px #ecd676; +} + +.field-row.is-deprecated .field-type, +.field-row.is-deprecated .field-name { + text-decoration: line-through; + text-decoration-color: rgba(155, 28, 21, 0.55); +} + +.field-type { + overflow-wrap: anywhere; + color: #526071; + font-weight: 700; +} + +.field-name { + overflow-wrap: anywhere; + font-family: + "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; + font-weight: 700; +} + +.field-group, +.field-badge { + grid-column: span 1; + justify-self: start; +} + +.empty-field { + display: flex; + color: var(--muted); + font-style: italic; +} + +.react-flow__handle { + width: 8px; + height: 8px; + border: 2px solid #fff; + background: #2d6f97; +} + +.node-target { + left: -5px; +} + +.field-handle { + right: -12px; + background: #2d6f97; +} + +.kind-enum .field-handle, +.kind-enum .react-flow__handle { + background: var(--amber); +} + +.flow-edge-rpc .react-flow__edge-path { + filter: drop-shadow(0 1px 1px rgba(11, 75, 143, 0.16)); +} + +.react-flow__minimap { + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: rgba(255, 255, 255, 0.94); +} + +.react-flow__controls { + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + box-shadow: 0 8px 20px rgba(31, 41, 55, 0.12); +} + +.react-flow__controls-button { + border-bottom-color: var(--line); +} + +.inspector { + position: absolute; + top: 18px; + right: 18px; + z-index: 4; + width: min(330px, calc(100vw - 36px)); + max-height: calc(100% - 36px); + overflow: auto; + padding: 16px; + border: 1px solid var(--line); + border-radius: 8px; + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 14px 34px rgba(31, 41, 55, 0.16); +} + +.inspector p { + margin: 10px 0 0; + color: var(--muted); + font-size: 14px; + line-height: 1.45; +} + +.inspector-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-top: 12px; +} + +.inspector-actions a { + display: inline-flex; + align-items: center; + gap: 7px; + min-height: 32px; + padding: 6px 9px; + border: 1px solid var(--line); + border-radius: 7px; + color: var(--blue); + font-size: 13px; + font-weight: 800; + text-decoration: none; +} + +.inspector-actions a:hover, +.inspector-actions a:focus-visible { + border-color: var(--blue); + outline: none; +} + +.inspector-fields { + display: grid; + gap: 6px; + margin-top: 14px; +} + +.inspector-field { + display: grid; + grid-template-columns: minmax(72px, 0.8fr) minmax(96px, 1fr); + gap: 8px; + padding: 7px 8px; + border-radius: 6px; + background: var(--panel-soft); + font-size: 12px; +} + +.inspector-field span { + overflow-wrap: anywhere; + color: var(--muted); + font-weight: 700; +} + +.inspector-field strong { + overflow-wrap: anywhere; + font-family: + "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace; +} + +@media (max-width: 760px) { + .topbar { + align-items: stretch; + flex-direction: column; + gap: 10px; + } + + .toolbar { + justify-content: flex-start; + } + + .search-box { + width: 100%; + } + + .tool-button { + flex: 1 1 120px; + } + + .inspector { + top: auto; + right: 10px; + bottom: 10px; + left: 10px; + width: auto; + max-height: 34%; + } +} From 9500406f0642d9540d72189ab461bb4dd834b16a Mon Sep 17 00:00:00 2001 From: Flosch62 Date: Wed, 27 May 2026 10:08:57 +0200 Subject: [PATCH 2/8] use latest gnmi specs --- README.md | 18 +- gnmi_0.10.0_map.pdf | Bin 0 -> 49903 bytes package-lock.json | 229 +++ package.json | 5 + public/gnmi_0.10.0_map.pdf | Bin 0 -> 49903 bytes scripts/generate-map-data.mjs | 532 +++++ scripts/generate-pdf.mjs | 368 ++++ scripts/validate-map.mjs | 85 + src/App.jsx | 80 +- src/gnmiMap.js | 3429 +++++++++++++++++++++++++++------ src/styles.css | 5 + 11 files changed, 4170 insertions(+), 581 deletions(-) create mode 100644 gnmi_0.10.0_map.pdf create mode 100644 public/gnmi_0.10.0_map.pdf create mode 100644 scripts/generate-map-data.mjs create mode 100644 scripts/generate-pdf.mjs create mode 100644 scripts/validate-map.mjs diff --git a/README.md b/README.md index 14bed92..d692261 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ gNMI Map makes it easy to understand the composition of the gNMI service as well ## React Flow map -This repository now includes a React Flow recreation of the gNMI 0.7.0 PDF map. It keeps the original PDF as a reference while adding an interactive canvas with search, field-level links, a minimap, source/documentation links, and an extension-edge toggle. +This repository now includes a React Flow recreation of the latest tagged upstream gNMI protobuf IDL. The app currently tracks `openconfig/gnmi` release `v0.14.1`, whose `gnmi.proto` advertises gNMI service compatibility `0.10.0`. It keeps the original gNMI 0.7.0 PDF as a reference while adding an interactive canvas with search, field-level links, a minimap, source/documentation links, an extension-edge toggle, and a deprecated-field toggle. Deprecated proto fields are hidden by default so the map follows the current spec-facing surface. ```bash npm install @@ -24,9 +24,25 @@ Open the dev-server URL printed by Vite. For a production build: npm run build ``` +To refresh the generated React Flow map from the latest `openconfig/gnmi` tag: + +```bash +npm run build:map +npm run test:map +``` + +Proto links are pinned to the latest gNMI tag. Specification links track `openconfig/reference` `master`, since that repository does not publish tags. + +To refresh the generated PDF map from the default non-deprecated view: + +```bash +npm run build:pdf +``` + ## Usage The map can be downloaded from this repository or viewed right in a browser. The maps for the following gNMI service versions have been created so far: +* **gNMI 0.10.0** - [view](./gnmi_0.10.0_map.pdf) * **gNMI 0.7.0** - [view](https://gitlab.com/rdodin/pics/-/wikis/uploads/d275425d2b66601be213c6722dadd4d6/gnmi_0.7.0_map.pdf) / [download](https://github.com/hellt/gnmi-map/raw/master/gnmi_0.7.0_map.pdf) ## OS X Preview app issue diff --git a/gnmi_0.10.0_map.pdf b/gnmi_0.10.0_map.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ec70b7d5beaad0234d88d3821ef9f7214b5fed8b GIT binary patch literal 49903 zcmcd!2RxPS`!}-pCMr9dvv5kbY>KQX8OO+88CfYZva&~{j7T;idvDTE5}7BZWMtF- zIY;mRmtXOopU>xg-_OzUIL~vQ`?|jO^}VlkKQGrg`Llu`AqX+o58yvykO->?tApuP zVhIUi;R~LQ2v%VQ4_8HPR})tRv9Os5tB8=8II*yt% zu!AGQ-ps+?!rD^U(%#Nm*wog+RQMWjd#Dgd*wNX+)nWJVs1JqE#~f-eQ_5ZHR?sSV5@4X#J&78GCyNS7Ko;gqbU=AxKmN$_fTU#92Y2U@-6-O!N}5 zu&jf#Il`IM5QT;aaD0{(`s>X^f4v#zvN4Q;pfCXOyv4z4bO4i>vc z46w9?gR>o)%zxDgr5MFl zU>IsC#)PgQhoq~+p$!%^GjTLAwYIf(^%OL>b}@6fhH&=WBm9AUh92#H!lTySUExt{ zFGj*2lFu*(76RR4$B+X#3bH>(K}4afAaO9T_=ndcc2%79uXTD?L;MhN1L_&7VaBfuKL31p$E(X44knt^WkC|AX&%IBa2{pH}gj(9Y(7!?2s&V zIFKQ`c5Hdz>I*D12g!dxpa=&n0PTE$;G!5>Fzk>-hB>&9JuD47ke<+^`7^j+2$0%B z3kQZkFa`IJ^mIsYcUNFExCfZ-ffNQLG4_iP0kMCg3x)uC!fbXQlEN@Y7Yh2-bVU#3 zCD{JF1Qvz=g{~L~Lv%$C$xDYr7rbk_|A~qYSc8ET<^T&uk9(g@FDgSPD~uY7zz_-6&C>@hKl`t{407$jyfE~ zP>jMPu>*MwJ>owbu(${SEn0jqfGj5Biye~1{s(0DWHx~4^#JolkLJ&!p&1$kAd4nw z5C+JK{nN|@hU}@B02$PSMBAU4;OI#U2MS{%s96gG{r#*3iVVNp3-i44;btui1cL}V zI@sF&m1uWF)y(!})x{2^BlK|pj4L2BYR;miM@?Fcq~Ajim`jiGqy_KI+KBDpi2n`= z#89KzM>r@d?t=mOI1m&S_rWko$cgVUe$0hCEVMrvX*cde18Zyj=e(Tw9w=^VIM|n*h3VUs{vz-A&@=3qxc@S_)R#}X!a2fm@I(hiUJseQAt-3@xM1; zz~KK?^Zg$%-a{_pCq6GLMsSI-)7TM!A!Ve@U z^l0|MR~(hPfdJkG;0^;e48g#?0LrAlOeV}H?_m*!{FP$Nlu5w z7X}6@+0FYqCp}D zauYB;_k|cNhRVJ|fyD_dhRVLeFj#?6>7HM#1UN>& z4C%iKY@$BwP#Jchw95(xp)%|s$ltHPs1tuNmwy^x5YS=f z=3-)Mix33bmjAys_%A~i-0d&>HJ$*Z_;rB4!HpjOKKcLv{!1UIR6Az+I3%NC3^%aG z?3M3ezB;hm8>l0S18{>xfs~&(Mra?Btq$dI0riys9qohJ4YWVI0ZITuC$u&I9~Luh z9Fp5G$9iwe83=SRbpeCl&jACYI#|R|-GrzH7BLJuzCnAt%l~Q127?71O$)zOx{B>8Rlrh_O8U}hVH+%xBS+~jw*odizcd9MHEPC0vj~I ztfFuXcp3UIRqTFI@b7c=0Oo-J zkuN&C4gmsROp@zB7W3DeVQ!lkO9vJa-?Pe~yRZ+KtNSw#;3NT!%&6Ld7?65}U{E*# z?QIDE8|lzHpv)X>P2DVj4Mi@yJEVYvJ==<0oPljc4i1*Ki2rjBtgL#m?RJ-{W4?zMobKMg&89}T+th-P`~`OI}E%hhzIR$ z6aQVr19%7WSM3%I#k6<}0@AXF67z6AgP0&7IKtiqRs2HjGeOPxy#OD~Lg>-{8DKzW zRD6euf1sGwZULY1aMB-Ssec3+3fU{>!E}Tk);?mwQB4>yR2Tq9HDSOoaSVYigNFk7 ze-?9ZrwIt%j=euGp@;QnF(F`7yEY71L?K{Qzcvh$RdjD}_&;c}qI+G1y^Z3(nJju( ze-;yrMiUH_zIM?BV^I16?d<{o2gMZIQ{MyaZ3zEe%>9`O0_+w6Dn9_;5HM=eVzhG= zaH9U5Nh`Le2?3yxU+??7n5bd>8O>cWQ5C#hF)^v&0cGu9Z}$(PDZXdx6VTi8>wSM0 zbAOKrERM=FiJ&M*9F=Jj!GwZ<;O5^!6TX*e(Y@6BQxJMs`=JR$=csBO1Pu5Bm{jY4 zoZ`PEB#`Lbv!3j2@%{}=^tk>kC7OZ6P{9gnIT6EzX7~1Y|AR9Y^k1&g-d^wDr96Ou zAVB;g3b?tz-fBQfOx)bPUEKeql#soKxwoVHcPS6zAHbPMxdsqa9|cC9!QOW6e^N^5 z-ozNXQF?#7M33vwGZuns7lEKX160U@(Tx4K$Op*d?6u2-Q$m2Vy6;+o+K7l=F;N>4 zFLEEU!1Hm?Cj*%*)HT4i^xZeKK>|5}-6QnnfZvKf+D#V$ ztL5&|?kT&mH?aL1{m0$d4Ul~IX!jJf%dW+`>pQaUI#;Y{uSkm(?KS{!yEob;c-CF+ zV+EXL)W~*ihjn*qv7%>$7Av}A9eCS41>Fe^9PNG!x_ed&D1V?YNB3C*N4wIZJMw@d z)Iy`hitgV6-cVIF;BEI5bkhrPw0p{KlY|z^ZAYK9TT(|I@9J&00t+0YOO>eipXdFh z-k^H7c>j4GkbFVid^hEadjEOf-Nz{acJlMQ-A(wYKB?XFclT}sytpgo zZnz0tzq{oXc>j6c?(Rrb4rKQ_v{wk6x4ZWYl{EPI_n-IO-L3;CsqO9`%iBM;>o3>+nE$b19G~DPQH4CJtt@HgT3&gsc_PiG zY0i{aQr?(KLI-i8_|&7$=>Y6_znZNv%|eGn_>xkGWLxW*=B@51hSj$wj(LG7U!kXcBcIn!Zf?<>m|2W>$C5SEimiOJ1c=U>jnL=pKif&E@&$~ zfA(Q(6>F<-NytYv>irn~s#Lt+599P5q@Ul0gF|wJ&;<%IQvDG~ot=B?X27a@b1U-P zxoT_@k6HW~+86G#SmjbOZL~CA30f<&FrOu%T8nngz~$t+lb=jh3VCzpXARl0?yk_y z8qb$JG`3JH?EGQ3a7#t4^K^dj8rbApy5`PX;UDR(6ZJdHwP$^N9u_Y5@!?E?ppS%- z-NRq4Pmk?<{;{$w{9}Vce<#vULMGJu##Gwm;v|uZjGzayo0^hg-pq7jdUh;rXY+^t z$YQwf+-&=g^=r1<&JRCwxfw{M)wFNSfv1ua{1H`8u8ufg-;heboWAo;nJ|uz5Mhhg zz#bl$s44fTdt>1U=m}-AjmFsyIg?v86ysHNKLQB*L$$;yG6z75Ho;qggeLKz3);T+ ztsfVds>-J&{8b1ZF#1oowk#{w_d*d@w302|^2wWb@sj23h$i&twatuywc{B}x zf?>i>b055Pk;eXJ_Q;=N_N@(QbIqtS7h2ixpWT@ht8~AZ4k`N(7hj?b2dlP8trUcv zJ@xzpS7{}`Py7T66#NyJcZ_h1U40pW7)_D3etD_eRE$4PtlrB2=(|@-7wY6$?`}*XQva1uhL`EDGdgW}PGl~!oEjopi zPamN3SPwHp@yi}hmnWX-9S_ulaB*jmyMx@6rP8|IZ1X7SdRFj_J?*$&Gzz-XyK;+h z1R^>KDMkL!Yz@<07(7=qH;7YaO>!0(rYIE9N+bX##+wvGQ2pA8JxH^%AH$RY4Wn&M-eQNw~nm873K=W19Ngm zq-S5C?8R;#GSr$;?t0l$x}wW?qFne)%ed$_$pw`+mO^Z`rWui|*c5;@NhC*4hUu%h zAjpyAC%-d$D3z58Da^b02&qmtV;|SnTJ$KY=4s-K2zhrsuFZ+WZyzq92bI3dWT&rE> zc-zc9)7zG_JGsb3WYn<#OYH=!BCxu@) zD?jkAb@Ln0)d&`l?1_ljXaz&^MtvluYk#9|^$T)!A}zb6(C7&q4!R82%n4rIxVUmz zzPw8?y);XGkk*IcIxpoKZ2`o*2hF-Bhie=0`Qp$UBNwz*gcy?F{SdeKD5p_xfuDbj zOrv%=r`dxd|7Ggu57rXOOdor4a-_2S$eZD}`@-{IAw|l}klNFp$i6dg<|9`_bbG#> zPV@wABMIMO36zDv29n4HZ}Pi7nxt^unsf5cl86)$`T=jMm=PJh5vg#}dj+X7+fU?2 zkpFTHnLOemjFdG!v#!<<$oX=?;%bci*-u!lr%7!e4Cgr}lMk}0e8D+#taR`$L9#e_ zz~@uu%kehd1bI6qU7lWi?{u%cQ?u-3!f~Eq26fK|R4MshY}=O)-j3wt_}oszsCmcT zYv5`@&C;V+%?&sVuA#)<4*FengRLfy@Dk*!m0O;lbCZ!un-ITGFtL?t(|1$@kCEy< z!x7dW)$uN@{hLzTHRcb!r8Q`u;C_j-e-x5BTu^r!lCi+N`O)x(D1O3Srv(s^6N7a9 z2zOdSGSAh{#G5KJ2G)61mb7Om(<{1&PYrc#Yc>cT|H1fD;S zBgl>VqJRxPiT};e{Jg#wtttc`RZqXV@#Mo}RaG{#)N9V&wH0ef*P@V(g{jZtEEihn z)35fm60kD*m$eY)`h^+yH*2H6sSrx5HV%2QWV(vKvn7-HkE(Th-w%QxW62b&M| z1!i9+cEw^+;Z%k4xdm|zo1eEC_;k$hfy*mvZ69gh+ea-S13Fg)w~1cRE^FlT1m?cH zOq}GNIvu93m80q3rSwoV$<29*qCd{kmx``vv~pM#ClT4pIU7*#NAnrm-}8N2a?wI{ zI83kZZCi2*F3I-EcZK8$$!n>!@mVU5%08x3h4vdYMvKnA(_(cIf7*c*k7o#POsd%m z>UimE>A5yXKcmqS7Q8$si6gbPs9o1*l&OZio5Wcx#-@Igl*ZtrW8~$^)gqYM%*{?$ zU3kA!TIZQdcb+DvA!QcZgFcUm26Z7h+I{IkAHFfDOwgNBhO(Z$#g3nmPdS;xNZ+i` z^_2!U--)nOFpxdG-d_H#P|PAn>&pUq$^MfwHuorN_`5`pYn~Z9BKjqiu@u>6d#|s~ zUBVl6doV6L-!hFfx}~n^G;k^!)n^vw=bNIVXL(Cyem(&U6t~wCIbSrd#*#Uj#J@TVRA;kS~}GSWpg z_5JxvgDJg0`lH(k@v7uugdH;6Z@M_|^*ZM+W#xE$g&9?*ww1BS_y%f+2{4>_-QE(G zEMn6W(R~r?MqK+kQp?3JuKzqzH2o6sJog0;TOq=RBhJS}S}C*6n3NIVa(O7xu2*Cy zT!3UJoBGxVD&8c3dvkqboh0{ww|bXd^STyYvgiR4x^vl6mmK`PXDz|mZg>{v4T?di zp7z0duEBocbVzD>OuDJzpkuW2CyVUI>Zk31k%&GiGuE>{$?BVn9prS~zxA7i|2=ZL z7GXTP%rlRi)g*7zg4Gd{t&z*l2>xeC@G%jbtM*9=j>`RIdV@)Pt4p^ooX&jG@a8L_ z@50CSyj$G{`3!6hDrXrVQ#W%B9y63g%1Yu;F;91RGkq5K3jJDZAY?>u5MBdwF6(g4 zpnvKcauy)}+)dHv8P&t~VE?h~)r|t1 z0Ig}-61$IUMCYfb8N1E9W~75jX)E=uFHc;cpj&5<|DIsVcz;QCpk7!YoF{qblw972 zmnU!DpnV%m#{Px-0*It{;j%}%#)i`!xd{rw!3t(mo1&n%H_pC5N)TjjBxbabuw*#m zauF+1FBjhD#8y!|nHyljM4I{0*N10z-lwK*$={qjQ@#CWB>C88^;XMZXx4{{oEuW6 zqjuhway(&{PieTu7v`=#*utsK%M7L5p0F{N)dGooF{>?rwI9{d!$?2!tEJ+myRuo! zKd(xyTSO}2XpmNRfP*J|oF3Rh@Nw-kS0+>41g1|e3XP776Anub@>s3Sh>WL9zAR>8 zu9GLKQzP@h$Hfues^eSj}`$pizc()uX4^e|I8PTb79xEaKIdA(@ld|a;=A+ z$US)Yj^w%!_EpP>w>k4U&5El+lDCA-71vL8=57}RL@3{PHN(Y9F2|l4gwEh2<4aC> z359jGSm(4HrF4Fy6GW17iBnpwbWpt%oMM3x?Nt#2UZ=WjU1Q&VWw|f$STMZ+w@kkesR;9d=RL)0g+lYW$}@CR$jNQ zO2M#Tdts?1~q~Yn*tZ{x@kx7R>req5*-> zv-#7qqCzBBPFK2~uI#zynDphtAo>M<*NCXN*^Rj7Sk+ISy*$O#S*aRTchBne`3u#n z`4iu3Hn;?)oa+ODc}zkRBWXzTR-YyNs`prR1?CEXcUgQOQ( zDK7+cx0|z@w`iNqE0x~**cZXL_0~f?$3A%|76*Hdm5jbw%*o{Gt>|t}?&G&PRhU03 zfqvDLWG{6k_W^(X^9RU_FV=l+s!7qW{QTLHlI-px^s6gbi2vsYsINnPQ4#tDd>!yX zs*QSrO}_Q8yZwwS6MB#cghql1`W1&B_v}nrFkQW~K&e@h<%QiTMp>XZGLw0!y-1!!gyMADgR3XvujSgYz5xF7mcI!ALXs@8&gofkkF@gzv{)feYu zEUJ_)I#qDTmbL40-;BfyZfrMg{V;h>ukFKzRJGioDgL7yYKJU86v3xqWdj=WD~Bx9`4dTqT)aVmS^ zcB2LI?$W14X^YFn*@tLwqe%Q>#J%b{|N zQ@l-r%#8IWci8ZcR1Mr&mq|!!ZLhbc@bkDMlv#4F0Rh+7pgl%2pUtmxYa)Sga*LPF zGq>qN)Xi!6aP9mc>|-?CeKHY^#BSG#b5ruSl$t`Y84U2xJA2&Yc|qFje>8Vne3ntb z$6BQlndpSq%5aRQ?qfhJ!=s}uDz zTGYLqGGc6lhP{~pUc!StMegs`dKn*rRo0iO=&uQ}*+7h`GrwQ_XwLF}RA)*!@Zr0v zjMUYm-yPL^>#VP?@T>P;z5tob%!P}oKY0T!&lXgFq6010ef9F9rTUXNH$`LElpgjM`Fs;&;-rnyrTyEICuI;PE4sNlT~zL+917Gm8sdwnDRs&^K?oY79Q z>e(O1IJVWJ?}1+V<^0fSXX)-+b-&7D;-^1?O?)^0Qg*qiTYuo}JMBjcZ$CeHj~FEt zP{kiweOAW4xRLq%sm4&9j|Jm;(Ie6mYhX^W!g-$CB>u;npv529Rkc24zqE=lk?wsZ zd5O~5|Lcr1ZESBa?%L5y^7(6_V^rU!zdpicOo5ynn(5)Scb+-tuQaJT&Te>8zB5(u z0!5D{W99_?gqokR{>6zNNpb%y;+BvbwAPooR3uKRF{M*$NZxxLDK~P%Pd-Nm)n1}5fv%WNi+d8eq}JThGG{9GBuWDJ z2FD~6eV=3z*40?`B66|5++>uJ5GBDtKX%HKsETLp^$Zv7)dua8!X^d1svUzz#w392 z`<6(qUY5;1GM0)Q0*(!(G@70oO>nim8M##@DrQm;lM;n28_$|#e@2wlRk9AMA`-kw zO+V9PFB{+HD|;vAVH6TNX?dqtr-OErI0-!3CGJULDAi~D>B>gvPScTZ<=cTc@3zV1 zO66a^J(Hb4QGtx~ckOpe^s`9We8I=RBc~;>aN=nAL*k0pL7N((Iy$;rw`JqV;<#2> zT`YdEsmdkl#9zAO`2-PQ1F22Fi5Yc{A*Gbnr&K zL&>k7`mpw5fyMz6}L9@zr2wP`Lwg4}*a8!5W zV)WY$>*XiUa|wjX?oekHTQG%45DUPg71<&gDYwok(U9apZ`_bXYh-8()EPT?_-0wdy!P2B3H1|BN=85NPkA1`B7wtzA??Hd(*38?%9OKz-j z+d7>8R@@gp;7wC6S}{B?yr_Q8G2um|@c8w*kaR+ih9m_9Q*S3Z;uu-1cBeol_TW@* zREg1f3#}42LhFFk%uYi#oB&cD+a2QjN?jH*y|bDph29v^%UV_Z0N1?<01m zp8sx$W&Sak;bU-u2*>4#k$URW6I|C?R8x!jOwQIwD@w>IO2jcO#G;oY?XST(yUS7f zYzLNxgkz)UN!v719K%aY%g6JD<+vNInz~|VdMUxG`bpq*2)EEwjtO>FVBGT<&B{|7 zWv4cwl`PG@GtrNJdwM2&ckjoxsK!5>dwcSt()_%r&bGMCbugu zSx#0>e!8zr*eSQ1e0i{Y{o|Jn$jI5S;{`<%?N@xL6@zrjTNa*nO?3@8+YzTgy)Gox zFwrm^Ij?_)B5+%pwm?JvNrZej`Gu7e- zmu}$lY?p&9~w#P(g@M<(J`h{zV%9)aa%9GA${?!ciCZCwCvWx;Cyl3`OI);_+jP zfjyAMwInBwUU8r?d;OMK+7l%9!n7;c)LoakYpy@J4gR`-qdD-UP0914E3opWZ%XPT zT5}hh?FFrIHp{aY`uf+QOAEQG%emc4*7iy1HHNj3?=I5J40VUSRhA;g%N}098_9)T z-x2KkSgZnK)LU73Ry?}psL(PV8qBXJZ&bxT;iy2V>=|hv-oxP-zc3<3x1^7$8D@x10$7nu(ahLvrRX=3rsNJq-1h1RuTA+J2soG*{ zFy5xQ{&qDBv0C+frtrJ}jrs<>Y+1i9f{Le3#7-WK6W;<_;}`@AYOY{-uwW#tUo%ER zf15#oTv*@8vvHyev`zC8OVS@QhilrR2n&mt&`z%vGR9 z-tK`!NmCHf)6LvM(cOAg%?s=Z<#nvQ-tyR>9hQLFt;N@6;?E8)JuM*s--No z8oNsa#PuS6#yM;o;R`x;?Z)W|51M~HU;wVLWHFl;_^SP}`$_VK^Wz!MDjj4Ir&j#5Zm*#Iox5fk=9TBJ zFw(PTnCa&qiM#9%_1%5e2n{s1`?L&kkj6+ zp74Hzfe|?OXyB6bsB;E_gCwezD=e-R@qNj;Zo?BUjEJ7zbWT%#$1Wv|G_2RSX!P1~ z)w!;%F10XM7-^H7umtPv_7viu0FDBo`dOZEV{PNUQhfy$)VJ4oIrzZ)Gub0NVfZuC z-k*mIg4=E4mw1<_z14l-#xH78k;(%CX;lKMp|W$9*d?i!#U-hlCYSq` zIIRFjIFd*|en~Bv-T**6K@dsf+e&geN_A&g>>6!l;fYDb3~!`)_dEJCmu=j5HC3`y zJs&#Mh;aLqky!E$WtUfXP?gwP3knYctUhfZ(4=Cso=ZEEB&wPj*|ZpBBf9c1qi^wHoTB zSy>+quw%=FbI?-06mP`|II7p^@1-6~*H1|BlEiIIaDtZdQOHEF^xzS4q9dau7W=8aU8E`MnvDZ-^7-2qh_hMdx}@7&Lim&_UrB`xn%ECTJiG4@}bt* zQyp8Tm7F^|v=a&~)SlND$?qWFXeX{Gg~b}g-<&~E1A(67I56yEF>Xi{v!u_MIntzO z->nQ}&RI6Wo_j%CJoh5i>rQCQ^u+yjsGm=n8#lu`v~~%(3Dx;{Te7#-?#hY#o-m(p zy^m+*5^ItrZ*@NAMJUI#m`4>;aTm_CaD8CM3An>!by@wymBp)GCEVOX12xIADjO%c z?ZSY+05N|oVbI{2_sCbvvBX(y$1^yNH{bENx<@9@sCSQ%{+g1wV!E{^>nB;`OTo#-|+?>|iSxuE?6_vZqM+n4WE4upo{bySD7 zc2#$;FfwZdkyO`x-I#v{*ZW2biA;}=7h*j+8(e|Je*_s;Vni@#)iGg`B!fDqhc zBO=rt>aT1-MF_X`><+Z`p=agCq37ohxPXK)!aoZ#@Sa}RF}?{xsqS1DibAN&-B*ta zY}Vm-XB0L%Ck|bKqrQZH0yw$_4ZiJ3)`oM$iJm{$68MN|@l)-07-P)kG5gEmH$2H+ z1JUZlPBM&9*7)Z1z|fVlK@$A^0$Nc*;5xNlgZCR_p>d@_T9zmi0Ji zR6LcuTr95?I97IjnVN0Lq709Gwe_r_zc`Z}_JldRa#FY`H-h4F=kca~=L|wW0qF-= zCHh3TAD23ynlQq4`5!g}1RiUL%}15yO2Vp{}9#M{M4 zqPTFv;)oO9xE@4DZRexe{)&X{|2P{y*Bi@>45x)*_A%lcPO03lNDqO?4qTuKC`nq zD)kpDMJYQaPHD|31Vq_N62W^mLsW^v2%{7LX@C=#*l(Ovc=qyWlS4`kb_@F3d2!<6%p2eV^)?O+crUZA%kYd_y>#i5K1*!WfSG>-djV-QJ=9pkt#a zB6a!4H0$ZdIjNh)$*M3@wFpFIyv++gct?Tx+d;om4}@?ilXtphu05{0!14OihX>Oy z$qXcc4~GZ+t~$*RAvWsWPo6QKY7kE#ZGO)@!&E;hmcI~ZI)bRL`X+U($!`TpOT59* zAxC9o7rlvdi>gXkI+1-9kMMDaPc3)YD;WD~c~fwun3JlL^#gI|bm$F^RXj`GjOA2* z?I}s#u$Hrdoz~&9sy&L=(;Q^x^#b z7zM$ElGwq;Olk!K&1Wt*`9haed41e^TDv@DQ;qXtBy#1yBPvYJ-Q(nWSfAP$%-eZA zlXN{gApQm|+|NuV{u<3yXtT)uvlYInvGvh|E@suuC$+F>$0jnyKR4ehjHIc%K})g#`||G?$D1{I%MCk7=Pe@Vqa1})g#nQ|7mpg z`H+r9lRNb@y{^HA6?_6w98vL}8v+FxxQ1AhhQf#ncp@p&nx0zQam`LgqAHasPWH?4 z0?|)TvV6uZXau`F2O}E<_*Sx~tfHr71;1{e;(oCSuxy1>O3%cda}PQ`Ej@`b0!Jb&q$5{2m4M|C!(SwaoP4RFI-Kx zxiv;X6-UAlA@I7?z=C|sNqs^FQfc*eq3~SGXe^6hiP59hTH})j^;6L|ujh}oXUk?w z6OD#lCSc>0u@)<=JPUPBJga3vqpno({1%Jo>lQ9xWhqMHHhrtnd=)3XjrFK?r4m!S zZ9_$J)I$Xt22I45A-tKR^_|CD1Z&bg)*35u-*btX=9-3DfjdMvFW=?hjU&Sxv!D^vB+VCP$W?VDKVeHEq%ub5R{sNVQcLn$m!?o69X z-Jb1Fp&CL%!aG=C62iW~{n9GTq~RUyaJrdR+K(HJr>;DlWpkvHG%N(t3p$l^fs~cR z>2jsDI^Lutyw%3X4Zp0OD^T`K;~|wyO<7DeH+?_qINWb0hrqtUJrEeWfQ>|}zPN*STlayMMly8=f-*u$IR*t2W zj>fmF2w&qJ)=3`K)h!CVv!Su{sh1(leSS!JA^n)0lpTlf+fSvLvWBM126wrsD&Lc= zggd(+KV8~rW3FX8FVc2h5DRNqF@bSM$;{AnZBprpB1}51(v*G1#lUneEQY*f>&}bU z5mPBa6#kJU-B&&-$V1fE#mQWJDMv!%F8FgUy0Y(NXKok6xmoL@c8Jv+~(mX(UuiR#x z+>N{5kHB^{YZvU>LcDN-dJ_Yfq|RBC2>a`fE%j;Rpd}ff`I5fdg5EWfdYurNPr9vy zpkyUje(o!CwD%T+3AMD#$QZ0Sq9CUG$deay|(e=EvbHNC3! ziE+a0Qs4Jf!nLEgzN%3^8=LF1)aF=UEt33~y)GGfVcQG0-x?*BIFbMUGt5x%l=Nw? zoFn{^Q?W%}3QR}!=hc82i7dEB_v~7-EoHpwle~L(wF6pTSHITq3jL0%vR(TUn@TM^ zk$p`Ia4HK1`-)<;CqgBfBs!Wdj|Lx^T2U*6x9eT2H{ba>)BP}sFUs9Ax>6K=+rK7( zf1-JcC35KM{VPRzuhsg!dUYmZGma5Gep@E>{sObnvij3Tg%K@#NAGh<(=8(}Q)Uh7 zr)93~=<8k_e`XmxHFoOD(^#YAu}#mUWVvh{Ib%)+meN4~@5 zufAVhzWaGx#i;Atgw`mJ6R*1EV?$mLfu7D&{>p+DEMM&q><4@x={Xfr$|02=ico@~ zqCsM@*vg=cmTadOYSo)%>3Y1yIbT9*HP!CltnxG5Y07b<@v-hZ5lbYwmN#&+hWkSs z{`}Ts_wnZWuoUl#mjkEfxsT*E&6asZWrW-?&J*|~z5q|K3Jx#(XoT01^rG>VgwY2r zyuPrp`~>^#*dXDNqNzuceCG+6pJtIcI*bf&@gRB=6O{v%?3dMNbEAXeTr_%qgm1-=2@rAkaV+^Cg36dO7>E>KGz1GdUG!p>f(GW#*owo#W)4u52aln1{`w;juq%z zFJTfb9L1vS`%ZX=}pPaM-v zMBa<3=k`8c*>PgpWp!BIYIxLUvm*&FjhxY=P81I;vNlS)W-3(C!D}6GCUToD_ruy< zm8Beq-747uHEyU}m>2G;iFP^V6d!Ck0e#-Kqt`=2K%NH5n5SK}A-3GeheiHiQLhuDU zTOkSdURNeL@$VZ)us(agA<3(iBJndfWRF;9I*&vyV4vO5qLNsK4t->$+BR*zpMeu2OX@2E;Z92hld_D6Wf{_wm z&Zsw%ENYY7QFG3-7jUV&ncr!7i&mOu(S2Q-EOuEOGIub66jnFSUn))2`ufbhw!9uc zO^7-pReIqOv=%AeBb|qHf@sc40KXv0tVy<;4l661%+vr;>(l*GHAdKm@I(+355=M< zdpDr|Y!*@KBxlwhXkW?1%v0YqMBhNeV|S+5C*k=IMMkB`AB#mz=EWFgIQU!p;L0+; z5#YxcZT7$=v-fpJe8=}FHS_pz4M z#Eyp)%wOzAxyCgLR@KdO5%I>@wb znC^wBy(yg&({_y#$YmQRVbZf~y4oE+I?i90KT%y*njhv34bE|`p3t8Do*+>WK)n$d zIZ4_b+s%^cuN>OUA!SxgU{&JHQ#Q~9PV}OBt~Qp&8u0d81_8Vz6OlzHF&Z3yE>|`= zp6RSk9ob;LnXXGcjhCV=r3Ajz$&?_KNp4^A&C7D{IVt=`(-zw z@#I7U%367~({9Gnx4s?;sYS9t9F@A+n>h)U=b04~hni*swzL#u`FO+p5|yfR`K8Rf z-fRa^h~NHexvpP+ZbYTfM^|RbMF(`QsfH@C+jZk}SXZsgXHMf@GPvQWi6zKGb!u_= z;wMdF$Rcmw_QMkP^a<(9R=7hS9{P(~8OysW9`7dh?D?FKNU)fAdQo1(>_Jb5&5u6W zw5ML06apmAm&mUAM9Nrsax@Dp<@QE#=qKtrZh^C=yB-X1ti0(7t>$$$S2UdJj<~k` zx``yLuO%q%!u_OdcvRA0wi6A1KgzO5TRJX&e`VS)%g!v}{rp2IPVyMh)r#vfO98k$ zqz}i)&R*Zb_G21m`KG3$sura@Pb?aKjH>OT!WTYLAvd}psv=ucJgmGXtN|THtk--1 zwS%teTPqypGx~IH{BDI#_$re-8^UAj@qami6c<*4ZZhq3I!${LHX!rIzx16vookEX%y z{5QAm&KB$08a(f&fythoN05z~1j?ybMvRV`@F}>CJ`U_nQPb zOapGLH;IzL#SiQZqRryzu*W?7)?^w9^hVSD(F{WDm_g8Cr)c8&aix}UIHIOxwEhRL z+=-IZ&FNw}Pxs+s(JsNUekL3w^>j>&*>_<&Y8H8`Dvg}wZ`^A6R=Ag^l>8~9g{fqn zj|;JRicA6?VX#^@mrC%!uF-y9;zY^|9UWXDe;RdjhnNSZc^wb0X^WV+rRx7locVSH zLZKs+#S@R+*jPsJvEI3*4ZE2I;>6c_4a-o;t}S9Oss2TZs-jimh*gTfNAnOuK?elO z{GHt14DM5*7nB<4Gq_B{27ungmQeegkW}!NTt>|T#llVS?f5u99&<=^&iILv@D%;l znG?iAM;zEl%D%SP!`&^V6spJSpO$JiJ~-p~gQHE4d?t-&XFDZD$x`kqP3`o^J>MXE zVVL#<{Iv6N!;v#3*}5+aN*J~D44?}axOEBhN`jkD z+*)fp(Lxb=@2Kt$D=Ba&vL#aiOmV9n)*5a}lE`QGz6l_|t}j)-3u~24xwaOL>o9iA z8KQ0O_SQ4YvBcoo<7fZu`g5qXbacGSiF3BTvKh&XQD3Q zBt%LT;EJn97A0Q5g{^a?^co3ui$bCmt|bMsysjUY9qz*kwFSkzVL#pT>Q?Ji4v%g2 zbQ2;vu*1|rWtg5!XwdY_ka9Oc8r8%l=1rKKB2XmtzSSDLk|4es*%d0T@bW@S5>!9w zL-HNOjR=pRh)SZcPR;XgdS+{`l=6tQiyqf<3ca@$vZ+9DMbrs4m?M5Xxa*V9EirdfVrnSV-ilJ3_Kfn->=dZYcT$J)rvHv!cK;yvfqG0UoR* zj1|o*JeS!cW)Ro|7D}9Zd@n?`P2q?+lR7KsHgOwYT>1pz3ZQ8P`uo{h)PohMs)bE` zp8;fD;QZXYe%8jF0jfygWpY-ct;!E{ttAAFJCo2I#H|QaA6G2XLKM1cF8B4<5RCMC z@QEsw?}?wa^6Jo?%J$4dnc5#JBl***>-YsYPk+0`GIU*#p-a94htrTPEY3AB^Xq%j z?3}fCfHs&mmJRQDS6DrO3WAL)f$#|Vt}TsS3hS#8Wp+NyHDF>&saz5+8DGZWFmw9Y z`RxytE8|gLjA-lVOKMLyhmha5IOUOibtm#3;b`R0t1i7@g}9c?qKowB-CT^iUparN zPSzfJ9(Ly-J%qkj}7i7%gp&pT4VM>~1svSt&k0sHKq6gFZ_Y65U3>F)T*f5BBapYGL3w?Qo4QeB6WL1rHm$7*<` zCV+I3F0;f7kGF8l;&`F|#}(qlv^GV9+JM*nrI%&MuNQuJgE&Fmb$QH^^5SS$@X1N- z6iEBE`?}xAvj%Gva+lv+N;POLdX&oZSt|Yn0y~)d4cQqUmr@fhIxFt4tTQ|>{&Fq& zZB=y(3yTbA%{aM6-apy;ztYYEEUM;v{Gcd;K??%nf;0%rvcb|I-L-T`EJ(vrlA?eh zNOyx05>g@!f^{7g)!*Yo7TT=ca6M*_nI|nVM;G9a^xJ@@dpY zzJ0fsEBU70mx%9Pb0{GzvBsBA-pz$j9}U+J?7Q|u@RG@@P~kZK&uRnCo*tI*Hkt!k z!I-=w8RV+OkTfv9I~CI(GB~|()MKV9U`bO~*fgOzMA5HKlG*am}uVL=30 z2*3`M`Va9*cm0ZMav$@0alQ&=+tqDVEheRo4-g)f65S>oMJ+@F9&=C<;4!QCJ!Sgi z!~Kz(r&KV&_h|&o#x6Y&+kp6>7NIW4wk)@qO#ub~_HFp_6V#wKA_lS<@@I zOyuL-X>$AX^A*j6-l0M|VfV4C3?B>Wr0|henkgv6!6XWC?`xsrjgo3Dgp}IYRdS5X zqO5L_!>4iRH8$;6Om1T=ht(?i%41iz71x*1h2K2jU+0+FBUyvLA1(}S+Rw~Q6$U&i zcAH9n{b(UK#pqi^sceLQ-oeGL+m!VO+2ieR)z{`z8K@08+`Em+hfH}FgQ!R)5%Qk9_CSKa)p{IBkHr#HEIK{001H&=a|KrU4t z?Jfk*?vh&(MhJ6l6>E%zI4Y$Let$&B6Rd`g%))g!kB|B*lkQ+QO) z?mCeMLx%FTmL!Pm>`gL?9LeXOiSFP-;~DU2Shc%SP}qRN#9cy;YlC^hUCC*ueWsG) zs93iBX}smzvONAxayF`Zw#OGULAH)r?;f0o70-%?7pY)^ny?eYx??k|WjJcM3Njc2LWj&x!SZlz= zdVt$WI^Mpo=Mm~LFe@TEZm#Hg)UY94KpmgSVyVIRQS>FTLXzGa;t89}oPT^+8LD}T zic7Pe91IwEe-Z&^z&u%U7H8cQjOqpgE8Wx=VqRWk9^wKOD17HD)(<*9d9apN(vu{& z1;Gd&Ub@=dwtnGrQ~cs17wRrI?+}HNRQSipmp$i=)I!J7!m>h{b@T%h3>+0wW3N9! zotrehH;BJNbSL*P@Plq=cYv_}pUo{KG8I(RaYK9f1Q^<`*-JrMHU#0ZrVWqIEnH2S zk{!NKmkD)?;hdzqr$6Wa9tf-`_}>A7E>E^D_O0LcjM`x9=jD$?G*>p^KVSZ=uI-rh zW2YDq)jB{Yus3=oT~kHE1Q;TXd%zd&+VG>n`Ia^3zKmWOQCH~)r}q`y=;aKO{=$5C zvHXNN-?ER%x=1MyBg44e|i5SjJ{hWXr*zJ8j z8rH*hw72LB`F6uAN>yB6TfYU8tiq&ue?*`+G!>W(D`kbvk$2o?q7o(TVy+&K?@$JH zbL3%cR>jF1&&AxL^f?V@GDHT9NFN8zE?4GwIjl$~ByZ|ockr9{UHp=qhM#!V1bLoe zJtbnmU)K0XuIV3&K?JoF`ep{4BHEM`Ty;;lFwAM8U5Vd1hImL0C4N=uX=^Y-(6Wja zp%b>We?>F()1=@X8+r^KS5A$%xn#BGa>Lix>5Ig^STiP_Y_`pRC}h!m&)YhwiKUCDLHE;%7V##pbtE47UP91ARlc_mr)Jka3LJ4$x{u*K zuFG;%^=BpGK8}fwfa+Gi*6NBTe%_c^8KdCA|Gr|I;ZP|@gO@3Db&8_q9`_Be_d!%# zukI!DygZFFU*WRG#+i>{r*YW+yP~GnVRPsr})UXBY!k~hL;Z$?;ip_ z#*o7n)iLba2>IvfpR_UE)^hMGJDJGmEl6bvrop@H3L`Ceu@OmyAhmb%j~7N0v{uNJ z@{pu%FFKKBPf_!?FG#;kWEETe+;6JW8(_h%RGcFj>HEA3aGv3R1v_1HxGAfpJc6J8 z(ye}R+ryAUkcc2#K3>^axlp73POCI8Rho*#%Gc17nENy;4|@w0uD=Iil`lnW;(+*2 z;sX%H+cvmg>~ z)RkG0V`AYFLgLOG)h3$LoO7-6Qvx$rzr7eDeD(6KvnFk5YERYtu$SIq7l%f4=I8Kv z%llr~nAX=&#cjuQmDJGQ{>`5a3eGcxo!uogz7G3->p}~g7N1taj!+v=^81XNGr40I z({n0kk#lhgt9sV83fJieJ_F~q({Ue!B9*NhcdQky8)F+rC4efL`xrmm6VOmrUR-(Z zxG)%a@R&a4 zQ|2|$no;psp85+F&BethOuhP+!%d4Rv%{el8jF;{Ow?kOwV$e(5~nGXOGk@NV&>Q( zBJ4-jy4LMZCQ&CPU09FZ2G9-Z&^5)0CT-G{23N1_b?2P(6&d8FPi-IWPH4PLo){1{7^QFUxu17b9w`I zyjhz8Bf*Ok1&$xW;;84dt}K2jgyPpQm%~d2&r@?vx6tr_0?X_ zI_TRD-og82{Hecu(Lzs94ez1EKEAumr-v2_%in)0t~3Oc5@qb%dX&i7StFlcfKj;{ zzcWeEfhx?dE!L`SR{Jt3Vme!|teUj1B4VlpL$P}0_MBwd2?|tee9?cxNljCjh7d6w z2BL#uS~s>o?gUEAPH_e)CCDgG-HLsk-Fj1Oygl^Ff$<3veslht?)-GUX{hV;SocMg zv-EvI3u1F`6}SsKWV3R!m8wFb9V#AY0x{~$i!*J1#H*)r%>8lesg$?TQ--z*VBTmp zP96WywsJ;<|MYD`r(M#o1TaIn>5(`n-7bkUJXKt<#7O~$JyB`gtkDDBzln-52;~I^s>8=_-}zv`iNJdYE!GZ}+B3$zvsn-L#3wIO3~Y{gnbVaSTFf z6h&=TPr}rAm7DAmmPS1UezXK~X%kJEnOKMSwpQAwp@T4#dM{()iu_kQb`Xl?6(6P9 z{6SDcn#z)t(aNfE_GT@0n7(!Nz1eD+fN(B5>ImW3i`m9nqFt68lXRelqMniB8IM=A z&<53;6?&YNGu^K~JZi&6la<<5*kPX{o*pdCl6o?-Crna@LZT&&Pi6;f>gA*k|JZ06 zme9GKjiJm`@JQw&+$u0ldzGD{py~0$mnRXDDaPU)ylDF6c_8o> z{`on&^Q|PAw6g78K3>zoZ}I9^3Jy}t_Yw~qXsSw=x6M}EMU1USmVB0*c%->`VhHJdO=Bo*_j%Eu z3EZGRl{~8`RLw+gRkyXPO|ovlSKF-1B_QJ37s$Eww3#%S(N1?Rz6R!hB^S^<7{#z| zyGF8fo{Hzd$^sYVePCrl$W$#!OQ~dV={r_0D#`}poAt!DD+e5sZJ6loWf>zYjCu&G zk+vgSy4V_|%7M`C$Pk4zl3CtdV5F8;t!rh%=bDw-ztpST?$s0 zfia|;gv#>|plh|1El_UenCT^`A!ja(4Ied}89UB-gZUk}XOV+e;YnCr`U$b#n({x< z)?qibIE{E?8=P2E2`udpXJiYt&(CwM`V;#dAvfBmQG7E@;dW_MNx|5HatNVK0^z@j`Z`;R5d-Z4_)I+gK9krOqAt}&69r5w%6R3|7i)BAt1ER*mkIpZSgesvDVyo$ z+jCo6|5B&A|7P`mm(OfdN(=B7C{}x!W`&jDrs3^QCd5(7X2qa)HO$V|WJhG(w=>{6 zKaVt~I`*ak^9iHpaVkkb#Kfo^huxD-U>QnvdKV{$Vi5hKu`7wRD?04Jju4iZg{#To z;84Q^OCF5`JXv{Qt=HcX$Ib@>e!!lA?@1yA2%@`L*kG&~5R47(r79=S{z;jnrY)-+ff8i-c!$Da0v8Hz8AJ8Wbi81!FyLpc z>_uW@-WYd%4lg%c0SjD?iu(a9viYEcI59biYfZ*RVxwbTVfPRKuLX*K3U=q;WiSfa zw|M7O{D{WINMV=*1eX7FuX^>ROx(wug5?T}0xOM}QGRR8r~yyh^Nb5lM=WH$6N&e; z;7uo#T&2Kz76|LLrhHy(6rD+8E60R-JfJ*E)rxI%*ud>wXnu(z#H3+Tu{rK5A%`Ui zp#$6Ta8P{?@q}?*?(Uu7u_yaYLDXrjEH0NOKG~XX?@_*!Baw$O)>JN%JxBF4fXy@> zU*wp0b)`4g;%%A#JH7{ccG08_Z&H4;9A$h2*65-WUNPqC^m3}(#Rv8|0dz8M|E?zdGs;?e|UFcVPz2(l1`gJlc=TQ5|qIrwIdtZE>^6!rq zTuZ0$LvQ;>g@e zpbth-3twOe*_Yz-dx@e>D^heW+p>&Ah#0QL5IZ4fB2zMa zdM2GVOrRm{%Nd3j`Dwbd>6a6IGr5WD_k*Co3PSqCT$5!3x0dopO%!y98jAjCyhpM4 zo4QV;=4}VG*7MoS>pBxI#1ks6(`u`4cTA@)`%m3lxZN8O$*rTGzOWzS7{T-KQ_Jmq zcb7u2h&QSx^d{>fVde?vCyYli-r`2|xjMqe&-(c6Ut`S_KFTQ#&Qf_sX$}L^tD&2k z+`yE&a@8z)35Yo_4vIst zoBQc6o;hyt!g++^lK)p?3*Zi&Z}`<1;#SXFd4PIXUBWi6wG;p@rdlFMQ|FMtcU)xo zhZlxG(AoO1>|xBeh3hx3B~Ep>&c2J6{Qh~=-VMLMF;kDXQZM%IuW4_-ghbI#g~byG zLPqEz%eN!(iDkPS^uI_8@bWLOwA}%GE)ja8gm}Uu7S7xvaGq7>Rh{yO?G-juGc)bm zV~*x@i1PWi0;4sFvtri!$|h|ZR7 z6PLG=hCZShHi*La-(Bk4k9VBDN9f=|yu__(&v%ra-;?OpZ+@^=bFQttvodk$cBM0( zzV=#@8Pd2TT{Fec`ihWeRB!?s`{v)8G+Ak?Lr;4e8-t(^wYxpfa!ndJ6bvY|dPe^l+1*ik?MX3uLYL_2dkruW$0{HgF8rpQvKPwxxEOHxfflLpw>g=E{RM)e~0M&wc31<+!B=`|D6%uXQrdMOyY z{6oCxyGOtlxahltc%3QL#cF%1iyw>Med@4dn5HWN*F>Zm=moXZsz!FUtZEd!PyeQ@ zl`XccJnd>3lD@c8M(FEjPI5H8CXuPLm&M!4uLQv!l!oVeg(EF z(pNzOg=KHYFfN95`>UXuzE9gmcq;~_iCfWr(XAP7Z8X!1_5@*^(q^r^-tYyjpZsc5 z;Oe+$LL)nr542+TO&P@9m8kIHxvL$maD`jvEuy%{fR!Xsk4l6>(ww;-uTn*2&x9rE zS_d8PKA=zI31wjOPS^VBhENE7?RCcOFUC?WrfFRPAzawdMsh+undIM!6uT$0dUUPEwnSE3PLP8j zn}{)%Bco6&wm*oVAWDc_vX_M@yZ`5tr&LD%q{xK~f{~)eq4y37z!dy>(yE1FLK2`y zHXm7%>h3#MZWt!kv;{2iFXnE{M0Gui8L&#c%SovQY1)1=7bL%OqdDD{LLJy{#zM|y z5WV~=7LiIc*!Yb?&%oUy3CTC5o<0OU%ocnRTdp~NqyNK2S~jT|utviHNKi(PoY;Qv z;7Up-%P(?J}J~Y7;@1PE5LE~wpA;E&}eh3;8mr#+5a$2$h z?`w#d6P}Bk-B8r$c1$zV$~}mS*Sr#TTBANq1032jhoa>gegiD-AL+S&agO#18e7v| zF{Sa*qVmZ?kcZQSa>X1JC4HD8m89!TK*x7?zr&1jT~-+31=4JTf^yPz<%7B`1fMG9 z{T&U~6NPLhXp}lfXES7lNUXS*=PtwKl;ew>qqmq>vt~jbU7BYypQNKsHl-4&?UGa3 zZI!OGQvbX~kh*#*eo1Op^ zzwco-pKKzbd=W1;_?nPkRZy76B9q^O45K^L_xlJh(%wWw)-CJ)xWuh) z-z~k{6VOUhk=kA{OuWL7mh^-}#=XRz%gbufv z4RGii0YArLSRR7L9><~5-0Q%x0341bx?KTw3~^&rVY(euVM(1-VOU!Qu9>&qcRmc2 z7X2w$^5A0(9fCW?+okm}|4P{&YjvGz^u6gaIX~bqJ$@ohybc_#VM*`kc$vhyCT;cP zgKlZcJ^n0R2}H(fm!y>vrw7q&y0f(z-b0ogbJShl`>v%Cl`$SXOOtv}&q<=QC`k!E zufAWO857}nkauM{@UA^fAZYAOpt7Q-e!;82X3Dyp?5bS5BKx4cDyk=w+7Dh-QKclD zt~Z-h7+206shVUplo8|An9_1Ln@jm9{8-X5bM|tcbC;YFn)lcG&wfGLwgcw_gDY$P z#IBmFu9p`4n5WleZf5mayoRHJ079Rc_l`!}LUG^3a!H@w!T?b^JRWI|LIW{Z{vj^( z5+Sd#} zPAF3IEd>mr_`+mobZvM4AZ6Be+xuA8thhWFoWlgYpxOi=h<-=}gy8=P$G#W+uz6{L@mM$VJf!<# z;BXC*#e8Uv)_I7t#`oI&n~1+(P`j!t#K!(X*UXA@+-WJD(meajCK(&O$6s zWd@@B0zbI-QK6)`7t3wBYVGd@@LaT}{A&3zv|OR)C4IQwvf}HpyWd;CfJj2e9R9Si z(Er$&Je{7aAXjDvr?7wt+O%av@_tydxIE+0WNuDg@97QAWUQ2$YO{B{Hbu&Nns;nT zze<0xiaH#la|vdM>;4GDQmt6yIO+5lmQP32*+DLN#bbkEmBQ~Psy5!~98_7&F?$-p zV&j1^q)U%B@E)~+N$@*7o)n(D`QQQtzaiIRRoQz~(@)7%$G4`ao8ij&T6CskjfZBuon2E0M2^2)OHFceI-(K9WtrTG8l+*nQa-8h_91U4xC?sxr&$YEwb5(}` znb->Lsin{E#|IYotwM=>xkm~oVSO%ONiOYHfc4aAFdbM*tCzKwQ2%@_ z^KgA1-6E;^8v1lTdUt_jjy+hZWOfL@f*ykG^;>$$ty=8dCktyzjVD z%ai)3ZNq3hZM^Yr`grp2VaTedXbj;@O1v#*(no6~Dbj8s&ru3UFT!(j04Ry+TxT#I z(VK`(^H)=Io%&Pzphz~|V<@}(`eVzkdq+lgO-ABLME-aIQczvI{xu?_OwW!2W;Yl?tHmM)A6 z^m4u_H5cJCL>@M8AQ_yuKSno*$ok%F>=3w{;qWDdXR_zru} zU2E|t5dSORN?!=Cd?+@I-I>@D5cnKq(m8T>Bf2j$tY$w-L~F0fE4-h&V%S1HT$Mp5 zNW6T&{y?Z8YTQRs(M~isTy>_xjF7iieN#rG?l9_yYnHGap$70k*wGnCgB_h!8V7&1 zjr%axe+hl>lOZmX4t@MS=cwd(v{nH&BlR|xn5dtvGH{7vvTY<)7oFi700im$jO&-b zNF=LouVw7tf!-P^uV2pZP%c)R+4)uY-iMOJl?^f{mg_yM$zIg>(l63w@OE;Ip6LDL z$lfl(J5xJ<>`Hg}+->&ZuX)}@7{1DRi#oTHO0&0618SV0$wJH9TS^~}xCga`dXauC zF6+)Vfgd7r{~Y&#w*ePFI1(*XJ)*LN-Q#NRdum6VV|4!8Bk{s8P3vibil~`3Oel>m z`@m4e#h6=dFfKccs1O5Te$g~Z(lB}TRqmeZfHcSD<5Xdxw z@2w=K+Y?os~m>lvJ z-$u4(njs+7uKe=T)EZOu=`z>pvSR1fC$YdD`TYXywqvTMq?0KB2PMJEfk&AxGcq%}m$zBNEuX)L`37OdI^B?Mqt% zeJlo=-VJ%i3+yG{rAd0AUS7fX==~jDzfh`Cr_UIM*t_KCBjiSydRZ`fM{YqsWJ)2U zB*UmQ`>=zq7BUYP-nKN+hWk|znda^ZD+QB*-F1$*=;O2^$>@VMbIAnar=kN;xn1Yn z;QF?T?FhrGD2|)NN`tqEJx>*K$T7%LUaQW|@6M^^>ao4O`AjyMEApI%C@I#p7Ej&e zCiKZnNixPKYFb!wzL4XQiG6p75(>0aJaB!4zp%Buf9X)mx+i)%7Wkn__9OgMm_bI7 zu2)`lnhbE*lWT9=S*WB~rrn*Wwrf-)MYFRpaHnCH`}mwdJ}PjdMGv z*&qpPrKT)*2`#m~jqcuRLiFnTl|j^VAC)a~147UWJy{@YV4-}O^!_KQ@&IQe=L@0V zLamYVJg$%uu3paQZ=qju9CpDX1|q=B4P;&At*er0RAwNm3Ah3h_P6p%Ab!GjbYlB2 z?~STCeSIVftC7hXSNin1SEj(+HM@Z#bJ5(DV_Z>u(Q5FDJNGBai7@x#J1<1W!mAq1 zL$9ki4cLU!OOBN5zt~?Ixi$QF)bVEbfwE9W1%`hcp2YKU?ch}8=5 zS1sg4e>5#v>b@b~Uj=$eBR7KvIrrx-%Iep@KK$Htklz$=>Ex>?-L-6XUvJ5m9q{%_ zkd~LpLRFRa_ZfKZA*bhydI{uftV_^jowa>9DkcfP$RgHtyR{;QIJ(=F@;@_T7duS? zg))YXT1CHktG9A{&E^AMy$uEyb@NoR(W^aD+WDM|5|6$<&Y{Vm zFU&^uTG{eD%Ab&h!$j>5+;f;xCS-(#BCv7&OEm|4X+>tsy5_j4x&qA*R$&Z>%M)XN%n<@v2AsajnH6nUCI zwiWX<5f|gsTC}v-6}6+TlZPSvW1DtL!`FB61mU6 z0}D4G@u|bzS6_UnP@qhI_J#SHIqNn>FyC!8B}`z}-L|q}KFxlCJD|61*vQo@;I%#J z0#c`XvBBkj=7&OF4zI+*q--l0$GlJ6$W412!MZUy9q$IkBqqcUp=7s;s6C!Wr~bK8 z;`xE*`GFQcKBZrNjAb<*&6(Vk+bTY{e^^@mI*ww<+UB^YbI6>JW)r#kk~Tv!1tl6Z zi4tvxYKi{(quogHS{vw6Bl)eMJz@TtAHTjWsf$_5@YmPzcU&UR5PM)scb6fVr8=$a z@}2GQhag8o#<~2$n;$?VhgP;zq}yt5OI@fIJ$=yED$?dCYkLa3C-^mjQ zjDa-*uUwXwZ`;b))f4oUo*F1|#76lA{bIO!D?6@7*Nyl(u-SCCd`j8lUFSRATV4Y6 z#ApYLzaS&PUsyH?9|DUw!T!Y#Kvop3T{*C6{`>|DJo)e6VDTrX-$)5Uzz{eX20?%Y zz;Gy3|6iB?jn87YQc;!%0ni|KYXFi0=HUbL0CX0#sktji#?H|Lq-^T+udDusrm$lH zxOeP-`|fWz47)7a0S&;=&=w#|jH5m13<4VD4rBu5;RBt5(g5N$NM1_mUs+)3FQ=u) z7K|ID>V$RxphuS0R^0z87WY3_|Le+sGbRB#jfI;zKymrA3IW{AT@2dP)e$TB3l(!J z0C>w%Kv4ndpU@ayEo)ah?CB}c2tX*gx!ym$=#uQtNcZLm**#oqkMdcsn0h`3n|1J0z!baf#JcB!uj&>IRZ@HYJdH(0;xC=0-tkIbN zk4#Q0?|;tZpUS$UYG!Bc>;_P?fY$iGuC%`u|4$y!UrqJ5$^56O?wY!q+BsTb$y`eGv`v@SO+)CB z0e^u&ynkk6Kzy3^KlwZW*y6-sK+^<>!TNq=&w<6qo=L|C#Mj((*y5yvW6^tO?uWo~-_FReboDbb7#Pb<`#l{D0FvX# zApE%3Vo|JTuZ7~;ISh_v>z&C1f!iK10W1UXOgbb^{{ja3I-F;N^C5Arh4bUqJ^a74 zIUI&t_wfJH=5PVr_JAXC>jmf{aLR(%}o>+|P@Au;eWBHwDWPCWf#s|11xas(DY=(~? zish7^xgXF0;>ZwK?&z6x0$8H!85t5wWc^(R#q~=7>L{-N2L&D&=UOP1TYBbxFx=;b z!g2a`KwWU_04jj%i^2d8>uer?Dd5T=IDH+^>*DGa42r8)Fc^-n2ar>7Z4f4aThA~g zuAO7&-)GAV=fm;;_~2MH{h9iM^W*gGeDMEb`*1jJS>Om99|Z6)amGQw1aZbZJ|JFz zqeBQFmTO!CprUcEh2!+&z^nqtzv2U2Zyenf0KDb1_Y(jt2}cHj z;`DU_xcwCraG%fWE);gk*ZobO0U6F*1CZh9DHMk5BLOlTKM`=@aONq1?BD&QE5_8? z4vjgDA!u5AVIva)0QK3?(G>(d88!eR?_lW&NP#*!J(B_%2%8EBAOzqra|D_X3C!{k v7GN;c6pcop!Dxgjp9NeL^uJGXI`wpMHO07|&N`uRJ^?5(3yZX>4DtU059B*J literal 0 HcmV?d00001 diff --git a/package-lock.json b/package-lock.json index 5d9fa02..7519140 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,11 +10,13 @@ "dependencies": { "@xyflow/react": "^12.8.4", "lucide-react": "^0.468.0", + "pdfkit": "^0.18.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.4", + "protobufjs": "^8.4.2", "vite": "^6.0.7" } }, @@ -793,6 +795,30 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1150,6 +1176,15 @@ "win32" ] }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1304,6 +1339,26 @@ "d3-zoom": "^3.0.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/baseline-browser-mapping": { "version": "2.10.32", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz", @@ -1317,6 +1372,24 @@ "node": ">=6.0.0" } }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "license": "MIT", + "dependencies": { + "pako": "~1.0.5" + } + }, "node_modules/browserslist": { "version": "4.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", @@ -1379,6 +1452,15 @@ "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", "license": "MIT" }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -1510,6 +1592,12 @@ } } }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.362", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.362.tgz", @@ -1569,6 +1657,12 @@ "node": ">=6" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -1587,6 +1681,23 @@ } } }, + "node_modules/fontkit": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", + "integrity": "sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.5.12", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "dfa": "^1.2.0", + "fast-deep-equal": "^3.1.3", + "restructure": "^3.0.0", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.4.0", + "unicode-trie": "^2.0.0" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1612,6 +1723,12 @@ "node": ">=6.9.0" } }, + "node_modules/js-md5": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/js-md5/-/js-md5-0.8.3.tgz", + "integrity": "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ==", + "license": "MIT" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -1644,6 +1761,32 @@ "node": ">=6" } }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -1711,6 +1854,26 @@ "node": ">=18" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, + "node_modules/pdfkit": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.18.0.tgz", + "integrity": "sha512-NvUwSDZ0eYEzqAiWwVQkRkjYUkZ48kcsHuCO31ykqPPIVkwoSDjDGiwIgHHNtsiwls3z3P/zy4q00hl2chg2Ug==", + "license": "MIT", + "dependencies": { + "@noble/ciphers": "^1.0.0", + "@noble/hashes": "^1.6.0", + "fontkit": "^2.0.4", + "js-md5": "^0.8.3", + "linebreak": "^1.1.0", + "png-js": "^1.0.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1732,6 +1895,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/png-js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.1.0.tgz", + "integrity": "sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q==", + "dependencies": { + "browserify-zlib": "^0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", @@ -1761,6 +1932,20 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/protobufjs": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.4.2.tgz", + "integrity": "sha512-64rfNzkWOZAIazXzpBFPWq6F9up6gMvTzjE2oWIzApx2N/dqVUEE7+bCn2+40780dFVtKOUab8QfxJ6KJDWbqA==", + "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "long": "^5.3.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -1798,6 +1983,12 @@ "node": ">=0.10.0" } }, + "node_modules/restructure": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-3.0.2.tgz", + "integrity": "sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==", + "license": "MIT" + }, "node_modules/rollup": { "version": "4.60.4", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz", @@ -1872,6 +2063,12 @@ "node": ">=0.10.0" } }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, "node_modules/tinyglobby": { "version": "0.2.16", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", @@ -1889,6 +2086,38 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", diff --git a/package.json b/package.json index d260f03..1cebc7e 100644 --- a/package.json +++ b/package.json @@ -5,17 +5,22 @@ "type": "module", "scripts": { "dev": "vite --host 0.0.0.0", + "build:map": "node scripts/generate-map-data.mjs", "build": "vite build", + "build:pdf": "node scripts/generate-pdf.mjs", + "test:map": "node scripts/validate-map.mjs", "preview": "vite preview --host 0.0.0.0" }, "dependencies": { "@xyflow/react": "^12.8.4", "lucide-react": "^0.468.0", + "pdfkit": "^0.18.0", "react": "^18.3.1", "react-dom": "^18.3.1" }, "devDependencies": { "@vitejs/plugin-react": "^4.3.4", + "protobufjs": "^8.4.2", "vite": "^6.0.7" } } diff --git a/public/gnmi_0.10.0_map.pdf b/public/gnmi_0.10.0_map.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ec70b7d5beaad0234d88d3821ef9f7214b5fed8b GIT binary patch literal 49903 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 literal 0 HcmV?d00001 diff --git a/scripts/generate-map-data.mjs b/scripts/generate-map-data.mjs new file mode 100644 index 0000000..502968b --- /dev/null +++ b/scripts/generate-map-data.mjs @@ -0,0 +1,532 @@ +import fs from 'node:fs/promises'; +import path from 'node:path'; +import protobuf from 'protobufjs'; +import { mapBounds, mapNodes as layoutNodes } from '../src/gnmiMap.js'; + +const GNMI_TAGS_API = 'https://api.github.com/repos/openconfig/gnmi/tags?per_page=30'; +const GNMI_GITHUB_BASE = 'https://github.com/openconfig/gnmi/blob'; +const GNMI_RAW_BASE = 'https://raw.githubusercontent.com/openconfig/gnmi'; +const SPECBASE = + 'https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md'; +const OUTPUT_PATH = path.resolve('src/gnmiMap.js'); + +const SCALAR_TYPES = new Set([ + 'bool', + 'bytes', + 'double', + 'fixed32', + 'fixed64', + 'float', + 'int32', + 'int64', + 'sfixed32', + 'sfixed64', + 'sint32', + 'sint64', + 'string', + 'uint32', + 'uint64', +]); + +const EXTERNAL_REFS = new Map([ + ['google.protobuf.Any', 'any'], + ['google.protobuf.Duration', 'duration'], +]); + +async function fetchJson(url) { + const response = await fetch(url, { + headers: { Accept: 'application/vnd.github+json' }, + }); + if (!response.ok) { + throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`); + } + return response.json(); +} + +async function fetchText(url) { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`); + } + return response.text(); +} + +function stripLineComment(line) { + return line.replace(/\/\/.*$/, ''); +} + +function definitionLines(protoText) { + const lines = new Map(); + const stack = []; + let packageName = ''; + let depth = 0; + + protoText.split('\n').forEach((line, index) => { + const code = stripLineComment(line); + const packageMatch = code.match(/^\s*package\s+([A-Za-z0-9_.]+)\s*;/); + if (packageMatch) { + packageName = packageMatch[1]; + } + + const definitionMatch = code.match(/^\s*(message|enum|service)\s+([A-Za-z_][A-Za-z0-9_]*)\b/); + if (definitionMatch) { + const [, , name] = definitionMatch; + const parents = stack.map((entry) => entry.name); + const fullName = [packageName, ...parents, name].filter(Boolean).join('.'); + lines.set(fullName, index + 1); + } + + const openCount = (code.match(/{/g) ?? []).length; + const closeCount = (code.match(/}/g) ?? []).length; + + if (definitionMatch && openCount > 0) { + stack.push({ name: definitionMatch[2], depth: depth + openCount }); + } + + depth += openCount - closeCount; + while (stack.length && depth < stack[stack.length - 1].depth) { + stack.pop(); + } + }); + + return lines; +} + +function methodLines(protoText) { + const lines = new Map(); + protoText.split('\n').forEach((line, index) => { + const match = stripLineComment(line).match(/^\s*rpc\s+([A-Za-z_][A-Za-z0-9_]*)\b/); + if (match) { + lines.set(match[1], index + 1); + } + }); + return lines; +} + +function kebab(value) { + return value + .replace(/([a-z0-9])([A-Z])/g, '$1-$2') + .replace(/_/g, '-') + .replace(/\./g, '-') + .toLowerCase(); +} + +function titleFromNode(node) { + return node.data.label.replace(/^enum\s+/, ''); +} + +function collectDefinitions(root) { + const definitions = new Map(); + + function visit(namespace) { + if (!namespace.nested) { + return; + } + + Object.values(namespace.nested).forEach((item) => { + if (item instanceof protobuf.Type || item instanceof protobuf.Enum) { + definitions.set(item.fullName.replace(/^\./, ''), item); + } + visit(item); + }); + } + + visit(root); + return definitions; +} + +function groupByShortName(definitions) { + const byShort = new Map(); + definitions.forEach((definition, fullName) => { + const shortName = fullName.split('.').at(-1); + byShort.set(shortName, [...(byShort.get(shortName) ?? []), fullName]); + }); + return byShort; +} + +function specUrlFromLayout(node) { + if (!node.data.specUrl) { + return undefined; + } + const hash = node.data.specUrl.split('#')[1]; + return hash ? `${SPECBASE}#${hash}` : SPECBASE; +} + +function resolveSymbol(node, definitions, byShortName) { + if (node.data.sourceSymbol) { + return node.data.sourceSymbol; + } + + const title = titleFromNode(node); + if (definitions.has(title)) { + return title; + } + + if (title.startsWith('gnmi_ext.')) { + return title; + } + + const matches = byShortName.get(title) ?? []; + if (matches.length === 1) { + return matches[0]; + } + + const gnmiMatch = matches.find((name) => name.startsWith('gnmi.')); + if (gnmiMatch) { + return gnmiMatch; + } + + const extMatch = matches.find((name) => name.startsWith('gnmi_ext.')); + if (extMatch) { + return extMatch; + } + + return null; +} + +function displayType(field) { + const base = field.map ? `map<${field.keyType},${field.type}>` : field.type; + return field.repeated ? `repeated ${base}` : base; +} + +function resolveFieldRef(field, currentSymbol, symbolToNodeId, byShortName) { + if (field.map || SCALAR_TYPES.has(field.type)) { + return null; + } + + if (EXTERNAL_REFS.has(field.type)) { + return EXTERNAL_REFS.get(field.type); + } + + const currentParts = currentSymbol.split('.'); + const packageName = currentParts[0]; + const candidates = []; + + if (field.type.includes('.')) { + candidates.push(field.type); + } else { + candidates.push(`${currentSymbol}.${field.type}`); + candidates.push(`${packageName}.${field.type}`); + candidates.push(...(byShortName.get(field.type) ?? [])); + } + + const target = candidates.find((candidate) => symbolToNodeId.has(candidate)); + return target ? symbolToNodeId.get(target) : null; +} + +function reservedFields(type) { + const numbers = []; + const names = []; + + for (const item of type.reserved ?? []) { + if (Array.isArray(item)) { + const [start, end] = item; + numbers.push(start === end ? `${start}` : `${start}-${end}`); + } else { + names.push(item); + } + } + + const count = Math.max(numbers.length, names.length); + return Array.from({ length: count }, (_, index) => { + const name = names[index]; + const number = numbers[index]; + const label = [name, number].filter(Boolean).join(' / '); + return { + id: `reserved-${kebab(name ?? number ?? `${index + 1}`)}`, + type: 'reserved', + name: label, + ref: null, + badge: 'reserved', + }; + }); +} + +function fieldData(field, currentSymbol, symbolToNodeId, byShortName) { + const deprecated = Boolean(field.options?.deprecated); + const data = { + id: kebab(field.name), + type: displayType(field), + name: field.name, + ref: resolveFieldRef(field, currentSymbol, symbolToNodeId, byShortName), + }; + + if (field.partOf) { + data.group = `oneof ${field.partOf.name}`; + } + if (deprecated) { + data.badge = 'deprecated'; + data.deprecated = true; + } else if (field.name === 'extension' && field.type === 'gnmi_ext.Extension') { + data.badge = 'optional'; + } + + return data; +} + +function enumFields(enumDefinition) { + return Object.entries(enumDefinition.values).map(([name, value]) => ({ + id: kebab(name.replace(/^EID_/, '')), + type: `${value}`, + name, + ref: null, + })); +} + +function protoUrl(source, line, gnmiTag) { + if (!line) { + return undefined; + } + + const file = + source === 'gnmi_ext' ? 'proto/gnmi_ext/gnmi_ext.proto' : 'proto/gnmi/gnmi.proto'; + return `${GNMI_GITHUB_BASE}/${gnmiTag}/${file}#L${line}`; +} + +function buildSymbolMaps(nodes, definitions, byShortName) { + const symbolToNodeId = new Map(); + const nodeIdToSymbol = new Map(); + + nodes.forEach((node) => { + const symbol = resolveSymbol(node, definitions, byShortName); + if (!symbol || !definitions.has(symbol)) { + return; + } + symbolToNodeId.set(symbol, node.id); + nodeIdToSymbol.set(node.id, symbol); + }); + + return { nodeIdToSymbol, symbolToNodeId }; +} + +function serviceNode(node, service, serviceLine, serviceVersion, gnmiTag) { + const methods = service.methodsArray; + return { + ...node, + style: { ...node.style }, + data: { + id: node.id, + kind: node.data.kind, + label: `service gNMI ${serviceVersion}`, + protoUrl: protoUrl('gnmi', serviceLine, gnmiTag), + specUrl: specUrlFromLayout(node), + fields: methods.map((method) => ({ + id: kebab(method.name), + type: 'rpc', + name: method.name, + ref: `rpc-${kebab(method.name)}`, + ...(method.requestStream || method.responseStream ? { badge: 'stream' } : {}), + })), + }, + }; +} + +function rpcNode(node, service, rpcLines, gnmiTag) { + const methodName = node.id.replace(/^rpc-/, '').replace(/(^|-)([a-z])/g, (_, __, letter) => + letter.toUpperCase(), + ); + const method = service.methods[methodName]; + if (!method) { + return node; + } + + return { + ...node, + style: { ...node.style }, + data: { + id: node.id, + kind: node.data.kind, + label: `rpc ${method.name}`, + protoUrl: protoUrl('gnmi', rpcLines.get(method.name), gnmiTag), + specUrl: specUrlFromLayout(node), + fields: [ + { + id: 'takes', + type: method.requestStream ? 'takes stream' : 'takes', + name: method.requestType, + ref: kebab(method.requestType), + }, + { + id: 'returns', + type: method.responseStream ? 'returns stream' : 'returns', + name: method.responseType, + ref: kebab(method.responseType), + }, + ], + }, + }; +} + +function schemaNode( + node, + definition, + symbol, + symbolToNodeId, + byShortName, + linesBySource, + gnmiTag, +) { + const source = symbol.startsWith('gnmi_ext.') ? 'gnmi_ext' : 'gnmi'; + const fields = + definition instanceof protobuf.Type + ? [ + ...definition.fieldsArray.map((field) => + fieldData(field, symbol, symbolToNodeId, byShortName), + ), + ...reservedFields(definition), + ] + : enumFields(definition); + const deprecated = Boolean(definition.options?.deprecated); + const badges = deprecated + ? [...new Set([...(node.data.badges ?? []), 'deprecated'])] + : node.data.badges?.filter((badge) => badge !== 'deprecated'); + + return { + ...node, + style: { ...node.style }, + data: { + id: node.id, + kind: node.data.kind, + label: node.data.label, + sourceSymbol: symbol, + deprecated, + protoUrl: protoUrl(source, linesBySource[source].get(symbol), gnmiTag), + specUrl: specUrlFromLayout(node), + ...(badges?.length ? { badges } : {}), + fields, + }, + }; +} + +function edgeKind(sourceNode, field, targetNode) { + if (field.type === 'rpc') { + return 'rpc'; + } + if (field.type.includes('gnmi_ext.Extension')) { + return 'extension'; + } + if (sourceNode.data.sourceSymbol?.startsWith('gnmi_ext.')) { + return 'extension-detail'; + } + if (targetNode.data.sourceSymbol?.startsWith('gnmi_ext.')) { + return 'extension-detail'; + } + return 'field'; +} + +function buildEdges(nodes) { + const nodesById = new Map(nodes.map((node) => [node.id, node])); + const edges = []; + + for (const node of nodes) { + for (const field of node.data.fields ?? []) { + if (!field.ref || !nodesById.has(field.ref)) { + continue; + } + const targetNode = nodesById.get(field.ref); + const kind = edgeKind(node, field, targetNode); + edges.push({ + id: `${node.id}:${field.id}->${field.ref}`, + source: node.id, + sourceHandle: field.id, + target: field.ref, + kind, + deprecated: Boolean(field.deprecated || targetNode.data.deprecated), + }); + } + } + + return edges; +} + +function generatedSource({ nodes, edges, bounds, source }) { + return `// Generated by scripts/generate-map-data.mjs. Do not edit by hand.\n\nexport const mapSource = ${JSON.stringify( + source, + null, + 2, + )};\n\nexport const mapNodes = ${JSON.stringify(nodes, null, 2)};\n\nexport const mapEdges = ${JSON.stringify( + edges, + null, + 2, + )};\n\nexport const mapBounds = ${JSON.stringify(bounds, null, 2)};\n\nexport function getVisibleMap({ showDeprecated = false, showExtensions = true } = {}) {\n const visibleNodes = mapNodes\n .filter((node) => showDeprecated || !node.data.deprecated)\n .map((node) => ({\n ...node,\n data: {\n ...node.data,\n fields: (node.data.fields ?? []).filter((field) => showDeprecated || !field.deprecated),\n },\n }));\n const visibleNodeIds = new Set(visibleNodes.map((node) => node.id));\n const visibleHandles = new Set(\n visibleNodes.flatMap((node) =>\n (node.data.fields ?? []).map((field) => \`\${node.id}:\${field.id}\`),\n ),\n );\n const visibleEdges = mapEdges.filter((edge) => {\n if (!showExtensions && edge.kind === 'extension') {\n return false;\n }\n if (!showDeprecated && edge.deprecated) {\n return false;\n }\n return (\n visibleNodeIds.has(edge.source) &&\n visibleNodeIds.has(edge.target) &&\n visibleHandles.has(\`\${edge.source}:\${edge.sourceHandle}\`)\n );\n });\n\n return { nodes: visibleNodes, edges: visibleEdges };\n}\n`; +} + +async function main() { + const tags = await fetchJson(GNMI_TAGS_API); + const latestTag = tags.find((tag) => /^v\d+\.\d+\.\d+$/.test(tag.name)); + if (!latestTag) { + throw new Error('Could not resolve latest openconfig/gnmi tag'); + } + + const gnmiTag = latestTag.name; + const gnmiRawUrl = `${GNMI_RAW_BASE}/${gnmiTag}/proto/gnmi/gnmi.proto`; + const extRawUrl = `${GNMI_RAW_BASE}/${gnmiTag}/proto/gnmi_ext/gnmi_ext.proto`; + const [gnmiProto, extProto] = await Promise.all([fetchText(gnmiRawUrl), fetchText(extRawUrl)]); + + const root = new protobuf.Root(); + protobuf.parse(extProto, root, { keepCase: true }); + protobuf.parse(gnmiProto, root, { keepCase: true }); + + const definitions = collectDefinitions(root); + const byShortName = groupByShortName(definitions); + const { nodeIdToSymbol, symbolToNodeId } = buildSymbolMaps( + layoutNodes, + definitions, + byShortName, + ); + const service = root.lookupService('gnmi.gNMI'); + const serviceVersion = root.nested.gnmi.options['(gnmi_service)']; + const linesBySource = { + gnmi: definitionLines(gnmiProto), + gnmi_ext: definitionLines(extProto), + }; + const rpcLines = methodLines(gnmiProto); + + const nodes = layoutNodes.map((node) => { + if (node.id === 'service-gnmi') { + return serviceNode(node, service, linesBySource.gnmi.get('gnmi.gNMI'), serviceVersion, gnmiTag); + } + if (node.data.kind === 'rpc') { + return rpcNode(node, service, rpcLines, gnmiTag); + } + + const symbol = nodeIdToSymbol.get(node.id); + if (symbol) { + return schemaNode( + node, + definitions.get(symbol), + symbol, + symbolToNodeId, + byShortName, + linesBySource, + gnmiTag, + ); + } + + return { + ...node, + style: { ...node.style }, + data: { + ...node.data, + id: node.id, + specUrl: specUrlFromLayout(node), + }, + }; + }); + const edges = buildEdges(nodes); + const source = { + gnmiTag, + gnmiServiceVersion: serviceVersion, + gnmiBase: `${GNMI_GITHUB_BASE}/${gnmiTag}/proto/gnmi/gnmi.proto`, + extBase: `${GNMI_GITHUB_BASE}/${gnmiTag}/proto/gnmi_ext/gnmi_ext.proto`, + specBase: SPECBASE, + }; + + await fs.writeFile(OUTPUT_PATH, generatedSource({ nodes, edges, bounds: mapBounds, source })); + console.log(`Wrote ${OUTPUT_PATH} from openconfig/gnmi ${gnmiTag}`); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/scripts/generate-pdf.mjs b/scripts/generate-pdf.mjs new file mode 100644 index 0000000..4a6d3dc --- /dev/null +++ b/scripts/generate-pdf.mjs @@ -0,0 +1,368 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import PDFDocument from 'pdfkit'; +import { getVisibleMap, mapBounds, mapSource } from '../src/gnmiMap.js'; + +const outputPath = path.resolve('gnmi_0.10.0_map.pdf'); +const publicOutputPath = path.resolve('public/gnmi_0.10.0_map.pdf'); +const margin = 56; +const pageWidth = mapBounds.width + margin * 2; +const pageHeight = mapBounds.height + margin * 2; +const headerHeight = 42; +const rowHeight = 29; +const bodyPadding = 8; + +const colors = { + background: '#f4f6f8', + panel: '#ffffff', + panelSoft: '#f8fafc', + border: '#b8c5d3', + text: '#172033', + muted: '#677486', + blue: '#0b4b8f', + rpc: '#1c62a0', + teal: '#16736b', + amber: '#a15c03', + gray: '#4d5a6b', + reserved: '#e7ebf0', + deprecated: '#fee2df', +}; + +const headerColors = { + service: colors.blue, + rpc: colors.rpc, + enum: colors.amber, + external: colors.gray, + legend: colors.gray, + message: colors.teal, +}; + +const edgeColors = { + rpc: '#0b4b8f', + field: '#5b708a', + extension: '#8a6a1f', + 'extension-detail': '#b47a18', +}; + +function nodeWidth(node) { + return node.style?.width ?? 320; +} + +function nodeHeight(node) { + const fields = node.data.fields ?? []; + const badgeHeight = node.data.badges?.length ? 24 : 0; + const rows = Math.max(fields.length, 1); + return headerHeight + badgeHeight + bodyPadding * 2 + rows * rowHeight; +} + +function sourcePoint(node, handleId) { + const fields = node.data.fields ?? []; + const index = Math.max( + 0, + fields.findIndex((field) => field.id === handleId), + ); + const y = + node.position.y + + headerHeight + + (node.data.badges?.length ? 24 : 0) + + bodyPadding + + index * rowHeight + + rowHeight / 2; + + return { + x: node.position.x + nodeWidth(node), + y, + }; +} + +function targetPoint(node) { + return { + x: node.position.x, + y: node.position.y + nodeHeight(node) / 2, + }; +} + +function trimText(value, limit) { + if (value.length <= limit) { + return value; + } + + return `${value.slice(0, limit - 1)}...`; +} + +function drawArrow(doc, x, y, angle, color) { + const size = 8; + doc + .save() + .fillColor(color) + .moveTo(x, y) + .lineTo( + x - size * Math.cos(angle - Math.PI / 6), + y - size * Math.sin(angle - Math.PI / 6), + ) + .lineTo( + x - size * Math.cos(angle + Math.PI / 6), + y - size * Math.sin(angle + Math.PI / 6), + ) + .closePath() + .fill() + .restore(); +} + +function drawEdge(doc, edge, nodesById) { + const source = nodesById.get(edge.source); + const target = nodesById.get(edge.target); + if (!source || !target) { + return; + } + + const start = sourcePoint(source, edge.sourceHandle); + const end = targetPoint(target); + const color = edgeColors[edge.kind] ?? edgeColors.field; + const dx = Math.max(90, Math.abs(end.x - start.x) * 0.38); + const c1 = { x: start.x + dx, y: start.y }; + const c2 = { x: end.x - dx, y: end.y }; + + doc.save(); + doc.lineWidth(edge.kind === 'rpc' ? 2.2 : 1.4).strokeColor(color).opacity(0.78); + if (edge.kind === 'extension') { + doc.dash(8, { space: 6 }); + } + doc + .moveTo(start.x + margin, start.y + margin) + .bezierCurveTo( + c1.x + margin, + c1.y + margin, + c2.x + margin, + c2.y + margin, + end.x + margin, + end.y + margin, + ) + .stroke(); + doc.undash(); + doc.opacity(1); + drawArrow( + doc, + end.x + margin, + end.y + margin, + Math.atan2(end.y - c2.y, end.x - c2.x), + color, + ); + doc.restore(); +} + +function drawBadge(doc, text, x, y, color, fill) { + const width = Math.max(46, doc.widthOfString(text) + 12); + doc + .save() + .fillColor(fill) + .roundedRect(x, y, width, 16, 8) + .fill() + .fillColor(color) + .font('Helvetica-Bold') + .fontSize(6.8) + .text(text.toUpperCase(), x + 6, y + 5, { lineBreak: false }) + .restore(); + return width; +} + +function drawNode(doc, node) { + const x = node.position.x + margin; + const y = node.position.y + margin; + const width = nodeWidth(node); + const height = nodeHeight(node); + const fields = node.data.fields ?? []; + const headerColor = headerColors[node.data.kind] ?? colors.teal; + + doc + .save() + .fillColor(colors.panel) + .roundedRect(x, y, width, height, 8) + .fill() + .lineWidth(1) + .strokeColor(colors.border) + .roundedRect(x, y, width, height, 8) + .stroke() + .restore(); + + doc + .save() + .fillColor(headerColor) + .roundedRect(x, y, width, headerHeight, 8) + .fill() + .rect(x, y + headerHeight - 8, width, 8) + .fill() + .restore(); + + const kindLabel = node.data.kind.toUpperCase(); + doc + .font('Helvetica-Bold') + .fontSize(7) + .fillColor('#dbeafe') + .text(kindLabel, x + 10, y + 9, { lineBreak: false }); + + doc + .font('Helvetica-Bold') + .fontSize(13) + .fillColor('#ffffff') + .text(trimText(node.data.label, 38), x + 10, y + 22, { + width: width - 72, + lineBreak: false, + }); + + let linkX = x + width - 52; + if (node.data.protoUrl) { + doc + .roundedRect(linkX, y + 10, 18, 18, 4) + .fillOpacity(0.18) + .fillColor('#ffffff') + .fill() + .fillOpacity(1) + .font('Helvetica-Bold') + .fontSize(8) + .fillColor('#ffffff') + .text('P', linkX + 6, y + 15, { lineBreak: false }); + doc.link(linkX, y + 10, 18, 18, node.data.protoUrl); + linkX += 23; + } + + if (node.data.specUrl) { + doc + .roundedRect(linkX, y + 10, 18, 18, 4) + .fillOpacity(0.18) + .fillColor('#ffffff') + .fill() + .fillOpacity(1) + .font('Helvetica-Bold') + .fontSize(8) + .fillColor('#ffffff') + .text('D', linkX + 6, y + 15, { lineBreak: false }); + doc.link(linkX, y + 10, 18, 18, node.data.specUrl); + } + + let rowY = y + headerHeight + bodyPadding; + + if (node.data.badges?.length) { + let badgeX = x + 10; + for (const badge of node.data.badges) { + badgeX += drawBadge(doc, badge, badgeX, rowY, '#9b1c15', colors.deprecated) + 6; + } + rowY += 24; + } + + if (!fields.length) { + doc + .font('Helvetica-Oblique') + .fontSize(10) + .fillColor(colors.muted) + .text('empty message', x + 12, rowY + 8, { lineBreak: false }); + return; + } + + fields.forEach((field, index) => { + const currentY = rowY + index * rowHeight; + if (index % 2 === 0) { + doc + .save() + .fillColor(colors.panelSoft) + .roundedRect(x + 8, currentY, width - 16, rowHeight - 2, 5) + .fill() + .restore(); + } + + doc + .font('Helvetica-Bold') + .fontSize(8.5) + .fillColor(colors.muted) + .text(trimText(field.type, 26), x + 14, currentY + 7, { + width: Math.floor(width * 0.43), + lineBreak: false, + }); + + const nameX = x + Math.floor(width * 0.46); + doc + .font('Courier-Bold') + .fontSize(8.5) + .fillColor(colors.text) + .text(trimText(field.name, 31), nameX, currentY + 7, { + width: width - (nameX - x) - 24, + lineBreak: false, + }); + + if (field.badge) { + const fill = field.badge === 'reserved' ? colors.reserved : colors.deprecated; + const textColor = field.badge === 'reserved' ? colors.gray : '#9b1c15'; + drawBadge(doc, field.badge, x + width - 70, currentY + 6, textColor, fill); + } + }); +} + +async function main() { + const { nodes: pdfNodes, edges: pdfEdges } = getVisibleMap({ + showDeprecated: false, + showExtensions: true, + }); + const nodesById = new Map(pdfNodes.map((node) => [node.id, node])); + const doc = new PDFDocument({ + autoFirstPage: false, + compress: true, + info: { + Title: `gNMI service ${mapSource.gnmiServiceVersion} React Flow Map`, + Author: 'gnmi-map', + Subject: `Generated from openconfig/gnmi ${mapSource.gnmiTag} protobuf IDL`, + Keywords: 'gNMI, OpenConfig, React Flow, protobuf', + }, + }); + + const stream = fs.createWriteStream(outputPath); + doc.pipe(stream); + doc.addPage({ size: [pageWidth, pageHeight], margin: 0 }); + + doc.rect(0, 0, pageWidth, pageHeight).fill(colors.background); + + doc + .font('Helvetica-Bold') + .fontSize(26) + .fillColor(colors.text) + .text(`gNMI service ${mapSource.gnmiServiceVersion} map`, margin, 18, { lineBreak: false }); + doc + .font('Helvetica') + .fontSize(12) + .fillColor(colors.muted) + .text(`Generated from openconfig/gnmi ${mapSource.gnmiTag} protobuf IDL`, margin, 48, { + lineBreak: false, + }); + + for (const edge of pdfEdges) { + drawEdge(doc, edge, nodesById); + } + + for (const node of pdfNodes) { + drawNode(doc, node); + } + + doc + .font('Helvetica') + .fontSize(10) + .fillColor(colors.muted) + .text('P = proto definition, D = specification documentation', margin, pageHeight - 32, { + lineBreak: false, + }); + + doc.end(); + await new Promise((resolve, reject) => { + stream.on('finish', resolve); + stream.on('error', reject); + }); + + fs.mkdirSync(path.dirname(publicOutputPath), { recursive: true }); + fs.copyFileSync(outputPath, publicOutputPath); + + console.log(`Wrote ${outputPath}`); + console.log(`Wrote ${publicOutputPath}`); +} + +main().catch((error) => { + console.error(error); + process.exit(1); +}); diff --git a/scripts/validate-map.mjs b/scripts/validate-map.mjs new file mode 100644 index 0000000..90bb727 --- /dev/null +++ b/scripts/validate-map.mjs @@ -0,0 +1,85 @@ +import { getVisibleMap, mapEdges, mapNodes, mapSource } from '../src/gnmiMap.js'; + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +function validateEdges(nodes, edges, label) { + const nodeIds = new Set(nodes.map((node) => node.id)); + const handles = new Set( + nodes.flatMap((node) => (node.data.fields ?? []).map((field) => `${node.id}:${field.id}`)), + ); + + for (const edge of edges) { + assert(nodeIds.has(edge.source), `${label}: edge ${edge.id} has missing source ${edge.source}`); + assert(nodeIds.has(edge.target), `${label}: edge ${edge.id} has missing target ${edge.target}`); + assert( + handles.has(`${edge.source}:${edge.sourceHandle}`), + `${label}: edge ${edge.id} has missing source handle ${edge.sourceHandle}`, + ); + } +} + +function validateLinks() { + for (const node of mapNodes) { + if (node.data.protoUrl?.startsWith('https://github.com/openconfig/gnmi/blob/')) { + const validProtoBase = + node.data.protoUrl.startsWith(mapSource.gnmiBase) || + node.data.protoUrl.startsWith(mapSource.extBase); + assert(validProtoBase, `${node.id}: protoUrl does not use configured proto bases`); + } + + if (node.data.specUrl) { + assert( + node.data.specUrl.startsWith(mapSource.specBase), + `${node.id}: specUrl does not use configured spec base`, + ); + } + } +} + +function validateDeprecatedVisibility() { + const rawSubscribeResponse = mapNodes.find((node) => node.id === 'subscribe-response'); + assert(rawSubscribeResponse, 'raw map is missing SubscribeResponse'); + const rawErrorField = rawSubscribeResponse.data.fields.find((field) => field.name === 'error'); + assert(rawErrorField?.deprecated, 'raw SubscribeResponse.error must be preserved as deprecated'); + + const defaultMap = getVisibleMap(); + const defaultSubscribeResponse = defaultMap.nodes.find((node) => node.id === 'subscribe-response'); + assert(defaultSubscribeResponse, 'default map is missing SubscribeResponse'); + assert( + !defaultSubscribeResponse.data.fields.some((field) => field.name === 'error'), + 'default SubscribeResponse must hide deprecated error field', + ); + assert( + defaultMap.nodes.every((node) => !node.data.deprecated), + 'default map must hide deprecated nodes', + ); + assert( + defaultMap.nodes.every((node) => (node.data.fields ?? []).every((field) => !field.deprecated)), + 'default map must hide deprecated fields', + ); + + const deprecatedMap = getVisibleMap({ showDeprecated: true }); + const deprecatedSubscribeResponse = deprecatedMap.nodes.find( + (node) => node.id === 'subscribe-response', + ); + assert( + deprecatedSubscribeResponse?.data.fields.some((field) => field.name === 'error'), + 'deprecated map must include SubscribeResponse.error', + ); +} + +validateEdges(mapNodes, mapEdges, 'raw map'); +validateEdges(getVisibleMap().nodes, getVisibleMap().edges, 'default map'); +validateEdges( + getVisibleMap({ showDeprecated: true }).nodes, + getVisibleMap({ showDeprecated: true }).edges, + 'deprecated map', +); +validateLinks(); +validateDeprecatedVisibility(); + +console.log('Map data is valid'); diff --git a/src/App.jsx b/src/App.jsx index 5605399..94f2c9a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,13 +14,14 @@ import '@xyflow/react/dist/style.css'; import { BookOpen, ExternalLink, + EyeOff, FileCode2, FileDown, Focus, GitBranch, Search, } from 'lucide-react'; -import { mapBounds, mapEdges, mapNodes } from './gnmiMap.js'; +import { getVisibleMap, mapBounds, mapSource } from './gnmiMap.js'; const edgeStyleByKind = { rpc: { stroke: '#0b4b8f', strokeWidth: 2.2 }, @@ -59,9 +60,14 @@ function AppShell() { const { fitView } = useReactFlow(); const [queryValue, setQueryValue] = useState(''); const [showExtensions, setShowExtensions] = useState(false); + const [showDeprecated, setShowDeprecated] = useState(false); const [selectedId, setSelectedId] = useState(null); const query = queryValue.trim().toLowerCase(); + const visibleMap = useMemo( + () => getVisibleMap({ showDeprecated, showExtensions }), + [showDeprecated, showExtensions], + ); const nodeMatches = useMemo(() => { if (!query) { @@ -69,15 +75,15 @@ function AppShell() { } return new Set( - mapNodes + visibleMap.nodes .filter((currentNode) => searchableText(currentNode).includes(query)) .map((currentNode) => currentNode.id), ); - }, [query]); + }, [query, visibleMap.nodes]); const nodes = useMemo( () => - mapNodes.map((currentNode) => { + visibleMap.nodes.map((currentNode) => { const active = !query || nodeMatches.has(currentNode.id); return { ...currentNode, @@ -89,37 +95,29 @@ function AppShell() { }, }; }), - [nodeMatches, query, showExtensions], + [nodeMatches, query, showExtensions, visibleMap.nodes], ); const edges = useMemo( () => - mapEdges - .filter((edge) => { - if (showExtensions) { - return true; - } - - return edge.kind !== 'extension'; - }) - .map((edge) => { - const connectedToMatch = - !query || nodeMatches.has(edge.source) || nodeMatches.has(edge.target); - const style = edgeStyleByKind[edge.kind] ?? edgeStyleByKind.field; - - return { - ...edge, - type: 'smoothstep', - className: `flow-edge flow-edge-${edge.kind}`, - markerEnd: { type: MarkerType.ArrowClosed, color: style.stroke }, - animated: query ? connectedToMatch : edge.kind === 'rpc', - style: { - ...style, - opacity: connectedToMatch ? 1 : 0.12, - }, - }; - }), - [nodeMatches, query, showExtensions], + visibleMap.edges.map((edge) => { + const connectedToMatch = + !query || nodeMatches.has(edge.source) || nodeMatches.has(edge.target); + const style = edgeStyleByKind[edge.kind] ?? edgeStyleByKind.field; + + return { + ...edge, + type: 'smoothstep', + className: `flow-edge flow-edge-${edge.kind}`, + markerEnd: { type: MarkerType.ArrowClosed, color: style.stroke }, + animated: query ? connectedToMatch : edge.kind === 'rpc', + style: { + ...style, + opacity: connectedToMatch ? 1 : 0.12, + }, + }; + }), + [nodeMatches, query, visibleMap.edges], ); const selectedNode = useMemo( @@ -135,7 +133,7 @@ function AppShell() {
- gNMI 0.7.0 + gNMI service {mapSource.gnmiServiceVersion}

React Flow Map

@@ -165,7 +163,17 @@ function AppShell() { Extensions - + + + @@ -197,7 +205,11 @@ function AppShell() { /> - +
); diff --git a/src/gnmiMap.js b/src/gnmiMap.js index 7cc4b10..ac833b6 100644 --- a/src/gnmiMap.js +++ b/src/gnmiMap.js @@ -1,556 +1,2893 @@ -const GNMIBASE = - 'https://github.com/openconfig/gnmi/blob/d19cebf5e7be48e7a6fa9fbdff668d18ad87be9d/proto/gnmi/gnmi.proto'; -const EXTBBASE = - 'https://github.com/openconfig/gnmi/blob/d19cebf5e7be48e7a6fa9fbdff668d18ad87be9d/proto/gnmi_ext/gnmi_ext.proto'; -const SPECBASE = - 'https://github.com/openconfig/reference/blob/638fba23f697d67a0f8b6b683d492b8a1254817d/rpc/gnmi/gnmi-specification.md'; +// Generated by scripts/generate-map-data.mjs. Do not edit by hand. -const proto = (line) => `${GNMIBASE}#L${line}`; -const extProto = (line) => `${EXTBBASE}#L${line}`; -const spec = (anchor) => `${SPECBASE}#${anchor}`; - -const node = (id, kind, label, x, y, width, data = {}) => ({ - id, - type: 'schema', - position: { x, y }, - style: { width }, - data: { - id, - kind, - label, - ...data, - }, -}); - -const f = (id, type, name, ref, extra = {}) => ({ - id, - type, - name, - ref, - ...extra, -}); - -const e = (source, sourceHandle, target, kind = 'field') => ({ - id: `${source}:${sourceHandle}->${target}`, - source, - sourceHandle, - target, - kind, -}); +export const mapSource = { + "gnmiTag": "v0.14.1", + "gnmiServiceVersion": "0.10.0", + "gnmiBase": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto", + "extBase": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto", + "specBase": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md" +}; export const mapNodes = [ - node('service-gnmi', 'service', 'service gNMI 0.7.0', 1240, 40, 330, { - protoUrl: proto(44), - specUrl: spec('grpc-network-management-interface-gnmi'), - fields: [ - f('capabilities', 'rpc', 'Capabilities', 'rpc-capabilities'), - f('get', 'rpc', 'Get', 'rpc-get'), - f('set', 'rpc', 'Set', 'rpc-set'), - f('subscribe', 'rpc', 'Subscribe', 'rpc-subscribe', { badge: 'stream' }), - ], - }), - - node('rpc-set', 'rpc', 'rpc Set', 80, 230, 250, { - protoUrl: proto(62), - specUrl: spec('34-modifying-state'), - fields: [ - f('takes', 'takes', 'SetRequest', 'set-request'), - f('returns', 'returns', 'SetResponse', 'set-response'), - ], - }), - node('rpc-subscribe', 'rpc', 'rpc Subscribe', 780, 230, 290, { - protoUrl: proto(68), - specUrl: spec('35-subscribing-to-telemetry-updates'), - fields: [ - f('takes', 'takes stream', 'SubscribeRequest', 'subscribe-request'), - f('returns', 'returns stream', 'SubscribeResponse', 'subscribe-response'), - ], - }), - node('rpc-get', 'rpc', 'rpc Get', 1520, 230, 250, { - protoUrl: proto(57), - specUrl: spec('33-retrieving-snapshots-of-state-information'), - fields: [ - f('takes', 'takes', 'GetRequest', 'get-request'), - f('returns', 'returns', 'GetResponse', 'get-response'), - ], - }), - node('rpc-capabilities', 'rpc', 'rpc Capabilities', 2200, 230, 300, { - protoUrl: proto(51), - specUrl: spec('32-capability-discovery'), - fields: [ - f('takes', 'takes', 'CapabilityRequest', 'capability-request'), - f('returns', 'returns', 'CapabilityResponse', 'capability-response'), - ], - }), - - node('set-request', 'message', 'SetRequest', 20, 450, 340, { - protoUrl: proto(339), - specUrl: spec('341-the-setrequest-message'), - fields: [ - f('prefix', 'Path', 'prefix', 'path'), - f('delete', 'repeated Path', 'delete', 'path'), - f('replace', 'repeated Update', 'replace', 'update'), - f('update', 'repeated Update', 'update', 'update'), - f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { - badge: 'optional', - }), - ], - }), - node('set-response', 'message', 'SetResponse', 390, 450, 360, { - protoUrl: proto(356), - specUrl: spec('342-the-setresponse-message'), - fields: [ - f('prefix', 'Path', 'prefix', 'path'), - f('response', 'repeated UpdateResult', 'response', 'update-result'), - f('message', 'Error', 'message', 'error', { badge: 'deprecated' }), - f('timestamp', 'int64', 'timestamp'), - f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { - badge: 'optional', - }), - ], - }), - node('subscribe-request', 'message', 'SubscribeRequest', 780, 450, 360, { - protoUrl: proto(208), - specUrl: spec('3511-the-subscriberequest-message'), - fields: [ - f('subscribe', 'SubscriptionList', 'subscribe', 'subscription-list', { - group: 'oneof request', - }), - f('poll', 'Poll', 'poll', 'poll', { group: 'oneof request' }), - f('aliases', 'AliasList', 'aliases', 'alias-list', { - group: 'oneof request', - }), - f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { - badge: 'optional', - }), - ], - }), - node('subscribe-response', 'message', 'SubscribeResponse', 1180, 450, 360, { - protoUrl: proto(232), - specUrl: spec('3514-the-subscriberesponse-message'), - fields: [ - f('update', 'Notification', 'update', 'notification', { - group: 'oneof response', - }), - f('sync-response', 'bool', 'sync_response', null, { - group: 'oneof response', - }), - f('error', 'Error', 'error', 'error', { - badge: 'deprecated', - group: 'oneof response', - }), - f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { - badge: 'optional', - }), - ], - }), - node('get-request', 'message', 'GetRequest', 1570, 450, 350, { - protoUrl: proto(395), - specUrl: spec('331-the-getrequest-message'), - fields: [ - f('prefix', 'Path', 'prefix', 'path'), - f('path', 'repeated Path', 'path', 'path'), - f('type', 'DataType', 'type', 'data-type'), - f('encoding', 'Encoding', 'encoding', 'encoding'), - f('use-models', 'repeated ModelData', 'use_models', 'model-data'), - f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { - badge: 'optional', - }), - ], - }), - node('get-response', 'message', 'GetResponse', 1960, 450, 350, { - protoUrl: proto(420), - specUrl: spec('332-the-getresponse-message'), - fields: [ - f('notification', 'repeated Notification', 'notification', 'notification'), - f('error', 'Error', 'error', 'error', { badge: 'deprecated' }), - f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { - badge: 'optional', - }), - ], - }), - node('capability-request', 'message', 'CapabilityRequest', 2350, 450, 340, { - protoUrl: proto(431), - specUrl: spec('321-the-capabilityrequest-message'), - fields: [ - f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { - badge: 'optional', - }), - ], - }), - node('capability-response', 'message', 'CapabilityResponse', 2730, 450, 370, { - protoUrl: proto(440), - specUrl: spec('322-the-capabilityresponse-message'), - fields: [ - f('supported-models', 'repeated ModelData', 'supported_models', 'model-data'), - f('supported-encodings', 'repeated Encoding', 'supported_encodings', 'encoding'), - f('version', 'string', 'gNMI_version'), - f('extension', 'repeated gnmi_ext.Extension', 'extension', 'extension', { - badge: 'optional', - }), - ], - }), - - node('error', 'message', 'Error', 120, 850, 300, { - protoUrl: proto(179), - specUrl: spec('23-structured-data-types'), - badges: ['deprecated'], - fields: [ - f('code', 'uint32', 'code'), - f('message', 'string', 'message'), - f('data', 'google.protobuf.Any', 'data', 'any'), - ], - }), - node('update-result', 'message', 'UpdateResult', 450, 820, 330, { - protoUrl: proto(371), - specUrl: spec('342-the-setresponse-message'), - fields: [ - f('timestamp', 'int64', 'timestamp', null, { badge: 'deprecated' }), - f('path', 'Path', 'path', 'path'), - f('message', 'Error', 'message', 'error', { badge: 'deprecated' }), - f('op', 'Operation', 'op', 'operation'), - ], - }), - node('operation', 'enum', 'enum Operation', 430, 1120, 260, { - protoUrl: proto(373), - fields: [ - f('invalid', '0', 'INVALID'), - f('delete', '1', 'DELETE'), - f('replace', '2', 'REPLACE'), - f('update', '3', 'UPDATE'), - ], - }), - - node('poll', 'message', 'Poll', 800, 780, 180, { - protoUrl: proto(223), - specUrl: spec('35153-poll-subscriptions'), - fields: [], - }), - node('alias-list', 'message', 'AliasList', 720, 940, 300, { - protoUrl: proto(328), - specUrl: spec('3516-client-defined-aliases-within-a-subscription'), - fields: [f('alias', 'repeated Alias', 'alias', 'alias')], - }), - node('alias', 'message', 'Alias', 710, 1160, 280, { - protoUrl: proto(320), - specUrl: spec('242-path-aliases'), - fields: [ - f('path', 'Path', 'path', 'path'), - f('alias', 'string', 'alias'), - ], - }), - node('subscription-list', 'message', 'SubscriptionList', 1050, 750, 390, { - protoUrl: proto(251), - specUrl: spec('3512-the-subscriptionlist-message'), - fields: [ - f('prefix', 'Path', 'prefix', 'path'), - f('subscription', 'repeated Subscription', 'subscription', 'subscription'), - f('use-aliases', 'bool', 'use_aliases'), - f('qos', 'QOSMarking', 'qos', 'qos-marking'), - f('mode', 'Mode', 'mode', 'mode'), - f('allow-aggregation', 'bool', 'allow_aggregation'), - f('use-models', 'repeated ModelData', 'use_models', 'model-data'), - f('encoding', 'Encoding', 'encoding', 'encoding'), - f('updates-only', 'bool', 'updates_only'), - ], - }), - node('qos-marking', 'message', 'QOSMarking', 990, 1210, 270, { - protoUrl: proto(311), - specUrl: spec('3512-the-subscriptionlist-message'), - fields: [f('marking', 'uint32', 'marking')], - }), - node('mode', 'enum', 'enum Mode', 990, 1390, 240, { - protoUrl: proto(258), - specUrl: spec('3512-the-subscriptionlist-message'), - fields: [ - f('stream', '0', 'STREAM'), - f('once', '1', 'ONCE'), - f('poll', '2', 'POLL'), - ], - }), - node('subscription', 'message', 'Subscription', 1460, 920, 360, { - protoUrl: proto(286), - specUrl: spec('3513-the-subscription-message'), - fields: [ - f('path', 'Path', 'path', 'path'), - f('mode', 'SubscriptionMode', 'mode', 'subscription-mode'), - f('sample-interval', 'uint64', 'sample_interval'), - f('suppress-redundant', 'bool', 'suppress_redundant'), - f('heartbeat-interval', 'uint64', 'heartbeat_interval'), - ], - }), - node('subscription-mode', 'enum', 'enum SubscriptionMode', 1460, 1250, 310, { - protoUrl: proto(302), - specUrl: spec('35152-stream-subscriptions'), - fields: [ - f('target-defined', '0', 'TARGET_DEFINED'), - f('on-change', '1', 'ON_CHANGE'), - f('sample', '2', 'SAMPLE'), - ], - }), - - node('data-type', 'enum', 'enum DataType', 1660, 720, 280, { - protoUrl: proto(399), - specUrl: spec('331-the-getrequest-message'), - fields: [ - f('all', '0', 'ALL'), - f('config', '1', 'CONFIG'), - f('state', '2', 'STATE'), - f('operational', '3', 'OPERATIONAL'), - ], - }), - node('model-data', 'message', 'ModelData', 2410, 790, 300, { - protoUrl: proto(454), - specUrl: spec('261-the-modeldata-message'), - fields: [ - f('name', 'string', 'name'), - f('organization', 'string', 'organization'), - f('version', 'string', 'version'), - ], - }), - node('encoding', 'enum', 'enum Encoding', 2380, 1120, 260, { - protoUrl: proto(167), - specUrl: spec('23-structured-data-types'), - fields: [ - f('json', '0', 'JSON'), - f('bytes', '1', 'BYTES'), - f('proto', '2', 'PROTO'), - f('ascii', '3', 'ASCII'), - f('json-ietf', '4', 'JSON_IETF'), - ], - }), - - node('notification', 'message', 'Notification', 1740, 1160, 350, { - protoUrl: proto(79), - specUrl: spec('21-reusable-notification-message-format'), - fields: [ - f('timestamp', 'int64', 'timestamp'), - f('prefix', 'Path', 'prefix', 'path'), - f('alias', 'string', 'alias'), - f('update', 'repeated Update', 'update', 'update'), - f('delete', 'repeated Path', 'delete', 'path'), - f('atomic', 'bool', 'atomic'), - ], - }), - node('update', 'message', 'Update', 1320, 1460, 330, { - protoUrl: proto(95), - specUrl: spec('21-reusable-notification-message-format'), - fields: [ - f('path', 'Path', 'path', 'path'), - f('value', 'Value', 'value', 'value', { badge: 'deprecated' }), - f('val', 'TypedValue', 'val', 'typed-value'), - f('duplicates', 'uint32', 'duplicates'), - ], - }), - node('path', 'message', 'Path', 1780, 1530, 360, { - protoUrl: proto(135), - specUrl: spec('222-paths'), - fields: [ - f('element', 'repeated string', 'element', null, { badge: 'deprecated' }), - f('origin', 'string', 'origin'), - f('elem', 'repeated PathElem', 'elem', 'path-elem'), - f('target', 'string', 'target'), - ], - }), - node('path-elem', 'message', 'PathElem', 2210, 1600, 330, { - protoUrl: proto(148), - specUrl: spec('222-paths'), - fields: [ - f('name', 'string', 'name'), - f('key', 'map', 'key'), - ], - }), - node('value', 'message', 'Value', 700, 1570, 300, { - protoUrl: proto(156), - specUrl: spec('223-node-values'), - badges: ['deprecated'], - fields: [ - f('value', 'bytes', 'value'), - f('type', 'Encoding', 'type', 'encoding'), - ], - }), - node('typed-value', 'message', 'TypedValue', 980, 1780, 390, { - protoUrl: proto(104), - fields: [ - f('string-val', 'string', 'string_val', null, { group: 'oneof value' }), - f('int-val', 'int64', 'int_val', null, { group: 'oneof value' }), - f('uint-val', 'uint64', 'uint_val', null, { group: 'oneof value' }), - f('bool-val', 'bool', 'bool_val', null, { group: 'oneof value' }), - f('bytes-val', 'bytes', 'bytes_val', null, { group: 'oneof value' }), - f('float-val', 'float', 'float_val', null, { group: 'oneof value' }), - f('decimal-val', 'Decimal64', 'decimal_val', 'decimal64', { - group: 'oneof value', - }), - f('leaflist-val', 'ScalarArray', 'leaflist_val', 'scalar-array', { - group: 'oneof value', - }), - f('any-val', 'google.protobuf.Any', 'any_val', 'any', { - group: 'oneof value', - }), - f('json-val', 'bytes', 'json_val', null, { group: 'oneof value' }), - f('json-ietf-val', 'bytes', 'json_ietf_val', null, { - group: 'oneof value', - }), - f('ascii-val', 'string', 'ascii_val', null, { group: 'oneof value' }), - f('proto-bytes', 'bytes', 'proto_bytes', null, { group: 'oneof value' }), - ], - }), - node('decimal64', 'message', 'Decimal64', 1430, 1780, 280, { - protoUrl: proto(189), - fields: [ - f('digits', 'int64', 'digits'), - f('precision', 'uint32', 'precision'), - ], - }), - node('scalar-array', 'message', 'ScalarArray', 1430, 1980, 330, { - protoUrl: proto(195), - fields: [f('element', 'repeated TypedValue', 'element', 'typed-value')], - }), - node('any', 'external', 'google.protobuf.Any', 550, 1870, 330, { - protoUrl: 'https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto', - fields: [ - f('type-url', 'string', 'type_url'), - f('value', 'bytes', 'value'), - ], - }), - - node('extension', 'message', 'gnmi_ext.Extension', 2020, 820, 360, { - protoUrl: extProto(27), - specUrl: spec('27-extensions-to-gnmi'), - fields: [ - f('registered-ext', 'RegisteredExtension', 'registered_ext', 'registered-extension', { - group: 'oneof ext', - }), - f('master-arbitration', 'MasterArbitration', 'master_arbitration', 'master-arbitration', { - group: 'oneof ext', - }), - ], - }), - node('registered-extension', 'message', 'gnmi_ext.RegisteredExtension', 2510, 760, 390, { - protoUrl: extProto(37), - fields: [ - f('id', 'ExtensionID', 'id', 'extension-id'), - f('msg', 'bytes', 'msg'), - ], - }), - node('extension-id', 'enum', 'enum gnmi_ext.ExtensionID', 2940, 700, 310, { - protoUrl: extProto(44), - fields: [ - f('unset', '0', 'EID_UNSET'), - f('experimental', '999', 'EID_EXPERIMENTAL'), - ], - }), - node('master-arbitration', 'message', 'gnmi_ext.MasterArbitration', 2510, 1010, 390, { - protoUrl: extProto(59), - fields: [ - f('role', 'Role', 'role', 'role'), - f('election-id', 'Uint128', 'election_id', 'uint128'), - ], - }), - node('uint128', 'message', 'gnmi_ext.Uint128', 2940, 1040, 260, { - protoUrl: extProto(65), - fields: [ - f('high', 'uint64', 'high'), - f('low', 'uint64', 'low'), - ], - }), - node('role', 'message', 'gnmi_ext.Role', 2940, 1210, 260, { - protoUrl: extProto(71), - fields: [f('id', 'string', 'id')], - }), - - node('legend', 'legend', 'Legend', 2720, 1470, 410, { - fields: [ - f('proto', 'file icon', 'proto definition link'), - f('docs', 'book icon', 'documentation link'), - f('extension-note', 'toggle', 'extension relationship edges'), - f('pdf', 'reference', 'gnmi_0.7.0_map.pdf'), - ], - }), + { + "id": "service-gnmi", + "type": "schema", + "position": { + "x": 1240, + "y": 40 + }, + "style": { + "width": 360 + }, + "data": { + "id": "service-gnmi", + "kind": "service", + "label": "service gNMI 0.10.0", + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L49", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#grpc-network-management-interface-gnmi", + "fields": [ + { + "id": "capabilities", + "type": "rpc", + "name": "Capabilities", + "ref": "rpc-capabilities" + }, + { + "id": "get", + "type": "rpc", + "name": "Get", + "ref": "rpc-get" + }, + { + "id": "set", + "type": "rpc", + "name": "Set", + "ref": "rpc-set" + }, + { + "id": "subscribe", + "type": "rpc", + "name": "Subscribe", + "ref": "rpc-subscribe", + "badge": "stream" + } + ] + } + }, + { + "id": "rpc-set", + "type": "schema", + "position": { + "x": 80, + "y": 230 + }, + "style": { + "width": 250 + }, + "data": { + "id": "rpc-set", + "kind": "rpc", + "label": "rpc Set", + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L67", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#34-modifying-state", + "fields": [ + { + "id": "takes", + "type": "takes", + "name": "SetRequest", + "ref": "set-request" + }, + { + "id": "returns", + "type": "returns", + "name": "SetResponse", + "ref": "set-response" + } + ] + } + }, + { + "id": "rpc-subscribe", + "type": "schema", + "position": { + "x": 780, + "y": 230 + }, + "style": { + "width": 290 + }, + "data": { + "id": "rpc-subscribe", + "kind": "rpc", + "label": "rpc Subscribe", + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L73", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#35-subscribing-to-telemetry-updates", + "fields": [ + { + "id": "takes", + "type": "takes stream", + "name": "SubscribeRequest", + "ref": "subscribe-request" + }, + { + "id": "returns", + "type": "returns stream", + "name": "SubscribeResponse", + "ref": "subscribe-response" + } + ] + } + }, + { + "id": "rpc-get", + "type": "schema", + "position": { + "x": 1520, + "y": 230 + }, + "style": { + "width": 250 + }, + "data": { + "id": "rpc-get", + "kind": "rpc", + "label": "rpc Get", + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L62", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#33-retrieving-snapshots-of-state-information", + "fields": [ + { + "id": "takes", + "type": "takes", + "name": "GetRequest", + "ref": "get-request" + }, + { + "id": "returns", + "type": "returns", + "name": "GetResponse", + "ref": "get-response" + } + ] + } + }, + { + "id": "rpc-capabilities", + "type": "schema", + "position": { + "x": 2200, + "y": 230 + }, + "style": { + "width": 300 + }, + "data": { + "id": "rpc-capabilities", + "kind": "rpc", + "label": "rpc Capabilities", + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L56", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#32-capability-discovery", + "fields": [ + { + "id": "takes", + "type": "takes", + "name": "CapabilityRequest", + "ref": "capability-request" + }, + { + "id": "returns", + "type": "returns", + "name": "CapabilityResponse", + "ref": "capability-response" + } + ] + } + }, + { + "id": "set-request", + "type": "schema", + "position": { + "x": 20, + "y": 450 + }, + "style": { + "width": 340 + }, + "data": { + "id": "set-request", + "kind": "message", + "label": "SetRequest", + "sourceSymbol": "gnmi.SetRequest", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L342", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#341-the-setrequest-message", + "fields": [ + { + "id": "prefix", + "type": "Path", + "name": "prefix", + "ref": "path" + }, + { + "id": "delete", + "type": "repeated Path", + "name": "delete", + "ref": "path" + }, + { + "id": "replace", + "type": "repeated Update", + "name": "replace", + "ref": "update" + }, + { + "id": "update", + "type": "repeated Update", + "name": "update", + "ref": "update" + }, + { + "id": "union-replace", + "type": "repeated Update", + "name": "union_replace", + "ref": "update" + }, + { + "id": "extension", + "type": "repeated gnmi_ext.Extension", + "name": "extension", + "ref": "extension", + "badge": "optional" + } + ] + } + }, + { + "id": "set-response", + "type": "schema", + "position": { + "x": 390, + "y": 450 + }, + "style": { + "width": 360 + }, + "data": { + "id": "set-response", + "kind": "message", + "label": "SetResponse", + "sourceSymbol": "gnmi.SetResponse", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L364", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#342-the-setresponse-message", + "fields": [ + { + "id": "prefix", + "type": "Path", + "name": "prefix", + "ref": "path" + }, + { + "id": "response", + "type": "repeated UpdateResult", + "name": "response", + "ref": "update-result" + }, + { + "id": "message", + "type": "Error", + "name": "message", + "ref": "error", + "badge": "deprecated", + "deprecated": true + }, + { + "id": "timestamp", + "type": "int64", + "name": "timestamp", + "ref": null + }, + { + "id": "extension", + "type": "repeated gnmi_ext.Extension", + "name": "extension", + "ref": "extension", + "badge": "optional" + } + ] + } + }, + { + "id": "subscribe-request", + "type": "schema", + "position": { + "x": 780, + "y": 450 + }, + "style": { + "width": 360 + }, + "data": { + "id": "subscribe-request", + "kind": "message", + "label": "SubscribeRequest", + "sourceSymbol": "gnmi.SubscribeRequest", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L220", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3511-the-subscriberequest-message", + "fields": [ + { + "id": "subscribe", + "type": "SubscriptionList", + "name": "subscribe", + "ref": "subscription-list", + "group": "oneof request" + }, + { + "id": "poll", + "type": "Poll", + "name": "poll", + "ref": "poll", + "group": "oneof request" + }, + { + "id": "extension", + "type": "repeated gnmi_ext.Extension", + "name": "extension", + "ref": "extension", + "badge": "optional" + }, + { + "id": "reserved-aliases", + "type": "reserved", + "name": "aliases / 4", + "ref": null, + "badge": "reserved" + } + ] + } + }, + { + "id": "subscribe-response", + "type": "schema", + "position": { + "x": 1180, + "y": 450 + }, + "style": { + "width": 360 + }, + "data": { + "id": "subscribe-response", + "kind": "message", + "label": "SubscribeResponse", + "sourceSymbol": "gnmi.SubscribeResponse", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L245", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3514-the-subscriberesponse-message", + "fields": [ + { + "id": "update", + "type": "Notification", + "name": "update", + "ref": "notification", + "group": "oneof response" + }, + { + "id": "sync-response", + "type": "bool", + "name": "sync_response", + "ref": null, + "group": "oneof response" + }, + { + "id": "error", + "type": "Error", + "name": "error", + "ref": "error", + "group": "oneof response", + "badge": "deprecated", + "deprecated": true + }, + { + "id": "extension", + "type": "repeated gnmi_ext.Extension", + "name": "extension", + "ref": "extension", + "badge": "optional" + } + ] + } + }, + { + "id": "get-request", + "type": "schema", + "position": { + "x": 1570, + "y": 450 + }, + "style": { + "width": 350 + }, + "data": { + "id": "get-request", + "kind": "message", + "label": "GetRequest", + "sourceSymbol": "gnmi.GetRequest", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L405", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#331-the-getrequest-message", + "fields": [ + { + "id": "prefix", + "type": "Path", + "name": "prefix", + "ref": "path" + }, + { + "id": "path", + "type": "repeated Path", + "name": "path", + "ref": "path" + }, + { + "id": "type", + "type": "DataType", + "name": "type", + "ref": "data-type" + }, + { + "id": "encoding", + "type": "Encoding", + "name": "encoding", + "ref": "encoding" + }, + { + "id": "use-models", + "type": "repeated ModelData", + "name": "use_models", + "ref": "model-data" + }, + { + "id": "extension", + "type": "repeated gnmi_ext.Extension", + "name": "extension", + "ref": "extension", + "badge": "optional" + } + ] + } + }, + { + "id": "get-response", + "type": "schema", + "position": { + "x": 1960, + "y": 450 + }, + "style": { + "width": 350 + }, + "data": { + "id": "get-response", + "kind": "message", + "label": "GetResponse", + "sourceSymbol": "gnmi.GetResponse", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L430", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#332-the-getresponse-message", + "fields": [ + { + "id": "notification", + "type": "repeated Notification", + "name": "notification", + "ref": "notification" + }, + { + "id": "error", + "type": "Error", + "name": "error", + "ref": "error", + "badge": "deprecated", + "deprecated": true + }, + { + "id": "extension", + "type": "repeated gnmi_ext.Extension", + "name": "extension", + "ref": "extension", + "badge": "optional" + } + ] + } + }, + { + "id": "capability-request", + "type": "schema", + "position": { + "x": 2350, + "y": 450 + }, + "style": { + "width": 340 + }, + "data": { + "id": "capability-request", + "kind": "message", + "label": "CapabilityRequest", + "sourceSymbol": "gnmi.CapabilityRequest", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L441", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#321-the-capabilityrequest-message", + "fields": [ + { + "id": "extension", + "type": "repeated gnmi_ext.Extension", + "name": "extension", + "ref": "extension", + "badge": "optional" + } + ] + } + }, + { + "id": "capability-response", + "type": "schema", + "position": { + "x": 2730, + "y": 450 + }, + "style": { + "width": 370 + }, + "data": { + "id": "capability-response", + "kind": "message", + "label": "CapabilityResponse", + "sourceSymbol": "gnmi.CapabilityResponse", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L450", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#322-the-capabilityresponse-message", + "fields": [ + { + "id": "supported-models", + "type": "repeated ModelData", + "name": "supported_models", + "ref": "model-data" + }, + { + "id": "supported-encodings", + "type": "repeated Encoding", + "name": "supported_encodings", + "ref": "encoding" + }, + { + "id": "g-nmi-version", + "type": "string", + "name": "gNMI_version", + "ref": null + }, + { + "id": "extension", + "type": "repeated gnmi_ext.Extension", + "name": "extension", + "ref": "extension", + "badge": "optional" + } + ] + } + }, + { + "id": "error", + "type": "schema", + "position": { + "x": 120, + "y": 850 + }, + "style": { + "width": 300 + }, + "data": { + "id": "error", + "kind": "message", + "label": "Error", + "sourceSymbol": "gnmi.Error", + "deprecated": true, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L187", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#23-structured-data-types", + "badges": [ + "deprecated" + ], + "fields": [ + { + "id": "code", + "type": "uint32", + "name": "code", + "ref": null + }, + { + "id": "message", + "type": "string", + "name": "message", + "ref": null + }, + { + "id": "data", + "type": "google.protobuf.Any", + "name": "data", + "ref": "any" + } + ] + } + }, + { + "id": "update-result", + "type": "schema", + "position": { + "x": 450, + "y": 820 + }, + "style": { + "width": 330 + }, + "data": { + "id": "update-result", + "kind": "message", + "label": "UpdateResult", + "sourceSymbol": "gnmi.UpdateResult", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L380", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#342-the-setresponse-message", + "fields": [ + { + "id": "timestamp", + "type": "int64", + "name": "timestamp", + "ref": null, + "badge": "deprecated", + "deprecated": true + }, + { + "id": "path", + "type": "Path", + "name": "path", + "ref": "path" + }, + { + "id": "message", + "type": "Error", + "name": "message", + "ref": "error", + "badge": "deprecated", + "deprecated": true + }, + { + "id": "op", + "type": "Operation", + "name": "op", + "ref": "operation" + } + ] + } + }, + { + "id": "operation", + "type": "schema", + "position": { + "x": 430, + "y": 1120 + }, + "style": { + "width": 260 + }, + "data": { + "id": "operation", + "kind": "enum", + "label": "enum Operation", + "sourceSymbol": "gnmi.UpdateResult.Operation", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L382", + "fields": [ + { + "id": "invalid", + "type": "0", + "name": "INVALID", + "ref": null + }, + { + "id": "delete", + "type": "1", + "name": "DELETE", + "ref": null + }, + { + "id": "replace", + "type": "2", + "name": "REPLACE", + "ref": null + }, + { + "id": "update", + "type": "3", + "name": "UPDATE", + "ref": null + }, + { + "id": "union-replace", + "type": "4", + "name": "UNION_REPLACE", + "ref": null + } + ] + } + }, + { + "id": "poll", + "type": "schema", + "position": { + "x": 800, + "y": 780 + }, + "style": { + "width": 180 + }, + "data": { + "id": "poll", + "kind": "message", + "label": "Poll", + "sourceSymbol": "gnmi.Poll", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L237", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#35153-poll-subscriptions", + "fields": [] + } + }, + { + "id": "subscription-list", + "type": "schema", + "position": { + "x": 1050, + "y": 750 + }, + "style": { + "width": 390 + }, + "data": { + "id": "subscription-list", + "kind": "message", + "label": "SubscriptionList", + "sourceSymbol": "gnmi.SubscriptionList", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L264", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3512-the-subscriptionlist-message", + "fields": [ + { + "id": "prefix", + "type": "Path", + "name": "prefix", + "ref": "path" + }, + { + "id": "subscription", + "type": "repeated Subscription", + "name": "subscription", + "ref": "subscription" + }, + { + "id": "qos", + "type": "QOSMarking", + "name": "qos", + "ref": "qos-marking" + }, + { + "id": "mode", + "type": "Mode", + "name": "mode", + "ref": "mode" + }, + { + "id": "allow-aggregation", + "type": "bool", + "name": "allow_aggregation", + "ref": null + }, + { + "id": "use-models", + "type": "repeated ModelData", + "name": "use_models", + "ref": "model-data" + }, + { + "id": "encoding", + "type": "Encoding", + "name": "encoding", + "ref": "encoding" + }, + { + "id": "updates-only", + "type": "bool", + "name": "updates_only", + "ref": null + }, + { + "id": "reserved-use-aliases", + "type": "reserved", + "name": "use_aliases / 3", + "ref": null, + "badge": "reserved" + } + ] + } + }, + { + "id": "qos-marking", + "type": "schema", + "position": { + "x": 990, + "y": 1210 + }, + "style": { + "width": 270 + }, + "data": { + "id": "qos-marking", + "kind": "message", + "label": "QOSMarking", + "sourceSymbol": "gnmi.QOSMarking", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L331", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3512-the-subscriptionlist-message", + "fields": [ + { + "id": "marking", + "type": "uint32", + "name": "marking", + "ref": null + } + ] + } + }, + { + "id": "mode", + "type": "schema", + "position": { + "x": 990, + "y": 1390 + }, + "style": { + "width": 240 + }, + "data": { + "id": "mode", + "kind": "enum", + "label": "enum Mode", + "sourceSymbol": "gnmi.SubscriptionList.Mode", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L269", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3512-the-subscriptionlist-message", + "fields": [ + { + "id": "stream", + "type": "0", + "name": "STREAM", + "ref": null + }, + { + "id": "once", + "type": "1", + "name": "ONCE", + "ref": null + }, + { + "id": "poll", + "type": "2", + "name": "POLL", + "ref": null + } + ] + } + }, + { + "id": "subscription", + "type": "schema", + "position": { + "x": 1460, + "y": 920 + }, + "style": { + "width": 360 + }, + "data": { + "id": "subscription", + "kind": "message", + "label": "Subscription", + "sourceSymbol": "gnmi.Subscription", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L300", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#3513-the-subscription-message", + "fields": [ + { + "id": "path", + "type": "Path", + "name": "path", + "ref": "path" + }, + { + "id": "mode", + "type": "SubscriptionMode", + "name": "mode", + "ref": "subscription-mode" + }, + { + "id": "sample-interval", + "type": "uint64", + "name": "sample_interval", + "ref": null + }, + { + "id": "suppress-redundant", + "type": "bool", + "name": "suppress_redundant", + "ref": null + }, + { + "id": "heartbeat-interval", + "type": "uint64", + "name": "heartbeat_interval", + "ref": null + } + ] + } + }, + { + "id": "subscription-mode", + "type": "schema", + "position": { + "x": 1460, + "y": 1250 + }, + "style": { + "width": 310 + }, + "data": { + "id": "subscription-mode", + "kind": "enum", + "label": "enum SubscriptionMode", + "sourceSymbol": "gnmi.SubscriptionMode", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L322", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#35152-stream-subscriptions", + "fields": [ + { + "id": "target-defined", + "type": "0", + "name": "TARGET_DEFINED", + "ref": null + }, + { + "id": "on-change", + "type": "1", + "name": "ON_CHANGE", + "ref": null + }, + { + "id": "sample", + "type": "2", + "name": "SAMPLE", + "ref": null + } + ] + } + }, + { + "id": "data-type", + "type": "schema", + "position": { + "x": 1660, + "y": 720 + }, + "style": { + "width": 280 + }, + "data": { + "id": "data-type", + "kind": "enum", + "label": "enum DataType", + "sourceSymbol": "gnmi.GetRequest.DataType", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L409", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#331-the-getrequest-message", + "fields": [ + { + "id": "all", + "type": "0", + "name": "ALL", + "ref": null + }, + { + "id": "config", + "type": "1", + "name": "CONFIG", + "ref": null + }, + { + "id": "state", + "type": "2", + "name": "STATE", + "ref": null + }, + { + "id": "operational", + "type": "3", + "name": "OPERATIONAL", + "ref": null + } + ] + } + }, + { + "id": "model-data", + "type": "schema", + "position": { + "x": 2410, + "y": 790 + }, + "style": { + "width": 300 + }, + "data": { + "id": "model-data", + "kind": "message", + "label": "ModelData", + "sourceSymbol": "gnmi.ModelData", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L464", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#261-the-modeldata-message", + "fields": [ + { + "id": "name", + "type": "string", + "name": "name", + "ref": null + }, + { + "id": "organization", + "type": "string", + "name": "organization", + "ref": null + }, + { + "id": "version", + "type": "string", + "name": "version", + "ref": null + } + ] + } + }, + { + "id": "encoding", + "type": "schema", + "position": { + "x": 2380, + "y": 1120 + }, + "style": { + "width": 260 + }, + "data": { + "id": "encoding", + "kind": "enum", + "label": "enum Encoding", + "sourceSymbol": "gnmi.Encoding", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L175", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#23-structured-data-types", + "fields": [ + { + "id": "json", + "type": "0", + "name": "JSON", + "ref": null + }, + { + "id": "bytes", + "type": "1", + "name": "BYTES", + "ref": null + }, + { + "id": "proto", + "type": "2", + "name": "PROTO", + "ref": null + }, + { + "id": "ascii", + "type": "3", + "name": "ASCII", + "ref": null + }, + { + "id": "json-ietf", + "type": "4", + "name": "JSON_IETF", + "ref": null + } + ] + } + }, + { + "id": "notification", + "type": "schema", + "position": { + "x": 1740, + "y": 1160 + }, + "style": { + "width": 350 + }, + "data": { + "id": "notification", + "kind": "message", + "label": "Notification", + "sourceSymbol": "gnmi.Notification", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L84", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#21-reusable-notification-message-format", + "fields": [ + { + "id": "timestamp", + "type": "int64", + "name": "timestamp", + "ref": null + }, + { + "id": "prefix", + "type": "Path", + "name": "prefix", + "ref": "path" + }, + { + "id": "update", + "type": "repeated Update", + "name": "update", + "ref": "update" + }, + { + "id": "delete", + "type": "repeated Path", + "name": "delete", + "ref": "path" + }, + { + "id": "atomic", + "type": "bool", + "name": "atomic", + "ref": null + }, + { + "id": "reserved-alias", + "type": "reserved", + "name": "alias / 3", + "ref": null, + "badge": "reserved" + } + ] + } + }, + { + "id": "update", + "type": "schema", + "position": { + "x": 1320, + "y": 1460 + }, + "style": { + "width": 330 + }, + "data": { + "id": "update", + "kind": "message", + "label": "Update", + "sourceSymbol": "gnmi.Update", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L100", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#21-reusable-notification-message-format", + "fields": [ + { + "id": "path", + "type": "Path", + "name": "path", + "ref": "path" + }, + { + "id": "value", + "type": "Value", + "name": "value", + "ref": "value", + "badge": "deprecated", + "deprecated": true + }, + { + "id": "val", + "type": "TypedValue", + "name": "val", + "ref": "typed-value" + }, + { + "id": "duplicates", + "type": "uint32", + "name": "duplicates", + "ref": null + } + ] + } + }, + { + "id": "path", + "type": "schema", + "position": { + "x": 1780, + "y": 1530 + }, + "style": { + "width": 360 + }, + "data": { + "id": "path", + "kind": "message", + "label": "Path", + "sourceSymbol": "gnmi.Path", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L142", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#222-paths", + "fields": [ + { + "id": "element", + "type": "repeated string", + "name": "element", + "ref": null, + "badge": "deprecated", + "deprecated": true + }, + { + "id": "origin", + "type": "string", + "name": "origin", + "ref": null + }, + { + "id": "elem", + "type": "repeated PathElem", + "name": "elem", + "ref": "path-elem" + }, + { + "id": "target", + "type": "string", + "name": "target", + "ref": null + } + ] + } + }, + { + "id": "path-elem", + "type": "schema", + "position": { + "x": 2210, + "y": 1600 + }, + "style": { + "width": 330 + }, + "data": { + "id": "path-elem", + "kind": "message", + "label": "PathElem", + "sourceSymbol": "gnmi.PathElem", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L155", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#222-paths", + "fields": [ + { + "id": "name", + "type": "string", + "name": "name", + "ref": null + }, + { + "id": "key", + "type": "map", + "name": "key", + "ref": null + } + ] + } + }, + { + "id": "value", + "type": "schema", + "position": { + "x": 700, + "y": 1570 + }, + "style": { + "width": 300 + }, + "data": { + "id": "value", + "kind": "message", + "label": "Value", + "sourceSymbol": "gnmi.Value", + "deprecated": true, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L163", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#223-node-values", + "badges": [ + "deprecated" + ], + "fields": [ + { + "id": "value", + "type": "bytes", + "name": "value", + "ref": null + }, + { + "id": "type", + "type": "Encoding", + "name": "type", + "ref": "encoding" + } + ] + } + }, + { + "id": "typed-value", + "type": "schema", + "position": { + "x": 980, + "y": 1780 + }, + "style": { + "width": 390 + }, + "data": { + "id": "typed-value", + "kind": "message", + "label": "TypedValue", + "sourceSymbol": "gnmi.TypedValue", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L109", + "fields": [ + { + "id": "string-val", + "type": "string", + "name": "string_val", + "ref": null, + "group": "oneof value" + }, + { + "id": "int-val", + "type": "int64", + "name": "int_val", + "ref": null, + "group": "oneof value" + }, + { + "id": "uint-val", + "type": "uint64", + "name": "uint_val", + "ref": null, + "group": "oneof value" + }, + { + "id": "bool-val", + "type": "bool", + "name": "bool_val", + "ref": null, + "group": "oneof value" + }, + { + "id": "bytes-val", + "type": "bytes", + "name": "bytes_val", + "ref": null, + "group": "oneof value" + }, + { + "id": "float-val", + "type": "float", + "name": "float_val", + "ref": null, + "group": "oneof value", + "badge": "deprecated", + "deprecated": true + }, + { + "id": "double-val", + "type": "double", + "name": "double_val", + "ref": null, + "group": "oneof value" + }, + { + "id": "decimal-val", + "type": "Decimal64", + "name": "decimal_val", + "ref": "decimal64", + "group": "oneof value", + "badge": "deprecated", + "deprecated": true + }, + { + "id": "leaflist-val", + "type": "ScalarArray", + "name": "leaflist_val", + "ref": "scalar-array", + "group": "oneof value" + }, + { + "id": "any-val", + "type": "google.protobuf.Any", + "name": "any_val", + "ref": "any", + "group": "oneof value" + }, + { + "id": "json-val", + "type": "bytes", + "name": "json_val", + "ref": null, + "group": "oneof value" + }, + { + "id": "json-ietf-val", + "type": "bytes", + "name": "json_ietf_val", + "ref": null, + "group": "oneof value" + }, + { + "id": "ascii-val", + "type": "string", + "name": "ascii_val", + "ref": null, + "group": "oneof value" + }, + { + "id": "proto-bytes", + "type": "bytes", + "name": "proto_bytes", + "ref": null, + "group": "oneof value" + } + ] + } + }, + { + "id": "decimal64", + "type": "schema", + "position": { + "x": 1430, + "y": 1780 + }, + "style": { + "width": 280 + }, + "data": { + "id": "decimal64", + "kind": "message", + "label": "Decimal64", + "sourceSymbol": "gnmi.Decimal64", + "deprecated": true, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L200", + "badges": [ + "deprecated" + ], + "fields": [ + { + "id": "digits", + "type": "int64", + "name": "digits", + "ref": null + }, + { + "id": "precision", + "type": "uint32", + "name": "precision", + "ref": null + } + ] + } + }, + { + "id": "scalar-array", + "type": "schema", + "position": { + "x": 1430, + "y": 1980 + }, + "style": { + "width": 330 + }, + "data": { + "id": "scalar-array", + "kind": "message", + "label": "ScalarArray", + "sourceSymbol": "gnmi.ScalarArray", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi/gnmi.proto#L208", + "fields": [ + { + "id": "element", + "type": "repeated TypedValue", + "name": "element", + "ref": "typed-value" + } + ] + } + }, + { + "id": "any", + "type": "schema", + "position": { + "x": 550, + "y": 1870 + }, + "style": { + "width": 330 + }, + "data": { + "id": "any", + "kind": "external", + "label": "google.protobuf.Any", + "protoUrl": "https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto", + "fields": [ + { + "id": "type-url", + "type": "string", + "name": "type_url" + }, + { + "id": "value", + "type": "bytes", + "name": "value" + } + ] + } + }, + { + "id": "extension", + "type": "schema", + "position": { + "x": 2020, + "y": 820 + }, + "style": { + "width": 360 + }, + "data": { + "id": "extension", + "kind": "message", + "label": "gnmi_ext.Extension", + "sourceSymbol": "gnmi_ext.Extension", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L29", + "specUrl": "https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md#27-extensions-to-gnmi", + "fields": [ + { + "id": "registered-ext", + "type": "RegisteredExtension", + "name": "registered_ext", + "ref": "registered-extension", + "group": "oneof ext" + }, + { + "id": "master-arbitration", + "type": "MasterArbitration", + "name": "master_arbitration", + "ref": "master-arbitration", + "group": "oneof ext" + }, + { + "id": "history", + "type": "History", + "name": "history", + "ref": "history", + "group": "oneof ext" + }, + { + "id": "commit", + "type": "Commit", + "name": "commit", + "ref": "commit", + "group": "oneof ext" + }, + { + "id": "depth", + "type": "Depth", + "name": "depth", + "ref": "depth", + "group": "oneof ext" + }, + { + "id": "config-subscription", + "type": "ConfigSubscription", + "name": "config_subscription", + "ref": "config-subscription", + "group": "oneof ext" + } + ] + } + }, + { + "id": "registered-extension", + "type": "schema", + "position": { + "x": 2510, + "y": 760 + }, + "style": { + "width": 390 + }, + "data": { + "id": "registered-extension", + "kind": "message", + "label": "gnmi_ext.RegisteredExtension", + "sourceSymbol": "gnmi_ext.RegisteredExtension", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L43", + "fields": [ + { + "id": "id", + "type": "ExtensionID", + "name": "id", + "ref": "extension-id" + }, + { + "id": "msg", + "type": "bytes", + "name": "msg", + "ref": null + } + ] + } + }, + { + "id": "extension-id", + "type": "schema", + "position": { + "x": 2940, + "y": 700 + }, + "style": { + "width": 310 + }, + "data": { + "id": "extension-id", + "kind": "enum", + "label": "enum gnmi_ext.ExtensionID", + "sourceSymbol": "gnmi_ext.ExtensionID", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L50", + "fields": [ + { + "id": "unset", + "type": "0", + "name": "EID_UNSET", + "ref": null + }, + { + "id": "experimental", + "type": "999", + "name": "EID_EXPERIMENTAL", + "ref": null + } + ] + } + }, + { + "id": "master-arbitration", + "type": "schema", + "position": { + "x": 2510, + "y": 1010 + }, + "style": { + "width": 390 + }, + "data": { + "id": "master-arbitration", + "kind": "message", + "label": "gnmi_ext.MasterArbitration", + "sourceSymbol": "gnmi_ext.MasterArbitration", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L65", + "fields": [ + { + "id": "role", + "type": "Role", + "name": "role", + "ref": "role" + }, + { + "id": "election-id", + "type": "Uint128", + "name": "election_id", + "ref": "uint128" + } + ] + } + }, + { + "id": "uint128", + "type": "schema", + "position": { + "x": 2940, + "y": 1040 + }, + "style": { + "width": 260 + }, + "data": { + "id": "uint128", + "kind": "message", + "label": "gnmi_ext.Uint128", + "sourceSymbol": "gnmi_ext.Uint128", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L71", + "fields": [ + { + "id": "high", + "type": "uint64", + "name": "high", + "ref": null + }, + { + "id": "low", + "type": "uint64", + "name": "low", + "ref": null + } + ] + } + }, + { + "id": "role", + "type": "schema", + "position": { + "x": 2940, + "y": 1210 + }, + "style": { + "width": 260 + }, + "data": { + "id": "role", + "kind": "message", + "label": "gnmi_ext.Role", + "sourceSymbol": "gnmi_ext.Role", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L77", + "fields": [ + { + "id": "id", + "type": "string", + "name": "id", + "ref": null + } + ] + } + }, + { + "id": "history", + "type": "schema", + "position": { + "x": 2510, + "y": 1260 + }, + "style": { + "width": 330 + }, + "data": { + "id": "history", + "kind": "message", + "label": "gnmi_ext.History", + "sourceSymbol": "gnmi_ext.History", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L86", + "fields": [ + { + "id": "snapshot-time", + "type": "int64", + "name": "snapshot_time", + "ref": null, + "group": "oneof request" + }, + { + "id": "range", + "type": "TimeRange", + "name": "range", + "ref": "time-range", + "group": "oneof request" + } + ] + } + }, + { + "id": "time-range", + "type": "schema", + "position": { + "x": 2940, + "y": 1390 + }, + "style": { + "width": 270 + }, + "data": { + "id": "time-range", + "kind": "message", + "label": "gnmi_ext.TimeRange", + "sourceSymbol": "gnmi_ext.TimeRange", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L93", + "fields": [ + { + "id": "start", + "type": "int64", + "name": "start", + "ref": null + }, + { + "id": "end", + "type": "int64", + "name": "end", + "ref": null + } + ] + } + }, + { + "id": "commit", + "type": "schema", + "position": { + "x": 2510, + "y": 1500 + }, + "style": { + "width": 390 + }, + "data": { + "id": "commit", + "kind": "message", + "label": "gnmi_ext.Commit", + "sourceSymbol": "gnmi_ext.Commit", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L104", + "fields": [ + { + "id": "id", + "type": "string", + "name": "id", + "ref": null + }, + { + "id": "commit", + "type": "CommitRequest", + "name": "commit", + "ref": "commit-request", + "group": "oneof action" + }, + { + "id": "confirm", + "type": "CommitConfirm", + "name": "confirm", + "ref": "commit-confirm", + "group": "oneof action" + }, + { + "id": "cancel", + "type": "CommitCancel", + "name": "cancel", + "ref": "commit-cancel", + "group": "oneof action" + }, + { + "id": "set-rollback-duration", + "type": "CommitSetRollbackDuration", + "name": "set_rollback_duration", + "ref": "commit-set-rollback-duration", + "group": "oneof action" + } + ] + } + }, + { + "id": "commit-request", + "type": "schema", + "position": { + "x": 2960, + "y": 1580 + }, + "style": { + "width": 360 + }, + "data": { + "id": "commit-request", + "kind": "message", + "label": "gnmi_ext.CommitRequest", + "sourceSymbol": "gnmi_ext.CommitRequest", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L129", + "fields": [ + { + "id": "rollback-duration", + "type": "google.protobuf.Duration", + "name": "rollback_duration", + "ref": "duration" + } + ] + } + }, + { + "id": "commit-confirm", + "type": "schema", + "position": { + "x": 2960, + "y": 1760 + }, + "style": { + "width": 300 + }, + "data": { + "id": "commit-confirm", + "kind": "message", + "label": "gnmi_ext.CommitConfirm", + "sourceSymbol": "gnmi_ext.CommitConfirm", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L136", + "fields": [] + } + }, + { + "id": "commit-cancel", + "type": "schema", + "position": { + "x": 2960, + "y": 1900 + }, + "style": { + "width": 300 + }, + "data": { + "id": "commit-cancel", + "kind": "message", + "label": "gnmi_ext.CommitCancel", + "sourceSymbol": "gnmi_ext.CommitCancel", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L140", + "fields": [] + } + }, + { + "id": "commit-set-rollback-duration", + "type": "schema", + "position": { + "x": 2960, + "y": 2040 + }, + "style": { + "width": 400 + }, + "data": { + "id": "commit-set-rollback-duration", + "kind": "message", + "label": "gnmi_ext.CommitSetRollbackDuration", + "sourceSymbol": "gnmi_ext.CommitSetRollbackDuration", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L144", + "fields": [ + { + "id": "rollback-duration", + "type": "google.protobuf.Duration", + "name": "rollback_duration", + "ref": "duration" + } + ] + } + }, + { + "id": "duration", + "type": "schema", + "position": { + "x": 3400, + "y": 1740 + }, + "style": { + "width": 300 + }, + "data": { + "id": "duration", + "kind": "external", + "label": "google.protobuf.Duration", + "protoUrl": "https://github.com/protocolbuffers/protobuf/blob/main/src/google/protobuf/duration.proto", + "fields": [ + { + "id": "seconds", + "type": "int64", + "name": "seconds" + }, + { + "id": "nanos", + "type": "int32", + "name": "nanos" + } + ] + } + }, + { + "id": "depth", + "type": "schema", + "position": { + "x": 3350, + "y": 850 + }, + "style": { + "width": 260 + }, + "data": { + "id": "depth", + "kind": "message", + "label": "gnmi_ext.Depth", + "sourceSymbol": "gnmi_ext.Depth", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L155", + "fields": [ + { + "id": "level", + "type": "uint32", + "name": "level", + "ref": null + } + ] + } + }, + { + "id": "config-subscription", + "type": "schema", + "position": { + "x": 3350, + "y": 1030 + }, + "style": { + "width": 400 + }, + "data": { + "id": "config-subscription", + "kind": "message", + "label": "gnmi_ext.ConfigSubscription", + "sourceSymbol": "gnmi_ext.ConfigSubscription", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L166", + "fields": [ + { + "id": "start", + "type": "ConfigSubscriptionStart", + "name": "start", + "ref": "config-subscription-start", + "group": "oneof action" + }, + { + "id": "sync-done", + "type": "ConfigSubscriptionSyncDone", + "name": "sync_done", + "ref": "config-subscription-sync-done", + "group": "oneof action" + } + ] + } + }, + { + "id": "config-subscription-start", + "type": "schema", + "position": { + "x": 3810, + "y": 980 + }, + "style": { + "width": 360 + }, + "data": { + "id": "config-subscription-start", + "kind": "message", + "label": "gnmi_ext.ConfigSubscriptionStart", + "sourceSymbol": "gnmi_ext.ConfigSubscriptionStart", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L178", + "fields": [] + } + }, + { + "id": "config-subscription-sync-done", + "type": "schema", + "position": { + "x": 3810, + "y": 1130 + }, + "style": { + "width": 400 + }, + "data": { + "id": "config-subscription-sync-done", + "kind": "message", + "label": "gnmi_ext.ConfigSubscriptionSyncDone", + "sourceSymbol": "gnmi_ext.ConfigSubscriptionSyncDone", + "deprecated": false, + "protoUrl": "https://github.com/openconfig/gnmi/blob/v0.14.1/proto/gnmi_ext/gnmi_ext.proto#L182", + "fields": [ + { + "id": "commit-confirm-id", + "type": "string", + "name": "commit_confirm_id", + "ref": null + }, + { + "id": "server-commit-id", + "type": "string", + "name": "server_commit_id", + "ref": null + }, + { + "id": "done", + "type": "bool", + "name": "done", + "ref": null + } + ] + } + }, + { + "id": "legend", + "type": "schema", + "position": { + "x": 2720, + "y": 1470 + }, + "style": { + "width": 410 + }, + "data": { + "id": "legend", + "kind": "legend", + "label": "Legend", + "fields": [ + { + "id": "proto", + "type": "file icon", + "name": "proto definition link" + }, + { + "id": "docs", + "type": "book icon", + "name": "documentation link" + }, + { + "id": "extension-note", + "type": "toggle", + "name": "extension relationship edges" + }, + { + "id": "pdf", + "type": "reference", + "name": "gnmi_0.7.0_map.pdf" + } + ] + } + } ]; export const mapEdges = [ - e('service-gnmi', 'set', 'rpc-set', 'rpc'), - e('service-gnmi', 'subscribe', 'rpc-subscribe', 'rpc'), - e('service-gnmi', 'get', 'rpc-get', 'rpc'), - e('service-gnmi', 'capabilities', 'rpc-capabilities', 'rpc'), - - e('rpc-set', 'takes', 'set-request', 'rpc'), - e('rpc-set', 'returns', 'set-response', 'rpc'), - e('rpc-subscribe', 'takes', 'subscribe-request', 'rpc'), - e('rpc-subscribe', 'returns', 'subscribe-response', 'rpc'), - e('rpc-get', 'takes', 'get-request', 'rpc'), - e('rpc-get', 'returns', 'get-response', 'rpc'), - e('rpc-capabilities', 'takes', 'capability-request', 'rpc'), - e('rpc-capabilities', 'returns', 'capability-response', 'rpc'), - - e('set-request', 'prefix', 'path'), - e('set-request', 'delete', 'path'), - e('set-request', 'replace', 'update'), - e('set-request', 'update', 'update'), - e('set-request', 'extension', 'extension', 'extension'), - e('set-response', 'prefix', 'path'), - e('set-response', 'response', 'update-result'), - e('set-response', 'message', 'error'), - e('set-response', 'extension', 'extension', 'extension'), - e('update-result', 'path', 'path'), - e('update-result', 'message', 'error'), - e('update-result', 'op', 'operation'), - - e('subscribe-request', 'subscribe', 'subscription-list'), - e('subscribe-request', 'poll', 'poll'), - e('subscribe-request', 'aliases', 'alias-list'), - e('subscribe-request', 'extension', 'extension', 'extension'), - e('subscribe-response', 'update', 'notification'), - e('subscribe-response', 'error', 'error'), - e('subscribe-response', 'extension', 'extension', 'extension'), - e('alias-list', 'alias', 'alias'), - e('alias', 'path', 'path'), - e('subscription-list', 'prefix', 'path'), - e('subscription-list', 'subscription', 'subscription'), - e('subscription-list', 'qos', 'qos-marking'), - e('subscription-list', 'mode', 'mode'), - e('subscription-list', 'use-models', 'model-data'), - e('subscription-list', 'encoding', 'encoding'), - e('subscription', 'path', 'path'), - e('subscription', 'mode', 'subscription-mode'), - - e('get-request', 'prefix', 'path'), - e('get-request', 'path', 'path'), - e('get-request', 'type', 'data-type'), - e('get-request', 'encoding', 'encoding'), - e('get-request', 'use-models', 'model-data'), - e('get-request', 'extension', 'extension', 'extension'), - e('get-response', 'notification', 'notification'), - e('get-response', 'error', 'error'), - e('get-response', 'extension', 'extension', 'extension'), - e('capability-request', 'extension', 'extension', 'extension'), - e('capability-response', 'supported-models', 'model-data'), - e('capability-response', 'supported-encodings', 'encoding'), - e('capability-response', 'extension', 'extension', 'extension'), - - e('notification', 'prefix', 'path'), - e('notification', 'update', 'update'), - e('notification', 'delete', 'path'), - e('update', 'path', 'path'), - e('update', 'value', 'value'), - e('update', 'val', 'typed-value'), - e('path', 'elem', 'path-elem'), - e('value', 'type', 'encoding'), - e('typed-value', 'decimal-val', 'decimal64'), - e('typed-value', 'leaflist-val', 'scalar-array'), - e('typed-value', 'any-val', 'any'), - e('scalar-array', 'element', 'typed-value'), - e('error', 'data', 'any'), - - e('extension', 'registered-ext', 'registered-extension', 'extension-detail'), - e('extension', 'master-arbitration', 'master-arbitration', 'extension-detail'), - e('registered-extension', 'id', 'extension-id', 'extension-detail'), - e('master-arbitration', 'role', 'role', 'extension-detail'), - e('master-arbitration', 'election-id', 'uint128', 'extension-detail'), + { + "id": "service-gnmi:capabilities->rpc-capabilities", + "source": "service-gnmi", + "sourceHandle": "capabilities", + "target": "rpc-capabilities", + "kind": "rpc", + "deprecated": false + }, + { + "id": "service-gnmi:get->rpc-get", + "source": "service-gnmi", + "sourceHandle": "get", + "target": "rpc-get", + "kind": "rpc", + "deprecated": false + }, + { + "id": "service-gnmi:set->rpc-set", + "source": "service-gnmi", + "sourceHandle": "set", + "target": "rpc-set", + "kind": "rpc", + "deprecated": false + }, + { + "id": "service-gnmi:subscribe->rpc-subscribe", + "source": "service-gnmi", + "sourceHandle": "subscribe", + "target": "rpc-subscribe", + "kind": "rpc", + "deprecated": false + }, + { + "id": "rpc-set:takes->set-request", + "source": "rpc-set", + "sourceHandle": "takes", + "target": "set-request", + "kind": "field", + "deprecated": false + }, + { + "id": "rpc-set:returns->set-response", + "source": "rpc-set", + "sourceHandle": "returns", + "target": "set-response", + "kind": "field", + "deprecated": false + }, + { + "id": "rpc-subscribe:takes->subscribe-request", + "source": "rpc-subscribe", + "sourceHandle": "takes", + "target": "subscribe-request", + "kind": "field", + "deprecated": false + }, + { + "id": "rpc-subscribe:returns->subscribe-response", + "source": "rpc-subscribe", + "sourceHandle": "returns", + "target": "subscribe-response", + "kind": "field", + "deprecated": false + }, + { + "id": "rpc-get:takes->get-request", + "source": "rpc-get", + "sourceHandle": "takes", + "target": "get-request", + "kind": "field", + "deprecated": false + }, + { + "id": "rpc-get:returns->get-response", + "source": "rpc-get", + "sourceHandle": "returns", + "target": "get-response", + "kind": "field", + "deprecated": false + }, + { + "id": "rpc-capabilities:takes->capability-request", + "source": "rpc-capabilities", + "sourceHandle": "takes", + "target": "capability-request", + "kind": "field", + "deprecated": false + }, + { + "id": "rpc-capabilities:returns->capability-response", + "source": "rpc-capabilities", + "sourceHandle": "returns", + "target": "capability-response", + "kind": "field", + "deprecated": false + }, + { + "id": "set-request:prefix->path", + "source": "set-request", + "sourceHandle": "prefix", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "set-request:delete->path", + "source": "set-request", + "sourceHandle": "delete", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "set-request:replace->update", + "source": "set-request", + "sourceHandle": "replace", + "target": "update", + "kind": "field", + "deprecated": false + }, + { + "id": "set-request:update->update", + "source": "set-request", + "sourceHandle": "update", + "target": "update", + "kind": "field", + "deprecated": false + }, + { + "id": "set-request:union-replace->update", + "source": "set-request", + "sourceHandle": "union-replace", + "target": "update", + "kind": "field", + "deprecated": false + }, + { + "id": "set-request:extension->extension", + "source": "set-request", + "sourceHandle": "extension", + "target": "extension", + "kind": "extension", + "deprecated": false + }, + { + "id": "set-response:prefix->path", + "source": "set-response", + "sourceHandle": "prefix", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "set-response:response->update-result", + "source": "set-response", + "sourceHandle": "response", + "target": "update-result", + "kind": "field", + "deprecated": false + }, + { + "id": "set-response:message->error", + "source": "set-response", + "sourceHandle": "message", + "target": "error", + "kind": "field", + "deprecated": true + }, + { + "id": "set-response:extension->extension", + "source": "set-response", + "sourceHandle": "extension", + "target": "extension", + "kind": "extension", + "deprecated": false + }, + { + "id": "subscribe-request:subscribe->subscription-list", + "source": "subscribe-request", + "sourceHandle": "subscribe", + "target": "subscription-list", + "kind": "field", + "deprecated": false + }, + { + "id": "subscribe-request:poll->poll", + "source": "subscribe-request", + "sourceHandle": "poll", + "target": "poll", + "kind": "field", + "deprecated": false + }, + { + "id": "subscribe-request:extension->extension", + "source": "subscribe-request", + "sourceHandle": "extension", + "target": "extension", + "kind": "extension", + "deprecated": false + }, + { + "id": "subscribe-response:update->notification", + "source": "subscribe-response", + "sourceHandle": "update", + "target": "notification", + "kind": "field", + "deprecated": false + }, + { + "id": "subscribe-response:error->error", + "source": "subscribe-response", + "sourceHandle": "error", + "target": "error", + "kind": "field", + "deprecated": true + }, + { + "id": "subscribe-response:extension->extension", + "source": "subscribe-response", + "sourceHandle": "extension", + "target": "extension", + "kind": "extension", + "deprecated": false + }, + { + "id": "get-request:prefix->path", + "source": "get-request", + "sourceHandle": "prefix", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "get-request:path->path", + "source": "get-request", + "sourceHandle": "path", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "get-request:type->data-type", + "source": "get-request", + "sourceHandle": "type", + "target": "data-type", + "kind": "field", + "deprecated": false + }, + { + "id": "get-request:encoding->encoding", + "source": "get-request", + "sourceHandle": "encoding", + "target": "encoding", + "kind": "field", + "deprecated": false + }, + { + "id": "get-request:use-models->model-data", + "source": "get-request", + "sourceHandle": "use-models", + "target": "model-data", + "kind": "field", + "deprecated": false + }, + { + "id": "get-request:extension->extension", + "source": "get-request", + "sourceHandle": "extension", + "target": "extension", + "kind": "extension", + "deprecated": false + }, + { + "id": "get-response:notification->notification", + "source": "get-response", + "sourceHandle": "notification", + "target": "notification", + "kind": "field", + "deprecated": false + }, + { + "id": "get-response:error->error", + "source": "get-response", + "sourceHandle": "error", + "target": "error", + "kind": "field", + "deprecated": true + }, + { + "id": "get-response:extension->extension", + "source": "get-response", + "sourceHandle": "extension", + "target": "extension", + "kind": "extension", + "deprecated": false + }, + { + "id": "capability-request:extension->extension", + "source": "capability-request", + "sourceHandle": "extension", + "target": "extension", + "kind": "extension", + "deprecated": false + }, + { + "id": "capability-response:supported-models->model-data", + "source": "capability-response", + "sourceHandle": "supported-models", + "target": "model-data", + "kind": "field", + "deprecated": false + }, + { + "id": "capability-response:supported-encodings->encoding", + "source": "capability-response", + "sourceHandle": "supported-encodings", + "target": "encoding", + "kind": "field", + "deprecated": false + }, + { + "id": "capability-response:extension->extension", + "source": "capability-response", + "sourceHandle": "extension", + "target": "extension", + "kind": "extension", + "deprecated": false + }, + { + "id": "error:data->any", + "source": "error", + "sourceHandle": "data", + "target": "any", + "kind": "field", + "deprecated": false + }, + { + "id": "update-result:path->path", + "source": "update-result", + "sourceHandle": "path", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "update-result:message->error", + "source": "update-result", + "sourceHandle": "message", + "target": "error", + "kind": "field", + "deprecated": true + }, + { + "id": "update-result:op->operation", + "source": "update-result", + "sourceHandle": "op", + "target": "operation", + "kind": "field", + "deprecated": false + }, + { + "id": "subscription-list:prefix->path", + "source": "subscription-list", + "sourceHandle": "prefix", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "subscription-list:subscription->subscription", + "source": "subscription-list", + "sourceHandle": "subscription", + "target": "subscription", + "kind": "field", + "deprecated": false + }, + { + "id": "subscription-list:qos->qos-marking", + "source": "subscription-list", + "sourceHandle": "qos", + "target": "qos-marking", + "kind": "field", + "deprecated": false + }, + { + "id": "subscription-list:mode->mode", + "source": "subscription-list", + "sourceHandle": "mode", + "target": "mode", + "kind": "field", + "deprecated": false + }, + { + "id": "subscription-list:use-models->model-data", + "source": "subscription-list", + "sourceHandle": "use-models", + "target": "model-data", + "kind": "field", + "deprecated": false + }, + { + "id": "subscription-list:encoding->encoding", + "source": "subscription-list", + "sourceHandle": "encoding", + "target": "encoding", + "kind": "field", + "deprecated": false + }, + { + "id": "subscription:path->path", + "source": "subscription", + "sourceHandle": "path", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "subscription:mode->subscription-mode", + "source": "subscription", + "sourceHandle": "mode", + "target": "subscription-mode", + "kind": "field", + "deprecated": false + }, + { + "id": "notification:prefix->path", + "source": "notification", + "sourceHandle": "prefix", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "notification:update->update", + "source": "notification", + "sourceHandle": "update", + "target": "update", + "kind": "field", + "deprecated": false + }, + { + "id": "notification:delete->path", + "source": "notification", + "sourceHandle": "delete", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "update:path->path", + "source": "update", + "sourceHandle": "path", + "target": "path", + "kind": "field", + "deprecated": false + }, + { + "id": "update:value->value", + "source": "update", + "sourceHandle": "value", + "target": "value", + "kind": "field", + "deprecated": true + }, + { + "id": "update:val->typed-value", + "source": "update", + "sourceHandle": "val", + "target": "typed-value", + "kind": "field", + "deprecated": false + }, + { + "id": "path:elem->path-elem", + "source": "path", + "sourceHandle": "elem", + "target": "path-elem", + "kind": "field", + "deprecated": false + }, + { + "id": "value:type->encoding", + "source": "value", + "sourceHandle": "type", + "target": "encoding", + "kind": "field", + "deprecated": false + }, + { + "id": "typed-value:decimal-val->decimal64", + "source": "typed-value", + "sourceHandle": "decimal-val", + "target": "decimal64", + "kind": "field", + "deprecated": true + }, + { + "id": "typed-value:leaflist-val->scalar-array", + "source": "typed-value", + "sourceHandle": "leaflist-val", + "target": "scalar-array", + "kind": "field", + "deprecated": false + }, + { + "id": "typed-value:any-val->any", + "source": "typed-value", + "sourceHandle": "any-val", + "target": "any", + "kind": "field", + "deprecated": false + }, + { + "id": "scalar-array:element->typed-value", + "source": "scalar-array", + "sourceHandle": "element", + "target": "typed-value", + "kind": "field", + "deprecated": false + }, + { + "id": "extension:registered-ext->registered-extension", + "source": "extension", + "sourceHandle": "registered-ext", + "target": "registered-extension", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "extension:master-arbitration->master-arbitration", + "source": "extension", + "sourceHandle": "master-arbitration", + "target": "master-arbitration", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "extension:history->history", + "source": "extension", + "sourceHandle": "history", + "target": "history", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "extension:commit->commit", + "source": "extension", + "sourceHandle": "commit", + "target": "commit", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "extension:depth->depth", + "source": "extension", + "sourceHandle": "depth", + "target": "depth", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "extension:config-subscription->config-subscription", + "source": "extension", + "sourceHandle": "config-subscription", + "target": "config-subscription", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "registered-extension:id->extension-id", + "source": "registered-extension", + "sourceHandle": "id", + "target": "extension-id", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "master-arbitration:role->role", + "source": "master-arbitration", + "sourceHandle": "role", + "target": "role", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "master-arbitration:election-id->uint128", + "source": "master-arbitration", + "sourceHandle": "election-id", + "target": "uint128", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "history:range->time-range", + "source": "history", + "sourceHandle": "range", + "target": "time-range", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "commit:commit->commit-request", + "source": "commit", + "sourceHandle": "commit", + "target": "commit-request", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "commit:confirm->commit-confirm", + "source": "commit", + "sourceHandle": "confirm", + "target": "commit-confirm", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "commit:cancel->commit-cancel", + "source": "commit", + "sourceHandle": "cancel", + "target": "commit-cancel", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "commit:set-rollback-duration->commit-set-rollback-duration", + "source": "commit", + "sourceHandle": "set-rollback-duration", + "target": "commit-set-rollback-duration", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "commit-request:rollback-duration->duration", + "source": "commit-request", + "sourceHandle": "rollback-duration", + "target": "duration", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "commit-set-rollback-duration:rollback-duration->duration", + "source": "commit-set-rollback-duration", + "sourceHandle": "rollback-duration", + "target": "duration", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "config-subscription:start->config-subscription-start", + "source": "config-subscription", + "sourceHandle": "start", + "target": "config-subscription-start", + "kind": "extension-detail", + "deprecated": false + }, + { + "id": "config-subscription:sync-done->config-subscription-sync-done", + "source": "config-subscription", + "sourceHandle": "sync-done", + "target": "config-subscription-sync-done", + "kind": "extension-detail", + "deprecated": false + } ]; export const mapBounds = { - width: 3300, - height: 2220, + "width": 4260, + "height": 2250 }; + +export function getVisibleMap({ showDeprecated = false, showExtensions = true } = {}) { + const visibleNodes = mapNodes + .filter((node) => showDeprecated || !node.data.deprecated) + .map((node) => ({ + ...node, + data: { + ...node.data, + fields: (node.data.fields ?? []).filter((field) => showDeprecated || !field.deprecated), + }, + })); + const visibleNodeIds = new Set(visibleNodes.map((node) => node.id)); + const visibleHandles = new Set( + visibleNodes.flatMap((node) => + (node.data.fields ?? []).map((field) => `${node.id}:${field.id}`), + ), + ); + const visibleEdges = mapEdges.filter((edge) => { + if (!showExtensions && edge.kind === 'extension') { + return false; + } + if (!showDeprecated && edge.deprecated) { + return false; + } + return ( + visibleNodeIds.has(edge.source) && + visibleNodeIds.has(edge.target) && + visibleHandles.has(`${edge.source}:${edge.sourceHandle}`) + ); + }); + + return { nodes: visibleNodes, edges: visibleEdges }; +} diff --git a/src/styles.css b/src/styles.css index f471489..a138ca9 100644 --- a/src/styles.css +++ b/src/styles.css @@ -280,6 +280,11 @@ a { color: #744200; } +.badge-reserved { + background: #e7ebf0; + color: #4d5a6b; +} + .field-group { background: var(--blue-soft); color: #164675; From 17043b9ee510e898abba306d82c6315f3d194d0d Mon Sep 17 00:00:00 2001 From: Flosch62 Date: Wed, 27 May 2026 10:23:40 +0200 Subject: [PATCH 3/8] moved to typescript --- index.html | 2 +- package-lock.json | 586 ++++++++++++++++++ package.json | 15 +- ...rate-map-data.mjs => generate-map-data.ts} | 285 ++++++--- scripts/{generate-pdf.mjs => generate-pdf.ts} | 61 +- scripts/{validate-map.mjs => validate-map.ts} | 23 +- src/{App.jsx => App.tsx} | 62 +- src/{gnmiMap.js => gnmiMap.ts} | 77 ++- src/main.jsx | 10 - src/main.tsx | 16 + src/vite-env.d.ts | 2 + tsconfig.json | 21 + 12 files changed, 1027 insertions(+), 133 deletions(-) rename scripts/{generate-map-data.mjs => generate-map-data.ts} (59%) rename scripts/{generate-pdf.mjs => generate-pdf.ts} (87%) rename scripts/{validate-map.mjs => validate-map.ts} (80%) rename src/{App.jsx => App.tsx} (88%) rename src/{gnmiMap.js => gnmiMap.ts} (97%) delete mode 100644 src/main.jsx create mode 100644 src/main.tsx create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.json diff --git a/index.html b/index.html index 82ec82e..31dcb56 100644 --- a/index.html +++ b/index.html @@ -7,6 +7,6 @@
- + diff --git a/package-lock.json b/package-lock.json index 7519140..0a4bf0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,8 +15,14 @@ "react-dom": "^18.3.1" }, "devDependencies": { + "@types/node": "^25.9.1", + "@types/pdfkit": "^0.17.6", + "@types/react": "^18.3.29", + "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^4.3.4", "protobufjs": "^8.4.2", + "tsx": "^4.22.3", + "typescript": "^6.0.3", "vite": "^6.0.7" } }, @@ -1286,6 +1292,55 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz", + "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/@types/pdfkit": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.6.tgz", + "integrity": "sha512-tIwzxk2uWKp0Cq9JIluQXJid77lYhF52EsIOwhsMF4iWLA6YneoBR1xVKYYdAysHuepUB0OX4tdwMiUDdGKmig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.29", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz", + "integrity": "sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==", + "devOptional": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", @@ -1468,6 +1523,13 @@ "dev": true, "license": "MIT" }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -2092,6 +2154,530 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", + "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", + "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", + "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", + "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/android-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", + "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", + "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/darwin-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", + "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", + "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", + "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", + "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", + "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", + "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-loong64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", + "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", + "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", + "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", + "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-s390x": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", + "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/linux-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", + "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", + "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", + "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", + "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", + "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", + "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/sunos-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", + "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-arm64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", + "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-ia32": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", + "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/@esbuild/win32-x64": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", + "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsx/node_modules/esbuild": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", + "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.0", + "@esbuild/android-arm": "0.28.0", + "@esbuild/android-arm64": "0.28.0", + "@esbuild/android-x64": "0.28.0", + "@esbuild/darwin-arm64": "0.28.0", + "@esbuild/darwin-x64": "0.28.0", + "@esbuild/freebsd-arm64": "0.28.0", + "@esbuild/freebsd-x64": "0.28.0", + "@esbuild/linux-arm": "0.28.0", + "@esbuild/linux-arm64": "0.28.0", + "@esbuild/linux-ia32": "0.28.0", + "@esbuild/linux-loong64": "0.28.0", + "@esbuild/linux-mips64el": "0.28.0", + "@esbuild/linux-ppc64": "0.28.0", + "@esbuild/linux-riscv64": "0.28.0", + "@esbuild/linux-s390x": "0.28.0", + "@esbuild/linux-x64": "0.28.0", + "@esbuild/netbsd-arm64": "0.28.0", + "@esbuild/netbsd-x64": "0.28.0", + "@esbuild/openbsd-arm64": "0.28.0", + "@esbuild/openbsd-x64": "0.28.0", + "@esbuild/openharmony-arm64": "0.28.0", + "@esbuild/sunos-x64": "0.28.0", + "@esbuild/win32-arm64": "0.28.0", + "@esbuild/win32-ia32": "0.28.0", + "@esbuild/win32-x64": "0.28.0" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "dev": true, + "license": "MIT" + }, "node_modules/unicode-properties": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", diff --git a/package.json b/package.json index 1cebc7e..70ebfac 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,11 @@ "type": "module", "scripts": { "dev": "vite --host 0.0.0.0", - "build:map": "node scripts/generate-map-data.mjs", - "build": "vite build", - "build:pdf": "node scripts/generate-pdf.mjs", - "test:map": "node scripts/validate-map.mjs", + "build:map": "tsx scripts/generate-map-data.ts", + "build": "npm run typecheck && vite build", + "build:pdf": "tsx scripts/generate-pdf.ts", + "test:map": "tsx scripts/validate-map.ts", + "typecheck": "tsc --noEmit", "preview": "vite preview --host 0.0.0.0" }, "dependencies": { @@ -19,8 +20,14 @@ "react-dom": "^18.3.1" }, "devDependencies": { + "@types/node": "^25.9.1", + "@types/pdfkit": "^0.17.6", + "@types/react": "^18.3.29", + "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^4.3.4", "protobufjs": "^8.4.2", + "tsx": "^4.22.3", + "typescript": "^6.0.3", "vite": "^6.0.7" } } diff --git a/scripts/generate-map-data.mjs b/scripts/generate-map-data.ts similarity index 59% rename from scripts/generate-map-data.mjs rename to scripts/generate-map-data.ts index 502968b..e120020 100644 --- a/scripts/generate-map-data.mjs +++ b/scripts/generate-map-data.ts @@ -1,16 +1,55 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import protobuf from 'protobufjs'; -import { mapBounds, mapNodes as layoutNodes } from '../src/gnmiMap.js'; +import { + mapBounds, + mapNodes as layoutNodes, + type MapBounds, + type MapBadge, + type MapEdge, + type MapEdgeKind, + type MapField, + type MapNode, + type MapSource, +} from '../src/gnmiMap'; const GNMI_TAGS_API = 'https://api.github.com/repos/openconfig/gnmi/tags?per_page=30'; const GNMI_GITHUB_BASE = 'https://github.com/openconfig/gnmi/blob'; const GNMI_RAW_BASE = 'https://raw.githubusercontent.com/openconfig/gnmi'; const SPECBASE = 'https://github.com/openconfig/reference/blob/master/rpc/gnmi/gnmi-specification.md'; -const OUTPUT_PATH = path.resolve('src/gnmiMap.js'); - -const SCALAR_TYPES = new Set([ +const OUTPUT_PATH = path.resolve('src/gnmiMap.ts'); + +type Definition = protobuf.Type | protobuf.Enum; +type Definitions = Map; +type ByShortName = Map; +type DefinitionLineStackEntry = { + name: string; + depth: number; +}; +type ProtoNamespace = protobuf.ReflectionObject & { + nested?: Record; +}; +type ProtoField = protobuf.Field & { + keyType?: string; +}; +type SourceKind = 'gnmi' | 'gnmi_ext'; +type LinesBySource = Record>; +type SymbolMaps = { + nodeIdToSymbol: Map; + symbolToNodeId: Map; +}; +type GitHubTag = { + name: string; +}; +type GeneratedSourceInput = { + nodes: MapNode[]; + edges: MapEdge[]; + bounds: MapBounds; + source: MapSource; +}; + +const SCALAR_TYPES = new Set([ 'bool', 'bytes', 'double', @@ -28,22 +67,22 @@ const SCALAR_TYPES = new Set([ 'uint64', ]); -const EXTERNAL_REFS = new Map([ +const EXTERNAL_REFS = new Map([ ['google.protobuf.Any', 'any'], ['google.protobuf.Duration', 'duration'], ]); -async function fetchJson(url) { +async function fetchJson(url: string): Promise { const response = await fetch(url, { headers: { Accept: 'application/vnd.github+json' }, }); if (!response.ok) { throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`); } - return response.json(); + return response.json() as Promise; } -async function fetchText(url) { +async function fetchText(url: string): Promise { const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to fetch ${url}: ${response.status} ${response.statusText}`); @@ -51,13 +90,13 @@ async function fetchText(url) { return response.text(); } -function stripLineComment(line) { +function stripLineComment(line: string): string { return line.replace(/\/\/.*$/, ''); } -function definitionLines(protoText) { - const lines = new Map(); - const stack = []; +function definitionLines(protoText: string): Map { + const lines = new Map(); + const stack: DefinitionLineStackEntry[] = []; let packageName = ''; let depth = 0; @@ -92,8 +131,8 @@ function definitionLines(protoText) { return lines; } -function methodLines(protoText) { - const lines = new Map(); +function methodLines(protoText: string): Map { + const lines = new Map(); protoText.split('\n').forEach((line, index) => { const match = stripLineComment(line).match(/^\s*rpc\s+([A-Za-z_][A-Za-z0-9_]*)\b/); if (match) { @@ -103,7 +142,7 @@ function methodLines(protoText) { return lines; } -function kebab(value) { +function kebab(value: string): string { return value .replace(/([a-z0-9])([A-Z])/g, '$1-$2') .replace(/_/g, '-') @@ -111,14 +150,14 @@ function kebab(value) { .toLowerCase(); } -function titleFromNode(node) { +function titleFromNode(node: MapNode): string { return node.data.label.replace(/^enum\s+/, ''); } -function collectDefinitions(root) { - const definitions = new Map(); +function collectDefinitions(root: protobuf.Root): Definitions { + const definitions: Definitions = new Map(); - function visit(namespace) { + function visit(namespace: ProtoNamespace): void { if (!namespace.nested) { return; } @@ -127,7 +166,7 @@ function collectDefinitions(root) { if (item instanceof protobuf.Type || item instanceof protobuf.Enum) { definitions.set(item.fullName.replace(/^\./, ''), item); } - visit(item); + visit(item as ProtoNamespace); }); } @@ -135,16 +174,16 @@ function collectDefinitions(root) { return definitions; } -function groupByShortName(definitions) { - const byShort = new Map(); +function groupByShortName(definitions: Definitions): ByShortName { + const byShort: ByShortName = new Map(); definitions.forEach((definition, fullName) => { - const shortName = fullName.split('.').at(-1); + const shortName = fullName.split('.').at(-1) ?? fullName; byShort.set(shortName, [...(byShort.get(shortName) ?? []), fullName]); }); return byShort; } -function specUrlFromLayout(node) { +function specUrlFromLayout(node: MapNode): string | undefined { if (!node.data.specUrl) { return undefined; } @@ -152,7 +191,11 @@ function specUrlFromLayout(node) { return hash ? `${SPECBASE}#${hash}` : SPECBASE; } -function resolveSymbol(node, definitions, byShortName) { +function resolveSymbol( + node: MapNode, + definitions: Definitions, + byShortName: ByShortName, +): string | null { if (node.data.sourceSymbol) { return node.data.sourceSymbol; } @@ -184,23 +227,28 @@ function resolveSymbol(node, definitions, byShortName) { return null; } -function displayType(field) { - const base = field.map ? `map<${field.keyType},${field.type}>` : field.type; +function displayType(field: ProtoField): string { + const base = field.map ? `map<${field.keyType ?? 'string'},${field.type}>` : field.type; return field.repeated ? `repeated ${base}` : base; } -function resolveFieldRef(field, currentSymbol, symbolToNodeId, byShortName) { +function resolveFieldRef( + field: ProtoField, + currentSymbol: string, + symbolToNodeId: Map, + byShortName: ByShortName, +): string | null { if (field.map || SCALAR_TYPES.has(field.type)) { return null; } if (EXTERNAL_REFS.has(field.type)) { - return EXTERNAL_REFS.get(field.type); + return EXTERNAL_REFS.get(field.type) ?? null; } const currentParts = currentSymbol.split('.'); const packageName = currentParts[0]; - const candidates = []; + const candidates: string[] = []; if (field.type.includes('.')) { candidates.push(field.type); @@ -211,12 +259,12 @@ function resolveFieldRef(field, currentSymbol, symbolToNodeId, byShortName) { } const target = candidates.find((candidate) => symbolToNodeId.has(candidate)); - return target ? symbolToNodeId.get(target) : null; + return target ? (symbolToNodeId.get(target) ?? null) : null; } -function reservedFields(type) { - const numbers = []; - const names = []; +function reservedFields(type: protobuf.Type): MapField[] { + const numbers: string[] = []; + const names: string[] = []; for (const item of type.reserved ?? []) { if (Array.isArray(item)) { @@ -242,9 +290,14 @@ function reservedFields(type) { }); } -function fieldData(field, currentSymbol, symbolToNodeId, byShortName) { +function fieldData( + field: ProtoField, + currentSymbol: string, + symbolToNodeId: Map, + byShortName: ByShortName, +): MapField { const deprecated = Boolean(field.options?.deprecated); - const data = { + const data: MapField = { id: kebab(field.name), type: displayType(field), name: field.name, @@ -264,7 +317,7 @@ function fieldData(field, currentSymbol, symbolToNodeId, byShortName) { return data; } -function enumFields(enumDefinition) { +function enumFields(enumDefinition: protobuf.Enum): MapField[] { return Object.entries(enumDefinition.values).map(([name, value]) => ({ id: kebab(name.replace(/^EID_/, '')), type: `${value}`, @@ -273,7 +326,7 @@ function enumFields(enumDefinition) { })); } -function protoUrl(source, line, gnmiTag) { +function protoUrl(source: SourceKind, line: number | undefined, gnmiTag: string): string | undefined { if (!line) { return undefined; } @@ -283,9 +336,13 @@ function protoUrl(source, line, gnmiTag) { return `${GNMI_GITHUB_BASE}/${gnmiTag}/${file}#L${line}`; } -function buildSymbolMaps(nodes, definitions, byShortName) { - const symbolToNodeId = new Map(); - const nodeIdToSymbol = new Map(); +function buildSymbolMaps( + nodes: MapNode[], + definitions: Definitions, + byShortName: ByShortName, +): SymbolMaps { + const symbolToNodeId = new Map(); + const nodeIdToSymbol = new Map(); nodes.forEach((node) => { const symbol = resolveSymbol(node, definitions, byShortName); @@ -299,7 +356,13 @@ function buildSymbolMaps(nodes, definitions, byShortName) { return { nodeIdToSymbol, symbolToNodeId }; } -function serviceNode(node, service, serviceLine, serviceVersion, gnmiTag) { +function serviceNode( + node: MapNode, + service: protobuf.Service, + serviceLine: number | undefined, + serviceVersion: string, + gnmiTag: string, +): MapNode { const methods = service.methodsArray; return { ...node, @@ -315,14 +378,22 @@ function serviceNode(node, service, serviceLine, serviceVersion, gnmiTag) { type: 'rpc', name: method.name, ref: `rpc-${kebab(method.name)}`, - ...(method.requestStream || method.responseStream ? { badge: 'stream' } : {}), + ...(method.requestStream || method.responseStream ? { badge: 'stream' as const } : {}), })), }, }; } -function rpcNode(node, service, rpcLines, gnmiTag) { - const methodName = node.id.replace(/^rpc-/, '').replace(/(^|-)([a-z])/g, (_, __, letter) => +function rpcNode( + node: MapNode, + service: protobuf.Service, + rpcLines: Map, + gnmiTag: string, +): MapNode { + const methodName = node.id.replace( + /^rpc-/, + '', + ).replace(/(^|-)([a-z])/g, (_match: string, _separator: string, letter: string) => letter.toUpperCase(), ); const method = service.methods[methodName]; @@ -358,15 +429,15 @@ function rpcNode(node, service, rpcLines, gnmiTag) { } function schemaNode( - node, - definition, - symbol, - symbolToNodeId, - byShortName, - linesBySource, - gnmiTag, -) { - const source = symbol.startsWith('gnmi_ext.') ? 'gnmi_ext' : 'gnmi'; + node: MapNode, + definition: Definition, + symbol: string, + symbolToNodeId: Map, + byShortName: ByShortName, + linesBySource: LinesBySource, + gnmiTag: string, +): MapNode { + const source: SourceKind = symbol.startsWith('gnmi_ext.') ? 'gnmi_ext' : 'gnmi'; const fields = definition instanceof protobuf.Type ? [ @@ -378,7 +449,7 @@ function schemaNode( : enumFields(definition); const deprecated = Boolean(definition.options?.deprecated); const badges = deprecated - ? [...new Set([...(node.data.badges ?? []), 'deprecated'])] + ? [...new Set([...(node.data.badges ?? []), 'deprecated'])] : node.data.badges?.filter((badge) => badge !== 'deprecated'); return { @@ -398,7 +469,7 @@ function schemaNode( }; } -function edgeKind(sourceNode, field, targetNode) { +function edgeKind(sourceNode: MapNode, field: MapField, targetNode: MapNode): MapEdgeKind { if (field.type === 'rpc') { return 'rpc'; } @@ -414,9 +485,9 @@ function edgeKind(sourceNode, field, targetNode) { return 'field'; } -function buildEdges(nodes) { - const nodesById = new Map(nodes.map((node) => [node.id, node])); - const edges = []; +function buildEdges(nodes: MapNode[]): MapEdge[] { + const nodesById = new Map(nodes.map((node) => [node.id, node])); + const edges: MapEdge[] = []; for (const node of nodes) { for (const field of node.data.fields ?? []) { @@ -424,6 +495,9 @@ function buildEdges(nodes) { continue; } const targetNode = nodesById.get(field.ref); + if (!targetNode) { + continue; + } const kind = edgeKind(node, field, targetNode); edges.push({ id: `${node.id}:${field.id}->${field.ref}`, @@ -439,20 +513,82 @@ function buildEdges(nodes) { return edges; } -function generatedSource({ nodes, edges, bounds, source }) { - return `// Generated by scripts/generate-map-data.mjs. Do not edit by hand.\n\nexport const mapSource = ${JSON.stringify( +const GENERATED_TYPE_DEFINITIONS = `import type { Edge, Node } from '@xyflow/react'; + +export type MapNodeKind = 'service' | 'rpc' | 'message' | 'enum' | 'external' | 'legend'; +export type MapEdgeKind = 'rpc' | 'field' | 'extension' | 'extension-detail'; +export type MapBadge = 'stream' | 'optional' | 'deprecated' | 'reserved'; + +export type MapSource = { + gnmiTag: string; + gnmiServiceVersion: string; + gnmiBase: string; + extBase: string; + specBase: string; +}; + +export type MapBounds = { + width: number; + height: number; +}; + +export type MapField = { + id: string; + type: string; + name: string; + ref?: string | null; + group?: string; + badge?: MapBadge; + deprecated?: boolean; +}; + +export type MapNodeData = Record & { + id: string; + kind: MapNodeKind; + label: string; + sourceSymbol?: string; + deprecated?: boolean; + protoUrl?: string; + specUrl?: string; + fields?: MapField[]; + badges?: MapBadge[]; + active?: boolean; + query?: string; + showExtensions?: boolean; +}; + +export type MapNode = Node; + +export type MapEdge = Edge, 'smoothstep'> & { + sourceHandle: string; + kind: MapEdgeKind; + deprecated: boolean; +}; + +export type VisibleMapOptions = { + showDeprecated?: boolean; + showExtensions?: boolean; +}; + +export type VisibleMap = { + nodes: MapNode[]; + edges: MapEdge[]; +};`; + +function generatedSource({ nodes, edges, bounds, source }: GeneratedSourceInput): string { + return `// Generated by scripts/generate-map-data.ts. Do not edit by hand.\n\n${GENERATED_TYPE_DEFINITIONS}\n\nexport const mapSource: MapSource = ${JSON.stringify( source, null, 2, - )};\n\nexport const mapNodes = ${JSON.stringify(nodes, null, 2)};\n\nexport const mapEdges = ${JSON.stringify( + )};\n\nexport const mapNodes: MapNode[] = ${JSON.stringify(nodes, null, 2)};\n\nexport const mapEdges: MapEdge[] = ${JSON.stringify( edges, null, 2, - )};\n\nexport const mapBounds = ${JSON.stringify(bounds, null, 2)};\n\nexport function getVisibleMap({ showDeprecated = false, showExtensions = true } = {}) {\n const visibleNodes = mapNodes\n .filter((node) => showDeprecated || !node.data.deprecated)\n .map((node) => ({\n ...node,\n data: {\n ...node.data,\n fields: (node.data.fields ?? []).filter((field) => showDeprecated || !field.deprecated),\n },\n }));\n const visibleNodeIds = new Set(visibleNodes.map((node) => node.id));\n const visibleHandles = new Set(\n visibleNodes.flatMap((node) =>\n (node.data.fields ?? []).map((field) => \`\${node.id}:\${field.id}\`),\n ),\n );\n const visibleEdges = mapEdges.filter((edge) => {\n if (!showExtensions && edge.kind === 'extension') {\n return false;\n }\n if (!showDeprecated && edge.deprecated) {\n return false;\n }\n return (\n visibleNodeIds.has(edge.source) &&\n visibleNodeIds.has(edge.target) &&\n visibleHandles.has(\`\${edge.source}:\${edge.sourceHandle}\`)\n );\n });\n\n return { nodes: visibleNodes, edges: visibleEdges };\n}\n`; + )};\n\nexport const mapBounds: MapBounds = ${JSON.stringify(bounds, null, 2)};\n\nexport function getVisibleMap({\n showDeprecated = false,\n showExtensions = true,\n}: VisibleMapOptions = {}): VisibleMap {\n const visibleNodes = mapNodes\n .filter((node) => showDeprecated || !node.data.deprecated)\n .map((node) => ({\n ...node,\n data: {\n ...node.data,\n fields: (node.data.fields ?? []).filter((field) => showDeprecated || !field.deprecated),\n },\n }));\n const visibleNodeIds = new Set(visibleNodes.map((node) => node.id));\n const visibleHandles = new Set(\n visibleNodes.flatMap((node) =>\n (node.data.fields ?? []).map((field) => \`\${node.id}:\${field.id}\`),\n ),\n );\n const visibleEdges = mapEdges.filter((edge) => {\n if (!showExtensions && edge.kind === 'extension') {\n return false;\n }\n if (!showDeprecated && edge.deprecated) {\n return false;\n }\n return (\n visibleNodeIds.has(edge.source) &&\n visibleNodeIds.has(edge.target) &&\n visibleHandles.has(\`\${edge.source}:\${edge.sourceHandle}\`)\n );\n });\n\n return { nodes: visibleNodes, edges: visibleEdges };\n}\n`; } async function main() { - const tags = await fetchJson(GNMI_TAGS_API); + const tags = await fetchJson(GNMI_TAGS_API); const latestTag = tags.find((tag) => /^v\d+\.\d+\.\d+$/.test(tag.name)); if (!latestTag) { throw new Error('Could not resolve latest openconfig/gnmi tag'); @@ -475,14 +611,21 @@ async function main() { byShortName, ); const service = root.lookupService('gnmi.gNMI'); - const serviceVersion = root.nested.gnmi.options['(gnmi_service)']; - const linesBySource = { + const gnmiNamespace = root.lookup('gnmi'); + if (!gnmiNamespace) { + throw new Error('Could not resolve gNMI namespace'); + } + const serviceVersion = gnmiNamespace.options?.['(gnmi_service)']; + if (typeof serviceVersion !== 'string') { + throw new Error('Could not resolve gNMI service version'); + } + const linesBySource: LinesBySource = { gnmi: definitionLines(gnmiProto), gnmi_ext: definitionLines(extProto), }; const rpcLines = methodLines(gnmiProto); - const nodes = layoutNodes.map((node) => { + const nodes: MapNode[] = layoutNodes.map((node) => { if (node.id === 'service-gnmi') { return serviceNode(node, service, linesBySource.gnmi.get('gnmi.gNMI'), serviceVersion, gnmiTag); } @@ -492,9 +635,13 @@ async function main() { const symbol = nodeIdToSymbol.get(node.id); if (symbol) { + const definition = definitions.get(symbol); + if (!definition) { + return node; + } return schemaNode( node, - definitions.get(symbol), + definition, symbol, symbolToNodeId, byShortName, @@ -514,7 +661,7 @@ async function main() { }; }); const edges = buildEdges(nodes); - const source = { + const source: MapSource = { gnmiTag, gnmiServiceVersion: serviceVersion, gnmiBase: `${GNMI_GITHUB_BASE}/${gnmiTag}/proto/gnmi/gnmi.proto`, diff --git a/scripts/generate-pdf.mjs b/scripts/generate-pdf.ts similarity index 87% rename from scripts/generate-pdf.mjs rename to scripts/generate-pdf.ts index 4a6d3dc..7557839 100644 --- a/scripts/generate-pdf.mjs +++ b/scripts/generate-pdf.ts @@ -1,7 +1,15 @@ import fs from 'node:fs'; import path from 'node:path'; import PDFDocument from 'pdfkit'; -import { getVisibleMap, mapBounds, mapSource } from '../src/gnmiMap.js'; +import { + getVisibleMap, + mapBounds, + mapSource, + type MapEdge, + type MapEdgeKind, + type MapNode, + type MapNodeKind, +} from '../src/gnmiMap'; const outputPath = path.resolve('gnmi_0.10.0_map.pdf'); const publicOutputPath = path.resolve('public/gnmi_0.10.0_map.pdf'); @@ -28,7 +36,12 @@ const colors = { deprecated: '#fee2df', }; -const headerColors = { +type Point = { + x: number; + y: number; +}; + +const headerColors: Record = { service: colors.blue, rpc: colors.rpc, enum: colors.amber, @@ -37,25 +50,26 @@ const headerColors = { message: colors.teal, }; -const edgeColors = { +const edgeColors: Record = { rpc: '#0b4b8f', field: '#5b708a', extension: '#8a6a1f', 'extension-detail': '#b47a18', }; -function nodeWidth(node) { - return node.style?.width ?? 320; +function nodeWidth(node: MapNode): number { + const width = node.style?.width; + return typeof width === 'number' ? width : 320; } -function nodeHeight(node) { +function nodeHeight(node: MapNode): number { const fields = node.data.fields ?? []; const badgeHeight = node.data.badges?.length ? 24 : 0; const rows = Math.max(fields.length, 1); return headerHeight + badgeHeight + bodyPadding * 2 + rows * rowHeight; } -function sourcePoint(node, handleId) { +function sourcePoint(node: MapNode, handleId: string): Point { const fields = node.data.fields ?? []; const index = Math.max( 0, @@ -75,14 +89,14 @@ function sourcePoint(node, handleId) { }; } -function targetPoint(node) { +function targetPoint(node: MapNode): Point { return { x: node.position.x, y: node.position.y + nodeHeight(node) / 2, }; } -function trimText(value, limit) { +function trimText(value: string, limit: number): string { if (value.length <= limit) { return value; } @@ -90,7 +104,13 @@ function trimText(value, limit) { return `${value.slice(0, limit - 1)}...`; } -function drawArrow(doc, x, y, angle, color) { +function drawArrow( + doc: PDFKit.PDFDocument, + x: number, + y: number, + angle: number, + color: string, +): void { const size = 8; doc .save() @@ -109,7 +129,11 @@ function drawArrow(doc, x, y, angle, color) { .restore(); } -function drawEdge(doc, edge, nodesById) { +function drawEdge( + doc: PDFKit.PDFDocument, + edge: MapEdge, + nodesById: Map, +): void { const source = nodesById.get(edge.source); const target = nodesById.get(edge.target); if (!source || !target) { @@ -151,7 +175,14 @@ function drawEdge(doc, edge, nodesById) { doc.restore(); } -function drawBadge(doc, text, x, y, color, fill) { +function drawBadge( + doc: PDFKit.PDFDocument, + text: string, + x: number, + y: number, + color: string, + fill: string, +): number { const width = Math.max(46, doc.widthOfString(text) + 12); doc .save() @@ -166,7 +197,7 @@ function drawBadge(doc, text, x, y, color, fill) { return width; } -function drawNode(doc, node) { +function drawNode(doc: PDFKit.PDFDocument, node: MapNode): void { const x = node.position.x + margin; const y = node.position.y + margin; const width = nodeWidth(node); @@ -302,7 +333,7 @@ async function main() { showDeprecated: false, showExtensions: true, }); - const nodesById = new Map(pdfNodes.map((node) => [node.id, node])); + const nodesById = new Map(pdfNodes.map((node) => [node.id, node])); const doc = new PDFDocument({ autoFirstPage: false, compress: true, @@ -350,7 +381,7 @@ async function main() { }); doc.end(); - await new Promise((resolve, reject) => { + await new Promise((resolve, reject) => { stream.on('finish', resolve); stream.on('error', reject); }); diff --git a/scripts/validate-map.mjs b/scripts/validate-map.ts similarity index 80% rename from scripts/validate-map.mjs rename to scripts/validate-map.ts index 90bb727..4e80008 100644 --- a/scripts/validate-map.mjs +++ b/scripts/validate-map.ts @@ -1,12 +1,19 @@ -import { getVisibleMap, mapEdges, mapNodes, mapSource } from '../src/gnmiMap.js'; +import { + getVisibleMap, + mapEdges, + mapNodes, + mapSource, + type MapEdge, + type MapNode, +} from '../src/gnmiMap'; -function assert(condition, message) { +function assert(condition: unknown, message: string): asserts condition { if (!condition) { throw new Error(message); } } -function validateEdges(nodes, edges, label) { +function validateEdges(nodes: MapNode[], edges: MapEdge[], label: string): void { const nodeIds = new Set(nodes.map((node) => node.id)); const handles = new Set( nodes.flatMap((node) => (node.data.fields ?? []).map((field) => `${node.id}:${field.id}`)), @@ -22,7 +29,7 @@ function validateEdges(nodes, edges, label) { } } -function validateLinks() { +function validateLinks(): void { for (const node of mapNodes) { if (node.data.protoUrl?.startsWith('https://github.com/openconfig/gnmi/blob/')) { const validProtoBase = @@ -40,17 +47,17 @@ function validateLinks() { } } -function validateDeprecatedVisibility() { +function validateDeprecatedVisibility(): void { const rawSubscribeResponse = mapNodes.find((node) => node.id === 'subscribe-response'); assert(rawSubscribeResponse, 'raw map is missing SubscribeResponse'); - const rawErrorField = rawSubscribeResponse.data.fields.find((field) => field.name === 'error'); + const rawErrorField = rawSubscribeResponse.data.fields?.find((field) => field.name === 'error'); assert(rawErrorField?.deprecated, 'raw SubscribeResponse.error must be preserved as deprecated'); const defaultMap = getVisibleMap(); const defaultSubscribeResponse = defaultMap.nodes.find((node) => node.id === 'subscribe-response'); assert(defaultSubscribeResponse, 'default map is missing SubscribeResponse'); assert( - !defaultSubscribeResponse.data.fields.some((field) => field.name === 'error'), + !(defaultSubscribeResponse.data.fields?.some((field) => field.name === 'error') ?? false), 'default SubscribeResponse must hide deprecated error field', ); assert( @@ -67,7 +74,7 @@ function validateDeprecatedVisibility() { (node) => node.id === 'subscribe-response', ); assert( - deprecatedSubscribeResponse?.data.fields.some((field) => field.name === 'error'), + deprecatedSubscribeResponse?.data.fields?.some((field) => field.name === 'error'), 'deprecated map must include SubscribeResponse.error', ); } diff --git a/src/App.jsx b/src/App.tsx similarity index 88% rename from src/App.jsx rename to src/App.tsx index 94f2c9a..d5bb4c9 100644 --- a/src/App.jsx +++ b/src/App.tsx @@ -1,10 +1,12 @@ -import React, { useCallback, useMemo, useState } from 'react'; +import { type CSSProperties, useCallback, useMemo, useState } from 'react'; import { Background, Controls, Handle, MarkerType, MiniMap, + type NodeProps, + type NodeTypes, Position, ReactFlow, ReactFlowProvider, @@ -21,9 +23,17 @@ import { GitBranch, Search, } from 'lucide-react'; -import { getVisibleMap, mapBounds, mapSource } from './gnmiMap.js'; - -const edgeStyleByKind = { +import { + getVisibleMap, + type MapEdge, + type MapEdgeKind, + type MapField, + type MapNode, + type MapNodeKind, + mapSource, +} from './gnmiMap'; + +const edgeStyleByKind: Record = { rpc: { stroke: '#0b4b8f', strokeWidth: 2.2 }, field: { stroke: '#5b708a', strokeWidth: 1.6 }, extension: { @@ -34,11 +44,11 @@ const edgeStyleByKind = { 'extension-detail': { stroke: '#b47a18', strokeWidth: 1.5 }, }; -const nodeTypes = { +const nodeTypes: NodeTypes = { schema: SchemaNode, }; -function searchableText(node) { +function searchableText(node: MapNode): string { const fieldText = node.data.fields ?.map((field) => `${field.type} ${field.name} ${field.group ?? ''} ${field.badge ?? ''}`) .join(' '); @@ -46,7 +56,7 @@ function searchableText(node) { return `${node.data.kind} ${node.data.label} ${fieldText ?? ''}`.toLowerCase(); } -function fieldMatches(field, query) { +function fieldMatches(field: MapField, query: string): boolean { if (!query) { return false; } @@ -57,11 +67,11 @@ function fieldMatches(field, query) { } function AppShell() { - const { fitView } = useReactFlow(); + const { fitView } = useReactFlow(); const [queryValue, setQueryValue] = useState(''); const [showExtensions, setShowExtensions] = useState(false); const [showDeprecated, setShowDeprecated] = useState(false); - const [selectedId, setSelectedId] = useState(null); + const [selectedId, setSelectedId] = useState(null); const query = queryValue.trim().toLowerCase(); const visibleMap = useMemo( @@ -71,7 +81,7 @@ function AppShell() { const nodeMatches = useMemo(() => { if (!query) { - return new Set(); + return new Set(); } return new Set( @@ -98,7 +108,7 @@ function AppShell() { [nodeMatches, query, showExtensions, visibleMap.nodes], ); - const edges = useMemo( + const edges = useMemo( () => visibleMap.edges.map((edge) => { const connectedToMatch = @@ -107,7 +117,7 @@ function AppShell() { return { ...edge, - type: 'smoothstep', + type: 'smoothstep' as const, className: `flow-edge flow-edge-${edge.kind}`, markerEnd: { type: MarkerType.ArrowClosed, color: style.stroke }, animated: query ? connectedToMatch : edge.kind === 'rpc', @@ -181,7 +191,7 @@ function AppShell() {
- nodes={nodes} edges={edges} nodeTypes={nodeTypes} @@ -196,7 +206,7 @@ function AppShell() { > - position="bottom-right" pannable zoomable @@ -215,7 +225,7 @@ function AppShell() { ); } -function SchemaNode({ data, selected }) { +function SchemaNode({ data, selected }: NodeProps) { const fields = data.fields ?? []; const dimmed = data.active === false; const className = [ @@ -262,7 +272,7 @@ function SchemaNode({ data, selected }) { )) @@ -274,7 +284,13 @@ function SchemaNode({ data, selected }) { ); } -function FieldRow({ field, highlighted, showExtensions }) { +type FieldRowProps = { + field: MapField; + highlighted: boolean; + showExtensions?: boolean; +}; + +function FieldRow({ field, highlighted, showExtensions }: FieldRowProps) { const isExtension = field.ref === 'extension'; const visibleExtensionHandle = !isExtension || showExtensions; @@ -306,7 +322,13 @@ function FieldRow({ field, highlighted, showExtensions }) { ); } -function Inspector({ node, totalNodes, totalEdges }) { +type InspectorProps = { + node?: MapNode; + totalNodes: number; + totalEdges: number; +}; + +function Inspector({ node, totalNodes, totalEdges }: InspectorProps) { if (!node) { return (