@cobaltcore-dev/aurora 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -9
- package/dist/client/App.d.ts +2 -2
- package/dist/client/AuroraApp.d.ts +33 -13
- package/dist/client/{AuthProvider-BS5OVv8n.mjs → AuthProvider-CVSAwW9f.mjs} +22 -8
- package/dist/client/AuthProvider-CVSAwW9f.mjs.map +1 -0
- package/dist/client/{ContentHeader-BHMUzd9H.mjs → ContentHeader-CPJbgYWn.mjs} +18 -18
- package/dist/client/{ContentHeader-BHMUzd9H.mjs.map → ContentHeader-CPJbgYWn.mjs.map} +1 -1
- package/dist/client/DeleteFlavorModal-V44QCJBd.mjs +773 -0
- package/dist/client/DeleteFlavorModal-V44QCJBd.mjs.map +1 -0
- package/dist/client/DynamicBreadcrumbContext-CVcUQFou.mjs +26 -0
- package/dist/client/DynamicBreadcrumbContext-CVcUQFou.mjs.map +1 -0
- package/dist/client/{FiltersInput-B8CNBg1L.mjs → FiltersInput-CKI6zWnm.mjs} +18 -18
- package/dist/client/{FiltersInput-B8CNBg1L.mjs.map → FiltersInput-CKI6zWnm.mjs.map} +1 -1
- package/dist/client/FloatingIpActionModals-Ch8uozH9.mjs +466 -0
- package/dist/client/FloatingIpActionModals-Ch8uozH9.mjs.map +1 -0
- package/dist/client/ImageToastNotifications-m6c3tVPb.mjs +1152 -0
- package/dist/client/ImageToastNotifications-m6c3tVPb.mjs.map +1 -0
- package/dist/client/SortInput-4g7nSTHW.mjs +34 -0
- package/dist/client/SortInput-4g7nSTHW.mjs.map +1 -0
- package/dist/client/{TwoColumnDescriptionList-DiDx3PnH.mjs → TwoColumnDescriptionList-DKS42RKa.mjs} +10 -4
- package/dist/client/TwoColumnDescriptionList-DKS42RKa.mjs.map +1 -0
- package/dist/client/_-CAsCx-Ay.mjs +6 -0
- package/dist/client/_-CAsCx-Ay.mjs.map +1 -0
- package/dist/client/_auth-SrDjHL6j.mjs +12 -0
- package/dist/client/_auth-SrDjHL6j.mjs.map +1 -0
- package/dist/client/{_flavorId-BN_qessZ.mjs → _flavorId-Dg7MS0Af.mjs} +3 -9
- package/dist/client/_flavorId-Dg7MS0Af.mjs.map +1 -0
- package/dist/client/_flavorId-kHl-b8rI.mjs +232 -0
- package/dist/client/_flavorId-kHl-b8rI.mjs.map +1 -0
- package/dist/client/{_floatingIpId-CwDGHkWm.mjs → _floatingIpId-C9xfRWot.mjs} +3 -9
- package/dist/client/_floatingIpId-C9xfRWot.mjs.map +1 -0
- package/dist/client/_floatingIpId-DNn6ABCB.mjs +196 -0
- package/dist/client/_floatingIpId-DNn6ABCB.mjs.map +1 -0
- package/dist/client/_imageId-8JIbFmGB.mjs +39 -0
- package/dist/client/_imageId-8JIbFmGB.mjs.map +1 -0
- package/dist/client/{_imageId-BtJ7GmYB.mjs → _imageId-BBAlRioC.mjs} +217 -201
- package/dist/client/_imageId-BBAlRioC.mjs.map +1 -0
- package/dist/client/{_projectId-Byt3GQDW.mjs → _projectId-7Nl1MQTJ.mjs} +5 -5
- package/dist/client/_projectId-7Nl1MQTJ.mjs.map +1 -0
- package/dist/client/_projectId-B3wmSala.mjs +281 -0
- package/dist/client/_projectId-B3wmSala.mjs.map +1 -0
- package/dist/client/_projectId-DPzv0CCp.mjs +77 -0
- package/dist/client/_projectId-DPzv0CCp.mjs.map +1 -0
- package/dist/client/_projectId-DfeVJy0G.mjs +30 -0
- package/dist/client/_projectId-DfeVJy0G.mjs.map +1 -0
- package/dist/client/{_securityGroupId-BcbkRhsN.mjs → _securityGroupId-C6eHmrx_.mjs} +3 -9
- package/dist/client/_securityGroupId-C6eHmrx_.mjs.map +1 -0
- package/dist/client/_securityGroupId-DmplhDgt.mjs +1810 -0
- package/dist/client/_securityGroupId-DmplhDgt.mjs.map +1 -0
- package/dist/client/_serviceType-DS8DxsrP.mjs +20 -0
- package/dist/client/_serviceType-DS8DxsrP.mjs.map +1 -0
- package/dist/client/_serviceType-M8d9rOT6.mjs +6 -0
- package/dist/client/_serviceType-M8d9rOT6.mjs.map +1 -0
- package/dist/client/_storageType-BRWWJtow.mjs +14 -0
- package/dist/client/_storageType-BRWWJtow.mjs.map +1 -0
- package/dist/client/_storageType-C5gS9eut.mjs +845 -0
- package/dist/client/_storageType-C5gS9eut.mjs.map +1 -0
- package/dist/client/_storageType-CzZ4u9x_.mjs +12 -0
- package/dist/client/_storageType-CzZ4u9x_.mjs.map +1 -0
- package/dist/client/_storageType-zeSZe--V.mjs.map +1 -1
- package/dist/client/{about-_73VmpTB.mjs → about-BmQTyLWc.mjs} +2 -2
- package/dist/client/{about-_73VmpTB.mjs.map → about-BmQTyLWc.mjs.map} +1 -1
- package/dist/client/{aurora-EbwGRLLf.mjs → aurora-CIml-f2f.mjs} +2 -2
- package/dist/client/{aurora-EbwGRLLf.mjs.map → aurora-CIml-f2f.mjs.map} +1 -1
- package/dist/client/{build-C_4vRMCq.mjs → build-WuxXRAqf.mjs} +121 -24
- package/dist/client/{build-C_4vRMCq.mjs.map → build-WuxXRAqf.mjs.map} +1 -1
- package/dist/client/components/Breadcrumbs.d.ts +1 -0
- package/dist/client/components/ListToolbar/SortInput.d.ts +3 -0
- package/dist/client/context/BreadcrumbExtensionContext.d.ts +9 -0
- package/dist/client/context/DynamicBreadcrumbContext.d.ts +9 -0
- package/dist/client/{flavors-CiHCYrtZ.mjs → flavors-B1SDOPDu.mjs} +3 -5
- package/dist/client/flavors-B1SDOPDu.mjs.map +1 -0
- package/dist/client/flavors-D8_PzQUB.mjs +628 -0
- package/dist/client/flavors-D8_PzQUB.mjs.map +1 -0
- package/dist/client/flavors-DWMZ6TuJ.mjs.map +1 -1
- package/dist/client/floatingips-Cz9k80cc.mjs +8 -0
- package/dist/client/floatingips-Cz9k80cc.mjs.map +1 -0
- package/dist/client/floatingips-DrCvtXtT.mjs +551 -0
- package/dist/client/floatingips-DrCvtXtT.mjs.map +1 -0
- package/dist/client/hooks/useBreadcrumbs.d.ts +16 -0
- package/dist/client/hooks/usePushBreadcrumbs.d.ts +10 -0
- package/dist/client/hooks/useSetBreadcrumb.d.ts +11 -0
- package/dist/client/{hooks-LutR2Yg3.mjs → hooks-D0yKIumu.mjs} +2 -2
- package/dist/client/{hooks-LutR2Yg3.mjs.map → hooks-D0yKIumu.mjs.map} +1 -1
- package/dist/client/images-BGQv9kKT.mjs +1983 -0
- package/dist/client/images-BGQv9kKT.mjs.map +1 -0
- package/dist/client/{images-CS4kZQyu.mjs → images-CPmMa4SQ.mjs} +3 -5
- package/dist/client/images-CPmMa4SQ.mjs.map +1 -0
- package/dist/client/images-NBf2bV43.mjs.map +1 -1
- package/dist/client/index.d.ts +7 -3
- package/dist/client/index.js +549 -535
- package/dist/client/index.js.map +1 -1
- package/dist/client/objects-AERkhfiM.mjs +66 -0
- package/dist/client/objects-AERkhfiM.mjs.map +1 -0
- package/dist/client/{objects-C3azvW8m.mjs → objects-BR3eeSNH.mjs} +7087 -4211
- package/dist/client/objects-BR3eeSNH.mjs.map +1 -0
- package/dist/client/objects-DVccbrL1.mjs +66 -0
- package/dist/client/objects-DVccbrL1.mjs.map +1 -0
- package/dist/client/projects-yiK0HGSA.mjs.map +1 -1
- package/dist/client/routeInfo-DP3v74Wg.mjs +19 -0
- package/dist/client/routeInfo-DP3v74Wg.mjs.map +1 -0
- package/dist/client/router.d.ts +2 -2
- package/dist/client/{routes-CdDm-hhO.mjs → routes-CJj7SR_K.mjs} +2 -2
- package/dist/client/routes-CJj7SR_K.mjs.map +1 -0
- package/dist/client/routes-xofA3ksU.mjs +110 -0
- package/dist/client/routes-xofA3ksU.mjs.map +1 -0
- package/dist/client/securitygroups-BjhgLBHm.mjs +620 -0
- package/dist/client/securitygroups-BjhgLBHm.mjs.map +1 -0
- package/dist/client/securitygroups-CtKne8UQ.mjs +8 -0
- package/dist/client/securitygroups-CtKne8UQ.mjs.map +1 -0
- package/dist/client/{securitygroups-Byte-iSp.mjs → securitygroups-DEJQBcxm.mjs} +3 -5
- package/dist/client/securitygroups-DEJQBcxm.mjs.map +1 -0
- package/dist/client/trpcClient.d.ts +135 -3
- package/dist/client/useBreadcrumbs-ha3IlaxX.mjs +54 -0
- package/dist/client/useBreadcrumbs-ha3IlaxX.mjs.map +1 -0
- package/dist/client/useModalTracking-BcOKTYRN.mjs +33 -0
- package/dist/client/useModalTracking-BcOKTYRN.mjs.map +1 -0
- package/dist/client/{useSecurityGroupPermissions-C29mzb3b.mjs → useSecurityGroupPermissions-BOyLQLdC.mjs} +85 -19
- package/dist/client/useSecurityGroupPermissions-BOyLQLdC.mjs.map +1 -0
- package/dist/client/useSetBreadcrumb-tn7lBaQV.mjs +19 -0
- package/dist/client/useSetBreadcrumb-tn7lBaQV.mjs.map +1 -0
- package/dist/client/utils/formatSwiftDate.d.ts +30 -0
- package/dist/server/index.d.ts +136 -4
- package/dist/server/index.js +845 -260
- package/package.json +37 -36
- package/dist/client/AuthProvider-BS5OVv8n.mjs.map +0 -1
- package/dist/client/DeleteFlavorModal-BP83t9zX.mjs +0 -721
- package/dist/client/DeleteFlavorModal-BP83t9zX.mjs.map +0 -1
- package/dist/client/FloatingIpActionModals-BraiJWBi.mjs +0 -417
- package/dist/client/FloatingIpActionModals-BraiJWBi.mjs.map +0 -1
- package/dist/client/ImageToastNotifications-BXMHJaIH.mjs +0 -1131
- package/dist/client/ImageToastNotifications-BXMHJaIH.mjs.map +0 -1
- package/dist/client/RouteError-hAF8_jbe.mjs +0 -25
- package/dist/client/RouteError-hAF8_jbe.mjs.map +0 -1
- package/dist/client/SortInput-Cqs2poV3.mjs +0 -34
- package/dist/client/SortInput-Cqs2poV3.mjs.map +0 -1
- package/dist/client/StatusError-C5KodQOq.mjs +0 -39
- package/dist/client/StatusError-C5KodQOq.mjs.map +0 -1
- package/dist/client/TwoColumnDescriptionList-DiDx3PnH.mjs.map +0 -1
- package/dist/client/_auth-DJ9wHZaj.mjs +0 -11
- package/dist/client/_auth-DJ9wHZaj.mjs.map +0 -1
- package/dist/client/_flavorId-BN_qessZ.mjs.map +0 -1
- package/dist/client/_flavorId-CJjEB_Ao.mjs +0 -210
- package/dist/client/_flavorId-CJjEB_Ao.mjs.map +0 -1
- package/dist/client/_floatingIpId-CwDGHkWm.mjs.map +0 -1
- package/dist/client/_floatingIpId-foMIwbE4.mjs +0 -215
- package/dist/client/_floatingIpId-foMIwbE4.mjs.map +0 -1
- package/dist/client/_imageId-BtJ7GmYB.mjs.map +0 -1
- package/dist/client/_projectId-BmtCuzJu.mjs +0 -29
- package/dist/client/_projectId-BmtCuzJu.mjs.map +0 -1
- package/dist/client/_projectId-Byt3GQDW.mjs.map +0 -1
- package/dist/client/_projectId-NYk1IN_a.mjs +0 -291
- package/dist/client/_projectId-NYk1IN_a.mjs.map +0 -1
- package/dist/client/_projectId-QueiFczy.mjs +0 -46
- package/dist/client/_projectId-QueiFczy.mjs.map +0 -1
- package/dist/client/_securityGroupId-BcbkRhsN.mjs.map +0 -1
- package/dist/client/_securityGroupId-pPtJsCoe.mjs +0 -1797
- package/dist/client/_securityGroupId-pPtJsCoe.mjs.map +0 -1
- package/dist/client/_storageType-BmqrU06i.mjs +0 -2202
- package/dist/client/_storageType-BmqrU06i.mjs.map +0 -1
- package/dist/client/components/Error/StatusError.d.ts +0 -11
- package/dist/client/components/ProjectView/ProjectInfoBox.d.ts +0 -12
- package/dist/client/flavors-CiHCYrtZ.mjs.map +0 -1
- package/dist/client/flavors-DY-bDc8l.mjs +0 -650
- package/dist/client/flavors-DY-bDc8l.mjs.map +0 -1
- package/dist/client/floatingips-Ci4jHkaG.mjs +0 -591
- package/dist/client/floatingips-Ci4jHkaG.mjs.map +0 -1
- package/dist/client/images-91U7tr9Y2.mjs +0 -1976
- package/dist/client/images-91U7tr9Y2.mjs.map +0 -1
- package/dist/client/images-CS4kZQyu.mjs.map +0 -1
- package/dist/client/objects-C3azvW8m.mjs.map +0 -1
- package/dist/client/objects-CM265S6b.mjs +0 -56
- package/dist/client/objects-CM265S6b.mjs.map +0 -1
- package/dist/client/objects-EE9Cd1wI.mjs +0 -56
- package/dist/client/objects-EE9Cd1wI.mjs.map +0 -1
- package/dist/client/routeInfo-DXqB2HCf.mjs +0 -32
- package/dist/client/routeInfo-DXqB2HCf.mjs.map +0 -1
- package/dist/client/routes-CBd8CCuM.mjs +0 -115
- package/dist/client/routes-CBd8CCuM.mjs.map +0 -1
- package/dist/client/routes-CdDm-hhO.mjs.map +0 -1
- package/dist/client/securitygroups-Bd_uV4S9.mjs +0 -595
- package/dist/client/securitygroups-Bd_uV4S9.mjs.map +0 -1
- package/dist/client/securitygroups-Byte-iSp.mjs.map +0 -1
- package/dist/client/useSecurityGroupPermissions-C29mzb3b.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -114,15 +114,16 @@ await server.listen({ host: "0.0.0.0", port: 4000 })
|
|
|
114
114
|
|
|
115
115
|
### `<AuroraApp />`
|
|
116
116
|
|
|
117
|
-
| Prop
|
|
118
|
-
|
|
|
119
|
-
| `bffEndpoint`
|
|
120
|
-
| `theme`
|
|
121
|
-
| `onThemeChange`
|
|
122
|
-
| `appName`
|
|
123
|
-
| `slots`
|
|
124
|
-
| `onTrackEvent`
|
|
125
|
-
| `enabledServices`
|
|
117
|
+
| Prop | Type | Default | Description |
|
|
118
|
+
| ------------------- | ------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
119
|
+
| `bffEndpoint` | `string` | `"/polaris-bff"` | Must match the server's `bffEndpoint` |
|
|
120
|
+
| `theme` | `"theme-light" \| "theme-dark"` | `"theme-light"` | Initial theme |
|
|
121
|
+
| `onThemeChange` | `(theme) => void` | — | Called when the user toggles the theme |
|
|
122
|
+
| `appName` | `string` | `"Aurora"` | App name shown in the header breadcrumb and logo |
|
|
123
|
+
| `slots` | `Slots` | — | Optional UI extension points — see [Slots](#slots) |
|
|
124
|
+
| `onTrackEvent` | `OnTrackEventCallback` | — | Called on user interactions for analytics — see [Analytics](#analytics) |
|
|
125
|
+
| `enabledServices` | `string[]` | — | Whitelist of service keys to show. When omitted, all services are shown — see [Enabled services](#enabled-services) |
|
|
126
|
+
| `serviceExtensions` | `ServiceExtension[]` | — | Consumer-defined services to mount under project scope — see [Service extensions](#service-extensions) |
|
|
126
127
|
|
|
127
128
|
## Slots
|
|
128
129
|
|
|
@@ -214,6 +215,55 @@ enabledServices={import.meta.env.VITE_ENABLED_SERVICES?.split(",").map(s => s.tr
|
|
|
214
215
|
VITE_ENABLED_SERVICES="ceph-containers,securitygroups"
|
|
215
216
|
```
|
|
216
217
|
|
|
218
|
+
## Service extensions
|
|
219
|
+
|
|
220
|
+
Aurora lets you register consumer-owned services (service extensions) that appear alongside the built-in ones in the project navigation. Each extension is tied to an OpenStack catalog entry — Aurora checks whether the service type and name exist in the project's catalog and only shows the service when they do.
|
|
221
|
+
|
|
222
|
+
Pass an array of service descriptors to `AuroraApp`:
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
import type { ServiceExtension } from "@cobaltcore-dev/aurora/client"
|
|
226
|
+
import { MyServiceApp } from "./myService/MyServiceApp"
|
|
227
|
+
|
|
228
|
+
const serviceExtensions: ServiceExtension[] = [
|
|
229
|
+
{
|
|
230
|
+
serviceType: "my-service", // OpenStack catalog service type
|
|
231
|
+
serviceName: "my-service", // OpenStack catalog service name
|
|
232
|
+
label: "My Service", // Label shown in the side nav and project home
|
|
233
|
+
component: MyServiceApp, // React component that renders the service UI
|
|
234
|
+
},
|
|
235
|
+
]
|
|
236
|
+
|
|
237
|
+
;<AuroraApp serviceExtensions={serviceExtensions} />
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The `component` receives a `basePath` (the URL prefix Aurora has mounted the service at) and a `context` object of host-provided data (`ServiceExtensionContext`). Today `context` carries the current `projectId`; it is an extensible object, so future host data (e.g. domain, scope) can be added without changing the component signature.
|
|
241
|
+
|
|
242
|
+
Because Aurora mounts your extension at `basePath` and strips it from the sub-router, your routes never see `projectId` as a URL param. Seed `context` into your own router context so any component can read it:
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { RouterProvider, createRouter, useRouteContext } from "@tanstack/react-router"
|
|
246
|
+
import { useMemo } from "react"
|
|
247
|
+
import type { ServiceExtensionProps, ServiceExtensionContext } from "@cobaltcore-dev/aurora/client"
|
|
248
|
+
import { routeTree } from "./routeTree.gen"
|
|
249
|
+
|
|
250
|
+
export function MyServiceApp({ basePath, context }: ServiceExtensionProps) {
|
|
251
|
+
// Seed the host context into the extension's own router context.
|
|
252
|
+
const router = useMemo(() => createRouter({ routeTree, context }), [basePath, context])
|
|
253
|
+
return <RouterProvider basepath={basePath} router={router} />
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// In your root route, type the context so it flows through:
|
|
257
|
+
// createRootRouteWithContext<ServiceExtensionContext>()({ ... })
|
|
258
|
+
//
|
|
259
|
+
// Then read projectId anywhere in your tree:
|
|
260
|
+
function useProjectId(): string {
|
|
261
|
+
return useRouteContext({ strict: false, select: (c: ServiceExtensionContext) => c.projectId })
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Aurora handles routing to your service, breadcrumb rendering for the service level, and showing or hiding the nav entry based on catalog availability. Navigation and breadcrumb details below the service level are the responsibility of the service component.
|
|
266
|
+
|
|
217
267
|
## Analytics
|
|
218
268
|
|
|
219
269
|
Aurora provides an optional `onTrackEvent` callback to track user interactions and feature usage. The API is source-agnostic, supporting various event types like navigation, link clicks, modals, and more.
|
package/dist/client/App.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Slots, OnTrackEventCallback,
|
|
1
|
+
import { Slots, OnTrackEventCallback, ServiceExtension } from './AuroraApp';
|
|
2
2
|
type AppProps = {
|
|
3
3
|
theme?: "theme-dark" | "theme-light";
|
|
4
4
|
bffEndpoint?: string;
|
|
@@ -7,7 +7,7 @@ type AppProps = {
|
|
|
7
7
|
appName?: string;
|
|
8
8
|
onTrackEvent?: OnTrackEventCallback;
|
|
9
9
|
enabledServices?: string[];
|
|
10
|
-
|
|
10
|
+
serviceExtensions?: ServiceExtension[];
|
|
11
11
|
};
|
|
12
12
|
declare const App: (props: AppProps) => import("react").JSX.Element;
|
|
13
13
|
export default App;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { FC } from 'react';
|
|
2
|
-
import { AnyRoute } from '@tanstack/react-router';
|
|
3
2
|
import { TrpcClient } from './trpcClient';
|
|
4
3
|
/** Context object passed to every slot component. */
|
|
5
4
|
export type SlotProps = {
|
|
@@ -49,13 +48,32 @@ export type TrackEventPayload = {
|
|
|
49
48
|
*/
|
|
50
49
|
export type OnTrackEventCallback = (payload: TrackEventPayload) => void;
|
|
51
50
|
/**
|
|
52
|
-
*
|
|
51
|
+
* Host-provided data handed to a mounted service extension. Extend this as extensions need more
|
|
52
|
+
* from OSS (domainId, scope, a typed tRPC client, ...). Seed it into the extension's own router
|
|
53
|
+
* context so pages can read it via `useRouteContext`.
|
|
54
|
+
*/
|
|
55
|
+
export type ServiceExtensionContext = {
|
|
56
|
+
/** The current project ID. */
|
|
57
|
+
projectId: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Props passed to a service extension component mounted by OSS.
|
|
61
|
+
* The basePath is the full URL prefix up to and including the service type segment.
|
|
62
|
+
*/
|
|
63
|
+
export type ServiceExtensionProps = {
|
|
64
|
+
/** Full URL prefix for this service, e.g. "/projects/abc/services/pca". Pass as `basepath` to RouterProvider. */
|
|
65
|
+
basePath: string;
|
|
66
|
+
/** Host context to seed into the extension's own router context. */
|
|
67
|
+
context: ServiceExtensionContext;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* A project-scoped service extension a consumer wants to register.
|
|
53
71
|
* Aurora checks `serviceIndex[serviceType][serviceName]` against the project's OpenStack catalog.
|
|
54
|
-
* When present, the service appears in the "Services" nav section and its
|
|
55
|
-
* under
|
|
56
|
-
* `
|
|
72
|
+
* When present, the service appears in the "Services" nav section and its component is mounted
|
|
73
|
+
* under `/projects/$projectId/services/$serviceType`. The component receives `basePath` and
|
|
74
|
+
* `context`: use these to set up a standalone RouterProvider for the service.
|
|
57
75
|
*/
|
|
58
|
-
export type
|
|
76
|
+
export type ServiceExtension = {
|
|
59
77
|
/** OpenStack catalog service type, e.g. `"pca"`. */
|
|
60
78
|
serviceType: string;
|
|
61
79
|
/** OpenStack catalog service name, e.g. `"clavis"`. */
|
|
@@ -63,12 +81,14 @@ export type AdditionalProjectService = {
|
|
|
63
81
|
/** Nav item and service card label. */
|
|
64
82
|
label: string;
|
|
65
83
|
/**
|
|
66
|
-
* The
|
|
67
|
-
*
|
|
68
|
-
* the
|
|
69
|
-
*
|
|
84
|
+
* The React component to render at `/projects/$projectId/services/$serviceType/**`.
|
|
85
|
+
* Receives `basePath` (the URL prefix) and `context` (host data). Create your own RouterProvider
|
|
86
|
+
* inside this component using `basePath` as the `basepath` prop and `context` as the router context.
|
|
87
|
+
*
|
|
88
|
+
* Note: this is a self-contained sub-app. Host slots (see `Slots`) are not injected into it, so the
|
|
89
|
+
* extension owns its own header/actions (e.g. via the re-exported `PageContentHeader` `actions` prop).
|
|
70
90
|
*/
|
|
71
|
-
|
|
91
|
+
component: FC<ServiceExtensionProps>;
|
|
72
92
|
};
|
|
73
93
|
/** Props for the top-level `<AuroraApp />` component. */
|
|
74
94
|
export type AuroraAppProps = {
|
|
@@ -105,7 +125,7 @@ export type AuroraAppProps = {
|
|
|
105
125
|
onTrackEvent?: OnTrackEventCallback;
|
|
106
126
|
/** Whitelist of service keys to show in the side nav and project home page. When omitted, all available services are shown. */
|
|
107
127
|
enabledServices?: string[];
|
|
108
|
-
/**
|
|
109
|
-
|
|
128
|
+
/** Service extensions to register. Each is activated when its OpenStack service appears in the project catalog. */
|
|
129
|
+
serviceExtensions?: ServiceExtension[];
|
|
110
130
|
};
|
|
111
131
|
export declare const AuroraApp: FC<AuroraAppProps>;
|
|
@@ -20,15 +20,22 @@ function o({ children: n }) {
|
|
|
20
20
|
}).finally(() => l(!1));
|
|
21
21
|
}, []), r(() => {
|
|
22
22
|
if (!o || !f) return;
|
|
23
|
-
let
|
|
24
|
-
|
|
23
|
+
let t = new Date(f).getTime() - Date.now(), n = async () => {
|
|
24
|
+
l(!0);
|
|
25
|
+
try {
|
|
26
|
+
await e.auth.terminateUserSession.mutate();
|
|
27
|
+
} catch (e) {
|
|
28
|
+
d(e instanceof Error ? e.message : `Session termination failed: ${e}`);
|
|
29
|
+
} finally {
|
|
30
|
+
h(), l(!1), m(!0);
|
|
31
|
+
}
|
|
25
32
|
};
|
|
26
|
-
if (
|
|
27
|
-
|
|
33
|
+
if (t <= 0) {
|
|
34
|
+
n();
|
|
28
35
|
return;
|
|
29
36
|
}
|
|
30
|
-
let
|
|
31
|
-
return () => clearTimeout(
|
|
37
|
+
let r = setTimeout(n, t);
|
|
38
|
+
return () => clearTimeout(r);
|
|
32
39
|
}, [o, f]), /*#__PURE__*/ t(a.Provider, {
|
|
33
40
|
value: {
|
|
34
41
|
user: o,
|
|
@@ -51,7 +58,14 @@ function o({ children: n }) {
|
|
|
51
58
|
}
|
|
52
59
|
},
|
|
53
60
|
logout: async () => {
|
|
54
|
-
l(!0),
|
|
61
|
+
l(!0), d(null);
|
|
62
|
+
try {
|
|
63
|
+
await e.auth.terminateUserSession.mutate();
|
|
64
|
+
} catch (e) {
|
|
65
|
+
d(e instanceof Error ? e.message : `Logout failed: ${e}`);
|
|
66
|
+
} finally {
|
|
67
|
+
h(), l(!1), m(!1);
|
|
68
|
+
}
|
|
55
69
|
},
|
|
56
70
|
expiresAt: f ? new Date(f) : void 0
|
|
57
71
|
},
|
|
@@ -66,4 +80,4 @@ function s() {
|
|
|
66
80
|
//#endregion
|
|
67
81
|
export { s as n, o as t };
|
|
68
82
|
|
|
69
|
-
//# sourceMappingURL=AuthProvider-
|
|
83
|
+
//# sourceMappingURL=AuthProvider-CVSAwW9f.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthProvider-CVSAwW9f.mjs","names":["React","useEffect","useState","trpcClient","AuthContext","createContext","AuthProvider","children","user","setUser","isLoading","setIsLoading","error","setError","expiresAt","setExpiresAt","redirectToLogin","saveReturnUrl","window","location","pathname","returnUrl","encodeURIComponent","search","replace","clearLocalSession","auth","getCurrentUserSession","query","then","session","expires_at","catch","err","Error","message","finally","timeUntilExpiry","Date","getTime","now","handleExpiry","terminateUserSession","mutate","timer","setTimeout","clearTimeout","login","domain","password","createUserSession","domainName","success","logout","Provider","value","isAuthenticated","undefined","useAuth","context","useContext"],"sources":["../../src/client/store/AuthProvider.tsx"],"sourcesContent":["import React, { useEffect, useState } from \"react\"\nimport { TokenData } from \"../../server/Authentication/types/models\"\nimport { trpcClient } from \"../trpcClient\"\n\nexport type User = TokenData[\"user\"] | null\n\nexport interface AuthContext {\n isAuthenticated: boolean\n isLoading: boolean\n error: string | null\n login: (credentials: { domain: string; user: string; password: string }) => Promise<{ success: boolean }>\n logout: () => Promise<void>\n user?: User\n expiresAt?: Date\n}\n\nconst AuthContext = React.createContext<AuthContext | null>(null)\n\nexport function AuthProvider({ children }: { children: React.ReactNode }) {\n const [user, setUser] = useState<User | null>(null)\n const [isLoading, setIsLoading] = useState(true)\n const [error, setError] = useState<string | null>(null)\n const [expiresAt, setExpiresAt] = useState<string | null>(null)\n\n // Redirect to login, optionally with return URL\n const redirectToLogin = (saveReturnUrl: boolean = false) => {\n if (window.location.pathname !== \"/\") {\n const returnUrl = saveReturnUrl\n ? `/?redirect=${encodeURIComponent(window.location.pathname + window.location.search)}`\n : \"/\"\n window.location.replace(returnUrl)\n }\n }\n\n // Clear local session state\n const clearLocalSession = () => {\n setUser(null)\n setExpiresAt(null)\n }\n\n // Get current session on mount\n useEffect(() => {\n trpcClient.auth.getCurrentUserSession\n .query()\n .then((session) => {\n if (session) {\n setUser(session.user)\n setExpiresAt(session.expires_at ?? null)\n }\n })\n .catch((err) => {\n setError(err instanceof Error ? err.message : \"Could not load session\")\n })\n .finally(() => setIsLoading(false))\n }, [])\n\n // Auto-logout when session expires\n useEffect(() => {\n if (!user || !expiresAt) return\n\n const timeUntilExpiry = new Date(expiresAt).getTime() - Date.now()\n\n const handleExpiry = async () => {\n setIsLoading(true)\n try {\n await trpcClient.auth.terminateUserSession.mutate()\n } catch (err) {\n // Server invalidation failed - show error\n setError(err instanceof Error ? err.message : `Session termination failed: ${err}`)\n } finally {\n clearLocalSession()\n setIsLoading(false)\n redirectToLogin(true)\n }\n }\n\n if (timeUntilExpiry <= 0) {\n handleExpiry()\n return\n }\n\n const timer = setTimeout(handleExpiry, timeUntilExpiry)\n return () => clearTimeout(timer)\n }, [user, expiresAt])\n\n const login = async ({ domain, user, password }: { domain: string; user: string; password: string }) => {\n setError(null)\n setIsLoading(true)\n try {\n const session = await trpcClient.auth.createUserSession.mutate({ domainName: domain, user, password })\n setUser(session.user)\n setExpiresAt(session.expires_at)\n return { success: true }\n } catch (err) {\n setError(err instanceof Error ? err.message : `Could not create session: ${err}`)\n return { success: false }\n } finally {\n setIsLoading(false)\n }\n }\n\n const logout = async () => {\n setIsLoading(true)\n setError(null)\n try {\n await trpcClient.auth.terminateUserSession.mutate()\n } catch (err) {\n // Server invalidation failed - show error\n setError(err instanceof Error ? err.message : `Logout failed: ${err}`)\n } finally {\n clearLocalSession()\n setIsLoading(false)\n redirectToLogin(false) // Don't save return URL on manual logout\n }\n }\n\n return (\n <AuthContext.Provider\n value={{\n user,\n isAuthenticated: !!user,\n isLoading,\n error,\n login,\n logout,\n expiresAt: expiresAt ? new Date(expiresAt) : undefined,\n }}\n >\n {children}\n </AuthContext.Provider>\n )\n}\n\nexport function useAuth() {\n const context = React.useContext(AuthContext)\n if (!context) {\n throw new Error(\"useAuth must be used within an AuthProvider\")\n }\n return context\n}\n"],"mappings":"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"}
|
|
@@ -1,32 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { H as e, Q as t, Z as n, _ as r, at as i, d as a, tt as o } from "./build-WuxXRAqf.mjs";
|
|
2
2
|
import { t as s } from "./Slot-CWb612-_.mjs";
|
|
3
|
-
import { t as c } from "./routeInfo-
|
|
3
|
+
import { t as c } from "./routeInfo-DP3v74Wg.mjs";
|
|
4
4
|
import { jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
5
5
|
import { useState as d } from "react";
|
|
6
6
|
import { useMatches as f, useParams as p, useRouteContext as m } from "@tanstack/react-router";
|
|
7
7
|
import { Trans as h, useLingui as g } from "@lingui/react";
|
|
8
8
|
//#region src/client/components/ClipboardText.tsx
|
|
9
|
-
var _ = ({ text:
|
|
9
|
+
var _ = ({ text: r, tooltipContent: i, className: s, truncateAt: c, showTooltip: f = !0, ...p }) => {
|
|
10
10
|
let { i18n: m, _: h } = g(), [_, v] = d(!1), [y, b] = d(!1), x = `copyableTooltip inline-flex items-center ${s || ""}`, S = async (e) => {
|
|
11
11
|
e.preventDefault(), e.stopPropagation();
|
|
12
12
|
try {
|
|
13
|
-
await navigator.clipboard.writeText(
|
|
13
|
+
await navigator.clipboard.writeText(r), v(!0), b(!1), setTimeout(() => v(!1), 2e3);
|
|
14
14
|
} catch (e) {
|
|
15
15
|
console.error("Failed to copy text:", e);
|
|
16
16
|
}
|
|
17
|
-
}, C = c &&
|
|
17
|
+
}, C = c && r.length > c ? `${r.slice(0, c)}...` : r, w = () => _ ? i || m._({ id: "u+VWhB" }) : m._({ id: "he3ygx" }), T = _ && f || y && f;
|
|
18
18
|
return /*#__PURE__*/ l("div", {
|
|
19
19
|
...p,
|
|
20
20
|
className: x,
|
|
21
|
-
children: /*#__PURE__*/ u(
|
|
21
|
+
children: /*#__PURE__*/ u(n, {
|
|
22
22
|
open: T,
|
|
23
|
-
children: [/*#__PURE__*/ l(
|
|
23
|
+
children: [/*#__PURE__*/ l(o, {
|
|
24
24
|
onClick: S,
|
|
25
25
|
onMouseEnter: () => !_ && b(!0),
|
|
26
26
|
onMouseLeave: () => b(!1),
|
|
27
27
|
"aria-label": m._({
|
|
28
28
|
id: "Wbg1jv",
|
|
29
|
-
values: { text:
|
|
29
|
+
values: { text: r }
|
|
30
30
|
}),
|
|
31
31
|
className: "cursor-pointer",
|
|
32
32
|
asChild: !0,
|
|
@@ -34,7 +34,7 @@ var _ = ({ text: i, tooltipContent: o, className: s, truncateAt: c, showTooltip:
|
|
|
34
34
|
children: /*#__PURE__*/ l("button", {
|
|
35
35
|
type: "button",
|
|
36
36
|
className: "group border-none bg-transparent p-0 text-inherit",
|
|
37
|
-
children: /*#__PURE__*/ u(
|
|
37
|
+
children: /*#__PURE__*/ u(t, {
|
|
38
38
|
direction: "horizontal",
|
|
39
39
|
gap: "1",
|
|
40
40
|
className: "items-center hover:underline",
|
|
@@ -47,13 +47,13 @@ var _ = ({ text: i, tooltipContent: o, className: s, truncateAt: c, showTooltip:
|
|
|
47
47
|
})]
|
|
48
48
|
})
|
|
49
49
|
})
|
|
50
|
-
}), /*#__PURE__*/ l(
|
|
50
|
+
}), /*#__PURE__*/ l(e, { children: w() })]
|
|
51
51
|
})
|
|
52
52
|
});
|
|
53
53
|
};
|
|
54
54
|
//#endregion
|
|
55
55
|
//#region src/client/components/ContentHeader/ContentHeader.tsx
|
|
56
|
-
function v({ title: e, projectId: t, description: n, actions:
|
|
56
|
+
function v({ title: e, projectId: t, description: n, actions: a, badges: o }) {
|
|
57
57
|
let { slots: d } = m({ strict: !1 }), g = f(), { provider: v } = p({ strict: !1 }), y = [...g].reverse().find((e) => c(e.staticData)), b = y && c(y.staticData) ? y.staticData.service : void 0, x = b === "containers" && v === "ceph" ? "ceph-containers" : b, S = d?.servicePageActions && x ? /*#__PURE__*/ l(s, {
|
|
58
58
|
component: d.servicePageActions,
|
|
59
59
|
useShadowDOM: !1,
|
|
@@ -73,7 +73,7 @@ function v({ title: e, projectId: t, description: n, actions: r, badges: a }) {
|
|
|
73
73
|
className: "flex flex-col sm:flex-row sm:items-center sm:justify-between",
|
|
74
74
|
children: [/*#__PURE__*/ u("div", {
|
|
75
75
|
className: "flex items-center gap-3",
|
|
76
|
-
children: [/*#__PURE__*/ l(
|
|
76
|
+
children: [/*#__PURE__*/ l(i, { children: e }), S]
|
|
77
77
|
}), /*#__PURE__*/ u("div", {
|
|
78
78
|
className: "text-theme-light flex shrink-0 items-center gap-1 text-sm",
|
|
79
79
|
children: [/*#__PURE__*/ u("span", {
|
|
@@ -95,15 +95,15 @@ function v({ title: e, projectId: t, description: n, actions: r, badges: a }) {
|
|
|
95
95
|
}),
|
|
96
96
|
C,
|
|
97
97
|
w,
|
|
98
|
-
/*#__PURE__*/ l(
|
|
99
|
-
(
|
|
98
|
+
/*#__PURE__*/ l(r, { className: "mt-4" }),
|
|
99
|
+
(o || a) && /*#__PURE__*/ u("div", {
|
|
100
100
|
className: "mt-3 flex items-start justify-between",
|
|
101
101
|
children: [/*#__PURE__*/ u("div", {
|
|
102
102
|
className: "flex items-center gap-2",
|
|
103
|
-
children: [
|
|
104
|
-
}),
|
|
103
|
+
children: [o, " "]
|
|
104
|
+
}), a && /*#__PURE__*/ l("div", {
|
|
105
105
|
className: "flex gap-2",
|
|
106
|
-
children:
|
|
106
|
+
children: a
|
|
107
107
|
})]
|
|
108
108
|
})
|
|
109
109
|
]
|
|
@@ -112,4 +112,4 @@ function v({ title: e, projectId: t, description: n, actions: r, badges: a }) {
|
|
|
112
112
|
//#endregion
|
|
113
113
|
export { _ as n, v as t };
|
|
114
114
|
|
|
115
|
-
//# sourceMappingURL=ContentHeader-
|
|
115
|
+
//# sourceMappingURL=ContentHeader-CPJbgYWn.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContentHeader-
|
|
1
|
+
{"version":3,"file":"ContentHeader-CPJbgYWn.mjs","names":["React","useState","Tooltip","TooltipTrigger","TooltipContent","Icon","Stack","ClipboardText","text","tooltipContent","className","truncateAt","showTooltip","props","useLingui","copied","setCopied","isHovering","setIsHovering","combinedClassName","handleCopy","e","preventDefault","stopPropagation","navigator","clipboard","writeText","setTimeout","err","console","error","displayText","length","slice","getTooltipContent","t","tooltipIsOpen","div","open","onClick","onMouseEnter","onMouseLeave","aria-label","asChild","data-testid","button","type","direction","gap","span","icon","size","Divider","ContentHeading","useRouteContext","useMatches","useParams","ClipboardText","Slot","isRouteInfo","ContentHeader","title","projectId","description","actions","badges","slots","strict","matches","provider","activeMatch","reverse","find","m","staticData","routeService","service","undefined","currentService","slotActions","servicePageActions","component","useShadowDOM","serviceBanner","projectOverviewBanner","header","className","div","span","text","truncateAt","p"],"sources":["../../src/client/components/ClipboardText.tsx","../../src/client/components/ContentHeader/ContentHeader.tsx"],"sourcesContent":["import React, { useState } from \"react\"\nimport { Tooltip, TooltipTrigger, TooltipContent, Icon, Stack } from \"@cloudoperators/juno-ui-components\"\nimport { useLingui } from \"@lingui/react/macro\"\n\nexport interface ClipboardTextProps extends React.HTMLAttributes<HTMLDivElement> {\n tooltipContent?: string\n text: string\n className?: string\n truncateAt?: number\n showTooltip?: boolean\n}\n\nconst ClipboardText: React.FC<ClipboardTextProps> = ({\n text,\n tooltipContent,\n className,\n truncateAt,\n showTooltip = true,\n ...props\n}) => {\n const { t } = useLingui()\n const [copied, setCopied] = useState(false)\n const [isHovering, setIsHovering] = useState(false)\n\n const combinedClassName = `copyableTooltip inline-flex items-center ${className || \"\"}`\n\n const handleCopy = async (e: React.MouseEvent) => {\n e.preventDefault()\n e.stopPropagation()\n\n try {\n await navigator.clipboard.writeText(text)\n setCopied(true)\n setIsHovering(false)\n setTimeout(() => setCopied(false), 2000)\n } catch (err) {\n console.error(\"Failed to copy text:\", err)\n }\n }\n\n const displayText = truncateAt && text.length > truncateAt ? `${text.slice(0, truncateAt)}...` : text\n\n // Determine tooltip content based on state\n const getTooltipContent = () => {\n if (copied) {\n return tooltipContent || t`Copied to clipboard!`\n }\n return t`Copy`\n }\n\n const tooltipIsOpen = (copied && showTooltip) || (isHovering && showTooltip)\n\n return (\n <div {...props} className={combinedClassName}>\n <Tooltip open={tooltipIsOpen}>\n <TooltipTrigger\n onClick={handleCopy}\n onMouseEnter={() => !copied && setIsHovering(true)}\n onMouseLeave={() => setIsHovering(false)}\n aria-label={t`Copy ${text} to clipboard`}\n className=\"cursor-pointer\"\n asChild\n data-testid=\"clipboard-copy-trigger\"\n >\n <button type=\"button\" className=\"group border-none bg-transparent p-0 text-inherit\">\n <Stack direction=\"horizontal\" gap=\"1\" className=\"items-center hover:underline\">\n <span className=\"select-none\">{displayText}</span>\n <Icon icon={copied ? \"check\" : \"contentCopy\"} size=\"18\" />\n </Stack>\n </button>\n </TooltipTrigger>\n <TooltipContent>{getTooltipContent()}</TooltipContent>\n </Tooltip>\n </div>\n )\n}\n\nexport default ClipboardText\n","import type { ReactNode } from \"react\"\nimport { Divider, ContentHeading } from \"@cloudoperators/juno-ui-components\"\nimport { Trans } from \"@lingui/react/macro\"\nimport { useRouteContext, useMatches, useParams } from \"@tanstack/react-router\"\nimport ClipboardText from \"../ClipboardText\"\nimport { Slot } from \"../Slot\"\nimport { isRouteInfo } from \"@/client/routes/routeInfo\"\n\ninterface ContentHeaderProps {\n title: string\n projectId: string\n description?: string | null\n actions?: ReactNode\n badges?: ReactNode\n}\n\nexport function ContentHeader({ title, projectId, description, actions, badges }: ContentHeaderProps) {\n const { slots } = useRouteContext({ strict: false })\n const matches = useMatches()\n const { provider } = useParams({ strict: false }) as { provider?: string }\n\n const activeMatch = [...matches].reverse().find((m) => isRouteInfo(m.staticData))\n const routeService = activeMatch && isRouteInfo(activeMatch.staticData) ? activeMatch.staticData.service : undefined\n\n // Storage routes share service: \"containers\" for both Swift and Ceph.\n // Distinguish them by the $provider param.\n const currentService = routeService === \"containers\" && provider === \"ceph\" ? \"ceph-containers\" : routeService\n\n const slotActions =\n slots?.servicePageActions && currentService ? (\n <Slot component={slots.servicePageActions} useShadowDOM={false} currentService={currentService} />\n ) : null\n\n const serviceBanner =\n slots?.serviceBanner && currentService ? (\n <Slot component={slots.serviceBanner} useShadowDOM={false} currentService={currentService} />\n ) : null\n\n const projectOverviewBanner =\n slots?.projectOverviewBanner && !currentService ? (\n <Slot component={slots.projectOverviewBanner} useShadowDOM={false} />\n ) : null\n\n return (\n <header className=\"mb-8\">\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-3\">\n <ContentHeading>{title}</ContentHeading>\n {slotActions}\n </div>\n <div className=\"text-theme-light flex shrink-0 items-center gap-1 text-sm\">\n <span className=\"font-semibold\">\n <Trans>Project ID</Trans>:{\" \"}\n </span>\n <ClipboardText text={projectId} truncateAt={15} />\n </div>\n </div>\n {description && <p className=\"text-sm font-normal\">{description}</p>}\n {serviceBanner}\n {projectOverviewBanner}\n <Divider className=\"mt-4\" />\n {(badges || actions) && (\n <div className=\"mt-3 flex items-start justify-between\">\n <div className=\"flex items-center gap-2\">{badges} </div>\n {actions && <div className=\"flex gap-2\">{actions}</div>}\n </div>\n )}\n </header>\n )\n}\n"],"mappings":"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"}
|