Skip to content

HDDS-15985. Add new Overview page for Ozone Manager - #10900

Open
spacemonkd wants to merge 4 commits into
apache:HDDS-11541from
spacemonkd:HDDS-15985
Open

HDDS-15985. Add new Overview page for Ozone Manager#10900
spacemonkd wants to merge 4 commits into
apache:HDDS-11541from
spacemonkd:HDDS-15985

Conversation

@spacemonkd

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

HDDS-15985. Add new Overview page for Ozone Manager

Please describe your PR in detail:
This PR adds the new React based Overview page for Ozone Manager

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-15985

How was this patch tested?

Patch was tested manually via mocked DB.

Screen.Recording.2026-07-29.at.23.20.12.mov

Output of "Copy Arguments" button at the end:

<configuration>
  <property>
    <name>-Dorg.apache.ratis.thirdparty.io.netty.allocator.useCacheForAllThreads</name>
    <value>false</value>
  </property>
  <property>
    <name>-XX:+PrintGCDetails</name>
    <value>Enabled</value>
  </property>
</configuration>

@spacemonkd spacemonkd self-assigned this Jul 29, 2026
@spacemonkd
spacemonkd marked this pull request as ready for review July 29, 2026 18:02
@spacemonkd

Copy link
Copy Markdown
Contributor Author

@chihsuan would you like to take a look as well?

@chihsuan

Copy link
Copy Markdown
Contributor

@chihsuan would you like to take a look as well?

Thanks for the ping! Sure, I'll take a look. 🙂

@chihsuan chihsuan 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.

Nice work overall, @spacemonkd! The page structure is clean, and the mock setup makes local development easy.

I tested it with both the mock and a real OM and found a few issues. Please see the inline comments.

I also wanted to share a few broader suggestions. None of these need to be addressed in this PR: 🙂

  1. Unit tests: I understand that the current diff is already quite large 😅. the pure parsers in overview.ts should be easy to cover.
  2. JMX as a UI contract: The UI currently depends directly on JMX bean shapes, which can change silently when the Java implementation evolves. It might be worth considering documenting or validating the bean shapes consumed by the UI. Longer term, a dedicated REST endpoint could provide a more stable contract.
  3. Data fetching: It would be useful to decide early whether the new UIs should adopt a server-state library such as TanStack Query. I left an inline comment with more context.
  4. Storybook: As the shared component library grows, what do you think about setting up Storybook? I think this could help develop, review, and document shared components in isolation.

Happy to help with any of these follow-ups.


export interface OzoneManagerInfoBean {
RpcPort: string;
RatisRoles: string;

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.

I tried to run this against a real OM, and found the Overview page renders blank. It seems that RatisRoles in the JMX bean is an array of tuples, not a string:

I would change RatisRoles: string to string[][] and update the mock.

* `{ HostName: h1 | Node-Id: om1 | Ratis-Port : 9872 | Role: FOLLOWER } {...}`.
*/
export function parseRatisRoles(raw: string, currentNodeId?: string): RatisRole[] {
const groups = raw?.match(/\{[^}]*\}/g) ?? [];

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.

Same issue as above: with a real OM, raw.match(...) throws TypeError: raw.match is not a function. Could we parse the tuple array here?

Comment thread ozone-ui/packages/om/vite.config.ts Outdated
},
// JMX endpoint — proxied to the json-server mock in dev (see mock/server.cjs).
'/jmx': {
target: 'http://localhost:9878',

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.

I hit a port conflict here when trying to run this against a real OM. 9878 is also the S3 Gateway port in the Compose cluster, and pointing at a real OM requires editing this file.

Would it make sense to support an env override? e.g. target: process.env.OM_JMX_TARGET ?? 'http://localhost:9878'?

}
const size = Number(match[1]);
const unit = (match[2] ?? 'B').toUpperCase();
const megabytes = unit === 'G' ? size * 1024 : unit === 'K' ? Math.round(size / 1024) : size;

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.

I noticed that the JVM interprets an -Xmx value without a suffix as bytes, but the B case currently labels the raw value as MB. For example, -Xmx2511000000 would be displayed as 2,511,000,000 MB.

I think we should divide byte values by 1024x1024 before displaying them as MB and could probably consider using a switch/case for better readability

  function formatHeap(xmx: string | undefined): string {
    if (!xmx) {
      return 'Not set';
    }

    const match = xmx.slice(4).match(/^(\d+)\s*([kKmMgG])?/);
    if (!match) {
      return xmx.slice(4);
    }

    const size = Number(match[1]);
    const unit = (match[2] ?? 'B').toUpperCase();

    let megabytes: number;
    switch (unit) {
      case 'G':
        megabytes = size * 1024;
        break;
      case 'M':
        megabytes = size;
        break;
      case 'K':
        megabytes = size / 1024;
        break;
      default:
        megabytes = size / (1024 * 1024);
        break;
    }

    return `${Math.round(megabytes).toLocaleString('en-US')} MB`;
  }

loading,
error,
} = useJmxBean<OzoneManagerInfoBean>(JMX_QUERY.omInfo, refreshToken);
const { data: ratis } = useJmxBean<RatisServerBean>(JMX_QUERY.ratisServer, refreshToken);

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.

These hooks only consume data, so failures in the Ratis or leader-election queries appear as or missing fields. Could we handle these query errors, at least by showing a partial-data warning? Or do we plan to do this in a follow-up?

Comment thread ozone-ui/packages/om/src/api/jmx.ts Outdated
/** Fetch a single MBean for a query (the first bean), or `undefined`. */
export async function fetchJmxBean<T>(qry: string): Promise<T | undefined> {
const beans = await fetchJmxBeans<T>(qry);
return beans[0];

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.

When no MBean matches a query, the JMX servlet can return 200 OK with {"beans":[]}.

I think this might happen when an MBean isn't yet registered, during service reinitialization, or when the UI and OM expose different MBean versions. Would it be worth representing “no matching bean” as an explicit empty state and showing a message such as No JMX data available?

dataIndex: 'hostName',
key: 'hostName',
render: (hostName: string, row) => (
<TextLink href="#" style={{ fontWeight: row.isCurrent ? 600 : undefined }}>

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.

Is href="#" intentional here, or a placeholder for now?

If the real link is planned for a follow-up, would plain text work better for now so it doesn't read as clickable?

Image

Comment thread ozone-ui/packages/om/src/api/jmx.ts Outdated
* Fetch MBeans for a query, sharing a single request across all callers that ask
* for the same query. Failed requests are evicted so they can be retried.
*/
export function fetchJmxBeans<T>(qry: string): Promise<T[]> {

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.

Since this is the first data-fetching page in the new UI, though, do we want to consider a library like TanStack Query for this layer?

As more OM and SCM pages add API queries, this may become difficult to maintain and introduce edge cases such as the refresh/cache race.

Other ASF React UIs already use this approach, including the Apache Airflow UI and Apache APISIX Dashboard. Since this introduces a dependency, it could also be tracked as a follow-up if it is outside this PR’s scope.

* Renders a section's async state: a skeleton while loading, an error alert on
* failure, or the resolved content.
*/
export const SectionBody: React.FC<SectionBodyProps> = ({

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.

SectionBody handles loading and errors, but not the empty state here. I think we can consider centralizing it so that state management is all in this component.

For example, adding an isEmpty prop rendering a small "No data" placeholder would make
this visible with a minimal diff.

Longer term, if we adopt TanStack Query (see the other comment), its Suspense mode
would restructure this nicely, like below:

<ErrorBoundary fallback={<Alert ... />}>
  <Suspense fallback={<Skeleton rows={2} />}>
    <InstanceDetailsContent />  {/* useSuspenseQuery inside, data typed non-null */}
  </Suspense>
</ErrorBoundary>

Not asking for that in this PR, just sharing some thoughts. 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants