-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat!: add support for session secrets in Renku 2.0 projects (#3413)
Closes #3412. Details: * Add support for session secrets slots and session secrets in Renku 2.0 projects. * Ask logged-in users to provide missing session secrets at launch. * Re-work user secrets: - Split the "user secrets" page into v1 and v2 versions. * `/secrets` lists secrets usable in Renku 1.0. * `/v2/secrets` lists secrets usable in Renku 2.0 (all secrets). - The `name` field does not have constraints, e.g. a secret can have "My Secret" as a name. - A new field `default_filename` has been added for Renku 1.0 sessions. - Secrets list the connected session secret slot and data connectors. * Update the "Session Secrets" section to allow for the mount location to be configured. * Polish the session secrets feature: - Launch interrupt for anon users - Re-designed user secrets page v2 - Updated UI for session secrets and session secret slots - Ask for secret value after creating a new secret slot
- Loading branch information
Showing
99 changed files
with
6,768 additions
and
785 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -243,6 +243,7 @@ | |
"rproj", | ||
"rstudio", | ||
"scala", | ||
"scrollable", | ||
"selectautosuggest", | ||
"semibold", | ||
"serializable", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
.modal :global(.modal-content) { | ||
height: unset !important; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
226 changes: 226 additions & 0 deletions
226
...t/src/features/ProjectPageV2/ProjectPageContent/SessionSecrets/AddSessionSecretButton.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
/*! | ||
* Copyright 2024 - Swiss Data Science Center (SDSC) | ||
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and | ||
* Eidgenössische Technische Hochschule Zürich (ETHZ). | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import cx from "classnames"; | ||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; | ||
import { PlusLg, XLg } from "react-bootstrap-icons"; | ||
import { useForm } from "react-hook-form"; | ||
import { | ||
Button, | ||
Form, | ||
Modal, | ||
ModalBody, | ||
ModalFooter, | ||
ModalHeader, | ||
UncontrolledTooltip, | ||
} from "reactstrap"; | ||
|
||
import { Loader } from "../../../../components/Loader"; | ||
import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert"; | ||
import { usePostSessionSecretSlotsMutation } from "../../../projectsV2/api/projectV2.enhanced-api"; | ||
import { useProject } from "../../ProjectPageContainer/ProjectPageContainer"; | ||
import DescriptionField from "./fields/DescriptionField"; | ||
import type { SessionSecretSlot } from "../../../projectsV2/api/projectV2.api"; | ||
import FilenameField from "./fields/FilenameField"; | ||
import NameField from "./fields/NameField"; | ||
import ProvideSessionSecretModalContent from "./ProvideSessionSecretModalContent"; | ||
import { SuccessAlert } from "../../../../components/Alert"; | ||
|
||
export default function AddSessionSecretButton() { | ||
const ref = useRef<HTMLButtonElement>(null); | ||
|
||
const [isOpen, setIsOpen] = useState(false); | ||
const toggle = useCallback(() => setIsOpen((isOpen) => !isOpen), []); | ||
|
||
return ( | ||
<> | ||
<Button color="outline-primary" innerRef={ref} onClick={toggle} size="sm"> | ||
<PlusLg className="bi" /> | ||
<span className="visually-hidden">Add session secret slot</span> | ||
</Button> | ||
<AddSessionSecretModal isOpen={isOpen} toggle={toggle} /> | ||
<UncontrolledTooltip target={ref}> | ||
Add session secret slot | ||
</UncontrolledTooltip> | ||
</> | ||
); | ||
} | ||
|
||
interface AddSessionSecretModalProps { | ||
isOpen: boolean; | ||
toggle: () => void; | ||
} | ||
|
||
function AddSessionSecretModal({ isOpen, toggle }: AddSessionSecretModalProps) { | ||
const [state, setState] = useState<AddSessionSecretModalState>({ | ||
step: "add-secret-slot", | ||
}); | ||
const { step } = state; | ||
const onFirstStepSuccess = useCallback( | ||
(secretSlot: SessionSecretSlot) => | ||
setState({ step: "provide-secret", secretSlot }), | ||
[] | ||
); | ||
|
||
useEffect(() => { | ||
if (!isOpen) { | ||
setState({ step: "add-secret-slot" }); | ||
} | ||
}, [isOpen]); | ||
|
||
const slotSavedAlert = step === "provide-secret" && ( | ||
<SuccessAlert timeout={0} dismissible={false}> | ||
The session secret slot{" "} | ||
<span className="fw-bold">{state.secretSlot.name}</span> has been | ||
successfully added. You can now provide a value for it. | ||
</SuccessAlert> | ||
); | ||
|
||
return ( | ||
<Modal backdrop="static" centered isOpen={isOpen} size="lg" toggle={toggle}> | ||
{step === "add-secret-slot" && ( | ||
<AddSessionSecretModalContentStep1 | ||
isOpen={isOpen} | ||
onSuccess={onFirstStepSuccess} | ||
toggle={toggle} | ||
/> | ||
)} | ||
{step === "provide-secret" && ( | ||
<ProvideSessionSecretModalContent | ||
isOpen={isOpen} | ||
previousStepAlert={slotSavedAlert} | ||
secretSlot={state.secretSlot} | ||
toggle={toggle} | ||
/> | ||
)} | ||
</Modal> | ||
); | ||
} | ||
|
||
type AddSessionSecretModalState = | ||
| { step: "add-secret-slot" } | ||
| { step: "provide-secret"; secretSlot: SessionSecretSlot }; | ||
|
||
interface AddSessionSecretModalContentStep1Props | ||
extends AddSessionSecretModalProps { | ||
onSuccess: (secretSlot: SessionSecretSlot) => void; | ||
} | ||
|
||
function AddSessionSecretModalContentStep1({ | ||
isOpen, | ||
onSuccess, | ||
toggle, | ||
}: AddSessionSecretModalContentStep1Props) { | ||
const { project } = useProject(); | ||
const { id: projectId, secrets_mount_directory: secretsMountDirectory } = | ||
project; | ||
|
||
const [postSessionSecretSlot, result] = usePostSessionSecretSlotsMutation(); | ||
|
||
const { | ||
control, | ||
formState: { errors }, | ||
handleSubmit, | ||
reset, | ||
} = useForm<AddSessionSecretForm>({ | ||
defaultValues: { | ||
description: "", | ||
filename: "", | ||
name: "", | ||
}, | ||
}); | ||
|
||
const submitHandler = useCallback( | ||
(data: AddSessionSecretForm) => { | ||
const description = data.description?.trim(); | ||
const name = data.name?.trim(); | ||
postSessionSecretSlot({ | ||
sessionSecretSlotPost: { | ||
filename: data.filename, | ||
project_id: projectId, | ||
description: description ? description : undefined, | ||
name: name ? name : undefined, | ||
}, | ||
}); | ||
}, | ||
[postSessionSecretSlot, projectId] | ||
); | ||
const onSubmit = useMemo( | ||
() => handleSubmit(submitHandler), | ||
[handleSubmit, submitHandler] | ||
); | ||
|
||
useEffect(() => { | ||
if (!isOpen) { | ||
reset(); | ||
result.reset(); | ||
} | ||
}, [isOpen, reset, result]); | ||
|
||
useEffect(() => { | ||
if (result.isSuccess) { | ||
onSuccess(result.data); | ||
} | ||
}, [onSuccess, result.data, result.isSuccess]); | ||
|
||
return ( | ||
<Form noValidate onSubmit={onSubmit}> | ||
<ModalHeader toggle={toggle}>Add session secret slot</ModalHeader> | ||
<ModalBody> | ||
<p>Add a new slot for a secret to be mounted in sessions.</p> | ||
|
||
{result.error && ( | ||
<RtkOrNotebooksError error={result.error} dismissible={false} /> | ||
)} | ||
|
||
<NameField control={control} errors={errors} name="name" /> | ||
<DescriptionField | ||
control={control} | ||
errors={errors} | ||
name="description" | ||
/> | ||
<FilenameField | ||
control={control} | ||
errors={errors} | ||
name="filename" | ||
secretsMountDirectory={secretsMountDirectory} | ||
/> | ||
</ModalBody> | ||
<ModalFooter> | ||
<Button color="outline-primary" onClick={toggle}> | ||
<XLg className={cx("bi", "me-1")} /> | ||
Close | ||
</Button> | ||
<Button color="primary" disabled={result.isLoading} type="submit"> | ||
{result.isLoading ? ( | ||
<Loader className="me-1" inline size={16} /> | ||
) : ( | ||
<PlusLg className={cx("bi", "me-1")} /> | ||
)} | ||
Add session secret slot | ||
</Button> | ||
</ModalFooter> | ||
</Form> | ||
); | ||
} | ||
|
||
interface AddSessionSecretForm { | ||
name: string | undefined; | ||
description: string | undefined; | ||
filename: string; | ||
} |
Oops, something went wrong.