diff --git a/pkg/webui/account.js b/pkg/webui/account.js index 8196eca9b4..83bbfd4ec9 100644 --- a/pkg/webui/account.js +++ b/pkg/webui/account.js @@ -25,7 +25,7 @@ import Header from '@ttn-lw/components/header' import WithLocale from '@ttn-lw/lib/components/with-locale' import { ErrorView } from '@ttn-lw/lib/components/error-view' -import FullViewError from '@ttn-lw/lib/components/full-view-error' +import { FullViewError } from '@ttn-lw/lib/components/full-view-error' import Init from '@ttn-lw/lib/components/init' import Logo from '@account/containers/logo' diff --git a/pkg/webui/account/containers/collaborators-table/index.js b/pkg/webui/account/containers/collaborators-table/index.js index 093ec54553..2d28c12b5d 100644 --- a/pkg/webui/account/containers/collaborators-table/index.js +++ b/pkg/webui/account/containers/collaborators-table/index.js @@ -13,8 +13,9 @@ // limitations under the License. import React from 'react' -import { useDispatch, useSelector } from 'react-redux' +import { connect, useDispatch } from 'react-redux' import { defineMessages, useIntl } from 'react-intl' +import { bindActionCreators } from 'redux' import { createSelector } from 'reselect' import Icon from '@ttn-lw/components/icon' @@ -28,12 +29,10 @@ import FetchTable from '@ttn-lw/containers/fetch-table' import Message from '@ttn-lw/lib/components/message' import { getCollaboratorId } from '@ttn-lw/lib/selectors/id' +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 { - getCollaboratorsList, - deleteCollaborator as deleteCollaboratorAction, -} from '@ttn-lw/lib/store/actions/collaborators' +import { getCollaboratorsList, deleteCollaborator } from '@ttn-lw/lib/store/actions/collaborators' import { selectCollaborators, selectCollaboratorsTotalCount, @@ -53,17 +52,9 @@ const m = defineMessages({ }) const CollaboratorsTable = props => { - const { ...rest } = props + const { clientId, currentUserId, handleDeleteCollaborator, ...rest } = props const dispatch = useDispatch() const intl = useIntl() - const clientId = useSelector(selectSelectedClientId) - const currentUserId = useSelector(selectUserId) - - const handleDeleteCollaborator = React.useCallback( - (collaboratorID, isUsr) => - dispatch(attachPromise(deleteCollaboratorAction('client', clientId, collaboratorID, isUsr))), - [clientId, dispatch], - ) const deleteCollaborator = React.useCallback( async ids => { @@ -203,4 +194,30 @@ const CollaboratorsTable = props => { ) } -export default CollaboratorsTable +CollaboratorsTable.propTypes = { + clientId: PropTypes.string.isRequired, + currentUserId: PropTypes.string.isRequired, + handleDeleteCollaborator: PropTypes.func.isRequired, +} + +export default connect( + state => ({ + clientId: selectSelectedClientId(state), + currentUserId: selectUserId(state), + }), + dispatch => ({ + ...bindActionCreators( + { + handleDeleteCollaborator: attachPromise(deleteCollaborator), + }, + dispatch, + ), + }), + (stateProps, dispatchProps, ownProps) => ({ + ...stateProps, + ...dispatchProps, + ...ownProps, + handleDeleteCollaborator: (collaboratorID, isUsr) => + dispatchProps.handleDeleteCollaborator('client', stateProps.clientId, collaboratorID, isUsr), + }), +)(CollaboratorsTable) diff --git a/pkg/webui/console.js b/pkg/webui/console.js index ffb1423631..fdba93024f 100644 --- a/pkg/webui/console.js +++ b/pkg/webui/console.js @@ -24,7 +24,7 @@ import { BreadcrumbsProvider } from '@ttn-lw/components/breadcrumbs/context' import Header from '@ttn-lw/components/header' import { ErrorView } from '@ttn-lw/lib/components/error-view' -import FullViewError from '@ttn-lw/lib/components/full-view-error' +import { FullViewError } from '@ttn-lw/lib/components/full-view-error' import Init from '@ttn-lw/lib/components/init' import WithLocale from '@ttn-lw/lib/components/with-locale' diff --git a/pkg/webui/console/components/pubsub-form/index.js b/pkg/webui/console/components/pubsub-form/index.js index 3d62c6961d..081214a6fb 100644 --- a/pkg/webui/console/components/pubsub-form/index.js +++ b/pkg/webui/console/components/pubsub-form/index.js @@ -475,6 +475,7 @@ const PubsubForm = props => { title={sharedMessages.provider} name="_provider" component={Radio.Group} + description={natsDisabled || mqttDisabled ? m.providerDescription : undefined} disabled={natsDisabled || mqttDisabled} > diff --git a/pkg/webui/lib/components/full-view-error/connect.js b/pkg/webui/lib/components/full-view-error/connect.js new file mode 100644 index 0000000000..618e10dc4d --- /dev/null +++ b/pkg/webui/lib/components/full-view-error/connect.js @@ -0,0 +1,22 @@ +// Copyright © 2020 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 { connect } from 'react-redux' + +import { selectOnlineStatus } from '@ttn-lw/lib/store/selectors/status' + +export default ErrorView => + connect(state => ({ + onlineStatus: selectOnlineStatus(state), + }))(ErrorView) diff --git a/pkg/webui/lib/components/full-view-error/error.js b/pkg/webui/lib/components/full-view-error/error.js new file mode 100644 index 0000000000..96e0dca16b --- /dev/null +++ b/pkg/webui/lib/components/full-view-error/error.js @@ -0,0 +1,340 @@ +// Copyright © 2020 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, useEffect, useRef, useState } from 'react' +import { Container, Row, Col } from 'react-grid-system' +import clipboard from 'clipboard' +import { Helmet } from 'react-helmet' +import classnames from 'classnames' + +import Footer from '@ttn-lw/components/footer' +import buttonStyle from '@ttn-lw/components/button/button.styl' +import Icon from '@ttn-lw/components/icon' + +import Message from '@ttn-lw/lib/components/message' +import IntlHelmet from '@ttn-lw/lib/components/intl-helmet' + +import errorMessages from '@ttn-lw/lib/errors/error-messages' +import sharedMessages from '@ttn-lw/lib/shared-messages' +import * as cache from '@ttn-lw/lib/cache' +import { + isUnknown as isUnknownError, + isNotFoundError, + isFrontend as isFrontendError, + isBackend as isBackendError, + getCorrelationId, + getBackendErrorId, + isOAuthClientRefusedError, + isOAuthInvalidStateError, +} from '@ttn-lw/lib/errors/utils' +import statusCodeMessages from '@ttn-lw/lib/errors/status-code-messages' +import PropTypes from '@ttn-lw/lib/prop-types' +import { + selectApplicationRootPath, + selectSupportLinkConfig, + selectApplicationSiteName, + selectApplicationSiteTitle, + selectDocumentationUrlConfig, +} from '@ttn-lw/lib/selectors/env' + +import style from './error.styl' + +const appRoot = selectApplicationRootPath() +const siteName = selectApplicationSiteName() +const siteTitle = selectApplicationSiteTitle() +const supportLink = selectSupportLinkConfig() +const documentationLink = selectDocumentationUrlConfig() +const hasSupportLink = Boolean(supportLink) +const lastRefreshAttemptCacheKey = 'last-refresh-attempt' + +// Mind any rendering that is dependant on context, since the errors +// can be rendered before such context is injected. Use the `safe` +// prop to conditionally render any context-dependant nodes. +const FullViewError = ({ error, header, onlineStatus, safe, action, unexpected }) => ( +
+ {Boolean(header) && header} +
+ +
+
+
+) + +const FullViewErrorInner = ({ error, safe, action, unexpected }) => { + const isUnknown = isUnknownError(error) + const isNotFound = isNotFoundError(error) + const isFrontend = isFrontendError(error) + const isBackend = isBackendError(error) + const hasAction = Boolean(action) + const isErrorObject = error instanceof Error + const isOAuthCallback = /oauth.*\/callback$/.test(window.location.pathname) + + const errorId = getBackendErrorId(error) || 'n/a' + const correlationId = getCorrelationId(error) || 'n/a' + + const [copied, setCopied] = useState(false) + + const lastRefreshAttempt = cache.get(lastRefreshAttemptCacheKey) + let doBlankRender = false + + let errorMessage + let errorTitle + if (isNotFound) { + errorTitle = statusCodeMessages['404'] + errorMessage = errorMessages.genericNotFound + } else if (isOAuthCallback) { + errorTitle = sharedMessages.loginFailed + errorMessage = errorMessages.loginFailedDescription + if (isOAuthClientRefusedError(error) || error.error === 'access_denied') { + errorMessage = errorMessages.loginFailedAbortDescription + } else if (isOAuthInvalidStateError(error)) { + // Usually in case of state errors, the state has expired or otherwise + // invalidated since initiating the OAuth request. This can usually be + // resolved by re-initiating the OAuth flow. We need to keep track of + // refresh attempts though to avoid infinite refresh loops. + if (!lastRefreshAttempt || Date.now() - lastRefreshAttempt > 6 * 1000) { + cache.set(lastRefreshAttemptCacheKey, Date.now()) + doBlankRender = true + window.location = appRoot + } + } + } else if (isFrontend) { + errorMessage = error.errorMessage + if (Boolean(error.errorTitle)) { + errorTitle = error.errorTitle + } + } else if (!isUnknown) { + errorTitle = errorMessages.error + errorMessage = errorMessages.errorOccurred + } else { + errorTitle = errorMessages.error + errorMessage = errorMessages.genericError + } + + const copiedTimer = useRef(undefined) + const handleCopyClick = useCallback(() => { + if (!copied) { + setCopied(true) + copiedTimer.current = setTimeout(() => setCopied(false), 3000) + } + }, [setCopied, copied]) + + const copyButton = useRef(null) + useEffect(() => { + if (copyButton.current) { + new clipboard(copyButton.current) + } + return () => { + // Clear timer on unmount. + if (copiedTimer.current) { + clearTimeout(copiedTimer.current) + } + } + }, []) + + const errorDetails = JSON.stringify(error, undefined, 2) + const hasErrorDetails = + (!isNotFound && Boolean(error) && errorDetails.length > 2) || (isFrontend && error.errorCode) + const buttonClasses = classnames(buttonStyle.button, style.actionButton) + + // Do not render anything when performing a redirect. + if (doBlankRender) { + return null + } + + return ( +
+ + + + {safe ? ( + + Error + + ) : ( + + )} +

+ + +

+
+ + {!isNotFound && unexpected && ( + <> + {' '} + +
+ + + )} +
+
+ {isNotFound && ( + + + + + )} + {isOAuthCallback && ( + + + + + )} + {hasAction && ( + + )} + {hasSupportLink && !isNotFound && ( + <> + + + + + {hasErrorDetails && ( + + )} + + )} +
+ {hasErrorDetails && ( + <> + {isErrorObject && ( + <> +
+
+ + Error Type: {error.name} + +
+ + )} + {isFrontend && ( + <> +
+
+ + Frontend Error ID: {error.errorCode} + +
+ + )} + {isBackend && ( + <> +
+
+ + Error ID: {errorId} + + + Correlation ID: {correlationId} + +
+ + )} +
+
+ + + +
{errorDetails}
+ +
+ + )} + +
+
+
+ ) +} + +FullViewErrorInner.propTypes = { + action: PropTypes.shape({ + action: PropTypes.func.isRequired, + icon: PropTypes.string.isRequired, + message: PropTypes.message.isRequired, + }), + error: PropTypes.error.isRequired, + safe: PropTypes.bool, + unexpected: PropTypes.bool, +} + +FullViewErrorInner.defaultProps = { + action: undefined, + safe: false, + unexpected: true, +} + +FullViewError.propTypes = { + action: PropTypes.shape({ + action: PropTypes.func.isRequired, + icon: PropTypes.string.isRequired, + message: PropTypes.message.isRequired, + }), + error: PropTypes.error.isRequired, + header: PropTypes.node, + onlineStatus: PropTypes.onlineStatus, + safe: PropTypes.bool, + unexpected: PropTypes.bool, +} + +FullViewError.defaultProps = { + action: undefined, + header: undefined, + onlineStatus: undefined, + safe: false, + unexpected: true, +} + +export { FullViewError, FullViewErrorInner } diff --git a/pkg/webui/lib/components/full-view-error/index.js b/pkg/webui/lib/components/full-view-error/index.js index 4eb96545b9..e28a0ab4df 100644 --- a/pkg/webui/lib/components/full-view-error/index.js +++ b/pkg/webui/lib/components/full-view-error/index.js @@ -12,333 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import React, { useCallback, useEffect, useRef, useState } from 'react' -import { Container, Row, Col } from 'react-grid-system' -import clipboard from 'clipboard' -import { Helmet } from 'react-helmet' -import classnames from 'classnames' -import { useSelector } from 'react-redux' +import { FullViewError, FullViewErrorInner } from './error' +import connect from './connect' -import Footer from '@ttn-lw/components/footer' -import buttonStyle from '@ttn-lw/components/button/button.styl' -import Icon from '@ttn-lw/components/icon' +const ConnectedFullErrorView = connect(FullViewError) -import Message from '@ttn-lw/lib/components/message' -import IntlHelmet from '@ttn-lw/lib/components/intl-helmet' - -import errorMessages from '@ttn-lw/lib/errors/error-messages' -import sharedMessages from '@ttn-lw/lib/shared-messages' -import * as cache from '@ttn-lw/lib/cache' -import { - isUnknown as isUnknownError, - isNotFoundError, - isFrontend as isFrontendError, - isBackend as isBackendError, - getCorrelationId, - getBackendErrorId, - isOAuthClientRefusedError, - isOAuthInvalidStateError, -} from '@ttn-lw/lib/errors/utils' -import statusCodeMessages from '@ttn-lw/lib/errors/status-code-messages' -import PropTypes from '@ttn-lw/lib/prop-types' -import { - selectApplicationRootPath, - selectSupportLinkConfig, - selectApplicationSiteName, - selectApplicationSiteTitle, - selectDocumentationUrlConfig, -} from '@ttn-lw/lib/selectors/env' -import { selectOnlineStatus } from '@ttn-lw/lib/store/selectors/status' - -import style from './error.styl' - -const appRoot = selectApplicationRootPath() -const siteName = selectApplicationSiteName() -const siteTitle = selectApplicationSiteTitle() -const supportLink = selectSupportLinkConfig() -const documentationLink = selectDocumentationUrlConfig() -const hasSupportLink = Boolean(supportLink) -const lastRefreshAttemptCacheKey = 'last-refresh-attempt' - -// Mind any rendering that is dependant on context, since the errors -// can be rendered before such context is injected. Use the `safe` -// prop to conditionally render any context-dependant nodes. -const FullViewError = ({ error, header, safe, action, unexpected }) => { - const onlineStatus = useSelector(selectOnlineStatus) - - return ( -
- {Boolean(header) && header} -
- -
-
-
- ) -} - -const FullViewErrorInner = ({ error, safe, action, unexpected }) => { - const isUnknown = isUnknownError(error) - const isNotFound = isNotFoundError(error) - const isFrontend = isFrontendError(error) - const isBackend = isBackendError(error) - const hasAction = Boolean(action) - const isErrorObject = error instanceof Error - const isOAuthCallback = /oauth.*\/callback$/.test(window.location.pathname) - - const errorId = getBackendErrorId(error) || 'n/a' - const correlationId = getCorrelationId(error) || 'n/a' - - const [copied, setCopied] = useState(false) - - const lastRefreshAttempt = cache.get(lastRefreshAttemptCacheKey) - let doBlankRender = false - - let errorMessage - let errorTitle - if (isNotFound) { - errorTitle = statusCodeMessages['404'] - errorMessage = errorMessages.genericNotFound - } else if (isOAuthCallback) { - errorTitle = sharedMessages.loginFailed - errorMessage = errorMessages.loginFailedDescription - if (isOAuthClientRefusedError(error) || error.error === 'access_denied') { - errorMessage = errorMessages.loginFailedAbortDescription - } else if (isOAuthInvalidStateError(error)) { - // Usually in case of state errors, the state has expired or otherwise - // invalidated since initiating the OAuth request. This can usually be - // resolved by re-initiating the OAuth flow. We need to keep track of - // refresh attempts though to avoid infinite refresh loops. - if (!lastRefreshAttempt || Date.now() - lastRefreshAttempt > 6 * 1000) { - cache.set(lastRefreshAttemptCacheKey, Date.now()) - doBlankRender = true - window.location = appRoot - } - } - } else if (isFrontend) { - errorMessage = error.errorMessage - if (Boolean(error.errorTitle)) { - errorTitle = error.errorTitle - } - } else if (!isUnknown) { - errorTitle = errorMessages.error - errorMessage = errorMessages.errorOccurred - } else { - errorTitle = errorMessages.error - errorMessage = errorMessages.genericError - } - - const copiedTimer = useRef(undefined) - const handleCopyClick = useCallback(() => { - if (!copied) { - setCopied(true) - copiedTimer.current = setTimeout(() => setCopied(false), 3000) - } - }, [setCopied, copied]) - - const copyButton = useRef(null) - useEffect(() => { - if (copyButton.current) { - new clipboard(copyButton.current) - } - return () => { - // Clear timer on unmount. - if (copiedTimer.current) { - clearTimeout(copiedTimer.current) - } - } - }, []) - - const errorDetails = JSON.stringify(error, undefined, 2) - const hasErrorDetails = - (!isNotFound && Boolean(error) && errorDetails.length > 2) || (isFrontend && error.errorCode) - const buttonClasses = classnames(buttonStyle.button, style.actionButton) - - // Do not render anything when performing a redirect. - if (doBlankRender) { - return null - } - - return ( -
- - - - {safe ? ( - - Error - - ) : ( - - )} -

- - -

-
- - {!isNotFound && unexpected && ( - <> - {' '} - -
- - - )} -
-
- {isNotFound && ( - - - - - )} - {isOAuthCallback && ( - - - - - )} - {hasAction && ( - - )} - {hasSupportLink && !isNotFound && ( - <> - - - - - {hasErrorDetails && ( - - )} - - )} -
- {hasErrorDetails && ( - <> - {isErrorObject && ( - <> -
-
- - Error Type: {error.name} - -
- - )} - {isFrontend && ( - <> -
-
- - Frontend Error ID: {error.errorCode} - -
- - )} - {isBackend && ( - <> -
-
- - Error ID: {errorId} - - - Correlation ID: {correlationId} - -
- - )} -
-
- - - -
{errorDetails}
- -
- - )} - -
-
-
- ) -} - -FullViewErrorInner.propTypes = { - action: PropTypes.shape({ - action: PropTypes.func.isRequired, - icon: PropTypes.string.isRequired, - message: PropTypes.message.isRequired, - }), - error: PropTypes.error.isRequired, - safe: PropTypes.bool, - unexpected: PropTypes.bool, -} - -FullViewErrorInner.defaultProps = { - action: undefined, - safe: false, - unexpected: true, -} - -FullViewError.propTypes = { - action: PropTypes.shape({ - action: PropTypes.func.isRequired, - icon: PropTypes.string.isRequired, - message: PropTypes.message.isRequired, - }), - error: PropTypes.error.isRequired, - header: PropTypes.node, - safe: PropTypes.bool, - unexpected: PropTypes.bool, -} - -FullViewError.defaultProps = { - action: undefined, - header: undefined, - safe: false, - unexpected: true, -} - -export { FullViewError as default, FullViewErrorInner } +export { ConnectedFullErrorView as default, FullViewError, FullViewErrorInner } diff --git a/pkg/webui/locales/en.json b/pkg/webui/locales/en.json index 2bd84b2b6c..8f4e19e56c 100644 --- a/pkg/webui/locales/en.json +++ b/pkg/webui/locales/en.json @@ -205,16 +205,6 @@ "console.components.device-import-form.index.inputMethodManual": "Enter LoRaWAN versions and frequency plan manually", "console.components.device-import-form.index.fallbackValues": "Fallback values", "console.components.device-import-form.index.noFallback": "Do not set any fallback values", - "console.components.downlink-form.downlink-form.insertMode": "Insert Mode", - "console.components.downlink-form.downlink-form.payloadType": "Payload type", - "console.components.downlink-form.downlink-form.bytes": "Bytes", - "console.components.downlink-form.downlink-form.replace": "Replace downlink queue", - "console.components.downlink-form.downlink-form.push": "Push to downlink queue (append)", - "console.components.downlink-form.downlink-form.scheduleDownlink": "Schedule downlink", - "console.components.downlink-form.downlink-form.downlinkSuccess": "Downlink scheduled", - "console.components.downlink-form.downlink-form.bytesPayloadDescription": "The desired payload bytes of the downlink message", - "console.components.downlink-form.downlink-form.jsonPayloadDescription": "The decoded payload of the downlink message", - "console.components.downlink-form.downlink-form.invalidSessionWarning": "Downlinks can only be scheduled for end devices with a valid session. Please make sure your end device is properly connected to the network.", "console.components.downlink-form.index.insertMode": "Insert Mode", "console.components.downlink-form.index.payloadType": "Payload type", "console.components.downlink-form.index.bytes": "Bytes", @@ -499,25 +489,17 @@ "console.containers.device-onboarding-form.warning-tooltip.sessionDescription": "An ABP device is personalized with a session and MAC settings. These MAC settings are considered the current parameters and must match exactly the settings entered here. The Network Server uses desired parameters to change the MAC state with LoRaWAN MAC commands to the desired state. You can use the General Settings page to update the desired setting after you registered the end device.", "console.containers.device-payload-formatters.messages.defaultFormatter": "Click here to modify the default payload formatter for this application", "console.containers.device-payload-formatters.messages.mayNotViewLink": "You are not allowed to view link information of this application. This includes seeing the default payload formatter of this application.", - "console.containers.device-profile-section.device-card.device-card.productWebsite": "Product website", - "console.containers.device-profile-section.device-card.device-card.dataSheet": "Data sheet", - "console.containers.device-profile-section.device-card.device-card.classA": "Class A", - "console.containers.device-profile-section.device-card.device-card.classB": "Class B", - "console.containers.device-profile-section.device-card.device-card.classC": "Class C", "console.containers.device-profile-section.device-card.index.productWebsite": "Product website", "console.containers.device-profile-section.device-card.index.dataSheet": "Data sheet", "console.containers.device-profile-section.device-card.index.classA": "Class A", "console.containers.device-profile-section.device-card.index.classB": "Class B", "console.containers.device-profile-section.device-card.index.classC": "Class C", "console.containers.device-profile-section.device-selection.band-select.index.title": "Profile (Region)", - "console.containers.device-profile-section.device-selection.brand-select.brand-select.title": "End device brand", - "console.containers.device-profile-section.device-selection.brand-select.brand-select.noOptionsMessage": "No matching brand found", "console.containers.device-profile-section.device-selection.brand-select.index.title": "End device brand", "console.containers.device-profile-section.device-selection.brand-select.index.noOptionsMessage": "No matching brand found", "console.containers.device-profile-section.device-selection.fw-version-select.index.title": "Firmware Ver.", "console.containers.device-profile-section.device-selection.hw-version-select.index.title": "Hardware Ver.", "console.containers.device-profile-section.device-selection.model-select.index.noOptionsMessage": "No matching model found", - "console.containers.device-profile-section.device-selection.model-select.model-select.noOptionsMessage": "No matching model found", "console.containers.device-profile-section.hints.other-hint.hintTitle": "Your end device will be added soon!", "console.containers.device-profile-section.hints.other-hint.hintMessage": "We're sorry, but your device is not yet part of The LoRaWAN Device Repository. You can use enter end device specifics manually option above, using the information your end device manufacturer provided e.g. in the product's data sheet. Please also refer to our documentation on Adding Devices.", "console.containers.device-profile-section.hints.progress-hint.hintMessage": "Cannot find your exact end device? Get help here and try enter end device specifics manually option above.", @@ -533,27 +515,11 @@ "console.containers.freq-plans-select.utils.selectFrequencyPlan": "Select a frequency plan...", "console.containers.freq-plans-select.utils.addFrequencyPlan": "Add frequency plan", "console.containers.freq-plans-select.utils.frequencyPlanDescription": "Note: most gateways use a single frequency plan. Some 16 and 64 channel gateways however allow setting multiple.", - "console.containers.gateway-connection.gateway-connection.lastSeenAvailableTooltip": "The elapsed time since the network registered the last activity of this gateway. This is determined from received uplinks, or sent status messages of this gateway.", - "console.containers.gateway-connection.gateway-connection.disconnectedTooltip": "The gateway has currently no TCP connection established with the Gateway Server. For (rare) UDP based gateways, this can also mean that the gateway initiated no pull/push data request within the last 30 seconds.", - "console.containers.gateway-connection.gateway-connection.connectedTooltip": "This gateway is connected to the Gateway Server but the network has not registered any activity (sent uplinks or status messages) from it yet.", - "console.containers.gateway-connection.gateway-connection.otherClusterTooltip": "This gateway is connected to an external Gateway Server that is not handling messages for this cluster. You will hence not be able to see any activity from this gateway.", - "console.containers.gateway-connection.gateway-connection.messageCountTooltip": "The amount of received uplinks and sent downlinks of this gateway since the last (re)connect. Note that some gateway types reconnect frequently causing the counter to be reset.", "console.containers.gateway-connection.index.lastSeenAvailableTooltip": "The elapsed time since the network registered the last activity of this gateway. This is determined from received uplinks, or sent status messages of this gateway.", "console.containers.gateway-connection.index.disconnectedTooltip": "The gateway has currently no TCP connection established with the Gateway Server. For (rare) UDP based gateways, this can also mean that the gateway initiated no pull/push data request within the last 30 seconds.", "console.containers.gateway-connection.index.connectedTooltip": "This gateway is connected to the Gateway Server but the network has not registered any activity (sent uplinks or status messages) from it yet.", "console.containers.gateway-connection.index.otherClusterTooltip": "This gateway is connected to an external Gateway Server that is not handling messages for this cluster. You will hence not be able to see any activity from this gateway.", "console.containers.gateway-connection.index.messageCountTooltip": "The amount of received uplinks and sent downlinks of this gateway since the last (re)connect. Note that some gateway types reconnect frequently causing the counter to be reset.", - "console.containers.gateway-location-form.gateway-location-form.updateLocationFromStatus": "Update from status messages", - "console.containers.gateway-location-form.gateway-location-form.updateLocationFromStatusDescription": "Update the location of this gateway based on incoming status messages", - "console.containers.gateway-location-form.gateway-location-form.setGatewayLocation": "Gateway antenna location settings", - "console.containers.gateway-location-form.gateway-location-form.locationSource": "Location source", - "console.containers.gateway-location-form.gateway-location-form.locationPrivacy": "Location privacy", - "console.containers.gateway-location-form.gateway-location-form.placement": "Placement", - "console.containers.gateway-location-form.gateway-location-form.indoor": "Indoor", - "console.containers.gateway-location-form.gateway-location-form.outdoor": "Outdoor", - "console.containers.gateway-location-form.gateway-location-form.locationFromStatusMessage": "Location set automatically from status messages", - "console.containers.gateway-location-form.gateway-location-form.setLocationManually": "Set location manually", - "console.containers.gateway-location-form.gateway-location-form.noLocationSetInfo": "This gateway has no location information set", "console.containers.gateway-location-form.index.updateLocationFromStatus": "Update from status messages", "console.containers.gateway-location-form.index.updateLocationFromStatusDescription": "Update the location of this gateway based on incoming status messages", "console.containers.gateway-location-form.index.setGatewayLocation": "Gateway antenna location settings", diff --git a/pkg/webui/locales/ja.json b/pkg/webui/locales/ja.json index a33472acac..2f9f10b27d 100644 --- a/pkg/webui/locales/ja.json +++ b/pkg/webui/locales/ja.json @@ -205,26 +205,16 @@ "console.components.device-import-form.index.inputMethodManual": "LoRaWANのバージョンと周波数プランを手動で入力します", "console.components.device-import-form.index.fallbackValues": "フォールバック値", "console.components.device-import-form.index.noFallback": "フォールバック値は設定しない", - "console.components.downlink-form.downlink-form.insertMode": "挿入モード", - "console.components.downlink-form.downlink-form.payloadType": "ペイロード・タイプ", - "console.components.downlink-form.downlink-form.bytes": "バイト", - "console.components.downlink-form.downlink-form.replace": "ダウンリンクキューの交換", - "console.components.downlink-form.downlink-form.push": "ダウンリンクキューへのプッシュ(付け加え)", - "console.components.downlink-form.downlink-form.scheduleDownlink": "スケジュール ダウンリンク", - "console.components.downlink-form.downlink-form.downlinkSuccess": "ダウンリンク予定", - "console.components.downlink-form.downlink-form.bytesPayloadDescription": "ダウンリンクメッセージの希望ペイロードバイト数", - "console.components.downlink-form.downlink-form.jsonPayloadDescription": "ダウンリンクメッセージのデコードされたペイロード", - "console.components.downlink-form.downlink-form.invalidSessionWarning": "ダウンリンクは、有効なセッションを持つエンドデバイスに対してのみスケジュールすることができます。エンドデバイスがネットワークに正しく接続されていることを確認してください", - "console.components.downlink-form.index.insertMode": "", - "console.components.downlink-form.index.payloadType": "", - "console.components.downlink-form.index.bytes": "", - "console.components.downlink-form.index.replace": "", - "console.components.downlink-form.index.push": "", - "console.components.downlink-form.index.scheduleDownlink": "", - "console.components.downlink-form.index.downlinkSuccess": "", - "console.components.downlink-form.index.bytesPayloadDescription": "", - "console.components.downlink-form.index.jsonPayloadDescription": "", - "console.components.downlink-form.index.invalidSessionWarning": "", + "console.components.downlink-form.index.insertMode": "挿入モード", + "console.components.downlink-form.index.payloadType": "ペイロード・タイプ", + "console.components.downlink-form.index.bytes": "バイト", + "console.components.downlink-form.index.replace": "ダウンリンクキューの交換", + "console.components.downlink-form.index.push": "ダウンリンクキューへのプッシュ(付け加え)", + "console.components.downlink-form.index.scheduleDownlink": "スケジュール ダウンリンク", + "console.components.downlink-form.index.downlinkSuccess": "ダウンリンク予定", + "console.components.downlink-form.index.bytesPayloadDescription": "ダウンリンクメッセージの希望ペイロードバイト数", + "console.components.downlink-form.index.jsonPayloadDescription": "ダウンリンクメッセージのデコードされたペイロード", + "console.components.downlink-form.index.invalidSessionWarning": "ダウンリンクは、有効なセッションを持つエンドデバイスに対してのみスケジュールすることができます。エンドデバイスがネットワークに正しく接続されていることを確認してください", "console.components.events.messages.MACPayload": "MAC ペイロード", "console.components.events.messages.devAddr": "DevAddr", "console.components.events.messages.fPort": "FPort", @@ -352,7 +342,7 @@ "console.components.pubsub-form.messages.mqttClientIdPlaceholder": "my-client-id", "console.components.pubsub-form.messages.mqttServerUrlPlaceholder": "mqtts://example.com", "console.components.pubsub-form.messages.subscribeQos": "QoSを購読", - "console.components.pubsub-form.messages.providerDescription": "", + "console.components.pubsub-form.messages.providerDescription": "Pub/Subプロバイダーの変更が管理者により無効化", "console.components.pubsub-form.messages.publishQos": "QoSを発行", "console.components.pubsub-form.messages.tlsCa": "ルート認証局証明書", "console.components.pubsub-form.messages.tlsClientCert": "クライアント証明書", @@ -499,25 +489,17 @@ "console.containers.device-onboarding-form.warning-tooltip.sessionDescription": "ABP 装置は、セッションと MAC 設定でパーソナライズされます。これらのMAC設定は現在のパラメータとみなされ、ここで入力された設定と正確に一致しなければなりません。ネットワークサーバーは、LoRaWAN MACコマンドでMAC状態を希望する状態に変更するために希望するパラメータを使用します。エンドデバイスを登録した後に、一般設定ページを使用して希望する設定を更新することができます", "console.containers.device-payload-formatters.messages.defaultFormatter": "こちら をクリックすると、このアプリケーションのデフォルトのペイロードフォーマッターを変更できます", "console.containers.device-payload-formatters.messages.mayNotViewLink": "このアプリケーションのリンク情報を表示することは許可されていません。これには、このアプリケーションのデフォルトのペイロードフォーマッタを見ることも含まれます", - "console.containers.device-profile-section.device-card.device-card.productWebsite": "", - "console.containers.device-profile-section.device-card.device-card.dataSheet": "", - "console.containers.device-profile-section.device-card.device-card.classA": "", - "console.containers.device-profile-section.device-card.device-card.classB": "", - "console.containers.device-profile-section.device-card.device-card.classC": "", "console.containers.device-profile-section.device-card.index.productWebsite": "", "console.containers.device-profile-section.device-card.index.dataSheet": "", "console.containers.device-profile-section.device-card.index.classA": "", "console.containers.device-profile-section.device-card.index.classB": "", "console.containers.device-profile-section.device-card.index.classC": "", "console.containers.device-profile-section.device-selection.band-select.index.title": "プロフィール(リージョン)", - "console.containers.device-profile-section.device-selection.brand-select.brand-select.title": "", - "console.containers.device-profile-section.device-selection.brand-select.brand-select.noOptionsMessage": "", "console.containers.device-profile-section.device-selection.brand-select.index.title": "", "console.containers.device-profile-section.device-selection.brand-select.index.noOptionsMessage": "", "console.containers.device-profile-section.device-selection.fw-version-select.index.title": "", "console.containers.device-profile-section.device-selection.hw-version-select.index.title": "", "console.containers.device-profile-section.device-selection.model-select.index.noOptionsMessage": "", - "console.containers.device-profile-section.device-selection.model-select.model-select.noOptionsMessage": "", "console.containers.device-profile-section.hints.other-hint.hintTitle": "お客様のエンドデバイスはすぐに追加されます!", "console.containers.device-profile-section.hints.other-hint.hintMessage": "申し訳ありませんが、あなたのデバイスはまだLoRaWANデバイスリポジトリの一部ではありません。エンドデバイスの製造元が提供する情報(製品のデータシートなど)を使用して、上記のenter end device specifics manuallyオプションを使用することができます。また、デバイスの追加に関するドキュメントも参照してください", "console.containers.device-profile-section.hints.progress-hint.hintMessage": "", @@ -525,35 +507,19 @@ "console.containers.device-template-format-select.index.title": "ファイルフォーマット", "console.containers.device-template-format-select.index.warning": "エンドデバイスのテンプレートフォーマットが利用できません", "console.containers.device-title-section.index.uplinkDownlinkTooltip": "前回のフレームカウンタリセット以降、このエンドデバイスの送信アップリンクと受信ダウンリンクの数です", - "console.containers.device-title-section.index.lastSeenAvailableTooltip": "ネットワークがこのゲートウェイの最後のアクティビティを登録してから経過した時間です。これは、このゲートウェイの受信したアップリンク、または送信したステータスメッセージから決定されます", - "console.containers.device-title-section.index.noActivityTooltip": "", + "console.containers.device-title-section.index.lastSeenAvailableTooltip": "ネットワークがこのエンドデバイスの最後のアクティビティを登録してから経過した時間です。これは、送信されたアップリンク、確認されたダウンリンク、または(再)参加要求から判断されます。{lineBreak}最後のアクティビティは{lastActivityInfo}で受信します", + "console.containers.device-title-section.index.noActivityTooltip": "ネットワークは、このエンドデバイスからのアクティビティをまだ登録していません。これは、エンドデバイスがまだメッセージを送信していないか、EUIや周波数の不一致など、ネットワークで処理できないメッセージしか送信していないことを意味する可能性があります", "console.containers.devices-table.index.otherClusterTooltip": "このエンドデバイスは、別のクラスタ(`{host}`)に登録されています。このデバイスにアクセスするには、このエンドデバイスが登録されているクラスタのコンソールを使用します", "console.containers.freq-plans-select.utils.warning": "", "console.containers.freq-plans-select.utils.none": "", "console.containers.freq-plans-select.utils.selectFrequencyPlan": "", "console.containers.freq-plans-select.utils.addFrequencyPlan": "", "console.containers.freq-plans-select.utils.frequencyPlanDescription": "", - "console.containers.gateway-connection.gateway-connection.lastSeenAvailableTooltip": "", - "console.containers.gateway-connection.gateway-connection.disconnectedTooltip": "ゲートウェイは現在、ゲートウェイサーバーとの TCP 接続を確立していません。まれに)UDPベースのゲートウェイの場合、これはゲートウェイが過去30秒以内にpull/pushデータ要求を開始しなかったことを意味することもあります", - "console.containers.gateway-connection.gateway-connection.connectedTooltip": "このゲートウェイはゲートウェイサーバーに接続されていますが、ネットワークはまだゲートウェイからのアクティビティ(アップリンクやステータスメッセージの送信)を登録していません", - "console.containers.gateway-connection.gateway-connection.otherClusterTooltip": "このゲートウェイは、このクラスタのメッセージを処理しない外部のゲートウェイサーバーに接続されています。そのため、このゲートウェイからのアクティビティを見ることはできません", - "console.containers.gateway-connection.gateway-connection.messageCountTooltip": "最後の(再)接続以降、このゲートウェイの受信アップリンクと送信ダウンリンクの量です。ゲートウェイの種類によっては、頻繁に再接続するため、カウンタがリセットされることに注意してください", - "console.containers.gateway-connection.index.lastSeenAvailableTooltip": "", - "console.containers.gateway-connection.index.disconnectedTooltip": "", - "console.containers.gateway-connection.index.connectedTooltip": "", - "console.containers.gateway-connection.index.otherClusterTooltip": "", - "console.containers.gateway-connection.index.messageCountTooltip": "", - "console.containers.gateway-location-form.gateway-location-form.updateLocationFromStatus": "", - "console.containers.gateway-location-form.gateway-location-form.updateLocationFromStatusDescription": "", - "console.containers.gateway-location-form.gateway-location-form.setGatewayLocation": "", - "console.containers.gateway-location-form.gateway-location-form.locationSource": "", - "console.containers.gateway-location-form.gateway-location-form.locationPrivacy": "", - "console.containers.gateway-location-form.gateway-location-form.placement": "", - "console.containers.gateway-location-form.gateway-location-form.indoor": "", - "console.containers.gateway-location-form.gateway-location-form.outdoor": "", - "console.containers.gateway-location-form.gateway-location-form.locationFromStatusMessage": "", - "console.containers.gateway-location-form.gateway-location-form.setLocationManually": "", - "console.containers.gateway-location-form.gateway-location-form.noLocationSetInfo": "", + "console.containers.gateway-connection.index.lastSeenAvailableTooltip": "ネットワークがこのゲートウェイの最後のアクティビティを登録してから経過した時間です。これは、このゲートウェイの受信したアップリンク、または送信したステータスメッセージから決定されます", + "console.containers.gateway-connection.index.disconnectedTooltip": "ゲートウェイは現在、ゲートウェイサーバーとの TCP 接続を確立していません。まれに)UDPベースのゲートウェイの場合、これはゲートウェイが過去30秒以内にpull/pushデータ要求を開始しなかったことを意味することもあります", + "console.containers.gateway-connection.index.connectedTooltip": "このゲートウェイはゲートウェイサーバーに接続されていますが、ネットワークはまだゲートウェイからのアクティビティ(アップリンクやステータスメッセージの送信)を登録していません", + "console.containers.gateway-connection.index.otherClusterTooltip": "このゲートウェイは、このクラスタのメッセージを処理しない外部のゲートウェイサーバーに接続されています。そのため、このゲートウェイからのアクティビティを見ることはできません", + "console.containers.gateway-connection.index.messageCountTooltip": "最後の(再)接続以降、このゲートウェイの受信アップリンクと送信ダウンリンクの量です。ゲートウェイの種類によっては、頻繁に再接続するため、カウンタがリセットされることに注意してください", "console.containers.gateway-location-form.index.updateLocationFromStatus": "", "console.containers.gateway-location-form.index.updateLocationFromStatusDescription": "", "console.containers.gateway-location-form.index.setGatewayLocation": "",