Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start single server from backend #81

Merged
merged 6 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,13 @@ The available settings for this service are:
- `default_cpu_limit`: Default CPU limit of a user server; defaults to `None`
- `machine_profiles`: Instead of entering directly the CPU and Memory value, `tljh-repo2docker` can be configured with pre-defined machine profiles and users can only choose from the available option; defaults to `[]`

Here is an example of registering `tljh_repo2docker`'s service with JupyterHub
This service requires the following scopes : `read:users`, `admin:servers` and `read:roles:users`. Here is an example of registering `tljh_repo2docker`'s service with JupyterHub

```python
# jupyterhub_config.py

from tljh_repo2docker import TLJH_R2D_ADMIN_SCOPE
import sys

c.JupyterHub.services.extend(
[
Expand All @@ -77,7 +78,7 @@ c.JupyterHub.load_roles = [
{
"description": "Role for tljh_repo2docker service",
"name": "tljh-repo2docker-service",
"scopes": ["read:users", "read:servers", "read:roles:users"],
"scopes": ["read:users", "admin:servers", "read:roles:users"],
"services": ["tljh_repo2docker"],
},
{
Expand All @@ -99,6 +100,7 @@ Here is an example of the configuration
# jupyterhub_config.py

from tljh_repo2docker import TLJH_R2D_ADMIN_SCOPE
import sys

c.JupyterHub.services.extend(
[
Expand Down
8 changes: 4 additions & 4 deletions jupyterhub_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

tljh_custom_jupyterhub_config(c)


c.JupyterHub.authenticator_class = DummyAuthenticator

c.JupyterHub.allow_named_servers = True
Expand All @@ -37,8 +38,7 @@
"--machine_profiles",
'{"label": "Medium", "cpu": 4, "memory": 4}',
"--machine_profiles",
'{"label": "Large", "cpu": 8, "memory": 8}'

'{"label": "Large", "cpu": 8, "memory": 8}',
],
"oauth_no_confirm": True,
"oauth_client_allowed_scopes": [
Expand All @@ -58,11 +58,11 @@
{
"description": "Role for tljh_repo2docker service",
"name": "tljh-repo2docker-service",
"scopes": ["read:users", "read:servers", "read:roles:users"],
"scopes": ["read:users", "read:roles:users", "admin:servers"],
"services": ["tljh_repo2docker"],
},
{
"name": 'tljh-repo2docker-service-admin',
"name": "tljh-repo2docker-service-admin",
"users": ["alice"],
"scopes": [TLJH_R2D_ADMIN_SCOPE],
},
Expand Down
3 changes: 1 addition & 2 deletions src/common/AxiosContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { createContext, useContext } from 'react';
import { AxiosClient } from './axiosclient';

export const AxiosContext = createContext<{
hubClient: AxiosClient;
serviceClient: AxiosClient;
}>({ hubClient: new AxiosClient({}), serviceClient: new AxiosClient({}) });
}>({ serviceClient: new AxiosClient({}) });

export const useAxios = () => {
return useContext(AxiosContext);
Expand Down
9 changes: 2 additions & 7 deletions src/common/ButtonWithConfirm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ 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 CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
import { Fragment, memo, useCallback, useState } from 'react';
import { Loading } from './LoadingAnimation';

interface IButtonWithConfirm {
buttonLabel: string;
Expand All @@ -15,11 +14,7 @@ interface IButtonWithConfirm {
okLabel?: string;
cancelLabel?: string;
}
const Loading = () => (
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress />
</Box>
);

function _ButtonWithConfirm(props: IButtonWithConfirm) {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
Expand Down
7 changes: 7 additions & 0 deletions src/common/LoadingAnimation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import CircularProgress from '@mui/material/CircularProgress';
import Box from '@mui/material/Box';
export const Loading = () => (
<Box sx={{ display: 'flex', justifyContent: 'center' }}>
<CircularProgress />
</Box>
);
13 changes: 6 additions & 7 deletions src/common/axiosclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ 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 ?? '';
Expand All @@ -15,15 +13,15 @@ export class AxiosClient {

async request<T = any>(args: {
method: 'get' | 'post' | 'put' | 'option' | 'delete';
prefix: 'api' | 'spawn';
path: string;
query?: string;
data?: { [key: string]: any } | FormData;
params?: { [key: string]: string };
}): Promise<T> {
const { method, path } = args;

const { method, path, params } = args;
const prefix = 'api';
const data = args.data ?? {};
let url = urlJoin(args.prefix, encodeUriComponents(path));
let url = urlJoin(prefix, encodeUriComponents(path));
if (args.query) {
const sep = url.indexOf('?') === -1 ? '?' : '&';
url = `${url}${sep}${args.query}`;
Expand All @@ -35,7 +33,8 @@ export class AxiosClient {
const response = await this._axios.request<T>({
method,
url,
data
data,
params
});
return response.data;
}
Expand Down
8 changes: 1 addition & 7 deletions src/environments/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@ export interface IAppProps {
export default function App(props: IAppProps) {
const jhData = useJupyterhub();

const hubClient = useMemo(() => {
const baseUrl = jhData.hubPrefix;
const xsrfToken = jhData.xsrfToken;
return new AxiosClient({ baseUrl, xsrfToken });
}, [jhData]);

const serviceClient = useMemo(() => {
const baseUrl = jhData.servicePrefix;
const xsrfToken = jhData.xsrfToken;
Expand All @@ -34,7 +28,7 @@ export default function App(props: IAppProps) {

return (
<ThemeProvider theme={customTheme}>
<AxiosContext.Provider value={{ hubClient, serviceClient }}>
<AxiosContext.Provider value={{ serviceClient }}>
<ScopedCssBaseline>
<Stack sx={{ padding: 1 }} spacing={1}>
<NewEnvironmentDialog
Expand Down
2 changes: 2 additions & 0 deletions src/environments/EnvironmentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ export interface IEnvironmentListProps {
selectable?: boolean;
rowSelectionModel?: GridRowSelectionModel;
setRowSelectionModel?: (selected: GridRowSelectionModel) => void;
loading?: boolean;
}

function _EnvironmentList(props: IEnvironmentListProps) {
Expand All @@ -111,6 +112,7 @@ function _EnvironmentList(props: IEnvironmentListProps) {
return (
<Box sx={{ padding: 1 }}>
<DataGrid
loading={Boolean(props.loading)}
rows={rows}
columns={columns}
initialState={{
Expand Down
2 changes: 0 additions & 2 deletions src/environments/NewEnvironmentDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
} from '@mui/material';
import { Fragment, memo, useCallback, useMemo, useState } from 'react';

import { API_PREFIX } from '../common/axiosclient';
import { useAxios } from '../common/AxiosContext';
import { SmallTextField } from '../common/SmallTextField';
import { ENV_PREFIX } from './types';
Expand Down Expand Up @@ -174,7 +173,6 @@ function _NewEnvironmentDialog(props: INewEnvironmentDialogProps) {
data.password = data.password ?? '';
const response = await axios.serviceClient.request({
method: 'post',
prefix: API_PREFIX,
path: ENV_PREFIX,
data
});
Expand Down
2 changes: 0 additions & 2 deletions src/environments/RemoveEnvironmentButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { memo, useCallback } from 'react';
import { useAxios } from '../common/AxiosContext';
import { ButtonWithConfirm } from '../common/ButtonWithConfirm';
import { ENV_PREFIX } from './types';
import { API_PREFIX } from '../common/axiosclient';

interface IRemoveEnvironmentButton {
name: string;
Expand All @@ -18,7 +17,6 @@ function _RemoveEnvironmentButton(props: IRemoveEnvironmentButton) {
const removeEnv = useCallback(async () => {
const response = await axios.serviceClient.request({
method: 'delete',
prefix: API_PREFIX,
path: ENV_PREFIX,
data: { name: props.image }
});
Expand Down
8 changes: 1 addition & 7 deletions src/servers/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,6 @@ export interface IAppProps {
export default function App(props: IAppProps) {
const jhData = useJupyterhub();

const hubClient = useMemo(() => {
const baseUrl = jhData.hubPrefix;
const xsrfToken = jhData.xsrfToken;
return new AxiosClient({ baseUrl, xsrfToken });
}, [jhData]);

const serviceClient = useMemo(() => {
const baseUrl = jhData.servicePrefix;
const xsrfToken = jhData.xsrfToken;
Expand All @@ -36,7 +30,7 @@ export default function App(props: IAppProps) {

return (
<ThemeProvider theme={customTheme}>
<AxiosContext.Provider value={{ hubClient, serviceClient }}>
<AxiosContext.Provider value={{ serviceClient }}>
<ScopedCssBaseline>
<Stack sx={{ padding: 1 }} spacing={1}>
<NewServerDialog
Expand Down
29 changes: 16 additions & 13 deletions src/servers/NewServerDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import { IEnvironmentData } from '../environments/types';
import { SmallTextField } from '../common/SmallTextField';

import { useAxios } from '../common/AxiosContext';
import { SPAWN_PREFIX } from '../common/axiosclient';
import { useJupyterhub } from '../common/JupyterhubContext';
import { SERVER_PREFIX } from './types';

export interface INewServerDialogProps {
images: IEnvironmentData[];
allowNamedServers: boolean;
Expand All @@ -36,6 +37,7 @@ function _NewServerDialog(props: INewServerDialogProps) {
const axios = useAxios();
const jhData = useJupyterhub();
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const [serverName, setServerName] = useState<string>('');
const handleOpen = () => {
setOpen(true);
Expand Down Expand Up @@ -65,24 +67,22 @@ function _NewServerDialog(props: INewServerDialogProps) {

const createServer = useCallback(async () => {
const imageName = props.images[rowSelectionModel[0] as number].image_name;
const data = new FormData();
data.append('image', imageName);
let path = '';
if (serverName.length > 0) {
path = `${jhData.user}/${serverName}`;
} else {
path = jhData.user;
}
const data: { [key: string]: string } = {
imageName,
userName: jhData.user,
serverName
};
try {
await axios.hubClient.request({
setLoading(true);
await axios.serviceClient.request({
method: 'post',
prefix: SPAWN_PREFIX,
path,
path: SERVER_PREFIX,
data
});
window.location.reload();
} catch (e: any) {
console.error(e);
setLoading(false);
alert(e);
}
}, [serverName, rowSelectionModel, props.images, axios, jhData]);
const disabled = useMemo(() => {
Expand All @@ -106,6 +106,7 @@ function _NewServerDialog(props: INewServerDialogProps) {
</Box>
<Dialog open={open} onClose={handleClose} fullWidth maxWidth={'md'}>
<DialogTitle>Server Options</DialogTitle>

<DialogContent>
{props.allowNamedServers && (
<Box sx={{ padding: 1 }}>
Expand All @@ -131,8 +132,10 @@ function _NewServerDialog(props: INewServerDialogProps) {
selectable
rowSelectionModel={rowSelectionModel}
setRowSelectionModel={updateSelectedRow}
loading={loading}
/>
</DialogContent>

<DialogActions>
<Button variant="contained" color="error" onClick={handleClose}>
Cancel
Expand Down
Loading
Loading