diff --git a/app/cdap/components/NamespaceAdmin/AdminTabs.tsx b/app/cdap/components/NamespaceAdmin/AdminTabs.tsx index 07f641df887..db8c0ea9ff6 100644 --- a/app/cdap/components/NamespaceAdmin/AdminTabs.tsx +++ b/app/cdap/components/NamespaceAdmin/AdminTabs.tsx @@ -29,6 +29,7 @@ import Tab from '@material-ui/core/Tab'; import TabContext from '@material-ui/lab/TabContext'; import styled from 'styled-components'; import { useLocation } from 'react-router'; +import ServiceAccounts from './ServiceAccounts'; const StyledTabs = styled(Tabs)` border-bottom: 1px solid #e8e8e8; @@ -91,6 +92,11 @@ export const AdminTabs = () => { value={`${baseNSPath}/connections`} /> + {sourceControlManagementEnabled && ( { + diff --git a/app/cdap/components/NamespaceAdmin/ServiceAccounts/index.tsx b/app/cdap/components/NamespaceAdmin/ServiceAccounts/index.tsx new file mode 100644 index 00000000000..6fe0e6e0fff --- /dev/null +++ b/app/cdap/components/NamespaceAdmin/ServiceAccounts/index.tsx @@ -0,0 +1,249 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * 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 React, { useState } from 'react'; +import { connect } from 'react-redux'; + +import Table from 'components/shared/Table'; +import TableHeader from 'components/shared/Table/TableHeader'; +import TableRow from 'components/shared/Table/TableRow'; +import TableCell from 'components/shared/Table/TableCell'; +import TableBody from 'components/shared/Table/TableBody'; +import Button from '@material-ui/core/Button'; +import TextField from '@material-ui/core/TextField'; +import ActionsPopover, { IAction } from 'components/shared/ActionsPopover'; +import T from 'i18n-react'; +import InputAdornment from '@material-ui/core/InputAdornment'; +import HelpIcon from '@material-ui/icons/Help'; +import Box from '@material-ui/core/Box'; +import styled from 'styled-components'; +import IconButton from '@material-ui/core/IconButton'; +import { ConfirmDialog } from 'components/shared/ConfirmDialog'; + +const PREFIX = 'features.ServiceAccounts'; + +const SubTitleBox = styled(Box)` + margin-bottom: 15px; +`; + +const StyledTextField = styled(TextField)` + & .MuiInputLabel-shrink { + font-size: 15px; + } + & .MuiOutlinedInput-notchedOutline { + font-size: 14px; + border-color: #80868b; + } + & .MuiFormHelperText-root { + margin-left: 0px; + font-size: 12px; + } +`; + +const ServiceAccountsView = ({ serviceacnts }) => { + // mock data + // serviceacnts = [{ name: 'serviceaccount1' }, { name: 'serviceaccount2' }]; + + const [showPopover, setShowPopover] = useState(false); + const [isDeleteModalOpen, setDeleteModalOpen] = useState(false); + const [selectedServiceAcnt, setSelectedServiceAcnt] = useState(null); + const [open, setOpen] = React.useState(false); + const [deleteErrorMsg, setDeleteErrorMsg] = useState(null); + const [extendedErrorMsg, setExtendedErrorMsg] = useState(null); + const [saveErrorMsg, setSaveErrorMsg] = useState(null); + const [saveErrorDetails, setSaveErrorDetails] = useState(null); + + const handleClose = () => { + setOpen(false); + setSaveErrorMsg(null); + setSaveErrorDetails(null); + }; + + const handleSave = () => { + // alert('saving ' + selectedServiceAcnt); + // setSaveErrorMsg(T.translate(`${PREFIX}.saveErrorMessage`)); + // setSaveErrorDetails({ + // response: + // 'Sample Error message for testing : Error occurred as save is not intergrated with API', + // }); + }; + + const addHandler = () => { + setOpen(true); + setSelectedServiceAcnt(null); + }; + + const deleteHanlder = (serviceAcnt) => { + // alert('delete'); + // const sampleError = + // 'Error occurred : ' + + // "Exception in thread 'main' java.util.InputMismatchException" + + // 'at java.base/java.util.Scanner.throwFor(Scanner.java:939)' + + // 'at java.base/java.util.Scanner.next(Scanner.java:1594)' + + // 'at java.base/java.util.Scanner.nextFloat(Scanner.java:2496)' + + // 'at java.base/java.util.Scanner.throwFor(Scanner.java:939)' + + // 'at java.base/java.util.Scanner.next(Scanner.java:1594)' + + // 'at java.base/java.util.Scanner.nextFloat(Scanner.java:2496)' + + // 'at java.base/java.util.Scanner.throwFor(Scanner.java:939)' + + // 'at java.base/java.util.Scanner.next(Scanner.java:1594)' + + // 'at java.base/java.util.Scanner.nextFloat(Scanner.java:2496)'; + // setExtendedErrorMsg(sampleError); + // setDeleteErrorMsg(T.translate(`${PREFIX}.deleteErrorMessage`)); + }; + + const handleShowHelp = () => { + // to do + }; + + function showEditDialog(serviceAcnt) { + setSelectedServiceAcnt(serviceAcnt.name); + setOpen(true); + setShowPopover(!showPopover); + } + + const closeDeleteConfirmation = () => { + setDeleteModalOpen(false); + setDeleteErrorMsg(null); + }; + + const showDeleteConfirmation = (serviceAcnt) => { + setSelectedServiceAcnt(serviceAcnt.name); + setShowPopover(!showPopover); + setDeleteModalOpen(true); + }; + + const renderDeleteConfirm = () => { + return ( + + ); + }; + + const getEditDialogContent = () => { + return ( + { + setSelectedServiceAcnt(ev.target.value); + }} + InputProps={{ + endAdornment: ( + + + + + + ), + }} + /> + ); + }; + + const renderEditDialog = () => { + return ( + + ); + }; + + return ( +
+ {!serviceacnts && ( + + + + )} + + + + {T.translate(`${PREFIX}.serviceAccount`)} + + + + + + {serviceacnts && + serviceacnts.map((serviceAcnt) => { + const actions: IAction[] = [ + { + label: T.translate('commons.edit'), + actionFn: () => showEditDialog(serviceAcnt), + }, + { + label: 'separator', + }, + { + label: T.translate('commons.delete'), + actionFn: () => showDeleteConfirmation(serviceAcnt), + }, + ]; + + return ( + + {serviceAcnt.name} + + + + + ); + })} + +
+ {renderDeleteConfirm()} + {renderEditDialog()} +
+ ); +}; + +const mapStateToProps = (state) => { + return { + serviceacnts: state.serviceacnts, + }; +}; + +const ServiceAccounts = connect(mapStateToProps)(ServiceAccountsView); +export default ServiceAccounts; diff --git a/app/cdap/components/shared/ConfirmDialog/index.tsx b/app/cdap/components/shared/ConfirmDialog/index.tsx new file mode 100644 index 00000000000..a6c721ff8a4 --- /dev/null +++ b/app/cdap/components/shared/ConfirmDialog/index.tsx @@ -0,0 +1,123 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * 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 React, { ReactElement, ReactNode, useState } from 'react'; +import isObject from 'lodash/isObject'; +import DialogTitle from '@material-ui/core/DialogTitle'; +import DialogContent from '@material-ui/core/DialogContent'; +import DialogActions from '@material-ui/core/DialogActions'; +import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown'; +import KeyboardArrowUpIcon from '@material-ui/icons/KeyboardArrowUp'; + +import IconButton from '@material-ui/core/IconButton'; +import { StyledBox, StyledDialog, StyledAlert } from './styles'; +import PrimaryTextButton from 'components/shared/Buttons/PrimaryTextButton'; + +interface IConfirmDialogProps { + headerTitle: string | ReactNode; + isOpen: boolean; + cancelButtonText: string | ReactNode; + cancelFn: (arg0: any) => void; + confirmButtonText: string | ReactNode; + confirmFn: (arg0: any) => void; + confirmationElem?: string | ReactNode | ReactElement; + confirmationText?: string | ReactNode; + severity?: 'success' | 'info' | 'warning' | 'error'; + statusMessage?: string | ReactNode; + extendedMessage?: { response: string }; + disableAction?: boolean; +} + +export const ConfirmDialog = ({ + headerTitle, + isOpen, + cancelButtonText, + cancelFn, + confirmButtonText, + confirmFn, + confirmationElem, + confirmationText, + severity, + statusMessage, + extendedMessage, + disableAction, +}: IConfirmDialogProps) => { + const [isExpanded, setIsExpanded] = useState(false); + + const showStatusMessage = () => { + if (statusMessage) { + return ( + + {statusMessage} + {getExtendedMessage()} + + ); + } + }; + + const handleToggleExtendedMessage = () => { + setIsExpanded(!isExpanded); + }; + + const getExtendedMessage = () => { + if (extendedMessage) { + return ( + <> + + {isExpanded ? : } + + {isExpanded && ( + + {isObject(extendedMessage) ? ( +
{extendedMessage.response}
+ ) : ( +
{extendedMessage}
+ )} +
+ )} + + ); + } + }; + + return ( + + {headerTitle} + {showStatusMessage()} + + {confirmationText} + {confirmationElem} + + + + {cancelButtonText} + + + {confirmButtonText} + + + + ); +}; diff --git a/app/cdap/components/shared/ConfirmDialog/styles.ts b/app/cdap/components/shared/ConfirmDialog/styles.ts new file mode 100644 index 00000000000..484e88e1b0f --- /dev/null +++ b/app/cdap/components/shared/ConfirmDialog/styles.ts @@ -0,0 +1,50 @@ +/* + * Copyright © 2023 Cask Data, Inc. + * + * 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 styled from 'styled-components'; +import Dialog from '@material-ui/core/Dialog'; +import Alert from '@material-ui/lab/Alert'; +import Box from '@material-ui/core/Box'; + +export const StyledDialog = styled(Dialog)` + & .MuiDialogActions-root { + margin-right: 16px; + & .MuiButton-label { + font-size: 13px; + } + } +`; + +export const StyledAlert = styled(Alert)` + font-size: 12px; + margin-bottom: 8px; +`; + +export const StyledBox = styled(Box)` + overflow-y: auto; + max-height: 14vh; + word-break: break-all; + + & pre { + word-break: break-word; + margin-bottom: 0; + white-space: pre-wrap; + color: inherit; + border: 0; + border-radius: 0; + padding: 0px 8px; + } +`; diff --git a/app/cdap/text/text-en.yaml b/app/cdap/text/text-en.yaml index 144414f841f..1215ca2aac8 100644 --- a/app/cdap/text/text-en.yaml +++ b/app/cdap/text/text-en.yaml @@ -5,6 +5,7 @@ commons: apply: Apply as: as back: Back + cancel: Cancel learnMore: Learn More gotIt: Ok, Got it. cookieBanner: This application uses cookies from Google to deliver and enhance the quality of its services and to analyze traffic. @@ -79,6 +80,7 @@ commons: singular: Workflow export: Export formatLabel: Format + hideDetails: Hide details hydrator: Cask Hydrator keyValPairs: keyLabel: Key @@ -95,11 +97,13 @@ commons: pipelines: Pipelines requiredFieldMissingMsg: Required field cannot be empty resource-center: Add entity + save: Save schemaLabel: Schema scope: Scope secondsShortLabel: secs secShortLabel: sec showAll: Show all pipelines + showDetails: Show details showFailed: Show failed only status: Status then: Then @@ -3166,6 +3170,19 @@ features: symbolName: Symbol name ServiceEnableUtility: serviceNotFound: Cannot find {artifactName} artifact + ServiceAccounts: + addServiceAccount: Add service account + delete: Delete + deleteButtonText: Delete + cancelButtonText: Cancel + deleteConfirmation: Clicking Delete will remove *_{serviceaccount}_* from the default namespace. Are you sure you want to remove the service account? + deleteTitle: Delete service account + edit: Edit + editInputLabel: Pipeline Design Service Account + inputHelperText: Provide details of the service account for authorization + serviceAccount : Service account + deleteErrorMessage: Unable to delete service account + saveErrorMessage: Failed to save the service account changes SourceControlManagement: configModal: auth: