diff --git a/client/.eslintrc.json b/client/.eslintrc.json
index 09552ec567..edcb56f461 100644
--- a/client/.eslintrc.json
+++ b/client/.eslintrc.json
@@ -203,6 +203,7 @@
"papermill",
"pathname",
"pdfjs",
+ "pkce",
"plaintext",
"poller",
"popups",
diff --git a/client/src/features/admin/AddConnectedServiceButton.tsx b/client/src/features/admin/AddConnectedServiceButton.tsx
new file mode 100644
index 0000000000..c67f851bab
--- /dev/null
+++ b/client/src/features/admin/AddConnectedServiceButton.tsx
@@ -0,0 +1,173 @@
+/*!
+ * 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, useState } from "react";
+import { PlusLg, XLg } from "react-bootstrap-icons";
+import { Controller, useForm } from "react-hook-form";
+import {
+ Button,
+ Form,
+ Input,
+ Label,
+ Modal,
+ ModalBody,
+ ModalFooter,
+ ModalHeader,
+} from "reactstrap";
+import {
+ ConnectedServiceForm,
+ CreateProviderParams,
+} from "../connectedServices/connectedServices.types";
+import { useCreateProviderMutation } from "../connectedServices/connectedServices.api";
+import { RtkOrNotebooksError } from "../../components/errors/RtkErrorAlert";
+import { Loader } from "../../components/Loader";
+import ConnectedServiceFormContent from "./ConnectedServiceFormContent";
+
+export default function AddConnectedServiceButton() {
+ const [isOpen, setIsOpen] = useState(false);
+ const toggle = useCallback(() => {
+ setIsOpen((open) => !open);
+ }, []);
+
+ return (
+ <>
+
+
+ >
+ );
+}
+
+interface AddConnectedServiceModalProps {
+ isOpen: boolean;
+ toggle: () => void;
+}
+function AddConnectedServiceModal({
+ isOpen,
+ toggle,
+}: AddConnectedServiceModalProps) {
+ const [createProvider, result] = useCreateProviderMutation();
+
+ const {
+ control,
+ formState: { errors },
+ handleSubmit,
+ reset,
+ } = useForm({
+ defaultValues: {
+ id: "",
+ kind: "gitlab",
+ client_id: "",
+ client_secret: "",
+ display_name: "",
+ scope: "",
+ url: "",
+ use_pkce: false,
+ },
+ });
+ const onSubmit = useCallback(
+ (data: CreateProviderParams) => {
+ createProvider({
+ id: data.id,
+ kind: data.kind,
+ client_id: data.client_id,
+ client_secret: data.client_secret,
+ display_name: data.display_name,
+ scope: data.scope,
+ url: data.url,
+ use_pkce: data.use_pkce,
+ });
+ },
+ [createProvider]
+ );
+
+ useEffect(() => {
+ if (!result.isSuccess) {
+ return;
+ }
+ toggle();
+ }, [result.isSuccess, toggle]);
+
+ useEffect(() => {
+ if (!isOpen) {
+ reset();
+ result.reset();
+ }
+ }, [isOpen, reset, result]);
+
+ return (
+
+
+
+ );
+}
diff --git a/client/src/features/admin/AdminPage.tsx b/client/src/features/admin/AdminPage.tsx
index ef013906d9..b7a4d4b0de 100644
--- a/client/src/features/admin/AdminPage.tsx
+++ b/client/src/features/admin/AdminPage.tsx
@@ -62,6 +62,7 @@ import { ResourcePoolUser } from "./adminComputeResources.types";
import { useGetKeycloakUserQuery } from "./adminKeycloak.api";
import { KeycloakUser } from "./adminKeycloak.types";
import useKeycloakRealm from "./useKeycloakRealm.hook";
+import ConnectedServicesSection from "./ConnectedServicesSection";
export default function AdminPage() {
return (
@@ -69,6 +70,7 @@ export default function AdminPage() {
Admin Panel
+
>
);
@@ -76,8 +78,8 @@ export default function AdminPage() {
function ComputeResourcesSection() {
return (
-
-
Compute Resources
+
+
Compute Resources
);
diff --git a/client/src/features/admin/ConnectedServiceFormContent.tsx b/client/src/features/admin/ConnectedServiceFormContent.tsx
new file mode 100644
index 0000000000..5648ba00bc
--- /dev/null
+++ b/client/src/features/admin/ConnectedServiceFormContent.tsx
@@ -0,0 +1,201 @@
+/*!
+ * 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 { Control, Controller, FieldErrors } from "react-hook-form";
+import { Input, Label } from "reactstrap";
+
+import { ConnectedServiceForm } from "../connectedServices/connectedServices.types";
+
+export interface ConnectedServiceFormContentProps {
+ control: Control;
+ errors: FieldErrors;
+}
+export default function ConnectedServiceFormContent({
+ control,
+ errors,
+}: ConnectedServiceFormContentProps) {
+ return (
+ <>
+
+ Please provide a valid client secret or leave it empty
+
+
+
+
+
+ (
+
+ )}
+ />
+
+ Please provide a valid scope or leave it empty
+
+
+ >
+ );
+}
diff --git a/client/src/features/admin/ConnectedServicesSection.tsx b/client/src/features/admin/ConnectedServicesSection.tsx
new file mode 100644
index 0000000000..a210c4bbfc
--- /dev/null
+++ b/client/src/features/admin/ConnectedServicesSection.tsx
@@ -0,0 +1,161 @@
+/*!
+ * 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 {
+ Card,
+ CardBody,
+ CardHeader,
+ CardText,
+ Col,
+ Collapse,
+ Container,
+ Row,
+} from "reactstrap";
+import { RtkOrNotebooksError } from "../../components/errors/RtkErrorAlert";
+import { Loader } from "../../components/Loader";
+import { useGetProvidersQuery } from "../connectedServices/connectedServices.api";
+import {
+ Provider,
+ ProviderList,
+} from "../connectedServices/connectedServices.types";
+import AddConnectedServiceButton from "./AddConnectedServiceButton";
+import ChevronFlippedIcon from "../../components/icons/ChevronFlippedIcon";
+import { useCallback, useState } from "react";
+import DeleteConnectedServiceButton from "./DeleteConnectedServiceButton";
+import UpdateConnectedServiceButton from "./UpdateConnectedServiceButton";
+
+export default function ConnectedServicesSection() {
+ return (
+
+