0.4.0 (#47)
41
README.md
|
@ -1,6 +1,6 @@
|
|||
# VueTorrent
|
||||
|
||||
The sleekest looking WEBUI for qBittorrent made with Vuejs!
|
||||
The sleekest looking WEBUI for qBittorrent made with Vuejs! Forked from [WDaan/VueTorrent](https://github.com/WDaan/VueTorrent)
|
||||
|
||||
> Vue, qBitorrent, Vuetify
|
||||
|
||||
|
@ -8,22 +8,25 @@ The sleekest looking WEBUI for qBittorrent made with Vuejs!
|
|||
|
||||
<p align="center">
|
||||
|
||||
<a href="https://imgur.com/kEqGvem.jng"><img src="https://imgur.com/kEqGvem.jpg" title="Desktop" alt="Desktop Screenshot" ></a>
|
||||
<a href="https://imgur.com/hpjuVYb.png"><img src="https://imgur.com/hpjuVYb.png" title="Desktop" alt="Desktop Screenshot" ></a>
|
||||
|
||||
</p>
|
||||
|
||||
| | | |
|
||||
| :------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------------------------: |
|
||||
| <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/eN8qAM9.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/T1A2Bng.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/V83NMPg.png"> |
|
||||
| <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/Zcm98H3.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/OujrH0f.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/OkukwYY.png"> |
|
||||
| <img width="1604" alt="screen shot 2017-015 pm" src="https://imgur.com/QYpNCXs.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/6j5wxhl.png"> | <img width="1604" alt="screen shot 2017-08-07 at 12 18 15 pm" src="https://imgur.com/jnzDKjW.png"> |
|
||||
|
||||
<p align="center">
|
||||
<a href="https://imgur.com/kEqGvem.jpg"><img src="https://imgur.com/c5i63Yz.jpg" title="Desktop" width="300" alt="Mobile" >
|
||||
<a href="https://imgur.com/U3mes8r.png"><img src="https://imgur.com/U3mes8r.png" title="Desktop" width="300" alt="Mobile" >
|
||||
</a>
|
||||
</p>
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
- ### manual
|
||||
|
||||
- Visit the releases page!
|
||||
|
||||
- Download the latest release.zip
|
||||
|
@ -32,6 +35,12 @@ The sleekest looking WEBUI for qBittorrent made with Vuejs!
|
|||
|
||||
- Point your Alternate WEBUI location to the 'vuetorrent' folder
|
||||
|
||||
- ### 'automatic'
|
||||
|
||||
- head over to the 'latest_release' branch
|
||||
- clone it
|
||||
- pull every once in a while
|
||||
|
||||
## Development
|
||||
|
||||
- clone the repo
|
||||
|
@ -44,11 +53,15 @@ The sleekest looking WEBUI for qBittorrent made with Vuejs!
|
|||
|
||||
- viewing sessions status ( down / upload speed, session uploaded / downloaded )
|
||||
|
||||
- adding / removing / pausing / resuming torrents
|
||||
- adding / removing / pausing / resuming / renaming torrents
|
||||
|
||||
- selectively downloading torrents
|
||||
|
||||
- choosing / renaming torrent files
|
||||
|
||||
- filtering powered by Fuse.js!
|
||||
|
||||
- mobile friendly! (maybe not for thousands of torrents...)
|
||||
- mobile friendly! (can be installed as a PWA)
|
||||
|
||||
- torrent info / trackers / peers / content
|
||||
|
||||
|
@ -62,20 +75,10 @@ The sleekest looking WEBUI for qBittorrent made with Vuejs!
|
|||
|
||||
## Contributing
|
||||
|
||||
I'll gladly accept help/pull requests & advice! (this is my first project of this nature, pls be kind 😛 ).
|
||||
|
||||
## FAQ
|
||||
|
||||
- **Why build this??**
|
||||
|
||||
* Why not? Most WebUI's look very dated and now it's no longer necessary to search for a remote control app!
|
||||
I'll gladly accept help/pull requests & advice!
|
||||
|
||||
## Support
|
||||
|
||||
Reach out to me at one of the following places!
|
||||
|
||||
- <a href="https://m.me/WijnsDaan" target="_blank">`Facebook Messenger`</a>
|
||||
|
||||
* Open up an issue 😛
|
||||
|
||||
[<img src="https://cdn.buymeacoffee.com/buttons/lato-blue.png" alt="drawing" width="180"/>](https://www.buymeacoffee.com/wdaan 'Buy me a coffee')
|
||||
|
@ -84,4 +87,6 @@ Reach out to me at one of the following places!
|
|||
|
||||
- Dashboard design heavily inspired by: '[Net Ninja - Vuetify](https://github.com/iamshaunjp/vuetify-playlist)'.
|
||||
|
||||
* This repo '[CzBiX qb-web ](https://github.com/CzBiX/qb-web)'
|
||||
- This repo '[CzBiX qb-web ](https://github.com/CzBiX/qb-web)'
|
||||
|
||||
- Muertocaloh's [fork](https://github.com/muertocaloh/VueTorrent)
|
||||
|
|
109
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vuetorrent",
|
||||
"version": "0.3.7",
|
||||
"version": "0.4.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -845,9 +845,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/polyfill": {
|
||||
"version": "7.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.11.5.tgz",
|
||||
"integrity": "sha512-FunXnE0Sgpd61pKSj2OSOs1D44rKTD3pGOfGilZ6LGrrIH0QEtJlTjqOqdF8Bs98JmjfGhni2BBkTfv9KcKJ9g==",
|
||||
"version": "7.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.12.1.tgz",
|
||||
"integrity": "sha512-X0pi0V6gxLi6lFZpGmeNa4zxtwEmCs42isWLNjZZDE0Y8yVfgu0T2OAHlzBbdYlqbW/YXVvoBHpATEM+goCj8g==",
|
||||
"requires": {
|
||||
"core-js": "^2.6.5",
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
|
@ -1414,20 +1414,20 @@
|
|||
}
|
||||
},
|
||||
"@vue/cli-plugin-pwa": {
|
||||
"version": "4.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.6.tgz",
|
||||
"integrity": "sha512-jMTBo9oR3mkwcqFbtgbKgfuYLZoivDoH5KEwqOkzqamuapOUazAbmlrad0XSF92MKcF8XxWrdZjsEsD/TshDPw==",
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.7.tgz",
|
||||
"integrity": "sha512-mOaEgoLCT2yE8Pdvlz8LhXKqIs3w4xJjDr2dLrOrxh0+OhSpOHJdJ3yHswlgvkxgg0/FGS6t8haj0DfInQ+fYg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@vue/cli-shared-utils": "^4.5.6",
|
||||
"@vue/cli-shared-utils": "^4.5.7",
|
||||
"webpack": "^4.0.0",
|
||||
"workbox-webpack-plugin": "^4.3.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/cli-shared-utils": {
|
||||
"version": "4.5.6",
|
||||
"resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.6.tgz",
|
||||
"integrity": "sha512-p6ePDlEa7Xc0GEt99KDOCwPZtR7UnoEaZLMfwPYU5LAWkdCmtAw8HPAY/WWcjtoiaAkY4k9tz7ZehQasZ9mJxg==",
|
||||
"version": "4.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-4.5.7.tgz",
|
||||
"integrity": "sha512-oicFfx9PvgupxN/LW0s2ktdn1U6bBu8J4lPcW2xj6TtTWUkkxwzis4Tm+XOvgvZnu44+d7216y0Y4TX90q645w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@hapi/joi": "^15.0.1",
|
||||
|
@ -2123,9 +2123,9 @@
|
|||
}
|
||||
},
|
||||
"apexcharts": {
|
||||
"version": "3.21.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.21.0.tgz",
|
||||
"integrity": "sha512-yeulUZCTG57swbJ5oIJIjgfRdIsvmC/2WJanrZxNGhjtZf2B9NaT95pEtbrml1BILJKtMn4VbpXVZp+8Tzmydg==",
|
||||
"version": "3.22.0",
|
||||
"resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.22.0.tgz",
|
||||
"integrity": "sha512-DDh2eXnAEA8GoKU/hdicOaS2jzGehXwv8Bj1djYYudkeQzEdglFoWsVyIxff+Ds7+aUtVAJzd/9ythZuyyIbXQ==",
|
||||
"requires": {
|
||||
"svg.draggable.js": "^2.2.2",
|
||||
"svg.easing.js": "^2.0.0",
|
||||
|
@ -2584,6 +2584,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",
|
||||
|
@ -3128,9 +3138,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz",
|
||||
"integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==",
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz",
|
||||
"integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "~3.1.1",
|
||||
|
@ -3140,7 +3150,7 @@
|
|||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.4.0"
|
||||
"readdirp": "~3.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"braces": {
|
||||
|
@ -3167,6 +3177,15 @@
|
|||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
},
|
||||
"to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
@ -4084,9 +4103,9 @@
|
|||
}
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.1.tgz",
|
||||
"integrity": "sha512-01NCTBg8cuMJG1OQc6PR7T66+AFYiPwgDvdJmvJBn29NGzIG+DIFxPLNjHzwz3cpFIvG+NcwIjP9hSaPVoOaDg=="
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.9.3.tgz",
|
||||
"integrity": "sha512-V+1SyIvkS+HmNbN1G7A9+ERbFTV9KTXu6Oor98v2xHmzzpp52OIJhQuJSTywWuBY5pyAEmlwbCi1Me87n/SLOw=="
|
||||
},
|
||||
"de-indent": {
|
||||
"version": "1.0.2",
|
||||
|
@ -5503,6 +5522,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": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.6.1.tgz",
|
||||
|
@ -7867,9 +7893,9 @@
|
|||
}
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.9.0.tgz",
|
||||
"integrity": "sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ==",
|
||||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
|
||||
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==",
|
||||
"dev": true
|
||||
},
|
||||
"node-gyp": {
|
||||
|
@ -9655,6 +9681,7 @@
|
|||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
|
||||
"integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
}
|
||||
|
@ -10092,9 +10119,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"sass": {
|
||||
"version": "1.26.11",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.26.11.tgz",
|
||||
"integrity": "sha512-W1l/+vjGjIamsJ6OnTe0K37U2DBO/dgsv2Z4c89XQ8ZOO6l/VwkqwLSqoYzJeJs6CLuGSTRWc91GbQFL3lvrvw==",
|
||||
"version": "1.27.0",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.27.0.tgz",
|
||||
"integrity": "sha512-0gcrER56OkzotK/GGwgg4fPrKuiFlPNitO7eUJ18Bs+/NBlofJfMxmxqpqJxjae9vu0Wq8TZzrSyxZal00WDig==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chokidar": ">=2.0.0 <4.0.0"
|
||||
|
@ -10303,12 +10330,12 @@
|
|||
"dev": true
|
||||
},
|
||||
"selfsigned": {
|
||||
"version": "1.10.7",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.7.tgz",
|
||||
"integrity": "sha512-8M3wBCzeWIJnQfl43IKwOmC4H/RAp50S8DF60znzjW5GVqTcSe2vWclt7hmYVPkKPlHWOu5EaWOMZ2Y6W8ZXTA==",
|
||||
"version": "1.10.8",
|
||||
"resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.8.tgz",
|
||||
"integrity": "sha512-2P4PtieJeEwVgTU9QEcwIRDQ/mXJLX8/+I3ur+Pg16nS8oNbrGxEso9NyYWy8NAmXiNl4dlAp5MwoNeCWzON4w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"node-forge": "0.9.0"
|
||||
"node-forge": "^0.10.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
|
@ -11911,9 +11938,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz",
|
||||
"integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ=="
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
|
||||
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
|
||||
},
|
||||
"v8-compile-cache": {
|
||||
"version": "2.1.1",
|
||||
|
@ -12063,9 +12090,9 @@
|
|||
"integrity": "sha512-xo0CEVdkjSjhJoDdLSvoZoQrw/H2BlzB5jrCBKGZNXN2zdZgMuZ9BKrxXDjNP2AxlcCoKc8OahI3F3r3JGLv2Q=="
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "3.4.5",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.5.tgz",
|
||||
"integrity": "sha512-ioRY5QyDpXM9TDjOX6hX79gtaMXSVDDzSlbIlyAmbHNteIL81WIVB2e+jbzV23vzxtoV0krdS2XHm+GxFg+Nxg=="
|
||||
"version": "3.4.7",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.7.tgz",
|
||||
"integrity": "sha512-CbHXue5BLrDivOk5O4eZ0WT4Yj8XwdXa4kCnsEIOzYUPF/07ZukayA2jGxDCJxLc9SgVQX9QX0OuGOwGlVB4Qg=="
|
||||
},
|
||||
"vue-style-loader": {
|
||||
"version": "4.1.2",
|
||||
|
@ -12117,9 +12144,9 @@
|
|||
}
|
||||
},
|
||||
"vuetify": {
|
||||
"version": "2.3.12",
|
||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.12.tgz",
|
||||
"integrity": "sha512-FSt1pzpf0/Lh0xuctAPB7RiLbUl7bzVc7ejbXLLhfmgm7zD7yabuhVYuyVda/SzokjZMGS3j1lNu2lLfdrz0oQ=="
|
||||
"version": "2.3.14",
|
||||
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.3.14.tgz",
|
||||
"integrity": "sha512-1Ys1MreJQOL/Ddp3YotBi1SlC2+1A0/RVkDXX3Azspt8incPdAnNB0JyChHiJ/TM+L+KSA7T4EXF9YDrCWENmg=="
|
||||
},
|
||||
"vuex": {
|
||||
"version": "3.5.1",
|
||||
|
@ -12283,6 +12310,7 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
|
@ -12591,6 +12619,7 @@
|
|||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"bindings": "^1.5.0",
|
||||
"nan": "^2.12.1"
|
||||
}
|
||||
},
|
||||
|
|
18
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vuetorrent",
|
||||
"version": "0.3.7",
|
||||
"version": "0.4.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
|
@ -8,30 +8,30 @@
|
|||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.11.5",
|
||||
"apexcharts": "^3.21.0",
|
||||
"@babel/polyfill": "^7.12.1",
|
||||
"apexcharts": "^3.22.0",
|
||||
"axios": "^0.19.2",
|
||||
"core-js": "^3.6.4",
|
||||
"dayjs": "^1.9.1",
|
||||
"dayjs": "^1.9.3",
|
||||
"fuse.js": "^6.4.1",
|
||||
"lodash": "^4.17.20",
|
||||
"register-service-worker": "^1.7.1",
|
||||
"uuid": "^8.3.0",
|
||||
"uuid": "^8.3.1",
|
||||
"vue": "^2.6.12",
|
||||
"vue-apexcharts": "^1.6.0",
|
||||
"vue-context": "^5.2.0",
|
||||
"vue-observe-visibility": "^0.4.6",
|
||||
"vue-router": "^3.4.5",
|
||||
"vue-router": "^3.4.7",
|
||||
"vue-toastification": "^1.7.8",
|
||||
"vue2-perfect-scrollbar": "^1.5.0",
|
||||
"vuetify": "^2.3.12",
|
||||
"vuetify": "^2.3.14",
|
||||
"vuex": "^3.5.1",
|
||||
"vuex-persist": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.3.0",
|
||||
"@vue/cli-plugin-eslint": "~4.3.0",
|
||||
"@vue/cli-plugin-pwa": "^4.5.6",
|
||||
"@vue/cli-plugin-pwa": "^4.5.7",
|
||||
"@vue/cli-service": "~4.3.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
|
@ -40,7 +40,7 @@
|
|||
"eslint-plugin-vue": "^6.2.2",
|
||||
"fibers": "^5.0.0",
|
||||
"node-sass": "^4.14.1",
|
||||
"sass": "^1.26.11",
|
||||
"sass": "^1.27.0",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-cli-plugin-vuetify": "^2.0.7",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
|
|
|
@ -4,7 +4,7 @@ build_and_copy(){
|
|||
npm run build
|
||||
VERSION=$(jq -r .version package.json)
|
||||
mkdir ../vuetorrent-release
|
||||
git clone git@github.com:WDaan/VueTorrent.git ../vuetorrent-release
|
||||
git clone https://github.com/wdaan/VueTorrent.git ../vuetorrent-release
|
||||
cd ../vuetorrent-release
|
||||
git checkout latest-release
|
||||
git pull
|
||||
|
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 91 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 7.3 KiB |
|
@ -28,7 +28,9 @@ export default {
|
|||
methods: {
|
||||
async checkAuthenticated() {
|
||||
const res = await qbit.login()
|
||||
this.$store.commit('LOGIN', res === 'Ok.')
|
||||
const authenticated = res === 'Ok.'
|
||||
this.$store.commit('LOGIN', authenticated)
|
||||
if (!authenticated && this.$router.currentRoute.name !== 'login') this.$router.push('login')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import '~vuetify/src/styles/styles.sass';
|
||||
@import '../styles/variables.scss';
|
||||
|
||||
@mixin dialog-title {
|
||||
@include theme(v-card) using($material) {
|
||||
|
@ -18,58 +19,58 @@
|
|||
}
|
||||
|
||||
.sideborder.done {
|
||||
border-left: 4px solid #3cd1c2;
|
||||
border-left: 4px solid #{$torrent-done};
|
||||
}
|
||||
.sideborder.busy {
|
||||
border-left: 4px solid #ffaa2c;
|
||||
.sideborder.downloading {
|
||||
border-left: 4px solid #{$torrent-downloading};
|
||||
}
|
||||
.sideborder.fail {
|
||||
border-left: 4px solid #f83e70;
|
||||
border-left: 4px solid #{$torrent-fail};
|
||||
}
|
||||
.sideborder.paused {
|
||||
border-left: 4px solid #cfd8dc;
|
||||
border-left: 4px solid #{$torrent-paused};
|
||||
}
|
||||
.sideborder.queued {
|
||||
border-left: 4px solid #2e5eaa;
|
||||
border-left: 4px solid #{$torrent-queued};
|
||||
}
|
||||
.sideborder.seeding {
|
||||
border-left: 4px solid #26a69a;
|
||||
border-left: 4px solid #{$torrent-seeding};
|
||||
}
|
||||
.sideborder.checking {
|
||||
border-left: 4px solid #ff7043;
|
||||
border-left: 4px solid #{$torrent-checking};
|
||||
}
|
||||
.sideborder.stalled {
|
||||
border-left: 4px solid #81c784;
|
||||
border-left: 4px solid #{$torrent-stalled};
|
||||
}
|
||||
.sideborder.metadata {
|
||||
border-left: 4px solid #7e57c2;
|
||||
border-left: 4px solid #{$torrent-metadata};
|
||||
}
|
||||
.v-chip.done {
|
||||
background: #3cd1c2 !important;
|
||||
background: #{$torrent-done} !important;
|
||||
}
|
||||
.v-chip.busy {
|
||||
background: #ffaa2c !important;
|
||||
.v-chip.downloading {
|
||||
background: #{$torrent-downloading} !important;
|
||||
}
|
||||
.v-chip.fail {
|
||||
background: #f83e70 !important;
|
||||
background: #{$torrent-fail}!important;
|
||||
}
|
||||
.v-chip.paused {
|
||||
background: #cfd8dc !important;
|
||||
background: #{$torrent-paused} !important;
|
||||
}
|
||||
.v-chip.queued {
|
||||
background: #2e5eaa !important;
|
||||
background: #{$torrent-queued} !important;
|
||||
}
|
||||
.v-chip.seeding {
|
||||
background: #26a69a !important;
|
||||
background: #{$torrent-seeding} !important;
|
||||
}
|
||||
.v-chip.checking {
|
||||
background: #ff7043 !important;
|
||||
background: #{$torrent-checking} !important;
|
||||
}
|
||||
.v-chip.stalled {
|
||||
background: #81c784 !important;
|
||||
background: #{$torrent-stalled} !important;
|
||||
}
|
||||
.v-chip.metadata {
|
||||
background: #7e57c2 !important;
|
||||
background: #{$torrent-metadata} !important;
|
||||
}
|
||||
.noselect {
|
||||
-webkit-touch-callout: none; /* iOS Safari */
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<v-row no-gutters>
|
||||
<v-col ref="fileZone">
|
||||
<v-file-input
|
||||
v-if="!url"
|
||||
v-model="files"
|
||||
color="deep-purple accent-4"
|
||||
counter
|
||||
|
@ -44,6 +45,7 @@
|
|||
</template>
|
||||
</v-file-input>
|
||||
<v-text-field
|
||||
v-if="files.length == 0"
|
||||
label="URL"
|
||||
prepend-icon="mdi-link"
|
||||
:rows="
|
||||
|
@ -62,19 +64,29 @@
|
|||
clearable
|
||||
label="Category"
|
||||
prepend-icon="tag"
|
||||
@input="categoryChanged"
|
||||
></v-combobox>
|
||||
|
||||
<v-text-field
|
||||
:disabled="autoTMM"
|
||||
v-model="directory"
|
||||
:placeholder="savepath"
|
||||
label="Download Directory"
|
||||
prepend-icon="folder"
|
||||
></v-text-field>
|
||||
|
||||
<v-row no-gutters>
|
||||
<v-flex xs12 sm6>
|
||||
<v-checkbox
|
||||
v-model="autoTMM"
|
||||
label="Automatic Torrent Management"
|
||||
></v-checkbox>
|
||||
</v-flex>
|
||||
<v-flex xs12 sm6>
|
||||
<v-checkbox
|
||||
v-model="skip_checking"
|
||||
label="Skip hash check"
|
||||
></v-checkbox>
|
||||
</v-flex>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
@ -108,6 +120,7 @@ export default {
|
|||
files: [],
|
||||
category: null,
|
||||
directory: '',
|
||||
autoTMM: true,
|
||||
skip_checking: false,
|
||||
inputRules: [
|
||||
v =>
|
||||
|
@ -125,14 +138,11 @@ export default {
|
|||
submit() {
|
||||
if (this.files.length || this.url) {
|
||||
let torrents = []
|
||||
let params = { urls: null, autoTMM: true }
|
||||
let params = { urls: null, autoTMM: this.autoTMM }
|
||||
if (this.files.length) torrents.push(...this.files)
|
||||
if (this.url) params.urls = this.url
|
||||
if (this.category) params.category = this.category
|
||||
if (this.directory) {
|
||||
params.savepath = this.directory
|
||||
params.autoTMM = false
|
||||
}
|
||||
if (!this.autoTMM) params.savepath = this.directory
|
||||
if (this.skip_checking) params.skip_checking = this.skip_checking
|
||||
|
||||
qbit.addTorrents(params, torrents)
|
||||
|
@ -142,6 +152,9 @@ export default {
|
|||
this.$store.commit('DELETE_MODAL', this.guid)
|
||||
}
|
||||
},
|
||||
categoryChanged() {
|
||||
if (this.autoTMM) this.directory = this.savepath
|
||||
},
|
||||
resetForm() {
|
||||
this.url = null
|
||||
this.files = []
|
||||
|
@ -162,8 +175,8 @@ export default {
|
|||
let savePath = this.getSettings().save_path
|
||||
if (this.category) {
|
||||
savePath += this.category
|
||||
let category_path = this.getCategories()[this.category].savePath
|
||||
if (category_path) savePath = category_path
|
||||
let category = this.getCategories()[this.category]
|
||||
if (category && category.savePath) savePath = category.savePath
|
||||
}
|
||||
return savePath
|
||||
},
|
||||
|
@ -174,6 +187,7 @@ export default {
|
|||
created() {
|
||||
this.$store.commit('FETCH_SETTINGS')
|
||||
this.$store.commit('FETCH_CATEGORIES')
|
||||
this.directory = this.savepath
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
88
src/components/Modals/ChangeLocationModal.vue
Normal file
|
@ -0,0 +1,88 @@
|
|||
<template>
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
scrollable
|
||||
:width="dialogWidth"
|
||||
:fullscreen="phoneLayout"
|
||||
>
|
||||
<v-card style="overflow: hidden !important">
|
||||
<v-container :style="{ height: phoneLayout ? '100vh' : '' }">
|
||||
<v-card-title class="pb-0 justify-center">
|
||||
<h2>Change Torrent Location</h2>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
label="Torrent Name"
|
||||
prepend-icon="insert_drive_file"
|
||||
readonly
|
||||
v-model="torrent.name"
|
||||
/>
|
||||
<v-text-field
|
||||
label="Directory"
|
||||
prepend-icon="folder"
|
||||
v-model="newPath"
|
||||
v-on:keydown.enter="setLocation"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<div>
|
||||
<v-card-actions class="justify-center">
|
||||
<v-btn color="success" @click="setLocation">Save</v-btn>
|
||||
</v-card-actions>
|
||||
</div>
|
||||
</v-container>
|
||||
<v-fab-transition v-if="phoneLayout">
|
||||
<v-btn @click="close" color="red" dark absolute bottom right>
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
</v-fab-transition>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import { Modal, FullScreenModal } from '@/mixins'
|
||||
import qbit from '@/services/qbit'
|
||||
export default {
|
||||
name: 'ChangeLocationModal',
|
||||
mixins: [Modal, FullScreenModal],
|
||||
props: {
|
||||
hash: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
newPath: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getTorrent']),
|
||||
dialogWidth() {
|
||||
return this.phoneLayout ? '100%' : '750px'
|
||||
},
|
||||
torrent() {
|
||||
return this.getTorrent(this.hash)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setLocation() {
|
||||
qbit.setTorrentLocation([this.hash], this.newPath)
|
||||
this.close()
|
||||
},
|
||||
close() {
|
||||
this.$store.commit('DELETE_MODAL', this.guid)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.newPath = this.torrent.savePath
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -10,7 +10,7 @@
|
|||
<v-card-title class="pb-0 justify-center primary">
|
||||
<h2 class="white--text">Settings</h2>
|
||||
</v-card-title>
|
||||
<v-tabs v-model="tab" background-color="primary" center-active>
|
||||
<v-tabs v-model="tab" background-color="primary" dark fixed-tabs>
|
||||
<v-tab href="#downloads">Downloads</v-tab>
|
||||
<v-tab href="#bittorrent">BitTorrent</v-tab>
|
||||
<v-tab href="#webui">WebUI</v-tab>
|
||||
|
|
|
@ -51,6 +51,29 @@
|
|||
Global Remove/Resume/Pause Buttons</template
|
||||
>
|
||||
</v-switch>
|
||||
<v-switch
|
||||
class="v-input--reverse v-input--expand pa-0 ma-0"
|
||||
inset
|
||||
v-model="denseDashboard"
|
||||
color="green_accent"
|
||||
>
|
||||
<template #label>
|
||||
Dense version of the dasbhoard</template
|
||||
>
|
||||
</v-switch>
|
||||
<v-row dense>
|
||||
<v-col cols="10" sm="10" md="10">
|
||||
<p class="subtitle-1">Pagination size:</p>
|
||||
</v-col>
|
||||
<v-col cols="2" sm="2" md="2">
|
||||
<v-select
|
||||
class="pa-0 ma-0"
|
||||
color="green_accent"
|
||||
:items="paginationSizes"
|
||||
v-model="paginationSize"
|
||||
></v-select>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col cols="10" sm="10" md="11">
|
||||
<p class="subtitle-1">Current Version:</p>
|
||||
|
@ -71,6 +94,11 @@
|
|||
import { mapState, mapGetters } from 'vuex'
|
||||
export default {
|
||||
name: 'VueTorrent',
|
||||
data() {
|
||||
return {
|
||||
paginationSizes: [5, 15, 30, 50]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['webuiSettings']),
|
||||
...mapGetters(['getAppVersion']),
|
||||
|
@ -114,6 +142,22 @@ export default {
|
|||
this.webuiSettings.showGlobalRemoveResumePause = val
|
||||
}
|
||||
},
|
||||
denseDashboard: {
|
||||
get() {
|
||||
return this.webuiSettings.denseDashboard
|
||||
},
|
||||
set(val) {
|
||||
this.webuiSettings.denseDashboard = val
|
||||
}
|
||||
},
|
||||
paginationSize: {
|
||||
get() {
|
||||
return this.webuiSettings.paginationSize
|
||||
},
|
||||
set(val) {
|
||||
this.webuiSettings.paginationSize = val
|
||||
}
|
||||
},
|
||||
version() {
|
||||
return this.getAppVersion()
|
||||
}
|
||||
|
|
|
@ -46,10 +46,10 @@ export default {
|
|||
mixins: [Modal],
|
||||
data() {
|
||||
return {
|
||||
sortProperty: { value: '', name: 'Default' },
|
||||
reverse: false,
|
||||
sortProperty: { value: 'added_on', name: 'Default' },
|
||||
reverse: true,
|
||||
options: [
|
||||
{ value: 'default', name: 'Default' },
|
||||
{ value: 'added_on', name: 'Default' },
|
||||
{ value: 'availability', name: 'Availability' },
|
||||
{ value: 'category', name: 'Category' },
|
||||
{ value: 'completed', name: 'Completed' },
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<v-card>
|
||||
<v-container style="min-height: 200px" :class="`pa-0 project done`">
|
||||
<v-card-title class="justify-center">
|
||||
<h2>Create New Category</h2>
|
||||
<h2>{{ hasInitialCategory ? 'Edit' : 'Create New' }} Category</h2>
|
||||
</v-card-title>
|
||||
|
||||
<v-form ref="categoryForm" class="px-6 mt-3">
|
||||
|
@ -69,8 +69,7 @@ export default {
|
|||
hasInitialCategory() {
|
||||
return (
|
||||
this.initialCategory &&
|
||||
this.initialCategory.name &&
|
||||
this.initialCategory.savePath
|
||||
this.initialCategory.name
|
||||
)
|
||||
}
|
||||
},
|
||||
|
|
|
@ -3,11 +3,13 @@
|
|||
<perfect-scrollbar>
|
||||
<v-card-text style="max-height: 500px; min-height: 400px">
|
||||
<v-treeview
|
||||
v-model="tree"
|
||||
v-model="selected"
|
||||
:items="fileTree"
|
||||
:open.sync="opened"
|
||||
activatable
|
||||
item-key="name"
|
||||
open-on-click
|
||||
selectable
|
||||
item-key="fullName"
|
||||
open-all
|
||||
>
|
||||
<template v-slot:prepend="{ item, open }">
|
||||
<v-icon v-if="!item.icon">
|
||||
|
@ -15,6 +17,14 @@
|
|||
</v-icon>
|
||||
<v-icon v-else>{{ item.icon }}</v-icon>
|
||||
</template>
|
||||
<template v-slot:label="{ item }">
|
||||
<span v-if="!item.editing">{{item.name}}</span>
|
||||
<v-text-field
|
||||
autofocus
|
||||
v-if="item.editing"
|
||||
v-model="item.newName"
|
||||
/>
|
||||
</template>
|
||||
<template v-slot:append="{ item }">
|
||||
<span v-if="!item.icon"
|
||||
>{{ item.children.length }} Files</span
|
||||
|
@ -22,6 +32,33 @@
|
|||
<div v-else>
|
||||
<span>[{{ item.size }}]</span>
|
||||
<span class="ml-4">{{ item.progress }}%</span>
|
||||
<v-btn
|
||||
v-if="!item.editing"
|
||||
class="mb-2 ml-4"
|
||||
x-small
|
||||
fab
|
||||
@click="edit(item)"
|
||||
>
|
||||
<v-icon>mdi-pencil</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="item.editing"
|
||||
class="mb-2 ml-4"
|
||||
x-small
|
||||
fab
|
||||
@click="renameFile(item)"
|
||||
>
|
||||
<v-icon>save</v-icon>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
v-if="item.editing"
|
||||
class="mb-2 ml-2"
|
||||
x-small
|
||||
fab
|
||||
@click="togleEditing(item)"
|
||||
>
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
</div>
|
||||
</template>
|
||||
</v-treeview>
|
||||
|
@ -41,7 +78,8 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
tree: [],
|
||||
opened: null,
|
||||
selected: [],
|
||||
treeData: null
|
||||
}
|
||||
},
|
||||
|
@ -56,7 +94,42 @@ export default {
|
|||
methods: {
|
||||
async getTorrentFiles() {
|
||||
const { data } = await qbit.getTorrentFiles(this.hash)
|
||||
data.forEach((d, i) => {
|
||||
d.id = i
|
||||
d.name = d.name.replace('.unwanted/', '')
|
||||
})
|
||||
this.treeData = data
|
||||
},
|
||||
async changeFilePriorities(newValue, oldValue) {
|
||||
if (newValue.length == oldValue.length) return
|
||||
|
||||
const filesToExclude = oldValue.filter(f => !newValue.includes(f))
|
||||
.map(name => this.treeData.find(f => f.name === name))
|
||||
.filter(f => f.priority !== 0)
|
||||
.map(f => f.id)
|
||||
const filesToInclude = newValue.filter(f => !oldValue.includes(f))
|
||||
.map(name => this.treeData.find(f => f.name === name))
|
||||
.filter(f => f.priority === 0)
|
||||
.map(f => f.id)
|
||||
|
||||
if (filesToExclude.length)
|
||||
await qbit.setTorrentFilePriority(this.hash, filesToExclude, 0)
|
||||
if (filesToInclude.length)
|
||||
await qbit.setTorrentFilePriority(this.hash, filesToInclude, 1)
|
||||
if (filesToExclude.length || filesToInclude.length)
|
||||
await this.getTorrentFiles()
|
||||
},
|
||||
togleEditing(item) {
|
||||
item.editing = !item.editing
|
||||
},
|
||||
edit(item){
|
||||
item.newName = item.name
|
||||
this.togleEditing(item)
|
||||
},
|
||||
renameFile(item) {
|
||||
qbit.renameFile(this.hash, item.id, item.newName)
|
||||
item.name = item.newName
|
||||
this.togleEditing(item)
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@ -64,10 +137,21 @@ export default {
|
|||
if (active) {
|
||||
this.getTorrentFiles()
|
||||
}
|
||||
},
|
||||
selected(newValue, oldValue) {
|
||||
this.changeFilePriorities(newValue, oldValue)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getTorrentFiles()
|
||||
this.getTorrentFiles().then(() => {
|
||||
this.opened = [].concat(
|
||||
...this.treeData.map(file => file.name.split('/'))
|
||||
.filter(f => f.splice(-1, 1)))
|
||||
.filter((f, index, self) => index === self.indexOf(f)
|
||||
)
|
||||
this.selected = this.treeData.filter(file => file.priority !== 0)
|
||||
.map(file => file.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<v-card flat>
|
||||
<perfect-scrollbar>
|
||||
<v-card-text
|
||||
class="pa-0"
|
||||
style="font-size: 1.1em; max-height: 500px; min-height: 400px"
|
||||
style="font-size: 1.1em; min-height: 400px;"
|
||||
:style="{ maxHeight: phoneLayout ? '' : '500px' }"
|
||||
>
|
||||
<v-simple-table>
|
||||
<tbody>
|
||||
|
@ -13,6 +13,12 @@
|
|||
{{ torrent.name }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">Directory</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
{{ torrent.savePath }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="margin-top: 10px !important">
|
||||
<td class="grey--text">hash</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
|
@ -32,13 +38,25 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">Download</td>
|
||||
<td class="grey--text">Uploaded:</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
{{ torrent.uploaded }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">Ratio</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
{{ torrent.ratio }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">Download Speed</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
{{ torrent.dlspeed }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">Upload</td>
|
||||
<td class="grey--text">Upload Speed</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
{{ torrent.upspeed }}
|
||||
</td>
|
||||
|
@ -68,39 +86,21 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">Ratio</td>
|
||||
<td class="grey--text">Added on</td>
|
||||
<td class="torrentmodaltext--text">
|
||||
{{ torrent.ratio }}%
|
||||
{{ torrent.added_on }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">Tags</td>
|
||||
<td v-if="torrent.tags">
|
||||
{{ torrent.tags.join(',') }}
|
||||
</td>
|
||||
<td v-else>None</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">Status</td>
|
||||
<v-chip
|
||||
small
|
||||
:class="`${torrent.state} white--text my-2 caption`"
|
||||
:class="`${torrent.state.toLowerCase()} white--text my-2 caption`"
|
||||
>{{ torrent.state }}</v-chip
|
||||
>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-simple-table>
|
||||
|
||||
<v-flex class="pt-3 pb-4">
|
||||
<v-progress-linear
|
||||
height="5"
|
||||
stream
|
||||
rounded
|
||||
color="cyan darken-1"
|
||||
background-color="cyan lighten-3"
|
||||
:buffer-value="torrent.progress"
|
||||
></v-progress-linear>
|
||||
</v-flex>
|
||||
</v-card-text>
|
||||
</perfect-scrollbar>
|
||||
</v-card>
|
||||
|
@ -117,6 +117,9 @@ export default {
|
|||
...mapGetters(['getTorrent']),
|
||||
torrent() {
|
||||
return this.getTorrent(this.hash)
|
||||
},
|
||||
phoneLayout() {
|
||||
return this.$vuetify.breakpoint.xsOnly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
<v-card-title class="pb-0 justify-center primary">
|
||||
<h2 class="white--text">Torrent Detail</h2>
|
||||
</v-card-title>
|
||||
<v-tabs v-model="tab" background-color="primary" center-active>
|
||||
<v-tabs v-model="tab" background-color="primary" dark fixed-tabs>
|
||||
<v-tab href="#info">Info</v-tab>
|
||||
<v-tab href="#trackers">Trackers</v-tab>
|
||||
<v-tab href="#peers">Peers</v-tab>
|
||||
|
|
|
@ -92,7 +92,7 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
drawer: false
|
||||
drawer: this.$vuetify.breakpoint.mdAndUp
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -93,6 +93,7 @@ export default {
|
|||
},
|
||||
removeTorrents() {
|
||||
qbit.deleteTorrents(this.selected_torrents, false)
|
||||
this.$store.commit('RESET_SELECTED')
|
||||
},
|
||||
addModal(name) {
|
||||
this.createModal(name)
|
||||
|
|
|
@ -7,14 +7,12 @@
|
|||
@click.native="selectTorrent(torrent.hash)"
|
||||
@dblclick.prevent="showInfo(torrent.hash)"
|
||||
>
|
||||
<v-tooltip top>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-layout v-on="on" row wrap :class="style">
|
||||
<v-flex xs12 sm2 md3>
|
||||
<v-layout row wrap :class="style">
|
||||
<v-flex xs12 class="mb-4">
|
||||
<div class="caption grey--text">Torrent title</div>
|
||||
<div class="truncate">{{ torrent.name }}</div>
|
||||
</v-flex>
|
||||
<v-flex xs6 sm1 md1 class="mr-2">
|
||||
<v-flex xs6 sm1 md1>
|
||||
<div class="caption grey--text">Size</div>
|
||||
<div>
|
||||
{{ torrent.size | getDataValue }}
|
||||
|
@ -23,16 +21,33 @@
|
|||
}}</span>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs5 sm1 md1 class="mr-2">
|
||||
<v-flex xs12 sm1 md1 class="mr-4">
|
||||
<div class="caption grey--text">Done</div>
|
||||
<v-progress-linear
|
||||
v-model="torrent.progress"
|
||||
height="20"
|
||||
:style="phoneLayout ? '' : 'width: 80%;'"
|
||||
:color="`torrent-${state}-color`" >
|
||||
<span
|
||||
class="caption"
|
||||
>
|
||||
{{ torrent.progress }}%
|
||||
</span>
|
||||
</v-progress-linear>
|
||||
</v-flex>
|
||||
<v-flex xs6 sm1 md1>
|
||||
<div class="caption grey--text">Ratio</div>
|
||||
<div>
|
||||
{{ torrent.dloaded | getDataValue }}
|
||||
<span class="caption grey--text">{{
|
||||
torrent.dloaded | getDataUnit
|
||||
}}</span>
|
||||
{{ torrent.ratio }}
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs6 sm1 md1 class="mr-2">
|
||||
<v-flex xs6 sm1 md1>
|
||||
<div class="caption grey--text">ETA</div>
|
||||
<div>
|
||||
{{ torrent.eta }}
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs6 sm1 md1>
|
||||
<div class="caption grey--text">Download</div>
|
||||
<div>
|
||||
{{ torrent.dlspeed | getDataValue }}
|
||||
|
@ -41,7 +56,7 @@
|
|||
}}</span>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs5 sm1 md1 class="mr-2">
|
||||
<v-flex xs6 sm1 md1>
|
||||
<div class="caption grey--text">Upload</div>
|
||||
<div>
|
||||
{{ torrent.upspeed | getDataValue }}
|
||||
|
@ -50,13 +65,7 @@
|
|||
}}</span>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs6 sm1 md1 class="mr-2">
|
||||
<div class="caption grey--text">ETA</div>
|
||||
<div>
|
||||
{{ torrent.eta | formatEta({ dayLimit: 100 }) }}
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs5 sm1 md1 class="mr-2">
|
||||
<v-flex xs6 sm1 md1>
|
||||
<div class="caption grey--text">Peers</div>
|
||||
<div>
|
||||
{{ torrent.num_leechs }}
|
||||
|
@ -65,7 +74,7 @@
|
|||
>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs5 sm1 md1 class="mr-2">
|
||||
<v-flex xs6 sm1 md1>
|
||||
<div class="caption grey--text">Seeds</div>
|
||||
<div>
|
||||
{{ torrent.num_seeds }}
|
||||
|
@ -74,165 +83,53 @@
|
|||
>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs4 sm12 md1>
|
||||
<div class="right">
|
||||
<v-flex xs6 sm1 md1 :class="phoneLayout ? '' : 'mr-4'">
|
||||
<div class="caption grey--text">Status</div>
|
||||
<v-chip
|
||||
small
|
||||
class="my-2 caption"
|
||||
class="caption"
|
||||
:class="
|
||||
theme === 'light'
|
||||
? `${torrent.state} white--text `
|
||||
: `${torrent.state} black--text`
|
||||
"
|
||||
>{{ torrent.state }}</v-chip
|
||||
>
|
||||
</div>
|
||||
? `${state} white--text `
|
||||
: `${state} black--text`">
|
||||
{{ torrent.state }}
|
||||
</v-chip>
|
||||
</v-flex>
|
||||
<!-- labels -->
|
||||
|
||||
<v-flex v-for="tag in torrent.tags" :key="tag" xs3 sm1 md1>
|
||||
<v-chip
|
||||
<!-- Category -->
|
||||
<v-flex v-if="torrent.category" xs4 sm1 md1>
|
||||
<div class="caption grey--text">Category</div>
|
||||
<v-chip small class="upload white--text caption">
|
||||
{{ torrent.category }}
|
||||
</v-chip>
|
||||
</v-flex>
|
||||
<!-- Tags -->
|
||||
<v-flex xs12 sm1>
|
||||
<div class="caption grey--text">Tags</div>
|
||||
<v-row wrap class="ma-0">
|
||||
<v-chip v-for="tag in torrent.tags" :key="tag"
|
||||
small
|
||||
:class="
|
||||
theme === 'light'
|
||||
? 'white--text'
|
||||
: 'black--text'
|
||||
"
|
||||
class="download my-2 caption"
|
||||
class="download caption mb-1 mx-1"
|
||||
>
|
||||
{{ tag }}
|
||||
</v-chip>
|
||||
</v-flex>
|
||||
<v-flex v-if="torrent.category" xs3 sm1 md1>
|
||||
<v-chip small class="upload white--text my-2 caption">
|
||||
{{ torrent.category }}
|
||||
</v-chip>
|
||||
</v-flex>
|
||||
<v-flex xs12 sm12 md12>
|
||||
<v-progress-linear
|
||||
height="3"
|
||||
rounded
|
||||
color="cyan darken-1"
|
||||
background-color="cyan lighten-3"
|
||||
:value="torrent.progress"
|
||||
></v-progress-linear>
|
||||
</v-row>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</template>
|
||||
<span>{{ torrent.name }}</span>
|
||||
</v-tooltip>
|
||||
<v-divider v-if="index !== length"></v-divider>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { General } from '@/mixins'
|
||||
|
||||
import { mapGetters } from 'vuex'
|
||||
import { General, Torrent } from '@/mixins'
|
||||
|
||||
export default {
|
||||
name: 'Torrent',
|
||||
mixins: [General],
|
||||
|
||||
props: {
|
||||
torrent: Object,
|
||||
index: Number,
|
||||
length: Number
|
||||
},
|
||||
computed: {
|
||||
chips() {
|
||||
let chips = []
|
||||
|
||||
if (this.torrent.category.length > 0) {
|
||||
chips.push(this.torrent.category)
|
||||
}
|
||||
|
||||
return chips
|
||||
},
|
||||
...mapGetters(['getTheme']),
|
||||
theme() {
|
||||
return this.getTheme() ? 'dark' : 'light'
|
||||
},
|
||||
style() {
|
||||
let base = `pa-4 ml-0 sideborder ${this.torrent.state} `
|
||||
if (this.index === this.length) base += ' bottomBorderRadius'
|
||||
if (this.index === 0) base += ' topBorderRadius'
|
||||
return base
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectTorrent(hash) {
|
||||
if (this.containsTorrent(hash)) {
|
||||
this.$store.commit('SET_SELECTED', { type: 'remove', hash })
|
||||
} else {
|
||||
this.$store.commit('SET_SELECTED', { type: 'add', hash })
|
||||
}
|
||||
},
|
||||
containsTorrent(hash) {
|
||||
return this.$store.getters.containsTorrent(hash)
|
||||
},
|
||||
showInfo(hash) {
|
||||
this.createModal('TorrentDetailModal', { hash })
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
formatEta(value, options) {
|
||||
const minute = 60
|
||||
const hour = minute * 60
|
||||
const day = hour * 24
|
||||
const year = day * 365
|
||||
|
||||
const durations = [year, day, hour, minute, 1]
|
||||
const units = 'ydhms'
|
||||
|
||||
let index = 0
|
||||
let unitSize = 0
|
||||
const parts = []
|
||||
|
||||
const defaultOptions = {
|
||||
maxUnitSize: 2,
|
||||
dayLimit: 0,
|
||||
minUnit: 0
|
||||
}
|
||||
|
||||
const opt = options
|
||||
? Object.assign(defaultOptions, options)
|
||||
: defaultOptions
|
||||
|
||||
if (opt.dayLimit && value >= opt.dayLimit * day) {
|
||||
return '∞'
|
||||
}
|
||||
|
||||
while (
|
||||
(!opt.maxUnitSize || unitSize !== opt.maxUnitSize) &&
|
||||
index !== durations.length
|
||||
) {
|
||||
const duration = durations[index]
|
||||
if (value < duration) {
|
||||
index++
|
||||
continue
|
||||
} else if (
|
||||
opt.minUnit &&
|
||||
durations.length - index <= opt.minUnit
|
||||
) {
|
||||
break
|
||||
}
|
||||
|
||||
const result = Math.floor(value / duration)
|
||||
parts.push(result + units[index])
|
||||
|
||||
value %= duration
|
||||
index++
|
||||
unitSize++
|
||||
}
|
||||
|
||||
if (!parts.length) {
|
||||
return '0' + units[durations.length - 1 - opt.minUnit]
|
||||
}
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
}
|
||||
mixins: [General, Torrent]
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -20,10 +20,16 @@
|
|||
>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
<v-list-item @click="directory" link>
|
||||
<v-icon>folder</v-icon>
|
||||
<v-list-item-title class="ml-2" style="font-size: 15px"
|
||||
>Change location</v-list-item-title
|
||||
>
|
||||
</v-list-item>
|
||||
<v-list-item @click="reannounce" link>
|
||||
<v-icon>record_voice_over</v-icon>
|
||||
<v-list-item-title class="ml-2" style="font-size: 15px"
|
||||
>reannounce</v-list-item-title
|
||||
>Reannounce</v-list-item-title
|
||||
>
|
||||
</v-list-item>
|
||||
<v-divider />
|
||||
|
@ -58,6 +64,9 @@ export default {
|
|||
pause() {
|
||||
qbit.pauseTorrents([this.hash])
|
||||
},
|
||||
directory() {
|
||||
this.createModal('ChangeLocationModal', { hash: this.hash })
|
||||
},
|
||||
reannounce() {
|
||||
qbit.reannounceTorrents([this.hash])
|
||||
},
|
||||
|
|
135
src/components/TorrentDense.vue
Normal file
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<v-card
|
||||
ripple
|
||||
flat
|
||||
class="pointer torrent noselect"
|
||||
:class="{ torrent_selected: containsTorrent(torrent.hash) }"
|
||||
@click.native="selectTorrent(torrent.hash)"
|
||||
@dblclick.prevent="showInfo(torrent.hash)"
|
||||
>
|
||||
<v-tooltip top>
|
||||
<template v-slot:activator="{ on }">
|
||||
<v-layout v-on="on" row wrap :class="style">
|
||||
<v-flex xs12 sm2 md3>
|
||||
<div class="caption grey--text">Torrent title</div>
|
||||
<div class="truncate">{{ torrent.name }}</div>
|
||||
</v-flex>
|
||||
<v-flex xs6 sm1 md1 class="mr-2">
|
||||
<div class="caption grey--text">Size</div>
|
||||
<div>
|
||||
{{ torrent.size | getDataValue }}
|
||||
<span class="caption grey--text">{{
|
||||
torrent.size | getDataUnit
|
||||
}}</span>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs5 sm1 md1 class="mr-2">
|
||||
<div class="caption grey--text">Done</div>
|
||||
<div>
|
||||
{{ torrent.progress }}<span class="grey--text">% </span>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs6 sm1 md1 class="mr-2">
|
||||
<div class="caption grey--text">Download</div>
|
||||
<div>
|
||||
{{ torrent.dlspeed | getDataValue }}
|
||||
<span class="caption grey--text">{{
|
||||
torrent.dlspeed | getDataUnit
|
||||
}}</span>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs5 sm1 md1 class="mr-2">
|
||||
<div class="caption grey--text">Upload</div>
|
||||
<div>
|
||||
{{ torrent.upspeed | getDataValue }}
|
||||
<span class="caption grey--text">{{
|
||||
torrent.upspeed | getDataUnit
|
||||
}}</span>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs6 sm1 md1 class="mr-2">
|
||||
<div class="caption grey--text">ETA</div>
|
||||
<div>
|
||||
{{ torrent.eta }}
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs5 sm1 md1 class="mr-2">
|
||||
<div class="caption grey--text">Peers</div>
|
||||
<div>
|
||||
{{ torrent.num_leechs }}
|
||||
<span class="grey--text caption"
|
||||
>/{{ torrent.available_peers }}</span
|
||||
>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs6 sm1 md1 class="mr-2">
|
||||
<div class="caption grey--text">Seeds</div>
|
||||
<div>
|
||||
{{ torrent.num_seeds }}
|
||||
<span class="grey--text caption"
|
||||
>/{{ torrent.available_seeds }}</span
|
||||
>
|
||||
</div>
|
||||
</v-flex>
|
||||
<v-flex xs5 sm1>
|
||||
<div class="caption grey--text">Status</div>
|
||||
<v-chip
|
||||
small
|
||||
class="caption"
|
||||
:class="
|
||||
theme === 'light'
|
||||
? `${state} white--text `
|
||||
: `${state} black--text`">
|
||||
{{ torrent.state }}
|
||||
</v-chip>
|
||||
</v-flex>
|
||||
<!-- Category -->
|
||||
<v-flex v-if="torrent.category" class="mr-2" xs6 sm1 md1>
|
||||
<div class="caption grey--text">Category</div>
|
||||
<v-chip small class="upload white--text caption">
|
||||
{{ torrent.category }}
|
||||
</v-chip>
|
||||
</v-flex>
|
||||
<!-- Tags -->
|
||||
<v-flex xs5 sm4>
|
||||
<div class="caption grey--text">Tags</div>
|
||||
<v-row wrap class="ma-0">
|
||||
<v-chip v-for="tag in torrent.tags" :key="tag"
|
||||
small
|
||||
:class="
|
||||
theme === 'light'
|
||||
? 'white--text'
|
||||
: 'black--text'
|
||||
"
|
||||
class="download caption mb-1 mx-1"
|
||||
>
|
||||
{{ tag }}
|
||||
</v-chip>
|
||||
</v-row>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</template>
|
||||
<span>{{ torrent.name }}</span>
|
||||
</v-tooltip>
|
||||
<v-divider v-if="index !== length"></v-divider>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { General, Torrent } from '@/mixins'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'TorrentDense',
|
||||
mixins: [General, Torrent]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.topBorderRadius {
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
.bottomBorderRadius {
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
</style>
|
|
@ -54,7 +54,7 @@ export function treeify(paths) {
|
|||
let level = { result }
|
||||
|
||||
paths.forEach(path => {
|
||||
path.name.split('/').reduce((r, name, i, a) => {
|
||||
path.name.split('/').reduce((r, name) => {
|
||||
if (!r[name]) {
|
||||
r[name] = { result: [] }
|
||||
r.result.push(createFile(path, name, r[name].result))
|
||||
|
@ -81,7 +81,9 @@ export function treeify(paths) {
|
|||
|
||||
function createFile(data, name, children) {
|
||||
return {
|
||||
id: data.id,
|
||||
name: name,
|
||||
fullName: data.name,
|
||||
progress: Math.round(data.progress * 100),
|
||||
size: formatBytes(data.size),
|
||||
icon: getIconForFileType(name.split('.').pop()),
|
||||
|
@ -92,6 +94,7 @@ function createFile(data, name, children) {
|
|||
function createFolder(name, children) {
|
||||
return {
|
||||
name: name,
|
||||
fullName: name,
|
||||
type: 'directory',
|
||||
children: children
|
||||
}
|
||||
|
|
42
src/mixins/Torrent.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
import {mapGetters} from 'vuex'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
torrent: Object,
|
||||
index: Number,
|
||||
length: Number
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getTheme']),
|
||||
theme() {
|
||||
return this.getTheme() ? 'dark' : 'light'
|
||||
},
|
||||
state() {
|
||||
return this.torrent.state.toLowerCase()
|
||||
},
|
||||
style() {
|
||||
let base = `pa-4 ml-0 sideborder ${this.state} `
|
||||
if (this.index === this.length) base += ' bottomBorderRadius'
|
||||
if (this.index === 0) base += ' topBorderRadius'
|
||||
return base
|
||||
},
|
||||
phoneLayout() {
|
||||
return this.$vuetify.breakpoint.xsOnly
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectTorrent(hash) {
|
||||
if (this.containsTorrent(hash)) {
|
||||
this.$store.commit('SET_SELECTED', { type: 'remove', hash })
|
||||
} else {
|
||||
this.$store.commit('SET_SELECTED', { type: 'add', hash })
|
||||
}
|
||||
},
|
||||
containsTorrent(hash) {
|
||||
return this.$store.getters.containsTorrent(hash)
|
||||
},
|
||||
showInfo(hash) {
|
||||
this.createModal('TorrentDetailModal', { hash })
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,5 +3,6 @@ import Modal from './Modal'
|
|||
import SettingsTab from './SettingsTab'
|
||||
import Tab from './Tab'
|
||||
import General from './General'
|
||||
import Torrent from './Torrent'
|
||||
|
||||
export { FullScreenModal, Modal, SettingsTab, Tab, General }
|
||||
export { FullScreenModal, Modal, SettingsTab, Tab, General, Torrent }
|
||||
|
|
|
@ -2,68 +2,127 @@ export default class Torrent {
|
|||
constructor(data) {
|
||||
this.name = data.name
|
||||
this.size = this.formatBytes(data.size)
|
||||
this.birth = new Date(data.added_on * 1000).toLocaleString()
|
||||
this.dlspeed = this.formatBytes(data.dlspeed, 1)
|
||||
this.added_on = new Date(data.added_on * 1000).toLocaleString()
|
||||
this.dlspeed = this.formatBytes(data.dlspeed, 1) + '/s'
|
||||
this.dloaded = this.formatBytes(data.completed)
|
||||
this.upspeed = this.formatBytes(data.upspeed, 1)
|
||||
this.upspeed = this.formatBytes(data.upspeed, 1) + '/s'
|
||||
this.uploaded = this.formatBytes(data.uploaded)
|
||||
this.eta = data.eta
|
||||
this.eta = this.formatEta(data.eta)
|
||||
this.num_leechs = data.num_leechs
|
||||
this.num_seeds = data.num_seeds
|
||||
this.path = data.path === undefined ? '/downloads' : data.path
|
||||
this.state = this.formatState(data.state)
|
||||
this.state = this.formatState(data)
|
||||
// hash is used to identify
|
||||
this.hash = data.hash
|
||||
// available seeds
|
||||
this.available_seeds = data.num_complete
|
||||
this.available_peers = data.num_incomplete
|
||||
this.savePath = data.save_path
|
||||
this.progress = data.progress * 100
|
||||
this.ratio = Math.round(data.ratio * 100)
|
||||
this.progress = Math.round(data.progress * 10000) / 100
|
||||
this.ratio = Math.round(data.ratio * 100) / 100
|
||||
this.tags = data.tags.length > 0 ? data.tags.split(',') : null
|
||||
this.category = data.category
|
||||
}
|
||||
|
||||
formatState(state) {
|
||||
switch (state) {
|
||||
formatState(item) {
|
||||
if (!item.tracker) return 'Fail'
|
||||
switch (item.state) {
|
||||
case 'forceDL':
|
||||
case 'downloading':
|
||||
return 'busy'
|
||||
return 'Downloading'
|
||||
case 'metaDL':
|
||||
return 'metadata'
|
||||
return 'Metadata'
|
||||
case 'forcedUP':
|
||||
case 'uploading':
|
||||
case 'stalledUP':
|
||||
return 'seeding'
|
||||
return 'Seeding'
|
||||
case 'pausedDL':
|
||||
return 'paused'
|
||||
return 'Paused'
|
||||
case 'pausedUP':
|
||||
return 'done'
|
||||
return 'Done'
|
||||
case 'queuedDL':
|
||||
case 'queuedUP':
|
||||
return 'queued'
|
||||
return 'Queued'
|
||||
case 'allocating':
|
||||
case 'checkingDL':
|
||||
case 'checkingUP':
|
||||
case 'checkingResumeData':
|
||||
case 'moving':
|
||||
return 'checking'
|
||||
return 'Checking'
|
||||
case 'unknown':
|
||||
case 'missingFiles':
|
||||
return 'fail'
|
||||
return 'Fail'
|
||||
case 'stalledDL':
|
||||
return 'stalled'
|
||||
return 'Stalled'
|
||||
default:
|
||||
return 'fail'
|
||||
return 'Fail'
|
||||
}
|
||||
}
|
||||
|
||||
formatBytes(a, b) {
|
||||
if (a == 0) return '0 Bytes'
|
||||
if (a == 0) return '0 B'
|
||||
const c = 1024
|
||||
const d = b || 2
|
||||
const e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
const e = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
const f = Math.floor(Math.log(a) / Math.log(c))
|
||||
return `${parseFloat((a / Math.pow(c, f)).toFixed(d))} ${e[f]}`
|
||||
}
|
||||
|
||||
formatEta(value) {
|
||||
let options = { dayLimit: 100 }
|
||||
const minute = 60
|
||||
const hour = minute * 60
|
||||
const day = hour * 24
|
||||
const year = day * 365
|
||||
|
||||
const durations = [year, day, hour, minute, 1]
|
||||
const units = 'ydhms'
|
||||
|
||||
let index = 0
|
||||
let unitSize = 0
|
||||
const parts = []
|
||||
|
||||
const defaultOptions = {
|
||||
maxUnitSize: 2,
|
||||
dayLimit: 0,
|
||||
minUnit: 0
|
||||
}
|
||||
|
||||
const opt = options
|
||||
? Object.assign(defaultOptions, options)
|
||||
: defaultOptions
|
||||
|
||||
if (opt.dayLimit && value >= opt.dayLimit * day) {
|
||||
return '∞'
|
||||
}
|
||||
|
||||
while (
|
||||
(!opt.maxUnitSize || unitSize !== opt.maxUnitSize) &&
|
||||
index !== durations.length
|
||||
) {
|
||||
const duration = durations[index]
|
||||
if (value < duration) {
|
||||
index++
|
||||
continue
|
||||
} else if (
|
||||
opt.minUnit &&
|
||||
durations.length - index <= opt.minUnit
|
||||
) {
|
||||
break
|
||||
}
|
||||
|
||||
const result = Math.floor(value / duration)
|
||||
parts.push(result + units[index])
|
||||
|
||||
value %= duration
|
||||
index++
|
||||
unitSize++
|
||||
}
|
||||
|
||||
if (!parts.length) {
|
||||
return '0' + units[durations.length - 1 - opt.minUnit]
|
||||
}
|
||||
|
||||
return parts.join(' ')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import Vuetify from 'vuetify'
|
|||
import 'vuetify/dist/vuetify.min.css'
|
||||
|
||||
import colors from 'vuetify/lib/util/colors'
|
||||
import variables from '../styles/variables.scss'
|
||||
|
||||
Vue.use(Vuetify)
|
||||
|
||||
|
@ -28,7 +29,8 @@ export default new Vuetify({
|
|||
background: colors.grey.lighten4,
|
||||
search: colors.grey.darken1,
|
||||
torrentmodaltext: colors.grey.darken4,
|
||||
select: colors.grey.lighten4
|
||||
select: colors.grey.lighten4,
|
||||
...variables
|
||||
},
|
||||
dark: {
|
||||
primary: '#35495e',
|
||||
|
@ -45,7 +47,8 @@ export default new Vuetify({
|
|||
background: colors.grey.darken4,
|
||||
search: colors.grey.darken3,
|
||||
torrentmodaltext: colors.grey.lighten4,
|
||||
select: colors.grey.darken3
|
||||
select: colors.grey.darken3,
|
||||
...variables
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -214,6 +214,16 @@ class Qbit {
|
|||
})
|
||||
}
|
||||
|
||||
renameFile(hash, id, name) {
|
||||
const params = {
|
||||
hash,
|
||||
id,
|
||||
name
|
||||
}
|
||||
const data = new URLSearchParams(params)
|
||||
return this.axios.post('/torrents/renameFile', data)
|
||||
}
|
||||
|
||||
getAvailableTags() {
|
||||
return this.axios.get('/torrents/tags')
|
||||
}
|
||||
|
@ -299,6 +309,7 @@ class Qbit {
|
|||
// End Categories
|
||||
|
||||
actionTorrents(action, hashes, extra) {
|
||||
if (action == 'delete' && !hashes.length) return
|
||||
const params = {
|
||||
hashes: hashes.length ? hashes.join('|') : 'all',
|
||||
...extra
|
||||
|
|
|
@ -31,8 +31,8 @@ export default new Vuex.Store({
|
|||
selected_torrents: [],
|
||||
authenticated: false,
|
||||
sort_options: {
|
||||
sort: 'name',
|
||||
reverse: false,
|
||||
sort: 'default',
|
||||
reverse: true,
|
||||
hashes: [],
|
||||
filter: null
|
||||
},
|
||||
|
@ -46,7 +46,9 @@ export default new Vuex.Store({
|
|||
showSpeedGraph: true,
|
||||
showSessionStat: true,
|
||||
showCurrentSpeed: true,
|
||||
showGlobalRemoveResumePause: true
|
||||
showGlobalRemoveResumePause: true,
|
||||
denseDashboard: true,
|
||||
paginationSize: 15
|
||||
},
|
||||
categories: [],
|
||||
filteredTorrentsCount: 0
|
||||
|
|
|
@ -61,10 +61,10 @@ export default {
|
|||
state.settings = data
|
||||
},
|
||||
UPDATE_SORT_OPTIONS: (state, payload) => {
|
||||
state.sort_options.sort = payload.name ? payload.name : null
|
||||
state.sort_options.reverse = payload.reverse ? payload.reverse : null
|
||||
state.sort_options.hashes = payload.hashes ? payload.hashes : null
|
||||
state.sort_options.filter = payload.filter ? payload.filter : null
|
||||
state.sort_options.sort = payload.name ? payload.name : state.sort_options.sort
|
||||
state.sort_options.reverse = payload.reverse ? payload.reverse : state.sort_options.reverse
|
||||
state.sort_options.hashes = payload.hashes ? payload.hashes : state.sort_options.hashes
|
||||
state.sort_options.filter = payload.filter ? payload.filter : state.sort_options.filter
|
||||
state.sort_options.category =
|
||||
payload.category !== null ? payload.category : null
|
||||
},
|
||||
|
|
21
src/styles/variables.scss
Normal file
|
@ -0,0 +1,21 @@
|
|||
$torrent-done: #3cd1c2;
|
||||
$torrent-downloading: #ffaa2c;
|
||||
$torrent-fail: #f83e70;
|
||||
$torrent-paused: #cfd8dc;
|
||||
$torrent-queued: #2e5eaa;
|
||||
$torrent-seeding: #26a69a;
|
||||
$torrent-checking: #ff7043;
|
||||
$torrent-stalled: #81c784;
|
||||
$torrent-metadata: #7e57c2;
|
||||
|
||||
:export {
|
||||
torrent-done-color: $torrent-done;
|
||||
torrent-downloading-color: $torrent-downloading;
|
||||
torrent-fail-color: $torrent-fail;
|
||||
torrent-paused-color: $torrent-paused;
|
||||
torrent-queued-color: $torrent-queued;
|
||||
torrent-seeding-color: $torrent-seeding;
|
||||
torrent-checking-color: $torrent-checking;
|
||||
torrent-stalled-color: $torrent-stalled;
|
||||
torrent-metadata-color: $torrent-metadata;
|
||||
}
|
|
@ -10,9 +10,9 @@
|
|||
</p>
|
||||
</h1>
|
||||
|
||||
<v-container
|
||||
<div
|
||||
color="background"
|
||||
class="my-4 pt-5 pa-0"
|
||||
class="my-4 pt-5 px-8"
|
||||
@click.self="resetSelected"
|
||||
>
|
||||
<v-flex xs12 sm6 md3 @click.self="resetSelected">
|
||||
|
@ -23,6 +23,7 @@
|
|||
clearable
|
||||
solo
|
||||
color="search"
|
||||
@click:clear="resetInput()"
|
||||
v-model="input"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
@ -32,10 +33,21 @@
|
|||
<div v-else>
|
||||
<div
|
||||
@contextmenu.prevent="$refs.menu.open($event, { torrent })"
|
||||
v-for="(torrent, index) in torrents"
|
||||
v-for="(torrent, index) in paginatedData"
|
||||
:key="torrent.hash"
|
||||
>
|
||||
<Torrent
|
||||
<Torrent v-if="!denseDashboard"
|
||||
:class="{
|
||||
topBorderRadius: index === 0,
|
||||
noBorderRadius:
|
||||
index !== 0 && index !== torrent.length - 1,
|
||||
bottomBorderRadius: index === torrents.length - 1
|
||||
}"
|
||||
:torrent="torrent"
|
||||
:index="index"
|
||||
:length="torrents.length - 1"
|
||||
/>
|
||||
<TorrentDense v-if="denseDashboard"
|
||||
:class="{
|
||||
topBorderRadius: index === 0,
|
||||
noBorderRadius:
|
||||
|
@ -47,8 +59,20 @@
|
|||
:length="torrents.length - 1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<v-row v-if="pageCount > 1" xs12 justify="center">
|
||||
<v-col>
|
||||
<v-container>
|
||||
<v-pagination
|
||||
v-model="pageNumber"
|
||||
:length="pageCount"
|
||||
:total-visible="7"
|
||||
@input="toTop"
|
||||
></v-pagination>
|
||||
</v-container>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</div>
|
||||
<vue-context ref="menu" v-slot="{ data }">
|
||||
<TorrentRightClickMenu v-if="data" :hash="data.torrent.hash" />
|
||||
</vue-context>
|
||||
|
@ -58,6 +82,7 @@
|
|||
<script>
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import Torrent from '@/components/Torrent'
|
||||
import TorrentDense from '@/components/TorrentDense'
|
||||
import Fuse from 'fuse.js'
|
||||
import { VueContext } from 'vue-context'
|
||||
import 'vue-context/src/sass/vue-context.scss'
|
||||
|
@ -65,17 +90,18 @@ import TorrentRightClickMenu from '@/components/Torrent/TorrentRightClickMenu.vu
|
|||
|
||||
export default {
|
||||
name: 'Dashboard',
|
||||
components: { Torrent, VueContext, TorrentRightClickMenu },
|
||||
components: { Torrent, TorrentDense, VueContext, TorrentRightClickMenu },
|
||||
data() {
|
||||
return {
|
||||
input: ''
|
||||
input: '',
|
||||
pageNumber: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['mainData']),
|
||||
...mapGetters(['getTorrents', 'getTorrentCountString']),
|
||||
...mapGetters(['getTorrents', 'getTorrentCountString', 'getWebuiSettings']),
|
||||
torrents() {
|
||||
if (this.input.length === 0) return this.getTorrents()
|
||||
if (!this.input || !this.input.length) return this.getTorrents()
|
||||
|
||||
const options = {
|
||||
threshold: 0.3,
|
||||
|
@ -92,13 +118,35 @@ export default {
|
|||
const fuse = new Fuse(this.getTorrents(), options)
|
||||
return fuse.search(this.input).map(el => el.item)
|
||||
},
|
||||
paginationSize() {
|
||||
return this.getWebuiSettings().paginationSize
|
||||
},
|
||||
pageCount(){
|
||||
let l = this.torrents.length,
|
||||
s = this.paginationSize
|
||||
return Math.ceil(l/s)
|
||||
},
|
||||
paginatedData(){
|
||||
const start = (this.pageNumber - 1) * this.paginationSize,
|
||||
end = start + this.paginationSize
|
||||
return this.torrents.slice(start, end)
|
||||
},
|
||||
torrentCountString() {
|
||||
return this.getTorrentCountString()
|
||||
},
|
||||
denseDashboard(){
|
||||
return this.getWebuiSettings().denseDashboard
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSelected() {
|
||||
this.$store.commit('RESET_SELECTED')
|
||||
},
|
||||
resetInput(){
|
||||
this.input = ''
|
||||
},
|
||||
toTop () {
|
||||
this.$vuetify.goTo(0)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
const webpack = require('webpack')
|
||||
module.exports = {
|
||||
chainWebpack: config => {
|
||||
config
|
||||
.plugin('html')
|
||||
.tap(args => {
|
||||
args[0].title = 'VueTorrent'
|
||||
return args
|
||||
})
|
||||
},
|
||||
outputDir: 'vuetorrent/public',
|
||||
publicPath: './',
|
||||
transpileDependencies: ['vuetify'],
|
||||
|
|