Merge pull request #360 from danditomaso/add_pki_backup_dialog
Add PKI backup dialog, add reminder toast to suggest backing up private key.
This commit is contained in:
@@ -49,6 +49,7 @@
|
|||||||
"cmdk": "^1.0.0",
|
"cmdk": "^1.0.0",
|
||||||
"crypto-random-string": "^5.0.0",
|
"crypto-random-string": "^5.0.0",
|
||||||
"immer": "^10.1.1",
|
"immer": "^10.1.1",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
"lucide-react": "^0.363.0",
|
"lucide-react": "^0.363.0",
|
||||||
"mapbox-gl": "^3.6.0",
|
"mapbox-gl": "^3.6.0",
|
||||||
"maplibre-gl": "4.1.2",
|
"maplibre-gl": "4.1.2",
|
||||||
@@ -70,6 +71,7 @@
|
|||||||
"@rsbuild/core": "^1.0.10",
|
"@rsbuild/core": "^1.0.10",
|
||||||
"@rsbuild/plugin-react": "^1.0.3",
|
"@rsbuild/plugin-react": "^1.0.3",
|
||||||
"@types/chrome": "^0.0.263",
|
"@types/chrome": "^0.0.263",
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
"@types/node": "^20.14.9",
|
"@types/node": "^20.14.9",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^18.3.3",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
|
|||||||
Generated
+129
-85
@@ -89,6 +89,9 @@ importers:
|
|||||||
immer:
|
immer:
|
||||||
specifier: ^10.1.1
|
specifier: ^10.1.1
|
||||||
version: 10.1.1
|
version: 10.1.1
|
||||||
|
js-cookie:
|
||||||
|
specifier: ^3.0.5
|
||||||
|
version: 3.0.5
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.363.0
|
specifier: ^0.363.0
|
||||||
version: 0.363.0(react@18.3.1)
|
version: 0.363.0(react@18.3.1)
|
||||||
@@ -127,7 +130,7 @@ importers:
|
|||||||
version: 3.0.6(react@18.3.1)
|
version: 3.0.6(react@18.3.1)
|
||||||
vite-plugin-node-polyfills:
|
vite-plugin-node-polyfills:
|
||||||
specifier: ^0.22.0
|
specifier: ^0.22.0
|
||||||
version: 0.22.0(rollup@4.24.0)(vite@5.3.6(@types/node@20.14.9))
|
version: 0.22.0(rollup@4.29.1)(vite@5.3.6(@types/node@20.14.9))
|
||||||
zustand:
|
zustand:
|
||||||
specifier: 4.5.2
|
specifier: 4.5.2
|
||||||
version: 4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1)
|
version: 4.5.2(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1)
|
||||||
@@ -147,6 +150,9 @@ importers:
|
|||||||
'@types/chrome':
|
'@types/chrome':
|
||||||
specifier: ^0.0.263
|
specifier: ^0.0.263
|
||||||
version: 0.0.263
|
version: 0.0.263
|
||||||
|
'@types/js-cookie':
|
||||||
|
specifier: ^3.0.6
|
||||||
|
version: 3.0.6
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.14.9
|
specifier: ^20.14.9
|
||||||
version: 20.14.9
|
version: 20.14.9
|
||||||
@@ -173,7 +179,7 @@ importers:
|
|||||||
version: 8.4.38
|
version: 8.4.38
|
||||||
rollup-plugin-visualizer:
|
rollup-plugin-visualizer:
|
||||||
specifier: ^5.12.0
|
specifier: ^5.12.0
|
||||||
version: 5.12.0(rollup@4.24.0)
|
version: 5.12.0(rollup@4.29.1)
|
||||||
tailwindcss:
|
tailwindcss:
|
||||||
specifier: ^3.4.4
|
specifier: ^3.4.4
|
||||||
version: 3.4.4
|
version: 3.4.4
|
||||||
@@ -1165,83 +1171,98 @@ packages:
|
|||||||
rollup:
|
rollup:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-android-arm-eabi@4.24.0':
|
'@rollup/rollup-android-arm-eabi@4.29.1':
|
||||||
resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==}
|
resolution: {integrity: sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
'@rollup/rollup-android-arm64@4.24.0':
|
'@rollup/rollup-android-arm64@4.29.1':
|
||||||
resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==}
|
resolution: {integrity: sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [android]
|
os: [android]
|
||||||
|
|
||||||
'@rollup/rollup-darwin-arm64@4.24.0':
|
'@rollup/rollup-darwin-arm64@4.29.1':
|
||||||
resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==}
|
resolution: {integrity: sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@rollup/rollup-darwin-x64@4.24.0':
|
'@rollup/rollup-darwin-x64@4.29.1':
|
||||||
resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==}
|
resolution: {integrity: sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-gnueabihf@4.24.0':
|
'@rollup/rollup-freebsd-arm64@4.29.1':
|
||||||
resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
|
resolution: {integrity: sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-x64@4.29.1':
|
||||||
|
resolution: {integrity: sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf@4.29.1':
|
||||||
|
resolution: {integrity: sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.24.0':
|
'@rollup/rollup-linux-arm-musleabihf@4.29.1':
|
||||||
resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
|
resolution: {integrity: sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.24.0':
|
'@rollup/rollup-linux-arm64-gnu@4.29.1':
|
||||||
resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
|
resolution: {integrity: sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.24.0':
|
'@rollup/rollup-linux-arm64-musl@4.29.1':
|
||||||
resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
|
resolution: {integrity: sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
|
'@rollup/rollup-linux-loongarch64-gnu@4.29.1':
|
||||||
resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
|
resolution: {integrity: sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-powerpc64le-gnu@4.29.1':
|
||||||
|
resolution: {integrity: sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.24.0':
|
'@rollup/rollup-linux-riscv64-gnu@4.29.1':
|
||||||
resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
|
resolution: {integrity: sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.24.0':
|
'@rollup/rollup-linux-s390x-gnu@4.29.1':
|
||||||
resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
|
resolution: {integrity: sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.24.0':
|
'@rollup/rollup-linux-x64-gnu@4.29.1':
|
||||||
resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
|
resolution: {integrity: sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.24.0':
|
'@rollup/rollup-linux-x64-musl@4.29.1':
|
||||||
resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
|
resolution: {integrity: sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
|
||||||
'@rollup/rollup-win32-arm64-msvc@4.24.0':
|
'@rollup/rollup-win32-arm64-msvc@4.29.1':
|
||||||
resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
|
resolution: {integrity: sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@rollup/rollup-win32-ia32-msvc@4.24.0':
|
'@rollup/rollup-win32-ia32-msvc@4.29.1':
|
||||||
resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==}
|
resolution: {integrity: sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==}
|
||||||
cpu: [ia32]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
'@rollup/rollup-win32-x64-msvc@4.24.0':
|
'@rollup/rollup-win32-x64-msvc@4.29.1':
|
||||||
resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==}
|
resolution: {integrity: sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
@@ -1690,6 +1711,9 @@ packages:
|
|||||||
'@types/har-format@1.2.15':
|
'@types/har-format@1.2.15':
|
||||||
resolution: {integrity: sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==}
|
resolution: {integrity: sha512-RpQH4rXLuvTXKR0zqHq3go0RVXYv/YVqv4TnPH95VbwUxZdQlK1EtcMvQvMpDngHbt13Csh9Z4qT9AbkiQH5BA==}
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6':
|
||||||
|
resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
|
||||||
|
|
||||||
'@types/mapbox-gl@3.1.0':
|
'@types/mapbox-gl@3.1.0':
|
||||||
resolution: {integrity: sha512-hI6cQDjw1bkJw7MC/eHMqq5TWUamLwsujnUUeiIX2KDRjxRNSYMjnHz07+LATz9I9XIsKumOtUz4gRYnZOJ/FA==}
|
resolution: {integrity: sha512-hI6cQDjw1bkJw7MC/eHMqq5TWUamLwsujnUUeiIX2KDRjxRNSYMjnHz07+LATz9I9XIsKumOtUz4gRYnZOJ/FA==}
|
||||||
|
|
||||||
@@ -1864,8 +1888,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001638:
|
caniuse-lite@1.0.30001690:
|
||||||
resolution: {integrity: sha512-5SuJUJ7cZnhPpeLHaH0c/HPAnAHZvS6ElWyHK9GSIbVOQABLzowiI2pjmpvZ1WEbkyz46iFd4UXlOHR5SqgfMQ==}
|
resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==}
|
||||||
|
|
||||||
cheap-ruler@4.0.0:
|
cheap-ruler@4.0.0:
|
||||||
resolution: {integrity: sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==}
|
resolution: {integrity: sha512-0BJa8f4t141BYKQyn9NSQt1PguFQXMXwZiA5shfoaBYHAb2fFk2RAX+tiWMoQU+Agtzt3mdt0JtuyshAXqZ+Vw==}
|
||||||
@@ -2417,6 +2441,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
|
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
js-cookie@3.0.5:
|
||||||
|
resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
js-sha3@0.8.0:
|
js-sha3@0.8.0:
|
||||||
resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==}
|
resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==}
|
||||||
|
|
||||||
@@ -2923,8 +2951,8 @@ packages:
|
|||||||
rollup:
|
rollup:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
rollup@4.24.0:
|
rollup@4.29.1:
|
||||||
resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==}
|
resolution: {integrity: sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==}
|
||||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -4258,68 +4286,77 @@ snapshots:
|
|||||||
|
|
||||||
'@radix-ui/rect@1.1.0': {}
|
'@radix-ui/rect@1.1.0': {}
|
||||||
|
|
||||||
'@rollup/plugin-inject@5.0.5(rollup@4.24.0)':
|
'@rollup/plugin-inject@5.0.5(rollup@4.29.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/pluginutils': 5.1.0(rollup@4.24.0)
|
'@rollup/pluginutils': 5.1.0(rollup@4.29.1)
|
||||||
estree-walker: 2.0.2
|
estree-walker: 2.0.2
|
||||||
magic-string: 0.30.11
|
magic-string: 0.30.11
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
rollup: 4.24.0
|
rollup: 4.29.1
|
||||||
|
|
||||||
'@rollup/pluginutils@5.1.0(rollup@4.24.0)':
|
'@rollup/pluginutils@5.1.0(rollup@4.29.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.5
|
'@types/estree': 1.0.5
|
||||||
estree-walker: 2.0.2
|
estree-walker: 2.0.2
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
rollup: 4.24.0
|
rollup: 4.29.1
|
||||||
|
|
||||||
'@rollup/rollup-android-arm-eabi@4.24.0':
|
'@rollup/rollup-android-arm-eabi@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-android-arm64@4.24.0':
|
'@rollup/rollup-android-arm64@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-darwin-arm64@4.24.0':
|
'@rollup/rollup-darwin-arm64@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-darwin-x64@4.24.0':
|
'@rollup/rollup-darwin-x64@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-gnueabihf@4.24.0':
|
'@rollup/rollup-freebsd-arm64@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.24.0':
|
'@rollup/rollup-freebsd-x64@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.24.0':
|
'@rollup/rollup-linux-arm-gnueabihf@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.24.0':
|
'@rollup/rollup-linux-arm-musleabihf@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
|
'@rollup/rollup-linux-arm64-gnu@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.24.0':
|
'@rollup/rollup-linux-arm64-musl@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.24.0':
|
'@rollup/rollup-linux-loongarch64-gnu@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.24.0':
|
'@rollup/rollup-linux-powerpc64le-gnu@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.24.0':
|
'@rollup/rollup-linux-riscv64-gnu@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-win32-arm64-msvc@4.24.0':
|
'@rollup/rollup-linux-s390x-gnu@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-win32-ia32-msvc@4.24.0':
|
'@rollup/rollup-linux-x64-gnu@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rollup/rollup-win32-x64-msvc@4.24.0':
|
'@rollup/rollup-linux-x64-musl@4.29.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-arm64-msvc@4.29.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-ia32-msvc@4.29.1':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-msvc@4.29.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@rsbuild/core@1.0.10':
|
'@rsbuild/core@1.0.10':
|
||||||
@@ -4381,7 +4418,7 @@ snapshots:
|
|||||||
'@module-federation/runtime-tools': 0.5.1
|
'@module-federation/runtime-tools': 0.5.1
|
||||||
'@rspack/binding': 1.0.8
|
'@rspack/binding': 1.0.8
|
||||||
'@rspack/lite-tapable': 1.0.1
|
'@rspack/lite-tapable': 1.0.1
|
||||||
caniuse-lite: 1.0.30001638
|
caniuse-lite: 1.0.30001690
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@swc/helpers': 0.5.13
|
'@swc/helpers': 0.5.13
|
||||||
|
|
||||||
@@ -5255,6 +5292,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/har-format@1.2.15': {}
|
'@types/har-format@1.2.15': {}
|
||||||
|
|
||||||
|
'@types/js-cookie@3.0.6': {}
|
||||||
|
|
||||||
'@types/mapbox-gl@3.1.0':
|
'@types/mapbox-gl@3.1.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/geojson': 7946.0.14
|
'@types/geojson': 7946.0.14
|
||||||
@@ -5343,7 +5382,7 @@ snapshots:
|
|||||||
autoprefixer@10.4.19(postcss@8.4.38):
|
autoprefixer@10.4.19(postcss@8.4.38):
|
||||||
dependencies:
|
dependencies:
|
||||||
browserslist: 4.23.1
|
browserslist: 4.23.1
|
||||||
caniuse-lite: 1.0.30001638
|
caniuse-lite: 1.0.30001690
|
||||||
fraction.js: 4.3.7
|
fraction.js: 4.3.7
|
||||||
normalize-range: 0.1.2
|
normalize-range: 0.1.2
|
||||||
picocolors: 1.0.1
|
picocolors: 1.0.1
|
||||||
@@ -5424,7 +5463,7 @@ snapshots:
|
|||||||
|
|
||||||
browserslist@4.23.1:
|
browserslist@4.23.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
caniuse-lite: 1.0.30001638
|
caniuse-lite: 1.0.30001690
|
||||||
electron-to-chromium: 1.4.812
|
electron-to-chromium: 1.4.812
|
||||||
node-releases: 2.0.14
|
node-releases: 2.0.14
|
||||||
update-browserslist-db: 1.0.16(browserslist@4.23.1)
|
update-browserslist-db: 1.0.16(browserslist@4.23.1)
|
||||||
@@ -5459,7 +5498,7 @@ snapshots:
|
|||||||
|
|
||||||
camelcase-css@2.0.1: {}
|
camelcase-css@2.0.1: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001638: {}
|
caniuse-lite@1.0.30001690: {}
|
||||||
|
|
||||||
cheap-ruler@4.0.0: {}
|
cheap-ruler@4.0.0: {}
|
||||||
|
|
||||||
@@ -6075,6 +6114,8 @@ snapshots:
|
|||||||
|
|
||||||
jiti@1.21.6: {}
|
jiti@1.21.6: {}
|
||||||
|
|
||||||
|
js-cookie@3.0.5: {}
|
||||||
|
|
||||||
js-sha3@0.8.0: {}
|
js-sha3@0.8.0: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
@@ -6620,35 +6661,38 @@ snapshots:
|
|||||||
|
|
||||||
robust-predicates@3.0.2: {}
|
robust-predicates@3.0.2: {}
|
||||||
|
|
||||||
rollup-plugin-visualizer@5.12.0(rollup@4.24.0):
|
rollup-plugin-visualizer@5.12.0(rollup@4.29.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
open: 8.4.2
|
open: 8.4.2
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
source-map: 0.7.4
|
source-map: 0.7.4
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
rollup: 4.24.0
|
rollup: 4.29.1
|
||||||
|
|
||||||
rollup@4.24.0:
|
rollup@4.29.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.6
|
'@types/estree': 1.0.6
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@rollup/rollup-android-arm-eabi': 4.24.0
|
'@rollup/rollup-android-arm-eabi': 4.29.1
|
||||||
'@rollup/rollup-android-arm64': 4.24.0
|
'@rollup/rollup-android-arm64': 4.29.1
|
||||||
'@rollup/rollup-darwin-arm64': 4.24.0
|
'@rollup/rollup-darwin-arm64': 4.29.1
|
||||||
'@rollup/rollup-darwin-x64': 4.24.0
|
'@rollup/rollup-darwin-x64': 4.29.1
|
||||||
'@rollup/rollup-linux-arm-gnueabihf': 4.24.0
|
'@rollup/rollup-freebsd-arm64': 4.29.1
|
||||||
'@rollup/rollup-linux-arm-musleabihf': 4.24.0
|
'@rollup/rollup-freebsd-x64': 4.29.1
|
||||||
'@rollup/rollup-linux-arm64-gnu': 4.24.0
|
'@rollup/rollup-linux-arm-gnueabihf': 4.29.1
|
||||||
'@rollup/rollup-linux-arm64-musl': 4.24.0
|
'@rollup/rollup-linux-arm-musleabihf': 4.29.1
|
||||||
'@rollup/rollup-linux-powerpc64le-gnu': 4.24.0
|
'@rollup/rollup-linux-arm64-gnu': 4.29.1
|
||||||
'@rollup/rollup-linux-riscv64-gnu': 4.24.0
|
'@rollup/rollup-linux-arm64-musl': 4.29.1
|
||||||
'@rollup/rollup-linux-s390x-gnu': 4.24.0
|
'@rollup/rollup-linux-loongarch64-gnu': 4.29.1
|
||||||
'@rollup/rollup-linux-x64-gnu': 4.24.0
|
'@rollup/rollup-linux-powerpc64le-gnu': 4.29.1
|
||||||
'@rollup/rollup-linux-x64-musl': 4.24.0
|
'@rollup/rollup-linux-riscv64-gnu': 4.29.1
|
||||||
'@rollup/rollup-win32-arm64-msvc': 4.24.0
|
'@rollup/rollup-linux-s390x-gnu': 4.29.1
|
||||||
'@rollup/rollup-win32-ia32-msvc': 4.24.0
|
'@rollup/rollup-linux-x64-gnu': 4.29.1
|
||||||
'@rollup/rollup-win32-x64-msvc': 4.24.0
|
'@rollup/rollup-linux-x64-musl': 4.29.1
|
||||||
|
'@rollup/rollup-win32-arm64-msvc': 4.29.1
|
||||||
|
'@rollup/rollup-win32-ia32-msvc': 4.29.1
|
||||||
|
'@rollup/rollup-win32-x64-msvc': 4.29.1
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
run-parallel@1.2.0:
|
run-parallel@1.2.0:
|
||||||
@@ -6986,9 +7030,9 @@ snapshots:
|
|||||||
|
|
||||||
validator@13.12.0: {}
|
validator@13.12.0: {}
|
||||||
|
|
||||||
vite-plugin-node-polyfills@0.22.0(rollup@4.24.0)(vite@5.3.6(@types/node@20.14.9)):
|
vite-plugin-node-polyfills@0.22.0(rollup@4.29.1)(vite@5.3.6(@types/node@20.14.9)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@rollup/plugin-inject': 5.0.5(rollup@4.24.0)
|
'@rollup/plugin-inject': 5.0.5(rollup@4.29.1)
|
||||||
node-stdlib-browser: 1.2.0
|
node-stdlib-browser: 1.2.0
|
||||||
vite: 5.3.6(@types/node@20.14.9)
|
vite: 5.3.6(@types/node@20.14.9)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -6998,7 +7042,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.21.5
|
esbuild: 0.21.5
|
||||||
postcss: 8.4.49
|
postcss: 8.4.49
|
||||||
rollup: 4.24.0
|
rollup: 4.29.1
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 20.14.9
|
'@types/node': 20.14.9
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|||||||
+3
-1
@@ -2,7 +2,7 @@ import { DeviceWrapper } from "@app/DeviceWrapper.tsx";
|
|||||||
import { PageRouter } from "@app/PageRouter.tsx";
|
import { PageRouter } from "@app/PageRouter.tsx";
|
||||||
import { CommandPalette } from "@components/CommandPalette.tsx";
|
import { CommandPalette } from "@components/CommandPalette.tsx";
|
||||||
import { DeviceSelector } from "@components/DeviceSelector.tsx";
|
import { DeviceSelector } from "@components/DeviceSelector.tsx";
|
||||||
import { DialogManager } from "@components/Dialog/DialogManager.tsx";
|
import { DialogManager } from "@components/Dialog/DialogManager";
|
||||||
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx";
|
import { NewDeviceDialog } from "@components/Dialog/NewDeviceDialog.tsx";
|
||||||
import { Toaster } from "@components/Toaster.tsx";
|
import { Toaster } from "@components/Toaster.tsx";
|
||||||
import Footer from "@components/UI/Footer.tsx";
|
import Footer from "@components/UI/Footer.tsx";
|
||||||
@@ -11,6 +11,7 @@ import { useAppStore } from "@core/stores/appStore.ts";
|
|||||||
import { useDeviceStore } from "@core/stores/deviceStore.ts";
|
import { useDeviceStore } from "@core/stores/deviceStore.ts";
|
||||||
import { Dashboard } from "@pages/Dashboard/index.tsx";
|
import { Dashboard } from "@pages/Dashboard/index.tsx";
|
||||||
import { MapProvider } from "react-map-gl";
|
import { MapProvider } from "react-map-gl";
|
||||||
|
import { KeyBackupReminder } from "@components/KeyBackupReminder";
|
||||||
|
|
||||||
export const App = (): JSX.Element => {
|
export const App = (): JSX.Element => {
|
||||||
const { getDevice } = useDeviceStore();
|
const { getDevice } = useDeviceStore();
|
||||||
@@ -37,6 +38,7 @@ export const App = (): JSX.Element => {
|
|||||||
{device ? (
|
{device ? (
|
||||||
<div className="flex h-screen">
|
<div className="flex h-screen">
|
||||||
<DialogManager />
|
<DialogManager />
|
||||||
|
<KeyBackupReminder />
|
||||||
<CommandPalette />
|
<CommandPalette />
|
||||||
<PageRouter />
|
<PageRouter />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { QRDialog } from "@components/Dialog/QRDialog.tsx";
|
|||||||
import { RebootDialog } from "@components/Dialog/RebootDialog.tsx";
|
import { RebootDialog } from "@components/Dialog/RebootDialog.tsx";
|
||||||
import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx";
|
import { ShutdownDialog } from "@components/Dialog/ShutdownDialog.tsx";
|
||||||
import { useDevice } from "@core/stores/deviceStore.ts";
|
import { useDevice } from "@core/stores/deviceStore.ts";
|
||||||
|
import { PkiBackupDialog } from "@components/Dialog/PKIBackupDialog";
|
||||||
|
|
||||||
export const DialogManager = (): JSX.Element => {
|
export const DialogManager = (): JSX.Element => {
|
||||||
const { channels, config, dialog, setDialogOpen } = useDevice();
|
const { channels, config, dialog, setDialogOpen } = useDevice();
|
||||||
@@ -49,6 +50,12 @@ export const DialogManager = (): JSX.Element => {
|
|||||||
setDialogOpen("nodeRemoval", open);
|
setDialogOpen("nodeRemoval", open);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<PkiBackupDialog
|
||||||
|
open={dialog.pkiBackup}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setDialogOpen("pkiBackup", open);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import { useDevice } from "@app/core/stores/deviceStore";
|
||||||
|
import { Button } from "@components/UI/Button";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@components/UI/Dialog.tsx";
|
||||||
|
import { fromByteArray } from "base64-js";
|
||||||
|
import { DownloadIcon, PrinterIcon } from "lucide-react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export interface PkiBackupDialogProps {
|
||||||
|
open: boolean;
|
||||||
|
onOpenChange: (open: boolean) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PkiBackupDialog = ({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: PkiBackupDialogProps) => {
|
||||||
|
const { config, setDialogOpen } = useDevice();
|
||||||
|
const privateKey = config.security?.privateKey;
|
||||||
|
const publicKey = config.security?.publicKey;
|
||||||
|
|
||||||
|
const decodeKeyData = React.useCallback(
|
||||||
|
(key: Uint8Array<ArrayBufferLike>) => {
|
||||||
|
if (!key) return "";
|
||||||
|
return fromByteArray(key ?? new Uint8Array(0));
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeDialog = React.useCallback(() => {
|
||||||
|
setDialogOpen("pkiBackup", false);
|
||||||
|
}, [setDialogOpen]);
|
||||||
|
|
||||||
|
const renderPrintWindow = React.useCallback(() => {
|
||||||
|
if (!privateKey || !publicKey) return;
|
||||||
|
|
||||||
|
const printWindow = window.open("", "_blank");
|
||||||
|
if (printWindow) {
|
||||||
|
printWindow.document.write(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>=== MESHTASTIC KEYS ===</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; padding: 20px; }
|
||||||
|
h1 { font-size: 18px; }
|
||||||
|
p { font-size: 14px; word-break: break-all; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>=== MESHTASTIC KEYS ===</h1>
|
||||||
|
<br>
|
||||||
|
<h2>Public Key:</h2>
|
||||||
|
<p>${decodeKeyData(publicKey)}</p>
|
||||||
|
<h2>Private Key:</h2>
|
||||||
|
<p>${decodeKeyData(privateKey)}</p>
|
||||||
|
<br>
|
||||||
|
<p>=== END OF KEYS ===</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
printWindow.document.close();
|
||||||
|
printWindow.print();
|
||||||
|
closeDialog();
|
||||||
|
}
|
||||||
|
}, [decodeKeyData, privateKey, publicKey, closeDialog]);
|
||||||
|
|
||||||
|
const createDownloadKeyFile = React.useCallback(() => {
|
||||||
|
if (!privateKey || !publicKey) return;
|
||||||
|
|
||||||
|
const decodedPrivateKey = decodeKeyData(privateKey);
|
||||||
|
const decodedPublicKey = decodeKeyData(publicKey);
|
||||||
|
|
||||||
|
const formattedContent = [
|
||||||
|
"=== MESHTASTIC KEYS ===\n\n",
|
||||||
|
"Private Key:\n",
|
||||||
|
decodedPrivateKey,
|
||||||
|
"\n\nPublic Key:\n",
|
||||||
|
decodedPublicKey,
|
||||||
|
"\n\n=== END OF KEYS ===",
|
||||||
|
].join("");
|
||||||
|
|
||||||
|
const blob = new Blob([formattedContent], { type: "text/plain" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.href = url;
|
||||||
|
link.download = "meshtastic_keys.txt";
|
||||||
|
link.style.display = "none";
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
closeDialog();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}, [decodeKeyData, privateKey, publicKey, closeDialog]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Backup Keys</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Its important to backup your public and private keys and store your
|
||||||
|
backup securely!
|
||||||
|
</DialogDescription>
|
||||||
|
<DialogDescription>
|
||||||
|
<span className="font-bold break-before-auto">
|
||||||
|
If you lose your keys, you will need to reset your device.
|
||||||
|
</span>
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter className="mt-6">
|
||||||
|
<Button
|
||||||
|
variant={"default"}
|
||||||
|
onClick={() => createDownloadKeyFile()}
|
||||||
|
className=""
|
||||||
|
>
|
||||||
|
<DownloadIcon size={20} className="mr-2" />
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
<Button variant={"default"} onClick={() => renderPrintWindow()}>
|
||||||
|
<PrinterIcon size={20} className="mr-2" />
|
||||||
|
Print
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -7,6 +7,7 @@ import { Eye, EyeOff } from "lucide-react";
|
|||||||
import type { ChangeEventHandler, MouseEventHandler } from "react";
|
import type { ChangeEventHandler, MouseEventHandler } from "react";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Controller, type FieldValues } from "react-hook-form";
|
import { Controller, type FieldValues } from "react-hook-form";
|
||||||
|
import type { ButtonVariant } from "@components/UI/Button";
|
||||||
|
|
||||||
export interface PasswordGeneratorProps<T> extends BaseFormBuilderProps<T> {
|
export interface PasswordGeneratorProps<T> extends BaseFormBuilderProps<T> {
|
||||||
type: "passwordGenerator";
|
type: "passwordGenerator";
|
||||||
@@ -15,7 +16,12 @@ export interface PasswordGeneratorProps<T> extends BaseFormBuilderProps<T> {
|
|||||||
devicePSKBitCount: number;
|
devicePSKBitCount: number;
|
||||||
inputChange: ChangeEventHandler;
|
inputChange: ChangeEventHandler;
|
||||||
selectChange: (event: string) => void;
|
selectChange: (event: string) => void;
|
||||||
buttonClick: MouseEventHandler;
|
actionButtons: {
|
||||||
|
text: string;
|
||||||
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
variant: ButtonVariant;
|
||||||
|
className?: string;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PasswordGenerator<T extends FieldValues>({
|
export function PasswordGenerator<T extends FieldValues>({
|
||||||
@@ -38,19 +44,18 @@ export function PasswordGenerator<T extends FieldValues>({
|
|||||||
action={
|
action={
|
||||||
field.hide
|
field.hide
|
||||||
? {
|
? {
|
||||||
icon: passwordShown ? EyeOff : Eye,
|
icon: passwordShown ? EyeOff : Eye,
|
||||||
onClick: togglePasswordVisiblity,
|
onClick: togglePasswordVisiblity,
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
devicePSKBitCount={field.devicePSKBitCount}
|
devicePSKBitCount={field.devicePSKBitCount}
|
||||||
bits={field.bits}
|
bits={field.bits}
|
||||||
inputChange={field.inputChange}
|
inputChange={field.inputChange}
|
||||||
selectChange={field.selectChange}
|
selectChange={field.selectChange}
|
||||||
buttonClick={field.buttonClick}
|
|
||||||
value={value}
|
value={value}
|
||||||
variant={field.validationText ? "invalid" : "default"}
|
variant={field.validationText ? "invalid" : "default"}
|
||||||
buttonText="Generate"
|
actionButtons={field.actionButtons}
|
||||||
{...field.properties}
|
{...field.properties}
|
||||||
{...rest}
|
{...rest}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { useBackupReminder } from "@app/core/hooks/useKeyBackupReminder";
|
||||||
|
import { useDevice } from "@app/core/stores/deviceStore";
|
||||||
|
|
||||||
|
export const KeyBackupReminder = (): JSX.Element => {
|
||||||
|
const { setDialogOpen } = useDevice();
|
||||||
|
|
||||||
|
useBackupReminder({
|
||||||
|
reminderInDays: 7,
|
||||||
|
message:
|
||||||
|
"We recommend backing up your key data regularly. Would you like to back up now?",
|
||||||
|
onAccept: () => setDialogOpen("pkiBackup", true),
|
||||||
|
enabled: true,
|
||||||
|
cookieOptions: {
|
||||||
|
secure: true,
|
||||||
|
sameSite: "strict",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return <></>;
|
||||||
|
};
|
||||||
@@ -6,6 +6,7 @@ import { Protobuf } from "@meshtastic/js";
|
|||||||
import { fromByteArray, toByteArray } from "base64-js";
|
import { fromByteArray, toByteArray } from "base64-js";
|
||||||
import cryptoRandomString from "crypto-random-string";
|
import cryptoRandomString from "crypto-random-string";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import { PkiRegenerateDialog } from "../Dialog/PkiRegenerateDialog";
|
||||||
|
|
||||||
export interface SettingsPanelProps {
|
export interface SettingsPanelProps {
|
||||||
channel: Protobuf.Channel.Channel;
|
channel: Protobuf.Channel.Channel;
|
||||||
@@ -22,6 +23,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
|||||||
channel?.settings?.psk.length ?? 16,
|
channel?.settings?.psk.length ?? 16,
|
||||||
);
|
);
|
||||||
const [validationText, setValidationText] = useState<string>();
|
const [validationText, setValidationText] = useState<string>();
|
||||||
|
const [preSharedDialogOpen, setPreSharedDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const onSubmit = (data: ChannelValidation) => {
|
const onSubmit = (data: ChannelValidation) => {
|
||||||
const channel = new Protobuf.Channel.Channel({
|
const channel = new Protobuf.Channel.Channel({
|
||||||
@@ -46,7 +48,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const clickEvent = () => {
|
const preSharedKeyRegenerate = () => {
|
||||||
setPass(
|
setPass(
|
||||||
btoa(
|
btoa(
|
||||||
cryptoRandomString({
|
cryptoRandomString({
|
||||||
@@ -56,6 +58,11 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
setValidationText(undefined);
|
setValidationText(undefined);
|
||||||
|
setPreSharedDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const preSharedClickEvent = () => {
|
||||||
|
setPreSharedDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const validatePass = (input: string, count: number) => {
|
const validatePass = (input: string, count: number) => {
|
||||||
@@ -79,132 +86,139 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicForm<ChannelValidation>
|
<>
|
||||||
onSubmit={onSubmit}
|
<DynamicForm<ChannelValidation>
|
||||||
submitType="onSubmit"
|
onSubmit={onSubmit}
|
||||||
hasSubmitButton={true}
|
submitType="onSubmit"
|
||||||
defaultValues={{
|
hasSubmitButton={true}
|
||||||
...channel,
|
defaultValues={{
|
||||||
...{
|
...channel,
|
||||||
settings: {
|
...{
|
||||||
...channel?.settings,
|
settings: {
|
||||||
psk: pass,
|
...channel?.settings,
|
||||||
positionEnabled:
|
psk: pass,
|
||||||
channel?.settings?.moduleSettings?.positionPrecision !==
|
positionEnabled:
|
||||||
undefined &&
|
channel?.settings?.moduleSettings?.positionPrecision !==
|
||||||
channel?.settings?.moduleSettings?.positionPrecision > 0,
|
undefined &&
|
||||||
preciseLocation:
|
channel?.settings?.moduleSettings?.positionPrecision > 0,
|
||||||
channel?.settings?.moduleSettings?.positionPrecision === 32,
|
preciseLocation:
|
||||||
positionPrecision:
|
channel?.settings?.moduleSettings?.positionPrecision === 32,
|
||||||
channel?.settings?.moduleSettings?.positionPrecision === undefined
|
positionPrecision:
|
||||||
? 10
|
channel?.settings?.moduleSettings?.positionPrecision === undefined
|
||||||
: channel?.settings?.moduleSettings?.positionPrecision,
|
? 10
|
||||||
|
: channel?.settings?.moduleSettings?.positionPrecision,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}}
|
||||||
}}
|
fieldGroups={[
|
||||||
fieldGroups={[
|
{
|
||||||
{
|
label: "Channel Settings",
|
||||||
label: "Channel Settings",
|
description: "Crypto, MQTT & misc settings",
|
||||||
description: "Crypto, MQTT & misc settings",
|
fields: [
|
||||||
fields: [
|
{
|
||||||
{
|
type: "select",
|
||||||
type: "select",
|
name: "role",
|
||||||
name: "role",
|
label: "Role",
|
||||||
label: "Role",
|
disabled: channel.index === 0,
|
||||||
disabled: channel.index === 0,
|
description:
|
||||||
description:
|
"Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
|
||||||
"Device telemetry is sent over PRIMARY. Only one PRIMARY allowed",
|
properties: {
|
||||||
properties: {
|
enumValue:
|
||||||
enumValue:
|
channel.index === 0
|
||||||
channel.index === 0
|
? { PRIMARY: 1 }
|
||||||
? { PRIMARY: 1 }
|
: { DISABLED: 0, SECONDARY: 2 },
|
||||||
: { DISABLED: 0, SECONDARY: 2 },
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
type: "passwordGenerator",
|
||||||
type: "passwordGenerator",
|
name: "settings.psk",
|
||||||
name: "settings.psk",
|
label: "Pre-Shared Key",
|
||||||
label: "Pre-Shared Key",
|
description: "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)",
|
||||||
description: "Supported PSK lengths: 256-bit, 128-bit, 8-bit, Empty (0-bit)",
|
validationText: validationText,
|
||||||
validationText: validationText,
|
devicePSKBitCount: bitCount ?? 0,
|
||||||
devicePSKBitCount: bitCount ?? 0,
|
inputChange: inputChangeEvent,
|
||||||
inputChange: inputChangeEvent,
|
selectChange: selectChangeEvent,
|
||||||
selectChange: selectChangeEvent,
|
actionButtons: [{ text: 'Generate', variant: 'success', onClick: preSharedClickEvent }],
|
||||||
buttonClick: clickEvent,
|
hide: true,
|
||||||
hide: true,
|
properties: {
|
||||||
properties: {
|
value: pass,
|
||||||
value: pass,
|
},
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
{
|
type: "text",
|
||||||
type: "text",
|
name: "settings.name",
|
||||||
name: "settings.name",
|
label: "Name",
|
||||||
label: "Name",
|
description:
|
||||||
description:
|
"A unique name for the channel <12 bytes, leave blank for default",
|
||||||
"A unique name for the channel <12 bytes, leave blank for default",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "toggle",
|
|
||||||
name: "settings.uplinkEnabled",
|
|
||||||
label: "Uplink Enabled",
|
|
||||||
description: "Send messages from the local mesh to MQTT",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "toggle",
|
|
||||||
name: "settings.downlinkEnabled",
|
|
||||||
label: "Downlink Enabled",
|
|
||||||
description: "Send messages from MQTT to the local mesh",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "toggle",
|
|
||||||
name: "settings.positionEnabled",
|
|
||||||
label: "Allow Position Requests",
|
|
||||||
description: "Send position to channel",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "toggle",
|
|
||||||
name: "settings.preciseLocation",
|
|
||||||
label: "Precise Location",
|
|
||||||
description: "Send precise location to channel",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "select",
|
|
||||||
name: "settings.positionPrecision",
|
|
||||||
label: "Approximate Location",
|
|
||||||
description:
|
|
||||||
"If not sharing precise location, position shared on channel will be accurate within this distance",
|
|
||||||
properties: {
|
|
||||||
enumValue:
|
|
||||||
config.display?.units === 0
|
|
||||||
? {
|
|
||||||
"Within 23 km": 10,
|
|
||||||
"Within 12 km": 11,
|
|
||||||
"Within 5.8 km": 12,
|
|
||||||
"Within 2.9 km": 13,
|
|
||||||
"Within 1.5 km": 14,
|
|
||||||
"Within 700 m": 15,
|
|
||||||
"Within 350 m": 16,
|
|
||||||
"Within 200 m": 17,
|
|
||||||
"Within 90 m": 18,
|
|
||||||
"Within 50 m": 19,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
"Within 15 miles": 10,
|
|
||||||
"Within 7.3 miles": 11,
|
|
||||||
"Within 3.6 miles": 12,
|
|
||||||
"Within 1.8 miles": 13,
|
|
||||||
"Within 0.9 miles": 14,
|
|
||||||
"Within 0.5 miles": 15,
|
|
||||||
"Within 0.2 miles": 16,
|
|
||||||
"Within 600 feet": 17,
|
|
||||||
"Within 300 feet": 18,
|
|
||||||
"Within 150 feet": 19,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
{
|
||||||
],
|
type: "toggle",
|
||||||
},
|
name: "settings.uplinkEnabled",
|
||||||
]}
|
label: "Uplink Enabled",
|
||||||
/>
|
description: "Send messages from the local mesh to MQTT",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
name: "settings.downlinkEnabled",
|
||||||
|
label: "Downlink Enabled",
|
||||||
|
description: "Send messages from MQTT to the local mesh",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
name: "settings.positionEnabled",
|
||||||
|
label: "Allow Position Requests",
|
||||||
|
description: "Send position to channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "toggle",
|
||||||
|
name: "settings.preciseLocation",
|
||||||
|
label: "Precise Location",
|
||||||
|
description: "Send precise location to channel",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "select",
|
||||||
|
name: "settings.positionPrecision",
|
||||||
|
label: "Approximate Location",
|
||||||
|
description:
|
||||||
|
"If not sharing precise location, position shared on channel will be accurate within this distance",
|
||||||
|
properties: {
|
||||||
|
enumValue:
|
||||||
|
config.display?.units === 0
|
||||||
|
? {
|
||||||
|
"Within 23 km": 10,
|
||||||
|
"Within 12 km": 11,
|
||||||
|
"Within 5.8 km": 12,
|
||||||
|
"Within 2.9 km": 13,
|
||||||
|
"Within 1.5 km": 14,
|
||||||
|
"Within 700 m": 15,
|
||||||
|
"Within 350 m": 16,
|
||||||
|
"Within 200 m": 17,
|
||||||
|
"Within 90 m": 18,
|
||||||
|
"Within 50 m": 19,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
"Within 15 miles": 10,
|
||||||
|
"Within 7.3 miles": 11,
|
||||||
|
"Within 3.6 miles": 12,
|
||||||
|
"Within 1.8 miles": 13,
|
||||||
|
"Within 0.9 miles": 14,
|
||||||
|
"Within 0.5 miles": 15,
|
||||||
|
"Within 0.2 miles": 16,
|
||||||
|
"Within 600 feet": 17,
|
||||||
|
"Within 300 feet": 18,
|
||||||
|
"Within 150 feet": 19,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<PkiRegenerateDialog
|
||||||
|
open={preSharedDialogOpen}
|
||||||
|
onOpenChange={() => setPreSharedDialogOpen(false)}
|
||||||
|
onSubmit={() => preSharedKeyRegenerate()}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { Eye, EyeOff } from "lucide-react";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export const Security = (): JSX.Element => {
|
export const Security = (): JSX.Element => {
|
||||||
const { config, nodes, hardware, setWorkingConfig } = useDevice();
|
const { config, nodes, hardware, setWorkingConfig, setDialogOpen } = useDevice();
|
||||||
|
|
||||||
const [privateKey, setPrivateKey] = useState<string>(
|
const [privateKey, setPrivateKey] = useState<string>(
|
||||||
fromByteArray(config.security?.privateKey ?? new Uint8Array(0)),
|
fromByteArray(config.security?.privateKey ?? new Uint8Array(0)),
|
||||||
@@ -31,7 +31,7 @@ export const Security = (): JSX.Element => {
|
|||||||
);
|
);
|
||||||
const [adminKeyValidationText, setAdminKeyValidationText] =
|
const [adminKeyValidationText, setAdminKeyValidationText] =
|
||||||
useState<string>();
|
useState<string>();
|
||||||
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
const [privateKeyDialogOpen, setPrivateKeyDialogOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
const onSubmit = (data: SecurityValidation) => {
|
const onSubmit = (data: SecurityValidation) => {
|
||||||
if (privateKeyValidationText || adminKeyValidationText) return;
|
if (privateKeyValidationText || adminKeyValidationText) return;
|
||||||
@@ -71,9 +71,13 @@ export const Security = (): JSX.Element => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const privateKeyClickEvent = () => {
|
const privateKeyClickEvent = () => {
|
||||||
setDialogOpen(true);
|
setPrivateKeyDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pkiBackupClickEvent = () => {
|
||||||
|
setDialogOpen("pkiBackup", true);
|
||||||
|
}
|
||||||
|
|
||||||
const pkiRegenerate = () => {
|
const pkiRegenerate = () => {
|
||||||
const privateKey = getX25519PrivateKey();
|
const privateKey = getX25519PrivateKey();
|
||||||
const publicKey = getX25519PublicKey(privateKey);
|
const publicKey = getX25519PublicKey(privateKey);
|
||||||
@@ -86,7 +90,7 @@ export const Security = (): JSX.Element => {
|
|||||||
setPrivateKeyValidationText,
|
setPrivateKeyValidationText,
|
||||||
);
|
);
|
||||||
|
|
||||||
setDialogOpen(false);
|
setPrivateKeyDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const privateKeyInputChangeEvent = (
|
const privateKeyInputChangeEvent = (
|
||||||
@@ -149,7 +153,18 @@ export const Security = (): JSX.Element => {
|
|||||||
inputChange: privateKeyInputChangeEvent,
|
inputChange: privateKeyInputChangeEvent,
|
||||||
selectChange: privateKeySelectChangeEvent,
|
selectChange: privateKeySelectChangeEvent,
|
||||||
hide: !privateKeyVisible,
|
hide: !privateKeyVisible,
|
||||||
buttonClick: privateKeyClickEvent,
|
actionButtons: [
|
||||||
|
{
|
||||||
|
text: "Generate",
|
||||||
|
onClick: privateKeyClickEvent,
|
||||||
|
variant: "success",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Backup Key",
|
||||||
|
onClick: pkiBackupClickEvent,
|
||||||
|
variant: "subtle",
|
||||||
|
},
|
||||||
|
],
|
||||||
properties: {
|
properties: {
|
||||||
value: privateKey,
|
value: privateKey,
|
||||||
action: {
|
action: {
|
||||||
@@ -228,8 +243,8 @@ export const Security = (): JSX.Element => {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<PkiRegenerateDialog
|
<PkiRegenerateDialog
|
||||||
open={dialogOpen}
|
open={privateKeyDialogOpen}
|
||||||
onOpenChange={() => setDialogOpen(false)}
|
onOpenChange={() => setPrivateKeyDialogOpen(false)}
|
||||||
onSubmit={() => pkiRegenerate()}
|
onSubmit={() => pkiRegenerate()}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
+12
-13
@@ -1,5 +1,3 @@
|
|||||||
import { useToast } from "@core/hooks/useToast.ts";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Toast,
|
Toast,
|
||||||
ToastClose,
|
ToastClose,
|
||||||
@@ -7,24 +5,25 @@ import {
|
|||||||
ToastProvider,
|
ToastProvider,
|
||||||
ToastTitle,
|
ToastTitle,
|
||||||
ToastViewport,
|
ToastViewport,
|
||||||
} from "@components/UI/Toast.tsx";
|
} from "@components/UI/Toast";
|
||||||
|
import { useToast } from "@core/hooks/useToast";
|
||||||
|
|
||||||
export function Toaster() {
|
export function Toaster() {
|
||||||
const { toasts } = useToast();
|
const { toasts } = useToast();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
{toasts.map(({ id, title, description, action, ...props }) => (
|
{toasts.map(({ id, title, description, action, duration, ...props }) => (
|
||||||
<Toast key={id} {...props}>
|
<Toast
|
||||||
|
key={id}
|
||||||
|
{...props}
|
||||||
|
|
||||||
|
duration={duration}
|
||||||
|
className="flex flex-col gap-4"
|
||||||
|
>
|
||||||
<div className="grid gap-1">
|
<div className="grid gap-1">
|
||||||
{title && (
|
{title && <ToastTitle>{title}</ToastTitle>}
|
||||||
<ToastTitle className="dark:text-white">{title}</ToastTitle>
|
{description && <ToastDescription>{description}</ToastDescription>}
|
||||||
)}
|
|
||||||
{description && (
|
|
||||||
<ToastDescription className="dark:text-white-400">
|
|
||||||
{description}
|
|
||||||
</ToastDescription>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{action}
|
{action}
|
||||||
<ToastClose />
|
<ToastClose />
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ const buttonVariants = cva(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export type ButtonVariant = VariantProps<typeof buttonVariants>["variant"];
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
VariantProps<typeof buttonVariants> {}
|
VariantProps<typeof buttonVariants> { }
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, ...props }, ref) => {
|
({ className, variant, size, ...props }, ref) => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import { Button } from "@components/UI/Button.tsx";
|
import { Button, type ButtonVariant } from "@components/UI/Button.tsx";
|
||||||
import { Input } from "@components/UI/Input.tsx";
|
import { Input } from "@components/UI/Input.tsx";
|
||||||
import {
|
import {
|
||||||
Select,
|
Select,
|
||||||
@@ -16,11 +16,15 @@ export interface GeneratorProps extends React.BaseHTMLAttributes<HTMLElement> {
|
|||||||
devicePSKBitCount?: number;
|
devicePSKBitCount?: number;
|
||||||
value: string;
|
value: string;
|
||||||
variant: "default" | "invalid";
|
variant: "default" | "invalid";
|
||||||
buttonText?: string;
|
actionButtons: {
|
||||||
|
text: string;
|
||||||
|
onClick: React.MouseEventHandler<HTMLButtonElement>;
|
||||||
|
variant: ButtonVariant;
|
||||||
|
className?: string;
|
||||||
|
}[];
|
||||||
bits?: { text: string; value: string; key: string }[];
|
bits?: { text: string; value: string; key: string }[];
|
||||||
selectChange: (event: string) => void;
|
selectChange: (event: string) => void;
|
||||||
inputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
inputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
buttonClick: React.MouseEventHandler<HTMLButtonElement>;
|
|
||||||
action?: {
|
action?: {
|
||||||
icon: LucideIcon;
|
icon: LucideIcon;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
@@ -35,7 +39,7 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
|
|||||||
devicePSKBitCount,
|
devicePSKBitCount,
|
||||||
variant,
|
variant,
|
||||||
value,
|
value,
|
||||||
buttonText,
|
actionButtons,
|
||||||
bits = [
|
bits = [
|
||||||
{ text: "256 bit", value: "32", key: "bit256" },
|
{ text: "256 bit", value: "32", key: "bit256" },
|
||||||
{ text: "128 bit", value: "16", key: "bit128" },
|
{ text: "128 bit", value: "16", key: "bit128" },
|
||||||
@@ -44,7 +48,6 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
|
|||||||
],
|
],
|
||||||
selectChange,
|
selectChange,
|
||||||
inputChange,
|
inputChange,
|
||||||
buttonClick,
|
|
||||||
action,
|
action,
|
||||||
disabled,
|
disabled,
|
||||||
...props
|
...props
|
||||||
@@ -94,15 +97,21 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
|
|||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
<Button
|
<div className="flex ml-4 space-x-4">
|
||||||
type="button"
|
{actionButtons?.map(({ text, onClick, variant, className }) => (
|
||||||
variant="success"
|
<Button
|
||||||
onClick={buttonClick}
|
key={text}
|
||||||
disabled={disabled}
|
type="button"
|
||||||
{...props}
|
onClick={onClick}
|
||||||
>
|
disabled={disabled}
|
||||||
{buttonText}
|
variant={variant}
|
||||||
</Button>
|
className={className}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{text}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
+35
-35
@@ -1,11 +1,11 @@
|
|||||||
import * as ToastPrimitives from "@radix-ui/react-toast";
|
import * as React from "react"
|
||||||
import { type VariantProps, cva } from "class-variance-authority";
|
import * as ToastPrimitives from "@radix-ui/react-toast"
|
||||||
import { X } from "lucide-react";
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import * as React from "react";
|
import { X } from 'lucide-react'
|
||||||
|
|
||||||
import { cn } from "@core/utils/cn.ts";
|
import { cn } from "@core/utils/cn"
|
||||||
|
|
||||||
const ToastProvider = ToastPrimitives.Provider;
|
const ToastProvider = ToastPrimitives.Provider
|
||||||
|
|
||||||
const ToastViewport = React.forwardRef<
|
const ToastViewport = React.forwardRef<
|
||||||
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
React.ElementRef<typeof ToastPrimitives.Viewport>,
|
||||||
@@ -14,35 +14,34 @@ const ToastViewport = React.forwardRef<
|
|||||||
<ToastPrimitives.Viewport
|
<ToastPrimitives.Viewport
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"fixed top-0 z-50 flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
|
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-24 sm:right-6 sm:top-auto sm:flex-col md:max-w-[420px]",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
|
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
|
||||||
|
|
||||||
const toastVariants = cva(
|
const toastVariants = cva(
|
||||||
"data-[swipe=move]:transition-none grow-1 group relative pointer-events-auto flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full mt-4 data-[state=closed]:slide-out-to-right-full dark:border-slate-700 last:mt-0 sm:last:mt-4",
|
"group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default:
|
default: "border bg-background text-foreground dark:bg-slate-700 dark:border-slate-600 dark:text-slate-50",
|
||||||
"bg-white border-slate-200 dark:bg-slate-800 dark:border-slate-700",
|
|
||||||
destructive:
|
destructive:
|
||||||
"group destructive bg-red-600 text-white border-red-600 dark:border-red-600",
|
"group destructive bg-red-600 text-white dark:border-red-900 dark:bg-red-900 dark:text-red-50"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
);
|
)
|
||||||
|
|
||||||
const Toast = React.forwardRef<
|
const Toast = React.forwardRef<
|
||||||
React.ElementRef<typeof ToastPrimitives.Root>,
|
React.ElementRef<typeof ToastPrimitives.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
|
||||||
VariantProps<typeof toastVariants>
|
VariantProps<typeof toastVariants>
|
||||||
>(({ className, variant, ...props }, ref) => {
|
>(({ className, variant, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<ToastPrimitives.Root
|
<ToastPrimitives.Root
|
||||||
@@ -50,9 +49,9 @@ const Toast = React.forwardRef<
|
|||||||
className={cn(toastVariants({ variant }), className)}
|
className={cn(toastVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
Toast.displayName = ToastPrimitives.Root.displayName;
|
Toast.displayName = ToastPrimitives.Root.displayName
|
||||||
|
|
||||||
const ToastAction = React.forwardRef<
|
const ToastAction = React.forwardRef<
|
||||||
React.ElementRef<typeof ToastPrimitives.Action>,
|
React.ElementRef<typeof ToastPrimitives.Action>,
|
||||||
@@ -61,13 +60,13 @@ const ToastAction = React.forwardRef<
|
|||||||
<ToastPrimitives.Action
|
<ToastPrimitives.Action
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border border-slate-200 bg-transparent px-3 text-sm font-medium transition-colors hover:bg-slate-100 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-red-100 group-[.destructive]:hover:border-slate-50 group-[.destructive]:hover:bg-red-100 group-[.destructive]:hover:text-red-600 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:border-slate-700 dark:text-slate-100 dark:hover:bg-slate-700 dark:hover:text-slate-100 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900 dark:data-[state=open]:bg-slate-800",
|
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
ToastAction.displayName = ToastPrimitives.Action.displayName;
|
ToastAction.displayName = ToastPrimitives.Action.displayName
|
||||||
|
|
||||||
const ToastClose = React.forwardRef<
|
const ToastClose = React.forwardRef<
|
||||||
React.ElementRef<typeof ToastPrimitives.Close>,
|
React.ElementRef<typeof ToastPrimitives.Close>,
|
||||||
@@ -76,16 +75,16 @@ const ToastClose = React.forwardRef<
|
|||||||
<ToastPrimitives.Close
|
<ToastPrimitives.Close
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"absolute right-2 top-2 rounded-md p-1 text-slate-500 opacity-0 transition-opacity hover:text-slate-900 focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:hover:text-slate-50",
|
"absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600 dark:text-slate-400 dark:hover:text-slate-50",
|
||||||
className,
|
className
|
||||||
)}
|
)}
|
||||||
toast-close=""
|
toast-close=""
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
</ToastPrimitives.Close>
|
</ToastPrimitives.Close>
|
||||||
));
|
))
|
||||||
ToastClose.displayName = ToastPrimitives.Close.displayName;
|
ToastClose.displayName = ToastPrimitives.Close.displayName
|
||||||
|
|
||||||
const ToastTitle = React.forwardRef<
|
const ToastTitle = React.forwardRef<
|
||||||
React.ElementRef<typeof ToastPrimitives.Title>,
|
React.ElementRef<typeof ToastPrimitives.Title>,
|
||||||
@@ -96,8 +95,8 @@ const ToastTitle = React.forwardRef<
|
|||||||
className={cn("text-sm font-semibold", className)}
|
className={cn("text-sm font-semibold", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
ToastTitle.displayName = ToastPrimitives.Title.displayName;
|
ToastTitle.displayName = ToastPrimitives.Title.displayName
|
||||||
|
|
||||||
const ToastDescription = React.forwardRef<
|
const ToastDescription = React.forwardRef<
|
||||||
React.ElementRef<typeof ToastPrimitives.Description>,
|
React.ElementRef<typeof ToastPrimitives.Description>,
|
||||||
@@ -108,12 +107,12 @@ const ToastDescription = React.forwardRef<
|
|||||||
className={cn("text-sm opacity-90", className)}
|
className={cn("text-sm opacity-90", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
));
|
))
|
||||||
ToastDescription.displayName = ToastPrimitives.Description.displayName;
|
ToastDescription.displayName = ToastPrimitives.Description.displayName
|
||||||
|
|
||||||
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
|
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
|
||||||
|
|
||||||
type ToastActionElement = React.ReactElement<typeof ToastAction>;
|
type ToastActionElement = React.ReactElement<typeof ToastAction>
|
||||||
|
|
||||||
export {
|
export {
|
||||||
type ToastProps,
|
type ToastProps,
|
||||||
@@ -125,4 +124,5 @@ export {
|
|||||||
ToastDescription,
|
ToastDescription,
|
||||||
ToastClose,
|
ToastClose,
|
||||||
ToastAction,
|
ToastAction,
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import Cookies, { type CookieAttributes } from "js-cookie";
|
||||||
|
import { useCallback, useState } from "react";
|
||||||
|
|
||||||
|
interface CookieHookResult<T> {
|
||||||
|
value: T | undefined;
|
||||||
|
setCookie: (value: T, options?: CookieAttributes) => void;
|
||||||
|
removeCookie: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useCookie<T extends object>(
|
||||||
|
cookieName: string,
|
||||||
|
initialValue?: T,
|
||||||
|
): CookieHookResult<T> {
|
||||||
|
const [cookieValue, setCookieValue] = useState<T | undefined>(() => {
|
||||||
|
try {
|
||||||
|
const cookie = Cookies.get(cookieName);
|
||||||
|
return cookie ? (JSON.parse(cookie) as T) : initialValue;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error parsing cookie ${cookieName}:`, error);
|
||||||
|
return initialValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const setCookie = useCallback(
|
||||||
|
(value: T, options?: CookieAttributes) => {
|
||||||
|
try {
|
||||||
|
Cookies.set(cookieName, JSON.stringify(value), options);
|
||||||
|
setCookieValue(value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error setting cookie ${cookieName}:`, error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[cookieName],
|
||||||
|
);
|
||||||
|
|
||||||
|
const removeCookie = useCallback(() => {
|
||||||
|
try {
|
||||||
|
Cookies.remove(cookieName);
|
||||||
|
setCookieValue(undefined);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error removing cookie ${cookieName}:`, error);
|
||||||
|
}
|
||||||
|
}, [cookieName]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: cookieValue,
|
||||||
|
setCookie,
|
||||||
|
removeCookie,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useCookie;
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { Button } from "@app/components/UI/Button";
|
||||||
|
import type { CookieAttributes } from "js-cookie";
|
||||||
|
import { useCallback, useEffect, useRef } from "react";
|
||||||
|
import useCookie from "./useCookie";
|
||||||
|
import { useToast } from "./useToast";
|
||||||
|
|
||||||
|
interface UseBackupReminderOptions {
|
||||||
|
reminderInDays?: number;
|
||||||
|
message: string;
|
||||||
|
onAccept?: () => void | Promise<void>;
|
||||||
|
enabled: boolean;
|
||||||
|
cookieOptions?: CookieAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReminderState {
|
||||||
|
suppressed: boolean;
|
||||||
|
lastShown: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TOAST_APPEAR_DELAY = 10_000 // 10 seconds;
|
||||||
|
const TOAST_DURATION = 30_000 // 30 seconds;:
|
||||||
|
|
||||||
|
// remind user in 1 year to backup keys again, if they accept the reminder;
|
||||||
|
const ON_ACCEPT_REMINDER_DAYS = 365
|
||||||
|
|
||||||
|
function isReminderExpired(lastShown: string): boolean {
|
||||||
|
const lastShownDate = new Date(lastShown);
|
||||||
|
const now = new Date();
|
||||||
|
const daysSinceLastShown =
|
||||||
|
(now.getTime() - lastShownDate.getTime()) / (1000 * 60 * 60 * 24);
|
||||||
|
return daysSinceLastShown >= 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBackupReminder({
|
||||||
|
reminderInDays = 7,
|
||||||
|
enabled,
|
||||||
|
message,
|
||||||
|
onAccept = () => { },
|
||||||
|
cookieOptions,
|
||||||
|
}: UseBackupReminderOptions) {
|
||||||
|
const { toast } = useToast();
|
||||||
|
const toastShownRef = useRef(false);
|
||||||
|
const { value: reminderCookie, setCookie } =
|
||||||
|
useCookie<ReminderState>("key_backup_reminder");
|
||||||
|
|
||||||
|
const suppressReminder = useCallback(
|
||||||
|
(days: number) => {
|
||||||
|
const expiryDate = new Date();
|
||||||
|
expiryDate.setDate(expiryDate.getDate() + days);
|
||||||
|
|
||||||
|
setCookie(
|
||||||
|
{
|
||||||
|
suppressed: true,
|
||||||
|
lastShown: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{ ...cookieOptions, expires: expiryDate },
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[setCookie, cookieOptions],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!enabled || toastShownRef.current) return;
|
||||||
|
|
||||||
|
const shouldShowReminder =
|
||||||
|
!reminderCookie?.suppressed ||
|
||||||
|
isReminderExpired(reminderCookie.lastShown);
|
||||||
|
if (!shouldShowReminder) return;
|
||||||
|
|
||||||
|
toastShownRef.current = true;
|
||||||
|
|
||||||
|
const { dismiss } = toast(
|
||||||
|
{
|
||||||
|
title: "Backup Reminder",
|
||||||
|
duration: TOAST_DURATION,
|
||||||
|
delay: TOAST_APPEAR_DELAY,
|
||||||
|
description: message,
|
||||||
|
action: (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="default"
|
||||||
|
onClick={() => {
|
||||||
|
onAccept();
|
||||||
|
dismiss();
|
||||||
|
suppressReminder(ON_ACCEPT_REMINDER_DAYS);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Back up now
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => {
|
||||||
|
dismiss();
|
||||||
|
suppressReminder(reminderInDays);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remind me in {reminderInDays} days
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (!toastShownRef.current) {
|
||||||
|
dismiss();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
enabled,
|
||||||
|
message,
|
||||||
|
onAccept,
|
||||||
|
reminderInDays,
|
||||||
|
suppressReminder,
|
||||||
|
toast,
|
||||||
|
reminderCookie,
|
||||||
|
]);
|
||||||
|
}
|
||||||
+14
-11
@@ -10,6 +10,7 @@ type ToasterToast = ToastProps & {
|
|||||||
title?: ReactNode;
|
title?: ReactNode;
|
||||||
description?: ReactNode;
|
description?: ReactNode;
|
||||||
action?: ToastActionElement;
|
action?: ToastActionElement;
|
||||||
|
delay?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const actionTypes = {
|
const actionTypes = {
|
||||||
@@ -137,7 +138,7 @@ function dispatch(action: Action) {
|
|||||||
|
|
||||||
type Toast = Omit<ToasterToast, "id">;
|
type Toast = Omit<ToasterToast, "id">;
|
||||||
|
|
||||||
function toast({ ...props }: Toast) {
|
function toast({ delay = 0, ...props }: Toast) {
|
||||||
const id = genId();
|
const id = genId();
|
||||||
|
|
||||||
const update = (props: ToasterToast) =>
|
const update = (props: ToasterToast) =>
|
||||||
@@ -147,17 +148,19 @@ function toast({ ...props }: Toast) {
|
|||||||
});
|
});
|
||||||
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
|
||||||
|
|
||||||
dispatch({
|
setTimeout(() => {
|
||||||
type: "ADD_TOAST",
|
dispatch({
|
||||||
toast: {
|
type: "ADD_TOAST",
|
||||||
...props,
|
toast: {
|
||||||
id,
|
...props,
|
||||||
open: true,
|
id,
|
||||||
onOpenChange: (open) => {
|
open: true,
|
||||||
if (!open) dismiss();
|
onOpenChange: (open) => {
|
||||||
|
if (!open) dismiss();
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
});
|
||||||
});
|
}, delay);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: id,
|
id: id,
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ export type DialogVariant =
|
|||||||
| "shutdown"
|
| "shutdown"
|
||||||
| "reboot"
|
| "reboot"
|
||||||
| "deviceName"
|
| "deviceName"
|
||||||
| "nodeRemoval";
|
| "nodeRemoval"
|
||||||
|
| "pkiBackup";
|
||||||
|
|
||||||
export interface Device {
|
export interface Device {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -60,6 +61,7 @@ export interface Device {
|
|||||||
reboot: boolean;
|
reboot: boolean;
|
||||||
deviceName: boolean;
|
deviceName: boolean;
|
||||||
nodeRemoval: boolean;
|
nodeRemoval: boolean;
|
||||||
|
pkiBackup: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
setStatus: (status: Types.DeviceStatusEnum) => void;
|
setStatus: (status: Types.DeviceStatusEnum) => void;
|
||||||
@@ -142,6 +144,7 @@ export const useDeviceStore = create<DeviceState>((set, get) => ({
|
|||||||
reboot: false,
|
reboot: false,
|
||||||
deviceName: false,
|
deviceName: false,
|
||||||
nodeRemoval: false,
|
nodeRemoval: false,
|
||||||
|
pkiBackup: false,
|
||||||
},
|
},
|
||||||
pendingSettingsChanges: false,
|
pendingSettingsChanges: false,
|
||||||
messageDraft: "",
|
messageDraft: "",
|
||||||
|
|||||||
Reference in New Issue
Block a user