mirror of
https://github.com/Steffo99/sophon.git
synced 2024-12-22 14:54:22 +00:00
🚧 idk notebook stuff
This commit is contained in:
parent
1f1b0c6e41
commit
0c528b0f7c
14 changed files with 472 additions and 18 deletions
|
@ -39,6 +39,35 @@ class NotebooksViewSet(SophonGroupViewSet, metaclass=abc.ABCMeta):
|
||||||
serializer = Serializer(notebook)
|
serializer = Serializer(notebook)
|
||||||
return Response(serializer.data, status.HTTP_200_OK)
|
return Response(serializer.data, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(["PATCH"], detail=True)
|
||||||
|
def lock(self, request: Request, **kwargs):
|
||||||
|
"""
|
||||||
|
Lock the `Notebook`.
|
||||||
|
|
||||||
|
Note that this does nothing on the backend; it's only meant to be an indication for the frontend.
|
||||||
|
"""
|
||||||
|
notebook: Notebook = self.get_object()
|
||||||
|
if notebook.locked_by is None:
|
||||||
|
notebook.locked_by = self.request.user
|
||||||
|
notebook.save()
|
||||||
|
Serializer = notebook.get_access_serializer(request.user)
|
||||||
|
serializer = Serializer(notebook)
|
||||||
|
return Response(serializer.data, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@action(["PATCH"], detail=True)
|
||||||
|
def unlock(self, request: Request, **kwargs):
|
||||||
|
"""
|
||||||
|
Unlock the `Notebook`.
|
||||||
|
|
||||||
|
Note that this does nothing on the backend; it's only meant to be an indication for the frontend.
|
||||||
|
"""
|
||||||
|
notebook: Notebook = self.get_object()
|
||||||
|
notebook.locked_by = None
|
||||||
|
notebook.save()
|
||||||
|
Serializer = notebook.get_access_serializer(request.user)
|
||||||
|
serializer = Serializer(notebook)
|
||||||
|
return Response(serializer.data, status.HTTP_200_OK)
|
||||||
|
|
||||||
@action(["PATCH"], detail=True)
|
@action(["PATCH"], detail=True)
|
||||||
def stop(self, request: Request, **kwargs):
|
def stop(self, request: Request, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -16,6 +16,7 @@ import {SophonDescriptionBox} from "./components/informative/SophonDescriptionBo
|
||||||
import {InstanceDescriptionBox} from "./components/instance/InstanceDescriptionBox"
|
import {InstanceDescriptionBox} from "./components/instance/InstanceDescriptionBox"
|
||||||
import {InstanceFormBox} from "./components/instance/InstanceFormBox"
|
import {InstanceFormBox} from "./components/instance/InstanceFormBox"
|
||||||
import {InstanceRouter} from "./components/instance/InstanceRouter"
|
import {InstanceRouter} from "./components/instance/InstanceRouter"
|
||||||
|
import {NotebookCreateBox} from "./components/notebook/NotebookCreateBox"
|
||||||
import {NotebookListBox} from "./components/notebook/NotebookListBox"
|
import {NotebookListBox} from "./components/notebook/NotebookListBox"
|
||||||
import {NotebookRouter} from "./components/notebook/NotebookRouter"
|
import {NotebookRouter} from "./components/notebook/NotebookRouter"
|
||||||
import {DebugBox} from "./components/placeholder/DebugBox"
|
import {DebugBox} from "./components/placeholder/DebugBox"
|
||||||
|
@ -28,6 +29,8 @@ import {AuthorizationProvider} from "./contexts/authorization"
|
||||||
import {CacheProvider} from "./contexts/cache"
|
import {CacheProvider} from "./contexts/cache"
|
||||||
import {GroupProvider} from "./contexts/group"
|
import {GroupProvider} from "./contexts/group"
|
||||||
import {InstanceProvider} from "./contexts/instance"
|
import {InstanceProvider} from "./contexts/instance"
|
||||||
|
import {NotebookProvider} from "./contexts/notebook"
|
||||||
|
import {ProjectProvider} from "./contexts/project"
|
||||||
import {ThemeProvider} from "./contexts/theme"
|
import {ThemeProvider} from "./contexts/theme"
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,17 +70,23 @@ function App({..._}: RouteComponentProps) {
|
||||||
<ProjectCreateBox viewSet={viewSet}/>
|
<ProjectCreateBox viewSet={viewSet}/>
|
||||||
</>}
|
</>}
|
||||||
selectedRoute={({selection}) => <>
|
selectedRoute={({selection}) => <>
|
||||||
|
<ProjectProvider resource={selection}>
|
||||||
<ResourceDescriptionBox resource={selection} icon={faProjectDiagram}/>
|
<ResourceDescriptionBox resource={selection} icon={faProjectDiagram}/>
|
||||||
<ProjectCreateBox resource={selection}/>
|
|
||||||
<NotebookRouter
|
<NotebookRouter
|
||||||
projectPk={selection.value.slug}
|
projectPk={selection.value.slug}
|
||||||
unselectedRoute={({viewSet}) => <>
|
unselectedRoute={({viewSet}) => <>
|
||||||
|
<ProjectCreateBox resource={selection}/>
|
||||||
<NotebookListBox viewSet={viewSet}/>
|
<NotebookListBox viewSet={viewSet}/>
|
||||||
|
<NotebookCreateBox viewSet={viewSet}/>
|
||||||
</>}
|
</>}
|
||||||
selectedRoute={(props) => <>
|
selectedRoute={({selection}) => <>
|
||||||
<DebugBox {...props}/>
|
<NotebookProvider resource={selection}>
|
||||||
|
<NotebookCreateBox resource={selection}/>
|
||||||
|
<DebugBox {...selection}/>
|
||||||
|
</NotebookProvider>
|
||||||
</>}
|
</>}
|
||||||
/>
|
/>
|
||||||
|
</ProjectProvider>
|
||||||
</>}
|
</>}
|
||||||
/>
|
/>
|
||||||
</GroupProvider>
|
</GroupProvider>
|
||||||
|
|
120
frontend/src/components/notebook/NotebookCreateBox.tsx
Normal file
120
frontend/src/components/notebook/NotebookCreateBox.tsx
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
import {Box, Details, Form, Idiomatic as I, useFormState} from "@steffo/bluelib-react"
|
||||||
|
import * as React from "react"
|
||||||
|
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||||
|
import {useProjectContext} from "../../contexts/project"
|
||||||
|
import {ManagedResource, ManagedViewSet} from "../../hooks/useManagedViewSet"
|
||||||
|
import {SophonNotebook} from "../../types/SophonTypes"
|
||||||
|
import {Validators} from "../../utils/Validators"
|
||||||
|
|
||||||
|
|
||||||
|
export interface NotebookCreateBoxProps {
|
||||||
|
viewSet?: ManagedViewSet<SophonNotebook>,
|
||||||
|
resource?: ManagedResource<SophonNotebook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function NotebookCreateBox({viewSet, resource}: NotebookCreateBoxProps): JSX.Element {
|
||||||
|
const authorization = useAuthorizationContext()
|
||||||
|
const project = useProjectContext()
|
||||||
|
|
||||||
|
const name =
|
||||||
|
useFormState<string>(
|
||||||
|
resource?.value.name ?? "",
|
||||||
|
Validators.notZeroLength,
|
||||||
|
)
|
||||||
|
|
||||||
|
const slug =
|
||||||
|
React.useMemo(
|
||||||
|
() => resource ? resource.value.slug : name.value.replaceAll(/[^A-Za-z0-9-]/g, "-").toLowerCase(),
|
||||||
|
[resource, name],
|
||||||
|
)
|
||||||
|
|
||||||
|
const image =
|
||||||
|
useFormState<string>(
|
||||||
|
resource?.value.container_image ?? "steffo45/jupyterlab-docker-sophon",
|
||||||
|
Validators.alwaysValid,
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: Fix this
|
||||||
|
const applyChanges =
|
||||||
|
React.useCallback(
|
||||||
|
async () => {
|
||||||
|
if(!project) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(resource) {
|
||||||
|
await resource.update({
|
||||||
|
name: name.value,
|
||||||
|
slug: slug,
|
||||||
|
container_image: image.value,
|
||||||
|
project: project?.value.slug,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await viewSet!.create({
|
||||||
|
name: name.value,
|
||||||
|
slug: slug,
|
||||||
|
container_image: image.value,
|
||||||
|
project: project?.value.slug,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[viewSet, resource, name, slug, image, project],
|
||||||
|
)
|
||||||
|
|
||||||
|
const canApply =
|
||||||
|
React.useMemo(
|
||||||
|
() => name.validity === true && Boolean(authorization?.state.user?.username),
|
||||||
|
[name, authorization],
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasError =
|
||||||
|
React.useMemo(
|
||||||
|
() => viewSet?.operationError || resource?.error,
|
||||||
|
[viewSet, resource],
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Details>
|
||||||
|
<Details.Summary>
|
||||||
|
{resource ? <>Edit <I>{resource.value.name}</I></> : "Create a new notebook"}
|
||||||
|
</Details.Summary>
|
||||||
|
<Details.Content>
|
||||||
|
<Form>
|
||||||
|
<Form.Field
|
||||||
|
label={"Name"}
|
||||||
|
required={true}
|
||||||
|
{...name}
|
||||||
|
/>
|
||||||
|
<Form.Field
|
||||||
|
label={"Slug"}
|
||||||
|
required={true}
|
||||||
|
disabled={true}
|
||||||
|
value={slug}
|
||||||
|
validity={slug.length > 0 ? true : undefined}
|
||||||
|
/>
|
||||||
|
<Form.Select
|
||||||
|
label={"Image"}
|
||||||
|
options={{
|
||||||
|
"Python (Sophonic)": "steffo45/jupyterlab-docker-sophon",
|
||||||
|
}}
|
||||||
|
{...image}
|
||||||
|
/>
|
||||||
|
<Form.Row>
|
||||||
|
<Form.Button
|
||||||
|
type={"button"}
|
||||||
|
onClick={applyChanges}
|
||||||
|
disabled={!canApply}
|
||||||
|
builtinColor={hasError ? "red" : undefined}
|
||||||
|
>
|
||||||
|
{resource ? "Edit" : "Create"}
|
||||||
|
</Form.Button>
|
||||||
|
</Form.Row>
|
||||||
|
</Form>
|
||||||
|
</Details.Content>
|
||||||
|
</Details>
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
}
|
40
frontend/src/components/notebook/NotebookDeleteButton.tsx
Normal file
40
frontend/src/components/notebook/NotebookDeleteButton.tsx
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import {faTrash} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
|
import * as React from "react"
|
||||||
|
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||||
|
import {useGroupContext} from "../../contexts/group"
|
||||||
|
import {ManagedResource} from "../../hooks/useManagedViewSet"
|
||||||
|
import {SophonNotebook} from "../../types/SophonTypes"
|
||||||
|
import {SafetyButton} from "../elements/SafetyButton"
|
||||||
|
|
||||||
|
|
||||||
|
export interface NotebookDeleteButtonProps {
|
||||||
|
resource: ManagedResource<SophonNotebook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function NotebookDeleteButton({resource}: NotebookDeleteButtonProps): JSX.Element | null {
|
||||||
|
const authorization = useAuthorizationContext()
|
||||||
|
const group = useGroupContext()
|
||||||
|
|
||||||
|
if(!authorization) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!group) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!authorization.state.user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!(
|
||||||
|
group.value.members.includes(authorization.state.user.id) || group.value.owner === authorization.state.user.id
|
||||||
|
)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafetyButton timeout={3} onClick={() => resource.destroy()} disabled={resource.busy}>
|
||||||
|
<FontAwesomeIcon icon={faTrash} spin={resource.busy}/> Delete
|
||||||
|
</SafetyButton>
|
||||||
|
)
|
||||||
|
}
|
43
frontend/src/components/notebook/NotebookLockButton.tsx
Normal file
43
frontend/src/components/notebook/NotebookLockButton.tsx
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
import {faLock} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
|
import {Button} from "@steffo/bluelib-react"
|
||||||
|
import * as React from "react"
|
||||||
|
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||||
|
import {useGroupContext} from "../../contexts/group"
|
||||||
|
import {ManagedResource} from "../../hooks/useManagedViewSet"
|
||||||
|
import {SophonNotebook} from "../../types/SophonTypes"
|
||||||
|
|
||||||
|
|
||||||
|
export interface NotebookLockButtonProps {
|
||||||
|
resource: ManagedResource<SophonNotebook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function NotebookLockButton({resource}: NotebookLockButtonProps): JSX.Element | null {
|
||||||
|
const authorization = useAuthorizationContext()
|
||||||
|
const group = useGroupContext()
|
||||||
|
|
||||||
|
if(!authorization) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!group) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!authorization.state.user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!(
|
||||||
|
group.value.members.includes(authorization.state.user.id) || group.value.owner === authorization.state.user.id
|
||||||
|
)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(resource.value.locked_by) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={() => resource.action("PATCH", "lock", {})} disabled={resource.busy}>
|
||||||
|
<FontAwesomeIcon icon={faLock} spin={resource.busy}/> Lock
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,10 +1,16 @@
|
||||||
import {faBook} from "@fortawesome/free-solid-svg-icons"
|
import {faBook} from "@fortawesome/free-solid-svg-icons"
|
||||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import {useCacheContext} from "../../contexts/cache"
|
||||||
import {ManagedResource} from "../../hooks/useManagedViewSet"
|
import {ManagedResource} from "../../hooks/useManagedViewSet"
|
||||||
import {SophonNotebook} from "../../types/SophonTypes"
|
import {SophonNotebook} from "../../types/SophonTypes"
|
||||||
import {Link} from "../elements/Link"
|
import {Link} from "../elements/Link"
|
||||||
import {ResourcePanel} from "../elements/ResourcePanel"
|
import {ResourcePanel} from "../elements/ResourcePanel"
|
||||||
|
import {NotebookDeleteButton} from "./NotebookDeleteButton"
|
||||||
|
import {NotebookLockButton} from "./NotebookLockButton"
|
||||||
|
import {NotebookStartButton} from "./NotebookStartButton"
|
||||||
|
import {NotebookStopButton} from "./NotebookStopButton"
|
||||||
|
import {NotebookUnlockButton} from "./NotebookUnlockButton"
|
||||||
|
|
||||||
|
|
||||||
export interface NotebookResourcePanelProps {
|
export interface NotebookResourcePanelProps {
|
||||||
|
@ -13,6 +19,9 @@ export interface NotebookResourcePanelProps {
|
||||||
|
|
||||||
|
|
||||||
export function NotebookResourcePanel({resource}: NotebookResourcePanelProps): JSX.Element {
|
export function NotebookResourcePanel({resource}: NotebookResourcePanelProps): JSX.Element {
|
||||||
|
const cache = useCacheContext()
|
||||||
|
|
||||||
|
const locked_by = cache?.getUserById(resource.value.locked_by)?.value.username
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ResourcePanel>
|
<ResourcePanel>
|
||||||
|
@ -25,10 +34,20 @@ export function NotebookResourcePanel({resource}: NotebookResourcePanelProps): J
|
||||||
</Link>
|
</Link>
|
||||||
</ResourcePanel.Name>
|
</ResourcePanel.Name>
|
||||||
<ResourcePanel.Text>
|
<ResourcePanel.Text>
|
||||||
|
{
|
||||||
|
resource.value.locked_by
|
||||||
|
?
|
||||||
|
`Locked by ${locked_by}`
|
||||||
|
:
|
||||||
|
null
|
||||||
|
}
|
||||||
</ResourcePanel.Text>
|
</ResourcePanel.Text>
|
||||||
<ResourcePanel.Buttons>
|
<ResourcePanel.Buttons>
|
||||||
|
<NotebookStopButton resource={resource}/>
|
||||||
|
<NotebookStartButton resource={resource}/>
|
||||||
|
<NotebookUnlockButton resource={resource}/>
|
||||||
|
<NotebookLockButton resource={resource}/>
|
||||||
|
<NotebookDeleteButton resource={resource}/>
|
||||||
</ResourcePanel.Buttons>
|
</ResourcePanel.Buttons>
|
||||||
</ResourcePanel>
|
</ResourcePanel>
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,7 +16,7 @@ export function NotebookRouter({projectPk, ...props}: ProjectRouterProps): JSX.E
|
||||||
return (
|
return (
|
||||||
<ViewSetRouter
|
<ViewSetRouter
|
||||||
{...props}
|
{...props}
|
||||||
viewSet={useManagedViewSet<SophonResearchProject>(`/api/notebooks/by-project/${projectPk}`, "slug")}
|
viewSet={useManagedViewSet<SophonResearchProject>(`/api/notebooks/by-project/${projectPk}/`, "slug")}
|
||||||
pathSegment={"notebook"}
|
pathSegment={"notebook"}
|
||||||
pkKey={"slug"}
|
pkKey={"slug"}
|
||||||
/>
|
/>
|
||||||
|
|
46
frontend/src/components/notebook/NotebookStartButton.tsx
Normal file
46
frontend/src/components/notebook/NotebookStartButton.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import {faLightbulb} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
|
import {Button} from "@steffo/bluelib-react"
|
||||||
|
import * as React from "react"
|
||||||
|
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||||
|
import {useGroupContext} from "../../contexts/group"
|
||||||
|
import {ManagedResource} from "../../hooks/useManagedViewSet"
|
||||||
|
import {SophonNotebook} from "../../types/SophonTypes"
|
||||||
|
|
||||||
|
|
||||||
|
export interface NotebookStartButtonProps {
|
||||||
|
resource: ManagedResource<SophonNotebook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function NotebookStartButton({resource}: NotebookStartButtonProps): JSX.Element | null {
|
||||||
|
const authorization = useAuthorizationContext()
|
||||||
|
const group = useGroupContext()
|
||||||
|
|
||||||
|
if(!authorization) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!group) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!authorization.state.user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!(
|
||||||
|
group.value.members.includes(authorization.state.user.id) || group.value.owner === authorization.state.user.id
|
||||||
|
)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(resource.value.is_running) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(resource.value.locked_by) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={() => resource.action("PATCH", "start", {})} disabled={resource.busy}>
|
||||||
|
<FontAwesomeIcon icon={faLightbulb} spin={resource.busy}/> Start
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
46
frontend/src/components/notebook/NotebookStopButton.tsx
Normal file
46
frontend/src/components/notebook/NotebookStopButton.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import {faLightbulb} from "@fortawesome/free-regular-svg-icons"
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
|
import {Button} from "@steffo/bluelib-react"
|
||||||
|
import * as React from "react"
|
||||||
|
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||||
|
import {useGroupContext} from "../../contexts/group"
|
||||||
|
import {ManagedResource} from "../../hooks/useManagedViewSet"
|
||||||
|
import {SophonNotebook} from "../../types/SophonTypes"
|
||||||
|
|
||||||
|
|
||||||
|
export interface NotebookStopButtonProps {
|
||||||
|
resource: ManagedResource<SophonNotebook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function NotebookStopButton({resource}: NotebookStopButtonProps): JSX.Element | null {
|
||||||
|
const authorization = useAuthorizationContext()
|
||||||
|
const group = useGroupContext()
|
||||||
|
|
||||||
|
if(!authorization) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!group) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!authorization.state.user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!(
|
||||||
|
group.value.members.includes(authorization.state.user.id) || group.value.owner === authorization.state.user.id
|
||||||
|
)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!resource.value.is_running) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(resource.value.locked_by) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={() => resource.action("PATCH", "stop", {})} disabled={resource.busy}>
|
||||||
|
<FontAwesomeIcon icon={faLightbulb} spin={resource.busy}/> Stop
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
52
frontend/src/components/notebook/NotebookUnlockButton.tsx
Normal file
52
frontend/src/components/notebook/NotebookUnlockButton.tsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import {faLockOpen} from "@fortawesome/free-solid-svg-icons"
|
||||||
|
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||||
|
import {Button} from "@steffo/bluelib-react"
|
||||||
|
import * as React from "react"
|
||||||
|
import {useAuthorizationContext} from "../../contexts/authorization"
|
||||||
|
import {useGroupContext} from "../../contexts/group"
|
||||||
|
import {ManagedResource} from "../../hooks/useManagedViewSet"
|
||||||
|
import {SophonNotebook} from "../../types/SophonTypes"
|
||||||
|
import {SafetyButton} from "../elements/SafetyButton"
|
||||||
|
|
||||||
|
|
||||||
|
export interface NotebookUnlockButtonProps {
|
||||||
|
resource: ManagedResource<SophonNotebook>,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function NotebookUnlockButton({resource}: NotebookUnlockButtonProps): JSX.Element | null {
|
||||||
|
const authorization = useAuthorizationContext()
|
||||||
|
const group = useGroupContext()
|
||||||
|
|
||||||
|
if(!authorization) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!group) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!authorization.state.user) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!(
|
||||||
|
group.value.members.includes(authorization.state.user.id) || group.value.owner === authorization.state.user.id
|
||||||
|
)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if(!resource.value.locked_by) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if(resource.value.locked_by === authorization.state.user.id) {
|
||||||
|
return (
|
||||||
|
<Button onClick={() => resource.action("PATCH", "unlock", {})} disabled={resource.busy}>
|
||||||
|
<FontAwesomeIcon icon={faLockOpen} spin={resource.busy}/> Unlock
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafetyButton timeout={3} onClick={() => resource.action("PATCH", "unlock", {})} disabled={resource.busy}>
|
||||||
|
<FontAwesomeIcon icon={faLockOpen} spin={resource.busy}/> Unlock
|
||||||
|
</SafetyButton>
|
||||||
|
)
|
||||||
|
}
|
|
@ -65,7 +65,7 @@ export function ProjectCreateBox({viewSet, resource}: ProjectCreateBoxProps): JS
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[authorization, resource],
|
[authorization, group, resource],
|
||||||
)
|
)
|
||||||
|
|
||||||
const applyChanges =
|
const applyChanges =
|
||||||
|
@ -96,7 +96,7 @@ export function ProjectCreateBox({viewSet, resource}: ProjectCreateBoxProps): JS
|
||||||
const canApply =
|
const canApply =
|
||||||
React.useMemo(
|
React.useMemo(
|
||||||
() => name.validity === true && visibility.validity === true && Boolean(authorization?.state.user?.username) && group,
|
() => name.validity === true && visibility.validity === true && Boolean(authorization?.state.user?.username) && group,
|
||||||
[name, visibility, authorization],
|
[name, visibility, authorization, group],
|
||||||
)
|
)
|
||||||
|
|
||||||
const hasError =
|
const hasError =
|
||||||
|
|
|
@ -52,8 +52,16 @@ export function CacheProvider({children}: WithChildren): JSX.Element {
|
||||||
|
|
||||||
const getUserById =
|
const getUserById =
|
||||||
React.useCallback(
|
React.useCallback(
|
||||||
(id: number) => usersIdMap?.[id.toString()],
|
(id: number) => {
|
||||||
[usersIdMap],
|
if(!id) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if(!usersIdMap) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return usersIdMap[id.toString()]
|
||||||
|
},
|
||||||
|
[usersIdMap]
|
||||||
)
|
)
|
||||||
|
|
||||||
return <CacheContext.Provider value={{users, getUserById}} children={children}/>
|
return <CacheContext.Provider value={{users, getUserById}} children={children}/>
|
||||||
|
|
21
frontend/src/contexts/notebook.tsx
Normal file
21
frontend/src/contexts/notebook.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import {ManagedResource} from "../hooks/useManagedViewSet"
|
||||||
|
import {WithChildren, WithResource} from "../types/ExtraTypes"
|
||||||
|
import {SophonNotebook} from "../types/SophonTypes"
|
||||||
|
|
||||||
|
|
||||||
|
const notebookContext = React.createContext<ManagedResource<SophonNotebook> | undefined>(undefined)
|
||||||
|
const NotebookContext = notebookContext
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to access the {@link notebookContext}.
|
||||||
|
*/
|
||||||
|
export function useNotebookContext(): ManagedResource<SophonNotebook> | undefined {
|
||||||
|
return React.useContext(notebookContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function NotebookProvider({resource, children}: WithResource<SophonNotebook> & WithChildren): JSX.Element {
|
||||||
|
return <NotebookContext.Provider value={resource} children={children}/>
|
||||||
|
}
|
21
frontend/src/contexts/project.tsx
Normal file
21
frontend/src/contexts/project.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import * as React from "react"
|
||||||
|
import {ManagedResource} from "../hooks/useManagedViewSet"
|
||||||
|
import {WithChildren, WithResource} from "../types/ExtraTypes"
|
||||||
|
import {SophonResearchProject} from "../types/SophonTypes"
|
||||||
|
|
||||||
|
|
||||||
|
const projectContext = React.createContext<ManagedResource<SophonResearchProject> | undefined>(undefined)
|
||||||
|
const ProjectContext = projectContext
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to access the {@link projectContext}.
|
||||||
|
*/
|
||||||
|
export function useProjectContext(): ManagedResource<SophonResearchProject> | undefined {
|
||||||
|
return React.useContext(projectContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function ProjectProvider({resource, children}: WithResource<SophonResearchProject> & WithChildren): JSX.Element {
|
||||||
|
return <ProjectContext.Provider value={resource} children={children}/>
|
||||||
|
}
|
Loading…
Reference in a new issue