diff --git a/.github/project.yml b/.github/project.yml index 9f6c26ea4..12eee1723 100644 --- a/.github/project.yml +++ b/.github/project.yml @@ -1,5 +1,5 @@ name: Web UI release: - current-version: 0.0.4 - next-version: 0.0.5-SNAPSHOT + current-version: 0.0.5 + next-version: 0.0.6-SNAPSHOT diff --git a/ui/api/auth.tsx b/ui/api/auth.tsx deleted file mode 100644 index 6fcc043c8..000000000 --- a/ui/api/auth.tsx +++ /dev/null @@ -1,81 +0,0 @@ -"use server"; -import { getSession, setSession } from "@/utils/session"; -import { redirect } from "next/navigation"; -import { z } from "zod"; - -const ClusterSchema = z.object({ - id: z.string(), - type: z.string(), - attributes: z.object({ - name: z.string(), - bootstrapServers: z.string(), - }), -}); - -const Response = z.object({ - data: z.array(ClusterSchema), -}); - -export type Cluster = z.infer; - -const AuthProfileSchema = z.object({ - id: z.string(), - type: z.string(), - attributes: z.object({ - name: z.string(), - cluster: ClusterSchema, - mechanism: z.string(), - }), -}); - -export type AuthProfile = z.infer; - -export async function getAuthProfiles(): Promise { - const session = await getSession(); - - if (session?.authProfiles && Array.isArray(session.authProfiles)) { - return session.authProfiles.map((p) => AuthProfileSchema.parse(p)); - } - return []; -} - -export async function getAuthProfile(id: string) { - const authProfiles = await getAuthProfiles(); - - const authProfile = authProfiles.find((p) => p.id === id); - if (!authProfile) { - redirect("/"); - } - return authProfile; -} - -export async function createAuthProfile(clusterId: string, name: string) { - const session = await getSession(); - const clusters = await getClusters(); - const cluster = clusters.find((c) => c.id === clusterId); - if (!cluster) throw new Error("Invalid cluster id"); - const authProfiles = (session?.authProfiles || []) as AuthProfile[]; - const newProfile: AuthProfile = { - id: `${authProfiles.length + 1}`, - type: "auth_profile", - attributes: { - cluster, - mechanism: "PLAIN", - name, - }, - }; - const newAuthProfiles = [...authProfiles, newProfile]; - await setSession({ authProfiles: newAuthProfiles }); - return newProfile; -} - -export async function getClusters(): Promise { - const url = `${process.env.BACKEND_URL}/api/kafkas?fields%5Bkafkas%5D=name,bootstrapServers,authType`; - const res = await fetch(url, { - headers: { - Accept: "application/json", - }, - }); - const rawData = await res.json(); - return Response.parse(rawData).data; -} diff --git a/ui/api/bookmarks.ts b/ui/api/bookmarks.ts new file mode 100644 index 000000000..a4140a76b --- /dev/null +++ b/ui/api/bookmarks.ts @@ -0,0 +1,72 @@ +"use server"; +import { Bookmark, BookmarkSchema, Cluster, Response } from "@/api/types"; +import { getSession, setSession } from "@/utils/session"; +import { redirect } from "next/navigation"; + +export async function getBookmarks(): Promise { + const session = await getSession(); + + if (session?.bookmarks && Array.isArray(session.bookmarks)) { + return session.bookmarks.map((p) => BookmarkSchema.parse(p)); + } + return []; +} + +export async function getBookmark(id: string) { + const bookmarks = await getBookmarks(); + + const bookmark = bookmarks.find((p) => p.id === id); + if (!bookmark) { + redirect("/"); + } + return bookmark; +} + +export async function setPartialBookmark(formData: FormData) { + const session = await getSession(); + const newBookmark = session?.newBookmark || {}; + const data = Object.fromEntries(formData); + const newSession = { ...session, newBookmark: { ...newBookmark, ...data } }; + console.dir({ data, session, newSession }); + await setSession(newSession); +} + +export async function createBookmark({ + name, + bootstrapServer, + principal, + cluster, +}: { + bootstrapServer: string; + principal: string; + name: string; + cluster: Cluster | undefined; +}) { + const session = await getSession(); + const bookmarks = (session?.bookmarks || []) as Bookmark[]; + const newProfile: Bookmark = { + id: crypto.randomUUID(), + type: "bookmark", + attributes: { + cluster, + mechanism: "PLAIN", + name, + bootstrapServer, + principal, + }, + }; + const newAuthProfiles = [...bookmarks, newProfile]; + await setSession({ bookmarks: newAuthProfiles, newBookmark: undefined }); + return newProfile; +} + +export async function getClusters(): Promise { + const url = `${process.env.BACKEND_URL}/api/kafkas?fields%5Bkafkas%5D=name,bootstrapServers,authType`; + const res = await fetch(url, { + headers: { + Accept: "application/json", + }, + }); + const rawData = await res.json(); + return Response.parse(rawData).data; +} diff --git a/ui/api/tools.tsx b/ui/api/tools.ts similarity index 100% rename from ui/api/tools.tsx rename to ui/api/tools.ts diff --git a/ui/api/topics.tsx b/ui/api/topics.ts similarity index 66% rename from ui/api/topics.tsx rename to ui/api/topics.ts index 3d538d25c..e756dc8f5 100644 --- a/ui/api/topics.tsx +++ b/ui/api/topics.ts @@ -1,61 +1,4 @@ -import { z } from "zod"; - -const PartitionSchema = z.object({ - partition: z.number(), - leader: z.object({ - id: z.number(), - host: z.string(), - port: z.number(), - }), - replicas: z.array( - z.object({ - id: z.number(), - host: z.string(), - port: z.number(), - }), - ), - isr: z.array( - z.object({ - id: z.number(), - host: z.string(), - port: z.number(), - }), - ), - offset: z.object({ - offset: z.number(), - leaderEpoch: z.number(), - }), -}); - -const ConfigSchema = z.object({ - value: z.string(), - source: z.string(), - sensitive: z.boolean(), - readOnly: z.boolean(), - type: z.string(), -}); - -const TopicSchema = z.object({ - id: z.string(), - type: z.string(), - attributes: z.object({ - name: z.string(), - internal: z.boolean(), - partitions: z.array(PartitionSchema), - authorizedOperations: z.array(z.string()), - configs: z.record(z.string(), ConfigSchema), - }), -}); - -const TopicsResponse = z.object({ - data: z.array(TopicSchema), -}); - -const TopicResponse = z.object({ - data: TopicSchema, -}); - -export type Topic = z.infer; +import { Topic, TopicResponse, TopicsResponse } from "@/api/types"; export async function getTopics(kafkaId: string): Promise { const url = `${process.env.BACKEND_URL}/api/kafkas/${kafkaId}/topics?fields%5Btopics%5D=name,internal,partitions,authorizedOperations,configs&offsetSpec=latest`; diff --git a/ui/api/types.ts b/ui/api/types.ts new file mode 100644 index 000000000..bb974ea94 --- /dev/null +++ b/ui/api/types.ts @@ -0,0 +1,77 @@ +import { z } from "zod"; + +const ClusterSchema = z.object({ + id: z.string(), + type: z.string(), + attributes: z.object({ + name: z.string(), + bootstrapServers: z.string(), + }), +}); +export const Response = z.object({ + data: z.array(ClusterSchema), +}); +export type Cluster = z.infer; +export const BookmarkSchema = z.object({ + id: z.string(), + type: z.string(), + attributes: z.object({ + name: z.string(), + bootstrapServer: z.string(), + principal: z.string(), + cluster: ClusterSchema.optional(), + mechanism: z.string(), + }), +}); +export type Bookmark = z.infer; +const PartitionSchema = z.object({ + partition: z.number(), + leader: z.object({ + id: z.number(), + host: z.string(), + port: z.number(), + }), + replicas: z.array( + z.object({ + id: z.number(), + host: z.string(), + port: z.number(), + }), + ), + isr: z.array( + z.object({ + id: z.number(), + host: z.string(), + port: z.number(), + }), + ), + offset: z.object({ + offset: z.number(), + leaderEpoch: z.number(), + }), +}); +const ConfigSchema = z.object({ + value: z.string(), + source: z.string(), + sensitive: z.boolean(), + readOnly: z.boolean(), + type: z.string(), +}); +const TopicSchema = z.object({ + id: z.string(), + type: z.string(), + attributes: z.object({ + name: z.string(), + internal: z.boolean(), + partitions: z.array(PartitionSchema), + authorizedOperations: z.array(z.string()), + configs: z.record(z.string(), ConfigSchema), + }), +}); +export const TopicsResponse = z.object({ + data: z.array(TopicSchema), +}); +export const TopicResponse = z.object({ + data: TopicSchema, +}); +export type Topic = z.infer; diff --git a/ui/api/user.ts b/ui/api/user.ts deleted file mode 100644 index 5af8bbfab..000000000 --- a/ui/api/user.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type User = { - username: string; -} diff --git a/ui/app/[locale]/(authProfiles)/loading.tsx b/ui/app/[locale]/(authProfiles)/loading.tsx deleted file mode 100644 index 8fce40771..000000000 --- a/ui/app/[locale]/(authProfiles)/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Loading } from "@/components/loading"; - -export default function Content() { - return ; -} diff --git a/ui/app/[locale]/(authProfiles)/new-auth-profile/[cluster]/page.tsx b/ui/app/[locale]/(authProfiles)/new-auth-profile/[cluster]/page.tsx deleted file mode 100644 index 4371f9960..000000000 --- a/ui/app/[locale]/(authProfiles)/new-auth-profile/[cluster]/page.tsx +++ /dev/null @@ -1,151 +0,0 @@ -"use client"; -import { ButtonLink } from "@/components/buttonLink"; -import { - ActionGroup, - Card, - CardBody, - Form, - FormGroup, - PageSection, - ProgressStep, - ProgressStepper, - Radio, - Split, - SplitItem, - Text, - TextContent, - TextInput, - Title, -} from "@/libs/patternfly/react-core"; -import { useState } from "react"; - -export default function NewAuthProfilePage({ - params: { cluster }, -}: { - params: { cluster: string }; -}) { - const [username, setUsername] = useState(""); - return ( - <> - - - - - - Create a new Authorization Profile to access a Cluster - - - Brief description of what an Authorization Profile is and works - - - - - - - - - Select a Cluster - - - Configure Authentication - - - Validate Connection - - - - - - -
- - - - - - - - - setUsername(value)} - /> - - - - - - - Validate - - -
-
-
-
- - ); -} diff --git a/ui/app/[locale]/(authProfiles)/new-auth-profile/[cluster]/validate/page.tsx b/ui/app/[locale]/(authProfiles)/new-auth-profile/[cluster]/validate/page.tsx deleted file mode 100644 index 606c3f17c..000000000 --- a/ui/app/[locale]/(authProfiles)/new-auth-profile/[cluster]/validate/page.tsx +++ /dev/null @@ -1,154 +0,0 @@ -"use client"; -import { createAuthProfile } from "@/api/auth"; -import { - Button, - EmptyState, - EmptyStateActions, - EmptyStateBody, - EmptyStateFooter, - EmptyStateHeader, - EmptyStateIcon, - PageSection, - Progress, - ProgressStep, - ProgressStepper, - Split, - SplitItem, - Text, - TextContent, - Title, -} from "@/libs/patternfly/react-core"; -import { CogsIcon } from "@/libs/patternfly/react-icons"; -import { Route } from "next"; -import { useRouter, useSearchParams } from "next/navigation"; -import { useCallback, useEffect, useState, useTransition } from "react"; - -export default function NewAuthProfilePage({ - params: { cluster }, -}: { - params: { cluster: string }; -}) { - const search = useSearchParams(); - return ( - <> - - - - - - Create a new Authorization Profile to access a Cluster - - - Brief description of what an Authorization Profile is and works - - - - - - - - - Select a Cluster - - - Configure Authentication - - - Validate Connection - - - - - - - - ); -} - -function ValidationProgress({ - cluster, - username, -}: { - cluster: string; - username: string; -}) { - const router = useRouter(); - const [isPending, startTransition] = useTransition(); - const [percentValidated, setPercentValidated] = useState(0); - - const tick = useCallback(() => { - if (percentValidated < 100) { - setPercentValidated((prevValue) => prevValue + 20); - } - }, [percentValidated]); - - useEffect(() => { - const interval = setInterval(() => tick(), 300); - - return () => { - clearInterval(interval); - }; - }, [tick]); - - return ( -
- - } - /> - - - - - Description can be used to further elaborate on the validation step, - or give the user a better idea of how long the process will take. - - - - - - - -
- ); -} diff --git a/ui/app/[locale]/(authProfiles)/new-auth-profile/page.tsx b/ui/app/[locale]/(authProfiles)/new-auth-profile/page.tsx deleted file mode 100644 index a607d215c..000000000 --- a/ui/app/[locale]/(authProfiles)/new-auth-profile/page.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { getClusters } from "@/api/auth"; -import { ClusterCard } from "@/components/clusterCard"; -import { - Gallery, - PageSection, - ProgressStep, - ProgressStepper, - Split, - SplitItem, - Text, - TextContent, - Title, -} from "@/libs/patternfly/react-core"; - -export default async function NewAuthProfilePage() { - const clusters = await getClusters(); - - return ( - <> - - - - - - Create a new Authorization Profile to access a Cluster - - - Brief description of what an Authorization Profile is and how it - works. - - - - - - - - - Select a Cluster - - - Configure Authentication - - - Validate Connection - - - - - - {clusters.map((c) => ( - - ))} - - - - ); -} diff --git a/ui/app/[locale]/(authProfiles)/@breadcrumbs/new-auth-profile/[cluster]/validate/page.tsx b/ui/app/[locale]/(bookmarks)/@breadcrumbs/create/authentication/page.tsx similarity index 64% rename from ui/app/[locale]/(authProfiles)/@breadcrumbs/new-auth-profile/[cluster]/validate/page.tsx rename to ui/app/[locale]/(bookmarks)/@breadcrumbs/create/authentication/page.tsx index e3a922824..f59caac9d 100644 --- a/ui/app/[locale]/(authProfiles)/@breadcrumbs/new-auth-profile/[cluster]/validate/page.tsx +++ b/ui/app/[locale]/(bookmarks)/@breadcrumbs/create/authentication/page.tsx @@ -4,8 +4,8 @@ import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; export default function DefaultBreadcrumb() { return ( - Authorization Profiles - Brokers + Bookmarks + Create ); } diff --git a/ui/app/[locale]/(authProfiles)/@breadcrumbs/new-auth-profile/[cluster]/page.tsx b/ui/app/[locale]/(bookmarks)/@breadcrumbs/create/page.tsx similarity index 64% rename from ui/app/[locale]/(authProfiles)/@breadcrumbs/new-auth-profile/[cluster]/page.tsx rename to ui/app/[locale]/(bookmarks)/@breadcrumbs/create/page.tsx index e3a922824..f59caac9d 100644 --- a/ui/app/[locale]/(authProfiles)/@breadcrumbs/new-auth-profile/[cluster]/page.tsx +++ b/ui/app/[locale]/(bookmarks)/@breadcrumbs/create/page.tsx @@ -4,8 +4,8 @@ import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; export default function DefaultBreadcrumb() { return ( - Authorization Profiles - Brokers + Bookmarks + Create ); } diff --git a/ui/app/[locale]/(authProfiles)/@breadcrumbs/new-auth-profile/page.tsx b/ui/app/[locale]/(bookmarks)/@breadcrumbs/create/validate/page.tsx similarity index 61% rename from ui/app/[locale]/(authProfiles)/@breadcrumbs/new-auth-profile/page.tsx rename to ui/app/[locale]/(bookmarks)/@breadcrumbs/create/validate/page.tsx index 0ceed936c..f59caac9d 100644 --- a/ui/app/[locale]/(authProfiles)/@breadcrumbs/new-auth-profile/page.tsx +++ b/ui/app/[locale]/(bookmarks)/@breadcrumbs/create/validate/page.tsx @@ -4,8 +4,8 @@ import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; export default function DefaultBreadcrumb() { return ( - Authorization Profiles - New Authorization Profile + Bookmarks + Create ); } diff --git a/ui/app/[locale]/(authProfiles)/@breadcrumbs/page.tsx b/ui/app/[locale]/(bookmarks)/@breadcrumbs/page.tsx similarity index 71% rename from ui/app/[locale]/(authProfiles)/@breadcrumbs/page.tsx rename to ui/app/[locale]/(bookmarks)/@breadcrumbs/page.tsx index 681362fe0..1bf7ff5f5 100644 --- a/ui/app/[locale]/(authProfiles)/@breadcrumbs/page.tsx +++ b/ui/app/[locale]/(bookmarks)/@breadcrumbs/page.tsx @@ -3,7 +3,7 @@ import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; export default function DefaultBreadcrumb() { return ( - Authorization Profiles + Bookmarks ); } diff --git a/ui/app/[locale]/(bookmarks)/create/CreateBookmarkStep1Page.client.tsx b/ui/app/[locale]/(bookmarks)/create/CreateBookmarkStep1Page.client.tsx new file mode 100644 index 000000000..021d1539e --- /dev/null +++ b/ui/app/[locale]/(bookmarks)/create/CreateBookmarkStep1Page.client.tsx @@ -0,0 +1,158 @@ +"use client"; +import { setPartialBookmark } from "@/api/bookmarks"; +import { + ActionGroup, + Button, + Card, + CardBody, + Form, + FormGroup, + FormHelperText, + HelperText, + HelperTextItem, + PageSection, + ProgressStep, + ProgressStepper, + Sidebar, + SidebarContent, + SidebarPanel, + Split, + SplitItem, + Text, + TextContent, + TextInput, + Title, + Truncate, +} from "@/libs/patternfly/react-core"; +import { useRouter } from "next/navigation"; +import { useState, useTransition } from "react"; + +export function CreateBookmarkStep1Page({ clusters }: { clusters: string[] }) { + const router = useRouter(); + const [bootstrapServer, setBootstrapServer] = useState(""); + const [name, setName] = useState(""); + const [pending, startTransition] = useTransition(); + return ( + <> + + + + + + Create a new Bookmark to access a Cluster + + + Brief description of what a Bookmark is and how it works. + + + + + + + + + + + + + Cluster information + + + Configure Authentication + + + Validate Connection + + + + +
{ + await setPartialBookmark(formData); + startTransition(() => { + router.push("/create/authentication"); + }); + }} + > + + setBootstrapServer(value)} + /> + + + +

+ Demo note: provide any value to the bootstrap url to + see how the "Validate Connection" works + when failing to connect to a cluster. +

+

Valid boostrap servers value for the demo are:

+ +
+
+
+
+ + setName(value)} + /> + + + + +
+
+
+
+
+
+ + ); +} diff --git a/ui/app/[locale]/(bookmarks)/create/authentication/CreateBookmarkStep2Page.client.tsx b/ui/app/[locale]/(bookmarks)/create/authentication/CreateBookmarkStep2Page.client.tsx new file mode 100644 index 000000000..8d41ab48d --- /dev/null +++ b/ui/app/[locale]/(bookmarks)/create/authentication/CreateBookmarkStep2Page.client.tsx @@ -0,0 +1,148 @@ +"use client"; +import { setPartialBookmark } from "@/api/bookmarks"; +import { + ActionGroup, + Card, + CardBody, + Form, + FormGroup, + PageSection, + ProgressStep, + ProgressStepper, + Radio, + Sidebar, + SidebarContent, + SidebarPanel, + Split, + SplitItem, + Text, + TextContent, + TextInput, + Title, +} from "@/libs/patternfly/react-core"; +import { Button } from "@patternfly/react-core"; +import { useRouter } from "next/navigation"; +import { useState, useTransition } from "react"; + +export function CreateBookmarkStep2Page() { + const router = useRouter(); + const [principal, setPrincipal] = useState(""); + const [password, setPassword] = useState(""); + const [pending, startTransition] = useTransition(); + return ( + <> + + + + + + Create a new Bookmark to access a Cluster + + + Brief description of what a Bookmark is and how it works. + + + + + + + + + + + + + Cluster Information + + + Configure Authentication + + + Validate Connection + + + + +
{ + await setPartialBookmark(formData); + startTransition(() => { + router.push("/create/validate"); + }); + }} + > + + + + + + + + + setPrincipal(value)} + /> + + + setPassword(value)} + /> + + + + +
+
+
+
+
+
+ + ); +} diff --git a/ui/app/[locale]/(bookmarks)/create/authentication/page.tsx b/ui/app/[locale]/(bookmarks)/create/authentication/page.tsx new file mode 100644 index 000000000..6c43c8f17 --- /dev/null +++ b/ui/app/[locale]/(bookmarks)/create/authentication/page.tsx @@ -0,0 +1,13 @@ +import { getSession } from "@/utils/session"; +import { redirect } from "next/navigation"; +import { CreateBookmarkStep2Page } from "./CreateBookmarkStep2Page.client"; + +export default async function AsyncCreateBookmarkStep2Page() { + const session = await getSession(); + const newBookmark = session?.newBookmark; + const { name, boostrapServer } = newBookmark || {}; + if (!name || !boostrapServer) { + redirect("/"); + } + return ; +} diff --git a/ui/app/[locale]/(bookmarks)/create/page.tsx b/ui/app/[locale]/(bookmarks)/create/page.tsx new file mode 100644 index 000000000..f491caf0b --- /dev/null +++ b/ui/app/[locale]/(bookmarks)/create/page.tsx @@ -0,0 +1,11 @@ +import { getClusters } from "@/api/bookmarks"; +import { CreateBookmarkStep1Page } from "./CreateBookmarkStep1Page.client"; + +export default async function AsyncCreateBookmarkStep1Page() { + const clusters = await getClusters(); + return ( + c.attributes.bootstrapServers)} + /> + ); +} diff --git a/ui/app/[locale]/(bookmarks)/create/validate/CreateBookmarkStep3Page.client.tsx b/ui/app/[locale]/(bookmarks)/create/validate/CreateBookmarkStep3Page.client.tsx new file mode 100644 index 000000000..2a5b552d2 --- /dev/null +++ b/ui/app/[locale]/(bookmarks)/create/validate/CreateBookmarkStep3Page.client.tsx @@ -0,0 +1,213 @@ +"use client"; +import { createBookmark } from "@/api/bookmarks"; +import { Cluster } from "@/api/types"; +import { + Button, + Card, + CardBody, + EmptyState, + EmptyStateActions, + EmptyStateBody, + EmptyStateFooter, + EmptyStateHeader, + EmptyStateIcon, + PageSection, + Progress, + ProgressStep, + ProgressStepper, + Sidebar, + SidebarContent, + SidebarPanel, + Split, + SplitItem, + Text, + TextContent, + Title, +} from "@/libs/patternfly/react-core"; +import { CogsIcon } from "@/libs/patternfly/react-icons"; +import { WarningTriangleIcon } from "@patternfly/react-icons"; +import { Route } from "next"; +import { useRouter } from "next/navigation"; +import { useCallback, useEffect, useState, useTransition } from "react"; + +type Props = { + name: string; + bootstrapServer: string; + principal: string; + cluster: Cluster | undefined; +}; + +export function CreateBookmarkStep3Page({ + name, + bootstrapServer, + principal, + cluster, +}: Props) { + return ( + <> + + + + + + Create a new Bookmark to access a Cluster + + + Brief description of what a Bookmark is and how it works. + + + + + + + + + + + + + Select a Cluster + + + Configure Authentication + + + Validate Connection + + + + + + + + + + + + ); +} + +function ValidationProgress({ + name, + bootstrapServer, + cluster, + principal, +}: Props) { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + const [percentValidated, setPercentValidated] = useState(0); + + const tick = useCallback(() => { + if (percentValidated < 100) { + setPercentValidated((prevValue) => prevValue + 20); + } + }, [percentValidated]); + + useEffect(() => { + const interval = setInterval(() => tick(), 300); + + return () => { + clearInterval(interval); + }; + }, [tick]); + + return ( +
+ + + } + /> + + + + + Description can be used to further elaborate on the validation step, + or give the user a better idea of how long the process will take. + + + + + {percentValidated === 100 && cluster === undefined && ( + + )} + + + +
+ ); +} diff --git a/ui/app/[locale]/(bookmarks)/create/validate/page.tsx b/ui/app/[locale]/(bookmarks)/create/validate/page.tsx new file mode 100644 index 000000000..682dc6718 --- /dev/null +++ b/ui/app/[locale]/(bookmarks)/create/validate/page.tsx @@ -0,0 +1,28 @@ +import { getClusters } from "@/api/bookmarks"; +import { getSession } from "@/utils/session"; +import { redirect } from "next/navigation"; +import { CreateBookmarkStep3Page } from "./CreateBookmarkStep3Page.client"; + +export default async function AsyncNewAuthProfilePage() { + const session = await getSession(); + const newBookmark = session?.newBookmark; + console.log("wtf", session, newBookmark); + + const { name, principal, boostrapServer } = newBookmark || {}; + if (!name || !principal || !boostrapServer) { + redirect("/"); + } + + const clusters = await getClusters(); + const cluster = clusters.find( + (c) => c.attributes.bootstrapServers === boostrapServer, + ); + return ( + + ); +} diff --git a/ui/app/[locale]/(authProfiles)/layout.tsx b/ui/app/[locale]/(bookmarks)/layout.tsx similarity index 100% rename from ui/app/[locale]/(authProfiles)/layout.tsx rename to ui/app/[locale]/(bookmarks)/layout.tsx diff --git a/ui/app/[locale]/(authProfiles)/page.tsx b/ui/app/[locale]/(bookmarks)/page.tsx similarity index 54% rename from ui/app/[locale]/(authProfiles)/page.tsx rename to ui/app/[locale]/(bookmarks)/page.tsx index 3f7ba141d..090d1f215 100644 --- a/ui/app/[locale]/(authProfiles)/page.tsx +++ b/ui/app/[locale]/(bookmarks)/page.tsx @@ -1,5 +1,5 @@ -import { getAuthProfiles } from "@/api/auth"; -import { AuthProfileCard } from "@/components/authProfileCard"; +import { getBookmarks } from "@/api/bookmarks"; +import { BookmarkCard } from "@/components/bookmarkCard"; import { ButtonLink } from "@/components/buttonLink"; import { EmptyState, @@ -18,7 +18,7 @@ import { import { PlusCircleIcon } from "@/libs/patternfly/react-icons"; export default async function Home() { - const authProfiles = await getAuthProfiles(); + const bookmarks = await getBookmarks(); return ( <> @@ -26,42 +26,36 @@ export default async function Home() { - Authorization Profiles + Bookmarks - Select an Authorization Profile to access a Cluster from a list - of previously created ones. Clusters can have more than one - Authorization Profile. + Brief description of what a Bookmark is and how it works. - {authProfiles.length > 0 && ( + {bookmarks.length > 0 && ( - - Add a new Authorization Profile - + Add a Bookmark )} - {authProfiles.map((ap) => ( - + {bookmarks.map((ap) => ( + ))} - {authProfiles.length === 0 && ( + {bookmarks.length === 0 && ( - No Authorization Profile + No Bookmarks - - To get started, create an Authorization Profile - + To get started, create a Bookmark - - Create a new Authorization Profile + + Create a Bookmark diff --git a/ui/app/[locale]/[authProfile]/@authProfile/default.tsx b/ui/app/[locale]/[authProfile]/@authProfile/default.tsx deleted file mode 100644 index 070492de0..000000000 --- a/ui/app/[locale]/[authProfile]/@authProfile/default.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { getAuthProfile } from "@/api/auth"; -import { AppBreadcrumbs } from "@/components/appBreadcrumbs"; - -export default async function ToolsBreadcrumb({ - params, -}: { - params: { - authProfile: string; - }; -}) { - const authProfile = await getAuthProfile(params.authProfile); - return ( - - ); -} diff --git a/ui/app/[locale]/[authProfile]/@breadcrumbs/default.tsx b/ui/app/[locale]/[authProfile]/@breadcrumbs/default.tsx deleted file mode 100644 index 74717cd6b..000000000 --- a/ui/app/[locale]/[authProfile]/@breadcrumbs/default.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function DefaultBreadcrumb() { - return undefined; -} diff --git a/ui/app/[locale]/[authProfile]/loading.tsx b/ui/app/[locale]/[authProfile]/loading.tsx deleted file mode 100644 index ce3112250..000000000 --- a/ui/app/[locale]/[authProfile]/loading.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { Loading } from "@/components/loading"; -import { PageSection } from "@/libs/patternfly/react-core"; - -export default function Content() { - return ( - - - - ); -} diff --git a/ui/app/[locale]/[authProfile]/topics/[topic]/messages/loading.tsx b/ui/app/[locale]/[authProfile]/topics/[topic]/messages/loading.tsx deleted file mode 100644 index 8fce40771..000000000 --- a/ui/app/[locale]/[authProfile]/topics/[topic]/messages/loading.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Loading } from "@/components/loading"; - -export default function Content() { - return ; -} diff --git a/ui/app/[locale]/[authProfile]/topics/[topic]/overview/page.tsx b/ui/app/[locale]/[authProfile]/topics/[topic]/overview/page.tsx deleted file mode 100644 index 335232319..000000000 --- a/ui/app/[locale]/[authProfile]/topics/[topic]/overview/page.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { getAuthProfile } from "@/api/auth"; -import { getTopic } from "@/api/topics"; -import { TopicDashboard } from "@/components/topicPage"; - -export default async function AsyncTopicPage({ - params, -}: { - params: { authProfile: string; topic: string }; -}) { - const authProfile = await getAuthProfile(params.authProfile); - const topic = await getTopic(authProfile.attributes.cluster.id, params.topic); - return ; -} diff --git a/ui/app/[locale]/[bookmark]/@appBreadcrumb/default.tsx b/ui/app/[locale]/[bookmark]/@appBreadcrumb/default.tsx new file mode 100644 index 000000000..91be820a4 --- /dev/null +++ b/ui/app/[locale]/[bookmark]/@appBreadcrumb/default.tsx @@ -0,0 +1,27 @@ +import { getBookmark } from "@/api/bookmarks"; +import { AppBreadcrumbs } from "@/components/appBreadcrumbs"; +import { Truncate } from "@/libs/patternfly/react-core"; + +export default async function ToolsBreadcrumb({ + params, +}: { + params: { + bookmark: string; + }; +}) { + const bookmark = await getBookmark(params.bookmark); + return ( + + {bookmark.attributes.name} ({bookmark.attributes.principal}@ + + ) + + } + /> + ); +} diff --git a/ui/app/[locale]/[bookmark]/@appBreadcrumb/loading.tsx b/ui/app/[locale]/[bookmark]/@appBreadcrumb/loading.tsx new file mode 100644 index 000000000..7f22eac98 --- /dev/null +++ b/ui/app/[locale]/[bookmark]/@appBreadcrumb/loading.tsx @@ -0,0 +1,5 @@ +import { AppBreadcrumbs } from "@/components/appBreadcrumbs"; + +export default function LoadingAppBreadcrumb() { + return ; +} diff --git a/ui/app/[locale]/[authProfile]/@breadcrumbs/brokers/page.tsx b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/brokers/page.tsx similarity index 100% rename from ui/app/[locale]/[authProfile]/@breadcrumbs/brokers/page.tsx rename to ui/app/[locale]/[bookmark]/@sectionBreadcrumb/brokers/page.tsx diff --git a/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/default.tsx b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/default.tsx new file mode 100644 index 000000000..e095f21c4 --- /dev/null +++ b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/default.tsx @@ -0,0 +1,9 @@ +import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; + +export default function DefaultBreadcrumb() { + return ( + + Loading... + + ); +} diff --git a/ui/app/[locale]/[authProfile]/@breadcrumbs/overview/page.tsx b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/overview/page.tsx similarity index 100% rename from ui/app/[locale]/[authProfile]/@breadcrumbs/overview/page.tsx rename to ui/app/[locale]/[bookmark]/@sectionBreadcrumb/overview/page.tsx diff --git a/ui/app/[locale]/[authProfile]/@breadcrumbs/topics/[topic]/overview/page.tsx b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/[topic]/messages/page.tsx similarity index 64% rename from ui/app/[locale]/[authProfile]/@breadcrumbs/topics/[topic]/overview/page.tsx rename to ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/[topic]/messages/page.tsx index 607b9d306..23247edeb 100644 --- a/ui/app/[locale]/[authProfile]/@breadcrumbs/topics/[topic]/overview/page.tsx +++ b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/[topic]/messages/page.tsx @@ -1,4 +1,4 @@ -import { getAuthProfile } from "@/api/auth"; +import { getBookmark } from "@/api/bookmarks"; import { getTopic } from "@/api/topics"; import { BreadcrumbLink } from "@/components/breadcrumbLink"; import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; @@ -6,10 +6,10 @@ import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; export default async function TopicBreadcrumb({ params, }: { - params: { authProfile: string; topic: string }; + params: { bookmark: string; topic: string }; }) { - const authProfile = await getAuthProfile(params.authProfile); - const topic = await getTopic(authProfile.attributes.cluster.id, params.topic); + const bookmark = await getBookmark(params.bookmark); + const topic = await getTopic(bookmark.attributes.cluster.id, params.topic); return ( Topics diff --git a/ui/app/[locale]/[authProfile]/@breadcrumbs/topics/[topic]/messages/page.tsx b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/[topic]/overview/page.tsx similarity index 64% rename from ui/app/[locale]/[authProfile]/@breadcrumbs/topics/[topic]/messages/page.tsx rename to ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/[topic]/overview/page.tsx index 607b9d306..23247edeb 100644 --- a/ui/app/[locale]/[authProfile]/@breadcrumbs/topics/[topic]/messages/page.tsx +++ b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/[topic]/overview/page.tsx @@ -1,4 +1,4 @@ -import { getAuthProfile } from "@/api/auth"; +import { getBookmark } from "@/api/bookmarks"; import { getTopic } from "@/api/topics"; import { BreadcrumbLink } from "@/components/breadcrumbLink"; import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; @@ -6,10 +6,10 @@ import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; export default async function TopicBreadcrumb({ params, }: { - params: { authProfile: string; topic: string }; + params: { bookmark: string; topic: string }; }) { - const authProfile = await getAuthProfile(params.authProfile); - const topic = await getTopic(authProfile.attributes.cluster.id, params.topic); + const bookmark = await getBookmark(params.bookmark); + const topic = await getTopic(bookmark.attributes.cluster.id, params.topic); return ( Topics diff --git a/ui/app/[locale]/[authProfile]/@breadcrumbs/topics/[topic]/page.tsx b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/[topic]/page.tsx similarity index 54% rename from ui/app/[locale]/[authProfile]/@breadcrumbs/topics/[topic]/page.tsx rename to ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/[topic]/page.tsx index d8fecbf41..59e409b5b 100644 --- a/ui/app/[locale]/[authProfile]/@breadcrumbs/topics/[topic]/page.tsx +++ b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/[topic]/page.tsx @@ -1,4 +1,4 @@ -import { getAuthProfile } from "@/api/auth"; +import { getBookmark } from "@/api/bookmarks"; import { getTopic } from "@/api/topics"; import { BreadcrumbLink } from "@/components/breadcrumbLink"; import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; @@ -6,13 +6,13 @@ import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; export default async function TopicBreadcrumb({ params, }: { - params: { authProfile: string; topic: string }; + params: { bookmark: string; topic: string }; }) { - const authProfile = await getAuthProfile(params.authProfile); - const topic = await getTopic(authProfile.attributes.cluster.id, params.topic); + const bookmark = await getBookmark(params.bookmark); + const topic = await getTopic(bookmark.attributes.cluster.id, params.topic); return ( - Topics + Topics {topic.attributes.name} ); diff --git a/ui/app/[locale]/[authProfile]/@breadcrumbs/topics/page.tsx b/ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/page.tsx similarity index 100% rename from ui/app/[locale]/[authProfile]/@breadcrumbs/topics/page.tsx rename to ui/app/[locale]/[bookmark]/@sectionBreadcrumb/topics/page.tsx diff --git a/ui/app/[locale]/[authProfile]/brokers/page.tsx b/ui/app/[locale]/[bookmark]/brokers/page.tsx similarity index 100% rename from ui/app/[locale]/[authProfile]/brokers/page.tsx rename to ui/app/[locale]/[bookmark]/brokers/page.tsx diff --git a/ui/app/[locale]/[authProfile]/layout.tsx b/ui/app/[locale]/[bookmark]/layout.tsx similarity index 86% rename from ui/app/[locale]/[authProfile]/layout.tsx rename to ui/app/[locale]/[bookmark]/layout.tsx index 1fa41e62b..317caafc8 100644 --- a/ui/app/[locale]/[authProfile]/layout.tsx +++ b/ui/app/[locale]/[bookmark]/layout.tsx @@ -16,14 +16,14 @@ import { ReactNode } from "react"; type Props = { children: ReactNode; - authProfile: ReactNode; - breadcrumbs: ReactNode; + appBreadcrumb: ReactNode; + sectionBreadcrumb: ReactNode; }; export default async function ToolsLayout({ children, - authProfile, - breadcrumbs, + appBreadcrumb, + sectionBreadcrumb, }: Props) { const user = await getUser(); @@ -37,7 +37,7 @@ export default async function ToolsLayout({ content={ - {authProfile} + {appBreadcrumb} } @@ -56,7 +56,7 @@ export default async function ToolsLayout({ } - breadcrumb={breadcrumbs} + breadcrumb={sectionBreadcrumb} > {children} diff --git a/ui/app/[locale]/[authProfile]/overview/page.tsx b/ui/app/[locale]/[bookmark]/overview/page.tsx similarity index 100% rename from ui/app/[locale]/[authProfile]/overview/page.tsx rename to ui/app/[locale]/[bookmark]/overview/page.tsx diff --git a/ui/app/[locale]/[authProfile]/page.tsx b/ui/app/[locale]/[bookmark]/page.tsx similarity index 63% rename from ui/app/[locale]/[authProfile]/page.tsx rename to ui/app/[locale]/[bookmark]/page.tsx index a86b6217d..63be5a2ff 100644 --- a/ui/app/[locale]/[authProfile]/page.tsx +++ b/ui/app/[locale]/[bookmark]/page.tsx @@ -2,13 +2,13 @@ import { getTools } from "@/api/tools"; import { getUser } from "@/utils/session"; import { redirect } from "next/navigation"; -export default async function AuthProfileIndexPage({ +export default async function bookmarkIndexPage({ params, }: { - params: { authProfile: string }; + params: { bookmark: string }; }) { const auth = await getUser(); const tools = await getTools(auth.username === "admin"); - redirect(`${params.authProfile}${tools[0].url}`); + redirect(`${params.bookmark}${tools[0].url}`); } diff --git a/ui/app/[locale]/[authProfile]/topics/[topic]/layout.tsx b/ui/app/[locale]/[bookmark]/topics/[topic]/layout.tsx similarity index 55% rename from ui/app/[locale]/[authProfile]/topics/[topic]/layout.tsx rename to ui/app/[locale]/[bookmark]/topics/[topic]/layout.tsx index 3d36769c7..cbd668fa5 100644 --- a/ui/app/[locale]/[authProfile]/topics/[topic]/layout.tsx +++ b/ui/app/[locale]/[bookmark]/topics/[topic]/layout.tsx @@ -1,4 +1,4 @@ -import { getAuthProfile } from "@/api/auth"; +import { getBookmark } from "@/api/bookmarks"; import { getTopic } from "@/api/topics"; import { TopicPage } from "@/components/topicPage"; import { PropsWithChildren } from "react"; @@ -7,10 +7,10 @@ export default async function AsyncTopicPage({ children, params, }: PropsWithChildren<{ - params: { authProfile: string; topic: string }; + params: { bookmark: string; topic: string }; }>) { - const authProfile = await getAuthProfile(params.authProfile); - const topic = await getTopic(authProfile.attributes.cluster.id, params.topic); + const bookmark = await getBookmark(params.bookmark); + const topic = await getTopic(bookmark.attributes.cluster.id, params.topic); return {children}; } diff --git a/ui/app/[locale]/[bookmark]/topics/[topic]/messages/loading.tsx b/ui/app/[locale]/[bookmark]/topics/[topic]/messages/loading.tsx new file mode 100644 index 000000000..b652ad851 --- /dev/null +++ b/ui/app/[locale]/[bookmark]/topics/[topic]/messages/loading.tsx @@ -0,0 +1,5 @@ +import { TableSkeleton } from "@/components/table"; + +export default function Content() { + return ; +} diff --git a/ui/app/[locale]/[authProfile]/topics/[topic]/messages/page.tsx b/ui/app/[locale]/[bookmark]/topics/[topic]/messages/page.tsx similarity index 90% rename from ui/app/[locale]/[authProfile]/topics/[topic]/messages/page.tsx rename to ui/app/[locale]/[bookmark]/topics/[topic]/messages/page.tsx index 15db9d002..c219b73fb 100644 --- a/ui/app/[locale]/[authProfile]/topics/[topic]/messages/page.tsx +++ b/ui/app/[locale]/[bookmark]/topics/[topic]/messages/page.tsx @@ -1,4 +1,4 @@ -import { getAuthProfile } from "@/api/auth"; +import { getBookmark } from "@/api/bookmarks"; import { getTopicMessages } from "@/api/topics"; import { KafkaMessageBrowser, @@ -10,11 +10,11 @@ import { revalidateTag } from "next/cache"; export default async function Principals({ params, }: { - params: { authProfile: string; topic: string }; + params: { bookmark: string; topic: string }; }) { - const authProfile = await getAuthProfile(params.authProfile); + const bookmark = await getBookmark(params.bookmark); const data = await getTopicMessages( - authProfile.attributes.cluster.id, + bookmark.attributes.cluster.id, params.topic, ); switch (true) { diff --git a/ui/app/[locale]/[bookmark]/topics/[topic]/overview/page.tsx b/ui/app/[locale]/[bookmark]/topics/[topic]/overview/page.tsx new file mode 100644 index 000000000..7947e79ce --- /dev/null +++ b/ui/app/[locale]/[bookmark]/topics/[topic]/overview/page.tsx @@ -0,0 +1,13 @@ +import { getBookmark } from "@/api/bookmarks"; +import { getTopic } from "@/api/topics"; +import { TopicDashboard } from "@/components/topicPage"; + +export default async function AsyncTopicPage({ + params, +}: { + params: { bookmark: string; topic: string }; +}) { + const bookmark = await getBookmark(params.bookmark); + const topic = await getTopic(bookmark.attributes.cluster.id, params.topic); + return ; +} diff --git a/ui/app/[locale]/[authProfile]/topics/[topic]/page.tsx b/ui/app/[locale]/[bookmark]/topics/[topic]/page.tsx similarity index 74% rename from ui/app/[locale]/[authProfile]/topics/[topic]/page.tsx rename to ui/app/[locale]/[bookmark]/topics/[topic]/page.tsx index 734e94a42..8270ac838 100644 --- a/ui/app/[locale]/[authProfile]/topics/[topic]/page.tsx +++ b/ui/app/[locale]/[bookmark]/topics/[topic]/page.tsx @@ -3,7 +3,7 @@ import { redirect } from "next/navigation"; export default function TopicPage({ params, }: { - params: { authProfile: string; topic: string }; + params: { bookmark: string; topic: string }; }) { redirect(`${params.topic}/overview`); } diff --git a/ui/app/[locale]/[authProfile]/topics/page.tsx b/ui/app/[locale]/[bookmark]/topics/page.tsx similarity index 71% rename from ui/app/[locale]/[authProfile]/topics/page.tsx rename to ui/app/[locale]/[bookmark]/topics/page.tsx index e6ec58d46..2f1c3734a 100644 --- a/ui/app/[locale]/[authProfile]/topics/page.tsx +++ b/ui/app/[locale]/[bookmark]/topics/page.tsx @@ -1,5 +1,6 @@ -import { getAuthProfile } from "@/api/auth"; -import { getTopics, Topic } from "@/api/topics"; +import { getBookmark } from "@/api/bookmarks"; +import { getTopics } from "@/api/topics"; +import { Topic } from "@/api/types"; import { Topics } from "@/components/topics"; import { PageSection, Title } from "@/libs/patternfly/react-core"; import { getUser } from "@/utils/session"; @@ -7,11 +8,11 @@ import { getUser } from "@/utils/session"; export default async function TopicsPage({ params, }: { - params: { authProfile: string }; + params: { bookmark: string }; }) { const auth = await getUser(); - const authProfile = await getAuthProfile(params.authProfile); - const topics = await getTopics(authProfile.attributes.cluster.id); + const bookmark = await getBookmark(params.bookmark); + const topics = await getTopics(bookmark.attributes.cluster.id); return ( ); diff --git a/ui/components/appBreadcrumbs.tsx b/ui/components/appBreadcrumbs.tsx index 85576897b..d4e6edf7b 100644 --- a/ui/components/appBreadcrumbs.tsx +++ b/ui/components/appBreadcrumbs.tsx @@ -1,19 +1,30 @@ import { Breadcrumb, BreadcrumbItem } from "@/libs/patternfly/react-core"; +import { ReactElement } from "react"; import { BreadcrumbLink } from "./breadcrumbLink"; -export function AppBreadcrumbs({ authProfileName, breadcrumbs = [] }: { authProfileName: string; breadcrumbs?: { href?: string, label: string }[] }) { +export function AppBreadcrumbs({ + bookmarkName, + breadcrumbs = [], +}: { + bookmarkName: ReactElement | string; + breadcrumbs?: { href?: string; label: string }[]; +}) { return ( - Authorization Profiles - {authProfileName} - {breadcrumbs?.map( - ({ href, label }, idx, breadcrumbs) => { - const isActive = idx === (breadcrumbs.length - 1) - return href ? - {label} - : {label} - })} + Bookmarks + {bookmarkName} + {breadcrumbs?.map(({ href, label }, idx, breadcrumbs) => { + const isActive = idx === breadcrumbs.length - 1; + return href ? ( + + {label} + + ) : ( + + {label} + + ); + })} - - ) + ); } diff --git a/ui/components/authProfileCard.tsx b/ui/components/bookmarkCard.tsx similarity index 70% rename from ui/components/authProfileCard.tsx rename to ui/components/bookmarkCard.tsx index 78ca04318..659cee75f 100644 --- a/ui/components/authProfileCard.tsx +++ b/ui/components/bookmarkCard.tsx @@ -1,5 +1,5 @@ "use client"; -import { AuthProfile } from "@/api/auth"; +import { Bookmark } from "@/api/types"; import { Card, CardBody, @@ -16,10 +16,10 @@ import { CardFooter } from "@patternfly/react-core"; import Link from "next/link"; import { useRouter } from "next/navigation"; -export function AuthProfileCard({ +export function BookmarkCard({ id, - attributes: { name, cluster, mechanism }, -}: AuthProfile) { + attributes: { name, mechanism, principal, bootstrapServer }, +}: Bookmark) { const router = useRouter(); const cardId = `tool-card-${id}`; return ( @@ -31,26 +31,19 @@ export function AuthProfileCard({ isCompact={true} > - - {name}@{cluster.attributes.name} - + {name} Cluster Address - + - Cluster ID - - {cluster.attributes.name} - + Principal + {principal} SASL Mechanism diff --git a/ui/components/clusterCard.tsx b/ui/components/clusterCard.tsx index 1fa034a37..4181bf990 100644 --- a/ui/components/clusterCard.tsx +++ b/ui/components/clusterCard.tsx @@ -1,6 +1,6 @@ "use client"; "use client"; -import { Cluster } from "@/api/auth"; +import { Cluster } from "@/api/types"; import { Card, CardBody, diff --git a/ui/components/table/ResponsiveTable.tsx b/ui/components/table/ResponsiveTable.tsx index d35d72108..a21a5e8e8 100644 --- a/ui/components/table/ResponsiveTable.tsx +++ b/ui/components/table/ResponsiveTable.tsx @@ -1,5 +1,5 @@ "use client"; -import { Skeleton } from "@patternfly/react-core"; +import { TableSkeleton } from "@/components/table/TableSkeleton"; import type { ActionsColumnProps, TableVariant, @@ -15,12 +15,7 @@ import { Thead, Tr, } from "@patternfly/react-table"; -import { useTranslations } from "next-intl"; -import type { - PropsWithChildren, - ReactElement, - VoidFunctionComponent, -} from "react"; +import type { PropsWithChildren, ReactElement } from "react"; import { forwardRef, memo, useCallback, useMemo, useState } from "react"; import useResizeObserver from "use-resize-observer"; import "./ResponsiveTable.css"; @@ -361,29 +356,3 @@ export const DeletableRow = memo( }, ); DeletableRow.displayName = "DeletableRow"; - -const TableSkeleton: VoidFunctionComponent<{ - columns: number; - rows: number; - getTd: (index: number) => typeof Td; -}> = ({ columns, rows, getTd }) => { - const t = useTranslations(); - const skeletonCells = new Array(columns).fill(0).map((_, index) => { - const Td = getTd(index); - return ( - - - - ); - }); - const skeletonRows = new Array(rows) - .fill(0) - .map((_, index) => {skeletonCells}); - return <>{skeletonRows}; -}; diff --git a/ui/components/table/TableSkeleton.tsx b/ui/components/table/TableSkeleton.tsx new file mode 100644 index 000000000..7b13f514f --- /dev/null +++ b/ui/components/table/TableSkeleton.tsx @@ -0,0 +1,30 @@ +import { Skeleton } from "@/libs/patternfly/react-core"; +import { Td, Tr } from "@/libs/patternfly/react-table"; +import { useTranslations } from "next-intl"; + +type Props = { + columns: number; + rows: number; + getTd?: (index: number) => typeof Td; +}; +export function TableSkeleton({ columns, rows, getTd = () => Td }: Props) { + const t = useTranslations(); + const skeletonCells = new Array(columns).fill(0).map((_, index) => { + const Td = getTd(index); + return ( + + + + ); + }); + const skeletonRows = new Array(rows) + .fill(0) + .map((_, index) => {skeletonCells}); + return <>{skeletonRows}; +} diff --git a/ui/components/table/TableView.tsx b/ui/components/table/TableView.tsx index 35f6bfadf..c9a61e0b9 100644 --- a/ui/components/table/TableView.tsx +++ b/ui/components/table/TableView.tsx @@ -147,8 +147,7 @@ export const TableView = ({ ))} - , - , + diff --git a/ui/components/table/index.ts b/ui/components/table/index.ts index a146357c9..e5f7a90b1 100644 --- a/ui/components/table/index.ts +++ b/ui/components/table/index.ts @@ -1,2 +1,3 @@ export * from "./TableView"; export * from "./ResponsiveTable"; +export { TableSkeleton } from "@/components/table/TableSkeleton"; diff --git a/ui/components/toolNavItem.tsx b/ui/components/toolNavItem.tsx index 8fda5d55c..34cfd60dc 100644 --- a/ui/components/toolNavItem.tsx +++ b/ui/components/toolNavItem.tsx @@ -1,20 +1,18 @@ "use client"; -import { - NavItem -} from "@/libs/patternfly/react-core"; +import { NavItem } from "@/libs/patternfly/react-core"; import { useTranslations } from "next-intl"; import Link from "next/link"; import { useParams, useSelectedLayoutSegment } from "next/navigation"; -export function ToolNavItem({ url, title }: Tool) { +export function ToolNavItem({ url, title }: { url: string; title: string }) { const t = useTranslations(); - const { authProfile } = useParams(); - const segment = useSelectedLayoutSegment() + const { bookmark } = useParams(); + const segment = useSelectedLayoutSegment(); const isActive = url === `/${segment}`; return ( - {t(title)} + {t(title)} ); } diff --git a/ui/components/topicPage.tsx b/ui/components/topicPage.tsx index 2f6a19efe..6e9a62f44 100644 --- a/ui/components/topicPage.tsx +++ b/ui/components/topicPage.tsx @@ -1,5 +1,5 @@ "use client"; -import { Topic } from "@/api/topics"; +import { Topic } from "@/api/types"; import { TableView } from "@/components/table"; import { Card, diff --git a/ui/components/topics.tsx b/ui/components/topics.tsx index b2840d445..079b35c09 100644 --- a/ui/components/topics.tsx +++ b/ui/components/topics.tsx @@ -1,5 +1,5 @@ "use client"; -import { Topic } from "@/api/topics"; +import { Topic } from "@/api/types"; import { TableView } from "@/components/table"; import { useFormatter } from "next-intl"; import Link from "next/link"; diff --git a/ui/utils/session.ts b/ui/utils/session.ts index 2219640e6..5a95e1cee 100644 --- a/ui/utils/session.ts +++ b/ui/utils/session.ts @@ -1,7 +1,20 @@ +import { BookmarkSchema } from "@/api/types"; import { authOptions } from "@/app/api/auth/[...nextauth]/route"; import { sealData, unsealData } from "iron-session"; import { getServerSession } from "next-auth"; import { cookies } from "next/headers"; +import z from "zod"; + +const SessionShape = z.object({ + newBookmark: z + .object({ + name: z.string().optional(), + boostrapServer: z.string().optional(), + principal: z.string().optional(), + }) + .optional(), + bookmarks: z.array(BookmarkSchema).optional(), +}); export async function getSession() { const user = await getUser(); @@ -9,12 +22,12 @@ export async function getSession() { const encryptedSession = cookieStore.get(user.username)?.value; try { - const session = encryptedSession + const rawSession = encryptedSession ? await unsealData(encryptedSession, { password: process.env.SESSION_SECRET, }) : null; - return session; + return SessionShape.parse(rawSession); } catch { return null; }