Skip to content

fix(dashboard): show LoadBalancer external IP on the app Services tab - #3172

Merged
scooby87 merged 1 commit into
mainfrom
fix/3111-services-external-ip
Jul 2, 2026
Merged

scooby87 merged 1 commit into
mainfrom
fix/3111-services-external-ip

Conversation

@scooby87

@scooby87 scooby87 commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

For an application exposed with external: true (which creates a type: LoadBalancer Service), the app detail → Services tab showed the service Type and Cluster IP but not the external IP assigned by MetalLB — so there was no way to learn the connection address from the per-app view. The pre-1.4.0 dashboard showed this, so it read as a regression (reported in #2789).

Root cause: ServicesTab.tsx queried Services with a spec-only local type ({ type, clusterIP, ports }) and never read status.loadBalancer.ingress[]. The data was already reachable — ExternalIpsPage.tsx renders it — so this was a wiring gap in the per-app tab, not missing data.

Changes:

  • Add a ServiceStatus type and extend the useK8sList query to K8sResource<ServiceSpec, ServiceStatus>.
  • Add an External IP column: ip ?? hostname ?? "Pending" for LoadBalancer services, — otherwise — mirroring ExternalIpsPage.
  • Add a co-located regression test (ServicesTab.test.tsx) covering the IP, hostname→Pending fallback, and the ClusterIP no-external case.

Verified: pnpm typecheck clean, pnpm test green (320 tests, incl. 3 new), eslint clean on the changed files.

Fixes #3111

Screenshots

Снимок экрана 2026-07-02 в 14 12 11

Release note

fix(dashboard): show the external LoadBalancer IP on the per-app Services tab

Summary by CodeRabbit

  • New Features

    • Added an External IP column to the service details view.
    • Load balancer services now show the external IP or hostname when available.
  • Bug Fixes

    • Fixed the external IP display for load balancer services so it no longer misses valid IPs.
    • Improved fallback behavior by showing Pending when no external endpoint is assigned.
    • Non-load balancer services now show an em dash instead of misleading external IP text.

The per-app Services tab queried Services with a spec-only type, so it
never read status.loadBalancer.ingress and showed only the cluster-internal
IP for external: true (type: LoadBalancer) services. This regressed the
pre-1.4.0 behaviour (#2789).

Add a ServiceStatus type to the useK8sList query and surface an
"External IP" column (ip / hostname / Pending for LoadBalancer, — otherwise),
mirroring what ExternalIpsPage already does.

Fixes #3111

Assisted-By: Claude <[email protected]>
Signed-off-by: Алексей Артамонов <[email protected]>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a regression in the application dashboard where the external IP address for LoadBalancer services was no longer visible in the Services tab. By updating the data fetching logic to include service status information and adding a dedicated column to the UI, users can now easily identify the connection address for their exposed applications. The changes are accompanied by comprehensive unit tests to ensure future stability.

Highlights

  • External IP visibility: Added an 'External IP' column to the Services tab to display LoadBalancer ingress IPs or hostnames, resolving a regression where this information was hidden.
  • Data fetching update: Extended the useK8sList query to include ServiceStatus, allowing the dashboard to access status.loadBalancer.ingress data.
  • Regression testing: Introduced a new test file ServicesTab.test.tsx to verify the correct rendering of LoadBalancer IPs, hostname fallbacks, and proper handling of non-LoadBalancer services.
New Features

🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@coderabbitai

coderabbitai Bot commented Jul 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a ServiceStatus interface and updates the ServicesTab data query to fetch service status alongside spec. Introduces an "External IP" column that derives its value from LoadBalancer ingress IP/hostname with "Pending" fallback, or "—" for other service types. Includes a new test suite covering these behaviors.

Changes

ServicesTab External IP Display

Layer / File(s) Summary
ServiceStatus type and data fetching
ServicesTab.tsx
Adds ServiceStatus interface for status.loadBalancer.ingress and updates useK8sList generic to fetch ServiceSpec and ServiceStatus.
External IP column rendering and tests
ServicesTab.tsx, ServicesTab.test.tsx
Adds "External IP" table column deriving value from ingress ip/hostname/Pending for LoadBalancer services or em dash otherwise, with tests covering IP precedence, hostname/Pending fallback, and non-LoadBalancer em dash.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ServicesTab
  participant K8sClient
  participant ServiceRow

  ServicesTab->>K8sClient: useK8sList query ServiceSpec + ServiceStatus
  K8sClient-->>ServicesTab: services list with status.loadBalancer.ingress
  ServicesTab->>ServiceRow: render row per service
  ServiceRow->>ServiceRow: check spec.type === LoadBalancer
  ServiceRow->>ServiceRow: derive external = ingress.ip or hostname or Pending
  ServiceRow-->>ServicesTab: render External IP cell or em dash
Loading

Possibly related issues

  • cozystack/cozystack-ui#38: Targets the same ServicesTab.tsx to surface a LoadBalancer service's external IP/hostname.

Possibly related PRs

  • cozystack/cozystack#1524: Both PRs adjust the displayed LoadBalancer IP source to status.loadBalancer.ingress[0].ip with hostname/Pending fallback and non-LoadBalancer handling.

Suggested reviewers: kvaps, lllamnyp, androndo

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: showing LoadBalancer external IPs in the Services tab.
Linked Issues check ✅ Passed The code changes implement #3111 by reading service status and rendering External IP with the required fallbacks.
Out of Scope Changes check ✅ Passed All changes support the Services tab external IP fix or regression tests; no unrelated scope is evident.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/3111-services-external-ip

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dosubot dosubot Bot added area/dashboard Issues or PRs related to the dashboard / UI kind/bug Categorizes issue or PR as related to a bug labels Jul 1, 2026
@github-actions github-actions Bot added the size/L This PR changes 100-499 lines, ignoring generated files label Jul 1, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for displaying the External IP of LoadBalancer services in the dashboard's ServicesTab component. It introduces an "External IP" column to the services table, resolving the IP or hostname from the service status (or displaying "Pending" if not yet assigned), and falls back to an em dash for non-LoadBalancer services. Additionally, a comprehensive test suite has been added to verify this behavior under various scenarios. I have no feedback to provide as the implementation is clean and well-tested.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NOT LGTM — the PR template requires a screenshot for UI changes; this PR ships without one.

Business context: Cozystack 1.4.0 dropped the LoadBalancer external IP from the per-app Services tab (it was visible pre-1.4.0), and this PR restores it by threading status.loadBalancer.ingress[] through the existing query, mirroring the logic already used on the Administration → External IPs page.

Blockers

B1: No screenshot for a UI change

File: PR description, "Screenshots" section
Issue: The PR adds a new "External IP" column to the Services tab UI, but the Screenshots section still contains the unfilled placeholder <!-- TODO: attach a screenshot of the Services tab showing the External IP column for a LoadBalancer service. -->. No image is attached anywhere in the PR body or comments.
Evidence: .github/PULL_REQUEST_TEMPLATE.md states: "REQUIRED for UI changes: attach screenshots or screen recordings demonstrating the visual impact of your changes. PRs with UI changes without screenshots will not be merged." This PR modifies ServicesTab.tsx's rendered table — a UI change squarely within the template's stated scope.
Impact: Reviewers can't visually verify the rendered column (alignment, font-mono styling, hostname truncation, Pending/— states) without checking out the branch. Merging as-is bypasses the project's own completeness gate for UI PRs.
Fix: Attach a screenshot of the Services tab showing the new External IP column for a LoadBalancer service (ideally also the Pending/— states) and remove the TODO placeholder.

Non-blocking follow-ups

  1. ServiceSpec/ServiceStatus and the ip ?? hostname ?? "Pending" fallback logic are now duplicated verbatim between ServicesTab.tsx and ExternalIpsPage.tsx. Two occurrences fits this project's "three similar widgets over a premature factory" convention — worth extracting only if a third call site shows up.
  2. The failing "E2E Tests" check on this PR is a cozy-vertical-pod-autoscaler Helm upgrade/liveness-probe failure during cluster install — unrelated to this frontend-only change.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: LGTM with one non-blocking note

Correct minimal frontend fix for a real regression.

Non-blocking

  • ServicesTab.test.tsx ClusterIP test: expect(screen.getAllByText("—").length).toBeGreaterThan(0) does not actually pin the External IP behavior. All three fixtures omit creationTimestamp, and formatAge(undefined) returns "—" (lib/status.ts:11), so the Age column already renders an em dash on its own. The assertion passes on the Age cell regardless of the External IP column, and would still pass if the External IP rendering were broken. Suggest scoping the query to the External IP cell, or giving the fixture a creationTimestamp so the em-dash count is deterministic. The other two tests use specific string matches and are robust.

Verified

  • Reading status.loadBalancer needs no new RBAC: the status field of a core Service is returned by get/list/watch services (not a separate subresource), and the dashboard already list/watches services (ExternalIpsPage does the identical read).
  • The lb?.ip ?? lb?.hostname ?? "Pending" fallback matches ExternalIpsPage; ServicesTab correctly gates on type === "LoadBalancer" and renders — otherwise.

Upgrade/fresh-install impact: none beyond a dashboard image rebuild. Purely additive UI column.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — correct, minimal, additive fix for a real 1.4.0 regression, and the screenshot is now attached, which resolves my earlier request for changes.

The change threads status.loadBalancer.ingress[] through the existing useK8sList query and renders ip ?? hostname ?? "Pending" for LoadBalancer services and — otherwise, mirroring ExternalIpsPage.tsx. No new RBAC: status ships with a core Service list/watch, and the dashboard already reads it on the External IPs page. Existing columns are unchanged; no install/upgrade impact beyond a dashboard image rebuild.

Non-blocking (already raised in review, adding evidence): the ClusterIP test asserts getAllByText("—").length > 0, but all fixtures omit creationTimestamp and formatAge(undefined) returns "—" (lib/status.ts:11), so the Age cell alone satisfies the assertion — it would still pass if the External IP cell were broken. Scope the query to the External IP cell, or give the fixture a creationTimestamp, so the test pins the intended behavior. The IP and hostname→Pending tests use exact string matches and are solid.

@scooby87
scooby87 merged commit 6bf836d into main Jul 2, 2026
33 of 35 checks passed
@scooby87
scooby87 deleted the fix/3111-services-external-ip branch July 2, 2026 17:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/dashboard Issues or PRs related to the dashboard / UI kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

App detail Services tab does not show the external LoadBalancer IP

3 participants