From 4585624bb6a4e8849a85c92facaa6a6829b24e04 Mon Sep 17 00:00:00 2001 From: Duc Trung Le Date: Wed, 17 Jan 2024 23:04:47 +0100 Subject: [PATCH] Start new server --- frontend/src/common/JupyterhubContext.ts | 20 +++++++++++++++++ frontend/src/common/axiosclient.ts | 12 ++++++++-- frontend/src/environments/App.tsx | 9 ++++---- .../src/environments/NewEnvironmentDialog.tsx | 6 +++-- .../environments/RemoveEnvironmentButton.tsx | 6 +++-- frontend/src/environments/main.tsx | 16 ++++++++++++-- frontend/src/environments/types.ts | 2 +- frontend/src/servers/App.tsx | 9 ++++---- frontend/src/servers/NewServerDialog.tsx | 22 +++++++++++++++++-- frontend/src/servers/RemoveServerButton.tsx | 22 ++++++++++--------- frontend/src/servers/ServersList.tsx | 2 +- frontend/src/servers/main.tsx | 16 ++++++++++++-- frontend/src/servers/types.ts | 1 - tljh_repo2docker/servers.py | 1 - 14 files changed, 110 insertions(+), 34 deletions(-) create mode 100644 frontend/src/common/JupyterhubContext.ts diff --git a/frontend/src/common/JupyterhubContext.ts b/frontend/src/common/JupyterhubContext.ts new file mode 100644 index 0000000..938df46 --- /dev/null +++ b/frontend/src/common/JupyterhubContext.ts @@ -0,0 +1,20 @@ +import { createContext, useContext } from 'react'; + +export interface IJupyterhubData { + baseUrl: string; + prefix: string; + user: string; + adminAccess: boolean; + xsrfToken: string; +} +export const JupyterhubContext = createContext({ + baseUrl: '', + prefix: '', + user: '', + adminAccess: false, + xsrfToken: '' +}); + +export const useJupyterhub = () => { + return useContext(JupyterhubContext); +}; diff --git a/frontend/src/common/axiosclient.ts b/frontend/src/common/axiosclient.ts index 0320e89..3be8759 100644 --- a/frontend/src/common/axiosclient.ts +++ b/frontend/src/common/axiosclient.ts @@ -2,6 +2,8 @@ import urlJoin from 'url-join'; import { encodeUriComponents } from './utils'; import axios, { AxiosInstance } from 'axios'; +export const API_PREFIX = 'api'; +export const SPAWN_PREFIX = 'spawn'; export class AxiosClient { constructor(options: AxiosClient.IOptions) { this._baseUrl = options.baseUrl ?? ''; @@ -13,13 +15,19 @@ export class AxiosClient { async request(args: { method: 'get' | 'post' | 'put' | 'option' | 'delete'; + prefix: 'api' | 'spawn'; path: string; - data?: { [key: string]: any }; + query?: string; + data?: { [key: string]: any } | FormData; }): Promise { const { method, path } = args; const data = args.data ?? {}; - let url = urlJoin('api', encodeUriComponents(path)); + let url = urlJoin(args.prefix, encodeUriComponents(path)); + if (args.query) { + const sep = url.indexOf('?') === -1 ? '?' : '&'; + url = `${url}${sep}${args.query}`; + } if (this._xsrfToken) { const sep = url.indexOf('?') === -1 ? '?' : '&'; url = `${url}${sep}_xsrf=${this._xsrfToken}`; diff --git a/frontend/src/environments/App.tsx b/frontend/src/environments/App.tsx index 0c29b3b..ab23951 100644 --- a/frontend/src/environments/App.tsx +++ b/frontend/src/environments/App.tsx @@ -9,6 +9,7 @@ import { NewEnvironmentDialog } from './NewEnvironmentDialog'; import { AxiosContext } from '../common/AxiosContext'; import { useMemo } from 'react'; import { AxiosClient } from '../common/axiosclient'; +import { useJupyterhub } from '../common/JupyterhubContext'; export interface IAppProps { images: IEnvironmentData[]; @@ -16,12 +17,12 @@ export interface IAppProps { default_mem_limit: string; } export default function App(props: IAppProps) { + const jhData = useJupyterhub(); const axios = useMemo(() => { - const jhData = (window as any).jhdata; - const baseUrl = jhData.base_url; - const xsrfToken = jhData.xsrf_token; + const baseUrl = jhData.baseUrl; + const xsrfToken = jhData.xsrfToken; return new AxiosClient({ baseUrl, xsrfToken }); - }, []); + }, [jhData]); return ( diff --git a/frontend/src/environments/NewEnvironmentDialog.tsx b/frontend/src/environments/NewEnvironmentDialog.tsx index b6cb8e3..130632d 100644 --- a/frontend/src/environments/NewEnvironmentDialog.tsx +++ b/frontend/src/environments/NewEnvironmentDialog.tsx @@ -13,7 +13,8 @@ import { Fragment, memo, useCallback, useMemo, useState } from 'react'; import { useAxios } from '../common/AxiosContext'; import { SmallTextField } from '../common/SmallTextField'; -import { API_PREFIX } from './types'; +import { ENV_PREFIX } from './types'; +import { API_PREFIX } from '../common/axiosclient'; export interface INewEnvironmentDialogProps { default_cpu_limit: string; @@ -98,7 +99,8 @@ function _NewEnvironmentDialog(props: INewEnvironmentDialogProps) { data.password = data.password ?? ''; const response = await axios.request({ method: 'post', - path: API_PREFIX, + prefix: API_PREFIX, + path: ENV_PREFIX, data }); if (response?.status === 'ok') { diff --git a/frontend/src/environments/RemoveEnvironmentButton.tsx b/frontend/src/environments/RemoveEnvironmentButton.tsx index f39a798..941388a 100644 --- a/frontend/src/environments/RemoveEnvironmentButton.tsx +++ b/frontend/src/environments/RemoveEnvironmentButton.tsx @@ -4,7 +4,8 @@ import { memo, useCallback } from 'react'; import { useAxios } from '../common/AxiosContext'; import { ButtonWithConfirm } from '../common/ButtonWithConfirm'; -import { API_PREFIX } from './types'; +import { ENV_PREFIX } from './types'; +import { API_PREFIX } from '../common/axiosclient'; interface IRemoveEnvironmentButton { name: string; @@ -17,7 +18,8 @@ function _RemoveEnvironmentButton(props: IRemoveEnvironmentButton) { const removeEnv = useCallback(async () => { const response = await axios.request({ method: 'delete', - path: API_PREFIX, + prefix: API_PREFIX, + path: ENV_PREFIX, data: { name: props.image } }); if (response?.status === 'ok') { diff --git a/frontend/src/environments/main.tsx b/frontend/src/environments/main.tsx index 1e19c1f..f018fc0 100644 --- a/frontend/src/environments/main.tsx +++ b/frontend/src/environments/main.tsx @@ -5,7 +5,7 @@ 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'; const rootElement = document.getElementById('environments-root'); @@ -19,9 +19,21 @@ let configData: IAppProps = { if (dataElement) { configData = JSON.parse(dataElement.textContent || '') as IAppProps; } +const jhData = (window as any).jhdata; +const { base_url, xsrf_token, user, prefix, admin_access } = jhData; root.render( - + + + ); diff --git a/frontend/src/environments/types.ts b/frontend/src/environments/types.ts index aea0cbe..5f23f3b 100644 --- a/frontend/src/environments/types.ts +++ b/frontend/src/environments/types.ts @@ -1,4 +1,4 @@ -export const API_PREFIX = 'environments'; +export const ENV_PREFIX = 'environments'; export interface IEnvironmentData { image_name: string; cpu_limit: string; diff --git a/frontend/src/servers/App.tsx b/frontend/src/servers/App.tsx index 16b1f3d..5dcc7bd 100644 --- a/frontend/src/servers/App.tsx +++ b/frontend/src/servers/App.tsx @@ -10,6 +10,7 @@ import { AxiosClient } from '../common/axiosclient'; import { ServerList } from './ServersList'; import { NewServerDialog } from './NewServerDialog'; import { IEnvironmentData } from '../environments/types'; +import { useJupyterhub } from '../common/JupyterhubContext'; export interface IAppProps { images: IEnvironmentData[]; @@ -18,12 +19,12 @@ export interface IAppProps { named_server_limit_per_user: number; } export default function App(props: IAppProps) { + const jhData = useJupyterhub(); const axios = useMemo(() => { - const jhData = (window as any).jhdata; - const baseUrl = jhData.base_url; - const xsrfToken = jhData.xsrf_token; + const baseUrl = jhData.baseUrl; + 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 2a128ff..7f13c3d 100644 --- a/frontend/src/servers/NewServerDialog.tsx +++ b/frontend/src/servers/NewServerDialog.tsx @@ -15,7 +15,9 @@ import { EnvironmentList } from '../environments/EnvironmentList'; import { IEnvironmentData } from '../environments/types'; import { SmallTextField } from '../common/SmallTextField'; -// import { useAxios } from '../common/AxiosContext'; +import { useAxios } from '../common/AxiosContext'; +import { SPAWN_PREFIX } from '../common/axiosclient'; +import { useJupyterhub } from '../common/JupyterhubContext'; export interface INewServerDialogProps { images: IEnvironmentData[]; allowNamedServers: boolean; @@ -29,7 +31,8 @@ const commonInputProps: OutlinedTextFieldProps = { variant: 'outlined' }; function _NewServerDialog(props: INewServerDialogProps) { - // const axios = useAxios(); + const axios = useAxios(); + const jhData = useJupyterhub(); const [open, setOpen] = useState(false); const [serverName, setServerName] = useState(''); const handleOpen = () => { @@ -57,6 +60,20 @@ function _NewServerDialog(props: INewServerDialogProps) { }, [setRowSelectionModel] ); + + 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]); return ( @@ -102,6 +119,7 @@ function _NewServerDialog(props: INewServerDialogProps) { rowSelectionModel.length === 0 || (serverName.length === 0 && props.allowNamedServers) } + onClick={createServer} > Create Server diff --git a/frontend/src/servers/RemoveServerButton.tsx b/frontend/src/servers/RemoveServerButton.tsx index 1388d9d..56eba94 100644 --- a/frontend/src/servers/RemoveServerButton.tsx +++ b/frontend/src/servers/RemoveServerButton.tsx @@ -4,25 +4,27 @@ import { memo, useCallback } from 'react'; import { useAxios } from '../common/AxiosContext'; import { ButtonWithConfirm } from '../common/ButtonWithConfirm'; -import { API_PREFIX } from './types'; +import { useJupyterhub } from '../common/JupyterhubContext'; +import { API_PREFIX } from '../common/axiosclient'; interface IRemoveServerButton { - user: string; server: string; } function _RemoveServerButton(props: IRemoveServerButton) { const axios = useAxios(); - + const jhData = useJupyterhub(); const removeEnv = useCallback(async () => { - const response = await axios.request({ - method: 'delete', - path: API_PREFIX, - data: { name: props.server } - }); - if (response?.status === 'ok') { + try { + await axios.request({ + method: 'delete', + prefix: API_PREFIX, + path: `users/${jhData.user}/servers/${props.server}`, + data: { remove: true } + }); window.location.reload(); - } else { + } catch (e: any) { + console.error(e); } }, [props.server, axios]); diff --git a/frontend/src/servers/ServersList.tsx b/frontend/src/servers/ServersList.tsx index 094d321..f4d15eb 100644 --- a/frontend/src/servers/ServersList.tsx +++ b/frontend/src/servers/ServersList.tsx @@ -31,7 +31,7 @@ const columns: GridColDef[] = [ sortable: false, hideable: false, renderCell: params => { - return ; + return ; } }, { diff --git a/frontend/src/servers/main.tsx b/frontend/src/servers/main.tsx index 5a23a00..5c825c6 100644 --- a/frontend/src/servers/main.tsx +++ b/frontend/src/servers/main.tsx @@ -6,6 +6,7 @@ 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'; const rootElement = document.getElementById('servers-root'); @@ -21,9 +22,20 @@ let configData: IAppProps = { if (dataElement) { configData = JSON.parse(dataElement.textContent || '') as IAppProps; } - +const jhData = (window as any).jhdata; +const { base_url, xsrf_token, user, prefix, admin_access } = jhData; root.render( - + + + ); diff --git a/frontend/src/servers/types.ts b/frontend/src/servers/types.ts index 61047c8..34c77fa 100644 --- a/frontend/src/servers/types.ts +++ b/frontend/src/servers/types.ts @@ -1,4 +1,3 @@ -export const API_PREFIX = 'spawn'; export interface IServerData { name: string; url: string; diff --git a/tljh_repo2docker/servers.py b/tljh_repo2docker/servers.py index f3d1ebc..e6eea49 100644 --- a/tljh_repo2docker/servers.py +++ b/tljh_repo2docker/servers.py @@ -26,7 +26,6 @@ async def get(self): server_data.append( self._spawner_to_server_data(sp, user) ) - print('@@@@@@@@@@@@@', server_data) result = self.render_template( "servers.html", images=images,