Skip to content

Commit f969e5d

Browse files
committed
Update mockup UI, extract out components
1 parent 52028af commit f969e5d

File tree

5 files changed

+851
-289
lines changed

5 files changed

+851
-289
lines changed

website/src/app/page.tsx

Lines changed: 2 additions & 289 deletions
Original file line numberDiff line numberDiff line change
@@ -1,294 +1,7 @@
1-
'use client'
2-
3-
import Box from "@mui/material/Box";
4-
import {IconButton, TextField, Typography} from "@mui/material";
5-
import { Button} from "@mui/material";
6-
import {Grid} from "@mui/material";
7-
import {useCallback, useEffect, useMemo, useState} from "react";
8-
import Card from "@mui/material/Card";
9-
import CardContent from "@mui/material/CardContent";
10-
import Divider from "@mui/material/Divider";
11-
import LinearProgress from "@mui/material/LinearProgress";
12-
import { useSessionStorage } from "usehooks-ts";
13-
import {MenuOpen, Download, Lock} from "@mui/icons-material";
14-
15-
import {
16-
ObjectList,
17-
TokenPermission,
18-
Federation,
19-
ObjectPrefixToNamespaceKeyMap,
20-
UnauthenticatedError,
21-
generateCodeVerifier,
22-
getAuthorizationCode,
23-
getToken,
24-
startAuthorizationCodeFlow,
25-
parseObjectUrl,
26-
fetchFederation,
27-
fetchNamespace,
28-
list,
29-
get,
30-
put,
31-
permissions
32-
} from "../../../src/index";
33-
import {downloadResponse} from "../../../src/util";
1+
import { redirect } from "next/navigation";
342

353
function Page() {
36-
37-
// Pelican client state
38-
const [federations, setFederations, removeFederations] = useSessionStorage<Record<string, Federation>>('federations', {})
39-
const [prefixToNamespace, setPrefixToNamespace, removePrefixToNamespace] = useSessionStorage<ObjectPrefixToNamespaceKeyMap>('prefixToNamespace', {})
40-
const [codeVerifier, setCodeVerifier, removeCodeVerifier] = useSessionStorage<string | undefined>('codeVerifier', undefined)
41-
42-
// Initialize code verifier if not present
43-
useEffect(() => {
44-
if(!codeVerifier){
45-
setCodeVerifier(generateCodeVerifier())
46-
}
47-
}, [codeVerifier])
48-
49-
// Run list on load
50-
useEffect(() => {
51-
(async () => {
52-
await onObjectUrlChange(objectUrl, federations, setFederations, prefixToNamespace, setPrefixToNamespace, setPermissions, setLoginRequired, setObjectList)
53-
})()
54-
}, [])
55-
56-
// On load, check if there is a code in the URL to exchange for a token
57-
useEffect(() => {(async () => {
58-
59-
const {federationHostname, namespacePrefix, code} = getAuthorizationCode()
60-
61-
// If there is a code in the URL, exchange it for a token
62-
if (code && federationHostname && namespacePrefix && codeVerifier) {
63-
const namespace = federations[federationHostname]?.namespaces[namespacePrefix]
64-
const token = await getToken(namespace?.oidcConfiguration, codeVerifier, namespace?.clientId, namespace?.clientSecret, code)
65-
setFederations({
66-
...federations,
67-
[federationHostname]: {
68-
...federations[federationHostname],
69-
namespaces: {
70-
...federations[federationHostname]?.namespaces,
71-
[namespacePrefix]: {
72-
...federations[federationHostname]?.namespaces[namespacePrefix],
73-
token: token.accessToken
74-
}
75-
}
76-
}
77-
})
78-
}
79-
80-
})()}, [federations, codeVerifier])
81-
82-
// UI State
83-
let [loginRequired, setLoginRequired] = useState<boolean>(false);
84-
let [objectUrl, _setObjectUrl] = useSessionStorage<string>('objectUrl', "pelican://osg-htc.org/ncar");
85-
let [permissions, setPermissions] = useState<TokenPermission[] | undefined>(undefined);
86-
let [object, setObject] = useState<File | undefined>(undefined);
87-
let [objectList, setObjectList] = useState<ObjectList[] | undefined>([]);
88-
let [loading, setLoading] = useState<boolean>(false);
89-
90-
let {federationHostname, objectPrefix, objectPath} = useMemo(() => parseObjectUrl(objectUrl), [objectUrl])
91-
92-
const setObjectUrl = useCallback(async (url: string) => {
93-
setLoading(true);
94-
_setObjectUrl(url)
95-
await onObjectUrlChange(url, federations, setFederations, prefixToNamespace, setPrefixToNamespace, setPermissions, setLoginRequired, setObjectList)
96-
setLoading(false)
97-
}, [federations, prefixToNamespace])
98-
99-
return (
100-
<Box minHeight={"90vh"}>
101-
<Grid height={"100%"} justifyContent={"center"} container gap={2}>
102-
<Grid size={{xl: 4, md: 8, xs: 11}} display={"flex"} flexDirection={"column"}>
103-
<Box mt={6} mx={"auto"} width={"100%"} display={"flex"} flexDirection={"column"}>
104-
<Box pt={2}>
105-
<Box display={'flex'} flexDirection={'column'}>
106-
<Box display={'flex'} alignItems={'center'}>
107-
<TextField fullWidth onChange={async (e) => {
108-
await setObjectUrl(e.target.value)
109-
}} value={objectUrl} id="outlined-basic" label="Object Name" variant="outlined"/>
110-
{loginRequired && codeVerifier && (
111-
<IconButton
112-
onClick={async () => {
113-
const {federationHostname, objectPrefix} = parseObjectUrl(objectUrl)
114-
115-
const federation = federations[federationHostname]
116-
const namespaceKey = prefixToNamespace[objectPrefix]
117-
const namespace = federation.namespaces[namespaceKey.namespace]
118-
119-
startAuthorizationCodeFlow(codeVerifier, namespace, federation)
120-
}}
121-
>
122-
<Lock/>
123-
</IconButton>
124-
)}
125-
</Box>
126-
{loading && <LinearProgress />}
127-
</Box>
128-
<Typography variant={'subtitle2'}>
129-
Namespace Permissions: {permissions ? permissions.join(", ") : "Unknown"}
130-
</Typography>
131-
<Box mt={2}>
132-
<input
133-
type="file"
134-
onChange={e => {
135-
if (e.target.files && e.target.files[0]) {
136-
setObjectUrl(`pelican://localhost:80/mnt/${e.target.files[0].name}`);
137-
setObject(e.target.files?.[0])
138-
}
139-
}}
140-
/>
141-
</Box>
142-
</Box>
143-
</Box>
144-
<Box mt={6} mx={"auto"} width={"100%"} display={"flex"} flexDirection={"column"}>
145-
<Box pt={2}>
146-
{objectList?.length === 0 ? (
147-
<Typography variant="body2" color="textSecondary">No objects found.</Typography>
148-
) : (
149-
objectList?.map((obj, index) => (
150-
<Card key={index} sx={{mb: 2}} variant="outlined">
151-
<CardContent>
152-
<Box display={"flex"} justifyContent={"space-between"} alignItems={"center"}>
153-
<Typography variant="h6" gutterBottom>{obj.href}</Typography>
154-
{ obj.iscollection &&
155-
<Button endIcon={<MenuOpen/>} onClick={() => setObjectUrl(`pelican://${federationHostname}${obj.href}/`)}>
156-
Explore
157-
</Button>
158-
}
159-
{ !obj.iscollection &&
160-
<Button
161-
endIcon={<Download/>}
162-
onClick={async () => {
163-
try {
164-
downloadResponse(await get(`pelican://${federationHostname}${obj.href}`, federations[federationHostname], federations[federationHostname].namespaces?.[prefixToNamespace[objectPrefix]?.namespace]))
165-
} catch{}
166-
}}
167-
>
168-
Download
169-
</Button>
170-
}
171-
</Box>
172-
173-
<Divider sx={{my: 1}}/>
174-
<Typography
175-
variant="body2"><strong>Type:</strong> {obj.resourcetype}{obj.iscollection ? " (Collection)" : ""}
176-
</Typography>
177-
<Typography variant="body2"><strong>Size:</strong> {obj.getcontentlength} bytes</Typography>
178-
<Typography variant="body2"><strong>Last Modified:</strong> {obj.getlastmodified}</Typography>
179-
<Typography variant="body2"><strong>Executable:</strong> {obj.executable}</Typography>
180-
<Typography variant="body2"><strong>Status:</strong> {obj.status}</Typography>
181-
</CardContent>
182-
</Card>
183-
))
184-
)}
185-
</Box>
186-
</Box>
187-
</Grid>
188-
<Grid size={{xl: 7, md: 8, xs: 11}} display={"flex"} flexDirection={"column"}>
189-
<Box pt={1} mx={"auto"}>
190-
{/*<Button variant="contained" onClick={submit}>{object ? 'Upload' : 'Download'}</Button>*/}
191-
<Button onClick={() => {
192-
setFederations({})
193-
setPrefixToNamespace({})
194-
}}>Clear Federations</Button>
195-
</Box>
196-
<Box mt={6} mx={"auto"} width={"100%"} display={"flex"} flexDirection={"column"}>
197-
<Typography variant="h6" gutterBottom>Client Federations ( for debug only )</Typography>
198-
<Box overflow={'auto'}>
199-
<pre>
200-
<code>
201-
{JSON.stringify(federations, null, 2)}
202-
</code>
203-
</pre>
204-
</Box>
205-
</Box>
206-
</Grid>
207-
</Grid>
208-
</Box>
209-
)
210-
}
211-
212-
/**
213-
* Pull in objectUrl related information into React state
214-
* @param objectUrl
215-
* @param federations
216-
* @param setFederations
217-
* @param prefixToNamespace
218-
* @param setPrefixToNamespace
219-
* @param setPermissions
220-
* @param setLoginRequired
221-
* @param setObjectList
222-
*/
223-
const onObjectUrlChange = async (objectUrl: string, federations: Record<string, Federation>, setFederations: (f: Record<string, Federation>) => void, prefixToNamespace: ObjectPrefixToNamespaceKeyMap, setPrefixToNamespace: (m: ObjectPrefixToNamespaceKeyMap) => void, setPermissions: (p: TokenPermission[]) => void, setLoginRequired: (b: boolean) => void, setObjectList: (l: ObjectList[]) => void) => {
224-
225-
let {federationHostname, objectPrefix, objectPath} = parseObjectUrl(objectUrl)
226-
227-
// If we haven't registered the federation
228-
try {
229-
if(!(federationHostname in federations)) {
230-
const federation = await fetchFederation(federationHostname)
231-
federations = {
232-
...federations,
233-
[federationHostname]: federation
234-
}
235-
setFederations(federations)
236-
}
237-
} catch {}
238-
239-
240-
// If we haven't mapped this prefix to a namespace
241-
try {
242-
if(!(objectPrefix in prefixToNamespace)) {
243-
const namespace = await fetchNamespace(objectPath, federations[federationHostname])
244-
prefixToNamespace = {
245-
...prefixToNamespace,
246-
[objectPrefix]: {
247-
federation: federationHostname,
248-
namespace: namespace.prefix
249-
}
250-
}
251-
setPrefixToNamespace(prefixToNamespace)
252-
253-
// If we haven't registered this namespace
254-
if(!(namespace.prefix in federations[federationHostname].namespaces)) {
255-
setFederations({
256-
...federations,
257-
[federationHostname]: {
258-
...federations[federationHostname],
259-
namespaces: {
260-
...federations[federationHostname]?.namespaces,
261-
[namespace.prefix]: namespace
262-
}
263-
}
264-
})
265-
}
266-
}
267-
} catch {}
268-
269-
// Try to list
270-
try {
271-
try {
272-
setObjectList((await list(`pelican://${objectPrefix}`, federations[federationHostname], federations[federationHostname].namespaces?.[prefixToNamespace[objectPrefix]?.namespace])).reverse())
273-
setLoginRequired(false)
274-
} catch (e) {
275-
setObjectList((await list(`pelican://${federationHostname}${objectPath}`, federations[federationHostname], federations[federationHostname].namespaces?.[prefixToNamespace[objectPrefix]?.namespace])).reverse())
276-
setLoginRequired(false)
277-
}
278-
} catch (e) {
279-
if(e instanceof UnauthenticatedError) {
280-
setLoginRequired(true)
281-
setObjectList([])
282-
}
283-
}
284-
285-
// Check permissions
286-
try {
287-
if(federations[federationHostname].namespaces?.[prefixToNamespace[objectPrefix]?.namespace]){
288-
const perms = await permissions(objectUrl, federations[federationHostname].namespaces?.[prefixToNamespace[objectPrefix]?.namespace])
289-
setPermissions(perms)
290-
}
291-
} catch {}
4+
redirect("/v1");
2925
}
2936

2947
export default Page;

0 commit comments

Comments
 (0)