Skip to content

Commit 8803ba9

Browse files
committed
feat(aurora): add branding slots, appName and pageFooter slot
1 parent 7b80141 commit 8803ba9

11 files changed

Lines changed: 143 additions & 90 deletions

File tree

.changeset/branding-slots.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@cobaltcore-dev/aurora": minor
3+
---
4+
5+
Add branding slots, appName prop, and pageFooter slot to AuroraApp.
6+
7+
- `slots.logo`: consumers can now supply a custom logo component rendered in the page header, replacing the default Aurora SVG
8+
- `slots.pageFooter`: consumers can now supply a custom footer component rendered at the bottom of the page, replacing the default empty footer
9+
- `appName`: string prop that replaces the hardcoded "Aurora" text in the header breadcrumb and logo title

apps/dashboard/.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ DEFAULT_ENDPOINT_INTERFACE="public"
33
PORT="4001"
44
# Prefix VITE_ so Vite exposes this to the browser via import.meta.env
55
VITE_BFF_ENDPOINT="/polaris-bff"
6+
VITE_APP_TITLE="Aurora"
67
CEPH_S3_ENDPOINT="https://rgw.example.com"
78
# Ceph region identifier for AWS Signature V4 signing and LocationConstraint
89
# SAP Converged Cloud examples:
14.7 KB
Binary file not shown.

apps/dashboard/src/client/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta charset="UTF-8" />
55
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico" />
66
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7-
<title>Dashboard</title>
7+
<title>%VITE_APP_TITLE%</title>
88
</head>
99
<body>
1010
<div id="app"></div>

commitlint.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export const scopes = [
2424
"gardener",
2525
"network",
2626
"template",
27-
"aurora-portal",
27+
"aurora",
2828
"portal",
2929
"aurora-sdk",
3030
"signal-openstack",

packages/aurora/README.md

Lines changed: 73 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,31 @@ npm install react react-dom fastify
1818

1919
Aurora has two entry points: `server` and `client`.
2020

21-
### Server (`src/server/server.ts`)
21+
### Server
2222

2323
```ts
2424
import path from "path"
2525
import { createServer } from "@cobaltcore-dev/aurora/server"
2626

27-
createServer({
28-
viteRoot: path.resolve(__dirname, "../.."), // root of your app (where dist/client lives)
29-
identityEndpoint: process.env.IDENTITY_ENDPOINT, // OpenStack Keystone URL
30-
bffEndpoint: process.env.BFF_ENDPOINT, // tRPC prefix, default "/polaris-bff"
31-
defaultEndpointInterface: process.env.DEFAULT_ENDPOINT_INTERFACE, // "public" | "internal"
32-
proxyUrl: process.env.GLOBAL_AGENT_HTTP_PROXY, // optional HTTP proxy (dev only)
33-
cephRegion: process.env.CEPH_REGION, // Ceph/S3 region for object storage
34-
imageMetadataExcludedProperties: process.env.IMAGE_METADATA_EXCLUDED_PROPERTIES, // comma-separated
35-
insecureCookies: process.env.INSECURE_COOKIES === "true", // disable Secure flag locally
36-
}).then((server) => server.listen({ host: "0.0.0.0", port: 4000 }))
27+
const server = await createServer({
28+
identityEndpoint: "https://keystone.example.com/v3/",
29+
policyDir: path.resolve(__dirname, "../policies"),
30+
})
31+
32+
server.listen({ host: "0.0.0.0", port: 4000 })
3733
```
3834

39-
### Client (`src/client/App.tsx`)
35+
### Client
4036

4137
```tsx
4238
import { AuroraApp } from "@cobaltcore-dev/aurora/client"
43-
import "@cobaltcore-dev/aurora/client/style.css" // include once at your app root
4439

4540
export function App() {
4641
return (
4742
<AuroraApp
48-
bffEndpoint="/polaris-bff" // must match the server's bffEndpoint
49-
theme="theme-light" // "theme-light" | "theme-dark"
50-
onThemeChange={(theme) => {
51-
// called when user toggles — persist however you like
52-
localStorage.setItem("theme", theme)
53-
}}
43+
bffEndpoint="/polaris-bff"
44+
theme="theme-light"
45+
onThemeChange={(theme) => localStorage.setItem("theme", theme)}
5446
/>
5547
)
5648
}
@@ -60,80 +52,84 @@ export function App() {
6052

6153
### `createServer(config)`
6254

63-
| Option | Type | Default | Description |
64-
| --------------------------------- | --------- | -------------------------- | ---------------------------------------------------------------- |
65-
| `identityEndpoint` | `string` || OpenStack Keystone v3 URL (required) |
66-
| `bffEndpoint` | `string` | `"/polaris-bff"` | URL prefix for all tRPC routes |
67-
| `viteRoot` | `string` | `__dirname/../../` | Directory that contains `dist/client/` in production |
68-
| `defaultEndpointInterface` | `string` | `"public"` | OpenStack service catalog interface |
69-
| `proxyUrl` | `string` || HTTP proxy for OpenStack calls (dev only, ignored in production) |
70-
| `cephRegion` | `string` || Ceph RGW region for S3 operations |
71-
| `imageMetadataExcludedProperties` | `string` || Comma-separated image metadata keys to hide in the UI |
72-
| `cookieName` | `string` | `"dashboard-session-auth"` | Override the session cookie name |
73-
| `crossDomainCookie` | `boolean` | `true` | Share cookie across subdomains |
74-
| `insecureCookies` | `boolean` | `false` | Disable `Secure` flag — only for HTTP-only local dev |
55+
| Option | Type | Required | Default | Description |
56+
| --------------------------------- | --------- | -------- | -------------------------- | ---------------------------------------------------------------- |
57+
| `identityEndpoint` | `string` | yes || OpenStack Keystone v3 URL |
58+
| `policyDir` | `string` | yes || Absolute path to a directory of OpenStack policy YAML files |
59+
| `bffEndpoint` | `string` | no | `"/polaris-bff"` | URL prefix for all tRPC routes |
60+
| `viteRoot` | `string` | no || Directory that contains `dist/client/` in production |
61+
| `defaultEndpointInterface` | `string` | no | `"public"` | OpenStack service catalog interface |
62+
| `proxyUrl` | `string` | no || HTTP proxy for OpenStack calls (dev only, ignored in production) |
63+
| `cephRegion` | `string` | no || Ceph RGW region for S3 operations |
64+
| `imageMetadataExcludedProperties` | `string` | no || Comma-separated image metadata keys to hide in the UI |
65+
| `cookieName` | `string` | no | `"dashboard-session-auth"` | Override the session cookie name |
66+
| `crossDomainCookie` | `boolean` | no | `true` | Share cookie across subdomains |
67+
| `insecureCookies` | `boolean` | no | `false` | Disable `Secure` flag — only for HTTP-only local dev |
7568

7669
### `<AuroraApp />`
7770

78-
| Prop | Type | Default | Description |
79-
| --------------- | ------------------------------- | ---------------- | -------------------------------------- |
80-
| `bffEndpoint` | `string` | `"/polaris-bff"` | Must match the server's `bffEndpoint` |
81-
| `theme` | `"theme-light" \| "theme-dark"` | `"theme-light"` | Initial theme |
82-
| `onThemeChange` | `(theme) => void` || Called when the user toggles the theme |
71+
| Prop | Type | Default | Description |
72+
| --------------- | ------------------------------- | ---------------- | -------------------------------------------------- |
73+
| `bffEndpoint` | `string` | `"/polaris-bff"` | Must match the server's `bffEndpoint` |
74+
| `theme` | `"theme-light" \| "theme-dark"` | `"theme-light"` | Initial theme |
75+
| `onThemeChange` | `(theme) => void` || Called when the user toggles the theme |
76+
| `appName` | `string` | `"Aurora"` | App name shown in the header breadcrumb and logo |
77+
| `slots` | `Slots` || Optional UI extension points — see [Slots](#slots) |
8378

84-
## Environment variables
79+
## Slots
8580

86-
Your app reads these from `.env` and passes them to `createServer()`. Aurora itself never reads environment variables.
81+
Slots let you inject your own React components into specific locations inside `AuroraApp` without forking the package. Each slot receives an `auroraContext` object with a `client` (tRPC client) for making API calls.
8782

88-
```env
89-
IDENTITY_ENDPOINT="https://keystone.example.com/v3/"
90-
DEFAULT_ENDPOINT_INTERFACE="public"
91-
BFF_ENDPOINT="/polaris-bff"
92-
PORT="4000"
83+
```tsx
84+
import type { SlotProps } from "@cobaltcore-dev/aurora/client"
9385

94-
# Object storage (optional)
95-
CEPH_REGION="ceph-objectstore-st1-region-1"
86+
function MyLogo(_props: SlotProps) {
87+
return <img src="/my-logo.svg" alt="My App" style={{ height: "1.5rem" }} />
88+
}
9689

97-
# Local dev only
98-
INSECURE_COOKIES=true
99-
# GLOBAL_AGENT_HTTP_PROXY=http://localhost:8888
100-
```
90+
function MyBanner({ auroraContext }: SlotProps) {
91+
// auroraContext.client gives you access to the tRPC API
92+
return <div>Custom sidebar content</div>
93+
}
10194

102-
The `VITE_BFF_ENDPOINT` variable (prefixed with `VITE_`) is read by Vite and passed to the client:
95+
function MyFooter(_props: SlotProps) {
96+
return (
97+
<footer>
98+
<a href="/imprint">Imprint</a> · <a href="/privacy">Privacy</a>
99+
</footer>
100+
)
101+
}
103102

104-
```env
105-
VITE_BFF_ENDPOINT="/polaris-bff"
103+
;<AuroraApp
104+
slots={{
105+
logo: MyLogo,
106+
sideNavBanner: MyBanner,
107+
pageFooter: MyFooter,
108+
}}
109+
/>
106110
```
107111

108-
```tsx
109-
<AuroraApp bffEndpoint={import.meta.env.VITE_BFF_ENDPOINT} />
110-
```
112+
### Available slots
111113

112-
## Vite config
114+
| Slot | Location | Renders in shadow DOM |
115+
| --------------- | ----------------------------------------------- | --------------------- |
116+
| `logo` | Page header, replacing the default Aurora logo | No |
117+
| `sideNavBanner` | Bottom of the project side navigation | Yes |
118+
| `pageFooter` | Page footer, replacing the default empty footer | No |
113119

114-
In development, point Vite at your own server plugin. In production no extra plugins are needed — aurora ships pre-built CSS and assets.
120+
**Shadow DOM isolation:** Slots rendered in a shadow DOM cannot inherit styles from the host page. If your slot component uses a CSS framework, inject the styles inline:
115121

116-
```js
117-
// vite.config.mjs
118-
import { defineConfig } from "vite"
119-
import react from "@vitejs/plugin-react-swc"
120-
import viteFastify from "@fastify/vite/plugin"
121-
122-
export default defineConfig(({ mode }) => ({
123-
root: "./src/client",
124-
build: { outDir: "../../dist/client" },
125-
plugins: [mode !== "production" && viteFastify(), react()],
126-
}))
127-
```
128-
129-
## Running the app
130-
131-
```bash
132-
# Development (hot-reload)
133-
tsx watch --env-file=.env src/server/server.ts
122+
```tsx
123+
import styles from "my-lib/styles.css?inline"
134124

135-
# Production
136-
NODE_ENV=production tsx --env-file=.env src/server/server.ts
125+
function MyBanner(_props: SlotProps) {
126+
return (
127+
<>
128+
<style>{styles}</style>
129+
<div>...</div>
130+
</>
131+
)
132+
}
137133
```
138134

139135
## License

packages/aurora/src/client/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ type AppProps = {
2323
bffEndpoint?: string
2424
onThemeChange?: (theme: "theme-dark" | "theme-light") => void
2525
slots?: Slots
26+
appName?: string
2627
}
2728

2829
// Additional navigation items can be added here and will be passed to the layout via context
@@ -92,6 +93,7 @@ const App = (props: AppProps) => {
9293
navItems={navItems}
9394
handleThemeToggle={handleThemeToggle}
9495
slots={props.slots}
96+
appName={props.appName}
9597
/>
9698
</AuthProvider>
9799
</QueryClientProvider>
@@ -107,11 +109,13 @@ function AppInner({
107109
navItems,
108110
handleThemeToggle,
109111
slots,
112+
appName,
110113
}: {
111114
router: ReturnType<typeof createAuroraRouter>
112115
navItems: NavigationItem[]
113116
handleThemeToggle: (theme: string) => void
114117
slots?: Slots
118+
appName?: string
115119
}) {
116120
const auth = useAuth()
117121

@@ -122,6 +126,7 @@ function AppInner({
122126
navItems,
123127
handleThemeToggle,
124128
slots,
129+
appName,
125130
}
126131

127132
return <RouterProvider router={router} context={routerContext} />

packages/aurora/src/client/AuroraApp.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,36 @@ import type { FC } from "react"
22
import type { TrpcClient } from "./trpcClient"
33
import App from "./App"
44

5+
/** Context object passed to every slot component. */
56
export type SlotProps = {
67
auroraContext: {
8+
/** tRPC client for making API calls to the Aurora BFF. */
79
client: TrpcClient
810
}
911
}
1012

13+
/** Named extension points where consumers can inject their own React components. */
1114
export type Slots = {
15+
/** Replaces the default Aurora logo in the page header. Renders outside shadow DOM — inherits header styles. */
16+
logo?: FC<SlotProps>
17+
/** Rendered at the bottom of the project side navigation. Renders inside a shadow DOM. */
1218
sideNavBanner?: FC<SlotProps>
19+
/** Replaces the default page footer. Renders outside shadow DOM — inherits page styles. */
20+
pageFooter?: FC<SlotProps>
1321
}
1422

23+
/** Props for the top-level `<AuroraApp />` component. */
1524
export type AuroraAppProps = {
25+
/** Initial theme. Defaults to `"theme-light"`. */
1626
theme?: "theme-dark" | "theme-light"
27+
/** URL prefix for all tRPC routes. Must match the server's `bffEndpoint`. Defaults to `"/polaris-bff"`. */
1728
bffEndpoint?: string
29+
/** Called when the user toggles the theme. Use this to persist the selection. */
1830
onThemeChange?: (theme: "theme-dark" | "theme-light") => void
31+
/** Optional UI extension points. */
1932
slots?: Slots
33+
/** App name shown in the header breadcrumb and as the default logo title. Defaults to `"Aurora"`. */
34+
appName?: string
2035
}
2136

2237
export const AuroraApp: FC<AuroraAppProps> = App

packages/aurora/src/client/components/Slot.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,22 @@ function SlotShadowRoot({ children }: { children: ReactNode }) {
1919
)
2020
}
2121

22-
export function Slot({ component: Component }: { component: FC<SlotProps> }) {
22+
export function Slot({
23+
component: Component,
24+
useShadowDOM = true,
25+
}: {
26+
component: FC<SlotProps>
27+
useShadowDOM?: boolean
28+
}) {
2329
const { trpcClient } = useRouteContext({ strict: false })
2430

2531
if (!trpcClient) return null
2632

27-
return (
28-
<SlotShadowRoot>
29-
<Component auroraContext={{ client: trpcClient }} />
30-
</SlotShadowRoot>
31-
)
33+
const content = <Component auroraContext={{ client: trpcClient }} />
34+
35+
if (!useShadowDOM) {
36+
return content
37+
}
38+
39+
return <SlotShadowRoot>{content}</SlotShadowRoot>
3240
}

packages/aurora/src/client/components/navigation/MainNavigation.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import { isMatch, Link, MakeRouteMatchUnion, useRouterState } from "@tanstack/re
44
import { UserMenu } from "./UserMenu"
55
import { PageHeader, ThemeToggle } from "@cloudoperators/juno-ui-components/index"
66
import { cn } from "@/client/utils/cn"
7+
import type { Slots } from "../../AuroraApp"
8+
import { Slot } from "../Slot"
79

810
interface NavigationProps {
911
items: NavigationItem[]
1012
handleThemeToggle?: (theme: string) => void
13+
appName?: string
14+
slots?: Slots
1115
}
1216

1317
const textColorClass = "text-theme-pageheader-appname-default"
@@ -38,18 +42,25 @@ function getProject(matches: MakeRouteMatchUnion[]) {
3842
}
3943
}
4044

41-
export function MainNavigation({ items, handleThemeToggle }: NavigationProps) {
45+
export function MainNavigation({ items, handleThemeToggle, appName, slots }: NavigationProps) {
4246
const matches = useRouterState({ select: (s) => s.matches })
4347
const domain = getDomain(matches)
4448
const project = getProject(matches)
49+
const displayName = appName ?? "Aurora"
4550

4651
return (
4752
<PageHeader
48-
logo={<Logo className={cn("h-6 w-6 shrink-0 fill-current", textColorClass)} title="Aurora" />}
53+
logo={
54+
slots?.logo ? (
55+
<Slot component={slots.logo} useShadowDOM={false} />
56+
) : (
57+
<Logo className={cn("h-6 w-6 shrink-0 fill-current", textColorClass)} title={displayName} />
58+
)
59+
}
4960
applicationName={
5061
<div className="flex flex-nowrap items-center space-x-2">
5162
<Link to="/projects" className={cn("shrink-0", textColorClass, textHoverClass)}>
52-
Aurora
63+
{displayName}
5364
</Link>
5465
{domain && (
5566
<>

0 commit comments

Comments
 (0)