Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 1 addition & 17 deletions ui/src/Layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import Button from '@mui/material/Button'
import { Badge, Box, Container, Stack, Theme, Typography, useMediaQuery } from '@mui/material'
import { useAuth } from './AuthContext'
import { Badge, Box, Container, Stack, Theme, useMediaQuery } from '@mui/material'
import { Outlet, useLocation, useNavigate } from 'react-router-dom'
import { AppsOutlined, EditOutlined, KeyboardArrowLeft, NotificationsNone, SettingsOutlined } from '@mui/icons-material'
import AuthenticationForm from './pages/SettingsPage/components/AuthenticationForm'
import { useNotificationsCenterOpenContext, useNotificationsCenterOpenDispatch, useNotificationsContext } from './components/NotificationsCenter/NotificationsContext'
import NotificationsCenter from './components/NotificationsCenter'

Expand All @@ -12,25 +10,11 @@ export function Layout() {
const notificationsCenterOpen = useNotificationsCenterOpenContext()
const dispatchNotificationsCenterOpen = useNotificationsCenterOpenDispatch()

const auth = useAuth()
const navigate = useNavigate()
const location = useLocation()

const isSmallScreen = useMediaQuery((theme: Theme) => theme.breakpoints.down('lg'))

if (auth === null) {
return (
<Box component='main' sx={ { width: '100%', flexGrow: 1, py: 3 } }>
<Container maxWidth={ isSmallScreen ? 'md' : 'lg' }>
<Typography variant='h2'>Authorization required</Typography>
<Typography variant='h5' gutterBottom>This extension requires login.</Typography>
<Typography variant='body1' sx={ { my: 3 } }>Learn more about authentication methods in the Authentication section of our documentation site.</Typography>
<AuthenticationForm />
</Container>
</Box>
)
}

return (
<>
<Box component='main' sx={ { display: 'flex', alignItems: 'start', width: '100%', flexGrow: 1, py: 3 } }>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,48 +1,57 @@
import { Card, CardActionArea, CardContent, Skeleton, Stack, Typography } from '@mui/material'
import { Card, CardActionArea, CardContent, Skeleton, Stack, Tooltip, Typography } from '@mui/material'
import { ArtifactListItemReducedDTO } from '../../../../../autogenerated/client/backend'
import { useNavigate } from 'react-router-dom'
import moment from 'moment'
import { AdjustOutlined, LayersOutlined, SvgIconComponent, SyncOutlined } from '@mui/icons-material'
import GitBranch from '../../../../components/GitBranch/GitBranch'
import { useState } from 'react'
import { ReactElement, useState } from 'react'
import HintIcon, { LoadingHintIcon } from './HintIcon'
import InstallDialog from '../../../../components/Helm/InstallDialog'

export default function BranchCard({ branch, version, artifact }:
{ branch: { name: string, pattern: string }, version: string, artifact: ArtifactListItemReducedDTO }) {
export default function BranchCard({ branch, version, artifact, disabled = false }:
{ branch: { name: string, pattern: string }, version: string, artifact: ArtifactListItemReducedDTO, disabled?: boolean }) {
const [ installDialogOpen, setInstallDialogOpen ] = useState<boolean>(false)

const navigate = useNavigate()

function onInstall() {
setInstallDialogOpen(false)
navigate('/workloads')
if (!disabled) {
setInstallDialogOpen(false)
navigate('/workloads')
}
}

const AuthnWrapper = ({ children } : { children: ReactElement }) => disabled ? <Tooltip title='Log in to install this application'>{ children }</Tooltip> : <>{ children }</>

return (
<>
<Card
variant='outlined'
sx={ {
maxWidth: 'lg',
'&:hover': { borderColor: 'primary.main', boxShadow: 2 }
} }>
<CardActionArea onClick={ () => setInstallDialogOpen(true) }>
<CardContent>
<Stack direction='row' justifyContent='space-between'>
<Stack direction='column' alignItems='start' spacing={ 1 }>
<Typography variant='h4'>Version { version }</Typography>
<Stack direction='row' justifyContent='start' alignItems='center' spacing={ 2 }>
<HintIcon tooltip='Branch' icon={ GitBranch as SvgIconComponent } text={ branch.name } />
<HintIcon tooltip='Chart version' icon={ LayersOutlined } text={ `${ artifact.version }-${ artifact.revision }` } />
<HintIcon tooltip='Last updated' icon={ SyncOutlined } text={ moment(artifact.registered_at).fromNow() } />
<HintIcon tooltip='Short digest' icon={ AdjustOutlined } text={ artifact?.digest.value.substring(0, 7) } />
<AuthnWrapper>
<Card
variant='outlined'
sx={ {
maxWidth: 'lg',
'&:hover': disabled ? { opacity: 0.75 } : { borderColor: 'primary.main', boxShadow: 2 }
} }>
<CardActionArea
onClick={ disabled ? undefined : () => setInstallDialogOpen(true) }
disableRipple={ disabled }
sx={ { cursor: disabled ? 'default' : 'pointer' } }>
<CardContent>
<Stack direction='row' justifyContent='space-between'>
<Stack direction='column' alignItems='start' spacing={ 1 }>
<Typography variant='h4'>Version { version }</Typography>
<Stack direction='row' justifyContent='start' alignItems='center' spacing={ 2 }>
<HintIcon tooltip='Branch' icon={ GitBranch as SvgIconComponent } text={ branch.name } />
<HintIcon tooltip='Chart version' icon={ LayersOutlined } text={ `${ artifact.version }-${ artifact.revision }` } />
<HintIcon tooltip='Last updated' icon={ SyncOutlined } text={ moment(artifact.registered_at).fromNow() } />
<HintIcon tooltip='Short digest' icon={ AdjustOutlined } text={ artifact?.digest.value.substring(0, 7) } />
</Stack>
</Stack>
</Stack>
</Stack>
</CardContent>
</CardActionArea>
</Card>
</CardContent>
</CardActionArea>
</Card>
</AuthnWrapper>
<InstallDialog
branch={ branch.name }
artifact={ artifact }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { Stack, Typography } from '@mui/material'
import { ApplicationDTOPackagingFormatEnum, BranchDTO } from '../../../../../autogenerated/client/backend'
import { createDockerDesktopClient } from '@docker/extension-api-client'
import { sortArtifacts } from '../../../../clients/util'
import BranchCard from './BranchCard'

const ddClient = createDockerDesktopClient()

export default function BranchesList({ branches, packagingFormat }: { branches: BranchDTO[], packagingFormat?: ApplicationDTOPackagingFormatEnum }) {
export default function BranchesList({ branches, packagingFormat, disabled = false }: { branches: BranchDTO[], packagingFormat?: ApplicationDTOPackagingFormatEnum, disabled?: boolean }) {
if (!packagingFormat) {
return <Typography>This application cannot be deployed yet</Typography>
}
Expand All @@ -30,7 +28,8 @@ export default function BranchesList({ branches, packagingFormat }: { branches:
key={ artifact?.name }
branch={ branch }
version={ version as string }
artifact={ artifact } />
artifact={ artifact }
disabled={ disabled } />
)
}
</Stack>
Expand Down
68 changes: 34 additions & 34 deletions ui/src/pages/ApplicationDetailsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,39 +21,37 @@ export default function ApplicationDetailsPage() {
const slugName = useLoaderData()

useEffect(() => {
if (auth) {
applicationsClient(auth).getApplication(slugName as string)
.then(response => {
if (response.status == 200) {
setApplication(response.data)
}
}).catch(e => {
console.error(`Unexpected error fetching application [slugName=${slugName}]`, e)
let content: ReactElement
if (e instanceof AxiosError && e.status == 401) {
content = <>Authentication failed. Go to the <Link to='/settings'>Settings page</Link> to manage the access token.</>
} else {
content = <>There was an unexpected error loading the application. Try again in a couple of minutes.</>
}
setError(content)
})
componentsClient(auth).getComponent(slugName as string)
.then(response => {
if (response.status == 200) {
setComponent(response.data)
}
}).catch(e => {
console.error(`Unexpected error fetching component [slugName=${slugName}]`, e)
let content: ReactElement
if (e instanceof AxiosError && e.status == 401) {
content = <>Authentication failed. Go to the <Link to='/settings'>Settings page</Link> to manage the access token.</>
} else {
content = <>There was an unexpected error loading the application. Try again in a couple of minutes.</>
}
setError(content)
})
}
}, [])
applicationsClient(auth || null).getApplication(slugName as string)
.then(response => {
if (response.status == 200) {
setApplication(response.data)
}
}).catch(e => {
console.error(`Unexpected error fetching application [slugName=${slugName}]`, e)
let content: ReactElement
if (e instanceof AxiosError && e.status == 401) {
content = <>Authentication failed. Go to the <Link to='/settings'>Settings page</Link> to manage the access token.</>
} else {
content = <>There was an unexpected error loading the application. Try again in a couple of minutes.</>
}
setError(content)
})
componentsClient(auth || null).getComponent(slugName as string)
.then(response => {
if (response.status == 200) {
setComponent(response.data)
}
}).catch(e => {
console.error(`Unexpected error fetching component [slugName=${slugName}]`, e)
let content: ReactElement
if (e instanceof AxiosError && e.status == 401) {
content = <>Authentication failed. Go to the <Link to='/settings'>Settings page</Link> to manage the access token.</>
} else {
content = <>There was an unexpected error loading the application. Try again in a couple of minutes.</>
}
setError(content)
})
}, [auth])

if (error) {
return <Typography color='error' sx={ { mt: 2 } }>{ error }</Typography>
Expand Down Expand Up @@ -100,7 +98,9 @@ export default function ApplicationDetailsPage() {
<Typography sx={ { my: 2 } }>{ application.description }</Typography>
<Typography variant='h3'>Manage branches</Typography>
<Typography variant='h5' sx={ { mb: 3 } }>Run new workloads in your cluster</Typography>
<BranchesList branches={ component.branches.filter(b => !b.inactive_at || new Date(b.inactive_at) > new Date()) } packagingFormat={ application.packaging_format } />
<BranchesList
branches={ component.branches.filter(b => !b.inactive_at || new Date(b.inactive_at) > new Date()) } packagingFormat={ application.packaging_format }
disabled={ !auth } />
</>
)
}
70 changes: 33 additions & 37 deletions ui/src/pages/ApplicationsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,46 +17,42 @@ export default function ApplicationsPage() {
const auth = useAuth()

useEffect(() => {
if (auth) {
applicationsClient(auth).getApplications(undefined, undefined, ApplicationDTOPackagingFormatEnum.HelmChart)
.then(response => {
if (response.status == 200) {
setApplications(response.data.items)
setNextPage(response.data.page === response.data.total_pages ? response.data.page || 0 : nextPage + 1)
setTotalSize(response.data.total_size || 0)
}
})
.catch(e => {
console.error('Unexpected error fetching applications', e)
let content
if (e instanceof AxiosError && e.status == 401) {
content = <>Authentication failed. Go to the <Link to='/settings'>Settings page</Link> to manage the access token.</>
} else {
content = 'There was an unexpected error loading the collection. Try again in a couple of minutes.'
}
setError(<Typography color='error' sx={ { mt: 2 } }>{ content }</Typography>)
})
}
applicationsClient(auth || null).getApplications(undefined, undefined, ApplicationDTOPackagingFormatEnum.HelmChart)
.then(response => {
if (response.status == 200) {
setApplications(response.data.items)
setNextPage(response.data.page === response.data.total_pages ? response.data.page || 0 : nextPage + 1)
setTotalSize(response.data.total_size || 0)
}
})
.catch(e => {
console.error('Unexpected error fetching applications', e)
let content
if (e instanceof AxiosError && e.status == 401) {
content = <>Authentication failed. Go to the <Link to='/settings'>Settings page</Link> to manage the access token.</>
} else {
content = 'There was an unexpected error loading the collection. Try again in a couple of minutes.'
}
setError(<Typography color='error' sx={ { mt: 2 } }>{ content }</Typography>)
})
}, [auth])

function loadAppsPage() {
if (auth) {
applicationsClient(auth).getApplications(undefined, undefined, ApplicationDTOPackagingFormatEnum.HelmChart, undefined, nextPage)
.then((response) => {
setApplications([...applications, ...response.data.items])
setNextPage(response.data.page === response.data.total_pages ? response.data.page || 0 : nextPage + 1)
})
.catch(e => {
console.error('Unexpected error fetching applications', e)
let content
if (e instanceof AxiosError && e.status == 401) {
content = <>Authentication failed. Go to the <Link to='/settings'>Settings page</Link> to manage the access token.</>
} else {
content = 'There was an unexpected error loading the collection. Try again in a couple of minutes.'
}
setError(<Typography color='error' sx={ { mt: 2 } }>{ content }</Typography>)
})
}
applicationsClient(auth || null).getApplications(undefined, undefined, ApplicationDTOPackagingFormatEnum.HelmChart, undefined, nextPage)
.then((response) => {
setApplications([...applications, ...response.data.items])
setNextPage(response.data.page === response.data.total_pages ? response.data.page || 0 : nextPage + 1)
})
.catch(e => {
console.error('Unexpected error fetching applications', e)
let content
if (e instanceof AxiosError && e.status == 401) {
content = <>Authentication failed. Go to the <Link to='/settings'>Settings page</Link> to manage the access token.</>
} else {
content = 'There was an unexpected error loading the collection. Try again in a couple of minutes.'
}
setError(<Typography color='error' sx={ { mt: 2 } }>{ content }</Typography>)
})
}

return (
Expand Down
7 changes: 3 additions & 4 deletions ui/src/pages/SettingsPage/components/AuthenticationForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ export default function AuthenticationForm() {
setState('error')
})
)

}
}

Expand Down Expand Up @@ -152,9 +151,9 @@ export default function AuthenticationForm() {
</Alert>
}
<Card sx={ { mt: 3 } } variant='outlined'>
<CardContent>
<CardContent sx={ { p: 3 } }>
<Typography>The following will be configured automatically:</Typography>
<List sx={ { width: '100%' } }>
<List sx={ { width: '100%', pb: 0 } }>
<ListItem sx={ { alignItems: 'end' } }>
<ListItemText
primary='Docker'
Expand All @@ -167,7 +166,7 @@ export default function AuthenticationForm() {
secondary='For pulling and installing Helm Charts' />
<Typography variant='code' color='text.secondary' sx={ { mb: 0.75 } }>helm registry login</Typography>
</ListItem>
<ListItem sx={ { alignItems: 'end' } }>
<ListItem sx={ { alignItems: 'end', pb: 0 } }>
<ListItemText
primary='kubectl'
secondary='For running kubernetes workloads' />
Expand Down
21 changes: 16 additions & 5 deletions ui/src/pages/WorkloadDetailsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { findRelease, HelmListItem, HelmReleaseDetails } from '../../clients/hel
import { ChangeCircleOutlined, Delete, EditOutlined, HomeOutlined, LanOutlined, SyncOutlined, Upgrade } from '@mui/icons-material'
import moment from 'moment'
import StatusIcon from '../WorkloadsPage/components/StatusIcon'
import { useEffect, useState } from 'react'
import { ReactElement, useEffect, useState } from 'react'
import { ArtifactListItemReducedDTO, ArtifactListItemReducedDTOPackagingFormatEnum, BranchDTO } from '../../../autogenerated/client/backend'
import { useAuth } from '../../AuthContext'
import { componentsClient } from '../../clients/backend'
Expand Down Expand Up @@ -118,6 +118,7 @@ export default function WorkloadDetailsPage() {
</>
}

const AuthnWrapper = ({ children } : { children: ReactElement }) => !auth ? <Tooltip title='Log in to update this application'><span>{ children }</span></Tooltip> : <>{ children }</>

return <>
<Stack direction='row' alignItems='start' justifyContent='space-between'>
Expand Down Expand Up @@ -153,7 +154,9 @@ export default function WorkloadDetailsPage() {
</Tooltip>
</Stack>
</Stack>
<Button variant='outlined' startIcon={ <EditOutlined /> } onClick={ () => setEditDialogOpen(true) }>Edit</Button>
<AuthnWrapper>
<Button disabled={ !auth } variant='outlined' startIcon={ <EditOutlined /> } onClick={ () => setEditDialogOpen(true) }>Edit</Button>
</AuthnWrapper>
</Stack>
{
release.history[release.history.length - 1].status === 'failed' &&
Expand Down Expand Up @@ -193,6 +196,10 @@ export default function WorkloadDetailsPage() {
<Grid size={ { xs: 6 } }>
<Card variant='outlined' sx={ { p: 2 } }>
<Typography variant='body1' fontWeight='500' sx={ { mb: 1 } }>Port mappings</Typography>
{
portMappings.length === 0 &&
<Typography variant='body2' color='text.secondary' fontStyle='italic'>This application does not declare any NodePort.</Typography>
}
{
portMappings.map((portMapping, i) =>
<Stack
Expand Down Expand Up @@ -230,9 +237,13 @@ export default function WorkloadDetailsPage() {
</Box>
<Stack direction='row' justifyContent='space-between' spacing={ 2 } sx={ { mt: 3 } }>
<Button color='error' variant='text' startIcon={ <Delete /> } onClick={ () => setDeleteDialogOpen(true) }>Delete</Button>
{ update === undefined ?
<Skeleton variant='rounded' width={ 102 } height={ 32 } /> :
update && <Button color='primary' variant='contained' startIcon={ <Upgrade /> } onClick={ () => setUpdateDialogOpen(true) }>Update</Button> }
{
update === undefined ?
<Skeleton variant='rounded' width={ 102 } height={ 32 } /> :
update && <AuthnWrapper>
<Button disabled={ !auth } color='primary' variant='contained' startIcon={ <Upgrade /> } onClick={ () => setUpdateDialogOpen(true) }>Update</Button>
</AuthnWrapper>
}
</Stack>
{ update && <UpgradeDialog
artifact={ update }
Expand Down