-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add project readonly configuration view for SSM
This adds the first of hopefully many views for the project configuration tab. This checks to see if the project uses the SSM configuration type and if so, will fetch SSM params for this project and display them in a table similar to Operations Log.
- Loading branch information
Showing
8 changed files
with
325 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
import React, { useContext, useEffect } from 'react' | ||
import { Context } from '../../../state' | ||
import PropTypes from 'prop-types' | ||
import { SSMConfiguration } from './SSMConfiguration' | ||
import { WishedFutureState } from '../../../components' | ||
|
||
function Configuration({ urlPath, project }) { | ||
const [globalState, dispatch] = useContext(Context) | ||
|
||
useEffect(() => { | ||
dispatch({ | ||
type: 'SET_CURRENT_PAGE', | ||
payload: { | ||
title: 'common.configuration', | ||
url: new URL(`${urlPath}/configuration`, globalState.baseURL) | ||
} | ||
}) | ||
}, []) | ||
|
||
switch (project.configuration_type) { | ||
case 'ssm': | ||
return <SSMConfiguration project={project} /> | ||
default: | ||
return ( | ||
<div className="pt-20 flex items-center justify-center"> | ||
<WishedFutureState> | ||
This tab will provide an abstracted interface for editing the | ||
configuration in Consul, Vault, AWS SSM Parameter Store, and K8s | ||
Configuration Maps. | ||
</WishedFutureState> | ||
</div> | ||
) | ||
} | ||
} | ||
|
||
Configuration.propTypes = { | ||
urlPath: PropTypes.string.isRequired, | ||
project: PropTypes.object.isRequired | ||
} | ||
export { Configuration } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import React from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import PropTypes from 'prop-types' | ||
import { DescriptionList } from '../../../components/DescriptionList/DescriptionList' | ||
import { Definition } from '../../../components/DescriptionList/Definition' | ||
|
||
function DisplaySSMParam({ param }) { | ||
const { t } = useTranslation() | ||
|
||
return ( | ||
<> | ||
<DescriptionList> | ||
<Definition term={t('common.name')}>{param.name}</Definition> | ||
<Definition term={t('common.type')}>{param.type}</Definition> | ||
</DescriptionList> | ||
<h1 className="text-xl font-medium text-gray-900 mt-6 mb-3">Values</h1> | ||
<DescriptionList> | ||
{param.values.map(({ environment, value }, i) => { | ||
return ( | ||
<Definition key={i} className="break-words" term={environment}> | ||
{value} | ||
</Definition> | ||
) | ||
})} | ||
</DescriptionList> | ||
</> | ||
) | ||
} | ||
|
||
DisplaySSMParam.propTypes = { | ||
param: PropTypes.object.isRequired | ||
} | ||
|
||
export { DisplaySSMParam } |
47 changes: 47 additions & 0 deletions
47
src/js/views/Project/Configuration/DisplaySecureStringParam.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import React, { useState } from 'react' | ||
import { useTranslation } from 'react-i18next' | ||
import PropTypes from 'prop-types' | ||
import { DescriptionList } from '../../../components/DescriptionList/DescriptionList' | ||
import { Definition } from '../../../components/DescriptionList/Definition' | ||
import { Toggle } from '../../../components/Form/Toggle' | ||
|
||
function DisplaySecureStringParam({ param }) { | ||
const { t } = useTranslation() | ||
const [isShown, setIsShown] = useState(false) | ||
|
||
return ( | ||
<> | ||
<DescriptionList> | ||
<Definition term={t('common.name')}>{param.name}</Definition> | ||
<Definition term={t('common.type')}>{param.type}</Definition> | ||
</DescriptionList> | ||
<div className="flex items-center justify-between mt-6 mb-3"> | ||
<h1 className="text-xl font-medium text-gray-900">Values</h1> | ||
<div className="flex items-center gap-1"> | ||
<p>Show decrypted value</p> | ||
<Toggle | ||
onChange={(name, value) => setIsShown(value)} | ||
name="is-hidden" | ||
value={isShown} | ||
/> | ||
</div> | ||
</div> | ||
|
||
<DescriptionList> | ||
{param.values.map(({ environment, value }, i) => { | ||
return ( | ||
<Definition key={i} className="break-words" term={environment}> | ||
{isShown ? value : '********'} | ||
</Definition> | ||
) | ||
})} | ||
</DescriptionList> | ||
</> | ||
) | ||
} | ||
|
||
DisplaySecureStringParam.propTypes = { | ||
param: PropTypes.object.isRequired | ||
} | ||
|
||
export { DisplaySecureStringParam } |
180 changes: 180 additions & 0 deletions
180
src/js/views/Project/Configuration/SSMConfiguration.jsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import PropTypes from 'prop-types' | ||
import React, { useContext, useEffect, useRef, useState } from 'react' | ||
|
||
import { Context } from '../../../state' | ||
import { Alert, Icon, Loading, Table } from '../../../components' | ||
import { useTranslation } from 'react-i18next' | ||
import { SlideOver } from '../../../components/SlideOver/SlideOver' | ||
import { ViewSSMParam } from './ViewSSMParam' | ||
import { useSearchParams } from 'react-router-dom' | ||
import { httpGet } from '../../../utils' | ||
|
||
function cloneParams(searchParams) { | ||
const newParams = new URLSearchParams() | ||
for (const [key, value] of searchParams) { | ||
newParams.set(key, value) | ||
} | ||
return newParams | ||
} | ||
|
||
function SSMConfiguration({ project }) { | ||
const [globalState, dispatch] = useContext(Context) | ||
const { t } = useTranslation() | ||
const [searchParams, setSearchParams] = useSearchParams() | ||
const [onFetch, setOnFetch] = useState(true) | ||
const [fetching, setFetching] = useState(false) | ||
const [errorMessage, setErrorMessage] = useState() | ||
const [rows, setRows] = useState([]) | ||
const [slideOverOpen, setSlideOverOpen] = useState(false) | ||
const [selectedIndex, setSelectedIndex] = useState() | ||
const [slideOverFocusTrigger, setSlideOverFocusTrigger] = useState({}) | ||
const [showArrows, setShowArrows] = useState(false) | ||
const arrowLeftRef = useRef(null) | ||
const arrowRightRef = useRef(null) | ||
|
||
if (searchParams.get('v') && !slideOverOpen && rows.length > 0) { | ||
setSlideOverOpen(true) | ||
setShowArrows(true) | ||
setSelectedIndex( | ||
rows.findIndex((row) => row.name === searchParams.get('v')) | ||
) | ||
} else if (!searchParams.get('v') && slideOverOpen) { | ||
setSlideOverOpen(false) | ||
setShowArrows(false) | ||
} | ||
|
||
function move(index) { | ||
setSelectedIndex(index) | ||
const newParams = cloneParams(searchParams) | ||
newParams.set('v', rows[index].name) | ||
setSearchParams(newParams) | ||
} | ||
|
||
useEffect(() => { | ||
if (fetching || !onFetch) return | ||
setFetching(true) | ||
|
||
httpGet( | ||
globalState.fetch, | ||
new URL(`/projects/${project.id}/configuration/ssm`, globalState.baseURL), | ||
({ data }) => { | ||
setRows(data.sort((a, b) => (a.name > b.name ? 1 : -1))) | ||
setFetching(false) | ||
}, | ||
(message) => { | ||
setErrorMessage(message) | ||
setFetching(false) | ||
} | ||
) | ||
setOnFetch(false) | ||
}, [onFetch]) | ||
|
||
if (fetching) return <Loading></Loading> | ||
if (errorMessage) return <Alert level="error">{errorMessage}</Alert> | ||
|
||
return ( | ||
<div className="m-0"> | ||
<Table | ||
columns={[ | ||
{ | ||
title: t('common.name'), | ||
name: 'name', | ||
type: 'text', | ||
tableOptions: { | ||
headerClassName: 'w-10/12' | ||
} | ||
}, | ||
{ | ||
title: t('common.type'), | ||
name: 'type', | ||
type: 'text', | ||
tableOptions: { | ||
className: 'truncate' | ||
} | ||
} | ||
]} | ||
data={rows} | ||
onRowClick={({ index }) => { | ||
const newParams = cloneParams(searchParams) | ||
newParams.set('v', rows[index].name) | ||
setSearchParams(newParams) | ||
setSlideOverOpen(true) | ||
setSelectedIndex(index) | ||
setShowArrows(true) | ||
}} | ||
checkIsHighlighted={(row) => row.name === searchParams.get('v')} | ||
/> | ||
<SlideOver | ||
open={slideOverOpen} | ||
title={ | ||
<div className="flex items-center"> | ||
{t('aws.ssmParameter')} | ||
{selectedIndex !== undefined && showArrows && ( | ||
<> | ||
<button | ||
ref={arrowLeftRef} | ||
className="ml-4 mr-2 h-min outline-offset-4" | ||
onClick={() => { | ||
if (selectedIndex > 0) move(selectedIndex - 1) | ||
}} | ||
tabIndex={selectedIndex === 0 ? -1 : 0}> | ||
<Icon | ||
icon="fas arrow-left" | ||
className={ | ||
'h-4 select-none block' + | ||
(selectedIndex === 0 ? ' text-gray-200' : '') | ||
} | ||
/> | ||
</button> | ||
|
||
<button | ||
ref={arrowRightRef} | ||
className="outline-offset-4" | ||
onClick={() => { | ||
if (selectedIndex < rows.length - 1) move(selectedIndex + 1) | ||
}} | ||
tabIndex={selectedIndex === rows.length - 1 ? -1 : 0}> | ||
<Icon | ||
icon="fas arrow-right" | ||
className={ | ||
'h-4 select-none block' + | ||
(selectedIndex === rows.length - 1 | ||
? ' text-gray-200' | ||
: '') | ||
} | ||
/> | ||
</button> | ||
</> | ||
)} | ||
</div> | ||
} | ||
focusTrigger={slideOverFocusTrigger} | ||
onClose={() => { | ||
const newParams = cloneParams(searchParams) | ||
newParams.delete('v') | ||
setSearchParams(newParams) | ||
setSlideOverOpen(false) | ||
}} | ||
onKeyDown={(e) => { | ||
if (!showArrows) return | ||
if (selectedIndex > 0 && e.key === 'ArrowLeft') { | ||
arrowLeftRef.current?.focus() | ||
move(selectedIndex - 1) | ||
} else if ( | ||
selectedIndex < rows.length - 1 && | ||
e.key === 'ArrowRight' | ||
) { | ||
arrowRightRef.current?.focus() | ||
move(selectedIndex + 1) | ||
} | ||
}}> | ||
<ViewSSMParam param={rows[selectedIndex]} /> | ||
</SlideOver> | ||
</div> | ||
) | ||
} | ||
|
||
SSMConfiguration.propTypes = { | ||
project: PropTypes.object.isRequired | ||
} | ||
export { SSMConfiguration } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import React from 'react' | ||
import PropTypes from 'prop-types' | ||
import { DisplaySSMParam } from './DisplaySSMParam' | ||
import { DisplaySecureStringParam } from './DisplaySecureStringParam' | ||
|
||
function ViewSSMParam({ param }) { | ||
if (param.type === 'SecureString') { | ||
return <DisplaySecureStringParam param={param} /> | ||
} else { | ||
return <DisplaySSMParam param={param} /> | ||
} | ||
} | ||
|
||
ViewSSMParam.propTypes = { | ||
param: PropTypes.object | ||
} | ||
|
||
export { ViewSSMParam } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters