diff --git a/package-lock.json b/package-lock.json
index 0fc567a..d92c9af 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "react-policy-topology",
- "version": "0.1.0",
+ "version": "0.1.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "react-policy-topology",
- "version": "0.1.0",
+ "version": "0.1.10",
"dependencies": {
"@patternfly/patternfly": "^4.224.5",
"@patternfly/react-core": "^4.278.1",
@@ -21,12 +21,14 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
- "web-vitals": "^2.1.4"
+ "web-vitals": "^2.1.4",
+ "ws": "^8.18.0"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@puppeteer/browsers": "^2.3.1",
"chai": "^5.1.1",
+ "concurrently": "^7.0.0",
"mocha": "^10.7.3",
"puppeteer": "^23.1.1",
"wait-on": "^8.0.0",
@@ -6747,6 +6749,126 @@
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
+ "node_modules/concurrently": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-7.6.0.tgz",
+ "integrity": "sha512-BKtRgvcJGeZ4XttiDiNcFiRlxoAeZOseqUvyYRUp/Vtd+9p1ULmeoSqGsDA+2ivdeDFpqrJvGvmI+StKfKl5hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "date-fns": "^2.29.1",
+ "lodash": "^4.17.21",
+ "rxjs": "^7.0.0",
+ "shell-quote": "^1.7.3",
+ "spawn-command": "^0.0.2-1",
+ "supports-color": "^8.1.0",
+ "tree-kill": "^1.2.2",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "conc": "dist/bin/concurrently.js",
+ "concurrently": "dist/bin/concurrently.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
+ }
+ },
+ "node_modules/concurrently/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/concurrently/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/concurrently/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/concurrently/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concurrently/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/concurrently/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
"node_modules/confusing-browser-globals": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz",
@@ -7704,6 +7826,23 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/date-fns": {
+ "version": "2.30.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
+ "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.21.0"
+ },
+ "engines": {
+ "node": ">=0.11"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/date-fns"
+ }
+ },
"node_modules/debug": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
@@ -12826,6 +12965,27 @@
}
}
},
+ "node_modules/jsdom/node_modules/ws": {
+ "version": "7.5.10",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
+ "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.3.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": "^5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
"node_modules/jsesc": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
@@ -15919,27 +16079,6 @@
"node": ">=18"
}
},
- "node_modules/puppeteer-core/node_modules/ws": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
- "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
- "dev": true,
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
"node_modules/q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
@@ -18392,6 +18531,12 @@
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"deprecated": "Please use @jridgewell/sourcemap-codec instead"
},
+ "node_modules/spawn-command": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz",
+ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==",
+ "dev": true
+ },
"node_modules/spdy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz",
@@ -19355,6 +19500,16 @@
"node": ">=8"
}
},
+ "node_modules/tree-kill": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
+ "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "tree-kill": "cli.js"
+ }
+ },
"node_modules/tryer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
@@ -20009,26 +20164,6 @@
}
}
},
- "node_modules/webpack-dev-server/node_modules/ws": {
- "version": "8.18.0",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
- "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
"node_modules/webpack-manifest-plugin": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz",
@@ -20666,15 +20801,16 @@
}
},
"node_modules/ws": {
- "version": "7.5.10",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
- "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "license": "MIT",
"engines": {
- "node": ">=8.3.0"
+ "node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
- "utf-8-validate": "^5.0.2"
+ "utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
diff --git a/package.json b/package.json
index e1faf80..e8d7a9e 100644
--- a/package.json
+++ b/package.json
@@ -18,10 +18,11 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-scripts": "5.0.1",
- "web-vitals": "^2.1.4"
+ "web-vitals": "^2.1.4",
+ "ws": "^8.18.0"
},
"scripts": {
- "start": "react-scripts start",
+ "start": "concurrently \"react-scripts start\" \"node watch-configmap.js\"",
"build": "react-scripts build",
"test": "mocha tests --timeout 10000",
"test:ci": "npm start & wait-on http://localhost:3000 && mocha tests --timeout 10000",
@@ -49,6 +50,7 @@
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@puppeteer/browsers": "^2.3.1",
"chai": "^5.1.1",
+ "concurrently": "^7.0.0",
"mocha": "^10.7.3",
"puppeteer": "^23.1.1",
"wait-on": "^8.0.0",
diff --git a/public/index.html b/public/index.html
index aa069f2..b2bcea8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
-
React App
+ Policy Machinery
diff --git a/public/manifest.json b/public/manifest.json
index 080d6c7..bbb4bb5 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -1,6 +1,6 @@
{
- "short_name": "React App",
- "name": "Create React App Sample",
+ "short_name": "Policy Machinery",
+ "name": "Policy Machinery",
"icons": [
{
"src": "favicon.ico",
diff --git a/src/App.css b/src/App.css
index 4ac8ad7..1a98c83 100644
--- a/src/App.css
+++ b/src/App.css
@@ -14,14 +14,12 @@
}
.App-header {
- background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
- color: white;
}
.App .policy-topology-container svg text {
@@ -35,6 +33,10 @@
color: #61dafb;
}
+/* .App .policy-topology-container {
+ width: 400px;
+} */
+
.App .pf-c-dropdown__toggle {
background-color: #fff;
margin-bottom: 1em;
diff --git a/src/App.js b/src/App.js
index 8c2650d..5c95828 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,134 +1,48 @@
-import React, { useState, useEffect } from 'react';
-import PolicyTopology from './PolicyTopology.js';
-import PickResource from './PickResource.js';
-import * as dot from 'graphlib-dot'; // still needed for parsing dotString
-import './App.css';
+import React, { useState, useEffect } from "react";
+import PolicyTopology from "./PolicyTopology.js";
+import PickResource from "./PickResource.js";
+import * as dot from "graphlib-dot"; // Needed to parse dotString
+import "./App.css";
function App() {
- const initialDotString = `
- strict digraph "" {
- graph [bb="0,0,440.51,352"];
- node [fillcolor=lightgrey,
- label="",
- shape=ellipse
- ];
- "gateway.gateway.networking.k8s.io:default/prod-web" [fillcolor="#e5e5e5",
- height=0.57778,
- label="Gateway\ndefault/prod-web",
- pos="280.92,253.6",
- shape=box,
- style=filled,
- width=1.5612];
- "gateway.gateway.networking.k8s.io:default/prod-web#http" [fillcolor="#e5e5e5",
- height=0.57778,
- label="Listener\ndefault/prod-web#http",
- pos="369.92,176",
- shape=box,
- style=filled,
- width=1.9609];
- "gateway.gateway.networking.k8s.io:default/prod-web" -> "gateway.gateway.networking.k8s.io:default/prod-web#http" [key="Gateway -> Listener",
- pos="e,346.01,196.85 304.77,232.8 315.05,223.85 327.21,213.24 338.23,203.63"];
- "gateway.gateway.networking.k8s.io:default/prod-web#https" [fillcolor="#e5e5e5",
- height=0.57778,
- label="Listener\ndefault/prod-web#https",
- pos="74.922,176",
- shape=box,
- style=filled,
- width=2.0365];
- "gateway.gateway.networking.k8s.io:default/prod-web" -> "gateway.gateway.networking.k8s.io:default/prod-web#https" [key="Gateway -> Listener",
- pos="e,129.7,196.64 225.99,232.91 199.33,222.87 167.14,210.74 139.34,200.27"];
- "httproute.gateway.networking.k8s.io:default/my-app" [fillcolor="#e5e5e5",
- height=0.57778,
- label="HTTPRoute\ndefault/my-app",
- pos="223.92,98.4",
- shape=box,
- style=filled,
- width=1.4101];
- "httproute.gateway.networking.k8s.io:default/my-app#rule-1" [fillcolor="#e5e5e5",
- height=0.57778,
- label="HTTPRouteRule\ndefault/my-app#rule-1",
- pos="72.922,20.8",
- shape=box,
- style=filled,
- width=1.9716];
- "httproute.gateway.networking.k8s.io:default/my-app" -> "httproute.gateway.networking.k8s.io:default/my-app#rule-1" [key="HTTPRoute -> HTTPRouteRule",
- pos="e,113.24,41.518 183.46,77.605 164.59,67.911 141.98,56.29 122.14,46.092"];
- "httproute.gateway.networking.k8s.io:default/my-app#rule-2" [fillcolor="#e5e5e5",
- height=0.57778,
- label="HTTPRouteRule\ndefault/my-app#rule-2",
- pos="232.92,20.8",
- shape=box,
- style=filled,
- width=1.9716];
- "httproute.gateway.networking.k8s.io:default/my-app" -> "httproute.gateway.networking.k8s.io:default/my-app#rule-2" [key="HTTPRoute -> HTTPRouteRule",
- pos="e,230.5,41.653 226.33,77.605 227.25,69.689 228.32,60.489 229.32,51.828"];
- "gateway.gateway.networking.k8s.io:default/prod-web#http" -> "httproute.gateway.networking.k8s.io:default/my-app" [key="Listener -> HTTPRoute",
- pos="e,262.9,119.12 330.8,155.2 312.64,145.55 290.89,133.99 271.77,123.83"];
- "gateway.gateway.networking.k8s.io:default/prod-web#https" -> "httproute.gateway.networking.k8s.io:default/my-app" [key="Listener -> HTTPRoute",
- pos="e,184.14,119.12 114.85,155.2 133.38,145.55 155.58,133.99 175.09,123.83"];
- "dnspolicy.kuadrant.io:default/geo" [height=0.57778,
- label="DNSPolicy\ndefault/geo",
- pos="215.92,331.2",
- shape=note,
- style=dashed,
- width=1.108];
- "dnspolicy.kuadrant.io:default/geo" -> "gateway.gateway.networking.k8s.io:default/prod-web" [key="Policy -> Target",
- pos="e,263.45,274.45 233.34,310.4 240.55,301.79 249.04,291.66 256.83,282.36",
- style=dashed];
- "tlspolicy.kuadrant.io:default/https" [height=0.57778,
- label="TLSPolicy\ndefault/https",
- pos="74.922,253.6",
- shape=note,
- style=dashed,
- width=1.1943];
- "tlspolicy.kuadrant.io:default/https" -> "gateway.gateway.networking.k8s.io:default/prod-web#https" [key="Policy -> Target",
- pos="e,74.922,196.85 74.922,232.8 74.922,224.89 74.922,215.69 74.922,207.03",
- style=dashed];
- "authpolicy.kuadrant.io:default/api-key-admins" [height=0.57778,
- label="AuthPolicy\ndefault/api-key-admins",
- pos="72.922,98.4",
- shape=note,
- style=dashed,
- width=2.0256];
- "authpolicy.kuadrant.io:default/api-key-admins" -> "httproute.gateway.networking.k8s.io:default/my-app#rule-1" [key="Policy -> Target",
- pos="e,72.922,41.653 72.922,77.605 72.922,69.689 72.922,60.489 72.922,51.828",
- style=dashed];
- "authpolicy.kuadrant.io:default/business-hours" [height=0.57778,
- label="AuthPolicy\ndefault/business-hours",
- pos="344.92,331.2",
- shape=note,
- style=dashed,
- width=1.9718];
- "authpolicy.kuadrant.io:default/business-hours" -> "gateway.gateway.networking.k8s.io:default/prod-web" [key="Policy -> Target",
- pos="e,298.12,274.45 327.77,310.4 320.67,301.79 312.31,291.66 304.64,282.36",
- style=dashed];
- "ratelimitpolicy.kuadrant.io:default/my-app-rl" [height=0.57778,
- label="RateLimitPolicy\ndefault/my-app-rl",
- pos="223.92,176",
- shape=note,
- style=dashed,
- width=1.5936];
- "ratelimitpolicy.kuadrant.io:default/my-app-rl" -> "httproute.gateway.networking.k8s.io:default/my-app" [key="Policy -> Target",
- pos="e,223.92,119.25 223.92,155.2 223.92,147.29 223.92,138.09 223.92,129.43",
- style=dashed];
- }
- `;
-
+ const [dotString, setDotString] = useState("");
const [graph, setGraph] = useState(null);
useEffect(() => {
- const g = dot.read(initialDotString);
- setGraph(g);
- }, [initialDotString]);
+ let ws;
+ const connectWebSocket = () => {
+ ws = new WebSocket("ws://localhost:4000/ws");
+
+ ws.onmessage = (event) => {
+ try {
+ const { dotString: updatedDotString } = JSON.parse(event.data);
+ console.log("WebSocket message received:", updatedDotString);
+ setDotString(updatedDotString);
+
+ const parsedGraph = dot.read(updatedDotString);
+ setGraph(parsedGraph);
+ } catch (error) {
+ console.error("Error processing WebSocket message:", error);
+ }
+ };
+
+ ws.onclose = () => {
+ console.warn("WebSocket closed. Attempting to reconnect...");
+ setTimeout(connectWebSocket, 3000); // Retry after 3 seconds
+ };
+
+ ws.onerror = (error) => console.error("WebSocket error:", error);
+ };
+
+ connectWebSocket();
+ return () => ws && ws.close();
+ }, []);
return (
- Policy Topology Example
-
-
+ Policy Machinery
+
);
diff --git a/src/PickResource.js b/src/PickResource.js
index 594d326..074539e 100644
--- a/src/PickResource.js
+++ b/src/PickResource.js
@@ -1,30 +1,48 @@
-import React, { useState, useEffect, useCallback } from 'react';
-import { Dropdown, DropdownToggle, DropdownItem } from '@patternfly/react-core';
+import React, { useState, useEffect, useCallback } from "react";
+import { Dropdown, DropdownToggle, DropdownItem } from "@patternfly/react-core";
const PickResource = ({ graph, onResourceSelect }) => {
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const [dropdownItems, setDropdownItems] = useState([]);
- const [selectedLabel, setSelectedLabel] = useState('Select a resource');
+ const [selectedLabel, setSelectedLabel] = useState("Select a resource");
- const handleSelection = useCallback((nodeId) => {
- setSelectedLabel(nodeId ? graph.node(nodeId).label : 'Select a resource');
- onResourceSelect(nodeId);
- setIsDropdownOpen(false);
- }, [graph, onResourceSelect]);
+ const handleSelection = useCallback(
+ (nodeId) => {
+ if (graph && typeof graph.node === "function") {
+ setSelectedLabel(
+ nodeId ? graph.node(nodeId).label : "Select a resource"
+ );
+ }
+ onResourceSelect(nodeId);
+ setIsDropdownOpen(false);
+ },
+ [graph, onResourceSelect]
+ );
useEffect(() => {
- if (graph) {
+ if (graph && typeof graph.nodes === "function") {
const items = [
- handleSelection(null)}>
+ handleSelection(null)}
+ >
-
,
- ...graph.nodes().map(node => (
- handleSelection(node)}>
- {graph.node(node).label}
+ ...graph.nodes().map((node) => (
+ handleSelection(node)}
+ >
+ {graph.node(node)?.label || node}
)),
];
setDropdownItems(items);
+ } else {
+ console.warn("Invalid graph object:", graph);
+ setDropdownItems([]);
}
}, [graph, handleSelection]);
@@ -35,7 +53,9 @@ const PickResource = ({ graph, onResourceSelect }) => {
return (
setIsDropdownOpen(false)}
- toggle={{selectedLabel}}
+ toggle={
+ {selectedLabel}
+ }
isOpen={isDropdownOpen}
dropdownItems={dropdownItems}
/>
diff --git a/src/PolicyTopology.js b/src/PolicyTopology.js
index f2a7e02..b76ee37 100644
--- a/src/PolicyTopology.js
+++ b/src/PolicyTopology.js
@@ -1,10 +1,10 @@
-import React, { useEffect, useRef, useState, useCallback } from 'react';
-import * as d3 from 'd3';
-import { graphviz } from 'd3-graphviz'; // eslint-disable-line no-unused-vars
-import { Button } from '@patternfly/react-core';
-import graphlib from 'graphlib';
-import * as dot from 'graphlib-dot';
-import './PolicyTopology.css';
+import React, { useEffect, useRef, useState, useCallback } from "react";
+import * as d3 from "d3";
+import { graphviz } from "d3-graphviz"; // eslint-disable-line no-unused-vars
+import { Button } from "@patternfly/react-core";
+import graphlib from "graphlib";
+import * as dot from "graphlib-dot";
+import "./PolicyTopology.css";
const PolicyTopology = ({ initialDotString }) => {
const containerRef = useRef(null);
@@ -12,78 +12,111 @@ const PolicyTopology = ({ initialDotString }) => {
const [graph, setGraph] = useState(null);
// Function to update graph when a node is selected
- const handleNodeSelection = useCallback((nodeId) => {
- if (!graph) return;
+ const handleNodeSelection = useCallback(
+ (nodeId) => {
+ if (!graph) return;
- const filteredGraph = new graphlib.Graph();
- const nodesToInclude = new Set();
+ const filteredGraph = new graphlib.Graph();
+ const nodesToInclude = new Set();
- const addPredecessors = (node) => {
- if (!nodesToInclude.has(node)) {
- nodesToInclude.add(node);
- const predecessors = graph.predecessors(node) || [];
- predecessors.forEach(addPredecessors);
- }
- };
+ const addPredecessors = (node) => {
+ if (!nodesToInclude.has(node)) {
+ nodesToInclude.add(node);
+ const predecessors = graph.predecessors(node) || [];
+ predecessors.forEach(addPredecessors);
+ }
+ };
- const addSuccessors = (node) => {
- const successors = graph.successors(node) || [];
- successors.forEach(successor => {
- nodesToInclude.add(successor);
- });
- };
+ const addSuccessors = (node) => {
+ const successors = graph.successors(node) || [];
+ successors.forEach((successor) => {
+ nodesToInclude.add(successor);
+ });
+ };
- addPredecessors(nodeId);
- addSuccessors(nodeId);
+ addPredecessors(nodeId);
+ addSuccessors(nodeId);
- nodesToInclude.forEach(node => {
- filteredGraph.setNode(node, graph.node(node));
- });
+ nodesToInclude.forEach((node) => {
+ filteredGraph.setNode(node, graph.node(node));
+ });
- graph.edges().forEach(edge => {
- if (nodesToInclude.has(edge.v) && nodesToInclude.has(edge.w)) {
- filteredGraph.setEdge(edge.v, edge.w, graph.edge(edge.v, edge.w));
- }
- });
+ graph.edges().forEach((edge) => {
+ if (nodesToInclude.has(edge.v) && nodesToInclude.has(edge.w)) {
+ filteredGraph.setEdge(edge.v, edge.w, graph.edge(edge.v, edge.w));
+ }
+ });
+
+ const filteredDotString = dot.write(filteredGraph);
+ setDotString(filteredDotString); // Update the dotString state
+ },
+ [graph]
+ );
- const filteredDotString = dot.write(filteredGraph);
- setDotString(filteredDotString); // Update the dotString state
- }, [graph]);
+ // Parse the DOT string into a graph object when dotString or initialDotString changes
+ useEffect(() => {
+ if (initialDotString !== dotString) {
+ setDotString(initialDotString);
+ }
+ }, [initialDotString]);
- // Parse the DOT string into a graph object when dotString changes
useEffect(() => {
if (dotString) {
- const g = dot.read(dotString);
- setGraph(g);
+ try {
+ const g = dot.read(dotString);
+ console.log("Parsed graph in PolicyTopology:", g);
+ console.log("Graph nodes in PolicyTopology:", g.nodes());
+ setGraph(g);
+ } catch (error) {
+ console.error("Error parsing DOT string in PolicyTopology:", error);
+ }
+ } else {
+ console.warn("Empty dotString received in PolicyTopology");
}
}, [dotString]);
// Render the graph with updates using d3-graphviz
useEffect(() => {
- if (containerRef.current && graph) {
- const renderGraph = () => {
- d3.select(containerRef.current)
+ if (containerRef.current && dotString) {
+ try {
+ console.log("Rendering dotString with d3-graphviz:", dotString);
+
+ // Calculate dimensions dynamically based on viewport
+ const viewportWidth = window.innerWidth * 1.2; // Use 95% of the viewport width
+ const viewportHeight = window.innerHeight * 0.85; // Use 85% of the viewport height
+
+ const graphvizInstance = d3
+ .select(containerRef.current)
.graphviz()
- .height(containerRef.current.clientHeight) // Set SVG height to container height
- .fit(true)
- .zoom(false)
- .transition(() => d3.transition().duration(750)) // Animate transitions
+ .width(viewportWidth) // Set width to fill most of the viewport
+ .height(viewportHeight) // Set height to fill most of the viewport
+ .fit(true) // Ensure it scales proportionally
+ .zoom(false); // Disable zoom for now
+
+ // Render the graph with animation
+ graphvizInstance
+ .transition(() => d3.transition().duration(750)) // Animation duration
.renderDot(dotString)
- .on('end', () => {
- const nodes = containerRef.current.querySelectorAll('g.node');
- nodes.forEach(node => {
- node.addEventListener('click', (event) => {
- const nodeElement = event.target.closest('g.node');
- const nodeId = nodeElement.querySelector('title').textContent;
+ .on("end", () => {
+ console.log("Graph rendered successfully");
+ // Add click event listeners to nodes
+ const nodes = containerRef.current.querySelectorAll("g.node");
+ nodes.forEach((node) => {
+ node.addEventListener("click", (event) => {
+ const nodeElement = event.target.closest("g.node");
+ const nodeId = nodeElement.querySelector("title").textContent;
handleNodeSelection(nodeId);
});
});
});
- };
-
- renderGraph();
+ } catch (error) {
+ console.error("Error rendering dotString with d3-graphviz:", error);
+ }
+ } else if (!dotString) {
+ console.warn("No dotString available for rendering");
}
- }, [graph, dotString, handleNodeSelection]);
+ }, [dotString, handleNodeSelection]);
+
// Function to reset the graph to its initial state
const resetGraph = useCallback(() => {
@@ -93,7 +126,11 @@ const PolicyTopology = ({ initialDotString }) => {
return (
diff --git a/watch-configmap.js b/watch-configmap.js
new file mode 100644
index 0000000..7d69414
--- /dev/null
+++ b/watch-configmap.js
@@ -0,0 +1,58 @@
+import { WebSocketServer } from 'ws';
+import { spawn } from 'child_process';
+
+const wss = new WebSocketServer({ port: 4000 });
+
+console.log('WebSocket server listening on ws://localhost:4000');
+
+wss.on('connection', (ws) => {
+ console.log('Client connected to WebSocket');
+
+ const kubectl = spawn('kubectl', [
+ 'get',
+ 'configmap',
+ 'topology',
+ '-n',
+ 'kuadrant-system',
+ '-o',
+ 'json',
+ '--watch',
+ ]);
+
+ let buffer = '';
+
+ kubectl.stdout.on('data', (chunk) => {
+ buffer += chunk.toString(); // Accumulate chunks in the buffer
+
+ try {
+ // Attempt to parse the buffer
+ const configMap = JSON.parse(buffer);
+ const dotString = configMap.data.topology;
+
+ // Send the updated dotString to the client
+ console.log('Sending dotString to WebSocket:', dotString);
+ ws.send(JSON.stringify({ dotString }));
+
+ // Clear the buffer after successful parsing
+ buffer = '';
+ } catch (error) {
+ if (error.name !== 'SyntaxError') {
+ console.error('Unexpected error:', error);
+ buffer = ''; // Clear buffer for unexpected errors
+ }
+ }
+ });
+
+ kubectl.stderr.on('data', (data) => {
+ console.error('kubectl error:', data.toString());
+ });
+
+ kubectl.on('close', (code) => {
+ console.log(`kubectl process exited with code ${code}`);
+ ws.close();
+ });
+
+ ws.on('close', () => {
+ console.log('WebSocket connection closed');
+ });
+});