diff --git a/frontend/src/common/utils.ts b/frontend/src/common/utils.ts index df2b572..b8b109b 100644 --- a/frontend/src/common/utils.ts +++ b/frontend/src/common/utils.ts @@ -11,6 +11,9 @@ export function encodeUriComponents(uri: string): string { } export function formatTime(time: string): string { + if(!time || time.length === 0){ + return 'unknown' + } const units: { [key: string]: number } = { year: 24 * 60 * 60 * 1000 * 365, month: (24 * 60 * 60 * 1000 * 365) / 12, @@ -28,5 +31,5 @@ export function formatTime(time: string): string { return rtf.format(Math.round(elapsed / units[u]), u as any); } } - return ''; + return 'unknown'; } diff --git a/frontend/src/environments/EnvironmentList.tsx b/frontend/src/environments/EnvironmentList.tsx index 2ae5310..4ac0991 100644 --- a/frontend/src/environments/EnvironmentList.tsx +++ b/frontend/src/environments/EnvironmentList.tsx @@ -20,7 +20,7 @@ const columns: GridColDef[] = [ flex: 1, renderCell: params => { return ( - + {params.value} ); @@ -132,6 +132,17 @@ function _EnvironmentList(props: IEnvironmentListProps) { checkboxSelection={Boolean(props.selectable)} rowSelectionModel={props.rowSelectionModel} onRowSelectionModelChange={props.setRowSelectionModel} + density='compact' + autoHeight + slots={{ + noRowsOverlay: () => { + return ( + + No environment available + + ); + } + }} /> ); diff --git a/frontend/src/environments/NewEnvironmentDialog.tsx b/frontend/src/environments/NewEnvironmentDialog.tsx index 3e761d1..b6cb8e3 100644 --- a/frontend/src/environments/NewEnvironmentDialog.tsx +++ b/frontend/src/environments/NewEnvironmentDialog.tsx @@ -1,14 +1,20 @@ -import { Box, Button, Typography } from '@mui/material'; -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 { OutlinedTextFieldProps } from '@mui/material/TextField'; +import { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Divider, + OutlinedTextFieldProps, + Typography, +} from '@mui/material'; import { Fragment, memo, useCallback, useMemo, useState } from 'react'; -import Divider from '@mui/material/Divider'; -import { SmallTextField } from '../common/SmallTextField'; + import { useAxios } from '../common/AxiosContext'; +import { SmallTextField } from '../common/SmallTextField'; import { API_PREFIX } from './types'; + export interface INewEnvironmentDialogProps { default_cpu_limit: string; default_mem_limit: string; diff --git a/frontend/src/servers/App.tsx b/frontend/src/servers/App.tsx index baf2149..16b1f3d 100644 --- a/frontend/src/servers/App.tsx +++ b/frontend/src/servers/App.tsx @@ -12,7 +12,7 @@ import { NewServerDialog } from './NewServerDialog'; import { IEnvironmentData } from '../environments/types'; export interface IAppProps { - images: IEnvironmentData[] + images: IEnvironmentData[]; server_data: IServerData[]; allow_named_servers: boolean; named_server_limit_per_user: number; @@ -25,13 +25,16 @@ export default function App(props: IAppProps) { return new AxiosClient({ baseUrl, xsrfToken }); }, []); console.log('props', props); - + return ( - + diff --git a/frontend/src/servers/NewServerDialog.tsx b/frontend/src/servers/NewServerDialog.tsx index 93a1529..2a128ff 100644 --- a/frontend/src/servers/NewServerDialog.tsx +++ b/frontend/src/servers/NewServerDialog.tsx @@ -1,22 +1,37 @@ -import { Box, Button, DialogContentText } from '@mui/material'; -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 { + Box, + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + OutlinedTextFieldProps +} from '@mui/material'; +import { GridRowSelectionModel } from '@mui/x-data-grid'; import { Fragment, memo, useCallback, useState } from 'react'; -// import { useAxios } from '../common/AxiosContext'; -import { IEnvironmentData } from '../environments/types'; import { EnvironmentList } from '../environments/EnvironmentList'; -import { GridRowSelectionModel } from '@mui/x-data-grid'; +import { IEnvironmentData } from '../environments/types'; +import { SmallTextField } from '../common/SmallTextField'; +// import { useAxios } from '../common/AxiosContext'; export interface INewServerDialogProps { images: IEnvironmentData[]; + allowNamedServers: boolean; } +const commonInputProps: OutlinedTextFieldProps = { + autoFocus: true, + required: true, + margin: 'dense', + fullWidth: true, + variant: 'outlined' +}; function _NewServerDialog(props: INewServerDialogProps) { // const axios = useAxios(); const [open, setOpen] = useState(false); + const [serverName, setServerName] = useState(''); const handleOpen = () => { setOpen(true); }; @@ -52,6 +67,20 @@ function _NewServerDialog(props: INewServerDialogProps) { Server Options + {props.allowNamedServers && ( + + setServerName(e.target.value)} + value={serverName} + /> + + )} Select an environment Create Server diff --git a/frontend/src/servers/ServersList.tsx b/frontend/src/servers/ServersList.tsx index 0d0dece..094d321 100644 --- a/frontend/src/servers/ServersList.tsx +++ b/frontend/src/servers/ServersList.tsx @@ -79,9 +79,15 @@ function _ServerList(props: IServerListProps) { }} pageSizeOptions={[100]} disableRowSelectionOnClick - sx={{ - '& .MuiDataGrid-virtualScroller::-webkit-scrollbar': { - overflow: rows.length > 0 ? 'auto' : 'hidden' + density='compact' + autoHeight + slots={{ + noRowsOverlay: () => { + return ( + + No servers are running + + ); } }} /> diff --git a/tljh_repo2docker/servers.py b/tljh_repo2docker/servers.py index 04b9789..f3d1ebc 100644 --- a/tljh_repo2docker/servers.py +++ b/tljh_repo2docker/servers.py @@ -1,4 +1,6 @@ from inspect import isawaitable +from typing import Any, Dict +from jupyterhub.orm import Spawner from jupyterhub.handlers.base import BaseHandler from tornado import web @@ -22,14 +24,9 @@ async def get(self): server_data = [] for sp in named_spawners: server_data.append( - { - "name": sp.name, - "url": user.server_url(sp.name), - "last_activity": sp.last_activity.isoformat() + "Z", - "user_options": sp.user_options, - } + self._spawner_to_server_data(sp, user) ) - + print('@@@@@@@@@@@@@', server_data) result = self.render_template( "servers.html", images=images, @@ -43,3 +40,25 @@ async def get(self): self.write(await result) else: self.write(result) + + def _spawner_to_server_data(self, sp: Spawner, user: Any) -> Dict: + data = { + "name": sp.name, + } + try: + data["url"] = user.server_url(sp.name) + except Exception: + data["url"] = "" + try: + data["last_activity"] = sp.last_activity.isoformat() + "Z" + except Exception: + data["last_activity"] = "" + + try: + if sp.user_options: + data["user_options"] = sp.user_options + else: + data["user_options"] = {} + except: + data["user_options"] = {} + return data