Skip to content

Commit

Permalink
feat(dashboard): connect bff to apis (#83)
Browse files Browse the repository at this point in the history
* feat(dashboard): connect bff to api

* feat(dashboard): connect bff to api
  • Loading branch information
andypf authored Feb 28, 2025
1 parent eb75f67 commit c20c6c7
Show file tree
Hide file tree
Showing 31 changed files with 176 additions and 224 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useState } from "react"
import { ButtonRow, Button, Form, FormRow, TextInput, Spinner } from "@cloudoperators/juno-ui-components"
import { useAuth } from "../../Shell/AuthProvider"
import { useAuth } from "../Shell/AuthProvider"

export function SignIn() {
const [form, setForm] = useState({ domainName: "", user: "", password: "" })
const { user, error, isLoading, login } = useAuth()
const { isAuthenticated, user, error, isLoading, login } = useAuth()

if (isLoading)
return (
Expand All @@ -13,10 +13,10 @@ export function SignIn() {
</div>
)

if (user)
if (isAuthenticated)
return (
<div className="signed-in-notice">
<strong>Welcome back, {user.name}!</strong> <br />
<strong>Welcome back, {user?.name}!</strong> <br />
You are already signed in.
</div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it, vi, beforeEach } from "vitest"
import { render, screen, waitFor, fireEvent } from "@testing-library/react"
import { ComputeOverview } from "./ComputeOverview"
import { TrpcClient } from "../trpcClient"
import { Server } from "../../shared/types/models"
import { Server } from "../../server/Compute/types/models"

const mockGetServers = vi.fn()

Expand Down
2 changes: 1 addition & 1 deletion apps/aurora-portal/src/client/Compute/ComputeOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useState } from "react"
import type { Server } from "../../shared/types/models"
import type { Server } from "../../server/Compute/types/models"
import { TrpcClient } from "../trpcClient"
import ServerListView from "./components/ServerListView"
import ServerCardView from "./components/ServerCardView"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect } from "vitest"
import { render, screen } from "@testing-library/react"
import { ServerCardView } from "./ServerCardView"
import type { Server } from "../../../shared/types/models"
import type { Server } from "../../../server/Compute/types/models"

const mockServers: Server[] = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Server } from "../../../shared/types/models"
import type { Server } from "../../../server/Compute/types/models"
import { Button, Pill, Icon } from "@cloudoperators/juno-ui-components"

type ServerListViewProps = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { describe, it, expect } from "vitest"
import { render, screen } from "@testing-library/react"
import { ServerListView } from "./ServerListView"
import type { Server } from "../../../shared/types/models"
import type { Server } from "../../../server/Compute/types/models"

const mockServers: Server[] = [
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Server } from "../../../shared/types/models"
import type { Server } from "../../../server/Compute/types/models"
import { Button, Icon } from "@cloudoperators/juno-ui-components"

type ServerListViewProps = {
Expand Down
3 changes: 0 additions & 3 deletions apps/aurora-portal/src/client/Identity/Overview.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions apps/aurora-portal/src/client/Project/ProejctsOverview.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react"
import { ProjectsOverviewNavNbar, ViewMode } from "./components/ProjectOverviewNavBar"
import { Project } from "../../shared/types/models"
import { Project } from "../../server/Project/types/models"
import { TrpcClient } from "../trpcClient"
import { ProjectCardView } from "./components/ProjectCardView"
import { ProjectListView } from "./components/ProjectListView"
Expand All @@ -16,7 +16,7 @@ export function ProjectsOverview({ client }: { client: TrpcClient["project"] })
const [viewMode, setViewMode] = useState<ViewMode>("card")

useEffect(() => {
client.list
client.authProjects
.query()
.then((data) => updateGetProjects({ data, isLoading: false }))
.catch((error) => updateGetProjects({ error: error.message, isLoading: false }))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Icon, PopupMenu } from "@cloudoperators/juno-ui-components"
import { Link, useLocation } from "wouter"
import { Project } from "../../../shared/types/models"
import { Project } from "../../../server/Project/types/models"

type ProjectListViewProps = {
projects: Project[] | undefined
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Icon, PopupMenu } from "@cloudoperators/juno-ui-components"
import { useLocation, Link } from "wouter"
import { Project } from "../../../shared/types/models"
import { Project } from "../../../server/Project/types/models"

type ProjectListViewProps = {
projects: Project[] | undefined
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Button, ComboBox, ComboBoxOption, Icon, Select } from "@cloudoperators/juno-ui-components"
import { Button, ComboBox, ComboBoxOption, Icon } from "@cloudoperators/juno-ui-components"
export type ViewMode = "list" | "card"

type ProjectsOverviewNavNbarProps = {
Expand Down
6 changes: 1 addition & 5 deletions apps/aurora-portal/src/client/Shell/AppContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import React, { Suspense } from "react"
import { Home } from "./Home"
import { About } from "./About"
import { ComputeOverview } from "../Compute/ComputeOverview"
import { Overview as IdentityOverview } from "../Identity/Overview"
import { SignIn } from "../Identity/Auth/SignIn"
import { SignIn } from "../Auth/SignIn"
import { trpcClient } from "../trpcClient"
import { Route, Switch } from "wouter"
import { useAuth } from "./AuthProvider"
Expand Down Expand Up @@ -66,9 +65,6 @@ export function AppContent() {
</Route>
{user && (
<>
<Route path="/identity">
<IdentityOverview />
</Route>
<Route path="/projects">
<ProjectsOverview client={trpcClient.project} />
</Route>
Expand Down
71 changes: 41 additions & 30 deletions apps/aurora-portal/src/client/Shell/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,72 +1,83 @@
import { createContext, ReactNode, useContext, useEffect, useState } from "react"
import { trpcClient } from "../trpcClient"
import { TokenData } from "../../server/Authentication/types/models"

interface LoginParams {
user: string
password: string
domainName: string
}

interface User {
id: string
name: string
domain: { id?: string; name?: string }
password_expires_at: string
session_expires_at: string
interface State {
tokenData?: TokenData | null
error?: string | null
isLoading: boolean
}

interface AuthApi {
login: (props: LoginParams) => Promise<void>
logout: () => Promise<void>
error: string | null
isLoading: boolean
user: User | null
}

interface State {
user: User | null
error: string | null
reason?: string | null
getAuthToken: () => Promise<string | null>
isAuthenticated: boolean
user?: TokenData["user"]
roles?: TokenData["roles"]
error?: string | null
isLoading: boolean
scopedDomain?: { id?: string; name?: string }
scopedProject?: { id?: string; name?: string }
session_expires_at?: TokenData["expires_at"]
}

const useAuthApi = (): AuthApi => {
const [auth, setAuth] = useState<State>({ user: null, error: null, isLoading: true })
const [authData, setAuthData] = useState<State>({ isLoading: true })

useEffect(() => {
trpcClient.identity.getAuthStatus
trpcClient.auth.token
.query()
.then((res) => {
setAuth({ user: res.user, error: null, isLoading: false, reason: res.reason })
.then((tokenData) => {
setAuthData({ tokenData, error: null, isLoading: false })
})
.catch((e) => setAuth({ user: null, error: e.message, isLoading: false }))
.catch((e) => setAuthData({ tokenData: null, error: e.message, isLoading: false }))
}, [])

const login = (props: LoginParams) => {
setAuth({ ...auth, error: null, isLoading: true })
return trpcClient.identity.login
setAuthData({ tokenData: null, error: null, isLoading: true })
return trpcClient.auth.login
.mutate(props)
.then((res) => {
setAuth({ user: res.user, error: null, isLoading: false })
.then((tokenData) => {
setAuthData({ tokenData, error: null, isLoading: false })
})
.catch((e) => setAuth({ user: null, error: e.message, isLoading: false }))
.catch((e) => setAuthData({ tokenData: null, error: e.message, isLoading: false }))
}

const logout = () => {
setAuth({ ...auth, error: null, isLoading: true })
return trpcClient.identity.logout
setAuthData({ ...authData, error: null, isLoading: true })
return trpcClient.auth.logout
.mutate()
.then(() => {
setAuth({ user: null, error: null, isLoading: false })
setAuthData({ tokenData: null, error: null, isLoading: false })
})
.catch((e) => setAuth({ user: null, error: e.message, isLoading: false }))
.catch((e) => setAuthData({ ...authData, error: e.message, isLoading: false }))
}

const getAuthToken = async () => {
const authToken = await trpcClient.auth.authToken.query()
return authToken
}

// Consolidate state and API logic into a single object
return {
login,
logout,
...auth,
getAuthToken,
session_expires_at: authData.tokenData?.expires_at,
isLoading: authData.isLoading,
error: authData.error,
isAuthenticated: !!authData.tokenData,
user: authData.tokenData?.user,
roles: authData.tokenData?.roles,
scopedDomain: authData.tokenData?.project?.domain || authData.tokenData?.domain,
scopedProject: authData.tokenData?.project,
}
}

Expand Down
26 changes: 19 additions & 7 deletions apps/aurora-portal/src/client/Shell/Navigation/MainNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { Link, useLocation } from "wouter"
import { Link } from "wouter"
import Logo from "../../assets/logo.svg?react"

import { UserMenu } from "./UserMenu"
import { NavigationItem } from "./types"
import { useAuth } from "../AuthProvider"

const navItem = (active: boolean) =>
`px-2 py-2 hover:text-theme-high ${active ? "font-semibold text-theme-accent" : "text-gray-700"}`
// const navItem = (active: boolean) =>
// `px-2 py-2 hover:text-theme-high ${active ? "font-semibold text-theme-accent" : "text-gray-700"}`

interface NavigationProps {
items: NavigationItem[]
}

export function MainNavigation({ items }: NavigationProps) {
const [location] = useLocation()

// const [location] = useLocation()
const { scopedDomain, scopedProject } = useAuth()
console.info(items)
return (
<nav>
{/* Main Navigation Bar */}
Expand All @@ -26,8 +28,18 @@ export function MainNavigation({ items }: NavigationProps) {
<span className="text-lg font-medium text-sap-grey-2">Aurora</span>
{/* Slightly smaller font for better balance */}
</Link>
<span className="text-sap-grey-1">/</span>
<span className="font-semibold text-lg text-sap-grey-2">mansoon1</span>
{scopedDomain?.name && (
<>
<span className="text-sap-grey-1">/</span>
<span className="font-semibold text-lg text-sap-grey-2">{scopedDomain.name}</span>
</>
)}
{scopedProject?.name && (
<>
<span className="text-sap-grey-1">/</span>
<span className="font-semibold text-lg text-sap-grey-2">{scopedProject.name}</span>
</>
)}
</div>

{/* Right Section: About & User Menu */}
Expand Down
6 changes: 3 additions & 3 deletions apps/aurora-portal/src/client/Shell/Navigation/UserMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react"
import { useLocation } from "wouter"
import { Button, Icon, PopupMenu } from "@cloudoperators/juno-ui-components"
import { Button, Icon } from "@cloudoperators/juno-ui-components"
import { useAuth } from "../../Shell/AuthProvider"

function Countdown({ className, passwordExpiresAt }: { className?: string; passwordExpiresAt: string }) {
Expand Down Expand Up @@ -40,7 +40,7 @@ function Countdown({ className, passwordExpiresAt }: { className?: string; passw
}

export function UserMenu() {
const { user, isLoading, logout: authLogout } = useAuth()
const { user, session_expires_at, isLoading, logout: authLogout } = useAuth()
const setLocation = useLocation()[1]
const [isOpen, setIsOpen] = useState(false)

Expand All @@ -65,7 +65,7 @@ export function UserMenu() {
<Button disabled={isLoading} variant="default" size="small" onClick={logout}>
Sign Out
</Button>
<Countdown passwordExpiresAt={user.session_expires_at} />
{session_expires_at && <Countdown passwordExpiresAt={session_expires_at} />}
</div>
) : (
<Button disabled={isLoading} variant="primary" size="small" onClick={login} className="w-full">
Expand Down
9 changes: 9 additions & 0 deletions apps/aurora-portal/src/server/Authentication/routers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { tokenRouter } from "./tokenRouter"

import { auroraRouter } from "../../trpc"

export const authRouters = {
auth: auroraRouter({
...tokenRouter,
}),
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { z } from "zod"
import { publicProcedure } from "../../trpc"

export const tokenRouter = {
token: publicProcedure.query(async ({ ctx }) => {
const token = ctx.openstack?.getToken()

return token?.tokenData || null
}),

authToken: publicProcedure.query(async ({ ctx }) => {
const token = ctx.openstack?.getToken()
return token?.authToken || null
}),

login: publicProcedure
.input(z.object({ user: z.string(), password: z.string(), domainName: z.string() }))
.mutation(async ({ input, ctx }) => {
const openstackSession = await ctx.createSession({
user: input.user,
password: input.password,
domain: input.domainName,
})

return openstackSession.getToken()?.tokenData || null
}),

logout: publicProcedure.mutation(async ({ ctx }) => {
ctx.terminateSession()
}),
}
9 changes: 9 additions & 0 deletions apps/aurora-portal/src/server/Authentication/types/models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { SignalOpenstackTokenType } from "@cobaltcore-dev/signal-openstack"

export interface User {
id: number
name: string
}

export type TokenData = SignalOpenstackTokenType["tokenData"]
export type AuthToken = SignalOpenstackTokenType["authToken"]
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { z } from "zod"
import { protectedProcedure } from "../../trpc"
import type { Server } from "../../../shared/types/models"
import type { Server } from "../types/models"

const sampleServers: Server[] = [
{
Expand Down
Loading

0 comments on commit c20c6c7

Please sign in to comment.