-
Notifications
You must be signed in to change notification settings - Fork 70
feat: add export button for service map connections #1033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: add export button for service map connections #1033
Conversation
Add an export button to the service map that allows users to download endpoint connections as JSON. The export includes: - Source and destination service names, IDs, and namespaces - Destination ports and IP protocols - Connection verdicts (Allowed, Dropped, Error, etc.) - Authentication types and encryption status - Throughput metrics (flow amount, latency, bytes transferred) The export contains only the connection topology (arrows between services), not the underlying flow logs, making it suitable for documentation, analysis, and integration with external tools. Fixes cilium#239
yannikmesserli
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @aviary for this PR! I haven't yet tested it locally, but I want to request code changes first. Especially, the class situation and the console situation.
I know this code base is filled with pattern like you are proposing, but I believe it's not good one. Especially static methods are much better isolated, pure, so we can easily tests them and reuse without the entire context of the class. Can you change this already in the PR - I am working on a proposal for restructuring Hubble UI with better frontend patterns.
Secondly, I will also challenge the feature itself you are implementing. You export a specific schema (ExportedServiceMap) which seems particular to some use-cases (?) but does not feel useful for the entire community. Should we build on top of your PR to address fully #239? A SVG of the service map seems more appropriate for everyone... and we can directly get it from the rendering. I can help with this, if you want?
| import { Link } from '~/domain/link'; | ||
| import { ExportedConnection, ExportedServiceMap } from './types'; | ||
|
|
||
| export class ServiceMapExporter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, use pure functions and unit tests them. (No class, and especially no static method)
| const connections: ExportedConnection[] = []; | ||
| let skippedLinks = 0; | ||
|
|
||
| console.log('[ServiceMapExporter] Starting export...', { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is all the console logs really necessary for this feature? Hubble UI console is already filled with some many messages, which usually are useless for most users, I think we should use them with care. Hopefully in the future we can find a more sustainable way. I would recommend to only console log the summary, especially remove all the console logs that you have used for developing (e.g. sample flow)
| onExport?: () => void; | ||
| } | ||
|
|
||
| export const ExportButton = observer(function ExportButton(props: Props) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| export const ExportButton = observer(function ExportButton(props: Props) { | |
| export const ExportButton = function ExportButton(props: Props) { |
you are not using any Mobx elements in this component, so no need to wrap it in observer.
Add Export Button for Service Map Connections
Related issue #239
Overview
This PR adds an export button to the service map that allows users to download endpoint connections as a JSON file. The export captures the current service map topology without including the underlying flow logs.
Changes
New ExportButton component (
src/components/TopBar/ExportButton.tsx)Export utilities (
src/utils/export/)ServiceMapExporterclass with JSON export functionalityIntegration
Exported Data Format
The JSON export includes for each connection:
Testing
Tested with a local kind cluster running Cilium/Hubble:
Example Export
Extracted from tests
{ "exportedAt": "2025-12-03T16:15:23.768Z", "connections": [ { "source": { "id": "4231", "name": "client", "namespace": "app-a" }, "destination": { "id": "43557", "name": "kube-dns", "namespace": "kube-system" }, "destinationPort": 53, "ipProtocol": "2", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } }, { "source": { "id": "5853", "name": "client", "namespace": "app-b" }, "destination": { "id": "43557", "name": "kube-dns", "namespace": "kube-system" }, "destinationPort": 53, "ipProtocol": "2", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } }, { "source": { "id": "1", "name": "host", "namespace": null }, "destination": { "id": "43557", "name": "kube-dns", "namespace": "kube-system" }, "destinationPort": 8080, "ipProtocol": "1", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } }, { "source": { "id": "1", "name": "host", "namespace": null }, "destination": { "id": "43557", "name": "kube-dns", "namespace": "kube-system" }, "destinationPort": 8181, "ipProtocol": "1", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } }, { "source": { "id": "14998", "name": "hubble-relay", "namespace": "kube-system" }, "destination": { "id": "1", "name": "host", "namespace": null }, "destinationPort": 4244, "ipProtocol": "1", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } }, { "source": { "id": "1", "name": "host", "namespace": null }, "destination": { "id": "14998", "name": "hubble-relay", "namespace": "kube-system" }, "destinationPort": 4222, "ipProtocol": "1", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } }, { "source": { "id": "1", "name": "host", "namespace": null }, "destination": { "id": "3615", "name": "hubble-ui", "namespace": "kube-system" }, "destinationPort": 8081, "ipProtocol": "1", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } }, { "source": { "id": "3615", "name": "hubble-ui", "namespace": "kube-system" }, "destination": { "id": "43557", "name": "kube-dns", "namespace": "kube-system" }, "destinationPort": 53, "ipProtocol": "2", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } }, { "source": { "id": "3615", "name": "hubble-ui", "namespace": "kube-system" }, "destination": { "id": "14998", "name": "hubble-relay", "namespace": "kube-system" }, "destinationPort": 4245, "ipProtocol": "1", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } }, { "source": { "id": "43557", "name": "kube-dns", "namespace": "kube-system" }, "destination": { "id": "1", "name": "host", "namespace": null }, "destinationPort": 6443, "ipProtocol": "1", "verdicts": [ "1" ], "authTypes": [ "0" ], "isEncrypted": false, "throughput": { "flowAmount": 0, "latency": { "min": 0, "max": 0, "avg": 0 }, "bytesTransfered": 0 } } ], "summary": { "totalConnections": 10, "totalServices": 6 } }UI changes