Skip to content

Commit 4f9b737

Browse files
committed
Merge branch '492-console-wide-account' of github.com:eth-cscs/firecrest-ui into 492-console-wide-account
2 parents 25b4311 + a8ce369 commit 4f9b737

File tree

5 files changed

+125
-19
lines changed

5 files changed

+125
-19
lines changed

app/components/switchers/GroupSwitcher.tsx

Lines changed: 50 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,64 @@
66
*************************************************************************/
77

88
import { useNavigate } from '@remix-run/react'
9+
import { createPortal } from 'react-dom'
10+
import React, { useEffect, useState } from 'react'
911

1012
// contexts
1113
import { useGroup } from '~/contexts/GroupContext'
1214

13-
// TODO: improve the UI/UX of this switcher and implement the redirect upon switch
14-
const GroupSwitcher: React.FC<any> = () => {
15-
const navigate = useNavigate()
15+
interface GroupSwitcherPortalProps {
16+
systemName: string
17+
basePath: '/compute' | '/filesystems'
18+
}
19+
20+
export const GroupSwitcherPortal: React.FC<GroupSwitcherPortalProps> = ({
21+
systemName,
22+
basePath,
23+
}) => {
24+
const [target, setTarget] = useState<HTMLElement | null>(null)
25+
useEffect(() => {
26+
const el = document.getElementById('app-header-slot')
27+
setTarget(el)
28+
}, [])
29+
if (!target) return null
30+
return createPortal(<GroupSwitcher systemName={systemName} basePath={basePath} />, target)
31+
}
1632

17-
const { groups, selectedGroup } = useGroup()
33+
interface GroupSwitcherProps {
34+
systemName: string
35+
basePath: '/compute' | '/filesystems'
36+
}
1837

38+
export const GroupSwitcher: React.FC<GroupSwitcherProps> = ({
39+
systemName,
40+
basePath,
41+
}: GroupSwitcherProps) => {
42+
const navigate = useNavigate()
43+
const { groups, selectedGroup, setSelectedGroupId } = useGroup()
1944
const handleSwitch = (groupId: string) => {
20-
navigate(`/tbd`)
45+
setSelectedGroupId(groupId)
46+
const group = groups.find((g) => g.id === groupId)
47+
if (!group || !systemName) return
48+
navigate(`${basePath}/systems/${systemName}/accounts/${group.name}`)
2149
}
22-
2350
return (
24-
<select value={selectedGroup?.id ?? ''} onChange={(e) => handleSwitch(e.target.value)}>
25-
{groups.map((p) => (
26-
<option key={p.id} value={p.id}>
27-
{p.name}
28-
</option>
29-
))}
30-
</select>
51+
<>
52+
<div className='flex items-center gap-2'>
53+
<span className='text-sm text-gray-600'>Account</span>
54+
<select
55+
value={selectedGroup?.id ?? ''}
56+
onChange={(e) => handleSwitch(e.target.value)}
57+
className='block rounded-md border-gray-300 bg-white py-1 pl-2 pr-6 text-sm shadow-sm cursor-pointer'
58+
>
59+
{groups.map((g) => (
60+
<option key={g.id} value={g.id}>
61+
{g.name}
62+
</option>
63+
))}
64+
</select>
65+
</div>
66+
<div className='h-6 w-px bg-gray-300 ml-8 mr-0'></div>
67+
</>
3168
)
3269
}
33-
34-
export default GroupSwitcher

app/layouts/Header.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ interface HeaderProps {
1414
setSidebarOpen: (open: boolean) => void
1515
authUser: any
1616
fixed?: boolean
17+
hasRightSidebar?: boolean
1718
}
1819

1920
const Header: React.FC<HeaderProps> = ({
2021
setSidebarOpen,
2122
authUser,
2223
fixed = false,
24+
hasRightSidebar = false,
2325
}: HeaderProps) => {
2426
return (
2527
<div
@@ -35,11 +37,13 @@ const Header: React.FC<HeaderProps> = ({
3537
</button>
3638
<div className='flex-1 px-4 flex justify-between'>
3739
<div className='flex-1 flex'></div>
38-
<div className='ml-4 flex items-center md:ml-6'>
40+
<div className='ml-4 flex items-center gap-4 md:ml-6'>
41+
{/* Slot to insert additional components */}
42+
<div id='app-header-slot' className='flex items-center' />
3943
{/* Profile dropdown */}
40-
<Menu as='div' className='ml-3 relative'>
44+
<Menu as='div' className='relative'>
4145
<div>
42-
<Menu.Button className='max-w-xs bg-white rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 lg:p-2 lg:rounded-md lg:hover:bg-gray-50'>
46+
<Menu.Button className='max-w-xs bg-white rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 lg:py-2 lg:rounded-md lg:hover:bg-gray-50'>
4347
<span className='hidden ml-3 text-gray-700 text-sm font-medium lg:block'>
4448
<span className='sr-only'>Open user menu for </span>
4549
<span>

app/routes/_app.compute.systems.$systemName.accounts.$accountName._index.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { useLoaderData, useRouteError } from '@remix-run/react'
1616
import type { LoaderFunction, LoaderFunctionArgs } from '@remix-run/node'
1717
// types
1818
import type { GetSystemJobsResponse } from '~/types/api-job'
19-
import type { System } from '~/types/api-status'
2019
// loggers
2120
import logger from '~/logger/logger'
2221
// helpers

app/routes/_app.compute.systems.$systemName.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import { getUserInfo } from '~/apis/status-api'
2626
import ErrorView from '~/components/views/ErrorView'
2727
// contexts
2828
import { GroupProvider } from '~/contexts/GroupContext'
29+
// switchers
30+
import { GroupSwitcherPortal } from '~/components/switchers/GroupSwitcher'
2931

3032
export const loader: LoaderFunction = async ({ request, params }: LoaderFunctionArgs) => {
3133
// Check authentication
@@ -50,6 +52,7 @@ export default function AppComputeIndexRoute() {
5052
const { groups, systemName }: any = useLoaderData()
5153
return (
5254
<GroupProvider groups={groups}>
55+
<GroupSwitcherPortal systemName={systemName} basePath='/compute' />
5356
<Outlet />
5457
</GroupProvider>
5558
)
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*************************************************************************
2+
Copyright (c) 2025, ETH Zurich. All rights reserved.
3+
4+
Please, refer to the LICENSE file in the root directory.
5+
SPDX-License-Identifier: BSD-3-Clause
6+
*************************************************************************/
7+
8+
/*************************************************************************
9+
Copyright (c) 2025, ETH Zurich. All rights reserved.
10+
11+
Please, refer to the LICENSE file in the root directory.
12+
SPDX-License-Identifier: BSD-3-Clause
13+
*************************************************************************/
14+
15+
import { Outlet, useLoaderData, useRouteError } from '@remix-run/react'
16+
import type { LoaderFunction, LoaderFunctionArgs } from '@remix-run/node'
17+
// loggers
18+
import logger from '~/logger/logger'
19+
// helpers
20+
import { logInfoHttp } from '~/helpers/log-helper'
21+
// utils
22+
import { getAuthAccessToken, authenticator } from '~/utils/auth.server'
23+
// apis
24+
import { getUserInfo } from '~/apis/status-api'
25+
// views
26+
import ErrorView from '~/components/views/ErrorView'
27+
// contexts
28+
import { GroupProvider } from '~/contexts/GroupContext'
29+
// switchers
30+
import { GroupSwitcherPortal } from '~/components/switchers/GroupSwitcher'
31+
32+
export const loader: LoaderFunction = async ({ request, params }: LoaderFunctionArgs) => {
33+
// Check authentication
34+
const auth = await authenticator.isAuthenticated(request, {
35+
failureRedirect: '/login',
36+
})
37+
const systemName = params.systemName!
38+
logInfoHttp({
39+
message: `Filesystems system ${systemName} layout page`,
40+
request: request,
41+
extraInfo: { username: auth.user.username },
42+
})
43+
// Get auth access token
44+
const accessToken = await getAuthAccessToken(request)
45+
// Call api/s and fetch data
46+
const { groups } = await getUserInfo(accessToken, systemName)
47+
// Return response
48+
return { groups, systemName }
49+
}
50+
51+
export default function AppFilesystemsIndexRoute() {
52+
const { groups, systemName }: any = useLoaderData()
53+
return (
54+
<GroupProvider groups={groups}>
55+
<GroupSwitcherPortal systemName={systemName} basePath='/filesystems' />
56+
<Outlet />
57+
</GroupProvider>
58+
)
59+
}
60+
61+
export function ErrorBoundary() {
62+
const error = useRouteError()
63+
logger.error(error)
64+
return <ErrorView error={error} />
65+
}

0 commit comments

Comments
 (0)