From 8cd0b621fecdc27f36b6f2774f0faca4705bde90 Mon Sep 17 00:00:00 2001 From: Darya Plotnytska Date: Thu, 5 Sep 2024 11:54:49 +0200 Subject: [PATCH 01/10] console: Add scran qr code section to gateway registration --- .../gateway-onboarding-form/index.js | 2 + .../qr-scan-section/index.js | 171 ++++++++++++++++++ pkg/webui/console/views/gateway-add/index.js | 1 - 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 pkg/webui/console/containers/gateway-onboarding-form/qr-scan-section/index.js diff --git a/pkg/webui/console/containers/gateway-onboarding-form/index.js b/pkg/webui/console/containers/gateway-onboarding-form/index.js index 6f0af90b27..f990a82b79 100644 --- a/pkg/webui/console/containers/gateway-onboarding-form/index.js +++ b/pkg/webui/console/containers/gateway-onboarding-form/index.js @@ -33,6 +33,7 @@ import GatewayProvisioningFormSection from './gateway-provisioning-form' import validationSchema from './gateway-provisioning-form/validation-schema' import { initialValues as registerInitialValues } from './gateway-provisioning-form/gateway-registration-form-section' import { initialValues as claimingInitialValues } from './gateway-provisioning-form/gateway-claim-form-section' +import GatewayQRScanSection from './qr-scan-section' const GatewayOnboardingForm = props => { const { onSuccess } = props @@ -190,6 +191,7 @@ const GatewayOnboardingForm = props => { validationSchema={validationSchema} validateAgainstCleanedValues > + diff --git a/pkg/webui/console/containers/gateway-onboarding-form/qr-scan-section/index.js b/pkg/webui/console/containers/gateway-onboarding-form/qr-scan-section/index.js new file mode 100644 index 0000000000..26f66bc55c --- /dev/null +++ b/pkg/webui/console/containers/gateway-onboarding-form/qr-scan-section/index.js @@ -0,0 +1,171 @@ +// 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, useState } from 'react' +import { useDispatch } from 'react-redux' +import { defineMessages } from 'react-intl' + +import QRModalButton from '@ttn-lw/components/qr-modal-button' +import { useFormContext } from '@ttn-lw/components/form' +import Icon from '@ttn-lw/components/icon' +import ModalButton from '@ttn-lw/components/button/modal-button' +import ButtonGroup from '@ttn-lw/components/button/group' + +import Message from '@ttn-lw/lib/components/message' + +import attachPromise from '@ttn-lw/lib/store/actions/attach-promise' +import sharedMessages from '@ttn-lw/lib/shared-messages' + +// TODO: Get the correct sdk (gateway qr code generator) +import { parseGatewayQRCode } from '@console/store/actions/qr-code-generator' + +const m = defineMessages({ + hasGatewayQR: + 'Does your gateway have a LoRaWAN® Gateway Identification QR Code? Scan it to speed up onboarding.', + gatewayGuide: 'Gateway registration help', + invalidQRCode: + 'Invalid QR code data. Please note that only TTIGPRO1 Gateway Identification QR Code can be scanned. Some gateways have unrelated QR codes printed on them that cannot be used.', +}) + +const qrDataInitialState = { + valid: false, + approved: false, + data: undefined, + gateway: undefined, +} + +const GatewayQRScanSection = () => { + const dispatch = useDispatch() + const { resetForm, setValues } = useFormContext() + const [qrData, setQrData] = useState(qrDataInitialState) + + const handleReset = useCallback(() => { + resetForm() + setQrData(qrDataInitialState) + }, [resetForm]) + + const handleQRCodeApprove = useCallback(() => { + const { + gateway: { claim_gateway_request }, + } = qrData + + setValues(values => ({ + ...values, + _withQRdata: true, + ids: { + ...values.ids, + eui: claim_gateway_request.authenticated_identifiers.gateway_eui, + }, + authenticated_identifiers: { + gateway_eui: claim_gateway_request.authenticated_identifiers.gateway_eui, + authentication_code: claim_gateway_request.authenticated_identifiers.authentication_code + ? claim_gateway_request.authenticated_identifiers.authentication_code + : '', + }, + })) + + setQrData({ ...qrData, approved: true }) + }, [qrData, setValues]) + + const handleQRCodeCancel = useCallback(() => { + setQrData(qrDataInitialState) + }, []) + + const handleQRCodeRead = useCallback( + async qrCode => { + try { + // Get gateway from QR code + const gateway = await dispatch(attachPromise(parseGatewayQRCode(qrCode))) + const { + claim_gateway_request: { authenticated_identifiers }, + } = gateway + + const sheetData = [ + { + header: sharedMessages.qrCodeData, + items: [ + { + key: sharedMessages.claimAuthCode, + value: authenticated_identifiers.authentication_code, + type: 'code', + sensitive: true, + }, + { + key: sharedMessages.gatewayEUI, + value: authenticated_identifiers.gateway_eui, + type: 'byte', + sensitive: false, + }, + ], + }, + ] + setQrData({ + ...qrData, + valid: true, + data: sheetData, + gateway, + }) + } catch (error) { + setQrData({ ...qrData, data: [], valid: false }) + } + }, + [dispatch, qrData], + ) + + return ( + <> + {qrData.approved ? ( +
+ + +
+ ) : ( +
+ +
+ )} + + {qrData.approved ? ( + , + approveButtonProps: { + icon: 'close', + }, + }} + /> + ) : ( + + )} + +
+ + ) +} + +export default GatewayQRScanSection diff --git a/pkg/webui/console/views/gateway-add/index.js b/pkg/webui/console/views/gateway-add/index.js index 60c15afd09..aa661680d6 100644 --- a/pkg/webui/console/views/gateway-add/index.js +++ b/pkg/webui/console/views/gateway-add/index.js @@ -73,7 +73,6 @@ const GatewayAdd = () => { content={m.gtwOnboardingDescription} values={{ Link: GatewayGuideLink, break:
}} /> -
From 90fbda721964b48142067582fea92677ab6c68b2 Mon Sep 17 00:00:00 2001 From: Darya Plotnytska Date: Thu, 5 Sep 2024 11:57:50 +0200 Subject: [PATCH 02/10] console: Add gateway qr code generator sdk --- .../store/actions/qr-code-generator.js | 30 ++++++++++++++++--- .../middleware/logics/qr-code-generator.js | 16 +++++++--- sdk/js/src/service/qr-code-generator.js | 10 ++++++- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/pkg/webui/console/store/actions/qr-code-generator.js b/pkg/webui/console/store/actions/qr-code-generator.js index 9eccdbf25e..7ea712f5fd 100644 --- a/pkg/webui/console/store/actions/qr-code-generator.js +++ b/pkg/webui/console/store/actions/qr-code-generator.js @@ -14,8 +14,30 @@ import createRequestActions from '@ttn-lw/lib/store/actions/create-request-actions' -const PARSE_QR_CODE_BASE = 'PARSE_QR_CODE' +const PARSE_END_DEVICE_QR_CODE_BASE = 'PARSE_END_DEVICE_QR_CODE' export const [ - { request: PARSE_QR_CODE, success: PARSE_QR_CODE_SUCCESS, failure: PARSE_QR_CODE_FAILURE }, - { request: parseQRCode, success: parseQRCodeSuccess, failure: parseQRCodeFailure }, -] = createRequestActions(PARSE_QR_CODE_BASE, qrCode => ({ qrCode })) + { + request: PARSE_END_DEVICE_QR_CODE, + success: PARSE_END_DEVICE_QR_CODE_SUCCESS, + failure: PARSE_END_DEVICE_QR_CODE_FAILURE, + }, + { + request: parseEndDeviceQRCode, + success: parseEndDeviceQRCodeSuccess, + failure: parseEndDeviceQRCodeFailure, + }, +] = createRequestActions(PARSE_END_DEVICE_QR_CODE_BASE, qrCode => ({ qrCode })) + +const PARSE_GATEWAY_QR_CODE_BASE = 'PARSE_GATEWAY_QR_CODE' +export const [ + { + request: PARSE_GATEWAY_QR_CODE, + success: PARSE_GATEWAY_QR_CODE_SUCCESS, + failure: PARSE_GATEWAY_QR_CODE_FAILURE, + }, + { + request: parseGatewayQRCode, + success: parseGatewayQRCodeSuccess, + failure: parseGatewayQRCodeFailure, + }, +] = createRequestActions(PARSE_GATEWAY_QR_CODE_BASE, qrCode => ({ qrCode })) diff --git a/pkg/webui/console/store/middleware/logics/qr-code-generator.js b/pkg/webui/console/store/middleware/logics/qr-code-generator.js index 0b7bec66e8..e5e6127467 100644 --- a/pkg/webui/console/store/middleware/logics/qr-code-generator.js +++ b/pkg/webui/console/store/middleware/logics/qr-code-generator.js @@ -18,12 +18,20 @@ import createRequestLogic from '@ttn-lw/lib/store/logics/create-request-logic' import * as QRCodeGenerator from '@console/store/actions/qr-code-generator' -const parseQRCodeLogic = createRequestLogic({ - type: QRCodeGenerator.PARSE_QR_CODE, +const parseEndDeviceQRCodeLogic = createRequestLogic({ + type: QRCodeGenerator.PARSE_END_DEVICE_QR_CODE, process: async ({ action }) => { const { qrCode } = action.payload - return await tts.QRCodeGenerator.parse(qrCode) + return await tts.QRCodeGenerator.parseEndDeviceQrCode(qrCode) }, }) -export default [parseQRCodeLogic] +const parseGatewayQRCodeLogic = createRequestLogic({ + type: QRCodeGenerator.PARSE_GATEWAY_QR_CODE, + process: async ({ action }) => { + const { qrCode } = action.payload + return await tts.QRCodeGenerator.parseGatewayQrCode(qrCode) + }, +}) + +export default [parseEndDeviceQRCodeLogic, parseGatewayQRCodeLogic] diff --git a/sdk/js/src/service/qr-code-generator.js b/sdk/js/src/service/qr-code-generator.js index 7b86f9d66f..fad3291dcd 100644 --- a/sdk/js/src/service/qr-code-generator.js +++ b/sdk/js/src/service/qr-code-generator.js @@ -23,13 +23,21 @@ class QRCodeGenerator { autoBind(this) } - async parse(qrCode) { + async parseEndDeviceQrCode(qrCode) { const response = await this._api.EndDeviceQRCodeGenerator.Parse(undefined, { qr_code: btoa(qrCode), }) return Marshaler.payloadSingleResponse(response) } + + async parseGatewayQrCode(qrCode) { + const response = await this._api.GatewayQRCodeGenerator.Parse(undefined, { + qr_code: btoa(qrCode), + }) + + return Marshaler.payloadSingleResponse(response) + } } export default QRCodeGenerator From d1d33fa48b8e8bf6540d50ce2427501f46b82b63 Mon Sep 17 00:00:00 2001 From: Darya Plotnytska Date: Thu, 5 Sep 2024 11:58:35 +0200 Subject: [PATCH 03/10] console: Use qr code data in gateway form --- .../gateway-claim-form-section/index.js | 2 ++ .../gateway-provisioning-form/index.js | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/webui/console/containers/gateway-onboarding-form/gateway-provisioning-form/gateway-claim-form-section/index.js b/pkg/webui/console/containers/gateway-onboarding-form/gateway-provisioning-form/gateway-claim-form-section/index.js index 06b676ce69..fd1782b251 100644 --- a/pkg/webui/console/containers/gateway-onboarding-form/gateway-provisioning-form/gateway-claim-form-section/index.js +++ b/pkg/webui/console/containers/gateway-onboarding-form/gateway-provisioning-form/gateway-claim-form-section/index.js @@ -52,6 +52,7 @@ const initialValues = { const GatewayClaimFormSection = () => { const { values, addToFieldRegistry, removeFromFieldRegistry } = useFormikContext() const isManaged = values._inputMethod === 'managed' + const withQRdata = values._withQRdata // Register hidden fields so they don't get cleaned. useEffect(() => { @@ -83,6 +84,7 @@ const GatewayClaimFormSection = () => { component={Input} encode={btoa} decode={atob} + disabled={withQRdata} autoFocus /> { values: { _ownerId: ownerId, _inputMethod: inputMethod, + _withQRdata: withQRdata, ids: { eui = '' }, }, initialValues, @@ -120,6 +121,13 @@ const GatewayProvisioningFormSection = () => { } }, [dispatch, eui, hasEmptyEui, setFieldValue]) + useEffect(() => { + // Auto-confirm the join EUI when using QR code data. + if (withQRdata) { + handleGatewayEUI() + } + }, [withQRdata, handleGatewayEUI]) + const handleEuiReset = useCallback(async () => { setEuiError(undefined) resetForm({ values: { ...initialValues, _ownerId: ownerId } }) @@ -162,7 +170,7 @@ const GatewayProvisioningFormSection = () => { component={Input} tooltipId={tooltipIds.GATEWAY_EUI} required={inputMethod !== 'register'} - disabled={hasInputMethod} + disabled={hasInputMethod || withQRdata} onKeyDown={handleGatewayEUIKeydown} encode={gatewayEuiEncoder} decode={gatewayEuiDecoder} @@ -184,6 +192,7 @@ const GatewayProvisioningFormSection = () => { message={sharedMessages.reset} onClick={handleEuiReset} secondary + disabled={withQRdata} /> ) : (
@@ -80,7 +74,7 @@ const QRModalButton = props => { onApprove={onApprove} message={message} modalData={{ - title: sharedMessages.scanEndDevice, + title: message, children: modalData, buttonMessage: m.apply, approveButtonProps: { @@ -95,6 +89,7 @@ const QRModalButton = props => { } QRModalButton.propTypes = { + invalidMessage: PropTypes.message.isRequired, message: PropTypes.message.isRequired, onApprove: PropTypes.func.isRequired, onCancel: PropTypes.func.isRequired, diff --git a/pkg/webui/components/qr/require-permission.js b/pkg/webui/components/qr/require-permission.js index a72522c7ab..24e43509ae 100644 --- a/pkg/webui/components/qr/require-permission.js +++ b/pkg/webui/components/qr/require-permission.js @@ -18,6 +18,7 @@ import { defineMessages } from 'react-intl' import ErrorMessage from '@ttn-lw/lib/components/error-message' import PropTypes from '@ttn-lw/lib/prop-types' +import sharedMessages from '@ttn-lw/lib/shared-messages' import Button from '../button' @@ -113,7 +114,12 @@ const RequirePermission = props => {

-
) } diff --git a/pkg/webui/console/containers/device-onboarding-form/messages.js b/pkg/webui/console/containers/device-onboarding-form/messages.js index 0e1b7999f2..8b99e68eb6 100644 --- a/pkg/webui/console/containers/device-onboarding-form/messages.js +++ b/pkg/webui/console/containers/device-onboarding-form/messages.js @@ -49,9 +49,6 @@ export default defineMessages({ hasEndDeviceQR: 'Does your end device have a LoRaWAN® Device Identification QR Code? Scan it to speed up onboarding.', deviceGuide: 'Device registration help', - deviceInfo: 'Found QR code data', - resetQRCodeData: 'Reset QR code data', - resetConfirm: - 'Are you sure you want to discard QR code data? The scanned device will not be registered and the form will be reset.', - scanSuccess: 'QR code scanned successfully', + invalidData: + 'Invalid QR code data. Please note that only TR005 LoRaWAN® Device Identification QR Code can be scanned. Some devices have unrelated QR codes printed on them that cannot be used.', }) diff --git a/pkg/webui/console/containers/device-onboarding-form/qr-scan-form-section/index.js b/pkg/webui/console/containers/device-onboarding-form/qr-scan-form-section/index.js index 86d81aa1ae..ab64fd22ea 100644 --- a/pkg/webui/console/containers/device-onboarding-form/qr-scan-form-section/index.js +++ b/pkg/webui/console/containers/device-onboarding-form/qr-scan-form-section/index.js @@ -27,7 +27,7 @@ import Message from '@ttn-lw/lib/components/message' import attachPromise from '@ttn-lw/lib/store/actions/attach-promise' import sharedMessages from '@ttn-lw/lib/shared-messages' -import { parseQRCode } from '@console/store/actions/qr-code-generator' +import { parseEndDeviceQRCode } from '@console/store/actions/qr-code-generator' import { selectDeviceBrands } from '@console/store/selectors/device-repository' @@ -101,7 +101,7 @@ const DeviceQRScanFormSection = () => { async qrCode => { try { // Get end device template from QR code - const device = await dispatch(attachPromise(parseQRCode(qrCode))) + const device = await dispatch(attachPromise(parseEndDeviceQRCode(qrCode))) const { end_device } = device.end_device_template const { lora_alliance_profile_ids } = end_device @@ -109,7 +109,7 @@ const DeviceQRScanFormSection = () => { const brand = getBrand(lora_alliance_profile_ids.vendor_id) const sheetData = [ { - header: m.deviceInfo, + header: sharedMessages.qrCodeData, items: [ { key: sharedMessages.claimAuthCode, @@ -151,7 +151,7 @@ const DeviceQRScanFormSection = () => { {qrData.approved ? (
- +
) : (
@@ -164,12 +164,12 @@ const DeviceQRScanFormSection = () => { type="button" icon={IconX} onApprove={handleReset} - message={m.resetQRCodeData} + message={sharedMessages.qrCodeDataReset} modalData={{ - title: m.resetQRCodeData, + title: sharedMessages.qrCodeDataReset, noTitleLine: true, - buttonMessage: m.resetQRCodeData, - children: , + buttonMessage: sharedMessages.qrCodeDataReset, + children: , approveButtonProps: { icon: IconX, }, @@ -178,6 +178,7 @@ const DeviceQRScanFormSection = () => { ) : ( desired value of {value} for this property.", + "console.containers.device-onboarding-form.warning-tooltip.sessionDescription": "ABP 装置は、セッションと MAC 設定でパーソナライズされます。これらのMAC設定は現在のパラメータとみなされ、ここで入力された設定と正確に一致しなければなりません。ネットワークサーバーは、LoRaWAN MACコマンドでMAC状態を希望する状態に変更するために希望するパラメータを使用します。エンドデバイスを登録した後に、一般設定ページを使用して希望する設定を更新することができます", "console.containers.device-overview-header.index.addBookmarkFail": "", "console.containers.device-overview-header.index.removeBookmarkFail": "", "console.containers.device-overview-header.index.uplinkDownlinkTooltip": "", @@ -656,10 +656,14 @@ "console.containers.gateway-status-panel.transmissions.noDataYet": "", "console.containers.gateway-status-panel.transmissions.established": "", "console.containers.gateway-status-panel.transmissions.notEstablished": "", - "console.containers.gateways-table.index.restoreSuccess": "", - "console.containers.gateways-table.index.restoreFail": "", - "console.containers.gateways-table.index.purgeSuccess": "", - "console.containers.gateways-table.index.purgeFail": "", + "console.containers.gateway-onboarding-form.qr-scan-section.index.hasGatewayQR": "", + "console.containers.gateway-onboarding-form.qr-scan-section.index.gatewayGuide": "", + "console.containers.gateway-onboarding-form.qr-scan-section.index.invalidQRCode": "", + "console.containers.gateways-table.index.ownedTabTitle": "所有するゲートウェイ", + "console.containers.gateways-table.index.restoreSuccess": "ゲートウェイが復旧", + "console.containers.gateways-table.index.restoreFail": "エラーが発生し、ゲートウェイを復元することができませんでした", + "console.containers.gateways-table.index.purgeSuccess": "ゲートウェイをパージ", + "console.containers.gateways-table.index.purgeFail": "エラーが発生したため、ゲートウェイをパージすることができませんでした", "console.containers.header.bookmarks-dropdown.noBookmarks": "", "console.containers.header.bookmarks-dropdown.noBookmarksDescription": "", "console.containers.header.bookmarks-dropdown.threshold": "", @@ -1607,6 +1611,11 @@ "lib.shared-messages.purge": "パージ(消去)", "lib.shared-messages.quicklyTroubleshoot": "", "lib.shared-messages.received": "", + "lib.shared-messages.qrCodeData": "", + "lib.shared-messages.qrCodeDataReset": "", + "lib.shared-messages.resetConfirm": "", + "lib.shared-messages.scanSuccess": "", + "lib.shared-messages.scanGatewayQR": "", "lib.shared-messages.redirecting": "リダイレクト中...", "lib.shared-messages.refresh": "リフレッシュ", "lib.shared-messages.registerEndDevice": "エンドデバイスの登録", From ec81b256b48eefe9a51b623ee3b25312b7e76289 Mon Sep 17 00:00:00 2001 From: Darya Plotnytska Date: Thu, 5 Sep 2024 12:12:21 +0200 Subject: [PATCH 05/10] dev: Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9eecd0e08..7b1f9d7dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ For details about compatibility between different releases, see the **Commitment - Option to pause application webhooks. - Endpoint for claiming gateways using a qr code - Update the GetTemplate endpoint in device repository to check for profile identifiers in the vendor index. +- Support for claiming a gateway via QR code in the Console. ### Changed From c2bce683c59b5f136763c9ae379842a8241b8626 Mon Sep 17 00:00:00 2001 From: Darya Plotnytska Date: Tue, 10 Sep 2024 14:51:09 +0200 Subject: [PATCH 06/10] console: Fix gateway values --- .../qr-scan-section/index.js | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/pkg/webui/console/containers/gateway-onboarding-form/qr-scan-section/index.js b/pkg/webui/console/containers/gateway-onboarding-form/qr-scan-section/index.js index 26f66bc55c..ec26c41f1a 100644 --- a/pkg/webui/console/containers/gateway-onboarding-form/qr-scan-section/index.js +++ b/pkg/webui/console/containers/gateway-onboarding-form/qr-scan-section/index.js @@ -56,22 +56,18 @@ const GatewayQRScanSection = () => { }, [resetForm]) const handleQRCodeApprove = useCallback(() => { - const { - gateway: { claim_gateway_request }, - } = qrData + const { gateway } = qrData setValues(values => ({ ...values, _withQRdata: true, ids: { ...values.ids, - eui: claim_gateway_request.authenticated_identifiers.gateway_eui, + eui: gateway.gateway_eui, }, authenticated_identifiers: { - gateway_eui: claim_gateway_request.authenticated_identifiers.gateway_eui, - authentication_code: claim_gateway_request.authenticated_identifiers.authentication_code - ? claim_gateway_request.authenticated_identifiers.authentication_code - : '', + gateway_eui: gateway.gateway_eui, + authentication_code: gateway.owner_token ? gateway.owner_token : '', }, })) @@ -87,9 +83,6 @@ const GatewayQRScanSection = () => { try { // Get gateway from QR code const gateway = await dispatch(attachPromise(parseGatewayQRCode(qrCode))) - const { - claim_gateway_request: { authenticated_identifiers }, - } = gateway const sheetData = [ { @@ -97,13 +90,13 @@ const GatewayQRScanSection = () => { items: [ { key: sharedMessages.claimAuthCode, - value: authenticated_identifiers.authentication_code, + value: gateway.owner_token, type: 'code', sensitive: true, }, { key: sharedMessages.gatewayEUI, - value: authenticated_identifiers.gateway_eui, + value: gateway.gateway_eui, type: 'byte', sensitive: false, }, From 120e193ca7d0ac720b07ec19cc82fd59d5d8eb30 Mon Sep 17 00:00:00 2001 From: Darya Plotnytska Date: Tue, 10 Sep 2024 14:52:59 +0200 Subject: [PATCH 07/10] console: Update messages --- pkg/webui/locales/en.json | 7 +++---- pkg/webui/locales/ja.json | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/pkg/webui/locales/en.json b/pkg/webui/locales/en.json index d97b8b3e22..75319472c4 100644 --- a/pkg/webui/locales/en.json +++ b/pkg/webui/locales/en.json @@ -628,6 +628,9 @@ "console.containers.gateway-onboarding-form.gateway-provisioning-form.index.continue": "To continue, please confirm the Gateway EUI so we can determine onboarding options", "console.containers.gateway-onboarding-form.gateway-provisioning-form.index.emtyEui": "Continue without EUI", "console.containers.gateway-onboarding-form.gateway-provisioning-form.index.noEui": "No gateway EUI", + "console.containers.gateway-onboarding-form.qr-scan-section.index.hasGatewayQR": "Does your gateway have a LoRaWAN® Gateway Identification QR Code? Scan it to speed up onboarding.", + "console.containers.gateway-onboarding-form.qr-scan-section.index.gatewayGuide": "Gateway registration help", + "console.containers.gateway-onboarding-form.qr-scan-section.index.invalidQRCode": "Invalid QR code data. Please note that only TTIGPRO1 Gateway Identification QR Code can be scanned. Some gateways have unrelated QR codes printed on them that cannot be used.", "console.containers.gateway-overview-header.index.addBookmarkFail": "There was an error and the gateway could not be bookmarked", "console.containers.gateway-overview-header.index.duplicateGateway": "Duplicate gateway", "console.containers.gateway-overview-header.index.removeBookmarkFail": "There was an error and the gateway could not be removed from bookmarks", @@ -652,10 +655,6 @@ "console.containers.gateway-status-panel.transmissions.noDataYet": "No data yet", "console.containers.gateway-status-panel.transmissions.established": "Connection started {days}", "console.containers.gateway-status-panel.transmissions.notEstablished": "Connection not started", - "console.containers.gateway-onboarding-form.qr-scan-section.index.hasGatewayQR": "Does your gateway have a LoRaWAN® Gateway Identification QR Code? Scan it to speed up onboarding.", - "console.containers.gateway-onboarding-form.qr-scan-section.index.gatewayGuide": "Gateway registration help", - "console.containers.gateway-onboarding-form.qr-scan-section.index.invalidQRCode": "Invalid QR code data. Please note that only TTIGPRO1 Gateway Identification QR Code can be scanned. Some gateways have unrelated QR codes printed on them that cannot be used.", - "console.containers.gateways-table.index.ownedTabTitle": "Owned gateways", "console.containers.gateways-table.index.restoreSuccess": "Gateway restored", "console.containers.gateways-table.index.restoreFail": "There was an error and the gateway could not be restored", "console.containers.gateways-table.index.purgeSuccess": "Gateway purged", diff --git a/pkg/webui/locales/ja.json b/pkg/webui/locales/ja.json index cf24a822aa..3f66442a26 100644 --- a/pkg/webui/locales/ja.json +++ b/pkg/webui/locales/ja.json @@ -632,6 +632,9 @@ "console.containers.gateway-onboarding-form.gateway-provisioning-form.index.continue": "", "console.containers.gateway-onboarding-form.gateway-provisioning-form.index.emtyEui": "", "console.containers.gateway-onboarding-form.gateway-provisioning-form.index.noEui": "", + "console.containers.gateway-onboarding-form.qr-scan-section.index.hasGatewayQR": "", + "console.containers.gateway-onboarding-form.qr-scan-section.index.gatewayGuide": "", + "console.containers.gateway-onboarding-form.qr-scan-section.index.invalidQRCode": "", "console.containers.gateway-overview-header.index.addBookmarkFail": "", "console.containers.gateway-overview-header.index.duplicateGateway": "", "console.containers.gateway-overview-header.index.removeBookmarkFail": "", @@ -656,10 +659,6 @@ "console.containers.gateway-status-panel.transmissions.noDataYet": "", "console.containers.gateway-status-panel.transmissions.established": "", "console.containers.gateway-status-panel.transmissions.notEstablished": "", - "console.containers.gateway-onboarding-form.qr-scan-section.index.hasGatewayQR": "", - "console.containers.gateway-onboarding-form.qr-scan-section.index.gatewayGuide": "", - "console.containers.gateway-onboarding-form.qr-scan-section.index.invalidQRCode": "", - "console.containers.gateways-table.index.ownedTabTitle": "所有するゲートウェイ", "console.containers.gateways-table.index.restoreSuccess": "ゲートウェイが復旧", "console.containers.gateways-table.index.restoreFail": "エラーが発生し、ゲートウェイを復元することができませんでした", "console.containers.gateways-table.index.purgeSuccess": "ゲートウェイをパージ", From fa211dc418d8f90038ac9372a23c9460fbc96e84 Mon Sep 17 00:00:00 2001 From: Johan Stokking Date: Tue, 10 Sep 2024 17:00:39 +0200 Subject: [PATCH 08/10] qrg: Fix length check for TTIGPRO1 --- pkg/qrcodegenerator/qrcode/gateways/gateways.go | 2 +- pkg/qrcodegenerator/qrcode/gateways/ttigpro1.go | 10 +++++----- pkg/qrcodegenerator/qrcode/gateways/ttigpro1_test.go | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/qrcodegenerator/qrcode/gateways/gateways.go b/pkg/qrcodegenerator/qrcode/gateways/gateways.go index 06570c6ef4..a11e5e4520 100644 --- a/pkg/qrcodegenerator/qrcode/gateways/gateways.go +++ b/pkg/qrcodegenerator/qrcode/gateways/gateways.go @@ -123,5 +123,5 @@ func (s *Server) Parse(formatID string, data []byte) (ret Data, err error) { return f, nil } - return nil, errUnknownFormat + return nil, errUnknownFormat.New() } diff --git a/pkg/qrcodegenerator/qrcode/gateways/ttigpro1.go b/pkg/qrcodegenerator/qrcode/gateways/ttigpro1.go index 96f5e1dccb..a43b980e8d 100644 --- a/pkg/qrcodegenerator/qrcode/gateways/ttigpro1.go +++ b/pkg/qrcodegenerator/qrcode/gateways/ttigpro1.go @@ -26,8 +26,8 @@ const ( ) // ttigpro1Regex is the regular expression to match the TTIGPRO1 format. -// The format is as follows: https://ttig.pro/c/{16 lowercase base16 chars}/{12 base62 chars}. -var ttigpro1Regex = regexp.MustCompile(`^https://ttig\.pro/c/([a-f0-9]{16})/([a-z0-9]{12})$`) +// The format is as follows: https://ttig.pro/c/{16 lowercase base16 chars}/{8+ base32 chars}. +var ttigpro1Regex = regexp.MustCompile(`^https://ttig\.pro/c/([a-f0-9]{16})/([a-z0-9]{8,})$`) // TTIGPRO1 is a format for gateway identification QR codes. type ttigpro1 struct { @@ -40,7 +40,7 @@ func (m *ttigpro1) UnmarshalText(text []byte) error { // Match the URL against the pattern matches := ttigpro1Regex.FindStringSubmatch(string(text)) if matches == nil || len(matches) != 3 { - return errInvalidFormat + return errInvalidFormat.New() } if err := m.gatewayEUI.UnmarshalText([]byte(matches[1])); err != nil { @@ -49,8 +49,8 @@ func (m *ttigpro1) UnmarshalText(text []byte) error { m.ownerToken = matches[2] - if len(m.ownerToken) != 12 /* owner token length */ { - return errInvalidLength + if len(m.ownerToken) < 8 { + return errInvalidLength.New() } return nil diff --git a/pkg/qrcodegenerator/qrcode/gateways/ttigpro1_test.go b/pkg/qrcodegenerator/qrcode/gateways/ttigpro1_test.go index 2d404a7495..e4ccabe59a 100644 --- a/pkg/qrcodegenerator/qrcode/gateways/ttigpro1_test.go +++ b/pkg/qrcodegenerator/qrcode/gateways/ttigpro1_test.go @@ -37,10 +37,10 @@ func TestTTIGPRO1(t *testing.T) { }{ { Name: "CorrectQRCode", - Data: []byte("https://ttig.pro/c/ec656efffe000128/abcdef123456"), + Data: []byte("https://ttig.pro/c/ec656efffe000128/abcdef12"), Expected: ttigpro1{ gatewayEUI: types.EUI64{0xec, 0x65, 0x6e, 0xff, 0xfe, 0x00, 0x01, 0x28}, - ownerToken: "abcdef123456", + ownerToken: "abcdef12", }, }, { @@ -77,7 +77,7 @@ func TestTTIGPRO1(t *testing.T) { }, { Name: "Invalid/OwnerTokenLength", - Data: []byte("https://ttig.pro/c/ec656efffe000128/abcdef123"), + Data: []byte("https://ttig.pro/c/ec656efffe000128/abcdef"), ErrorAssertion: func(t *testing.T, err error) bool { t.Helper() return assertions.New(t).So(errors.IsInvalidArgument(err), should.BeTrue) From 7b5025783f0aff75044816b607d458854ba030ae Mon Sep 17 00:00:00 2001 From: Darya Plotnytska Date: Wed, 11 Sep 2024 11:54:32 +0200 Subject: [PATCH 09/10] console: Fix modal styling --- pkg/webui/components/modal/modal.styl | 2 +- pkg/webui/components/qr-modal-button/index.js | 7 ++++++- pkg/webui/components/qr/input/video/index.js | 2 +- pkg/webui/components/qr/qr.styl | 4 ++-- pkg/webui/components/qr/require-permission.js | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/pkg/webui/components/modal/modal.styl b/pkg/webui/components/modal/modal.styl index 3908ef6516..272cdc7ec8 100644 --- a/pkg/webui/components/modal/modal.styl +++ b/pkg/webui/components/modal/modal.styl @@ -28,7 +28,7 @@ max-height: 80vh +media-query-min($bp.sm) - min-width: 500px + min-width: 385px max-width: 780px +media-query($bp.sm) diff --git a/pkg/webui/components/qr-modal-button/index.js b/pkg/webui/components/qr-modal-button/index.js index 15d83cb92d..6d95e8ae72 100644 --- a/pkg/webui/components/qr-modal-button/index.js +++ b/pkg/webui/components/qr-modal-button/index.js @@ -60,7 +60,12 @@ const QRModalButton = props => { ) : ( <> - + )}
diff --git a/pkg/webui/components/qr/input/video/index.js b/pkg/webui/components/qr/input/video/index.js index 1c3240ada7..56799be456 100644 --- a/pkg/webui/components/qr/input/video/index.js +++ b/pkg/webui/components/qr/input/video/index.js @@ -234,7 +234,7 @@ const Video = props => { data-test-id="webcam-feed" /> ) : ( - + )} diff --git a/pkg/webui/components/qr/qr.styl b/pkg/webui/components/qr/qr.styl index 02d8f2c499..c6d8f8f631 100644 --- a/pkg/webui/components/qr/qr.styl +++ b/pkg/webui/components/qr/qr.styl @@ -36,5 +36,5 @@ width: 100% max-height: 75vh -.msg - color: var(--c-bg-neutral-min) + +media-query($bp.xl) + max-height: 47vh diff --git a/pkg/webui/components/qr/require-permission.js b/pkg/webui/components/qr/require-permission.js index 24e43509ae..d7e2986af9 100644 --- a/pkg/webui/components/qr/require-permission.js +++ b/pkg/webui/components/qr/require-permission.js @@ -112,7 +112,7 @@ const RequirePermission = props => { if (!allow || videoError) { return (
- +