Skip to content

Commit

Permalink
feat!: support custom env variables in Renku 2.0 (#3227)
Browse files Browse the repository at this point in the history
  • Loading branch information
andre-code authored Oct 11, 2024
1 parent cd14630 commit 69f25bb
Show file tree
Hide file tree
Showing 35 changed files with 2,434 additions and 1,438 deletions.
1 change: 1 addition & 0 deletions client/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@
"jupyter",
"katex",
"kernelspec",
"kubernetes",
"Keycloak",
"Lausanne",
"linkify",
Expand Down
46 changes: 46 additions & 0 deletions client/src/components/MoreInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*!
* 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 { ReactNode, useRef } from "react";
import { InfoCircleFill } from "react-bootstrap-icons";
import { PopoverBody, UncontrolledPopover } from "reactstrap";

export function MoreInfo({
trigger = "hover focus",
children,
}: {
trigger?: string;
children?: ReactNode;
}) {
const ref = useRef<HTMLSpanElement>(null);

return (
<>
<span ref={ref}>
<InfoCircleFill
className={cx("bi", "ms-1", "cursor-pointer", "text-light-emphasis")}
tabIndex={0}
/>
</span>
<UncontrolledPopover target={ref} placement="right" trigger={trigger}>
<PopoverBody>{children}</PopoverBody>
</UncontrolledPopover>
</>
);
}
28 changes: 21 additions & 7 deletions client/src/features/admin/AddSessionEnvironmentButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import SessionEnvironmentFormContent, {
SessionEnvironmentForm,
} from "./SessionEnvironmentFormContent";
import { useAddSessionEnvironmentMutation } from "./adminSessions.api";
import { safeParseJSONStringArray } from "../sessionsV2/session.utils";

export default function AddSessionEnvironmentButton() {
const [isOpen, setIsOpen] = useState(false);
Expand Down Expand Up @@ -79,12 +80,26 @@ function AddSessionEnvironmentModal({
});
const onSubmit = useCallback(
(data: SessionEnvironmentForm) => {
addSessionEnvironment({
container_image: data.container_image,
name: data.name,
default_url: data.default_url.trim() ? data.default_url : undefined,
description: data.description.trim() ? data.description : undefined,
});
const commandParsed = safeParseJSONStringArray(data.command);
const argsParsed = safeParseJSONStringArray(data.args);
if (commandParsed.parsed && argsParsed.parsed)
addSessionEnvironment({
container_image: data.container_image,
name: data.name,
default_url: data.default_url.trim() ? data.default_url : undefined,
description: data.description.trim() ? data.description : undefined,
port: data.port ?? undefined,
working_directory: data.working_directory.trim()
? data.working_directory
: undefined,
mount_directory: data.mount_directory.trim()
? data.mount_directory
: undefined,
uid: data.uid ?? undefined,
gid: data.gid ?? undefined,
command: commandParsed.data,
args: argsParsed.data,
});
},
[addSessionEnvironment]
);
Expand Down Expand Up @@ -120,7 +135,6 @@ function AddSessionEnvironmentModal({
<ModalHeader toggle={toggle}>Add session environment</ModalHeader>
<ModalBody>
{result.error && <RtkErrorAlert error={result.error} />}

<SessionEnvironmentFormContent control={control} errors={errors} />
</ModalBody>
<ModalFooter>
Expand Down
73 changes: 73 additions & 0 deletions client/src/features/admin/SessionEnvironmentAdvancedFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*!
* 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, useState } from "react";
import { Control, FieldErrors } from "react-hook-form";
import { Collapse } from "reactstrap";
import ChevronFlippedIcon from "../../components/icons/ChevronFlippedIcon";
import { SessionEnvironmentForm } from "./SessionEnvironmentFormContent";
import { AdvancedSettingsFields } from "../sessionsV2/components/SessionForm/AdvancedSettingsFields";

interface SessionEnvironmentAdvancedFieldsProps {
control: Control<SessionEnvironmentForm, unknown>;
errors: FieldErrors<SessionEnvironmentForm>;
}

export default function SessionEnvironmentAdvancedFields({
control,
errors,
}: SessionEnvironmentAdvancedFieldsProps) {
const [isAdvancedSettingOpen, setIsAdvancedSettingsOpen] = useState(false);
const toggleIsOpen = useCallback(
() =>
setIsAdvancedSettingsOpen(
(isAdvancedSettingOpen) => !isAdvancedSettingOpen
),
[]
);
return (
<>
<div>
<button
className={cx(
"d-flex",
"align-items-center",
"w-100",
"bg-transparent",
"border-0",
"fw-bold"
)}
type="button"
onClick={toggleIsOpen}
>
Advanced settings{" "}
<ChevronFlippedIcon flipped={isAdvancedSettingOpen} />
</button>
</div>
<Collapse isOpen={isAdvancedSettingOpen}>
<div className="mt-3">
<AdvancedSettingsFields<SessionEnvironmentForm>
control={control}
errors={errors}
/>
</div>
</Collapse>
</>
);
}
28 changes: 9 additions & 19 deletions client/src/features/admin/SessionEnvironmentFormContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@
import cx from "classnames";
import { Control, Controller, FieldErrors } from "react-hook-form";
import { Input, Label } from "reactstrap";
import SessionEnvironmentAdvancedFields from "./SessionEnvironmentAdvancedFields";

export interface SessionEnvironmentForm {
container_image: string;
default_url: string;
description: string;
name: string;
port: number;
working_directory: string;
uid: number;
gid: number;
mount_directory: string;
command: string;
args: string;
}

interface SessionEnvironmentFormContentProps {
Expand Down Expand Up @@ -101,25 +109,7 @@ export default function SessionEnvironmentFormContent({
/>
<div className="invalid-feedback">Please provide a container image</div>
</div>

<div>
<Label className="form-label" for="addSessionEnvironmentDefaultUrl">
Default URL
</Label>
<Controller
control={control}
name="default_url"
render={({ field }) => (
<Input
className="form-control"
id="addSessionEnvironmentDefaultUrl"
placeholder="/lab"
type="text"
{...field}
/>
)}
/>
</div>
<SessionEnvironmentAdvancedFields control={control} errors={errors} />
</>
);
}
46 changes: 43 additions & 3 deletions client/src/features/admin/SessionEnvironmentsSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ import {
Container,
Row,
} from "reactstrap";

import { Loader } from "../../components/Loader";
import { TimeCaption } from "../../components/TimeCaption";
import { CommandCopy } from "../../components/commandCopy/CommandCopy";
import { RtkErrorAlert } from "../../components/errors/RtkErrorAlert";
import { ErrorLabel } from "../../components/formlabels/FormLabels.tsx";
import { safeStringify } from "../sessionsV2/session.utils";
import type {
SessionEnvironment,
SessionEnvironmentList,
Expand Down Expand Up @@ -107,8 +108,20 @@ interface SessionEnvironmentDisplayProps {
function SessionEnvironmentDisplay({
environment,
}: SessionEnvironmentDisplayProps) {
const { container_image, creation_date, name, default_url, description } =
environment;
const {
container_image,
creation_date,
name,
default_url,
description,
port,
gid,
uid,
working_directory,
mount_directory,
command,
args,
} = environment;

return (
<Col className={cx("col-12", "col-sm-6")}>
Expand All @@ -135,6 +148,28 @@ function SessionEnvironmentDisplay({
</i>
)}
</CardText>
<CardText className="mb-0">
Mount directory: <code>{mount_directory}</code>
</CardText>
<CardText className="mb-0">
Work directory: <code>{working_directory}</code>
</CardText>
<CardText className="mb-0">
Port: <code>{port}</code>
</CardText>
<CardText className="mb-0">
GID: <code>{gid}</code>
</CardText>
<CardText className="mb-0">
UID: <code>{uid}</code>
</CardText>
<CardText className="mb-0">
Command:{" "}
<EnvironmentCode value={command ? safeStringify(command) : "-"} />
</CardText>
<CardText className="mb-0">
Args: <EnvironmentCode value={args ? safeStringify(args) : "-"} />
</CardText>
<CardText>
<TimeCaption
datetime={creation_date}
Expand All @@ -152,3 +187,8 @@ function SessionEnvironmentDisplay({
</Col>
);
}

function EnvironmentCode({ value }: { value: string | null }) {
if (value === null) return <ErrorLabel text={"Invalid JSON array value"} />;
return <code>{value}</code>;
}
46 changes: 25 additions & 21 deletions client/src/features/admin/UpdateSessionEnvironmentButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@ import {
ModalFooter,
ModalHeader,
} from "reactstrap";

import { Loader } from "../../components/Loader";
import ButtonStyles from "../../components/buttons/Buttons.module.scss";
import { RtkErrorAlert } from "../../components/errors/RtkErrorAlert";
import { safeParseJSONStringArray } from "../sessionsV2/session.utils";
import { SessionEnvironment } from "../sessionsV2/sessionsV2.types";
import SessionEnvironmentFormContent, {
SessionEnvironmentForm,
} from "./SessionEnvironmentFormContent";
import { useUpdateSessionEnvironmentMutation } from "./adminSessions.api";
import { getSessionEnvironmentValues } from "./adminSessions.utils";

interface UpdateSessionEnvironmentButtonProps {
environment: SessionEnvironment;
Expand Down Expand Up @@ -93,22 +94,31 @@ function UpdateSessionEnvironmentModal({
handleSubmit,
reset,
} = useForm<SessionEnvironmentForm>({
defaultValues: {
container_image: environment.container_image,
default_url: environment.default_url,
description: environment.description,
name: environment.name,
},
defaultValues: getSessionEnvironmentValues(environment),
});
const onSubmit = useCallback(
(data: SessionEnvironmentForm) => {
updateSessionEnvironment({
environmentId: environment.id,
container_image: data.container_image,
name: data.name,
default_url: data.default_url.trim() ? data.default_url : "",
description: data.description.trim() ? data.description : "",
});
const commandParsed = safeParseJSONStringArray(data.command);
const argsParsed = safeParseJSONStringArray(data.args);
if (commandParsed.parsed && argsParsed.parsed)
updateSessionEnvironment({
environmentId: environment.id,
container_image: data.container_image,
name: data.name,
default_url: data.default_url.trim() ? data.default_url : "",
description: data.description.trim() ? data.description : "",
port: data.port ?? undefined,
working_directory: data.working_directory.trim()
? data.working_directory
: undefined,
mount_directory: data.mount_directory.trim()
? data.mount_directory
: undefined,
uid: data.uid ?? undefined,
gid: data.gid ?? undefined,
command: commandParsed.data,
args: argsParsed.data,
});
},
[environment.id, updateSessionEnvironment]
);
Expand All @@ -127,12 +137,7 @@ function UpdateSessionEnvironmentModal({
}, [isOpen, result]);

useEffect(() => {
reset({
container_image: environment.container_image,
default_url: environment.default_url ?? "",
description: environment.description ?? "",
name: environment.name,
});
reset(getSessionEnvironmentValues(environment));
}, [environment, reset]);

return (
Expand All @@ -152,7 +157,6 @@ function UpdateSessionEnvironmentModal({
<ModalHeader toggle={toggle}>Update session environment</ModalHeader>
<ModalBody>
{result.error && <RtkErrorAlert error={result.error} />}

<SessionEnvironmentFormContent control={control} errors={errors} />
</ModalBody>
<ModalFooter>
Expand Down
Loading

0 comments on commit 69f25bb

Please sign in to comment.