├── .babelrc ├── .eslintrc ├── .gitignore ├── .snyk ├── .travis.yml ├── LICENSE ├── README.md ├── app.html ├── app.js ├── appveyor.yml ├── assets ├── LICENSE.txt ├── Metropolis-Bold.otf ├── Metropolis-Medium.otf ├── Metropolis-Regular.ttf ├── fontawesome-webfont.woff2 ├── icon.icns ├── icon.ico ├── icon.png ├── siaLogo.svg ├── trayTemplate.png ├── trayTemplate@2x.png ├── trayWin.png └── trayWin@2x.png ├── css ├── font-awesome.min.css ├── fonts.css ├── general.css ├── grids-responsive-min.css ├── plugin-standard.css └── pure-min.css ├── doc ├── Developers.md ├── DevelopmentFlow.md ├── PluginTutorial.md ├── Plugins.md ├── Structure.md ├── Technologies.md ├── Testing.md ├── assets │ ├── basic-overview.png │ ├── files.png │ ├── github-plugin.png │ ├── sidebar.png │ ├── styled-overview.png │ ├── update-button.png │ ├── wallet-notifications.png │ ├── wallet-tooltip.png │ └── working-overview.png └── spec │ ├── PluginAPI.md │ ├── Siad.md │ └── Startup.md ├── internals ├── scripts │ ├── CheckNodeEnv.js │ └── CheckPortInUse.js └── webpack │ ├── webpack.config.base.js │ ├── webpack.config.eslint.js │ ├── webpack.config.main.prod.js │ ├── webpack.config.renderer.dev.js │ └── webpack.config.renderer.prod.js ├── js ├── mainjs │ ├── appMenu.js │ ├── config.js │ ├── contextMenu.js │ ├── initWindow.js │ └── trayMenu.js └── rendererjs │ ├── disabledplugin.js │ ├── index.js │ ├── loadingScreen.js │ ├── pluginapi.js │ ├── plugins.js │ └── statusbar.js ├── package-lock.json ├── package.json ├── plugins ├── About │ ├── assets │ │ └── button.png │ ├── css │ │ └── about.css │ ├── index.html │ └── js │ │ └── index.js ├── Files │ ├── assets │ │ └── button.png │ ├── css │ │ └── files.css │ ├── index.html │ └── js │ │ ├── actions │ │ └── files.js │ │ ├── components │ │ ├── addfolderbutton.js │ │ ├── addfolderdialog.js │ │ ├── allowanceconfirmation.js │ │ ├── allowancedialog.js │ │ ├── app.js │ │ ├── contractorstatus.js │ │ ├── deletedialog.js │ │ ├── directoryinfobar.js │ │ ├── downloadlist.js │ │ ├── dragoverlay.js │ │ ├── file.js │ │ ├── filebrowser.js │ │ ├── filecontrols.js │ │ ├── filelist.js │ │ ├── filetransfers.js │ │ ├── progressbar.js │ │ ├── redundancystatus.js │ │ ├── renamedialog.js │ │ ├── searchbutton.js │ │ ├── searchfield.js │ │ ├── setallowancebutton.js │ │ ├── transfer.js │ │ ├── transferlist.js │ │ ├── transfersbutton.js │ │ ├── unlockwarning.js │ │ ├── uploadbutton.js │ │ ├── uploaddialog.js │ │ ├── uploadlist.js │ │ └── usagestats.js │ │ ├── constants │ │ └── files.js │ │ ├── containers │ │ ├── addfolderbutton.js │ │ ├── addfolderdialog.js │ │ ├── allowancedialog.js │ │ ├── app.js │ │ ├── contractorstatus.js │ │ ├── deletedialog.js │ │ ├── filebrowser.js │ │ ├── filecontrols.js │ │ ├── filelist.js │ │ ├── filetransfers.js │ │ ├── renamedialog.js │ │ ├── searchbutton.js │ │ ├── searchfield.js │ │ ├── setallowancebutton.js │ │ ├── transfersbutton.js │ │ ├── uploadbutton.js │ │ ├── uploaddialog.js │ │ └── usagestats.js │ │ ├── index.js │ │ ├── reducers │ │ ├── allowancedialog.js │ │ ├── deletedialog.js │ │ ├── files.js │ │ ├── index.js │ │ ├── renamedialog.js │ │ └── wallet.js │ │ └── sagas │ │ ├── files.js │ │ ├── helpers.js │ │ └── index.js ├── Hosting │ ├── assets │ │ └── button.png │ ├── css │ │ └── hosting.css │ ├── index.html │ └── js │ │ ├── actions │ │ └── actions.js │ │ ├── components │ │ ├── announce.js │ │ ├── app.js │ │ ├── body.js │ │ ├── fileslist.js │ │ ├── header.js │ │ ├── hoststatus.js │ │ ├── resizeDialog.js │ │ ├── settingslist.js │ │ ├── walletmodal.js │ │ ├── warningbar.js │ │ └── warningmodal.js │ │ ├── constants │ │ └── constants.js │ │ ├── containers │ │ ├── announce.js │ │ ├── body.js │ │ ├── fileslist.js │ │ ├── header.js │ │ ├── resizeDialog.js │ │ ├── settingslist.js │ │ └── walletmodal.js │ │ ├── index.js │ │ ├── main.js │ │ ├── reducers │ │ ├── hosting.js │ │ ├── index.js │ │ ├── modal.js │ │ └── setting.js │ │ ├── sagas │ │ └── saga.js │ │ └── utils │ │ └── host.js ├── Logs │ ├── assets │ │ └── button.png │ ├── index.html │ └── js │ │ ├── actions.js │ │ ├── components │ │ ├── app.js │ │ ├── filtercontrol.js │ │ ├── filtercontrols.js │ │ └── logview.js │ │ ├── constants.js │ │ ├── containers │ │ ├── filtercontrols.js │ │ └── logview.js │ │ ├── filters.js │ │ ├── index.js │ │ ├── logparse.js │ │ ├── main.js │ │ ├── reducer.js │ │ └── utils.js ├── Terminal │ ├── assets │ │ ├── button.png │ │ ├── roboto-mono-100.woff2 │ │ ├── roboto-mono-300.woff2 │ │ └── roboto-mono.woff2 │ ├── css │ │ ├── roboto-mono.css │ │ └── style.css │ ├── index.html │ └── js │ │ ├── actions │ │ └── commandline.js │ │ ├── components │ │ ├── app.js │ │ ├── commandhistorylist.js │ │ ├── commandinput.js │ │ ├── commandline.js │ │ ├── seedprompt.js │ │ └── walletpasswordprompt.js │ │ ├── constants │ │ ├── commandline.js │ │ └── helper.js │ │ ├── containers │ │ ├── commandhistorylist.js │ │ ├── commandinput.js │ │ ├── commandline.js │ │ ├── seedprompt.js │ │ └── walletpasswordprompt.js │ │ ├── index.js │ │ ├── reducers │ │ ├── commandline.js │ │ └── index.js │ │ └── utils │ │ └── helpers.js └── Wallet │ ├── assets │ └── button.png │ ├── css │ └── wallet.css │ ├── index.html │ └── js │ ├── actions │ ├── error.js │ └── wallet.js │ ├── components │ ├── app.js │ ├── backupbutton.js │ ├── backupprompt.js │ ├── balanceinfo.js │ ├── changepasswordbutton.js │ ├── changepassworddialog.js │ ├── confirmationdialog.js │ ├── initseedform.js │ ├── lockbutton.js │ ├── lockscreen.js │ ├── newwalletdialog.js │ ├── newwalletform.js │ ├── passwordprompt.js │ ├── receivebutton.js │ ├── receiveprompt.js │ ├── recoverbutton.js │ ├── recoverydialog.js │ ├── rescandialog.js │ ├── sendbutton.js │ ├── sendprompt.js │ ├── transactionlist.js │ ├── uninitializedwalletdialog.js │ └── wallet.js │ ├── constants │ ├── error.js │ └── wallet.js │ ├── containers │ ├── backupbutton.js │ ├── backupprompt.js │ ├── balanceinfo.js │ ├── changepasswordbutton.js │ ├── changepassworddialog.js │ ├── confirmationdialog.js │ ├── lockbutton.js │ ├── lockscreen.js │ ├── newwalletdialog.js │ ├── newwalletform.js │ ├── passwordprompt.js │ ├── receivebutton.js │ ├── receiveprompt.js │ ├── recoverbutton.js │ ├── recoverydialog.js │ ├── sendprompt.js │ ├── transactionlist.js │ ├── uninitializedwalletdialog.js │ └── wallet.js │ ├── index.js │ ├── main.js │ ├── reducers │ ├── index.js │ ├── newwalletdialog.js │ ├── passwordprompt.js │ ├── receiveprompt.js │ ├── sendprompt.js │ └── wallet.js │ └── sagas │ ├── helpers.js │ ├── index.js │ └── wallet.js ├── release.sh └── test ├── app.js ├── config.unit.js ├── dom.setup.js ├── files ├── filebrowser.component.js ├── filelist.component.js ├── files.sagas.js ├── helpers.js ├── uploadbutton.component.js ├── uploaddialog.component.js └── usagestats.component.js ├── logs ├── logs.integration.js ├── logs.unit.js └── testdir │ ├── consensus │ └── consensus.log │ ├── gateway │ └── gateway.log │ ├── host │ ├── host.log │ └── storagemanager │ │ └── storagemanager.log │ ├── miner │ └── miner.log │ ├── renter │ ├── contractor.log │ ├── hostdb.log │ └── renter.log │ ├── siad-output.log │ └── wallet │ └── wallet.log ├── pluginapi.unit.js ├── plugins.unit.js ├── wallet.integration.js └── wallet ├── balanceinfo.component.js ├── lockscreen.component.js ├── passwordprompt.component.js ├── receiveprompt.component.js ├── sendprompt.component.js ├── transactionlist.component.js └── wallet.component.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "electron": "2.0.2", 8 | "node": 8 9 | }, 10 | "useBuiltIns": true 11 | } 12 | ], 13 | "stage-0", 14 | "react" 15 | ], 16 | "plugins": [ 17 | "add-module-exports" 18 | ], 19 | "env": { 20 | "production": { 21 | "presets": [ 22 | "react-optimize" 23 | ] 24 | }, 25 | "development": { 26 | "plugins": [ 27 | "transform-class-properties", 28 | "transform-es2015-classes", 29 | "react-hot-loader/babel" 30 | ] 31 | }, 32 | "plugins": ["syntax-dynamic-import"] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "allowImportExportEverywhere": true 6 | }, 7 | "env": { 8 | "browser": true, 9 | "node": true 10 | }, 11 | "settings": { 12 | "import/resolver": { 13 | "webpack": { 14 | "config": "./internals/webpack/webpack.config.eslint.js" 15 | } 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated Items 2 | release 3 | node_modules 4 | npm-debug.log 5 | config.json 6 | Sia 7 | doc/Sia-UI 8 | errors.log 9 | dist 10 | sia-testing 11 | profiles 12 | test.json 13 | 14 | #VSCode 15 | .vscode 16 | 17 | # Vim 18 | *.sw? 19 | 20 | # IntelliJ 21 | .idea 22 | *.iml 23 | -------------------------------------------------------------------------------- /.snyk: -------------------------------------------------------------------------------- 1 | # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. 2 | version: v1.7.1 3 | ignore: {} 4 | # patches apply the minimum changes required to fix a vulnerability 5 | patch: 6 | 'npm:ms:20170412': 7 | - electron > extract-zip > debug > ms: 8 | patched: '2017-09-28T17:53:43.125Z' 9 | 'npm:debug:20170905': 10 | - electron > extract-zip > debug: 11 | patched: '2017-09-28T17:53:43.125Z' 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8.11.1" 5 | 6 | notifications: 7 | email: false 8 | 9 | sudo: false 10 | 11 | branches: 12 | only: 13 | - master 14 | 15 | before_script: 16 | - "export DISPLAY=:99.0" 17 | - "sh -e /etc/init.d/xvfb start" 18 | - sleep 3 19 | 20 | install: 21 | - wget https://github.com/NebulousLabs/Sia/releases/download/v1.3.3/Sia-v1.3.3-linux-amd64.zip 22 | - unzip Sia-v1.3.3-linux-amd64.zip 23 | - mv Sia-v1.3.3-linux-amd64 Sia 24 | - export PATH=$PATH:`pwd`/Sia/ 25 | - npm install 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nebulous 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [![Sia Logo](http://sia.tech/img/svg/sia-green-logo.svg)](http://sia.tech/) User Interface 2 | 3 | This project has moved to Gitlab: https://gitlab.com/NebulousLabs/Sia-UI -------------------------------------------------------------------------------- /app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sia-UI 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |

18 | 19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 |
30 | 31 | 32 |
33 | 34 | 36 | 37 | 38 |
39 |
40 |
41 | Tooltip 42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 |
50 | 51 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | import { app } from 'electron' 2 | import Path from 'path' 3 | import loadConfig from './js/mainjs/config.js' 4 | import initWindow from './js/mainjs/initWindow.js' 5 | 6 | // load config.json manager 7 | global.config = loadConfig(Path.join(app.getPath('userData'), 'config.json')) 8 | let mainWindow 9 | 10 | // disable hardware accelerated rendering 11 | app.disableHardwareAcceleration() 12 | 13 | // Fixes Chrome bug to render correct colors: 14 | // https://github.com/electron/electron/issues/10732 15 | app.commandLine.appendSwitch('force-color-profile', 'srgb') 16 | 17 | // Allow only one instance of Sia-UI 18 | const shouldQuit = app.makeSingleInstance(() => { 19 | if (mainWindow) { 20 | if (mainWindow.isMinimized()) { 21 | mainWindow.restore() 22 | } 23 | mainWindow.focus() 24 | } 25 | }) 26 | 27 | if (shouldQuit) { 28 | app.quit() 29 | } 30 | 31 | // When Electron loading has finished, start Sia-UI. 32 | app.on('ready', () => { 33 | // Load mainWindow 34 | mainWindow = initWindow(config) 35 | }) 36 | 37 | // Quit once all windows have been closed. 38 | app.on('window-all-closed', () => { 39 | app.quit() 40 | }) 41 | 42 | // On quit, save the config. There's no need to call siad.stop here, since if 43 | // siad was launched by the UI, it will be a descendant of the UI in the 44 | // process tree and will therefore be killed. 45 | app.on('quit', () => { 46 | config.save() 47 | }) 48 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{build}" 2 | 3 | platform: x64 4 | environment: 5 | nodejs_version: "6.9.2" 6 | 7 | install: 8 | - ps: wget https://github.com/NebulousLabs/Sia/releases/download/v1.0.3/Sia-v1.0.3-windows-amd64.zip -O Sia-v1.0.3-windows-amd64.zip 9 | - ps: 7z.exe x Sia-v1.0.3-windows-amd64.zip 10 | - rename Sia-v1.0.3-windows-amd64 Sia 11 | - npm install 12 | 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /assets/Metropolis-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/Metropolis-Bold.otf -------------------------------------------------------------------------------- /assets/Metropolis-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/Metropolis-Medium.otf -------------------------------------------------------------------------------- /assets/Metropolis-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/Metropolis-Regular.ttf -------------------------------------------------------------------------------- /assets/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/icon.png -------------------------------------------------------------------------------- /assets/trayTemplate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/trayTemplate.png -------------------------------------------------------------------------------- /assets/trayTemplate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/trayTemplate@2x.png -------------------------------------------------------------------------------- /assets/trayWin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/trayWin.png -------------------------------------------------------------------------------- /assets/trayWin@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/assets/trayWin@2x.png -------------------------------------------------------------------------------- /css/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Metropolis'; 3 | src: url('Metropolis-Regular.ttf') format('truetype'); 4 | font-weight: normal; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Metropolis'; 10 | src: url('Metropolis-Bold.ttf') format('truetype'); 11 | font-weight: bold; 12 | font-style: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Metropolis'; 17 | src: url('Metropolis-Medium.ttf') format('truetype'); 18 | font-weight: 500; 19 | font-style: normal; 20 | } 21 | -------------------------------------------------------------------------------- /doc/Developers.md: -------------------------------------------------------------------------------- 1 | # Developers 2 | 3 | To get Sia-UI running, ensure you have installed node, and npm. After that, 4 | starting the application is as easy as running: `npm install && npm start` 5 | 6 | ## Resources 7 | 8 | The following documents explains the environment that Sia-UI runs in. 9 | 10 | * [Technologies](Technologies.md) 11 | * [Testing](Testing.md) 12 | * [Structure](Structure.md) 13 | 14 | ## Development 15 | 16 | The following explains some aspects of how I, [Ming 17 | Luo](https://github.com/Mingling94), develop Sia-UI and my general workflow 18 | 19 | * [Development Flow](DevelopmentFlow.md) 20 | * [Plugins](Plugins.md) 21 | 22 | -------------------------------------------------------------------------------- /doc/DevelopmentFlow.md: -------------------------------------------------------------------------------- 1 | # Development Flow 2 | 3 | ## Packaging & Releasing Sia-UI 4 | 5 | There are some packaging scripts (using electron-packager) in the package.json. 6 | 7 | For them to work, you will need to have release archives of the correct version 8 | in the `release` folder of the Sia package in your GOPATH. To do so, run `make 9 | xc` from the Sia repository followed by `npm run release` from the Sia-UI 10 | repository. 11 | 12 | ## Building Distributables 13 | 14 | Places packaged versions into release/ folder, see the package.json for details. 15 | 16 | * `npm run release` 17 | 18 | ## Other Commands 19 | 20 | Useful commands for development. 21 | 22 | * `npm run clean` 23 | will remove node_modules, your Sia state kept in lib/Sia, and the 24 | configuration settings from config.json. 25 | * `npm run fresh` 26 | will run clean, install, then start to simulate a fresh install run of the UI. 27 | * `npm run debug` 28 | will run the UI with a debug port to aide in inspecting the main process. 29 | * `npm run doc` 30 | will generate documentation about the UI's classes and functions. It's somewhat 31 | messy though. 32 | * `npm run lint` 33 | will output style suggestions for the UI's javascript, including for plugins. 34 | 35 | -------------------------------------------------------------------------------- /doc/Plugins.md: -------------------------------------------------------------------------------- 1 | # Plugins 2 | 3 | In order to form a more modular codebase, most functionality in the UI is 4 | contained in webpage like structures we call "Plugins". 5 | 6 | ## What is a plugin? 7 | 8 | A plugin, in the context of Sia-UI, is a self-contained add-on that offers 9 | graphical functionality to interact with the Sia-network. We'll develop plugins 10 | we believe would be widely used, but we're also redesigning Sia-UI to enable 11 | third-party developers interested in our project to make their own plugins. 12 | 13 | The structure of a plugin is the exact same as a webpage, with some added 14 | functionality via Node.js & Electron. There are only two hard-rules to a 15 | plugin: 16 | 17 | 1. It must be self-contained in a directory of its name. 18 | 2. It must have an index.html in this directory. 19 | 20 | ## Why plugins? 21 | 22 | We at NebulousLabs are all about decentralization... of everything! Thus we are 23 | redesigning our GUI desktop application with that in mind. We want the 24 | community interested in the Sia network to be able to: 25 | 26 | 1. Use Sia-UI in the way they want with only the plugins they care about instead 27 | of using our rigid set of tools. 28 | 2. Be able to design and implement their own plugins. 29 | 3. Customize their own UI experience simply without obfuscating menubars. 30 | 31 | -------------------------------------------------------------------------------- /doc/Technologies.md: -------------------------------------------------------------------------------- 1 | # Technologies 2 | 3 | We use three major tools in this application and they follow this hierarchy: 4 | Javascript -> Node/NPM -> Electron. 5 | 6 | ### Javascript 7 | 8 | This should be familiar to most webdevs. We mostly adhere to [certain style 9 | conventions](http://javascript.crockford.com/code.html) 10 | 11 | ### [NPM](https://www.npmjs.com/) 12 | 13 | Node Package Manager gives easy package management. We use NPM to manage our 14 | dependencies (such as Electron) and our development dependencies (such as 15 | JSHint or JSDoc). NPM also handles our command scripting. To understand this 16 | and view available npm scripts, see the [package.json](../package.json) file 17 | and [this npm documentation](https://docs.npmjs.com/misc/scripts) on how npm 18 | commands work. 19 | 20 | A shortcut explanation of the npm commands you most commonly run from terminal 21 | 22 | * `npm install` installs node packages from the npm registry listed under 23 | `devDependencies` and `dependencies` in the package.json. 24 | * `npm start` runs the app 25 | * `npm test` lints the app with [JSHint](http://jshint.com/about/) and then 26 | runs some test scripts in the `test` folder 27 | * There's a slew of other custom npm commands under `scripts` in the 28 | package.json 29 | 30 | OPTIONAL: [A useful guide about using NPM as a build 31 | tool](http://blog.keithcirkel.co.uk/how-to-use-npm-as-a-build-tool/) 32 | 33 | ### [Electron](http://electron.atom.io/) 34 | 35 | It's the core set of libararies that power the Atom text editor and is useful 36 | for creating cross-platform desktop applications. 37 | 38 | Making this a desktop application instead of a webapp gives us libraries to 39 | access filepaths and other OS resources (via Node libraries) that a webapp 40 | would be limited from. 41 | 42 | The code does not have to adhere to compatibility for all browsers (looking 43 | at you, Internet Explorer) because electron is run on chromium. This extends 44 | from JS to CSS (with the use of -webkit- rules when applicable). 45 | 46 | We do occasionally use ES2015 and ES2016 conventions and syntax in the code 47 | base with no worries for the same reason 48 | 49 | -------------------------------------------------------------------------------- /doc/Testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | Testing Sia-UI is fairly immature and needs many more tests written to aide 4 | development. 5 | 6 | ## Technologies 7 | The testing environment has a lot of node modules that aren't used elsewhere in 8 | the app. The open source community really shines here in that the testing 9 | environment for our app, though using a somewhat obscure framework like 10 | electron, has a lot of node packages that cater to its testing needs. 11 | 12 | ### [WebDriverIO](http://webdriver.io/) 13 | The primary technology used for testing is since it seems to have [some level 14 | of 15 | support](https://github.com/atom/electron/blob/master/docs/tutorial/using-selenium-and-webdriver.md#setting-up-with-webdriverio) 16 | for an electron framework. It is a module of node.js bindings for the older 17 | Selenium WebDriver. 18 | 19 | #### [spectron](https://github.com/kevinsawicki/spectron) 20 | You'll notice WebDriverIO isn't actually a devDependency in the `package.json`, 21 | that's because there's a lot of heavy lifting to make WebDriverIO cooperate 22 | with electron that is already done in this package. This module is super 23 | useful and a great source of examples for testing and working with Javascript 24 | Promises in general. 25 | 26 | ### [Mocha](https://mochajs.org/) 27 | This is a popular choice for running series of tests within a node environment. 28 | 29 | #### [Electron-mocha](https://github.com/jprichardson/electron-mocha) 30 | This module enables electron modules to be `require()`ed in js test files ran 31 | by mocha. 32 | 33 | ### [Chai](http://chaijs.com/) 34 | This module allows for clean testing syntax in the form of 35 | behavior-driven-development [(BDD)](http://chaijs.com/api/bdd/). 36 | 37 | #### [Chai As Promised](https://github.com/domenic/chai-as-promised) 38 | This builds off of Chai and "pairs really nicely with" WebDriverIO as detailed 39 | in [spectron's 40 | README](https://github.com/kevinsawicki/spectron#with-chai-as-promised) 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /doc/assets/basic-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/doc/assets/basic-overview.png -------------------------------------------------------------------------------- /doc/assets/files.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/doc/assets/files.png -------------------------------------------------------------------------------- /doc/assets/github-plugin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/doc/assets/github-plugin.png -------------------------------------------------------------------------------- /doc/assets/sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/doc/assets/sidebar.png -------------------------------------------------------------------------------- /doc/assets/styled-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/doc/assets/styled-overview.png -------------------------------------------------------------------------------- /doc/assets/update-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/doc/assets/update-button.png -------------------------------------------------------------------------------- /doc/assets/wallet-notifications.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/doc/assets/wallet-notifications.png -------------------------------------------------------------------------------- /doc/assets/wallet-tooltip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/doc/assets/wallet-tooltip.png -------------------------------------------------------------------------------- /doc/assets/working-overview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/doc/assets/working-overview.png -------------------------------------------------------------------------------- /doc/spec/PluginAPI.md: -------------------------------------------------------------------------------- 1 | # Sia-UI Plugin API Specification 2 | 3 | ## Introduction 4 | 5 | This specification outlines the functionality that Sia-UI's plugin API exposes to developers. 6 | 7 | ## Functionality 8 | 9 | The Sia-UI Plugin api exposes a simple interface for making API calls to siad, creating file dialogs, displaying notifications, and displaying error messages. This interface is assigned to the `window` object of each plugin, and has the following functions: 10 | 11 | - `SiaAPI.call()`, a wrapper to the configured `sia.js`'s `.apiCall` function. 12 | - `SiaAPI.config`, the current Sia-UI config. 13 | - `SiaAPI.hastingsToSiacoins`, conversion function from hastings to siacoins. Returns a `BigNumber` and takes either a `BigNumber` or `string`. 14 | - `SiaAPI.siacoinsToHastings`, conversion function from siacoins to hastings. 15 | - `SiaAPI.openFile(options)`, a wrapper which calls Electron.dialog.showOpenDialog with `options`. 16 | - `SiaAPI.saveFile(options)`, a wrapper which calls Electron.dialog.showSaveDialog with `options`. 17 | - `SiaAPI.showMessage(options)`, a wrapper which calls Electron.showMessageBox with `options`. 18 | - `SiaAPI.showerror(options)`, a wrapper which calls Electron.showErrorBox with `options`. 19 | -------------------------------------------------------------------------------- /doc/spec/Siad.md: -------------------------------------------------------------------------------- 1 | # Sia-UI Siad lifecycle specification 2 | 3 | ## Introduction 4 | 5 | The purpose of this spec is to outline the desired behaviour of Sia-UI as it relates to starting, stopping, or connecting to an existing Siad. 6 | 7 | ## Desired Functionality 8 | 9 | - Sia-UI should check for the existence of a running daemon on launch, by calling `/daemon/version` using the UI's current config. 10 | If the daemon isn't running, Sia-UI should launch a new siad instance, using the bundled siad binary. If a bundled binary cannot be found, prompt the user for the location of their `siad`. Siad's lifetime should be bound to Sia-UI, meaning that `/daemon/stop` should be called when Sia-UI is exited. 11 | - Alternatively, if an instance of `siad` is found to be running when Sia-UI starts up, Sia-UI should not quit the daemon when it is exited. 12 | 13 | This behaviour can be implemented without any major changes to the codebase by leveraging the existing `detached` flag. 14 | 15 | ## Considerations 16 | 17 | - Calling `/daemon/version` using the UI's config does not actually tell you whether or not there is an active `siad` running on the host, since a different `siad` instance could be running using a bindaddr different than the one specified in `config`. 18 | -------------------------------------------------------------------------------- /doc/spec/Startup.md: -------------------------------------------------------------------------------- 1 | # Sia-UI Startup Behaviour Specification 2 | 3 | ## Introduction 4 | 5 | This specification outlines the desired behaviour of Sia-UI when it first launches. 6 | 7 | ## Desired Functionality 8 | 9 | ### Main Process 10 | - Initialize electron's main window. Register applicable event listeners (`close`, `closed`, etc), and load the renderer's entrypoint `index.html`. 11 | 12 | ### Renderer Process 13 | - Display a loading screen until communication with an active `siad` has been established. 14 | - Disable the loading screen and initialize the plugin system. 15 | -------------------------------------------------------------------------------- /internals/scripts/CheckNodeEnv.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | 3 | export default function CheckNodeEnv (expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set') 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ) 14 | process.exit(2) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /internals/scripts/CheckPortInUse.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import detectPort from 'detect-port' 3 | 4 | ;(function CheckPortInUse () { 5 | const port = process.env.PORT || '1212' 6 | 7 | detectPort(port, (err, availablePort) => { 8 | if (port !== String(availablePort)) { 9 | throw new Error( 10 | chalk.whiteBright.bgRed.bold( 11 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm run dev` 12 | ) 13 | ) 14 | } else { 15 | process.exit(0) 16 | } 17 | }) 18 | })() 19 | -------------------------------------------------------------------------------- /internals/webpack/webpack.config.base.js: -------------------------------------------------------------------------------- 1 | import path from 'path' 2 | import webpack from 'webpack' 3 | const version = require('../../package.json').version 4 | 5 | export default { 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.jsx?$/, 10 | exclude: /node_modules/, 11 | use: { 12 | loader: 'babel-loader', 13 | options: { 14 | cacheDirectory: true 15 | } 16 | } 17 | } 18 | ] 19 | }, 20 | 21 | output: { 22 | path: path.resolve('./dist'), 23 | // https://github.com/webpack/webpack/issues/1114 24 | libraryTarget: 'commonjs2' 25 | }, 26 | 27 | resolve: { 28 | extensions: ['.js', '.jsx', '.json'], 29 | modules: [path.join(__dirname, 'app'), 'node_modules'] 30 | }, 31 | plugins: [ 32 | new webpack.EnvironmentPlugin({ 33 | NODE_ENV: 'production' 34 | }), 35 | 36 | new webpack.DefinePlugin({ 37 | VERSION: JSON.stringify(version) 38 | }), 39 | 40 | new webpack.NamedModulesPlugin() 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /internals/webpack/webpack.config.eslint.js: -------------------------------------------------------------------------------- 1 | require('babel-register') 2 | 3 | module.exports = require('./webpack.config.renderer.dev') 4 | -------------------------------------------------------------------------------- /internals/webpack/webpack.config.main.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import webpack from 'webpack' 6 | import merge from 'webpack-merge' 7 | import UglifyJSPlugin from 'uglifyjs-webpack-plugin' 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' 9 | import baseConfig from './webpack.config.base' 10 | import CheckNodeEnv from '../scripts/CheckNodeEnv' 11 | import path from 'path' 12 | 13 | CheckNodeEnv('production') 14 | 15 | export default merge.smart(baseConfig, { 16 | devtool: 'none', 17 | 18 | mode: 'production', 19 | 20 | target: 'electron-main', 21 | 22 | entry: './app.js', 23 | 24 | output: { 25 | path: path.resolve('./dist'), 26 | filename: './main.js' 27 | }, 28 | 29 | plugins: [ 30 | new UglifyJSPlugin({ 31 | parallel: true, 32 | sourceMap: true 33 | }), 34 | 35 | new BundleAnalyzerPlugin({ 36 | analyzerMode: process.env.OPEN_ANALYZER === 'true' 37 | ? 'server' 38 | : 'disabled', 39 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 40 | }), 41 | 42 | /** 43 | * Create global constants which can be configured at compile time. 44 | * 45 | * Useful for allowing different behaviour between development builds and 46 | * release builds 47 | * 48 | * NODE_ENV should be production so that modules do not perform certain 49 | * development checks 50 | */ 51 | new webpack.EnvironmentPlugin({ 52 | NODE_ENV: 'production', 53 | DEBUG_PROD: 'false' 54 | }) 55 | ], 56 | 57 | /** 58 | * Disables webpack processing of __dirname and __filename. 59 | * If you run the bundle in node.js it falls back to these values of node.js. 60 | * https://github.com/webpack/webpack/issues/2010 61 | */ 62 | node: { 63 | __dirname: false, 64 | __filename: false 65 | } 66 | }) 67 | -------------------------------------------------------------------------------- /js/mainjs/appMenu.js: -------------------------------------------------------------------------------- 1 | import { Menu } from 'electron' 2 | 3 | export default function (window) { 4 | // Template for OSX app menu commands 5 | // Selectors call the main app's NSApplication methods. 6 | const menutemplate = [ 7 | { 8 | label: 'Sia', 9 | submenu: [ 10 | { label: 'About Sia', selector: 'orderFrontStandardAboutPanel:' }, 11 | { type: 'separator' }, 12 | { label: 'Hide Sia', accelerator: 'CmdOrCtrl+H', selector: 'hide:' }, 13 | { type: 'separator' }, 14 | { 15 | label: 'Quit', 16 | accelerator: 'CmdOrCtrl+Q', 17 | click: () => window.webContents.send('quit') 18 | } 19 | ] 20 | }, 21 | { 22 | label: 'Edit', 23 | submenu: [ 24 | { label: 'Undo', accelerator: 'CmdOrCtrl+Z', selector: 'undo:' }, 25 | { label: 'Redo', accelerator: 'Shift+CmdOrCtrl+Z', selector: 'redo:' }, 26 | { type: 'separator' }, 27 | { label: 'Cut', accelerator: 'CmdOrCtrl+X', selector: 'cut:' }, 28 | { label: 'Copy', accelerator: 'CmdOrCtrl+C', selector: 'copy:' }, 29 | { label: 'Paste', accelerator: 'CmdOrCtrl+V', selector: 'paste:' }, 30 | { 31 | label: 'Select All', 32 | accelerator: 'CmdOrCtrl+A', 33 | selector: 'selectAll:' 34 | } 35 | ] 36 | } 37 | ] 38 | 39 | return Menu.buildFromTemplate(menutemplate) 40 | } 41 | -------------------------------------------------------------------------------- /js/mainjs/trayMenu.js: -------------------------------------------------------------------------------- 1 | import { Menu } from 'electron' 2 | 3 | export default function (window) { 4 | // Template for Sia-UI tray menu. 5 | const menutemplate = [ 6 | { 7 | label: 'Show Sia', 8 | click: () => window.show() 9 | }, 10 | { type: 'separator' }, 11 | { 12 | label: 'Hide Sia', 13 | click: () => window.hide() 14 | }, 15 | { type: 'separator' }, 16 | { 17 | label: 'Quit Sia', 18 | click: () => { 19 | window.webContents.send('quit') 20 | } 21 | } 22 | ] 23 | 24 | return Menu.buildFromTemplate(menutemplate) 25 | } 26 | -------------------------------------------------------------------------------- /js/rendererjs/disabledplugin.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { shell } from 'electron' 4 | 5 | const containerStyle = { 6 | display: 'flex', 7 | alignItems: 'center', 8 | justifyContent: 'center', 9 | flexDirection: 'column', 10 | backgroundColor: '#C6C6C6', 11 | width: '100%', 12 | height: '100%' 13 | } 14 | 15 | const errorLogStyle = { 16 | height: '300px', 17 | width: '80%', 18 | overflow: 'auto', 19 | marginBottom: '15px' 20 | } 21 | 22 | const reportStyle = { 23 | color: 'blue', 24 | cursor: 'pointer' 25 | } 26 | 27 | const handleReport = () => { 28 | shell.openExternal('https://github.com/NebulousLabs/Sia/issues') 29 | } 30 | 31 | const DisabledPlugin = ({ errorMsg, startSiad }) => ( 32 |
33 |

34 | Siad has exited unexpectedly. Please submit a bug report including the 35 | error log 36 | 37 | here. 38 | 39 |

40 |

Error Log:

41 | 44 | 45 |
46 | ) 47 | 48 | DisabledPlugin.propTypes = { 49 | errorMsg: PropTypes.string.isRequired, 50 | startSiad: PropTypes.func.isRequired 51 | } 52 | 53 | export default DisabledPlugin 54 | -------------------------------------------------------------------------------- /plugins/About/assets/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/plugins/About/assets/button.png -------------------------------------------------------------------------------- /plugins/About/css/about.css: -------------------------------------------------------------------------------- 1 | /* Style Guide: 2 | * Transparent: 70% Opacity 3 | * 4 | * White: #FFFFFF 5 | * Grey-White: #F5F5F5 6 | * Faint-Grey: #ECECEC 7 | * Light-Grey: #DDDDDD 8 | * Grey: #C5C5C5 9 | * Grey-Black: #4A4A4A 10 | * Black: #000000 11 | */ 12 | .about { 13 | color: #c5c5c5; 14 | margin: 20px 20px; 15 | padding-left: 10px; 16 | padding-bottom: 10px; 17 | border-bottom: 1px solid #e0e0e0; 18 | } 19 | .about .version { 20 | display: inline-block; 21 | color: #4a4a4a; 22 | } 23 | button { 24 | color: #4a4a4a; 25 | } 26 | -------------------------------------------------------------------------------- /plugins/About/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | About 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 |
About
20 |
21 | 22 | 23 |
24 | Sia UI version: 25 |
26 | 27 |
28 |
29 |
30 | Sia version: 31 |
32 | 33 |
34 |
35 |
36 | 37 |
38 |
39 | 40 |
41 | 44 | 53 |
54 | 55 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /plugins/About/js/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | import { platform } from 'os' 3 | import { shell } from 'electron' 4 | 5 | // If dev enable window reload 6 | if (process.env.NODE_ENV === 'development') { 7 | require('electron-css-reload')() 8 | } 9 | 10 | // Set UI version via package.json. 11 | document.getElementById('uiversion').innerHTML = VERSION 12 | 13 | // Set daemon version via API call. 14 | SiaAPI.call('/daemon/version', (err, result) => { 15 | if (err) { 16 | SiaAPI.showError('Error', err.toString()) 17 | } else { 18 | document.getElementById('siaversion').innerHTML = result.version 19 | } 20 | }) 21 | 22 | function genDownloadLink (version, thePlatform) { 23 | let plat = thePlatform 24 | if (plat === 'darwin') { 25 | plat = 'osx' 26 | } 27 | 28 | return `https://github.com/NebulousLabs/Sia-UI/releases/download/v${version}/Sia-UI-v${version}-${plat}-x64.zip` 29 | } 30 | 31 | function updateCheck () { 32 | SiaAPI.call('/daemon/update', (err, result) => { 33 | if (err) { 34 | SiaAPI.showError('Error', err.toString()) 35 | } else if (result.available) { 36 | document.getElementById('newversion').innerHTML = result.version 37 | document.getElementById('downloadlink').href = genDownloadLink( 38 | result.version, 39 | platform() 40 | ) 41 | document.getElementById('nonew').style.display = 'none' 42 | document.getElementById('yesnew').style.display = 'block' 43 | } else { 44 | document.getElementById('nonew').style.display = 'block' 45 | document.getElementById('yesnew').style.display = 'none' 46 | } 47 | }) 48 | } 49 | 50 | document.getElementById('updatecheck').onclick = updateCheck 51 | document.getElementById('datadiropen').onclick = () => { 52 | shell.showItemInFolder(SiaAPI.config.siad.datadir) 53 | } 54 | -------------------------------------------------------------------------------- /plugins/Files/assets/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/plugins/Files/assets/button.png -------------------------------------------------------------------------------- /plugins/Files/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Files 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /plugins/Files/js/components/addfolderbutton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const AddFolderButton = ({ actions }) => { 4 | const handleClick = () => actions.showAddFolderDialog() 5 | return ( 6 |
7 | 8 | New Folder 9 |
10 | ) 11 | } 12 | 13 | export default AddFolderButton 14 | -------------------------------------------------------------------------------- /plugins/Files/js/components/addfolderdialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const AddFolderDialog = ({ actions }) => { 4 | const onConfirmClick = e => { 5 | e.preventDefault() 6 | actions.addFolder(e.target.name.value) 7 | actions.hideAddFolderDialog() 8 | } 9 | const onCancelClick = () => actions.hideAddFolderDialog() 10 | return ( 11 |
12 |
13 |
Enter a name for the new folder:
14 |
15 |
16 | 17 |
18 |
19 | 20 | 23 |
24 |
25 |
26 |
27 | ) 28 | } 29 | 30 | export default AddFolderDialog 31 | -------------------------------------------------------------------------------- /plugins/Files/js/components/allowanceconfirmation.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const ConfirmationDialog = ({ allowance, onConfirmClick, onCancelClick }) => { 5 | const confirmationStyle = { 6 | padding: '40px 40px 40px 40px', 7 | display: 'flex', 8 | flexDirection: 'column', 9 | alignItems: 'center', 10 | justifyContent: 'space-around', 11 | backgroundColor: '#ececec', 12 | width: '50%', 13 | height: '400px' 14 | } 15 | const buttonStyle = { 16 | marginLeft: '5px', 17 | marginRight: '5px' 18 | } 19 | const confirmationTextStyle = { 20 | marginBottom: '20px', 21 | fontSize: '24px' 22 | } 23 | return ( 24 |
25 |

26 | Please confirm that you would like to set aside {allowance} SC for 27 | storage on the Sia network. 28 |

29 |
30 | 33 | 36 |
37 |
38 | ) 39 | } 40 | 41 | ConfirmationDialog.propTypes = { 42 | allowance: PropTypes.string.isRequired, 43 | onConfirmClick: PropTypes.func.isRequired, 44 | onCancelClick: PropTypes.func.isRequired 45 | } 46 | 47 | export default ConfirmationDialog 48 | -------------------------------------------------------------------------------- /plugins/Files/js/components/app.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import FileBrowser from '../containers/filebrowser.js' 4 | import AllowanceDialog from '../containers/allowancedialog.js' 5 | 6 | const FilesApp = ({ showAllowanceDialog }) => ( 7 |
8 | {showAllowanceDialog ? : null} 9 | 10 |
11 | ) 12 | 13 | FilesApp.propTypes = { 14 | showAllowanceDialog: PropTypes.bool.isRequired 15 | } 16 | 17 | export default FilesApp 18 | -------------------------------------------------------------------------------- /plugins/Files/js/components/contractorstatus.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const ContractorStatus = ({ settingAllowance, contractCount }) => ( 5 |
6 | {settingAllowance ? ( 7 |
8 | 9 | Forming Contracts... 10 |
11 | ) : ( 12 | {contractCount} contracts 13 | )} 14 |
15 | ) 16 | 17 | ContractorStatus.propTypes = { 18 | settingAllowance: PropTypes.bool.isRequired, 19 | contractCount: PropTypes.number.isRequired 20 | } 21 | 22 | export default ContractorStatus 23 | -------------------------------------------------------------------------------- /plugins/Files/js/components/deletedialog.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { List } from 'immutable' 4 | 5 | const DeleteDialog = ({ files, actions }) => { 6 | const onYesClick = () => { 7 | files.map(actions.deleteFile) 8 | actions.hideDeleteDialog() 9 | } 10 | const onNoClick = () => actions.hideDeleteDialog() 11 | return ( 12 |
13 |
14 |

Confirm Deletion

15 |
16 | Are you sure you want to delete {files.size} 17 | {files.size === 1 ? ' file' : ' files'} 18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 | ) 26 | } 27 | 28 | DeleteDialog.propTypes = { 29 | files: PropTypes.instanceOf(List).isRequired 30 | } 31 | 32 | export default DeleteDialog 33 | -------------------------------------------------------------------------------- /plugins/Files/js/components/directoryinfobar.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const colorBackDisabled = '#C5C5C5' 5 | const colorBackEnabled = '#00CBA0' 6 | 7 | const DirectoryInfoBar = ({ 8 | path, 9 | nfiles, 10 | onBackClick, 11 | setDragFolderTarget 12 | }) => { 13 | const backButtonStyle = { 14 | color: (() => { 15 | if (path === '') { 16 | return colorBackDisabled 17 | } 18 | return colorBackEnabled 19 | })() 20 | } 21 | // handle file drag onto the info bar: move the file into the parent 22 | // directory 23 | const handleDragOver = () => { 24 | setDragFolderTarget('../') 25 | } 26 | return ( 27 |
  • 28 |
    33 | 34 | Back 35 |
    36 |
    37 | {path} 38 | 39 | {nfiles} {nfiles === 1 ? 'file' : 'files'} 40 | 41 |
    42 |
  • 43 | ) 44 | } 45 | 46 | DirectoryInfoBar.propTypes = { 47 | path: PropTypes.string.isRequired, 48 | nfiles: PropTypes.number.isRequired, 49 | onBackClick: PropTypes.func.isRequired, 50 | setDragFolderTarget: PropTypes.func.isRequired 51 | } 52 | 53 | export default DirectoryInfoBar 54 | -------------------------------------------------------------------------------- /plugins/Files/js/components/downloadlist.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import TransferList from './transferlist.js' 4 | import { List } from 'immutable' 5 | 6 | const DownloadList = ({ downloads, onDownloadClick, onClearClick }) => ( 7 |
    8 |

    Downloads

    9 | 10 | 13 |
    14 | ) 15 | 16 | DownloadList.propTypes = { 17 | downloads: PropTypes.instanceOf(List).isRequired, 18 | onDownloadClick: PropTypes.func, 19 | onClearClick: PropTypes.func 20 | } 21 | 22 | export default DownloadList 23 | -------------------------------------------------------------------------------- /plugins/Files/js/components/dragoverlay.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const DragOverlay = () => ( 4 |
    5 | 6 | 7 |

    Drag to Upload

    8 |
    9 |
    10 | ) 11 | 12 | export default DragOverlay 13 | -------------------------------------------------------------------------------- /plugins/Files/js/components/filecontrols.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { Set } from 'immutable' 4 | import Path from 'path' 5 | 6 | const FileControls = ({ files, actions }) => { 7 | const onDownloadClick = () => { 8 | const downloadpath = SiaAPI.openFile({ 9 | title: 'Where should we download?', 10 | properties: ['openDirectory', 'createDirectories'] 11 | }) 12 | if (downloadpath.length === 0) { 13 | // No files selected, nop 14 | return 15 | } 16 | files.forEach(async file => { 17 | actions.downloadFile( 18 | file, 19 | Path.join(downloadpath[0], Path.basename(file.siapath)) 20 | ) 21 | await new Promise(resolve => setTimeout(resolve, 300)) 22 | }) 23 | } 24 | const onDeleteClick = () => { 25 | actions.showDeleteDialog(files.toList()) 26 | } 27 | const onRenameClick = () => { 28 | actions.showRenameDialog(files.first()) 29 | } 30 | return ( 31 |
    32 | {files.size} {files.size === 1 ? ' item' : ' items'} selected 33 |
    34 | 35 |
    36 | {files.size === 1 ? ( 37 |
    38 | 39 |
    40 | ) : null} 41 |
    42 | 43 |
    44 |
    45 | ) 46 | } 47 | 48 | FileControls.propTypes = { 49 | files: PropTypes.instanceOf(Set).isRequired 50 | } 51 | 52 | export default FileControls 53 | -------------------------------------------------------------------------------- /plugins/Files/js/components/filetransfers.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { List } from 'immutable' 4 | import UploadList from './uploadlist.js' 5 | import DownloadList from './downloadlist.js' 6 | import { shell } from 'electron' 7 | 8 | const FileTransfers = ({ uploads, downloads, actions }) => { 9 | const onCloseClick = () => actions.hideFileTransfers() 10 | const onDownloadClick = download => () => 11 | shell.showItemInFolder(download.destination) 12 | const onDownloadsClearClick = () => { 13 | actions.clearDownloads() 14 | actions.getDownloads() 15 | } 16 | return ( 17 |
    18 |
    19 | 20 |
    21 | {downloads.size === 0 && uploads.size === 0 ? ( 22 |

    No file transfers in progress.

    23 | ) : null} 24 | {downloads.size > 0 ? ( 25 | 30 | ) : null} 31 | {uploads.size > 0 ? : null} 32 |
    33 | ) 34 | } 35 | 36 | FileTransfers.propTypes = { 37 | uploads: PropTypes.instanceOf(List).isRequired, 38 | downloads: PropTypes.instanceOf(List).isRequired 39 | } 40 | 41 | export default FileTransfers 42 | -------------------------------------------------------------------------------- /plugins/Files/js/components/progressbar.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const ProgressBar = ({ progress }) => { 5 | const style = { 6 | width: progress.toString() + '%', 7 | height: '100%', 8 | transition: 'width 200ms', 9 | backgroundColor: '#46CF87' 10 | } 11 | return ( 12 |
    13 |
    14 |
    15 | ) 16 | } 17 | 18 | ProgressBar.propTypes = { 19 | progress: PropTypes.number.isRequired 20 | } 21 | 22 | export default ProgressBar 23 | -------------------------------------------------------------------------------- /plugins/Files/js/components/redundancystatus.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const colorNotAvailable = '#FF8080' 5 | const colorGoodRedundancy = '#20EE82' 6 | const colorNegativeRedundancy = '#b7afaf' 7 | 8 | const RedundancyStatus = ({ available, redundancy, uploadprogress }) => { 9 | const indicatorStyle = { 10 | opacity: (() => { 11 | if (!available || redundancy < 1.0) { 12 | return 1 13 | } 14 | if (uploadprogress > 100) { 15 | return 1 16 | } 17 | return uploadprogress / 100 18 | })(), 19 | color: (() => { 20 | if (redundancy < 0) { 21 | return colorNegativeRedundancy 22 | } 23 | if (!available || redundancy < 1.0) { 24 | return colorNotAvailable 25 | } 26 | return colorGoodRedundancy 27 | })() 28 | } 29 | return ( 30 |
    31 | 32 | 33 | {redundancy > 0 ? redundancy + 'x' : '--'} 34 | 35 |
    36 | ) 37 | } 38 | 39 | RedundancyStatus.propTypes = { 40 | available: PropTypes.bool.isRequired, 41 | redundancy: PropTypes.number.isRequired, 42 | uploadprogress: PropTypes.number.isRequired 43 | } 44 | 45 | export default RedundancyStatus 46 | -------------------------------------------------------------------------------- /plugins/Files/js/components/renamedialog.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import Path from 'path' 4 | 5 | const RenameDialog = ({ file, actions }) => { 6 | const onYesClick = e => { 7 | e.preventDefault() 8 | actions.renameFile( 9 | file, 10 | Path.posix.join(Path.posix.dirname(file.siapath), e.target.newname.value) 11 | ) 12 | } 13 | const onNoClick = () => actions.hideRenameDialog() 14 | return ( 15 |
    16 |
    17 |
    18 | Enter a new name for {Path.basename(file.siapath)}: 19 |
    20 |
    21 |
    22 | 29 |
    30 |
    31 | 32 | 35 |
    36 |
    37 |
    38 |
    39 | ) 40 | } 41 | 42 | RenameDialog.propTypes = { 43 | file: PropTypes.object.isRequired 44 | } 45 | 46 | export default RenameDialog 47 | -------------------------------------------------------------------------------- /plugins/Files/js/components/searchbutton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const SearchButton = ({ path, actions }) => { 4 | const handleClick = () => { 5 | actions.toggleSearchField() 6 | actions.setSearchText('', path) 7 | } 8 | return ( 9 |
    10 | 11 | Search Files 12 |
    13 | ) 14 | } 15 | 16 | export default SearchButton 17 | -------------------------------------------------------------------------------- /plugins/Files/js/components/searchfield.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const SearchField = ({ searchText, path, actions }) => { 5 | const onSearchChange = e => actions.setSearchText(e.target.value, path) 6 | return ( 7 |
    8 | 9 | 10 |
    11 | ) 12 | } 13 | 14 | SearchField.propTypes = { 15 | searchText: PropTypes.string.isRequired, 16 | path: PropTypes.string.isRequired 17 | } 18 | export default SearchField 19 | -------------------------------------------------------------------------------- /plugins/Files/js/components/setallowancebutton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const SetAllowanceButton = ({ actions }) => { 4 | const handleClick = () => actions.showAllowanceDialog() 5 | return ( 6 |
    7 | 8 | Create Allowance 9 |
    10 | ) 11 | } 12 | 13 | export default SetAllowanceButton 14 | -------------------------------------------------------------------------------- /plugins/Files/js/components/transfer.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import ProgressBar from './progressbar.js' 4 | 5 | const Transfer = ({ name, progress, status, speed, onClick }) => { 6 | const statusText = status === 'Downloading' ? status + ' - ' + speed : status 7 | return ( 8 |
  • 9 |
    10 |
    {name}
    11 | 12 | {statusText} 13 |
    14 |
  • 15 | ) 16 | } 17 | 18 | Transfer.propTypes = { 19 | name: PropTypes.string.isRequired, 20 | progress: PropTypes.number.isRequired, 21 | status: PropTypes.string.isRequired, 22 | onClick: PropTypes.func.isRequired 23 | } 24 | 25 | export default Transfer 26 | -------------------------------------------------------------------------------- /plugins/Files/js/components/transferlist.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { List } from 'immutable' 4 | import Transfer from './transfer.js' 5 | 6 | const defaultTransferClick = () => () => {} 7 | 8 | const TransferList = ({ 9 | transfers, 10 | onTransferClick = defaultTransferClick 11 | }) => { 12 | const transferComponents = transfers.map((transfer, key) => ( 13 | 21 | )) 22 | return
      {transferComponents}
    23 | } 24 | 25 | TransferList.propTypes = { 26 | transfers: PropTypes.instanceOf(List).isRequired, 27 | onTransferClick: PropTypes.func 28 | } 29 | 30 | export default TransferList 31 | -------------------------------------------------------------------------------- /plugins/Files/js/components/transfersbutton.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const FileTransfersButton = ({ unread, actions }) => { 5 | const onTransfersClick = () => actions.toggleFileTransfers() 6 | return ( 7 |
    8 | 9 | {unread > 0 ? ( 10 | 11 | {unread > 10 ? '10+' : unread} 12 | 13 | ) : null} 14 | File Transfers 15 |
    16 | ) 17 | } 18 | 19 | FileTransfersButton.propTypes = { 20 | unread: PropTypes.number.isRequired 21 | } 22 | 23 | export default FileTransfersButton 24 | -------------------------------------------------------------------------------- /plugins/Files/js/components/unlockwarning.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const UnlockWarning = ({ onClick }) => ( 5 |
    6 |

    7 | Your wallet must be unlocked and synchronized to buy storage. 8 |

    9 |
    10 | 13 |
    14 |
    15 | ) 16 | 17 | UnlockWarning.propTypes = { 18 | onClick: PropTypes.func.isRequired 19 | } 20 | 21 | export default UnlockWarning 22 | -------------------------------------------------------------------------------- /plugins/Files/js/components/uploadbutton.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const minimumContracts = 14 5 | 6 | const UploadButton = ({ contracts = minimumContracts, actions }) => { 7 | const onUploadClick = type => () => { 8 | if (contracts < minimumContracts) { 9 | SiaAPI.showError({ 10 | title: 'Sia-UI files error', 11 | content: 12 | 'Not enough contracts to upload. You must buy storage before uploading, or wait for contracts to form.' 13 | }) 14 | return 15 | } 16 | let dialogProperties 17 | if (type === 'folder') { 18 | dialogProperties = ['openDirectory'] 19 | } else if (type === 'file') { 20 | dialogProperties = ['openFile', 'multiSelections'] 21 | } 22 | const filepaths = SiaAPI.openFile({ 23 | title: 'Choose a ' + type + ' to upload', 24 | properties: dialogProperties 25 | }) 26 | if (filepaths) { 27 | actions.showUploadDialog(filepaths) 28 | } 29 | } 30 | return ( 31 |
    32 |
    33 | 34 | Upload Files 35 |
    36 |
    37 | 38 | Upload Folder 39 |
    40 |
    41 | ) 42 | } 43 | 44 | UploadButton.propTypes = { 45 | contracts: PropTypes.number 46 | } 47 | 48 | export default UploadButton 49 | -------------------------------------------------------------------------------- /plugins/Files/js/components/uploaddialog.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import fs from 'graceful-fs' 4 | 5 | const UploadDialog = ({ source, path, actions }) => { 6 | const onUploadClick = () => { 7 | source.forEach(file => { 8 | if (fs.statSync(file).isDirectory()) { 9 | actions.uploadFolder(path, file) 10 | } else { 11 | actions.uploadFile(path, file) 12 | } 13 | }) 14 | actions.hideUploadDialog() 15 | } 16 | const onCancelClick = () => actions.hideUploadDialog() 17 | return ( 18 |
    19 |
    20 |

    Confirm Upload

    21 |
    22 | Would you like to upload {source.length}{' '} 23 | {source.length === 1 ? 'item' : 'items'}? 24 |
    25 |
    26 | 27 | 28 |
    29 |
    30 |
    31 | ) 32 | } 33 | 34 | UploadDialog.propTypes = { 35 | source: PropTypes.array.isRequired, 36 | path: PropTypes.string.isRequired 37 | } 38 | 39 | export default UploadDialog 40 | -------------------------------------------------------------------------------- /plugins/Files/js/components/uploadlist.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import TransferList from './transferlist.js' 4 | import { List } from 'immutable' 5 | 6 | const UploadList = ({ uploads, onUploadClick }) => ( 7 |
    8 |

    Uploads

    9 | 10 |
    11 | ) 12 | 13 | UploadList.propTypes = { 14 | uploads: PropTypes.instanceOf(List).isRequired, 15 | onUploadClick: PropTypes.func 16 | } 17 | 18 | export default UploadList 19 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/addfolderbutton.js: -------------------------------------------------------------------------------- 1 | import AddFolderButtonView from '../components/addfolderbutton.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { showAddFolderDialog } from '../actions/files.js' 5 | 6 | const mapStateToProps = () => ({}) 7 | const mapDispatchToProps = dispatch => ({ 8 | actions: bindActionCreators({ showAddFolderDialog }, dispatch) 9 | }) 10 | 11 | const AddFolderButton = connect(mapStateToProps, mapDispatchToProps)( 12 | AddFolderButtonView 13 | ) 14 | export default AddFolderButton 15 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/addfolderdialog.js: -------------------------------------------------------------------------------- 1 | import AddFolderDialogView from '../components/addfolderdialog.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { hideAddFolderDialog, addFolder } from '../actions/files.js' 5 | 6 | const mapStateToProps = () => ({}) 7 | const mapDispatchToProps = dispatch => ({ 8 | actions: bindActionCreators({ hideAddFolderDialog, addFolder }, dispatch) 9 | }) 10 | 11 | const AddFolderDialog = connect(mapStateToProps, mapDispatchToProps)( 12 | AddFolderDialogView 13 | ) 14 | export default AddFolderDialog 15 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/allowancedialog.js: -------------------------------------------------------------------------------- 1 | import AllowanceDialogView from '../components/allowancedialog.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { 5 | showAllowanceConfirmation, 6 | hideAllowanceConfirmation, 7 | closeAllowanceDialog, 8 | setAllowance, 9 | setFeeEstimate, 10 | getStorageEstimate 11 | } from '../actions/files.js' 12 | 13 | const mapStateToProps = state => ({ 14 | unlocked: state.wallet.get('unlocked'), 15 | synced: state.wallet.get('synced'), 16 | storageEstimate: state.allowancedialog.get('storageEstimate'), 17 | feeEstimate: state.allowancedialog.get('feeEstimate'), 18 | confirmationAllowance: state.allowancedialog.get('confirmationAllowance'), 19 | confirming: state.allowancedialog.get('confirming') 20 | }) 21 | const mapDispatchToProps = dispatch => ({ 22 | actions: bindActionCreators( 23 | { 24 | getStorageEstimate, 25 | setFeeEstimate, 26 | showAllowanceConfirmation, 27 | setAllowance, 28 | hideAllowanceConfirmation, 29 | closeAllowanceDialog 30 | }, 31 | dispatch 32 | ) 33 | }) 34 | 35 | const AllowanceDialog = connect(mapStateToProps, mapDispatchToProps)( 36 | AllowanceDialogView 37 | ) 38 | export default AllowanceDialog 39 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/app.js: -------------------------------------------------------------------------------- 1 | import AppView from '../components/app.js' 2 | import { connect } from 'react-redux' 3 | import { hot } from 'react-hot-loader' 4 | 5 | const mapStateToProps = state => ({ 6 | showAllowanceDialog: state.files.get('showAllowanceDialog') 7 | }) 8 | 9 | const App = connect(mapStateToProps)(AppView) 10 | export default hot(module)(App) 11 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/contractorstatus.js: -------------------------------------------------------------------------------- 1 | import ContractorStatusView from '../components/contractorstatus.js' 2 | import { connect } from 'react-redux' 3 | 4 | const mapStateToProps = state => ({ 5 | settingAllowance: state.files.get('settingAllowance'), 6 | contractCount: state.files.get('contractCount') 7 | }) 8 | 9 | const ContractorStatus = connect(mapStateToProps)(ContractorStatusView) 10 | export default ContractorStatus 11 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/deletedialog.js: -------------------------------------------------------------------------------- 1 | import DeleteDialogView from '../components/deletedialog.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { hideDeleteDialog, deleteFile } from '../actions/files.js' 5 | 6 | const mapStateToProps = state => ({ 7 | files: state.deletedialog.get('files') 8 | }) 9 | const mapDispatchToProps = dispatch => ({ 10 | actions: bindActionCreators({ hideDeleteDialog, deleteFile }, dispatch) 11 | }) 12 | 13 | const DeleteDialog = connect(mapStateToProps, mapDispatchToProps)( 14 | DeleteDialogView 15 | ) 16 | export default DeleteDialog 17 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/filebrowser.js: -------------------------------------------------------------------------------- 1 | import FileBrowserView from '../components/filebrowser.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { 5 | setDragging, 6 | deselectAll, 7 | setNotDragging, 8 | showUploadDialog 9 | } from '../actions/files.js' 10 | 11 | const mapStateToProps = state => ({ 12 | dragging: state.files.get('dragging'), 13 | settingAllowance: state.files.get('settingAllowance'), 14 | showRenameDialog: state.files.get('showRenameDialog'), 15 | showUploadDialog: state.files.get('showUploadDialog'), 16 | showFileTransfers: state.files.get('showFileTransfers'), 17 | showDeleteDialog: state.files.get('showDeleteDialog'), 18 | showAddFolderDialog: state.files.get('showAddFolderDialog'), 19 | dragUploadEnabled: state.files.get('dragUploadEnabled') 20 | }) 21 | 22 | const mapDispatchToProps = dispatch => ({ 23 | actions: bindActionCreators( 24 | { setDragging, deselectAll, setNotDragging, showUploadDialog }, 25 | dispatch 26 | ) 27 | }) 28 | 29 | const FileBrowser = connect(mapStateToProps, mapDispatchToProps)( 30 | FileBrowserView 31 | ) 32 | export default FileBrowser 33 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/filecontrols.js: -------------------------------------------------------------------------------- 1 | import FileControlsView from '../components/filecontrols.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { 5 | downloadFile, 6 | showRenameDialog, 7 | showDeleteDialog 8 | } from '../actions/files.js' 9 | 10 | const mapStateToProps = state => ({ 11 | files: state.files.get('selected') 12 | }) 13 | const mapDispatchToProps = dispatch => ({ 14 | actions: bindActionCreators( 15 | { downloadFile, showRenameDialog, showDeleteDialog }, 16 | dispatch 17 | ) 18 | }) 19 | 20 | const FileControls = connect(mapStateToProps, mapDispatchToProps)( 21 | FileControlsView 22 | ) 23 | export default FileControls 24 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/filelist.js: -------------------------------------------------------------------------------- 1 | import FileListView from '../components/filelist.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { 5 | renameSiaUIFolder, 6 | deleteSiaUIFolder, 7 | renameFile, 8 | getFiles, 9 | setDragFolderTarget, 10 | setDragFileOrigin, 11 | setDragUploadEnabled, 12 | setPath, 13 | selectUpTo, 14 | deselectFile, 15 | deselectAll, 16 | selectFile, 17 | downloadFile, 18 | showDeleteDialog, 19 | showRenameDialog 20 | } from '../actions/files.js' 21 | 22 | const mapStateToProps = state => ({ 23 | files: state.files.get('workingDirectoryFiles'), 24 | selected: state.files.get('selected'), 25 | searchResults: state.files.get('searchResults'), 26 | path: state.files.get('path'), 27 | showSearchField: state.files.get('showSearchField'), 28 | dragFolderTarget: state.files.get('dragFolderTarget'), 29 | dragFileOrigin: state.files.get('dragFileOrigin') 30 | }) 31 | const mapDispatchToProps = dispatch => ({ 32 | actions: bindActionCreators( 33 | { 34 | renameSiaUIFolder, 35 | deleteSiaUIFolder, 36 | getFiles, 37 | renameFile, 38 | setDragFileOrigin, 39 | setDragFolderTarget, 40 | setDragUploadEnabled, 41 | selectUpTo, 42 | setPath, 43 | deselectFile, 44 | deselectAll, 45 | selectFile, 46 | showRenameDialog, 47 | downloadFile, 48 | showDeleteDialog 49 | }, 50 | dispatch 51 | ) 52 | }) 53 | 54 | const FileList = connect(mapStateToProps, mapDispatchToProps)(FileListView) 55 | export default FileList 56 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/filetransfers.js: -------------------------------------------------------------------------------- 1 | import FileTransfersView from '../components/filetransfers.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { 5 | getDownloads, 6 | clearDownloads, 7 | hideFileTransfers 8 | } from '../actions/files.js' 9 | 10 | const mapStateToProps = state => ({ 11 | uploads: state.files.get('uploading'), 12 | downloads: state.files.get('downloading') 13 | }) 14 | const mapDispatchToProps = dispatch => ({ 15 | actions: bindActionCreators( 16 | { getDownloads, clearDownloads, hideFileTransfers }, 17 | dispatch 18 | ) 19 | }) 20 | 21 | const FileTransfers = connect(mapStateToProps, mapDispatchToProps)( 22 | FileTransfersView 23 | ) 24 | export default FileTransfers 25 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/renamedialog.js: -------------------------------------------------------------------------------- 1 | import RenameDialogView from '../components/renamedialog.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { hideRenameDialog, renameFile } from '../actions/files.js' 5 | 6 | const mapStateToProps = state => ({ 7 | file: state.renamedialog.get('file') 8 | }) 9 | const mapDispatchToProps = dispatch => ({ 10 | actions: bindActionCreators({ hideRenameDialog, renameFile }, dispatch) 11 | }) 12 | 13 | const RenameDialog = connect(mapStateToProps, mapDispatchToProps)( 14 | RenameDialogView 15 | ) 16 | export default RenameDialog 17 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/searchbutton.js: -------------------------------------------------------------------------------- 1 | import SearchButtonView from '../components/searchbutton.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { toggleSearchField, setSearchText } from '../actions/files.js' 5 | 6 | const mapStateToProps = state => ({ 7 | path: state.files.get('path') 8 | }) 9 | const mapDispatchToProps = dispatch => ({ 10 | actions: bindActionCreators({ toggleSearchField, setSearchText }, dispatch) 11 | }) 12 | 13 | const SearchButton = connect(mapStateToProps, mapDispatchToProps)( 14 | SearchButtonView 15 | ) 16 | export default SearchButton 17 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/searchfield.js: -------------------------------------------------------------------------------- 1 | import SearchFieldView from '../components/searchfield.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { setSearchText } from '../actions/files.js' 5 | 6 | const mapStateToProps = state => ({ 7 | searchText: state.files.get('searchText'), 8 | path: state.files.get('path') 9 | }) 10 | const mapDispatchToProps = dispatch => ({ 11 | actions: bindActionCreators({ setSearchText }, dispatch) 12 | }) 13 | 14 | const SearchField = connect(mapStateToProps, mapDispatchToProps)( 15 | SearchFieldView 16 | ) 17 | export default SearchField 18 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/setallowancebutton.js: -------------------------------------------------------------------------------- 1 | import SetAllowanceButtonView from '../components/setallowancebutton.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { showAllowanceDialog } from '../actions/files.js' 5 | 6 | const mapStateToProps = () => ({}) 7 | const mapDispatchToProps = dispatch => ({ 8 | actions: bindActionCreators({ showAllowanceDialog }, dispatch) 9 | }) 10 | 11 | const SetAllowanceButton = connect(mapStateToProps, mapDispatchToProps)( 12 | SetAllowanceButtonView 13 | ) 14 | export default SetAllowanceButton 15 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/transfersbutton.js: -------------------------------------------------------------------------------- 1 | import TransfersButtonView from '../components/transfersbutton.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { toggleFileTransfers } from '../actions/files.js' 5 | 6 | const mapStateToProps = state => ({ 7 | unread: 8 | state.files.get('unreadUploads').size + 9 | state.files.get('unreadDownloads').size 10 | }) 11 | const mapDispatchToProps = dispatch => ({ 12 | actions: bindActionCreators({ toggleFileTransfers }, dispatch) 13 | }) 14 | 15 | const TransfersButton = connect(mapStateToProps, mapDispatchToProps)( 16 | TransfersButtonView 17 | ) 18 | export default TransfersButton 19 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/uploadbutton.js: -------------------------------------------------------------------------------- 1 | import UploadButtonView from '../components/uploadbutton.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { showUploadDialog } from '../actions/files.js' 5 | 6 | const mapStateToProps = state => ({ 7 | contracts: state.files.get('contractCount') 8 | }) 9 | const mapDispatchToProps = dispatch => ({ 10 | actions: bindActionCreators({ showUploadDialog }, dispatch) 11 | }) 12 | 13 | const UploadButton = connect(mapStateToProps, mapDispatchToProps)( 14 | UploadButtonView 15 | ) 16 | export default UploadButton 17 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/uploaddialog.js: -------------------------------------------------------------------------------- 1 | import UploadDialogView from '../components/uploaddialog.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { hideUploadDialog, uploadFile, uploadFolder } from '../actions/files.js' 5 | 6 | const mapStateToProps = state => ({ 7 | source: state.files.get('uploadSource'), 8 | path: state.files.get('path') 9 | }) 10 | const mapDispatchToProps = dispatch => ({ 11 | actions: bindActionCreators( 12 | { hideUploadDialog, uploadFile, uploadFolder }, 13 | dispatch 14 | ) 15 | }) 16 | 17 | const UploadDialog = connect(mapStateToProps, mapDispatchToProps)( 18 | UploadDialogView 19 | ) 20 | export default UploadDialog 21 | -------------------------------------------------------------------------------- /plugins/Files/js/containers/usagestats.js: -------------------------------------------------------------------------------- 1 | import UsageStatsView from '../components/usagestats.js' 2 | import { connect } from 'react-redux' 3 | 4 | const mapStateToProps = state => ({ 5 | allowance: state.files.get('allowance'), 6 | downloadspending: state.files.get('downloadspending'), 7 | uploadspending: state.files.get('uploadspending'), 8 | storagespending: state.files.get('storagespending'), 9 | contractspending: state.files.get('contractspending'), 10 | unspent: state.files.get('unspent'), 11 | renewheight: state.files.get('renewheight') 12 | }) 13 | 14 | const UsageStats = connect(mapStateToProps)(UsageStatsView) 15 | export default UsageStats 16 | -------------------------------------------------------------------------------- /plugins/Files/js/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import createSagaMiddleware from 'redux-saga' 4 | import { createStore, applyMiddleware } from 'redux' 5 | import { Provider } from 'react-redux' 6 | import rootReducer from './reducers/index.js' 7 | import rootSaga from './sagas/index.js' 8 | import App from './containers/app.js' 9 | import { fetchData } from './actions/files.js' 10 | 11 | // If dev enable window reload 12 | if (process.env.NODE_ENV === 'development') { 13 | require('electron-css-reload')() 14 | } 15 | 16 | const sagaMiddleware = createSagaMiddleware() 17 | const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)) 18 | sagaMiddleware.run(rootSaga) 19 | 20 | const rootElement = ( 21 | 22 | 23 | 24 | ) 25 | ReactDOM.render(rootElement, document.getElementById('react-root')) 26 | 27 | // update state when plugin is focused 28 | window.onfocus = () => { 29 | store.dispatch(fetchData()) 30 | } 31 | -------------------------------------------------------------------------------- /plugins/Files/js/reducers/allowancedialog.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable' 2 | import * as constants from '../constants/files.js' 3 | 4 | const initialState = Map({ 5 | storageEstimate: '0 B', 6 | feeEstimate: 0, 7 | confirming: false, 8 | confirmationAllowance: '0' 9 | }) 10 | 11 | export default function allowancedialogReduceR (state = initialState, action) { 12 | switch (action.type) { 13 | case constants.SHOW_ALLOWANCE_CONFIRMATION: 14 | return state 15 | .set('confirming', true) 16 | .set('confirmationAllowance', action.allowance) 17 | case constants.HIDE_ALLOWANCE_CONFIRMATION: 18 | return state.set('confirming', false) 19 | case constants.CLOSE_ALLOWANCE_DIALOG: 20 | return state.set('confirming', false) 21 | case constants.SET_FEE_ESTIMATE: 22 | return state.set('feeEstimate', action.estimate) 23 | case constants.SET_STORAGE_ESTIMATE: 24 | return state.set('storageEstimate', action.estimate) 25 | default: 26 | return state 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugins/Files/js/reducers/deletedialog.js: -------------------------------------------------------------------------------- 1 | import { Map, List } from 'immutable' 2 | import * as constants from '../constants/files.js' 3 | 4 | const initialState = Map({ 5 | files: List() 6 | }) 7 | 8 | export default function deletedialogReducer (state = initialState, action) { 9 | switch (action.type) { 10 | case constants.SHOW_DELETE_DIALOG: 11 | return state.set('files', action.files) 12 | default: 13 | return state 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /plugins/Files/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import wallet from './wallet.js' 3 | import files from './files.js' 4 | import deletedialog from './deletedialog.js' 5 | import renamedialog from './renamedialog.js' 6 | import allowancedialog from './allowancedialog.js' 7 | 8 | const rootReducer = combineReducers({ 9 | wallet, 10 | files, 11 | deletedialog, 12 | renamedialog, 13 | allowancedialog 14 | }) 15 | 16 | export default rootReducer 17 | -------------------------------------------------------------------------------- /plugins/Files/js/reducers/renamedialog.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable' 2 | import * as constants from '../constants/files.js' 3 | 4 | const initialState = Map({ 5 | file: {} 6 | }) 7 | 8 | export default function renamedialogReducer (state = initialState, action) { 9 | switch (action.type) { 10 | case constants.SHOW_RENAME_DIALOG: 11 | return state.set('file', action.file) 12 | default: 13 | return state 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /plugins/Files/js/reducers/wallet.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable' 2 | import * as constants from '../constants/files.js' 3 | 4 | const initialState = Map({ 5 | unlocked: false, 6 | synced: false, 7 | balance: '' 8 | }) 9 | 10 | export default function walletReducer (state = initialState, action) { 11 | switch (action.type) { 12 | case constants.RECEIVE_WALLET_LOCKSTATE: 13 | return state.set('unlocked', action.unlocked) 14 | case constants.RECEIVE_WALLET_BALANCE: 15 | return state.set('balance', action.balance) 16 | case constants.SET_WALLET_SYNCSTATE: 17 | return state.set('synced', action.synced) 18 | default: 19 | return state 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /plugins/Files/js/sagas/index.js: -------------------------------------------------------------------------------- 1 | import * as sagas from './files.js' 2 | import { fork } from 'redux-saga/effects' 3 | 4 | export default function * rootSaga () { 5 | const watchers = Object.values(sagas).map(fork) 6 | yield watchers 7 | } 8 | -------------------------------------------------------------------------------- /plugins/Hosting/assets/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/plugins/Hosting/assets/button.png -------------------------------------------------------------------------------- /plugins/Hosting/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hosting 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
    18 | 19 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /plugins/Hosting/js/components/announce.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const AnnounceDialogModal = ({ announceAddress, actions }) => { 4 | const handleSettingInput = e => 5 | actions.updateModal('announceAddress', e.target.value) 6 | const hideAnnounceDialog = address => actions.hideAnnounceDialog(address) 7 | const closeAnnounceDialog = () => hideAnnounceDialog('') 8 | const handleSubmit = () => { 9 | if (announceAddress !== '') { 10 | hideAnnounceDialog(announceAddress) 11 | } 12 | } 13 | 14 | const handleSettingKeyDown = e => { 15 | if (e.keyCode === 13) { 16 | handleSubmit() 17 | e.preventDefault() 18 | } 19 | } 20 | 21 | return ( 22 |
    28 |
    29 |
    30 | X 31 |
    32 | 33 |

    Announce Host

    34 |

    35 | 36 | 42 |

    43 | 44 | Click to announce your host to the network. This will incur a small 45 | transaction fee and only needs to be done once per host. 46 | 47 |

    48 | 56 |

    57 |
    58 |
    59 | ) 60 | } 61 | 62 | export default AnnounceDialogModal 63 | -------------------------------------------------------------------------------- /plugins/Hosting/js/components/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Header from '../containers/header.js' 3 | import Body from '../containers/body.js' 4 | import ResizeDialog from '../containers/resizeDialog.js' 5 | import AnnounceDialog from '../containers/announce.js' 6 | import WalletModal from '../containers/walletmodal.js' 7 | import { hot } from 'react-hot-loader' 8 | 9 | const HostingApp = () => ( 10 |
    11 |
    12 | 13 | 14 | 15 | 16 |
    17 | ) 18 | 19 | export default hot(module)(HostingApp) 20 | -------------------------------------------------------------------------------- /plugins/Hosting/js/components/body.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FilesList from '../containers/fileslist.js' 3 | import SettingsList from '../containers/settingslist.js' 4 | 5 | const Body = ({ actions }) => { 6 | const announceHost = () => actions.announceHost() 7 | 8 | return ( 9 |
    10 |
    11 |
    12 |
    Help
    13 |
    14 |
    15 | 16 | Announce 17 |
    18 |
    19 |
    20 |
    21 |
    22 | To start hosting: 23 |
      24 |
    1. Add a storage folder.
    2. 25 |
    3. 26 | Set your prefered price, bandwidth cost, collateral, and 27 | duration. 28 |
    4. 29 |
    5. Set 'Accepting Contracts' to 'Yes'
    6. 30 |
    7. 31 | Announce your host by clicking the above 'Announce' button. 32 |
    8. 33 |
    34 |
    35 |
    36 |
    37 | 38 | 39 | 40 |
    41 | ) 42 | } 43 | 44 | export default Body 45 | -------------------------------------------------------------------------------- /plugins/Hosting/js/components/header.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import BigNumber from 'bignumber.js' 3 | import WarningBar from './warningbar.js' 4 | import HostStatus from './hoststatus.js' 5 | 6 | const Header = ({ 7 | numContracts, 8 | earned, 9 | expected, 10 | walletsize, 11 | walletLocked, 12 | workingstatus, 13 | connectabilitystatus 14 | }) => ( 15 |
    16 |
    Hosting
    17 |
    18 |
    19 | 23 |
    24 |
    25 | {numContracts} active contracts 26 |
    27 |
    28 | {earned} SC earned 29 |
    30 |
    31 | {expected} SC expected 32 |
    33 |
    34 | {new BigNumber(walletsize).lessThan('2000') && !walletLocked ? ( 35 | 39 | ) : null} 40 |
    41 | ) 42 | 43 | export default Header 44 | -------------------------------------------------------------------------------- /plugins/Hosting/js/components/hoststatus.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const HostStatus = ({ connectabilitystatus, workingstatus }) => { 5 | if (connectabilitystatus === 'checking' && workingstatus === 'checking') { 6 | return ( 7 |
    8 | 9 | Checking Host Status... 10 |
    11 | Sia-UI is determining the status of your Host. 12 |
    13 |
    14 | ) 15 | } 16 | 17 | if ( 18 | connectabilitystatus === 'not connectable' && 19 | workingstatus === 'not working' 20 | ) { 21 | return ( 22 |
    23 | 24 | Host Unreachable 25 |
    26 | Your host is not connectable at the configured net address. Check your 27 | UPNP or NAT settings. 28 |
    29 |
    30 | ) 31 | } 32 | 33 | if ( 34 | connectabilitystatus === 'connectable' && 35 | workingstatus === 'not working' 36 | ) { 37 | return ( 38 |
    39 | 40 | Host Inactive 41 |
    42 | Your host is connectable, but it is not being used by any renters. 43 |
    44 |
    45 | ) 46 | } 47 | 48 | return ( 49 |
    50 | 51 | Host Online 52 |
    53 | Your host is connectable and is being contacted by renters. 54 |
    55 |
    56 | ) 57 | } 58 | 59 | HostStatus.propTypes = { 60 | connectabilitystatus: PropTypes.string.isRequired, 61 | workingstatus: PropTypes.string.isRequired 62 | } 63 | 64 | export default HostStatus 65 | -------------------------------------------------------------------------------- /plugins/Hosting/js/components/resizeDialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Map } from 'immutable' 3 | import Path from 'path' 4 | 5 | const ResizeDialogModal = ({ 6 | resizePath, 7 | resizeSize, 8 | initialSize, 9 | actions 10 | }) => { 11 | const handleSettingInput = e => 12 | actions.updateModal('resizeSize', e.target.value) 13 | const hideResizeDialog = newSize => 14 | actions.hideResizeDialog(Map({ path: resizePath, size: newSize })) 15 | const closeResizeDialog = () => hideResizeDialog(0) 16 | const handleSubmit = () => { 17 | if (resizeSize >= 35 && resizeSize !== initialSize.toString()) { 18 | hideResizeDialog(resizeSize) 19 | } 20 | } 21 | 22 | const handleSettingKeyDown = e => { 23 | if (e.keyCode === 13) { 24 | handleSubmit() 25 | e.preventDefault() 26 | } 27 | } 28 | 29 | return ( 30 |
    33 |
    34 |
    35 | X 36 |
    37 | 38 |

    Resize "{Path.basename(resizePath)}"

    39 |

    40 | 41 | 48 |

    49 | 50 | Storage folder must be at least 35 GB. 51 | 52 |

    53 | = 35 57 | ? '' 58 | : ' disabled') 59 | } 60 | type='button' 61 | value='Save' 62 | onClick={handleSubmit} 63 | /> 64 |

    65 |
    66 |
    67 | ) 68 | } 69 | 70 | export default ResizeDialogModal 71 | -------------------------------------------------------------------------------- /plugins/Hosting/js/components/walletmodal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const WalletUnlockModal = ({ walletLocked }) => ( 4 |
    7 |
    8 |

    You must unlock the wallet to host files.

    9 | 10 |
    11 |
    12 | ) 13 | 14 | export default WalletUnlockModal 15 | -------------------------------------------------------------------------------- /plugins/Hosting/js/components/warningbar.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const WarningBarModal = ({ title, message }) => ( 4 |
    5 |
    6 |

    {title}

    7 |

    {message}

    8 |
    9 |
    10 | ) 11 | 12 | export default WarningBarModal 13 | -------------------------------------------------------------------------------- /plugins/Hosting/js/components/warningmodal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const WarningModalModal = ({ title, message, actions }) => { 4 | const handleAccept = () => actions.acceptModal() 5 | const handleDecline = () => actions.declineModal() 6 | 7 | return ( 8 |
    9 |
    10 |
    11 | X 12 |
    13 |

    {title}

    14 |

    {message}

    15 |

    16 | 22 |
    23 | 29 |

    30 |
    31 |
    32 | ) 33 | } 34 | 35 | export default WarningModalModal 36 | -------------------------------------------------------------------------------- /plugins/Hosting/js/constants/constants.js: -------------------------------------------------------------------------------- 1 | export const UPDATE_MODAL = 'UPDATE_MODAL' 2 | export const PUSH_SETTINGS = 'PUSH_SETTINGS' 3 | export const UPDATE_SETTINGS = 'UPDATE_SETTINGS' 4 | export const UPDATE_DEFAULT_SETTINGS = 'UPDATE_DEFAULT_SETTINGS' 5 | export const FETCH_DATA = 'FETCH_DATA' 6 | export const FETCH_DATA_SUCCESS = 'FETCH_DATA_SUCCESS' 7 | export const TOGGLE_ACCEPTING = 'TOGGLE_ACCEPTING' 8 | export const SHOW_TOGGLE_ACCEPTING_MODAL = 'SHOW_TOGGLE_ACCEPTING_MODAL' 9 | export const HIDE_TOGGLE_ACCEPTING_MODAL = 'HIDE_TOGGLE_ACCEPTING_MODAL' 10 | export const RESET_HOST = 'RESET_HOST' 11 | export const ADD_FOLDER = 'ADD_FOLDER' 12 | export const ADD_FOLDER_ASK = 'ADD_FOLDER_ASK' 13 | export const REMOVE_FOLDER = 'REMOVE_FOLDER' 14 | export const UPDATE_FOLDER_TO_REMOVE = 'UPDATE_FOLDER_TO_REMOVE' 15 | export const RESIZE_FOLDER = 'RESIZE_FOLDER' 16 | export const SHOW_RESIZE_DIALOG = 'SHOW_RESIZE_DIALOG' 17 | export const HIDE_RESIZE_DIALOG = 'HIDE_RESIZE_DIALOG' 18 | export const ANNOUNCE_HOST = 'ANNOUNCE_HOST' 19 | export const SHOW_ANNOUNCE_DIALOG = 'SHOW_ANNOUNCE_DIALOG' 20 | export const HIDE_ANNOUNCE_DIALOG = 'HIDE_ANNOUNCE_DIALOG' 21 | export const REQUEST_DEFAULT_SETTINGS = 'REQUEST_DEFAULT_SETTINGS' 22 | export const RECEIVE_DEFAULT_SETTINGS = 'RECEIVE_DEFAULT_SETTINGS' 23 | export const GET_HOST_STATUS = 'GET_HOST_STATUS' 24 | export const SET_HOST_STATUS = 'SET_HOST_STATUS' 25 | export const SET_ESTIMATED_SCORE = 'SET_ESTIMATED_SCORE' 26 | -------------------------------------------------------------------------------- /plugins/Hosting/js/containers/announce.js: -------------------------------------------------------------------------------- 1 | import AnnounceDialogView from '../components/announce.js' 2 | import { connect } from 'react-redux' 3 | import { hideAnnounceDialog, updateModal } from '../actions/actions.js' 4 | import { bindActionCreators } from 'redux' 5 | 6 | const mapStateToProps = state => ({ 7 | shouldShowAnnounceDialog: state.modalReducer.get('shouldShowAnnounceDialog'), 8 | announceAddress: state.modalReducer.get('announceAddress') 9 | }) 10 | 11 | const mapDispatchToProps = dispatch => ({ 12 | actions: bindActionCreators({ hideAnnounceDialog, updateModal }, dispatch) 13 | }) 14 | 15 | const AnnounceDialog = connect(mapStateToProps, mapDispatchToProps)( 16 | AnnounceDialogView 17 | ) 18 | export default AnnounceDialog 19 | -------------------------------------------------------------------------------- /plugins/Hosting/js/containers/body.js: -------------------------------------------------------------------------------- 1 | import BodyView from '../components/body.js' 2 | import { connect } from 'react-redux' 3 | import { updateSettings, announceHost } from '../actions/actions.js' 4 | import { bindActionCreators } from 'redux' 5 | 6 | const mapDispatchToProps = dispatch => ({ 7 | actions: bindActionCreators({ updateSettings, announceHost }, dispatch) 8 | }) 9 | 10 | const mapStateToProps = state => ({ 11 | usersettings: state.hostingReducer.get('usersettings'), 12 | defaultsettings: state.hostingReducer.get('defaultsettings'), 13 | acceptingContracts: state.hostingReducer.get('acceptingContracts'), 14 | files: state.hostingReducer.get('files') 15 | }) 16 | 17 | const Body = connect(mapStateToProps, mapDispatchToProps)(BodyView) 18 | export default Body 19 | -------------------------------------------------------------------------------- /plugins/Hosting/js/containers/fileslist.js: -------------------------------------------------------------------------------- 1 | import FilesListView from '../components/fileslist.js' 2 | import { connect } from 'react-redux' 3 | import { 4 | addFolderAskPathSize, 5 | removeFolder, 6 | resizeFolder, 7 | updateFolderToRemove 8 | } from '../actions/actions.js' 9 | import { bindActionCreators } from 'redux' 10 | 11 | const mapDispatchToProps = dispatch => ({ 12 | actions: bindActionCreators( 13 | { addFolderAskPathSize, removeFolder, resizeFolder, updateFolderToRemove }, 14 | dispatch 15 | ) 16 | }) 17 | 18 | const mapStateToProps = state => ({ 19 | folders: state.hostingReducer.get('files'), 20 | folderPathToRemove: state.modalReducer.get('folderPathToRemove') 21 | }) 22 | 23 | const FilesList = connect(mapStateToProps, mapDispatchToProps)(FilesListView) 24 | export default FilesList 25 | -------------------------------------------------------------------------------- /plugins/Hosting/js/containers/header.js: -------------------------------------------------------------------------------- 1 | import HeaderView from '../components/header.js' 2 | import { connect } from 'react-redux' 3 | 4 | const mapStateToProps = state => ({ 5 | numContracts: state.hostingReducer.get('numContracts'), 6 | storage: state.hostingReducer.get('storage'), 7 | earned: state.hostingReducer.get('earned'), 8 | expected: state.hostingReducer.get('expected'), 9 | walletsize: state.hostingReducer.get('walletsize'), 10 | walletLocked: state.hostingReducer.get('walletLocked'), 11 | connectabilitystatus: state.hostingReducer.get('connectabilitystatus'), 12 | workingstatus: state.hostingReducer.get('workingstatus') 13 | }) 14 | 15 | const Header = connect(mapStateToProps)(HeaderView) 16 | export default Header 17 | -------------------------------------------------------------------------------- /plugins/Hosting/js/containers/resizeDialog.js: -------------------------------------------------------------------------------- 1 | import ResizeDialogView from '../components/resizeDialog.js' 2 | import { connect } from 'react-redux' 3 | import { hideResizeDialog, updateModal } from '../actions/actions.js' 4 | import { bindActionCreators } from 'redux' 5 | 6 | const mapStateToProps = state => ({ 7 | resizePath: state.modalReducer.get('resizePath'), 8 | resizeSize: state.modalReducer.get('resizeSize'), 9 | initialSize: state.modalReducer.get('initialSize') 10 | }) 11 | 12 | const mapDispatchToProps = dispatch => ({ 13 | actions: bindActionCreators({ hideResizeDialog, updateModal }, dispatch) 14 | }) 15 | 16 | const ResizeDialog = connect(mapStateToProps, mapDispatchToProps)( 17 | ResizeDialogView 18 | ) 19 | export default ResizeDialog 20 | -------------------------------------------------------------------------------- /plugins/Hosting/js/containers/settingslist.js: -------------------------------------------------------------------------------- 1 | import SettingsListView from '../components/settingslist.js' 2 | import { connect } from 'react-redux' 3 | import { 4 | showToggleAcceptingModal, 5 | hideToggleAcceptingModal, 6 | updateSettings, 7 | pushSettings 8 | } from '../actions/actions.js' 9 | import { bindActionCreators } from 'redux' 10 | import { Map } from 'immutable' 11 | 12 | const mapDispatchToProps = dispatch => ({ 13 | actions: bindActionCreators( 14 | { 15 | showToggleAcceptingModal, 16 | hideToggleAcceptingModal, 17 | updateSettings, 18 | pushSettings 19 | }, 20 | dispatch 21 | ) 22 | }) 23 | 24 | const mapStateToProps = state => ({ 25 | usersettings: Map({ 26 | maxduration: Map({ 27 | name: 'Max Duration (Weeks)', 28 | value: state.settingsReducer.get('maxduration'), 29 | min: 12 30 | }), 31 | collateral: Map({ 32 | name: 'Collateral per TB per Month (SC)', 33 | value: state.settingsReducer.get('collateral') 34 | }), 35 | storageprice: Map({ 36 | name: 'Price per TB per Month (SC)', 37 | value: state.settingsReducer.get('storageprice') 38 | }), 39 | downloadbandwidthprice: Map({ 40 | name: 'Bandwidth Price (SC/TB)', 41 | value: state.settingsReducer.get('downloadbandwidthprice') 42 | }) 43 | }), 44 | conversionRate: state.settingsReducer.get('conversionRate'), 45 | acceptingContracts: state.settingsReducer.get('acceptingContracts'), 46 | settingsChanged: state.settingsReducer.get('settingsChanged'), 47 | defaultsettings: state.settingsReducer.get('defaultsettings'), 48 | shouldShowToggleAcceptingModal: state.modalReducer.get( 49 | 'shouldShowToggleAcceptingModal' 50 | ) 51 | }) 52 | 53 | const SettingsList = connect(mapStateToProps, mapDispatchToProps)( 54 | SettingsListView 55 | ) 56 | export default SettingsList 57 | -------------------------------------------------------------------------------- /plugins/Hosting/js/containers/walletmodal.js: -------------------------------------------------------------------------------- 1 | import WalletUnlockView from '../components/walletmodal.js' 2 | import { connect } from 'react-redux' 3 | 4 | const mapStateToProps = state => ({ 5 | walletLocked: state.hostingReducer.get('walletLocked') 6 | }) 7 | 8 | const WalletUnlock = connect(mapStateToProps)(WalletUnlockView) 9 | export default WalletUnlock 10 | -------------------------------------------------------------------------------- /plugins/Hosting/js/index.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom' 2 | import { hostingPlugin } from './main.js' 3 | 4 | // If dev enable window reload 5 | if (process.env.NODE_ENV === 'development') { 6 | require('electron-css-reload')() 7 | } 8 | 9 | ReactDOM.render(hostingPlugin(), document.getElementById('react-root')) 10 | -------------------------------------------------------------------------------- /plugins/Hosting/js/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createStore, applyMiddleware } from 'redux' 3 | import createSagaMiddleware from 'redux-saga' 4 | import { Provider } from 'react-redux' 5 | import rootReducer from './reducers/index.js' 6 | import rootSaga from './sagas/saga.js' 7 | import HostingApp from './components/app.js' 8 | import * as actions from './actions/actions.js' 9 | 10 | export const hostingPlugin = () => { 11 | const sagaMiddleware = createSagaMiddleware() 12 | const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)) 13 | sagaMiddleware.run(rootSaga) 14 | 15 | store.dispatch(actions.requestDefaultSettings()) 16 | store.dispatch(actions.fetchData()) 17 | 18 | const updateState = () => { 19 | store.dispatch(actions.fetchData()) 20 | store.dispatch(actions.getHostStatus()) 21 | } 22 | 23 | // Poll Siad for state changes. 24 | setInterval(updateState, 20000) 25 | 26 | // update state immediately when this plugin is focused 27 | window.onfocus = updateState 28 | 29 | return ( 30 | 31 | 32 | 33 | ) 34 | } 35 | -------------------------------------------------------------------------------- /plugins/Hosting/js/reducers/hosting.js: -------------------------------------------------------------------------------- 1 | import { Map, List } from 'immutable' 2 | import * as constants from '../constants/constants.js' 3 | 4 | const initialState = Map({ 5 | numContracts: 0, 6 | storage: 0, 7 | earned: 0, 8 | expected: 0, 9 | walletLocked: true, 10 | walletsize: 0, 11 | files: List([]), 12 | 13 | workingstatus: 'checking', 14 | connectabilitystatus: 'checking' 15 | }) 16 | 17 | export default function hostingReducer (state = initialState, action) { 18 | switch (action.type) { 19 | case constants.SET_HOST_STATUS: 20 | return state 21 | .set('workingstatus', action.working) 22 | .set('connectabilitystatus', action.connectable) 23 | case constants.FETCH_DATA_SUCCESS: 24 | return state.merge(action.data) 25 | default: 26 | return state 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /plugins/Hosting/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import hostingReducer from './hosting.js' 3 | import settingsReducer from './setting.js' 4 | import modalReducer from './modal.js' 5 | 6 | const rootReducer = combineReducers({ 7 | hostingReducer, 8 | settingsReducer, 9 | modalReducer 10 | }) 11 | 12 | export default rootReducer 13 | -------------------------------------------------------------------------------- /plugins/Hosting/js/reducers/modal.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable' 2 | import * as constants from '../constants/constants.js' 3 | 4 | const initialState = Map({ 5 | resizeSize: 0, 6 | initialSize: 0, 7 | resizePath: '', 8 | folderPathToRemove: '', 9 | announceAddress: undefined, 10 | defaultAnnounceAddress: '', 11 | shouldShowToggleAcceptingModal: false 12 | }) 13 | 14 | export default function modalReducer (state = initialState, action) { 15 | switch (action.type) { 16 | case constants.UPDATE_MODAL: 17 | return state.set(action.key, action.value) 18 | 19 | case constants.SHOW_TOGGLE_ACCEPTING_MODAL: 20 | return state.set('shouldShowToggleAcceptingModal', true) 21 | 22 | case constants.HIDE_TOGGLE_ACCEPTING_MODAL: 23 | return state.set('shouldShowToggleAcceptingModal', false) 24 | 25 | case constants.SHOW_RESIZE_DIALOG: 26 | return state 27 | .set('resizePath', action.folder.get('path')) 28 | .set('resizeSize', action.folder.get('size')) 29 | .set( 30 | 'initialSize', 31 | action.ignoreInitial ? 0 : action.folder.get('size') 32 | ) 33 | 34 | case constants.HIDE_RESIZE_DIALOG: 35 | return state.set('resizePath', '') 36 | 37 | case constants.HIDE_ANNOUNCE_DIALOG: 38 | return state.set('announceAddress', undefined) 39 | 40 | case constants.SHOW_ANNOUNCE_DIALOG: 41 | return state.set( 42 | 'announceAddress', 43 | action.address || state.get('defaultAnnounceAddress') 44 | ) 45 | 46 | case constants.UPDATE_FOLDER_TO_REMOVE: 47 | return state.set('folderPathToRemove', action.folder || '') 48 | 49 | case constants.FETCH_DATA_SUCCESS: 50 | return state.merge(action.modals) 51 | 52 | default: 53 | return state 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /plugins/Hosting/js/reducers/setting.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable' 2 | import * as constants from '../constants/constants.js' 3 | 4 | const initialState = Map({ 5 | maxduration: 0, 6 | collateral: 0, 7 | conversionRate: 0, 8 | storageprice: 0, 9 | downloadbandwidthprice: 0, 10 | acceptingContracts: false, 11 | defaultsettings: Map(), 12 | settingsChanged: false 13 | }) 14 | 15 | export default function settingsReducer (state = initialState, action) { 16 | switch (action.type) { 17 | case constants.UPDATE_SETTINGS: 18 | return state.merge(action.settings).set('settingsChanged', true) 19 | case constants.PUSH_SETTINGS: 20 | return state 21 | .set('defaultsettings', action.settings) 22 | .set('settingsChanged', false) 23 | case constants.RECEIVE_DEFAULT_SETTINGS: 24 | return state 25 | .set('defaultsettings', action.settings) 26 | .merge(action.settings) 27 | case constants.FETCH_DATA_SUCCESS: 28 | return state.get('settingsChanged') ? state : state.merge(action.settings) 29 | case constants.SET_ESTIMATED_SCORE: 30 | return state.set('conversionRate', action.conversionRate) 31 | default: 32 | return state 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /plugins/Hosting/js/utils/host.js: -------------------------------------------------------------------------------- 1 | import BigNumber from 'bignumber.js' 2 | 3 | export const chooseFileLocation = function () { 4 | const selectedFile = SiaAPI.openFile({ 5 | title: 'Choose new storage location.', 6 | properties: ['openDirectory'] 7 | }) 8 | if (selectedFile) { 9 | return selectedFile[0] 10 | } else { 11 | return undefined 12 | } 13 | } 14 | 15 | export const hastingsByteToSCTB = hastings => 16 | SiaAPI.hastingsToSiacoins(hastings).times('1e12') 17 | 18 | export const SCTBToHastingsByte = SC => 19 | SiaAPI.siacoinsToHastings(SC).dividedBy('1e12') 20 | 21 | export const validNumbers = values => 22 | // Expects array of dict, first is value, second is minimum. 23 | values.reduce( 24 | (isValid, val) => 25 | !isNaN(val.value) && val.value > (val.min || 0) && isValid, 26 | true 27 | ) 28 | 29 | export const hastingsByteBlockToSCTBMonth = hastings => 30 | hastingsByteToSCTB(hastings).times('4320') // 4320 = blocks per month 31 | 32 | export const SCTBMonthToHastingsByteBlock = SC => 33 | SCTBToHastingsByte(SC).dividedBy('4320') // 4320 = blocks per month 34 | 35 | export const blocksToWeeks = blocks => new BigNumber(blocks).dividedBy('1008') // 1008 = blocks per week 36 | 37 | export const weeksToBlocks = weeks => new BigNumber(weeks).times('1008') // 1008 = blocks per week 38 | -------------------------------------------------------------------------------- /plugins/Logs/assets/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/plugins/Logs/assets/button.png -------------------------------------------------------------------------------- /plugins/Logs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Logs 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
    16 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /plugins/Logs/js/actions.js: -------------------------------------------------------------------------------- 1 | import * as constants from './constants.js' 2 | 3 | export const addLogFilters = filters => ({ 4 | type: constants.ADD_LOG_FILTERS, 5 | filters 6 | }) 7 | export const removeLogFilters = filters => ({ 8 | type: constants.REMOVE_LOG_FILTERS, 9 | filters 10 | }) 11 | export const setLogFilters = filters => ({ 12 | type: constants.SET_LOG_FILTERS, 13 | filters 14 | }) 15 | export const incrementLogSize = (increment = 50000) => ({ 16 | type: constants.INCREMENT_LOG_SIZE, 17 | increment 18 | }) 19 | export const reloadLog = () => ({ 20 | type: constants.RELOAD_LOG 21 | }) 22 | export const setScrolling = () => ({ 23 | type: constants.SET_SCROLLING 24 | }) 25 | export const setNotScrolling = () => ({ 26 | type: constants.SET_NOT_SCROLLING 27 | }) 28 | -------------------------------------------------------------------------------- /plugins/Logs/js/components/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import FilterControls from '../containers/filtercontrols.js' 3 | import LogView from '../containers/logview.js' 4 | import { hot } from 'react-hot-loader' 5 | 6 | const App = () => ( 7 |
    8 | 9 | 10 |
    11 | ) 12 | 13 | export default hot(module)(App) 14 | -------------------------------------------------------------------------------- /plugins/Logs/js/components/filtercontrol.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const FilterControl = ({ 5 | name, 6 | filters, 7 | checked, 8 | addLogFilters, 9 | removeLogFilters, 10 | setLogFilters 11 | }) => { 12 | const filterControlStyle = { 13 | width: '100px', 14 | height: '50px', 15 | margin: '0', 16 | padding: '0', 17 | display: 'flex', 18 | alignItems: 'center', 19 | justifyContent: 'center', 20 | cursor: 'pointer', 21 | borderBottom: checked ? '4px solid #00CBA0' : '1px solid #00CBA0' 22 | } 23 | const onFilterClick = e => { 24 | if (!e.shiftKey && !e.ctrlKey) { 25 | setLogFilters(filters) 26 | return 27 | } 28 | if (checked) { 29 | removeLogFilters(filters) 30 | return 31 | } 32 | addLogFilters(filters) 33 | } 34 | return ( 35 |
    36 | {name} 37 |
    38 | ) 39 | } 40 | 41 | FilterControl.propTypes = { 42 | name: PropTypes.string.isRequired, 43 | filters: PropTypes.array.isRequired, 44 | checked: PropTypes.bool.isRequired, 45 | addLogFilters: PropTypes.func.isRequired, 46 | removeLogFilters: PropTypes.func.isRequired, 47 | setLogFilters: PropTypes.func.isRequired 48 | } 49 | 50 | export default FilterControl 51 | -------------------------------------------------------------------------------- /plugins/Logs/js/components/filtercontrols.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { Set } from 'immutable' 4 | import FilterControl from './filtercontrol' 5 | import { filters } from '../filters.js' 6 | 7 | const filterControlsStyle = { 8 | width: '100%', 9 | position: 'absolute', 10 | top: '0', 11 | margin: '0', 12 | padding: '0', 13 | backgroundColor: '#ECECEC', 14 | display: 'flex', 15 | alignItems: 'center', 16 | justifyContent: 'center' 17 | } 18 | 19 | const FilterControls = ({ logFilters, actions }) => ( 20 |
    21 | {filters.map((filter, key) => ( 22 | 27 | isChecked || logFilters.includes(filtertext), 28 | false 29 | )} 30 | addLogFilters={actions.addLogFilters} 31 | removeLogFilters={actions.removeLogFilters} 32 | setLogFilters={actions.setLogFilters} 33 | key={key} 34 | /> 35 | ))} 36 |
    37 | ) 38 | 39 | FilterControls.propTypes = { 40 | logFilters: PropTypes.instanceOf(Set).isRequired 41 | } 42 | 43 | export default FilterControls 44 | -------------------------------------------------------------------------------- /plugins/Logs/js/components/logview.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const logViewStyle = { 5 | position: 'absolute', 6 | top: '55px', 7 | bottom: '0', 8 | left: '2px', 9 | right: '0', 10 | margin: '0', 11 | padding: '0', 12 | overflowY: 'scroll', 13 | whiteSpace: 'pre', 14 | fontSize: '12px', 15 | fontFamily: 'monospace' 16 | } 17 | 18 | export default class LogView extends React.Component { 19 | componentDidMount () { 20 | this._logView.scrollTop = this._logView.scrollHeight 21 | this._logView.addEventListener('scroll', this.handleScroll.bind(this)) 22 | } 23 | componentDidUpdate () { 24 | if (!this.props.scrolling) { 25 | this._logView.scrollTop = this._logView.scrollHeight 26 | } 27 | } 28 | handleScroll () { 29 | if (this._logView.scrollTop === 0) { 30 | this.props.actions.incrementLogSize() 31 | this._logView.scrollTop = 1 32 | } 33 | if ( 34 | this._logView.scrollTop === 35 | this._logView.scrollHeight - this._logView.clientHeight 36 | ) { 37 | this.props.actions.setNotScrolling() 38 | } else { 39 | this.props.actions.setScrolling() 40 | } 41 | } 42 | render () { 43 | return ( 44 |
    (this._logView = lv)}> 45 | {this.props.logText} 46 |
    47 | ) 48 | } 49 | } 50 | 51 | LogView.propTypes = { 52 | logText: PropTypes.string.isRequired, 53 | scrolling: PropTypes.bool.isRequired 54 | } 55 | -------------------------------------------------------------------------------- /plugins/Logs/js/constants.js: -------------------------------------------------------------------------------- 1 | export const ADD_LOG_FILTERS = 'ADD_LOG_FILTERS' 2 | export const REMOVE_LOG_FILTERS = 'REMOVE_LOG_FILTERS' 3 | export const SET_LOG_FILTERS = 'SET_LOG_FILTERS' 4 | export const INCREMENT_LOG_SIZE = 'INCREMENT_LOG_SIZE' 5 | export const RELOAD_LOG = 'RELOAD_LOG' 6 | export const SET_SCROLLING = 'SET_SCROLLING' 7 | export const SET_NOT_SCROLLING = 'SET_NOT_SCROLLING' 8 | -------------------------------------------------------------------------------- /plugins/Logs/js/containers/filtercontrols.js: -------------------------------------------------------------------------------- 1 | import FilterControlsView from '../components/filtercontrols.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { setLogFilters, addLogFilters, removeLogFilters } from '../actions.js' 5 | 6 | const mapStateToProps = state => ({ 7 | logFilters: state.get('logFilters') 8 | }) 9 | 10 | const mapDispatchToProps = dispatch => ({ 11 | actions: bindActionCreators( 12 | { setLogFilters, addLogFilters, removeLogFilters }, 13 | dispatch 14 | ) 15 | }) 16 | 17 | const FilterControls = connect(mapStateToProps, mapDispatchToProps)( 18 | FilterControlsView 19 | ) 20 | export default FilterControls 21 | -------------------------------------------------------------------------------- /plugins/Logs/js/containers/logview.js: -------------------------------------------------------------------------------- 1 | import LogViewView from '../components/logview.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { setScrolling, setNotScrolling, incrementLogSize } from '../actions.js' 5 | 6 | const mapStateToProps = state => ({ 7 | logText: state.get('logText'), 8 | scrolling: state.get('scrolling') 9 | }) 10 | 11 | const mapDispatchToProps = dispatch => ({ 12 | actions: bindActionCreators( 13 | { setScrolling, setNotScrolling, incrementLogSize }, 14 | dispatch 15 | ) 16 | }) 17 | 18 | const LogView = connect(mapStateToProps, mapDispatchToProps)(LogViewView) 19 | export default LogView 20 | -------------------------------------------------------------------------------- /plugins/Logs/js/filters.js: -------------------------------------------------------------------------------- 1 | export const filters = [ 2 | { 3 | name: 'Siad', 4 | filters: ['siad-output.log'] 5 | }, 6 | { 7 | name: 'Consensus', 8 | filters: ['consensus.log'] 9 | }, 10 | { 11 | name: 'Gateway', 12 | filters: ['gateway.log'] 13 | }, 14 | { 15 | name: 'Host', 16 | filters: ['host.log', 'storagemanager.log'] 17 | }, 18 | { 19 | name: 'Renter', 20 | filters: ['renter/contractor.log', 'renter/hostdb.log', 'renter.log'] 21 | }, 22 | { 23 | name: 'Miner', 24 | filters: ['miner.log'] 25 | }, 26 | { 27 | name: 'Wallet', 28 | filters: ['wallet.log'] 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /plugins/Logs/js/index.js: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom' 2 | import { logsPlugin } from './main.js' 3 | 4 | // If dev enable window reload 5 | if (process.env.NODE_ENV === 'development') { 6 | require('electron-css-reload')() 7 | } 8 | 9 | ReactDOM.render(logsPlugin(), document.getElementById('react-root')) 10 | -------------------------------------------------------------------------------- /plugins/Logs/js/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { createStore } from 'redux' 3 | import { Provider } from 'react-redux' 4 | import loggingReducer from './reducer.js' 5 | import App from './components/app.js' 6 | import * as actions from './actions.js' 7 | 8 | export const logsPlugin = () => { 9 | const store = createStore(loggingReducer) 10 | setInterval(() => { 11 | store.dispatch(actions.reloadLog()) 12 | }, 10000) 13 | return ( 14 | 15 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /plugins/Logs/js/reducer.js: -------------------------------------------------------------------------------- 1 | import { Map, Set } from 'immutable' 2 | import * as constants from './constants.js' 3 | import { 4 | addLogFilters, 5 | removeLogFilters, 6 | updateLogFilters, 7 | parseLogs 8 | } from './logparse.js' 9 | 10 | const siadir = SiaAPI.config.siad.datadir 11 | const defaultLogSize = 50000 12 | 13 | const initialState = Map({ 14 | logFilters: Set(['consensus.log', 'gateway.log']), 15 | logText: parseLogs(siadir, 50000, ['consensus.log', 'gateway.log']), 16 | logSize: defaultLogSize, 17 | 18 | scrolling: false 19 | }) 20 | 21 | export default function loggingReducer (state = initialState, action) { 22 | switch (action.type) { 23 | case constants.INCREMENT_LOG_SIZE: 24 | return updateLogFilters( 25 | state, 26 | state.get('logSize') + action.increment, 27 | state.get('logFilters') 28 | ) 29 | case constants.RELOAD_LOG: 30 | return updateLogFilters( 31 | state, 32 | state.get('logSize'), 33 | state.get('logFilters') 34 | ) 35 | case constants.ADD_LOG_FILTERS: 36 | return addLogFilters(state, action.filters) 37 | case constants.REMOVE_LOG_FILTERS: 38 | return removeLogFilters(state, action.filters) 39 | case constants.SET_LOG_FILTERS: 40 | return updateLogFilters( 41 | state, 42 | state.get('logSize'), 43 | Set(action.filters) 44 | ).set('logSize', defaultLogSize) 45 | case constants.SET_SCROLLING: 46 | return state.set('scrolling', true) 47 | case constants.SET_NOT_SCROLLING: 48 | return state.set('scrolling', false) 49 | default: 50 | return state 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /plugins/Logs/js/utils.js: -------------------------------------------------------------------------------- 1 | import fs from 'graceful-fs' 2 | import { List } from 'immutable' 3 | import Path from 'path' 4 | 5 | export const readdirRecursive = (dir, files = List()) => { 6 | const dirfiles = fs.readdirSync(dir) 7 | let filelist = files 8 | dirfiles.forEach(file => { 9 | const filepath = Path.join(dir, file) 10 | const stat = fs.statSync(filepath) 11 | if (stat.isDirectory()) { 12 | filelist = readdirRecursive(filepath, filelist) 13 | } else if (stat.isFile()) { 14 | filelist = filelist.push(filepath) 15 | } 16 | }) 17 | return filelist 18 | } 19 | -------------------------------------------------------------------------------- /plugins/Terminal/assets/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/plugins/Terminal/assets/button.png -------------------------------------------------------------------------------- /plugins/Terminal/assets/roboto-mono-100.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/plugins/Terminal/assets/roboto-mono-100.woff2 -------------------------------------------------------------------------------- /plugins/Terminal/assets/roboto-mono-300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/plugins/Terminal/assets/roboto-mono-300.woff2 -------------------------------------------------------------------------------- /plugins/Terminal/assets/roboto-mono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/plugins/Terminal/assets/roboto-mono.woff2 -------------------------------------------------------------------------------- /plugins/Terminal/css/roboto-mono.css: -------------------------------------------------------------------------------- 1 | /* latin */ 2 | @font-face { 3 | font-family: 'Roboto Mono'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: local('Roboto Mono'), local('RobotoMono-Regular'), url('../assets/roboto-mono.woff2') format('woff2'); 7 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 8 | } 9 | 10 | /* latin */ 11 | @font-face { 12 | font-family: 'Roboto Mono'; 13 | font-style: normal; 14 | font-weight: 300; 15 | src: local('Roboto Mono Light'), local('RobotoMono-Light'), url('../assets/roboto-mono-300.woff2') format('woff2'); 16 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 17 | } 18 | 19 | /* latin */ 20 | @font-face { 21 | font-family: 'Roboto Mono'; 22 | font-style: normal; 23 | font-weight: 100; 24 | src: local('Roboto Mono Thin'), local('RobotoMono-Thin'), url('../assets/roboto-mono-100.woff2') format('woff2'); 25 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000; 26 | } 27 | -------------------------------------------------------------------------------- /plugins/Terminal/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wallet 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    17 |
    Terminal
    18 |
    19 | 20 |
    21 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /plugins/Terminal/js/actions/commandline.js: -------------------------------------------------------------------------------- 1 | import * as constants from '../constants/commandline.js' 2 | 3 | export const addCommand = command => ({ 4 | type: constants.ADD_COMMAND, 5 | command 6 | }) 7 | export const updateCommand = (command, id, dataChunk) => ({ 8 | type: constants.UPDATE_COMMAND, 9 | command, 10 | id, 11 | dataChunk 12 | }) 13 | export const endCommand = (command, id) => ({ 14 | type: constants.END_COMMAND, 15 | command, 16 | id 17 | }) 18 | 19 | export const loadPrevCommand = () => ({ 20 | type: constants.LOAD_PREV_COMMAND 21 | }) 22 | export const loadNextCommand = () => ({ 23 | type: constants.LOAD_NEXT_COMMAND 24 | }) 25 | export const setCurrentCommand = commandText => ({ 26 | type: constants.SET_CURRENT_COMMAND, 27 | command: commandText 28 | }) 29 | 30 | export const setWalletPassword = walletPassword => ({ 31 | type: constants.SET_WALLET_PASSWORD, 32 | walletPassword 33 | }) 34 | 35 | export const showWalletPrompt = () => ({ 36 | type: constants.SHOW_WALLET_PROMPT 37 | }) 38 | export const hideWalletPrompt = () => ({ 39 | type: constants.HIDE_WALLET_PROMPT 40 | }) 41 | 42 | export const showSeedPrompt = () => ({ 43 | type: constants.SHOW_SEED_PROMPT 44 | }) 45 | export const hideSeedPrompt = () => ({ 46 | type: constants.HIDE_SEED_PROMPT 47 | }) 48 | 49 | export const showCommandOverview = () => ({ 50 | type: constants.SHOW_COMMAND_OVERVIEW 51 | }) 52 | export const hideCommandOverview = () => ({ 53 | type: constants.HIDE_COMMAND_OVERVIEW 54 | }) 55 | -------------------------------------------------------------------------------- /plugins/Terminal/js/components/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CommandLine from '../containers/commandline.js' 3 | import { hot } from 'react-hot-loader' 4 | 5 | const CommandLineApp = () => ( 6 |
    7 | 8 |
    9 | ) 10 | 11 | export default hot(module)(CommandLineApp) 12 | -------------------------------------------------------------------------------- /plugins/Terminal/js/components/commandhistorylist.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import { List } from 'immutable' 4 | 5 | export default class CommandHistoryList extends React.Component { 6 | componentDidUpdate () { 7 | this._commandHistoryList.scrollTop = this._commandHistoryList.scrollHeight 8 | } 9 | 10 | render () { 11 | const CommandHistoryComponents = this.props.commandHistory 12 | .filterNot( 13 | command => 14 | command.get('command') === 'help' || command.get('command') === '?' 15 | ) 16 | .map((command, key) => ( 17 |
  • 18 |

    19 | {command.get('command')} 20 | 26 |

    27 |

    {command.get('result')}

    28 |
  • 29 | )) 30 | 31 | return ( 32 |
    (this._commandHistoryList = c)} 35 | > 36 |
      {CommandHistoryComponents}
    37 |
    38 | ) 39 | } 40 | } 41 | 42 | CommandHistoryList.propTypes = { commandHistory: PropTypes.instanceOf(List) } 43 | -------------------------------------------------------------------------------- /plugins/Terminal/js/components/commandinput.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { commandInputHelper } from '../utils/helpers.js' 3 | 4 | export default class CommandInput extends React.Component { 5 | componentDidUpdate () { 6 | // Give DOM time to register the update. 7 | if (!this.props.showWalletPrompt && !this.props.showSeedPrompt) { 8 | this._input.focus() 9 | } 10 | } 11 | 12 | render () { 13 | const handleTextInput = e => 14 | this.props.actions.setCurrentCommand(e.target.value) 15 | const handleKeyboardPress = e => 16 | commandInputHelper( 17 | e, 18 | this.props.actions, 19 | this.props.currentCommand, 20 | this.props.showCommandOverview, 21 | this.props.commandHistory.size 22 | ) 23 | return ( 24 | (this._input = c)} 33 | /> 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /plugins/Terminal/js/components/commandline.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import CommandHistoryList from '../containers/commandhistorylist.js' 3 | import CommandInput from '../containers/commandinput.js' 4 | import WalletPasswordPrompt from '../containers/walletpasswordprompt.js' 5 | import WalletSeedPrompt from '../containers/seedprompt.js' 6 | 7 | const CommandLine = ({ showCommandOverview, actions }) => { 8 | const toggleCommandOverview = () => { 9 | if (showCommandOverview) { 10 | actions.hideCommandOverview() 11 | } else { 12 | actions.showCommandOverview() 13 | } 14 | } 15 | 16 | return ( 17 |
    20 | 21 |
    22 |
    27 |
    28 | ? 29 |
    30 |
    31 |

    Available Commands:

    32 |

    33 | consensus Print the current state of consensus
    34 | gateway Perform gateway actions
    35 | host Perform host actions
    36 | hostdb View or modify the host database
    37 | miner Perform miner actions
    38 | renter Perform renter actions
    39 | stop Stop the Sia daemon
    40 | update Update Sia
    41 | version Print version information
    42 | wallet Perform wallet actions
    43 |
    44 | Use '[command] --help' for more information about a command.
    45 |

    46 |
    47 |
    48 | 49 |
    50 | 51 | 52 |
    53 | ) 54 | } 55 | 56 | export default CommandLine 57 | -------------------------------------------------------------------------------- /plugins/Terminal/js/components/seedprompt.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { httpCommand } from '../utils/helpers.js' 3 | import querystring from 'querystring' 4 | 5 | export default class WalletSeedPrompt extends React.Component { 6 | componentDidUpdate () { 7 | // Give DOM time to register the update. 8 | if (this.props.showSeedPrompt) { 9 | this._seedPasswd.focus() 10 | } 11 | } 12 | 13 | render () { 14 | const handleKeyboardPress = e => { 15 | if (e.keyCode === 13) { 16 | // Grab input, spawn process, and pipe text field to stdin. 17 | const siac = httpCommand( 18 | this.props.currentCommand, 19 | this.props.actions, 20 | this.props.commandHistory.size 21 | ) 22 | 23 | siac.write( 24 | querystring.stringify({ 25 | encryptionpassword: this.props.walletPassword, 26 | seed: e.target.value, 27 | dictionary: 'english' 28 | }) 29 | ) 30 | siac.end() 31 | e.target.value = '' 32 | this.props.actions.setWalletPassword('') 33 | this.props.actions.hideSeedPrompt() 34 | } 35 | } 36 | 37 | return ( 38 |
    42 |
    43 |

    New seed

    44 |

    Please type your new seed and press enter to continue.

    45 | (this._seedPasswd = c)} 50 | /> 51 |
    52 |
    53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /plugins/Terminal/js/constants/commandline.js: -------------------------------------------------------------------------------- 1 | export const ADD_COMMAND = 'ADD_COMMAND' 2 | export const UPDATE_COMMAND = 'UPDATE_COMMAND' 3 | export const END_COMMAND = 'END_COMMAND' 4 | export const LOAD_PREV_COMMAND = 'LOAD_PREV_COMMAND' 5 | export const LOAD_NEXT_COMMAND = 'LOAD_NEXT_COMMAND' 6 | export const SET_CURRENT_COMMAND = 'SET_CURRENT_COMMAND' 7 | export const SET_WALLET_PASSWORD = 'SET_WALLET_PASSWORD' 8 | export const SHOW_WALLET_PROMPT = 'SHOW_WALLET_PROMPT' 9 | export const HIDE_WALLET_PROMPT = 'HIDE_WALLET_PROMPT' 10 | export const SHOW_SEED_PROMPT = 'SHOW_SEED_PROMPT' 11 | export const HIDE_SEED_PROMPT = 'HIDE_SEED_PROMPT' 12 | export const SHOW_COMMAND_OVERVIEW = 'SHOW_COMMAND_OVERVIEW' 13 | export const HIDE_COMMAND_OVERVIEW = 'HIDE_COMMAND_OVERVIEW' 14 | -------------------------------------------------------------------------------- /plugins/Terminal/js/constants/helper.js: -------------------------------------------------------------------------------- 1 | export const REGULAR_COMMAND = -1 2 | export const WALLET_SEED = 0 3 | export const WALLET_INIT_SEED = 1 4 | export const WALLET_UNLOCK = 2 5 | export const WALLET_033X = 3 6 | export const WALLET_SIAG = 4 7 | export const HELP = 5 8 | export const HELP_QMARK = 6 9 | 10 | // These commands need a password prompt or other special handling. 11 | export const specialCommands = [ 12 | ['wallet', 'load', 'seed'], 13 | ['wallet', 'init-seed'], 14 | ['wallet', 'unlock'], 15 | ['wallet', 'load', '033x'], 16 | ['wallet', 'load', 'siag'], 17 | ['help'], 18 | ['?'] 19 | ] 20 | -------------------------------------------------------------------------------- /plugins/Terminal/js/containers/commandhistorylist.js: -------------------------------------------------------------------------------- 1 | import CommandHistoryListView from '../components/commandhistorylist.js' 2 | import { connect } from 'react-redux' 3 | 4 | const mapStateToProps = state => ({ 5 | commandHistory: state.commandLineReducer.get('commandHistory') 6 | }) 7 | 8 | const CommandHistoryList = connect(mapStateToProps)(CommandHistoryListView) 9 | export default CommandHistoryList 10 | -------------------------------------------------------------------------------- /plugins/Terminal/js/containers/commandinput.js: -------------------------------------------------------------------------------- 1 | import CommandInputView from '../components/commandinput.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { 5 | addCommand, 6 | updateCommand, 7 | endCommand, 8 | loadPrevCommand, 9 | loadNextCommand, 10 | setCurrentCommand, 11 | showWalletPrompt, 12 | showSeedPrompt, 13 | hideSeedPrompt, 14 | hideWalletPrompt, 15 | showCommandOverview, 16 | hideCommandOverview 17 | } from '../actions/commandline.js' 18 | 19 | const mapStateToProps = state => ({ 20 | currentCommand: state.commandLineReducer.get('currentCommand'), 21 | commandIndex: state.commandLineReducer.get('commandIndex'), 22 | showCommandOverview: state.commandLineReducer.get('showCommandOverview'), 23 | commandRunning: state.commandLineReducer.get('commandRunning'), 24 | commandHistory: state.commandLineReducer.get('commandHistory'), 25 | showWalletPrompt: state.commandLineReducer.get('showWalletPrompt'), 26 | showSeedPrompt: state.commandLineReducer.get('showSeedPrompt') 27 | }) 28 | 29 | const mapDispatchToProps = dispatch => ({ 30 | actions: bindActionCreators( 31 | { 32 | addCommand, 33 | updateCommand, 34 | endCommand, 35 | loadPrevCommand, 36 | loadNextCommand, 37 | setCurrentCommand, 38 | showWalletPrompt, 39 | hideWalletPrompt, 40 | showSeedPrompt, 41 | hideSeedPrompt, 42 | showCommandOverview, 43 | hideCommandOverview 44 | }, 45 | dispatch 46 | ) 47 | }) 48 | 49 | const CommandInput = connect(mapStateToProps, mapDispatchToProps)( 50 | CommandInputView 51 | ) 52 | export default CommandInput 53 | -------------------------------------------------------------------------------- /plugins/Terminal/js/containers/commandline.js: -------------------------------------------------------------------------------- 1 | import CommandLineView from '../components/commandline.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { 5 | showCommandOverview, 6 | hideCommandOverview 7 | } from '../actions/commandline.js' 8 | 9 | const mapStateToProps = state => ({ 10 | showCommandOverview: state.commandLineReducer.get('showCommandOverview') 11 | }) 12 | 13 | const mapDispatchToProps = dispatch => ({ 14 | actions: bindActionCreators( 15 | { showCommandOverview, hideCommandOverview }, 16 | dispatch 17 | ) 18 | }) 19 | 20 | const CommandLine = connect(mapStateToProps, mapDispatchToProps)( 21 | CommandLineView 22 | ) 23 | export default CommandLine 24 | -------------------------------------------------------------------------------- /plugins/Terminal/js/containers/seedprompt.js: -------------------------------------------------------------------------------- 1 | import WalletSeedPromptView from '../components/seedprompt.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { 5 | addCommand, 6 | updateCommand, 7 | endCommand, 8 | setWalletPassword, 9 | hideSeedPrompt, 10 | hideCommandOverview 11 | } from '../actions/commandline.js' 12 | 13 | const mapStateToProps = state => ({ 14 | showSeedPrompt: state.commandLineReducer.get('showSeedPrompt'), 15 | currentCommand: state.commandLineReducer.get('currentCommand'), 16 | commandHistory: state.commandLineReducer.get('commandHistory'), 17 | walletPassword: state.commandLineReducer.get('walletPassword') 18 | }) 19 | 20 | const mapDispatchToProps = dispatch => ({ 21 | actions: bindActionCreators( 22 | { 23 | addCommand, 24 | updateCommand, 25 | endCommand, 26 | setWalletPassword, 27 | hideSeedPrompt, 28 | hideCommandOverview 29 | }, 30 | dispatch 31 | ) 32 | }) 33 | 34 | const WalletSeedPrompt = connect(mapStateToProps, mapDispatchToProps)( 35 | WalletSeedPromptView 36 | ) 37 | export default WalletSeedPrompt 38 | -------------------------------------------------------------------------------- /plugins/Terminal/js/containers/walletpasswordprompt.js: -------------------------------------------------------------------------------- 1 | import WalletPasswordPromptView from '../components/walletpasswordprompt.js' 2 | import { connect } from 'react-redux' 3 | import { bindActionCreators } from 'redux' 4 | import { 5 | addCommand, 6 | updateCommand, 7 | endCommand, 8 | setWalletPassword, 9 | hideWalletPrompt, 10 | showSeedPrompt, 11 | hideCommandOverview 12 | } from '../actions/commandline.js' 13 | 14 | const mapStateToProps = state => ({ 15 | showWalletPrompt: state.commandLineReducer.get('showWalletPrompt'), 16 | currentCommand: state.commandLineReducer.get('currentCommand'), 17 | commandHistory: state.commandLineReducer.get('commandHistory'), 18 | walletPassword: state.commandLineReducer.get('walletPassword') 19 | }) 20 | 21 | const mapDispatchToProps = dispatch => ({ 22 | actions: bindActionCreators( 23 | { 24 | addCommand, 25 | updateCommand, 26 | endCommand, 27 | setWalletPassword, 28 | hideWalletPrompt, 29 | showSeedPrompt, 30 | hideCommandOverview 31 | }, 32 | dispatch 33 | ) 34 | }) 35 | 36 | const WalletPasswordPrompt = connect(mapStateToProps, mapDispatchToProps)( 37 | WalletPasswordPromptView 38 | ) 39 | export default WalletPasswordPrompt 40 | -------------------------------------------------------------------------------- /plugins/Terminal/js/index.js: -------------------------------------------------------------------------------- 1 | // index.js: main entrypoint for the Sia-UI wallet plugin. 2 | import React from 'react' 3 | import ReactDOM from 'react-dom' 4 | import CommandLine from './components/app.js' 5 | import { createStore } from 'redux' 6 | import { Provider } from 'react-redux' 7 | import rootReducer from './reducers/index.js' 8 | import { initPlugin } from './utils/helpers.js' 9 | 10 | // If dev enable window reload 11 | if (process.env.NODE_ENV === 'development') { 12 | require('electron-css-reload')() 13 | } 14 | 15 | // Render the wallet plugin 16 | const store = createStore(rootReducer) 17 | const rootElement = ( 18 | 19 | 20 | 21 | ) 22 | 23 | initPlugin() 24 | ReactDOM.render(rootElement, document.getElementById('react-root')) 25 | -------------------------------------------------------------------------------- /plugins/Terminal/js/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux' 2 | import commandLineReducer from './commandline.js' 3 | 4 | const rootReducer = combineReducers({ 5 | commandLineReducer 6 | }) 7 | 8 | export default rootReducer 9 | -------------------------------------------------------------------------------- /plugins/Wallet/assets/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NebulousLabs/Sia-UI/cd7e221b98fc7a395de25bd8dfaa4f4de2a6e3d2/plugins/Wallet/assets/button.png -------------------------------------------------------------------------------- /plugins/Wallet/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Wallet 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
    17 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /plugins/Wallet/js/actions/error.js: -------------------------------------------------------------------------------- 1 | import { WALLET_UNLOCK_ERROR } from '../constants/error.js' 2 | 3 | export const walletUnlockError = err => ({ 4 | type: WALLET_UNLOCK_ERROR, 5 | err 6 | }) 7 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LockScreen from '../containers/lockscreen.js' 3 | import Wallet from '../containers/wallet.js' 4 | import { hot } from 'react-hot-loader' 5 | 6 | const WalletApp = () => ( 7 |
    8 | 9 | 10 |
    11 | ) 12 | 13 | export default hot(module)(WalletApp) 14 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/backupbutton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const BackupButton = ({ actions }) => { 4 | const handleClick = () => actions.showBackupPrompt() 5 | return ( 6 |
    7 | 8 | Backup Wallet 9 |
    10 | ) 11 | } 12 | 13 | export default BackupButton 14 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/backupprompt.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const BackupPrompt = ({ primarySeed, auxSeeds, actions }) => { 5 | const handleOkClick = () => actions.hideBackupPrompt() 6 | return ( 7 |
    8 |
    9 | {auxSeeds.length === 0 ? ( 10 |

    11 | Write down your seed to back up your wallet. You can restore your 12 | wallet using only this seed. 13 |

    14 | ) : ( 15 |

    16 | Write down your seeds to back up your wallet. You can restore your 17 | wallet using only these seeds. 18 |

    19 | )} 20 | 21 |

    Primary Seed:

    22 |

    {primarySeed}

    23 | {auxSeeds.length > 0 ?

    Auxiliary Seeds:

    : null} 24 | {auxSeeds.length > 0 ? ( 25 |
    26 | {auxSeeds.map((seed, key) => ( 27 |
    28 | {seed} 29 |
    30 | ))} 31 |
    32 | ) : null} 33 | 36 |
    37 |
    38 | ) 39 | } 40 | BackupPrompt.propTypes = { 41 | primarySeed: PropTypes.string.isRequired, 42 | auxSeeds: PropTypes.array.isRequired 43 | } 44 | 45 | export default BackupPrompt 46 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/balanceinfo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const BalanceInfo = ({ 5 | synced, 6 | confirmedbalance, 7 | unconfirmedbalance, 8 | siafundbalance, 9 | siacoinclaimbalance 10 | }) => ( 11 |
    12 | Confirmed Balance: {confirmedbalance} SC 13 | Unconfirmed Delta: {unconfirmedbalance} SC 14 | {siafundbalance !== '0' ? ( 15 | Siafund Balance: {siafundbalance} SF 16 | ) : null} 17 | {siacoinclaimbalance !== '0' ? ( 18 | Siacoin Claim Balance: {siacoinclaimbalance} SC 19 | ) : null} 20 | {!synced ? ( 21 | 25 | Your wallet is not synced, balances are not final. 26 | 27 | ) : null} 28 |
    29 | ) 30 | BalanceInfo.propTypes = { 31 | synced: PropTypes.bool.isRequired, 32 | confirmedbalance: PropTypes.string.isRequired, 33 | unconfirmedbalance: PropTypes.string.isRequired, 34 | siafundbalance: PropTypes.string.isRequired, 35 | siacoinclaimbalance: PropTypes.string.isRequired 36 | } 37 | export default BalanceInfo 38 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/changepasswordbutton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const ChangePasswordButton = ({ actions }) => { 4 | const handleChangePasswordClick = () => actions.showChangePasswordDialog() 5 | return ( 6 |
    10 | 11 | Change Password 12 |
    13 | ) 14 | } 15 | 16 | export default ChangePasswordButton 17 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/changepassworddialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const ChangePasswordDialog = ({ changePasswordError, actions }) => { 5 | const handleChangePasswordClick = e => { 6 | e.preventDefault() 7 | if (e.target.newpassword.value !== e.target['newpassword-again'].value) { 8 | actions.setChangePasswordError('passwords did not match!') 9 | return 10 | } 11 | actions.changePassword( 12 | e.target.currentpassword.value, 13 | e.target.newpassword.value 14 | ) 15 | } 16 | const handleCancelClick = e => { 17 | e.preventDefault() 18 | actions.hideChangePasswordDialog() 19 | } 20 | 21 | return ( 22 |
    23 |
    27 |

    28 | Enter your current password, and the new password you wish to replace 29 | it with. 30 |

    31 | 38 | 44 | 50 |
    51 | 52 | 58 |
    59 |
    {changePasswordError}
    60 |
    61 |
    62 | ) 63 | } 64 | 65 | ChangePasswordDialog.propTypes = { 66 | changePasswordError: PropTypes.string.isRequired 67 | } 68 | 69 | export default ChangePasswordDialog 70 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/confirmationdialog.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import PropTypes from 'prop-types' 3 | 4 | const ConfirmationDialog = ({ seed, password, error, actions }) => { 5 | const onCancelClick = () => actions.hideConfirmationDialog() 6 | const onOkClick = e => { 7 | e.preventDefault() 8 | if (e.target.seed.value === seed && e.target.password.value === password) { 9 | actions.dismissNewWalletDialog() 10 | } else if (e.target.seed.value !== seed) { 11 | actions.setConfirmationError('seed did not match!') 12 | } else if (e.target.password.value !== password) { 13 | actions.setConfirmationError('password did not match!') 14 | } 15 | } 16 | return ( 17 |
    18 |
    19 |

    Please confirm your password and seed to continue

    20 |
    21 | 28 | 35 |
    36 | 37 | 40 |
    41 |
    42 | {error} 43 |
    44 |
    45 | ) 46 | } 47 | 48 | ConfirmationDialog.propTypes = { 49 | seed: PropTypes.string.isRequired, 50 | password: PropTypes.string.isRequired, 51 | error: PropTypes.string.isRequired 52 | } 53 | 54 | export default ConfirmationDialog 55 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/initseedform.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const InitSeedForm = ({ 4 | initializingSeed, 5 | useCustomPassphrase, 6 | hideInitSeedForm, 7 | createNewWallet 8 | }) => { 9 | if (initializingSeed) { 10 | return ( 11 |
    12 |

    Initializing seed...

    13 | 14 |
    15 | ) 16 | } 17 | const handleInitSeedClick = e => { 18 | e.preventDefault() 19 | if (useCustomPassphrase) { 20 | createNewWallet( 21 | e.target.password.value.trim(), 22 | e.target.seed.value.trim() 23 | ) 24 | } else { 25 | createNewWallet(undefined, e.target.seed.value.trim()) 26 | } 27 | } 28 | const handleCancelClick = e => { 29 | e.preventDefault() 30 | hideInitSeedForm() 31 | } 32 | return ( 33 |
    34 |

    Enter a seed to initialize your wallet from.

    35 |

    36 | This will initialize your wallet from the provided seed, rescanning the 37 | blockchain to find all your money. This rescan process can take a while. 38 | The blockchain must also be synced. 39 |

    40 | 41 | {useCustomPassphrase ? ( 42 | 43 | ) : null} 44 |
    45 | 46 | 47 |
    48 |
    49 | ) 50 | } 51 | 52 | export default InitSeedForm 53 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/lockbutton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const LockButton = ({ actions }) => { 4 | const handleLockButtonClick = () => actions.lockWallet() 5 | return ( 6 |
    7 | 8 | Lock Wallet 9 |
    10 | ) 11 | } 12 | 13 | export default LockButton 14 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/lockscreen.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import PasswordPrompt from '../containers/passwordprompt.js' 4 | import UninitializedWalletDialog from '../containers/uninitializedwalletdialog.js' 5 | import RescanDialog from './rescandialog.js' 6 | 7 | const LockScreen = ({ unlocked, unlocking, encrypted, rescanning }) => { 8 | if (unlocked && encrypted && !unlocking && !rescanning) { 9 | // Wallet is unlocked and encrypted, return an empty lock screen. 10 | return
    11 | } 12 | let lockscreenContents 13 | if (!unlocked && encrypted && !rescanning) { 14 | lockscreenContents = 15 | } else if (rescanning) { 16 | lockscreenContents = 17 | } else if (!encrypted) { 18 | // Wallet is not encrypted, return a lockScreen that initializes a new wallet. 19 | lockscreenContents = 20 | } 21 | return ( 22 |
    23 |
    {lockscreenContents}
    24 |
    25 | ) 26 | } 27 | LockScreen.propTypes = { 28 | unlocked: PropTypes.bool.isRequired, 29 | unlocking: PropTypes.bool.isRequired, 30 | encrypted: PropTypes.bool.isRequired, 31 | rescanning: PropTypes.bool.isRequired 32 | } 33 | 34 | export default LockScreen 35 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/newwalletdialog.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | import ConfirmationDialog from '../containers/confirmationdialog.js' 4 | 5 | const NewWalletDialog = ({ 6 | password, 7 | seed, 8 | showConfirmationDialog, 9 | actions 10 | }) => { 11 | const handleDismissClick = () => actions.showConfirmationDialog() 12 | return ( 13 |
    14 | {showConfirmationDialog ? : null} 15 |

    16 | You have created a new wallet! Please write down the seed and password 17 | in a safe place. If you forget your password, you won't be able to 18 | access your wallet. 19 |

    20 |

    Seed:

    21 | {seed} 22 |

    Password:

    23 | {password} 24 | 27 |
    28 | ) 29 | } 30 | 31 | NewWalletDialog.propTypes = { 32 | password: PropTypes.string.isRequired, 33 | seed: PropTypes.string.isRequired 34 | } 35 | 36 | export default NewWalletDialog 37 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/newwalletform.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const NewWalletForm = ({ actions }) => { 4 | const handleCreateWalletClick = e => { 5 | e.preventDefault() 6 | actions.createNewWallet(e.target.password.value) 7 | actions.hideNewWalletForm() 8 | } 9 | const handleCancelClick = e => { 10 | e.preventDefault() 11 | actions.hideNewWalletForm() 12 | } 13 | return ( 14 |
    15 |

    16 | Enter a password to encrypt your wallet. You can leave this empty to use 17 | a secure, automatically generated password. 18 |

    19 | 20 |
    21 | 22 | 23 |
    24 |
    25 | ) 26 | } 27 | 28 | export default NewWalletForm 29 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/passwordprompt.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const PasswordPrompt = ({ password, error, unlocking, actions }) => { 5 | const onPasswordChange = e => actions.handlePasswordChange(e.target.value) 6 | const onUnlockClick = () => actions.unlockWallet(password) 7 | if (unlocking) { 8 | return ( 9 | 10 | Unlocking your wallet, this may take a while (up to several minutes)... 11 | 12 | ) 13 | } 14 | return ( 15 |
    16 |

    Wallet Locked

    17 | Enter your wallet password to unlock the wallet. 18 | 19 | 25 | 28 |
    {error}
    29 |
    30 | ) 31 | } 32 | PasswordPrompt.propTypes = { 33 | password: PropTypes.string.isRequired, 34 | error: PropTypes.string 35 | } 36 | 37 | export default PasswordPrompt 38 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/receivebutton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const ReceiveButton = ({ actions }) => { 4 | const handleReceiveButtonClick = () => actions.showReceivePrompt() 5 | return ( 6 |
    10 | 11 | Receive Siacoin 12 |
    13 | ) 14 | } 15 | 16 | export default ReceiveButton 17 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/recoverbutton.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const RecoverButton = ({ actions }) => { 4 | const handleRecoverButtonClick = () => actions.showSeedRecoveryDialog() 5 | return ( 6 |
    10 | 11 | Recover Seed 12 |
    13 | ) 14 | } 15 | 16 | export default RecoverButton 17 | -------------------------------------------------------------------------------- /plugins/Wallet/js/components/recoverydialog.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types' 2 | import React from 'react' 3 | 4 | const RecoveryDialog = ({ recovering, actions }) => { 5 | const handleRecoverClick = e => { 6 | e.preventDefault() 7 | actions.recoverSeed(e.target.seed.value) 8 | } 9 | const handleCancelClick = e => { 10 | e.preventDefault() 11 | actions.hideSeedRecoveryDialog() 12 | } 13 | 14 | if (recovering) { 15 | return ( 16 |
    17 |
    18 |