Skip to content

Commit

Permalink
console: Integrate delete gateway logic
Browse files Browse the repository at this point in the history
  • Loading branch information
PavelJankoski committed May 29, 2024
1 parent e159341 commit a37a913
Show file tree
Hide file tree
Showing 13 changed files with 291 additions and 254 deletions.
2 changes: 1 addition & 1 deletion pkg/webui/components/delete-modal-button/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const DeleteModalButton = props => {
title: sharedMessages.deleteModalConfirmDeletion,
approveButtonProps: {
disabled: shouldConfirm && confirmId !== entityId,
icon: { IconTrash },
icon: IconTrash,
primary: true,
message,
},
Expand Down
225 changes: 225 additions & 0 deletions pkg/webui/console/containers/delete-gateway-modal/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
// Copyright © 2024 The Things Network Foundation, The Things Industries B.V.
//
// 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, { useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'

import { IconTrash } from '@ttn-lw/components/icon'
import PortalledModal from '@ttn-lw/components/modal/portalled'
import toast from '@ttn-lw/components/toast'
import Form from '@ttn-lw/components/form'
import Checkbox from '@ttn-lw/components/checkbox'
import Notification from '@ttn-lw/components/notification'
import Link from '@ttn-lw/components/link'
import Input from '@ttn-lw/components/input'

import RequireRequest from '@ttn-lw/lib/components/require-request'
import Message from '@ttn-lw/lib/components/message'

import sharedMessages from '@ttn-lw/lib/shared-messages'
import PropTypes from '@ttn-lw/lib/prop-types'
import attachPromise from '@ttn-lw/lib/store/actions/attach-promise'
import { getCollaboratorsList } from '@ttn-lw/lib/store/actions/collaborators'
import { selectCollaboratorsTotalCount } from '@ttn-lw/lib/store/selectors/collaborators'

import {
checkFromState,
mayDeleteGateway,
mayPurgeEntities,
mayViewOrEditGatewayApiKeys,
mayViewOrEditGatewayCollaborators,
} from '@console/lib/feature-checks'

import { getApiKeysList } from '@console/store/actions/api-keys'
import { getIsConfiguration } from '@console/store/actions/identity-server'
import { deleteGateway } from '@console/store/actions/gateways'

import { selectApiKeysTotalCount } from '@console/store/selectors/api-keys'

const DeleteGatewayModal = props => {
const { gtwId, gtwName, visible, setVisible, setError } = props

const [confirmId, setConfirmId] = React.useState('')
const [purgeEntity, setPurgeEntity] = React.useState(false)
const dispatch = useDispatch()
const navigate = useNavigate()

const mayPurgeGtw = useSelector(state => checkFromState(mayPurgeEntities, state))
const mayDeleteGtw = useSelector(state => checkFromState(mayDeleteGateway, state))
const apiKeysCount = useSelector(selectApiKeysTotalCount)
const collaboratorsCount = useSelector(state =>
selectCollaboratorsTotalCount(state, { id: gtwId }),
)
const hasApiKeys = apiKeysCount > 0
const hasAddedCollaborators = collaboratorsCount > 1
const isPristine = !hasAddedCollaborators && !hasApiKeys
const mayViewCollaborators = useSelector(state =>
checkFromState(mayViewOrEditGatewayCollaborators, state),
)
const mayViewApiKeys = useSelector(state => checkFromState(mayViewOrEditGatewayApiKeys, state))
const shouldConfirmDelete = !isPristine || !mayViewCollaborators || !mayViewApiKeys

const name = gtwName ? gtwName : gtwId

const handlePurgeEntityChange = React.useCallback(() => {
setPurgeEntity(purge => !purge)
setConfirmId('')
}, [])

const handleComplete = useCallback(
async confirmed => {
if (confirmed) {
{
try {
if (setError) {
setError(undefined)
}
await dispatch(attachPromise(deleteGateway(gtwId, { purge: purgeEntity || false })))
navigate('/gateways')
toast({
title: gtwId,
message: sharedMessages.gatewayDeleted,
type: toast.types.SUCCESS,
})
} catch (error) {
if (setError) {
setError(error)
}
toast({
title: gtwId,
message: sharedMessages.gatewayDeleteError,
type: toast.types.ERROR,
})
}
}
}
setVisible(false)
},
[dispatch, gtwId, navigate, purgeEntity, setError, setVisible],
)

const loadData = useCallback(
async dispatch => {
if (mayDeleteGtw) {
if (mayViewApiKeys) {
await dispatch(attachPromise(getApiKeysList('gateway', gtwId)))
}
if (mayViewCollaborators) {
await dispatch(attachPromise(getCollaboratorsList('gateway', gtwId)))
}
}
dispatch(attachPromise(getIsConfiguration()))
},
[gtwId, mayDeleteGtw, mayViewApiKeys, mayViewCollaborators],
)

const initialValues = React.useMemo(
() => ({
purge: false,
}),
[],
)

return (
<RequireRequest requestAction={loadData}>
<PortalledModal
danger
visible={visible}
onComplete={handleComplete}
title={sharedMessages.deleteModalConfirmDeletion}
approveButtonProps={{
disabled: shouldConfirmDelete && confirmId !== gtwId,
icon: IconTrash,
primary: true,
message: sharedMessages.deleteGateway,
}}
>
<div>
<Message
content={sharedMessages.deleteModalTitle}
values={{ entityName: name, pre: name => <pre className="d-inline">{name}</pre> }}
component="span"
/>
<Message
content={
purgeEntity
? sharedMessages.deleteModalPurgeMessage
: sharedMessages.deleteModalDefaultMessage
}
values={{ strong: txt => <strong>{txt}</strong> }}
component="p"
/>
<Form initialValues={initialValues}>
{mayPurgeGtw && (
<Form.Field
name="purge"
className="mt-ls-xxs"
component={Checkbox}
onChange={handlePurgeEntityChange}
title={sharedMessages.deleteModalReleaseIdTitle}
label={sharedMessages.deleteModalReleaseIdLabel}
/>
)}
{purgeEntity && (
<Notification
small
warning
content={sharedMessages.deleteModalPurgeWarning}
messageValues={{
strong: txt => <strong>{txt}</strong>,
DocLink: txt => (
<Link.DocLink primary raw path="/the-things-stack/management/purge/">
{txt}
</Link.DocLink>
),
}}
/>
)}
{shouldConfirmDelete && (
<>
<Message
content={sharedMessages.deleteModalConfirmMessage}
values={{ entityId: gtwId, pre: id => <pre className="d-inline">{id}</pre> }}
component="span"
/>
<Input
className="mt-ls-xxs"
data-test-id="confirm_deletion"
value={confirmId}
onChange={setConfirmId}
/>
</>
)}
</Form>
</div>
</PortalledModal>
</RequireRequest>
)
}

DeleteGatewayModal.propTypes = {
gtwId: PropTypes.string.isRequired,
gtwName: PropTypes.string,
setError: PropTypes.func,
setVisible: PropTypes.func.isRequired,
visible: PropTypes.bool.isRequired,
}

DeleteGatewayModal.defaultProps = {
gtwName: undefined,
setError: undefined,
}

export default DeleteGatewayModal
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ const DeviceQRScanFormSection = () => {
buttonMessage: m.resetQRCodeData,
children: <Message content={m.resetConfirm} component="span" />,
approveButtonProps: {
icon: { IconX },
icon: IconX,
},
}}
/>
Expand Down
7 changes: 1 addition & 6 deletions pkg/webui/console/containers/gateway-connection/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,7 @@ import classnames from 'classnames'
import { FormattedNumber, defineMessages } from 'react-intl'
import { useDispatch, useSelector } from 'react-redux'

import Icon, {
IconArrowsSort,
IconDownlink,
IconUplink,
IconBroadcast,
} from '@ttn-lw/components/icon'
import Icon, { IconArrowsSort, IconBroadcast } from '@ttn-lw/components/icon'
import Status from '@ttn-lw/components/status'
import DocTooltip from '@ttn-lw/components/tooltip/doc'
import Tooltip from '@ttn-lw/components/tooltip'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
white-space: nowrap
color: var(--c-text-neutral-heavy)
font-weight: $fw.bold
font-size: $fsv2.m
font-size: $fsv2.l
margin: 0

.id
color: var(--c-text-neutral-semilight)
font-size: $fsv2.s
font-size: $fs.s
font-family: $font-family-mono
&-prefix
&:after
Expand Down
24 changes: 21 additions & 3 deletions pkg/webui/console/containers/gateway-overview-header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import React, { useCallback, useMemo } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { defineMessages } from 'react-intl'
import classnames from 'classnames'
Expand All @@ -34,13 +34,16 @@ import Message from '@ttn-lw/lib/components/message'
import LastSeen from '@console/components/last-seen'

import GatewayConnection from '@console/containers/gateway-connection'
import DeleteGatewayModal from '@console/containers/delete-gateway-modal'

import PropTypes from '@ttn-lw/lib/prop-types'
import sharedMessages from '@ttn-lw/lib/shared-messages'
import attachPromise from '@ttn-lw/lib/store/actions/attach-promise'
import { selectFetchingEntry } from '@ttn-lw/lib/store/selectors/fetching'
import { composeDataUri, downloadDataUriAsFile } from '@ttn-lw/lib/data-uri'

import { checkFromState, mayDeleteGateway } from '@console/lib/feature-checks'

import {
ADD_BOOKMARK_BASE,
addBookmark,
Expand All @@ -60,6 +63,8 @@ const m = defineMessages({
})

const GatewayOverviewHeader = ({ gateway }) => {
const [deleteGatewayVisible, setDeleteGatewayVisible] = useState(false)

const dispatch = useDispatch()
const { ids, name, created_at } = gateway
const { gateway_id } = ids
Expand All @@ -69,6 +74,7 @@ const GatewayOverviewHeader = ({ gateway }) => {
const deleteBookmarkLoading = useSelector(state =>
selectFetchingEntry(state, DELETE_BOOKMARK_BASE),
)
const mayDeleteGtw = useSelector(state => checkFromState(mayDeleteGateway, state))

const isBookmarked = useMemo(
() => bookmarks.map(b => b.entity_ids?.gateway_ids?.gateway_id).some(b => b === gateway_id),
Expand Down Expand Up @@ -112,11 +118,17 @@ const GatewayOverviewHeader = ({ gateway }) => {
}
}, [gateway_id])

const handleOpenDeleteGatewayModal = useCallback(() => {
setDeleteGatewayVisible(true)
}, [])

const menuDropdownItems = (
<>
<Dropdown.Item title={sharedMessages.downloadGlobalConf} action={handleGlobalConfDownload} />
<Dropdown.Item title={m.duplicateGateway} action={() => {}} />
<Dropdown.Item title={sharedMessages.deleteGateway} action={() => {}} />
{/* <Dropdown.Item title={m.duplicateGateway} action={() => {}} />*/}
{mayDeleteGtw && (
<Dropdown.Item title={sharedMessages.deleteGateway} action={handleOpenDeleteGatewayModal} />
)}
</>
)

Expand Down Expand Up @@ -158,6 +170,12 @@ const GatewayOverviewHeader = ({ gateway }) => {
dropdownPosition="below left"
/>
</div>
<DeleteGatewayModal
gtwId={gateway_id}
gtwName={name}
setVisible={setDeleteGatewayVisible}
visible={deleteGatewayVisible}
/>
</div>
</div>
)
Expand Down
Loading

0 comments on commit a37a913

Please sign in to comment.