diff --git a/frontend/src/common/ButtonWithConfirm.tsx b/frontend/src/common/ButtonWithConfirm.tsx index 70e4ad3..97557c8 100644 --- a/frontend/src/common/ButtonWithConfirm.tsx +++ b/frontend/src/common/ButtonWithConfirm.tsx @@ -40,7 +40,7 @@ function _ButtonWithConfirm(props: IButtonWithConfirm) { setLoading(true); await props.action(); handleClose(); - }, [props.action, setLoading]); + }, [props, setLoading]); return ( diff --git a/frontend/src/common/utils.ts b/frontend/src/common/utils.ts index b8b109b..5f588b3 100644 --- a/frontend/src/common/utils.ts +++ b/frontend/src/common/utils.ts @@ -11,8 +11,8 @@ export function encodeUriComponents(uri: string): string { } export function formatTime(time: string): string { - if(!time || time.length === 0){ - return 'unknown' + if (!time || time.length === 0) { + return 'unknown'; } const units: { [key: string]: number } = { year: 24 * 60 * 60 * 1000 * 365, @@ -27,7 +27,7 @@ export function formatTime(time: string): string { const d2 = new Date(); const elapsed = d1.getTime() - d2.getTime(); for (const u in units) { - if (Math.abs(elapsed) > units[u] || u == 'second') { + if (Math.abs(elapsed) > units[u] || u === 'second') { return rtf.format(Math.round(elapsed / units[u]), u as any); } } diff --git a/frontend/src/environments/EnvironmentList.tsx b/frontend/src/environments/EnvironmentList.tsx index 4ac0991..10541e0 100644 --- a/frontend/src/environments/EnvironmentList.tsx +++ b/frontend/src/environments/EnvironmentList.tsx @@ -122,17 +122,17 @@ function _EnvironmentList(props: IEnvironmentListProps) { } }} pageSizeOptions={[props.pageSize ?? 100]} - disableRowSelectionOnClick={!Boolean(props.selectable)} + disableRowSelectionOnClick={!props.selectable} sx={{ '& .MuiDataGrid-virtualScroller::-webkit-scrollbar': { overflow: rows.length > 0 ? 'auto' : 'hidden' } }} - columnVisibilityModel={{ remove: !Boolean(props.hideRemoveButton) }} + columnVisibilityModel={{ remove: !props.hideRemoveButton }} checkboxSelection={Boolean(props.selectable)} rowSelectionModel={props.rowSelectionModel} onRowSelectionModelChange={props.setRowSelectionModel} - density='compact' + density="compact" autoHeight slots={{ noRowsOverlay: () => { diff --git a/frontend/src/environments/LogDialog.tsx b/frontend/src/environments/LogDialog.tsx index cddae13..2489354 100644 --- a/frontend/src/environments/LogDialog.tsx +++ b/frontend/src/environments/LogDialog.tsx @@ -7,10 +7,11 @@ import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; -import { Fragment, memo, useRef, useState } from 'react'; +import { Fragment, memo, useCallback, useRef, useState } from 'react'; import urlJoin from 'url-join'; import { Terminal } from 'xterm'; import { FitAddon } from 'xterm-addon-fit'; +import { useJupyterhub } from '../common/JupyterhubContext'; interface IEnvironmentLogButton { name: string; @@ -25,24 +26,24 @@ const terminalFactory = () => { }; function _EnvironmentLogButton(props: IEnvironmentLogButton) { + const jhData = useJupyterhub(); const [open, setOpen] = useState(false); const [built, setBuilt] = useState(false); const divRef = useRef(null); const terminalRef = useRef<{ terminal: Terminal; fitAddon: FitAddon }>( terminalFactory() ); - const handleOpen = () => { + const handleOpen = useCallback(() => { setOpen(true); if (divRef.current) { - const {terminal, fitAddon} = terminalFactory() - terminalRef.current.terminal = terminal - terminalRef.current.fitAddon = fitAddon + const { terminal, fitAddon } = terminalFactory(); + terminalRef.current.terminal = terminal; + terminalRef.current.fitAddon = fitAddon; terminal.open(divRef.current); fitAddon.fit(); - const jhData = (window as any).jhdata; - const baseUrl = jhData.base_url; - const xsrfToken = jhData.xsrf_token; + const { baseUrl, xsrfToken } = jhData; + let logsUrl = urlJoin( baseUrl, 'api', @@ -73,7 +74,7 @@ function _EnvironmentLogButton(props: IEnvironmentLogButton) { fitAddon.fit(); }; } - }; + }, [jhData, props.image]); const handleClose = ( event?: any, reason?: 'backdropClick' | 'escapeKeyDown' diff --git a/frontend/src/environments/NewEnvironmentDialog.tsx b/frontend/src/environments/NewEnvironmentDialog.tsx index 130632d..c4c7615 100644 --- a/frontend/src/environments/NewEnvironmentDialog.tsx +++ b/frontend/src/environments/NewEnvironmentDialog.tsx @@ -7,7 +7,7 @@ import { DialogTitle, Divider, OutlinedTextFieldProps, - Typography, + Typography } from '@mui/material'; import { Fragment, memo, useCallback, useMemo, useState } from 'react'; diff --git a/frontend/src/environments/RemoveEnvironmentButton.tsx b/frontend/src/environments/RemoveEnvironmentButton.tsx index 941388a..aacb84f 100644 --- a/frontend/src/environments/RemoveEnvironmentButton.tsx +++ b/frontend/src/environments/RemoveEnvironmentButton.tsx @@ -25,6 +25,7 @@ function _RemoveEnvironmentButton(props: IRemoveEnvironmentButton) { if (response?.status === 'ok') { window.location.reload(); } else { + /* */ } }, [props.image, axios]); diff --git a/frontend/src/environments/main.tsx b/frontend/src/environments/main.tsx index f018fc0..bef626f 100644 --- a/frontend/src/environments/main.tsx +++ b/frontend/src/environments/main.tsx @@ -3,7 +3,6 @@ import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; -import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { JupyterhubContext } from '../common/JupyterhubContext'; import App, { IAppProps } from './App'; @@ -23,17 +22,15 @@ const jhData = (window as any).jhdata; const { base_url, xsrf_token, user, prefix, admin_access } = jhData; root.render( - - - - - + + + ); diff --git a/frontend/src/servers/App.tsx b/frontend/src/servers/App.tsx index 5dcc7bd..79ad26d 100644 --- a/frontend/src/servers/App.tsx +++ b/frontend/src/servers/App.tsx @@ -25,7 +25,6 @@ export default function App(props: IAppProps) { const xsrfToken = jhData.xsrfToken; return new AxiosClient({ baseUrl, xsrfToken }); }, [jhData]); - console.log('props', props); return ( diff --git a/frontend/src/servers/NewServerDialog.tsx b/frontend/src/servers/NewServerDialog.tsx index 7f13c3d..fbb744a 100644 --- a/frontend/src/servers/NewServerDialog.tsx +++ b/frontend/src/servers/NewServerDialog.tsx @@ -63,17 +63,20 @@ function _NewServerDialog(props: INewServerDialogProps) { const createServer = useCallback(async () => { const imageName = props.images[rowSelectionModel[0] as number].image_name; - console.log('serverName', serverName, imageName); const data = new FormData(); data.append('image', imageName); - const res = await axios.request({ - method: 'post', - prefix: SPAWN_PREFIX, - path: `${jhData.user}/${serverName}`, - data - }); - console.log('AAAAA', res) - }, [serverName, rowSelectionModel, props.images]); + try { + await axios.request({ + method: 'post', + prefix: SPAWN_PREFIX, + path: `${jhData.user}/${serverName}`, + data + }); + window.location.reload(); + } catch (e: any) { + console.error(e); + } + }, [serverName, rowSelectionModel, props.images, axios, jhData]); return ( diff --git a/frontend/src/servers/OpenServerButton.tsx b/frontend/src/servers/OpenServerButton.tsx new file mode 100644 index 0000000..bfa88d0 --- /dev/null +++ b/frontend/src/servers/OpenServerButton.tsx @@ -0,0 +1,77 @@ +import { Button, IconButton } from '@mui/material'; +import { Fragment, memo, useEffect, useState } from 'react'; + +// import { useAxios } from '../common/AxiosContext'; +import { useJupyterhub } from '../common/JupyterhubContext'; +import urlJoin from 'url-join'; +import SyncIcon from '@mui/icons-material/Sync'; + +interface IOpenServerButton { + url: string; + serverName: string; +} + +function _OpenServerButton(props: IOpenServerButton) { + // const axios = useAxios(); + const jhData = useJupyterhub(); + const [progress, setProgress] = useState(0); + useEffect(() => { + const { user, baseUrl, xsrfToken } = jhData; + let progressUrl = urlJoin( + baseUrl, + 'api', + 'users', + user, + 'servers', + props.serverName, + 'progress' + ); + if (xsrfToken) { + // add xsrf token to url parameter + const sep = progressUrl.indexOf('?') === -1 ? '?' : '&'; + progressUrl = progressUrl + sep + '_xsrf=' + xsrfToken; + } + + const eventSource = new EventSource(progressUrl); + eventSource.onerror = err => { + setProgress(100); + eventSource.close(); + }; + + eventSource.onmessage = event => { + const data = JSON.parse(event.data); + + setProgress(data.progress ?? 0); + }; + }, [jhData, setProgress]); + + return ( + + {progress === 100 && ( + + )} + {progress < 100 && ( + + + + )} + + ); +} + +export const OpenServerButton = memo(_OpenServerButton); diff --git a/frontend/src/servers/RemoveServerButton.tsx b/frontend/src/servers/RemoveServerButton.tsx index 56eba94..50013f9 100644 --- a/frontend/src/servers/RemoveServerButton.tsx +++ b/frontend/src/servers/RemoveServerButton.tsx @@ -26,7 +26,7 @@ function _RemoveServerButton(props: IRemoveServerButton) { } catch (e: any) { console.error(e); } - }, [props.server, axios]); + }, [props.server, axios, jhData]); return ( { - return ; + return ; } }, { @@ -43,9 +43,7 @@ const columns: GridColDef[] = [ hideable: false, renderCell: params => { return ( - + ); } } @@ -79,7 +77,7 @@ function _ServerList(props: IServerListProps) { }} pageSizeOptions={[100]} disableRowSelectionOnClick - density='compact' + density="compact" autoHeight slots={{ noRowsOverlay: () => { diff --git a/frontend/src/servers/main.tsx b/frontend/src/servers/main.tsx index 5c825c6..7d267d4 100644 --- a/frontend/src/servers/main.tsx +++ b/frontend/src/servers/main.tsx @@ -3,7 +3,6 @@ import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; import '@fontsource/roboto/700.css'; -import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { JupyterhubContext } from '../common/JupyterhubContext'; @@ -25,17 +24,15 @@ if (dataElement) { const jhData = (window as any).jhdata; const { base_url, xsrf_token, user, prefix, admin_access } = jhData; root.render( - - - - - + + + );