Merged 1.3.x_branch into master

This commit is contained in:
2021-02-26 14:10:47 -06:00
90 changed files with 3774 additions and 1533 deletions

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ build/
chrome_data/ chrome_data/
dist/ dist/
/.cache /.cache
/temp.json
/.eslintcache

1
.nvimrc Symbolic link
View File

@@ -0,0 +1 @@
.vimrc

14
.vim/coc-settings.json Normal file
View File

@@ -0,0 +1,14 @@
{
"cSpell.words": [
"HKEY",
"HKLM",
"Redistributable",
"Skylinks",
"Skynet",
"Unmount",
"msiexec",
"relver",
"siaprime",
"skylink"
]
}

7
.vimrc Normal file
View File

@@ -0,0 +1,7 @@
set autoread
set path+=.,public/**,src/**,test/**
if has('win32')
let &makeprg="create_dist.cmd"
else
let &makeprg="./create_dist.sh"
endif

View File

@@ -1,4 +1,29 @@
# Changelog # Changelog
## 1.3.2
* \#48: Support pinning files to cache
* Fixed Skynet export display
* Properly detect existing remote
* Reduced number of Linux binaries to:
* CentOS 7
* Solus
* S3 mount support [disabled]
## 1.3.1
* \#45: Skynet mount support
## 1.3.0
* \#38: Enhance new repertory release available notification
* \#46: Fix Mount Manager unmount and mount detection
* Skynet support
* Synchronize UI major/minor version with `repertory` major/minor version
* Added `Password` component
* Reduced number of Linux binaries to:
* CentOS 7
* Debian 9
* Debian 10
* Solus
* Added `FocusTrap` to modals
## 1.1.4 ## 1.1.4
* \#39: Cleanup old releases and UI upgrades * \#39: Cleanup old releases and UI upgrades

View File

@@ -1,5 +1,5 @@
# Repertory UI MIT License # Repertory UI MIT License
### Copyright <2018-2019> <scott.e.graves@protonmail.com> ### Copyright <2018-2020> <scott.e.graves@protonmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@@ -1,57 +1,39 @@
# Repertory UI # Repertory UI
![alt text](https://i.ibb.co/y4bwwhV/repertory-1-1-4.png) ![alt text](https://i.ibb.co/jMvpQj8/repertory-ui-1-3-1.png)
## GUI for [Repertory](https://bitbucket.org/blockstorage/repertory) ## GUI for [Repertory](https://bitbucket.org/blockstorage/repertory)
Repertory allows you to mount Sia and/or ScPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows. Repertory allows you to mount Sia, Skynet and/or ScPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.
# Windows Issue
If you're experiencing slow copy, read, and/or write, please install the following UI version. This is a special build that includes WinFSP 2020.2:
* [Repertory UI v1.1.5 Windows 64-bit](https://bitbucket.org/blockstorage/repertory-testing/downloads/repertory-ui_1.1.5_win.exe)
# Discord Invite
* [Repertory Discord](https://discord.gg/f2rCfqRgrJ)
## Requirements ## Requirements
* Sia >=1.4.1 * Sia >=1.4.1
* ScPrime >=1.4.1.2 * ScPrime >=1.4.1.2
## Downloads ## Downloads
* **Repertory UI v1.1.4 Linux 64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage) * **Repertory UI v1.3.2 Linux
64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.2_linux_x86_64.AppImage)
* NOTE: Linux distributions require `fuse` and `libfuse` to be installed. * NOTE: Linux distributions require `fuse` and `libfuse` to be installed.
* **Repertory UI v1.1.4 OS X 64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_mac.dmg) * **Repertory UI v1.3.2 OS X
* **Repertory UI v1.1.4 Windows 64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_win.exe) 64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.2_mac.dmg)
* **Repertory UI v1.3.2 Windows
64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.2_win.exe)
## Supported Platforms ## Supported Platforms
* OS X 64-bit * OS X 64-bit
* Windows 64-bit * Windows 64-bit
* Linux 64-bit Distributions: * Linux 64-bit Distributions:
* Antergos * Antergos
* Uses `Ubuntu 18.10` binaries for compatibility
* Arch Linux * Arch Linux
* Bodhi 5.0.0 * Bodhi 5.0.0
* CentOS 7 * CentOS 7, 8
* CentOS 8 * Debian 9, 10
* Debian 9 * Elementary OS 5.0, 5.1
* Debian 10 * Fedora 28, 29, 30, 31, 32, 33
* Elementary OS 5.0 * Linux Mint 19, 19.1, 19.2, 19.3, 20, 20.1
* Fedora 28
* Fedora 29
* Fedora 30
* Fedora 31
* Linux Mint 19
* Linux Mint 19.1
* Linux Mint 19.2
* Manjaro * Manjaro
* Uses `Ubuntu 18.10` binaries for compatibility * OpenSUSE Leap 15.0, 15.1
* OpenSUSE Leap 15.0
* OpenSUSE Leap 15.1
* OpenSUSE Tumbleweed * OpenSUSE Tumbleweed
* Solus * Solus
* Ubuntu 18.04 * Ubuntu 18.04, 18.10, 19.04, 19.10, 20.04
* Ubuntu 18.10
* Ubuntu 19.04
* Ubuntu 19.10
## Issues/Suggestions ## Issues/Suggestions
Please submit [here](https://bitbucket.org/blockstorage/repertory-ui/issues?status=new&status=open) Please submit [here](https://bitbucket.org/blockstorage/repertory-ui/issues?status=new&status=open)
@@ -63,10 +45,6 @@ This feature allows you to share your mounts with other PC's and mount them remo
### Connecting to a Remote Mount ### Connecting to a Remote Mount
[Watch Demo](src/assets/images/Connect_To_Remote.gif) [Watch Demo](src/assets/images/Connect_To_Remote.gif)
## The FAQ of One
### My distro isn't listed... Why?
It may still function properly as long as it derives from one listed above. I do plan to test more, so this list should grow over time.
## Tips ## Tips
* BTC: 1CMvhGaJfH95VS4CTS6dJYDs8kwYXTUCEA * BTC: 1CMvhGaJfH95VS4CTS6dJYDs8kwYXTUCEA
* SC: c5510db5c2e85794b9cb81851daa57d6dfd5b4e42cd02789442e8789cd6492b07c754b2056ed * SC: c5510db5c2e85794b9cb81851daa57d6dfd5b4e42cd02789442e8789cd6492b07c754b2056ed

View File

@@ -4,7 +4,12 @@ setlocal EnableDelayedExpansion
set ROOT=%~dp0% set ROOT=%~dp0%
set /a ENABLE_UPLOAD=%1 set /a ENABLE_UPLOAD=%1
set BITBUCKET_AUTH=%2 set BITBUCKET_AUTH=%2
set BITBUCKET_TESTING=%3
if "%BITBUCKET_TESTING%" == "1" (
set REPOSITORY=repertory-testing
) else (
set REPOSITORY=repertory-ui
)
set OPENSSL_BIN="c:\OpenSSL-Win64\bin\openssl.exe" set OPENSSL_BIN="c:\OpenSSL-Win64\bin\openssl.exe"
if NOT EXIST %OPENSSL_BIN% ( if NOT EXIST %OPENSSL_BIN% (
set OPENSSL_BIN="c:\Program Files\OpenSSL-Win64\bin\openssl.exe" set OPENSSL_BIN="c:\Program Files\OpenSSL-Win64\bin\openssl.exe"
@@ -41,23 +46,17 @@ pushd "%ROOT%"
del /q upload_response.json 1>NUL 2>&1 del /q upload_response.json 1>NUL 2>&1
("%CURL_BIN%" -F name="%OUT_FILE%" -F anonymous=true -F file="@%OUT_FILE%" https://pixeldrain.com/api/file > upload_response.json) || (
call :PIXEL_RESPONSE 0
)
call :PIXEL_RESPONSE 1
set PIXEL_LOCATION=https://pixeldrain.com/api/file/!PIXEL_ID!
call :UPLOAD_TO_BITBUCKET "%OUT_FILE%" call :UPLOAD_TO_BITBUCKET "%OUT_FILE%"
call :UPLOAD_TO_BITBUCKET "%OUT_FILE%.sha256" call :UPLOAD_TO_BITBUCKET "%OUT_FILE%.sha256"
call :UPLOAD_TO_BITBUCKET "%OUT_FILE%.sig" call :UPLOAD_TO_BITBUCKET "%OUT_FILE%.sig"
set BITBUCKET_LOCATION=https://bitbucket.org/blockstorage/repertory-ui/downloads/%OUT_FILE% set BITBUCKET_LOCATION=https://bitbucket.org/blockstorage/%REPOSITORY%/downloads/%OUT_FILE%
del /q releases.json 1>NUL 2>&1 del /q releases.json 1>NUL 2>&1
("%JQ_BIN%" ".Versions.win32|=(.+ ["""%APP_VER%"""]|unique)" ..\releases.json>releases_temp.json && move /Y releases_temp.json releases.json 1>NUL 2>&1) || (call :ERROR "Update releases.json Versions failed") ("%JQ_BIN%" ".Versions.win32|=(.+ ["""%APP_VER%"""]|unique)" ..\releases.json>releases_temp.json && move /Y releases_temp.json releases.json 1>NUL 2>&1) || (call :ERROR "Update releases.json Versions failed")
("%JQ_BIN%" ".Locations.win32."""%APP_VER%""".sig="""!APP_SIG!"""" releases.json>releases_temp.json && move /Y releases_temp.json releases.json 1>NUL 2>&1) || (call :ERROR "Update releases.json sig failed") ("%JQ_BIN%" ".Locations.win32."""%APP_VER%""".sig="""!APP_SIG!"""" releases.json>releases_temp.json && move /Y releases_temp.json releases.json 1>NUL 2>&1) || (call :ERROR "Update releases.json sig failed")
("%JQ_BIN%" ".Locations.win32."""%APP_VER%""".sha256="""!APP_SHA256!"""" releases.json>releases_temp.json && move /Y releases_temp.json releases.json 1>NUL 2>&1) || (call :ERROR "Update releases.json sha256 failed") ("%JQ_BIN%" ".Locations.win32."""%APP_VER%""".sha256="""!APP_SHA256!"""" releases.json>releases_temp.json && move /Y releases_temp.json releases.json 1>NUL 2>&1) || (call :ERROR "Update releases.json sha256 failed")
("%JQ_BIN%" ".Locations.win32."""%APP_VER%""".urls=["""!PIXEL_LOCATION!""","""!BITBUCKET_LOCATION!"""]" releases.json>releases_temp.json && move /Y releases_temp.json releases.json 1>NUL 2>&1) || (call :ERROR "Update releases.json URL failed") ("%JQ_BIN%" ".Locations.win32."""%APP_VER%""".urls=["""!BITBUCKET_LOCATION!"""]" releases.json>releases_temp.json && move /Y releases_temp.json releases.json 1>NUL 2>&1) || (call :ERROR "Update releases.json URL failed")
) )
popd popd
) || ( ) || (
@@ -70,36 +69,12 @@ goto :END
set %~1=!%~1:"=! set %~1=!%~1:"=!
goto :EOF goto :EOF
:PIXEL_RESPONSE
set PIXEL_RESPONSE=
if %1==1 (
for /f "delims=" %%i in ('%JQ_BIN% .success upload_response.json') do (
if "%%i" == "false" (
for /f "delims=" %%i in ('%JQ_BIN% .message upload_response.json') do (
set PIXEL_RESPONSE=!PIXEL_RESPONSE!%%i
)
call :ERROR "Upload to pixeldrain failed: !PIXEL_RESPONSE!"
) else (
for /f "delims=" %%i in ('%JQ_BIN% .id upload_response.json') do (
set PIXEL_ID=%%i
call :NO_QUOTES PIXEL_ID
)
)
)
) else (
for /f "delims=" %%i in ('type upload_response.json') do (
set PIXEL_RESPONSE=!PIXEL_RESPONSE!%%i
)
call :ERROR "Upload to pixeldrain failed: !PIXEL_RESPONSE!"
)
goto :EOF
:UPLOAD_TO_BITBUCKET :UPLOAD_TO_BITBUCKET
set SOURCE_FILE=%1 set SOURCE_FILE=%1
call :NO_QUOTES SOURCE_FILE call :NO_QUOTES SOURCE_FILE
call :NO_QUOTES BITBUCKET_AUTH call :NO_QUOTES BITBUCKET_AUTH
echo "Uploading !SOURCE_FILE! to Bitbucket" echo "Uploading !SOURCE_FILE! to Bitbucket %REPOSITORY%"
(curl --fail -u "!BITBUCKET_AUTH!" -X POST https://api.bitbucket.org/2.0/repositories/blockstorage/repertory-ui/downloads -F files="@!SOURCE_FILE!" > upload_response.json) || (call :ERROR "Upload to Bitbucket failed: %SOURCE_FILE%") (curl --fail -u "!BITBUCKET_AUTH!" -X POST https://api.bitbucket.org/2.0/repositories/blockstorage/%REPOSITORY%/downloads -F files="@!SOURCE_FILE!" > upload_response.json) || (call :ERROR "Upload to Bitbucket %REPOSITORY% failed: %SOURCE_FILE%")
goto :EOF goto :EOF
:ERROR :ERROR

View File

@@ -7,6 +7,13 @@ export PATH
ENABLE_UPLOAD=$1 ENABLE_UPLOAD=$1
BITBUCKET_AUTH=$2 BITBUCKET_AUTH=$2
BITBUCKET_TESTING=$3
if [ "$BITBUCKET_TESTING" = "1" ]; then
REPOSITORY=repertory-testing
else
REPOSITORY=repertory-ui
fi
PRIVATE_KEY=../../blockstorage_dev_private.pem PRIVATE_KEY=../../blockstorage_dev_private.pem
PUBLIC_KEY=../blockstorage_dev_public.pem PUBLIC_KEY=../blockstorage_dev_public.pem
@@ -19,7 +26,7 @@ if beginsWith darwin "$OSTYPE"; then
JQ_EXEC=jq-osx-amd64 JQ_EXEC=jq-osx-amd64
SHA256_EXEC="shasum -a 256 -b" SHA256_EXEC="shasum -a 256 -b"
else else
DISTRO_LIST="arch centos7 centos8 debian9 debian10 fedora28 fedora29 fedora30 fedora31 opensuse15 opensuse15.1 solus tumbleweed ubuntu18.04 ubuntu18.10 ubuntu19.04 ubuntu19.10" DISTRO_LIST="centos7 debian9 debian10 solus"
OUT_FILE=repertory-ui_${APP_VER}_linux_x86_64.AppImage OUT_FILE=repertory-ui_${APP_VER}_linux_x86_64.AppImage
BASE64_EXEC="base64 -w0" BASE64_EXEC="base64 -w0"
JQ_EXEC=jq-linux64 JQ_EXEC=jq-linux64
@@ -33,7 +40,8 @@ exit_script() {
upload_to_bitbucket() { upload_to_bitbucket() {
SOURCE_FILE=$1 SOURCE_FILE=$1
curl --fail -u "${BITBUCKET_AUTH}" -X POST https://api.bitbucket.org/2.0/repositories/blockstorage/repertory-ui/downloads -F files=@${SOURCE_FILE} > upload_response.json || exit_script "Upload to Bitbucket failed: ${SOURCE_FILE}" echo "Uploading ${SOURCE_FILE} to Bitbucket ${REPOSITORY}"
curl --fail -u "${BITBUCKET_AUTH}" -X POST https://api.bitbucket.org/2.0/repositories/blockstorage/${REPOSITORY}/downloads -F files=@${SOURCE_FILE} > upload_response.json || exit_script "Upload to Bitbucket failed: ${SOURCE_FILE}"
} }
chmod +x "bin/${JQ_EXEC}" || exit_script "chmod +x ${JQ_EXEC} failed" chmod +x "bin/${JQ_EXEC}" || exit_script "chmod +x ${JQ_EXEC} failed"
@@ -52,31 +60,21 @@ if npm run dist; then
APP_SHA256=$(cat ${OUT_FILE}.sha256 | awk '{print $1;}') APP_SHA256=$(cat ${OUT_FILE}.sha256 | awk '{print $1;}')
rm -f upload_response.json 1>/dev/null 2>&1 rm -f upload_response.json 1>/dev/null 2>&1
curl --fail -F name="${OUT_FILE}" -F anonymous=true -F file="@${OUT_FILE}" https://pixeldrain.com/api/file > upload_response.json || exit_script "Upload to Pixeldrain failed"
PIXEL_SUCCESS=$(${JQ_EXEC} .success upload_response.json)
if [ "${PIXEL_SUCCESS}" = "false" ]; then
PIXEL_MESSAGE=$(${JQ_EXEC} .message upload_response.json)
exit_script "${PIXEL_MESSAGE}"
else
PIXEL_ID=$(${JQ_EXEC} .id upload_response.json|sed s/\"//g)
PIXEL_LOCATION=https://pixeldrain.com/api/file/${PIXEL_ID}
upload_to_bitbucket "${OUT_FILE}" upload_to_bitbucket "${OUT_FILE}"
upload_to_bitbucket "${OUT_FILE}.sha256" upload_to_bitbucket "${OUT_FILE}.sha256"
upload_to_bitbucket "${OUT_FILE}.sig" upload_to_bitbucket "${OUT_FILE}.sig"
BITBUCKET_LOCATION=https://bitbucket.org/blockstorage/repertory-ui/downloads/${OUT_FILE} BITBUCKET_LOCATION=https://bitbucket.org/blockstorage/${REPOSITORY}/downloads/${OUT_FILE}
cp -f ../releases.json ./releases.json cp -f ../releases.json ./releases.json
for DISTRONAME in ${DISTRO_LIST}; do for DISTRONAME in ${DISTRO_LIST}; do
${JQ_EXEC} ".Versions[\"${DISTRONAME}\"]|=(.+ [\"${APP_VER}\"]|unique)" releases.json > releases_temp.json || exit_script "Update releases.json Versions failed" ${JQ_EXEC} ".Versions[\"${DISTRONAME}\"]|=(.+ [\"${APP_VER}\"]|unique)" releases.json > releases_temp.json || exit_script "Update releases.json Versions failed"
${JQ_EXEC} ".Locations[\"${DISTRONAME}\"].\"${APP_VER}\".sig=\"${APP_SIG}\"" releases_temp.json > releases.json || exit_script "Update releases.json sig failed" ${JQ_EXEC} ".Locations[\"${DISTRONAME}\"].\"${APP_VER}\".sig=\"${APP_SIG}\"" releases_temp.json > releases.json || exit_script "Update releases.json sig failed"
${JQ_EXEC} ".Locations[\"${DISTRONAME}\"].\"${APP_VER}\".sha256=\"${APP_SHA256}\"" releases.json > releases_temp.json || exit_script "Update releases.json sha256 failed" ${JQ_EXEC} ".Locations[\"${DISTRONAME}\"].\"${APP_VER}\".sha256=\"${APP_SHA256}\"" releases.json > releases_temp.json || exit_script "Update releases.json sha256 failed"
${JQ_EXEC} ".Locations[\"${DISTRONAME}\"].\"${APP_VER}\".urls=[\"${PIXEL_LOCATION}\",\"${BITBUCKET_LOCATION}\"]" releases_temp.json > releases.json || exit_script "Update releases.json URL failed" ${JQ_EXEC} ".Locations[\"${DISTRONAME}\"].\"${APP_VER}\".urls=[\"${BITBUCKET_LOCATION}\"]" releases_temp.json > releases.json || exit_script "Update releases.json URL failed"
done done
rm -f releases_temp.json rm -f releases_temp.json
fi fi
fi
cd - cd -
else else
exit_script "Create dist failed" exit_script "Create dist failed"

View File

@@ -1,43 +1,43 @@
{ {
"name": "repertory-ui", "name": "repertory-ui",
"version": "1.1.4", "version": "1.3.2",
"private": true, "private": true,
"author": "scott.e.graves@protonmail.com", "author": "scott.e.graves@protonmail.com",
"description": "GUI for Repertory - Repertory allows you to mount Sia and/or ScPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.", "description": "GUI for Repertory - Repertory allows you to mount Sia, Skynet, and/or ScPrime storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.27", "@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-solid-svg-icons": "^5.12.1", "@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/react-fontawesome": "^0.1.8", "@fortawesome/react-fontawesome": "^0.1.13",
"@reduxjs/toolkit": "^1.2.4", "@reduxjs/toolkit": "^1.5.0",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"axios": "^0.19.2", "axios": "^0.21.0",
"devtron": "^1.4.0", "devtron": "^1.4.0",
"electron-debug": "^3.0.1", "electron-debug": "^3.1.0",
"electron-log": "^4.0.6", "electron-log": "^4.3.0",
"focus-trap-react": "^8.3.2",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"node-schedule": "^1.3.2", "node-cron": "^1.2.1",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
"react": "^16.12.0", "react": "^16.14.0",
"react-dom": "^16.12.0", "react-checkbox-tree": "^1.6.0",
"react-loader-spinner": "^3.1.5", "react-dom": "^16.14.0",
"react-redux": "^7.1.3", "react-loader-spinner": "^3.1.14",
"react-scripts": "3.3.1", "react-redux": "^7.2.2",
"react-tooltip": "^4.0.3", "react-scripts": "4.0.3",
"react-tooltip": "^4.2.11",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"unzipper": "^0.10.8", "unzipper": "^0.10.11",
"winreg": "^1.2.4" "winreg": "^1.2.4"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.0", "babel-loader": "8.1.0",
"electron": "^5.0.13", "cross-env": "^7.0.3",
"electron-builder": "^20.44.4", "electron": "5.0.13",
"extract-text-webpack-plugin": "^3.0.2", "electron-builder": "22.9.1",
"fibers": "^4.0.2", "electron-webpack": "^2.8.2",
"node-sass": "^4.13.1", "webpack": "4.44.2",
"sass": "^1.25.0", "webpack-dev-server": "3.11.1"
"typescript": "^3.7.5",
"webpack-browser-plugin": "^1.0.20"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

84
public/detect_linux.sh Normal file → Executable file
View File

@@ -8,89 +8,23 @@ resetDistVer() {
DISTVER= DISTVER=
} }
if [ -f /etc/centos-release ]; then IS_ARM=`(uname -a | grep aarch64) 1>/dev/null 2>&1 && echo 1`
. /etc/os-release
if [ "$VERSION_ID" = "7" ]; then
DISTNAME=centos
DISTVER=7
elif [ "$VERSION_ID" = "8" ]; then
DISTNAME=centos
DISTVER=8
else
resetDistVer
fi
elif [ -f /etc/fedora-release ]; then
. /etc/os-release
if [ "$VERSION_ID" != "31" ] && [ "$VERSION_ID" != "30" ] && [ "$VERSION_ID" != "29" ] && [ "$VERSION_ID" != "28" ]; then
resetDistVer
else
DISTNAME=fedora
DISTVER=$VERSION_ID
fi
elif [ -f /etc/solus-release ]; then
DISTNAME=solus
elif [ -f /etc/lsb-release ]; then
. /etc/lsb-release
DISTNAME=$(echo ${DISTRIB_ID} | awk '{print tolower($0)}')
DISTVER=${DISTRIB_RELEASE}
if [ "$DISTNAME" != "ubuntu" ]; then
if [ "$DISTNAME" = "linuxmint" ]; then
if [ "$DISTVER" = "19" ] || [ "$DISTVER" = "19.1" ] || [ "$DISTVER" = "19.2" ]; then
DISTNAME=ubuntu
DISTVER=18.04
else
resetDistVer
fi
elif [ "$DISTNAME" = "elementary" ]; then
if [ "$DISTVER" = "5.0" ]; then
DISTNAME=ubuntu
DISTVER=18.04
else
resetDistVer
fi
else
resetDistVer
fi
elif [ "$DISTVER" != "18.04" ] && [ "$DISTVER" != "18.10" ] && [ "$DISTVER" != "19.04" ] && [ "$DISTVER" != "19.10" ]; then
resetDistVer
fi
fi
if [ "$DISTNAME" = "unknown" ] && [ -f /etc/debian_version ]; then if [ -f /etc/solus-release ]; then
DISTNAME=solus
elif [ "$IS_ARM" = "1" ] && [ -f /etc/debian_version ]; then
DISTNAME=debian DISTNAME=debian
DISTVER=$(head -1 /etc/debian_version|awk -F. '{print $1}') DISTVER=$(head -1 /etc/debian_version|awk -F. '{print $1}')
if [ "$DISTVER" != "9" ] && [ "$DISTVER" != "10" ]; then if [ "$DISTVER" != "9" ] && [ "$DISTVER" != "10" ]; then
resetDistVer if [ $(grep sid /etc/debian_version) ]; then
fi DISTVER=10
fi
if [ "$DISTNAME" = "unknown" ]; then
if [ -f /etc/os-release ]; then
. /etc/os-release
if [ "$ID" = "arch" ]; then
DISTNAME=arch
elif [ "$ID" = "antergos" ] || [ "$ID" = "manjaro" ]; then
DISTNAME=ubuntu
DISTVER=18.10
elif [ "$ID" = "opensuse-leap" ]; then
if [ "$VERSION_ID" = "15.0" ]; then
DISTNAME=opensuse
DISTVER=15
elif [ "$VERSION_ID" = "15.1" ]; then
DISTNAME=opensuse
DISTVER=15.1
else else
resetDistVer resetDistVer
fi fi
elif [ "$ID" = "opensuse-tumbleweed" ]; then fi
DISTNAME=tumbleweed else
DISTNAME=centos7
DISTVER= DISTVER=
else
resetDistVer
fi
else
resetDistVer
fi
fi fi
echo ${DISTNAME}${DISTVER} echo ${DISTNAME}${DISTVER}

View File

@@ -33,8 +33,10 @@ const DependencyIPC = require('../src/renderer/ipc/DependencyIPC');
const DownloadIPC = require('../src/renderer/ipc/DownloadIPC'); const DownloadIPC = require('../src/renderer/ipc/DownloadIPC');
const FilesystemIPC = require('../src/renderer/ipc/FilesystemIPC'); const FilesystemIPC = require('../src/renderer/ipc/FilesystemIPC');
const MountsIPC = require('../src/renderer/ipc/MountsIPC'); const MountsIPC = require('../src/renderer/ipc/MountsIPC');
const PinnedIPC = require('../src/renderer/ipc/PinnedIPC');
const PlatformIPC = require('../src/renderer/ipc/PlatformIPC'); const PlatformIPC = require('../src/renderer/ipc/PlatformIPC');
const ReleaseIPC = require('../src/renderer/ipc/ReleaseIPC'); const ReleaseIPC = require('../src/renderer/ipc/ReleaseIPC');
const SkynetIPC = require('../src/renderer/ipc/SkynetIPC');
const StateIPC = require('../src/renderer/ipc/StateIPC'); const StateIPC = require('../src/renderer/ipc/StateIPC');
const SystemIPC = require('../src/renderer/ipc/SystemIPC'); const SystemIPC = require('../src/renderer/ipc/SystemIPC');
const UpgradeIPC = require('../src/renderer/ipc/UpgradeIPC'); const UpgradeIPC = require('../src/renderer/ipc/UpgradeIPC');
@@ -199,7 +201,7 @@ const createWindow = () => {
autoLauncher autoLauncher
.isEnabled() .isEnabled()
.then((enabled) => { .then((enabled) => {
trayContextMenu.items[1].checked = enabled; trayContextMenu.items[2].checked = enabled;
mainWindowTray.setToolTip('Repertory UI'); mainWindowTray.setToolTip('Repertory UI');
mainWindowTray.setContextMenu(trayContextMenu) mainWindowTray.setContextMenu(trayContextMenu)
}) })
@@ -308,16 +310,31 @@ if (!instanceLock) {
configurePrimaryApp(); configurePrimaryApp();
} }
const AppFunctions = {
closeApplication,
detectScript,
dialog,
getCleanupReleases: () => cleanupReleases,
getMainWindow,
saveUiSettings,
setIsInstalling,
setTrayImage,
setWindowVisibility,
standardIPCReply,
unmountAllDrives: MountsIPC.unmountAllDrives,
};
AppIPC.addListeners(ipcMain, closeApplication, setWindowVisibility); AppIPC.addListeners(ipcMain, AppFunctions);
ConfigIPC.addListeners(ipcMain, standardIPCReply); ConfigIPC.addListeners(ipcMain, AppFunctions);
DaemonIPC.addListeners(ipcMain, standardIPCReply); DaemonIPC.addListeners(ipcMain, AppFunctions);
DependencyIPC.addListeners(ipcMain, standardIPCReply); DependencyIPC.addListeners(ipcMain, AppFunctions);
DownloadIPC.addListeners(ipcMain, standardIPCReply); DownloadIPC.addListeners(ipcMain, AppFunctions);
FilesystemIPC.addListeners(ipcMain, getMainWindow, dialog); FilesystemIPC.addListeners(ipcMain, AppFunctions);
MountsIPC.addListeners(ipcMain, setTrayImage, standardIPCReply); MountsIPC.addListeners(ipcMain, AppFunctions);
PlatformIPC.addListeners(ipcMain, detectScript, saveUiSettings); PinnedIPC.addListeners(ipcMain, AppFunctions);
ReleaseIPC.addListeners(ipcMain, () => cleanupReleases, standardIPCReply); PlatformIPC.addListeners(ipcMain, AppFunctions);
StateIPC.addListeners(ipcMain); ReleaseIPC.addListeners(ipcMain, AppFunctions);
SystemIPC.addListeners(ipcMain, closeApplication); SkynetIPC.addListeners(ipcMain, AppFunctions);
UpgradeIPC.addListeners(ipcMain, setIsInstalling, MountsIPC.unmountAllDrives, standardIPCReply); StateIPC.addListeners(ipcMain, AppFunctions);
SystemIPC.addListeners(ipcMain, AppFunctions);
UpgradeIPC.addListeners(ipcMain, AppFunctions);

View File

@@ -1,259 +1,84 @@
{ {
"Locations": { "Locations": {
"arch": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"centos7": { "centos7": {
"1.1.4": { "1.3.2": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b", "sha256": "a8b9bff91d0f0685041737cfa40b4bc8ef765d24419104f3dd90f977023e7858",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=", "sig": "CBEtKUDJwjPrchhNXvzulpfaBWFElDD+Kn9b14oF426IuKncsmqTWUesG3cT/beDk1onaDOhDIchyan6iW/BCdB+sVAuGAoRpm2lJ4lWba7ZPnXP7H10uIjVf1xASwIciP4jpAUPo0bO8hi9kqu1zkF9GeVHHb+SXlLhDP31wN7kHwgb/MeZ95/NqfucumYcwfFOZOnofSKaGQwHpbypy57YzyVCf/NzQDnYC46lcH9xVMmbgodT+y5MIIF0kkSJn0DbSfxLVMWTx1pnZUzJ7J827EKUgAS4d4uLI7ShskS+d84i8rNV9gXgqwYkqDvYbdFi7ArJZB7GJtyZ4VPTqRqGfxwF8rL3K0ml+635DfEXiHN8MbTuqwLHD2giChOfsdAAV4ngJ8xg7+Th8dqpgC92GXoOjNK7GSAyrOFsQ7xeqdVOBZMNk/dHVAb9eCTbPIsOjGFTAzOk8n3roM8naDUOq7PefUj1Jb2EGVhWnwS2ngvhNfRggZmmr3l9IElddaHro0JpnubsFmiAa+akV0P8VLb1jjpIJRfr2CIEnPTQbT4UQ1DdBitgkWiPbagTp8BQF1YGcVZ8jc9mbOxmF46z0MyuMQZjDHw2Vai0uPsoLIJHhy3I7nJqsnEw6gQX/l1VGj9wPHeaqMgGYGFykFfvVssh0CH45tUCdWL6Q9Hgx8+yLw0Lky/HIGMQc0qZUM9ute/9Buifd9cOK8+S5PaA0Zg0G/pIY6fThtO86wKJQnoPeiuBkJ9yXc20ZGrfb39Qnf2nZxrPyVl2iQJlven6CDBxc88henxzvxK61bXUynK8zHVuoIAC7ejWZEbPOqDV3DOOSVDz1+eq1MU2KZ4JmaLipzbdy/y+ph1TnCGwdL9R3s+xLYSaxA+ybhQVNXHhQ7+eZD6CWXytaIVYe8giL1ki8oN/18Bm1B2dVxCZ4KKbAToFpBOGvW+w8JFAC5nBK5aj2BGWEocyWQ1XIJmqVBd8RSOMYVYjQOhVnIvyJxOqvnrLUoRzJruj8R/riuCSMnfq17ykpjCzoNzIjvimCPyAib9kdWx7M8dyOS9Sls2sMLwJvDcsGi/FfuGqBmV2v28XagRZz70iypqNaIUrj4CShfz8e110su3BtLQHr1O6yffxy+whbB7w+ogZ6gz86AZ/63Zz6xbLIk4wuNx1UL/ZuRMGq4xOW/7zRpOYYSe/h98YFd9/UTcoi8Osjl+o1eVup6YWVeDvOG69uqwfID8XHivJnEV2NVFx8fbb6fHSrMg/QbjSngdyQ3b+5rWiZG01DGCDB5RFOvEKY24qrXXWQGAx+JXbj+/7xYWts+/Fv7hSqvpvf7H8aVCoOKXGtjJiy+K/9tAJJLQDAzo=",
"urls": [ "urls": [
"https://pixeldrain.com/api/file/KfWzWfxp", "https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.2_linux_x86_64.AppImage"
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"centos8": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
] ]
} }
}, },
"darwin": { "darwin": {
"1.1.4": { "1.3.2": {
"sha256": "8c91046a83b689564355be3e0a3e194ab986c4633f112696e33e4bb7ebd7e46e", "sha256": "d8e5749a43541edaeaa91cbc81c42fb21c8e4c8446c9c1dff0a8e3a0efc23270",
"sig": "BBds0xSaIqr2rns+cSr90bmxtfy2aIGNVcKgRfMYYqDntV5SkWxD8ivqPglFiLQA/jfgd5A8DjMJs4FA6FDvF1ZFcfgZLFw94OT1M3jPJ7cf5Izv1EQb+5AV7SK8H7DACDixcgKDDXO0WARmeqNXJZzyxkny8XKIiGDwLbmrXfFVuKPSLucbf6eaTem+xJHjQPzP9FH4Lwf9kbQxis2/PIT63yflQdM9CAAvfZUB4YUCOF5T8I43uNV2YDR0Z+k5FLTBexJDORDRixbrldWqNo7NQlNMR+26LY2IBppQqOMtDw9cpcfPj8bNgkWsZkFk/eOcrcpc8ENQodGxb01JahKJ20g9soJEAOhiFW3TOxzOLNq8cQFdKYgJv2K68lav/QzVPD6n14Bsr3+5mkX4fE1GmmN1HLgFO+JbD25qDrS4ExKR5TJ2Zdu136n/Mxbj2apYDhWtU8X6OD86F29m348XDonC9Fn+vzoE2M4xNKptMhukem7NFG7Z03v6+J73PaVM1NldARMGgVUYFA6QTn6Ns55YEXnsHoSoreHmgl5y3s8eRo0/oehv0cpPG6shCz71+YlVG/IgbKWIgYZ0jj03Cdsgv/XyadiwfCI9Q7uvl368Vlbuad4mqttMTZwHeZlKikTYb74QDyXqVoz0Q2ij7WET7MkXpA3++hDrPIN5Cb8qi/YuMp63w6TLfz4c/ZCQeje+0d7x6p63Z7TObEXDyBgdUAphX3T/u4pkEjXQKsfNRh8jaFOXU3z+hG8+IXjrZtLcDEzqICZ8JJCrkklgliTM1ECiekvJ9jwXTN2ClrzLQ3qhX8N+8jkvQ+yZxGxH+3rOX55RyW4OcMYUYvDpTtNHncVvABBlRvPGEGxtA7RMDTzOGe0lk70y07al6iFKDktaoYSFlcH+pUw7ifDZamefxlKDmtrMOMNmnrm+v8WH0g0MG8qy/nGPxv5dFIl9gAC/rvRElxX8FpA7y18i7fQ0IfhpaC58gZZADkF0nFOW15N7zwjS3qnhAGCBCBEDwlFG9brD71qjShtodgnz+GvrDvtqGTwNRllnZXSHPCWe33BOHy2c30LwE8vJq+ChVaJobCA9P8ipgZv/gAuZz7LNFc/Uong5TyKZk2uXun3XF1Qkw+ogBpl9aqMWIySZhkDnBcZjjE9pL/FQAkO1H90+zNnSdSAKZQaPckxLSyqHXxb4P7Da4nj3tad609NcxEzsz3dpEJTuYlqLqmO5kJmy29jNUHhQQdPCuKJtdm/coz3kWtC+uR6/SMnhviGnc7tQUqyb6HfGywKbmoAM93NHSGxNGyNMU+VJ20/YK1lmVFo3Pg/wq+aX35utcjQafpATqHeOeDw26fnZxBs=", "sig": "AKXLbLDnkBlB2b8dKLPZ5Vr9bcbv1dCvYGJTCltxYRJkOBzmQMTJ+ADKAtQQCfZKtNji+Dez28A401BMUY7xBd3Sh4FDnmNv64Q2zfqxQpOkhdrK8KcwK2aFCFbWn3vTnU3GBk2IXqGY8qIeR4d/v2UaBnnpK0EulztxPXojLz0pA6DVndJOodItoE6OUNohPd5MTWh5ewpjXJN+VKeao3ABkXgCzYLH/SSYCyi8ZFH8Ija20HJk8ST2QIokmRQGk5MDcyxylyWkusGOf7yn9M6vdtHNfrHfWXGxnMTuaw9+YNTuxZNDgIFpmoVx4t7HSoNti+ZZwyo+1jtDeh0xPmdEGpnZOY7zp94zlh4zP9Cx5cYg6W5/NQKQzDtlUZlELqR8Kwoa/bDCwWeSkifGqEJT2Yezs9r5llSoIh8PrxLkmdaX2vkOvaRYYE0kvdO2MllT8SSyYkum/9eD8uA52IYbUliJqfAf43gOj9n0ooRcCo45lBY5PIUB44XBLEGK15OuarybSyaBDMFbc3HWh1A9jB9kSPctnMMJMNMsocTI5AEN8eRbLyF7z01WE+nB7JDS8NW3+QRFyQInxdXpcGeKO6riuSO7aXHLWAZvUVSxThSuV7JtsugtUnIsP9ah7njtcVHOSEcUG3U2WxZRbe6PG5pdjS4Px+E9Yx35zLSwo39TcwLb2fM7mQKREsH0fUrRd4WN1T0pWOuzDlWSq4pfDXWdO8y828l8XVJzw5LUH9U0F7qAiuzEVQIYJ+xjjAp4JVJXBof4iWeW4N+7PYgntEgr94Ti6rjb6BrKWdl4B+9j79EkC2yLWafcGnryUaSfOFDFiB8Upf6NQ+a1TxY5ufFe1JfuI58rchUZi4SjNdyFMat4GSdTI6I513ZVcohBb1KVIxEM0u38hJqKIFx3stWMZrLvyFH9ceqzXzVQCIDE31usUKFsXallWMQyB2kIy7qoif7zmDhdJQZboD7ZHsTrcSK1u9D+h3oG1poN+AULmhgYZNT11ktRiVUbmzDS0qA7lnF8dcsuthj0vHP0/BFN6a7ljQuwDVY1xoeCf+RobfNWucK2MLxmqV1z9nO4jpdy/GfC/5ln4Tev+5luo5ux0BXXXAWuzGyQ0QK+13k3lIUwxsDNou0dMDHJPaMavBjmlm0Uf5esZdSIU9U2sdfuO+j/6enwGvESlrKmBCsdPatHREP931dGPZCsF1m30OylV+m7WhBh9w6AbklmjHNCZu3C2Ln23gOKGX8HpQ9nnaPCicmlfSyBjPoKVtDCACVbjwRfTJ+UXCocrCVSbAhskOSvlq050QFQcZO5DzFWdeTfG6x6MrqdSelYThjy5xn8AXuqsFSLIdYpCAQ=",
"urls": [ "urls": [
"https://pixeldrain.com/api/file/68WJfKsz", "https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.2_mac.dmg"
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_mac.dmg"
]
}
},
"debian9": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"debian10": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"fedora28": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"fedora29": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"fedora30": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"fedora31": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"opensuse15": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"opensuse15.1": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
] ]
} }
}, },
"solus": { "solus": {
"1.1.4": { "1.3.2": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b", "sha256": "a8b9bff91d0f0685041737cfa40b4bc8ef765d24419104f3dd90f977023e7858",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=", "sig": "CBEtKUDJwjPrchhNXvzulpfaBWFElDD+Kn9b14oF426IuKncsmqTWUesG3cT/beDk1onaDOhDIchyan6iW/BCdB+sVAuGAoRpm2lJ4lWba7ZPnXP7H10uIjVf1xASwIciP4jpAUPo0bO8hi9kqu1zkF9GeVHHb+SXlLhDP31wN7kHwgb/MeZ95/NqfucumYcwfFOZOnofSKaGQwHpbypy57YzyVCf/NzQDnYC46lcH9xVMmbgodT+y5MIIF0kkSJn0DbSfxLVMWTx1pnZUzJ7J827EKUgAS4d4uLI7ShskS+d84i8rNV9gXgqwYkqDvYbdFi7ArJZB7GJtyZ4VPTqRqGfxwF8rL3K0ml+635DfEXiHN8MbTuqwLHD2giChOfsdAAV4ngJ8xg7+Th8dqpgC92GXoOjNK7GSAyrOFsQ7xeqdVOBZMNk/dHVAb9eCTbPIsOjGFTAzOk8n3roM8naDUOq7PefUj1Jb2EGVhWnwS2ngvhNfRggZmmr3l9IElddaHro0JpnubsFmiAa+akV0P8VLb1jjpIJRfr2CIEnPTQbT4UQ1DdBitgkWiPbagTp8BQF1YGcVZ8jc9mbOxmF46z0MyuMQZjDHw2Vai0uPsoLIJHhy3I7nJqsnEw6gQX/l1VGj9wPHeaqMgGYGFykFfvVssh0CH45tUCdWL6Q9Hgx8+yLw0Lky/HIGMQc0qZUM9ute/9Buifd9cOK8+S5PaA0Zg0G/pIY6fThtO86wKJQnoPeiuBkJ9yXc20ZGrfb39Qnf2nZxrPyVl2iQJlven6CDBxc88henxzvxK61bXUynK8zHVuoIAC7ejWZEbPOqDV3DOOSVDz1+eq1MU2KZ4JmaLipzbdy/y+ph1TnCGwdL9R3s+xLYSaxA+ybhQVNXHhQ7+eZD6CWXytaIVYe8giL1ki8oN/18Bm1B2dVxCZ4KKbAToFpBOGvW+w8JFAC5nBK5aj2BGWEocyWQ1XIJmqVBd8RSOMYVYjQOhVnIvyJxOqvnrLUoRzJruj8R/riuCSMnfq17ykpjCzoNzIjvimCPyAib9kdWx7M8dyOS9Sls2sMLwJvDcsGi/FfuGqBmV2v28XagRZz70iypqNaIUrj4CShfz8e110su3BtLQHr1O6yffxy+whbB7w+ogZ6gz86AZ/63Zz6xbLIk4wuNx1UL/ZuRMGq4xOW/7zRpOYYSe/h98YFd9/UTcoi8Osjl+o1eVup6YWVeDvOG69uqwfID8XHivJnEV2NVFx8fbb6fHSrMg/QbjSngdyQ3b+5rWiZG01DGCDB5RFOvEKY24qrXXWQGAx+JXbj+/7xYWts+/Fv7hSqvpvf7H8aVCoOKXGtjJiy+K/9tAJJLQDAzo=",
"urls": [ "urls": [
"https://pixeldrain.com/api/file/KfWzWfxp", "https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.2_linux_x86_64.AppImage"
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"tumbleweed": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"ubuntu18.04": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"ubuntu18.10": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"ubuntu19.04": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
]
}
},
"ubuntu19.10": {
"1.1.4": {
"sha256": "85347471f33dd41a35eb296cda03590d451322cd54a077cef470ccbfe52a141b",
"sig": "CGialiJMT6nETdGgP3vQvsXSWQYUEX7j7Cck0NSiW0cE4A1kW3mabM2I/5Yt4NwkDqBIAXofPyUd1JQ4UrF8cijfLNv3ECSaDJs7bWrISDr3Z8ti0uZnN3RUdJDCYDvAWBVqGC82Q1fmLkYJhd33o2B9DzjmvO+i7+buNyDB3dBIY5Fa3rnLKsIY7WB5EASBG3TzDlsZOtLvNgKo/3fLJP2RFyczSVCFZeHlppJbyXCakj50eCNbxjP6SLaD3oE+M0xGvyPyKTk0OGoJTGKM13q30WKbM6cMfL87BbUueGkBFDZeNMWU8Mz/fdGG21WqNN1p7k3CilJJQz7ZEE9bBhlsIX5j5rvL2qNYJoQbJQbxVjyoWHOOlqA62WUTOZhyiHD3OWQ58Fd9JqGDiIhg+ChIBWzyEXNdE6GQr7/qEiAQDbEsU06WOzfj3uxAV//iy/noalpyhlnr0TUu5xKSnoR7jQhCtvOdlrmgs8y+mTEpwDjXCR0S7jDxp+cEbVHb57Rl8N5thVrOs5RQfshhcqwBR/scCM9iCr0v3D7uyGMinTBT3A8lwerN8iO17FhtEerovn4Zhg2p7quZRiUcYLdx4EOlIMWuORyDk3s0S4VZ27t92NYwMlwMA1TbUQ/XuN1oZaQQ9ipSDjNDDVZto3Q4vwlqL3g1TIs1JyxfHkwUTpuFjg3ZqY+uRNxeJTDlj9JHXiAIZ+VeBggB7EixKE+wMi1A6a+Si/ZpBbroK5gP1gODShXYUN4eGmodxqF2/gVrJcimEeHkyBZ74dZIa9gTSIOxeo1OS70fFU+hQy9/V0/q4PElPZn+EzLTGknWh1EDmqReZ1svv0tYf0Ve/l5szhdNGsb2LUEZ7G/r0Bs7fyQjA5YZI+byOPTSA1Q9vlT63sodpBRDURWkqTRFRPWlaEY2kMZu/vJ422PSjK51X4EPHWmpOkDkxFzx2WAs2rKZ9QXCkU0RGu0Ctial/zNVhm20eVklcuIirEnmUK9Kod0KBmjl+U6U6jHvlhOEC4zp2QcDsrLSQu7gDHvJWbYmaKBTiXrEvk52G/EN9aD86Am5k0bwPU5ktj8z38+5sxVns1R4e9J1WaZgD2dtFvpSxJbjVf09AZHIeyNw/BRu3GfJU6dX6KJVMCMtN+R5xv/ZckylI355R4nrf+7PLNsGg2tqlemvHiwdQkOb3/TOLrJpO94JRjm9k16huysztkpgJIGyKvHXT6z1A3cwCoJzrK7sQ1awedPvO+N8UGJ7kXHRgLLhQ2yWfZfIsD/s0Ji5GFrItm1nlqHAd/ZFh82jNKmvh6+n42l5TimH9Q0Cf+7bK5Y7SVYzhSAW2B8ivHsPMpAkLJ6d17ZMxtrr4/Q=",
"urls": [
"https://pixeldrain.com/api/file/KfWzWfxp",
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage"
] ]
} }
}, },
"win32": { "win32": {
"1.1.4": { "1.3.1": {
"sha256": "c942d2779e7da597605d9fb8fa2876f9c82e5255ee859dac47d209e64f6d1707", "sig": "29b91fcba36fc7cac38837674559c6af1169b3dc54bd57284c3ecf44a1af869d",
"sig": "AhYYFZxYLVibiRvc0PyOSnU+CWoaVwWvKfMWibdYsxINuwPUMVRjvXe2dPB8ghqe3qODw4XswZnwu7BwqTbCwaEDNdj8f4kpmd0cNnFTP4jP9q0qKcM4thOq1rCr8VuptS8UsTT0UXVqExf2K06efyrG1GYjeTG9LJpCJ5YFiet2vrarqHap/FiRjgVhRcE6mkoDPLj+eu9z9AFPQ4uNrMazFgHj7Vbq4H+oEIwMiuC6umbd3xGBSvlmudp3OONYrybwIuMpqaUPv874eFQOfrlYVPadAr9vn6QxFEjl1rBkd9xQNte/CBYNe1/fD5p0iLjJOAqz24cgZCMpKsRUOpgJcwf7NVU819Ymbu9/zVtBJ2F/T2wg2o8wTyymWqTdMSEDZu0fUb3StHzcrEqFzAHckyDF6d6qrKBXc8bzs0T85FQUZUqzoZng3b+Y2xtSoaiJA/6Es+Qx6t1T7dXqtN7xu43ZRrNNLcLG/FtHx8X7UuAzNt4FL5kbPM3EMtK1J3gsDpTQSsLKPKTWZXuEfUOCI/b1psL3YcsQv9Ta1o2I9bGqYTJtsGTvCkXG20KoqcqXiYFyGWTnztd8jcPhvxjI9aI0sblOTMN4lBmkNroOkSu39/+z8UvsKgioE1FNmjr5edtqbXdJ2C/6PZITKe3Hl+sfJDHinn3QhYp/Hl9gzL64KD2lJRiM7rCWYfyUvMnwewG5aQvkE/Jf9t05DYCX9rwbUoz8QSV/HBGOXrrDpo+tJeZ74FbaNVlW4IApnpSdQpVqwdJCf7rdPwOm13DvkgSDjlz02BdLKk93X0fX4zCGnF/RNYw4meLkbs0UmrDml9hCfEHRjrYJe0i9GqRwyBH9kp+sbBTYcomZgro24pEQRXi6DoK5xjpu8K+ywAAjWFn7vrZQeuNTmw5Itib2xQQnFzbg/u35KFXvaZ6c0MBi+rIwUQTqOO1l6U6PN5+U7imRxDKvy6y1GsusJGXee4HATJiZc/5PCRK42A2YJ1NBHDJOm0aLcx8oboYtmvGOIObAcpEIkfrDdykxBvpxFPBW6AXacj5vwi0Io+1TjB7aXmyRiOGV/nzerUdFTtnbkvgOLIGiOy+B3NN6b8WZVkbubcaWBqW0yuHwVcRJtsbhtiLhGXc4n3iFPR+1gJCHd0rUKF2HKs3MALk4Gh/V1rZqY7iklceEJkMgCHwKZ56vIYiNkwT5uco0UNEpOAedAfRMBAvs1WQuyltdYlDL/9XwPfc6Wg7BHL1wfmq/wFaE4kKtbEXM+Esh9GmLXPcsNrDU71hkhTTl0edmXlp4f8olJjJxas0YerwyVvhIDlG9RSV0PewYIWnLevb9kA9xAGmFbgp4yfyWAqC2ORY=", "sha256":"BLy7sutkbfkK39sOPPlDdn8DVoorVC7NiEl7LvCqp9Dcnt+6afmszRLFYNu0ipu2krN8HdA2uCqCS2lAlNwOzGzkCGN6RHfobHRqj/HObNrWsvGKSp+Vdt96J76TmrV/gjPGLJOBq5dobBGmN1KJSrkLpWj6IOoEuR4/7WkSJah4XWMIfVlPAZKwP6yNv6OaYqjIy74geKkoCI5/mmwDzUX/T8M3DsdSbm3kuYJ0S3E3mEO2lGH++nxDDPMdA2yjFbBwHU2RFQShbL7jB/AqodSrIU4wOstbRWiF34KUZ/KYG+ZFvwEBshbgDoZQyNbVmFDD86g/sEatnQRpN7juGBWwScgPfoTbdl9y0lf5U85dicOe7+iholAUVprLuPk9Y3NVXoBFM9g9mkqgMVoYoSUgh/BJBnomDgvt1pd4Id/CO26buhSU28fRbQ/c8i+x5zdsfqN7p/64sj/2Xp0HQMwFmc3IotjDEtUTcfI+VGP62pJLMao5vheZ8JU05GXn7ZA7Sojfov9luuoajguukXHEclaxgWKyo0u/ZcowExrKpkegxui6v/3+W/pTv/cSg01CFyJTMTvT9cT7O0bWSdUcxOgpYptV5wVOdglUm/aQscqtDVaVv90ZMNldQ2YVI6wuFTIcUpWNCa/r5oIcGVWWrxBu9uq0jLF3AoTNpLeLJuJL37KacJwNqNDUolG6bwdjvAI1cpcy7fGrzbGE95pPmHpruVfKpLpSQ7tKnT5dV9H2gQz63gl/QzvVGnov0pLUNuJwQQhdvcGZXnVcB96u0i8GBacr4WZVhhJWGWk/u5zx5ganXcXYkodDLN4zpI/aV/TdW2QH2xN8OOmQOZ8yZS9toS0vcIUeoT6eQUg5clJniKtivUv2GSldGzKG9xk3s/2IQlivOX2BxnTSYaP1AkWTUSyJeXcEYbYytLiFrMj875NxeYfKD74SGfxWh//NWyzL7GG/q8G77xw/1s4LorzhSN5jifyPgtSWI4h3WaSu6nfLJHioujAEMDwhkwP9mPmPmBUbTveKWdTZy7YQGGN+4DMJLoGQnx0qBbsIOWfmt+/cjQ72Sx8V7oRzElSohxg9q/oCKkMo4f1JuS1kOGasbPnXjjjuWTX1bOeQ86TjBi+wSt1Y5VgifvyfEdEeDvvwrcm2Cp3NnWYAXhqE5tgVUIYLEtsbHaAtWOv4XSlp8EISd1XIDeTADHNonEEWUWGjNJvylQ05iGUjQny6CObSN3gNbwL0xO/gI1o1+SR4HsM5u2leIKaD5hU/6OdsorVX53b1xPNLyLrDr1uMgfM6lGgCyrs8DY5hVIujIycTSEbaAGpMA2+ovzpa9jO/cZ0/slMMf55Roic2yCQ=",
"urls": [ "urls": [
"https://pixeldrain.com/api/file/h5HoAqxF", "https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.2_win.exe"
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_win.exe" ]
}
},
"debian9": {
"1.3.2": {
"sig": "CBEtKUDJwjPrchhNXvzulpfaBWFElDD+Kn9b14oF426IuKncsmqTWUesG3cT/beDk1onaDOhDIchyan6iW/BCdB+sVAuGAoRpm2lJ4lWba7ZPnXP7H10uIjVf1xASwIciP4jpAUPo0bO8hi9kqu1zkF9GeVHHb+SXlLhDP31wN7kHwgb/MeZ95/NqfucumYcwfFOZOnofSKaGQwHpbypy57YzyVCf/NzQDnYC46lcH9xVMmbgodT+y5MIIF0kkSJn0DbSfxLVMWTx1pnZUzJ7J827EKUgAS4d4uLI7ShskS+d84i8rNV9gXgqwYkqDvYbdFi7ArJZB7GJtyZ4VPTqRqGfxwF8rL3K0ml+635DfEXiHN8MbTuqwLHD2giChOfsdAAV4ngJ8xg7+Th8dqpgC92GXoOjNK7GSAyrOFsQ7xeqdVOBZMNk/dHVAb9eCTbPIsOjGFTAzOk8n3roM8naDUOq7PefUj1Jb2EGVhWnwS2ngvhNfRggZmmr3l9IElddaHro0JpnubsFmiAa+akV0P8VLb1jjpIJRfr2CIEnPTQbT4UQ1DdBitgkWiPbagTp8BQF1YGcVZ8jc9mbOxmF46z0MyuMQZjDHw2Vai0uPsoLIJHhy3I7nJqsnEw6gQX/l1VGj9wPHeaqMgGYGFykFfvVssh0CH45tUCdWL6Q9Hgx8+yLw0Lky/HIGMQc0qZUM9ute/9Buifd9cOK8+S5PaA0Zg0G/pIY6fThtO86wKJQnoPeiuBkJ9yXc20ZGrfb39Qnf2nZxrPyVl2iQJlven6CDBxc88henxzvxK61bXUynK8zHVuoIAC7ejWZEbPOqDV3DOOSVDz1+eq1MU2KZ4JmaLipzbdy/y+ph1TnCGwdL9R3s+xLYSaxA+ybhQVNXHhQ7+eZD6CWXytaIVYe8giL1ki8oN/18Bm1B2dVxCZ4KKbAToFpBOGvW+w8JFAC5nBK5aj2BGWEocyWQ1XIJmqVBd8RSOMYVYjQOhVnIvyJxOqvnrLUoRzJruj8R/riuCSMnfq17ykpjCzoNzIjvimCPyAib9kdWx7M8dyOS9Sls2sMLwJvDcsGi/FfuGqBmV2v28XagRZz70iypqNaIUrj4CShfz8e110su3BtLQHr1O6yffxy+whbB7w+ogZ6gz86AZ/63Zz6xbLIk4wuNx1UL/ZuRMGq4xOW/7zRpOYYSe/h98YFd9/UTcoi8Osjl+o1eVup6YWVeDvOG69uqwfID8XHivJnEV2NVFx8fbb6fHSrMg/QbjSngdyQ3b+5rWiZG01DGCDB5RFOvEKY24qrXXWQGAx+JXbj+/7xYWts+/Fv7hSqvpvf7H8aVCoOKXGtjJiy+K/9tAJJLQDAzo=",
"sha256": "a8b9bff91d0f0685041737cfa40b4bc8ef765d24419104f3dd90f977023e7858",
"urls": [
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.2_linux_x86_64.AppImage"
]
}
},
"debian10": {
"1.3.2": {
"sig": "CBEtKUDJwjPrchhNXvzulpfaBWFElDD+Kn9b14oF426IuKncsmqTWUesG3cT/beDk1onaDOhDIchyan6iW/BCdB+sVAuGAoRpm2lJ4lWba7ZPnXP7H10uIjVf1xASwIciP4jpAUPo0bO8hi9kqu1zkF9GeVHHb+SXlLhDP31wN7kHwgb/MeZ95/NqfucumYcwfFOZOnofSKaGQwHpbypy57YzyVCf/NzQDnYC46lcH9xVMmbgodT+y5MIIF0kkSJn0DbSfxLVMWTx1pnZUzJ7J827EKUgAS4d4uLI7ShskS+d84i8rNV9gXgqwYkqDvYbdFi7ArJZB7GJtyZ4VPTqRqGfxwF8rL3K0ml+635DfEXiHN8MbTuqwLHD2giChOfsdAAV4ngJ8xg7+Th8dqpgC92GXoOjNK7GSAyrOFsQ7xeqdVOBZMNk/dHVAb9eCTbPIsOjGFTAzOk8n3roM8naDUOq7PefUj1Jb2EGVhWnwS2ngvhNfRggZmmr3l9IElddaHro0JpnubsFmiAa+akV0P8VLb1jjpIJRfr2CIEnPTQbT4UQ1DdBitgkWiPbagTp8BQF1YGcVZ8jc9mbOxmF46z0MyuMQZjDHw2Vai0uPsoLIJHhy3I7nJqsnEw6gQX/l1VGj9wPHeaqMgGYGFykFfvVssh0CH45tUCdWL6Q9Hgx8+yLw0Lky/HIGMQc0qZUM9ute/9Buifd9cOK8+S5PaA0Zg0G/pIY6fThtO86wKJQnoPeiuBkJ9yXc20ZGrfb39Qnf2nZxrPyVl2iQJlven6CDBxc88henxzvxK61bXUynK8zHVuoIAC7ejWZEbPOqDV3DOOSVDz1+eq1MU2KZ4JmaLipzbdy/y+ph1TnCGwdL9R3s+xLYSaxA+ybhQVNXHhQ7+eZD6CWXytaIVYe8giL1ki8oN/18Bm1B2dVxCZ4KKbAToFpBOGvW+w8JFAC5nBK5aj2BGWEocyWQ1XIJmqVBd8RSOMYVYjQOhVnIvyJxOqvnrLUoRzJruj8R/riuCSMnfq17ykpjCzoNzIjvimCPyAib9kdWx7M8dyOS9Sls2sMLwJvDcsGi/FfuGqBmV2v28XagRZz70iypqNaIUrj4CShfz8e110su3BtLQHr1O6yffxy+whbB7w+ogZ6gz86AZ/63Zz6xbLIk4wuNx1UL/ZuRMGq4xOW/7zRpOYYSe/h98YFd9/UTcoi8Osjl+o1eVup6YWVeDvOG69uqwfID8XHivJnEV2NVFx8fbb6fHSrMg/QbjSngdyQ3b+5rWiZG01DGCDB5RFOvEKY24qrXXWQGAx+JXbj+/7xYWts+/Fv7hSqvpvf7H8aVCoOKXGtjJiy+K/9tAJJLQDAzo=",
"sha256": "a8b9bff91d0f0685041737cfa40b4bc8ef765d24419104f3dd90f977023e7858",
"urls": [
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.2_linux_x86_64.AppImage"
] ]
} }
} }
}, },
"Versions": { "Versions": {
"arch": [
"1.1.4"
],
"centos7": [ "centos7": [
"1.1.4" "1.3.2"
], ],
"darwin": [ "darwin": [
"1.1.4" "1.3.2"
],
"debian9": [
"1.1.4"
],
"debian10": [
"1.1.4"
],
"fedora28": [
"1.1.4"
],
"fedora29": [
"1.1.4"
],
"fedora30": [
"1.1.4"
],
"fedora31": [
"1.1.4"
], ],
"linux": [ "linux": [
"unavailable" "unavailable"
], ],
"opensuse15": [
"1.1.4"
],
"opensuse15.1": [
"1.1.4"
],
"solus": [ "solus": [
"1.1.4" "1.3.2"
],
"tumbleweed": [
"1.1.4"
],
"ubuntu18.04": [
"1.1.4"
],
"ubuntu18.10": [
"1.1.4"
],
"ubuntu19.04": [
"1.1.4"
],
"ubuntu19.10": [
"1.1.4"
], ],
"unknown": [ "unknown": [
"unavailable" "unavailable"
], ],
"win32": [ "win32": [
"1.1.4" "1.3.2"
], ],
"centos8": [ "debian9": [
"1.1.4" "1.3.2"
],
"debian10": [
"1.3.2"
] ]
} }
} }

View File

@@ -11,8 +11,13 @@ import InfoDetails from './components/InfoDetails/InfoDetails';
import IPCContainer from './containers/IPCContainer/IPCContainer'; import IPCContainer from './containers/IPCContainer/IPCContainer';
import Loading from './components/UI/Loading/Loading'; import Loading from './components/UI/Loading/Loading';
import MountItems from './containers/MountItems/MountItems'; import MountItems from './containers/MountItems/MountItems';
import NewReleases from './components/NewReleases/NewReleases';
import {notifyError} from './redux/actions/error_actions'; import {notifyError} from './redux/actions/error_actions';
import Reboot from './components/Reboot/Reboot'; import Reboot from './components/Reboot/Reboot';
import {
setDismissNewReleasesAvailable,
setNewReleasesAvailable
} from './redux/actions/release_version_actions';
import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVersionDisplay'; import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVersionDisplay';
import { import {
displaySelectAppPlatform, displaySelectAppPlatform,
@@ -28,9 +33,13 @@ import {
} from './redux/actions/release_version_actions'; } from './redux/actions/release_version_actions';
import YesNo from './components/YesNo/YesNo'; import YesNo from './components/YesNo/YesNo';
import {createModalConditionally} from './utils'; import {createModalConditionally} from './utils';
import SkynetImport from './containers/SkynetImport/SkynetImport';
import ApplicationBusy from './components/ApplicationBusy/ApplicationBusy';
import SkynetExport from './containers/SkynetExport/SkynetExport';
import PinnedManager from './containers/PinnedManager/PinnedManager';
const Constants = require('./constants'); const Constants = require('./constants');
const Scheduler = require('node-schedule'); const Scheduler = require('node-cron');
class App extends IPCContainer { class App extends IPCContainer {
componentDidMount() { componentDidMount() {
@@ -46,7 +55,7 @@ class App extends IPCContainer {
} }
}; };
detectUpgrades(); detectUpgrades();
this.scheduledUpdateJob = Scheduler.scheduleJob('23 11 * * *', detectUpgrades); this.scheduledUpdateJob = Scheduler.schedule('23 11 * * *', detectUpgrades);
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@@ -62,7 +71,7 @@ class App extends IPCContainer {
} }
componentWillUnmount() { componentWillUnmount() {
Scheduler.cancelJob(this.scheduledUpdateJob); this.scheduledUpdateJob.stop();
super.componentWillUnmount(); super.componentWillUnmount();
} }
@@ -72,6 +81,15 @@ class App extends IPCContainer {
this.props.VersionLookup[Constants.RELEASE_TYPES[this.props.Release]][this.props.ReleaseVersion]; this.props.VersionLookup[Constants.RELEASE_TYPES[this.props.Release]][this.props.ReleaseVersion];
}; };
handleUpgradeIconClicked = () => {
if (this.props.UpgradeAvailable) {
this.props.setDismissUIUpgrade(false)
} else if (this.props.NewReleasesAvailable2.length > 0) {
this.props.setNewReleasesAvailable(this.props.NewReleasesAvailable2);
this.props.setDismissNewReleasesAvailable(false);
}
};
render() { render() {
const selectedVersion = this.getSelectedVersion(); const selectedVersion = this.getSelectedVersion();
@@ -92,10 +110,27 @@ class App extends IPCContainer {
const remoteSupported = this.props.LocationsLookup[selectedVersion] && const remoteSupported = this.props.LocationsLookup[selectedVersion] &&
this.props.LocationsLookup[selectedVersion].supports_remote; this.props.LocationsLookup[selectedVersion].supports_remote;
const s3Supported = this.props.LocationsLookup[selectedVersion] &&
this.props.LocationsLookup[selectedVersion].s3_support;
const skynetSupported = this.props.LocationsLookup[selectedVersion] &&
this.props.LocationsLookup[selectedVersion].skynet_support;
const scPrimeSupported = this.props.LocationsLookup[selectedVersion] &&
this.props.LocationsLookup[selectedVersion].siaprime_support;
const siaSupported = this.props.LocationsLookup[selectedVersion] &&
this.props.LocationsLookup[selectedVersion].sia_support;
const showConfig = !missingDependencies && const showConfig = !missingDependencies &&
!this.props.DisplayPinnedManager &&
this.props.DisplayConfiguration && this.props.DisplayConfiguration &&
!this.props.RebootRequired; !this.props.RebootRequired;
const showPinnedManager = !missingDependencies &&
!this.props.RebootRequired &&
this.props.DisplayPinnedManager;
const showUpgrade = this.props.UpgradeAvailable && const showUpgrade = this.props.UpgradeAvailable &&
!this.props.DisplayError && !this.props.DisplayError &&
!showConfig && !showConfig &&
@@ -111,15 +146,61 @@ class App extends IPCContainer {
!this.props.DismissDependencies && !this.props.DismissDependencies &&
this.props.AllowMount; this.props.AllowMount;
const configDisplay = createModalConditionally(showConfig, <Configuration version={selectedVersion} remoteSupported={remoteSupported} />); const showNewReleases = !showConfig &&
!this.props.DisplayConfirmYesNo &&
!showDependencies &&
!this.props.DownloadActive &&
!this.props.DisplayError &&
!this.props.DisplayInfo &&
!this.props.InstallActive &&
!this.props.RebootRequired &&
!this.props.DisplaySelectAppPlatform &&
!showUpgrade &&
!this.props.DismissNewReleasesAvailable &&
(this.props.NewReleasesAvailable.length > 0);
const showSkynetImport = !showConfig &&
!showDependencies &&
!this.props.DownloadActive &&
!showNewReleases &&
!this.props.RebootRequired &&
!this.props.DisplaySelectAppPlatform &&
!showUpgrade &&
this.props.DisplayImport;
const showSkynetExport = !showConfig &&
!showDependencies &&
!this.props.DownloadActive &&
!showNewReleases &&
!this.props.RebootRequired &&
!this.props.DisplaySelectAppPlatform &&
!showUpgrade &&
this.props.DisplayExport;
const configDisplay = createModalConditionally(showConfig, <Configuration
version={selectedVersion}
s3Supported={s3Supported}
remoteSupported={remoteSupported}/>);
const pinnedManagerDisplay = createModalConditionally(showPinnedManager, <PinnedManager
version={selectedVersion}/>)
const confirmDisplay = createModalConditionally(this.props.DisplayConfirmYesNo, <YesNo/>); const confirmDisplay = createModalConditionally(this.props.DisplayConfirmYesNo, <YesNo/>);
const dependencyDisplay = createModalConditionally(showDependencies, <DependencyList/>); const dependencyDisplay = createModalConditionally(showDependencies,
const downloadDisplay = createModalConditionally(this.props.DownloadActive, <DownloadProgress/>); <DependencyList/>, false, this.props.InstallActive);
const downloadDisplay = createModalConditionally(this.props.DownloadActive,
<DownloadProgress/>, false, true);
const errorDisplay = createModalConditionally(this.props.DisplayError, <ErrorDetails/>, true); const errorDisplay = createModalConditionally(this.props.DisplayError, <ErrorDetails/>, true);
const infoDisplay = createModalConditionally(this.props.DisplayInfo, <InfoDetails/>, true); const infoDisplay = createModalConditionally(this.props.DisplayInfo, <InfoDetails/>, true);
const newReleasesDisplay = createModalConditionally(showNewReleases, <NewReleases/>);
const rebootDisplay = createModalConditionally(this.props.RebootRequired, <Reboot/>); const rebootDisplay = createModalConditionally(this.props.RebootRequired, <Reboot/>);
const selectAppPlatformDisplay = createModalConditionally(this.props.DisplaySelectAppPlatform, <SelectAppPlatform/>); const selectAppPlatformDisplay = createModalConditionally(this.props.DisplaySelectAppPlatform,
<SelectAppPlatform/>);
const upgradeDisplay = createModalConditionally(showUpgrade, <UpgradeUI/>); const upgradeDisplay = createModalConditionally(showUpgrade, <UpgradeUI/>);
const importDisplay = createModalConditionally(showSkynetImport, <SkynetImport
version={selectedVersion}/>);
const exportDisplay = createModalConditionally(showSkynetExport, <SkynetExport
version={selectedVersion}/>)
const appBusyDisplay = createModalConditionally(this.props.AppBusy,
<ApplicationBusy/>, false, true, this.props.AppBusyTransparent);
let mainContent = []; let mainContent = [];
if (this.props.DisplaySelectAppPlatform || !this.props.AppReady) { if (this.props.DisplaySelectAppPlatform || !this.props.AppReady) {
@@ -142,7 +223,18 @@ class App extends IPCContainer {
mainContent.push(( mainContent.push((
<Box dxStyle={{padding: 'var(--default_spacing)', height: 'auto'}} <Box dxStyle={{padding: 'var(--default_spacing)', height: 'auto'}}
key={'md_' + key++}> key={'md_' + key++}>
<MountItems remoteSupported={remoteSupported}/> <MountItems s3Supported={s3Supported}
remoteSupported={remoteSupported}
scPrimeSupported={scPrimeSupported}
siaSupported={siaSupported}
skynetSupported={skynetSupported}/>
</Box>
));
} else if (selectedVersion !== 'unavailable') {
mainContent.push((
<Box dxStyle={{padding: 'var(--default_spacing)', height: '170px'}}
key={'md_' + key++}>
<Loading/>
</Box> </Box>
)); ));
} }
@@ -162,8 +254,9 @@ class App extends IPCContainer {
textAlign={'center'} textAlign={'center'}
type={'Heading1'}/> type={'Heading1'}/>
<UpgradeIcon <UpgradeIcon
available={this.props.UpgradeAvailable} available={this.props.UpgradeAvailable || (this.props.NewReleasesAvailable2.length > 0)}
clicked={()=>this.props.setDismissUIUpgrade(false)} newReleases={!this.props.UpgradeAvailable && (this.props.NewReleasesAvailable2.length > 0)}
clicked={this.handleUpgradeIconClicked}
col={dimensions => dimensions.columns - 6} col={dimensions => dimensions.columns - 6}
colSpan={5} colSpan={5}
row={1} row={1}
@@ -175,14 +268,19 @@ class App extends IPCContainer {
{mainContent} {mainContent}
</div> </div>
</div> </div>
{importDisplay}
{exportDisplay}
{newReleasesDisplay}
{selectAppPlatformDisplay} {selectAppPlatformDisplay}
{dependencyDisplay} {dependencyDisplay}
{upgradeDisplay} {upgradeDisplay}
{pinnedManagerDisplay}
{configDisplay} {configDisplay}
{infoDisplay} {infoDisplay}
{confirmDisplay} {confirmDisplay}
{downloadDisplay} {downloadDisplay}
{rebootDisplay} {rebootDisplay}
{appBusyDisplay}
{errorDisplay} {errorDisplay}
</div> </div>
); );
@@ -194,19 +292,27 @@ const mapStateToProps = state => {
AllowDownload: state.download.AllowDownload, AllowDownload: state.download.AllowDownload,
AllowMount: state.common.AllowMount, AllowMount: state.common.AllowMount,
AppPlatform: state.common.AppPlatform, AppPlatform: state.common.AppPlatform,
AppBusy: state.common.AppBusy,
AppBusyTransparent: state.common.AppBusyTransparent,
AppReady: state.common.AppReady, AppReady: state.common.AppReady,
DismissDependencies: state.install.DismissDependencies, DismissDependencies: state.install.DismissDependencies,
DisplayConfiguration: state.mounts.DisplayConfiguration, DisplayConfiguration: state.mounts.DisplayConfiguration,
DisplayConfirmYesNo: state.common.DisplayConfirmYesNo, DisplayConfirmYesNo: state.common.DisplayConfirmYesNo,
DisplayError: state.error.DisplayError, DisplayError: state.error.DisplayError,
DisplayExport: state.skynet.DisplayExport,
DisplayImport: state.skynet.DisplayImport,
DisplayInfo: state.error.DisplayInfo, DisplayInfo: state.error.DisplayInfo,
DisplayPinnedManager: state.pinned.DisplayPinnedManager,
DisplaySelectAppPlatform: state.common.DisplaySelectAppPlatform, DisplaySelectAppPlatform: state.common.DisplaySelectAppPlatform,
DismissNewReleasesAvailable: state.relver.DismissNewReleasesAvailable,
DownloadActive: state.download.DownloadActive, DownloadActive: state.download.DownloadActive,
InstallActive: state.install.InstallActive, InstallActive: state.install.InstallActive,
InstalledVersion: state.relver.InstalledVersion, InstalledVersion: state.relver.InstalledVersion,
LocationsLookup: state.relver.LocationsLookup, LocationsLookup: state.relver.LocationsLookup,
MissingDependencies: state.install.MissingDependencies, MissingDependencies: state.install.MissingDependencies,
MountsBusy: state.mounts.MountsBusy, MountsBusy: state.mounts.MountsBusy,
NewReleasesAvailable: state.relver.NewReleasesAvailable,
NewReleasesAvailable2: state.relver.NewReleasesAvailable2,
Platform: state.common.Platform, Platform: state.common.Platform,
ProviderState: state.mounts.ProviderState, ProviderState: state.mounts.ProviderState,
RebootRequired: state.common.RebootRequired, RebootRequired: state.common.RebootRequired,
@@ -225,6 +331,8 @@ const mapDispatchToProps = dispatch => {
loadReleases: () => dispatch(loadReleases()), loadReleases: () => dispatch(loadReleases()),
notifyError: (msg, critical, callback) => dispatch(notifyError(msg, critical, callback)), notifyError: (msg, critical, callback) => dispatch(notifyError(msg, critical, callback)),
saveState: () => dispatch(saveState()), saveState: () => dispatch(saveState()),
setDismissNewReleasesAvailable: dismiss => dispatch(setDismissNewReleasesAvailable(dismiss)),
setNewReleasesAvailable: items => dispatch(setNewReleasesAvailable(items)),
setDismissUIUpgrade: dismiss => dispatch(setDismissUIUpgrade(dismiss)), setDismissUIUpgrade: dismiss => dispatch(setDismissUIUpgrade(dismiss)),
}; };
}; };

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -30,12 +30,19 @@
"EventLevel": "Internally, events are fired during certain operations. This setting determines which events should be logged to repertory.log. Valid values are Error, Warn, Normal, Debug, and Verbose.", "EventLevel": "Internally, events are fired during certain operations. This setting determines which events should be logged to repertory.log. Valid values are Error, Warn, Normal, Debug, and Verbose.",
"EvictionDelaySeconds": "Number of seconds to wait after all file handles are closed before allowing file to be evicted from cache.", "EvictionDelaySeconds": "Number of seconds to wait after all file handles are closed before allowing file to be evicted from cache.",
"EvictionDelayMinutes": "Number of minutes to wait after all file handles are closed before allowing file to be evicted from cache.", "EvictionDelayMinutes": "Number of minutes to wait after all file handles are closed before allowing file to be evicted from cache.",
"EvictionUsesAccessedTime": "Use oldest accessed time instead of oldest modified time when evicting files.",
"MaxCacheSizeBytes": "This value specifies the maximum amount of local space to consume before files are removed from cache. EnableMaxCacheSize must also be set to true for this value to take affect.", "MaxCacheSizeBytes": "This value specifies the maximum amount of local space to consume before files are removed from cache. EnableMaxCacheSize must also be set to true for this value to take affect.",
"MaxUploadCount": "Maximum number of simultaneous uploads.",
"MinimumRedundancy": "Files are elected for removal once this value has been reached. Be aware that this value cannot be set to less than 1.5x.", "MinimumRedundancy": "Files are elected for removal once this value has been reached. Be aware that this value cannot be set to less than 1.5x.",
"OnlineCheckRetrySeconds": "Number of seconds to wait for Sia/ScPrime daemon to become available/connectable.", "OnlineCheckRetrySeconds": "Number of seconds to wait for Sia/ScPrime daemon to become available/connectable.",
"OrphanedFileRetentionDays": "Repertory attempts to keep modifications between Sia-UI and the mounted location in sync as much as possible. In the event a file is removed from Sia-UI, it will be marked as orphaned in Repertory and moved into an 'orphaned' directory within Repertory's data directory. This setting specifies the number of days this file is retained before final deletion.", "OrphanedFileRetentionDays": "Repertory attempts to keep modifications between Sia-UI and the mounted location in sync as much as possible. In the event a file is removed from Sia-UI, it will be marked as orphaned in Repertory and moved into an 'orphaned' directory within Repertory's data directory. This setting specifies the number of days this file is retained before final deletion.",
"PreferredDownloadType": "Repertory supports 3 download modes for reading files that are not cached locally: full file allocation, ring buffer mode and direct mode.\n\nFull file allocation mode pre-allocates the entire file prior to downloading. This mode is required for writes but also ensures the best performance when reading data.\n\nRing buffer mode utilizes a fixed size file buffer to enable a reasonable amount of seeking. This alleviates the need to fully allocate a file. By default, it is 512MiB. When the buffer is full, it attempts to maintain the ability to seek 50% ahead or behind the current read location without the need to re-download data from Sia/ScPrime.\n\nDirect mode utilizes no disk space. All data is read directly from Sia/ScPrime.\n\nPreferred download type modes are:\n\nFallback - If there isn't enough local space to allocate the full file, ring buffer mode is used. If there isn't enough local space to create the ring buffer's file, then direct mode is used.\nRingBuffer - Full file allocation is always bypassed; however, if there isn't enough space to create the ring buffer's file, then direct mode will be chosen.\nDirect - All files will be read directly from Sia/ScPrime.", "PreferredDownloadType": "Repertory supports 3 download modes for reading files that are not cached locally: full file allocation, ring buffer mode and direct mode.\n\nFull file allocation mode pre-allocates the entire file prior to downloading. This mode is required for writes but also ensures the best performance when reading data.\n\nRing buffer mode utilizes a fixed size file buffer to enable a reasonable amount of seeking. This alleviates the need to fully allocate a file. By default, it is 512MiB. When the buffer is full, it attempts to maintain the ability to seek 50% ahead or behind the current read location without the need to re-download data from Sia/ScPrime.\n\nDirect mode utilizes no disk space. All data is read directly from Sia/ScPrime.\n\nPreferred download type modes are:\n\nFallback - If there isn't enough local space to allocate the full file, ring buffer mode is used. If there isn't enough local space to create the ring buffer's file, then direct mode is used.\nRingBuffer - Full file allocation is always bypassed; however, if there isn't enough space to create the ring buffer's file, then direct mode will be chosen.\nDirect - All files will be read directly from Sia/ScPrime.",
"ReadAheadCount": "This value specifies the number of read-ahead chunks to use when downloading a file. This is a per-open file setting and will result in the creation of an equal number of threads.", "ReadAheadCount": "This value specifies the number of read-ahead chunks to use when downloading a file. This is a per-open file setting and will result in the creation of an equal number of threads.",
"RetryReadCount": "Number of times to retry a failed read operation. A 1 second delay is used between retries. This setting applies to downloads only.",
"RingBufferFileSize": "The size of the ring buffer file in MiB. Default is 512. Valid values are: 64, 128, 256, 512, 1024." "RingBufferFileSize": "The size of the ring buffer file in MiB. Default is 512. Valid values are: 64, 128, 256, 512, 1024."
},
"SkynetConfig": {
"EncryptionToken": "Encryption token used to encrypt files in Skynet. Set this value once as it applies to all files stored.",
"PortalList": "List of Skynet portals used to load-balance uploads and downloads."
} }
} }

View File

@@ -0,0 +1,21 @@
import React from 'react';
import Box from '../UI/Box/Box';
import Loader from 'react-loader-spinner';
import Text from '../UI/Text/Text';
export default ({title}) => {
return (
<Box dxStyle={{padding: 'var(--default_spacing)'}}>
<Text
text={title || 'Please Wait...'}
textAlign={'center'}
type={'Heading1'}/>
<div style={{paddingLeft: 'calc(50% - 16px)', paddingTop: 'var(--default_spacing)'}}>
<Loader color={'var(--heading_text_color)'}
height={32}
width={32}
type='TailSpin'/>
</div>
</Box>
);
}

View File

@@ -22,7 +22,7 @@ export default connect(mapStateToProps)(props => {
</td> </td>
<td> <td>
{props.AllowDownload ? {props.AllowDownload ?
<a href={void(0)} <a href={'#'}
className={'DependencyLink'} className={'DependencyLink'}
onClick={()=>{props.onDownload(); return false;}}><u>Install</u></a> : onClick={()=>{props.onDownload(); return false;}}><u>Install</u></a> :
'Installing...'} 'Installing...'}

View File

@@ -31,15 +31,13 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
); );
}); });
let dismissDisplay; const dismissDisplay = (
if (props.AllowDismissDependencies) {
dismissDisplay = (
<div style={{float: 'right', margin: 0, paddingRight: '4px', boxSizing: 'border-box', display: 'block'}}> <div style={{float: 'right', margin: 0, paddingRight: '4px', boxSizing: 'border-box', display: 'block'}}>
<b style={{cursor: 'pointer'}} <a href={'#'}
onClick={()=>props.setDismissDependencies(true)}>X onClick={props.AllowDismissDependencies ? () => props.setDismissDependencies(true) : e => e.preventDefault()}
</b> style={{cursor: props.AllowDismissDependencies ? 'pointer' : 'no-drop'}}>X</a>
</div>); </div>
} );
return ( return (
<Box dxStyle={{width: '300px', height: 'auto', padding: '5px'}}> <Box dxStyle={{width: '300px', height: 'auto', padding: '5px'}}>

View File

@@ -6,6 +6,14 @@
.InfoDetailsContent { .InfoDetailsContent {
max-height: 60vh; max-height: 60vh;
min-width: 80vw; min-width: 80vw;
max-width: 90vw;
overflow-y: auto; overflow-y: auto;
overflow-x: auto;
white-space: pre-line;
margin-bottom: var(--default_spacing); margin-bottom: var(--default_spacing);
} }
.InfoDetailsContent.Alternate {
white-space: pre;
hyphens: none;
}

View File

@@ -18,11 +18,48 @@ const mapDispatchToProps = dispatch => {
}; };
export default connect(mapStateToProps, mapDispatchToProps)(props => { export default connect(mapStateToProps, mapDispatchToProps)(props => {
let msg = props.InfoMessage.message;
const classes = ['InfoDetailsContent'];
let changed = true;
let copyable = false;
while (changed) {
changed = false;
if (msg.startsWith('!alternate!')) {
classes.push('Alternate');
msg = msg.substr('!alternate!'.length);
changed = true;
}
if (msg.startsWith('!copyable!')) {
classes.push('Copyable');
msg = msg.substr('!copyable!'.length);
copyable = changed = true;
}
}
const scrollToTop = textArea => {
if (!textArea.firstClick) {
textArea.firstClick = true;
textArea.scrollTop = 0;
textArea.setSelectionRange(0, 0);
}
};
return ( return (
<Box dxDark dxStyle={{padding: 'var(--default_spacing)'}}> <Box dxDark dxStyle={{padding: 'var(--default_spacing)'}}>
<h1 className={'InfoDetailsHeading'}>{props.InfoMessage.title}</h1> <h1 className={'InfoDetailsHeading'}>{props.InfoMessage.title}</h1>
<div className={'InfoDetailsContent'}> <div className={classes.join(' ')}>
<p style={{textAlign: 'left', whiteSpace: 'pre-line'}}>{props.InfoMessage.message}</p> {
copyable ? (
<textarea autoFocus
rows={9}
value={msg}
className={'SkynetImportTextArea'}
onClick={e => scrollToTop(e.target)}/>
) : (
<p style={{textAlign: 'left'}}>{msg}</p>
)
}
</div> </div>
<Button clicked={props.dismissInfo}>Dismiss</Button> <Button clicked={props.dismissInfo}>Dismiss</Button>
</Box> </Box>

View File

@@ -0,0 +1,57 @@
import React from 'react';
import {connect} from 'react-redux';
import * as Constants from '../../../constants';
import Button from '../../UI/Button/Button';
import {formatLinesForDisplay, getChangesForRepertoryVersion} from '../../../utils';
import {
notifyError,
notifyInfo
} from '../../../redux/actions/error_actions';
import {installReleaseByVersion} from '../../../redux/actions/install_actions';
const mapDispatchToProps = dispatch => {
return {
installReleaseByVersion: (release, version) => dispatch(installReleaseByVersion(release, version)),
notifyError: msg => dispatch(notifyError(msg)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)),
};
};
export default connect(null, mapDispatchToProps)(({dismiss, release, lastItem, notifyError, notifyInfo, installReleaseByVersion}) => {
const title = '[' + Constants.RELEASE_TYPES[release.Release] + '] ' + release.Display;
const installReleaseVersion = () => {
dismiss();
installReleaseByVersion(release.Release, release.Version);
};
const displayChanges = async () => {
try {
const lines = await getChangesForRepertoryVersion(release.VersionString);
notifyInfo(title, formatLinesForDisplay(lines));
} catch (e) {
notifyError(e);
}
};
return (
<div>
<h2>{title}</h2>
<table cellSpacing={0} cellPadding={0} width="97%">
<tbody>
<tr style={{height: '4px'}}/>
<tr>
<td width="50%">
<Button buttonStyles={{width: '100%'}} clicked={displayChanges}>Changes</Button>
</td>
<td>
<div style={{width: 'var(--default_spacing)'}}/>
</td>
<td width="50%">
<Button buttonStyles={{width: '100%'}} clicked={installReleaseVersion}>Install</Button>
</td>
</tr>
{lastItem ? null : <tr style={{height: 'var(--default_spacing)'}}/>}
</tbody>
</table>
</div>
);
});

View File

@@ -0,0 +1,11 @@
.NewReleasesHeading {
text-align: center;
margin-bottom: 4px;
}
.NewReleasesContent {
max-height: 60vh;
min-width: 50vw;
overflow-y: auto;
margin-bottom: var(--default_spacing);
}

View File

@@ -0,0 +1,38 @@
import React from 'react';
import {connect} from 'react-redux';
import Box from '../UI/Box/Box';
import Button from '../UI/Button/Button';
import NewRelease from './NewRelease/NewRelease';
import './NewReleases.css';
import {setDismissNewReleasesAvailable} from '../../redux/actions/release_version_actions';
const mapStateToProps = state => {
return {
NewReleasesAvailable: state.relver.NewReleasesAvailable,
};
};
const mapDispatchToProps = dispatch => {
return {
dismissNewReleasesAvailable: () => dispatch(setDismissNewReleasesAvailable(true)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(props => {
const newReleases = props.NewReleasesAvailable.map((i, idx) => {
return <NewRelease dismiss={props.dismissNewReleasesAvailable}
key={'new_release_' + i.Release + '_' + i.Version}
lastItem={idx === (props.NewReleasesAvailable.length - 1)}
release={i} />;
});
return (
<Box dxDark dxStyle={{padding: 'var(--default_spacing)'}}>
<h1 className={'NewReleasesHeading'}>New Repertory Versions Available</h1>
<div className={'NewReleasesContent'}>
{newReleases}
</div>
<Button clicked={props.dismissNewReleasesAvailable}>Dismiss</Button>
</Box>
);
});

View File

@@ -12,6 +12,7 @@ import {downloadItem} from '../../redux/actions/download_actions';
const mapStateToProps = state => { const mapStateToProps = state => {
return { return {
AllowMount: state.common.AllowMount,
AppPlatform: state.common.AppPlatform, AppPlatform: state.common.AppPlatform,
DismissDependencies: state.install.DismissDependencies, DismissDependencies: state.install.DismissDependencies,
DownloadActive: state.download.DownloadActive, DownloadActive: state.download.DownloadActive,
@@ -35,6 +36,12 @@ const mapDispatchToProps = dispatch => {
}; };
export default connect(mapStateToProps, mapDispatchToProps)(props => { export default connect(mapStateToProps, mapDispatchToProps)(props => {
const getSelectedVersion = () => {
return (props.ReleaseVersion === -1) ?
'unavailable' :
props.VersionLookup[Constants.RELEASE_TYPES[props.Release]][props.ReleaseVersion];
};
const handleDownloadRelease = () => { const handleDownloadRelease = () => {
const fileName = props.version + '.zip'; const fileName = props.version + '.zip';
props.downloadItem(fileName, Constants.INSTALL_TYPES.Release, props.LocationsLookup[props.version].urls); props.downloadItem(fileName, Constants.INSTALL_TYPES.Release, props.LocationsLookup[props.version].urls);
@@ -52,7 +59,10 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
}; };
const text = props.InstalledVersion + ' [' + props.AppPlatform + ']'; const text = props.InstalledVersion + ' [' + props.AppPlatform + ']';
const disabled = props.DownloadActive || props.InstallActive || props.MountsBusy; const disabled = props.DownloadActive ||
props.InstallActive ||
props.MountsBusy ||
(!props.AllowMount && (getSelectedVersion() !== 'unavailable')) ;
const releaseExtracting = (props.InstallType === Constants.INSTALL_TYPES.Release); const releaseExtracting = (props.InstallType === Constants.INSTALL_TYPES.Release);
let optionsDisplay = []; let optionsDisplay = [];

View File

@@ -4,6 +4,7 @@ import './Button.css';
export default props => { export default props => {
return ( return (
<button disabled={props.disabled} <button disabled={props.disabled}
autoFocus={props.autoFocus}
className={'Button'} className={'Button'}
style={props.buttonStyles} style={props.buttonStyles}
onClick={props.clicked}>{props.children}</button> onClick={props.clicked}>{props.children}</button>

View File

@@ -68,7 +68,7 @@ label.CheckBoxLabel .CheckBoxCheckMark:after {
top: 1px; top: 1px;
width: 5px; width: 5px;
height: 10px; height: 10px;
border: solid var(--heading_text_color); border: solid var(--heading_other_text_color);
border-width: 0 3px 3px 0; border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg); -webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg); -ms-transform: rotate(45deg);

View File

@@ -6,6 +6,7 @@ export default props => {
<div className={'CheckBoxOwner'}> <div className={'CheckBoxOwner'}>
<label className='CheckBoxLabel'>{props.label} <label className='CheckBoxLabel'>{props.label}
<input checked={JSON.parse(props.checked)} <input checked={JSON.parse(props.checked)}
autoFocus={props.autoFocus}
disabled={props.disabled} disabled={props.disabled}
onChange={props.changed} onChange={props.changed}
type='checkbox'/> type='checkbox'/>

View File

@@ -6,30 +6,18 @@
padding: 0; padding: 0;
} }
.DropDownSelect { .DropDownSelect, .DropDownSelect.Alt {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: block; display: block;
margin: 0; margin: 0;
padding: 2px; padding: 2px;
border-radius: var(--border_radius); border-radius: var(--border_radius);
background: rgba(10, 10, 20, 0.75); background: var(--control_background);
border-color: rgba(10, 10, 20, 0.9);
color: var(--text_color);
box-sizing: border-box;
}
.DropDownSelect.Alt {
width: 100%;
height: 100%;
display: block;
margin: 0;
padding: 2px;
border-radius: var(--border_radius);
background: rgba(160, 160, 160, 0.1);
border: none; border: none;
color: var(--text_color); color: var(--text_color);
box-sizing: border-box; box-sizing: border-box;
outline: none !important;
} }
.DropDownSelect.Auto { .DropDownSelect.Auto {

View File

@@ -11,6 +11,7 @@ export default props => {
return ( return (
<div className={'DropDown'}> <div className={'DropDown'}>
<select className={'DropDownSelect' + (props.auto ? ' Auto ' : '') + (props.alt ? ' Alt ' : '') } <select className={'DropDownSelect' + (props.auto ? ' Auto ' : '') + (props.alt ? ' Alt ' : '') }
autoFocus={props.autoFocus}
disabled={props.disabled} disabled={props.disabled}
onChange={props.changed} onChange={props.changed}
value={props.selected}> value={props.selected}>

View File

@@ -2,7 +2,7 @@ import React from 'react';
import './Loading.css' import './Loading.css'
import Loader from 'react-loader-spinner'; import Loader from 'react-loader-spinner';
export default props => { export default () => {
return ( return (
<div <div
className={'Loading'}> className={'Loading'}>

View File

@@ -8,6 +8,10 @@
z-index: 2000; z-index: 2000;
} }
.Modal.Transparent {
background-color: rgba(0, 0, 0, 0);
}
.ModalContent { .ModalContent {
position: fixed; position: fixed;
width: auto; width: auto;

View File

@@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import './Modal.css' import './Modal.css'
import FocusTrap from 'focus-trap-react';
export default props => { export default props => {
let modalStyles = []; let modalStyles = [];
@@ -11,12 +13,19 @@ export default props => {
contentStyles.push('ModalCritical'); contentStyles.push('ModalCritical');
} }
if (props.transparent) {
modalStyles.push('Transparent');
}
return ( return (
<FocusTrap active={!props.disableFocusTrap}>
<div <div
className={modalStyles.join(' ')} className={modalStyles.join(' ')}
onClick={props.clicked}> onClick={props.clicked}>
<div className={contentStyles.join(' ')}> <div className={contentStyles.join(' ')}>
{props.children} {props.children}
</div> </div>
</div>); </div>
</FocusTrap>
);
}; };

View File

@@ -17,5 +17,9 @@
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
box-sizing: border-box; box-sizing: border-box;
opacity: 0.65; color: var(--heading_text_color);
}
.UpgradeIcon.Release {
color: var(--heading_other_text_color);
} }

View File

@@ -1,13 +1,19 @@
import React from 'react'; import React from 'react';
import './UpgradeIcon.css'; import './UpgradeIcon.css';
import availableImage from '../../assets/images/release_available.png';
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
export default props => { export default props => {
const styles = ['UpgradeIcon'];
let placement = 'left'; let placement = 'left';
let toolTipText = 'UI Upgrade Available'; let toolTipText = 'UI Upgrade Available';
if (props.release) { if (props.release) {
placement='bottom'; placement='bottom';
styles.push('Release');
}
if (props.release || props.newReleases) {
toolTipText = 'New Release Available'; toolTipText = 'New Release Available';
} }
@@ -16,10 +22,11 @@ export default props => {
( (
<div className={'UpgradeIconOwner'}> <div className={'UpgradeIconOwner'}>
<p data-tip='' data-for={placement}> <p data-tip='' data-for={placement}>
<img alt='' <a href={'#'}
onClick={props.clicked} className={styles.join(' ')}
src={availableImage} onClick={props.clicked}>
className={'UpgradeIcon'}/> <FontAwesomeIcon icon={faExclamationTriangle}/>
</a>
</p> </p>
<ReactTooltip id={placement} place={placement}>{toolTipText}</ReactTooltip> <ReactTooltip id={placement} place={placement}>{toolTipText}</ReactTooltip>
</div> </div>

View File

@@ -1,7 +1,6 @@
Object.defineProperty(exports, "__esModule", { Object.defineProperty(exports, '__esModule', {value : true});
value: true exports.DEV_PUBLIC_KEY =
}); '-----BEGIN PUBLIC KEY-----\n' +
exports.DEV_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----\n' +
'MIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKCBAEKfZmq5mMAtD4kSt2Gc/5J\n' + 'MIIEIjANBgkqhkiG9w0BAQEFAAOCBA8AMIIECgKCBAEKfZmq5mMAtD4kSt2Gc/5J\n' +
'H+HHTYtUZE6YYvsvz8TNG/bNL67ZtNRyaoMyhLTfIN4rPBNLUfD+owNS+u5Yk+lS\n' + 'H+HHTYtUZE6YYvsvz8TNG/bNL67ZtNRyaoMyhLTfIN4rPBNLUfD+owNS+u5Yk+lS\n' +
'ZLYyOuhoCZIFefayYqKLr42G8EeuRbx0IMzXmJtN0a4rqxlWhkYufJubpdQ+V4DF\n' + 'ZLYyOuhoCZIFefayYqKLr42G8EeuRbx0IMzXmJtN0a4rqxlWhkYufJubpdQ+V4DF\n' +
@@ -27,19 +26,45 @@ exports.DEV_PUBLIC_KEY = '-----BEGIN PUBLIC KEY-----\n' +
'9wIDAQAB\n' + '9wIDAQAB\n' +
'-----END PUBLIC KEY-----'; '-----END PUBLIC KEY-----';
const REPERTORY_BRANCH = 'master'; const _REPERTORY_BRANCH = 'master';
const REPERTORY_UI_BRANCH = 'master'; const _REPERTORY_UI_BRANCH = 'master';
exports.RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory/raw/' + REPERTORY_BRANCH + '/releases_1.1.json'; exports.REPERTORY_BRANCH = _REPERTORY_BRANCH;
exports.UI_RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory-ui/raw/' + REPERTORY_UI_BRANCH + '/releases.json'; exports.REPERTORY_UI_BRANCH = _REPERTORY_UI_BRANCH;
exports.LINUX_DETECT_SCRIPT_URL = 'https://bitbucket.org/blockstorage/repertory/raw/' + REPERTORY_BRANCH + '/detect_linux.sh'; exports.RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory/raw/' +
_REPERTORY_BRANCH + '/releases_1.3.json';
exports.UI_RELEASES_URL =
'https://bitbucket.org/blockstorage/repertory-ui/raw/' +
_REPERTORY_UI_BRANCH + '/releases.json';
exports.LINUX_DETECT_SCRIPT_URL =
'https://bitbucket.org/blockstorage/repertory/raw/' + _REPERTORY_BRANCH +
'/detect_linux2.sh';
exports.LINUX_SELECTABLE_PLATFORMS = [ exports.LINUX_SELECTABLE_PLATFORMS = [
'ubuntu18.04', 'centos7',
'ubuntu18.10', ];
'ubuntu19.04',
'ubuntu19.10' exports.WINFSP_VERSION_NAMES = [
'WinFsp 2019',
'WinFsp 2019.0',
'WinFsp 2019.1',
'WinFsp 2019.2',
'WinFsp 2019.3',
'WinFsp 2019.3 B1',
'WinFsp 2019.3 B2',
'WinFsp 2019.3 B3',
'WinFsp 2019.3 B4',
'WinFsp 2019.3 B5',
'WinFsp 2020',
'WinFsp 2020.0',
'WinFsp 2020.1',
'WinFsp 2020.2',
'WinFsp 2021 B1',
'WinFsp 2021 B2',
'WinFsp 2021 Beta1',
'WinFsp 2021 Beta2',
]; ];
exports.DATA_LOCATIONS = { exports.DATA_LOCATIONS = {
@@ -48,16 +73,38 @@ exports.DATA_LOCATIONS = {
win32 : '%LOCALAPPDATA%\\repertory\\ui' win32 : '%LOCALAPPDATA%\\repertory\\ui'
}; };
exports.REPERTORY_LOCATIONS = {
linux : '~/.local/repertory',
darwin : '~/Library/Application Support/repertory',
win32 : '%LOCALAPPDATA%\\repertory'
};
exports.S3_PROVIDER_LIST = [
'Filebase',
];
exports.S3_REGION_PROVIDER_REGION = [
'us-east-1',
];
exports.S3_PROVIDER_URL = {
'Filebase' : 'https://s3.filebase.com',
};
exports.PROVIDER_LIST = [ exports.PROVIDER_LIST = [
'Sia', 'Sia',
'ScPrime' 'Skynet',
'ScPrime',
]; ];
exports.PROVIDER_ARG = { exports.PROVIDER_ARG = {
sia : '', sia : '',
scprime: '-sp' skynet : '-sk',
scprime : '-sp',
s3 : '-s3',
}; };
exports.DEFAULT_RELEASE = 0;
exports.RELEASE_TYPES = [ exports.RELEASE_TYPES = [
'Release', 'Release',
'RC', 'RC',
@@ -96,6 +143,9 @@ exports.IPC_Download_File = 'download_file';
exports.IPC_Download_File_Complete = 'download_file_complete'; exports.IPC_Download_File_Complete = 'download_file_complete';
exports.IPC_Download_File_Progress = 'download_file_progress'; exports.IPC_Download_File_Progress = 'download_file_progress';
exports.IPC_Export_Skylinks = 'export_skylinks';
exports.IPC_Export_Skylinks_Reply = 'export_skylinks_reply';
exports.IPC_Extract_Release = 'extract_release'; exports.IPC_Extract_Release = 'extract_release';
exports.IPC_Extract_Release_Complete = 'extract_release_complete'; exports.IPC_Extract_Release_Complete = 'extract_release_complete';
@@ -105,12 +155,27 @@ exports.IPC_Get_Config_Reply = 'get_config_reply';
exports.IPC_Get_Config_Template = 'get_config_template'; exports.IPC_Get_Config_Template = 'get_config_template';
exports.IPC_Get_Config_Template_Reply = 'get_config_template_reply'; exports.IPC_Get_Config_Template_Reply = 'get_config_template_reply';
exports.IPC_Get_Directory_Items = 'get_directory_items';
exports.IPC_Get_Directory_Items_Reply = 'get_directory_items_reply';
exports.IPC_Get_Pinned_Files = 'get_pinned_files';
exports.IPC_Get_Pinned_Files_Reply = 'get_pinned_files_reply';
exports.IPC_Get_Pinned_Files_Status = 'get_pinned_files_status';
exports.IPC_Get_Pinned_Files_Status_Reply = 'get_pinned_files_status_reply';
exports.IPC_Get_Platform = 'get_platform'; exports.IPC_Get_Platform = 'get_platform';
exports.IPC_Get_Platform_Reply = 'get_platform_reply'; exports.IPC_Get_Platform_Reply = 'get_platform_reply';
exports.IPC_Get_State = 'get_state'; exports.IPC_Get_State = 'get_state';
exports.IPC_Get_State_Reply = 'get_state_reply'; exports.IPC_Get_State_Reply = 'get_state_reply';
exports.IPC_Grab_Skynet_Tree = 'grab_skynet_tree';
exports.IPC_Grab_Skynet_Tree_Reply = 'grab_skynet_tree_reply';
exports.IPC_Import_Skylinks = 'import_skylinks';
exports.IPC_Import_Skylinks_Reply = 'import_skylinks_reply';
exports.IPC_Install_Dependency = 'install_dependency'; exports.IPC_Install_Dependency = 'install_dependency';
exports.IPC_Install_Dependency_Reply = 'install_dependency_reply'; exports.IPC_Install_Dependency_Reply = 'install_dependency_reply';
@@ -120,13 +185,15 @@ exports.IPC_Install_Upgrade_Reply = 'install_upgrade_reply';
exports.IPC_Mount_Drive = 'mount_drive'; exports.IPC_Mount_Drive = 'mount_drive';
exports.IPC_Mount_Drive_Reply = 'mount_drive_reply'; exports.IPC_Mount_Drive_Reply = 'mount_drive_reply';
exports.IPC_Remove_Remote_Mount = 'remove_remote_mount'; exports.IPC_Remove_Mount = 'remove_mount';
exports.IPC_Remove_Remote_Mount_Reply = 'remove_remote_mount_reply'; exports.IPC_Remove_Mount_Reply = 'remove_mount_reply';
exports.IPC_Reboot_System = 'reboot_system'; exports.IPC_Reboot_System = 'reboot_system';
exports.IPC_Save_State = 'save_state'; exports.IPC_Save_State = 'save_state';
exports.IPC_Set_Pinned = 'set_pinned';
exports.IPC_Show_Window = 'show_window'; exports.IPC_Show_Window = 'show_window';
exports.IPC_Set_Config_Values = 'set_config_values'; exports.IPC_Set_Config_Values = 'set_config_values';

View File

@@ -0,0 +1,13 @@
.AddRemoteMount {
margin: 0;
padding: 0;
}
.AddMountButtons {
display: flex;
flex-direction: row;
}
.AddMountButton {
flex: 1;
}

View File

@@ -0,0 +1,246 @@
import React from 'react';
import {Component} from 'react';
import './AddMount.css';
import {connect} from 'react-redux';
import Button from '../../components/UI/Button/Button';
import Box from '../../components/UI/Box/Box';
import Text from '../../components/UI/Text/Text';
import {notifyError} from '../../redux/actions/error_actions';
import {addRemoteMount, addS3Mount} from '../../redux/actions/mount_actions';
import {createModalConditionally} from '../../utils';
import DropDown from '../../components/UI/DropDown/DropDown';
import * as Constants from '../../constants';
const mapStateToProps = state => {
return {
RemoteMounts: state.mounts.RemoteMounts,
S3Mounts: state.mounts.S3Mounts,
};
};
const mapDispatchToProps = dispatch => {
return {
addRemoteMount: (hostNameOrIp, port, token) => dispatch(addRemoteMount(hostNameOrIp, port, token)),
addS3Mount: (name, accessKey, secretKey, region, bucketName, url) => dispatch(addS3Mount(name, accessKey, secretKey, region, bucketName, url)),
notifyError: (msg, critical, callback) => dispatch(notifyError(msg, critical, callback)),
}
};
const default_state = {
AccessKey: '',
BucketName: '',
DisplayRemote: false,
DisplayS3: false,
HostNameOrIp: '',
Name: '',
Port: 20000,
Provider: Constants.S3_PROVIDER_LIST[0],
Region: Constants.S3_REGION_PROVIDER_REGION[0],
SecretKey: '',
Token: '',
};
export default connect(mapStateToProps, mapDispatchToProps)(class extends Component {
state = {
...default_state,
};
addRemoteMount = () => {
if (this.state.HostNameOrIp.length === 0) {
this.props.notifyError('Hostname or IP cannot be empty.');
} else {
const provider = 'Remote' + this.state.HostNameOrIp + ':' + this.state.Port;
if (this.props.RemoteMounts.includes(provider)) {
this.props.notifyError('Remote host already exists');
} else {
this.setState({
DisplayRemote: false
}, () => {
this.props.addRemoteMount(this.state.HostNameOrIp, this.state.Port, this.state.Token);
this.setState({
...default_state,
});
});
}
}
};
addS3Mount = () => {
if (this.state.Name.length === 0) {
this.props.notifyError('Name cannot be empty.');
} else if (this.state.AccessKey.length === 0) {
this.props.notifyError('AccessKey cannot be empty.');
} else if (this.state.SecretKey.length === 0) {
this.props.notifyError('SecretKey cannot be empty.')
} else {
const provider = 'S3' + this.state.Name;
if (this.props.S3Mounts.includes(provider)) {
this.props.notifyError('Remote host already exists');
} else {
this.setState({
DisplayS3: false
}, () => {
this.props.addS3Mount(this.state.Name, this.state.AccessKey, this.state.SecretKey,
this.state.Region, this.state.BucketName, Constants.S3_PROVIDER_URL[this.state.Provider]);
this.setState({
...default_state,
});
});
}
}
};
handleAddS3Mount = () => {
this.setState({
DisplayRemote: false,
DisplayS3: true,
});
};
handleAddRemoteMount = () => {
this.setState({
DisplayRemote: true,
DisplayS3: false,
});
};
render() {
const displayAddRemote = createModalConditionally(this.state.DisplayRemote, (
<Box dxDark
dxStyle={{width: 'auto', height: 'auto', padding: 'var(--default_spacing)'}}>
<h1 style={{textAlign: 'center', paddingBottom: 'var(--default_spacing)'}}>Add Remote
Mount</h1>
<Text text={'Hostname or IP'}
textAlign={'left'}
type={'Heading2'}/>
<input onChange={e => this.setState({HostNameOrIp: e.target.value.trim()})}
className={'ConfigurationItemInput'}
type={'text'}
value={this.state.HostNameOrIp}/>
<div style={{paddingTop: 'var(--default_spacing)'}}/>
<Text text={'Port'}
textAlign={'left'}
type={'Heading2'}/>
<input max={65535}
min={1025}
onChange={e => this.setState({Port: e.target.value})}
className={'ConfigurationItemInput'}
type={'number'}
value={this.state.Port}/>
<div style={{paddingTop: 'var(--default_spacing)'}}/>
<Text text={'Remote Token'}
textAlign={'left'}
type={'Heading2'}/>
<input onChange={e => this.setState({Token: e.target.value})}
className={'ConfigurationItemInput'}
type={'text'}
value={this.state.Token}/>
<div style={{paddingTop: 'var(--default_spacing)'}}/>
<div style={{display: 'flex', flexDirection: 'row'}}>
<Button buttonStyles={{width: '100%'}}
clicked={() => this.addRemoteMount()}>OK</Button>
<div style={{paddingLeft: 'var(--default_spacing)'}}/>
<Button buttonStyles={{width: '100%'}}
clicked={() => this.setState({DisplayRemote: false})}>Cancel</Button>
</div>
</Box>
));
const displayAddS3 = createModalConditionally(this.state.DisplayS3, (
<Box dxDark
dxStyle={{width: 'auto', height: 'auto', padding: 'var(--default_spacing)'}}>
<h1 style={{textAlign: 'center', paddingBottom: 'var(--default_spacing)'}}>Add S3
Mount</h1>
<div style={{display: 'flex', flexDirection: 'row'}}>
<Text text={'Name'}
textAlign={'left'}
type={'Heading2'}/>
<div style={{paddingLeft: 'var(--default_spacing)'}}/>
<Text text={'Provider'}
textAlign={'left'}
type={'Heading2'}/>
</div>
<div style={{display: 'flex', flexDirection: 'row'}}>
<input onChange={e => this.setState({Name: e.target.value.trim()})}
className={'ConfigurationItemInput'}
style={{width: '100%'}}
type={'text'}
value={this.state.Name}/>
<div style={{paddingLeft: 'var(--default_spacing)'}}/>
<DropDown changed={e => this.setState({Provider: e.target.value})}
items={Constants.S3_PROVIDER_LIST}
selected={this.state.Provider}/>
</div>
<div style={{paddingTop: 'var(--default_spacing)'}}/>
<div style={{display: 'flex', flexDirection: 'row'}}>
<Text text={'Bucket Name (optional)'}
textAlign={'left'}
type={'Heading2'}/>
<div style={{paddingLeft: 'var(--default_spacing)'}}/>
<Text text={'Region'}
textAlign={'left'}
type={'Heading2'}/>
</div>
<div style={{display: 'flex', flexDirection: 'row'}}>
<input onChange={e => this.setState({BucketName: e.target.value})}
className={'ConfigurationItemInput'}
style={{width: '100%'}}
type={'text'}
value={this.state.BucketName}/>
<div style={{paddingLeft: 'var(--default_spacing)'}}/>
<input onChange={e => this.setState({Region: e.target.value})}
className={'ConfigurationItemInput'}
type={'text'}
value={this.state.Region}/>
</div>
<div style={{paddingTop: 'var(--default_spacing)'}}/>
<div style={{display: 'flex', flexDirection: 'row'}}>
<Text text={'Access Key'}
textAlign={'left'}
type={'Heading2'}/>
<div style={{paddingLeft: 'var(--default_spacing)'}}/>
<Text text={'Secret Key'}
textAlign={'left'}
type={'Heading2'}/>
</div>
<div style={{display: 'flex', flexDirection: 'row'}}>
<input onChange={e => this.setState({AccessKey: e.target.value})}
className={'ConfigurationItemInput'}
type={'text'}
value={this.state.AccessKey}/>
<div style={{paddingLeft: 'var(--default_spacing)'}}/>
<input onChange={e => this.setState({SecretKey: e.target.value})}
className={'ConfigurationItemInput'}
type={'text'}
value={this.state.SecretKey}/>
</div>
<div style={{paddingTop: 'calc(var(--default_spacing) * 2)'}}/>
<div style={{display: 'flex', flexDirection: 'row'}}>
<div style={{width: '200%'}}/>
<Button buttonStyles={{width: '100%'}}
clicked={() => this.addS3Mount()}>OK</Button>
<div style={{paddingLeft: 'var(--default_spacing)'}}/>
<Button buttonStyles={{width: '100%'}}
clicked={() => this.setState({DisplayS3: false})}>Cancel</Button>
</div>
</Box>
));
return (
<div className={'AddMount'}>
{displayAddRemote}
{displayAddS3}
<div className={'AddMountButtons'}>
{this.props.remoteSupported ?
<Button className={'AddMountButton'}
clicked={this.handleAddRemoteMount}>Add Remote Mount</Button> : null}
{this.props.remoteSupported && this.props.s3Supported ?
<div style={{paddingRight: 'var(--default_spacing)'}}/> : null}
{this.props.s3Supported ?
<Button className={'AddMountButton'}
clicked={this.handleAddS3Mount}>Add S3 Mount</Button> : null}
</div>
</div>
);
}
});

View File

@@ -1,4 +0,0 @@
.AddRemoteMount {
margin: 0;
padding: 0;
}

View File

@@ -1,117 +0,0 @@
import React from 'react';
import {Component} from 'react';
import './AddRemoteMount.css';
import {connect} from 'react-redux';
import Button from '../../components/UI/Button/Button';
import Box from '../../components/UI/Box/Box';
import Text from '../../components/UI/Text/Text';
import {notifyError} from '../../redux/actions/error_actions';
import {addRemoteMount} from '../../redux/actions/mount_actions';
import {createModalConditionally} from '../../utils';
const mapStateToProps = state => {
return {
RemoteMounts: state.mounts.RemoteMounts,
};
};
const mapDispatchToProps = dispatch => {
return {
addRemoteMount: (hostNameOrIp, port, token) => dispatch(addRemoteMount(hostNameOrIp, port, token)),
notifyError: (msg, critical, callback) => dispatch(notifyError(msg, critical, callback)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(class extends Component {
state = {
Display: false,
HostNameOrIp: '',
Port: 20000,
Token: '',
};
addRemoteMount = () => {
if (this.state.HostNameOrIp.length === 0) {
this.props.notifyError('Hostname or IP cannot be empty.');
} else {
const provider = 'Remote' + this.state.HostNameOrIp + ':' + this.state.Port;
if (this.props.RemoteMounts.includes(provider)) {
this.props.notifyError('Remote host already exists');
} else {
this.setState({
Display: false
}, () => {
this.props.addRemoteMount(this.state.HostNameOrIp, this.state.Port, this.state.Token);
this.setState({
HostNameOrIp: '',
Port: 20000,
Token: '',
});
});
}
}
};
handleAddRemoteMount = () => {
this.setState({
Display: true,
});
};
render() {
const displayAdd = createModalConditionally(this.state.Display, (
<Box dxDark
dxStyle={{width: 'auto', height: 'auto', padding: 'var(--default_spacing)'}}>
<h1 style={{color: 'var(--text_color_error)', textAlign: 'center', paddingBottom: 'var(--default_spacing)'}}>Add Remote Mount</h1>
<Text text={'Hostname or IP'}
textAlign={'left'}
type={'Heading1'}/>
<input onChange={e => this.setState({HostNameOrIp: e.target.value.trim()})}
className={'ConfigurationItemInput'}
type={'text'}
value={this.state.HostNameOrIp}/>
<div style={{paddingTop: 'var(--default_spacing)'}}/>
<Text text={'Port'}
textAlign={'left'}
type={'Heading1'}/>
<input max={65535}
min={1025}
onChange={e => this.setState({Port: e.target.value})}
className={'ConfigurationItemInput'}
type={'number'}
value={this.state.Port}/>
<div style={{paddingTop: 'var(--default_spacing)'}}/>
<Text text={'Remote Token'}
textAlign={'left'}
type={'Heading1'}/>
<input onChange={e => this.setState({Token: e.target.value})}
className={'ConfigurationItemInput'}
type={'text'}
value={this.state.Token}/>
<div style={{paddingTop: 'var(--default_spacing)'}}/>
<table cellSpacing={1}
width="100%">
<tbody>
<tr>
<td width="50%">
<Button buttonStyles={{width: '100%'}}
clicked={() => this.addRemoteMount()}>OK</Button>
</td>
<td width="50%">
<Button buttonStyles={{width: '100%'}}
clicked={() => this.setState({Display: false})}>Cancel</Button>
</td>
</tr>
</tbody>
</table>
</Box>
));
return (
<div className={'AddRemoteMount'}>
{displayAdd}
<Button clicked={this.handleAddRemoteMount}>Add Remote Mount</Button>
</div>
);
}
});

View File

@@ -4,3 +4,7 @@
padding: var(--default_spacing); padding: var(--default_spacing);
margin: 0; margin: 0;
} }
.ConfigurationLink {
cursor: pointer;
}

View File

@@ -8,6 +8,7 @@ import Modal from '../../components/UI/Modal/Modal';
import IPCContainer from '../IPCContainer/IPCContainer'; import IPCContainer from '../IPCContainer/IPCContainer';
import {displayConfiguration} from '../../redux/actions/mount_actions'; import {displayConfiguration} from '../../redux/actions/mount_actions';
import {notifyError} from '../../redux/actions/error_actions'; import {notifyError} from '../../redux/actions/error_actions';
import {displayPinnedManager} from '../../redux/actions/pinned_manager_actions';
const Constants = require('../../constants'); const Constants = require('../../constants');
@@ -27,11 +28,21 @@ class Configuration extends IPCContainer {
Template: {} Template: {}
}; };
checkItemChanged = (itemA, itemB) => {
if (itemA.type === 'string_array') {
if (itemA.value.length !== itemB.value.length) {
return true;
}
return itemA.value.filter(i => !itemB.value.includes(i)).length !== 0;
}
return itemA.value !== itemB.value;
};
checkSaveRequired = () => { checkSaveRequired = () => {
const changedItems = []; const changedItems = [];
let i = 0; let i = 0;
for (const item of this.state.ItemList) { for (const item of this.state.ItemList) {
if (this.state.OriginalItemList[i++].value !== item.value) { if (this.checkItemChanged(this.state.OriginalItemList[i++], item)) {
changedItems.push(item); changedItems.push(item);
} }
} }
@@ -41,7 +52,7 @@ class Configuration extends IPCContainer {
const changedObjectItems = []; const changedObjectItems = [];
let j = 0; let j = 0;
for (const item of this.state.ObjectLookup[key]) { for (const item of this.state.ObjectLookup[key]) {
if (this.state.OriginalObjectLookup[key][j++].value !== item.value) { if (this.checkItemChanged(this.state.OriginalObjectLookup[key][j++], item)) {
changedObjectItems.push(item); changedObjectItems.push(item);
} }
} }
@@ -72,6 +83,7 @@ class Configuration extends IPCContainer {
this.sendRequest(Constants.IPC_Get_Config_Template, { this.sendRequest(Constants.IPC_Get_Config_Template, {
Provider: this.props.DisplayConfiguration, Provider: this.props.DisplayConfiguration,
Remote: this.props.DisplayRemoteConfiguration, Remote: this.props.DisplayRemoteConfiguration,
S3: this.props.DisplayS3Configuration,
Version: this.props.version, Version: this.props.version,
}); });
} }
@@ -91,7 +103,10 @@ class Configuration extends IPCContainer {
hide_remote: template[key] ? template[key].hide_remote : false, hide_remote: template[key] ? template[key].hide_remote : false,
label: key, label: key,
remote: template[key] ? template[key].remote : false, remote: template[key] ? template[key].remote : false,
type: template[key] ? template[key].type : null,
value: (template[key] && (template[key].type === 'object')) ? value: (template[key] && (template[key].type === 'object')) ?
config[key] :
(template[key] && (template[key].type === 'string_array')) ?
config[key] : config[key] :
config[key].toString(), config[key].toString(),
}; };
@@ -114,7 +129,7 @@ class Configuration extends IPCContainer {
const itemList = [ const itemList = [
...this.state.ItemList ...this.state.ItemList
]; ];
itemList[idx].value = target.value.toString(); itemList[idx].value = target.type === 'textarea' ? target.string_array : target.value.toString();
this.setState({ this.setState({
ItemList: itemList ItemList: itemList
}); });
@@ -128,7 +143,7 @@ class Configuration extends IPCContainer {
...this.state.ObjectLookup, ...this.state.ObjectLookup,
}; };
itemList[idx].value = target.value.toString(); itemList[idx].value = target.type === 'textarea' ? target.string_array : target.value.toString();
objectLookup[name] = itemList; objectLookup[name] = itemList;
this.setState({ this.setState({
ObjectLookup: objectLookup, ObjectLookup: objectLookup,
@@ -163,6 +178,8 @@ class Configuration extends IPCContainer {
ObjectLookup: objectLookup, ObjectLookup: objectLookup,
OriginalItemList: itemListCopy, OriginalItemList: itemListCopy,
OriginalObjectLookup: objectLookupCopy, OriginalObjectLookup: objectLookupCopy,
}, () => {
}); });
} else { } else {
this.props.notifyError(arg.data.Error); this.props.notifyError(arg.data.Error);
@@ -177,6 +194,7 @@ class Configuration extends IPCContainer {
this.sendRequest(Constants.IPC_Get_Config, { this.sendRequest(Constants.IPC_Get_Config, {
Provider: this.props.DisplayConfiguration, Provider: this.props.DisplayConfiguration,
Remote: this.props.DisplayRemoteConfiguration, Remote: this.props.DisplayRemoteConfiguration,
S3: this.props.DisplayS3Configuration,
Version: this.props.version, Version: this.props.version,
}); });
}); });
@@ -201,7 +219,9 @@ class Configuration extends IPCContainer {
for (const item of this.state.ChangedItems) { for (const item of this.state.ChangedItems) {
changedItems.push({ changedItems.push({
Name: item.label, Name: item.label,
Value: item.value, Value: item.type === 'string_array' ?
item.value.join(';') :
item.value,
}); });
} }
@@ -210,7 +230,9 @@ class Configuration extends IPCContainer {
for (const item of this.state.ChangedObjectLookup[key]) { for (const item of this.state.ChangedObjectLookup[key]) {
changedItems.push({ changedItems.push({
Name: key + '.' + item.label, Name: key + '.' + item.label,
Value: item.value, Value: item.type === 'string_array' ?
item.value.join(';') :
item.value,
}); });
} }
} }
@@ -220,6 +242,7 @@ class Configuration extends IPCContainer {
Items: changedItems, Items: changedItems,
Provider: this.props.DisplayConfiguration, Provider: this.props.DisplayConfiguration,
Remote: this.props.DisplayRemoteConfiguration, Remote: this.props.DisplayRemoteConfiguration,
S3: this.props.DisplayS3Configuration,
Version: this.props.version, Version: this.props.version,
}); });
}); });
@@ -251,43 +274,38 @@ class Configuration extends IPCContainer {
<Modal> <Modal>
<Box dxStyle={{width: '40vw', padding: 'var(--default_spacing)'}}> <Box dxStyle={{width: '40vw', padding: 'var(--default_spacing)'}}>
<h1 style={{width: '100%', textAlign: 'center'}}>Save Changes?</h1> <h1 style={{width: '100%', textAlign: 'center'}}>Save Changes?</h1>
<table width='100%'><tbody> <table width='100%'>
<tbody>
<tr> <tr>
<td align='center' width='50%'><Button clicked={this.saveAndClose} disabled={this.state.Saving}>Yes</Button></td> <td align='center' width='50%'><Button clicked={this.saveAndClose}
<td align='center' width='50%'><Button clicked={this.props.hideConfiguration} disabled={this.state.Saving}>No</Button></td> disabled={this.state.Saving}>Yes</Button>
</td>
<td align='center' width='50%'><Button clicked={this.props.hideConfiguration}
disabled={this.state.Saving}>No</Button></td>
</tr> </tr>
</tbody></table> </tbody>
</table>
</Box> </Box>
</Modal> </Modal>
); );
} }
const configurationItems = this.state.ItemList let autoFocus = true;
.map((k, i) => {
return (
((!this.state.IsRemoteMount || !k.hide_remote) && (!k.advanced || (this.state.ShowAdvanced && k.advanced))) ?
<ConfigurationItem advanced={k.advanced}
changed={e=>this.handleItemChanged(e, i)}
grouping={'Settings'}
items={this.state.Template[k.label].items}
key={i}
label={k.label}
template={this.state.Template[k.label]}
value={k.value}/> :
null)
});
let objectItems = []; let objectItems = [];
for (const key of Object.keys(this.state.ObjectLookup)) { for (const key of Object.keys(this.state.ObjectLookup)) {
objectItems.push(( objectItems.push((
<div key={key}> <div key={key}>
<h1>{key}</h1> <h2>{key}</h2>
<div> <div>
{ {
this.state.ObjectLookup[key].map((k, i) => { this.state.ObjectLookup[key].map((k, i) => {
const shouldFocus = autoFocus;
autoFocus = false;
return ( return (
(!k.advanced || (this.state.ShowAdvanced && k.advanced && !k.remote) || this.showRemoteConfigItem(k, this.state.ObjectLookup[key])) ? (!k.advanced || (this.state.ShowAdvanced && k.advanced && !k.remote) || this.showRemoteConfigItem(k, this.state.ObjectLookup[key])) ?
<ConfigurationItem advanced={k.advanced} <ConfigurationItem advanced={k.advanced}
autoFocus={shouldFocus}
changed={e => this.handleObjectItemChanged(e, key, i)} changed={e => this.handleObjectItemChanged(e, key, i)}
grouping={key} grouping={key}
items={this.state.Template[key].template[k.label].items} items={this.state.Template[key].template[k.label].items}
@@ -304,21 +322,56 @@ class Configuration extends IPCContainer {
)); ));
} }
const configurationItems = this.state.ItemList
.map((k, i) => {
const shouldFocus = autoFocus;
autoFocus = false;
return (
((!this.state.IsRemoteMount || !k.hide_remote) && (!k.advanced || (this.state.ShowAdvanced && k.advanced))) ?
<ConfigurationItem advanced={k.advanced}
autoFocus={shouldFocus}
changed={e => this.handleItemChanged(e, i)}
grouping={'Settings'}
items={this.state.Template[k.label].items}
key={i}
label={k.label}
template={this.state.Template[k.label]}
value={k.value}/> :
null
);
});
return ( return (
<div className={'Configuration'}> <div className={'Configuration'}>
{confirmSave} {confirmSave}
<Box dxDark dxStyle={{padding: 'var(--default_spacing)'}}> <Box dxDark dxStyle={{padding: 'var(--default_spacing)'}}>
<div style={{float: 'right', margin: 0, padding: 0, marginTop: '-4px', boxSizing: 'border-box', display: 'block'}}> <div style={{
<b style={{cursor: 'pointer'}} float: 'right',
onClick={this.checkSaveRequired}>X</b> margin: 0,
padding: 0,
marginTop: '-4px',
boxSizing: 'border-box',
display: 'block'
}}>
<a href={'#'}
onClick={this.checkSaveRequired}
style={{cursor: 'pointer'}}>X</a>
</div> </div>
<h1 style={{width: '100%', textAlign: 'center'}}>{( <h1 style={{width: '100%', textAlign: 'center'}}>{(
this.props.DisplayRemoteConfiguration ? this.props.DisplayRemoteConfiguration ?
this.props.DisplayConfiguration.substr(6) : this.props.DisplayConfiguration.substr(6) :
this.props.DisplayConfiguration) + ' Configuration'}</h1> this.props.DisplayConfiguration) + ' Configuration '}
</h1>
<div style={{overflowY: 'auto', height: '90%'}}> <div style={{overflowY: 'auto', height: '90%'}}>
{this.props.MState.Mounted && (configurationItems.length > 0) ? <Button
buttonStyles={{width: 'auto', height: 'auto', marginLeft: 'auto', marginRight: '4px'}}
clicked={() => {
this.props.displayPinnedManager(true);
return false;
}}>&nbsp;Pinned File Manager...&nbsp;</Button> : null}
<div style={{marginBottom: '4px'}}/>
{objectItems} {objectItems}
{(configurationItems.length > 0) ? <h1>Settings</h1> : null} {(configurationItems.length > 0) ? <h2>Settings</h2> : null}
{configurationItems} {configurationItems}
</div> </div>
</Box> </Box>
@@ -331,12 +384,15 @@ const mapStateToProps = state => {
return { return {
DisplayConfiguration: state.mounts.DisplayConfiguration, DisplayConfiguration: state.mounts.DisplayConfiguration,
DisplayRemoteConfiguration: state.mounts.DisplayRemoteConfiguration, DisplayRemoteConfiguration: state.mounts.DisplayRemoteConfiguration,
DisplayS3Configuration: state.mounts.DisplayS3Configuration,
MState: state.mounts.MountState[state.mounts.DisplayConfiguration],
Platform: state.common.Platform, Platform: state.common.Platform,
} }
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
displayPinnedManager: display => dispatch(displayPinnedManager(display)),
notifyError: (msg, critical, callback) => dispatch(notifyError(msg, critical, callback)), notifyError: (msg, critical, callback) => dispatch(notifyError(msg, critical, callback)),
hideConfiguration: () => dispatch(displayConfiguration(null, false)), hideConfiguration: () => dispatch(displayConfiguration(null, false)),
} }

View File

@@ -8,7 +8,7 @@ input.ConfigurationItemInput {
margin: 0; margin: 0;
padding: 2px; padding: 2px;
border-radius: var(--border_radius); border-radius: var(--border_radius);
background: rgba(160, 160, 160, 0.1); background: var(--control_background);
border: none; border: none;
box-shadow: none; box-shadow: none;
outline: none; outline: none;
@@ -16,6 +16,23 @@ input.ConfigurationItemInput {
box-sizing: border-box; box-sizing: border-box;
} }
textarea.ConfigurationItemInput {
display: block;
margin: 0;
padding: 2px;
border-radius: var(--border_radius);
background: var(--control_background);
border: none;
box-shadow: none;
outline: none;
color: var(--text_color);
box-sizing: border-box;
resize: none;
overflow-y: scroll;
overflow:-moz-scrollbars-horizontal;
white-space: nowrap;
}
.ConfigurationInfo { .ConfigurationInfo {
cursor: pointer; cursor: pointer;
} }

View File

@@ -4,13 +4,18 @@ import CheckBox from '../../../components/UI/CheckBox/CheckBox';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
import {faInfoCircle} from '@fortawesome/free-solid-svg-icons'; import {faInfoCircle} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {notifyInfo} from '../../../redux/actions/error_actions'; import {
notifyError,
notifyInfo
} from '../../../redux/actions/error_actions';
import settings from '../../../assets/settings'; import settings from '../../../assets/settings';
import DropDown from '../../../components/UI/DropDown/DropDown'; import DropDown from '../../../components/UI/DropDown/DropDown';
import Password from '../../../containers/UI/Password/Password';
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)) notifyError: msg => dispatch(notifyError(msg)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)),
} }
}; };
@@ -18,7 +23,9 @@ export default connect(null, mapDispatchToProps)(props => {
const handleChanged = (e) => { const handleChanged = (e) => {
const target = e.target; const target = e.target;
if (target.type === 'checkbox') { if (target.type === 'checkbox') {
target.value = e.target.checked ? "true" : "false"; target.value = e.target.checked ? 'true' : 'false';
} else if (target.type === 'textarea') {
e.target.string_array = String(e.target.value).replace(/\r\n/g,'\n').split('\n');
} }
props.changed(target); props.changed(target);
}; };
@@ -30,49 +37,71 @@ export default connect(null, mapDispatchToProps)(props => {
props.notifyInfo(props.label, description); props.notifyInfo(props.label, description);
}; };
infoDisplay = <a href={void(0)} infoDisplay = <a href={'#'}
className={'ConfigurationInfo'} className={'ConfigurationInfo'}
onClick={()=>{displayInfo(); return false;}}><FontAwesomeIcon icon={faInfoCircle}/></a>; onClick={()=>{displayInfo(); return false;}}><FontAwesomeIcon icon={faInfoCircle}/></a>;
} }
let data; let data;
switch (props.template.type) { switch (props.template.type) {
case "bool": case 'bool':
data = <CheckBox changed={handleChanged} data = <CheckBox changed={handleChanged}
checked={props.value} checked={props.value}
disabled={props.readOnly}/>; disabled={props.readOnly}
autoFocus={props.autoFocus}/>;
break; break;
case "double": case 'double':
data = <input min={0.0} data = <input min={0.0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
step={"0.01"} step={'0.01'}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'number'} type={'number'}
value={parseFloat(props.value).toFixed(2)}/>; value={parseFloat(props.value).toFixed(2)}/>;
break; break;
case "list": case 'list':
data = <DropDown alt data = <DropDown alt
auto auto
autoFocus={props.autoFocus}
changed={handleChanged} changed={handleChanged}
disabled={props.readOnly} disabled={props.readOnly}
items={props.items} items={props.items}
selected={props.value} />; selected={props.value} />;
break; break;
case "string": case 'string':
data = <input onChange={e=>handleChanged(e)} if (props.template.subtype === 'password') {
data = (
<Password autoFocus={props.autoFocus}
changed={s => handleChanged({
target: {
type: 'password',
value: s,
},
})}
disabled={props.readOnly}
mismatchHandler={() => props.notifyError('Passwords do not match')}
value={props.value} />
);
} else {
data = (
<input onChange={e => handleChanged(e)}
autoFocus={props.autoFocus}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
disabled={props.readOnly} disabled={props.readOnly}
type={'text'} type={'text'}
value={props.value}/>; value={props.value}/>
);
}
break; break;
case "uint8": case 'uint8':
data = <input max={255} data = <input max={255}
min={0} min={0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
@@ -80,9 +109,10 @@ export default connect(null, mapDispatchToProps)(props => {
value={props.value}/>; value={props.value}/>;
break; break;
case "uint16": case 'uint16':
data = <input max={65535} data = <input max={65535}
min={0} min={0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
@@ -90,9 +120,10 @@ export default connect(null, mapDispatchToProps)(props => {
value={props.value}/>; value={props.value}/>;
break; break;
case "uint32": case 'uint32':
data = <input max={4294967295} data = <input max={4294967295}
min={0} min={0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
@@ -100,9 +131,10 @@ export default connect(null, mapDispatchToProps)(props => {
value={props.value}/>; value={props.value}/>;
break; break;
case "uint64": case 'uint64':
data = <input max={18446744073709551615} data = <input max={18446744073709551615}
min={0} min={0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
@@ -110,6 +142,33 @@ export default connect(null, mapDispatchToProps)(props => {
value={props.value}/>; value={props.value}/>;
break; break;
case 'string_array':
data = (
<textarea autoFocus={props.autoFocus}
disabled={props.readOnly}
rows={4}
cols={36}
onChange={e=>handleChanged(e)}
className={'ConfigurationItemInput'}
value={props.value.join('\n')} />
);
break;
case 'password':
data = (
<Password autoFocus={props.autoFocus}
changed={s => handleChanged({
target: {
type: 'password',
value: s,
},
})}
disabled={props.readOnly}
mismatchHandler={() => props.notifyError('Passwords do not match')}
value={props.value} />
);
break;
default: default:
data = <div>{props.value}</div>; data = <div>{props.value}</div>;
} }
@@ -121,8 +180,8 @@ export default connect(null, mapDispatchToProps)(props => {
<tbody> <tbody>
<tr> <tr>
{infoDisplay ? {infoDisplay ?
<td width='100%'>{infoDisplay} {props.label}</td> : <td width='100%' valign={'top'}>{infoDisplay} {props.label}</td> :
<td width='100%'>{props.label}</td>} <td width='100%' valign={'top'}>{props.label}</td>}
<td>{data}</td> <td>{data}</td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -8,7 +8,15 @@ import Text from '../../../components/UI/Text/Text';
import Grid from '../../../components/UI/Grid/Grid'; import Grid from '../../../components/UI/Grid/Grid';
import configureImage from '../../../assets/images/configure.png'; import configureImage from '../../../assets/images/configure.png';
import RootElem from '../../../components/UI/RootElem/RootElem'; import RootElem from '../../../components/UI/RootElem/RootElem';
import {displayConfiguration, removeRemoteMount, setProviderState} from '../../../redux/actions/mount_actions'; import {
displayConfiguration,
removeMount,
setProviderState
} from '../../../redux/actions/mount_actions';
import {
displaySkynetExport,
displaySkynetImport,
} from '../../../redux/actions/skynet_actions'
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { faTrashAlt} from '@fortawesome/free-solid-svg-icons'; import { faTrashAlt} from '@fortawesome/free-solid-svg-icons';
import CheckBox from '../../../components/UI/CheckBox/CheckBox'; import CheckBox from '../../../components/UI/CheckBox/CheckBox';
@@ -23,8 +31,10 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
return { return {
displayConfiguration: (provider, remote) => dispatch(displayConfiguration(provider, remote)), displayConfiguration: (provider, remote, s3) => dispatch(displayConfiguration(provider, remote, s3)),
removeRemoteMount: provider => dispatch(removeRemoteMount(provider)), displaySkynetExport: display => dispatch(displaySkynetExport(display)),
displaySkynetImport: display => dispatch(displaySkynetImport(display)),
removeMount: provider => dispatch(removeMount(provider)),
setProviderState: (provider, state) => dispatch(setProviderState(provider, state)), setProviderState: (provider, state) => dispatch(setProviderState(provider, state)),
} }
}; };
@@ -53,7 +63,9 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
rowSpan={6}> rowSpan={6}>
<img alt='' <img alt=''
height={'16px'} height={'16px'}
onClick={props.MState.AllowMount ? ()=>props.displayConfiguration(props.provider, props.remote) : e=>{e.preventDefault();}} onClick={props.MState.AllowMount ? () => props.displayConfiguration(props.provider, props.remote, props.s3) : e => {
e.preventDefault();
}}
src={configureImage} src={configureImage}
style={{padding: 0, border: 0, margin: 0, ...pointer}} style={{padding: 0, border: 0, margin: 0, ...pointer}}
width={'16px'}/> width={'16px'}/>
@@ -110,7 +122,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
width={19}/>; width={19}/>;
const actionsDisplay = ( const actionsDisplay = (
<Button clicked={()=>props.clicked(props.provider, props.remote, !props.MState.Mounted, props.PState.MountLocation)} <Button
clicked={() => props.clicked(props.provider, props.remote, props.s3, !props.MState.Mounted, props.PState.MountLocation)}
col={inputColumnSpan + 2} col={inputColumnSpan + 2}
colSpan={21} colSpan={21}
disabled={!props.MState.AllowMount} disabled={!props.MState.AllowMount}
@@ -125,7 +138,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
row={secondRow} row={secondRow}
rowSpan={7}> rowSpan={7}>
<CheckBox changed={handleAutoMountChanged} <CheckBox changed={handleAutoMountChanged}
checked={props.PState.AutoMount} label={'Auto-mount'}/> checked={props.PState.AutoMount}
label={'Auto-mount'}/>
</RootElem> </RootElem>
); );
@@ -141,26 +155,28 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
); );
let removeControl; let removeControl;
if (props.remote) { if (props.allowRemove) {
const removeDisabled = !props.MState.AllowMount || props.MState.Mounted; const removeDisabled = !props.MState.AllowMount || props.MState.Mounted;
const removeStyle = { const removeStyle = {
cursor: removeDisabled ? 'no-drop' : 'pointer' cursor: removeDisabled ? 'no-drop' : 'pointer'
}; };
const handleRemoveMount = () => { const handleRemoveMount = () => {
if (!removeDisabled) { if (!removeDisabled) {
props.removeRemoteMount(props.provider); props.removeMount(props.provider);
} }
}; };
removeControl = ( removeControl = (
<a col={dimensions=>dimensions.columns - 6} <RootElem col={dimensions=>dimensions.columns - 6}
href={void(0)} row={secondRow + 3}>
<a href={'#'}
onClick={handleRemoveMount} onClick={handleRemoveMount}
row={secondRow + 3}
style={removeStyle}> style={removeStyle}>
<FontAwesomeIcon icon={faTrashAlt}/> <FontAwesomeIcon icon={faTrashAlt}/>
</a>); </a>
</RootElem>);
} }
const isSkynet = (props.provider === 'Skynet');
return ( return (
<div className={'MountItem'}> <div className={'MountItem'}>
<Grid noScroll> <Grid noScroll>
@@ -168,8 +184,32 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
<Text <Text
col={configButton ? 6 : 0} col={configButton ? 6 : 0}
rowSpan={5} rowSpan={5}
text={props.remote ? props.provider.substr(6) : props.provider} colSpan={90}
type={'Heading1'}/> text={props.remote ? props.provider.substr(6) : props.s3 ? props.provider.substr(2) : isSkynet ? props.provider + ' [EXPERIMENTAL]' : props.provider}
textAlign={'Left'}
type={'Heading2'}/>
{(isSkynet && (props.MState.Mounted)) ? (
<a href={'#'}
col={(configButton ? 24 : 18) + 34}
onClick={props.MState.AllowMount ? () => props.displaySkynetExport(true) : e => {
e.preventDefault();
}}
rowSpan={5}
style={{...pointer, fontWeight: 'normal'}}>
<u>Export</u>
</a>
) : null}
{(isSkynet && (props.MState.Mounted)) ? (
<a href={'#'}
col={(configButton ? 24 + 13 : 18 + 13) + 34}
onClick={props.MState.AllowMount ? () => props.displaySkynetImport(true) : e => {
e.preventDefault();
}}
rowSpan={5}
style={{...pointer, fontWeight: 'normal'}}>
<u>Import</u>
</a>
) : null}
{inputControls} {inputControls}
{actionsDisplay} {actionsDisplay}
{autoMountControl} {autoMountControl}

View File

@@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import AddRemoteMount from '../AddRemoteMount/AddRemoteMount'; import AddMount from '../AddMount/AddMount';
import Box from '../../components/UI/Box/Box'; import Box from '../../components/UI/Box/Box';
import Button from '../../components/UI/Button/Button'; import Button from '../../components/UI/Button/Button';
import {connect} from 'react-redux'; import {connect} from 'react-redux';
@@ -80,6 +80,7 @@ class MountItems extends IPCContainer {
this.sendRequest(Constants.IPC_Detect_Mount, { this.sendRequest(Constants.IPC_Detect_Mount, {
RemoteMounts: this.props.RemoteMounts, RemoteMounts: this.props.RemoteMounts,
S3Mounts: this.props.S3Mounts,
Provider: provider, Provider: provider,
Version: this.props.InstalledVersion, Version: this.props.InstalledVersion,
}); });
@@ -94,7 +95,7 @@ class MountItems extends IPCContainer {
} }
}; };
displayRetryMount = (provider, remote, mountLocation, msg) => { displayRetryMount = (provider, remote, s3, mountLocation, msg) => {
if (!this.state.RetryItems[provider]) { if (!this.state.RetryItems[provider]) {
let retryItems = { let retryItems = {
...this.state.RetryItems ...this.state.RetryItems
@@ -121,7 +122,7 @@ class MountItems extends IPCContainer {
const retrySeconds = retryItems[provider].RetrySeconds - 1; const retrySeconds = retryItems[provider].RetrySeconds - 1;
if (retrySeconds === 0) { if (retrySeconds === 0) {
this.cancelRetryMount(provider, () => { this.cancelRetryMount(provider, () => {
this.handleMountUnMount(provider, remote,true, mountLocation); this.handleMountUnMount(provider, remote, s3, true, mountLocation);
}); });
} else { } else {
retryItems[provider].RetrySeconds = retrySeconds; retryItems[provider].RetrySeconds = retrySeconds;
@@ -152,17 +153,18 @@ class MountItems extends IPCContainer {
this.props.setProviderState(provider, state); this.props.setProviderState(provider, state);
}; };
handleMountUnMount = (provider, remote, mount, location) => { handleMountUnMount = (provider, remote, s3, mount, location) => {
if (!location || (location.trim().length === 0)) { if (!location || (location.trim().length === 0)) {
this.props.notifyError('Mount location is not set'); this.props.notifyError('Mount location is not set');
} else { } else {
let allowAction = true; let allowAction = true;
if (mount) { if (mount) {
let result = remote ? let result = remote || s3 || provider === 'Skynet' ?
{Valid: true, Success: true} : {Valid: true, Success: true} :
this.sendSyncRequest(Constants.IPC_Check_Daemon_Version, { this.sendSyncRequest(Constants.IPC_Check_Daemon_Version, {
Provider: provider, Provider: provider,
Remote: remote, Remote: remote,
S3: s3,
Version: this.props.InstalledVersion Version: this.props.InstalledVersion
}).data; }).data;
if (result.Success) { if (result.Success) {
@@ -179,11 +181,11 @@ class MountItems extends IPCContainer {
} else { } else {
allowAction = false; allowAction = false;
if ((result.Code === new Uint32Array([-1])[0]) || (result.Code === new Uint8Array([-1])[0])) { if ((result.Code === new Uint32Array([-1])[0]) || (result.Code === new Uint8Array([-1])[0])) {
this.displayRetryMount(provider, remote, location, 'Failed to connect to ' + provider + ' daemon'); this.displayRetryMount(provider, remote, s3, location, 'Failed to connect to ' + provider + ' daemon');
} else if ((result.Code === new Uint32Array([-3])[0]) || (result.Code === new Uint8Array([-3])[0])) { } else if ((result.Code === new Uint32Array([-3])[0]) || (result.Code === new Uint8Array([-3])[0])) {
this.displayRetryMount(provider, remote, location, 'Incompatible ' + provider + ' daemon. Please upgrade ' + provider + '.'); this.displayRetryMount(provider, remote, s3, location, 'Incompatible ' + provider + ' daemon. Please upgrade ' + provider + '.');
} else { } else {
this.displayRetryMount(provider, remote, location, 'Version check failed: ' + result.Error); this.displayRetryMount(provider, remote, s3, location, 'Version check failed: ' + result.Error);
} }
} }
} else { } else {
@@ -191,7 +193,7 @@ class MountItems extends IPCContainer {
if (this.props.Platform === 'win32') { if (this.props.Platform === 'win32') {
this.props.notifyError('Failed to launch repertory. Please install Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019.'); this.props.notifyError('Failed to launch repertory. Please install Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019.');
} else { } else {
this.displayRetryMount(provider, remote, location, 'Version check failed: ' + result.Error); this.displayRetryMount(provider, remote, s3, location, 'Version check failed: ' + result.Error);
} }
} }
} }
@@ -205,6 +207,7 @@ class MountItems extends IPCContainer {
Location: location, Location: location,
Provider: provider, Provider: provider,
Remote: remote, Remote: remote,
S3: s3,
Version: this.props.InstalledVersion, Version: this.props.InstalledVersion,
}); });
} else { } else {
@@ -212,6 +215,7 @@ class MountItems extends IPCContainer {
Location: location, Location: location,
Provider: provider, Provider: provider,
Remote: remote, Remote: remote,
S3: s3,
Version: this.props.InstalledVersion, Version: this.props.InstalledVersion,
}); });
} }
@@ -219,10 +223,27 @@ class MountItems extends IPCContainer {
} }
}; };
getProviderList = () => { getProviderList = providersOnly => {
const providerList = Constants.PROVIDER_LIST.filter(i => {
return ((i === 'Skynet') && this.props.skynetSupported) ||
((i === 'ScPrime') && this.props.scPrimeSupported) ||
((i === 'Sia') && this.props.siaSupported);
});
let remoteList = [];
if (this.props.remoteSupported && !providersOnly) {
remoteList = [...this.props.RemoteMounts];
}
let s3List = [];
if (this.props.s3Supported && !providersOnly) {
s3List = [...this.props.S3Mounts];
}
return [ return [
...Constants.PROVIDER_LIST, ...providerList,
...this.props.RemoteMounts, ...remoteList,
...s3List,
]; ];
}; };
@@ -298,7 +319,7 @@ class MountItems extends IPCContainer {
this.props.ProviderState[provider].AutoMount && this.props.ProviderState[provider].AutoMount &&
!mounted && !mounted &&
(location.length > 0)) { (location.length > 0)) {
this.handleMountUnMount(provider, this.props.RemoteMounts.includes(provider),true, location); this.handleMountUnMount(provider, this.props.RemoteMounts.includes(provider), this.props.S3Mounts.includes(provider), true, location);
} }
}; };
@@ -325,7 +346,11 @@ class MountItems extends IPCContainer {
retryDisplay = ( retryDisplay = (
<Modal> <Modal>
<Box dxDark dxStyle={{padding: 'var(--default_spacing)', minWidth: '70vw'}}> <Box dxDark dxStyle={{padding: 'var(--default_spacing)', minWidth: '70vw'}}>
<h1 style={{textAlign: 'center', paddingBottom: 'var(--default_spacing)', color: 'var(--text_color_error)'}}>Mount Failed</h1> <h1 style={{
textAlign: 'center',
paddingBottom: 'var(--default_spacing)',
color: 'var(--text_color_error)'
}}>Mount Failed</h1>
{retryList} {retryList}
</Box> </Box>
</Modal> </Modal>
@@ -333,15 +358,17 @@ class MountItems extends IPCContainer {
} }
let footerItems = []; let footerItems = [];
if (this.props.remoteSupported) { if (this.props.remoteSupported || this.props.s3Supported) {
footerItems.push(<AddRemoteMount key={'hi_' + footerItems.length}/>); footerItems.push(<AddMount remoteSupported={this.props.remoteSupported}
s3Supported={this.props.s3Supported}
key={'hi_' + footerItems.length}/>);
} else { } else {
footerItems.push(<div key={'hi_' + footerItems.length} footerItems.push(<div key={'hi_' + footerItems.length}
style={{height: '27px'}}/>); style={{height: '27px'}}/>);
} }
let items = []; let items = [];
for (const provider of Constants.PROVIDER_LIST) { for (const provider of this.getProviderList(true)) {
items.push(( items.push((
<MountItem allowRemove={false} <MountItem allowRemove={false}
browseClicked={this.handleBrowseLocation} browseClicked={this.handleBrowseLocation}
@@ -368,15 +395,31 @@ class MountItems extends IPCContainer {
items.push(<div key={'it_' + items.length} items.push(<div key={'it_' + items.length}
style={{paddingTop: 'var(--default_spacing)'}}/>) style={{paddingTop: 'var(--default_spacing)'}}/>)
} }
items.splice(items.length - 1, 1);
} else {
items.splice(items.length - 1, 1)
} }
if (this.props.s3Supported) {
for (const provider of this.props.S3Mounts) {
items.push((
<MountItem allowRemove={true}
browseClicked={this.handleBrowseLocation}
changed={e => this.handleMountLocationChanged(provider, e.target.value)}
clicked={this.handleMountUnMount}
key={'it_' + items.length}
provider={provider}
s3/>
));
items.push(<div key={'it_' + items.length}
style={{paddingTop: 'var(--default_spacing)'}}/>)
}
}
items.splice(items.length - 1, 1);
return ( return (
<div style={{margin: 0, padding: 0}}> <div style={{margin: 0, padding: 0}}>
{retryDisplay} {retryDisplay}
<div className={this.props.remoteSupported ? 'MountItemsRemote' : 'MountItems'}> <div
className={this.props.remoteSupported || this.props.s3Supported ? 'MountItemsRemote' : 'MountItems'}>
{items} {items}
</div> </div>
<div style={{paddingTop: 'var(--default_spacing)'}}/> <div style={{paddingTop: 'var(--default_spacing)'}}/>
@@ -394,6 +437,7 @@ const mapStateToProps = state => {
Platform: state.common.Platform, Platform: state.common.Platform,
ProviderState: state.mounts.ProviderState, ProviderState: state.mounts.ProviderState,
RemoteMounts: state.mounts.RemoteMounts, RemoteMounts: state.mounts.RemoteMounts,
S3Mounts: state.mounts.S3Mounts,
} }
}; };

View File

@@ -0,0 +1,44 @@
.PinnedManager {
display: flex;
flex-direction: column;
height: 100%;
}
.PinnedManagerActiveDirectory {
margin-top: var(--default_spacing);
margin-bottom: var(--default_spacing);
padding: 2px;
width: calc(100% - 4px);
overflow: auto;
text-align: left;
border-radius: var(--border_radius);
background: var(--control_background);
}
.PinnedManagerClose {
float: right;
flex: 0;
padding: 0;
margin-top: -4px;
box-sizing: border-box;
display: block;
}
.PinnedManagerHeading {
flex: 0;
}
.PinnedManagerItems {
height: 100%;
overflow: auto;
}
.PinnedManagerItemsOwner {
margin: 0;
padding: var(--default_spacing);
border-radius: var(--border_radius);
background: var(--control_background);
display: flex;
flex-direction: column;
flex: 1;
}

View File

@@ -0,0 +1,180 @@
import React from 'react';
import './PinnedManager.css';
import {connect} from 'react-redux';
import IPCContainer from '../IPCContainer/IPCContainer';
import {notifyApplicationBusy} from '../../redux/actions/common_actions';
import {notifyError, notifyInfo} from '../../redux/actions/error_actions';
import Box from '../../components/UI/Box/Box';
import {displayPinnedManager} from '../../redux/actions/pinned_manager_actions';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faFolder} from '@fortawesome/free-solid-svg-icons';
import Button from '../../components/UI/Button/Button';
import CheckBox from '../../components/UI/CheckBox/CheckBox';
const Constants = require('../../constants');
const mapStateToProps = state => {
return {
DisplayConfiguration: state.mounts.DisplayConfiguration,
DisplayRemoteConfiguration: state.mounts.DisplayRemoteConfiguration,
DisplayS3Configuration: state.mounts.DisplayS3Configuration,
}
};
const mapDispatchToProps = dispatch => {
return {
displayPinnedManager: display => dispatch(displayPinnedManager(display)),
notifyApplicationBusy: busy => dispatch(notifyApplicationBusy(busy, true)),
notifyError: (msg, cb) => dispatch(notifyError(msg, false, cb)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCContainer {
state = {
active_directory: '/',
items: [],
previous: [],
}
componentDidMount() {
this.setRequestHandler(Constants.IPC_Get_Directory_Items_Reply, this.onGetDirectoryItemsReply);
this.grabDirectoryItems();
}
componentWillUnmount() {
super.componentWillUnmount();
}
grabDirectoryItems = () => {
this.sendRequest(Constants.IPC_Get_Directory_Items, {
Provider: this.props.DisplayConfiguration,
Remote: this.props.DisplayRemoteConfiguration,
S3: this.props.DisplayS3Configuration,
Version: this.props.version,
Path: this.state.active_directory,
});
}
onGetDirectoryItemsReply = (_, {data}) => {
if (data.Success) {
const items = data.Items
.filter(i => i.path !== '.' && (this.state.active_directory !== '/' || (i.path.substr(0, 1) !== '.')))
.map(i => {
return {
...i,
name: i.path === '..' ? i.path : i.path.substr(i.path.lastIndexOf('/') + 1),
meta: {
...i.meta,
pinned: i.meta.pinned === '1',
}
}
});
this.setState({
items,
});
} else {
this.props.notifyError(data.Error, () => {
this.props.displayPinnedManager(false);
});
}
}
createDirectory = (name, path, idx, total, item_idx) => {
const style = {}
if (item_idx + 1 !== total) {
style.marginBottom = '4px';
}
return (
<div key={'dir_' + idx} style={{...style}}>
<Button buttonStyles={{textAlign: 'left'}}
clicked={() => {
const previous = [...this.state.previous];
if (path === '..') {
path = previous.pop();
} else {
previous.push(this.state.active_directory);
}
this.setState({
items: [],
active_directory: path,
previous,
}, () => {
this.grabDirectoryItems();
});
}}>
<FontAwesomeIcon icon={faFolder}
fixedWidth
color={'var(--heading_text_color)'}
style={{padding: 0, margin: 0}}/>
&nbsp;{name}
</Button>
</div>
);
}
createFile = (name, path, pinned, idx, total, item_idx) => {
const style = {textAlign: 'left'}
if (item_idx + 1 !== total) {
style.marginBottom = '2px';
}
return (
<div key={'file_' + idx} style={{...style}}>
<CheckBox checked={pinned}
changed={() => {
const items = JSON.parse(JSON.stringify(this.state.items));
const pinned = items[item_idx].meta.pinned = !items[item_idx].meta.pinned;
this.setState({
items
}, () => {
this.sendSyncRequest(Constants.IPC_Set_Pinned, {
Provider: this.props.DisplayConfiguration,
Remote: this.props.DisplayRemoteConfiguration,
S3: this.props.DisplayS3Configuration,
Version: this.props.version,
Path: path,
Pinned: pinned,
});
});
}}
label={name}/>
</div>
);
}
render() {
let idx = 0;
return (
<Box dxDark dxStyle={{
height: 'calc(100vh - (var(--default_spacing) * 4)',
padding: 'var(--default_spacing)',
width: 'calc(100vw - (var(--default_spacing) * 4)'
}}>
<div className={'PinnedManager'}>
<div className={'PinnedManagerHeading'}>
<div className={'PinnedManagerClose'}>
<a href={'#'}
onClick={() => this.props.displayPinnedManager(false)}
style={{cursor: 'pointer', flex: '0'}}>X</a>
</div>
<h1 style={{width: '100%', textAlign: 'center'}}>{'Pinned File Manager'}</h1>
<div className={'PinnedManagerActiveDirectory'}>
<b>&nbsp;{this.state.active_directory}</b>
</div>
</div>
<div className={'PinnedManagerItemsOwner'}>
<div className={'PinnedManagerItems'}>
{
this.state.items.map((i, k) => {
return i.directory ?
this.createDirectory(i.name, i.path, idx++, this.state.items.length, k) :
this.createFile(i.name, i.path, i.meta.pinned, idx++, this.state.items.length, k);
})
}
</div>
</div>
</div>
</Box>
)
}
});

View File

@@ -0,0 +1,21 @@
.SkynetExportHeading {
text-align: center;
padding-bottom: var(--default_spacing);
}
.SkynetExportList {
display: inline-block;
width: 100%;
overflow-x: auto;
overflow-y: auto;
height: calc(90vh - 80px);
}
.SkynetExportTree {
display: inline-flex;
overflow-x: scroll;
overflow-y: scroll;
white-space: nowrap;
height: calc(90vh - 80px);
width: 100%;
}

View File

@@ -0,0 +1,235 @@
import React from 'react';
import './SkynetExport.css';
import CheckboxTree from 'react-checkbox-tree';
import {connect} from 'react-redux';
import IPCContainer from '../IPCContainer/IPCContainer';
import {notifyApplicationBusy} from '../../redux/actions/common_actions';
import {notifyError, notifyInfo} from '../../redux/actions/error_actions';
import Box from '../../components/UI/Box/Box';
import {displaySkynetExport} from '../../redux/actions/skynet_actions';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {
faCheckSquare, faChevronDown,
faChevronRight, faFile, faFolder, faFolderOpen,
faHSquare, faMinusSquare, faPlusSquare,
faSquare
} from '@fortawesome/free-solid-svg-icons';
import Button from '../../components/UI/Button/Button';
const Constants = require('../../constants');
const mapStateToProps = state => {
return {
AppBusy: state.common.AppBusy,
};
};
const mapDispatchToProps = dispatch => {
return {
displaySkynetExport: display => dispatch(displaySkynetExport(display)),
notifyApplicationBusy: busy => dispatch(notifyApplicationBusy(busy, true)),
notifyError: msg => dispatch(notifyError(msg)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCContainer {
state = {
checked: [],
clicked: {},
expanded: [],
nodes: [],
second_stage: false,
}
componentDidMount() {
this.setRequestHandler(Constants.IPC_Grab_Skynet_Tree_Reply, this.onGrabSkynetTreeReply);
this.setRequestHandler(Constants.IPC_Export_Skylinks_Reply, this.onExportSkylinksReply);
this.sendRequest(Constants.IPC_Grab_Skynet_Tree, {Version: this.props.version});
}
componentWillUnmount() {
super.componentWillUnmount();
}
createNodes = items => {
/*
{
name: '',
path: '',
directory: true/false,
children: [],
}
*/
const ret = [];
for (const item of items) {
const treeItem = {
label: item.name,
path: item.path,
value: item.name === '/' ? 0 : item.path ? item.path : JSON.stringify(item),
};
if (item.directory) {
treeItem.children = this.createNodes(item.children);
}
ret.push(treeItem);
}
return ret;
}
handleNavigation = () => {
if (this.state.second_stage) {
this.props.notifyApplicationBusy(true);
this.sendRequest(Constants.IPC_Export_Skylinks, {
Version: this.props.version,
Paths: this.state.checked,
});
} else {
if (this.state.checked.length === 0) {
this.props.notifyError('No files have been checked');
} else {
this.setState({
second_stage: true,
});
}
}
}
onExportSkylinksReply = (_, arg) => {
this.props.notifyApplicationBusy(false);
if (arg.data.Success) {
this.setState({
checked: [],
clicked: {},
expanded: [],
nodes: [],
second_stage: false,
}, () => {
this.props.notifyInfo('Skylink Exports', '!alternate!!copyable!' + JSON.stringify(arg.data.Result.success, null, 2));
this.sendRequest(Constants.IPC_Grab_Skynet_Tree, {Version: this.props.version});
});
} else {
this.props.notifyError(arg.data.Error);
}
}
onGrabSkynetTreeReply = (_, arg) => {
if (arg.data.Success) {
this.setState({
checked: [],
expanded: [0],
nodes: this.createNodes(arg.data.Result),
});
} else {
this.setState({
checked: [],
expanded: [],
nodes: [],
}, () => {
this.props.notifyError(arg.data.Error);
});
}
}
render() {
return this.props.AppBusy ? (<div/>) : (
<Box dxDark dxStyle={{
height: '90vh',
padding: 'var(--default_spacing)',
width: 'calc(100vw - (var(--default_spacing) * 4)'
}}>
<div
style={{
float: 'right',
margin: 0,
padding: 0,
marginTop: '-4px',
boxSizing: 'border-box',
display: 'block'
}}>
<a href={'#'}
onClick={() => this.props.displaySkynetExport(false)}
style={{cursor: 'pointer'}}>X</a>
</div>
<h1
className={'SkynetExportHeading'}>{this.state.second_stage ? 'Verify Exports' : 'Export Files'}</h1>
<div className={this.state.second_stage ? 'SkynetExportList' : 'SkynetExportTree'}>
{
this.state.second_stage ?
this.state.checked.map(path => {
return (
<input readOnly
className={'ConfigurationItemInput'}
key={path}
style={{width: '100%', marginBottom: 'var(--default_spacing)'}}
type={'text'}
value={path}/>
);
})
: (
<CheckboxTree checked={this.state.checked}
expanded={this.state.expanded}
expandOnClick
showExpandAll
icons={{
check: <FontAwesomeIcon icon={faCheckSquare} fixedWidth
style={{padding: 0, margin: 0}}/>,
uncheck: <FontAwesomeIcon icon={faSquare} fixedWidth
style={{padding: 0, margin: 0}}/>,
halfCheck: <FontAwesomeIcon icon={faHSquare} fixedWidth
style={{padding: 0, margin: 0}}/>,
expandClose: <FontAwesomeIcon icon={faChevronRight} fixedWidth
style={{padding: 0, margin: 0}}/>,
expandOpen: <FontAwesomeIcon icon={faChevronDown} fixedWidth
style={{padding: 0, margin: 0}}/>,
expandAll: <FontAwesomeIcon icon={faPlusSquare} fixedWidth
style={{padding: 0, margin: 0}}/>,
collapseAll: <FontAwesomeIcon icon={faMinusSquare} fixedWidth
style={{padding: 0, margin: 0}}/>,
parentClose: <FontAwesomeIcon icon={faFolder}
fixedWidth
color={'var(--heading_text_color)'}
style={{padding: 0, margin: 0}}/>,
parentOpen: <FontAwesomeIcon icon={faFolderOpen}
fixedWidth
color={'var(--heading_text_color)'}
style={{padding: 0, margin: 0}}/>,
leaf: <FontAwesomeIcon icon={faFile}
fixedWidth
color={'var(--text_color)'}
style={{padding: 0, margin: 0}}/>
}}
nodes={this.state.nodes}
onClick={clicked => this.setState({clicked})}
onCheck={checked => this.setState({checked})}
onExpand={expanded => this.setState({expanded})}/>
)
}
</div>
<div style={{display: 'flex', justifyContent: 'flex-end'}}>
{
this.state.second_stage ?
<Button buttonStyles={{
height: 'auto',
marginLeft: 'var(--default_spacing)',
marginTop: 'var(--default_spacing)',
width: 'auto'
}} clicked={() => this.setState({
second_stage: false,
})}>{'Back'}</Button> :
null
}
<Button buttonStyles={{
height: 'auto',
marginLeft: 'var(--default_spacing)',
marginTop: 'var(--default_spacing)',
width: 'auto'
}}
clicked={this.handleNavigation}>{this.state.second_stage ? 'Export' : 'Next'}</Button>
</div>
</Box>
);
}
});

View File

@@ -0,0 +1,7 @@
.ImportOwner {
display: flex;
overflow-y: auto;
overflow-x: auto;
margin: 0;
padding-bottom: var(--default_spacing);
}

View File

@@ -0,0 +1,24 @@
import React from 'react'
import './Import.css'
export default ({data}) => {
return (
<div className={'ImportOwner'}>
<input readOnly
className={'ConfigurationItemInput'}
style={{maxWidth: 'calc(33.33% - var(--default_spacing)', marginRight: 'var(--default_spacing)'}}
type={'text'}
value={data.directory}/>
<input readOnly
className={'ConfigurationItemInput'}
style={{maxWidth: 'calc(33.33% - calc(var(--default_spacing) / 2))'}}
type={'text'}
value={data.skylink}/>
<input readOnly
className={'ConfigurationItemInput'}
style={{maxWidth: 'calc(33.33% - calc(var(--default_spacing) / 2))', marginLeft: 'var(--default_spacing)'}}
type={'text'}
value={data.token}/>
</div>
);
};

View File

@@ -0,0 +1,13 @@
.ImportListOwner {
max-height: 56vh;
margin: 0;
padding: 0;
overflow-x: auto;
overflow-y: auto;
}
.ImportListHeader {
margin: 0;
padding: 0;
display: flex;
}

View File

@@ -0,0 +1,29 @@
import React from 'react'
import './ImportList.css'
import Import from './Import/Import'
import Text from '../../../components/UI/Text/Text';
export default ({imports_array}) => {
let key = 0;
return (
<div>
<div className={'ImportListHeader'}>
<Text type={'Heading1'} text={'Directory'} style={{minWidth: '33.33%', maxWidth: '33.33%'}}/>
<Text type={'Heading1'} text={'Skylink'} style={{minWidth: '33.33%', maxWidth: '33.33%'}}/>
<Text type={'Heading1'} text={'Token'} style={{minWidth: '33.33%', maxWidth: '33.33%'}}/>
</div>
<hr/>
<div className={'ImportListOwner'}>
{
imports_array.map(data => {
return (
<div key={'import_' + key++}>
<Import data={data}/>
</div>
);
})
}
</div>
</div>
);
};

View File

@@ -0,0 +1,31 @@
.SkynetImportTextArea {
display: block;
width: 100%;
margin: 0;
padding: 2px;
border-radius: var(--border_radius);
background: var(--control_background);
border: none;
box-shadow: none;
outline: none;
color: var(--text_color);
box-sizing: border-box;
resize: none;
overflow-y: scroll;
overflow: -moz-scrollbars-horizontal;
white-space: nowrap;
}
.SkynetImportButtons {
display: flex;
justify-content: space-between;
}
.SkynetActionButtons {
display: flex;
}
.SkynetImportHeading {
text-align: center;
padding-bottom: var(--default_spacing);
}

View File

@@ -0,0 +1,228 @@
import React from 'react'
import {connect} from 'react-redux';
import './SkynetImport.css'
import Box from '../../components/UI/Box/Box';
import Button from '../../components/UI/Button/Button';
import {displaySkynetImport} from '../../redux/actions/skynet_actions';
import ImportList from './ImportList/ImportList'
import IPCContainer from '../IPCContainer/IPCContainer';
import {notifyApplicationBusy} from '../../redux/actions/common_actions';
import {
notifyError,
notifyInfo
} from '../../redux/actions/error_actions';
const Constants = require('../../constants');
const mapStateToProps = state => {
return {
AppBusy: state.common.AppBusy,
};
};
const mapDispatchToProps = dispatch => {
return {
displaySkynetImport: display => dispatch(displaySkynetImport(display)),
notifyApplicationBusy: busy => dispatch(notifyApplicationBusy(busy, true)),
notifyError: msg => dispatch(notifyError(msg)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCContainer {
state = {
import_text: '',
imports_array: [],
second_stage: false,
};
componentDidMount() {
this.setRequestHandler(Constants.IPC_Import_Skylinks_Reply, this.onImportSkylinksReply);
}
componentWillUnmount() {
super.componentWillUnmount();
}
displaySyntax = () => {
const msg = '!alternate!To import Skylinks into the root directory, list each Skylink on a new line:\n' +
' AACeCiD6WQG6DzDcCdIu3cFPSxMUMoQPx46NYSyijNMKUA\n' +
' AACyjmDGoHqY7mTaxi-rkpnKUJGZK1B4UhrF74Nv6tY6Cg\n' +
'\n' +
'To import Skylinks into new or existing directories, use the following syntax:\n' +
' directory="/my/sub/dir",skylink="AACeCiD6WQG6DzDcCdIu3cFPSxMUMoQPx46NYSyijNMKUA"\n' +
' directory="/my/sub/dir2",skylink="AACyjmDGoHqY7mTaxi-rkpnKUJGZK1B4UhrF74Nv6tY6Cg"\n' +
'\n' +
'You can also specify a password if this in an encrypted Repertory Skylink:\n' +
' directory="/my/sub/dir",skylink="AACeCiD6WQG6DzDcCdIu3cFPSxMUMoQPx46NYSyijNMKUA",token="My Password"\n' +
' directory="/my/sub/dir",skylink="AACyjmDGoHqY7mTaxi-rkpnKUJGZK1B4UhrF74Nv6tY6Cg",token="My Password"\n' +
'\n' +
'To import Skylinks using JSON syntax:\n' +
' [\n' +
' {\n' +
' "directory": "/",\n' +
' "skylink": "AACeCiD6WQG6DzDcCdIu3cFPSxMUMoQPx46NYSyijNMKUA",\n' +
' "token": "My Password"\n' +
' },\n' +
' {\n' +
' "directory": "/my/sub/dir",\n' +
' "skylink": "AACyjmDGoHqY7mTaxi-rkpnKUJGZK1B4UhrF74Nv6tY6Cg",\n' +
' "token": "My Password"\n' +
' }\n' +
' ]';
this.props.notifyInfo('Import Syntax', msg)
}
handleNavigation = () => {
if (this.state.second_stage) {
try {
this.props.notifyApplicationBusy(true);
this.sendRequest(Constants.IPC_Import_Skylinks, {
Version: this.props.version,
JsonArray: this.state.imports_array,
});
} catch (e) {
this.props.notifyApplicationBusy(false);
this.props.notifyError(e);
}
} else {
const items = this.state.import_text.split('\n');
let importsArray = [];
try {
for (let item of items) {
item = item.trim();
if (item.startsWith('[')) {
importsArray = JSON.parse(this.state.import_text.trim());
break;
} else if (item.indexOf('=') >= 0) {
const parts = item.split(',')
let importItem = {
directory: '/',
skylink: '',
token: '',
};
for (let part of parts) {
part = part.trim();
const pair = part.split('=');
if (pair.length !== 2) {
throw new Error('Invalid syntax for import: directory="",skylink="",token=""');
}
importItem = {
...importItem,
[pair[0].trim()]: pair[1].trim().replace(/"/g, ''),
};
}
if (!importItem.skylink) {
throw new Error('Invalid syntax for import: directory="",skylink="",token=""');
}
importsArray.push(importItem);
} else if (item.length > 0) {
importsArray.push({
directory: '/',
skylink: item,
});
}
}
if (importsArray.length === 0) {
throw new Error('Nothing to import');
}
this.setState({
imports_array: importsArray,
second_stage: true,
});
} catch (e) {
this.props.notifyError(e);
}
}
}
onImportSkylinksReply = (_, arg) => {
this.props.notifyApplicationBusy(false);
if (arg.data.Success) {
const failedImportsArray = this.state.imports_array.filter(i => {
return arg.data.Result.failed.includes(i.skylink);
});
const count = this.state.imports_array.length;
this.setState({
import_text: failedImportsArray.length > 0 ? JSON.stringify(failedImportsArray, null, 2) : '',
imports_array: [],
second_stage: false,
}, () => {
if (failedImportsArray.length > 0) {
this.props.notifyError(`Failed to import ${failedImportsArray.length} item(s)`);
} else {
this.props.notifyInfo('Import Result', `Successfully imported ${count} item(s)`);
}
});
} else {
this.props.notifyError(arg.data.Error);
}
};
render() {
return this.props.AppBusy ? (<div/>) : (
<Box dxDark dxStyle={{
height: 'auto',
padding: 'var(--default_spacing)',
width: 'calc(100vw - (var(--default_spacing) * 4)'
}}>
<div
style={{
float: 'right',
margin: 0,
padding: 0,
marginTop: '-4px',
boxSizing: 'border-box',
display: 'block'
}}>
<a href={'#'}
onClick={() => this.props.displaySkynetImport(false)}
style={{cursor: 'pointer'}}>X</a>
</div>
<h1
className={'SkynetImportHeading'}>{this.state.second_stage ? 'Verify Imports' : 'Import List'}</h1>
{
this.state.second_stage ? (
<ImportList imports_array={this.state.imports_array}/>
) : (
<textarea autoFocus={true}
className={'SkynetImportTextArea'}
onChange={e => this.setState({
import_text: e.target.value,
})}
value={this.state.import_text}
rows={10}/>
)
}
<div className={'SkynetImportButtons'}>
<Button
buttonStyles={{height: 'auto', marginTop: 'var(--default_spacing)', width: 'auto'}}
clicked={this.displaySyntax}>Import Syntax...</Button>
<div className={'SkynetActionButtons'}>
{
this.state.second_stage ?
<Button buttonStyles={{
height: 'auto',
marginLeft: 'var(--default_spacing)',
marginTop: 'var(--default_spacing)',
width: 'auto'
}} clicked={() => this.setState({
second_stage: false,
})}>{'Back'}</Button> :
null
}
<Button buttonStyles={{
height: 'auto',
marginLeft: 'var(--default_spacing)',
marginTop: 'var(--default_spacing)',
width: 'auto'
}}
clicked={this.handleNavigation}>{this.state.second_stage ? 'Import' : 'Next'}</Button>
</div>
</div>
</Box>
);
}
});

View File

@@ -0,0 +1,34 @@
.PasswordOwner {
padding: 0;
width: auto;
height: auto;
display: flex;
justify-content: left;
align-content: center;
flex-direction: row;
}
input.PasswordInput {
display: block;
margin-right: var(--default_spacing);
padding: 2px;
border-radius: var(--border_radius);
background: var(--control_background);
border: none;
box-shadow: none;
outline: none;
color: var(--text_color);
box-sizing: border-box;
}
.PasswordShowHide {
padding-top: 2px;
cursor: pointer;
min-width: 20px;
}
.PasswordLink {
margin-right: var(--default_spacing);
cursor: pointer;
font-weight: normal;
}

View File

@@ -0,0 +1,135 @@
import React, {Component} from 'react';
import './Password.css';
import {faEye, faEyeSlash} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
export default class extends Component {
state = {
button_text: 'clear',
password: '',
password2: '',
show_password: false,
};
componentDidMount() {
if (!this.props.value || (this.props.value.length === 0)) {
this.setState({
...this.state,
button_text: 'set',
password: '',
password2: '',
show_password: false,
})
} else {
this.setState({
...this.state,
button_text: 'clear',
password: this.props.value,
password2: '',
show_password: false,
})
}
}
handleActionClick = () => {
if (!this.props.disabled) {
switch (this.state.button_text) {
case 'clear':
this.setState({
...this.state,
button_text: 'set',
password: '',
password2: '',
});
break;
case 'confirm':
if (this.state.password === this.state.password2) {
this.props.changed(this.state.password);
this.setState({
...this.state,
button_text: this.state.password && this.state.password.length > 0 ? 'clear' : 'set',
password2: '',
});
} else {
this.setState({
...this.state,
button_text: 'set',
password: '',
password2: '',
}, () => {
if (this.props.mismatchHandler) {
this.props.mismatchHandler();
}
});
}
break;
case 'set':
this.setState({
...this.state,
button_text: 'confirm',
password2: '',
});
break;
default:
return;
}
}
};
handlePasswordChanged = e => {
if (this.state.button_text === 'set') {
this.setState({
...this.state,
password: e.target.value,
});
} else if (this.state.button_text === 'confirm') {
this.setState({
...this.state,
password2: e.target.value,
});
}
};
handlePasswordKeyUp = e => {
if ((e.keyCode === 13) && ((this.state.button_text === 'confirm') || (this.state.button_text === 'set'))) {
this.handleActionClick();
}
};
handleShowHideClick = () => {
this.setState({
...this.state,
show_password: !this.state.show_password,
});
};
render() {
return (
<div className={'PasswordOwner'} style={{...this.props.style}}>
{
this.props.readOnly ? null : <a href={'#'}
className={'PasswordLink'}
onClick={this.handleActionClick}>
<u>{this.state.button_text}</u>
</a>
}
<input autoFocus={this.props.autoFocus}
readOnly={this.props.readOnly}
className={'PasswordInput'}
disabled={this.props.readOnly || (this.state.button_text === 'clear')}
onChange={this.handlePasswordChanged}
onKeyUp={this.handlePasswordKeyUp}
type={this.state.show_password ? 'text' : 'password'}
value={(this.state.button_text === 'confirm') ? this.state.password2 : this.state.password}/>
<a href={'#'}
className={'PasswordShowHide'}
onClick={this.handleShowHideClick}>
<FontAwesomeIcon icon={this.state.show_password ? faEye : faEyeSlash} fixedWidth/>
</a>
</div>
);
}
};

View File

@@ -8,6 +8,154 @@ const spawn = require('child_process').spawn;
const Constants = require('./constants'); const Constants = require('./constants');
const RandomString = require('randomstring'); const RandomString = require('randomstring');
let vcRuntimeExists;
const _vcRuntimeExists = () => {
return new Promise((resolve, reject) => {
if (os.platform() !== 'win32') {
reject('Windows OS is not being used');
} else {
if (vcRuntimeExists) {
resolve(true);
} else {
const cmd = path.join(process.env.windir, 'system32', 'reg.exe');
const args = [
'QUERY',
'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
];
_execProcessGetOutput(cmd, null, args)
.then(lines => {
const parseLine = index => {
if (index < lines.length) {
const line = lines[index];
if (line.startsWith('HKEY_LOCAL_MACHINE\\')) {
let args2 = JSON.parse(JSON.stringify(args));
args2[1] = 'HKLM\\' + line.substr(19);
args2.push('/v');
args2.push('DisplayName');
args2.push('/t');
args2.push('REG_SZ');
_execProcessGetOutput(cmd, null, args2)
.then(lines => {
const value = lines[2]
.trim()
.substr(args2[3].length)
.trim()
.substr(6)
.trim();
if (value.includes(
'Microsoft Visual C++ 2015-2019 Redistributable (x64)')) {
vcRuntimeExists = true;
resolve(true);
} else {
parseLine(++index);
}
})
.catch(() => { parseLine(++index); });
} else {
parseLine(++index);
}
} else {
resolve(false);
}
};
parseLine(0);
})
.catch(err => { reject(err); });
}
}
});
};
// https://stackoverflow.com/questions/19531453/transform-file-directory-structure-into-tree-in-javascript
const _createTreeNodes = fileList => {
let tree = {}
const directorySort = (a, b) => {
return !!a.directory === !!b.directory ? a.name.localeCompare(b.name)
: a.directory ? -1 : 1;
};
const addNode =
obj => {
let fullPath;
const idx = obj.skylink.indexOf('/');
if (idx > -1) {
fullPath = path.join(obj.directory, obj.skylink.substr(idx + 1))
.replace(/\\/g, '/');
} else {
fullPath = path.join(obj.directory, obj.filename).replace(/\\/g, '/');
}
const pathParts = fullPath.replace(/^\/|\/$/g, '').split('/');
let ptr = tree;
for (let i = 0; i < pathParts.length; i++) {
const node = {
directory : true,
name : pathParts[i],
};
if (i === pathParts.length - 1) {
node.directory = false;
node.path = fullPath;
}
ptr[pathParts[i]] = ptr[pathParts[i]] || node;
ptr[pathParts[i]].children = ptr[pathParts[i]].children || {};
ptr = ptr[pathParts[i]].children;
}
}
const objectToArray =
node => {
Object.keys(node || {}).map((k) => {
if (node[k].children) {
objectToArray(node[k])
}
})
if (node.children) {
node.children = Object.values(node.children);
node.children.forEach(objectToArray)
node.children = node.children.sort(directorySort);
}
}
fileList.map(addNode);
objectToArray(tree);
return Object.values(tree).sort(directorySort);
};
const _exportAllSkylinks = version => {
return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = {
cwd : repertoryExec.working,
detached : true,
shell : false,
windowsHide : true,
};
const args = _getDefaultRepertoryArgs('Skynet');
args.push('-ea');
let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); });
process.stdout.on('data', (d) => { result += d; });
process.stderr.on('data', (d) => { result += d; });
process.on('exit', code => {
if (code === 0) {
result = result.substr(result.indexOf('{'));
resolve(JSON.parse(result));
} else {
reject(new Error('Failed to import: ' + code + ':' + result));
}
});
process.unref();
});
};
const _executeProcess = (command, working, args = []) => { const _executeProcess = (command, working, args = []) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let processOptions = { let processOptions = {
@@ -21,13 +169,9 @@ const _executeProcess = (command, working, args=[]) => {
const process = new spawn(command, args, processOptions); const process = new spawn(command, args, processOptions);
const pid = process.pid; const pid = process.pid;
process.on('error', (err) => { process.on('error', (err) => { reject(err, pid); });
reject(err, pid);
});
process.on('exit', (code) => { process.on('exit', (code) => { resolve(code); });
resolve(code);
});
process.unref(); process.unref();
}); });
@@ -45,18 +189,12 @@ const _execProcessGetOutput = (cmd, working, args) => {
const proc = spawn(cmd, args, processOptions); const proc = spawn(cmd, args, processOptions);
let output; let output;
proc.stdout.on('data', data => { proc.stdout.on('data', data => { output += data.toString(); });
output += data.toString();
});
proc.on('error', (err) => { proc.on('error', (err) => { reject(err); });
reject(err);
});
proc.on('exit', () => { proc.on('exit', () => {
const lines = output const lines = output.replace(/\r\n/g, '\n').split('\n');
.replace(/\r\n/g, '\n')
.split('\n');
resolve(lines); resolve(lines);
}); });
@@ -64,17 +202,25 @@ const _execProcessGetOutput = (cmd, working, args) => {
}); });
}; };
const _getDataDirectory = () => { const _getDataDirectory =
return _resolvePath(Constants.DATA_LOCATIONS[os.platform()]); () => { return _resolvePath(Constants.DATA_LOCATIONS[os.platform()]); };
const _getRepertoryDirectory = () => {
return _resolvePath(Constants.REPERTORY_LOCATIONS[os.platform()]);
}; };
const _getDefaultRepertoryArgs = (provider, remote) => { const _getDefaultRepertoryArgs = (provider, remote, s3) => {
const providerLower = provider.toLowerCase(); const providerLower = provider.toLowerCase();
const args = []; const args = [];
if (remote) { if (s3) {
args.push('-s3');
args.push('-na');
args.push(provider.substr(2));
} else if (remote) {
args.push('-rm'); args.push('-rm');
args.push(provider.substr(6)); args.push(provider.substr(6));
} else if (Constants.PROVIDER_ARG[providerLower] && (Constants.PROVIDER_ARG[providerLower].length > 0)) { } else if (Constants.PROVIDER_ARG[providerLower] &&
(Constants.PROVIDER_ARG[providerLower].length > 0)) {
args.push(Constants.PROVIDER_ARG[providerLower]); args.push(Constants.PROVIDER_ARG[providerLower]);
} }
return args; return args;
@@ -89,9 +235,7 @@ const _getRepertoryExec = version => {
const _removeDirectoryRecursively = dir => { const _removeDirectoryRecursively = dir => {
if (fs.existsSync(dir)) { if (fs.existsSync(dir)) {
fs fs.readdirSync(dir).forEach(file => {
.readdirSync(dir)
.forEach(file => {
const curPath = path.join(dir, file); const curPath = path.join(dir, file);
if (fs.lstatSync(curPath).isDirectory()) { if (fs.lstatSync(curPath).isDirectory()) {
module.exports.removeDirectoryRecursively(curPath); module.exports.removeDirectoryRecursively(curPath);
@@ -105,9 +249,7 @@ const _removeDirectoryRecursively = dir => {
const _resolvePath = str => { const _resolvePath = str => {
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
return str.replace(/%([^%]+)%/g, (_, n) => { return str.replace(/%([^%]+)%/g, (_, n) => { return process.env[n]; });
return process.env[n];
});
} else { } else {
return str.replace('~', os.homedir()); return str.replace('~', os.homedir());
} }
@@ -131,17 +273,13 @@ module.exports.checkDaemonVersion = (version, provider) => {
windowsHide : true, windowsHide : true,
}; };
const args = _getDefaultRepertoryArgs(provider, false); const args = _getDefaultRepertoryArgs(provider);
args.push('-cv'); args.push('-cv');
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', err => { process.on('error', err => { reject(err); });
reject(err);
});
process.on('exit', code => { process.on('exit', code => { resolve(code); });
resolve(code);
});
process.unref(); process.unref();
}); });
}; };
@@ -151,13 +289,12 @@ module.exports.cleanupOldReleases = versionList => {
try { try {
if (versionList && versionList.length > 0) { if (versionList && versionList.length > 0) {
const dataDir = _getDataDirectory(); const dataDir = _getDataDirectory();
const directoryList = fs const directoryList = fs.readdirSync(dataDir, {withFileTypes : true})
.readdirSync(dataDir, {withFileTypes: true})
.filter(dirent => dirent.isDirectory()) .filter(dirent => dirent.isDirectory())
.map(dirent => dirent); .map(dirent => dirent);
const removeList = directoryList const removeList =
.filter(dirent => !versionList.includes(dirent.name)) directoryList.filter(dirent => !versionList.includes(dirent.name))
.map(dirent => dirent.name); .map(dirent => dirent.name);
for (const dir of removeList) { for (const dir of removeList) {
@@ -177,14 +314,10 @@ module.exports.cleanupOldReleases = versionList => {
}; };
module.exports.createSignatureFiles = (signature, publicKey) => { module.exports.createSignatureFiles = (signature, publicKey) => {
const fileName1 = RandomString.generate({ const fileName1 =
length: 12, RandomString.generate({length : 12, charset : 'alphabetic'});
charset: 'alphabetic' const fileName2 =
}); RandomString.generate({length : 12, charset : 'alphabetic'});
const fileName2 = RandomString.generate({
length: 12,
charset: 'alphabetic'
});
const signatureFile = path.join(os.tmpdir(), fileName1 + '.sig'); const signatureFile = path.join(os.tmpdir(), fileName1 + '.sig');
const publicKeyFile = path.join(os.tmpdir(), fileName2 + '.pub'); const publicKeyFile = path.join(os.tmpdir(), fileName2 + '.pub');
@@ -223,23 +356,31 @@ module.exports.detectRepertoryMounts = (version, providerList) => {
windowsHide : true, windowsHide : true,
}; };
const args = _getDefaultRepertoryArgs(provider, !Constants.PROVIDER_LIST.includes(provider)); const args = _getDefaultRepertoryArgs(
provider,
!Constants.PROVIDER_LIST.includes(provider) &&
provider.toLowerCase().startsWith('remote'),
!Constants.PROVIDER_LIST.includes(provider) &&
provider.toLowerCase().startsWith('s3'));
args.push('-status'); args.push('-status');
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { process.on('error', (err) => { reject(err); });
reject(err);
});
process.stdout.on('data', (d) => { process.stdout.on('data', (d) => { result += d; });
result += d;
});
process.on('exit', () => { process.on('exit', () => {
mountState[provider] = _tryParse(result, defaultData)[provider]; mountState[provider] =
_tryParse(result, defaultData)[provider] || defaultData;
if (mountState[provider].Active &&
((mountState[provider].Location === 'elevating') ||
(mountState[provider].Location === ''))) {
setTimeout(() => { grabStatus(index); }, 2000);
} else {
grabStatus(++index); grabStatus(++index);
}
}); });
process.unref(); process.unref();
} }
@@ -248,7 +389,8 @@ module.exports.detectRepertoryMounts = (version, providerList) => {
}); });
}; };
module.exports.downloadFile = (url, destination, progressCallback, completeCallback) => { module.exports.downloadFile = (url, destination, progressCallback,
completeCallback) => {
try { try {
if (fs.existsSync(destination)) { if (fs.existsSync(destination)) {
fs.unlinkSync(destination); fs.unlinkSync(destination);
@@ -266,7 +408,7 @@ module.exports.downloadFile = (url, destination, progressCallback, completeCallb
try { try {
const total = parseInt(response.headers['content-length'], 10); const total = parseInt(response.headers['content-length'], 10);
if (total === 0) { if (total === 0) {
completeCallback(Error('No data available for download')); completeCallback(new Error('No data available for download'));
} else { } else {
const stream = fs.createWriteStream(destination); const stream = fs.createWriteStream(destination);
@@ -282,28 +424,25 @@ module.exports.downloadFile = (url, destination, progressCallback, completeCallb
response.data.on('end', () => { response.data.on('end', () => {
stream.end(() => { stream.end(() => {
if (downloaded === 0) { if (downloaded === 0) {
completeCallback(Error('Received 0 bytes')); completeCallback(new Error('Received 0 bytes'));
} else if (downloaded !== total) { } else if (downloaded !== total) {
completeCallback(Error('Received incorrect number of bytes')); completeCallback(
new Error('Received incorrect number of bytes'));
} else { } else {
completeCallback(); completeCallback();
} }
}); });
}); });
response.data.on('error', error => { response.data.on(
stream.end(() => { 'error',
completeCallback(error); error => { stream.end(() => { completeCallback(error); }); });
});
});
} }
} catch (error) { } catch (error) {
completeCallback(error); completeCallback(error);
} }
}) })
.catch(error => { .catch(error => { completeCallback(error); });
completeCallback(error);
});
}; };
module.exports.executeAndWait = (command, ignoreResult) => { module.exports.executeAndWait = (command, ignoreResult) => {
@@ -313,9 +452,7 @@ module.exports.executeAndWait = (command, ignoreResult) => {
exec(command, error => { exec(command, error => {
if (error) { if (error) {
if (!ignoreResult && (error.code === 1)) { if (!ignoreResult && (error.code === 1)) {
setTimeout(() => { setTimeout(() => { retryExecute(count, error); }, 1000);
retryExecute(count, error);
}, 1000);
} else { } else {
reject(error); reject(error);
} }
@@ -335,7 +472,9 @@ module.exports.executeAsync = (command, args=[]) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const launchProcess = (count, timeout) => { const launchProcess = (count, timeout) => {
let cmd = path.basename(command); let cmd = path.basename(command);
const working = cmd.length === command.length ? null : command.substr(0, command.length - cmd.length); const working = cmd.length === command.length
? null
: command.substr(0, command.length - cmd.length);
let processOptions = { let processOptions = {
detached : true, detached : true,
shell : false, shell : false,
@@ -355,7 +494,9 @@ module.exports.executeAsync = (command, args=[]) => {
reject(err, pid); reject(err, pid);
} else { } else {
clearTimeout(timeout); clearTimeout(timeout);
setTimeout(()=> launchProcess(count, setTimeout(() => resolve(), 3000)), 1000); setTimeout(
() => launchProcess(count, setTimeout(() => resolve(), 3000)),
1000);
} }
}); });
@@ -365,7 +506,9 @@ module.exports.executeAsync = (command, args=[]) => {
reject(code, pid); reject(code, pid);
} else { } else {
clearTimeout(timeout); clearTimeout(timeout);
setTimeout(() => launchProcess(count, setTimeout(() => resolve(), 3000)), 1000); setTimeout(
() => launchProcess(count, setTimeout(() => resolve(), 3000)),
1000);
} }
} }
}); });
@@ -386,29 +529,22 @@ module.exports.executeScript = script => {
}; };
const command = '/bin/sh'; const command = '/bin/sh';
const args = [ const args = [ script ];
script
];
const process = new spawn(command, args, processOptions); const process = new spawn(command, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { process.on('error', (err) => { reject(err); });
reject(err);
});
process.stdout.on('data', (d)=> { process.stdout.on('data', (d) => { result += d; });
result += d;
});
process.on('exit', () => { process.on('exit', () => { resolve(result); });
resolve(result);
});
process.unref(); process.unref();
}); });
}; };
module.exports.executeMount = (version, provider, remote, location, exitCallback) => { module.exports.executeMount =
(version, provider, remote, s3, location, exitCallback) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
@@ -418,7 +554,7 @@ module.exports.executeMount = (version, provider, remote, location, exitCallback
stdio : 'ignore', stdio : 'ignore',
}; };
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote, s3);
if ((os.platform() === 'linux') || (os.platform() === 'darwin')) { if ((os.platform() === 'linux') || (os.platform() === 'darwin')) {
args.push('-o'); args.push('-o');
@@ -433,9 +569,7 @@ module.exports.executeMount = (version, provider, remote, location, exitCallback
let process = new spawn(repertoryExec.cmd, args, processOptions); let process = new spawn(repertoryExec.cmd, args, processOptions);
const pid = process.pid; const pid = process.pid;
const timeout = setTimeout(() => { const timeout = setTimeout(() => { resolve(pid); }, 3000);
resolve(pid);
}, 3000);
process.on('error', (err) => { process.on('error', (err) => {
clearTimeout(timeout); clearTimeout(timeout);
@@ -449,7 +583,9 @@ module.exports.executeMount = (version, provider, remote, location, exitCallback
}); });
}; };
module.exports.getConfig = (version, provider, remote) => { module.exports.exportAllSkylinks = _exportAllSkylinks;
module.exports.exportSkylinks = (version, paths) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
@@ -459,24 +595,54 @@ module.exports.getConfig = (version, provider, remote) => {
windowsHide : true, windowsHide : true,
}; };
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs('Skynet');
args.push('-ex');
args.push(paths.join(','));
let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); });
process.stdout.on('data', (d) => { result += d; });
process.stderr.on('data', (d) => { result += d; });
process.on('exit', code => {
if (code === 0) {
result = result.substr(result.indexOf('{'));
resolve(JSON.parse(result));
} else {
reject(new Error('Failed to import: ' + code + ':' + result));
}
});
process.unref();
});
};
module.exports.getConfig = (version, provider, remote, s3) => {
return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = {
cwd : repertoryExec.working,
detached : true,
shell : false,
windowsHide : true,
};
const args = _getDefaultRepertoryArgs(provider, remote, s3);
args.push('-dc'); args.push('-dc');
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { process.on('error', (err) => { reject(err); });
reject(err);
});
process.stdout.on('data', (d)=> { process.stdout.on('data', (d) => { result += d; });
result += d;
});
process.on('exit', () => { process.on('exit', () => {
const lines = result const lines = result.replace(/\r\n/g, '\n').split('\n');
.replace(/\r\n/g, '\n')
.split('\n');
const code = parseInt(lines[0], 10); const code = parseInt(lines[0], 10);
if (code === 0) { if (code === 0) {
@@ -493,7 +659,7 @@ module.exports.getConfig = (version, provider, remote) => {
}); });
}; };
module.exports.getConfigTemplate = (version, provider, remote) => { module.exports.getConfigTemplate = (version, provider, remote, s3) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
@@ -503,33 +669,29 @@ module.exports.getConfigTemplate = (version, provider, remote) => {
windowsHide : true, windowsHide : true,
}; };
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote, s3);
args.push('-gt'); args.push('-gt');
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { process.on('error', (err) => { reject(err); });
reject(err);
});
process.stdout.on('data', (d)=> { process.stdout.on('data', (d) => { result += d; });
result += d;
});
process.on('exit', () => { process.on('exit', () => { resolve(JSON.parse(result)); });
resolve(JSON.parse(result));
});
process.unref(); process.unref();
}); });
}; };
module.exports.getDataDirectory = _getDataDirectory; module.exports.getDataDirectory = _getDataDirectory;
module.exports.getRepertoryDirectory = _getRepertoryDirectory;
module.exports.getMissingDependencies = dependencies => { module.exports.getMissingDependencies = dependencies => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!dependencies) { if (!dependencies) {
reject(Error('Dependency list is invalid')); reject(new Error('Dependency list is invalid'));
} }
let missing = []; let missing = [];
@@ -544,8 +706,22 @@ module.exports.getMissingDependencies = dependencies => {
const Registry = require('winreg'); const Registry = require('winreg');
const checkRegistry = (dep, index) => { const checkRegistry = (dep, index) => {
if (index >= dep.registry.length) { if (index >= dep.registry.length) {
if (dep.display === 'VC Runtime 2015-2019') {
_vcRuntimeExists()
.then(exists => {
if (!exists) {
missing.push(dep);
}
resolveIfComplete();
})
.catch(() => {
missing.push(dep); missing.push(dep);
resolveIfComplete(); resolveIfComplete();
})
} else {
missing.push(dep);
resolveIfComplete();
}
} else { } else {
let hive = null; let hive = null;
const hiveName = dep.registry[index].split('\\')[0]; const hiveName = dep.registry[index].split('\\')[0];
@@ -566,14 +742,11 @@ module.exports.getMissingDependencies = dependencies => {
hive = Registry.HKU; hive = Registry.HKU;
break; break;
default: default:
throw Error('Invalid registry hive: ' + hiveName); throw new Error('Invalid registry hive: ' + hiveName);
} }
const key = dep.registry[index].substr(hiveName.length); const key = dep.registry[index].substr(hiveName.length);
const regKey = new Registry({ const regKey = new Registry({hive : hive, key : key});
hive: hive,
key: key
});
regKey.valueExists('DisplayName', (err, exists) => { regKey.valueExists('DisplayName', (err, exists) => {
if (err || !exists) { if (err || !exists) {
regKey.valueExists('ProductName', (err, exists) => { regKey.valueExists('ProductName', (err, exists) => {
@@ -596,7 +769,8 @@ module.exports.getMissingDependencies = dependencies => {
} else { } else {
for (const dep of dependencies) { for (const dep of dependencies) {
try { try {
if (!(fs.lstatSync(dep.file).isFile() || fs.lstatSync(dep.file).isSymbolicLink())) { if (!(fs.lstatSync(dep.file).isFile() ||
fs.lstatSync(dep.file).isSymbolicLink())) {
missing.push(dep); missing.push(dep);
} }
} catch (e) { } catch (e) {
@@ -608,8 +782,130 @@ module.exports.getMissingDependencies = dependencies => {
}); });
}; };
module.exports.grabSkynetFileTree = version => {
return new Promise((resolve, reject) => {
_exportAllSkylinks(version)
.then(results => {
resolve([ {
name : '/',
directory : true,
children : _createTreeNodes(results.success),
} ]);
})
.catch(e => { reject(e); });
});
};
module.exports.grabDirectoryItems = (path, version, provider, remote, s3) => {
return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = {
cwd : repertoryExec.working,
detached : true,
shell : false,
windowsHide : true,
};
const args = _getDefaultRepertoryArgs(provider, remote, s3);
args.push('-gdi');
args.push(path);
let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); });
process.stdout.on('data', (d) => { result += d; });
process.stderr.on('data', (d) => { result += d; });
process.on('exit', code => {
if (code === 0) {
result = result.substr(result.indexOf('{'));
resolve(JSON.parse(result));
} else {
reject(new Error('Failed to import: ' + code + ':' + result));
}
});
process.unref();
});
};
module.exports.setPinned = (path, pinned, version, provider, remote, s3) => {
return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = {
cwd : repertoryExec.working,
detached : true,
shell : false,
windowsHide : true,
};
const args = _getDefaultRepertoryArgs(provider, remote, s3);
args.push(pinned ? '-pf' : '-uf');
args.push(path);
let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); });
process.stdout.on('data', (d) => { result += d; });
process.stderr.on('data', (d) => { result += d; });
process.on('exit', code => {
if (code === 0) {
resolve(JSON.parse(result).success);
} else {
reject(new Error('Failed to import: ' + code + ':' + result));
}
});
process.unref();
});
};
module.exports.importSkylinks = (version, jsonArray) => {
return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version);
const processOptions = {
cwd : repertoryExec.working,
detached : true,
shell : false,
windowsHide : true,
};
const args = _getDefaultRepertoryArgs('Skynet');
args.push('-ij');
args.push(JSON.stringify(jsonArray));
let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); });
process.stdout.on('data', (d) => { result += d; });
process.stderr.on('data', (d) => { result += d; });
process.on('exit', code => {
if (code === 0) {
result = result.substr(result.indexOf('{'));
resolve(JSON.parse(result));
} else {
reject(new Error('Failed to import: ' + code + ':' + result));
}
});
process.unref();
});
};
// https://stackoverflow.com/questions/31645738/how-to-create-full-path-with-nodes-fs-mkdirsync // https://stackoverflow.com/questions/31645738/how-to-create-full-path-with-nodes-fs-mkdirsync
module.exports.mkDirByPathSync = (targetDir, { isRelativeToScript = false } = {}) => { module.exports.mkDirByPathSync = (targetDir,
{isRelativeToScript = false} = {}) => {
const sep = path.sep; const sep = path.sep;
const initDir = path.isAbsolute(targetDir) ? sep : ''; const initDir = path.isAbsolute(targetDir) ? sep : '';
const baseDir = isRelativeToScript ? __dirname : '.'; const baseDir = isRelativeToScript ? __dirname : '.';
@@ -623,8 +919,10 @@ module.exports.mkDirByPathSync = (targetDir, { isRelativeToScript = false } = {}
return curDir; return curDir;
} }
// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on Windows. // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on
if (err.code === 'ENOENT') { // Throw the original parentDir error on curDir `ENOENT` failure. // Windows.
if (err.code === 'ENOENT') { // Throw the original parentDir error on
// curDir `ENOENT` failure.
throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);
} }
@@ -644,7 +942,10 @@ module.exports.performWindowsUninstall = names => {
reject('Windows OS is not being used'); reject('Windows OS is not being used');
} else { } else {
const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const cmd = path.join(process.env.windir, 'system32', 'reg.exe');
const args = ["QUERY", "HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"]; const args = [
'QUERY',
'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
];
_execProcessGetOutput(cmd, null, args) _execProcessGetOutput(cmd, null, args)
.then(lines => { .then(lines => {
const parseLine = index => { const parseLine = index => {
@@ -659,28 +960,32 @@ module.exports.performWindowsUninstall = names => {
args2.push('REG_SZ'); args2.push('REG_SZ');
_execProcessGetOutput(cmd, null, args2) _execProcessGetOutput(cmd, null, args2)
.then(lines => { .then(lines => {
const value = lines[2].trim().substr(args2[3].length).trim().substr(6).trim(); const value = lines[2]
.trim()
.substr(args2[3].length)
.trim()
.substr(6)
.trim();
if (names.includes(value)) { if (names.includes(value)) {
const items = line.split('\\'); const items = line.split('\\');
const productCode = items[items.length - 1]; const productCode = items[items.length - 1];
_executeProcess('msiexec.exe', null,['/x', productCode, '/norestart']) _executeProcess('msiexec.exe', null,
[ '/x', productCode, '/norestart' ])
.then(code => { .then(code => {
if ((code === 0) || (code === 3010) || (code === 1641)) { if ((code === 0) || (code === 3010) ||
(code === 1641)) {
resolve(true); resolve(true);
} else { } else {
reject('[' + value + '] uninstall failed: ' + code); reject('[' + value +
'] uninstall failed: ' + code);
} }
}) })
.catch(err => { .catch(err => { reject(err); });
reject(err);
});
} else { } else {
parseLine(++index); parseLine(++index);
} }
}) })
.catch(() => { .catch(() => { parseLine(++index); });
parseLine(++index);
});
} else { } else {
parseLine(++index); parseLine(++index);
} }
@@ -690,9 +995,7 @@ module.exports.performWindowsUninstall = names => {
}; };
parseLine(0); parseLine(0);
}) })
.catch( err => { .catch(err => { reject(err); });
reject(err);
});
} }
}); });
}; };
@@ -701,7 +1004,8 @@ module.exports.removeDirectoryRecursively = _removeDirectoryRecursively;
module.exports.resolvePath = _resolvePath; module.exports.resolvePath = _resolvePath;
module.exports.setConfigValue = (name, value, provider, remote, version) => { module.exports.setConfigValue =
(name, value, provider, remote, s3, version) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
@@ -711,26 +1015,28 @@ module.exports.setConfigValue = (name, value, provider, remote, version) => {
windowsHide : true, windowsHide : true,
}; };
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote, s3);
args.push('-set'); args.push('-set');
args.push(name); args.push(name);
args.push(value); args.push(value);
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { process.on('error', (err) => { reject(err); });
reject(err);
});
process.on('exit', () => { process.on('exit', code => {
if (code !== 0) {
reject(new Error('Failed to set configuration value: ' + code));
} else {
resolve(); resolve();
}
}); });
process.unref(); process.unref();
}); });
}; };
module.exports.stopMountProcess = (version, provider, remote) => { module.exports.stopMountProcess = (version, provider, remote, s3) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
@@ -740,14 +1046,12 @@ module.exports.stopMountProcess = (version, provider, remote) => {
windowsHide : true, windowsHide : true,
}; };
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote, s3);
args.push('-unmount'); args.push('-unmount');
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
const pid = process.pid; const pid = process.pid;
process.on('error', (err) => { process.on('error', (err) => { reject(err); });
reject(err);
});
process.on('exit', (code) => { process.on('exit', (code) => {
resolve({ resolve({
PID : pid, PID : pid,
@@ -761,7 +1065,7 @@ module.exports.stopMountProcess = (version, provider, remote) => {
}); });
}; };
module.exports.stopMountProcessSync = (version, provider, remote) => { module.exports.stopMountProcessSync = (version, provider, remote, s3) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
cwd : repertoryExec.working, cwd : repertoryExec.working,
@@ -770,7 +1074,7 @@ module.exports.stopMountProcessSync = (version, provider, remote) => {
windowsHide : true, windowsHide : true,
}; };
const args = _getDefaultRepertoryArgs(provider, remote); const args = _getDefaultRepertoryArgs(provider, remote, s3);
args.push('-unmount'); args.push('-unmount');
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
@@ -785,12 +1089,10 @@ module.exports.testRepertoryBinary = version => {
if (code === 0) { if (code === 0) {
resolve(); resolve();
} else { } else {
reject(Error('Invalid exit code: ' + code)); reject(new Error('Invalid exit code: ' + code));
} }
}) })
.catch(error => { .catch(error => { reject(error); });
reject(error);
});
}); });
}; };
@@ -806,7 +1108,7 @@ module.exports.verifyHash = (file, hash) => {
command = 'sha256sum'; command = 'sha256sum';
args = [ '-b', file, '-z' ]; args = [ '-b', file, '-z' ];
} else { } else {
reject(Error('Platform not supported: ' + os.platform())) reject(new Error('Platform not supported: ' + os.platform()))
} }
if (command) { if (command) {
execFile(command, args, (err, stdout) => { execFile(command, args, (err, stdout) => {
@@ -817,7 +1119,7 @@ module.exports.verifyHash = (file, hash) => {
if (hash2 === hash.toLowerCase()) { if (hash2 === hash.toLowerCase()) {
resolve(hash2); resolve(hash2);
} else { } else {
reject(Error('Checksum failed for file')); reject(new Error('Checksum failed for file'));
} }
} }
}); });
@@ -828,7 +1130,12 @@ module.exports.verifyHash = (file, hash) => {
module.exports.verifySignature = (file, signatureFile, publicKeyFile) => { module.exports.verifySignature = (file, signatureFile, publicKeyFile) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const executeVerify = openssl => { const executeVerify = openssl => {
execFile(openssl, ['dgst', '-sha256', '-verify', publicKeyFile, '-signature', signatureFile, file], (err, stdout) => { execFile(openssl,
[
'dgst', '-sha256', '-verify', publicKeyFile, '-signature',
signatureFile, file
],
(err, stdout) => {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
@@ -841,7 +1148,8 @@ module.exports.verifySignature = (file, signatureFile, publicKeyFile) => {
const Registry = require('winreg'); const Registry = require('winreg');
const regKey = new Registry({ const regKey = new Registry({
hive : Registry.HKLM, hive : Registry.HKLM,
key: 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1' key :
'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1'
}); });
regKey.valueExists('InstallLocation', (err, exists) => { regKey.valueExists('InstallLocation', (err, exists) => {
if (err) { if (err) {
@@ -855,13 +1163,13 @@ module.exports.verifySignature = (file, signatureFile, publicKeyFile) => {
} }
}); });
} else { } else {
reject(Error('Failed to locate \'openssl.exe\'')); reject(new Error('Failed to locate \'openssl.exe\''));
} }
}); });
} else if (os.platform() === 'linux') { } else if (os.platform() === 'linux') {
executeVerify('openssl'); executeVerify('openssl');
} else { } else {
reject(Error('Platform not supported: ' + os.platform())) reject(new Error('Platform not supported: ' + os.platform()))
} }
}); });
}; };

View File

@@ -8,11 +8,11 @@
--control_transparent_background: rgba(10, 10, 16, 0.5); --control_transparent_background: rgba(10, 10, 16, 0.5);
--control_dark_transparent_background: rgba(10, 10, 16, 0.7); --control_dark_transparent_background: rgba(10, 10, 16, 0.7);
--text_color: rgba(200, 200, 240, 0.7); --text_color: rgba(200, 200, 240, 0.65);
--text_color_hover: rgba(200, 200, 225, 0.7); --text_color_hover: rgba(200, 200, 225, 0.65);
--text_color_error: rgba(203, 120, 120, 0.7); --text_color_error: rgba(203, 120, 120, 0.8);
--heading_text_color: rgba(132, 160, 230, 0.7); --heading_text_color: rgba(132, 160, 230, 0.8);
--heading_other_text_color: var(--heading_text_color); --heading_other_text_color: rgba(132, 160, 230, 0.65);
--text_color_transition: color 0.3s; --text_color_transition: color 0.3s;
--default_font_size: 14px; --default_font_size: 14px;
@@ -30,6 +30,9 @@
a { a {
outline: 0; outline: 0;
color: var(--text_color);
text-decoration: none;
font-weight: bold;
} }
html, body { html, body {
@@ -80,6 +83,7 @@ p {
.scrollable-content, ::-webkit-scrollbar { .scrollable-content, ::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px;
} }
.scrollable-content, ::-webkit-scrollbar * { .scrollable-content, ::-webkit-scrollbar * {
@@ -89,3 +93,7 @@ p {
.scrollable-content, ::-webkit-scrollbar-thumb { .scrollable-content, ::-webkit-scrollbar-thumb {
background: var(--control_background_hover) !important; background: var(--control_background_hover) !important;
} }
.scrollbar-corner, ::-webkit-scrollbar-corner {
background: transparent;
}

View File

@@ -1,14 +1,18 @@
import './index.css';
import 'react-checkbox-tree/lib/react-checkbox-tree.css';
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import createAppStore from './redux/store/createAppStore';
import {getIPCRenderer} from './utils';
import packageJson from '../package.json';
import {Provider} from 'react-redux'; import {Provider} from 'react-redux';
import {setActiveRelease} from './redux/actions/release_version_actions';
import packageJson from '../package.json';
import App from './App.jsx';
import {setProviderState} from './redux/actions/mount_actions'; import {setProviderState} from './redux/actions/mount_actions';
import {setActiveRelease} from './redux/actions/release_version_actions';
import createAppStore from './redux/store/createAppStore';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
import {getIPCRenderer} from './utils';
const Constants = require('./constants'); const Constants = require('./constants');
@@ -16,22 +20,22 @@ const ipcRenderer = getIPCRenderer();
let store; let store;
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.once(Constants.IPC_Get_Platform_Reply, (event, platformInfo) => { ipcRenderer.once(Constants.IPC_Get_Platform_Reply, (_, platformInfo) => {
if (platformInfo.Platform === 'linux') { if (platformInfo.Platform === 'linux') {
const root = document.documentElement; const root = document.documentElement;
root.style.setProperty('--default_font_size', '15.3px'); root.style.setProperty('--default_font_size', '15.3px');
} }
ipcRenderer.once(Constants.IPC_Get_State_Reply, (event, result) => { ipcRenderer.once(Constants.IPC_Get_State_Reply, (_, result) => {
if (result.data) { if (result.data) {
store = createAppStore(platformInfo, packageJson.version, result.data); store = createAppStore(platformInfo, packageJson.version, result.data);
store.dispatch(setActiveRelease(result.data.Release, result.data.Version));
const providerList = [ const providerList = [
...Constants.PROVIDER_LIST, ...Constants.PROVIDER_LIST,
...store.getState().mounts.RemoteMounts, ...store.getState().mounts.RemoteMounts,
...store.getState().mounts.S3Mounts,
]; ];
for (const provider of providerList) { for (const provider of providerList) {
let state = result.data[provider] || store.getState().mounts.ProviderState[provider]; const state = result.data[provider];
if (state.AutoMount === undefined) { if (state.AutoMount === undefined) {
state['AutoMount'] = false; state['AutoMount'] = false;
} }
@@ -40,6 +44,8 @@ if (ipcRenderer) {
} }
store.dispatch(setProviderState(provider, state)); store.dispatch(setProviderState(provider, state));
} }
store.dispatch(
setActiveRelease(result.data.Release, result.data.Version));
} else { } else {
store = createAppStore(platformInfo, packageJson.version, {}); store = createAppStore(platformInfo, packageJson.version, {});
} }
@@ -55,4 +61,3 @@ if (ipcRenderer) {
}); });
ipcRenderer.send(Constants.IPC_Get_Platform); ipcRenderer.send(Constants.IPC_Get_Platform);
} }

View File

@@ -53,6 +53,17 @@ const handleConfirmYesNo = (show, titleOrConfirmed, resolve) => {
}; };
}; };
export const NOTIFY_APPLICATION_BUSY = 'common/notifyApplicationBusy';
export const notifyApplicationBusy = (busy, transparent) => {
return {
type: NOTIFY_APPLICATION_BUSY,
payload: {
busy,
transparent
},
};
};
export const notifyRebootRequired = createAction('common/notifyRebootRequired'); export const notifyRebootRequired = createAction('common/notifyRebootRequired');
export const rebootSystem = () => { export const rebootSystem = () => {

View File

@@ -5,14 +5,16 @@ import {
getSelectedVersionFromState getSelectedVersionFromState
} from '../../utils'; } from '../../utils';
import {notifyError} from './error_actions'; import {notifyError} from './error_actions';
import {setAllowDownload} from './download_actions'; import {downloadItem, setAllowDownload} from './download_actions';
import { import {
loadReleases, loadReleases,
setActiveRelease, setActiveRelease,
setInstalledVersion, setInstalledVersion,
setReleaseUpgradeAvailable setReleaseUpgradeAvailable,
setNewReleasesAvailable2,
} from './release_version_actions'; } from './release_version_actions';
import { import {
confirmYesNo,
displaySelectAppPlatform, displaySelectAppPlatform,
setAllowMount, setAllowMount,
setApplicationReady, setApplicationReady,
@@ -21,6 +23,7 @@ import {
showWindow, showWindow,
shutdownApplication shutdownApplication
} from './common_actions'; } from './common_actions';
import {unmountAll} from './mount_actions';
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
@@ -32,13 +35,15 @@ export const checkInstalled = (dependencies, version) => {
const installedVersion = result.Success && result.Exists ? result.Version : 'none'; const installedVersion = result.Success && result.Exists ? result.Version : 'none';
const state = getState(); const state = getState();
const release = state.relver.Release;
let version = state.relver.Version;
let upgradeAvailable = false; let upgradeAvailable = false;
if (installedVersion !== 'none') { if (installedVersion !== 'none') {
const latestVersion = state.relver.VersionLookup[Constants.RELEASE_TYPES[state.relver.Release]].length - 1; const latestVersion = state.relver.VersionLookup[Constants.RELEASE_TYPES[release]].length - 1;
let version = state.relver.Version;
if (version === -1) { if (version === -1) {
version = latestVersion; version = latestVersion;
dispatch(setActiveRelease(state.relver.Release, version)); dispatch(setActiveRelease(release, version));
} else { } else {
upgradeAvailable = version !== latestVersion; upgradeAvailable = version !== latestVersion;
} }
@@ -48,8 +53,18 @@ export const checkInstalled = (dependencies, version) => {
dispatch(setMissingDependencies(result.Dependencies)); dispatch(setMissingDependencies(result.Dependencies));
dispatch(setAllowDownload(true)); dispatch(setAllowDownload(true));
dispatch(setAllowMount(true)); dispatch(setAllowMount(true));
const autoInstallRelease = getState().install.AutoInstallRelease;
dispatch(setAutoInstallRelease(false));
if (result.Dependencies && (result.Dependencies.length > 0)) { if (result.Dependencies && (result.Dependencies.length > 0)) {
dispatch(showWindow()); dispatch(showWindow());
} else if ((installedVersion === 'none') && autoInstallRelease) {
dispatch(setAllowMount(false));
const versionString = getState().relver.VersionLookup[Constants.RELEASE_TYPES[release]][version];
const urls = getState().relver.LocationsLookup[versionString].urls;
const fileName = versionString + '.zip';
dispatch(downloadItem(fileName, Constants.INSTALL_TYPES.Release, urls));
} }
}; };
@@ -140,7 +155,7 @@ export const installDependency = (source, url, isWinFSP) => {
export const installAndTestRelease = (source, version, appPlatform) => { export const installAndTestRelease = (source, version, appPlatform) => {
return (dispatch, getState) => { return (dispatch, getState) => {
if (ipcRenderer && getState().install.InstallTestActive) { if (ipcRenderer && getState().install.InstallTestActive) {
const extractReleaseComplete = (event, arg) => { const extractReleaseComplete = () => {
ipcRenderer.send(Constants.IPC_Delete_File, { ipcRenderer.send(Constants.IPC_Delete_File, {
FilePath: source, FilePath: source,
}); });
@@ -174,6 +189,31 @@ export const installAndTestRelease = (source, version, appPlatform) => {
}; };
}; };
export const installReleaseByVersion = (release, version) => {
return (dispatch, getState) => {
const install = () => {
if (getState().download.AllowDownload && !getState().download.DownloadActive) {
dispatch(setAutoInstallRelease(true));
dispatch(setActiveRelease(release, version));
} else {
notifyError('Download is active. Unable to install release.');
}
};
if (getState().mounts.MountsBusy) {
dispatch(confirmYesNo('Unmount all drives?'))
.then(confirmed => {
if (confirmed) {
dispatch(unmountAll(install));
}
})
.catch(error => notifyError(error));
} else {
install();
}
};
};
export const installRelease = source => { export const installRelease = source => {
return (dispatch, getState) => { return (dispatch, getState) => {
if (ipcRenderer && !getState().install.InstallActive) { if (ipcRenderer && !getState().install.InstallActive) {
@@ -185,6 +225,11 @@ export const installRelease = source => {
FilePath: source, FilePath: source,
}); });
if (arg.data.Success) {
localStorage.setItem('previous_releases', localStorage.getItem('releases'));
dispatch(setNewReleasesAvailable2([]));
}
dispatch(setInstallComplete(arg.data)); dispatch(setInstallComplete(arg.data));
dispatch(checkVersionInstalled()); dispatch(checkVersionInstalled());
}; };
@@ -231,6 +276,7 @@ export const installUpgrade = (source, sha256, signature, skipVerification) => {
}; };
}; };
export const setAutoInstallRelease = createAction('install/setAutoInstallRelease');
export const setDismissDependencies = createAction('install/setDismissDependencies'); export const setDismissDependencies = createAction('install/setDismissDependencies');
export const setInstallActive = createAction('install/setInstallActive'); export const setInstallActive = createAction('install/setInstallActive');
export const setInstallTestActive = createAction('install/setInstallTestActive'); export const setInstallTestActive = createAction('install/setInstallTestActive');

View File

@@ -1,10 +1,9 @@
import * as Constants from '../../constants';
import {createAction} from '@reduxjs/toolkit'; import {createAction} from '@reduxjs/toolkit';
import * as Constants from '../../constants';
import {getIPCRenderer} from '../../utils'; import {getIPCRenderer} from '../../utils';
import {
confirmYesNo, import {confirmYesNo, saveState} from './common_actions';
saveState
} from './common_actions';
import {notifyError} from './error_actions'; import {notifyError} from './error_actions';
export const addRemoteMount = (hostNameOrIp, port, token) => { export const addRemoteMount = (hostNameOrIp, port, token) => {
@@ -12,18 +11,20 @@ export const addRemoteMount = (hostNameOrIp, port, token) => {
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
const provider = 'Remote' + hostNameOrIp + ':' + port; const provider = 'Remote' + hostNameOrIp + ':' + port;
dispatch(addRemoteMount2(provider));
dispatch(setBusy(true)); dispatch(setBusy(true));
ipcRenderer.once(Constants.IPC_Set_Config_Values_Reply, (_, arg) => { ipcRenderer.once(Constants.IPC_Set_Config_Values_Reply, (_, arg) => {
if (arg.data.Success) { if (arg.data.Success) {
dispatch(addRemoteMount2(provider));
ipcRenderer.send(Constants.IPC_Detect_Mount, { ipcRenderer.send(Constants.IPC_Detect_Mount, {
Provider: provider, Provider: provider,
RemoteMounts: getState().mounts.RemoteMounts, RemoteMounts: getState().mounts.RemoteMounts,
S3Mounts: getState().mounts.S3Mounts,
Version: getState().relver.InstalledVersion, Version: getState().relver.InstalledVersion,
}); });
} else { } else {
dispatch(notifyError('Failed to set \'RemoteToken\': ' + arg.data.Error)); dispatch(
notifyError('Failed to set \'RemoteToken\': ' + arg.data.Error));
dispatch(setBusy(false)); dispatch(setBusy(false));
} }
}); });
@@ -42,112 +43,129 @@ export const addRemoteMount = (hostNameOrIp, port, token) => {
}; };
}; };
export const addS3Mount = (name, accessKey, secretKey, region, bucketName, url) => {
return (dispatch, getState) => {
const ipcRenderer = getIPCRenderer();
const provider = 'S3' + name;
dispatch(setBusy(true));
ipcRenderer.once(Constants.IPC_Set_Config_Values_Reply, (_, arg) => {
if (arg.data.Success) {
dispatch(addS3Mount2(provider));
ipcRenderer.send(Constants.IPC_Detect_Mount, {
Provider: provider,
RemoteMounts: getState().mounts.RemoteMounts,
S3Mounts: getState().mounts.S3Mounts,
Version: getState().relver.InstalledVersion,
});
} else {
dispatch(
notifyError('Failed to create S3 instance: ' + arg.data.Error));
dispatch(setBusy(false));
}
});
ipcRenderer.send(Constants.IPC_Set_Config_Values, {
Items: [
{Name: 'S3Config.AccessKey', Value: accessKey},
{Name: 'S3Config.SecretKey', Value: secretKey},
{Name: 'S3Config.Region', Value: region},
{Name: 'S3Config.BucketName', Value: bucketName},
{Name: 'S3Config.URL', Value: url},
],
Provider: provider,
S3: true,
Version: getState().relver.InstalledVersion,
});
};
};
export const addRemoteMount2 = createAction('mounts/addRemoteMount2'); export const addRemoteMount2 = createAction('mounts/addRemoteMount2');
export const addS3Mount2 = createAction('mounts/addS3Mount2');
export const DISPLAY_CONFIGURATION = 'mounts/displayConfiguration'; export const DISPLAY_CONFIGURATION = 'mounts/displayConfiguration';
export const displayConfiguration = (provider, remote) => { export const displayConfiguration = (provider, remote, s3) => {
return { return {
type: DISPLAY_CONFIGURATION, type: DISPLAY_CONFIGURATION,
payload: { payload: {
provider, provider,
remote, remote,
s3,
}, },
}; };
}; };
export const removeRemoteMount = provider => { export const removeMount = provider => {
return dispatch => { return dispatch => {
dispatch(confirmYesNo('Delete [' + provider.substr(6) + ']?')) const isRemote = provider.startsWith('Remote');
dispatch(confirmYesNo('Delete [' + provider.substr(isRemote ? 6 : 2) + ']?'))
.then(confirmed => { .then(confirmed => {
if (confirmed) { if (confirmed) {
dispatch(removeRemoteMount2(provider)); dispatch(removeMount2(provider));
} }
}); });
}; };
}; };
const removeRemoteMount2 = provider => { const removeMount2 = provider => {
return dispatch => { return dispatch => {
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
ipcRenderer.once(Constants.IPC_Remove_Remote_Mount_Reply, (_, arg) => { ipcRenderer.once(Constants.IPC_Remove_Mount_Reply, (_, arg) => {
if (arg.data.Success) { if (arg.data.Success) {
dispatch(removeRemoteMount3(provider)); dispatch(removeMount3(provider));
dispatch(saveState()); dispatch(saveState());
} }
}); });
ipcRenderer.send(Constants.IPC_Remove_Remote_Mount, provider.substr(6)); const isRemote = provider.startsWith('Remote');
ipcRenderer.send(Constants.IPC_Remove_Mount, {
Remote: isRemote,
Name: provider.substr(isRemote ? 6 : 2)
});
}; };
}; };
export const removeRemoteMount3 = createAction('mounts/removeRemoteMount3'); export const removeMount3 = createAction('mounts/removeMount3');
export const RESET_MOUNTS_STATE = 'mounts/resetMountsState'; export const RESET_MOUNTS_STATE = 'mounts/resetMountsState';
export const resetMountsState = () => { export const resetMountsState = () => {
return { return {type: RESET_MOUNTS_STATE, payload: null,}
type: RESET_MOUNTS_STATE,
payload: null,
}
}; };
export const SET_ALLOW_MOUNT = 'mounts/setAllowMount'; export const SET_ALLOW_MOUNT = 'mounts/setAllowMount';
export const setAllowMount = (provider, allow) => { export const setAllowMount =
return { (provider,
type: SET_ALLOW_MOUNT, allow) => {
payload: { return {type: SET_ALLOW_MOUNT, payload: {provider, allow}};
provider,
allow
}
};
}; };
export const SET_AUTO_MOUNT_PROCESSED = 'mounts/setAutoMountProcessed'; export const SET_AUTO_MOUNT_PROCESSED = 'mounts/setAutoMountProcessed';
export const setAutoMountProcessed = (provider, processed) => { export const setAutoMountProcessed = (provider, processed) => {
return { return {type: SET_AUTO_MOUNT_PROCESSED, payload: {provider, processed}};
type: SET_AUTO_MOUNT_PROCESSED,
payload: {
provider,
processed
}
};
}; };
export const setBusy = createAction('mounts/setBusy'); export const setBusy = createAction('mounts/setBusy');
export const SET_MOUNT_STATE = 'mounts/setMountState'; export const SET_MOUNT_STATE = 'mounts/setMountState';
export const setMountState = (provider, state) => { export const setMountState =
return { (provider,
type: SET_MOUNT_STATE, state) => {
payload: { return {type: SET_MOUNT_STATE, payload: {provider, state}};
provider,
state
}
};
}; };
export const SET_MOUNTED = 'mounts/setMounted'; export const SET_MOUNTED = 'mounts/setMounted';
export const setMounted = (provider, mounted) => { export const setMounted =
return { (provider,
type: SET_MOUNTED, mounted) => {
payload: { return {type: SET_MOUNTED, payload: {provider, mounted}};
provider,
mounted
}
};
}; };
export const SET_PROVIDER_STATE = 'mounts/setProviderState'; export const SET_PROVIDER_STATE = 'mounts/setProviderState';
export const setProviderState = (provider, state) => { export const setProviderState = (provider, state) => {
return { return {type: SET_PROVIDER_STATE, payload: {provider, state}}
type: SET_PROVIDER_STATE,
payload: {
provider,
state
}
}
}; };
export const unmountAll = completedCallback => { export const unmountAll = completedCallback => {
return (dispatch, getState) => { return dispatch => {
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
const unmountedCallback = () => { const unmountedCallback = () => {
dispatch(resetMountsState()); dispatch(resetMountsState());

View File

@@ -0,0 +1,4 @@
import {createAction} from '@reduxjs/toolkit';
export const displayPinnedManager = createAction('pinned/displayPinnedManager');

View File

@@ -13,7 +13,11 @@ import {
setDismissDependencies setDismissDependencies
} from './install_actions'; } from './install_actions';
import {unmountAll} from './mount_actions'; import {unmountAll} from './mount_actions';
import {getIPCRenderer} from '../../utils'; import {
checkNewReleases,
getIPCRenderer,
getNewReleases, getSelectedVersionFromState
} from '../../utils';
export const CLEAR_UI_UPGRADE = 'relver/clearUIUpgrade'; export const CLEAR_UI_UPGRADE = 'relver/clearUIUpgrade';
export const clearUIUpgrade = () => { export const clearUIUpgrade = () => {
@@ -67,13 +71,13 @@ export const loadReleases = () => {
const state = getState().relver; const state = getState().relver;
let release = state.Release; let release = state.Release;
if (release >= Constants.RELEASE_TYPES.length) { if (release >= Constants.RELEASE_TYPES.length) {
release = state.ReleaseDefault; release = Constants.DEFAULT_RELEASE;
} }
let latestVersion = versionLookup[Constants.RELEASE_TYPES[release]].length - 1; let latestVersion = versionLookup[Constants.RELEASE_TYPES[release]].length - 1;
let version = state.Version; let version = state.Version;
if (versionLookup[Constants.RELEASE_TYPES[release]][0] === 'unavailable') { if (versionLookup[Constants.RELEASE_TYPES[release]][0] === 'unavailable') {
release = state.ReleaseDefault; release = Constants.DEFAULT_RELEASE;
version = latestVersion = versionLookup[Constants.RELEASE_TYPES[release]].length - 1 version = latestVersion = versionLookup[Constants.RELEASE_TYPES[release]].length - 1
} else if ((version === -1) || !versionLookup[Constants.RELEASE_TYPES[release]][version]) { } else if ((version === -1) || !versionLookup[Constants.RELEASE_TYPES[release]][version]) {
version = latestVersion; version = latestVersion;
@@ -123,11 +127,26 @@ export const loadReleases = () => {
...response.data.Locations[appPlatform], ...response.data.Locations[appPlatform],
}; };
const storedReleases = localStorage.getItem('releases');
let newReleases = [];
if (storedReleases && (storedReleases.length > 0)) {
newReleases = getNewReleases(JSON.parse(storedReleases).VersionLookup, versionLookup, getSelectedVersionFromState(getState()));
}
localStorage.setItem('releases', JSON.stringify({ localStorage.setItem('releases', JSON.stringify({
LocationsLookup: locationsLookup, LocationsLookup: locationsLookup,
VersionLookup: versionLookup VersionLookup: versionLookup
})); }));
dispatchActions(locationsLookup, versionLookup); dispatchActions(locationsLookup, versionLookup);
dispatch(setNewReleasesAvailable(newReleases));
if (getState().relver.NewReleasesAvailable.length > 0) {
dispatch(setNewReleasesAvailable2(newReleases));
localStorage.setItem('previous_releases', storedReleases);
dispatch(showWindow());
} else if ((newReleases = checkNewReleases(getSelectedVersionFromState(getState()))).length > 0) {
dispatch(setNewReleasesAvailable2(newReleases));
}
}).catch(error => { }).catch(error => {
const releases = localStorage.getItem('releases'); const releases = localStorage.getItem('releases');
if (releases && (releases.length > 0)) { if (releases && (releases.length > 0)) {
@@ -160,7 +179,7 @@ export const setActiveRelease = (release, version) => {
const relver = getState().relver; const relver = getState().relver;
const common = getState().common; const common = getState().common;
if (release >= Constants.RELEASE_TYPES.length) { if (release >= Constants.RELEASE_TYPES.length) {
release = relver.ReleaseDefault; release = Constants.DEFAULT_RELEASE;
version = -1; version = -1;
} }
const versions = relver.VersionLookup[Constants.RELEASE_TYPES[release]]; const versions = relver.VersionLookup[Constants.RELEASE_TYPES[release]];
@@ -174,8 +193,11 @@ export const setActiveRelease = (release, version) => {
}; };
export const setAllowDismissDependencies = createAction('relver/setAllowDismissDependencies'); export const setAllowDismissDependencies = createAction('relver/setAllowDismissDependencies');
export const setDismissNewReleasesAvailable = createAction('relver/setDismissNewReleasesAvailable');
export const setDismissUIUpgrade = createAction('relver/setDismissUIUpgrade'); export const setDismissUIUpgrade = createAction('relver/setDismissUIUpgrade');
export const setInstalledVersion = createAction('relver/setInstalledVersion'); export const setInstalledVersion = createAction('relver/setInstalledVersion');
export const setNewReleasesAvailable = createAction('relver/setNewReleasesAvailable');
export const setNewReleasesAvailable2 = createAction('relver/setNewReleasesAvailable2');
export const SET_RELEASE_DATA = 'relver/setReleaseData'; export const SET_RELEASE_DATA = 'relver/setReleaseData';
export const setReleaseData = (locationsLookup, versionLookup)=> { export const setReleaseData = (locationsLookup, versionLookup)=> {

View File

@@ -0,0 +1,4 @@
import {createAction} from '@reduxjs/toolkit';
export const displaySkynetExport = createAction('skynet/displaySkynetExport');
export const displaySkynetImport = createAction('skynet/displaySkynetImport');

View File

@@ -1,6 +1,7 @@
import {createReducer} from '@reduxjs/toolkit'; import {createReducer} from '@reduxjs/toolkit';
import { import {
DISPLAY_CONFIRM_YES_NO, DISPLAY_CONFIRM_YES_NO,
NOTIFY_APPLICATION_BUSY,
notifyRebootRequired, notifyRebootRequired,
setAllowMount, setAllowMount,
setApplicationReady, setApplicationReady,
@@ -11,6 +12,8 @@ import {
export const createCommonReducer = (platformInfo, version) => { export const createCommonReducer = (platformInfo, version) => {
return createReducer({ return createReducer({
AllowMount: false, AllowMount: false,
AppBusy: false,
AppBusyTransparent: false,
AppPlatform: platformInfo.AppPlatform, AppPlatform: platformInfo.AppPlatform,
AppReady: false, AppReady: false,
DisplayConfirmYesNo: false, DisplayConfirmYesNo: false,
@@ -51,6 +54,13 @@ export const createCommonReducer = (platformInfo, version) => {
AppPlatform: action.payload, AppPlatform: action.payload,
} }
}, },
[NOTIFY_APPLICATION_BUSY]: (state, action) => {
return {
...state,
AppBusy: action.payload.busy,
AppBusyTransparent: action.payload.busy && action.payload.transparent,
};
},
[notifyRebootRequired]: (state, action) => { [notifyRebootRequired]: (state, action) => {
return { return {
...state, ...state,

View File

@@ -1,5 +1,6 @@
import {createReducer} from '@reduxjs/toolkit'; import {createReducer} from '@reduxjs/toolkit';
import { import {
setAutoInstallRelease,
setDismissDependencies, setDismissDependencies,
setInstallActive, setInstallActive,
setInstallComplete, setInstallComplete,
@@ -8,6 +9,7 @@ import {
} from '../actions/install_actions'; } from '../actions/install_actions';
export const installReducer = createReducer({ export const installReducer = createReducer({
AutoInstallRelease: false,
DismissDependencies: false, DismissDependencies: false,
InstallActive: false, InstallActive: false,
InstallResult: null, InstallResult: null,
@@ -15,6 +17,12 @@ export const installReducer = createReducer({
InstallType: null, InstallType: null,
MissingDependencies: [], MissingDependencies: [],
}, { }, {
[setAutoInstallRelease]: (state, action) => {
return {
...state,
AutoInstallRelease: action.payload,
}
},
[setDismissDependencies]: (state, action) => { [setDismissDependencies]: (state, action) => {
return { return {
...state, ...state,

View File

@@ -1,24 +1,28 @@
import * as Constants from '../../constants';
import {createReducer} from '@reduxjs/toolkit'; import {createReducer} from '@reduxjs/toolkit';
import * as Constants from '../../constants';
import { import {
addRemoteMount2, addRemoteMount2,
addS3Mount2,
DISPLAY_CONFIGURATION, DISPLAY_CONFIGURATION,
removeRemoteMount3, removeMount3,
RESET_MOUNTS_STATE, RESET_MOUNTS_STATE,
SET_ALLOW_MOUNT, SET_ALLOW_MOUNT,
SET_AUTO_MOUNT_PROCESSED, SET_AUTO_MOUNT_PROCESSED,
setBusy,
SET_MOUNT_STATE, SET_MOUNT_STATE,
SET_MOUNTED, SET_MOUNTED,
SET_PROVIDER_STATE SET_PROVIDER_STATE,
setBusy
} from '../actions/mount_actions'; } from '../actions/mount_actions';
export const createMountReducer = state => { export const createMountReducer = state => {
let providerList = [ let providerList = [
...Constants.PROVIDER_LIST, ...Constants.PROVIDER_LIST,
...(state.RemoteMounts || []), ...(state.RemoteMounts || []),
...(state.S3Mounts || []),
]; ];
const providerState = providerList.map(provider=> { const providerState = providerList
.map(provider => {
return { return {
[provider]: { [provider]: {
AutoMount: false, AutoMount: false,
@@ -26,14 +30,13 @@ export const createMountReducer = state => {
MountLocation: '', MountLocation: '',
} }
} }
}).reduce((map, obj) => { })
return { .reduce((map, obj) => {
...map, return {...map, ...obj}
...obj
}
}); });
const mountState = providerList.map(provider => { const mountState = providerList
.map(provider => {
return { return {
[provider]: { [provider]: {
AllowMount: false, AllowMount: false,
@@ -41,33 +44,32 @@ export const createMountReducer = state => {
Mounted: false, Mounted: false,
} }
} }
}).reduce((map, obj) => { })
return { .reduce((map, obj) => {
...map, return {...map, ...obj}
...obj
}
}); });
const autoMountProcessed = providerList.map(provider => { const autoMountProcessed =
return { providerList.map(provider => {
[provider]: false, return {[provider]: false,}
} })
}).reduce((map, obj) => { .reduce((map, obj) => {
return { return {...map, ...obj}
...map,
...obj
}
}); });
return createReducer({ return createReducer(
{
AutoMountProcessed: autoMountProcessed, AutoMountProcessed: autoMountProcessed,
DisplayConfiguration: null, DisplayConfiguration: null,
DisplayRemoteConfiguration: false, DisplayRemoteConfiguration: false,
DisplayS3Configuration: false,
MountsBusy: false, MountsBusy: false,
MountState: mountState, MountState: mountState,
ProviderState: providerState, ProviderState: providerState,
RemoteMounts: state.RemoteMounts ? state.RemoteMounts : [], RemoteMounts: state.RemoteMounts ? state.RemoteMounts : [],
}, { S3Mounts: state.S3Mounts ? state.S3Mounts : [],
},
{
[addRemoteMount2]: (state, action) => { [addRemoteMount2]: (state, action) => {
let mountState = {...state.MountState}; let mountState = {...state.MountState};
mountState[action.payload] = { mountState[action.payload] = {
@@ -87,21 +89,44 @@ export const createMountReducer = state => {
autoMountProcessed[action.payload] = true; autoMountProcessed[action.payload] = true;
return { return {
...state, ...state, AutoMountProcessed: autoMountProcessed,
AutoMountProcessed: autoMountProcessed, MountState: mountState, ProviderState: providerState,
MountState: mountState,
ProviderState: providerState,
RemoteMounts: [...state.RemoteMounts, action.payload], RemoteMounts: [...state.RemoteMounts, action.payload],
} }
}, },
[addS3Mount2]: (state, action) => {
let mountState = {...state.MountState};
mountState[action.payload] = {
AllowMount: false,
DriveLetters: [],
Mounted: false,
};
let providerState = {...state.ProviderState};
providerState[action.payload] = {
AutoMount: false,
AutoRestart: false,
MountLocation: '',
};
let autoMountProcessed = {...state.AutoMountProcessed};
autoMountProcessed[action.payload] = true;
return {
...state, AutoMountProcessed: autoMountProcessed,
MountState: mountState, ProviderState: providerState,
S3Mounts: [...state.S3Mounts, action.payload],
}
},
[DISPLAY_CONFIGURATION]: (state, action) => { [DISPLAY_CONFIGURATION]: (state, action) => {
return { return {
...state, ...state,
DisplayConfiguration: action.payload.provider, DisplayConfiguration: action.payload.provider,
DisplayRemoteConfiguration: action.payload.remote, DisplayRemoteConfiguration: action.payload.remote,
DisplayS3Configuration: action.payload.s3,
}; };
}, },
[removeRemoteMount3]: (state, action) => { [removeMount3]: (state, action) => {
let mountState = {...state.MountState}; let mountState = {...state.MountState};
delete mountState[action.payload]; delete mountState[action.payload];
@@ -111,21 +136,21 @@ export const createMountReducer = state => {
let autoMountProcessed = {...state.AutoMountProcessed}; let autoMountProcessed = {...state.AutoMountProcessed};
delete autoMountProcessed[action.payload]; delete autoMountProcessed[action.payload];
const remoteMounts = state.RemoteMounts.filter(i => i !== action.payload); const remoteMounts =
state.RemoteMounts.filter(i => i !== action.payload);
const s3Mounts =
state.S3Mounts.filter(i => i !== action.payload);
return { return {
...state, ...state,
AutoMountProcessed: autoMountProcessed, AutoMountProcessed: autoMountProcessed,
MountState: mountState, MountState: mountState,
ProviderState: providerState, ProviderState: providerState,
RemoteMounts: remoteMounts, RemoteMounts: remoteMounts,
S3Mounts: s3Mounts,
}; };
}, },
[RESET_MOUNTS_STATE]: (state, action) => { [RESET_MOUNTS_STATE]: (state, action) => {
return { return {...state, MountsBusy: false, MountState: mountState,}
...state,
MountsBusy: false,
MountState: mountState,
}
}, },
[SET_AUTO_MOUNT_PROCESSED]: (state, action) => { [SET_AUTO_MOUNT_PROCESSED]: (state, action) => {
return { return {
@@ -148,11 +173,10 @@ export const createMountReducer = state => {
} }
}; };
}, },
[setBusy]: (state, action) => { [setBusy]:
return { (state,
...state, action) => {
MountsBusy: action.payload return {...state, MountsBusy: action.payload};
};
}, },
[SET_MOUNT_STATE]: (state, action) => { [SET_MOUNT_STATE]: (state, action) => {
return { return {

View File

@@ -0,0 +1,13 @@
import {createReducer} from '@reduxjs/toolkit';
import {displayPinnedManager} from '../actions/pinned_manager_actions';
export const pinnedManagerReducer = createReducer({
DisplayPinnedManager: false,
}, {
[displayPinnedManager]: (state, action) => {
return {
...state,
DisplayPinnedManager: action.payload,
};
},
});

View File

@@ -15,10 +15,12 @@ const versionLookup = Constants.RELEASE_TYPES.map(k=> {
export const releaseVersionReducer = createReducer({ export const releaseVersionReducer = createReducer({
AllowDismissDependencies: false, AllowDismissDependencies: false,
DismissNewReleasesAvailable: true,
InstalledVersion: 'none', InstalledVersion: 'none',
LocationsLookup: {}, LocationsLookup: {},
Release: 0, NewReleasesAvailable: [],
ReleaseDefault: 0, NewReleasesAvailable2: [],
Release: Constants.DEFAULT_RELEASE,
ReleaseUpgradeAvailable: false, ReleaseUpgradeAvailable: false,
UpgradeAvailable: false, UpgradeAvailable: false,
UpgradeData: null, UpgradeData: null,
@@ -49,6 +51,12 @@ export const releaseVersionReducer = createReducer({
AllowDismissDependencies: action.payload, AllowDismissDependencies: action.payload,
}; };
}, },
[Actions.setDismissNewReleasesAvailable]: (state, action) => {
return {
...state,
DismissNewReleasesAvailable: action.payload,
};
},
[Actions.setDismissUIUpgrade]: (state, action) => { [Actions.setDismissUIUpgrade]: (state, action) => {
return { return {
...state, ...state,
@@ -61,6 +69,19 @@ export const releaseVersionReducer = createReducer({
InstalledVersion: action.payload, InstalledVersion: action.payload,
} }
}, },
[Actions.setNewReleasesAvailable]: (state, action) => {
return {
...state,
DismissNewReleasesAvailable: false,
NewReleasesAvailable: action.payload,
};
},
[Actions.setNewReleasesAvailable2]: (state, action) => {
return {
...state,
NewReleasesAvailable2: action.payload,
};
},
[Actions.SET_RELEASE_DATA]: (state, action) => { [Actions.SET_RELEASE_DATA]: (state, action) => {
return { return {
...state, ...state,

View File

@@ -0,0 +1,20 @@
import {createReducer} from '@reduxjs/toolkit';
import * as Actions from '../actions/skynet_actions';
export const skynetReducer = createReducer({
DisplayExport: false,
DisplayImport: false,
}, {
[Actions.displaySkynetExport]: (state, action) => {
return {
...state,
DisplayExport: action.payload,
}
},
[Actions.displaySkynetImport]: (state, action) => {
return {
...state,
DisplayImport: action.payload,
}
},
});

View File

@@ -5,6 +5,8 @@ import {errorReducer} from '../reducers/error_reducer';
import {installReducer} from '../reducers/install_reducer'; import {installReducer} from '../reducers/install_reducer';
import {createMountReducer} from '../reducers/mount_reducer'; import {createMountReducer} from '../reducers/mount_reducer';
import {releaseVersionReducer} from '../reducers/release_version_reducer'; import {releaseVersionReducer} from '../reducers/release_version_reducer';
import {skynetReducer} from '../reducers/skynet_reducer';
import {pinnedManagerReducer} from '../reducers/pinned_manager_reducer'
export default function createAppStore(platformInfo, version, state) { export default function createAppStore(platformInfo, version, state) {
const reducer = { const reducer = {
@@ -14,6 +16,8 @@ export default function createAppStore(platformInfo, version, state) {
install: installReducer, install: installReducer,
mounts: createMountReducer(state), mounts: createMountReducer(state),
relver: releaseVersionReducer, relver: releaseVersionReducer,
skynet: skynetReducer,
pinned: pinnedManagerReducer,
}; };
const middleware = [...getDefaultMiddleware()]; const middleware = [...getDefaultMiddleware()];

View File

@@ -1,6 +1,6 @@
const Constants = require('../../constants'); const Constants = require('../../constants');
const addListeners = (ipcMain, closeApplication, setWindowVisibility) => { const addListeners = (ipcMain, {closeApplication, setWindowVisibility}) => {
ipcMain.on(Constants.IPC_Shutdown, () => { ipcMain.on(Constants.IPC_Shutdown, () => {
closeApplication(); closeApplication();
}); });

View File

@@ -1,10 +1,10 @@
const Constants = require('../../constants'); const Constants = require('../../constants');
const helpers = require('../../helpers'); const helpers = require('../../helpers');
const addListeners = (ipcMain, standardIPCReply) => { const addListeners = (ipcMain, {standardIPCReply}) => {
ipcMain.on(Constants.IPC_Get_Config, (event, data) => { ipcMain.on(Constants.IPC_Get_Config, (event, data) => {
helpers helpers
.getConfig(data.Version, data.Provider, data.Remote) .getConfig(data.Version, data.Provider, data.Remote, data.S3)
.then((data) => { .then((data) => {
if (data.Code === 0) { if (data.Code === 0) {
standardIPCReply(event, Constants.IPC_Get_Config_Reply, { standardIPCReply(event, Constants.IPC_Get_Config_Reply, {
@@ -21,7 +21,7 @@ const addListeners = (ipcMain, standardIPCReply) => {
ipcMain.on(Constants.IPC_Get_Config_Template, (event, data) => { ipcMain.on(Constants.IPC_Get_Config_Template, (event, data) => {
helpers helpers
.getConfigTemplate(data.Version, data.Provider, data.Remote) .getConfigTemplate(data.Version, data.Provider, data.Remote, data.S3)
.then((data) => { .then((data) => {
standardIPCReply(event, Constants.IPC_Get_Config_Template_Reply, { standardIPCReply(event, Constants.IPC_Get_Config_Template_Reply, {
Template: data, Template: data,
@@ -36,12 +36,12 @@ const addListeners = (ipcMain, standardIPCReply) => {
const setConfigValue = (i) => { const setConfigValue = (i) => {
if (i < data.Items.length) { if (i < data.Items.length) {
helpers helpers
.setConfigValue(data.Items[i].Name, data.Items[i].Value, data.Provider, data.Remote, data.Version) .setConfigValue(data.Items[i].Name, data.Items[i].Value, data.Provider, data.Remote, data.S3, data.Version)
.then(() => { .then(() => {
setConfigValue(++i); setConfigValue(++i);
}) })
.catch(() => { .catch(error => {
setConfigValue(++i); standardIPCReply(event, Constants.IPC_Set_Config_Values_Reply, {}, error);
}); });
} else { } else {
standardIPCReply(event, Constants.IPC_Set_Config_Values_Reply, {}); standardIPCReply(event, Constants.IPC_Set_Config_Values_Reply, {});

View File

@@ -1,7 +1,7 @@
const Constants = require('../../constants'); const Constants = require('../../constants');
const helpers = require('../../helpers'); const helpers = require('../../helpers');
const addListeners = (ipcMain, standardIPCReply) => { const addListeners = (ipcMain, {standardIPCReply}) => {
ipcMain.on(Constants.IPC_Check_Daemon_Version, (event, data) => { ipcMain.on(Constants.IPC_Check_Daemon_Version, (event, data) => {
helpers helpers
.checkDaemonVersion(data.Version, data.Provider) .checkDaemonVersion(data.Version, data.Provider)

View File

@@ -2,7 +2,7 @@ const Constants = require('../../constants');
const fs = require('fs'); const fs = require('fs');
const helpers = require('../../helpers'); const helpers = require('../../helpers');
const addListeners = (ipcMain, standardIPCReply) => { const addListeners = (ipcMain, {standardIPCReply}) => {
ipcMain.on(Constants.IPC_Check_Dependency_Installed, (event, data) => { ipcMain.on(Constants.IPC_Check_Dependency_Installed, (event, data) => {
try { try {
const exists = fs.lstatSync(data.File).isFile(); const exists = fs.lstatSync(data.File).isFile();
@@ -72,15 +72,7 @@ const addListeners = (ipcMain, standardIPCReply) => {
}; };
if (data.IsWinFSP) { if (data.IsWinFSP) {
helpers helpers
.performWindowsUninstall([ .performWindowsUninstall(Constants.WINFSP_VERSION_NAMES)
"WinFsp 2019.1",
"WinFsp 2019.2",
"WinFsp 2019.3 B1",
"WinFsp 2019.3 B2",
"WinFsp 2019.3 B3",
"WinFsp 2019.3 B4",
"WinFsp 2019.3 B5"
])
.then(uninstalled => { .then(uninstalled => {
if (uninstalled) { if (uninstalled) {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, { standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {

View File

@@ -2,7 +2,7 @@ const Constants = require('../../constants');
const helpers = require('../../helpers'); const helpers = require('../../helpers');
const path = require('path'); const path = require('path');
const addListeners = (ipcMain, standardIPCReply) => { const addListeners = (ipcMain, {standardIPCReply}) => {
ipcMain.on(Constants.IPC_Download_File, (event, data) => { ipcMain.on(Constants.IPC_Download_File, (event, data) => {
const destination = path.join(helpers.getDataDirectory(), data.Filename); const destination = path.join(helpers.getDataDirectory(), data.Filename);
helpers.downloadFile(data.URL, destination, (progress) => { helpers.downloadFile(data.URL, destination, (progress) => {

View File

@@ -1,7 +1,7 @@
const Constants = require('../../constants'); const Constants = require('../../constants');
const fs = require('fs'); const fs = require('fs');
const addListeners = (ipcMain, getMainWindow, dialog) => { const addListeners = (ipcMain, {getMainWindow, dialog}) => {
ipcMain.on(Constants.IPC_Browse_Directory + '_sync', (event, data) => { ipcMain.on(Constants.IPC_Browse_Directory + '_sync', (event, data) => {
dialog.showOpenDialog(getMainWindow(), { dialog.showOpenDialog(getMainWindow(), {
defaultPath: data.Location, defaultPath: data.Location,

View File

@@ -53,16 +53,16 @@ const unmountAllDrives = () => {
} }
// Unmount all items // Unmount all items
for (const i in mountedLocations) { for (const mountLocation of mountedLocations) {
const data = mountedData[mountedLocations[i]]; const data = mountedData[mountLocation];
helpers.stopMountProcessSync(data.Version, data.Provider, data.Remote); helpers.stopMountProcessSync(data.Version, data.Provider, data.Remote, data.S3);
} }
mountedLocations = []; mountedLocations = [];
mountedData = {}; mountedData = {};
}; };
const addListeners = (ipcMain, setTrayImage, standardIPCReply) => { const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
ipcMain.on(Constants.IPC_Check_Mount_Location + '_sync', (event, data) => { ipcMain.on(Constants.IPC_Check_Mount_Location + '_sync', (event, data) => {
let response = { let response = {
Success: true, Success: true,
@@ -93,6 +93,7 @@ const addListeners = (ipcMain, setTrayImage, standardIPCReply) => {
const providerList = [ const providerList = [
...Constants.PROVIDER_LIST, ...Constants.PROVIDER_LIST,
...data.RemoteMounts, ...data.RemoteMounts,
...data.S3Mounts,
]; ];
for (const provider of providerList) { for (const provider of providerList) {
driveLetters[provider] = []; driveLetters[provider] = [];
@@ -134,11 +135,11 @@ const addListeners = (ipcMain, setTrayImage, standardIPCReply) => {
} }
}; };
const setImage = (locations) => { const setImage = locations => {
let driveInUse; let driveInUse;
if (Object.keys(locations).length > 0) { if (Object.keys(locations).length > 0) {
for (const provider of providerList) { for (const provider of providerList) {
driveInUse = locations[provider].length > 0; driveInUse = locations[provider] && locations[provider].length > 0;
if (driveInUse) if (driveInUse)
break; break;
} }
@@ -206,6 +207,7 @@ const addListeners = (ipcMain, setTrayImage, standardIPCReply) => {
mountedData[data.Location] = { mountedData[data.Location] = {
Provider: data.Provider, Provider: data.Provider,
Remote: data.Remote, Remote: data.Remote,
S3: data.S3,
Version: data.Version, Version: data.Version,
}; };
const errorHandler = (pid, error) => { const errorHandler = (pid, error) => {
@@ -219,16 +221,18 @@ const addListeners = (ipcMain, setTrayImage, standardIPCReply) => {
Location: data.Location, Location: data.Location,
Provider: data.Provider, Provider: data.Provider,
Remote: data.Remote, Remote: data.Remote,
S3: data.S3,
}, error || Error(data.Provider + ' Unmounted')); }, error || Error(data.Provider + ' Unmounted'));
}; };
helpers helpers
.executeMount(data.Version, data.Provider, data.Remote, data.Location, (error, pid) => { .executeMount(data.Version, data.Provider, data.Remote, data.S3, data.Location, (error, pid) => {
errorHandler(pid, error); errorHandler(pid, error);
}) })
.then(() => { .then(() => {
standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, { standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, {
Provider: data.Provider, Provider: data.Provider,
Remote: data.Remote, Remote: data.Remote,
S3: data.S3,
}); });
}) })
.catch(error => { .catch(error => {
@@ -237,19 +241,21 @@ const addListeners = (ipcMain, setTrayImage, standardIPCReply) => {
} }
}); });
ipcMain.on(Constants.IPC_Remove_Remote_Mount, (event, data) => { ipcMain.on(Constants.IPC_Remove_Mount, (event, data) => {
data = data.replace(':', '_'); if (data.Remote) {
const dataDirectory = path.resolve(path.join(helpers.getDataDirectory(), '..', 'remote', data)); data.Name = data.Name.replace(':', '_');
}
const dataDirectory = path.resolve(path.join(helpers.getDataDirectory(), '..', data.Remote ? 'remote' : 's3', data.Name));
try { try {
helpers.removeDirectoryRecursively(dataDirectory); helpers.removeDirectoryRecursively(dataDirectory);
standardIPCReply(event, Constants.IPC_Remove_Remote_Mount_Reply, {DataDirectory: dataDirectory}); standardIPCReply(event, Constants.IPC_Remove_Mount_Reply, {DataDirectory: dataDirectory});
} catch (e) { } catch (e) {
standardIPCReply(event, Constants.IPC_Remove_Remote_Mount_Reply, {DataDirectory: dataDirectory}, e); standardIPCReply(event, Constants.IPC_Remove_Mount_Reply, {DataDirectory: dataDirectory}, e);
} }
}); });
ipcMain.on(Constants.IPC_Unmount_All_Drives, (event, data) => { ipcMain.on(Constants.IPC_Unmount_All_Drives, event => {
unmountAllDrives(); unmountAllDrives();
standardIPCReply(event, Constants.IPC_Unmount_All_Drives_Reply); standardIPCReply(event, Constants.IPC_Unmount_All_Drives_Reply);
}); });
@@ -259,7 +265,7 @@ const addListeners = (ipcMain, setTrayImage, standardIPCReply) => {
expectedUnmount[data.Provider] = true; expectedUnmount[data.Provider] = true;
helpers helpers
.stopMountProcess(data.Version, data.Provider, data.Remote) .stopMountProcess(data.Version, data.Provider, data.Remote, data.S3)
.then(result => { .then(result => {
console.log(result); console.log(result);
}) })

View File

@@ -0,0 +1,49 @@
const Constants = require('../../constants');
const helpers = require('../../helpers');
const addListeners = (ipcMain, {standardIPCReply}) => {
ipcMain.on(Constants.IPC_Get_Directory_Items, (event, data) => {
helpers
.grabDirectoryItems(data.Path, data.Version, data.Provider, data.Remote, data.S3)
.then(data => {
standardIPCReply(event, Constants.IPC_Get_Directory_Items_Reply, {
Items: data.items,
});
})
.catch(e => {
standardIPCReply(event, Constants.IPC_Get_Directory_Items_Reply, {}, e);
});
});
ipcMain.on(Constants.IPC_Get_Pinned_Files, (event, data) => {
helpers
.grabDirectoryItems(data.Path, data.Version, data.Provider, data.Remote, data.S3)
.then(data => {
standardIPCReply(event, Constants.IPC_Get_Directory_Items_Reply, {
Items: data.items,
});
})
.catch(e => {
standardIPCReply(event, Constants.IPC_Get_Directory_Items_Reply, {}, e);
});
});
ipcMain.on(Constants.IPC_Get_Pinned_Files_Status, (event, data) => {
});
ipcMain.on(Constants.IPC_Set_Pinned + '_sync', (event, data) => {
helpers
.setPinned(data.Path, data.Pinned, data.Version, data.Provider, data.Remote, data.S3)
.then(success => {
event.returnValue = success;
})
.catch(e => {
event.returnValue = false;
});
});
};
module.exports = {
addListeners
};

View File

@@ -14,7 +14,7 @@ const setPlatformOverride = platformOverride => {
_platformOverride = platformOverride; _platformOverride = platformOverride;
}; };
const addListeners = (ipcMain, detectScript, saveUiSettings) => { const addListeners = (ipcMain, {detectScript, saveUiSettings}) => {
ipcMain.on(Constants.IPC_Get_Platform, (event) => { ipcMain.on(Constants.IPC_Get_Platform, (event) => {
const sendResponse = (appPlatform, platform) => { const sendResponse = (appPlatform, platform) => {
event.sender.send(Constants.IPC_Get_Platform_Reply, { event.sender.send(Constants.IPC_Get_Platform_Reply, {

View File

@@ -5,7 +5,7 @@ const os = require('os');
const path = require('path'); const path = require('path');
const unzip = require('unzipper'); const unzip = require('unzipper');
const addListeners = (ipcMain, getCleanupReleases, standardIPCReply) => { const addListeners = (ipcMain, {getCleanupReleases, standardIPCReply}) => {
ipcMain.on(Constants.IPC_Check_Installed, (event, data) => { ipcMain.on(Constants.IPC_Check_Installed, (event, data) => {
const destination = path.join(helpers.getDataDirectory(), data.Version); const destination = path.join(helpers.getDataDirectory(), data.Version);
helpers helpers

View File

@@ -0,0 +1,47 @@
const Constants = require('../../constants');
const helpers = require('../../helpers');
const addListeners = (ipcMain, {standardIPCReply}) => {
ipcMain.on(Constants.IPC_Export_Skylinks, (event, data) => {
helpers
.exportSkylinks(data.Version, data.Paths)
.then(result => {
standardIPCReply(event, Constants.IPC_Export_Skylinks_Reply, {
Result: result,
});
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Export_Skylinks_Reply, {}, error);
});
});
ipcMain.on(Constants.IPC_Grab_Skynet_Tree, (event, data) => {
helpers
.grabSkynetFileTree(data.Version)
.then(result => {
standardIPCReply(event, Constants.IPC_Grab_Skynet_Tree_Reply, {
Result: result,
});
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Grab_Skynet_Tree_Reply, {}, error);
});
});
ipcMain.on(Constants.IPC_Import_Skylinks, (event, data) => {
helpers
.importSkylinks(data.Version, data.JsonArray)
.then(result => {
standardIPCReply(event, Constants.IPC_Import_Skylinks_Reply, {
Result: result,
});
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Import_Skylinks_Reply, {}, error);
});
});
};
module.exports = {
addListeners
};

View File

@@ -3,19 +3,70 @@ const fs = require('fs');
const helpers = require('../../helpers'); const helpers = require('../../helpers');
const path = require('path'); const path = require('path');
const getDirectories = source => {
try {
return fs.readdirSync(source, {withFileTypes: true})
.filter(dirent => dirent.isDirectory())
.map(dirent => dirent.name);
} catch {
return [];
}
}
const addListeners = ipcMain => { const addListeners = ipcMain => {
ipcMain.on(Constants.IPC_Get_State, event => { ipcMain.on(Constants.IPC_Get_State, event => {
helpers.mkDirByPathSync(helpers.getDataDirectory()); helpers.mkDirByPathSync(helpers.getDataDirectory());
let data = {};
const configFile = path.join(helpers.getDataDirectory(), 'settings.json'); const configFile = path.join(helpers.getDataDirectory(), 'settings.json');
if (fs.existsSync(configFile)) { if (fs.existsSync(configFile)) {
event.sender.send(Constants.IPC_Get_State_Reply, { data = JSON.parse(fs.readFileSync(configFile, 'utf8'));
data: JSON.parse(fs.readFileSync(configFile, 'utf8')),
});
} else { } else {
event.sender.send(Constants.IPC_Get_State_Reply, { data.Release = Constants.DEFAULT_RELEASE;
data: null, data.Version = -1;
});
} }
for (const provider of Constants.PROVIDER_LIST) {
if (!data[provider]) {
data[provider] = {
AutoMount: false,
AutoRestart: true,
MountLocation: '',
}
}
}
data.RemoteMounts = data.RemoteMounts || [];
data.S3Mounts = data.S3Mounts || [];
const remoteItems = getDirectories(path.join(helpers.getRepertoryDirectory(), 'remote'));
for (const dir of remoteItems) {
const name = 'Remote' + dir.replace('_', ':');
if (!data.RemoteMounts || data.RemoteMounts.indexOf(name) === -1) {
data.RemoteMounts.push(name);
data[name] = {
AutoMount: false,
AutoRestart: true,
MountLocation: '',
};
}
}
const s3Items = getDirectories(path.join(helpers.getRepertoryDirectory(), 's3'));
for (const dir of s3Items) {
const name = 'S3' + dir;
if (!data.S3Mounts || data.S3Mounts.indexOf(name) === -1) {
data.S3Mounts.push(name);
data[name] = {
AutoMount: false,
AutoRestart: true,
MountLocation: '',
};
}
}
event.sender.send(Constants.IPC_Get_State_Reply, {
data,
});
}); });
ipcMain.on(Constants.IPC_Save_State, (event, data) => { ipcMain.on(Constants.IPC_Save_State, (event, data) => {

View File

@@ -2,7 +2,7 @@ const Constants = require('../../constants');
const os = require('os'); const os = require('os');
const helpers = require('../../helpers'); const helpers = require('../../helpers');
const addListeners = (ipcMain, closeApplication) => { const addListeners = (ipcMain, {closeApplication}) => {
ipcMain.on(Constants.IPC_Reboot_System, () => { ipcMain.on(Constants.IPC_Reboot_System, () => {
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
helpers.executeAsync('shutdown.exe', ['/r', '/t', '30']); helpers.executeAsync('shutdown.exe', ['/r', '/t', '30']);

View File

@@ -3,7 +3,7 @@ const fs = require('fs');
const helpers = require('../../helpers'); const helpers = require('../../helpers');
const os = require('os'); const os = require('os');
const addListeners = (ipcMain, setIsInstalling, unmountAllDrives, standardIPCReply) => { const addListeners = (ipcMain, {setIsInstalling, unmountAllDrives, standardIPCReply}) => {
ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => { ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => {
let allowSkipVerification = true; let allowSkipVerification = true;

View File

@@ -1,13 +1,29 @@
import React from 'react'; import React from 'react';
import * as Constants from './constants'; import * as Constants from './constants';
import Modal from './components/UI/Modal/Modal'; import Modal from './components/UI/Modal/Modal';
import axios from 'axios';
const ipcRenderer = (!process.versions.hasOwnProperty('electron') && window && window.require) ? const ipcRenderer = (!process.versions.hasOwnProperty('electron') && window && window.require) ?
window.require('electron').ipcRenderer : window.require('electron').ipcRenderer :
null; null;
export const createModalConditionally = (condition, jsx, critical) => { export const checkNewReleases = selectedVersion => {
const modalProps = {critical: critical}; let previousReleases = localStorage.getItem('previous_releases');
if (previousReleases) {
previousReleases = JSON.parse(previousReleases).VersionLookup;
let currentReleases = localStorage.getItem('releases');
if (currentReleases) {
currentReleases = JSON.parse(currentReleases).VersionLookup;
return getNewReleases(previousReleases, currentReleases, selectedVersion);
}
}
return [];
};
export const createModalConditionally = (condition, jsx, critical, disableFocusTrap, transparent) => {
const modalProps = {critical: critical, disableFocusTrap: disableFocusTrap, transparent: transparent};
return condition ? (<Modal {...modalProps}>{jsx}</Modal>) : null; return condition ? (<Modal {...modalProps}>{jsx}</Modal>) : null;
}; };
@@ -16,10 +32,72 @@ export const extractFileNameFromURL = url => {
return parts[parts.length - 1]; return parts[parts.length - 1];
}; };
export const formatLinesForDisplay = lines => {
let msg = '';
for (let i = 1; i < lines.length; i++) {
if (i > 1) {
msg += '\n';
}
msg += (lines[i].replace(/(\\#)/gm, '#') + '\n');
}
return msg;
};
export const getChangesForRepertoryVersion = version => {
return new Promise((resolve, reject) => {
const url = 'https://bitbucket.org/blockstorage/repertory/raw/' +
Constants.REPERTORY_BRANCH + '/CHANGELOG.md';
axios
.get(url, {
responseType: 'text',
})
.then(response => {
try {
let found = false;
let ended = false;
let lines = response.data
.replace(/(\r\n)/gm, '\n')
.split('\n')
.filter(l => {
return !ended && (l.length > 0) && (found
? !(ended = l.startsWith('## '))
: (found = l.startsWith(`## ${version}`)));
});
resolve(lines);
} catch (e) {
reject(e);
}
})
.catch(error => {
reject(error);
});
});
};
export const getIPCRenderer = () => { export const getIPCRenderer = () => {
return ipcRenderer; return ipcRenderer;
}; };
export const getNewReleases = (existingLocations, newLocations, selectedVersion) => {
const ret = [];
if (existingLocations && newLocations) {
Constants.RELEASE_TYPES.forEach(release => {
newLocations[release]
.filter(version => (version !== selectedVersion) && !existingLocations[release].includes(version) && (version !== 'unavailable'))
.forEach(version => {
ret.splice(0, 0, {
Display: version,
Release: Constants.RELEASE_TYPES.indexOf(release),
Version: newLocations[release].indexOf(version),
VersionString: version,
});
});
});
}
return ret;
};
export const getSelectedVersionFromState = state => { export const getSelectedVersionFromState = state => {
return (state.relver.Version === -1) ? return (state.relver.Version === -1) ?
'unavailable' : 'unavailable' :