Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ components/**
packages/**
!packages/gateway-management-ui/
!packages/gateway-management-ui/**
!packages/operational-dashboard-ui/
!packages/operational-dashboard-ui/**
!components/web-console/
!components/web-console/**
!scripts/bootstrap_pnpm.sh
Expand Down
15 changes: 15 additions & 0 deletions .github/component-paths.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@
".github/workflows/lint.yml"
]
},
"operational_dashboard_ui": {
"directory": "packages/operational-dashboard-ui",
"lint_job": "lint-operational-dashboard-ui",
"paths": [
"packages/operational-dashboard-ui/**",
"package.json",
"pnpm-lock.yaml",
"pnpm-workspace.yaml",
"Makefile",
".github/component-paths.json",
".github/scripts/detect-components.sh",
".github/workflows/lint.yml"
]
},
"e2e": {
"directory": "tests/e2e",
"paths": [
Expand Down Expand Up @@ -112,6 +126,7 @@
"paths": [
"components/web-console/**",
"packages/gateway-management-ui/**",
"packages/operational-dashboard-ui/**",
"components/sdk-typescript/**",
"package.json",
"pnpm-lock.yaml",
Expand Down
24 changes: 23 additions & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
cli: ${{ steps.detect.outputs.cli }}
control_plane: ${{ steps.detect.outputs.control_plane }}
gateway_management_ui: ${{ steps.detect.outputs.gateway_management_ui }}
operational_dashboard_ui: ${{ steps.detect.outputs.operational_dashboard_ui }}
pr_test: ${{ steps.detect.outputs.pr_test }}
sdk_typescript: ${{ steps.detect.outputs.sdk_typescript }}
web_console: ${{ steps.detect.outputs.web_console }}
Expand Down Expand Up @@ -189,6 +190,25 @@ jobs:
- name: Run gateway management UI package quality gates
run: pnpm --filter @openshift-online/hypershell-gateway-management-ui check

lint-operational-dashboard-ui:
name: Operational dashboard UI package quality gates
needs: detect-changes
if: needs.detect-changes.outputs.operational_dashboard_ui == 'true'
runs-on: ubuntu-24.04
steps:
- name: Check out repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version-file: .node-version
- name: Install pinned pnpm
run: bash scripts/bootstrap_pnpm.sh
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run operational dashboard UI package quality gates
run: pnpm --filter @openshift-online/hypershell-operational-dashboard-ui check

lint-pr-test:
name: PR test script validation
needs: detect-changes
Expand Down Expand Up @@ -254,6 +274,7 @@ jobs:
- lint-cli
- lint-control-plane
- lint-gateway-management-ui
- lint-operational-dashboard-ui
- lint-pr-test
- lint-sdk-typescript
- lint-web-console
Expand All @@ -266,11 +287,12 @@ jobs:
CLI_RESULT: ${{ needs.lint-cli.result }}
CONTROL_PLANE_RESULT: ${{ needs.lint-control-plane.result }}
GATEWAY_MANAGEMENT_UI_RESULT: ${{ needs.lint-gateway-management-ui.result }}
OPERATIONAL_DASHBOARD_UI_RESULT: ${{ needs.lint-operational-dashboard-ui.result }}
PR_TEST_RESULT: ${{ needs.lint-pr-test.result }}
SDK_TYPESCRIPT_RESULT: ${{ needs.lint-sdk-typescript.result }}
WEB_CONSOLE_RESULT: ${{ needs.lint-web-console.result }}
run: |
for result in "${DETECTION_RESULT}" "${API_SERVER_RESULT}" "${CLI_RESULT}" "${CONTROL_PLANE_RESULT}" "${GATEWAY_MANAGEMENT_UI_RESULT}" "${PR_TEST_RESULT}" "${SDK_TYPESCRIPT_RESULT}" "${WEB_CONSOLE_RESULT}"; do
for result in "${DETECTION_RESULT}" "${API_SERVER_RESULT}" "${CLI_RESULT}" "${CONTROL_PLANE_RESULT}" "${GATEWAY_MANAGEMENT_UI_RESULT}" "${OPERATIONAL_DASHBOARD_UI_RESULT}" "${PR_TEST_RESULT}" "${SDK_TYPESCRIPT_RESULT}" "${WEB_CONSOLE_RESULT}"; do
if [[ "${result}" == failure || "${result}" == cancelled ]]; then
echo "One or more lint jobs failed or were cancelled."
exit 1
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ checks manually with `make check`.
- `components/api-server/` - Go REST + gRPC API microservice (rh-trex-ai framework), PostgreSQL-backed
- `components/control-plane/` - Go service, watches API server via gRPC and reconciles gateway resources into K8s
- `packages/gateway-management-ui/` - Private reusable React package containing canonical gateway management workflows
- `packages/operational-dashboard-ui/` - Private reusable React package containing the operational metrics dashboard
- `specs/` - Desired state of the system ([platform](specs/platform/), [standards](specs/standards/))
- `skills/` - Agent skills: [reconcile](skills/build/reconcile), [spec](skills/plan/spec), [full-stack-pipeline](skills/build/full-stack-pipeline), [dev-cluster](skills/build/dev-cluster), [ibm-cluster](skills/deploy/ibm-cluster), [deploy-cluster](skills/deploy/deploy-cluster), [cloud-hub-ingress-bootstrap](skills/deploy/cloud-hub-ingress-bootstrap), [review](skills/review/review-guidance), [amber-review](skills/review/amber-review), [ui-standards](skills/review/ui-standards), [tooling](skills/tooling/)
- `apm.yml` - APM manifest declaring upstream skill dependencies
Expand Down Expand Up @@ -99,6 +100,7 @@ cd components/control-plane && go vet ./... # Vet
# All Components
make build-all # Build all container images
pnpm --filter @openshift-online/hypershell-gateway-management-ui check # Verify reusable gateway UI
pnpm --filter @openshift-online/hypershell-operational-dashboard-ui check # Verify operational dashboard UI
make kind-up # Start local Kind cluster
make kind-down # Destroy Kind cluster
make kind-status # Show cluster status
Expand Down
2 changes: 2 additions & 0 deletions components/web-console/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ WORKDIR /app
COPY --chown=${CONTAINER_DEFAULT_USER} package.json pnpm-lock.yaml pnpm-workspace.yaml ./
COPY --chown=${CONTAINER_DEFAULT_USER} components/sdk-typescript/package.json components/sdk-typescript/package.json
COPY --chown=${CONTAINER_DEFAULT_USER} packages/gateway-management-ui/package.json packages/gateway-management-ui/package.json
COPY --chown=${CONTAINER_DEFAULT_USER} packages/operational-dashboard-ui/package.json packages/operational-dashboard-ui/package.json
COPY --chown=${CONTAINER_DEFAULT_USER} components/web-console/package.json components/web-console/package.json
COPY --chown=${CONTAINER_DEFAULT_USER} components/web-console/bff/package.json components/web-console/bff/package.json
COPY --chown=${CONTAINER_DEFAULT_USER} components/web-console/domain-probes/package.json components/web-console/domain-probes/package.json
Expand All @@ -21,6 +22,7 @@ RUN pnpm install --frozen-lockfile

COPY --chown=${CONTAINER_DEFAULT_USER} components/sdk-typescript components/sdk-typescript
COPY --chown=${CONTAINER_DEFAULT_USER} packages/gateway-management-ui packages/gateway-management-ui
COPY --chown=${CONTAINER_DEFAULT_USER} packages/operational-dashboard-ui packages/operational-dashboard-ui
COPY --chown=${CONTAINER_DEFAULT_USER} components/web-console components/web-console
COPY --chown=${CONTAINER_DEFAULT_USER} images/brand/logo.png images/brand/logo.png

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type {
DashboardControlPlane,
DashboardInvocationContext,
} from "@openshift-online/hypershell-operational-dashboard-ui";
import { mockOperationalDashboardMetrics } from "@openshift-online/hypershell-operational-dashboard-ui/fixtures";

export function createMockDashboardControlPlane(): DashboardControlPlane {
return {
async getOperationalMetrics(context: DashboardInvocationContext) {
context.signal?.throwIfAborted();

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.

This 2s artificial delay ("just for demos") sits in the composition wired into the real application shell, so it ships in the actual web console, not just Storybook. It is also not abort-aware: throwIfAborted() runs before the wait but a cancellation during the 2s is ignored, so a navigation-away still resolves the promise. Remove the delay or move this artificial latency into a Storybook/fixture-only adapter.

await new Promise((resolve) => setTimeout(resolve, 2000)); // This is just for demos for now

return {
...mockOperationalDashboardMetrics,
lastSuccessfulRefresh: new Date(),
};
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe("session adapter", () => {
);
expect(session).toEqual({
authenticated: true,
authEnabled: true,
expiresAt: 1_723_401_600,
roles: ["hypershell-admins"],
user: {
Expand All @@ -55,7 +56,11 @@ describe("session adapter", () => {
const session =
await createSessionAdapter(fetchImplementation).getSession();

expect(session).toEqual({ authenticated: false, roles: [] });
expect(session).toEqual({
authenticated: false,
authEnabled: true,
roles: [],
});
});

it("treats an absent endpoint (no-auth mode) as no session", async () => {
Expand All @@ -66,7 +71,11 @@ describe("session adapter", () => {
const session =
await createSessionAdapter(fetchImplementation).getSession();

expect(session).toEqual({ authenticated: false, roles: [] });
expect(session).toEqual({
authenticated: false,
authEnabled: false,
roles: [],
});
});

it("treats a network failure as no session", async () => {
Expand All @@ -77,6 +86,10 @@ describe("session adapter", () => {
const session =
await createSessionAdapter(fetchImplementation).getSession();

expect(session).toEqual({ authenticated: false, roles: [] });
expect(session).toEqual({
authenticated: false,
authEnabled: false,
roles: [],
});
});
});
23 changes: 18 additions & 5 deletions components/web-console/app/adapters/session/session-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface BrowserSessionUser {

export interface BrowserSession {
authenticated: boolean;
/** True when the BFF exposes `/auth/session` (OIDC mode). */
authEnabled: boolean;
expiresAt?: number;
roles: string[];
user?: BrowserSessionUser;
Expand All @@ -24,7 +26,17 @@ export interface SessionGateway {
getSession(signal?: AbortSignal): Promise<BrowserSession>;
}

const unauthenticated: BrowserSession = { authenticated: false, roles: [] };
const noAuthSession: BrowserSession = {
authenticated: false,
authEnabled: false,
roles: [],
};

const unauthenticatedSession: BrowserSession = {
authenticated: false,
authEnabled: true,
roles: [],
};

function optionalString(value: unknown): string | undefined {
return typeof value === "string" && value.length > 0 ? value : undefined;
Expand All @@ -36,7 +48,7 @@ function toBrowserSession(body: unknown): BrowserSession {
body === null ||
(body as { authenticated?: unknown }).authenticated !== true
) {
return unauthenticated;
return unauthenticatedSession;
}
const record = body as {
expires_at?: unknown;
Expand Down Expand Up @@ -67,6 +79,7 @@ function toBrowserSession(body: unknown): BrowserSession {

return {
authenticated: true,
authEnabled: true,
...(typeof record.expires_at === "number"
? { expiresAt: record.expires_at }
: {}),
Expand All @@ -91,15 +104,15 @@ export function createSessionAdapter(
});
} catch {
// Network failure or absent endpoint (no-auth mode): treat as no session.
return unauthenticated;
return noAuthSession;
}
if (!response.ok) {
return unauthenticated;
return response.status === 404 ? noAuthSession : unauthenticatedSession;
}
try {
return toBrowserSession(await response.json());
} catch {
return unauthenticated;
return unauthenticatedSession;
}
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { createDashboardOperations } from "@openshift-online/hypershell-operational-dashboard-ui";

import { createMockDashboardControlPlane } from "../adapters/mock/dashboard-control-plane";

export const dashboardOperations = createDashboardOperations({

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.

[Minor] Dashboard domain probes are never wired into the fan-out publisher in production. createDashboardOperations is called with only controlPlane, so probes defaults to noopDashboardProbePublisher. The dashboard already emits typed workflow probes (dashboard.workflow.started/completed) and layout probes (dashboard.layout.template.*), but with a no-op publisher these facts are silently dropped in the browser. Compare gateway-composition.ts, which wires probes: gatewayObservability.probes into the fan-out port. CLAUDE.md's "Domain probes for UI observability" convention expects these facts to be published through the fan-out port. Since metrics are still mock data, this is Minor, but please wire the probe publisher (here and in the DashboardUiProvider below) before/with the live-API follow-up so dashboard observability isn't a no-op. Confidence: High.

controlPlane: createMockDashboardControlPlane(),

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.

[Major] The mock control-plane adapter is wired here as the production dependency, so authenticated admins are shown fabricated metrics (see the artificial 2s delay in mock/dashboard-control-plane.ts) presented as live data. The PR body notes this is interim, but there's no visible "sample data" indicator or feature flag. Consider gating the route behind a flag or rendering an unmistakable sample-data banner until the live adapter lands, so admins don't act on fake numbers.

});
Loading
Loading