Merged 1.3.x_branch into master

This commit is contained in:
2021-05-20 23:28:19 +00:00
114 changed files with 6031 additions and 4106 deletions

30
.eslintrc.json Normal file
View File

@@ -0,0 +1,30 @@
{
"root":true,
"env": {
"browser": true,
"es2021": true,
"node": true,
"jest/globals": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:prettier/recommended"
],
"parser": "babel-eslint",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"prettier",
"react",
"jest"
],
"rules": {
"prettier/prettier": "warn"
}
}

8
.prettierrc.json Normal file
View File

@@ -0,0 +1,8 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"jsxBracketSameLine": true,
"printWidth": 100
}

View File

@@ -1,14 +1,36 @@
{ {
"cSpell.words": [ "cSpell.words": [
"APPIMAGE",
"APPPLATFORM",
"Bodhi",
"Filebase",
"GUID's",
"HKCC",
"HKCR",
"HKCU",
"HKEY", "HKEY",
"HKLM", "HKLM",
"LOCALAPPDATA",
"Redistributable", "Redistributable",
"Skylink's",
"Skylinks", "Skylinks",
"Skynet", "Skynet",
"Unmount", "Unmount",
"WINFSP",
"blockstorage",
"centos",
"fontawesome",
"fortawesome",
"msiexec", "msiexec",
"norestart",
"randomstring",
"reduxjs",
"relver", "relver",
"scprime",
"siaprime", "siaprime",
"skylink" "skylink",
"undocked",
"valign",
"windir"
] ]
} }

2
.vimrc
View File

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

View File

@@ -1,150 +1,178 @@
# Changelog # Changelog
## 1.3.4
- \#55: Enable 'Activate' release instead of 'Install' in notification pop-up
- \#58: Add authentication support for Skynet premium portals
- \#59: Add agent string / API key support for Skynet portals
- Ability to export Skylink's to json file
- Ability to import Skylink's from json file
## 1.3.3 ## 1.3.3
* \#49: Download progress is not visible if dependencies are missing
* \#51: javascript error - \#49: Download progress is not visible if dependencies are missing
* \#52: Mount location is not set error on new install - \#51: javascript error
* \#53: Busy notification is still visible when 'Install' button is available - \#52: Mount location is not set error on new install
* \#54: Unable to download UI update while dependencies are being checked - \#53: Busy notification is still visible when 'Install' button is available
* Disabled 'Install' button in new release notification - \#54: Unable to download UI update while dependencies are being checked
- Disabled 'Install' button in new release notification
## 1.3.2 ## 1.3.2
* \#48: Support pinning files to cache
* Fixed Skynet export display - \#48: Support pinning files to cache
* Properly detect existing remote - Fixed Skynet export display
* Reduced number of Linux binaries to: - Properly detect existing remote
* CentOS 7 - Reduced number of Linux binaries to:
* Solus - CentOS 7
* S3 mount support [disabled] - Solus
- S3 mount support [disabled]
## 1.3.1 ## 1.3.1
* \#45: Skynet mount support
- \#45: Skynet mount support
## 1.3.0 ## 1.3.0
* \#38: Enhance new repertory release available notification
* \#46: Fix Mount Manager unmount and mount detection - \#38: Enhance new repertory release available notification
* Skynet support - \#46: Fix Mount Manager unmount and mount detection
* Synchronize UI major/minor version with `repertory` major/minor version - Skynet support
* Added `Password` component - Synchronize UI major/minor version with `repertory` major/minor version
* Reduced number of Linux binaries to: - Added `Password` component
* CentOS 7 - Reduced number of Linux binaries to:
* Debian 9 - CentOS 7
* Debian 10 - Debian 9
* Solus - Debian 10
* Added `FocusTrap` to modals - 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
## 1.1.3 ## 1.1.3
* CentOS 8 support
* Support remote send and receive timeouts - CentOS 8 support
* Support WinFSP mount manager - Support remote send and receive timeouts
* Fix `spawn()` failure on Windows when paths contain spaces - Support WinFSP mount manager
- Fix `spawn()` failure on Windows when paths contain spaces
## 1.1.2 ## 1.1.2
* Style changes
- Style changes
## 1.1.1 ## 1.1.1
* \#43: Support for remote UNIX mounts
* Improved mount state detection - \#43: Support for remote UNIX mounts
- Improved mount state detection
## 1.1.0 ## 1.1.0
* \#40: Support for remote Windows mounts
* \#42: Failing to unmount on OS X - \#40: Support for remote Windows mounts
* Allow enabling dev tools (Ctrl-Shift-I or F12) - \#42: Failing to unmount on OS X
* ScPrime re-brand - Allow enabling dev tools (Ctrl-Shift-I or F12)
* Ubuntu 19.10 support - ScPrime re-brand
* Linux Mint 19.2 support - Ubuntu 19.10 support
* Fedora 31 support - Linux Mint 19.2 support
- Fedora 31 support
## 1.0.11 (Linux only) ## 1.0.11 (Linux only)
* Fix Ubuntu 19.10 detection
- Fix Ubuntu 19.10 detection
## 1.0.10 ## 1.0.10
* Fix VC runtime detection (detect additional GUID's)
- Fix VC runtime detection (detect additional GUID's)
## 1.0.9 (Alpha Testing Release) ## 1.0.9 (Alpha Testing Release)
* \#40: Support for remote Windows mounts
* ScPrime re-brand - \#40: Support for remote Windows mounts
* Fix VC runtime detection (detect additional GUID's) - ScPrime re-brand
- Fix VC runtime detection (detect additional GUID's)
## 1.0.8 ## 1.0.8
* \#8: Add tooltips to settings
* \#36: Add ability to select Linux distribution type if OS is unsupported - \#8: Add tooltips to settings
* \#37: Version check fails with incorrect message when VC runtime is missing - \#36: Add ability to select Linux distribution type if OS is unsupported
* Added additional WinFsp uninstall strings - \#37: Version check fails with incorrect message when VC runtime is missing
- Added additional WinFsp uninstall strings
## 1.0.7 ## 1.0.7
* \#31: New installation displays 'Mount location is not set' on Windows
* \#33: Add 'Microsoft Visual C++ Redistributable' as dependency installation on Windows - \#31: New installation displays 'Mount location is not set' on Windows
* \#32: Don't display network error message when check for UI updates fails - \#33: Add 'Microsoft Visual C++ Redistributable' as dependency installation on Windows
* \#30: Add uninstall feature with reboot to handle WinFSP upgrades/downgrades - \#32: Don't display network error message when check for UI updates fails
* \#34: Allow cancelling/closing dependency installation if version count > 1 - \#30: Add uninstall feature with reboot to handle WinFSP upgrades/downgrades
* Handle incorrect download sizes for dependencies and releases - \#34: Allow cancelling/closing dependency installation if version count > 1
- Handle incorrect download sizes for dependencies and releases
## 1.0.6 ## 1.0.6
* Additional Linux distribution support:
* Antergos - Additional Linux distribution support:
* Manjaro - Antergos
* Download latest `detect_linux.sh` if bundled script returns `unknown` - Manjaro
* Display error message if OS is detected as `unknown` - Download latest `detect_linux.sh` if bundled script returns `unknown`
- Display error message if OS is detected as `unknown`
## 1.0.5 ## 1.0.5
* \#29: Mounts aren't being detected properly when switching releases
* Display window when dependencies are missing - \#29: Mounts aren't being detected properly when switching releases
* Display window when UI upgrade is available - Display window when dependencies are missing
* Display window and unmount all drives if release is no longer available - Display window when UI upgrade is available
* Will primarily affect pre-release versions (Alpha, Beta, and RC) - Display window and unmount all drives if release is no longer available
- Will primarily affect pre-release versions (Alpha, Beta, and RC)
## 1.0.4 ## 1.0.4
* \#27: Implement Bitbucket backup download location
* \#28: Fix Linux upgrade - \#27: Implement Bitbucket backup download location
* Additional Linux distribution support: - \#28: Fix Linux upgrade
* Debian 10 - Additional Linux distribution support:
* OpenSUSE Leap 15.0 - Debian 10
* OpenSUSE Leap 15.1 - OpenSUSE Leap 15.0
* OpenSUSE Tumbleweed - OpenSUSE Leap 15.1
- OpenSUSE Tumbleweed
## 1.0.3 ## 1.0.3
* Linux distribution support
* Arch Linux - Linux distribution support
* Bodhi 5.0.0 - Arch Linux
* CentOS 7 - Bodhi 5.0.0
* Debian 9 - CentOS 7
* Elementary OS 5.0 - Debian 9
* Fedora 28 - Elementary OS 5.0
* Fedora 29 - Fedora 28
* Fedora 30 - Fedora 29
* Linux Mint 19 - Fedora 30
* Linux Mint 19.1 - Linux Mint 19
* Solus - Linux Mint 19.1
* Ubuntu 18.04 - Solus
* Ubuntu 18.10 - Ubuntu 18.04
* Ubuntu 19.04 - Ubuntu 18.10
* Removed `react-css-modules` dependency - Ubuntu 19.04
* Removed Hyperspace (no active development/insufficient host network) - Removed `react-css-modules` dependency
* Restore main window on error message - Removed Hyperspace (no active development/insufficient host network)
- Restore main window on error message
## 1.0.2 ## 1.0.2
* Option to launch application hidden (notification icon only)
* Close window to notification area - Option to launch application hidden (notification icon only)
* Unmount on application exit - Close window to notification area
* Ability to cancel mount retry on unexpected failure - Unmount on application exit
* OS X support - Ability to cancel mount retry on unexpected failure
* SiaPrime support - OS X support
* Partial Linux support - SiaPrime support
* Electron to v4 - Partial Linux support
* Prevent mount if dependencies are missing - Electron to v4
- Prevent mount if dependencies are missing
## 1.0.1 ## 1.0.1
* Added configuration settings for Repertory 1.0.0-alpha.2 and above
* Fixed memory leak on component unmount - Added configuration settings for Repertory 1.0.0-alpha.2 and above
* Added error display - Fixed memory leak on component unmount
* Lighter tray icon on Windows - Added error display
* Tray icon indicates mount status on Windows - Lighter tray icon on Windows
* Various fixes/layout changes - Tray icon indicates mount status on Windows
- Various fixes/layout changes
## 1.0.0 ## 1.0.0
* Initial release
* Windows support - Initial release
- Windows support

View File

@@ -1,4 +1,5 @@
# Repertory UI # Repertory UI
* Lars Floe <lars@krankajen.se>
* Oleg Nypadymka <onypadymka@gmail.com> - Lars Floe <lars@krankajen.se>
* Scott E. Graves <scott.e.graves@protonmail.com> - Oleg Nypadymka <onypadymka@gmail.com>
- Scott E. Graves <scott.e.graves@protonmail.com>

View File

@@ -12,13 +12,13 @@ Skynet support is considered EXPERIMENTAL. Files added to Skynet should not be c
* ScPrime >=1.4.1.2 * ScPrime >=1.4.1.2
## Downloads ## Downloads
* **Repertory UI v1.3.3 Linux * **Repertory UI v1.3.4 Linux
64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.3_linux_x86_64.AppImage) 64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.4_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.3.3 OS X * **Repertory UI v1.3.4 OS X
64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.3_mac.dmg) 64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.4_mac.dmg)
* **Repertory UI v1.3.3 Windows * **Repertory UI v1.3.4 Windows
64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.3_win.exe) 64-bit** [<Download\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.4_win.exe)
## Supported Platforms ## Supported Platforms
* OS X 64-bit * OS X 64-bit

View File

@@ -26,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="centos7 debian9 debian10 solus" DISTRO_LIST="centos7 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

View File

@@ -1,6 +1,6 @@
{ {
"name": "repertory-ui", "name": "repertory-ui",
"version": "1.3.3", "version": "1.3.4",
"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, Skynet, and/or ScPrime 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.",
@@ -10,21 +10,22 @@
"@fortawesome/react-fontawesome": "^0.1.13", "@fortawesome/react-fontawesome": "^0.1.13",
"@reduxjs/toolkit": "^1.5.0", "@reduxjs/toolkit": "^1.5.0",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"axios": "^0.21.0", "axios": "^0.21.1",
"devtron": "^1.4.0", "devtron": "^1.4.0",
"electron-debug": "^3.1.0", "electron-debug": "^3.2.0",
"electron-log": "^4.3.0", "electron-log": "^4.3.2",
"focus-trap-react": "^8.3.2", "focus-trap-react": "^8.4.2",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"node-cron": "^1.2.1", "node-cron": "1.2.1",
"prop-types": "^15.7.2",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
"react": "^16.14.0", "react": "16.14.0",
"react-checkbox-tree": "^1.6.0", "react-checkbox-tree": "^1.6.0",
"react-dom": "^16.14.0", "react-dom": "16.14.0",
"react-loader-spinner": "^3.1.14", "react-loader-spinner": "^4.0.0",
"react-redux": "^7.2.2", "react-redux": "^7.2.2",
"react-scripts": "4.0.3", "react-scripts": "4.0.3",
"react-tooltip": "^4.2.11", "react-tooltip": "^4.2.15",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"unzipper": "^0.10.11", "unzipper": "^0.10.11",
@@ -36,6 +37,12 @@
"electron": "5.0.13", "electron": "5.0.13",
"electron-builder": "22.9.1", "electron-builder": "22.9.1",
"electron-webpack": "^2.8.2", "electron-webpack": "^2.8.2",
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-jest": "^24.3.2",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-react": "^7.23.0",
"prettier": "2.2.1",
"webpack": "4.44.2", "webpack": "4.44.2",
"webpack-dev-server": "3.11.1" "webpack-dev-server": "3.11.1"
}, },

View File

@@ -1,12 +1,4 @@
const { const { app, BrowserWindow, dialog, ipcMain, Menu, nativeImage, Tray } = require('electron');
app,
BrowserWindow,
dialog,
ipcMain,
Menu,
nativeImage,
Tray
} = require('electron');
const AutoLaunch = require('auto-launch'); const AutoLaunch = require('auto-launch');
const Constants = require('../src/constants'); const Constants = require('../src/constants');
const fs = require('fs'); const fs = require('fs');
@@ -44,12 +36,12 @@ const UpgradeIPC = require('../src/renderer/ipc/UpgradeIPC');
const platform = os.platform(); const platform = os.platform();
const dimensions = { const dimensions = {
height: (platform === 'win32') ? 326 : (platform === 'darwin') ? 322 : 300, height: platform === 'win32' || platform === 'darwin' ? 420 : 400,
width: (platform === 'win32') ? 468 : 628, width: platform === 'win32' ? 468 : 628,
}; };
let isShutdown = false; let isShutdown = false;
let isQuiting = false; let isQuitting = false;
let isInstalling = false; let isInstalling = false;
let launchHidden = false; let launchHidden = false;
let cleanupReleases = false; let cleanupReleases = false;
@@ -88,7 +80,7 @@ const createWindow = () => {
if (platform === 'linux') { if (platform === 'linux') {
extra = { extra = {
icon: path.join(__dirname, '../build/', 'logo.png'), icon: path.join(__dirname, '../build/', 'logo.png'),
} };
} }
// Create the browser window. // Create the browser window.
@@ -102,27 +94,29 @@ const createWindow = () => {
...extra, ...extra,
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
webSecurity: !process.env.ELECTRON_START_URL webSecurity: !process.env.ELECTRON_START_URL,
} },
}); });
if (platform === 'linux') { if (platform === 'linux') {
mainWindow.setMenuBarVisibility(false); mainWindow.setMenuBarVisibility(false);
} else { } else {
mainWindow.removeMenu(); mainWindow.removeMenu();
} }
if ((platform === 'darwin') && launchHidden) { if (platform === 'darwin' && launchHidden) {
app.dock.hide(); app.dock.hide();
} }
// and load the index.html of the app. // and load the index.html of the app.
const startUrl = process.env.ELECTRON_START_URL || url.format({ const startUrl =
process.env.ELECTRON_START_URL ||
url.format({
pathname: path.join(__dirname, '../build/index.html'), pathname: path.join(__dirname, '../build/index.html'),
protocol: 'file:', protocol: 'file:',
slashes: true slashes: true,
}); });
mainWindow.on('close', function (event) { mainWindow.on('close', function (event) {
if (!isQuiting) { if (!isQuitting) {
event.preventDefault(); event.preventDefault();
if (mainWindow.isVisible()) { if (mainWindow.isVisible()) {
setWindowVisibility(false); setWindowVisibility(false);
@@ -140,9 +134,12 @@ const createWindow = () => {
MountsIPC.unmountAllDrives(); MountsIPC.unmountAllDrives();
}); });
const appPath = (platform === 'win32') ? path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe')) : const appPath =
(platform === 'darwin') ? path.resolve(path.join(path.dirname(app.getAppPath()), '../MacOS/repertory-ui')) : platform === 'win32'
process.env.APPIMAGE; ? path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe'))
: platform === 'darwin'
? path.resolve(path.join(path.dirname(app.getAppPath()), '../MacOS/repertory-ui'))
: process.env.APPIMAGE;
const autoLauncher = new AutoLaunch({ const autoLauncher = new AutoLaunch({
name: 'Repertory UI', name: 'Repertory UI',
@@ -151,51 +148,56 @@ const createWindow = () => {
trayContextMenu = Menu.buildFromTemplate([ trayContextMenu = Menu.buildFromTemplate([
{ {
label: 'Visible', type: 'checkbox', click(item) { label: 'Visible',
type: 'checkbox',
click(item) {
setWindowVisibility(item.checked); setWindowVisibility(item.checked);
}, },
checked: !launchHidden, checked: !launchHidden,
}, },
{ type: 'separator' },
{ {
type: 'separator' label: 'Auto-start',
}, type: 'checkbox',
{ click(item) {
label: 'Auto-start', type: 'checkbox', click(item) {
if (item.checked) { if (item.checked) {
autoLauncher.enable(); autoLauncher.enable();
} else { } else {
autoLauncher.disable(); autoLauncher.disable();
} }
} },
}, },
{ {
label: 'Launch Hidden', type: 'checkbox', click(item) { label: 'Launch Hidden',
type: 'checkbox',
click(item) {
launchHidden = !!item.checked; launchHidden = !!item.checked;
saveUiSettings(); saveUiSettings();
}, },
checked: launchHidden, checked: launchHidden,
}, },
{ type: 'separator' },
{ {
type: 'separator' label: 'Delete Old Releases',
}, type: 'checkbox',
{ click(item) {
label: 'Delete Old Releases', type: 'checkbox', click(item) {
cleanupReleases = !!item.checked; cleanupReleases = !!item.checked;
saveUiSettings(); saveUiSettings();
}, },
checked: cleanupReleases, checked: cleanupReleases,
}, },
{ type: 'separator' },
{ {
type: 'separator' label: 'Exit and Unmount',
}, click() {
{
label: 'Exit and Unmount', click() {
closeApplication(); closeApplication();
} },
} },
]); ]);
const image = nativeImage.createFromPath(path.join(__dirname, (platform === 'darwin') ? '../build/logo_mac.png' : '../build/logo.png')); const image = nativeImage.createFromPath(
path.join(__dirname, platform === 'darwin' ? '../build/logo_mac.png' : '../build/logo.png')
);
mainWindowTray = new Tray(image); mainWindowTray = new Tray(image);
autoLauncher autoLauncher
@@ -203,7 +205,7 @@ const createWindow = () => {
.then((enabled) => { .then((enabled) => {
trayContextMenu.items[2].checked = enabled; trayContextMenu.items[2].checked = enabled;
mainWindowTray.setToolTip('Repertory UI'); mainWindowTray.setToolTip('Repertory UI');
mainWindowTray.setContextMenu(trayContextMenu) mainWindowTray.setContextMenu(trayContextMenu);
}) })
.catch(() => { .catch(() => {
closeApplication(); closeApplication();
@@ -225,37 +227,46 @@ const loadUiSettings = () => {
cleanupReleases = !!settings.cleanup_releases; cleanupReleases = !!settings.cleanup_releases;
PlatformIPC.setPlatformOverride(settings.platform_override); PlatformIPC.setPlatformOverride(settings.platform_override);
} }
} catch (e) { } catch (e) {}
}
}; };
const saveUiSettings = () => { const saveUiSettings = () => {
const settingFile = path.join(helpers.getDataDirectory(), 'ui.json'); const settingFile = path.join(helpers.getDataDirectory(), 'ui.json');
try { try {
fs.writeFileSync(settingFile, JSON.stringify({ fs.writeFileSync(
settingFile,
JSON.stringify({
cleanup_releases: cleanupReleases, cleanup_releases: cleanupReleases,
launch_hidden: launchHidden, launch_hidden: launchHidden,
platform_override: PlatformIPC.getPlatformOverride(), platform_override: PlatformIPC.getPlatformOverride(),
}), 'utf-8'); }),
} catch (e) { 'utf-8'
} );
} catch (e) {}
}; };
const setIsInstalling = installing => { const setIsInstalling = (installing) => {
isInstalling = installing; isInstalling = installing;
}; };
const setTrayImage = driveInUse => { const setTrayImage = (driveInUse) => {
let image; let image;
if (driveInUse) { if (driveInUse) {
image = nativeImage.createFromPath(path.join(__dirname, platform === 'darwin' ? '../build/logo_both_mac.png' : '../build/logo_both.png')); image = nativeImage.createFromPath(
path.join(
__dirname,
platform === 'darwin' ? '../build/logo_both_mac.png' : '../build/logo_both.png'
)
);
} else { } else {
image = nativeImage.createFromPath(path.join(__dirname, platform === 'darwin' ? '../build/logo_mac.png' : '../build/logo.png')); image = nativeImage.createFromPath(
path.join(__dirname, platform === 'darwin' ? '../build/logo_mac.png' : '../build/logo.png')
);
} }
mainWindowTray.setImage(image); mainWindowTray.setImage(image);
}; };
const setWindowVisibility = show => { const setWindowVisibility = (show) => {
if (show) { if (show) {
mainWindow.show(); mainWindow.show();
if (platform === 'darwin') { if (platform === 'darwin') {
@@ -277,7 +288,7 @@ const setWindowVisibility = show => {
if (trayContextMenu && mainWindowTray) { if (trayContextMenu && mainWindowTray) {
trayContextMenu.items[0].checked = show; trayContextMenu.items[0].checked = show;
mainWindowTray.setContextMenu(trayContextMenu) mainWindowTray.setContextMenu(trayContextMenu);
} }
}; };
@@ -288,13 +299,13 @@ const standardIPCReply = (event, channel, data, error) => {
...data, ...data,
Error: error instanceof Error ? error.toString() : error, Error: error instanceof Error ? error.toString() : error,
Success: !error, Success: !error,
} },
}); });
} }
}; };
app.on('before-quit', function () { app.on('before-quit', function () {
isQuiting = true; isQuitting = true;
}); });
let instanceLock = app.requestSingleInstanceLock(); let instanceLock = app.requestSingleInstanceLock();

View File

@@ -1,16 +1,22 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta
<meta name="theme-color" content="#000000"> name="viewport"
content="width=device-width, initial-scale=1, shrink-to-fit=no"
/>
<meta name="theme-color" content="#000000" />
<!-- <!--
manifest.json provides metadata used when your web app is added to the manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
--> -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<link href="https://fonts.googleapis.com/css?family=Nunito:400,700" rel="stylesheet"> <link
href="https://fonts.googleapis.com/css?family=Nunito:400,700"
rel="stylesheet"
/>
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
@@ -24,9 +30,7 @@
<title>Repertory UI</title> <title>Repertory UI</title>
</head> </head>
<body> <body>
<noscript> <noscript> You need to enable JavaScript to run this app.</noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div> <div id="root"></div>
<!-- <!--
This HTML file is a template. This HTML file is a template.

View File

@@ -1,60 +1,60 @@
{ {
"Locations": { "Locations": {
"centos7": { "centos7": {
"1.3.3": { "1.3.4": {
"sig": "BL1hmylvFtrY5GAJaZD4m2YAgOgf21N7aCRmQmVQqDpA5tLrLhaCxJaRwQaNTXrNFnwL3YHBXJQf5cJi43dVXI7o+rcltU5QDA2Q7DvzdlaMfL9saBzxLT0D94l017XvczaxhOlypEQIUzFVQYxGzdw3Kx6fKWFySeKgS4l+SB1FTXfeYhfp2ZApOdRB1Y9BzAIN0hATm/UBcBL12FlwCl4+YV6BEIz/07I8VypwxLx68dqGCE/ucTVCf/4D00oGcWEFfrIxBnGzSLFWSyvhvyJwJWvH4VIDIP2KwlhdEs/3p0ETzSl1YbV+bOeUbUGR6Nv2ljF62Y918WOXspiC6N2bDzvpBZkkpKs0xRih4AJQDHR6gs3/43XIqrnTrKMZbZVy2JnEkl3KNGmscLjzS8lus7Wmkn8l0qyOuGY30ef4q60q55VVUw4y+Ox66WbnMYpLpSCY56iNOjw0Kp+rpV7UreLFt5UffQuWWlOttrXs1XJKO9GYv8kbognKAf8gRr7iJWMbZWe8o4++P9ziwt4nd7JlcGzxKinntB4Jn/BHYJnJKGqHhA5j8EJ1QZlu3txruKdcOPV7RpXoFEvOJZ8rji7mUUrlPGM/UJ8JhLcr8P+goKza9dFcX3pFqVHOll4pNOmpPOE+ui+UPSk0I9oCBlEvHSMijywVnwjMh2Yb+VBbuC9Hw5qK2kMZgGXpysa40WrEO5dznJclRSDChIKWVJzdvqnQ2mkR7va+4YAi7Pi/7JnJkO1gcM7O0hGz2bHeG3j7xdUvf0pS8YCcEYojJ97xnBH5UWDYXPBnMsrBMy7mwu/TH8MEGtLiC+9k6THV4Xf9ooS2roHhVskkK5u3xhNFNLxeBVBnQvk7lHynjFbjVNa3URMsD5J5SzQ8O10UhAV8vnpTD70GO25htI1MZuMgV8KPZZ5pOlUMrxqgS14obph8TxMUrD3Wy6SZrwYj/0JTY0lkfhxaXuaecoKDVtCVmiTCcioFryvMymCuz0/0FV8ctlau5Ko3W2W0wmWCVmlDSU5xEybVDegSSnI6bWPQn1SgHUC6DpaWa5LOtCMBgA84AAEMRCMDIcc1EY/VAHz/NeU9UJFESdv/uniL2wcWRq72xPYVwD5fB5YEyFgHx3UXNxhtwMWeThEaOvmHp2snOksM6RSjXVcBLkzK7JQxb25AKEo0rpgHqcVZo+pRpl/6RyXmJWQb1Fl0N6/0fDzJUS3Ji1fAw4O9T2ftrrk1WUD/PsjdaXKRJfWDNl9iWN4gbv+KZAwx+BgF+oMD4b7bOU+cQnkISzT2WD8k/S7wKhrbyMGOWwPYHnebIj0TK0QR0+RWpcP5BggGe7/YpjizvVPGBySjYTcWcig=", "sig": "CQZdxHimuD3li5JcHKJC3zdqNdw18RXFBQ9fgtxXe5FpgPq3bycXrMjmqaJaz25fLPl++e33PSlnzdaC2ede9vrWSfqfB3UG5Vdxxth/YnsqV4YzgGwOed6v9F7YcR2wgts8FpCzy+vokGWu2lI05pCx4ObNgqyvbyGcvGdzgTylhF+MlgSDtXdDV6B1Ba9d+Oj8lh3eA2d7v09ltxavEoiQmrX0ODK9dov9axT3JT/RZTNpAhTSfTT5YUgXmYWYQjZKdwCbFc4k23C1V0SKivB8lQ26Eaaxegwse+Kybar6CzisSkvAlTza9OCo/pxmJ/YedReldyj8pbq7+BweG82JWm8HVl4Y+ppXej69zFtAgcv1QWMYyXmBTfzoKMEUhrH3Brnj3b4ssAG6MKH2Y+lzwhzDYhpNXZ/+jfknFZPaGQ4UgUd1Nf46dPk0Pt+x3tzcweRO6XacfI8e+PNf5YIk+CKxyGjoVJfz1dEAKKg1RL1RXqHPK44MTSrHAIyUPgRNUq6zX23VkfdT4b/IftaIYEEO738wT0nutAtg7RLo2jx6w3hTjDSmh9Wz/szhtS/pP670PycryqH/JaE/5n4lgvSIZN7Inz9WWHbttX54H36usqcEiKr5yoSeOvdIsjWpHguKxasRNbib+Idnhug/Zs8XmshUfwAH3Y+6Wy5dM+IGfuyFpwkfQE2DQmfriyyn2gQf1EJZ6lxuFy1q5vQ9pf3QOaEU72yCu63EEYiXEnPQKgMPkBXxgo6Rp/Nskth0F2XXUYDUPD3lzOJLNMKn3G7ta5EkmZZhrk94pm8X5DpXdxzxFr/lvlWNic5trhWhVtJfci+yxZTI9IiXEfLTck0oYzkpaSxgTvOZ4DklW92xk682F41eNmdo64NwyqeHYS7uvLsJa4aRmRVmsfbY5RuOrDlAmr+yDX9OaISFfhGgByqPlv/5Sm27GFCHFfkvPrUGQWBOFnoRrnr1VZqKr2xRImDUhsP2FNaZJH2E0lBh7P6WmFu2/N1XwJ9w3TX1pwQXGJ/XERSCj7w972VZ8YMj+LHZ4anpZHuY4eL9PAkgwEEpTIqUJuJ2Oj1LGG3GGlT/HrR7tSofybi6+tNsD4egdFZ4HI3+nrPFGbYTQNQzzxXmPnukdP66h4lGGz8bBVT/wezKCK06RrgUKoi+GxcndMnF6JlAzzmiEDh2CXHJsFIr5eDx+pgxbr4Ibz4Mwu9qfeN6fNC+EAkewC1W3f3HoX3oozAT1AobitkT6CV7rHUD4xHWih/7uA+9uuAI2VgxfpfzyvR5H7kd51p63sB1DyvzpB9ssf/E7vxyzQJJhDEOWXUHi0ciMgeElFgJMUI49xzGvJUizV6YmwE=",
"sha256": "03c29d95cb5406f90b076d633fd6a3e2f4a42a5bcf75433099453c0767acc761", "sha256": "ef29a5aad24f9f10cbac3e46b1be334a2f80b184efe0dcc09a5c1fa4526a68f4",
"urls": [ "urls": [
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.3_linux_x86_64.AppImage" "https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.4_linux_x86_64.AppImage"
] ]
} }
}, },
"darwin": { "darwin": {
"1.3.3": { "1.3.4": {
"sha256": "7fbcca92d5d3c728e3663a4cf4035c5d1c45f4cd3a43b5dab605969aa9f95d29", "sig": "BGYvkHV/lP0/6sBdrsO2L3W94oFHduVd8J3DT/SJIXUKRbjLIUGLKwnF27H7gVr1rO1vHQWaNuVaBHQ8guTqh2GAxEFr9w+sFMm0dvnuRndpxcJ4AtVT6j7TiRojPMAPFk9Kd8rNda0jGH9CPG8358K0SNVU61/upi3+zHv4R/KYC47ZK/7R6BWxBUTA1l8/5Q70TE9Kco75j6c8fK8dV6nFfN8rp2mAmxZhmRtV2uFL4OguYhxM6Oba+wxVlq7ywiPjtck4gkYrI9wMvWetK0erI6lwki5YpwEN1tRqDqD2DeKBqsMj5H8kElHMCh+g9Y5DvVIKR1pZdpopgmIQNsfmo/CYgicUvN0ppvyMuLJ/p4PPVIg6ldu6VgjOBXykZIRkZF4TgKsVaDdOpQ6E2Wd4+tERrXwKeP76+3h1RYl4W2NSZ9Lc2wWe3FmK4HKU6jbJ1qy8FsjjxEHs0OJvvYmmEp9bIMtCPZnwf5wR4V2Ht3BScV93mAaWbaykBuWWpXtxiuxw01BZ9w8LAHKJ6BX48wEtz6CAQqF8nc5kDkexsMy2Al2fv3lj+xa5RPjHO/tY4jbUrAn1TCVc98tz+vHbD9TITVkdYcSPNUSMpUANGTvOTQ1CkjOO6pavpMlo96kdLKG+YqK0ERfCQ4YI3V7CjJhEdrkg9ZhLtv7vtyG4WicXBOARaW1OiQFYB48/np/6anKoS2uCDRQdj9vHLyE4C7PwfPKIg+cFCzCMGcvjHrBbGf7PnmSyYFY7PvGcEWylINoWeHquqAGq+N6wTNm+5K/IzmXh+II4dm4f0G15S4gSE/Ux4sIgYADAVXz1uaeARr+EfDOtUjqxAX1Aay1y+1WEFI3L66gszcdpiRsc46gwbYIJ585rVs+FOLNZI6Ak0RawzyZcifCcwaTWGG+kASLyC5XIgayw0hT3qPoZofSasB1RGM3PQ6trz8937RdDK2rQpry01zWsG/Kk+gxI5mZkU2kWgPtUxCPTG6+lLkEY7p9mg/Lo4/8Ze/qlb/xR/9i4+BBqtRRUHJzAsps0K0KicO7+MXB+Sc4hbvXkGFwpFd+JYy/Ry7TVRpiCp8Hdhk9PRtp//Ub1Fun4f7eadTCsbJs7duwKeBhUP0uGP2xo1pyWtFmZLm5ww2g40nEIHy8C5cvadPlN3NTKBZ8lqXqiN6fNtXel+T65vyoVic1FbVYihXSZOmFsJ+7mwDF6ayelAnx9kC0uI0gxpjaZWGPa1ygLWYNkSz4KIO2m5t9NZL6JyVXIRgPvie9rJKAFB2lgxBnkehpQMbU/FgpbzWb0a78OEkAxr4mD7U4WerlVU6jg8xx53tkCSpv35kNwsAOwW7YE+KN8VMW+6j0=",
"sig": "BPM/WKDq+vueZ7YcSI3S+GbisBPsv/OjZPnyxAfHvDQst+Fjrx/9jmTJTTFMtP0xZ1RtrnmSkAwjnyTJRgEPCfe4yWsV52augPp9tvEBVKJgb5KM10h1yN0+DlNB4PMPdE6Ix520mIbQXY72BedPt3qhkom5RN/rQ/ZTHO9qmKwHpvaukh4tUsfsMlGEzjm/Bzch2qdj1pi4CGuug0KxIp23NSgk2ic4EcnVR+aa1lhEpjgE6pGg5ikQ07GR/mFkvr6xAlrEQVXuJ7qGStkXjNuNywSAhT+kUwRr1Wd43s7FEikInLLbewhXStZLG/Q9fVYFxXBoPhk6g5deshGabmNCWt/4JcYYFEq5HWCoaT/86DKQJnLZ3ZMnO/cqlj/7jbi29TmTxvoqdDADp7yRMAcM89LsjDMiFnADlnlsBhUYrM8kjJsd4VGZESviMWmls3kPr3oaZ+OjBQdcYX/bpvrFULFtRxOHYPJHJ9W1IkOd2EGYf/BcxyjRxGqI18oj3a9VaVzyM3IWm3i2Bw0ijj2GQ+p/qeyy3lzTNM25LuUe/PJF9d0wuLisTpvFyW52tNOBLqhrT2wQK8oCo95MqSNKd22vfxJzTcD8PlZSG9ZXNTrmKFbYqJmi6JptUNLVlyn1L8uyJf4S+vL8HAJ1hSqr4iJ/AIF5nMBONzE/4PCP3tathkJ67Yfw0eQbOwl7d+fXSJjZtggFUADLGcwrCLHRclSiW7/9LpL6YJD+h0Ud9ZgLNjYjU/bHL6TCqeAtbSBYG6VNFWN+s73NaBkzC2kx6HnWaa2q4AHAxoix7/LUWhdWh85XGa1UUFWtyxy+hIM3cpacKrSd3u9VdKLHL3GtdfvQjvEE/5UyjH2VzedRVZntjVlBTnVDHq/J4prBAZyA/PSOGtK1x0LmlH2UdT3QEU1WNZaag7vLKWxfPPVvO8zoJG21l8xFWfoAZ/90HA5WRxEsSavSUXzvffmv9O5C/WnWj0tNRsWYqwOYS9l7IcrUDB68V2Rws/sDG56xfgqzdZM4CjM3BBQyarVWr9wyzxAfR0VFOf7GsjF7A1OpQXPiY8pVHg7QPTUSG0Ssse1/leZnU11CEpr/v8JwmHi7LRZ0KSbUowRwPjkzVdDrRjnIQh+2ssphL5mJHNfoxNUFAbvQidN5OjzdUoqEK7H6/OjjHek9mgvJuDDYSHV5cUwgmgPFTwONarrepFThj5YwKGQhYYsCqJLZ+ZunzsjXTJ3VlIF+B0qir370oEWq9dWvaLUYkC6QCHifJ+ZVaPp/pNln7S0zh/dTGO84aKwS0hF7S0uvTTiKRXHTBDmHnmSQuoG8Fku1iqbE9fkScIYPF2wO5N7UPgU86te8/to=", "sha256": "5bb39cebbb0ccb3d010513d8f2aec11d370d78214a40515a4b484a3c65eb58ad",
"urls": [ "urls": [
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.3_mac.dmg" "https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.4_mac.dmg"
] ]
} }
}, },
"solus": { "solus": {
"1.3.3": { "1.3.4": {
"sig": "BL1hmylvFtrY5GAJaZD4m2YAgOgf21N7aCRmQmVQqDpA5tLrLhaCxJaRwQaNTXrNFnwL3YHBXJQf5cJi43dVXI7o+rcltU5QDA2Q7DvzdlaMfL9saBzxLT0D94l017XvczaxhOlypEQIUzFVQYxGzdw3Kx6fKWFySeKgS4l+SB1FTXfeYhfp2ZApOdRB1Y9BzAIN0hATm/UBcBL12FlwCl4+YV6BEIz/07I8VypwxLx68dqGCE/ucTVCf/4D00oGcWEFfrIxBnGzSLFWSyvhvyJwJWvH4VIDIP2KwlhdEs/3p0ETzSl1YbV+bOeUbUGR6Nv2ljF62Y918WOXspiC6N2bDzvpBZkkpKs0xRih4AJQDHR6gs3/43XIqrnTrKMZbZVy2JnEkl3KNGmscLjzS8lus7Wmkn8l0qyOuGY30ef4q60q55VVUw4y+Ox66WbnMYpLpSCY56iNOjw0Kp+rpV7UreLFt5UffQuWWlOttrXs1XJKO9GYv8kbognKAf8gRr7iJWMbZWe8o4++P9ziwt4nd7JlcGzxKinntB4Jn/BHYJnJKGqHhA5j8EJ1QZlu3txruKdcOPV7RpXoFEvOJZ8rji7mUUrlPGM/UJ8JhLcr8P+goKza9dFcX3pFqVHOll4pNOmpPOE+ui+UPSk0I9oCBlEvHSMijywVnwjMh2Yb+VBbuC9Hw5qK2kMZgGXpysa40WrEO5dznJclRSDChIKWVJzdvqnQ2mkR7va+4YAi7Pi/7JnJkO1gcM7O0hGz2bHeG3j7xdUvf0pS8YCcEYojJ97xnBH5UWDYXPBnMsrBMy7mwu/TH8MEGtLiC+9k6THV4Xf9ooS2roHhVskkK5u3xhNFNLxeBVBnQvk7lHynjFbjVNa3URMsD5J5SzQ8O10UhAV8vnpTD70GO25htI1MZuMgV8KPZZ5pOlUMrxqgS14obph8TxMUrD3Wy6SZrwYj/0JTY0lkfhxaXuaecoKDVtCVmiTCcioFryvMymCuz0/0FV8ctlau5Ko3W2W0wmWCVmlDSU5xEybVDegSSnI6bWPQn1SgHUC6DpaWa5LOtCMBgA84AAEMRCMDIcc1EY/VAHz/NeU9UJFESdv/uniL2wcWRq72xPYVwD5fB5YEyFgHx3UXNxhtwMWeThEaOvmHp2snOksM6RSjXVcBLkzK7JQxb25AKEo0rpgHqcVZo+pRpl/6RyXmJWQb1Fl0N6/0fDzJUS3Ji1fAw4O9T2ftrrk1WUD/PsjdaXKRJfWDNl9iWN4gbv+KZAwx+BgF+oMD4b7bOU+cQnkISzT2WD8k/S7wKhrbyMGOWwPYHnebIj0TK0QR0+RWpcP5BggGe7/YpjizvVPGBySjYTcWcig=", "sig": "CQZdxHimuD3li5JcHKJC3zdqNdw18RXFBQ9fgtxXe5FpgPq3bycXrMjmqaJaz25fLPl++e33PSlnzdaC2ede9vrWSfqfB3UG5Vdxxth/YnsqV4YzgGwOed6v9F7YcR2wgts8FpCzy+vokGWu2lI05pCx4ObNgqyvbyGcvGdzgTylhF+MlgSDtXdDV6B1Ba9d+Oj8lh3eA2d7v09ltxavEoiQmrX0ODK9dov9axT3JT/RZTNpAhTSfTT5YUgXmYWYQjZKdwCbFc4k23C1V0SKivB8lQ26Eaaxegwse+Kybar6CzisSkvAlTza9OCo/pxmJ/YedReldyj8pbq7+BweG82JWm8HVl4Y+ppXej69zFtAgcv1QWMYyXmBTfzoKMEUhrH3Brnj3b4ssAG6MKH2Y+lzwhzDYhpNXZ/+jfknFZPaGQ4UgUd1Nf46dPk0Pt+x3tzcweRO6XacfI8e+PNf5YIk+CKxyGjoVJfz1dEAKKg1RL1RXqHPK44MTSrHAIyUPgRNUq6zX23VkfdT4b/IftaIYEEO738wT0nutAtg7RLo2jx6w3hTjDSmh9Wz/szhtS/pP670PycryqH/JaE/5n4lgvSIZN7Inz9WWHbttX54H36usqcEiKr5yoSeOvdIsjWpHguKxasRNbib+Idnhug/Zs8XmshUfwAH3Y+6Wy5dM+IGfuyFpwkfQE2DQmfriyyn2gQf1EJZ6lxuFy1q5vQ9pf3QOaEU72yCu63EEYiXEnPQKgMPkBXxgo6Rp/Nskth0F2XXUYDUPD3lzOJLNMKn3G7ta5EkmZZhrk94pm8X5DpXdxzxFr/lvlWNic5trhWhVtJfci+yxZTI9IiXEfLTck0oYzkpaSxgTvOZ4DklW92xk682F41eNmdo64NwyqeHYS7uvLsJa4aRmRVmsfbY5RuOrDlAmr+yDX9OaISFfhGgByqPlv/5Sm27GFCHFfkvPrUGQWBOFnoRrnr1VZqKr2xRImDUhsP2FNaZJH2E0lBh7P6WmFu2/N1XwJ9w3TX1pwQXGJ/XERSCj7w972VZ8YMj+LHZ4anpZHuY4eL9PAkgwEEpTIqUJuJ2Oj1LGG3GGlT/HrR7tSofybi6+tNsD4egdFZ4HI3+nrPFGbYTQNQzzxXmPnukdP66h4lGGz8bBVT/wezKCK06RrgUKoi+GxcndMnF6JlAzzmiEDh2CXHJsFIr5eDx+pgxbr4Ibz4Mwu9qfeN6fNC+EAkewC1W3f3HoX3oozAT1AobitkT6CV7rHUD4xHWih/7uA+9uuAI2VgxfpfzyvR5H7kd51p63sB1DyvzpB9ssf/E7vxyzQJJhDEOWXUHi0ciMgeElFgJMUI49xzGvJUizV6YmwE=",
"sha256": "03c29d95cb5406f90b076d633fd6a3e2f4a42a5bcf75433099453c0767acc761", "sha256": "ef29a5aad24f9f10cbac3e46b1be334a2f80b184efe0dcc09a5c1fa4526a68f4",
"urls": [ "urls": [
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.3_linux_x86_64.AppImage" "https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.4_linux_x86_64.AppImage"
] ]
} }
}, },
"win32": { "win32": {
"1.3.3": { "1.3.4": {
"sha256": "a86091d453600506cccb25759b62a638c482ce8983d24a5b3ec1def9545ae1c3", "sig": "BbUW6SbAl5pS+WF2kzmy0O8Zw6sdkP1TOP7GS78I+jNLgXzvFodQDsC2dqZclrtcqNvVLIdfHfZeRFYKsrJnEFmZc9Jib3+4rALdhsJY0kRzhgfZkhwydAIuRP/RTXJ4AKh8XgCuxoToPtytslSCgHN5LJmtDQL10naJrvk40n9Pkt0Kjep/8Bj1fc+7WMgtLaKJXiKjGlfax4/2UeMVyqTmkEbRPf9o8w3esfSfRvMwqAN1LnwW4ZbPDLVm1cRh9+BovCKEmVc28LvdtWY0pOios8K5NOI8GcfDmL+a3NsKow1GQPTqEMEa6/z+njdYehVVMeFpA8kI0UKljrQTf4n22BNE1ZzpBOoOCRxcrI4BeG5cy1VhzM7BUm+jaKxY/IuqdLNplXa7VSfRuI1sPVEegmQmDhZCJJXdugVC008UXO8HH731EHUCctMutQTCNLVHg1djyVTtuevQRalXEQMSY+VbAEP1ED3cwfJgwk92ZAVc323IjGNvjeDDlZW5sFUJUYBv94tfy8AWFDk2HnXG5r480fHwrI3ZoZh6cAssiV3OYaLjWUYlQkfWi7fIr43tEIquMxqcuMN+iRfxl+klvQt50xdeE+S/YWMRnixj0h+xSL79f0dryyYofwL924e8U8XSwxGvAPNiADc+VoyRNSqQnPtHn164VOfAPZCaIn8puex9LA5JYEhY9ayB7EW2YVd/Pfp7uI/KAzmngFuKyKG8KVvVE7qF7zSjX222SlweBRLm7f1QPthl0XzVgzzZJ8S1cHPJ//1unOsimQGmPhZDQrnhdFSrtkd2D1kX5dqmTKEx+wiL+Tm8cWFwPvg2nUfWlC9Zytpi9VjpbnczTQLW+JsPB7HOQ4ZYaWaxQATvb3kku3icWv9dY6gWa9Lc9N+gXbGkPV9R+Np0m92m9QA+TDf9HrZwzXLfBjUbfqrMvGhCdznJ9PFflbNQIsDLJeBFdoaHYX88IJIgom60Ef+EjUAhAsvW5A12g+4phENSIg2iv7Ifxc4l8KgtIyjLst9sBdhTDOH3J3Cscd+BxWVLeGjMWoMZ2mU6Bf6ohnO4Ofh9+oqmemHu2rrI3RxOSQF4w9sEv6AtD6C7TG3B+8qFdMic1XkRDxAsNHrwfKOxCytQ89fBH+fzNHRzGys7ADoTH0vm7FR7iADdy83Xt+i+7TK9KvUkR/W5cGgxRHDS1g4SdLetMf/FQ5olbyaW/OiGDtpzipS/Yv3QCMfn4XB3GahMPgLzu0uI9MNXFz9zjwwy0owgzP3Q6+ff3cn8QOIfa0RRLfJgyS71kbcEjHHRehYfuRHNi9/5NMF29irsbAxhWZwncCsoDRJvycwyz6dY+DZHkaDqUv0h8ew=",
"sig": "AeaJxYZ8LpeCAKJEeS0QmfwL894tHmJ2tHi4JRQPoPOzU2fxMSthgOIOLgeTIDuagVk4hbUeZ02Q/t53AQCcrgmyT9xxJy1/2gdkgBN4yLcD76dg9QsSIAT/D3W3oOwzvRu2GRHzKKjD+dzR2HDSbMz6YHsIJDBGqWEcd3PTJ0jREWpQm8slbNflnmMIgumRKzwws93fus4rxXqTTgl/wn01wgSbgrSjncuTRXQUtk63LofkH87g3ivLd3iHVUnCHYQlpKKUuyK1t5Prr7akcFno588k5VGmRYrXljcakobZVeG5ikCFl3+463Eq4Bf1AzMHS6jlSAyEigF10Ys0iD59DnoE2/q2GJekbW0Zjs7qWTQldJyvwa5lhsU5gOxgJYcydhmAu1jEw74jHu/XDFxIpsrfAZfowHTuItsv5kSjJAQleMtDmMnOPs+S+PDhgOTQjtksplnu3aSHdKKE88nkUdWhcddxTFqD1R6IKSBvPZGcRX17n64MnzWqq3yuSV0qAe0CgRWF7Cy48YMeaPk1V+yCkUSh2nvIkjfRc5rRv14Jh2sq03beMxnEC2n+PSV5WA85gWcmkl53iDJSfWATCq6M1rj+PzTwmnjagjnhgDDLolH/g/RWabUEA9ywG9xtJb2M0aCAY7sqeX8I5WEODkfmHTGvYi6E9L7xvpHwmYPxVr2Frs+PbKa8h5C4RLJZGQlia5Rw7i3g0byd9shpbRcrzoGEJmBbkzdMWgSwIVXPD+EqqfiTOdcmQy1JwsTF9zUx7auxXviAddr6HlXXFLt661b47WElRJGXgl1m9qi8eSYRIH0a5wF5XRU+LyCDllUTZhMNNGGP1U3azu3iYsOaYphobn/IVlZBJ4phoafr4D69PbOG+k9B4pTxwHeY7jmszUpiJMgonoftp9TLsLIzDcXBp0vEVoPE0fr1ljOAaQgdtsi0wr0Z2Nxtp5d04d/cYfvfOKx2M/uupdpmU/aBCnv79r9etnnEHhcQtpfCNAZ4567pYAiLiJ4zVEct715hZ6LN57Wx5YWfNT6aCUogWaLx0K5CaL9kv0HyllIoM5z1+7unKwmq4nSO5A1QAh67yCfPTISL2m1SIODmnYUqgBk95oX/AAEk+gaCil0b2A6JGxbI3OcUEyN4BZcWyJRL2QToEmUMNkUuK+aaQl5OTp2wNLFXO1wpqXQAVppDeY2nHqjUY9B2Wku2wFV61T1VZ9sYlGoFYjK7G6PQUsBCTmXAlpzLUjWadgWvKPyRehy4Bzdqwy+BuZ8BEC+txUDnh90jUqd9YCXGsMLEGa6EXijjCBRhqdfQTLZpd3n62YpMvXkfQ6lNnaI4ybqqXwuDpvB5Cvx3tk2VOwc=", "sha256": "8b1e9bd73e11e4f8ef66f417c8735ea48715f41e822d7f77852bf392ac5970c6",
"urls": [ "urls": [
"https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.3_win.exe" "https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.3.4_win.exe"
] ]
} }
} }
}, },
"Versions": { "Versions": {
"centos7": [ "centos7": [
"1.3.3" "1.3.4"
], ],
"darwin": [ "darwin": [
"1.3.3" "1.3.4"
], ],
"linux": [ "linux": [
"unavailable" "unavailable"
], ],
"solus": [ "solus": [
"1.3.3" "1.3.4"
], ],
"unknown": [ "unknown": [
"unavailable" "unavailable"
], ],
"win32": [ "win32": [
"1.3.3" "1.3.4"
] ]
} }
} }

View File

@@ -8,19 +8,3 @@
background-size: cover; background-size: cover;
} }
.AppContainer {
width: 100%;
height: 100%;
box-sizing: border-box;
}
.AppHeader {
height: 28px;
margin-bottom: var(--default_spacing);
box-sizing: border-box;
}
.AppContent {
height: calc(100% - 36px);
box-sizing: border-box;
}

View File

@@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import './App.css'; import './App.css';
import AddEditHost from './containers/AddEditHost/AddEditHost';
import Box from './components/UI/Box/Box'; import Box from './components/UI/Box/Box';
import Configuration from './containers/Configuration/Configuration'; import Configuration from './containers/Configuration/Configuration';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@@ -11,28 +12,22 @@ 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 NewReleases from './components/NewReleases/NewReleases.jsx';
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 { import {
setDismissNewReleasesAvailable, setDismissNewReleasesAvailable,
setNewReleasesAvailable setNewReleasesAvailable,
} from './redux/actions/release_version_actions'; } from './redux/actions/release_version_actions';
import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVersionDisplay'; import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVersionDisplay';
import { import { displaySelectAppPlatform, saveState } from './redux/actions/common_actions';
displaySelectAppPlatform,
saveState
} from './redux/actions/common_actions';
import SelectAppPlatform from './containers/SelectAppPlatform/SelectAppPlatform'; import SelectAppPlatform from './containers/SelectAppPlatform/SelectAppPlatform';
import Text from './components/UI/Text/Text'; import Text from './components/UI/Text/Text';
import UpgradeIcon from './components/UpgradeIcon/UpgradeIcon'; import UpgradeIcon from './components/UpgradeIcon/UpgradeIcon';
import UpgradeUI from './components/UpgradeUI/UpgradeUI'; import UpgradeUI from './components/UpgradeUI/UpgradeUI';
import { import { loadReleases, setDismissUIUpgrade } from './redux/actions/release_version_actions';
loadReleases,
setDismissUIUpgrade
} 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.jsx';
import SkynetImport from './containers/SkynetImport/SkynetImport'; import SkynetImport from './containers/SkynetImport/SkynetImport';
import ApplicationBusy from './components/ApplicationBusy/ApplicationBusy'; import ApplicationBusy from './components/ApplicationBusy/ApplicationBusy';
import SkynetExport from './containers/SkynetExport/SkynetExport'; import SkynetExport from './containers/SkynetExport/SkynetExport';
@@ -59,13 +54,17 @@ class App extends IPCContainer {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if ((prevProps.Release !== this.props.Release) || if (
(prevProps.ReleaseVersion !== this.props.ReleaseVersion) || prevProps.Release !== this.props.Release ||
(prevProps.VersionLookup !== this.props.VersionLookup)) { prevProps.ReleaseVersion !== this.props.ReleaseVersion ||
prevProps.VersionLookup !== this.props.VersionLookup
) {
this.props.saveState(); this.props.saveState();
} else if (Object.keys(this.props.ProviderState).filter(k => { } else if (
Object.keys(this.props.ProviderState).filter((k) => {
return this.props.ProviderState[k] !== prevProps.ProviderState[k]; return this.props.ProviderState[k] !== prevProps.ProviderState[k];
}).length > 0) { }).length > 0
) {
this.props.saveState(); this.props.saveState();
} }
} }
@@ -76,14 +75,16 @@ class App extends IPCContainer {
} }
getSelectedVersion = () => { getSelectedVersion = () => {
return (this.props.ReleaseVersion === -1) ? return this.props.ReleaseVersion === -1
'unavailable' : ? 'unavailable'
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 = () => { handleUpgradeIconClicked = () => {
if (this.props.UpgradeAvailable) { if (this.props.UpgradeAvailable) {
this.props.setDismissUIUpgrade(false) this.props.setDismissUIUpgrade(false);
} else if (this.props.NewReleasesAvailable2.length > 0) { } else if (this.props.NewReleasesAvailable2.length > 0) {
this.props.setNewReleasesAvailable(this.props.NewReleasesAvailable2); this.props.setNewReleasesAvailable(this.props.NewReleasesAvailable2);
this.props.setDismissNewReleasesAvailable(false); this.props.setDismissNewReleasesAvailable(false);
@@ -93,44 +94,51 @@ class App extends IPCContainer {
render() { render() {
const selectedVersion = this.getSelectedVersion(); const selectedVersion = this.getSelectedVersion();
const downloadEnabled = this.props.AllowDownload && const downloadEnabled =
this.props.AllowDownload &&
!this.props.MountsBusy && !this.props.MountsBusy &&
!this.props.DownloadActive && !this.props.DownloadActive &&
(selectedVersion !== 'unavailable') && selectedVersion !== 'unavailable' &&
(selectedVersion !== this.props.InstalledVersion); selectedVersion !== this.props.InstalledVersion;
const missingDependencies = (this.props.MissingDependencies.length > 0) && const missingDependencies = this.props.MissingDependencies.length > 0 && this.props.AllowMount;
this.props.AllowMount;
const allowMount = this.props.AllowMount && const allowMount =
this.props.AllowMount &&
this.props.InstalledVersion !== 'none' && this.props.InstalledVersion !== 'none' &&
!missingDependencies && !missingDependencies &&
!this.props.InstallActive; !this.props.InstallActive;
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] && const s3Supported =
this.props.LocationsLookup[selectedVersion] &&
this.props.LocationsLookup[selectedVersion].s3_support; this.props.LocationsLookup[selectedVersion].s3_support;
const skynetSupported = this.props.LocationsLookup[selectedVersion] && const skynetSupported =
this.props.LocationsLookup[selectedVersion] &&
this.props.LocationsLookup[selectedVersion].skynet_support; this.props.LocationsLookup[selectedVersion].skynet_support;
const scPrimeSupported = this.props.LocationsLookup[selectedVersion] && const scPrimeSupported =
this.props.LocationsLookup[selectedVersion] &&
this.props.LocationsLookup[selectedVersion].siaprime_support; this.props.LocationsLookup[selectedVersion].siaprime_support;
const siaSupported = this.props.LocationsLookup[selectedVersion] && const siaSupported =
this.props.LocationsLookup[selectedVersion] &&
this.props.LocationsLookup[selectedVersion].sia_support; this.props.LocationsLookup[selectedVersion].sia_support;
const showConfig = !missingDependencies && const showConfig =
!missingDependencies &&
!this.props.DisplayPinnedManager && !this.props.DisplayPinnedManager &&
this.props.DisplayConfiguration && this.props.DisplayConfiguration &&
!this.props.RebootRequired; !this.props.RebootRequired;
const showPinnedManager = !missingDependencies && const showPinnedManager =
!this.props.RebootRequired && !missingDependencies && !this.props.RebootRequired && this.props.DisplayPinnedManager;
this.props.DisplayPinnedManager;
const showUpgrade = this.props.UpgradeAvailable && const showUpgrade =
this.props.UpgradeAvailable &&
!this.props.DisplayError && !this.props.DisplayError &&
!showConfig && !showConfig &&
!this.props.DownloadActive && !this.props.DownloadActive &&
@@ -138,14 +146,16 @@ class App extends IPCContainer {
!this.props.InstallActive && !this.props.InstallActive &&
!this.props.RebootRequired; !this.props.RebootRequired;
const showDependencies = !showUpgrade && const showDependencies =
!showUpgrade &&
missingDependencies && missingDependencies &&
!this.props.DownloadActive && !this.props.DownloadActive &&
!this.props.RebootRequired && !this.props.RebootRequired &&
!this.props.DismissDependencies && !this.props.DismissDependencies &&
this.props.AllowMount; this.props.AllowMount;
const showNewReleases = !showConfig && const showNewReleases =
!showConfig &&
!this.props.DisplayConfirmYesNo && !this.props.DisplayConfirmYesNo &&
!showDependencies && !showDependencies &&
!this.props.DownloadActive && !this.props.DownloadActive &&
@@ -156,9 +166,19 @@ class App extends IPCContainer {
!this.props.DisplaySelectAppPlatform && !this.props.DisplaySelectAppPlatform &&
!showUpgrade && !showUpgrade &&
!this.props.DismissNewReleasesAvailable && !this.props.DismissNewReleasesAvailable &&
(this.props.NewReleasesAvailable.length > 0); this.props.NewReleasesAvailable.length > 0;
const showSkynetImport = !showConfig && const showAddEditHost =
!showDependencies &&
!this.props.DownloadActive &&
!showNewReleases &&
!this.props.RebootRequired &&
!this.props.DisplaySelectAppPlatform &&
!showUpgrade &&
this.props.DisplayAddEditHost;
const showSkynetImport =
!showConfig &&
!showDependencies && !showDependencies &&
!this.props.DownloadActive && !this.props.DownloadActive &&
!showNewReleases && !showNewReleases &&
@@ -167,7 +187,8 @@ class App extends IPCContainer {
!showUpgrade && !showUpgrade &&
this.props.DisplayImport; this.props.DisplayImport;
const showSkynetExport = !showConfig && const showSkynetExport =
!showConfig &&
!showDependencies && !showDependencies &&
!this.props.DownloadActive && !this.props.DownloadActive &&
!showNewReleases && !showNewReleases &&
@@ -176,97 +197,143 @@ class App extends IPCContainer {
!showUpgrade && !showUpgrade &&
this.props.DisplayExport; this.props.DisplayExport;
const configDisplay = createModalConditionally(showConfig, <Configuration const configDisplay = createModalConditionally(
showConfig,
<Configuration
version={selectedVersion} version={selectedVersion}
s3Supported={s3Supported} s3Supported={s3Supported}
remoteSupported={remoteSupported}/>); remoteSupported={remoteSupported}
const pinnedManagerDisplay = createModalConditionally(showPinnedManager, <PinnedManager />
version={selectedVersion}/>) );
const addEditHostDisplay = createModalConditionally(showAddEditHost, <AddEditHost />);
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, const dependencyDisplay = createModalConditionally(
<DependencyList/>, false, this.props.InstallActive); showDependencies,
const downloadDisplay = createModalConditionally(this.props.DownloadActive, <DependencyList />,
<DownloadProgress/>, false, true); 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 newReleasesDisplay = createModalConditionally(showNewReleases, <NewReleases />);
const rebootDisplay = createModalConditionally(this.props.RebootRequired, <Reboot />); const rebootDisplay = createModalConditionally(this.props.RebootRequired, <Reboot />);
const selectAppPlatformDisplay = createModalConditionally(this.props.DisplaySelectAppPlatform, const selectAppPlatformDisplay = createModalConditionally(
<SelectAppPlatform/>); this.props.DisplaySelectAppPlatform,
<SelectAppPlatform />
);
const upgradeDisplay = createModalConditionally(showUpgrade, <UpgradeUI />); const upgradeDisplay = createModalConditionally(showUpgrade, <UpgradeUI />);
const importDisplay = createModalConditionally(showSkynetImport, <SkynetImport const importDisplay = createModalConditionally(
version={selectedVersion}/>); showSkynetImport,
const exportDisplay = createModalConditionally(showSkynetExport, <SkynetExport <SkynetImport version={selectedVersion} />
version={selectedVersion}/>) );
const appBusyDisplay = createModalConditionally(this.props.AppBusy, const exportDisplay = createModalConditionally(
<ApplicationBusy/>, false, true, this.props.AppBusyTransparent); 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) {
mainContent = ( mainContent = (
<Box dxStyle={{height: '100%'}}> <Box col={0} colSpan={'remain'} row={10} rowSpan={'remain'}>
<Loading /> <Loading />
</Box>); </Box>
);
} else { } else {
let key = 0; let key = 0;
mainContent.push(( mainContent.push(
<Box key={'md_' + key++} <Box
dxStyle={{padding: 'var(--default_spacing)', height: 'auto'}}> col={0}
<ReleaseVersionDisplay downloadDisabled={!downloadEnabled} colSpan={'remain'}
version={selectedVersion}/> row={10}
rowSpan={17}
key={'md_' + key++}
dxStyle={{ padding: 'var(--default_spacing)' }}>
<ReleaseVersionDisplay downloadDisabled={!downloadEnabled} version={selectedVersion} />
</Box> </Box>
)); );
mainContent.push(<div key={'md_' + key++}
style={{paddingTop: 'var(--default_spacing)'}}/>);
if (allowMount) { if (allowMount) {
mainContent.push(( mainContent.push(
<Box dxStyle={{padding: 'var(--default_spacing)', height: 'auto'}} <Box
row={29}
rowSpan={'remain'}
colSpan={'remain'}
dxStyle={{ padding: 'var(--default_spacing)' }}
key={'md_' + key++}> key={'md_' + key++}>
<MountItems s3Supported={s3Supported} <MountItems
s3Supported={s3Supported}
remoteSupported={remoteSupported} remoteSupported={remoteSupported}
scPrimeSupported={scPrimeSupported} scPrimeSupported={scPrimeSupported}
siaSupported={siaSupported} siaSupported={siaSupported}
skynetSupported={skynetSupported}/> skynetSupported={skynetSupported}
/>
</Box> </Box>
)); );
} else if (!downloadEnabled && (selectedVersion !== 'unavailable')) { } else if (!downloadEnabled && selectedVersion !== 'unavailable') {
mainContent.push(( mainContent.push(
<Box dxStyle={{padding: 'var(--default_spacing)', height: '170px'}} <Box
col={0}
colSpan={'remain'}
row={29}
rowSpan={'remain'}
dxStyle={{ padding: 'var(--default_spacing)' }}
key={'md_' + key++}> key={'md_' + key++}>
<Loading /> <Loading />
</Box> </Box>
)); );
} }
} }
return ( return (
<div className={'App'}> <div className={'App'}>
<div className={'AppContainer'}>
<div className={'AppHeader'}>
<Box>
<Grid> <Grid>
<Text col={0} <Box col={0} colSpan={'remain'} row={0} rowSpan={8}>
<Grid noScroll>
<Text
col={0}
colSpan={'remain'} colSpan={'remain'}
row={0} row={0}
rowSpan={'remain'} rowSpan={8}
text={'Repertory UI v' + this.props.Version} text={'Repertory UI v' + this.props.Version}
textAlign={'center'} textAlign={'center'}
type={'Heading1'}/> type={'Heading1'}
/>
<UpgradeIcon <UpgradeIcon
available={!missingDependencies && (this.props.UpgradeAvailable || (this.props.NewReleasesAvailable2.length > 0))} available={
newReleases={!missingDependencies && (!this.props.UpgradeAvailable && (this.props.NewReleasesAvailable2.length > 0))} !missingDependencies &&
(this.props.UpgradeAvailable || this.props.NewReleasesAvailable2.length > 0)
}
newReleases={
!missingDependencies &&
!this.props.UpgradeAvailable &&
this.props.NewReleasesAvailable2.length > 0
}
clicked={this.handleUpgradeIconClicked} clicked={this.handleUpgradeIconClicked}
col={dimensions => dimensions.columns - 6} col={(dimensions) => dimensions.columns - 6}
colSpan={5} colSpan={1}
row={1} row={1}
rowSpan={remain => remain - 1}/> rowSpan={(remain) => remain - 1}
/>
</Grid> </Grid>
</Box> </Box>
</div>
<div className={'AppContent'}>
{mainContent} {mainContent}
</div> </Grid>
</div>
{importDisplay} {importDisplay}
{exportDisplay} {exportDisplay}
{newReleasesDisplay} {newReleasesDisplay}
@@ -275,6 +342,7 @@ class App extends IPCContainer {
{upgradeDisplay} {upgradeDisplay}
{pinnedManagerDisplay} {pinnedManagerDisplay}
{configDisplay} {configDisplay}
{addEditHostDisplay}
{infoDisplay} {infoDisplay}
{confirmDisplay} {confirmDisplay}
{downloadDisplay} {downloadDisplay}
@@ -286,7 +354,7 @@ class App extends IPCContainer {
} }
} }
const mapStateToProps = state => { const mapStateToProps = (state) => {
return { return {
AllowDownload: state.download.AllowDownload, AllowDownload: state.download.AllowDownload,
AllowMount: state.common.AllowMount, AllowMount: state.common.AllowMount,
@@ -295,6 +363,7 @@ const mapStateToProps = state => {
AppBusyTransparent: state.common.AppBusyTransparent, AppBusyTransparent: state.common.AppBusyTransparent,
AppReady: state.common.AppReady, AppReady: state.common.AppReady,
DismissDependencies: state.install.DismissDependencies, DismissDependencies: state.install.DismissDependencies,
DisplayAddEditHost: state.host.DisplayAddEditHost,
DisplayConfiguration: state.mounts.DisplayConfiguration, DisplayConfiguration: state.mounts.DisplayConfiguration,
DisplayConfirmYesNo: state.common.DisplayConfirmYesNo, DisplayConfirmYesNo: state.common.DisplayConfirmYesNo,
DisplayError: state.error.DisplayError, DisplayError: state.error.DisplayError,
@@ -324,15 +393,15 @@ const mapStateToProps = state => {
}; };
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = (dispatch) => {
return { return {
displaySelectAppPlatform: display => dispatch(displaySelectAppPlatform(display)), displaySelectAppPlatform: (display) => dispatch(displaySelectAppPlatform(display)),
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)), setDismissNewReleasesAvailable: (dismiss) => dispatch(setDismissNewReleasesAvailable(dismiss)),
setNewReleasesAvailable: items => dispatch(setNewReleasesAvailable(items)), setNewReleasesAvailable: (items) => dispatch(setNewReleasesAvailable(items)),
setDismissUIUpgrade: dismiss => dispatch(setDismissUIUpgrade(dismiss)), setDismissUIUpgrade: (dismiss) => dispatch(setDismissUIUpgrade(dismiss)),
}; };
}; };

View File

@@ -2,22 +2,25 @@ import React from 'react';
import Box from '../UI/Box/Box'; import Box from '../UI/Box/Box';
import Loader from 'react-loader-spinner'; import Loader from 'react-loader-spinner';
import Text from '../UI/Text/Text'; import Text from '../UI/Text/Text';
import PropTypes from 'prop-types';
const ApplicationBusy = ({ title }) => { const ApplicationBusy = ({ title }) => {
return ( return (
<Box dxStyle={{padding: 'var(--default_spacing)'}}> <Box dxDark dxStyle={{ padding: 'var(--default_spacing)' }}>
<Text <Text text={title || 'Please Wait...'} textAlign={'center'} type={'Heading1'} />
text={title || 'Please Wait...'} <div
textAlign={'center'} style={{
type={'Heading1'}/> paddingLeft: 'calc(50% - 16px)',
<div style={{paddingLeft: 'calc(50% - 16px)', paddingTop: 'var(--default_spacing)'}}> paddingTop: 'var(--default_spacing)',
<Loader color={'var(--heading_text_color)'} }}>
height={32} <Loader color={'var(--heading_text_color)'} height={32} width={32} type="TailSpin" />
width={32}
type='TailSpin'/>
</div> </div>
</Box> </Box>
); );
}; };
ApplicationBusy.propTypes = {
title: PropTypes.string,
};
export default ApplicationBusy; export default ApplicationBusy;

View File

@@ -1,17 +1,10 @@
import React from 'react'; import React from 'react';
import './Dependency.css'; import './Dependency.css';
import {connect} from 'react-redux';
import * as Constants from '../../../constants'; import * as Constants from '../../../constants';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
const mapStateToProps = state => { const Dependency = (props) => {
return {
AllowDownload: (state.download.DownloadType !== Constants.INSTALL_TYPES.Dependency) &&
!state.download.DownloadActive &&
!state.install.InstallActive,
};
};
export default connect(mapStateToProps)(props => {
return ( return (
<div className={'Dependency'}> <div className={'Dependency'}>
<table width="100%"> <table width="100%">
@@ -21,15 +14,40 @@ export default connect(mapStateToProps)(props => {
<h3>{props.name}</h3> <h3>{props.name}</h3>
</td> </td>
<td> <td>
{props.AllowDownload ? {props.AllowDownload ? (
<a href={'#'} <a
href={'#'}
className={'DependencyLink'} className={'DependencyLink'}
onClick={()=>{props.onDownload(); return false;}}><u>Install</u></a> : onClick={() => {
'Installing...'} props.onDownload();
return false;
}}>
<u>Install</u>
</a>
) : (
'Installing...'
)}
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
); );
}); };
const mapStateToProps = (state) => {
return {
AllowDownload:
state.download.DownloadType !== Constants.INSTALL_TYPES.Dependency &&
!state.download.DownloadActive &&
!state.install.InstallActive,
};
};
Dependency.propTypes = {
AllowDownload: PropTypes.bool,
name: PropTypes.string.isRequired,
onDownload: PropTypes.func.isRequired,
};
export default connect(mapStateToProps)(Dependency);

View File

@@ -1,3 +1,2 @@
.DependencyList { .DependencyList {
} }

View File

@@ -1,14 +1,61 @@
import React from 'react'; import React from 'react';
import './DependencyList.css'; import './DependencyList.css';
import {connect} from 'react-redux';
import * as Constants from '../../constants'; import * as Constants from '../../constants';
import Dependency from './Dependency/Dependency';
import Box from '../UI/Box/Box'; import Box from '../UI/Box/Box';
import Dependency from './Dependency/Dependency';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createDismissDisplay } from '../../utils.jsx';
import { downloadItem } from '../../redux/actions/download_actions'; import { downloadItem } from '../../redux/actions/download_actions';
import {extractFileNameFromURL} from '../../utils'; import { extractFileNameFromURL } from '../../utils.jsx';
import { setDismissDependencies } from '../../redux/actions/install_actions'; import { setDismissDependencies } from '../../redux/actions/install_actions';
const mapStateToProps = state => { const DependencyList = (props) => {
const items = props.MissingDependencies.map((k, i) => {
return (
<Dependency
key={i}
name={k.display}
onDownload={() =>
props.downloadItem(
extractFileNameFromURL(k.download),
Constants.INSTALL_TYPES.Dependency,
k.download,
k.is_winfsp
)
}
/>
);
});
return (
<Box dxStyle={{ width: '300px', height: 'auto', padding: '5px' }}>
{createDismissDisplay(
() => props.setDismissDependencies(true),
!props.AllowDismissDependencies
)}
<div
style={{
width: '100%',
height: 'auto',
paddingBottom: '5px',
boxSizing: 'border-box',
}}>
<h1
style={{
width: '100%',
textAlign: 'center',
color: 'var(--text_color_error)',
}}>
Missing Dependencies
</h1>
</div>
{items}
</Box>
);
};
const mapStateToProps = (state) => {
return { return {
AllowDismissDependencies: state.relver.AllowDismissDependencies, AllowDismissDependencies: state.relver.AllowDismissDependencies,
MissingDependencies: state.install.MissingDependencies, MissingDependencies: state.install.MissingDependencies,
@@ -18,34 +65,15 @@ const mapStateToProps = state => {
const mapDispatchToProps = (dispatch) => { const mapDispatchToProps = (dispatch) => {
return { return {
downloadItem: (name, type, url, isWinFSP) => dispatch(downloadItem(name, type, url, isWinFSP)), downloadItem: (name, type, url, isWinFSP) => dispatch(downloadItem(name, type, url, isWinFSP)),
setDismissDependencies: dismiss => dispatch(setDismissDependencies(dismiss)), setDismissDependencies: (dismiss) => dispatch(setDismissDependencies(dismiss)),
}; };
}; };
export default connect(mapStateToProps, mapDispatchToProps)(props => { DependencyList.propTypes = {
const items = props.MissingDependencies.map((k, i)=> { AllowDismissDependencies: PropTypes.bool,
return ( MissingDependencies: PropTypes.array,
<Dependency key={i} downloadItem: PropTypes.func.isRequired,
name={k.display} setDismissDependencies: PropTypes.func.isRequired,
onDownload={()=>props.downloadItem(extractFileNameFromURL(k.download), Constants.INSTALL_TYPES.Dependency, k.download, k.is_winfsp)}/> };
);
});
const dismissDisplay = ( export default connect(mapStateToProps, mapDispatchToProps)(DependencyList);
<div style={{float: 'right', margin: 0, paddingRight: '4px', boxSizing: 'border-box', display: 'block'}}>
<a href={'#'}
onClick={props.AllowDismissDependencies ? () => props.setDismissDependencies(true) : e => e.preventDefault()}
style={{cursor: props.AllowDismissDependencies ? 'pointer' : 'no-drop'}}>X</a>
</div>
);
return (
<Box dxStyle={{width: '300px', height: 'auto', padding: '5px'}}>
{dismissDisplay}
<div style={{width: '100%', height: 'auto', paddingBottom: '5px', boxSizing: 'border-box'}}>
<h1 style={{width: '100%', textAlign: 'center', color: 'var(--text_color_error)'}}>Missing Dependencies</h1>
</div>
{items}
</Box>
);
});

View File

@@ -1,9 +1,29 @@
import Box from '../UI/Box/Box'; import Box from '../UI/Box/Box';
import {connect} from 'react-redux';
import React from 'react';
import './DownloadProgress.css'; import './DownloadProgress.css';
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
const mapStateToProps = state => { const DownloadProgress = (props) => {
const width = props.Platform === 'linux' ? '480px' : '380px';
return (
<Box dxStyle={{ width: width, height: 'auto', padding: '5px' }}>
<div style={{ width: '100%', height: 'auto' }}>
<h1 style={{ width: '100%', textAlign: 'center' }}>
{'Downloading ' + props.DownloadName}
</h1>
</div>
<progress
max={100.0}
id={'download_progress'}
style={{ width: '100%' }}
value={props.DownloadProgress}
/>
</Box>
);
};
const mapStateToProps = (state) => {
return { return {
DownloadName: state.download.DownloadName, DownloadName: state.download.DownloadName,
DownloadProgress: state.download.DownloadProgress, DownloadProgress: state.download.DownloadProgress,
@@ -11,15 +31,10 @@ const mapStateToProps = state => {
}; };
}; };
export default connect(mapStateToProps)(props => { DownloadProgress.propTypes = {
const width = props.Platform === 'linux' ? '480px' : '380px'; DownloadName: PropTypes.string.isRequired,
return ( DownloadProgress: PropTypes.number,
<Box dxStyle={{width: width, height: 'auto', padding: '5px'}}> Platform: PropTypes.string.isRequired,
<div style={{width: '100%', height: 'auto'}}> };
<h1 style={{width: '100%', textAlign: 'center'}}>{'Downloading ' + props.DownloadName}</h1>
</div> export default connect(mapStateToProps)(DownloadProgress);
<progress max={100.0} id={'download_progress'}
style={{width: '100%'}}
value={props.DownloadProgress}/>
</Box>);
});

View File

@@ -1,23 +1,12 @@
import React from 'react'; import React from 'react';
import {dismissError} from '../../redux/actions/error_actions'; import './ErrorDetails.css';
import {connect} from 'react-redux';
import Box from '../UI/Box/Box'; import Box from '../UI/Box/Box';
import Button from '../UI/Button/Button'; import Button from '../UI/Button/Button';
import './ErrorDetails.css'; import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { dismissError } from '../../redux/actions/error_actions';
const mapStateToProps = state => { const ErrorDetails = (props) => {
return {
ErrorMessage: state.error.ErrorStack.length > 0 ? state.error.ErrorStack[0] : '',
};
};
const mapDispatchToProps = dispatch => {
return {
dismissError: () => dispatch(dismissError()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(props => {
return ( return (
<Box dxDark dxStyle={{ padding: 'var(--default_spacing)' }}> <Box dxDark dxStyle={{ padding: 'var(--default_spacing)' }}>
<h1 className={'ErrorDetailsHeading'}>Application Error</h1> <h1 className={'ErrorDetailsHeading'}>Application Error</h1>
@@ -27,4 +16,23 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
<Button clicked={props.dismissError}>Dismiss</Button> <Button clicked={props.dismissError}>Dismiss</Button>
</Box> </Box>
); );
}); };
const mapStateToProps = (state) => {
return {
ErrorMessage: state.error.ErrorStack.length > 0 ? state.error.ErrorStack[0] : '',
};
};
const mapDispatchToProps = (dispatch) => {
return {
dismissError: () => dispatch(dismissError()),
};
};
ErrorDetails.propTypes = {
ErrorMessage: PropTypes.string.isRequired,
dismissError: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(ErrorDetails);

View File

@@ -17,3 +17,9 @@
white-space: pre; white-space: pre;
hyphens: none; hyphens: none;
} }
.InfoButtonOwner {
display: flex;
flex-direction: row;
justify-content: flex-end;
}

View File

@@ -1,23 +1,13 @@
import React from 'react'; import React from 'react';
import {dismissInfo} from '../../redux/actions/error_actions'; import './InfoDetails.css';
import {connect} from 'react-redux';
import Box from '../UI/Box/Box'; import Box from '../UI/Box/Box';
import Button from '../UI/Button/Button'; import Button from '../UI/Button/Button';
import './InfoDetails.css'; import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { dismissInfo, notifyError } from '../../redux/actions/error_actions';
import { promptLocationAndSaveFile } from '../../utils';
const mapStateToProps = state => { const InfoDetails = (props) => {
return {
InfoMessage: state.error.InfoStack.length > 0 ? state.error.InfoStack[0] : '',
};
};
const mapDispatchToProps = dispatch => {
return {
dismissInfo: () => dispatch(dismissInfo()),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(props => {
let msg = props.InfoMessage.message; let msg = props.InfoMessage.message;
const classes = ['InfoDetailsContent']; const classes = ['InfoDetailsContent'];
@@ -37,7 +27,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
} }
} }
const scrollToTop = textArea => { const scrollToTop = (textArea) => {
if (!textArea.firstClick) { if (!textArea.firstClick) {
textArea.firstClick = true; textArea.firstClick = true;
textArea.scrollTop = 0; textArea.scrollTop = 0;
@@ -49,19 +39,62 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
<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={classes.join(' ')}> <div className={classes.join(' ')}>
{ {copyable ? (
copyable ? ( <textarea
<textarea autoFocus autoFocus
rows={9} rows={9}
onChange={() => {}}
value={msg} value={msg}
className={'SkynetImportTextArea'} className={'SkynetImportTextArea'}
onClick={e => scrollToTop(e.target)}/> onClick={(e) => scrollToTop(e.target)}
/>
) : ( ) : (
<p style={{ textAlign: 'left' }}>{msg}</p> <p style={{ textAlign: 'left' }}>{msg}</p>
) )}
}
</div> </div>
{props.InfoMessage.saveToFile ? (
<div className={'InfoButtonOwner'}>
<Button clicked={props.dismissInfo}>Dismiss</Button> <Button clicked={props.dismissInfo}>Dismiss</Button>
<Button
buttonStyles={{ marginLeft: 'var(--default_spacing)' }}
clicked={() => {
if (
promptLocationAndSaveFile(
props.InfoMessage.fileName + '.' + props.InfoMessage.extension,
msg,
props.notifyError
)
) {
props.dismissInfo();
}
}}>
Save...
</Button>
</div>
) : (
<Button clicked={props.dismissInfo}>Dismiss</Button>
)}
</Box> </Box>
); );
}); };
const mapStateToProps = (state) => {
return {
InfoMessage: state.error.InfoStack.length > 0 ? state.error.InfoStack[0] : '',
};
};
const mapDispatchToProps = (dispatch) => {
return {
notifyError: (msg) => dispatch(notifyError(msg)),
dismissInfo: () => dispatch(dismissInfo()),
};
};
InfoDetails.propTypes = {
InfoMessage: PropTypes.object.isRequired,
dismissInfo: PropTypes.func.isRequired,
notifyError: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(InfoDetails);

View File

@@ -1,74 +0,0 @@
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 mapStateToProps = state => {
return {
AllowInstall: state.install.MissingDependencies.length === 0,
};
};
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(mapStateToProps, mapDispatchToProps)(({
AllowInstall,
dismiss,
release,
lastItem,
notifyError,
notifyInfo,
installReleaseByVersion
}) => {
const title = '[' + Constants.RELEASE_TYPES[release.Release] + '] ' + release.Display;
const displayChanges = async () => {
try {
const lines = await getChangesForRepertoryVersion(release.VersionString);
notifyInfo(title, formatLinesForDisplay(lines));
} catch (e) {
notifyError(e);
}
};
// TODO Switch to activate
/*const installReleaseVersion = () => {
dismiss();
installReleaseByVersion(release.Release, release.Version);
};
{AllowInstall ?
<Button buttonStyles={{width: '100%'}}
clicked={installReleaseVersion}>Install</Button> : null}*/
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%">
</td>
</tr>
{lastItem ? null : <tr style={{height: 'var(--default_spacing)'}}/>}
</tbody>
</table>
</div>
);
});

View File

@@ -0,0 +1,98 @@
import React from 'react';
import * as Constants from '../../../constants';
import Button from '../../UI/Button/Button';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { formatLinesForDisplay, getChangesForRepertoryVersion } from '../../../utils.jsx';
import { notifyError, notifyInfo } from '../../../redux/actions/error_actions';
import { setActiveRelease } from '../../../redux/actions/release_version_actions';
import { unmountAll } from '../../../redux/actions/mount_actions';
const NewRelease = ({
ActiveRelease,
ActiveVersion,
dismiss,
release,
lastItem,
notifyError,
notifyInfo,
setActiveRelease,
unmountAll,
}) => {
const title = '[' + Constants.RELEASE_TYPES[release.Release] + '] ' + release.Display;
const displayChanges = async () => {
try {
const lines = await getChangesForRepertoryVersion(release.VersionString);
notifyInfo(title, formatLinesForDisplay(lines));
} catch (e) {
notifyError(e);
}
};
const isActiveRelease = release.Release === ActiveRelease && release.Version === ActiveVersion;
const setReleaseAndVersion = () => {
dismiss();
unmountAll(() => {
setActiveRelease(release.Release, release.Version);
});
};
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%">
{!isActiveRelease ? (
<Button buttonStyles={{ width: '100%' }} clicked={setReleaseAndVersion}>
Activate
</Button>
) : null}
</td>
</tr>
{lastItem ? null : <tr style={{ height: 'var(--default_spacing)' }} />}
</tbody>
</table>
</div>
);
};
const mapStateToProps = (state) => {
return {
ActiveRelease: state.relver.Release,
ActiveVersion: state.relver.Version,
};
};
const mapDispatchToProps = (dispatch) => {
return {
setActiveRelease: (release, version) => dispatch(setActiveRelease(release, version)),
notifyError: (msg) => dispatch(notifyError(msg)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)),
unmountAll: (completedCallback) => dispatch(unmountAll(completedCallback)),
};
};
NewRelease.propTypes = {
ActiveRelease: PropTypes.number.isRequired,
ActiveVersion: PropTypes.number.isRequired,
dismiss: PropTypes.func.isRequired,
lastItem: PropTypes.bool.isRequired,
notifyError: PropTypes.func.isRequired,
notifyInfo: PropTypes.func.isRequired,
release: PropTypes.object.isRequired,
setActiveRelease: PropTypes.func.isRequired,
unmountAll: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(NewRelease);

View File

@@ -1,38 +0,0 @@
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

@@ -0,0 +1,48 @@
import React from 'react';
import './NewReleases.css';
import Box from '../UI/Box/Box';
import Button from '../UI/Button/Button';
import NewRelease from './NewRelease/NewRelease.jsx';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { setDismissNewReleasesAvailable } from '../../redux/actions/release_version_actions';
const NewReleases = (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>
);
};
const mapStateToProps = (state) => {
return {
NewReleasesAvailable: state.relver.NewReleasesAvailable,
};
};
const mapDispatchToProps = (dispatch) => {
return {
dismissNewReleasesAvailable: () => dispatch(setDismissNewReleasesAvailable(true)),
};
};
NewReleases.propTypes = {
NewReleasesAvailable: PropTypes.array.isRequired,
dismissNewReleasesAvailable: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(NewReleases);

View File

@@ -1,17 +1,12 @@
import React from 'react'; import React from 'react';
import './Reboot.css'; import './Reboot.css';
import {connect} from 'react-redux';
import Box from '../UI/Box/Box'; import Box from '../UI/Box/Box';
import Button from '../UI/Button/Button'; import Button from '../UI/Button/Button';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { rebootSystem } from '../../redux/actions/common_actions'; import { rebootSystem } from '../../redux/actions/common_actions';
const mapDispatchToProps = dispatch => { const Reboot = (props) => {
return {
rebootSystem: () => dispatch(rebootSystem()),
};
};
export default connect(null, mapDispatchToProps)(props => {
return ( return (
<Box dxDark dxStyle={{ padding: 'var(--default_spacing)' }}> <Box dxDark dxStyle={{ padding: 'var(--default_spacing)' }}>
<h1 className={'RebootHeading'}>Reboot System</h1> <h1 className={'RebootHeading'}>Reboot System</h1>
@@ -21,4 +16,16 @@ export default connect(null, mapDispatchToProps)(props => {
<Button clicked={() => props.rebootSystem()}>Reboot Now</Button> <Button clicked={() => props.rebootSystem()}>Reboot Now</Button>
</Box> </Box>
); );
}); };
const mapDispatchToProps = (dispatch) => {
return {
rebootSystem: () => dispatch(rebootSystem()),
};
};
Reboot.propTypes = {
rebootSystem: PropTypes.func.isRequired,
};
export default connect(null, mapDispatchToProps)(Reboot);

View File

@@ -1,16 +1,165 @@
import React from 'react'; import React from 'react';
import './ReleaseVersionDisplay.css'; import './ReleaseVersionDisplay.css';
import * as Constants from '../../constants'; import * as Constants from '../../constants';
import {connect} from 'react-redux'; import Button from '../UI/Button/Button';
import DropDown from '../UI/DropDown/DropDown'; import DropDown from '../UI/DropDown/DropDown';
import Grid from '../UI/Grid/Grid'; import Grid from '../UI/Grid/Grid';
import PropTypes from 'prop-types';
import Text from '../UI/Text/Text'; import Text from '../UI/Text/Text';
import Button from '../UI/Button/Button';
import UpgradeIcon from '../UpgradeIcon/UpgradeIcon'; import UpgradeIcon from '../UpgradeIcon/UpgradeIcon';
import {setActiveRelease} from '../../redux/actions/release_version_actions'; import { connect } from 'react-redux';
import { downloadItem } from '../../redux/actions/download_actions'; import { downloadItem } from '../../redux/actions/download_actions';
import { setActiveRelease } from '../../redux/actions/release_version_actions';
const mapStateToProps = state => { const ReleaseVersionDisplay = (props) => {
const getSelectedVersion = () => {
return props.ReleaseVersion === -1
? 'unavailable'
: props.VersionLookup[Constants.RELEASE_TYPES[props.Release]][props.ReleaseVersion];
};
const handleDownloadRelease = () => {
const fileName = props.version + '.zip';
props.downloadItem(
fileName,
Constants.INSTALL_TYPES.Release,
props.LocationsLookup[props.version].urls
);
};
const handleReleaseChanged = (e) => {
const release = Constants.RELEASE_TYPES.indexOf(e.target.value);
const releaseVersion = props.VersionLookup[Constants.RELEASE_TYPES[release]].length - 1;
props.setActiveRelease(release, releaseVersion);
};
const handleVersionChanged = (e) => {
const releaseVersion = props.VersionLookup[Constants.RELEASE_TYPES[props.Release]].indexOf(
e.target.value
);
props.setActiveRelease(props.Release, releaseVersion);
};
const text = props.InstalledVersion + ' [' + props.AppPlatform + ']';
const disabled =
props.DownloadActive ||
props.InstallActive ||
props.MountsBusy ||
(!props.AllowMount && getSelectedVersion() !== 'unavailable');
const releaseExtracting = props.InstallType === Constants.INSTALL_TYPES.Release;
let optionsDisplay = [];
let key = 0;
if (releaseExtracting) {
optionsDisplay.push(
<Text
col={(dimensions) => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
rowSpan={4}
text={'Activating'}
textAlign={'left'}
type={'Heading2'}
/>
);
optionsDisplay.push(
<Text
col={(dimensions) => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
row={5}
rowSpan={7}
text={text}
textAlign={'left'}
/>
);
} else if (props.downloadDisabled || props.DismissDependencies) {
optionsDisplay.push(
<Text
col={(dimensions) => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
rowSpan={4}
text={'Installed'}
textAlign={'left'}
type={'Heading2'}
/>
);
optionsDisplay.push(
<Text
col={(dimensions) => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
row={5}
rowSpan={7}
text={text}
textAlign={'left'}
/>
);
} else {
optionsDisplay.push(
<Button
clicked={handleDownloadRelease}
col={(dimensions) => (dimensions.columns / 3) * 2}
colSpan={20}
key={key++}
row={5}
rowSpan={7}>
Install
</Button>
);
}
return (
<Grid noScroll>
<Text
colSpan={(columns) => columns / 3}
rowSpan={4}
text={'Release'}
textAlign={'left'}
type={'Heading2'}
/>
<DropDown
changed={handleReleaseChanged}
colSpan={(remain) => remain / 3 - 1}
disabled={disabled}
items={Constants.RELEASE_TYPES}
row={5}
rowSpan={7}
selected={Constants.RELEASE_TYPES[props.Release]}
/>
<Text
col={(dimensions) => dimensions.columns / 3}
colSpan={(remain) => remain / 2}
rowSpan={4}
text={'Version'}
textAlign={'left'}
type={'Heading2'}
/>
<UpgradeIcon
available={props.ReleaseUpgradeAvailable}
col={(dimensions) => (dimensions.columns / 3) * 2 - 6}
colSpan={4}
release
rowSpan={4}
/>
<DropDown
changed={handleVersionChanged}
col={(dimensions) => dimensions.columns / 3}
colSpan={(remain) => remain / 2 - 1}
disabled={disabled}
items={props.VersionLookup[Constants.RELEASE_TYPES[props.Release]]}
row={5}
rowSpan={7}
selected={props.VersionLookup[Constants.RELEASE_TYPES[props.Release]][props.ReleaseVersion]}
/>
{optionsDisplay}
</Grid>
);
};
const mapStateToProps = (state) => {
return { return {
AllowMount: state.common.AllowMount, AllowMount: state.common.AllowMount,
AppPlatform: state.common.AppPlatform, AppPlatform: state.common.AppPlatform,
@@ -28,129 +177,31 @@ const mapStateToProps = state => {
}; };
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = (dispatch) => {
return { return {
downloadItem: (name, type, urls) => dispatch(downloadItem(name, type, urls)), downloadItem: (name, type, urls) => dispatch(downloadItem(name, type, urls)),
setActiveRelease: (release, version) => dispatch(setActiveRelease(release, version)), setActiveRelease: (release, version) => dispatch(setActiveRelease(release, version)),
} };
}; };
export default connect(mapStateToProps, mapDispatchToProps)(props => { ReleaseVersionDisplay.propTypes = {
const getSelectedVersion = () => { AllowMount: PropTypes.bool,
return (props.ReleaseVersion === -1) ? AppPlatform: PropTypes.string.isRequired,
'unavailable' : DismissDependencies: PropTypes.bool.isRequired,
props.VersionLookup[Constants.RELEASE_TYPES[props.Release]][props.ReleaseVersion]; DownloadActive: PropTypes.bool,
InstallActive: PropTypes.bool,
InstallType: PropTypes.string,
InstalledVersion: PropTypes.string,
LocationsLookup: PropTypes.object.isRequired,
MountsBusy: PropTypes.bool,
Release: PropTypes.number.isRequired,
ReleaseUpgradeAvailable: PropTypes.bool,
ReleaseVersion: PropTypes.number.isRequired,
VersionLookup: PropTypes.object.isRequired,
downloadDisabled: PropTypes.bool,
downloadItem: PropTypes.func.isRequired,
setActiveRelease: PropTypes.func.isRequired,
version: PropTypes.string.isRequired,
}; };
const handleDownloadRelease = () => { export default connect(mapStateToProps, mapDispatchToProps)(ReleaseVersionDisplay);
const fileName = props.version + '.zip';
props.downloadItem(fileName, Constants.INSTALL_TYPES.Release, props.LocationsLookup[props.version].urls);
};
const handleReleaseChanged = e => {
const release = Constants.RELEASE_TYPES.indexOf(e.target.value);
const releaseVersion = props.VersionLookup[Constants.RELEASE_TYPES[release]].length - 1;
props.setActiveRelease(release, releaseVersion);
};
const handleVersionChanged = e => {
const releaseVersion = props.VersionLookup[Constants.RELEASE_TYPES[props.Release]].indexOf(e.target.value);
props.setActiveRelease(props.Release, releaseVersion);
};
const text = props.InstalledVersion + ' [' + props.AppPlatform + ']';
const disabled = props.DownloadActive ||
props.InstallActive ||
props.MountsBusy ||
(!props.AllowMount && (getSelectedVersion() !== 'unavailable')) ;
const releaseExtracting = (props.InstallType === Constants.INSTALL_TYPES.Release);
let optionsDisplay = [];
let key = 0;
if (releaseExtracting) {
optionsDisplay.push((
<Text col={dimensions => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
rowSpan={4}
text={'Activating'}
textAlign={'left'}
type={'Heading2'}/>
));
optionsDisplay.push((
<Text col={dimensions => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
row={5}
rowSpan={7}
text={text}
textAlign={'left'}/>
));
} else if (props.downloadDisabled || props.DismissDependencies) {
optionsDisplay.push((
<Text col={dimensions => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
rowSpan={4}
text={'Installed'}
textAlign={'left'}
type={'Heading2'}/>
));
optionsDisplay.push((
<Text col={dimensions => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
row={5}
rowSpan={7}
text={text}
textAlign={'left'}/>
));
} else {
optionsDisplay.push((
<Button clicked={handleDownloadRelease}
col={dimensions => (dimensions.columns / 3) * 2}
colSpan={20}
key={key++}
row={5}
rowSpan={7}>Install</Button>
));
}
return (
<Grid noScroll>
<Text colSpan={columns=>columns / 3}
rowSpan={4}
text={'Release'}
textAlign={'left'}
type={'Heading2'}/>
<DropDown changed={handleReleaseChanged}
colSpan={remain=>remain / 3 - 1}
disabled={disabled}
items={Constants.RELEASE_TYPES}
row={5}
rowSpan={7}
selected={Constants.RELEASE_TYPES[props.Release]}/>
<Text col={dimensions => dimensions.columns / 3}
colSpan={remain=>remain / 2}
rowSpan={4}
text={'Version'}
textAlign={'left'}
type={'Heading2'}/>
<UpgradeIcon available={props.ReleaseUpgradeAvailable}
col={dimensions => ((dimensions.columns / 3) * 2) - 6}
colSpan={4}
release
rowSpan={4}/>
<DropDown changed={handleVersionChanged}
col={dimensions => dimensions.columns / 3}
colSpan={remain=>remain / 2 - 1}
disabled={disabled}
items={props.VersionLookup[Constants.RELEASE_TYPES[props.Release]]}
row={5}
rowSpan={7}
selected={props.VersionLookup[Constants.RELEASE_TYPES[props.Release]][props.ReleaseVersion]}/>
{optionsDisplay}
</Grid>
);
});

View File

@@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import './Box.css'; import './Box.css';
import PropTypes from 'prop-types';
const Box = props => { const Box = (props) => {
const styleList = []; const styleList = [];
styleList.push('Box'); styleList.push('Box');
if (props.dxDark) { if (props.dxDark) {
@@ -17,13 +18,20 @@ const Box = props => {
} }
return ( return (
<div <div onClick={props.clicked} className={styleList.join(' ')} style={{ ...props.dxStyle }}>
onClick={props.clicked}
className={styleList.join(' ')}
style={{...props.dxStyle}}>
{props.children} {props.children}
</div> </div>
); );
}; };
Box.propTypes = {
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
clicked: PropTypes.func,
dxDark: PropTypes.bool,
dxFadeIn: PropTypes.bool,
dxSlideOut: PropTypes.bool,
dxSlideOutTop: PropTypes.bool,
dxStyle: PropTypes.object,
};
export default Box; export default Box;

View File

@@ -1,14 +1,26 @@
import React from 'react'; import React from 'react';
import './Button.css'; import './Button.css';
import PropTypes from 'prop-types';
const Button = props => { const Button = (props) => {
return ( return (
<button disabled={props.disabled} <button
disabled={props.disabled}
autoFocus={props.autoFocus} 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>
); );
}; };
Button.propTypes = {
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string]),
autoFocus: PropTypes.bool,
buttonStyles: PropTypes.object,
clicked: PropTypes.func,
disabled: PropTypes.bool,
};
export default Button; export default Button;

View File

@@ -18,7 +18,7 @@ label.CheckBoxLabel {
} }
/* Hide the browser's default checkbox */ /* Hide the browser's default checkbox */
label.CheckBoxLabel input[type=checkbox] { label.CheckBoxLabel input[type='checkbox'] {
position: absolute; position: absolute;
opacity: 0; opacity: 0;
cursor: pointer; cursor: pointer;
@@ -41,7 +41,7 @@ label.CheckBoxLabel input[type=checkbox] {
} }
/* On mouse-over, add a grey background color */ /* On mouse-over, add a grey background color */
label.CheckBoxLabel:hover input[type=checkbox] ~ .CheckBoxCheckMark { label.CheckBoxLabel:hover input[type='checkbox'] ~ .CheckBoxCheckMark {
background-color: var(--control_background_hover); background-color: var(--control_background_hover);
} }
@@ -52,7 +52,7 @@ label.CheckBoxLabel input:checked ~ .CheckBoxCheckMark {
/* Create the CheckBoxCheckMark/indicator (hidden when not checked) */ /* Create the CheckBoxCheckMark/indicator (hidden when not checked) */
.CheckBoxCheckMark:after { .CheckBoxCheckMark:after {
content: ""; content: '';
position: absolute; position: absolute;
display: none; display: none;
} }

View File

@@ -1,19 +1,31 @@
import React from 'react'; import React from 'react';
import './CheckBox.css'; import './CheckBox.css';
import PropTypes from 'prop-types';
const CheckBox = props => { const CheckBox = (props) => {
return ( return (
<div className={'CheckBoxOwner'}> <div className={'CheckBoxOwner'}>
<label className='CheckBoxLabel'>{props.label} <label className="CheckBoxLabel">
<input checked={JSON.parse(props.checked)} {props.label}
<input
checked={JSON.parse(props.checked)}
autoFocus={props.autoFocus} autoFocus={props.autoFocus}
disabled={props.disabled} disabled={props.disabled}
onChange={props.changed} onChange={props.changed}
type='checkbox'/> type="checkbox"
<span className='CheckBoxCheckMark'/> />
<span className="CheckBoxCheckMark" />
</label> </label>
</div> </div>
); );
}; };
CheckBox.propTypes = {
autoFocus: PropTypes.bool,
checked: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
disabled: PropTypes.bool,
label: PropTypes.string,
changed: PropTypes.func,
};
export default CheckBox; export default CheckBox;

View File

@@ -6,7 +6,8 @@
padding: 0; padding: 0;
} }
.DropDownSelect, .DropDownSelect.Alt { .DropDownSelect,
.DropDownSelect.Alt {
width: 100%; width: 100%;
height: 100%; height: 100%;
display: block; display: block;

View File

@@ -1,10 +1,13 @@
import React from 'react'; import React from 'react';
import './DropDown.css'; import './DropDown.css';
import PropTypes from 'prop-types';
const DropDown = props => { const DropDown = (props) => {
const options = props.items.map((s, i) => { const options = props.items.map((s, i) => {
return ( return (
<option className={'DropDownOption'} key={i} value={s}>{s}</option> <option className={'DropDownOption'} key={i} value={s}>
{s}
</option>
); );
}); });
@@ -20,7 +23,16 @@ const DropDown = props => {
</select> </select>
</div> </div>
); );
};
DropDown.propTypes = {
alt: PropTypes.bool,
auto: PropTypes.bool,
autoFocus: PropTypes.bool,
changed: PropTypes.func,
disabled: PropTypes.bool,
items: PropTypes.array,
selected: PropTypes.string,
}; };
export default DropDown; export default DropDown;

View File

@@ -1,23 +1,29 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import './Grid.css'; import './Grid.css';
import GridComponent from './GridComponent/GridComponent'; import GridComponent from './GridComponent/GridComponent';
import PropTypes from 'prop-types';
const DEFAULT_GRID_SIZE = 4; const DEFAULT_GRID_SIZE = 4;
export default class Grid extends Component { class Grid extends Component {
constructor(props) {
super(props);
this.sizeRef = React.createRef();
}
resizeTimeout; resizeTimeout;
state = { state = {
calculated: false, calculated: false,
dimensions: { dimensions: {
columns: 0, columns: 0,
rows: 0 rows: 0,
} },
}; };
calculateDimensions = size => { calculateDimensions = (size) => {
return { return {
columns: Math.floor(size.width / this.getGridSize()), columns: Math.floor(size.width / this.getGridSize()),
rows: Math.floor(size.height / this.getGridSize()) rows: Math.floor(size.height / this.getGridSize()),
}; };
}; };
@@ -29,7 +35,7 @@ export default class Grid extends Component {
componentWillUnmount() { componentWillUnmount() {
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
clearInterval(this.resizeTimeout); clearInterval(this.resizeTimeout);
}; }
getGridSize = () => { getGridSize = () => {
return this.props.gridSize || DEFAULT_GRID_SIZE; return this.props.gridSize || DEFAULT_GRID_SIZE;
@@ -40,10 +46,10 @@ export default class Grid extends Component {
}; };
getSize = () => { getSize = () => {
const elem = this.refs.GridOwner; const elem = this.sizeRef.current;
return { return {
height: elem ? elem.offsetHeight : 0, height: elem ? elem.offsetHeight : 0,
width: elem ? elem.offsetWidth : 0 width: elem ? elem.offsetWidth : 0,
}; };
}; };
@@ -54,15 +60,15 @@ export default class Grid extends Component {
updateSize = () => { updateSize = () => {
const state = { const state = {
...this.state ...this.state,
}; };
const size = this.getSize(); const size = this.getSize();
const dimensions = this.calculateDimensions(size); const dimensions = this.calculateDimensions(size);
if (state.dimensions !== dimensions) { if (state.dimensions !== dimensions) {
this.setState({ this.setState({
calculated: true, calculated: true,
dimensions: dimensions dimensions: dimensions,
}) });
} }
}; };
@@ -79,34 +85,30 @@ export default class Grid extends Component {
children = React.Children.map(this.props.children, (child, i) => { children = React.Children.map(this.props.children, (child, i) => {
if (child) { if (child) {
let row = child.props.row || 0; let row = child.props.row || 0;
if (typeof(row) === 'function') { if (typeof row === 'function') {
row = row(dimensions); row = row(dimensions);
} }
let col = child.props.col || 0; let col = child.props.col || 0;
if (typeof(col) === 'function') { if (typeof col === 'function') {
col = col(dimensions); col = col(dimensions);
} }
let rowSpan = child.props.rowSpan; let rowSpan = child.props.rowSpan;
if (typeof(rowSpan) === 'function') { if (typeof rowSpan === 'function') {
rowSpan = rowSpan(dimensions.rows - row, dimensions.rows); rowSpan = rowSpan(dimensions.rows - row, dimensions.rows);
} }
let colSpan = child.props.colSpan; let colSpan = child.props.colSpan;
if (typeof(colSpan) === 'function') { if (typeof colSpan === 'function') {
colSpan = colSpan(dimensions.columns - col, dimensions.columns); colSpan = colSpan(dimensions.columns - col, dimensions.columns);
} }
rowSpan = rowSpan ? (rowSpan === 'remain' ? (dimensions.rows - row) : rowSpan) : null; rowSpan = rowSpan ? (rowSpan === 'remain' ? dimensions.rows - row : rowSpan) : null;
colSpan = colSpan ? (colSpan === 'remain' ? dimensions.columns - col : colSpan) : null; colSpan = colSpan ? (colSpan === 'remain' ? dimensions.columns - col : colSpan) : null;
return ( return (
<GridComponent row={row} <GridComponent row={row} col={col} rowSpan={rowSpan} colSpan={colSpan} key={'gc_' + i}>
col={col}
rowSpan={rowSpan}
colSpan={colSpan}
key={'gc_' + i}>
{child} {child}
</GridComponent> </GridComponent>
); );
@@ -123,7 +125,7 @@ export default class Grid extends Component {
gridTemplateRows: gridSizePx.repeat(dimensions.rows), gridTemplateRows: gridSizePx.repeat(dimensions.rows),
gridAutoColumns: gridSizePx, gridAutoColumns: gridSizePx,
gridAutoRows: gridSizePx, gridAutoRows: gridSizePx,
} },
}; };
if (this.props.noScroll) { if (this.props.noScroll) {
@@ -132,13 +134,19 @@ export default class Grid extends Component {
} }
return ( return (
<div <div ref={this.sizeRef} className={'GridOwner'}>
ref='GridOwner'
className={'GridOwner'}>
<div className={'Grid'} {...style}> <div className={'Grid'} {...style}>
{children} {children}
</div> </div>
</div> </div>
) );
} }
}
Grid.propTypes = {
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array, PropTypes.string]),
gridSize: PropTypes.number,
noScroll: PropTypes.bool,
}; };
export default Grid;

View File

@@ -1,14 +1,15 @@
import React from 'react'; import React from 'react';
import './GridComponent.css'; import './GridComponent.css';
import PropTypes from 'prop-types';
const GridComponent = props => { const GridComponent = (props) => {
const style = { const style = {
style: { style: {
gridRowStart: Math.floor(props.row + 1), gridRowStart: Math.floor(props.row + 1),
gridRowEnd: 'span ' + Math.floor(props.rowSpan || 1), gridRowEnd: 'span ' + Math.floor(props.rowSpan || 1),
gridColumnStart: Math.floor(props.col + 1), gridColumnStart: Math.floor(props.col + 1),
gridColumnEnd: 'span ' + Math.floor(props.colSpan || 1) gridColumnEnd: 'span ' + Math.floor(props.colSpan || 1),
} },
}; };
return ( return (
@@ -16,7 +17,14 @@ const GridComponent = props => {
{props.children} {props.children}
</div> </div>
); );
};
GridComponent.propTypes = {
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
col: PropTypes.number.isRequired,
colSpan: PropTypes.number,
row: PropTypes.number.isRequired,
rowSpan: PropTypes.number,
}; };
export default GridComponent; export default GridComponent;

View File

@@ -10,7 +10,8 @@
margin: 0; margin: 0;
padding: 0; padding: 0;
position: relative; position: relative;
top: 50%; left: 50%; top: 50%;
left: 50%;
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
width: 28px; width: 28px;
height: 28px; height: 28px;

View File

@@ -1,18 +1,15 @@
import React from 'react'; import React from 'react';
import './Loading.css' import './Loading.css';
import Loader from 'react-loader-spinner'; import Loader from 'react-loader-spinner';
const Loading = () => { const Loading = () => {
return ( return (
<div <div className={'Loading'}>
className={'Loading'}>
<div className={'LoadingContent'}> <div className={'LoadingContent'}>
<Loader color={'var(--heading_text_color)'} <Loader color={'var(--heading_text_color)'} height={28} width={28} type="ThreeDots" />
height={28}
width={28}
type='ThreeDots'/>
</div> </div>
</div>); </div>
);
}; };
export default Loading; export default Loading;

View File

@@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import './Modal.css';
import './Modal.css'
import FocusTrap from 'focus-trap-react'; import FocusTrap from 'focus-trap-react';
import PropTypes from 'prop-types';
const Modal = props => { const Modal = (props) => {
let modalStyles = []; let modalStyles = [];
let contentStyles = []; let contentStyles = [];
modalStyles.push('Modal'); modalStyles.push('Modal');
@@ -19,15 +19,19 @@ const Modal = props => {
return ( return (
<FocusTrap active={!props.disableFocusTrap}> <FocusTrap active={!props.disableFocusTrap}>
<div <div className={modalStyles.join(' ')} onClick={props.clicked}>
className={modalStyles.join(' ')} <div className={contentStyles.join(' ')}>{props.children}</div>
onClick={props.clicked}>
<div className={contentStyles.join(' ')}>
{props.children}
</div>
</div> </div>
</FocusTrap> </FocusTrap>
); );
}; };
Modal.propTypes = {
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
clicked: PropTypes.func,
critical: PropTypes.bool,
disableFocusTrap: PropTypes.bool,
transparent: PropTypes.bool,
};
export default Modal; export default Modal;

View File

@@ -1,11 +1,16 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
const RootElem = props => { const RootElem = (props) => {
return ( return (
<div style={{ margin: 0, padding: 0 }} {...props}> <div style={{ margin: 0, padding: 0 }} {...props}>
{props.children} {props.children}
</div> </div>
) );
};
RootElem.propTypes = {
children: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
}; };
export default RootElem; export default RootElem;

View File

@@ -1,7 +1,8 @@
import React from 'react';
import './Text.css'; import './Text.css';
import PropTypes from 'prop-types';
import React from 'react';
const Text = props => { const Text = (props) => {
const styleList = []; const styleList = [];
styleList.push('Text'); styleList.push('Text');
if (props.type) { if (props.type) {
@@ -14,15 +15,20 @@ const Text = props => {
} }
const text = ( const text = (
<div <div className={styleList.join(' ')} style={style}>
className={styleList.join(' ')} {props.text}
style={style}>{props.text} </div>
</div>); );
return props.noOwner ? text : ( return props.noOwner ? text : <div className={'TextOwner'}>{text}</div>;
<div className={'TextOwner'}> };
{text}
</div>); Text.propTypes = {
noOwner: PropTypes.bool,
style: PropTypes.object,
text: PropTypes.string,
textAlign: PropTypes.string,
type: PropTypes.string,
}; };
export default Text; export default Text;

View File

@@ -1,10 +1,11 @@
import React from 'react';
import './UpgradeIcon.css'; import './UpgradeIcon.css';
import PropTypes from 'prop-types';
import React from 'react';
import ReactTooltip from 'react-tooltip'; import ReactTooltip from 'react-tooltip';
import {faExclamationTriangle} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
const UpgradeIcon = props => { const UpgradeIcon = (props) => {
const styles = ['UpgradeIcon']; const styles = ['UpgradeIcon'];
let placement = 'left'; let placement = 'left';
let toolTipText = 'UI Upgrade Available'; let toolTipText = 'UI Upgrade Available';
@@ -17,21 +18,25 @@ const UpgradeIcon = props => {
toolTipText = 'New Release Available'; toolTipText = 'New Release Available';
} }
return props return props.available ? (
.available ?
(
<div className={'UpgradeIconOwner'}> <div className={'UpgradeIconOwner'}>
<p data-tip='' data-for={placement}> <p data-tip="" data-for={placement}>
<a href={'#'} <a href={'#'} className={styles.join(' ')} onClick={props.clicked}>
className={styles.join(' ')}
onClick={props.clicked}>
<FontAwesomeIcon icon={faExclamationTriangle} /> <FontAwesomeIcon icon={faExclamationTriangle} />
</a> </a>
</p> </p>
<ReactTooltip id={placement} place={placement}>{toolTipText}</ReactTooltip> <ReactTooltip id={placement} place={placement}>
{toolTipText}
</ReactTooltip>
</div> </div>
) ) : null;
: null; };
UpgradeIcon.propTypes = {
available: PropTypes.bool,
clicked: PropTypes.func,
newReleases: PropTypes.bool,
release: PropTypes.bool,
}; };
export default UpgradeIcon; export default UpgradeIcon;

View File

@@ -1,34 +1,21 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import Button from '../UI/Button/Button';
import Box from '../UI/Box/Box';
import * as Constants from '../../constants';
import React from 'react';
import './UpgradeUI.css'; import './UpgradeUI.css';
import {setDismissUIUpgrade} from '../../redux/actions/release_version_actions'; import * as Constants from '../../constants';
import Box from '../UI/Box/Box';
import Button from '../UI/Button/Button';
import PropTypes from 'prop-types';
import React from 'react';
import { downloadItem } from '../../redux/actions/download_actions'; import { downloadItem } from '../../redux/actions/download_actions';
import { setDismissUIUpgrade } from '../../redux/actions/release_version_actions';
const mapStateToProps = state => { const UpgradeUI = (props) => {
return {
Platform: state.common.Platform,
UpgradeData: state.relver.UpgradeData,
UpgradeVersion: state.relver.UpgradeVersion,
}
};
const mapDispatchToProps = dispatch => {
return {
downloadItem: (name, type, urls) => dispatch(downloadItem(name, type, urls)),
setDismissUIUpgrade: dismiss => dispatch(setDismissUIUpgrade(dismiss)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(props => {
const handleDownload = () => { const handleDownload = () => {
const name = (props.Platform === 'win32') ? const name =
'upgrade.exe' : props.Platform === 'win32'
(props.Platform === 'darwin') ? ? 'upgrade.exe'
'upgrade.dmg' : : props.Platform === 'darwin'
'repertory-ui_' + props.UpgradeVersion + '_linux_x86_64.AppImage'; ? 'upgrade.dmg'
: 'repertory-ui_' + props.UpgradeVersion + '_linux_x86_64.AppImage';
props.downloadItem(name, Constants.INSTALL_TYPES.Upgrade, props.UpgradeData.urls); props.downloadItem(name, Constants.INSTALL_TYPES.Upgrade, props.UpgradeData.urls);
}; };
@@ -41,15 +28,45 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
<tbody> <tbody>
<tr> <tr>
<td width="50%"> <td width="50%">
<Button buttonStyles={{width: '100%'}} <Button buttonStyles={{ width: '100%' }} clicked={handleDownload}>
clicked={handleDownload}>Install</Button> Install
</Button>
</td> </td>
<td width="50%"> <td width="50%">
<Button buttonStyles={{width: '100%'}} <Button
clicked={() => props.setDismissUIUpgrade(true)}>Cancel</Button> buttonStyles={{ width: '100%' }}
clicked={() => props.setDismissUIUpgrade(true)}>
Cancel
</Button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</Box>); </Box>
}); );
};
const mapStateToProps = (state) => {
return {
Platform: state.common.Platform,
UpgradeData: state.relver.UpgradeData,
UpgradeVersion: state.relver.UpgradeVersion,
};
};
const mapDispatchToProps = (dispatch) => {
return {
downloadItem: (name, type, urls) => dispatch(downloadItem(name, type, urls)),
setDismissUIUpgrade: (dismiss) => dispatch(setDismissUIUpgrade(dismiss)),
};
};
UpgradeUI.propTypes = {
Platform: PropTypes.string.isRequired,
UpgradeData: PropTypes.object.isRequired,
UpgradeVersion: PropTypes.string.isRequired,
downloadItem: PropTypes.func.isRequired,
setDismissUIUpgrade: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(UpgradeUI);

View File

@@ -1,25 +1,19 @@
import {connect} from 'react-redux';
import Button from '../UI/Button/Button';
import Box from '../UI/Box/Box';
import React from 'react'; import React from 'react';
import './YesNo.css'; import './YesNo.css';
import {hideConfirmYesNo} from '../../redux/actions/common_actions'; import Box from '../UI/Box/Box';
import Button from '../UI/Button/Button';
import PropTypes from 'prop-types';
import { confirmYesNoAction } from '../../redux/actions/common_actions';
import { connect } from 'react-redux';
const mapStateToProps = state => { const YesNo = (props) => {
return {
Title: state.common.ConfirmTitle,
}
};
const mapDispatchToProps = dispatch => {
return {
hideConfirmYesNo: confirmed => dispatch(hideConfirmYesNo(confirmed)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(props => {
return ( return (
<Box dxStyle={{minWidth: '180px', height: 'auto', padding: 'var(--default_spacing)'}}> <Box
dxStyle={{
minWidth: '180px',
height: 'auto',
padding: 'var(--default_spacing)',
}}>
<div style={{ width: '100%', height: 'auto' }}> <div style={{ width: '100%', height: 'auto' }}>
<h1 style={{ width: '100%', textAlign: 'center' }}>{props.Title}</h1> <h1 style={{ width: '100%', textAlign: 'center' }}>{props.Title}</h1>
</div> </div>
@@ -27,15 +21,37 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
<tbody> <tbody>
<tr> <tr>
<td width="50%"> <td width="50%">
<Button buttonStyles={{width: '100%'}} <Button buttonStyles={{ width: '100%' }} clicked={() => props.confirm(true)}>
clicked={() => props.hideConfirmYesNo(true)}>Yes</Button> Yes
</Button>
</td> </td>
<td width="50%"> <td width="50%">
<Button buttonStyles={{width: '100%'}} <Button buttonStyles={{ width: '100%' }} clicked={() => props.confirm(false)}>
clicked={() => props.hideConfirmYesNo(false)}>No</Button> No
</Button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</Box>); </Box>
}); );
};
const mapStateToProps = (state) => {
return {
Title: state.common.ConfirmTitle,
};
};
const mapDispatchToProps = (dispatch) => {
return {
confirm: (confirmed) => dispatch(confirmYesNoAction.complete(confirmed)),
};
};
YesNo.propTypes = {
Title: PropTypes.string.isRequired,
confirm: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(YesNo);

View File

@@ -32,19 +32,15 @@ const _REPERTORY_UI_BRANCH = 'master';
exports.REPERTORY_BRANCH = _REPERTORY_BRANCH; exports.REPERTORY_BRANCH = _REPERTORY_BRANCH;
exports.REPERTORY_UI_BRANCH = _REPERTORY_UI_BRANCH; exports.REPERTORY_UI_BRANCH = _REPERTORY_UI_BRANCH;
exports.RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory/raw/' + exports.RELEASES_URL =
_REPERTORY_BRANCH + '/releases_1.3.json'; 'https://bitbucket.org/blockstorage/repertory/raw/' + _REPERTORY_BRANCH + '/releases_1.3.json';
exports.UI_RELEASES_URL = exports.UI_RELEASES_URL =
'https://bitbucket.org/blockstorage/repertory-ui/raw/' + 'https://bitbucket.org/blockstorage/repertory-ui/raw/' + _REPERTORY_UI_BRANCH + '/releases.json';
_REPERTORY_UI_BRANCH + '/releases.json';
exports.LINUX_DETECT_SCRIPT_URL = exports.LINUX_DETECT_SCRIPT_URL =
'https://bitbucket.org/blockstorage/repertory/raw/' + _REPERTORY_BRANCH + 'https://bitbucket.org/blockstorage/repertory/raw/' + _REPERTORY_BRANCH + '/detect_linux2.sh';
'/detect_linux2.sh';
exports.LINUX_SELECTABLE_PLATFORMS = [ exports.LINUX_SELECTABLE_PLATFORMS = ['centos7'];
'centos7',
];
exports.WINFSP_VERSION_NAMES = [ exports.WINFSP_VERSION_NAMES = [
'WinFsp 2019', 'WinFsp 2019',
@@ -61,6 +57,7 @@ exports.WINFSP_VERSION_NAMES = [
'WinFsp 2020.0', 'WinFsp 2020.0',
'WinFsp 2020.1', 'WinFsp 2020.1',
'WinFsp 2020.2', 'WinFsp 2020.2',
'WinFsp 2021',
'WinFsp 2021 B1', 'WinFsp 2021 B1',
'WinFsp 2021 B2', 'WinFsp 2021 B2',
'WinFsp 2021 Beta1', 'WinFsp 2021 Beta1',
@@ -70,32 +67,31 @@ exports.WINFSP_VERSION_NAMES = [
exports.DATA_LOCATIONS = { exports.DATA_LOCATIONS = {
linux: '~/.local/repertory/ui', linux: '~/.local/repertory/ui',
darwin: '~/Library/Application Support/repertory/ui', darwin: '~/Library/Application Support/repertory/ui',
win32 : '%LOCALAPPDATA%\\repertory\\ui' win32: '%LOCALAPPDATA%\\repertory\\ui',
}; };
exports.REPERTORY_LOCATIONS = { exports.REPERTORY_LOCATIONS = {
linux: '~/.local/repertory', linux: '~/.local/repertory',
darwin: '~/Library/Application Support/repertory', darwin: '~/Library/Application Support/repertory',
win32 : '%LOCALAPPDATA%\\repertory' win32: '%LOCALAPPDATA%\\repertory',
}; };
exports.S3_PROVIDER_LIST = [ const _S3_CUSTOM_PROVIDER = 'Custom...';
'Filebase', exports.S3_CUSTOM_PROVIDER = _S3_CUSTOM_PROVIDER;
]; exports.S3_PROVIDER_LIST = ['Filebase', _S3_CUSTOM_PROVIDER];
const _S3_CUSTOM_REGION = 'Custom...';
exports.S3_CUSTOM_REGION = _S3_CUSTOM_REGION;
exports.S3_REGION_PROVIDER_REGION = [ exports.S3_REGION_PROVIDER_REGION = [
'us-east-1', ['us-east-1', _S3_CUSTOM_REGION],
['any', _S3_CUSTOM_REGION],
]; ];
exports.S3_PROVIDER_URL = { exports.S3_PROVIDER_URL = {
'Filebase' : 'https://s3.filebase.com', Filebase: 'https://s3.filebase.com',
}; };
exports.PROVIDER_LIST = [ exports.PROVIDER_LIST = ['Sia', 'Skynet', 'ScPrime'];
'Sia',
'Skynet',
'ScPrime',
];
exports.PROVIDER_ARG = { exports.PROVIDER_ARG = {
sia: '', sia: '',
@@ -105,12 +101,7 @@ exports.PROVIDER_ARG = {
}; };
exports.DEFAULT_RELEASE = 0; exports.DEFAULT_RELEASE = 0;
exports.RELEASE_TYPES = [ exports.RELEASE_TYPES = ['Release', 'RC', 'Beta', 'Alpha'];
'Release',
'RC',
'Beta',
'Alpha',
];
exports.INSTALL_TYPES = { exports.INSTALL_TYPES = {
Dependency: 'dependency', Dependency: 'dependency',
@@ -120,6 +111,9 @@ exports.INSTALL_TYPES = {
}; };
exports.IPC_Browse_Directory = 'browse_directory'; exports.IPC_Browse_Directory = 'browse_directory';
exports.IPC_Browse_File = 'browse_file';
exports.IPC_Select_File = 'select_file';
exports.IPC_Check_Daemon_Version = 'check_daemon_version'; exports.IPC_Check_Daemon_Version = 'check_daemon_version';
exports.IPC_Check_Daemon_Version_Reply = 'check_daemon_version_reply'; exports.IPC_Check_Daemon_Version_Reply = 'check_daemon_version_reply';
@@ -185,11 +179,14 @@ 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_Read_File = 'read_file';
exports.IPC_Remove_Mount = 'remove_mount'; exports.IPC_Remove_Mount = 'remove_mount';
exports.IPC_Remove_Mount_Reply = 'remove_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_File = 'save_file';
exports.IPC_Save_State = 'save_state'; exports.IPC_Save_State = 'save_state';
exports.IPC_Set_Pinned = 'set_pinned'; exports.IPC_Set_Pinned = 'set_pinned';
@@ -203,6 +200,9 @@ exports.IPC_Set_Linux_AppPlatform = 'IPC_Set_Linux_AppPlatform';
exports.IPC_Shutdown = 'shutdown'; exports.IPC_Shutdown = 'shutdown';
exports.IPC_Skynet_Test_Logon = 'skynet_test_logon';
exports.IPC_Skynet_Test_Logon_Reply = 'skynet_test_logon_reply';
exports.IPC_Test_Release = 'test_release'; exports.IPC_Test_Release = 'test_release';
exports.IPC_Test_Release_Reply = 'test_release_reply'; exports.IPC_Test_Release_Reply = 'test_release_reply';

View File

@@ -0,0 +1,291 @@
import React from 'react';
import Box from '../../components/UI/Box/Box';
import Button from '../../components/UI/Button/Button';
import DropDown from '../../components/UI/DropDown/DropDown';
import IPCContainer from '../IPCContainer/IPCContainer';
import PropTypes from 'prop-types';
import Text from '../../components/UI/Text/Text';
import { addEditHostAction } from '../../redux/actions/host_actions';
import { connect } from 'react-redux';
import { createDismissDisplay } from '../../utils.jsx';
import { notifyApplicationBusy } from '../../redux/actions/common_actions';
import { notifyError, notifyInfo } from '../../redux/actions/error_actions';
const Constants = require('../../constants');
class AddEditHost extends IPCContainer {
state = {
AgentString: '',
ApiPassword: '',
ApiPort: 443,
AuthPassword: '',
AuthURL: '',
AuthUser: '',
HostNameOrIp: '',
Path: '',
Protocol: 'https',
TimeoutMs: 60000,
};
componentDidMount() {
this.setRequestHandler(Constants.IPC_Skynet_Test_Logon_Reply, this.onSkynetTestLogonReply);
if (this.props.HostData) {
this.setState({ ...this.state, ...this.props.HostData });
}
}
componentWillUnmount() {
super.componentWillUnmount();
}
handleSave = () => {
if (this.state.HostNameOrIp.trim().length === 0) {
this.props.notifyError('Host / IP cannot be empty');
return;
}
if (this.state.HostNameOrIp.trim().indexOf('/') >= 0) {
this.props.notifyError(`Host / IP cannot be contain '/'`);
return;
}
if (this.state.HostNameOrIp.trim().indexOf(':') >= 0) {
this.props.notifyError(`Host / IP cannot be contain ':'`);
return;
}
if (
this.props.HostList.find(
(i) =>
(i.HostNameOrIp || '').trim() === this.state.HostNameOrIp &&
i.Protocol === this.state.Protocol &&
i.ApiPort == this.state.ApiPort
)
) {
this.props.notifyError(`Portal already exists`);
return;
}
this.props.completeAddEditHost(this.state);
};
handleTestLogon = () => {
try {
this.props.notifyApplicationBusy(true);
this.sendRequest(Constants.IPC_Skynet_Test_Logon, {
Version: this.props.InstalledVersion,
AuthURL: this.state.AuthURL,
AuthUser: this.state.AuthUser,
AuthPassword: this.state.AuthPassword,
});
} catch (e) {
this.props.notifyApplicationBusy(false);
this.props.notifyError(e);
}
};
onSkynetTestLogonReply = (_, arg) => {
this.props.notifyApplicationBusy(false);
if (arg.data.Success) {
this.props.notifyInfo('Logon was successful!');
} else {
this.props.notifyError(arg.data.Error);
}
};
render() {
const allowTestLogon = this.state.AuthURL && this.state.AuthUser;
return (
<Box dxDark dxStyle={{ width: '430px', height: 'auto', padding: '5px' }}>
{createDismissDisplay(this.props.Close)}
<div
style={{
width: '100%',
height: 'auto',
paddingBottom: '5px',
boxSizing: 'border-box',
}}>
<h1
style={{
width: '100%',
textAlign: 'center',
color: 'var(--text_color_error)',
}}>
Portal Settings
</h1>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Text text={'Host / IP'} textAlign={'left'} type={'Heading2'} />
</div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<input
onChange={(e) => this.setState({ HostNameOrIp: e.target.value.trim() })}
className={'ConfigurationItemInput'}
style={{ width: '100%' }}
type={'text'}
value={this.state.HostNameOrIp}
/>
</div>
<div style={{ height: 'var(--default_spacing)' }} />
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Text text={'Protocol'} textAlign={'left'} type={'Heading2'} />
<div style={{ paddingLeft: 'var(--default_spacing)' }} />
<Text text={'Port'} textAlign={'left'} type={'Heading2'} />
<div style={{ paddingLeft: 'var(--default_spacing)' }} />
<Text text={'Timeout (ms)'} textAlign={'left'} type={'Heading2'} />
</div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<DropDown
changed={(e) => this.setState({ Protocol: e.target.value })}
items={['https', 'http']}
selected={this.state.Protocol}
/>
<div style={{ width: 'var(--default_spacing)' }} />
<div style={{ width: 'var(--default_spacing)' }} />
<input
onChange={(e) => this.setState({ ApiPort: parseInt(e.target.value) })}
className={'ConfigurationItemInput'}
style={{ width: '100%' }}
type={'number'}
min={1}
max={65535}
value={this.state.ApiPort}
/>
<div style={{ width: 'var(--default_spacing)' }} />
<div style={{ width: 'var(--default_spacing)' }} />
<input
onChange={(e) => this.setState({ TimeoutMs: parseInt(e.target.value) })}
className={'ConfigurationItemInput'}
style={{ width: '100%' }}
type={'number'}
min={1000}
step={1000}
value={this.state.TimeoutMs}
/>
</div>
<div style={{ height: 'var(--default_spacing)' }} />
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Text text={'Agent String (optional)'} textAlign={'left'} type={'Heading2'} />
<div style={{ width: 'var(--default_spacing)' }} />
<Text text={'API Key (optional)'} textAlign={'left'} type={'Heading2'} />
</div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<input
onChange={(e) => this.setState({ AgentString: e.target.value })}
className={'ConfigurationItemInput'}
style={{ flex: '1' }}
type={'text'}
value={this.state.AgentString}
/>
<div style={{ width: 'var(--default_spacing)' }} />
<input
onChange={(e) => this.setState({ ApiPassword: e.target.value })}
className={'ConfigurationItemInput'}
style={{ flex: '1' }}
type={'text'}
value={this.state.ApiPassword}
/>
</div>
<div style={{ height: 'var(--default_spacing)' }} />
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Text
noOwner
text={'Authentication URL (premium)'}
textAlign={'left'}
type={'Heading2'}
style={{ marginRight: 'auto' }}
/>
{allowTestLogon ? (
<a
href={'#'}
onClick={(e) => {
this.handleTestLogon();
e.preventDefault();
}}>
<u>test logon</u>
</a>
) : null}
</div>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<input
onChange={(e) => this.setState({ AuthURL: e.target.value })}
className={'ConfigurationItemInput'}
type={'text'}
value={this.state.AuthURL}
/>
</div>
<div style={{ height: 'var(--default_spacing)' }} />
<div style={{ display: 'flex', flexDirection: 'row' }}>
<Text text={'User Name (premium)'} textAlign={'left'} type={'Heading2'} />
<div style={{ width: 'var(--default_spacing)' }} />
<Text text={'Password (premium)'} textAlign={'left'} type={'Heading2'} />
</div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<input
onChange={(e) => this.setState({ AuthUser: e.target.value })}
className={'ConfigurationItemInput'}
style={{ flex: '1' }}
type={'text'}
value={this.state.AuthUser}
/>
<div style={{ width: 'var(--default_spacing)' }} />
<input
onChange={(e) => this.setState({ AuthPassword: e.target.value })}
className={'ConfigurationItemInput'}
style={{ flex: '1' }}
type={'text'}
value={this.state.AuthPassword}
/>
</div>
<div style={{ height: 'var(--default_spacing)' }} />
<p>
<b>
{'Portal URL: ' +
this.state.Protocol +
'://' +
this.state.HostNameOrIp +
((this.state.Protocol === 'http' && this.state.ApiPort != 80) ||
(this.state.Protocol === 'https' && this.state.ApiPort != 443)
? ':' + this.state.ApiPort.toString()
: '')}
</b>
</p>
<div style={{ height: 'var(--default_spacing)' }} />
</div>
<Button clicked={this.handleSave}>Save</Button>
</Box>
);
}
}
const mapStateToProps = (state) => {
return {
HostData: state.host.HostData,
HostList: state.host.HostList,
InstalledVersion: state.relver.InstalledVersion,
};
};
const mapDispatchToProps = (dispatch) => {
return {
Close: () => dispatch(addEditHostAction.complete(false)),
completeAddEditHost: (host_data) => dispatch(addEditHostAction.complete(true, { host_data })),
notifyApplicationBusy: (busy) => dispatch(notifyApplicationBusy(busy, true)),
notifyError: (msg) => dispatch(notifyError(msg)),
notifyInfo: (msg) => dispatch(notifyInfo(msg)),
};
};
AddEditHost.propTypes = {
Close: PropTypes.func.isRequired,
HostData: PropTypes.object,
HostList: PropTypes.array.isRequired,
InstalledVersion: PropTypes.string.isRequired,
completeAddEditHost: PropTypes.func.isRequired,
notifyApplicationBusy: PropTypes.func.isRequired,
notifyError: PropTypes.func.isRequired,
notifyInfo: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(AddEditHost);

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types';
import { Component } from 'react'; import { Component } from 'react';
import './AddMount.css'; import './AddMount.css';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@@ -7,25 +8,10 @@ import Box from '../../components/UI/Box/Box';
import Text from '../../components/UI/Text/Text'; import Text from '../../components/UI/Text/Text';
import { notifyError } from '../../redux/actions/error_actions'; import { notifyError } from '../../redux/actions/error_actions';
import { addRemoteMount, addS3Mount } from '../../redux/actions/mount_actions'; import { addRemoteMount, addS3Mount } from '../../redux/actions/mount_actions';
import {createModalConditionally} from '../../utils'; import { createModalConditionally } from '../../utils.jsx';
import DropDown from '../../components/UI/DropDown/DropDown'; import DropDown from '../../components/UI/DropDown/DropDown';
import * as Constants from '../../constants'; 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 = { const default_state = {
AccessKey: '', AccessKey: '',
BucketName: '', BucketName: '',
@@ -35,12 +21,14 @@ const default_state = {
Name: '', Name: '',
Port: 20000, Port: 20000,
Provider: Constants.S3_PROVIDER_LIST[0], Provider: Constants.S3_PROVIDER_LIST[0],
Region: Constants.S3_REGION_PROVIDER_REGION[0], Region: Constants.S3_REGION_PROVIDER_REGION[0][0],
SecretKey: '', SecretKey: '',
Token: '', Token: '',
CustomURL: '',
CustomRegion: '',
}; };
export default connect(mapStateToProps, mapDispatchToProps)(class extends Component { class AddMount extends Component {
state = { state = {
...default_state, ...default_state,
}; };
@@ -53,39 +41,59 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends Compon
if (this.props.RemoteMounts.includes(provider)) { if (this.props.RemoteMounts.includes(provider)) {
this.props.notifyError('Remote host already exists'); this.props.notifyError('Remote host already exists');
} else { } else {
this.setState({ this.setState(
DisplayRemote: false {
}, () => { DisplayRemote: false,
},
() => {
this.props.addRemoteMount(this.state.HostNameOrIp, this.state.Port, this.state.Token); this.props.addRemoteMount(this.state.HostNameOrIp, this.state.Port, this.state.Token);
this.setState({ this.setState({
...default_state, ...default_state,
}); });
}); }
);
} }
} }
}; };
addS3Mount = () => { addS3Mount = () => {
const isCustomProvider = this.state.Provider === Constants.S3_CUSTOM_PROVIDER;
const isCustomRegion = this.state.Region === Constants.S3_CUSTOM_REGION;
if (this.state.Name.length === 0) { if (this.state.Name.length === 0) {
this.props.notifyError('Name cannot be empty.'); this.props.notifyError('Name cannot be empty.');
} else if (this.state.AccessKey.length === 0) { } else if (this.state.AccessKey.length === 0) {
this.props.notifyError('AccessKey cannot be empty.'); this.props.notifyError('AccessKey cannot be empty.');
} else if (this.state.SecretKey.length === 0) { } else if (this.state.SecretKey.length === 0) {
this.props.notifyError('SecretKey cannot be empty.') this.props.notifyError('SecretKey cannot be empty.');
} else if (isCustomProvider && !this.state.CustomURL.trim()) {
this.props.notifyError('Custom URL cannot be empty.');
} else if (isCustomRegion && !this.state.CustomRegion.trim()) {
this.props.notifyError('Custom Region cannot be empty.');
} else { } else {
const provider = 'S3' + this.state.Name; const provider = 'S3' + this.state.Name;
if (this.props.S3Mounts.includes(provider)) { if (this.props.S3Mounts.includes(provider)) {
this.props.notifyError('Remote host already exists'); this.props.notifyError('Remote host already exists');
} else { } else {
this.setState({ this.setState(
DisplayS3: false {
}, () => { 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.props.addS3Mount(
this.state.Name,
this.state.AccessKey,
this.state.SecretKey,
isCustomRegion ? this.state.CustomRegion : this.state.Region,
this.state.BucketName,
isCustomProvider
? this.state.CustomURL
: Constants.S3_PROVIDER_URL[this.state.Provider]
);
this.setState({ this.setState({
...default_state, ...default_state,
}); });
}); }
);
} }
} }
}; };
@@ -105,142 +113,257 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends Compon
}; };
render() { render() {
const displayAddRemote = createModalConditionally(this.state.DisplayRemote, ( const displayAddRemote = createModalConditionally(
<Box dxDark this.state.DisplayRemote,
dxStyle={{width: 'auto', height: 'auto', padding: 'var(--default_spacing)'}}> <Box
<h1 style={{textAlign: 'center', paddingBottom: 'var(--default_spacing)'}}>Add Remote dxDark
Mount</h1> dxStyle={{
<Text text={'Hostname or IP'} width: 'auto',
textAlign={'left'} height: 'auto',
type={'Heading2'}/> padding: 'var(--default_spacing)',
<input onChange={e => this.setState({HostNameOrIp: e.target.value.trim()})} }}>
<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'} className={'ConfigurationItemInput'}
type={'text'} type={'text'}
value={this.state.HostNameOrIp}/> value={this.state.HostNameOrIp}
/>
<div style={{ paddingTop: 'var(--default_spacing)' }} /> <div style={{ paddingTop: 'var(--default_spacing)' }} />
<Text text={'Port'} <Text text={'Port'} textAlign={'left'} type={'Heading2'} />
textAlign={'left'} <input
type={'Heading2'}/> max={65535}
<input max={65535}
min={1025} min={1025}
onChange={e => this.setState({Port: e.target.value})} onChange={(e) => this.setState({ Port: e.target.value })}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'number'} type={'number'}
value={this.state.Port}/> value={this.state.Port}
/>
<div style={{ paddingTop: 'var(--default_spacing)' }} /> <div style={{ paddingTop: 'var(--default_spacing)' }} />
<Text text={'Remote Token'} <Text text={'Remote Token'} textAlign={'left'} type={'Heading2'} />
textAlign={'left'} <input
type={'Heading2'}/> onChange={(e) => this.setState({ Token: e.target.value })}
<input onChange={e => this.setState({Token: e.target.value})}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'text'} type={'text'}
value={this.state.Token}/> value={this.state.Token}
/>
<div style={{ paddingTop: 'var(--default_spacing)' }} /> <div style={{ paddingTop: 'var(--default_spacing)' }} />
<div style={{ display: 'flex', flexDirection: 'row' }}> <div style={{ display: 'flex', flexDirection: 'row' }}>
<Button buttonStyles={{width: '100%'}} <Button buttonStyles={{ width: '100%' }} clicked={() => this.addRemoteMount()}>
clicked={() => this.addRemoteMount()}>OK</Button> OK
</Button>
<div style={{ paddingLeft: 'var(--default_spacing)' }} /> <div style={{ paddingLeft: 'var(--default_spacing)' }} />
<Button buttonStyles={{width: '100%'}} <Button
clicked={() => this.setState({DisplayRemote: false})}>Cancel</Button> buttonStyles={{ width: '100%' }}
clicked={() => this.setState({ DisplayRemote: false })}>
Cancel
</Button>
</div> </div>
</Box> </Box>
)); );
const displayAddS3 = createModalConditionally(this.state.DisplayS3, ( const displayAddS3 = createModalConditionally(
<Box dxDark this.state.DisplayS3,
dxStyle={{width: 'auto', height: 'auto', padding: 'var(--default_spacing)'}}> <Box
<h1 style={{textAlign: 'center', paddingBottom: 'var(--default_spacing)'}}>Add S3 dxDark
Mount</h1> 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' }}> <div style={{ display: 'flex', flexDirection: 'row' }}>
<Text text={'Name'} <Text text={'Name'} textAlign={'left'} type={'Heading2'} />
textAlign={'left'}
type={'Heading2'}/>
<div style={{ paddingLeft: 'var(--default_spacing)' }} /> <div style={{ paddingLeft: 'var(--default_spacing)' }} />
<Text text={'Provider'} <Text text={'Provider'} textAlign={'left'} type={'Heading2'} />
textAlign={'left'}
type={'Heading2'}/>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'row' }}> <div style={{ display: 'flex', flexDirection: 'row' }}>
<input onChange={e => this.setState({Name: e.target.value.trim()})} <input
onChange={(e) => this.setState({ Name: e.target.value.trim() })}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
style={{ width: '100%' }} style={{ width: '100%' }}
type={'text'} type={'text'}
value={this.state.Name}/> value={this.state.Name}
/>
<div style={{ paddingLeft: 'var(--default_spacing)' }} /> <div style={{ paddingLeft: 'var(--default_spacing)' }} />
<DropDown changed={e => this.setState({Provider: e.target.value})} <DropDown
changed={(e) =>
this.setState({
Provider: e.target.value,
Region:
Constants.S3_REGION_PROVIDER_REGION[
Constants.S3_PROVIDER_LIST.indexOf(e.target.value)
][0],
})
}
items={Constants.S3_PROVIDER_LIST} items={Constants.S3_PROVIDER_LIST}
selected={this.state.Provider}/> selected={this.state.Provider}
/>
</div> </div>
<div style={{ paddingTop: 'var(--default_spacing)' }} /> <div style={{ paddingTop: 'var(--default_spacing)' }} />
{this.state.Provider === Constants.S3_CUSTOM_PROVIDER ? (
<div>
<div style={{ display: 'flex', flexDirection: 'row' }}> <div style={{ display: 'flex', flexDirection: 'row' }}>
<Text text={'Bucket Name (optional)'} <Text text={'Custom URL'} textAlign={'left'} type={'Heading2'} />
textAlign={'left'}
type={'Heading2'}/>
<div style={{paddingLeft: 'var(--default_spacing)'}}/>
<Text text={'Region'}
textAlign={'left'}
type={'Heading2'}/>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'row' }}> <div style={{ display: 'flex', flexDirection: 'row' }}>
<input onChange={e => this.setState({BucketName: e.target.value})} <input
onChange={(e) => this.setState({ CustomURL: e.target.value })}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
style={{ width: '100%' }} style={{ width: '100%' }}
type={'text'} type={'text'}
value={this.state.BucketName}/> value={this.state.CustomURL}
<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>
<div style={{ paddingTop: 'var(--default_spacing)' }} /> <div style={{ paddingTop: 'var(--default_spacing)' }} />
</div>
) : null}
<div style={{ display: 'flex', flexDirection: 'row' }}> <div style={{ display: 'flex', flexDirection: 'row' }}>
<Text text={'Access Key'} <Text text={'Bucket Name (optional)'} textAlign={'left'} type={'Heading2'} />
textAlign={'left'}
type={'Heading2'}/>
<div style={{ paddingLeft: 'var(--default_spacing)' }} /> <div style={{ paddingLeft: 'var(--default_spacing)' }} />
<Text text={'Secret Key'} <Text text={'Region'} textAlign={'left'} type={'Heading2'} />
textAlign={'left'}
type={'Heading2'}/>
</div> </div>
<div style={{ display: 'flex', flexDirection: 'row' }}> <div style={{ display: 'flex', flexDirection: 'row' }}>
<input onChange={e => this.setState({AccessKey: e.target.value})} <input
onChange={(e) => this.setState({ BucketName: e.target.value })}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
style={{ width: '100%' }}
type={'text'} type={'text'}
value={this.state.AccessKey}/> value={this.state.BucketName}
/>
<div style={{ paddingLeft: 'var(--default_spacing)' }} /> <div style={{ paddingLeft: 'var(--default_spacing)' }} />
<input onChange={e => this.setState({SecretKey: e.target.value})} <DropDown
changed={(e) =>
this.setState({
Region: e.target.value,
})
}
items={
Constants.S3_REGION_PROVIDER_REGION[
Constants.S3_PROVIDER_LIST.indexOf(this.state.Provider)
]
}
selected={this.state.Region}
/>
</div>
<div style={{ paddingTop: 'var(--default_spacing)' }} />
{this.state.Region === Constants.S3_CUSTOM_REGION ? (
<div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<div style={{ paddingLeft: 'var(--default_spacing)', width: '100%' }} />
<Text text={'Custom Region'} textAlign={'left'} type={'Heading2'} />
</div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<div style={{ paddingLeft: 'var(--default_spacing)', width: '100%' }} />
<input
onChange={(e) => this.setState({ CustomRegion: e.target.value })}
className={'ConfigurationItemInput'}
style={{ width: '100%' }}
type={'text'}
value={this.state.CustomRegion}
/>
</div>
<div style={{ paddingTop: 'var(--default_spacing)' }} />
</div>
) : null}
<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'} className={'ConfigurationItemInput'}
type={'text'} type={'text'}
value={this.state.SecretKey}/> 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>
<div style={{ paddingTop: 'calc(var(--default_spacing) * 2)' }} /> <div style={{ paddingTop: 'calc(var(--default_spacing) * 2)' }} />
<div style={{ display: 'flex', flexDirection: 'row' }}> <div style={{ display: 'flex', flexDirection: 'row' }}>
<div style={{ width: '200%' }} /> <div style={{ width: '200%' }} />
<Button buttonStyles={{width: '100%'}} <Button buttonStyles={{ width: '100%' }} clicked={() => this.addS3Mount()}>
clicked={() => this.addS3Mount()}>OK</Button> OK
</Button>
<div style={{ paddingLeft: 'var(--default_spacing)' }} /> <div style={{ paddingLeft: 'var(--default_spacing)' }} />
<Button buttonStyles={{width: '100%'}} <Button
clicked={() => this.setState({DisplayS3: false})}>Cancel</Button> buttonStyles={{ width: '100%' }}
clicked={() => this.setState({ DisplayS3: false })}>
Cancel
</Button>
</div> </div>
</Box> </Box>
)); );
return ( return (
<div className={'AddMount'}> <div className={'AddMount'}>
{displayAddRemote} {displayAddRemote}
{displayAddS3} {displayAddS3}
<div className={'AddMountButtons'}> <div className={'AddMountButtons'}>
{this.props.remoteSupported ? {this.props.remoteSupported ? (
<Button className={'AddMountButton'} <Button className={'AddMountButton'} clicked={this.handleAddRemoteMount}>
clicked={this.handleAddRemoteMount}>Add Remote Mount</Button> : null} Add Remote Mount
{this.props.remoteSupported && this.props.s3Supported ? </Button>
<div style={{paddingRight: 'var(--default_spacing)'}}/> : null} ) : null}
{this.props.s3Supported ? {this.props.remoteSupported && this.props.s3Supported ? (
<Button className={'AddMountButton'} <div style={{ paddingRight: 'var(--default_spacing)' }} />
clicked={this.handleAddS3Mount}>Add S3 Mount</Button> : null} ) : null}
{this.props.s3Supported ? (
<Button className={'AddMountButton'} clicked={this.handleAddS3Mount}>
Add S3 Mount
</Button>
) : null}
</div> </div>
</div> </div>
); );
} }
}); }
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)),
};
};
AddMount.propTypes = {
RemoteMounts: PropTypes.array.isRequired,
S3Mounts: PropTypes.array.isRequired,
addRemoteMount: PropTypes.func.isRequired,
addS3Mount: PropTypes.func.isRequired,
notifyError: PropTypes.func.isRequired,
remoteSupported: PropTypes.bool,
s3Supported: PropTypes.bool,
};
export default connect(mapStateToProps, mapDispatchToProps)(AddMount);

View File

@@ -1,14 +1,16 @@
import React from 'react'; import React from 'react';
import './Configuration.css'; import './Configuration.css';
import {connect} from 'react-redux';
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 ConfigurationItem from './ConfigurationItem/ConfigurationItem'; import ConfigurationItem from './ConfigurationItem/ConfigurationItem';
import Modal from '../../components/UI/Modal/Modal';
import IPCContainer from '../IPCContainer/IPCContainer'; import IPCContainer from '../IPCContainer/IPCContainer';
import Modal from '../../components/UI/Modal/Modal';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { createDismissDisplay } from '../../utils.jsx';
import { displayConfiguration } from '../../redux/actions/mount_actions'; import { displayConfiguration } from '../../redux/actions/mount_actions';
import {notifyError} from '../../redux/actions/error_actions';
import { displayPinnedManager } from '../../redux/actions/pinned_manager_actions'; import { displayPinnedManager } from '../../redux/actions/pinned_manager_actions';
import { notifyError } from '../../redux/actions/error_actions';
const Constants = require('../../constants'); const Constants = require('../../constants');
@@ -25,7 +27,7 @@ class Configuration extends IPCContainer {
ItemList: [], ItemList: [],
Saving: false, Saving: false,
ShowAdvanced: false, ShowAdvanced: false,
Template: {} Template: {},
}; };
checkItemChanged = (itemA, itemB) => { checkItemChanged = (itemA, itemB) => {
@@ -33,29 +35,44 @@ class Configuration extends IPCContainer {
if (itemA.value.length !== itemB.value.length) { if (itemA.value.length !== itemB.value.length) {
return true; return true;
} }
return itemA.value.filter(i => !itemB.value.includes(i)).length !== 0; return itemA.value.filter((i) => !itemB.value.includes(i)).length !== 0;
}
if (itemA.type === 'host_list') {
if (itemA.value.length !== itemB.value.length) {
return true;
}
return (
itemA.value.filter((i) =>
itemB.value.find(
(j) =>
j.HostNameOrIp === i.HostNameOrIp &&
j.ApiPort === i.ApiPort &&
j.Protocol === i.Protocol &&
j.TimeoutMs === i.TimeoutMs &&
j.AgentString === i.AgentString &&
j.ApiPassword === i.ApiPassword &&
j.AuthURL === i.AuthURL &&
j.AuthUser === i.AuthUser &&
j.AuthPassword === i.AuthPassword
)
).length != itemA.value.length
);
} }
return itemA.value !== itemB.value; return itemA.value !== itemB.value;
}; };
checkSaveRequired = () => { checkSaveRequired = () => {
const changedItems = [];
let i = 0; let i = 0;
for (const item of this.state.ItemList) { const changedItems = this.state.ItemList.filter((item) => {
if (this.checkItemChanged(this.state.OriginalItemList[i++], item)) { return this.checkItemChanged(this.state.OriginalItemList[i++], item);
changedItems.push(item); });
}
}
let changedObjectLookup = null; let changedObjectLookup = null;
for (const key of Object.keys(this.state.ObjectLookup)) { for (const key of Object.keys(this.state.ObjectLookup)) {
const changedObjectItems = [];
let j = 0; let j = 0;
for (const item of this.state.ObjectLookup[key]) { const changedObjectItems = this.state.ObjectLookup[key].filter((item) => {
if (this.checkItemChanged(this.state.OriginalObjectLookup[key][j++], item)) { return this.checkItemChanged(this.state.OriginalObjectLookup[key][j++], item);
changedObjectItems.push(item); });
}
}
if (changedObjectItems.length > 0) { if (changedObjectItems.length > 0) {
if (changedObjectLookup === null) { if (changedObjectLookup === null) {
@@ -65,7 +82,7 @@ class Configuration extends IPCContainer {
} }
} }
if ((changedItems.length > 0) || changedObjectLookup) { if (changedItems.length > 0 || changedObjectLookup) {
this.setState({ this.setState({
ChangedItems: changedItems, ChangedItems: changedItems,
ChangedObjectLookup: changedObjectLookup, ChangedObjectLookup: changedObjectLookup,
@@ -95,25 +112,26 @@ class Configuration extends IPCContainer {
createItemList = (config, template) => { createItemList = (config, template) => {
const objectList = []; const objectList = [];
const itemList = Object const itemList = Object.keys(config)
.keys(config) .map((key) => {
.map(key => {
return { return {
advanced: template[key] ? template[key].advanced : false, advanced: template[key] ? template[key].advanced : false,
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, type: template[key] ? template[key].type : null,
value: (template[key] && (template[key].type === 'object')) ? value:
config[key] : template[key] &&
(template[key] && (template[key].type === 'string_array')) ? (template[key].type === 'string_array' || template[key].type === 'object')
config[key] : ? config[key]
config[key].toString(), : template[key] && template[key].type === 'host_list'
? config[key]
: config[key].toString(),
}; };
}) })
.filter(i => { .filter((i) => {
let ret = template[i.label]; let ret = template[i.label];
if (ret && (template[i.label].type === 'object')) { if (ret && template[i.label].type === 'object') {
objectList.push(i); objectList.push(i);
ret = false; ret = false;
} }
@@ -122,35 +140,41 @@ class Configuration extends IPCContainer {
return { return {
ObjectList: objectList, ObjectList: objectList,
ItemList: itemList, ItemList: itemList,
} };
}; };
handleItemChanged = (target, idx) => { handleItemChanged = (target, idx) => {
const itemList = [ const itemList = [...this.state.ItemList];
...this.state.ItemList itemList[idx].value =
]; target.type === 'textarea'
itemList[idx].value = target.type === 'textarea' ? target.string_array : target.value.toString(); ? target.string_array
: target.type === 'host_list'
? target.value
: target.value.toString();
this.setState({ this.setState({
ItemList: itemList ItemList: itemList,
}); });
}; };
handleObjectItemChanged = (target, name, idx) => { handleObjectItemChanged = (target, name, idx) => {
const itemList = [ const itemList = [...this.state.ObjectLookup[name]];
...this.state.ObjectLookup[name]
];
const objectLookup = { const objectLookup = {
...this.state.ObjectLookup, ...this.state.ObjectLookup,
}; };
itemList[idx].value = target.type === 'textarea' ? target.string_array : target.value.toString(); itemList[idx].value =
target.type === 'textarea'
? target.string_array
: target.type === 'host_list'
? target.value
: target.value.toString();
objectLookup[name] = itemList; objectLookup[name] = itemList;
this.setState({ this.setState({
ObjectLookup: objectLookup, ObjectLookup: objectLookup,
}); });
}; };
onGetConfigReply = (event, arg) => { onGetConfigReply = (_, arg) => {
if (arg.data.Success) { if (arg.data.Success) {
const list = this.createItemList(arg.data.Config, this.state.Template); const list = this.createItemList(arg.data.Config, this.state.Template);
const itemListCopy = JSON.parse(JSON.stringify(list.ItemList)); const itemListCopy = JSON.parse(JSON.stringify(list.ItemList));
@@ -161,8 +185,9 @@ class Configuration extends IPCContainer {
objectLookup[obj.label] = list2.ItemList; objectLookup[obj.label] = list2.ItemList;
} }
const isRemoteMount = this.props.remoteSupported && const isRemoteMount =
JSON.parse(objectLookup['RemoteMount'].find(s => s.label === 'IsRemoteMount').value); this.props.remoteSupported &&
JSON.parse(objectLookup['RemoteMount'].find((s) => s.label === 'IsRemoteMount').value);
if (isRemoteMount) { if (isRemoteMount) {
for (const obj of list.ObjectList) { for (const obj of list.ObjectList) {
if (obj.hide_remote) { if (obj.hide_remote) {
@@ -172,32 +197,36 @@ class Configuration extends IPCContainer {
} }
const objectLookupCopy = JSON.parse(JSON.stringify(objectLookup)); const objectLookupCopy = JSON.parse(JSON.stringify(objectLookup));
this.setState({ this.setState(
{
IsRemoteMount: isRemoteMount, IsRemoteMount: isRemoteMount,
ItemList: list.ItemList, ItemList: list.ItemList,
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);
} }
}; };
onGetConfigTemplateReply = (event, arg) => { onGetConfigTemplateReply = (_, arg) => {
if (arg.data.Success) { if (arg.data.Success) {
this.setState({ this.setState(
{
Template: arg.data.Template, Template: arg.data.Template,
}, () => { },
() => {
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, S3: this.props.DisplayS3Configuration,
Version: this.props.version, Version: this.props.version,
}); });
}); }
);
} else { } else {
this.props.notifyError(arg.data.Error, false, () => { this.props.notifyError(arg.data.Error, false, () => {
if (this._isMounted) { if (this._isMounted) {
@@ -212,29 +241,38 @@ class Configuration extends IPCContainer {
}; };
saveAndClose = () => { saveAndClose = () => {
this.setState({ this.setState(
{
Saving: true, Saving: true,
}, () => { },
const changedItems = []; () => {
for (const item of this.state.ChangedItems) { let changedItems = this.state.ChangedItems.map((item) => {
changedItems.push({ return {
Name: item.label, Name: item.label,
Value: item.type === 'string_array' ? Value:
item.value.join(';') : item.type === 'string_array'
item.value, ? item.value.join(';')
: item.type === 'host_list'
? JSON.stringify(item.value)
: item.value,
};
}); });
}
if (this.state.ChangedObjectLookup) { if (this.state.ChangedObjectLookup) {
for (const key of Object.keys(this.state.ChangedObjectLookup)) { for (const key of Object.keys(this.state.ChangedObjectLookup)) {
for (const item of this.state.ChangedObjectLookup[key]) { changedItems = changedItems.concat(
changedItems.push({ this.state.ChangedObjectLookup[key].map((item) => {
return {
Name: key + '.' + item.label, Name: key + '.' + item.label,
Value: item.type === 'string_array' ? Value:
item.value.join(';') : item.type === 'string_array'
item.value, ? item.value.join(';')
}); : item.type === 'host_list'
} ? JSON.stringify(item.value)
: item.value,
};
})
);
} }
} }
@@ -245,43 +283,54 @@ class Configuration extends IPCContainer {
S3: this.props.DisplayS3Configuration, S3: this.props.DisplayS3Configuration,
Version: this.props.version, Version: this.props.version,
}); });
}); }
);
}; };
showRemoteConfigItem = (item, itemList) => { showRemoteConfigItem = (item, itemList) => {
if (item.advanced && if (
item.advanced &&
item.remote && item.remote &&
this.props.remoteSupported && this.props.remoteSupported &&
(item.label !== 'IsRemoteMount')) { item.label !== 'IsRemoteMount'
const isRemoteMount = JSON.parse(itemList.find(s => s.label === 'IsRemoteMount').value); ) {
const enableRemoteMount = !isRemoteMount && const isRemoteMount = JSON.parse(itemList.find((s) => s.label === 'IsRemoteMount').value);
JSON.parse(itemList.find(s => s.label === 'EnableRemoteMount').value); const enableRemoteMount =
return (item.label === 'RemoteHostNameOrIp') || (item.label === 'RemoteMaxConnections') ? !isRemoteMount && JSON.parse(itemList.find((s) => s.label === 'EnableRemoteMount').value);
isRemoteMount : return item.label === 'RemoteHostNameOrIp' || item.label === 'RemoteMaxConnections'
(item.label === 'RemoteReceiveTimeoutSeconds') || (item.label === 'RemoteSendTimeoutSeconds') || (item.label === 'RemotePort') || (item.label === 'RemoteToken') ? ? isRemoteMount
isRemoteMount || enableRemoteMount : : item.label === 'RemoteReceiveTimeoutSeconds' ||
(item.label === 'EnableRemoteMount') ? item.label === 'RemoteSendTimeoutSeconds' ||
!isRemoteMount : item.label === 'RemotePort' ||
enableRemoteMount; item.label === 'RemoteToken'
? isRemoteMount || enableRemoteMount
: item.label === 'EnableRemoteMount'
? !isRemoteMount
: enableRemoteMount;
} }
return false; return false;
}; };
render() { render() {
let confirmSave = null; let confirmSave = null;
if ((this.state.ChangedItems.length > 0) || this.state.ChangedObjectLookup) { if (this.state.ChangedItems.length > 0 || this.state.ChangedObjectLookup) {
confirmSave = ( confirmSave = (
<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%'> <table width="100%">
<tbody> <tbody>
<tr> <tr>
<td align='center' width='50%'><Button clicked={this.saveAndClose} <td align="center" width="50%">
disabled={this.state.Saving}>Yes</Button> <Button clicked={this.saveAndClose} disabled={this.state.Saving}>
Yes
</Button>
</td>
<td align="center" width="50%">
<Button clicked={this.props.hideConfiguration} disabled={this.state.Saving}>
No
</Button>
</td> </td>
<td align='center' width='50%'><Button clicked={this.props.hideConfiguration}
disabled={this.state.Saving}>No</Button></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@@ -291,87 +340,98 @@ class Configuration extends IPCContainer {
} }
let autoFocus = true; let autoFocus = true;
const getAutoFocus = () => {
return autoFocus;
};
const setAutoFocus = (value) => {
autoFocus = value;
};
let objectItems = []; const objectItems = Object.keys(this.state.ObjectLookup).map((key) => {
for (const key of Object.keys(this.state.ObjectLookup)) { return (
objectItems.push((
<div key={key}> <div key={key}>
<h2>{key}</h2> <h2>{key}</h2>
<div> <div>
{ {this.state.ObjectLookup[key]
this.state.ObjectLookup[key].map((k, i) => { .map((k, i) => {
const shouldFocus = autoFocus; const shouldFocus = getAutoFocus();
autoFocus = false; setAutoFocus(false);
return ( return !k.advanced ||
(!k.advanced || (this.state.ShowAdvanced && k.advanced && !k.remote) || this.showRemoteConfigItem(k, this.state.ObjectLookup[key])) ? (this.state.ShowAdvanced && k.advanced && !k.remote) ||
<ConfigurationItem advanced={k.advanced} this.showRemoteConfigItem(k, this.state.ObjectLookup[key]) ? (
<ConfigurationItem
advanced={k.advanced}
autoFocus={shouldFocus} 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}
key={i} key={i}
label={k.label} label={k.label}
readOnly={this.state.IsRemoteMount && ((k.label === 'RemoteHostNameOrIp') || (k.label === 'RemotePort'))} readOnly={
this.state.IsRemoteMount &&
(k.label === 'RemoteHostNameOrIp' || k.label === 'RemotePort')
}
template={this.state.Template[key].template[k.label]} template={this.state.Template[key].template[k.label]}
value={k.value}/> : value={k.value}
null) />
) : null;
}) })
} .filter((i) => i !== null)}
</div> </div>
</div> </div>
)); );
} });
const configurationItems = this.state.ItemList const configurationItems = this.state.ItemList.map((k, i) => {
.map((k, i) => {
const shouldFocus = autoFocus; const shouldFocus = autoFocus;
autoFocus = false; autoFocus = false;
return ( return (!this.state.IsRemoteMount || !k.hide_remote) &&
((!this.state.IsRemoteMount || !k.hide_remote) && (!k.advanced || (this.state.ShowAdvanced && k.advanced))) ? (!k.advanced || (this.state.ShowAdvanced && k.advanced)) ? (
<ConfigurationItem advanced={k.advanced} <ConfigurationItem
advanced={k.advanced}
autoFocus={shouldFocus} autoFocus={shouldFocus}
changed={e => this.handleItemChanged(e, i)} changed={(e) => this.handleItemChanged(e, i)}
grouping={'Settings'} grouping={'Settings'}
items={this.state.Template[k.label].items} items={this.state.Template[k.label].items}
key={i} key={i}
label={k.label} label={k.label}
template={this.state.Template[k.label]} template={this.state.Template[k.label]}
value={k.value}/> : value={k.value}
null />
); ) : null;
}); }).filter((i) => i !== null);
return ( return (
<div className={'Configuration'}> <div className={'Configuration'}>
{confirmSave} {confirmSave}
<Box dxDark dxStyle={{padding: 'var(--default_spacing)'}}> <Box dxDark dxStyle={{ padding: '5px' }}>
<div style={{ {createDismissDisplay(this.checkSaveRequired)}
float: 'right', <h1 style={{ width: '100%', textAlign: 'center' }}>
margin: 0, {(this.props.DisplayRemoteConfiguration
padding: 0, ? this.props.DisplayConfiguration.substr(6)
marginTop: '-4px', : this.props.DisplayS3Configuration
boxSizing: 'border-box', ? this.props.DisplayConfiguration.substr(2)
display: 'block' : this.props.DisplayConfiguration) + ' Configuration '}
}}>
<a href={'#'}
onClick={this.checkSaveRequired}
style={{cursor: 'pointer'}}>X</a>
</div>
<h1 style={{width: '100%', textAlign: 'center'}}>{(
this.props.DisplayRemoteConfiguration ?
this.props.DisplayConfiguration.substr(6) :
this.props.DisplayConfiguration) + ' Configuration '}
</h1> </h1>
<div style={{ overflowY: 'auto', height: '90%' }}> <div style={{ overflowY: 'auto', height: '90%' }}>
{this.props.MState.Mounted && (configurationItems.length > 0) ? <Button {this.props.MState.Mounted && configurationItems.length > 0 ? (
buttonStyles={{width: 'auto', height: 'auto', marginLeft: 'auto', marginRight: '4px'}} <Button
buttonStyles={{
width: 'auto',
height: 'auto',
marginLeft: 'auto',
marginRight: '4px',
}}
clicked={() => { clicked={() => {
this.props.displayPinnedManager(true); this.props.displayPinnedManager(true);
return false; return false;
}}>&nbsp;Pinned File Manager...&nbsp;</Button> : null} }}>
&nbsp;Pinned File Manager...&nbsp;
</Button>
) : null}
<div style={{ marginBottom: '4px' }} /> <div style={{ marginBottom: '4px' }} />
{objectItems} {objectItems}
{(configurationItems.length > 0) ? <h2>Settings</h2> : null} {configurationItems.length > 0 ? <h2>Settings</h2> : null}
{configurationItems} {configurationItems}
</div> </div>
</Box> </Box>
@@ -380,22 +440,33 @@ class Configuration extends IPCContainer {
} }
} }
const mapStateToProps = state => { 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, DisplayS3Configuration: state.mounts.DisplayS3Configuration,
MState: state.mounts.MountState[state.mounts.DisplayConfiguration], 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)), 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)),
} };
};
Configuration.propTypes = {
displayPinnedManager: PropTypes.func.isRequired,
DisplayConfiguration: PropTypes.string.isRequired,
hideConfiguration: PropTypes.func.isRequired,
remoteSupported: PropTypes.bool.isRequired,
notifyError: PropTypes.func.isRequired,
MState: PropTypes.object.isRequired,
Platform: PropTypes.string.isRequired,
version: PropTypes.string.isRequired,
}; };
export default connect(mapStateToProps, mapDispatchToProps)(Configuration); export default connect(mapStateToProps, mapDispatchToProps)(Configuration);

View File

@@ -1,25 +1,17 @@
import React from 'react'; import React from 'react';
import './ConfigurationItem.css'; import './ConfigurationItem.css';
import CheckBox from '../../../components/UI/CheckBox/CheckBox'; import CheckBox from '../../../components/UI/CheckBox/CheckBox';
import DropDown from '../../../components/UI/DropDown/DropDown';
import HostList from '../../HostList/HostList';
import Password from '../../../containers/UI/Password/Password';
import PropTypes from 'prop-types';
import settings from '../../../assets/settings';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
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 { notifyError, notifyInfo } from '../../../redux/actions/error_actions';
import {
notifyError,
notifyInfo
} from '../../../redux/actions/error_actions';
import settings from '../../../assets/settings';
import DropDown from '../../../components/UI/DropDown/DropDown';
import Password from '../../../containers/UI/Password/Password';
const mapDispatchToProps = dispatch => { const ConfigurationItem = (props) => {
return {
notifyError: msg => dispatch(notifyError(msg)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)),
}
};
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') {
@@ -37,135 +29,202 @@ export default connect(null, mapDispatchToProps)(props => {
props.notifyInfo(props.label, description); props.notifyInfo(props.label, description);
}; };
infoDisplay = <a href={'#'} 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}/>; autoFocus={props.autoFocus}
/>
);
break; break;
case 'double': case 'double':
data = <input min={0.0} data = (
<input
min={0.0}
autoFocus={props.autoFocus} 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;
case 'host_list':
data = (
<HostList
autoFocus={props.autoFocus}
disabled={props.readOnly}
onChange={(items) =>
handleChanged({
target: {
type: 'host_list',
value: items,
},
})
}
type={props.template.subtype}
value={props.value}
/>
);
break; break;
case 'list': case 'list':
data = <DropDown alt data = (
<DropDown
alt
auto auto
autoFocus={props.autoFocus} 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':
if (props.template.subtype === 'password') { if (props.template.subtype === 'password') {
data = ( data = (
<Password autoFocus={props.autoFocus} <Password
changed={s => handleChanged({ autoFocus={props.autoFocus}
changed={(s) =>
handleChanged({
target: { target: {
type: 'password', type: 'password',
value: s, value: s,
}, },
})} })
}
disabled={props.readOnly} disabled={props.readOnly}
mismatchHandler={() => props.notifyError('Passwords do not match')} mismatchHandler={() => props.notifyError('Passwords do not match')}
value={props.value} /> value={props.value}
/>
); );
} else { } else {
data = ( data = (
<input onChange={e => handleChanged(e)} <input
onChange={(e) => handleChanged(e)}
autoFocus={props.autoFocus} 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} autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={(e) => handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'number'} type={'number'}
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} autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={(e) => handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'number'} type={'number'}
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} autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={(e) => handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'number'} type={'number'}
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} autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={(e) => handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'number'} type={'number'}
value={props.value}/>; value={props.value}
/>
);
break; break;
case 'string_array': case 'string_array':
data = ( data = (
<textarea autoFocus={props.autoFocus} <textarea
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
rows={4} rows={4}
cols={36} cols={36}
onChange={e=>handleChanged(e)} onChange={(e) => handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
value={props.value.join('\n')} /> value={props.value.join('\n')}
/>
); );
break; break;
case 'password': case 'password':
data = ( data = (
<Password autoFocus={props.autoFocus} <Password
changed={s => handleChanged({ autoFocus={props.autoFocus}
changed={(s) =>
handleChanged({
target: { target: {
type: 'password', type: 'password',
value: s, value: s,
}, },
})} })
}
disabled={props.readOnly} disabled={props.readOnly}
mismatchHandler={() => props.notifyError('Passwords do not match')} mismatchHandler={() => props.notifyError('Passwords do not match')}
value={props.value} /> value={props.value}
/>
); );
break; break;
@@ -175,17 +234,44 @@ export default connect(null, mapDispatchToProps)(props => {
return ( return (
<div className={'ConfigurationItem'}> <div className={'ConfigurationItem'}>
<table cellPadding='2' <table cellPadding="2" width="100%">
width='100%'>
<tbody> <tbody>
<tr> <tr>
{infoDisplay ? {infoDisplay ? (
<td width='100%' valign={'top'}>{infoDisplay} {props.label}</td> : <td width="100%" valign={'top'}>
<td width='100%' valign={'top'}>{props.label}</td>} {infoDisplay} {props.label}
</td>
) : (
<td width="100%" valign={'top'}>
{props.label}
</td>
)}
<td>{data}</td> <td>{data}</td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
</div> </div>
); );
}); };
const mapDispatchToProps = (dispatch) => {
return {
notifyError: (msg) => dispatch(notifyError(msg)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)),
};
};
ConfigurationItem.propTypes = {
autoFocus: PropTypes.bool,
changed: PropTypes.func.isRequired,
grouping: PropTypes.string,
items: PropTypes.array,
label: PropTypes.string,
notifyError: PropTypes.func.isRequired,
notifyInfo: PropTypes.func.isRequired,
readOnly: PropTypes.bool,
template: PropTypes.object.isRequired,
value: PropTypes.oneOfType([PropTypes.array, PropTypes.string, PropTypes.number]),
};
export default connect(null, mapDispatchToProps)(ConfigurationItem);

View File

View File

@@ -0,0 +1,77 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { addEditHostAction } from '../../../redux/actions/host_actions';
import { connect } from 'react-redux';
import { faTrashAlt, faEdit } from '@fortawesome/free-solid-svg-icons';
const mapDispatchToProps = (dispatch) => {
return {
editHost: (host_list, host_data, cb) =>
dispatch(addEditHostAction.display(true, cb, { host_list, host_data })),
};
};
const Host = ({ allowDelete, editHost, host_list, host_data, onChange, onDelete }) => {
const handleEditHost = () => {
editHost(host_list, host_data, (changed, { host_data }) => {
if (changed) {
onChange(host_data);
}
});
};
const getHostDisplay = () => {
return (
host_data.HostNameOrIp +
((host_data.Protocol === 'http' && host_data.ApiPort === 80) ||
(host_data.Protocol === 'https' && host_data.ApiPort === 443)
? ''
: ':' + host_data.ApiPort)
);
};
const premium = host_data.AuthURL && host_data.AuthUser;
return (
<div style={{ display: 'flex', flexDirection: 'row' }}>
<div
style={{
flex: 0,
paddingRight: 'calc(var(--default_spacing) * 1.25)',
}}>
<a href={'#'} onClick={handleEditHost}>
<FontAwesomeIcon icon={faEdit} />
</a>
</div>
{allowDelete ? (
<div
style={{
flex: 0,
paddingRight: 'calc(var(--default_spacing) * 1.25)',
}}>
<a href={'#'} onClick={onDelete}>
<FontAwesomeIcon icon={faTrashAlt} />
</a>
</div>
) : null}
{premium ? (
<p>
<b>{'(premium) ' + getHostDisplay()}</b>
</p>
) : (
<p>{getHostDisplay()}</p>
)}
</div>
);
};
Host.propTypes = {
allowDelete: PropTypes.bool.isRequired,
editHost: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
host_data: PropTypes.object.isRequired,
host_list: PropTypes.array.isRequired,
};
export default connect(null, mapDispatchToProps)(Host);

View File

View File

@@ -0,0 +1,119 @@
import React from 'react';
import './HostList.css';
import Host from './Host/Host';
import PropTypes from 'prop-types';
import { Component } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { confirmYesNoAction } from '../../redux/actions/common_actions';
import { connect } from 'react-redux';
import { addEditHostAction } from '../../redux/actions/host_actions';
import { faPlusCircle } from '@fortawesome/free-solid-svg-icons';
class HostList extends Component {
state = {
items: [],
};
// autoFocus={props.autoFocus}
// disabled={props.readOnly}
// type={props.template.subtype}
componentDidMount() {
this.setState({ items: this.props.value });
}
componentWillUnmount() {}
handleAddHost = () => {
this.props.AddHost(this.state.items, (changed, { host_data }) => {
if (changed) {
const items = [...this.state.items, host_data];
this.updateItems(items);
}
});
};
handleChanged = (host_data, index) => {
const items = [...this.state.items];
items[index] = host_data;
this.updateItems(items);
};
handleDeleted = (index) => {
this.props.ConfirmRemoveHost(
'Delete [' + this.state.items[index].HostNameOrIp + ']?',
(confirmed) => {
if (confirmed) {
const items = [...this.state.items];
items.splice(index, 1);
this.updateItems(items);
}
}
);
};
updateItems = (items) => {
this.setState(
{
items,
},
() => {
this.props.onChange(this.state.items);
}
);
};
render() {
let idx = 0;
return (
<div style={{ display: 'flex', flexDirection: 'column' }}>
<div
style={{
maxHeight: '80px',
maxWidth: '260px',
minWidth: '260px',
backgroundColor: 'var(--control_background)',
padding: 'var(--default_spacing)',
overflowY: 'scroll',
}}>
{this.state.items.map((v, index) => {
return (
<Host
key={idx++}
onChange={(host_data) => this.handleChanged(host_data, index)}
onDelete={() => this.handleDeleted(index)}
allowDelete={this.state.items.length > 1}
host_data={v}
host_list={this.state.items.filter((i) => i !== v)}
/>
);
})}
</div>
<a
href={'#'}
onClick={this.handleAddHost}
style={{
marginTop: 'var(--default_spacing)',
}}>
<FontAwesomeIcon icon={faPlusCircle} />
<b>{' Add Portal '}</b>
</a>
</div>
);
}
}
const mapDispatchToProps = (dispatch) => {
return {
AddHost: (host_list, cb) => dispatch(addEditHostAction.display(true, cb, { host_list })),
ConfirmRemoveHost: (title, cb) => dispatch(confirmYesNoAction.display(true, cb, { title })),
};
};
HostList.propTypes = {
AddHost: PropTypes.func.isRequired,
ConfirmRemoveHost: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
value: PropTypes.array.isRequired,
};
export default connect(null, mapDispatchToProps)(HostList);

View File

@@ -1,22 +1,22 @@
import { Component } from 'react'; import { Component } from 'react';
import {getIPCRenderer} from '../../utils'; import { getIPCRenderer } from '../../utils.jsx';
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
export default class IPCContainer extends Component { class IPCContainer extends Component {
handlerList = {}; handlerList = {};
componentWillUnmount() { componentWillUnmount() {
if (ipcRenderer) { if (ipcRenderer) {
for (let name in this.handlerList) { for (let name in this.handlerList) {
if (this.handlerList.hasOwnProperty(name)) { if (Object.prototype.hasOwnProperty.call(this.handlerList, name)) {
ipcRenderer.removeListener(name, this.handlerList[name]); ipcRenderer.removeListener(name, this.handlerList[name]);
} }
} }
} }
this.handlerList = {}; this.handlerList = {};
}; }
sendRequest = (name, data) => { sendRequest = (name, data) => {
if (ipcRenderer) { if (ipcRenderer) {
@@ -41,5 +41,6 @@ export default class IPCContainer extends Component {
ipcRenderer.on(name, callback); ipcRenderer.on(name, callback);
} }
}; };
}
}; export default IPCContainer;

View File

@@ -1,46 +1,26 @@
import React from 'react'; import React from 'react';
import './MountItem.css'; import './MountItem.css';
import {connect} from 'react-redux';
import DropDown from '../../../components/UI/DropDown/DropDown';
import Button from '../../../components/UI/Button/Button'; import Button from '../../../components/UI/Button/Button';
import Loader from 'react-loader-spinner'; import CheckBox from '../../../components/UI/CheckBox/CheckBox';
import Text from '../../../components/UI/Text/Text'; import DropDown from '../../../components/UI/DropDown/DropDown';
import Grid from '../../../components/UI/Grid/Grid'; import Grid from '../../../components/UI/Grid/Grid';
import configureImage from '../../../assets/images/configure.png'; import Loader from 'react-loader-spinner';
import PropTypes from 'prop-types';
import RootElem from '../../../components/UI/RootElem/RootElem'; import RootElem from '../../../components/UI/RootElem/RootElem';
import Text from '../../../components/UI/Text/Text';
import configureImage from '../../../assets/images/configure.png';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { connect } from 'react-redux';
import { import {
displayConfiguration, displayConfiguration,
removeMount, removeMount,
setProviderState setProviderState,
} from '../../../redux/actions/mount_actions'; } from '../../../redux/actions/mount_actions';
import { import { displaySkynetExport, displaySkynetImport } from '../../../redux/actions/skynet_actions';
displaySkynetExport,
displaySkynetImport,
} from '../../../redux/actions/skynet_actions'
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';
const mapStateToProps = (state, ownProps) => { const MountItem = (props) => {
return { const handleAutoMountChanged = (e) => {
MState: state.mounts.MountState[ownProps.provider],
Platform: state.common.Platform,
PState: state.mounts.ProviderState[ownProps.provider]
};
};
const mapDispatchToProps = dispatch => {
return {
displayConfiguration: (provider, remote, s3) => dispatch(displayConfiguration(provider, remote, s3)),
displaySkynetExport: display => dispatch(displaySkynetExport(display)),
displaySkynetImport: display => dispatch(displaySkynetImport(display)),
removeMount: provider => dispatch(removeMount(provider)),
setProviderState: (provider, state) => dispatch(setProviderState(provider, state)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(props => {
const handleAutoMountChanged = e => {
const state = { const state = {
...props.PState, ...props.PState,
AutoMount: e.target.checked, AutoMount: e.target.checked,
@@ -48,7 +28,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
props.setProviderState(props.provider, state); props.setProviderState(props.provider, state);
}; };
const handleAutoRestartChanged = e => { const handleAutoRestartChanged = (e) => {
const state = { const state = {
...props.PState, ...props.PState,
AutoRestart: e.target.checked, AutoRestart: e.target.checked,
@@ -59,16 +39,21 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
let secondRow = 6; let secondRow = 6;
const pointer = { cursor: props.MState.AllowMount ? 'pointer' : 'no-drop' }; const pointer = { cursor: props.MState.AllowMount ? 'pointer' : 'no-drop' };
const configButton = ( const configButton = (
<RootElem colSpan={4} <RootElem colSpan={4} rowSpan={6}>
rowSpan={6}> <img
<img alt='' alt=""
height={'16px'} height={'16px'}
onClick={props.MState.AllowMount ? () => props.displayConfiguration(props.provider, props.remote, props.s3) : e => { onClick={
props.MState.AllowMount
? () => props.displayConfiguration(props.provider, props.remote, props.s3)
: (e) => {
e.preventDefault(); 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'}
/>
</RootElem> </RootElem>
); );
@@ -77,80 +62,95 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
if (props.Platform === 'win32') { if (props.Platform === 'win32') {
inputColumnSpan = 20; inputColumnSpan = 20;
const index = props.MState.DriveLetters.indexOf(props.PState.MountLocation); const index = props.MState.DriveLetters.indexOf(props.PState.MountLocation);
inputControls = <DropDown changed={props.changed} inputControls = (
<DropDown
changed={props.changed}
colSpan={inputColumnSpan} colSpan={inputColumnSpan}
disabled={!props.MState.AllowMount || props.MState.Mounted} disabled={!props.MState.AllowMount || props.MState.Mounted}
items={props.MState.DriveLetters} items={props.MState.DriveLetters}
row={secondRow} row={secondRow}
rowSpan={7} rowSpan={7}
selected={index >= 0 ? props.PState.MountLocation : ''}/>; selected={index >= 0 ? props.PState.MountLocation : ''}
/>
);
} else { } else {
inputColumnSpan = 64; inputColumnSpan = 64;
inputControls = []; inputControls = [];
let key = 0; let key = 0;
inputControls.push(( inputControls.push(
<RootElem colSpan={inputColumnSpan - 8} <RootElem colSpan={inputColumnSpan - 8} key={'i' + key++} row={secondRow} rowSpan={7}>
key={'i' + key++} <input
row={secondRow} disabled={!props.MState.AllowMount || props.MState.Mounted}
rowSpan={7}>
<input disabled={!props.MState.AllowMount || props.MState.Mounted}
maxLength={4096} maxLength={4096}
onChange={props.changed} onChange={props.changed}
size={4096} size={4096}
className={'MountItemInput'} className={'MountItemInput'}
type={'text'} type={'text'}
value={props.PState.MountLocation}/> value={props.PState.MountLocation}
/>
</RootElem> </RootElem>
)); );
inputControls.push(( inputControls.push(
<Button clicked={()=>props.browseClicked(props.provider, props.PState.MountLocation)} <Button
clicked={() => props.browseClicked(props.provider, props.PState.MountLocation)}
col={inputColumnSpan - 7} col={inputColumnSpan - 7}
colSpan={7} colSpan={7}
disabled={props.MState.Mounted || !props.MState.AllowMount} disabled={props.MState.Mounted || !props.MState.AllowMount}
key={'b' + key++} key={'b' + key++}
row={secondRow} row={secondRow}
rowSpan={7}>...</Button> rowSpan={7}>
)); ...
</Button>
);
} }
const buttonDisplay = props.MState.AllowMount ? const buttonDisplay = props.MState.AllowMount ? (
(props.MState.Mounted ? 'Unmount' : 'Mount') : props.MState.Mounted ? (
<Loader color={'var(--heading_text_color)'} 'Unmount'
height={19} ) : (
type='Circles' 'Mount'
width={19}/>; )
) : (
<Loader color={'var(--heading_text_color)'} height={19} type="Circles" width={19} />
);
const actionsDisplay = ( const actionsDisplay = (
<Button <Button
clicked={() => props.clicked(props.provider, props.remote, props.s3, !props.MState.Mounted, props.PState.MountLocation)} 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}
row={secondRow} row={secondRow}
rowSpan={7}> rowSpan={7}>
{buttonDisplay} {buttonDisplay}
</Button>); </Button>
);
const autoMountControl = ( const autoMountControl = (
<RootElem col={inputColumnSpan + 24} <RootElem col={inputColumnSpan + 24} colSpan={28} row={secondRow} rowSpan={7}>
colSpan={28} <CheckBox
row={secondRow} changed={handleAutoMountChanged}
rowSpan={7}>
<CheckBox changed={handleAutoMountChanged}
checked={props.PState.AutoMount} checked={props.PState.AutoMount}
label={'Auto-mount'}/> label={'Auto-mount'}
/>
</RootElem> </RootElem>
); );
const autoRestartControl = ( const autoRestartControl = (
<RootElem col={inputColumnSpan + 24 + 28} <RootElem col={inputColumnSpan + 24 + 28} colSpan={24} row={secondRow} rowSpan={7}>
colSpan={24} <CheckBox
row={secondRow} changed={handleAutoRestartChanged}
rowSpan={7}>
<CheckBox changed={handleAutoRestartChanged}
checked={props.PState.AutoRestart} checked={props.PState.AutoRestart}
label={'Restart'}/> label={'Restart'}
/>
</RootElem> </RootElem>
); );
@@ -158,7 +158,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
if (props.allowRemove) { 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) {
@@ -166,17 +166,15 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
} }
}; };
removeControl = ( removeControl = (
<RootElem col={dimensions=>dimensions.columns - 6} <RootElem col={(dimensions) => dimensions.columns - 6} row={secondRow + 3}>
row={secondRow + 3}> <a href={'#'} onClick={handleRemoveMount} style={removeStyle}>
<a href={'#'}
onClick={handleRemoveMount}
style={removeStyle}>
<FontAwesomeIcon icon={faTrashAlt} /> <FontAwesomeIcon icon={faTrashAlt} />
</a> </a>
</RootElem>); </RootElem>
);
} }
const isSkynet = (props.provider === 'Skynet'); const isSkynet = props.provider === 'Skynet';
return ( return (
<div className={'MountItem'}> <div className={'MountItem'}>
<Grid noScroll> <Grid noScroll>
@@ -185,26 +183,45 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
col={configButton ? 6 : 0} col={configButton ? 6 : 0}
rowSpan={5} rowSpan={5}
colSpan={90} colSpan={90}
text={props.remote ? props.provider.substr(6) : props.s3 ? props.provider.substr(2) : isSkynet ? props.provider + ' [EXPERIMENTAL]' : props.provider} text={
props.remote
? props.provider.substr(6)
: props.s3
? props.provider.substr(2)
: isSkynet
? props.provider + ' [EXPERIMENTAL]'
: props.provider
}
textAlign={'Left'} textAlign={'Left'}
type={'Heading2'}/> type={'Heading2'}
{(isSkynet && (props.MState.Mounted)) ? ( />
<a href={'#'} {isSkynet && props.MState.Mounted ? (
<a
href={'#'}
col={(configButton ? 24 : 18) + 34} col={(configButton ? 24 : 18) + 34}
onClick={props.MState.AllowMount ? () => props.displaySkynetExport(true) : e => { onClick={
props.MState.AllowMount
? () => props.displaySkynetExport(true)
: (e) => {
e.preventDefault(); e.preventDefault();
}} }
}
rowSpan={5} rowSpan={5}
style={{ ...pointer, fontWeight: 'normal' }}> style={{ ...pointer, fontWeight: 'normal' }}>
<u>Export</u> <u>Export</u>
</a> </a>
) : null} ) : null}
{(isSkynet && (props.MState.Mounted)) ? ( {isSkynet && props.MState.Mounted ? (
<a href={'#'} <a
href={'#'}
col={(configButton ? 24 + 13 : 18 + 13) + 34} col={(configButton ? 24 + 13 : 18 + 13) + 34}
onClick={props.MState.AllowMount ? () => props.displaySkynetImport(true) : e => { onClick={
props.MState.AllowMount
? () => props.displaySkynetImport(true)
: (e) => {
e.preventDefault(); e.preventDefault();
}} }
}
rowSpan={5} rowSpan={5}
style={{ ...pointer, fontWeight: 'normal' }}> style={{ ...pointer, fontWeight: 'normal' }}>
<u>Import</u> <u>Import</u>
@@ -218,4 +235,43 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
</Grid> </Grid>
</div> </div>
); );
}); };
const mapStateToProps = (state, ownProps) => {
return {
MState: state.mounts.MountState[ownProps.provider],
Platform: state.common.Platform,
PState: state.mounts.ProviderState[ownProps.provider],
};
};
const mapDispatchToProps = (dispatch) => {
return {
displayConfiguration: (provider, remote, s3) =>
dispatch(displayConfiguration(provider, remote, s3)),
displaySkynetExport: (display) => dispatch(displaySkynetExport(display)),
displaySkynetImport: (display) => dispatch(displaySkynetImport(display)),
removeMount: (provider) => dispatch(removeMount(provider)),
setProviderState: (provider, state) => dispatch(setProviderState(provider, state)),
};
};
MountItem.propTypes = {
MState: PropTypes.object.isRequired,
PState: PropTypes.object.isRequired,
Platform: PropTypes.string.isRequired,
allowRemove: PropTypes.bool,
browseClicked: PropTypes.func.isRequired,
changed: PropTypes.func.isRequired,
clicked: PropTypes.func.isRequired,
displayConfiguration: PropTypes.func.isRequired,
displaySkynetExport: PropTypes.func.isRequired,
displaySkynetImport: PropTypes.func.isRequired,
provider: PropTypes.string.isRequired,
remote: PropTypes.bool,
removeMount: PropTypes.func.isRequired,
s3: PropTypes.bool,
setProviderState: PropTypes.func.isRequired,
};
export default connect(mapStateToProps, mapDispatchToProps)(MountItem);

View File

@@ -1,7 +1,7 @@
.MountItems { .MountItems {
padding: 0; padding: 0;
margin: 0; margin: 0;
height: 121px; height: 212px;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
@@ -10,7 +10,7 @@
.MountItemsRemote { .MountItemsRemote {
padding: 0; padding: 0;
margin: 0; margin: 0;
height: 121px; height: 212px;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
overflow-x: hidden; overflow-x: hidden;

View File

@@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import './MountItems.css';
import AddMount from '../AddMount/AddMount'; 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 IPCContainer from '../IPCContainer/IPCContainer';
import './MountItems.css';
import Modal from '../../components/UI/Modal/Modal'; import Modal from '../../components/UI/Modal/Modal';
import MountItem from './MountItem/MountItem'; import MountItem from './MountItem/MountItem';
import IPCContainer from '../IPCContainer/IPCContainer'; import { connect } from 'react-redux';
import { notifyError } from '../../redux/actions/error_actions';
import { import {
resetMountsState, resetMountsState,
setAllowMount, setAllowMount,
@@ -14,9 +15,8 @@ import {
setBusy, setBusy,
setMounted, setMounted,
setMountState, setMountState,
setProviderState setProviderState,
} from '../../redux/actions/mount_actions'; } from '../../redux/actions/mount_actions';
import {notifyError} from '../../redux/actions/error_actions';
const Constants = require('../../constants'); const Constants = require('../../constants');
@@ -29,7 +29,7 @@ class MountItems extends IPCContainer {
RetryItems: {}, RetryItems: {},
}; };
addMountsBusy = provider => { addMountsBusy = (provider) => {
this.props.setMountsBusy(true); this.props.setMountsBusy(true);
this.activeDetections.push(provider); this.activeDetections.push(provider);
}; };
@@ -43,15 +43,18 @@ class MountItems extends IPCContainer {
...this.state.RetryItems, ...this.state.RetryItems,
}; };
delete retryItems[provider]; delete retryItems[provider];
this.setState({ this.setState(
{
DisplayRetry: Object.keys(retryItems).length > 0, DisplayRetry: Object.keys(retryItems).length > 0,
RetryItems: retryItems, RetryItems: retryItems,
}, () => { },
() => {
if (this.state.DisplayRetry) { if (this.state.DisplayRetry) {
this.sendRequest(Constants.IPC_Show_Window); this.sendRequest(Constants.IPC_Show_Window);
} }
stateCallback(); stateCallback();
}); }
);
} }
}; };
@@ -64,18 +67,16 @@ class MountItems extends IPCContainer {
} }
componentWillUnmount() { componentWillUnmount() {
for (const provider in this.state.RetryItems) { Object.keys(this.state.RetryItems).forEach((provider) => {
if (this.state.RetryItems.hasOwnProperty(provider)) {
this.cancelRetryMount(provider); this.cancelRetryMount(provider);
} });
}
this.props.resetMountsState(); this.props.resetMountsState();
this.activeDetections = []; this.activeDetections = [];
super.componentWillUnmount(); super.componentWillUnmount();
}; }
detectMount = provider => { detectMount = (provider) => {
this.addMountsBusy(provider); this.addMountsBusy(provider);
this.sendRequest(Constants.IPC_Detect_Mount, { this.sendRequest(Constants.IPC_Detect_Mount, {
@@ -88,8 +89,7 @@ class MountItems extends IPCContainer {
detectMounts = () => { detectMounts = () => {
if (!this.state.DisplayRetry) { if (!this.state.DisplayRetry) {
const providerList = this.getProviderList(); this.getProviderList().forEach((provider) => {
providerList.forEach(provider => {
this.detectMount(provider); this.detectMount(provider);
}); });
} }
@@ -98,7 +98,7 @@ class MountItems extends IPCContainer {
displayRetryMount = (provider, remote, s3, 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,
}; };
retryItems[provider] = { retryItems[provider] = {
RetrySeconds: 10, RetrySeconds: 10,
@@ -110,13 +110,15 @@ class MountItems extends IPCContainer {
}; };
this.props.setMountState(provider, mountState); this.props.setMountState(provider, mountState);
this.setState({ this.setState(
{
DisplayRetry: true, DisplayRetry: true,
RetryItems: retryItems, RetryItems: retryItems,
}, () => { },
() => {
this.sendRequest(Constants.IPC_Show_Window); this.sendRequest(Constants.IPC_Show_Window);
this.retryIntervals[provider] = setInterval(() => { this.retryIntervals[provider] = setInterval(() => {
let retryItems = { const retryItems = {
...this.state.RetryItems, ...this.state.RetryItems,
}; };
const retrySeconds = retryItems[provider].RetrySeconds - 1; const retrySeconds = retryItems[provider].RetrySeconds - 1;
@@ -131,7 +133,8 @@ class MountItems extends IPCContainer {
}); });
} }
}, 1000); }, 1000);
}); }
);
} }
}; };
@@ -140,7 +143,7 @@ class MountItems extends IPCContainer {
Title: provider + ' Mount Location', Title: provider + ' Mount Location',
Location: location, Location: location,
}); });
if (location && (location.length > 0)) { if (location && location.length > 0) {
this.handleMountLocationChanged(provider, location); this.handleMountLocationChanged(provider, location);
} }
}; };
@@ -154,19 +157,23 @@ class MountItems extends IPCContainer {
}; };
handleMountUnMount = (provider, remote, s3, 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 || s3 || provider === 'Skynet' ? let result =
{Valid: true, Success: true} : remote || s3 || provider === 'Skynet'
this.sendSyncRequest(Constants.IPC_Check_Daemon_Version, { ? { Valid: true, Success: true }
: this.sendSyncRequest(Constants.IPC_Check_Daemon_Version, {
Provider: provider, Provider: provider,
Remote: remote, Remote: remote,
S3: s3, S3: s3,
Version: this.props.InstalledVersion Version: this.props.InstalledVersion,
}).data; }).data;
const displayRetry = (msg) => {
this.displayRetryMount(provider, remote, s3, location, msg);
};
if (result.Success) { if (result.Success) {
if (result.Valid) { if (result.Valid) {
if (this.props.Platform !== 'win32') { if (this.props.Platform !== 'win32') {
@@ -180,20 +187,30 @@ class MountItems extends IPCContainer {
} }
} else { } else {
allowAction = false; allowAction = false;
if ((result.Code === new Uint32Array([-1])[0]) || (result.Code === new Uint8Array([-1])[0])) { if (
this.displayRetryMount(provider, remote, s3, location, 'Failed to connect to ' + provider + ' daemon'); result.Code === new Uint32Array([-1])[0] ||
} else if ((result.Code === new Uint32Array([-3])[0]) || (result.Code === new Uint8Array([-3])[0])) { result.Code === new Uint8Array([-1])[0]
this.displayRetryMount(provider, remote, s3, location, 'Incompatible ' + provider + ' daemon. Please upgrade ' + provider + '.'); ) {
displayRetry('Failed to connect to ' + provider + ' daemon');
} else if (
result.Code === new Uint32Array([-3])[0] ||
result.Code === new Uint8Array([-3])[0]
) {
displayRetry(
'Incompatible ' + provider + ' daemon. Please upgrade ' + provider + '.'
);
} else { } else {
this.displayRetryMount(provider, remote, s3, location, 'Version check failed: ' + result.Error); displayRetry('Version check failed: ' + result.Error);
} }
} }
} else { } else {
allowAction = false; allowAction = false;
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, s3, location, 'Version check failed: ' + result.Error); displayRetry('Version check failed: ' + result.Error);
} }
} }
} }
@@ -202,32 +219,24 @@ class MountItems extends IPCContainer {
this.addMountsBusy(provider); this.addMountsBusy(provider);
this.props.setAllowMount(provider, false); this.props.setAllowMount(provider, false);
if (mount) { this.sendRequest(mount ? Constants.IPC_Mount_Drive : Constants.IPC_Unmount_Drive, {
this.sendRequest(Constants.IPC_Mount_Drive, {
Location: location, Location: location,
Provider: provider, Provider: provider,
Remote: remote, Remote: remote,
S3: s3, S3: s3,
Version: this.props.InstalledVersion, Version: this.props.InstalledVersion,
}); });
} else {
this.sendRequest(Constants.IPC_Unmount_Drive, {
Location: location,
Provider: provider,
Remote: remote,
S3: s3,
Version: this.props.InstalledVersion,
});
}
} }
} }
}; };
getProviderList = providersOnly => { getProviderList = (providersOnly) => {
const providerList = Constants.PROVIDER_LIST.filter(i => { const providerList = Constants.PROVIDER_LIST.filter((i) => {
return ((i === 'Skynet') && this.props.skynetSupported) || return (
((i === 'ScPrime') && this.props.scPrimeSupported) || (i === 'Skynet' && this.props.skynetSupported) ||
((i === 'Sia') && this.props.siaSupported); (i === 'ScPrime' && this.props.scPrimeSupported) ||
(i === 'Sia' && this.props.siaSupported)
);
}); });
let remoteList = []; let remoteList = [];
@@ -240,26 +249,22 @@ class MountItems extends IPCContainer {
s3List = [...this.props.S3Mounts]; s3List = [...this.props.S3Mounts];
} }
return [ return [...providerList, ...remoteList, ...s3List];
...providerList,
...remoteList,
...s3List,
];
}; };
hasActiveMount = () => { hasActiveMount = () => {
for (const provider of Object.keys(this.props.MountState)) { return !!Object.keys(this.props.MountState).find((provider) => {
if (this.props.MountState[provider].Mounted) return this.props.MountState[provider].Mounted;
return true; });
}
return false;
}; };
onDetectMountReply = (event, arg) => { onDetectMountReply = (_, arg) => {
const provider = arg.data.Provider; const provider = arg.data.Provider;
if (!this.state.RetryItems[provider]) { if (!this.state.RetryItems[provider]) {
if (arg.data.Success && (!arg.data.Active || (arg.data.Location && (arg.data.Location.length > 0)))) { if (
arg.data.Success &&
(!arg.data.Active || (arg.data.Location && arg.data.Location.length > 0))
) {
const mountState = { const mountState = {
AllowMount: true, AllowMount: true,
DriveLetters: arg.data.DriveLetters, DriveLetters: arg.data.DriveLetters,
@@ -267,7 +272,12 @@ class MountItems extends IPCContainer {
}; };
this.props.setMountState(provider, mountState); this.props.setMountState(provider, mountState);
this.updateMountLocation(provider, arg.data.Location, mountState.Mounted, arg.data.DriveLetters); this.updateMountLocation(
provider,
arg.data.Location,
mountState.Mounted,
arg.data.DriveLetters
);
this.props.setAutoMountProcessed(provider, true); this.props.setAutoMountProcessed(provider, true);
this.removeMountsBusy(provider); this.removeMountsBusy(provider);
} else { } else {
@@ -277,14 +287,20 @@ class MountItems extends IPCContainer {
} }
}; };
onMountDriveReply = (event, arg) => { onMountDriveReply = (_, arg) => {
this.props.setMounted(arg.data.Provider, arg.data.Success); this.props.setMounted(arg.data.Provider, arg.data.Success);
this.detectMount(arg.data.Provider); this.detectMount(arg.data.Provider);
this.removeMountsBusy(arg.data.Provider); this.removeMountsBusy(arg.data.Provider);
}; };
onUnmountDriveReply = (event, arg) => { onUnmountDriveReply = (_, arg) => {
if (arg && arg.data && !arg.data.Expected && arg.data.Location && this.props.ProviderState[arg.data.Provider].AutoRestart) { if (
arg &&
arg.data &&
!arg.data.Expected &&
arg.data.Location &&
this.props.ProviderState[arg.data.Provider].AutoRestart
) {
this.displayRetryMount(arg.data.Provider, arg.data.Remote, arg.data.Location); this.displayRetryMount(arg.data.Provider, arg.data.Remote, arg.data.Location);
} else { } else {
this.detectMount(arg.data.Provider); this.detectMount(arg.data.Provider);
@@ -292,140 +308,152 @@ class MountItems extends IPCContainer {
this.removeMountsBusy(arg.data.Provider); this.removeMountsBusy(arg.data.Provider);
}; };
removeMountsBusy = provider => { removeMountsBusy = (provider) => {
const idx = this.activeDetections.indexOf(provider); const idx = this.activeDetections.indexOf(provider);
if (idx > -1) { if (idx > -1) {
this.activeDetections.splice(idx, 1); this.activeDetections.splice(idx, 1);
} }
this.props.setMountsBusy((this.activeDetections.length > 0) || this.hasActiveMount()); this.props.setMountsBusy(this.activeDetections.length > 0 || this.hasActiveMount());
}; };
updateMountLocation = (provider, location, mounted, driveLetters) => { updateMountLocation = (provider, location, mounted, driveLetters) => {
const providerState = this.props.ProviderState[provider]; const providerState = this.props.ProviderState[provider];
if (location.length === 0) { if (location.length === 0) {
location = (this.props.Platform === 'win32') ? location =
!providerState.MountLocation || providerState.MountLocation.trim().length === 0 ? driveLetters[0] : providerState.MountLocation : this.props.Platform === 'win32'
providerState.MountLocation; ? !providerState.MountLocation || providerState.MountLocation.trim().length === 0
? driveLetters[0]
: providerState.MountLocation
: providerState.MountLocation;
} }
if (location !== providerState.MountLocation) { if (location !== providerState.MountLocation) {
this.handleMountLocationChanged(provider, location); this.handleMountLocationChanged(provider, location);
} }
if (!this.props.AutoMountProcessed[provider] && if (
!this.props.AutoMountProcessed[provider] &&
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), this.props.S3Mounts.includes(provider), true, location); ) {
this.handleMountUnMount(
provider,
this.props.RemoteMounts.includes(provider),
this.props.S3Mounts.includes(provider),
true,
location
);
} }
}; };
render() { render() {
let retryDisplay; let retryDisplay;
if (this.state.DisplayRetry) { if (this.state.DisplayRetry) {
let retryList = []; const retryList = [];
let retryCount = 0; let retryCount = 0;
for (const provider in this.state.RetryItems) { Object.keys(this.state.RetryItems).forEach((provider) => {
if (this.state.RetryItems.hasOwnProperty(provider)) {
if (this.state.RetryItems[provider].RetryMessage) { if (this.state.RetryItems[provider].RetryMessage) {
retryList.push(<p key={'rl_' + retryList.length}>{this.state.RetryItems[provider].RetryMessage}</p>); retryList.push(
<p key={'rl_' + retryList.length}>{this.state.RetryItems[provider].RetryMessage}</p>
);
} }
retryList.push(<Button clicked={() => this.cancelRetryMount(provider, () => this.detectMounts())} retryList.push(
key={'rl_' + retryList.length}>Cancel {provider} Remount <Button
({this.state.RetryItems[provider].RetrySeconds}s)</Button>); clicked={() => this.cancelRetryMount(provider, () => this.detectMounts())}
key={'rl_' + retryList.length}>
Cancel {provider} Remount ({this.state.RetryItems[provider].RetrySeconds}s)
</Button>
);
if (++retryCount < Object.keys(this.state.RetryItems).length) { if (++retryCount < Object.keys(this.state.RetryItems).length) {
retryList.push(<div style={{paddingTop: 'var(--default_spacing)'}} retryList.push(
key={'rl_' + retryList.length}/>); <div style={{ paddingTop: 'var(--default_spacing)' }} key={'rl_' + retryList.length} />
} );
}
} }
});
retryDisplay = ( retryDisplay = (
<Modal> <Modal>
<Box dxDark dxStyle={{ padding: 'var(--default_spacing)', minWidth: '70vw' }}> <Box dxDark dxStyle={{ padding: 'var(--default_spacing)', minWidth: '70vw' }}>
<h1 style={{ <h1
style={{
textAlign: 'center', textAlign: 'center',
paddingBottom: 'var(--default_spacing)', paddingBottom: 'var(--default_spacing)',
color: 'var(--text_color_error)' color: 'var(--text_color_error)',
}}>Mount Failed</h1> }}>
Mount Failed
</h1>
{retryList} {retryList}
</Box> </Box>
</Modal> </Modal>
) );
} }
let footerItems = []; const footerItems = [];
if (this.props.remoteSupported || this.props.s3Supported) { if (this.props.remoteSupported || this.props.s3Supported) {
footerItems.push(<AddMount remoteSupported={this.props.remoteSupported} footerItems.push(
<AddMount
remoteSupported={this.props.remoteSupported}
s3Supported={this.props.s3Supported} s3Supported={this.props.s3Supported}
key={'hi_' + footerItems.length}/>); 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 = []; const mountItems = [];
for (const provider of this.getProviderList(true)) { const addMountItem = (provider, remote, s3) => {
items.push(( if (mountItems.length > 0) {
<MountItem allowRemove={false} mountItems.push(
browseClicked={this.handleBrowseLocation} <div
changed={e => this.handleMountLocationChanged(provider, e.target.value)} key={'it_' + mountItems.length}
clicked={this.handleMountUnMount} style={{ paddingTop: 'calc(var(--default_spacing) * 2.5)' }}
key={'it_' + items.length} />
provider={provider}/> );
));
items.push(<div key={'it_' + items.length}
style={{paddingTop: 'var(--default_spacing)'}} />)
} }
mountItems.push(
<MountItem
allowRemove={remote || s3}
browseClicked={this.handleBrowseLocation}
changed={(e) => this.handleMountLocationChanged(provider, e.target.value)}
clicked={this.handleMountUnMount}
key={'it_' + mountItems.length}
provider={provider}
remote={remote}
s3={s3}
/>
);
};
this.getProviderList(true).forEach((provider) => addMountItem(provider));
if (this.props.remoteSupported) { if (this.props.remoteSupported) {
for (const provider of this.props.RemoteMounts) { this.props.RemoteMounts.forEach((provider) => addMountItem(provider, true));
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}
remote/>
));
items.push(<div key={'it_' + items.length}
style={{paddingTop: 'var(--default_spacing)'}}/>)
}
} }
if (this.props.s3Supported) { if (this.props.s3Supported) {
for (const provider of this.props.S3Mounts) { this.props.S3Mounts.forEach((provider) => addMountItem(provider, false, true));
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 <div
className={this.props.remoteSupported || this.props.s3Supported ? 'MountItemsRemote' : 'MountItems'}> className={
{items} this.props.remoteSupported || this.props.s3Supported ? 'MountItemsRemote' : 'MountItems'
}>
{mountItems}
</div> </div>
<div style={{ paddingTop: 'var(--default_spacing)' }} /> <div style={{ paddingTop: 'var(--default_spacing)' }} />
{footerItems} {footerItems}
</div>); </div>
);
} }
} }
const mapStateToProps = state => { const mapStateToProps = (state) => {
return { return {
AutoMountProcessed: state.mounts.AutoMountProcessed, AutoMountProcessed: state.mounts.AutoMountProcessed,
InstalledVersion: state.relver.InstalledVersion, InstalledVersion: state.relver.InstalledVersion,
@@ -435,20 +463,21 @@ const mapStateToProps = state => {
ProviderState: state.mounts.ProviderState, ProviderState: state.mounts.ProviderState,
RemoteMounts: state.mounts.RemoteMounts, RemoteMounts: state.mounts.RemoteMounts,
S3Mounts: state.mounts.S3Mounts, S3Mounts: state.mounts.S3Mounts,
} };
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = (dispatch) => {
return { return {
notifyError: (msg, critical, callback) => dispatch(notifyError(msg, critical, callback)), notifyError: (msg, critical, callback) => dispatch(notifyError(msg, critical, callback)),
resetMountsState: () => dispatch(resetMountsState()), resetMountsState: () => dispatch(resetMountsState()),
setAllowMount: (provider, allow) => dispatch(setAllowMount(provider, allow)), setAllowMount: (provider, allow) => dispatch(setAllowMount(provider, allow)),
setAutoMountProcessed: (provider, processed) => dispatch(setAutoMountProcessed(provider, processed)), setAutoMountProcessed: (provider, processed) =>
dispatch(setAutoMountProcessed(provider, processed)),
setMounted: (provider, mounted) => dispatch(setMounted(provider, mounted)), setMounted: (provider, mounted) => dispatch(setMounted(provider, mounted)),
setMountsBusy: busy => dispatch(setBusy(busy)), setMountsBusy: (busy) => dispatch(setBusy(busy)),
setMountState: (provider, state) => dispatch(setMountState(provider, state)), setMountState: (provider, state) => dispatch(setMountState(provider, state)),
setProviderState: (provider, state) => dispatch(setProviderState(provider, state)), setProviderState: (provider, state) => dispatch(setProviderState(provider, state)),
} };
}; };
export default connect(mapStateToProps, mapDispatchToProps)(MountItems); export default connect(mapStateToProps, mapDispatchToProps)(MountItems);

View File

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

View File

@@ -5,10 +5,7 @@ import {connect} from 'react-redux';
import * as Constants from '../../constants'; import * as Constants from '../../constants';
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 { import { downloadItem, setAllowDownload } from '../../redux/actions/download_actions';
downloadItem,
setAllowDownload
} from '../../redux/actions/download_actions';
import DropDown from '../../components/UI/DropDown/DropDown'; import DropDown from '../../components/UI/DropDown/DropDown';
import IPCContainer from '../IPCContainer/IPCContainer'; import IPCContainer from '../IPCContainer/IPCContainer';
import { notifyError } from '../../redux/actions/error_actions'; import { notifyError } from '../../redux/actions/error_actions';
@@ -19,25 +16,32 @@ class SelectAppPlatform extends IPCContainer {
Selected: 0, Selected: 0,
}; };
grabLatestRelease = appPlatform => { grabLatestRelease = (appPlatform) => {
const errorHandler = error => { const errorHandler = (error) => {
this.props.notifyError(error); this.props.notifyError(error);
this.props.setInstallTestActive(false); this.props.setInstallTestActive(false);
this.props.setAllowDownload(false); this.props.setAllowDownload(false);
}; };
axios axios
.get(Constants.RELEASES_URL) .get(Constants.RELEASES_URL)
.then(response => { .then((response) => {
try { try {
const releases = response.data.Versions.Release[appPlatform]; const releases = response.data.Versions.Release[appPlatform];
const latestVersion = releases[releases.length - 1]; const latestVersion = releases[releases.length - 1];
const release = response.data.Locations[appPlatform][latestVersion]; const release = response.data.Locations[appPlatform][latestVersion];
this.props.downloadItem(latestVersion + '.zip', Constants.INSTALL_TYPES.TestRelease, release.urls, false, latestVersion, appPlatform); this.props.downloadItem(
latestVersion + '.zip',
Constants.INSTALL_TYPES.TestRelease,
release.urls,
false,
latestVersion,
appPlatform
);
} catch (error) { } catch (error) {
errorHandler(error); errorHandler(error);
} }
}) })
.catch(error => { .catch((error) => {
errorHandler(error); errorHandler(error);
}); });
}; };
@@ -45,10 +49,10 @@ class SelectAppPlatform extends IPCContainer {
handleTestClicked = () => { handleTestClicked = () => {
this.props.setInstallTestActive(true); this.props.setInstallTestActive(true);
this.props.setAllowDownload(true); this.props.setAllowDownload(true);
this.grabLatestRelease(Constants.LINUX_SELECTABLE_PLATFORMS[this.state.Selected]) this.grabLatestRelease(Constants.LINUX_SELECTABLE_PLATFORMS[this.state.Selected]);
}; };
handleChanged = e => { handleChanged = (e) => {
this.setState({ this.setState({
...this.state, ...this.state,
Selected: Constants.LINUX_SELECTABLE_PLATFORMS.indexOf(e.target.value), Selected: Constants.LINUX_SELECTABLE_PLATFORMS.indexOf(e.target.value),
@@ -60,33 +64,40 @@ class SelectAppPlatform extends IPCContainer {
<Box dxDark dxStyle={{ padding: 'var(--default_spacing)' }}> <Box dxDark dxStyle={{ padding: 'var(--default_spacing)' }}>
<h1 className={'SAPHeading'}>Select Linux Platform</h1> <h1 className={'SAPHeading'}>Select Linux Platform</h1>
<div className={'SAPContent'}> <div className={'SAPContent'}>
<p>Repertory was unable to detect your Linux distribution. Please select one of the following and click <b>Test</b> to continue:</p> <p>
Repertory was unable to detect your Linux distribution. Please select one of the
following and click <b>Test</b> to continue:
</p>
</div> </div>
<div className={'SAPActions'}> <div className={'SAPActions'}>
<DropDown changed={this.handleChanged} <DropDown
changed={this.handleChanged}
disabled={this.props.InstallTestActive} disabled={this.props.InstallTestActive}
items={Constants.LINUX_SELECTABLE_PLATFORMS} items={Constants.LINUX_SELECTABLE_PLATFORMS}
selected={Constants.LINUX_SELECTABLE_PLATFORMS[this.state.Selected]}/> selected={Constants.LINUX_SELECTABLE_PLATFORMS[this.state.Selected]}
<Button clicked={this.handleTestClicked} />
disabled={this.props.InstallTestActive}>Test</Button> <Button clicked={this.handleTestClicked} disabled={this.props.InstallTestActive}>
Test
</Button>
</div> </div>
</Box> </Box>
); );
} }
} }
const mapStateToProps = state => { const mapStateToProps = (state) => {
return { return {
InstallTestActive: state.install.InstallTestActive, InstallTestActive: state.install.InstallTestActive,
} };
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = (dispatch) => {
return { return {
downloadItem: (name, type, urls, isWinFSP, testVersion, appPlatform) => dispatch(downloadItem(name, type, urls, isWinFSP, testVersion, appPlatform)), downloadItem: (name, type, urls, isWinFSP, testVersion, appPlatform) =>
notifyError: msg => dispatch(notifyError(msg)), dispatch(downloadItem(name, type, urls, isWinFSP, testVersion, appPlatform)),
setAllowDownload: allow => dispatch(setAllowDownload(allow)), notifyError: (msg) => dispatch(notifyError(msg)),
setInstallTestActive: active => dispatch(setInstallTestActive(active)), setAllowDownload: (allow) => dispatch(setAllowDownload(allow)),
setInstallTestActive: (active) => dispatch(setInstallTestActive(active)),
}; };
}; };

View File

@@ -9,50 +9,62 @@ import Box from '../../components/UI/Box/Box';
import { displaySkynetExport } from '../../redux/actions/skynet_actions'; import { displaySkynetExport } from '../../redux/actions/skynet_actions';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { import {
faCheckSquare, faChevronDown, faCheckSquare,
faChevronRight, faFile, faFolder, faFolderOpen, faChevronDown,
faHSquare, faMinusSquare, faPlusSquare, faChevronRight,
faSquare faFile,
faFolder,
faFolderOpen,
faHSquare,
faMinusSquare,
faPlusSquare,
faSquare,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import Button from '../../components/UI/Button/Button'; import Button from '../../components/UI/Button/Button';
const Constants = require('../../constants'); const Constants = require('../../constants');
const mapStateToProps = state => { const mapStateToProps = (state) => {
return { return {
AppBusy: state.common.AppBusy, AppBusy: state.common.AppBusy,
}; };
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = (dispatch) => {
return { return {
displaySkynetExport: display => dispatch(displaySkynetExport(display)), displaySkynetExport: (display) => dispatch(displaySkynetExport(display)),
notifyApplicationBusy: busy => dispatch(notifyApplicationBusy(busy, true)), notifyApplicationBusy: (busy) => dispatch(notifyApplicationBusy(busy, true)),
notifyError: msg => dispatch(notifyError(msg)), notifyError: (msg) => dispatch(notifyError(msg)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)), notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg, true, 'skynet_export', 'json')),
} };
}; };
export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCContainer { export default connect(
mapStateToProps,
mapDispatchToProps
)(
class extends IPCContainer {
state = { state = {
checked: [], checked: [],
clicked: {}, clicked: {},
expanded: [], expanded: [],
nodes: [], nodes: [],
second_stage: false, second_stage: false,
} };
componentDidMount() { componentDidMount() {
this.setRequestHandler(Constants.IPC_Grab_Skynet_Tree_Reply, this.onGrabSkynetTreeReply); this.setRequestHandler(Constants.IPC_Grab_Skynet_Tree_Reply, this.onGrabSkynetTreeReply);
this.setRequestHandler(Constants.IPC_Export_Skylinks_Reply, this.onExportSkylinksReply); this.setRequestHandler(Constants.IPC_Export_Skylinks_Reply, this.onExportSkylinksReply);
this.sendRequest(Constants.IPC_Grab_Skynet_Tree, {Version: this.props.version}); this.sendRequest(Constants.IPC_Grab_Skynet_Tree, {
Version: this.props.version,
});
} }
componentWillUnmount() { componentWillUnmount() {
super.componentWillUnmount(); super.componentWillUnmount();
} }
createNodes = items => { createNodes = (items) => {
/* /*
{ {
name: '', name: '',
@@ -77,7 +89,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCCon
} }
return ret; return ret;
} };
handleNavigation = () => { handleNavigation = () => {
if (this.state.second_stage) { if (this.state.second_stage) {
@@ -95,25 +107,33 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCCon
}); });
} }
} }
} };
onExportSkylinksReply = (_, arg) => { onExportSkylinksReply = (_, arg) => {
this.props.notifyApplicationBusy(false); this.props.notifyApplicationBusy(false);
if (arg.data.Success) { if (arg.data.Success) {
this.setState({ this.setState(
{
checked: [], checked: [],
clicked: {}, clicked: {},
expanded: [], expanded: [],
nodes: [], nodes: [],
second_stage: false, 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}); 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 { } else {
this.props.notifyError(arg.data.Error); this.props.notifyError(arg.data.Error);
} }
} };
onGrabSkynetTreeReply = (_, arg) => { onGrabSkynetTreeReply = (_, arg) => {
if (arg.data.Success) { if (arg.data.Success) {
@@ -123,22 +143,29 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCCon
nodes: this.createNodes(arg.data.Result), nodes: this.createNodes(arg.data.Result),
}); });
} else { } else {
this.setState({ this.setState(
{
checked: [], checked: [],
expanded: [], expanded: [],
nodes: [], nodes: [],
}, () => { },
() => {
this.props.notifyError(arg.data.Error); this.props.notifyError(arg.data.Error);
});
} }
);
} }
};
render() { render() {
return this.props.AppBusy ? (<div/>) : ( return this.props.AppBusy ? (
<Box dxDark dxStyle={{ <div />
) : (
<Box
dxDark
dxStyle={{
height: '90vh', height: '90vh',
padding: 'var(--default_spacing)', padding: 'var(--default_spacing)',
width: 'calc(100vw - (var(--default_spacing) * 4)' width: 'calc(100vw - (var(--default_spacing) * 4)',
}}> }}>
<div <div
style={{ style={{
@@ -147,89 +174,149 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends IPCCon
padding: 0, padding: 0,
marginTop: '-4px', marginTop: '-4px',
boxSizing: 'border-box', boxSizing: 'border-box',
display: 'block' display: 'block',
}}> }}>
<a href={'#'} <a
href={'#'}
onClick={() => this.props.displaySkynetExport(false)} onClick={() => this.props.displaySkynetExport(false)}
style={{cursor: 'pointer'}}>X</a> style={{ cursor: 'pointer' }}>
X
</a>
</div> </div>
<h1 <h1 className={'SkynetExportHeading'}>
className={'SkynetExportHeading'}>{this.state.second_stage ? 'Verify Exports' : 'Export Files'}</h1> {this.state.second_stage ? 'Verify Exports' : 'Export Files'}
</h1>
<div className={this.state.second_stage ? 'SkynetExportList' : 'SkynetExportTree'}> <div className={this.state.second_stage ? 'SkynetExportList' : 'SkynetExportTree'}>
{ {this.state.second_stage ? (
this.state.second_stage ? this.state.checked.map((path) => {
this.state.checked.map(path => {
return ( return (
<input readOnly <input
readOnly
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
key={path} key={path}
style={{width: '100%', marginBottom: 'var(--default_spacing)'}} style={{
width: '100%',
marginBottom: 'var(--default_spacing)',
}}
type={'text'} type={'text'}
value={path}/> value={path}
/>
); );
}) })
: ( ) : (
<CheckboxTree checked={this.state.checked} <CheckboxTree
checked={this.state.checked}
expanded={this.state.expanded} expanded={this.state.expanded}
expandOnClick expandOnClick
showExpandAll showExpandAll
icons={{ icons={{
check: <FontAwesomeIcon icon={faCheckSquare} fixedWidth check: (
style={{padding: 0, margin: 0}}/>, <FontAwesomeIcon
uncheck: <FontAwesomeIcon icon={faSquare} fixedWidth icon={faCheckSquare}
style={{padding: 0, margin: 0}}/>, fixedWidth
halfCheck: <FontAwesomeIcon icon={faHSquare} fixedWidth style={{ padding: 0, margin: 0 }}
style={{padding: 0, margin: 0}}/>, />
expandClose: <FontAwesomeIcon icon={faChevronRight} fixedWidth ),
style={{padding: 0, margin: 0}}/>, uncheck: (
expandOpen: <FontAwesomeIcon icon={faChevronDown} fixedWidth <FontAwesomeIcon icon={faSquare} fixedWidth style={{ padding: 0, margin: 0 }} />
style={{padding: 0, margin: 0}}/>, ),
expandAll: <FontAwesomeIcon icon={faPlusSquare} fixedWidth halfCheck: (
style={{padding: 0, margin: 0}}/>, <FontAwesomeIcon
collapseAll: <FontAwesomeIcon icon={faMinusSquare} fixedWidth icon={faHSquare}
style={{padding: 0, margin: 0}}/>, fixedWidth
parentClose: <FontAwesomeIcon icon={faFolder} 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 fixedWidth
color={'var(--heading_text_color)'} color={'var(--heading_text_color)'}
style={{padding: 0, margin: 0}}/>, style={{ padding: 0, margin: 0 }}
parentOpen: <FontAwesomeIcon icon={faFolderOpen} />
),
parentOpen: (
<FontAwesomeIcon
icon={faFolderOpen}
fixedWidth fixedWidth
color={'var(--heading_text_color)'} color={'var(--heading_text_color)'}
style={{padding: 0, margin: 0}}/>, style={{ padding: 0, margin: 0 }}
leaf: <FontAwesomeIcon icon={faFile} />
),
leaf: (
<FontAwesomeIcon
icon={faFile}
fixedWidth fixedWidth
color={'var(--text_color)'} color={'var(--text_color)'}
style={{padding: 0, margin: 0}}/> style={{ padding: 0, margin: 0 }}
/>
),
}} }}
nodes={this.state.nodes} nodes={this.state.nodes}
onClick={clicked => this.setState({clicked})} onClick={(clicked) => this.setState({ clicked })}
onCheck={checked => this.setState({checked})} onCheck={(checked) => this.setState({ checked })}
onExpand={expanded => this.setState({expanded})}/> onExpand={(expanded) => this.setState({ expanded })}
) />
} )}
</div> </div>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}> <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
{ {this.state.second_stage ? (
this.state.second_stage ? <Button
<Button buttonStyles={{ buttonStyles={{
height: 'auto', height: 'auto',
marginLeft: 'var(--default_spacing)', marginLeft: 'var(--default_spacing)',
marginTop: 'var(--default_spacing)', marginTop: 'var(--default_spacing)',
width: 'auto' 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> 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> </div>
</Box> </Box>
); );
} }
}); }
);

View File

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

View File

@@ -1,6 +1,7 @@
import React from 'react' import React from 'react';
import './ImportList.css' import './ImportList.css';
import Import from './Import/Import' import Import from './Import/Import';
import PropTypes from 'prop-types';
import Text from '../../../components/UI/Text/Text'; import Text from '../../../components/UI/Text/Text';
const ImportList = ({ imports_array }) => { const ImportList = ({ imports_array }) => {
@@ -8,25 +9,34 @@ const ImportList = ({imports_array}) => {
return ( return (
<div> <div>
<div className={'ImportListHeader'}> <div className={'ImportListHeader'}>
<Text type={'Heading1'} text={'Directory'} <Text
style={{minWidth: '33.33%', maxWidth: '33.33%'}}/> type={'Heading1'}
<Text type={'Heading1'} text={'Skylink'} style={{minWidth: '33.33%', maxWidth: '33.33%'}}/> 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%' }} /> <Text type={'Heading1'} text={'Token'} style={{ minWidth: '33.33%', maxWidth: '33.33%' }} />
</div> </div>
<hr /> <hr />
<div className={'ImportListOwner'}> <div className={'ImportListOwner'}>
{ {imports_array.map((data) => {
imports_array.map(data => {
return ( return (
<div key={'import_' + key++}> <div key={'import_' + key++}>
<Import data={data} /> <Import data={data} />
</div> </div>
); );
}) })}
}
</div> </div>
</div> </div>
); );
}; };
ImportList.propTypes = {
imports_array: PropTypes.array.isRequired,
};
export default ImportList; export default ImportList;

View File

@@ -1,36 +1,37 @@
import React from 'react' import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import './SkynetImport.css' import './SkynetImport.css';
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 { displaySkynetImport } from '../../redux/actions/skynet_actions'; import { displaySkynetImport } from '../../redux/actions/skynet_actions';
import ImportList from './ImportList/ImportList' import ImportList from './ImportList/ImportList';
import IPCContainer from '../IPCContainer/IPCContainer'; import IPCContainer from '../IPCContainer/IPCContainer';
import { notifyApplicationBusy } from '../../redux/actions/common_actions'; import { notifyApplicationBusy } from '../../redux/actions/common_actions';
import { import { notifyError, notifyInfo } from '../../redux/actions/error_actions';
notifyError, import { promptLocationAndReadFile } from '../../utils';
notifyInfo
} from '../../redux/actions/error_actions';
const Constants = require('../../constants'); const Constants = require('../../constants');
const mapStateToProps = state => { const mapStateToProps = (state) => {
return { return {
AppBusy: state.common.AppBusy, AppBusy: state.common.AppBusy,
}; };
}; };
const mapDispatchToProps = dispatch => { const mapDispatchToProps = (dispatch) => {
return { return {
displaySkynetImport: display => dispatch(displaySkynetImport(display)), displaySkynetImport: (display) => dispatch(displaySkynetImport(display)),
notifyApplicationBusy: busy => dispatch(notifyApplicationBusy(busy, true)), notifyApplicationBusy: (busy) => dispatch(notifyApplicationBusy(busy, true)),
notifyError: msg => dispatch(notifyError(msg)), notifyError: (msg) => dispatch(notifyError(msg)),
notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)), notifyInfo: (title, msg) => dispatch(notifyInfo(title, msg)),
} };
}; };
export default connect(mapStateToProps, mapDispatchToProps)(class SkynetImport extends IPCContainer { export default connect(
mapStateToProps,
mapDispatchToProps
)(
class SkynetImport extends IPCContainer {
state = { state = {
import_text: '', import_text: '',
imports_array: [], imports_array: [],
@@ -46,7 +47,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(class SkynetImport e
} }
displaySyntax = () => { displaySyntax = () => {
const msg = '!alternate!To import Skylinks into the root directory, list each Skylink on a new line:\n' + const msg =
'!alternate!To import Skylinks into the root directory, list each Skylink on a new line:\n' +
' AACeCiD6WQG6DzDcCdIu3cFPSxMUMoQPx46NYSyijNMKUA\n' + ' AACeCiD6WQG6DzDcCdIu3cFPSxMUMoQPx46NYSyijNMKUA\n' +
' AACyjmDGoHqY7mTaxi-rkpnKUJGZK1B4UhrF74Nv6tY6Cg\n' + ' AACyjmDGoHqY7mTaxi-rkpnKUJGZK1B4UhrF74Nv6tY6Cg\n' +
'\n' + '\n' +
@@ -71,8 +73,15 @@ export default connect(mapStateToProps, mapDispatchToProps)(class SkynetImport e
' "token": "My Password"\n' + ' "token": "My Password"\n' +
' }\n' + ' }\n' +
' ]'; ' ]';
this.props.notifyInfo('Import Syntax', msg) this.props.notifyInfo('Import Syntax', msg);
};
handleLoadFile = () => {
const data = promptLocationAndReadFile(this.props.notifyError);
if (data) {
this.setState({ import_text: data });
} }
};
handleNavigation = () => { handleNavigation = () => {
if (this.state.second_stage) { if (this.state.second_stage) {
@@ -96,7 +105,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class SkynetImport e
importsArray = JSON.parse(this.state.import_text.trim()); importsArray = JSON.parse(this.state.import_text.trim());
break; break;
} else if (item.indexOf('=') >= 0) { } else if (item.indexOf('=') >= 0) {
const parts = item.split(',') const parts = item.split(',');
let importItem = { let importItem = {
directory: '/', directory: '/',
skylink: '', skylink: '',
@@ -136,37 +145,45 @@ export default connect(mapStateToProps, mapDispatchToProps)(class SkynetImport e
this.props.notifyError(e); this.props.notifyError(e);
} }
} }
} };
onImportSkylinksReply = (_, arg) => { onImportSkylinksReply = (_, arg) => {
this.props.notifyApplicationBusy(false); this.props.notifyApplicationBusy(false);
if (arg.data.Success) { if (arg.data.Success) {
const failedImportsArray = this.state.imports_array.filter(i => { const failedImportsArray = this.state.imports_array.filter((i) => {
return arg.data.Result.failed.includes(i.skylink); return arg.data.Result.failed.includes(i.skylink);
}); });
const count = this.state.imports_array.length; const count = this.state.imports_array.length;
this.setState({ this.setState(
import_text: failedImportsArray.length > 0 ? JSON.stringify(failedImportsArray, null, 2) : '', {
import_text:
failedImportsArray.length > 0 ? JSON.stringify(failedImportsArray, null, 2) : '',
imports_array: [], imports_array: [],
second_stage: false, second_stage: false,
}, () => { },
() => {
if (failedImportsArray.length > 0) { if (failedImportsArray.length > 0) {
this.props.notifyError(`Failed to import ${failedImportsArray.length} item(s)`); this.props.notifyError(`Failed to import ${failedImportsArray.length} item(s)`);
} else { } else {
this.props.notifyInfo('Import Result', `Successfully imported ${count} item(s)`); this.props.notifyInfo('Import Result', `Successfully imported ${count} item(s)`);
} }
}); }
);
} else { } else {
this.props.notifyError(arg.data.Error); this.props.notifyError(arg.data.Error);
} }
}; };
render() { render() {
return this.props.AppBusy ? (<div/>) : ( return this.props.AppBusy ? (
<Box dxDark dxStyle={{ <div />
) : (
<Box
dxDark
dxStyle={{
height: 'auto', height: 'auto',
padding: 'var(--default_spacing)', padding: 'var(--default_spacing)',
width: 'calc(100vw - (var(--default_spacing) * 4)' width: 'calc(100vw - (var(--default_spacing) * 4)',
}}> }}>
<div <div
style={{ style={{
@@ -175,54 +192,86 @@ export default connect(mapStateToProps, mapDispatchToProps)(class SkynetImport e
padding: 0, padding: 0,
marginTop: '-4px', marginTop: '-4px',
boxSizing: 'border-box', boxSizing: 'border-box',
display: 'block' display: 'block',
}}> }}>
<a href={'#'} <a
href={'#'}
onClick={() => this.props.displaySkynetImport(false)} onClick={() => this.props.displaySkynetImport(false)}
style={{cursor: 'pointer'}}>X</a> style={{ cursor: 'pointer' }}>
X
</a>
</div> </div>
<h1 <h1 className={'SkynetImportHeading'}>
className={'SkynetImportHeading'}>{this.state.second_stage ? 'Verify Imports' : 'Import List'}</h1> {this.state.second_stage ? 'Verify Imports' : 'Import List'}
{ </h1>
this.state.second_stage ? ( {this.state.second_stage ? (
<ImportList imports_array={this.state.imports_array} /> <ImportList imports_array={this.state.imports_array} />
) : ( ) : (
<textarea autoFocus={true} <textarea
autoFocus={true}
className={'SkynetImportTextArea'} className={'SkynetImportTextArea'}
onChange={e => this.setState({ onChange={(e) =>
this.setState({
import_text: e.target.value, import_text: e.target.value,
})} })
value={this.state.import_text}
rows={10}/>
)
} }
value={this.state.import_text}
rows={10}
/>
)}
<div className={'SkynetImportButtons'}> <div className={'SkynetImportButtons'}>
<Button <Button
buttonStyles={{height: 'auto', marginTop: 'var(--default_spacing)', width: 'auto'}} buttonStyles={{
clicked={this.displaySyntax}>Import Syntax...</Button>
<div className={'SkynetActionButtons'}>
{
this.state.second_stage ?
<Button buttonStyles={{
height: 'auto', height: 'auto',
marginLeft: 'var(--default_spacing)',
marginTop: 'var(--default_spacing)', marginTop: 'var(--default_spacing)',
width: 'auto' 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> 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}
{!this.state.second_stage ? (
<Button
buttonStyles={{
height: 'auto',
marginLeft: 'var(--default_spacing)',
marginTop: 'var(--default_spacing)',
width: 'auto',
}}
clicked={this.handleLoadFile}>
{'Import File...'}
</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>
</div> </div>
</Box> </Box>
); );
} }
}); }
);

View File

@@ -1,9 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import './Password.css'; import './Password.css';
import {faEye, faEyeSlash} from '@fortawesome/free-solid-svg-icons'; import PropTypes from 'prop-types';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons';
export default class Password extends Component { class Password extends Component {
state = { state = {
button_text: 'clear', button_text: 'clear',
password: '', password: '',
@@ -12,14 +13,14 @@ export default class Password extends Component {
}; };
componentDidMount() { componentDidMount() {
if (!this.props.value || (this.props.value.length === 0)) { if (!this.props.value || this.props.value.length === 0) {
this.setState({ this.setState({
...this.state, ...this.state,
button_text: 'set', button_text: 'set',
password: '', password: '',
password2: '', password2: '',
show_password: false, show_password: false,
}) });
} else { } else {
this.setState({ this.setState({
...this.state, ...this.state,
@@ -27,7 +28,7 @@ export default class Password extends Component {
password: this.props.value, password: this.props.value,
password2: '', password2: '',
show_password: false, show_password: false,
}) });
} }
} }
@@ -52,16 +53,19 @@ export default class Password extends Component {
password2: '', password2: '',
}); });
} else { } else {
this.setState({ this.setState(
{
...this.state, ...this.state,
button_text: 'set', button_text: 'set',
password: '', password: '',
password2: '', password2: '',
}, () => { },
() => {
if (this.props.mismatchHandler) { if (this.props.mismatchHandler) {
this.props.mismatchHandler(); this.props.mismatchHandler();
} }
}); }
);
} }
break; break;
@@ -79,7 +83,7 @@ export default class Password extends Component {
} }
}; };
handlePasswordChanged = e => { handlePasswordChanged = (e) => {
if (this.state.button_text === 'set') { if (this.state.button_text === 'set') {
this.setState({ this.setState({
...this.state, ...this.state,
@@ -93,8 +97,11 @@ export default class Password extends Component {
} }
}; };
handlePasswordKeyUp = e => { handlePasswordKeyUp = (e) => {
if ((e.keyCode === 13) && ((this.state.button_text === 'confirm') || (this.state.button_text === 'set'))) { if (
e.keyCode === 13 &&
(this.state.button_text === 'confirm' || this.state.button_text === 'set')
) {
this.handleActionClick(); this.handleActionClick();
} }
}; };
@@ -109,27 +116,37 @@ export default class Password extends Component {
render() { render() {
return ( return (
<div className={'PasswordOwner'} style={{ ...this.props.style }}> <div className={'PasswordOwner'} style={{ ...this.props.style }}>
{ {this.props.readOnly ? null : (
this.props.readOnly ? null : <a href={'#'} <a href={'#'} className={'PasswordLink'} onClick={this.handleActionClick}>
className={'PasswordLink'}
onClick={this.handleActionClick}>
<u>{this.state.button_text}</u> <u>{this.state.button_text}</u>
</a> </a>
} )}
<input autoFocus={this.props.autoFocus} <input
autoFocus={this.props.autoFocus}
readOnly={this.props.readOnly} readOnly={this.props.readOnly}
className={'PasswordInput'} className={'PasswordInput'}
disabled={this.props.readOnly || (this.state.button_text === 'clear')} disabled={this.props.readOnly || this.state.button_text === 'clear'}
onChange={this.handlePasswordChanged} onChange={this.handlePasswordChanged}
onKeyUp={this.handlePasswordKeyUp} onKeyUp={this.handlePasswordKeyUp}
type={this.state.show_password ? 'text' : 'password'} type={this.state.show_password ? 'text' : 'password'}
value={(this.state.button_text === 'confirm') ? this.state.password2 : this.state.password}/> value={this.state.button_text === 'confirm' ? this.state.password2 : this.state.password}
<a href={'#'} />
className={'PasswordShowHide'} <a href={'#'} className={'PasswordShowHide'} onClick={this.handleShowHideClick}>
onClick={this.handleShowHideClick}>
<FontAwesomeIcon icon={this.state.show_password ? faEye : faEyeSlash} fixedWidth /> <FontAwesomeIcon icon={this.state.show_password ? faEye : faEyeSlash} fixedWidth />
</a> </a>
</div> </div>
); );
} }
}
Password.propTypes = {
autoFocus: PropTypes.bool,
changed: PropTypes.func,
disabled: PropTypes.bool,
mismatchHandler: PropTypes.func,
readOnly: PropTypes.bool,
style: PropTypes.object,
value: PropTypes.string,
}; };
export default Password;

View File

@@ -8,6 +8,8 @@ const spawn = require('child_process').spawn;
const Constants = require('./constants'); const Constants = require('./constants');
const RandomString = require('randomstring'); const RandomString = require('randomstring');
const IS_64BIT = process.arch.indexOf('64') >= 0;
let vcRuntimeExists; let vcRuntimeExists;
const _vcRuntimeExists = () => { const _vcRuntimeExists = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@@ -20,11 +22,13 @@ const _vcRuntimeExists = () => {
const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const cmd = path.join(process.env.windir, 'system32', 'reg.exe');
const args = [ const args = [
'QUERY', 'QUERY',
'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall' IS_64BIT
? 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
: 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
]; ];
_execProcessGetOutput(cmd, null, args) _execProcessGetOutput(cmd, null, args)
.then(lines => { .then((lines) => {
const parseLine = index => { const parseLine = (index) => {
if (index < lines.length) { if (index < lines.length) {
const line = lines[index]; const line = lines[index];
if (line.startsWith('HKEY_LOCAL_MACHINE\\')) { if (line.startsWith('HKEY_LOCAL_MACHINE\\')) {
@@ -35,22 +39,24 @@ const _vcRuntimeExists = () => {
args2.push('/t'); args2.push('/t');
args2.push('REG_SZ'); args2.push('REG_SZ');
_execProcessGetOutput(cmd, null, args2) _execProcessGetOutput(cmd, null, args2)
.then(lines => { .then((lines) => {
const value = lines[2] const value = lines[2].trim().substr(args2[3].length).trim().substr(6).trim();
.trim() if (
.substr(args2[3].length) value.includes(
.trim() IS_64BIT
.substr(6) ? 'Microsoft Visual C++ 2015-2019 Redistributable (x64)'
.trim(); : 'Microsoft Visual C++ 2015-2019 Redistributable (x32)',
if (value.includes( )
'Microsoft Visual C++ 2015-2019 Redistributable (x64)')) { ) {
vcRuntimeExists = true; vcRuntimeExists = true;
resolve(true); resolve(true);
} else { } else {
parseLine(++index); parseLine(++index);
} }
}) })
.catch(() => { parseLine(++index); }); .catch(() => {
parseLine(++index);
});
} else { } else {
parseLine(++index); parseLine(++index);
} }
@@ -60,28 +66,27 @@ const _vcRuntimeExists = () => {
}; };
parseLine(0); parseLine(0);
}) })
.catch(err => { reject(err); }); .catch((err) => {
reject(err);
});
} }
} }
}); });
}; };
// https://stackoverflow.com/questions/19531453/transform-file-directory-structure-into-tree-in-javascript // https://stackoverflow.com/questions/19531453/transform-file-directory-structure-into-tree-in-javascript
const _createTreeNodes = fileList => { const _createTreeNodes = (fileList) => {
let tree = {} let tree = {};
const directorySort = (a, b) => { const directorySort = (a, b) => {
return !!a.directory === !!b.directory ? a.name.localeCompare(b.name) return !!a.directory === !!b.directory ? a.name.localeCompare(b.name) : a.directory ? -1 : 1;
: a.directory ? -1 : 1;
}; };
const addNode = const addNode = (obj) => {
obj => {
let fullPath; let fullPath;
const idx = obj.skylink.indexOf('/'); const idx = obj.skylink.indexOf('/');
if (idx > -1) { if (idx > -1) {
fullPath = path.join(obj.directory, obj.skylink.substr(idx + 1)) fullPath = path.join(obj.directory, obj.skylink.substr(idx + 1)).replace(/\\/g, '/');
.replace(/\\/g, '/');
} else { } else {
fullPath = path.join(obj.directory, obj.filename).replace(/\\/g, '/'); fullPath = path.join(obj.directory, obj.filename).replace(/\\/g, '/');
} }
@@ -100,28 +105,27 @@ const _createTreeNodes = fileList => {
ptr[pathParts[i]].children = ptr[pathParts[i]].children || {}; ptr[pathParts[i]].children = ptr[pathParts[i]].children || {};
ptr = ptr[pathParts[i]].children; ptr = ptr[pathParts[i]].children;
} }
} };
const objectToArray = const objectToArray = (node) => {
node => { Object.keys(node || {}).forEach((k) => {
Object.keys(node || {}).map((k) => {
if (node[k].children) { if (node[k].children) {
objectToArray(node[k]) objectToArray(node[k]);
} }
}) });
if (node.children) { if (node.children) {
node.children = Object.values(node.children); node.children = Object.values(node.children);
node.children.forEach(objectToArray) node.children.forEach(objectToArray);
node.children = node.children.sort(directorySort); node.children = node.children.sort(directorySort);
} }
} };
fileList.map(addNode); fileList.map(addNode);
objectToArray(tree); objectToArray(tree);
return Object.values(tree).sort(directorySort); return Object.values(tree).sort(directorySort);
}; };
const _exportAllSkylinks = version => { const _exportAllSkylinks = (version) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
const processOptions = { const processOptions = {
@@ -137,13 +141,19 @@ const _exportAllSkylinks = version => {
let result = ''; let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => { result += d; }); process.stdout.on('data', (d) => {
result += d;
});
process.stderr.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => {
result += d;
});
process.on('exit', code => { process.on('exit', (code) => {
if (code === 0) { if (code === 0) {
result = result.substr(result.indexOf('{')); result = result.substr(result.indexOf('{'));
resolve(JSON.parse(result)); resolve(JSON.parse(result));
@@ -169,9 +179,13 @@ 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) => { reject(err, pid); }); process.on('error', (err) => {
reject(err, pid);
});
process.on('exit', (code) => { resolve(code); }); process.on('exit', (code) => {
resolve(code);
});
process.unref(); process.unref();
}); });
@@ -181,7 +195,7 @@ const _execProcessGetOutput = (cmd, working, args) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let processOptions = { let processOptions = {
env: process.env, env: process.env,
stdio : [ 'ignore', 'pipe', 'pipe' ] stdio: ['ignore', 'pipe', 'pipe'],
}; };
if (working) { if (working) {
processOptions.cwd = working; processOptions.cwd = working;
@@ -189,9 +203,13 @@ 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 => { output += data.toString(); }); proc.stdout.on('data', (data) => {
output += data.toString();
});
proc.on('error', (err) => { reject(err); }); proc.on('error', (err) => {
reject(err);
});
proc.on('exit', () => { proc.on('exit', () => {
const lines = output.replace(/\r\n/g, '\n').split('\n'); const lines = output.replace(/\r\n/g, '\n').split('\n');
@@ -202,8 +220,9 @@ 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 = () => { const _getRepertoryDirectory = () => {
return _resolvePath(Constants.REPERTORY_LOCATIONS[os.platform()]); return _resolvePath(Constants.REPERTORY_LOCATIONS[os.platform()]);
@@ -219,23 +238,25 @@ const _getDefaultRepertoryArgs = (provider, remote, s3) => {
} else if (remote) { } 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] && } else if (
(Constants.PROVIDER_ARG[providerLower].length > 0)) { 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;
}; };
const _getRepertoryExec = version => { const _getRepertoryExec = (version) => {
return { return {
cmd : (os.platform() === 'win32') ? 'repertory.exe' : './repertory', cmd: os.platform() === 'win32' ? 'repertory.exe' : './repertory',
working: path.join(_getDataDirectory(), version), working: path.join(_getDataDirectory(), version),
}; };
}; };
const _removeDirectoryRecursively = dir => { const _removeDirectoryRecursively = (dir) => {
if (fs.existsSync(dir)) { if (fs.existsSync(dir)) {
fs.readdirSync(dir).forEach(file => { fs.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);
@@ -247,9 +268,11 @@ const _removeDirectoryRecursively = dir => {
} }
}; };
const _resolvePath = str => { const _resolvePath = (str) => {
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
return str.replace(/%([^%]+)%/g, (_, n) => { return process.env[n]; }); return str.replace(/%([^%]+)%/g, (_, n) => {
return process.env[n];
});
} else { } else {
return str.replace('~', os.homedir()); return str.replace('~', os.homedir());
} }
@@ -277,25 +300,30 @@ module.exports.checkDaemonVersion = (version, 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 => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.on('exit', code => { resolve(code); }); process.on('exit', (code) => {
resolve(code);
});
process.unref(); process.unref();
}); });
}; };
module.exports.cleanupOldReleases = versionList => { module.exports.cleanupOldReleases = (versionList) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (versionList && versionList.length > 0) { if (versionList && versionList.length > 0) {
const dataDir = _getDataDirectory(); const dataDir = _getDataDirectory();
const directoryList = fs.readdirSync(dataDir, {withFileTypes : true}) const directoryList = fs
.filter(dirent => dirent.isDirectory()) .readdirSync(dataDir, { withFileTypes: true })
.map(dirent => dirent); .filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent);
const removeList = const removeList = directoryList
directoryList.filter(dirent => !versionList.includes(dirent.name)) .filter((dirent) => !versionList.includes(dirent.name))
.map(dirent => dirent.name); .map((dirent) => dirent.name);
for (const dir of removeList) { for (const dir of removeList) {
try { try {
@@ -314,10 +342,14 @@ module.exports.cleanupOldReleases = versionList => {
}; };
module.exports.createSignatureFiles = (signature, publicKey) => { module.exports.createSignatureFiles = (signature, publicKey) => {
const fileName1 = const fileName1 = RandomString.generate({
RandomString.generate({length : 12, charset : 'alphabetic'}); length: 12,
const fileName2 = charset: 'alphabetic',
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');
@@ -343,7 +375,7 @@ module.exports.detectRepertoryMounts = (version, providerList) => {
PID: -1, PID: -1,
}; };
} }
const grabStatus = index => { const grabStatus = (index) => {
if (index >= providerList.length) { if (index >= providerList.length) {
resolve(mountState); resolve(mountState);
} else { } else {
@@ -360,24 +392,30 @@ module.exports.detectRepertoryMounts = (version, providerList) => {
provider, provider,
!Constants.PROVIDER_LIST.includes(provider) && !Constants.PROVIDER_LIST.includes(provider) &&
provider.toLowerCase().startsWith('remote'), provider.toLowerCase().startsWith('remote'),
!Constants.PROVIDER_LIST.includes(provider) && !Constants.PROVIDER_LIST.includes(provider) && provider.toLowerCase().startsWith('s3'),
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) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => { result += d; }); process.stdout.on('data', (d) => {
result += d;
});
process.on('exit', () => { process.on('exit', () => {
mountState[provider] = mountState[provider] = _tryParse(result, defaultData)[provider] || defaultData;
_tryParse(result, defaultData)[provider] || defaultData; if (
if (mountState[provider].Active && mountState[provider].Active &&
((mountState[provider].Location === 'elevating') || (mountState[provider].Location === 'elevating' || mountState[provider].Location === '')
(mountState[provider].Location === ''))) { ) {
setTimeout(() => { grabStatus(index); }, 2000); setTimeout(() => {
grabStatus(index);
}, 2000);
} else { } else {
grabStatus(++index); grabStatus(++index);
} }
@@ -389,8 +427,7 @@ module.exports.detectRepertoryMounts = (version, providerList) => {
}); });
}; };
module.exports.downloadFile = (url, destination, progressCallback, module.exports.downloadFile = (url, destination, progressCallback, completeCallback) => {
completeCallback) => {
try { try {
if (fs.existsSync(destination)) { if (fs.existsSync(destination)) {
fs.unlinkSync(destination); fs.unlinkSync(destination);
@@ -404,7 +441,7 @@ module.exports.downloadFile = (url, destination, progressCallback,
.get(url, { .get(url, {
responseType: 'stream', responseType: 'stream',
}) })
.then(response => { .then((response) => {
try { try {
const total = parseInt(response.headers['content-length'], 10); const total = parseInt(response.headers['content-length'], 10);
if (total === 0) { if (total === 0) {
@@ -417,7 +454,7 @@ module.exports.downloadFile = (url, destination, progressCallback,
stream.write(Buffer.from(chunk)); stream.write(Buffer.from(chunk));
downloaded += chunk.length; downloaded += chunk.length;
if (progressCallback) { if (progressCallback) {
progressCallback((downloaded / total * 100.0).toFixed(2)); progressCallback(((downloaded / total) * 100.0).toFixed(2));
} }
}); });
@@ -426,33 +463,38 @@ module.exports.downloadFile = (url, destination, progressCallback,
if (downloaded === 0) { if (downloaded === 0) {
completeCallback(new Error('Received 0 bytes')); completeCallback(new Error('Received 0 bytes'));
} else if (downloaded !== total) { } else if (downloaded !== total) {
completeCallback( completeCallback(new Error('Received incorrect number of bytes'));
new Error('Received incorrect number of bytes'));
} else { } else {
completeCallback(); completeCallback();
} }
}); });
}); });
response.data.on( response.data.on('error', (error) => {
'error', stream.end(() => {
error => { stream.end(() => { completeCallback(error); }); }); completeCallback(error);
});
});
} }
} catch (error) { } catch (error) {
completeCallback(error); completeCallback(error);
} }
}) })
.catch(error => { completeCallback(error); }); .catch((error) => {
completeCallback(error);
});
}; };
module.exports.executeAndWait = (command, ignoreResult) => { module.exports.executeAndWait = (command, ignoreResult) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const retryExecute = (count, lastError) => { const retryExecute = (count, lastError) => {
if (++count <= 5) { if (++count <= 5) {
exec(command, error => { exec(command, (error) => {
if (error) { if (error) {
if (!ignoreResult && (error.code === 1)) { if (!ignoreResult && error.code === 1) {
setTimeout(() => { retryExecute(count, error); }, 1000); setTimeout(() => {
retryExecute(count, error);
}, 1000);
} else { } else {
reject(error); reject(error);
} }
@@ -472,9 +514,8 @@ 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 const working =
? null cmd.length === command.length ? null : command.substr(0, command.length - cmd.length);
: command.substr(0, command.length - cmd.length);
let processOptions = { let processOptions = {
detached: true, detached: true,
shell: false, shell: false,
@@ -489,14 +530,19 @@ module.exports.executeAsync = (command, args = []) => {
const process = new spawn(cmd, args, processOptions); const process = new spawn(cmd, args, processOptions);
const pid = process.pid; const pid = process.pid;
process.on('error', err => { process.on('error', (err) => {
if (++count === 5) { if (++count === 5) {
reject(err, pid); reject(err, pid);
} else { } else {
clearTimeout(timeout); clearTimeout(timeout);
setTimeout( setTimeout(
() => launchProcess(count, setTimeout(() => resolve(), 3000)), () =>
1000); launchProcess(
count,
setTimeout(() => resolve(), 3000),
),
1000,
);
} }
}); });
@@ -507,8 +553,13 @@ module.exports.executeAsync = (command, args = []) => {
} else { } else {
clearTimeout(timeout); clearTimeout(timeout);
setTimeout( setTimeout(
() => launchProcess(count, setTimeout(() => resolve(), 3000)), () =>
1000); launchProcess(
count,
setTimeout(() => resolve(), 3000),
),
1000,
);
} }
} }
}); });
@@ -516,11 +567,14 @@ module.exports.executeAsync = (command, args = []) => {
process.unref(); process.unref();
}; };
launchProcess(0, setTimeout(() => resolve(), 3000)); launchProcess(
0,
setTimeout(() => resolve(), 3000),
);
}); });
}; };
module.exports.executeScript = script => { module.exports.executeScript = (script) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const processOptions = { const processOptions = {
detached: false, detached: false,
@@ -534,17 +588,22 @@ module.exports.executeScript = script => {
const process = new spawn(command, args, processOptions); const process = new spawn(command, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => { result += d; }); process.stdout.on('data', (d) => {
result += d;
});
process.on('exit', () => { resolve(result); }); process.on('exit', () => {
resolve(result);
});
process.unref(); process.unref();
}); });
}; };
module.exports.executeMount = module.exports.executeMount = (version, provider, remote, s3, location, exitCallback) => {
(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 = {
@@ -556,7 +615,7 @@ module.exports.executeMount =
const args = _getDefaultRepertoryArgs(provider, remote, s3); 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');
args.push('big_writes'); args.push('big_writes');
args.push('-f'); args.push('-f');
@@ -569,7 +628,9 @@ module.exports.executeMount =
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(() => { resolve(pid); }, 3000); const timeout = setTimeout(() => {
resolve(pid);
}, 3000);
process.on('error', (err) => { process.on('error', (err) => {
clearTimeout(timeout); clearTimeout(timeout);
@@ -602,13 +663,19 @@ module.exports.exportSkylinks = (version, paths) => {
let result = ''; let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => { result += d; }); process.stdout.on('data', (d) => {
result += d;
});
process.stderr.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => {
result += d;
});
process.on('exit', code => { process.on('exit', (code) => {
if (code === 0) { if (code === 0) {
result = result.substr(result.indexOf('{')); result = result.substr(result.indexOf('{'));
resolve(JSON.parse(result)); resolve(JSON.parse(result));
@@ -637,9 +704,13 @@ module.exports.getConfig = (version, provider, remote, s3) => {
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => { result += d; }); process.stdout.on('data', (d) => {
result += d;
});
process.on('exit', () => { process.on('exit', () => {
const lines = result.replace(/\r\n/g, '\n').split('\n'); const lines = result.replace(/\r\n/g, '\n').split('\n');
@@ -675,11 +746,17 @@ module.exports.getConfigTemplate = (version, provider, remote, s3) => {
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
let result = ''; let result = '';
process.on('error', (err) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => { result += d; }); process.stdout.on('data', (d) => {
result += d;
});
process.on('exit', () => { resolve(JSON.parse(result)); }); process.on('exit', () => {
resolve(JSON.parse(result));
});
process.unref(); process.unref();
}); });
}; };
@@ -688,7 +765,7 @@ module.exports.getDataDirectory = _getDataDirectory;
module.exports.getRepertoryDirectory = _getRepertoryDirectory; 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(new Error('Dependency list is invalid')); reject(new Error('Dependency list is invalid'));
@@ -708,7 +785,7 @@ module.exports.getMissingDependencies = dependencies => {
if (index >= dep.registry.length) { if (index >= dep.registry.length) {
if (dep.display === 'VC Runtime 2015-2019') { if (dep.display === 'VC Runtime 2015-2019') {
_vcRuntimeExists() _vcRuntimeExists()
.then(exists => { .then((exists) => {
if (!exists) { if (!exists) {
missing.push(dep); missing.push(dep);
} }
@@ -717,7 +794,7 @@ module.exports.getMissingDependencies = dependencies => {
.catch(() => { .catch(() => {
missing.push(dep); missing.push(dep);
resolveIfComplete(); resolveIfComplete();
}) });
} else { } else {
missing.push(dep); missing.push(dep);
resolveIfComplete(); resolveIfComplete();
@@ -769,8 +846,7 @@ module.exports.getMissingDependencies = dependencies => {
} else { } else {
for (const dep of dependencies) { for (const dep of dependencies) {
try { try {
if (!(fs.lstatSync(dep.file).isFile() || if (!(fs.lstatSync(dep.file).isFile() || fs.lstatSync(dep.file).isSymbolicLink())) {
fs.lstatSync(dep.file).isSymbolicLink())) {
missing.push(dep); missing.push(dep);
} }
} catch (e) { } catch (e) {
@@ -782,17 +858,21 @@ module.exports.getMissingDependencies = dependencies => {
}); });
}; };
module.exports.grabSkynetFileTree = version => { module.exports.grabSkynetFileTree = (version) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
_exportAllSkylinks(version) _exportAllSkylinks(version)
.then(results => { .then((results) => {
resolve([ { resolve([
{
name: '/', name: '/',
directory: true, directory: true,
children: _createTreeNodes(results.success), children: _createTreeNodes(results.success),
} ]); },
]);
}) })
.catch(e => { reject(e); }); .catch((e) => {
reject(e);
});
}); });
}; };
@@ -813,13 +893,19 @@ module.exports.grabDirectoryItems = (path, version, provider, remote, s3) => {
let result = ''; let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => { result += d; }); process.stdout.on('data', (d) => {
result += d;
});
process.stderr.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => {
result += d;
});
process.on('exit', code => { process.on('exit', (code) => {
if (code === 0) { if (code === 0) {
result = result.substr(result.indexOf('{')); result = result.substr(result.indexOf('{'));
resolve(JSON.parse(result)); resolve(JSON.parse(result));
@@ -849,13 +935,19 @@ module.exports.setPinned = (path, pinned, version, provider, remote, s3) => {
let result = ''; let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => { result += d; }); process.stdout.on('data', (d) => {
result += d;
});
process.stderr.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => {
result += d;
});
process.on('exit', code => { process.on('exit', (code) => {
if (code === 0) { if (code === 0) {
resolve(JSON.parse(result).success); resolve(JSON.parse(result).success);
} else { } else {
@@ -884,13 +976,19 @@ module.exports.importSkylinks = (version, jsonArray) => {
let result = ''; let result = '';
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d) => { result += d; }); process.stdout.on('data', (d) => {
result += d;
});
process.stderr.on('data', (d) => { result += d; }); process.stderr.on('data', (d) => {
result += d;
});
process.on('exit', code => { process.on('exit', (code) => {
if (code === 0) { if (code === 0) {
result = result.substr(result.indexOf('{')); result = result.substr(result.indexOf('{'));
resolve(JSON.parse(result)); resolve(JSON.parse(result));
@@ -904,8 +1002,7 @@ module.exports.importSkylinks = (version, jsonArray) => {
}; };
// 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, module.exports.mkDirByPathSync = (targetDir, { isRelativeToScript = false } = {}) => {
{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 : '.';
@@ -915,19 +1012,21 @@ module.exports.mkDirByPathSync = (targetDir,
try { try {
fs.mkdirSync(curDir); fs.mkdirSync(curDir);
} catch (err) { } catch (err) {
if (err.code === 'EEXIST') { // curDir already exists! if (err.code === 'EEXIST') {
// curDir already exists!
return curDir; return curDir;
} }
// To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on // To avoid `EISDIR` error on Mac and `EACCES`-->`ENOENT` and `EPERM` on
// Windows. // Windows.
if (err.code === 'ENOENT') { // Throw the original parentDir error on if (err.code === 'ENOENT') {
// Throw the original parentDir error on
// curDir `ENOENT` failure. // curDir `ENOENT` failure.
throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`); throw new Error(`EACCES: permission denied, mkdir '${parentDir}'`);
} }
const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1; const caughtErr = ['EACCES', 'EPERM', 'EISDIR'].indexOf(err.code) > -1;
if (!caughtErr || (caughtErr && (targetDir === curDir))) { if (!caughtErr || (caughtErr && targetDir === curDir)) {
throw err; // Throw if it's just the last created dir. throw err; // Throw if it's just the last created dir.
} }
} }
@@ -936,7 +1035,7 @@ module.exports.mkDirByPathSync = (targetDir,
}, initDir); }, initDir);
}; };
module.exports.performWindowsUninstall = names => { module.exports.performWindowsUninstall = (names) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (os.platform() !== 'win32') { if (os.platform() !== 'win32') {
reject('Windows OS is not being used'); reject('Windows OS is not being used');
@@ -944,11 +1043,13 @@ module.exports.performWindowsUninstall = names => {
const cmd = path.join(process.env.windir, 'system32', 'reg.exe'); const cmd = path.join(process.env.windir, 'system32', 'reg.exe');
const args = [ const args = [
'QUERY', 'QUERY',
'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall' IS_64BIT
? 'HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall'
: 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall',
]; ];
_execProcessGetOutput(cmd, null, args) _execProcessGetOutput(cmd, null, args)
.then(lines => { .then((lines) => {
const parseLine = index => { const parseLine = (index) => {
if (index < lines.length) { if (index < lines.length) {
const line = lines[index]; const line = lines[index];
if (line.startsWith('HKEY_LOCAL_MACHINE\\')) { if (line.startsWith('HKEY_LOCAL_MACHINE\\')) {
@@ -959,33 +1060,29 @@ module.exports.performWindowsUninstall = names => {
args2.push('/t'); args2.push('/t');
args2.push('REG_SZ'); args2.push('REG_SZ');
_execProcessGetOutput(cmd, null, args2) _execProcessGetOutput(cmd, null, args2)
.then(lines => { .then((lines) => {
const value = lines[2] const value = lines[2].trim().substr(args2[3].length).trim().substr(6).trim();
.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, _executeProcess('msiexec.exe', null, ['/x', productCode, '/norestart'])
[ '/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 + reject('[' + value + '] uninstall failed: ' + code);
'] uninstall failed: ' + code);
} }
}) })
.catch(err => { reject(err); }); .catch((err) => {
reject(err);
});
} else { } else {
parseLine(++index); parseLine(++index);
} }
}) })
.catch(() => { parseLine(++index); }); .catch(() => {
parseLine(++index);
});
} else { } else {
parseLine(++index); parseLine(++index);
} }
@@ -995,7 +1092,9 @@ module.exports.performWindowsUninstall = names => {
}; };
parseLine(0); parseLine(0);
}) })
.catch(err => { reject(err); }); .catch((err) => {
reject(err);
});
} }
}); });
}; };
@@ -1004,8 +1103,7 @@ module.exports.removeDirectoryRecursively = _removeDirectoryRecursively;
module.exports.resolvePath = _resolvePath; module.exports.resolvePath = _resolvePath;
module.exports.setConfigValue = module.exports.setConfigValue = (name, value, provider, remote, s3, version) => {
(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 = {
@@ -1022,9 +1120,11 @@ module.exports.setConfigValue =
const process = new spawn(repertoryExec.cmd, args, processOptions); const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.on('exit', code => { process.on('exit', (code) => {
if (code !== 0) { if (code !== 0) {
reject(new Error('Failed to set configuration value: ' + code)); reject(new Error('Failed to set configuration value: ' + code));
} else { } else {
@@ -1036,6 +1136,37 @@ module.exports.setConfigValue =
}); });
}; };
module.exports.testSkynetLogon = (version, authURL, authUser, authPassword, agentString, apiKey) => {
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('-tsa');
args.push(authURL);
args.push(authUser);
args.push(authPassword);
args.push(agentString || '');
args.push(apiKey || '');
const process = new spawn(repertoryExec.cmd, args, processOptions);
process.on('error', (err) => {
reject(err);
});
process.on('exit', (code) => {
resolve(code === 0);
});
process.unref();
});
};
module.exports.stopMountProcess = (version, provider, remote, s3) => { 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);
@@ -1051,7 +1182,9 @@ module.exports.stopMountProcess = (version, provider, remote, s3) => {
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) => { reject(err); }); process.on('error', (err) => {
reject(err);
});
process.on('exit', (code) => { process.on('exit', (code) => {
resolve({ resolve({
PID: pid, PID: pid,
@@ -1081,18 +1214,20 @@ module.exports.stopMountProcessSync = (version, provider, remote, s3) => {
process.unref(); process.unref();
}; };
module.exports.testRepertoryBinary = version => { module.exports.testRepertoryBinary = (version) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const repertoryExec = _getRepertoryExec(version); const repertoryExec = _getRepertoryExec(version);
_executeProcess(repertoryExec.cmd, repertoryExec.working, ['-dc']) _executeProcess(repertoryExec.cmd, repertoryExec.working, ['-dc'])
.then(code => { .then((code) => {
if (code === 0) { if (code === 0) {
resolve(); resolve();
} else { } else {
reject(new Error('Invalid exit code: ' + code)); reject(new Error('Invalid exit code: ' + code));
} }
}) })
.catch(error => { reject(error); }); .catch((error) => {
reject(error);
});
}); });
}; };
@@ -1108,7 +1243,7 @@ module.exports.verifyHash = (file, hash) => {
command = 'sha256sum'; command = 'sha256sum';
args = ['-b', file, '-z']; args = ['-b', file, '-z'];
} else { } else {
reject(new 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) => {
@@ -1129,27 +1264,27 @@ 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, execFile(
[ openssl,
'dgst', '-sha256', '-verify', publicKeyFile, '-signature', ['dgst', '-sha256', '-verify', publicKeyFile, '-signature', signatureFile, file],
signatureFile, file
],
(err, stdout) => { (err, stdout) => {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {
resolve(stdout); resolve(stdout);
} }
}); },
);
}; };
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
const Registry = require('winreg'); const Registry = require('winreg');
const regKey = new Registry({ const regKey = new Registry({
hive: Registry.HKLM, hive: Registry.HKLM,
key : key: IS_64BIT
'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1' ? 'SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (64-bit)_is1'
: 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\OpenSSL (32-bit)_is1',
}); });
regKey.valueExists('InstallLocation', (err, exists) => { regKey.valueExists('InstallLocation', (err, exists) => {
if (err) { if (err) {
@@ -1169,7 +1304,7 @@ module.exports.verifySignature = (file, signatureFile, publicKeyFile) => {
} else if (os.platform() === 'linux') { } else if (os.platform() === 'linux') {
executeVerify('openssl'); executeVerify('openssl');
} else { } else {
reject(new Error('Platform not supported: ' + os.platform())) reject(new Error('Platform not supported: ' + os.platform()));
} }
}); });
}; };

View File

@@ -8,28 +8,30 @@ test('verify signature success', () => {
.verifySignature( .verifySignature(
path.resolve('test/test_verify_signature.dat'), path.resolve('test/test_verify_signature.dat'),
path.resolve('test/test_verify_signature.dat.sig'), path.resolve('test/test_verify_signature.dat.sig'),
path.resolve('blockstorage_dev_public.pem')) path.resolve('blockstorage_dev_public.pem')
.then(stdout => { )
.then((stdout) => {
expect(stdout).toBeDefined(); expect(stdout).toBeDefined();
}); });
}); });
test('verify signature fail', () => { test('verify signature fail', () => {
return expect(helpers return expect(
.verifySignature( helpers.verifySignature(
path.resolve('test/test_verify_signature_fail.dat'), path.resolve('test/test_verify_signature_fail.dat'),
path.resolve('test/test_verify_signature.dat.sig'), path.resolve('test/test_verify_signature.dat.sig'),
path.resolve('blockstorage_dev_public.pem'))) path.resolve('blockstorage_dev_public.pem')
.rejects )
.toThrow() ).rejects.toThrow();
}); });
test('create temp signature files', () => { test('create temp signature files', () => {
const b64signature = fs.readFileSync(path.resolve('test/test_create_signature.sig.b64'), {encoding: 'utf8'}).replace(/(\r\n|\n|\r)/gm,""); const b64signature = fs
const data = helpers .readFileSync(path.resolve('test/test_create_signature.sig.b64'), {
.createSignatureFiles( encoding: 'utf8',
b64signature, })
Constants.DEV_PUBLIC_KEY); .replace(/(\r\n|\n|\r)/gm, '');
const data = helpers.createSignatureFiles(b64signature, Constants.DEV_PUBLIC_KEY);
expect(data).toBeDefined(); expect(data).toBeDefined();
expect(data.PublicKeyFile).toBeDefined(); expect(data.PublicKeyFile).toBeDefined();
expect(data.SignatureFile).toBeDefined(); expect(data.SignatureFile).toBeDefined();
@@ -37,22 +39,33 @@ test('create temp signature files', () => {
expect(fs.statSync(data.SignatureFile).isFile()).toBe(true); expect(fs.statSync(data.SignatureFile).isFile()).toBe(true);
expect(fs.statSync(data.PublicKeyFile).isFile()).toBe(true); expect(fs.statSync(data.PublicKeyFile).isFile()).toBe(true);
const b64signature2 = fs.readFileSync(data.SignatureFile).toString('base64').replace(/(\r\n|\n|\r)/gm,""); const b64signature2 = fs
.readFileSync(data.SignatureFile)
.toString('base64')
.replace(/(\r\n|\n|\r)/gm, '');
expect(b64signature2).toEqual(b64signature); expect(b64signature2).toEqual(b64signature);
expect(fs.readFileSync(data.PublicKeyFile, {encoding: 'utf8'})).toEqual(Constants.DEV_PUBLIC_KEY); expect(fs.readFileSync(data.PublicKeyFile, { encoding: 'utf8' })).toEqual(
Constants.DEV_PUBLIC_KEY
);
fs.unlinkSync(data.PublicKeyFile); fs.unlinkSync(data.PublicKeyFile);
fs.unlinkSync(data.SignatureFile); fs.unlinkSync(data.SignatureFile);
}); });
test('verify sha56 success', () => { test('verify sha56 success', () => {
const hash = fs.readFileSync('test/test_verify_sha256.dat.sha256', {encoding: 'utf8'}); const hash = fs.readFileSync('test/test_verify_sha256.dat.sha256', {
return expect(helpers.verifyHash(path.resolve('test/test_verify_sha256.dat'), hash)) encoding: 'utf8',
.resolves.toBe(hash) });
return expect(
helpers.verifyHash(path.resolve('test/test_verify_sha256.dat'), hash)
).resolves.toBe(hash);
}); });
test('verify sha56 fail', () => { test('verify sha56 fail', () => {
const hash = fs.readFileSync('test/test_verify_sha256.dat.sha256', {encoding: 'utf8'}); const hash = fs.readFileSync('test/test_verify_sha256.dat.sha256', {
return expect(helpers.verifyHash(path.resolve('test/test_verify_sha256_fail.dat'), hash)) encoding: 'utf8',
.rejects.toThrow(); });
return expect(
helpers.verifyHash(path.resolve('test/test_verify_sha256_fail.dat'), hash)
).rejects.toThrow();
}); });

View File

@@ -6,7 +6,7 @@
--control_border: 1px solid rgba(80, 80, 90, 0.9); --control_border: 1px solid rgba(80, 80, 90, 0.9);
--control_box_shadow: 2px 2px 2px black; --control_box_shadow: 2px 2px 2px black;
--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.75);
--text_color: rgba(200, 200, 240, 0.65); --text_color: rgba(200, 200, 240, 0.65);
--text_color_hover: rgba(200, 200, 225, 0.65); --text_color_hover: rgba(200, 200, 225, 0.65);
@@ -35,7 +35,8 @@ a {
font-weight: bold; font-weight: bold;
} }
html, body { html,
body {
height: 100%; height: 100%;
width: 100%; width: 100%;
margin: 0; margin: 0;
@@ -52,7 +53,9 @@ p {
text-align: center; text-align: center;
} }
h1, h2, h3 { h1,
h2,
h3 {
padding: 0; padding: 0;
margin: 0; margin: 0;
font-weight: bold; font-weight: bold;
@@ -65,7 +68,8 @@ h1 {
color: var(--heading_text_color); color: var(--heading_text_color);
} }
h2, h3 { h2,
h3 {
color: var(--heading_other_text_color); color: var(--heading_other_text_color);
} }
@@ -81,19 +85,23 @@ p {
overflow-y: scroll; overflow-y: scroll;
} }
.scrollable-content, ::-webkit-scrollbar { .scrollable-content,
::-webkit-scrollbar {
width: 8px; width: 8px;
height: 8px; height: 8px;
} }
.scrollable-content, ::-webkit-scrollbar * { .scrollable-content,
::-webkit-scrollbar * {
background: transparent; background: transparent;
} }
.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 { .scrollbar-corner,
::-webkit-scrollbar-corner {
background: transparent; background: transparent;
} }

View File

@@ -12,7 +12,7 @@ import {setProviderState} from './redux/actions/mount_actions';
import { setActiveRelease } from './redux/actions/release_version_actions'; import { setActiveRelease } from './redux/actions/release_version_actions';
import createAppStore from './redux/store/createAppStore'; import createAppStore from './redux/store/createAppStore';
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from './serviceWorker';
import {getIPCRenderer} from './utils'; import { getIPCRenderer } from './utils.jsx';
const Constants = require('./constants'); const Constants = require('./constants');
@@ -44,17 +44,17 @@ if (ipcRenderer) {
} }
store.dispatch(setProviderState(provider, state)); store.dispatch(setProviderState(provider, state));
} }
store.dispatch( store.dispatch(setActiveRelease(result.data.Release, result.data.Version));
setActiveRelease(result.data.Release, result.data.Version));
} else { } else {
store = createAppStore(platformInfo, packageJson.version, {}); store = createAppStore(platformInfo, packageJson.version, {});
} }
ReactDOM.render(( ReactDOM.render(
<Provider store={store}> <Provider store={store}>
<App /> <App />
</Provider> </Provider>,
), document.getElementById('root')); document.getElementById('root')
);
serviceWorker.unregister(); serviceWorker.unregister();
}); });
ipcRenderer.send(Constants.IPC_Get_State); ipcRenderer.send(Constants.IPC_Get_State);

View File

@@ -1,31 +1,15 @@
import * as Constants from '../../constants';
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import {getIPCRenderer} from '../../utils'; import { createResponseDialogAction } from '../utils';
export const confirmYesNoAction = createResponseDialogAction('common', 'confirmYesNo');
import * as Constants from '../../constants';
import { getIPCRenderer } from '../../utils.jsx';
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
let yesNoResolvers = [];
export const confirmYesNo = title => { export const displaySelectAppPlatform = (display) => {
return dispatch => { return (dispatch) => {
return new Promise(resolve => {
dispatch(handleConfirmYesNo(true, title, resolve));
});
};
};
export const DISPLAY_CONFIRM_YES_NO = 'common/displayConfirmYesNo';
const displayConfirmYesNo = (show, title) => {
return {
type: DISPLAY_CONFIRM_YES_NO,
payload: {
show,
title
},
};
};
export const displaySelectAppPlatform = display => {
return dispatch => {
if (display) { if (display) {
dispatch(showWindow()); dispatch(showWindow());
} }
@@ -34,69 +18,47 @@ export const displaySelectAppPlatform = display => {
}; };
}; };
export const hideConfirmYesNo = confirmed => {
return dispatch => {
dispatch(handleConfirmYesNo(false, confirmed));
};
};
const handleConfirmYesNo = (show, titleOrConfirmed, resolve) => {
return dispatch => {
if (show) {
yesNoResolvers.push(resolve);
dispatch(displayConfirmYesNo(show, titleOrConfirmed));
} else {
yesNoResolvers[0](titleOrConfirmed);
yesNoResolvers.splice(0, 1);
dispatch(displayConfirmYesNo(false));
}
};
};
export const NOTIFY_APPLICATION_BUSY = 'common/notifyApplicationBusy'; export const NOTIFY_APPLICATION_BUSY = 'common/notifyApplicationBusy';
export const notifyApplicationBusy = (busy, transparent) => { export const notifyApplicationBusy = (busy, transparent) => {
return { return {
type: NOTIFY_APPLICATION_BUSY, type: NOTIFY_APPLICATION_BUSY,
payload: { payload: { busy, transparent },
busy,
transparent
},
}; };
}; };
export const notifyRebootRequired = createAction('common/notifyRebootRequired'); export const notifyRebootRequired = createAction('common/notifyRebootRequired');
export const rebootSystem = () => { export const rebootSystem = () => {
return dispatch => { return (dispatch) => {
dispatch(setApplicationReady(false)); dispatch(setApplicationReady(false));
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.send(Constants.IPC_Reboot_System); ipcRenderer.send(Constants.IPC_Reboot_System);
} }
} };
}; };
export const saveState = () => { export const saveState = () => {
return (dispatch, getState) => { return (_, getState) => {
const state = getState(); const state = getState();
if (state.common.AppReady) { if (state.common.AppReady) {
let currentState = { let currentState = {
Release: state.relver.Release, Release: state.relver.Release,
RemoteMounts: state.mounts.RemoteMounts, RemoteMounts: state.mounts.RemoteMounts,
S3Mounts: state.mounts.S3Mounts,
Version: state.relver.Version, Version: state.relver.Version,
}; };
const providerList = [ const providerList = [
...Constants.PROVIDER_LIST, ...Constants.PROVIDER_LIST,
...state.mounts.RemoteMounts, ...state.mounts.RemoteMounts,
...state.mounts.S3Mounts,
]; ];
for (const provider of providerList) { for (const provider of providerList) {
currentState[provider] = state.mounts.ProviderState[provider]; currentState[provider] = state.mounts.ProviderState[provider];
} }
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.send(Constants.IPC_Save_State, { ipcRenderer.send(Constants.IPC_Save_State, { State: currentState });
State: currentState
});
} }
} }
}; };
@@ -106,7 +68,7 @@ export const setAllowMount = createAction('common/setAllowMount');
export const setApplicationReady = createAction('common/setApplicationReady'); export const setApplicationReady = createAction('common/setApplicationReady');
export const SET_DISPLAY_SELECT_APPPLATFORM = 'common/displaySelectAppPlatform'; export const SET_DISPLAY_SELECT_APPPLATFORM = 'common/displaySelectAppPlatform';
export const setDisplaySelectAppPlatform = display => { export const setDisplaySelectAppPlatform = (display) => {
return { return {
type: SET_DISPLAY_SELECT_APPPLATFORM, type: SET_DISPLAY_SELECT_APPPLATFORM,
payload: display, payload: display,
@@ -116,14 +78,14 @@ export const setDisplaySelectAppPlatform = display => {
export const setLinuxAppPlatform = createAction('common/setLinuxAppPlatform'); export const setLinuxAppPlatform = createAction('common/setLinuxAppPlatform');
export const setRebootRequired = () => { export const setRebootRequired = () => {
return dispatch => { return (dispatch) => {
dispatch(showWindow()); dispatch(showWindow());
dispatch(notifyRebootRequired(true)); dispatch(notifyRebootRequired(true));
}; };
}; };
export const showWindow = () => { export const showWindow = () => {
return dispatch => { return () => {
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.send(Constants.IPC_Show_Window); ipcRenderer.send(Constants.IPC_Show_Window);
} }
@@ -131,7 +93,7 @@ export const showWindow = () => {
}; };
export const shutdownApplication = () => { export const shutdownApplication = () => {
return dispatch => { return (dispatch) => {
dispatch(setApplicationReady(false)); dispatch(setApplicationReady(false));
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.send(Constants.IPC_Shutdown); ipcRenderer.send(Constants.IPC_Shutdown);

View File

@@ -1,26 +1,21 @@
import * as Constants from '../../constants';
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import {getIPCRenderer} from '../../utils';
import * as Constants from '../../constants';
import { getIPCRenderer } from '../../utils.jsx';
import { notifyError } from './error_actions'; import { notifyError } from './error_actions';
import { import {
installAndTestRelease, installAndTestRelease,
installDependency, installDependency,
installRelease, installRelease,
installUpgrade installUpgrade,
} from './install_actions'; } from './install_actions';
export const setAllowDownload = createAction('download/setAllowDownload'); export const setAllowDownload = createAction('download/setAllowDownload');
export const SET_DOWNLOAD_BEGIN = 'download/setDownloadBegin'; export const SET_DOWNLOAD_BEGIN = 'download/setDownloadBegin';
export const setDownloadBegin = (name, type, url) => { export const setDownloadBegin = (name, type, url) => {
return { return { type: SET_DOWNLOAD_BEGIN, payload: { name, type, url } };
type: SET_DOWNLOAD_BEGIN,
payload: {
name,
type,
url
}
};
}; };
export const setDownloadEnd = createAction('download/setDownloadEnd'); export const setDownloadEnd = createAction('download/setDownloadEnd');
@@ -32,7 +27,7 @@ export const downloadItem = (name, type, urls, isWinFSP, testVersion, appPlatfor
urls = [urls]; urls = [urls];
} }
const downloadComplete = result => { const downloadComplete = (result) => {
if (result.Success) { if (result.Success) {
switch (type) { switch (type) {
case Constants.INSTALL_TYPES.Dependency: case Constants.INSTALL_TYPES.Dependency:
@@ -45,10 +40,11 @@ export const downloadItem = (name, type, urls, isWinFSP, testVersion, appPlatfor
dispatch(installAndTestRelease(result.Destination, testVersion, appPlatform)); dispatch(installAndTestRelease(result.Destination, testVersion, appPlatform));
break; break;
case Constants.INSTALL_TYPES.Upgrade: case Constants.INSTALL_TYPES.Upgrade:
//const info = this.props.LocationsLookup[this.props.AppPlatform][this.props.VersionLookup[this.props.AppPlatform][0]]; // const info =
const sha256 = null;//info.sha256; // this.props.LocationsLookup[this.props.AppPlatform][this.props.VersionLookup[this.props.AppPlatform][0]];
const signature = null;//info.sig; //const sha256 = null; // info.sha256;
dispatch(installUpgrade(result.Destination, sha256, signature, !!result.SkipVerification)); //const signature = null; // info.sig;
dispatch(installUpgrade(result.Destination, null, null, !!result.SkipVerification));
break; break;
default: default:
dispatch(notifyError('Unknown download type: ' + type)); dispatch(notifyError('Unknown download type: ' + type));
@@ -62,10 +58,10 @@ export const downloadItem = (name, type, urls, isWinFSP, testVersion, appPlatfor
} }
}; };
const downloadAtIndex = index => { const downloadAtIndex = (index) => {
const url = urls[index]; const url = urls[index];
const state = getState(); const state = getState();
if ((index > 0) || (!state.download.DownloadActive && state.download.AllowDownload)) { if (index > 0 || (!state.download.DownloadActive && state.download.AllowDownload)) {
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
if (ipcRenderer) { if (ipcRenderer) {
dispatch(setDownloadBegin(name, type, url)); dispatch(setDownloadBegin(name, type, url));
@@ -76,7 +72,7 @@ export const downloadItem = (name, type, urls, isWinFSP, testVersion, appPlatfor
const downloadFileComplete = (_, arg) => { const downloadFileComplete = (_, arg) => {
ipcRenderer.removeListener(Constants.IPC_Download_File_Progress, downloadFileProgress); ipcRenderer.removeListener(Constants.IPC_Download_File_Progress, downloadFileProgress);
if (!arg.data.Success && (++index < urls.length)) { if (!arg.data.Success && ++index < urls.length) {
downloadAtIndex(index); downloadAtIndex(index);
} else { } else {
downloadComplete(arg.data); downloadComplete(arg.data);

View File

@@ -1,7 +1,4 @@
import { import { showWindow, shutdownApplication } from './common_actions';
showWindow,
shutdownApplication
} from './common_actions';
let ErrorActions = []; let ErrorActions = [];
@@ -36,13 +33,13 @@ export const dismissError = () => {
}; };
export const dismissInfo = () => { export const dismissInfo = () => {
return dispatch => { return (dispatch) => {
dispatch(clearInfo()); dispatch(clearInfo());
}; };
}; };
export const notifyError = (msg, critical, callback) => { export const notifyError = (msg, critical, callback) => {
return dispatch => { return (dispatch) => {
ErrorActions = [callback, ...ErrorActions]; ErrorActions = [callback, ...ErrorActions];
msg = msg ? msg.toString() : 'Unknown Error'; msg = msg ? msg.toString() : 'Unknown Error';
dispatch(setErrorInfo(msg, critical)); dispatch(setErrorInfo(msg, critical));
@@ -50,33 +47,23 @@ export const notifyError = (msg, critical, callback) => {
}; };
}; };
export const notifyInfo = (title, msg) => { export const notifyInfo = (title, msg, saveToFile, fileName, extension) => {
return dispatch => { return (dispatch) => {
title = title ? title.toString() : 'Information'; title = title ? title.toString() : 'Information';
msg = msg ? msg.toString() : ''; msg = msg ? msg.toString() : '';
dispatch(setInfo(title, msg)); dispatch(setInfo(title, msg, saveToFile, fileName, extension));
}; };
}; };
export const SET_ERROR_INFO = 'error/setErrorInfo'; export const SET_ERROR_INFO = 'error/setErrorInfo';
export const setErrorInfo = (msg, critical) => { export const setErrorInfo = (msg, critical) => {
return { return { type: SET_ERROR_INFO, payload: { msg, critical } };
type: SET_ERROR_INFO,
payload: {
msg,
critical
}
}
}; };
export const SET_INFO = 'error/setInfo'; export const SET_INFO = 'error/setInfo';
export const setInfo = (title, msg) => { export const setInfo = (title, msg, saveToFile, fileName, extension) => {
return { return {
type: SET_INFO, type: SET_INFO,
payload: { payload: { title, msg, saveToFile, fileName, extension },
title, };
msg
}
}
}; };

View File

@@ -0,0 +1,3 @@
import { createResponseDialogAction } from '../utils';
export const addEditHostAction = createResponseDialogAction('host', 'displayAddEditHost');

View File

@@ -1,35 +1,34 @@
import * as Constants from '../../constants';
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import * as Constants from '../../constants';
import { getIPCRenderer, getSelectedVersionFromState } from '../../utils.jsx';
import { import {
getIPCRenderer, confirmYesNoAction,
getSelectedVersionFromState
} from '../../utils';
import {notifyError} from './error_actions';
import {downloadItem, setAllowDownload} from './download_actions';
import {
loadReleases,
setActiveRelease,
setInstalledVersion,
setReleaseUpgradeAvailable,
setNewReleasesAvailable2,
} from './release_version_actions';
import {
confirmYesNo,
displaySelectAppPlatform, displaySelectAppPlatform,
setAllowMount, setAllowMount,
setApplicationReady, setApplicationReady,
setLinuxAppPlatform, setLinuxAppPlatform,
setRebootRequired, setRebootRequired,
showWindow, showWindow,
shutdownApplication shutdownApplication,
} from './common_actions'; } from './common_actions';
import { downloadItem, setAllowDownload } from './download_actions';
import { notifyError } from './error_actions';
import { unmountAll } from './mount_actions'; import { unmountAll } from './mount_actions';
import {
loadReleases,
setActiveRelease,
setInstalledVersion,
setNewReleasesAvailable2,
setReleaseUpgradeAvailable,
} from './release_version_actions';
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
export const checkInstalled = (dependencies, version) => { export const checkInstalled = (dependencies, version) => {
return (dispatch, getState) => { return (dispatch, getState) => {
const checkInstalledComplete = (event, arg) => { const checkInstalledComplete = (_, arg) => {
const result = arg.data; const result = arg.data;
const updateState = () => { const updateState = () => {
const installedVersion = result.Success && result.Exists ? result.Version : 'none'; const installedVersion = result.Success && result.Exists ? result.Version : 'none';
@@ -40,7 +39,8 @@ export const checkInstalled = (dependencies, version) => {
let upgradeAvailable = false; let upgradeAvailable = false;
if (installedVersion !== 'none') { if (installedVersion !== 'none') {
const latestVersion = state.relver.VersionLookup[Constants.RELEASE_TYPES[release]].length - 1; const latestVersion =
state.relver.VersionLookup[Constants.RELEASE_TYPES[release]].length - 1;
if (version === -1) { if (version === -1) {
version = latestVersion; version = latestVersion;
dispatch(setActiveRelease(release, version)); dispatch(setActiveRelease(release, version));
@@ -57,11 +57,13 @@ export const checkInstalled = (dependencies, version) => {
const autoInstallRelease = getState().install.AutoInstallRelease; const autoInstallRelease = getState().install.AutoInstallRelease;
dispatch(setAutoInstallRelease(false)); 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) { } else if (installedVersion === 'none' && autoInstallRelease) {
dispatch(setAllowMount(false)); dispatch(setAllowMount(false));
const versionString = getState().relver.VersionLookup[Constants.RELEASE_TYPES[release]][version]; const versionString = getState().relver.VersionLookup[Constants.RELEASE_TYPES[release]][
version
];
const urls = getState().relver.LocationsLookup[versionString].urls; const urls = getState().relver.LocationsLookup[versionString].urls;
const fileName = versionString + '.zip'; const fileName = versionString + '.zip';
dispatch(downloadItem(fileName, Constants.INSTALL_TYPES.Release, urls)); dispatch(downloadItem(fileName, Constants.INSTALL_TYPES.Release, urls));
@@ -89,9 +91,12 @@ export const checkVersionInstalled = () => {
dispatch(setAllowDownload(false)); dispatch(setAllowDownload(false));
const selectedVersion = getSelectedVersionFromState(state); const selectedVersion = getSelectedVersionFromState(state);
if (selectedVersion && (selectedVersion !== 'unavailable')) { if (selectedVersion && selectedVersion !== 'unavailable') {
let dependencies = []; let dependencies = [];
if (state.relver.LocationsLookup[selectedVersion] && state.relver.LocationsLookup[selectedVersion].dependencies) { if (
state.relver.LocationsLookup[selectedVersion] &&
state.relver.LocationsLookup[selectedVersion].dependencies
) {
dependencies = state.relver.LocationsLookup[selectedVersion].dependencies; dependencies = state.relver.LocationsLookup[selectedVersion].dependencies;
} }
dispatch(checkInstalled(dependencies, selectedVersion)); dispatch(checkInstalled(dependencies, selectedVersion));
@@ -107,7 +112,7 @@ export const installDependency = (source, url, isWinFSP) => {
if (ipcRenderer && !getState().install.InstallActive) { if (ipcRenderer && !getState().install.InstallActive) {
dispatch(setInstallActive(Constants.INSTALL_TYPES.Dependency)); dispatch(setInstallActive(Constants.INSTALL_TYPES.Dependency));
const installDependencyComplete = (event, arg) => { const installDependencyComplete = (_, arg) => {
const result = arg.data; const result = arg.data;
const handleCompleted = () => { const handleCompleted = () => {
if (result.RebootRequired) { if (result.RebootRequired) {
@@ -122,7 +127,7 @@ export const installDependency = (source, url, isWinFSP) => {
}; };
if (result.Success && source.toLowerCase().endsWith('.dmg')) { if (result.Success && source.toLowerCase().endsWith('.dmg')) {
const dep = getState().install.MissingDependencies.find(d => { const dep = getState().install.MissingDependencies.find((d) => {
return d.download === url; return d.download === url;
}); });
const i = setInterval(() => { const i = setInterval(() => {
@@ -160,7 +165,7 @@ export const installAndTestRelease = (source, version, appPlatform) => {
FilePath: source, FilePath: source,
}); });
ipcRenderer.once(Constants.IPC_Test_Release_Reply, (event, arg) => { ipcRenderer.once(Constants.IPC_Test_Release_Reply, (_, arg) => {
if (arg.data.Success) { if (arg.data.Success) {
ipcRenderer.sendSync(Constants.IPC_Set_Linux_AppPlatform, { ipcRenderer.sendSync(Constants.IPC_Set_Linux_AppPlatform, {
AppPlatform: appPlatform, AppPlatform: appPlatform,
@@ -177,7 +182,7 @@ export const installAndTestRelease = (source, version, appPlatform) => {
}); });
ipcRenderer.send(Constants.IPC_Test_Release, { ipcRenderer.send(Constants.IPC_Test_Release, {
Version: version, Version: version,
}) });
}; };
ipcRenderer.once(Constants.IPC_Extract_Release_Complete, extractReleaseComplete); ipcRenderer.once(Constants.IPC_Extract_Release_Complete, extractReleaseComplete);
@@ -201,26 +206,26 @@ export const installReleaseByVersion = (release, version) => {
}; };
if (getState().mounts.MountsBusy) { if (getState().mounts.MountsBusy) {
dispatch(confirmYesNo('Unmount all drives?')) dispatch(confirmYesNoAction.display(true, null, { title: 'Unmount all drives?' }))
.then(confirmed => { .then(({ changed }) => {
if (confirmed) { if (changed) {
dispatch(unmountAll(install)); dispatch(unmountAll(install));
} }
}) })
.catch(error => notifyError(error)); .catch((error) => notifyError(error));
} else { } else {
install(); 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) {
dispatch(setInstallActive(Constants.INSTALL_TYPES.Release)); dispatch(setInstallActive(Constants.INSTALL_TYPES.Release));
const version = getSelectedVersionFromState(getState()); const version = getSelectedVersionFromState(getState());
const extractReleaseComplete = (event, arg) => { const extractReleaseComplete = (_, arg) => {
ipcRenderer.send(Constants.IPC_Delete_File, { ipcRenderer.send(Constants.IPC_Delete_File, {
FilePath: source, FilePath: source,
}); });
@@ -249,19 +254,26 @@ export const installUpgrade = (source, sha256, signature, skipVerification) => {
dispatch(setInstallActive(Constants.INSTALL_TYPES.Upgrade)); dispatch(setInstallActive(Constants.INSTALL_TYPES.Upgrade));
dispatch(setApplicationReady(false)); dispatch(setApplicationReady(false));
const installUpgradeComplete = (event, arg) => { const installUpgradeComplete = (_, arg) => {
const result = arg.data; const result = arg.data;
if (result.Success) { if (result.Success) {
dispatch(shutdownApplication()); dispatch(shutdownApplication());
} else { } else {
dispatch(setApplicationReady(true)); dispatch(setApplicationReady(true));
dispatch(setInstallComplete(result)); dispatch(setInstallComplete(result));
dispatch(notifyError(result.Error, false, () => { dispatch(
notifyError(
result.Error,
false,
() => {
// TODO Prompt to verify // TODO Prompt to verify
if (result.AllowSkipVerification) { if (result.AllowSkipVerification) {
dispatch(installUpgrade(source, sha256, signature, true)); dispatch(installUpgrade(source, sha256, signature, true));
} }
}, false)); },
false
)
);
} }
}; };

View File

@@ -1,9 +1,9 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import * as Constants from '../../constants'; import * as Constants from '../../constants';
import {getIPCRenderer} from '../../utils'; import { getIPCRenderer } from '../../utils.jsx';
import {confirmYesNo, saveState} from './common_actions'; import { confirmYesNoAction, 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) => {
@@ -23,8 +23,7 @@ export const addRemoteMount = (hostNameOrIp, port, token) => {
Version: getState().relver.InstalledVersion, Version: getState().relver.InstalledVersion,
}); });
} else { } else {
dispatch( dispatch(notifyError('Failed to create remote mount: ' + arg.data.Error));
notifyError('Failed to set \'RemoteToken\': ' + arg.data.Error));
dispatch(setBusy(false)); dispatch(setBusy(false));
} }
}); });
@@ -33,7 +32,7 @@ export const addRemoteMount = (hostNameOrIp, port, token) => {
Items: [ Items: [
{ Name: 'RemoteMount.RemoteHostNameOrIp', Value: hostNameOrIp }, { Name: 'RemoteMount.RemoteHostNameOrIp', Value: hostNameOrIp },
{ Name: 'RemoteMount.RemoteToken', Value: token }, { Name: 'RemoteMount.RemoteToken', Value: token },
{Name: 'RemoteMount.RemotePort', Value: port}, { Name: 'RemoteMount.RemotePort', Value: port.toString() },
{ Name: 'RemoteMount.IsRemoteMount', Value: 'true' }, { Name: 'RemoteMount.IsRemoteMount', Value: 'true' },
], ],
Provider: provider, Provider: provider,
@@ -59,8 +58,7 @@ export const addS3Mount = (name, accessKey, secretKey, region, bucketName, url)
Version: getState().relver.InstalledVersion, Version: getState().relver.InstalledVersion,
}); });
} else { } else {
dispatch( dispatch(notifyError('Failed to create S3 instance: ' + arg.data.Error));
notifyError('Failed to create S3 instance: ' + arg.data.Error));
dispatch(setBusy(false)); dispatch(setBusy(false));
} }
}); });
@@ -95,20 +93,23 @@ export const displayConfiguration = (provider, remote, s3) => {
}; };
}; };
export const removeMount = provider => { export const removeMount = (provider) => {
return dispatch => { return (dispatch) => {
const isRemote = provider.startsWith('Remote'); const isRemote = provider.startsWith('Remote');
dispatch(confirmYesNo('Delete [' + provider.substr(isRemote ? 6 : 2) + ']?')) dispatch(
.then(confirmed => { confirmYesNoAction.display(true, null, {
if (confirmed) { title: 'Delete [' + provider.substr(isRemote ? 6 : 2) + ']?',
})
).then(({ changed }) => {
if (changed) {
dispatch(removeMount2(provider)); dispatch(removeMount2(provider));
} }
}); });
}; };
}; };
const removeMount2 = provider => { const removeMount2 = (provider) => {
return dispatch => { return (dispatch) => {
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
ipcRenderer.once(Constants.IPC_Remove_Mount_Reply, (_, arg) => { ipcRenderer.once(Constants.IPC_Remove_Mount_Reply, (_, arg) => {
if (arg.data.Success) { if (arg.data.Success) {
@@ -119,7 +120,7 @@ const removeMount2 = provider => {
const isRemote = provider.startsWith('Remote'); const isRemote = provider.startsWith('Remote');
ipcRenderer.send(Constants.IPC_Remove_Mount, { ipcRenderer.send(Constants.IPC_Remove_Mount, {
Remote: isRemote, Remote: isRemote,
Name: provider.substr(isRemote ? 6 : 2) Name: provider.substr(isRemote ? 6 : 2),
}); });
}; };
}; };
@@ -128,13 +129,11 @@ 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 {type: RESET_MOUNTS_STATE, payload: null,} return { type: RESET_MOUNTS_STATE, payload: null };
}; };
export const SET_ALLOW_MOUNT = 'mounts/setAllowMount'; export const SET_ALLOW_MOUNT = 'mounts/setAllowMount';
export const setAllowMount = export const setAllowMount = (provider, allow) => {
(provider,
allow) => {
return { type: SET_ALLOW_MOUNT, payload: { provider, allow } }; return { type: SET_ALLOW_MOUNT, payload: { provider, allow } };
}; };
@@ -146,26 +145,22 @@ export const setAutoMountProcessed = (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 = export const setMountState = (provider, state) => {
(provider,
state) => {
return { type: SET_MOUNT_STATE, payload: { provider, state } }; return { type: SET_MOUNT_STATE, payload: { provider, state } };
}; };
export const SET_MOUNTED = 'mounts/setMounted'; export const SET_MOUNTED = 'mounts/setMounted';
export const setMounted = export const setMounted = (provider, mounted) => {
(provider,
mounted) => {
return { type: SET_MOUNTED, payload: { provider, mounted } }; return { type: SET_MOUNTED, payload: { 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 {type: SET_PROVIDER_STATE, payload: {provider, state}} return { type: SET_PROVIDER_STATE, payload: { provider, state } };
}; };
export const unmountAll = completedCallback => { export const unmountAll = (completedCallback) => {
return dispatch => { return (dispatch) => {
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
const unmountedCallback = () => { const unmountedCallback = () => {
dispatch(resetMountsState()); dispatch(resetMountsState());
@@ -173,5 +168,5 @@ export const unmountAll = completedCallback => {
}; };
ipcRenderer.once(Constants.IPC_Unmount_All_Drives_Reply, unmountedCallback); ipcRenderer.once(Constants.IPC_Unmount_All_Drives_Reply, unmountedCallback);
ipcRenderer.send(Constants.IPC_Unmount_All_Drives); ipcRenderer.send(Constants.IPC_Unmount_All_Drives);
} };
}; };

View File

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

View File

@@ -1,23 +1,18 @@
import axios from 'axios';
import * as Constants from '../../constants';
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import {notifyError} from './error_actions'; import axios from 'axios';
import {
saveState, import * as Constants from '../../constants';
setAllowMount,
setApplicationReady,
showWindow
} from './common_actions';
import {
checkVersionInstalled,
setDismissDependencies
} from './install_actions';
import {unmountAll} from './mount_actions';
import { import {
checkNewReleases, checkNewReleases,
getIPCRenderer, getIPCRenderer,
getNewReleases, getSelectedVersionFromState getNewReleases,
} from '../../utils'; getSelectedVersionFromState,
} from '../../utils.jsx';
import { saveState, setAllowMount, setApplicationReady, showWindow } from './common_actions';
import { notifyError } from './error_actions';
import { checkVersionInstalled, setDismissDependencies } from './install_actions';
import { unmountAll } from './mount_actions';
export const CLEAR_UI_UPGRADE = 'relver/clearUIUpgrade'; export const CLEAR_UI_UPGRADE = 'relver/clearUIUpgrade';
export const clearUIUpgrade = () => { export const clearUIUpgrade = () => {
@@ -27,12 +22,12 @@ export const clearUIUpgrade = () => {
}; };
}; };
const cleanupOldReleases = versionList => { const cleanupOldReleases = (versionList) => {
return dispatch => { return () => {
const ipcRenderer = getIPCRenderer(); const ipcRenderer = getIPCRenderer();
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.sendSync(Constants.IPC_Cleanup_Releases + '_sync', { ipcRenderer.sendSync(Constants.IPC_Cleanup_Releases + '_sync', {
version_list: versionList version_list: versionList,
}); });
} }
}; };
@@ -42,24 +37,32 @@ export const detectUIUpgrade = () => {
return (dispatch, getState) => { return (dispatch, getState) => {
axios axios
.get(Constants.UI_RELEASES_URL) .get(Constants.UI_RELEASES_URL)
.then(response => { .then((response) => {
const state = getState(); const state = getState();
const appPlatform = state.common.AppPlatform; const appPlatform = state.common.AppPlatform;
const version = state.common.Version; const version = state.common.Version;
const data = response.data; const data = response.data;
if (data.Versions && if (
data.Versions &&
data.Versions[appPlatform] && data.Versions[appPlatform] &&
(data.Versions[appPlatform].length > 0) && data.Versions[appPlatform].length > 0 &&
(data.Versions[appPlatform][0] !== version)) { data.Versions[appPlatform][0] !== version
dispatch(setUIUpgradeData(data.Locations[appPlatform][data.Versions[appPlatform][0]], data.Versions[appPlatform][0])); ) {
dispatch(
setUIUpgradeData(
data.Locations[appPlatform][data.Versions[appPlatform][0]],
data.Versions[appPlatform][0]
)
);
if (!state.relver.UpgradeDismissed) { if (!state.relver.UpgradeDismissed) {
dispatch(showWindow()); dispatch(showWindow());
} }
} else { } else {
dispatch(clearUIUpgrade()); dispatch(clearUIUpgrade());
} }
}).catch(() => { })
.catch(() => {
dispatch(clearUIUpgrade()); dispatch(clearUIUpgrade());
}); });
}; };
@@ -78,19 +81,21 @@ export const loadReleases = () => {
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 = Constants.DEFAULT_RELEASE; 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;
} }
dispatch(setReleaseData(locationsLookup, versionLookup)); dispatch(setReleaseData(locationsLookup, versionLookup));
const dispatchActions = (processAllowDismiss = true) => { const dispatchActions = (processAllowDismiss = true) => {
dispatch(setReleaseUpgradeAvailable((version !== latestVersion))); dispatch(setReleaseUpgradeAvailable(version !== latestVersion));
dispatch(setApplicationReady(true)); dispatch(setApplicationReady(true));
dispatch(detectUIUpgrade()); dispatch(detectUIUpgrade());
if (processAllowDismiss) { if (processAllowDismiss) {
dispatch(setAllowDismissDependencies(versionLookup[Constants.RELEASE_TYPES[release]].length > 1)); dispatch(
setAllowDismissDependencies(versionLookup[Constants.RELEASE_TYPES[release]].length > 1)
);
} }
dispatch(checkVersionInstalled()); dispatch(checkVersionInstalled());
@@ -98,16 +103,18 @@ export const loadReleases = () => {
for (const key of Object.keys(locationsLookup)) { for (const key of Object.keys(locationsLookup)) {
versionList.push(key); versionList.push(key);
} }
dispatch(cleanupOldReleases(versionList)) dispatch(cleanupOldReleases(versionList));
}; };
if ((version !== state.Version) || (release !== state.Release)) { if (version !== state.Version || release !== state.Release) {
dispatch(unmountAll(() => { dispatch(
unmountAll(() => {
dispatch(setActiveRelease(release, version)); dispatch(setActiveRelease(release, version));
dispatchActions(false); dispatchActions(false);
dispatch(showWindow()); dispatch(showWindow());
dispatch(saveState()); dispatch(saveState());
})); })
);
} else { } else {
dispatchActions(); dispatchActions();
} }
@@ -115,7 +122,7 @@ export const loadReleases = () => {
axios axios
.get(Constants.RELEASES_URL) .get(Constants.RELEASES_URL)
.then(response => { .then((response) => {
const appPlatform = getState().common.AppPlatform; const appPlatform = getState().common.AppPlatform;
const versionLookup = { const versionLookup = {
Release: response.data.Versions.Release[appPlatform], Release: response.data.Versions.Release[appPlatform],
@@ -129,14 +136,21 @@ export const loadReleases = () => {
const storedReleases = localStorage.getItem('releases'); const storedReleases = localStorage.getItem('releases');
let newReleases = []; let newReleases = [];
if (storedReleases && (storedReleases.length > 0)) { if (storedReleases && storedReleases.length > 0) {
newReleases = getNewReleases(JSON.parse(storedReleases).VersionLookup, versionLookup, getSelectedVersionFromState(getState())); 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)); dispatch(setNewReleasesAvailable(newReleases));
@@ -144,12 +158,15 @@ export const loadReleases = () => {
dispatch(setNewReleasesAvailable2(newReleases)); dispatch(setNewReleasesAvailable2(newReleases));
localStorage.setItem('previous_releases', storedReleases); localStorage.setItem('previous_releases', storedReleases);
dispatch(showWindow()); dispatch(showWindow());
} else if ((newReleases = checkNewReleases(getSelectedVersionFromState(getState()))).length > 0) { } else if (
(newReleases = checkNewReleases(getSelectedVersionFromState(getState()))).length > 0
) {
dispatch(setNewReleasesAvailable2(newReleases)); 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) {
const obj = JSON.parse(releases); const obj = JSON.parse(releases);
const locationsLookup = obj.LocationsLookup; const locationsLookup = obj.LocationsLookup;
const versionLookup = obj.VersionLookup; const versionLookup = obj.VersionLookup;
@@ -166,10 +183,7 @@ export const NOTIFY_ACTIVE_RELEASE = 'relver/notifyActiveRelease';
export const notifyActiveRelease = (release, version) => { export const notifyActiveRelease = (release, version) => {
return { return {
type: NOTIFY_ACTIVE_RELEASE, type: NOTIFY_ACTIVE_RELEASE,
payload: { payload: { release: release, version: version },
release: release,
version: version
},
}; };
}; };
@@ -183,7 +197,7 @@ export const setActiveRelease = (release, version) => {
version = -1; version = -1;
} }
const versions = relver.VersionLookup[Constants.RELEASE_TYPES[release]]; const versions = relver.VersionLookup[Constants.RELEASE_TYPES[release]];
dispatch(setAllowDismissDependencies(versions && (versions.length > 1))); dispatch(setAllowDismissDependencies(versions && versions.length > 1));
dispatch(setDismissDependencies(false)); dispatch(setDismissDependencies(false));
dispatch(notifyActiveRelease(release, version)); dispatch(notifyActiveRelease(release, version));
if (common.AppReady) { if (common.AppReady) {
@@ -206,8 +220,8 @@ export const setReleaseData = (locationsLookup, versionLookup)=> {
payload: { payload: {
locations: locationsLookup, locations: locationsLookup,
versions: versionLookup, versions: versionLookup,
} },
} };
}; };
export const setReleaseUpgradeAvailable = createAction('relver/setReleaseUpgradeAvailable'); export const setReleaseUpgradeAvailable = createAction('relver/setReleaseUpgradeAvailable');
@@ -219,6 +233,6 @@ export const setUIUpgradeData = (upgradeData, version) => {
payload: { payload: {
upgrade_data: upgradeData, upgrade_data: upgradeData,
version: version, version: version,
} },
} };
}; };

View File

@@ -1,16 +1,17 @@
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { import {
DISPLAY_CONFIRM_YES_NO, confirmYesNoAction,
NOTIFY_APPLICATION_BUSY, NOTIFY_APPLICATION_BUSY,
notifyRebootRequired, notifyRebootRequired,
SET_DISPLAY_SELECT_APPPLATFORM,
setAllowMount, setAllowMount,
setApplicationReady, setApplicationReady,
setLinuxAppPlatform, setLinuxAppPlatform,
SET_DISPLAY_SELECT_APPPLATFORM
} from '../actions/common_actions'; } from '../actions/common_actions';
export const createCommonReducer = (platformInfo, version) => { export const createCommonReducer = (platformInfo, version) => {
return createReducer({ return createReducer(
{
AllowMount: false, AllowMount: false,
AppBusy: false, AppBusy: false,
AppBusyTransparent: false, AppBusyTransparent: false,
@@ -22,25 +23,21 @@ export const createCommonReducer = (platformInfo, version) => {
Platform: platformInfo.Platform, Platform: platformInfo.Platform,
RebootRequired: false, RebootRequired: false,
Version: version, Version: version,
}, { },
[DISPLAY_CONFIRM_YES_NO]: (state, action) => { {
[confirmYesNoAction.action_type]: (state, action) => {
return { return {
...state, ...state,
DisplayConfirmYesNo: action.payload.show, DisplayConfirmYesNo: action.payload.display,
ConfirmTitle: action.payload.show ? action.payload.title : null, ConfirmTitle:
} action.payload.display && action.payload.data ? action.payload.data.title : null,
};
}, },
[SET_DISPLAY_SELECT_APPPLATFORM]: (state, action) => { [SET_DISPLAY_SELECT_APPPLATFORM]: (state, action) => {
return { return { ...state, DisplaySelectAppPlatform: action.payload };
...state,
DisplaySelectAppPlatform: action.payload,
}
}, },
[setAllowMount]: (state, action) => { [setAllowMount]: (state, action) => {
return { return { ...state, AllowMount: action.payload };
...state,
AllowMount: action.payload,
}
}, },
[setApplicationReady]: (state, action) => { [setApplicationReady]: (state, action) => {
return { return {
@@ -49,10 +46,7 @@ export const createCommonReducer = (platformInfo, version) => {
}; };
}, },
[setLinuxAppPlatform]: (state, action) => { [setLinuxAppPlatform]: (state, action) => {
return { return { ...state, AppPlatform: action.payload };
...state,
AppPlatform: action.payload,
}
}, },
[NOTIFY_APPLICATION_BUSY]: (state, action) => { [NOTIFY_APPLICATION_BUSY]: (state, action) => {
return { return {
@@ -67,5 +61,6 @@ export const createCommonReducer = (platformInfo, version) => {
RebootRequired: action.payload, RebootRequired: action.payload,
}; };
}, },
}); }
);
}; };

View File

@@ -1,9 +1,10 @@
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { import {
setAllowDownload,
SET_DOWNLOAD_BEGIN, SET_DOWNLOAD_BEGIN,
setAllowDownload,
setDownloadEnd, setDownloadEnd,
setDownloadProgress setDownloadProgress,
} from '../actions/download_actions'; } from '../actions/download_actions';
const defaultDownloadState = { const defaultDownloadState = {
@@ -17,10 +18,12 @@ const defaultDownloadState = {
DownloadType: null, DownloadType: null,
}; };
export const downloadReducer = createReducer({ export const downloadReducer = createReducer(
{
...defaultDownloadState, ...defaultDownloadState,
AllowDownload: false, AllowDownload: false,
}, { },
{
[setAllowDownload]: (state, action) => { [setAllowDownload]: (state, action) => {
return { return {
...state, ...state,
@@ -35,7 +38,7 @@ export const downloadReducer = createReducer({
DownloadName: action.payload.name, DownloadName: action.payload.name,
DownloadType: action.payload.type, DownloadType: action.payload.type,
DownloadURL: action.payload.url, DownloadURL: action.payload.url,
} };
}, },
[setDownloadEnd]: (state, action) => { [setDownloadEnd]: (state, action) => {
return { return {
@@ -45,9 +48,7 @@ export const downloadReducer = createReducer({
}; };
}, },
[setDownloadProgress]: (state, action) => { [setDownloadProgress]: (state, action) => {
return { return { ...state, DownloadProgress: action.payload };
...state, },
DownloadProgress: action.payload,
} }
} );
});

View File

@@ -1,33 +1,30 @@
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import { import { CLEAR_ERROR, CLEAR_INFO, SET_ERROR_INFO, SET_INFO } from '../actions/error_actions';
CLEAR_ERROR,
CLEAR_INFO,
SET_ERROR_INFO,
SET_INFO
} from '../actions/error_actions';
export const errorReducer = createReducer({ export const errorReducer = createReducer(
{
DisplayError: false, DisplayError: false,
DisplayInfo: false, DisplayInfo: false,
ErrorCritical: false, ErrorCritical: false,
ErrorStack: [], ErrorStack: [],
InfoStack: [], InfoStack: [],
}, {
[CLEAR_ERROR]: state => {
const errorStack = (state.ErrorStack.length > 0) ? state.ErrorStack.slice(1) : [];
return {
...state,
DisplayError: (errorStack.length > 0),
ErrorStack: errorStack,
}
}, },
[CLEAR_INFO]: state => { {
const infoStack = (state.InfoStack.length > 0) ? state.InfoStack.slice(1) : []; [CLEAR_ERROR]: (state) => {
const errorStack = state.ErrorStack.length > 0 ? state.ErrorStack.slice(1) : [];
return { return {
...state, ...state,
DisplayInfo: (infoStack.length > 0), DisplayError: errorStack.length > 0,
ErrorStack: errorStack,
};
},
[CLEAR_INFO]: (state) => {
const infoStack = state.InfoStack.length > 0 ? state.InfoStack.slice(1) : [];
return {
...state,
DisplayInfo: infoStack.length > 0,
InfoStack: infoStack, InfoStack: infoStack,
} };
}, },
[SET_ERROR_INFO]: (state, action) => { [SET_ERROR_INFO]: (state, action) => {
const errorStack = [action.payload.msg, ...state.ErrorStack]; const errorStack = [action.payload.msg, ...state.ErrorStack];
@@ -36,17 +33,20 @@ export const errorReducer = createReducer({
DisplayError: true, DisplayError: true,
ErrorCritical: state.ErrorCritical || action.payload.critical, ErrorCritical: state.ErrorCritical || action.payload.critical,
ErrorStack: errorStack, ErrorStack: errorStack,
} };
}, },
[SET_INFO]: (state, action) => { [SET_INFO]: (state, action) => {
const infoStack = [{ const infoStack = [
{
title: action.payload.title, title: action.payload.title,
message: action.payload.msg message: action.payload.msg,
}, ...state.InfoStack]; saveToFile: action.payload.saveToFile,
return { fileName: action.payload.fileName,
...state, extension: action.payload.extension,
DisplayInfo: true, },
InfoStack: infoStack, ...state.InfoStack,
];
return { ...state, DisplayInfo: true, InfoStack: infoStack };
},
} }
} );
});

View File

@@ -0,0 +1,20 @@
import { createReducer } from '@reduxjs/toolkit';
import { addEditHostAction } from '../actions/host_actions';
export const hostReducer = createReducer(
{
DisplayAddEditHost: false,
HostData: {},
HostList: [],
},
{
[addEditHostAction.action_type]: (state, action) => {
return {
...state,
DisplayAddEditHost: action.payload.display,
HostData: action.payload.data ? action.payload.data.host_data || {} : {},
HostList: action.payload.data ? action.payload.data.host_list || [] : [],
};
},
}
);

View File

@@ -5,10 +5,11 @@ import {
setInstallActive, setInstallActive,
setInstallComplete, setInstallComplete,
setInstallTestActive, setInstallTestActive,
setMissingDependencies setMissingDependencies,
} from '../actions/install_actions'; } from '../actions/install_actions';
export const installReducer = createReducer({ export const installReducer = createReducer(
{
AutoInstallRelease: false, AutoInstallRelease: false,
DismissDependencies: false, DismissDependencies: false,
InstallActive: false, InstallActive: false,
@@ -16,18 +17,13 @@ export const installReducer = createReducer({
InstallTestActive: false, InstallTestActive: false,
InstallType: null, InstallType: null,
MissingDependencies: [], MissingDependencies: [],
}, { },
{
[setAutoInstallRelease]: (state, action) => { [setAutoInstallRelease]: (state, action) => {
return { return { ...state, AutoInstallRelease: action.payload };
...state,
AutoInstallRelease: action.payload,
}
}, },
[setDismissDependencies]: (state, action) => { [setDismissDependencies]: (state, action) => {
return { return { ...state, DismissDependencies: action.payload };
...state,
DismissDependencies: action.payload,
}
}, },
[setInstallActive]: (state, action) => { [setInstallActive]: (state, action) => {
return { return {
@@ -43,18 +39,13 @@ export const installReducer = createReducer({
InstallActive: false, InstallActive: false,
InstallResult: action.payload, InstallResult: action.payload,
InstallType: null, InstallType: null,
} };
}, },
[setInstallTestActive]: (state, action) => { [setInstallTestActive]: (state, action) => {
return { return { ...state, InstallTestActive: action.payload };
...state,
InstallTestActive: action.payload,
}
}, },
[setMissingDependencies]: (state, action) => { [setMissingDependencies]: (state, action) => {
return { return { ...state, MissingDependencies: action.payload };
...state, },
MissingDependencies: action.payload,
} }
} );
});

View File

@@ -12,49 +12,49 @@ import {
SET_MOUNT_STATE, SET_MOUNT_STATE,
SET_MOUNTED, SET_MOUNTED,
SET_PROVIDER_STATE, SET_PROVIDER_STATE,
setBusy 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 || []), ...(state.S3Mounts || []),
]; ];
const providerState = providerList const providerState = providerList
.map(provider => { .map((provider) => {
return { return {
[provider]: { [provider]: {
AutoMount: false, AutoMount: false,
AutoRestart: false, AutoRestart: false,
MountLocation: '', MountLocation: '',
} },
} };
}) })
.reduce((map, obj) => { .reduce((map, obj) => {
return {...map, ...obj} return { ...map, ...obj };
}); });
const mountState = providerList const mountState = providerList
.map(provider => { .map((provider) => {
return { return {
[provider]: { [provider]: {
AllowMount: false, AllowMount: false,
DriveLetters: [], DriveLetters: [],
Mounted: false, Mounted: false,
} },
} };
}) })
.reduce((map, obj) => { .reduce((map, obj) => {
return {...map, ...obj} return { ...map, ...obj };
}); });
const autoMountProcessed = const autoMountProcessed = providerList
providerList.map(provider => { .map((provider) => {
return {[provider]: false,} return { [provider]: false };
}) })
.reduce((map, obj) => { .reduce((map, obj) => {
return {...map, ...obj} return { ...map, ...obj };
}); });
return createReducer( return createReducer(
@@ -89,10 +89,12 @@ export const createMountReducer = state => {
autoMountProcessed[action.payload] = true; autoMountProcessed[action.payload] = true;
return { return {
...state, AutoMountProcessed: autoMountProcessed, ...state,
MountState: mountState, ProviderState: providerState, AutoMountProcessed: autoMountProcessed,
MountState: mountState,
ProviderState: providerState,
RemoteMounts: [...state.RemoteMounts, action.payload], RemoteMounts: [...state.RemoteMounts, action.payload],
} };
}, },
[addS3Mount2]: (state, action) => { [addS3Mount2]: (state, action) => {
let mountState = { ...state.MountState }; let mountState = { ...state.MountState };
@@ -113,10 +115,12 @@ export const createMountReducer = state => {
autoMountProcessed[action.payload] = true; autoMountProcessed[action.payload] = true;
return { return {
...state, AutoMountProcessed: autoMountProcessed, ...state,
MountState: mountState, ProviderState: providerState, AutoMountProcessed: autoMountProcessed,
MountState: mountState,
ProviderState: providerState,
S3Mounts: [...state.S3Mounts, action.payload], S3Mounts: [...state.S3Mounts, action.payload],
} };
}, },
[DISPLAY_CONFIGURATION]: (state, action) => { [DISPLAY_CONFIGURATION]: (state, action) => {
return { return {
@@ -136,10 +140,8 @@ export const createMountReducer = state => {
let autoMountProcessed = { ...state.AutoMountProcessed }; let autoMountProcessed = { ...state.AutoMountProcessed };
delete autoMountProcessed[action.payload]; delete autoMountProcessed[action.payload];
const remoteMounts = const remoteMounts = state.RemoteMounts.filter((i) => i !== action.payload);
state.RemoteMounts.filter(i => i !== action.payload); const s3Mounts = state.S3Mounts.filter((i) => i !== action.payload);
const s3Mounts =
state.S3Mounts.filter(i => i !== action.payload);
return { return {
...state, ...state,
AutoMountProcessed: autoMountProcessed, AutoMountProcessed: autoMountProcessed,
@@ -149,8 +151,8 @@ export const createMountReducer = state => {
S3Mounts: s3Mounts, S3Mounts: s3Mounts,
}; };
}, },
[RESET_MOUNTS_STATE]: (state, action) => { [RESET_MOUNTS_STATE]: (state) => {
return {...state, MountsBusy: false, MountState: mountState,} return { ...state, MountsBusy: false, MountState: mountState };
}, },
[SET_AUTO_MOUNT_PROCESSED]: (state, action) => { [SET_AUTO_MOUNT_PROCESSED]: (state, action) => {
return { return {
@@ -158,7 +160,7 @@ export const createMountReducer = state => {
AutoMountProcessed: { AutoMountProcessed: {
...state.AutoMountProcessed, ...state.AutoMountProcessed,
[action.payload.provider]: action.payload.processed, [action.payload.provider]: action.payload.processed,
} },
}; };
}, },
[SET_ALLOW_MOUNT]: (state, action) => { [SET_ALLOW_MOUNT]: (state, action) => {
@@ -169,13 +171,11 @@ export const createMountReducer = state => {
[action.payload.provider]: { [action.payload.provider]: {
...state.MountState[action.payload.provider], ...state.MountState[action.payload.provider],
AllowMount: action.payload.allow, AllowMount: action.payload.allow,
} },
} },
}; };
}, },
[setBusy]: [setBusy]: (state, action) => {
(state,
action) => {
return { ...state, MountsBusy: action.payload }; return { ...state, MountsBusy: action.payload };
}, },
[SET_MOUNT_STATE]: (state, action) => { [SET_MOUNT_STATE]: (state, action) => {
@@ -185,9 +185,9 @@ export const createMountReducer = state => {
...state.MountState, ...state.MountState,
[action.payload.provider]: { [action.payload.provider]: {
...state.MountState[action.payload.provider], ...state.MountState[action.payload.provider],
...action.payload.state ...action.payload.state,
},
}, },
}
}; };
}, },
[SET_MOUNTED]: (state, action) => { [SET_MOUNTED]: (state, action) => {
@@ -198,8 +198,8 @@ export const createMountReducer = state => {
[action.payload.provider]: { [action.payload.provider]: {
...state.MountState[action.payload.provider], ...state.MountState[action.payload.provider],
Mounted: action.payload.mounted, Mounted: action.payload.mounted,
} },
} },
}; };
}, },
[SET_PROVIDER_STATE]: (state, action) => { [SET_PROVIDER_STATE]: (state, action) => {
@@ -209,10 +209,11 @@ export const createMountReducer = state => {
...state.ProviderState, ...state.ProviderState,
[action.payload.provider]: { [action.payload.provider]: {
...state.ProviderState[action.payload.provider], ...state.ProviderState[action.payload.provider],
...action.payload.state ...action.payload.state,
},
},
};
}, },
} }
}; );
}
});
}; };

View File

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

View File

@@ -1,11 +1,10 @@
import { createReducer } from '@reduxjs/toolkit'; import { createReducer } from '@reduxjs/toolkit';
import * as Actions from '../actions/release_version_actions';
import * as Constants from '../../constants';
const versionLookup = Constants.RELEASE_TYPES.map(k=> { import * as Constants from '../../constants';
return { import * as Actions from '../actions/release_version_actions';
[k]: ['unavailable']
}; const versionLookup = Constants.RELEASE_TYPES.map((k) => {
return { [k]: ['unavailable'] };
}).reduce((map, obj) => { }).reduce((map, obj) => {
return { return {
...map, ...map,
@@ -13,7 +12,8 @@ const versionLookup = Constants.RELEASE_TYPES.map(k=> {
}; };
}); });
export const releaseVersionReducer = createReducer({ export const releaseVersionReducer = createReducer(
{
AllowDismissDependencies: false, AllowDismissDependencies: false,
DismissNewReleasesAvailable: true, DismissNewReleasesAvailable: true,
InstalledVersion: 'none', InstalledVersion: 'none',
@@ -28,8 +28,9 @@ export const releaseVersionReducer = createReducer({
UpgradeDismissed: false, UpgradeDismissed: false,
Version: -1, Version: -1,
VersionLookup: versionLookup, VersionLookup: versionLookup,
}, { },
[Actions.CLEAR_UI_UPGRADE]: state => { {
[Actions.CLEAR_UI_UPGRADE]: (state) => {
return { return {
...state, ...state,
UpgradeAvailable: false, UpgradeAvailable: false,
@@ -42,7 +43,7 @@ export const releaseVersionReducer = createReducer({
return { return {
...state, ...state,
Release: action.payload.release, Release: action.payload.release,
Version: action.payload.version Version: action.payload.version,
}; };
}, },
[Actions.setAllowDismissDependencies]: (state, action) => { [Actions.setAllowDismissDependencies]: (state, action) => {
@@ -64,10 +65,7 @@ export const releaseVersionReducer = createReducer({
}; };
}, },
[Actions.setInstalledVersion]: (state, action) => { [Actions.setInstalledVersion]: (state, action) => {
return { return { ...state, InstalledVersion: action.payload };
...state,
InstalledVersion: action.payload,
}
}, },
[Actions.setNewReleasesAvailable]: (state, action) => { [Actions.setNewReleasesAvailable]: (state, action) => {
return { return {
@@ -103,5 +101,6 @@ export const releaseVersionReducer = createReducer({
UpgradeVersion: action.payload.version, UpgradeVersion: action.payload.version,
UpgradeDismissed: false, UpgradeDismissed: false,
}; };
},
} }
}); );

View File

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

View File

@@ -1,18 +1,21 @@
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'; import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import { createCommonReducer } from '../reducers/common_reducer'; import { createCommonReducer } from '../reducers/common_reducer';
import { downloadReducer } from '../reducers/download_reducer'; import { downloadReducer } from '../reducers/download_reducer';
import { errorReducer } from '../reducers/error_reducer'; 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 { pinnedManagerReducer } from '../reducers/pinned_manager_reducer';
import { releaseVersionReducer } from '../reducers/release_version_reducer'; import { releaseVersionReducer } from '../reducers/release_version_reducer';
import { skynetReducer } from '../reducers/skynet_reducer'; import { skynetReducer } from '../reducers/skynet_reducer';
import {pinnedManagerReducer} from '../reducers/pinned_manager_reducer' import { hostReducer } from '../reducers/host_reducer';
export default function createAppStore(platformInfo, version, state) { export default function createAppStore(platformInfo, version, state) {
const reducer = { const reducer = {
common: createCommonReducer(platformInfo, version), common: createCommonReducer(platformInfo, version),
download: downloadReducer, download: downloadReducer,
error: errorReducer, error: errorReducer,
host: hostReducer,
install: installReducer, install: installReducer,
mounts: createMountReducer(state), mounts: createMountReducer(state),
relver: releaseVersionReducer, relver: releaseVersionReducer,
@@ -25,6 +28,6 @@ export default function createAppStore(platformInfo, version, state) {
return configureStore({ return configureStore({
reducer, reducer,
middleware, middleware,
devTools: process.env.NODE_ENV !== 'production' devTools: process.env.NODE_ENV !== 'production',
}); });
} }

47
src/redux/utils.js Normal file
View File

@@ -0,0 +1,47 @@
export const createResponseDialogAction = (type, name) => {
let resolverList = [];
const display = (show, cb, data) => {
return (dispatch) => {
if (cb) {
dispatch(display(show, null, data)).then(({ changed, data }) => cb(changed, data));
} else {
return new Promise((resolve) => {
dispatch(handleDisplay(show, data, resolve));
});
}
};
};
const handleDisplay = (show, data, resolve) => {
return (dispatch) => {
if (show) {
resolverList.push(resolve);
dispatch(displayAction(show, data));
} else {
dispatch(complete(false));
}
};
};
const complete = (changed, data) => {
return (dispatch) => {
if (changed) {
resolverList[0]({ changed, data });
}
resolverList.splice(0, 1);
dispatch(displayAction(false));
};
};
const DISPLAY_ACTION = type + '/' + name;
const displayAction = (display, data) => {
return { type: DISPLAY_ACTION, payload: { display, data } };
};
return {
action_type: DISPLAY_ACTION,
complete,
display,
};
};

View File

@@ -9,12 +9,10 @@ const addListeners = (ipcMain, {closeApplication, setWindowVisibility}) => {
setWindowVisibility(true); setWindowVisibility(true);
}); });
ipcMain.on(Constants.IPC_Show_Window + '_sync', event => { ipcMain.on(Constants.IPC_Show_Window + '_sync', (event) => {
setWindowVisibility(true); setWindowVisibility(true);
event.returnValue = true; event.returnValue = true;
}); });
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

View File

@@ -14,7 +14,7 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
standardIPCReply(event, Constants.IPC_Get_Config_Reply, {}, data.Code); standardIPCReply(event, Constants.IPC_Get_Config_Reply, {}, data.Code);
} }
}) })
.catch(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Get_Config_Reply, {}, error); standardIPCReply(event, Constants.IPC_Get_Config_Reply, {}, error);
}); });
}); });
@@ -27,7 +27,7 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
Template: data, Template: data,
}); });
}) })
.catch(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Get_Config_Template_Reply, {}, error); standardIPCReply(event, Constants.IPC_Get_Config_Template_Reply, {}, error);
}); });
}); });
@@ -36,11 +36,18 @@ 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.S3, 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(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Set_Config_Values_Reply, {}, error); standardIPCReply(event, Constants.IPC_Set_Config_Values_Reply, {}, error);
}); });
} else { } else {
@@ -51,6 +58,4 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
}); });
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

View File

@@ -5,43 +5,42 @@ 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)
.then(code => { .then((code) => {
standardIPCReply(event, Constants.IPC_Check_Daemon_Version_Reply, { standardIPCReply(event, Constants.IPC_Check_Daemon_Version_Reply, {
Valid: (code === 0), Valid: code === 0,
Code: code, Code: code,
}); });
}) })
.catch(e => { .catch((e) => {
standardIPCReply(event, Constants.IPC_Check_Daemon_Version_Reply, { standardIPCReply(
event,
Constants.IPC_Check_Daemon_Version_Reply,
{
Valid: false, Valid: false,
}, e); },
e
);
}); });
}); });
ipcMain.on(Constants.IPC_Check_Daemon_Version + '_sync', (event, data) => { ipcMain.on(Constants.IPC_Check_Daemon_Version + '_sync', (event, data) => {
helpers helpers
.checkDaemonVersion(data.Version, data.Provider) .checkDaemonVersion(data.Version, data.Provider)
.then(code => { .then((code) => {
event.returnValue = { event.returnValue = {
data: { data: {
Success: true, Success: true,
Valid: (code === 0), Valid: code === 0,
Code: code, Code: code,
}, },
}; };
}) })
.catch(e => { .catch((e) => {
event.returnValue = { event.returnValue = {
data: { data: { Error: e.toString(), Success: false, Valid: false },
Error: e.toString(),
Success: false,
Valid: false
},
}; };
}); });
}); });
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

View File

@@ -30,9 +30,7 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
}; };
} catch (e) { } catch (e) {
event.returnValue = { event.returnValue = {
data: { data: { Exists: false },
Exists: false
},
}; };
} }
}); });
@@ -47,11 +45,16 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
URL: data.URL, URL: data.URL,
}); });
}) })
.catch(error=> { .catch((error) => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, { standardIPCReply(
event,
Constants.IPC_Install_Dependency_Reply,
{
Source: data.Source, Source: data.Source,
URL: data.URL, URL: data.URL,
}, error); },
error
);
}); });
} else { } else {
const execInstall = () => { const execInstall = () => {
@@ -63,17 +66,22 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
URL: data.URL, URL: data.URL,
}); });
}) })
.catch(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, { standardIPCReply(
event,
Constants.IPC_Install_Dependency_Reply,
{
Source: data.Source, Source: data.Source,
URL: data.URL, URL: data.URL,
}, error); },
error
);
}); });
}; };
if (data.IsWinFSP) { if (data.IsWinFSP) {
helpers helpers
.performWindowsUninstall(Constants.WINFSP_VERSION_NAMES) .performWindowsUninstall(Constants.WINFSP_VERSION_NAMES)
.then(uninstalled => { .then((uninstalled) => {
if (uninstalled) { if (uninstalled) {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, { standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
RebootRequired: true, RebootRequired: true,
@@ -84,11 +92,16 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
execInstall(); execInstall();
} }
}) })
.catch(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, { standardIPCReply(
event,
Constants.IPC_Install_Dependency_Reply,
{
Source: data.Source, Source: data.Source,
URL: data.URL, URL: data.URL,
}, error); },
error
);
}); });
} else { } else {
execInstall(); execInstall();
@@ -97,6 +110,4 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
}); });
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

View File

@@ -5,21 +5,29 @@ 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) => {
standardIPCReply(event, Constants.IPC_Download_File_Progress, { standardIPCReply(event, Constants.IPC_Download_File_Progress, {
Destination: destination, Destination: destination,
Progress: progress, Progress: progress,
URL: data.URL, URL: data.URL,
}); });
}, error => { },
standardIPCReply(event, Constants.IPC_Download_File_Complete, { (error) => {
standardIPCReply(
event,
Constants.IPC_Download_File_Complete,
{
Destination: destination, Destination: destination,
URL: data.URL, URL: data.URL,
}, error); },
}); error
);
}
);
}); });
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

View File

@@ -3,29 +3,84 @@ 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,
properties: ['openDirectory'], properties: ['openDirectory'],
title: data.Title, title: data.Title,
}, (filePaths) => { },
if (filePaths && (filePaths.length > 0)) { (filePaths) => {
if (filePaths && filePaths.length > 0) {
event.returnValue = filePaths[0]; event.returnValue = filePaths[0];
} else { } else {
event.returnValue = ''; event.returnValue = '';
} }
}); }
);
}); });
ipcMain.on(Constants.IPC_Delete_File, (event, data) => { ipcMain.on(Constants.IPC_Browse_File + '_sync', (event, data) => {
dialog.showOpenDialog(
getMainWindow(),
{
defaultPath: data.Location,
properties: ['openFile'],
title: data.Title,
},
(filePaths) => {
if (filePaths && filePaths.length > 0) {
event.returnValue = filePaths[0];
} else {
event.returnValue = '';
}
}
);
});
ipcMain.on(Constants.IPC_Delete_File, (_, data) => {
try { try {
if (fs.existsSync(data.FilePath)) { if (fs.existsSync(data.FilePath)) {
fs.unlinkSync(data.FilePath); fs.unlinkSync(data.FilePath);
} }
} catch (e) { } catch (e) {}
});
ipcMain.on(Constants.IPC_Select_File + '_sync', (event, data) => {
dialog.showSaveDialog(
getMainWindow(),
{
defaultPath: data.Location,
properties: ['createDirectory', 'showOverwriteConfirmation'],
title: data.Title,
},
(filePath) => {
if (filePath && filePath.length > 0) {
event.returnValue = filePath;
} else {
event.returnValue = '';
}
}
);
});
ipcMain.on(Constants.IPC_Read_File + '_sync', (event, data) => {
try {
const contents = fs.readFileSync(data.Location, 'utf8').toString();
event.returnValue = { success: true, contents };
} catch (err) {
event.returnValue = { success: false, error: err.toString() };
}
});
ipcMain.on(Constants.IPC_Save_File + '_sync', (event, data) => {
try {
fs.writeFileSync(data.Location, data.Data, 'utf8');
event.returnValue = { success: true };
} catch (err) {
event.returnValue = { success: false, error: err.toString() };
} }
}); });
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

View File

@@ -10,7 +10,7 @@ let manualMountDetection = {};
let mountedData = {}; let mountedData = {};
let mountedLocations = []; let mountedLocations = [];
const clearManualMountDetection = provider => { const clearManualMountDetection = (provider) => {
if (manualMountDetection[provider]) { if (manualMountDetection[provider]) {
clearInterval(manualMountDetection[provider]); clearInterval(manualMountDetection[provider]);
delete manualMountDetection[provider]; delete manualMountDetection[provider];
@@ -21,7 +21,7 @@ const monitorMount = (sender, provider, providerList, version, pid, location) =>
manualMountDetection[provider] = setInterval(() => { manualMountDetection[provider] = setInterval(() => {
helpers helpers
.detectRepertoryMounts(version, providerList) .detectRepertoryMounts(version, providerList)
.then(result => { .then((result) => {
if (result[provider].PID !== pid) { if (result[provider].PID !== pid) {
if (result[provider].PID === -1) { if (result[provider].PID === -1) {
clearManualMountDetection(provider); clearManualMountDetection(provider);
@@ -32,14 +32,14 @@ const monitorMount = (sender, provider, providerList, version, pid, location) =>
Provider: provider, Provider: provider,
Error: Error(provider + ' Unmounted').toString(), Error: Error(provider + ' Unmounted').toString(),
Success: false, Success: false,
} },
}); });
} else { } else {
pid = result[provider].PID; pid = result[provider].PID;
} }
} }
}) })
.catch(e => { .catch((e) => {
console.log(e); console.log(e);
}); });
}, 6000); }, 6000);
@@ -64,10 +64,7 @@ const unmountAllDrives = () => {
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, Error: '' };
Success: true,
Error: ''
};
try { try {
if (fs.existsSync(data.Location) && fs.statSync(data.Location).isDirectory()) { if (fs.existsSync(data.Location) && fs.statSync(data.Location).isDirectory()) {
@@ -90,11 +87,7 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
const provider = data.Provider; const provider = data.Provider;
let driveLetters = {}; let driveLetters = {};
const providerList = [ const providerList = [...Constants.PROVIDER_LIST, ...data.RemoteMounts, ...data.S3Mounts];
...Constants.PROVIDER_LIST,
...data.RemoteMounts,
...data.S3Mounts,
];
for (const provider of providerList) { for (const provider of providerList) {
driveLetters[provider] = []; driveLetters[provider] = [];
} }
@@ -106,8 +99,7 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
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].startsWith(drive); driveInUse = locations[provider].startsWith(drive);
if (driveInUse) if (driveInUse) break;
break;
} }
} }
if (!driveInUse) { if (!driveInUse) {
@@ -117,17 +109,18 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
driveLetters[provider].push(drive); driveLetters[provider].push(drive);
} }
} }
} catch (e) { } catch (e) {}
}
} }
} }
if (Object.keys(locations).length > 0) { if (Object.keys(locations).length > 0) {
for (const provider of providerList) { for (const provider of providerList) {
if (locations[provider].length > 0) { if (locations[provider].length > 0) {
if (!driveLetters[provider].find((driveLetter) => { if (
!driveLetters[provider].find((driveLetter) => {
return driveLetter === locations[provider]; return driveLetter === locations[provider];
})) { })
) {
driveLetters[provider].push(locations[provider]); driveLetters[provider].push(locations[provider]);
} }
} }
@@ -135,17 +128,16 @@ 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] && locations[provider].length > 0; driveInUse = locations[provider] && locations[provider].length > 0;
if (driveInUse) if (driveInUse) break;
break;
} }
} }
setTrayImage(driveInUse) setTrayImage(driveInUse);
}; };
helpers helpers
@@ -154,7 +146,9 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
let storageData = {}; let storageData = {};
let locations = {}; let locations = {};
for (const provider of providerList) { for (const provider of providerList) {
storageData[provider] = results[provider] ? results[provider] : { storageData[provider] = results[provider]
? results[provider]
: {
Active: false, Active: false,
Location: '', Location: '',
PID: -1, PID: -1,
@@ -164,7 +158,14 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
if (storageData[provider].PID !== -1) { if (storageData[provider].PID !== -1) {
expectedUnmount[provider] = false; expectedUnmount[provider] = false;
if (firstMountCheck) { if (firstMountCheck) {
monitorMount(event.sender, provider, providerList, data.Version, storageData[provider].PID, storageData[provider].Location); monitorMount(
event.sender,
provider,
providerList,
data.Version,
storageData[provider].PID,
storageData[provider].Location
);
} }
} }
} }
@@ -185,15 +186,20 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
Provider: provider, Provider: provider,
}); });
}) })
.catch(error => { .catch((error) => {
if (os.platform() === 'win32') { if (os.platform() === 'win32') {
grabDriveLetters({}); grabDriveLetters({});
} }
setImage({}); setImage({});
standardIPCReply(event, Constants.IPC_Detect_Mount_Reply, { standardIPCReply(
event,
Constants.IPC_Detect_Mount_Reply,
{
DriveLetters: driveLetters[provider], DriveLetters: driveLetters[provider],
Provider: provider, Provider: provider,
}, error); },
error
);
}); });
}); });
@@ -216,18 +222,30 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
delete mountedData[data.Location]; delete mountedData[data.Location];
} }
standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, { standardIPCReply(
event,
Constants.IPC_Unmount_Drive_Reply,
{
Expected: expectedUnmount[data.Provider], Expected: expectedUnmount[data.Provider],
Location: data.Location, Location: data.Location,
Provider: data.Provider, Provider: data.Provider,
Remote: data.Remote, Remote: data.Remote,
S3: data.S3, S3: data.S3,
}, error || Error(data.Provider + ' Unmounted')); },
error || Error(data.Provider + ' Unmounted')
);
}; };
helpers helpers
.executeMount(data.Version, data.Provider, data.Remote, data.S3, 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,
@@ -235,7 +253,7 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
S3: data.S3, S3: data.S3,
}); });
}) })
.catch(error => { .catch((error) => {
errorHandler(-1, error); errorHandler(-1, error);
}); });
} }
@@ -245,17 +263,26 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
if (data.Remote) { if (data.Remote) {
data.Name = data.Name.replace(':', '_'); data.Name = data.Name.replace(':', '_');
} }
const dataDirectory = path.resolve(path.join(helpers.getDataDirectory(), '..', data.Remote ? 'remote' : 's3', data.Name)); 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_Mount_Reply, {DataDirectory: dataDirectory}); standardIPCReply(event, Constants.IPC_Remove_Mount_Reply, {
DataDirectory: dataDirectory,
});
} catch (e) { } catch (e) {
standardIPCReply(event, Constants.IPC_Remove_Mount_Reply, {DataDirectory: dataDirectory}, e); standardIPCReply(
event,
Constants.IPC_Remove_Mount_Reply,
{ DataDirectory: dataDirectory },
e
);
} }
}); });
ipcMain.on(Constants.IPC_Unmount_All_Drives, event => { 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);
}); });
@@ -266,10 +293,10 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
expectedUnmount[data.Provider] = true; expectedUnmount[data.Provider] = true;
helpers helpers
.stopMountProcess(data.Version, data.Provider, data.Remote, data.S3) .stopMountProcess(data.Version, data.Provider, data.Remote, data.S3)
.then(result => { .then((result) => {
console.log(result); console.log(result);
}) })
.catch(e => { .catch((e) => {
console.log(e); console.log(e);
}); });
}); });
@@ -277,5 +304,5 @@ const addListeners = (ipcMain, {setTrayImage, standardIPCReply}) => {
module.exports = { module.exports = {
addListeners, addListeners,
unmountAllDrives unmountAllDrives,
}; };

View File

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

View File

@@ -10,7 +10,7 @@ const getPlatformOverride = () => {
return _platformOverride; return _platformOverride;
}; };
const setPlatformOverride = platformOverride => { const setPlatformOverride = (platformOverride) => {
_platformOverride = platformOverride; _platformOverride = platformOverride;
}; };
@@ -25,7 +25,7 @@ const addListeners = (ipcMain, {detectScript, saveUiSettings}) => {
const platform = os.platform(); const platform = os.platform();
if (platform === 'linux') { if (platform === 'linux') {
if (_platformOverride && (_platformOverride.length > 0)) { if (_platformOverride && _platformOverride.length > 0) {
sendResponse(_platformOverride, 'linux'); sendResponse(_platformOverride, 'linux');
} else { } else {
const scriptFile = path.join(os.tmpdir(), 'repertory_detect_linux.sh'); const scriptFile = path.join(os.tmpdir(), 'repertory_detect_linux.sh');
@@ -33,18 +33,17 @@ const addListeners = (ipcMain, {detectScript, saveUiSettings}) => {
helpers helpers
.executeScript(scriptFile) .executeScript(scriptFile)
.then(data => { .then((data) => {
let appPlatform = data.replace(/(\r\n|\n|\r)/gm, ""); let appPlatform = data.replace(/(\r\n|\n|\r)/gm, '');
if (appPlatform === 'unknown') { if (appPlatform === 'unknown') {
helpers helpers.downloadFile(Constants.LINUX_DETECT_SCRIPT_URL, scriptFile, null, (err) => {
.downloadFile(Constants.LINUX_DETECT_SCRIPT_URL, scriptFile, null, err => {
if (err) { if (err) {
sendResponse(appPlatform, platform); sendResponse(appPlatform, platform);
} else { } else {
helpers helpers
.executeScript(scriptFile) .executeScript(scriptFile)
.then(data => { .then((data) => {
appPlatform = data.replace(/(\r\n|\n|\r)/gm, ""); appPlatform = data.replace(/(\r\n|\n|\r)/gm, '');
sendResponse(appPlatform, platform); sendResponse(appPlatform, platform);
}) })
.catch(() => { .catch(() => {
@@ -75,5 +74,5 @@ const addListeners = (ipcMain, {detectScript, saveUiSettings}) => {
module.exports = { module.exports = {
getPlatformOverride, getPlatformOverride,
setPlatformOverride, setPlatformOverride,
addListeners addListeners,
}; };

View File

@@ -14,18 +14,23 @@ const addListeners = (ipcMain, {getCleanupReleases, standardIPCReply}) => {
let exists = false; let exists = false;
try { try {
exists = fs.existsSync(destination) && fs.lstatSync(destination).isDirectory(); exists = fs.existsSync(destination) && fs.lstatSync(destination).isDirectory();
} catch (e) { } catch (e) {}
}
standardIPCReply(event, Constants.IPC_Check_Installed_Reply, { standardIPCReply(event, Constants.IPC_Check_Installed_Reply, {
Dependencies: dependencies, Dependencies: dependencies,
Exists: exists, Exists: exists,
Version: data.Version, Version: data.Version,
}); });
}).catch(error => { })
standardIPCReply(event, Constants.IPC_Check_Installed_Reply, { .catch((error) => {
standardIPCReply(
event,
Constants.IPC_Check_Installed_Reply,
{
Dependencies: [], Dependencies: [],
Version: data.Version, Version: data.Version,
}, error); },
error
);
}); });
}); });
@@ -45,31 +50,40 @@ const addListeners = (ipcMain, {getCleanupReleases, standardIPCReply}) => {
const stream = fs.createReadStream(data.Source); const stream = fs.createReadStream(data.Source);
stream stream
.pipe(unzip.Extract({ path: destination })) .pipe(unzip.Extract({ path: destination }))
.on('error', error => { .on('error', (error) => {
try { try {
helpers.removeDirectoryRecursively(destination); helpers.removeDirectoryRecursively(destination);
} catch (e) { } catch (e) {}
}
stream.close(); stream.close();
standardIPCReply(event, Constants.IPC_Extract_Release_Complete, { standardIPCReply(
event,
Constants.IPC_Extract_Release_Complete,
{
Source: data.Source, Source: data.Source,
}, error); },
error
);
}) })
.on('finish', () => { .on('finish', () => {
stream.close(); stream.close();
if (os.platform() !== 'win32') { if (os.platform() !== 'win32') {
helpers helpers
.executeAndWait("chmod +x \"" + path.join(destination, 'repertory') + "\"") .executeAndWait('chmod +x "' + path.join(destination, 'repertory') + '"')
.then(() => { .then(() => {
standardIPCReply(event, Constants.IPC_Extract_Release_Complete, { standardIPCReply(event, Constants.IPC_Extract_Release_Complete, {
Source: data.Source, Source: data.Source,
}); });
}) })
.catch(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Extract_Release_Complete, { standardIPCReply(
event,
Constants.IPC_Extract_Release_Complete,
{
Source: data.Source, Source: data.Source,
}, error); },
}) error
);
});
} else { } else {
standardIPCReply(event, Constants.IPC_Extract_Release_Complete, { standardIPCReply(event, Constants.IPC_Extract_Release_Complete, {
Source: data.Source, Source: data.Source,
@@ -84,12 +98,10 @@ const addListeners = (ipcMain, {getCleanupReleases, standardIPCReply}) => {
.then(() => { .then(() => {
standardIPCReply(event, Constants.IPC_Test_Release_Reply, {}); standardIPCReply(event, Constants.IPC_Test_Release_Reply, {});
}) })
.catch(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Test_Release_Reply, {}, error); standardIPCReply(event, Constants.IPC_Test_Release_Reply, {}, error);
}); });
}); });
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

View File

@@ -5,12 +5,12 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
ipcMain.on(Constants.IPC_Export_Skylinks, (event, data) => { ipcMain.on(Constants.IPC_Export_Skylinks, (event, data) => {
helpers helpers
.exportSkylinks(data.Version, data.Paths) .exportSkylinks(data.Version, data.Paths)
.then(result => { .then((result) => {
standardIPCReply(event, Constants.IPC_Export_Skylinks_Reply, { standardIPCReply(event, Constants.IPC_Export_Skylinks_Reply, {
Result: result, Result: result,
}); });
}) })
.catch(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Export_Skylinks_Reply, {}, error); standardIPCReply(event, Constants.IPC_Export_Skylinks_Reply, {}, error);
}); });
}); });
@@ -18,12 +18,12 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
ipcMain.on(Constants.IPC_Grab_Skynet_Tree, (event, data) => { ipcMain.on(Constants.IPC_Grab_Skynet_Tree, (event, data) => {
helpers helpers
.grabSkynetFileTree(data.Version) .grabSkynetFileTree(data.Version)
.then(result => { .then((result) => {
standardIPCReply(event, Constants.IPC_Grab_Skynet_Tree_Reply, { standardIPCReply(event, Constants.IPC_Grab_Skynet_Tree_Reply, {
Result: result, Result: result,
}); });
}) })
.catch(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Grab_Skynet_Tree_Reply, {}, error); standardIPCReply(event, Constants.IPC_Grab_Skynet_Tree_Reply, {}, error);
}); });
}); });
@@ -31,17 +31,30 @@ const addListeners = (ipcMain, {standardIPCReply}) => {
ipcMain.on(Constants.IPC_Import_Skylinks, (event, data) => { ipcMain.on(Constants.IPC_Import_Skylinks, (event, data) => {
helpers helpers
.importSkylinks(data.Version, data.JsonArray) .importSkylinks(data.Version, data.JsonArray)
.then(result => { .then((result) => {
standardIPCReply(event, Constants.IPC_Import_Skylinks_Reply, { standardIPCReply(event, Constants.IPC_Import_Skylinks_Reply, {
Result: result, Result: result,
}); });
}) })
.catch(error => { .catch((error) => {
standardIPCReply(event, Constants.IPC_Import_Skylinks_Reply, {}, error); standardIPCReply(event, Constants.IPC_Import_Skylinks_Reply, {}, error);
}); });
}); });
ipcMain.on(Constants.IPC_Skynet_Test_Logon, (event, data) => {
helpers
.testSkynetLogon(data.Version, data.AuthURL, data.AuthUser, data.AuthPassword)
.then((success) => {
if (success) {
standardIPCReply(event, Constants.IPC_Skynet_Test_Logon_Reply, {});
} else {
standardIPCReply(event, Constants.IPC_Skynet_Test_Logon_Reply, {}, 'Logon failed. Please check credentials');
}
})
.catch((error) => {
standardIPCReply(event, Constants.IPC_Skynet_Test_Logon_Reply, {}, error);
});
});
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

View File

@@ -3,18 +3,19 @@ const fs = require('fs');
const helpers = require('../../helpers'); const helpers = require('../../helpers');
const path = require('path'); const path = require('path');
const getDirectories = source => { const getDirectories = (source) => {
try { try {
return fs.readdirSync(source, {withFileTypes: true}) return fs
.filter(dirent => dirent.isDirectory()) .readdirSync(source, { withFileTypes: true })
.map(dirent => dirent.name); .filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);
} catch { } catch {
return []; 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 = {}; let data = {};
@@ -32,7 +33,7 @@ const addListeners = ipcMain => {
AutoMount: false, AutoMount: false,
AutoRestart: true, AutoRestart: true,
MountLocation: '', MountLocation: '',
} };
} }
} }
@@ -69,13 +70,11 @@ const addListeners = ipcMain => {
}); });
}); });
ipcMain.on(Constants.IPC_Save_State, (event, data) => { ipcMain.on(Constants.IPC_Save_State, (_, data) => {
helpers.mkDirByPathSync(helpers.getDataDirectory()); helpers.mkDirByPathSync(helpers.getDataDirectory());
const configFile = path.join(helpers.getDataDirectory(), 'settings.json'); const configFile = path.join(helpers.getDataDirectory(), 'settings.json');
fs.writeFileSync(configFile, JSON.stringify(data.State), 'utf8'); fs.writeFileSync(configFile, JSON.stringify(data.State), 'utf8');
}); });
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

View File

@@ -11,6 +11,4 @@ const addListeners = (ipcMain, {closeApplication}) => {
}); });
}; };
module.exports = { module.exports = { addListeners };
addListeners
};

Some files were not shown because too many files have changed in this diff Show More