Skip to content

Commit

Permalink
feat: allow user to manage existing cloud accounts from dashboard
Browse files Browse the repository at this point in the history
  • Loading branch information
greghub committed Sep 21, 2023
1 parent af51eb2 commit bb42342
Show file tree
Hide file tree
Showing 11 changed files with 669 additions and 267 deletions.
184 changes: 184 additions & 0 deletions dashboard/components/account-details/AwsAccountDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import { ChangeEvent, ReactNode, useRef, useState } from 'react';
import Folder2Icon from '../icons/Folder2Icon';
import SelectInput from '../onboarding-wizard/SelectInput';
import LabelledInput from '../onboarding-wizard/LabelledInput';
import InputFileSelect from '../onboarding-wizard/InputFileSelect';
import KeyIcon from '../icons/KeyIcon';
import VariableIcon from '../icons/VariableIcon';
import DocumentTextIcon from '../icons/DocumentTextIcon';
import ShieldSecurityIcon from '../icons/ShieldSecurityIcon';
import { CloudAccount } from '../cloud-account/hooks/useCloudAccounts/useCloudAccount';

interface SelectOptions {
icon: ReactNode;
label: string;
value: string;
}

interface AwsAccountDetailsProps {
cloudAccountData: CloudAccount;
setCloudAccountData: (formData: CloudAccount) => void;
}

const options: SelectOptions[] = [
{
icon: <DocumentTextIcon />,
label: 'Credentials File',
value: 'credentials-file'
},
{
icon: <KeyIcon />,
label: 'Credentials keys',
value: 'credentials-keys'
},
{
icon: <VariableIcon />,
label: 'Environment Variables',
value: 'environment-variables'
},
{
icon: <ShieldSecurityIcon />,
label: 'IAM Instance Role',
value: 'iam-instance-role'
}
];

function AwsAccountDetails({
cloudAccountData,
setCloudAccountData
}: AwsAccountDetailsProps) {
const [credentialType, setCredentialType] = useState<string>(
options.find(option => option.value === cloudAccountData.credentials.source)
?.value ?? options[0].value
);

const fileInputRef = useRef<HTMLInputElement | null>(null);
const handleButtonClick = () => {
if (fileInputRef.current) {
fileInputRef.current.click();
}
};

function handleNameChange(event: ChangeEvent<HTMLInputElement>) {
setCloudAccountData({
...cloudAccountData,
name: event?.target.value
});
}

function handleSelectChange(newValue: string) {
setCredentialType(newValue);

setCloudAccountData({
...cloudAccountData,
credentials: {
...cloudAccountData.credentials,
source: newValue
}
});
}

function handleProfileChange(event: ChangeEvent<HTMLInputElement>) {
setCloudAccountData({
...cloudAccountData,
credentials: {
...cloudAccountData.credentials,
profile: event?.target.value
}
});
}

const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];

if (!file) return;

setCloudAccountData({
...cloudAccountData,
credentials: {
...cloudAccountData.credentials,
path: file.name
}
});
};

return (
<div className="flex flex-col space-y-8 py-10">
<LabelledInput
type="text"
id="account-name"
label="Account name"
placeholder="my-aws-account"
value={cloudAccountData.name}
onChange={handleNameChange}
/>

<div className="flex flex-col space-y-8 rounded-md bg-komiser-100 p-5">
<div>
<SelectInput
icon="Change"
label={'Source'}
displayValues={options}
value={credentialType}
handleChange={handleSelectChange}
values={options.map(option => option.value)}
/>
{[options[2].value, options[3].value].includes(credentialType) && (
<div className="mt-2 text-sm text-black-400">
{credentialType === options[3].value
? 'Komiser will fetch the credentials from AWS'
: 'Komiser will load credentials from AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.'}
</div>
)}
</div>

{credentialType === options[0].value && (
<div>
<InputFileSelect
type="text"
label="File path"
id="file-path-input"
icon={<Folder2Icon />}
subLabel="Enter the path or browse the file"
placeholder="C:\Documents\Komiser\credentials"
fileInputRef={fileInputRef}
iconClick={handleButtonClick}
handleFileChange={handleFileChange}
value={cloudAccountData.credentials.path}
/>
<LabelledInput
type="text"
id="profile"
label="Profile"
placeholder="default"
subLabel="Name of the section in the credentials file"
value={cloudAccountData.credentials.profile}
onChange={handleProfileChange}
/>
</div>
)}

{credentialType === options[1].value && (
<div>
<LabelledInput
type="text"
id="access-key-id"
label="Access key ID"
placeholder="AKIABCDEFGHIJKLMN12"
subLabel="Unique identifier used to access AWS services"
/>
<LabelledInput
type="text"
id="secret-access-key"
label="Secret access key"
placeholder="AbCdEfGhIjKlMnOpQrStUvWxYz0123456789AbCd"
subLabel="The secret access key is generated by AWS when an access key is created"
/>
</div>
)}
</div>
</div>
);
}

export default AwsAccountDetails;
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useState } from 'react';
import AlertCircleIcon from '../../icons/AlertCircleIcon';
import Button from '../../button/Button';
import { CloudAccount } from '../hooks/useCloudAccounts/useCloudAccount';
import settingsService from '../../../services/settingsService';
import { ToastProps } from '../../toast/hooks/useToast';

interface CloudAccountDeleteContentsProps {
cloudAccount: CloudAccount;
onCancel: () => void;
setToast: (toast: ToastProps) => void;
}

function CloudAccountDeleteContents({
cloudAccount,
onCancel,
setToast
}: CloudAccountDeleteContentsProps) {
const [loading, setLoading] = useState(false);

const deleteCloudAccount = () => {
if (!cloudAccount.id) return false;

setLoading(true);

settingsService.deleteCloudAccount(cloudAccount.id).then(res => {
setLoading(false);
if (res === Error) {
setToast({
hasError: true,
title: 'Cloud account was not deleted',
message:
'There was an error deleting this cloud account. Please try again.'
});
} else {
setToast({
hasError: false,
title: 'Cloud account deleted',
message: `The cloud account was successfully deleted!`
});
}
});

return true;
};

return (
<>
<div className="flex flex-col items-center gap-y-6">
<AlertCircleIcon className="h-16 w-16" />
<h1 className="text-center text-xl font-semibold text-black-800">
Are you sure you want to
<br />
remove this cloud account?
</h1>
<h3 className="text-center">
All related data (like custom views and tags) will be deleted
<br />
and the {cloudAccount.name} account will be disconnected from Komiser.
</h3>
</div>
<div className="flex flex-row place-content-end gap-x-8">
<Button style="text" onClick={() => onCancel()}>
Cancel
</Button>
<Button
style="delete"
loading={loading}
onClick={() => deleteCloudAccount()}
>
Delete account
</Button>
</div>
</>
);
}

export default CloudAccountDeleteContents;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import classNames from 'classnames';
import { CloudAccount } from '../hooks/useCloudAccounts/useCloudAccount';

function CloudAccountStatus({ status }: { status: CloudAccount['status'] }) {
if (!status) return null;

return (
<div
className={classNames(
'relative inline-block rounded-3xl px-2 py-1 text-sm',
{
'bg-green-200 text-green-600': status === 'CONNECTED',
'bg-red-200 text-red-600':
status === 'PERMISSION_ISSUE' || status === 'INTEGRATION_ISSUE'
}
)}
>
<span>{status.charAt(0) + status.slice(1).toLocaleLowerCase()}</span>
</div>
);
}

export default CloudAccountStatus;
Loading

0 comments on commit bb42342

Please sign in to comment.