diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/config.png b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/config.png new file mode 100644 index 000000000..3307d1503 Binary files /dev/null and b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/config.png differ diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/dashboard.png b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/dashboard.png new file mode 100644 index 000000000..b0c52462f Binary files /dev/null and b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/dashboard.png differ diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/login.png b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/login.png new file mode 100644 index 000000000..2beb90990 Binary files /dev/null and b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/login.png differ diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/mounts.png b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/mounts.png new file mode 100644 index 000000000..9ef29ea62 Binary files /dev/null and b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/mounts.png differ diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/remoteexplorer.png b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/remoteexplorer.png new file mode 100644 index 000000000..96779dd7b Binary files /dev/null and b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/remoteexplorer.png differ diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/shelved.patch new file mode 100644 index 000000000..8ac248a98 --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20,_7_26_PM_[Default_Changelist]/shelved.patch @@ -0,0 +1,222 @@ +Index: .github/workflows/nodejs.yml +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>name: Node CI\n\non:\n [push, pull_request] \n\njobs:\n build:\n\n strategy:\n matrix:\n platform: [ubuntu-latest, macos-latest, windows-latest]\n node-version: [8.x, 10.x, 12.x]\n runs-on: ${{ matrix.platform }}\n\n steps:\n - uses: actions/checkout@v1\n - name: Use Node.js ${{ matrix.node-version }}\n uses: actions/setup-node@v1\n with:\n node-version: ${{ matrix.node-version }}\n - name: npm install, build, and test\n run: |\n npm ci\n npm run test:cov\n npm run build\n env:\n CI: true\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- .github/workflows/nodejs.yml (revision 01ddd2adb079d0e67ca795e10625ebbb0dd4c019) ++++ .github/workflows/nodejs.yml (date 1592401555203) +@@ -1,7 +1,7 @@ + name: Node CI + + on: +- [push, pull_request] ++ [push, pull_request] + + jobs: + build: +@@ -21,7 +21,11 @@ + - name: npm install, build, and test + run: | + npm ci +- npm run test:cov ++ make test-coverage + npm run build + env: + CI: true ++ - name: Coveralls ++ uses: coverallsapp/github-action@master ++ with: ++ github-token: ${{ secrets.GITHUB_TOKEN }} +Index: package.json +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>{\n \"name\": \"@rclone/rclone-webui-react\",\n \"version\": \"2.0.1\",\n \"description\": \"A web interface for r-clone\",\n \"author\": \"Chaitanya Bankanhal\",\n \"copyright\": \"\",\n \"license\": \"MIT\",\n \"private\": false,\n \"homepage\": \"./\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git@github.com:negative0/rclone-webui-react.git\"\n },\n \"dependencies\": {\n \"@coreui/coreui\": \"^2.1.16\",\n \"@coreui/coreui-plugin-chartjs-custom-tooltips\": \"^1.3.1\",\n \"@coreui/icons\": \"0.3.0\",\n \"@coreui/react\": \"^2.5.4\",\n \"axios\": \"^0.19.2\",\n \"bootstrap\": \"^4.4.1\",\n \"chart.js\": \"^2.9.3\",\n \"classnames\": \"^2.2.6\",\n \"core-js\": \"^3.6.5\",\n \"flag-icon-css\": \"^3.4.6\",\n \"font-awesome\": \"^4.7.0\",\n \"jquery\": \"^3.5.1\",\n \"lodash\": \"^4.17.14\",\n \"node-sass\": \"^4.14.1\",\n \"package.json\": \"^2.0.1\",\n \"prop-types\": \"^15.7.2\",\n \"rclone-api\": \"^1.0.7\",\n \"react\": \"^16.12.0\",\n \"react-app-polyfill\": \"^1.0.6\",\n \"react-autosuggest\": \"^10.0.0\",\n \"react-awesome-player\": \"^1.0.11\",\n \"react-chartjs-2\": \"^2.9.0\",\n \"react-dnd\": \"^7.7.0\",\n \"react-dnd-html5-backend\": \"^7.7.0\",\n \"react-dom\": \"^16.12.0\",\n \"react-in-viewport\": \"0.0.38\",\n \"react-redux\": \"^7.1.3\",\n \"react-router-config\": \"^5.1.1\",\n \"react-router-dom\": \"~5.2.0\",\n \"react-test-renderer\": \"^16.12.0\",\n \"react-toastify\": \"^6.0.0\",\n \"reactstrap\": \"^8.4.1\",\n \"redux\": \"^4.0.5\",\n \"redux-thunk\": \"^2.3.0\",\n \"reselect\": \"^4.0.0\",\n \"simple-line-icons\": \"^2.4.1\",\n \"typescript\": \"^3.7.5\"\n },\n \"devDependencies\": {\n \"@babel/core\": \"^7.10.2\",\n \"check-prop-types\": \"^1.1.2\",\n \"coveralls\": \"^3.0.9\",\n \"enzyme\": \"^3.11.0\",\n \"enzyme-adapter-react-16\": \"^1.15.2\",\n \"enzyme-to-json\": \"^3.4.4\",\n \"fetch-mock\": \"^9.4.0\",\n \"react-dnd-test-backend\": \"^7.7.0\",\n \"react-dnd-test-utils\": \"^7.4.4\",\n \"react-scripts\": \"^3.3.1\",\n \"redux-mock-store\": \"^1.5.4\"\n },\n \"scripts\": {\n \"start\": \"react-scripts start\",\n \"build\": \"react-scripts build\",\n \"test\": \"react-scripts test\",\n \"test:cov\": \"react-scripts test --coverage\",\n \"test:debug\": \"react-scripts --inspect-brk test --runInBand\",\n \"coveralls\": \"cat ./coverage/lcov.info | node node_modules/.bin/coveralls\",\n \"eject\": \"react-scripts eject\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/negative0/rclone-webui-react/issues\"\n },\n \"eslintConfig\": {\n \"extends\": \"react-app\"\n },\n \"browserslist\": [\n \">0.2%\",\n \"not dead\",\n \"not ie <= 11\",\n \"not op_mini all\"\n ],\n \"resolutions\": {\n \"browserslist\": \"4.6.2\",\n \"caniuse-lite\": \"1.0.30000974\"\n },\n \"jest\": {\n \"collectCoverageFrom\": [\n \"src/**/*.{js,jsx}\",\n \"src/**/**/.{js,jsx}\",\n \"!**/node_modules/**\",\n \"!**/*index.js\",\n \"!src/serviceWorker.js\",\n \"!src/polyfill.js\"\n ]\n }\n}\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- package.json (revision 01ddd2adb079d0e67ca795e10625ebbb0dd4c019) ++++ package.json (date 1592401345849) +@@ -53,7 +53,7 @@ + "devDependencies": { + "@babel/core": "^7.10.2", + "check-prop-types": "^1.1.2", +- "coveralls": "^3.0.9", ++ "coveralls": "^3.1.0", + "enzyme": "^3.11.0", + "enzyme-adapter-react-16": "^1.15.2", + "enzyme-to-json": "^3.4.4", +Index: package-lock.json +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- package-lock.json (revision 01ddd2adb079d0e67ca795e10625ebbb0dd4c019) ++++ package-lock.json (date 1592401347889) +@@ -1,6 +1,6 @@ + { +- "name": "@rclone/rclone-webui", +- "version": "0.0.6", ++ "name": "@rclone/rclone-webui-react", ++ "version": "2.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { +@@ -3366,6 +3366,16 @@ + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, ++ "bindings": { ++ "version": "1.5.0", ++ "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", ++ "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", ++ "dev": true, ++ "optional": true, ++ "requires": { ++ "file-uri-to-path": "1.0.0" ++ } ++ }, + "block-stream": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", +@@ -6826,6 +6836,13 @@ + "schema-utils": "^2.5.0" + } + }, ++ "file-uri-to-path": { ++ "version": "1.0.0", ++ "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", ++ "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", ++ "dev": true, ++ "optional": true ++ }, + "filesize": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz", +@@ -8991,6 +9008,7 @@ + "dev": true, + "optional": true, + "requires": { ++ "bindings": "^1.5.0", + "nan": "^2.12.1" + } + } +@@ -16785,6 +16803,7 @@ + "dev": true, + "optional": true, + "requires": { ++ "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, +@@ -17128,6 +17147,7 @@ + "dev": true, + "optional": true, + "requires": { ++ "bindings": "^1.5.0", + "nan": "^2.12.1" + } + }, +Index: README.md +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+># Rclone Web UI [![Google Summer of Code 19](https://img.shields.io/badge/Google%20Summer%20of%20Code-2019-blue.svg)](https://summerofcode.withgoogle.com/projects/#5104629795258368)\n\n[![CCExtractor](https://img.shields.io/badge/CCExtractor-org-blue.svg)](https://www.ccextractor.org/) [![RClone](https://img.shields.io/badge/RClone-org-blue.svg)](https://rclone.org/)\n## About\n\nThis project is developed as a part of Google Summer of Code '19 under [ccextractor.org](https://ccextractor.org) and [rclone.org](https://rclone.org) by [negative0](https://github.com/negative0).\n\nThis is a reactjs based web UI for the rclone cli project @ [Rclone Website](https://rclone.org/) \n\n### Work Products (GSoC):\n\n- Proposal for developing this project: [here](https://docs.google.com/document/d/1l6OHrM2XemHP-l2_iBdYPdPNVgiSB5t1es_-0ogrty0/edit?usp=sharing)\n- The latest automated build can be found here: http://rclone.github.io/rclone-webui-react\n- GSoC phase I plan: [here](http://good2be.me/blog/gsoc-phase-i.html)\n- The thread for beta testing is here: https://forum.rclone.org/t/beta-testing-webgui-for-rclone/11156\n\n## Intro\n\nThis project can be unstable and is being actively developed. Feel free to create any issues, feature requests or enhancements as you encounter them. \n\n## Build Status\n\n[![Build Status](https://travis-ci.com/rclone/rclone-webui-react.svg?branch=master)](https://travis-ci.com/rclone/rclone-webui-react) \n[![Greenkeeper badge](https://badges.greenkeeper.io/rclone/rclone-webui-react.svg)](https://greenkeeper.io/)\n[![Coverage Status](https://coveralls.io/repos/github/rclone/rclone-webui-react/badge.svg?branch=master)](https://coveralls.io/github/rclone/rclone-webui-react?branch=master)\n\n## Getting Started\n\nThe project currently requires you to install and configure react and npm to run correctly.\nRead more about the project details at [good2be.me](http://good2be.me/blog)\n\n## Running the project through rclone:\n\nIf you have rclone installed, you can easily run this UI through rclone without any additional configuration.\n\nNote: The instructions for installing rclone can be found [here](https://rclone.org/install/).\n\nTo run the web-gui, simply run the following command:\n\n```shell script\n rclone rcd --rc-web-gui --rc-user= --rc-pass= \n```\nThe web-gui should now be available at the url http://localhost:5572\n\n### Parameters:\n--rc-web-gui - run the web-gui\n\n--rc-user - username to be used for login (Optional) default is gui.\n\n--rc-pass - password to be used for login (Optional) if not provided, rclone will generate one for you.\n\n--rc-addr - useful if you are running rclone on a headless machine, specify the private/public IP of your VM for rclone to bind to. (Optional)\n\n--rc-web-gui-update - Force update web-gui to the latest version.\n\n\n\n## Screenshots\n### Dashboard\n![Dashboard](screenshots/dashboard.png)\n\n### Login\n![Login](screenshots/login.png)\n\n### Remote Explorer\n![Explorer](screenshots/remoteexplorer.png)\n\n### Creating config\n![New Config](screenshots/newRemote.png)\n\n## Get the automated script and get running\n\n**Bash users:**\n\nDownload the sh file given here: \n[webui.sh](https://raw.githubusercontent.com/rclone/rclone-webui-react/master/webui.sh)\n\nCopy the file to root folder of rclone.\n\n```\ncp webui.sh /\n```\nFirst of all open the webui.sh\n\nYou need to edit this code to the username and password you would like to use.\n```\nusername=''\npassword=''\n```\nSave this file.\n\nNow you can run the following commands:\n\n- Download the project:\n```\n./webui.sh get\n```\n\n- Build the webui app:\n```\n./webui.sh build\n```\n\n- Run the app with rclone backend:\n```\n./webui.sh run\n```\n\n- At any point, you can update the webui with new changes from the repo (optional):\n\n```\n./webui.sh update\n```\n\n\n**Windows:**\nComing soon\n\n### Get the Project\n```\n git clone https://github.com/rclone/rclone-webui-react\n```\nOR download a zip from the option above.\n\n### Install dependencies\nIf you are using NPM:\n\n**Make sure that you are using the latest LTS version of NPM**\n```\n cd \n npm install \n```\n\nUsing yarn:\n```\n cd \n yarn install\n```\n\n\n### Run the project\n```\n npm start\n```\nOR\n```\n npm run start\n```\n\n### Run tests\n```npm test```OR ```yarn test``` if you want to run all jest tests. \nTest specific environment can be set using setupTests.js\n\n**With Coverage**: ```npm run test:cov```\n\n\n### Run Rclone\nYou have to run rclone with the following flags:\n```\n rclone rcd --rc-user= --rc-pass= --rc-serve\n```\nReplace username and password with your custom username password. This will be required to login to rclone. rc-no-auth is not available due to security concerns.\n\n--rc-serve: It serves the remote objects at localhost:5572/[remoteName:remotePath]/path/to/file. It enables us to download files via the RemoteExplorer through the browser.\n\n## Progress\n\nFor the progress and future implementation details please refer Progress.md\n\n\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- README.md (revision 01ddd2adb079d0e67ca795e10625ebbb0dd4c019) ++++ README.md (date 1592401077375) +@@ -1,13 +1,13 @@ +-# Rclone Web UI [![Google Summer of Code 19](https://img.shields.io/badge/Google%20Summer%20of%20Code-2019-blue.svg)](https://summerofcode.withgoogle.com/projects/#5104629795258368) ++# Rclone Web UI [![Google Summer of Code 19](https://img.shields.io/badge/Google%20Summer%20of%20Code-2019%202020-blue.svg)](https://summerofcode.withgoogle.com/projects/#5104629795258368) + +-[![CCExtractor](https://img.shields.io/badge/CCExtractor-org-blue.svg)](https://www.ccextractor.org/) [![RClone](https://img.shields.io/badge/RClone-org-blue.svg)](https://rclone.org/) ++[![CCExtractor](https://img.shields.io/badge/CCExtractor-org-red.svg)](https://www.ccextractor.org/) [![RClone](https://img.shields.io/badge/RClone-org-blue.svg)](https://rclone.org/) + ## About + +-This project is developed as a part of Google Summer of Code '19 under [ccextractor.org](https://ccextractor.org) and [rclone.org](https://rclone.org) by [negative0](https://github.com/negative0). ++This project is developed as a part of Google Summer of Code 2019 and 2020 under [ccextractor.org](https://ccextractor.org) and [rclone.org](https://rclone.org) by [negative0](https://github.com/negative0). + + This is a reactjs based web UI for the rclone cli project @ [Rclone Website](https://rclone.org/) + +-### Work Products (GSoC): ++### Work Products (GSoC 2019): + + - Proposal for developing this project: [here](https://docs.google.com/document/d/1l6OHrM2XemHP-l2_iBdYPdPNVgiSB5t1es_-0ogrty0/edit?usp=sharing) + - The latest automated build can be found here: http://rclone.github.io/rclone-webui-react +@@ -16,32 +16,49 @@ + + ## Intro + +-This project can be unstable and is being actively developed. Feel free to create any issues, feature requests or enhancements as you encounter them. ++This project is being actively developed. Feel free to create any issues, feature requests or enhancements as you encounter them. + + ## Build Status + +-[![Build Status](https://travis-ci.com/rclone/rclone-webui-react.svg?branch=master)](https://travis-ci.com/rclone/rclone-webui-react) +-[![Greenkeeper badge](https://badges.greenkeeper.io/rclone/rclone-webui-react.svg)](https://greenkeeper.io/) ++![Code scanning](https://github.com/rclone/rclone-webui-react/workflows/Code%20scanning%20-%20action/badge.svg) ++![Node CI](https://github.com/rclone/rclone-webui-react/workflows/Node%20CI/badge.svg) + [![Coverage Status](https://coveralls.io/repos/github/rclone/rclone-webui-react/badge.svg?branch=master)](https://coveralls.io/github/rclone/rclone-webui-react?branch=master) + + ## Getting Started + + The project currently requires you to install and configure react and npm to run correctly. +-Read more about the project details at [good2be.me](http://good2be.me/blog) ++Read more about the project details at [chaitanya.codes](http://chaitanya.codes) + + ## Running the project through rclone: + + If you have rclone installed, you can easily run this UI through rclone without any additional configuration. + +-Note: The instructions for installing rclone can be found [here](https://rclone.org/install/). ++**Note: The instructions for installing rclone can be found [here](https://rclone.org/install/).** + + To run the web-gui, simply run the following command: + + ```shell script +- rclone rcd --rc-web-gui --rc-user= --rc-pass= ++rclone rcd --rc-web-gui --rc-user= --rc-pass= + ``` + The web-gui should now be available at the url http://localhost:5572 + ++You may have to clear the browser local storage if needed, after switching to the older version. ++ ++## Alternatively, you can use the hosted version: ++With every release, we publish it to github-pages. You can directly use it without installing rclone locally. ++ ++Head over to https://rclone.github.io/rclone-webui-react/. And enter the IP address, username and password of rclone rc server. ++ ++While running the rclone rc server, use the following command, ++``` ++rclone rcd --rc-user=abc --rc-pass=abcd --rc-allow-origin="https://rclone.github.io" ++``` ++replace the username and password with your liking. If you are not comfortable with specifying it here, use the .htpasswd option. ++ ++``` ++rclone rcd --rc-allow-origin="https://rclone.github.io" --rc-htpasswd /path/to/.htpasswd ++``` ++ + ### Parameters: + --rc-web-gui - run the web-gui + +@@ -113,9 +130,6 @@ + ``` + + +-**Windows:** +-Coming soon +- + ### Get the Project + ``` + git clone https://github.com/rclone/rclone-webui-react +@@ -132,7 +146,7 @@ + ``` + + Using yarn: +-``` ++```explorer + cd + yarn install + ``` diff --git a/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20__7_26_PM__Default_Changelist_.xml b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20__7_26_PM__Default_Changelist_.xml new file mode 100644 index 000000000..a55344756 --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_Update_at_17_06_20__7_26_PM__Default_Changelist_.xml @@ -0,0 +1,29 @@ + + \ No newline at end of file diff --git a/.idea/shelf/Uncommitted_changes_before_rebase_[Default_Changelist]/shelved.patch b/.idea/shelf/Uncommitted_changes_before_rebase_[Default_Changelist]/shelved.patch new file mode 100644 index 000000000..2ff9e499c --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_rebase_[Default_Changelist]/shelved.patch @@ -0,0 +1,100 @@ +Index: src/utils/Tools.js +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>/**\n * Returns whether the passed in object (obj) is empty or it contains some entries.\n * @param obj {$ObjMap} An object to check if it is empty: it can be an array or a map.\n * @returns {boolean}\n */\nexport function isEmpty(obj) {\n if (Array.isArray(obj)) return obj.length === 0;\n for (let key in obj) {\n if (obj.hasOwnProperty(key))\n return false;\n }\n return true;\n}\n\n/**\n * Converts bytes into MB.\n * @param bytes {number} bytes to be converted.\n * @returns {number}\n */\nexport function bytesToMB(bytes) {\n if (bytes === 0) return 0;\n const mb = bytes / 1024 / 1024;\n\n return mb;\n}\n\n/**\n * Converts bytes to KB.\n * @param bytes {number} bytes to be converted\n * @returns {number}\n */\nexport function bytesToKB(bytes) {\n if (bytes === 0) return 0;\n const kb = bytes / 1024;\n\n return kb;\n}\n\n/**\n * Converts bytes to GB.\n * @param bytes {number} bytes to be converted\n * @returns {number}\n */\nexport function bytesToGB(bytes) {\n if (bytes === 0) return 0;\n const mb = bytes / 1024 / 1024 / 1024;\n\n return mb;\n}\n\n/**\n * Converts bytes per second to Megabytes per second.\n * @param bps {number} bytes per second.\n * @returns {number}\n */\nexport function bpsToMbps(bps) {\n if (bps === 0) return 0;\n const mbps = bytesToMB(bps);\n return mbps;\n}\n\n/**\n * Format bytes to a human readable format.\n * @param bytes {number} bytes to be formatted.\n * @param decimals {number} specifies the precision of numbers after the decimal point.\n * @returns {string}\n */\nexport function formatBytes(bytes, decimals = 2) {\n if (bytes < 1) return '0 B';\n\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];\n}\n\n/**\n * Converts time in seconds to a minute and hour string of the format \"HH:MM:SS hrs\"\n * @param seconds {number} Number of seconds since 00:00:00\n * @returns {string}\n */\nexport function secondsToMinutesHourString(seconds) {\n if (seconds === 0) {\n return `00:00:00 S`;\n }\n let minutes = seconds / 60;\n seconds = seconds % 60;\n let hours = minutes / 60;\n minutes = minutes % 60;\n\n return `${Math.round(hours)}:${Math.round(minutes)}:${Math.round(seconds)} hrs`;\n}\n\n/**\n * Converts seconds to a human readable string with support for year, day, week, minute, seconds.\n * @param seconds\n * @returns {string}\n */\nexport function secondsToStr(seconds) {\n // TIP: to find current time in milliseconds, use:\n // var current_time_milliseconds = new Date().getTime();\n\n function numberEnding(number) {\n return (number > 1) ? 's' : '';\n }\n\n let years = Math.floor(seconds / 31536000);\n if (years) {\n return years + ' year' + numberEnding(years);\n }\n //TODO: Months! Maybe weeks?\n let days = Math.floor((seconds %= 31536000) / 86400);\n if (days) {\n return days + ' day' + numberEnding(days);\n }\n let hours = Math.floor((seconds %= 86400) / 3600);\n if (hours) {\n return hours + ' hour' + numberEnding(hours);\n }\n let minutes = Math.floor((seconds %= 3600) / 60);\n if (minutes) {\n return minutes + ' minute' + numberEnding(minutes);\n }\n seconds = seconds % 60;\n if (seconds) {\n return seconds.toFixed(2) + ' second' + numberEnding(seconds);\n }\n return 'Just now'; //'just now' //or other string you like;\n}\n\n/**\n * Base validator takes in a regex exp and tests an input str against that regex.\n * @param regex\n * @param str\n * @returns {boolean | * | never}\n */\nexport function baseValidator(regex, str) {\n\n return regex.test(str);\n}\n\n/**\n * Validate Size Suffix of the format (off | 1K | 1M | 100G | 10P ) etc\n * @param str {string} String to be validated\n * @returns {boolean|*|never}\n */\nexport function validateSizeSuffix(str) {\n const regex = /^(off|(([0-9]+[.][0-9]+|[0-9]+)([KMGTP])))$/i;\n\n return baseValidator(regex, str);\n}\n\n/**\n * Validate integer without decimal points (0-9)\n * @param str {number|string} The string to be validated.\n * @returns {boolean|*|never}\n */\nexport function validateInt(str) {\n const regex = /^([0-9]+)$/;\n return baseValidator(regex, str);\n}\n\n/**\n * Validate duration hours, minutes, seconds, milliseconds etc.\n * @param str {number|string} The duration to be validated.\n * @returns {boolean|*|never}\n */\nexport function validateDuration(str) {\n const regex = /^(\\d+[h])?(\\d+[m])?(\\d+[s])?(\\d+ms)??$/i;\n return baseValidator(regex, str);\n}\n\n/**\n * Validate the allowed drive name\n * @returns {boolean|*|never}\n * */\nexport function validateDriveName(name) {\n const regex = /^[0-9A-Za-z_-]*$/i;\n return baseValidator(regex, name);\n}\n\nexport function validateURL(url) {\n const regex = /^(?:http(s)?:\\/\\/)?[\\w.-]+(?:\\.[\\w\\.-]+)+[\\w\\-\\._~:/?#[\\]@!\\$&'\\(\\)\\*\\+,;=.]+$/g;\n return baseValidator(regex, url);\n}\n\n/**\n * Opens the specified URL in a new tab and focus on it.\n * @param url {string} URL to be opened.\n */\nexport function openInNewTab(url) {\n let win = window.open(url, '_blank');\n win.focus();\n}\n\n/**\n * Helper function for finding the provider with a given prefix.\n * @param config {$ObjMap} Array of remote configs\n * @param name {string} Specifies the name of the provider to find.\n * @returns {*}\n */\n\nexport function findFromConfig(config, name) {\n const currentConfig = config.find((ele, idx, array) => {\n return (name === ele.Prefix);\n });\n return currentConfig;\n}\n\n\n/**\n * Helper function to add semicolon to the last.\n * Behaviour: if the passed in string does not have a semicolon at last, then insert it.\n * If there is a semicolon in the middle, skip insertion.\n * @param name\n * @returns {string}\n */\nexport function addColonAtLast(name) {\n if (name.indexOf(':') === -1) {\n if (name[name.length - 1] !== \":\") {\n name = name + \":\"\n }\n }\n\n return name;\n}\n\n/**\n * Allowed types for visibility Status modification in file explorer.\n * @type {{Pdf: string, Images: string}}\n */\nconst visibilityAssociation = {\n Images: \"image/jpeg\",\n Pdf: \"application/pdf\",\n};\n\n/**\n * Function to filter the list of files based on the provided visibility status.\n * @param list {$ObjMap}\n * @param filter {string} Specifies the type of files to display eg: Images, Pdf etc.\n * @param checkList {$ObjMap} Provides mimeType matches for every string visibility operation eg: Images: \"image/jpeg\"\n * @returns {$ObjMap}\n */\nexport function changeListVisibility(list, filter, checkList = visibilityAssociation) {\n let acceptType = checkList[filter];\n // console.log(list);\n if (acceptType) {\n let newList = list.filter((item) => {\n return (item.IsDir || item.MimeType === acceptType);\n });\n return newList;\n }\n return list;\n\n}\n\n/**\n * Function to filter the list of files based on the provided search query.\n * Uses linear search for filtering the relevant files.\n * @param list\n * @param searchQuery\n * @returns {*}\n */\nexport function changeSearchFilter(list, searchQuery = \"\") {\n searchQuery = searchQuery.toLowerCase();\n if (searchQuery) {\n let newList = list.filter((item) => {\n return item.Name.toLowerCase().includes(searchQuery);\n });\n return newList;\n }\n return list;\n\n}\n\n/**\n * Checks whether the remoteName is local or not. Returns true if it is a remote local path, false otherwise.\n * Behaviour: if the name starts with \"/\" it is a local name.\n * @param remoteName {string} Name of the remote to check for.\n * @returns {boolean}\n */\nexport function isLocalRemoteName(remoteName) {\n return (remoteName && remoteName !== \"\" && remoteName[0] === \"/\");\n}\n\n/**\n * Group the array items by the given key inside each object.\n *\n * @param xs{T} array of T type\n * @param keyGetter key to select from the T\n * @returns map{T} map with format {key: [...objects with same key]}\n */\n\n// export function groupByKey(xs, key) {\n// return xs.reduce(function(rv, x) {\n// (rv[x[key]] = rv[x[key]] || []).push(x);\n// return rv;\n// }, {});\n// }\nexport function groupByKey(xs, keyGetter) {\n const map = new Map();\n xs.forEach((item) => {\n const key = keyGetter(item);\n const collection = map.get(key);\n if (!collection) {\n map.set(key, [item]);\n } else {\n collection.push(item);\n }\n });\n return map;\n}\n\n/**\n *\n * @param type {string} One of {\"name\", \"size\", \"modified\"}\n * @param ascending {boolean} true for ascending order, false for descending\n * @returns {function(*, *): number}\n */\nexport function getSortCompareFunction(type, ascending) {\n\n // console.log(\"Here\", a,b)\n switch (type) {\n case \"name\":\n return (a, b) => {\n let x, y;\n x = a.Name.toLowerCase();\n y = b.Name.toLowerCase();\n if (x < y) {\n return ascending ? -1 : 1;\n }\n if (x > y) {return ascending ? 1 : -1;}\n return 0;\n }\n case \"size\":\n return (a, b) => {\n let x, y;\n x = a.Size ? a.Size : 0;\n y = b.Size ? b.Size : 0;\n return ascending ? ( x - y ) : ( y - x );\n }\n case \"modified\":\n return (a, b) => {\n let x, y;\n x = new Date(a.ModTime);\n y = new Date(b.ModTime);\n return ascending ? (x - y) : (y - x);\n }\n default:\n break;\n\n\n }\n}\n\nexport function makeUniqueID(length) {\n let result = '';\n const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n const charactersLength = characters.length;\n for (let i = 0; i < length; i++) {\n result += characters.charAt(Math.floor(Math.random() * charactersLength));\n }\n return result;\n}\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/utils/Tools.js (revision 22f550d93c5f69f46deeb8eb065e5bfe9ac158d8) ++++ src/utils/Tools.js (date 1592212322111) +@@ -183,7 +183,7 @@ + } + + export function validateURL(url) { +- const regex = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/g; ++ const regex = /^(?:http(s)?:\/\/)?[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#[\]@!$&'()*+,;=]+$/g; + return baseValidator(regex, url); + } + +Index: package.json +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>{\n \"name\": \"@rclone/rclone-webui\",\n \"version\": \"0.0.6\",\n \"description\": \"A web interface for r-clone\",\n \"author\": \"Chaitanya Bankanhal\",\n \"copyright\": \"\",\n \"license\": \"MIT\",\n \"private\": false,\n \"homepage\": \"./\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git@github.com:negative0/rclone-webui-react.git\"\n },\n \"dependencies\": {\n \"@coreui/coreui\": \"^2.1.16\",\n \"@coreui/coreui-plugin-chartjs-custom-tooltips\": \"^1.3.1\",\n \"@coreui/icons\": \"0.3.0\",\n \"@coreui/react\": \"^2.5.4\",\n \"axios\": \"^0.19.2\",\n \"bootstrap\": \"^4.4.1\",\n \"chart.js\": \"^2.9.3\",\n \"classnames\": \"^2.2.6\",\n \"core-js\": \"^3.6.5\",\n \"flag-icon-css\": \"^3.4.6\",\n \"font-awesome\": \"^4.7.0\",\n \"jquery\": \"^3.5.1\",\n \"lodash\": \"^4.17.14\",\n \"node-sass\": \"^4.14.1\",\n \"package.json\": \"^2.0.1\",\n \"prop-types\": \"^15.7.2\",\n \"rclone-api\": \"^1.0.0\",\n \"react\": \"^16.12.0\",\n \"react-app-polyfill\": \"^1.0.6\",\n \"react-autosuggest\": \"^10.0.0\",\n \"react-awesome-player\": \"^1.0.11\",\n \"react-chartjs-2\": \"^2.9.0\",\n \"react-dnd\": \"^7.7.0\",\n \"react-dnd-html5-backend\": \"^7.7.0\",\n \"react-dom\": \"^16.12.0\",\n \"react-iframe\": \"^1.8.0\",\n \"react-in-viewport\": \"0.0.38\",\n \"react-redux\": \"^7.1.3\",\n \"react-router-config\": \"^5.1.1\",\n \"react-router-dom\": \"~5.2.0\",\n \"react-test-renderer\": \"^16.12.0\",\n \"react-toastify\": \"^6.0.0\",\n \"reactstrap\": \"^8.4.1\",\n \"redux\": \"^4.0.5\",\n \"redux-thunk\": \"^2.3.0\",\n \"reselect\": \"^4.0.0\",\n \"simple-line-icons\": \"^2.4.1\",\n \"typescript\": \"^3.7.5\",\n \"rclone-api\": \"^1.0.0\"\n },\n \"devDependencies\": {\n \"check-prop-types\": \"^1.1.2\",\n \"coveralls\": \"^3.0.9\",\n \"enzyme\": \"^3.11.0\",\n \"enzyme-adapter-react-16\": \"^1.15.2\",\n \"enzyme-to-json\": \"^3.4.4\",\n \"fetch-mock\": \"^9.4.0\",\n \"react-dnd-test-backend\": \"^7.7.0\",\n \"react-dnd-test-utils\": \"^7.4.4\",\n \"react-scripts\": \"^3.3.1\",\n \"redux-mock-store\": \"^1.5.4\"\n },\n \"scripts\": {\n \"start\": \"react-scripts start\",\n \"build\": \"react-scripts build\",\n \"test\": \"react-scripts test\",\n \"test:cov\": \"react-scripts test --coverage\",\n \"test:debug\": \"react-scripts --inspect-brk test --runInBand\",\n \"coveralls\": \"cat ./coverage/lcov.info | node node_modules/.bin/coveralls\",\n \"eject\": \"react-scripts eject\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/negative0/rclone-webui-react/issues\"\n },\n \"eslintConfig\": {\n \"extends\": \"react-app\"\n },\n \"browserslist\": [\n \">0.2%\",\n \"not dead\",\n \"not ie <= 11\",\n \"not op_mini all\"\n ],\n \"resolutions\": {\n \"browserslist\": \"4.6.2\",\n \"caniuse-lite\": \"1.0.30000974\"\n },\n \"jest\": {\n \"collectCoverageFrom\": [\n \"src/**/*.{js,jsx}\",\n \"src/**/**/.{js,jsx}\",\n \"!**/node_modules/**\",\n \"!**/*index.js\",\n \"!src/serviceWorker.js\",\n \"!src/polyfill.js\"\n ]\n }\n}\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- package.json (revision 22f550d93c5f69f46deeb8eb065e5bfe9ac158d8) ++++ package.json (date 1592212154238) +@@ -28,7 +28,7 @@ + "node-sass": "^4.14.1", + "package.json": "^2.0.1", + "prop-types": "^15.7.2", +- "rclone-api": "^1.0.0", ++ "rclone-api": "^1.0.7", + "react": "^16.12.0", + "react-app-polyfill": "^1.0.6", + "react-autosuggest": "^10.0.0", +@@ -49,8 +49,7 @@ + "redux-thunk": "^2.3.0", + "reselect": "^4.0.0", + "simple-line-icons": "^2.4.1", +- "typescript": "^3.7.5", +- "rclone-api": "^1.0.0" ++ "typescript": "^3.7.5" + }, + "devDependencies": { + "check-prop-types": "^1.1.2", +Index: src/views/Explorer/FilesView/FilesView.js +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.BaseRevisionTextPatchEP +<+>import React from \"react\";\nimport axiosInstance from \"../../../utils/API/API\";\nimport {Alert, Col, Container, Row, Spinner, Table} from \"reactstrap\";\nimport {DropTarget} from \"react-dnd\";\nimport FileComponent from \"./FileComponent\";\nimport {ItemTypes} from \"./Constants\";\nimport {toast} from \"react-toastify\";\nimport {\n addColonAtLast,\n changeListVisibility,\n changeSearchFilter,\n getSortCompareFunction,\n isEmpty\n} from \"../../../utils/Tools\";\nimport {connect} from \"react-redux\";\nimport {getFiles} from \"../../../actions/explorerActions\";\nimport {compose} from \"redux\";\nimport {changePath, changeSortFilter, navigateUp} from \"../../../actions/explorerStateActions\";\nimport LinkShareModal from \"../../Base/LinkShareModal/LinkShareModal\";\nimport ScrollableDiv from \"../../Base/ScrollableDiv/ScrollableDiv\";\nimport {FILES_VIEW_HEIGHT} from \"../../../utils/Constants\";\nimport {PROP_CURRENT_PATH, PROP_FS_INFO} from \"../../../utils/RclonePropTypes\";\nimport * as PropTypes from 'prop-types';\nimport ErrorBoundary from \"../../../ErrorHandling/ErrorBoundary\";\nimport {createNewPublicLink, deleteFile, purgeDir} from \"rclone-api\";\nimport {createSelector} from \"reselect\";\n\n/*\n* Start code for react DND\n* */\n\nconst filesTarget = {\n drop(props, monitor, component) {\n if (monitor.didDrop()) return;\n // console.log(\"drop\", props, monitor, monitor.getItem(), component);\n\n let {Name, Path, IsDir, remoteName} = monitor.getItem();\n\n let srcRemoteName = addColonAtLast(remoteName);\n let srcRemotePath = Path;\n let destRemoteName = addColonAtLast(props.currentPath.remoteName);\n let destRemotePath = props.currentPath.remotePath;\n\n // console.log(\"drop:this\", this);\n\n return {\n srcRemoteName,\n srcRemotePath,\n destRemoteName,\n destRemotePath,\n Name,\n IsDir,\n updateHandler: component.updateHandler\n }\n\n },\n canDrop(props, monitor) {\n const {remoteName, remotePath} = monitor.getItem();\n const destRemoteName = props.currentPath.remoteName;\n const destRemotePath = props.currentPath.remotePath;\n if (destRemoteName === remoteName) {\n return destRemotePath !== remotePath;\n }\n return true;\n }\n};\n\nfunction collect(connect, monitor) {\n return {\n connectDropTarget: connect.dropTarget(),\n isOver: monitor.isOver(),\n canDrop: monitor.canDrop()\n }\n}\n\nfunction renderOverlay() {\n return (\n \n );\n}\n\n/*\n* END code for react DND\n* */\n\n// Provides the up button view in the files view\n// function UpButtonComponent({upButtonHandle, gridMode}) {\n// if (gridMode === \"card\") {\n// return (\n// \n// \n// \n// )\n// } else {\n// return (\n// upButtonHandle()} className={\"pointer-cursor\"}>\n// \n// Go Up...\n// );\n// }\n// }\n\n/**\n * FilesView component renders files in the file explorer.\n */\nclass FilesView extends React.PureComponent {\n\n\n constructor(props) {\n super(props);\n this.state = {\n isLoading: false,\n isDownloadProgress: false,\n downloadingItems: 0,\n shouldUpdate: true,\n showLinkShareModal: false,\n generatedLink: \"\",\n\n\n };\n this.handleFileClick = this.handleFileClick.bind(this);\n this.downloadHandle = this.downloadHandle.bind(this);\n this.deleteHandle = this.deleteHandle.bind(this);\n }\n\n closeLinkShareModal = () => {\n this.setState({\n showLinkShareModal: false\n })\n };\n\n showLinkShareModal = () => {\n this.setState({\n showLinkShareModal: true\n\n })\n };\n\n\n handleFileClick(e, item) {\n const {Path, IsDir, IsBucket} = item;\n if (IsDir || IsBucket) {\n this.updateRemotePath(Path, IsDir, IsBucket);\n } else {\n this.downloadHandle(item);\n }\n\n }\n\n updateRemotePath(newRemotePath, IsDir, IsBucket) {\n const {remoteName} = this.props.currentPath;\n\n let updateRemoteName = \"\";\n let updateRemotePath = \"\";\n\n\n if (IsBucket) {\n updateRemoteName = addColonAtLast(remoteName) + newRemotePath;\n updateRemotePath = \"\";\n // backStack.push({remoteName: addColonAtLast(backStack.peek().remoteName) + remotePath, remotePath: \"\"});\n\n } else if (IsDir) {\n updateRemoteName = remoteName;\n updateRemotePath = newRemotePath;\n // backStack.push({remoteName: backStack.peek().remoteName, remotePath: remotePath});\n }\n this.props.changePath(this.props.containerID, updateRemoteName, updateRemotePath);\n }\n\n\n getFilesList() {\n const {remoteName, remotePath} = this.props.currentPath;\n\n this.props.getFiles(remoteName, remotePath);\n\n }\n\n async downloadHandle(item) {\n // let {remoteName, remotePath} = this.props;\n let {remoteName, remotePath} = this.props.currentPath;\n const {fsInfo} = this.props;\n let downloadUrl = \"\";\n if (fsInfo.Features.BucketBased) {\n downloadUrl = `/[${remoteName}]/${remotePath}/${item.Name}`;\n\n } else {\n downloadUrl = `/[${remoteName}:${remotePath}]/${item.Name}`;\n }\n\n this.setState((prevState) => {\n return {\n downloadingItems: prevState.downloadingItems + 1,\n isDownloadProgress: true\n };\n });\n\n let response = await axiosInstance({\n url: downloadUrl,\n method: 'GET',\n responseType: 'blob',\n });\n\n const url = window.URL.createObjectURL(new Blob([response.data]));\n const link = document.createElement('a');\n link.href = url;\n link.setAttribute('download', item.Name);\n document.body.appendChild(link);\n link.click();\n\n this.setState((prevState) => {\n return {\n downloadingItems: prevState.downloadingItems - 1,\n };\n }, () => {\n if (this.state.downloadingItems === 0) {\n this.setState({isDownloadProgress: false})\n }\n });\n }\n\n async deleteHandle(item) {\n let {remoteName} = this.props.currentPath;\n\n const data = {\n fs: addColonAtLast(remoteName),\n remote: item.Path,\n };\n try {\n if (item.IsDir) {\n\n await purgeDir(remoteName, item.Path);\n\n this.updateHandler();\n toast.info(`${item.Name} deleted.`);\n\n } else {\n\n await deleteFile(remoteName, item.Path);\n this.updateHandler();\n toast.info(`${item.Name} deleted.`, {\n autoClose: true\n });\n }\n } catch (e) {\n // console.log(`Error in deleting file`);\n toast.error(`Error deleting file. ${e}`, {\n autoClose: false\n });\n }\n\n }\n\n updateHandler = () => {\n\n // const {remoteName, remotePath} = this.props.currentPath;\n this.getFilesList();\n };\n\n dismissAlert = (_) => {\n this.setState({isDownloadProgress: false});\n };\n\n linkShareHandle = (item) => {\n const {fsInfo} = this.props;\n if (fsInfo.Features.PublicLink) {\n // console.log(\"Sharing link\" + item.Name);\n const {remoteName} = this.props.currentPath;\n createNewPublicLink(remoteName, item.Path)\n .then((res) => {\n // console.log(\"Public Link: \" + res.data.url);\n\n this.setState({\n generatedLink: res.url,\n showLinkShareModal: true\n })\n }, (error) => {\n toast.error(\"Error Generating link: \" + error)\n })\n } else {\n toast.error(\"This remote does not support public link\");\n }\n\n };\n\n pluginHandle = (item) => {\n console.log(item);\n }\n\n getFileComponents = (isDir) => {\n const {files, containerID, gridMode, fsInfo, loadImages} = this.props;\n const {remoteName, remotePath} = this.props.currentPath;\n // console.log(fsInfo, files);\n if (fsInfo && !isEmpty(fsInfo)) {\n return files.reduce((result, item) => {\n let {ID, Name} = item;\n // Using fallback as fileName when the ID is not available (especially for local file system)\n if (ID === undefined) {\n ID = Name;\n }\n if (item.IsDir === isDir) {\n result.push(\n \n\n \n );\n }\n return result;\n }, []);\n }\n };\n\n applySortFilter = (sortFilter) => {\n const {changeSortFilter, containerID} = this.props;\n\n if (this.props.sortFilter === sortFilter) {\n return changeSortFilter(containerID, sortFilter, (this.props.sortFilterAscending !== true));\n } else {\n return changeSortFilter(containerID, sortFilter, true);\n }\n\n };\n\n\n render() {\n const {isLoading, isDownloadProgress, downloadingItems, generatedLink, showLinkShareModal} = this.state;\n const {connectDropTarget, isOver, files, gridMode, canDrop, sortFilter, sortFilterAscending} = this.props;\n const {remoteName} = this.props.currentPath;\n\n if (isLoading || !files) {\n return (
Loading
);\n } else {\n\n\n if (remoteName === \"\") {\n return (
No remote is selected. Select a remote from above to show files.
);\n }\n\n\n let dirComponentMap = this.getFileComponents(true);\n\n let fileComponentMap = this.getFileComponents(false);\n\n let renderElement = \"\";\n\n if (gridMode === \"card\") {\n\n renderElement = (\n\n \n\n \n \n

Directories

\n \n {dirComponentMap}\n \n \n \n

Files

\n \n \n {fileComponentMap}\n \n \n \n\n
\n\n\n
\n\n )\n } else {\n let filterIconClass = \"fa fa-lg fa-arrow-down\";\n if(sortFilterAscending){\n filterIconClass = \"fa fa-lg fa-arrow-up\";\n }\n renderElement = (\n\n \n\n \n\n \n \n \n \n \n \n \n \n \n \n {files.length > 0 ? (\n \n \n \n \n {dirComponentMap}\n \n \n \n {fileComponentMap}\n \n ) :\n \n \n \n }\n \n
this.applySortFilter(\"name\")}>Name {sortFilter === \"name\" &&\n } this.applySortFilter(\"size\")}>Size {sortFilter === \"size\" &&\n } this.applySortFilter(\"modified\")}>Modified {sortFilter === \"modified\" &&\n }Actions
Directories
Files
Files
\n
\n
\n\n\n );\n }\n\n\n return connectDropTarget(\n
\n {isOver && canDrop && renderOverlay()}\n \n\n \n Downloading {downloadingItems} file(s). Please wait.\n \n\n {renderElement}\n\n \n \n
\n );\n }\n }\n\n}\n\nconst propTypes = {\n containerID: PropTypes.string.isRequired,\n currentPath: PROP_CURRENT_PATH.isRequired,\n fsInfo: PROP_FS_INFO,\n gridMode: PropTypes.string,\n searchQuery: PropTypes.string,\n loadImages: PropTypes.bool.isRequired\n};\n\nconst defaultProps = {};\n\n\nFilesView.propTypes = propTypes;\nFilesView.defaultProps = defaultProps;\n\nlet count = 0;\n\nconst getVisibleFiles = createSelector(\n (state, props) => props.containerID,\n (state, props) => state.explorer.currentPaths[props.containerID],\n (state, props) => state.explorer.visibilityFilters[props.containerID],\n (state, props) => state.explorer.sortFilters[props.containerID],\n (state, props) => state.explorer.searchQueries[props.containerID],\n (state, props) => state.explorer.sortFiltersAscending[props.containerID],\n (state, props) => state.remote.files[`${state.explorer.currentPaths[props.containerID].remoteName}-${state.explorer.currentPaths[props.containerID].remotePath}`],\n (containerID, currentPath, visibilityFilter, sortFilter, searchQuery, sortFilterAscending, files) => {\n console.log(\"Files render : \" + (++count));\n files = files.files;\n // Filter according to visibility filters\n if (visibilityFilter && visibilityFilter !== \"\") {\n files = changeListVisibility(files, visibilityFilter);\n }\n\n //Filter according to search query, if any\n if (searchQuery) {\n files = changeSearchFilter(files, searchQuery);\n }\n files.sort(getSortCompareFunction(sortFilter, sortFilterAscending));\n\n return files;\n }\n)\n\nconst mapStateToProps = (state, ownProps) => {\n const {currentPaths, visibilityFilters, gridMode, searchQueries, loadImages, sortFilters, sortFiltersAscending} = state.explorer;\n const {containerID} = ownProps;\n const currentPath = currentPaths[containerID];\n const mgridMode = gridMode[containerID];\n const searchQuery = searchQueries[containerID];\n const mloadImages = loadImages[containerID];\n const sortFilter = sortFilters[containerID];\n const sortFilterAscending = sortFiltersAscending[containerID];\n\n let fsInfo = {};\n const {remoteName, remotePath} = currentPath;\n\n if (currentPath && state.remote.configs) {\n const tempRemoteName = remoteName.split(':')[0];\n if (state.remote.configs[tempRemoteName])\n fsInfo = state.remote.configs[tempRemoteName];\n }\n\n const pathKey = `${remoteName}-${remotePath}`;\n\n let files = state.remote.files[pathKey];\n\n if (files) {\n files = getVisibleFiles(state, ownProps);\n }\n\n // Sort the files\n return {\n files,\n currentPath,\n fsInfo,\n gridMode: mgridMode,\n searchQuery,\n loadImages: mloadImages,\n sortFilter,\n sortFilterAscending\n }\n};\n\nexport default compose(\n connect(\n mapStateToProps, {getFiles, navigateUp, changePath, changeSortFilter}\n ),\n DropTarget(ItemTypes.FILECOMPONENT, filesTarget, collect)\n)(FilesView)\n +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- src/views/Explorer/FilesView/FilesView.js (revision 22f550d93c5f69f46deeb8eb065e5bfe9ac158d8) ++++ src/views/Explorer/FilesView/FilesView.js (date 1592166181263) +@@ -153,6 +153,11 @@ + if (IsDir || IsBucket) { + this.updateRemotePath(Path, IsDir, IsBucket); + } else { ++ // check for compatible plugin ++ // if plugin is found, open using the plugin. If not found, then Open modal ++ // display plugins which can be used with this mime type if plugins are absent ++ // else allow to use plugin. ++ + this.downloadHandle(item); + } + +@@ -231,11 +236,7 @@ + + async deleteHandle(item) { + let {remoteName} = this.props.currentPath; +- +- const data = { +- fs: addColonAtLast(remoteName), +- remote: item.Path, +- }; ++ + try { + if (item.IsDir) { + +Index: package-lock.json +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +--- package-lock.json (revision 22f550d93c5f69f46deeb8eb065e5bfe9ac158d8) ++++ package-lock.json (date 1592212169757) +@@ -13634,9 +13634,9 @@ + } + }, + "rclone-api": { +- "version": "1.0.1", +- "resolved": "https://registry.npmjs.org/rclone-api/-/rclone-api-1.0.1.tgz", +- "integrity": "sha512-B98RvNClUKXoz0kx3be0hQ7rrbG13EszrOg69sL52Eg5Pv2k+Bsy/CScLzZ0Z2IU65qes7wjomaMwT1RgoSgqA==", ++ "version": "1.0.7", ++ "resolved": "https://registry.npmjs.org/rclone-api/-/rclone-api-1.0.7.tgz", ++ "integrity": "sha512-44Eko73NowZUDtQUFTvPDSPztmO1W1q7RL9hJZNP8dKmcizEHIUVO0Fy11gLHDIUeEShB85qH9S0IIwAPV7OSg==", + "requires": { + "axios": "^0.19.2" + } diff --git a/.idea/shelf/Uncommitted_changes_before_rebase__Default_Changelist_.xml b/.idea/shelf/Uncommitted_changes_before_rebase__Default_Changelist_.xml new file mode 100644 index 000000000..2b2dccb75 --- /dev/null +++ b/.idea/shelf/Uncommitted_changes_before_rebase__Default_Changelist_.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/README.md b/README.md index 5d70c2447..8cfe1226d 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# Rclone Web UI [![Google Summer of Code 19](https://img.shields.io/badge/Google%20Summer%20of%20Code-2019-blue.svg)](https://summerofcode.withgoogle.com/projects/#5104629795258368) +# Rclone Web UI [![Google Summer of Code 19](https://img.shields.io/badge/Google%20Summer%20of%20Code-2019%202020-blue.svg)](https://summerofcode.withgoogle.com/projects/#5104629795258368) -[![CCExtractor](https://img.shields.io/badge/CCExtractor-org-blue.svg)](https://www.ccextractor.org/) [![RClone](https://img.shields.io/badge/RClone-org-blue.svg)](https://rclone.org/) +[![CCExtractor](https://img.shields.io/badge/CCExtractor-org-red.svg)](https://www.ccextractor.org/) [![RClone](https://img.shields.io/badge/RClone-org-blue.svg)](https://rclone.org/) ## About -This project is developed as a part of Google Summer of Code '19 under [ccextractor.org](https://ccextractor.org) and [rclone.org](https://rclone.org) by [negative0](https://github.com/negative0). +This project is developed as a part of Google Summer of Code 2019 and 2020 under [ccextractor.org](https://ccextractor.org) and [rclone.org](https://rclone.org) by [negative0](https://github.com/negative0). This is a reactjs based web UI for the rclone cli project @ [Rclone Website](https://rclone.org/) -### Work Products (GSoC): +### Work Products (GSoC 2019): - Proposal for developing this project: [here](https://docs.google.com/document/d/1l6OHrM2XemHP-l2_iBdYPdPNVgiSB5t1es_-0ogrty0/edit?usp=sharing) - The latest automated build can be found here: http://rclone.github.io/rclone-webui-react @@ -16,32 +16,48 @@ This is a reactjs based web UI for the rclone cli project @ [Rclone Website](htt ## Intro -This project can be unstable and is being actively developed. Feel free to create any issues, feature requests or enhancements as you encounter them. +This project is being actively developed. Feel free to create any issues, feature requests or enhancements as you encounter them. ## Build Status -[![Build Status](https://travis-ci.com/rclone/rclone-webui-react.svg?branch=master)](https://travis-ci.com/rclone/rclone-webui-react) -[![Greenkeeper badge](https://badges.greenkeeper.io/rclone/rclone-webui-react.svg)](https://greenkeeper.io/) -[![Coverage Status](https://coveralls.io/repos/github/rclone/rclone-webui-react/badge.svg?branch=master)](https://coveralls.io/github/rclone/rclone-webui-react?branch=master) +![Node CI](https://github.com/rclone/rclone-webui-react/workflows/Node%20CI/badge.svg) +![Code scanning](https://github.com/rclone/rclone-webui-react/workflows/Code%20scanning%20-%20action/badge.svg) ## Getting Started The project currently requires you to install and configure react and npm to run correctly. -Read more about the project details at [good2be.me](http://good2be.me/blog) +Read more about the project details at [chaitanya.codes](http://chaitanya.codes) ## Running the project through rclone: If you have rclone installed, you can easily run this UI through rclone without any additional configuration. -Note: The instructions for installing rclone can be found [here](https://rclone.org/install/). +**Note: The instructions for installing rclone can be found [here](https://rclone.org/install/).** To run the web-gui, simply run the following command: ```shell script - rclone rcd --rc-web-gui --rc-user= --rc-pass= +rclone rcd --rc-web-gui --rc-user= --rc-pass= ``` The web-gui should now be available at the url http://localhost:5572 +You may have to clear the browser local storage if needed, after switching to the older version. + +## Alternatively, you can use the hosted version: +With every release, we publish it to github-pages. You can directly use it without installing rclone locally. + +Head over to https://rclone.github.io/rclone-webui-react/. And enter the IP address, username and password of rclone rc server. + +While running the rclone rc server, use the following command, +``` +rclone rcd --rc-user=abc --rc-pass=abcd --rc-allow-origin="https://rclone.github.io" +``` +replace the username and password with your liking. If you are not comfortable with specifying it here, use the .htpasswd option. + +``` +rclone rcd --rc-allow-origin="https://rclone.github.io" --rc-htpasswd /path/to/.htpasswd +``` + ### Parameters: --rc-web-gui - run the web-gui @@ -66,7 +82,10 @@ The web-gui should now be available at the url http://localhost:5572 ![Explorer](screenshots/remoteexplorer.png) ### Creating config -![New Config](screenshots/newRemote.png) +![New Config](screenshots/config.png) + +### Mounts +![Mounts](screenshots/mounts.png) ## Get the automated script and get running @@ -113,9 +132,6 @@ Now you can run the following commands: ``` -**Windows:** -Coming soon - ### Get the Project ``` git clone https://github.com/rclone/rclone-webui-react @@ -132,7 +148,7 @@ If you are using NPM: ``` Using yarn: -``` +```explorer cd yarn install ``` diff --git a/screenshots/config.png b/screenshots/config.png new file mode 100644 index 000000000..3307d1503 Binary files /dev/null and b/screenshots/config.png differ diff --git a/screenshots/dashboard.png b/screenshots/dashboard.png index 86fc4743d..b0c52462f 100644 Binary files a/screenshots/dashboard.png and b/screenshots/dashboard.png differ diff --git a/screenshots/login.png b/screenshots/login.png index d30a72e28..2beb90990 100644 Binary files a/screenshots/login.png and b/screenshots/login.png differ diff --git a/screenshots/mounts.png b/screenshots/mounts.png new file mode 100644 index 000000000..9ef29ea62 Binary files /dev/null and b/screenshots/mounts.png differ diff --git a/screenshots/remoteexplorer.png b/screenshots/remoteexplorer.png index c124b149b..96779dd7b 100644 Binary files a/screenshots/remoteexplorer.png and b/screenshots/remoteexplorer.png differ