Skip to content

Commit

Permalink
Add new tab for ServiceAccounts in NS admin UI
Browse files Browse the repository at this point in the history
  • Loading branch information
radhikav1 committed Sep 13, 2023
1 parent 1f73de0 commit 5abf3ec
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 0 deletions.
7 changes: 7 additions & 0 deletions app/cdap/components/NamespaceAdmin/AdminTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -91,6 +92,11 @@ export const AdminTabs = () => {
value={`${baseNSPath}/connections`}
/>
<LinkTab label="Drivers" to={`${baseNSPath}/drivers`} value={`${baseNSPath}/drivers`} />
<LinkTab
label="Service Accounts"
to={`${baseNSPath}/serviceaccounts`}
value={`${baseNSPath}/serviceaccounts`}
/>
{sourceControlManagementEnabled && (
<LinkTab
label="Source Control Management"
Expand All @@ -108,6 +114,7 @@ export const AdminTabs = () => {
<Route exact path={`${basepath}/connections`} component={Connections} />
<Route exact path={`${basepath}/drivers`} component={Drivers} />
<Route exact path={`${basepath}/scm`} component={SourceControlManagement} />
<Route exact path={`${basepath}/serviceaccounts`} component={ServiceAccounts} />
</Switch>
</div>
</>
Expand Down
272 changes: 272 additions & 0 deletions app/cdap/components/NamespaceAdmin/ServiceAccounts/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
/*
* 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';
import { boolean } from '@storybook/addon-knobs';

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 [saveStatusMsg, setSaveStatusMsg] = useState(null);
const [saveStatusDetails, setSaveStatusDetails] = useState(null);
const [saveStatus, setSaveStatus] = useState(null); // 'success' | 'info' | 'warning' | 'error';
const [isValidated, setIsValidated] = useState(false);

const handleClose = () => {
setOpen(false);
setSaveStatusMsg(null);
setSaveStatusDetails(null);
};

const handleSave = () => {
// alert('saving ' + selectedServiceAcnt);
// setSaveStatus('error');
// setSaveStatusMsg(T.translate(`${PREFIX}.saveErrorMessage`));
// setSaveStatusDetails({
// response:
// 'Sample Error message for testing : Error occurred as save is not intergrated with API',
// });
};

const addHandler = () => {
setOpen(true);
setSelectedServiceAcnt(null);
};

const handleValidate = () => {
const isValidateSuccess = false;
// To do: Call validate API
if (isValidateSuccess) {
setIsValidated(true);
setSaveStatus('success');
setSaveStatusMsg(T.translate(`${PREFIX}.validationSuccessMessage`));
} else {
setIsValidated(false);
setSaveStatus('error');
setSaveStatusMsg(T.translate(`${PREFIX}.validationErrorMessage`));
setSaveStatusDetails({
response: 'Sample Error message for testing : Validation failed',
});
}
};

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 (
<ConfirmDialog
headerTitle={T.translate(`${PREFIX}.deleteTitle`)}
confirmationElem={T.translate(`${PREFIX}.deleteConfirmation`, {
serviceaccount: selectedServiceAcnt,
})}
cancelButtonText={T.translate(`${PREFIX}.cancelButtonText`)}
confirmButtonText={T.translate(`${PREFIX}.deleteButtonText`)}
confirmFn={deleteHanlder}
cancelFn={closeDeleteConfirmation}
isOpen={isDeleteModalOpen}
severity="error"
statusMessage={deleteErrorMsg}
extendedMessage={extendedErrorMsg}
></ConfirmDialog>
);
};

const getEditDialogContent = () => {
return (
<StyledTextField
label={T.translate(`${PREFIX}.editInputLabel`)}
defaultValue={selectedServiceAcnt}
helperText={T.translate(`${PREFIX}.inputHelperText`)}
variant="outlined"
margin="dense"
fullWidth
color="primary"
onChange={(ev) => {
setSelectedServiceAcnt(ev.target.value);
}}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={handleShowHelp} size="small">
<HelpIcon />
</IconButton>
</InputAdornment>
),
}}
/>
);
};

const renderEditDialog = () => {
return (
<ConfirmDialog
headerTitle={T.translate(`${PREFIX}.serviceAccount`)}
confirmationElem={getEditDialogContent()}
cancelButtonText={T.translate('commons.cancel')}
confirmButtonText={
isValidated ? T.translate('commons.save') : T.translate('commons.validate')
}
confirmFn={isValidated ? handleSave : handleValidate}
cancelFn={handleClose}
disableAction={!selectedServiceAcnt ? true : false}
isOpen={open}
severity={saveStatus}
statusMessage={saveStatusMsg}
extendedMessage={saveStatusDetails}
></ConfirmDialog>
);
};

return (
<div>
{!serviceacnts && (
<SubTitleBox>
<Button variant="contained" color="primary" onClick={addHandler}>
{T.translate(`${PREFIX}.addServiceAccount`)}
</Button>
</SubTitleBox>
)}
<Table columnTemplate="1fr 100px">
<TableHeader>
<TableRow>
<TableCell>{T.translate(`${PREFIX}.serviceAccount`)}</TableCell>
<TableCell />
</TableRow>
</TableHeader>

<TableBody>
{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 (
<TableRow key={`${serviceAcnt.name}`}>
<TableCell>{serviceAcnt.name}</TableCell>
<TableCell>
<ActionsPopover actions={actions} />
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
{renderDeleteConfirm()}
{renderEditDialog()}
</div>
);
};

const mapStateToProps = (state) => {
return {
serviceacnts: state.serviceacnts,
};
};

const ServiceAccounts = connect(mapStateToProps)(ServiceAccountsView);
export default ServiceAccounts;
18 changes: 18 additions & 0 deletions app/cdap/text/text-en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -96,6 +97,7 @@ commons:
pipelines: Pipelines
requiredFieldMissingMsg: Required field cannot be empty
resource-center: Add entity
save: Save
schemaLabel: Schema
scope: Scope
secondsShortLabel: secs
Expand All @@ -107,6 +109,7 @@ commons:
then: Then
tracker: Cask Tracker
typeLabel: Type
validate: Validate
when: When
wrangler: Cask Wrangler
yesLabel: Yes
Expand Down Expand Up @@ -3168,6 +3171,21 @@ 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
validationSuccessMessage: Service account validation successful
validationErrorMessage: Service account validation failed
SourceControlManagement:
configModal:
auth:
Expand Down

0 comments on commit 5abf3ec

Please sign in to comment.