Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3007 api key UI #3324

Merged
merged 74 commits into from
Oct 19, 2023
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
1e37a17
update db
mastercactapus Jul 21, 2023
ef5f5de
add gqlauth
mastercactapus Jul 21, 2023
313794c
add api method
mastercactapus Jul 21, 2023
9ac6291
add apikey package
mastercactapus Jul 21, 2023
fce2145
add auth for api keys
mastercactapus Jul 21, 2023
01a4bb5
simplify policy
mastercactapus Jul 28, 2023
a2dafb7
Merge branch 'master' into graphql-api-keys
mastercactapus Sep 7, 2023
48d0610
update schema
mastercactapus Sep 7, 2023
89eb6c0
fix circular dep with db-schema cmd
mastercactapus Sep 7, 2023
61e3305
update schema
mastercactapus Sep 7, 2023
fc338b7
add stub methods & list fields
mastercactapus Sep 7, 2023
9d84cd6
update tables
mastercactapus Sep 7, 2023
659f753
api poc working
mastercactapus Sep 7, 2023
0298031
tweak logging
mastercactapus Sep 7, 2023
e8070a2
single row per last-used-key
mastercactapus Sep 7, 2023
8478ce5
add cache for last used
mastercactapus Sep 8, 2023
aca7ea4
use json to ensure consistant hashes
mastercactapus Sep 8, 2023
6c4570e
cache policy data and throttle usage updates
mastercactapus Sep 11, 2023
a686da2
add missing comments
mastercactapus Sep 11, 2023
e302899
Merge branch 'master' into graphql-api-keys
mastercactapus Sep 12, 2023
1f4f320
Initial commit for 3007 item
Sep 19, 2023
026bb72
Added confirmation dialog and save edit show hide functions
Sep 19, 2023
cd7af67
Merge remote-tracking branch 'origin/master' into 3007-api-key-ui
Sep 19, 2023
9fe249d
Added formfield imports
Sep 20, 2023
2c6808e
Merge branch 'graphql-api-keys' of https://github.com/target/goalert …
Sep 20, 2023
88e4d60
Implemented delete and update in UI
Sep 21, 2023
1a8d845
Merge branch 'master' of https://github.com/target/goalert into 3007-…
Sep 21, 2023
4539071
Implemented delete and update functions. Added role options in creati…
Sep 25, 2023
c4e7e15
Added allowed fields error message
Sep 27, 2023
fd2f8ac
Merge branch 'master' of https://github.com/target/goalert into 3007-…
Sep 27, 2023
7b32b3a
Converted Drawer Date Info to Time, Integrated Error Message to Creat…
Sep 27, 2023
f688096
Merge branch '3007-api-key-ui' of https://github.com/1ddo/goalert; br…
Sep 28, 2023
7ccc711
Converted create dialog for create and edit, fixed role information
Sep 28, 2023
7293600
Fixed issue on the UI theme, Applied time format on Edit, FIxed issue…
Sep 29, 2023
25b3ac5
removed claims.go
Sep 29, 2023
33112b7
discard changes to appgo
Sep 29, 2023
11144aa
addressed eslint issues
Oct 2, 2023
0cedff2
Added necessary code comments
Oct 2, 2023
66cdcfd
Merge branch 'master' into pr/1ddo/3324
mastercactapus Oct 2, 2023
0536d4e
Merge branch 'master' into pr/1ddo/3324
mastercactapus Oct 2, 2023
c48b85f
Switched apollo plugin to urql, switch Dialog to FormDialog for delet…
Oct 5, 2023
eff8d84
Merge branch '3007-api-key-ui' of https://github.com/1ddo/goalert int…
Oct 5, 2023
16f2864
Merge branch '3007-api-key-ui' of github.com:1ddo/goalert into pr/1dd…
mastercactapus Oct 5, 2023
4c4fffc
Merge branch 'master' into pr/1ddo/3324
mastercactapus Oct 5, 2023
8ee916c
only show link if flag is enabled
mastercactapus Oct 5, 2023
8162605
fix formfield types
mastercactapus Oct 5, 2023
5d523c7
add non-close support to MaterialSelect
mastercactapus Oct 5, 2023
3fcc9bc
simplify api key form
mastercactapus Oct 5, 2023
653c5a3
use Time component
mastercactapus Oct 5, 2023
f6cc247
use optional labels
mastercactapus Oct 5, 2023
1cda061
rework exp. field code
mastercactapus Oct 5, 2023
c6a397e
Merge branch 'master' into pr/1ddo/3324
mastercactapus Oct 5, 2023
a7940f9
Split Action Dialogs to Edit and Create and integrated Token Dialog t…
Oct 6, 2023
63f1f75
cleanup edit dialog
mastercactapus Oct 6, 2023
3c6bb6d
Removed backdropclose flag and removed mui class declarations and mov…
Oct 9, 2023
deb5a0f
Merge branch '3007-api-key-ui' of github.com:1ddo/goalert into pr/1dd…
mastercactapus Oct 9, 2023
3928fdf
Merge branch 'master' into pr/1ddo/3324
mastercactapus Oct 9, 2023
1b6bc10
Merge branch 'master' into pr/1ddo/3324
mastercactapus Oct 9, 2023
6e85a3e
simplify edit
mastercactapus Oct 10, 2023
c529a8a
Merge branch 'master' into pr/1ddo/3324
mastercactapus Oct 11, 2023
502bb33
simplify create dialog based on calsub dialog
mastercactapus Oct 11, 2023
52576b8
remove unused dialogs
mastercactapus Oct 11, 2023
f61cc06
fix clickaway when selecting different keys
mastercactapus Oct 11, 2023
5841e4a
fix linting issues
mastercactapus Oct 11, 2023
8c30138
fix type issue
mastercactapus Oct 11, 2023
46f75e4
fix drawer flicker
mastercactapus Oct 16, 2023
0474630
fix read-only label
mastercactapus Oct 16, 2023
581f2ae
update ui for consistency across components
Forfold Oct 17, 2023
544f330
Merge branch 'master' into 3007-api-key-ui
Forfold Oct 18, 2023
32fcd06
call onsubmit for confirm dialog if no form prop
Forfold Oct 18, 2023
2adc7c3
updating padding on success
Forfold Oct 18, 2023
c66d9bc
add edit and delete options to list
Forfold Oct 18, 2023
58bc3d3
fix type
Forfold Oct 18, 2023
22893f5
update prop names, fix transition on create button
Forfold Oct 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions web/src/app/admin/AdminAPIKeys.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import React, { useState } from 'react'
import makeStyles from '@mui/styles/makeStyles'
import {
Button,
Grid,
Typography,
Card,
ButtonBase,
CardHeader,
} from '@mui/material'
import { Add } from '@mui/icons-material'
import AdminAPIKeysDrawer from './admin-api-keys/AdminAPIKeysDrawer'
import { GQLAPIKey, CreatedGQLAPIKey } from '../../schema'
import { Time } from '../util/Time'
import { gql, useQuery } from 'urql'
import FlatList, { FlatListListItem } from '../lists/FlatList'
import Spinner from '../loading/components/Spinner'
import { GenericError } from '../error-pages'
import { Theme } from '@mui/material/styles'
import AdminAPIKeysActionDialog from './admin-api-keys/AdminAPIKeysActionDialog'
import AdminAPIKeysTokenDialog from './admin-api-keys/AdminAPIKeysTokenDialog'
import { DateTime } from 'luxon'
// query for getting existing API Keys
const query = gql`
query gqlAPIKeysQuery {
gqlAPIKeys {
id
name
description
createdAt
createdBy {
id
name
}
updatedAt
updatedBy {
id
name
}
lastUsed {
time
ua
ip
}
expiresAt
allowedFields
role
}
}
`
const useStyles = makeStyles((theme: Theme) => ({
root: {
'& .MuiListItem-root': {
'border-bottom': '1px solid #333333',
},
},
buttons: {
'margin-bottom': '15px',
},
containerDefault: {
[theme.breakpoints.up('md')]: {
maxWidth: '100%',
transition: `max-width ${theme.transitions.duration.leavingScreen}ms ease`,
},
'& .MuiListItem-root': {
padding: '0px',
},
},
containerSelected: {
[theme.breakpoints.up('md')]: {
maxWidth: '70%',
transition: `max-width ${theme.transitions.duration.enteringScreen}ms ease`,
},
'& .MuiListItem-root': {
padding: '0px',
},
},
}))

export default function AdminAPIKeys(): JSX.Element {
const classes = useStyles()
const [selectedAPIKey, setSelectedAPIKey] = useState<GQLAPIKey | null>(null)
const [tokenDialogClose, onTokenDialogClose] = useState(false)
const [openActionAPIKeyDialog, setOpenActionAPIKeyDialog] = useState(false)
const [create, setCreate] = useState(false)
const emptyAPIKey = {
id: '',
name: '',
description: '',
createdAt: '',
createdBy: null,
updatedAt: '',
updatedBy: null,
lastUsed: null,
expiresAt: DateTime.utc().plus({ days: 7 }).toISO(),
allowedFields: [],
role: 'user',
}
const [apiKey, setAPIKey] = useState<GQLAPIKey>(emptyAPIKey as GQLAPIKey)
const [token, setToken] = useState<CreatedGQLAPIKey>({
id: '',
token: '',
})
// handles the openning of the create dialog form which is used for creating new API Key
const handleOpenCreateDialog = (): void => {
setSelectedAPIKey(null)
setCreate(true)
setAPIKey(emptyAPIKey as GQLAPIKey)
setOpenActionAPIKeyDialog(!openActionAPIKeyDialog)
}
// Get API Key triggers/actions
const [{ data, fetching, error }] = useQuery({ query })

if (error) {
return <GenericError error={error.message} />
}

if (fetching && !data) {
return <Spinner />
}

const items = data.gqlAPIKeys.map(
(key: GQLAPIKey): FlatListListItem => ({
selected: (key as GQLAPIKey).id === selectedAPIKey?.id,
highlight: (key as GQLAPIKey).id === selectedAPIKey?.id,
subText: (
<ButtonBase
onClick={() => {
setSelectedAPIKey(key)
}}
style={{ width: '100%', textAlign: 'left', padding: '5px 15px' }}
>
<Grid container>
<Grid item xs justifyContent='flex-start'>
<Typography gutterBottom variant='subtitle1' component='div'>
{key.name}
</Typography>
<Typography gutterBottom variant='subtitle2' component='div'>
<Time prefix='Expires At: ' time={key.expiresAt} />
</Typography>
<Typography gutterBottom variant='subtitle2' component='div'>
{key.allowedFields.length + ' allowed fields (read-only)'}
</Typography>
</Grid>
<Grid item>
<Typography gutterBottom variant='subtitle2' component='div'>
<Time prefix='Last Used: ' time={key.expiresAt} />
</Typography>
</Grid>
</Grid>
</ButtonBase>
),
}),
)

return (
<React.Fragment>
{selectedAPIKey ? (
<AdminAPIKeysDrawer
onClose={() => {
if (!openActionAPIKeyDialog) {
setSelectedAPIKey(null)
}
}}
apiKey={selectedAPIKey}
setCreate={setCreate}
setOpenActionAPIKeyDialog={setOpenActionAPIKeyDialog}
setAPIKey={setAPIKey}
/>
) : null}
{openActionAPIKeyDialog ? (
<AdminAPIKeysActionDialog
onClose={() => {
setOpenActionAPIKeyDialog(false)
}}
onTokenDialogClose={onTokenDialogClose}
setToken={setToken}
create={create}
apiKey={apiKey}
setAPIKey={setAPIKey}
setSelectedAPIKey={setSelectedAPIKey}
/>
) : null}
{tokenDialogClose ? (
<AdminAPIKeysTokenDialog
input={token}
onTokenDialogClose={onTokenDialogClose}
tokenDialogClose={tokenDialogClose}
/>
) : null}
<Card
style={{ width: '100%', padding: '10px' }}
className={
selectedAPIKey ? classes.containerSelected : classes.containerDefault
}
>
<CardHeader
title='API Key List'
component='h2'
sx={{ paddingBottom: 0, margin: 0 }}
action={
<Button
data-cy='new'
variant='contained'
className={classes.buttons}
onClick={handleOpenCreateDialog}
startIcon={<Add />}
>
Create API Key
</Button>
}
/>
<FlatList emptyMessage='No Data Available' items={items} />
</Card>
</React.Fragment>
)
}
103 changes: 103 additions & 0 deletions web/src/app/admin/admin-api-keys/AdminAPIKeyExpirationField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import React, { useEffect, useState } from 'react'
import InputLabel from '@mui/material/InputLabel'
import MenuItem from '@mui/material/MenuItem'
import Select from '@mui/material/Select'
import Grid from '@mui/material/Grid'
import { Typography } from '@mui/material'
import makeStyles from '@mui/styles/makeStyles'
import { ISODateTimePicker } from '../../util/ISOPickers'
import { DateTime } from 'luxon'
import { Time } from '../../util/Time'
import { selectedDaysUntilTimestamp } from './util'

const useStyles = makeStyles(() => ({
expiresCon: {
'padding-top': '15px',
},
}))
// props object for this compoenent
interface FieldProps {
onChange: (val: string) => void
value: string
disabled: boolean
label: string
}

const presets = [7, 15, 30, 60, 90]

export default function AdminAPIKeyExpirationField(
props: FieldProps,
): JSX.Element {
const classes = useStyles()

const [selected, setSelected] = useState<number>(
selectedDaysUntilTimestamp(props.value, presets),
)

useEffect(() => {
if (!selected) return // if custom is selected, do nothing

// otherwise keep the selected preset in sync with expiration time
setSelected(selectedDaysUntilTimestamp(props.value, presets))
}, [props.value])

if (props.disabled) {
return (
<ISODateTimePicker
disabled
value={props.value}
label={props.label}
onChange={props.onChange}
/>
)
}

return (
<Grid container spacing={2}>
<Grid item justifyContent='flex-start' width='25%'>
<InputLabel id='expires-at-select-label'>{props.label}</InputLabel>
<Select
label={props.label}
fullWidth
onChange={(e) => {
const value = +e.target.value
setSelected(value)

if (value === 0) return
props.onChange(DateTime.utc().plus({ days: value }).toISO())
}}
value={selected}
>
{presets.map((days) => (
<MenuItem key={days} value={days}>
{days} days
</MenuItem>
))}
<MenuItem value={0}>Custom...</MenuItem>
</Select>
</Grid>

{selected ? ( // if a preset is selected, show the expiration time
<Grid item justifyContent='flex-start'>
<Typography
gutterBottom
variant='subtitle2'
component='div'
className={classes.expiresCon}
>
The token will expire <Time time={props.value} />
</Typography>
</Grid>
) : (
// if custom is selected, show date picker
<Grid item justifyContent='flex-end' width='75%'>
<ISODateTimePicker
value={props.value}
onChange={props.onChange}
fullWidth
/>
</Grid>
)}
</Grid>
)
}
Loading