mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2025-02-26 12:21:00 +03:00
webui start + basic torrent showing
This commit is contained in:
parent
402bb61a98
commit
d1d01aafe4
25 changed files with 15035 additions and 14945 deletions
|
@ -1,6 +0,0 @@
|
||||||
nodemodules
|
|
||||||
LICENSE.md
|
|
||||||
README.md
|
|
||||||
babel.config.js
|
|
||||||
.gitignore
|
|
||||||
.env
|
|
|
@ -1,7 +0,0 @@
|
||||||
VUE_APP_DOMAIN=http://localhost:3001
|
|
||||||
VUE_APP_WEB_USER=test
|
|
||||||
VUE_APP_WEB_PASS=test
|
|
||||||
QBIT_USER=test
|
|
||||||
QBIT_PASS=test
|
|
||||||
QBIT_HOST=localhost:8080
|
|
||||||
PORT=3001
|
|
55
.eslintrc.js
55
.eslintrc.js
|
@ -1,29 +1,30 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
env: {
|
env: {
|
||||||
browser: true,
|
browser: true,
|
||||||
commonjs: true,
|
commonjs: true,
|
||||||
es6: true,
|
es6: true
|
||||||
},
|
},
|
||||||
extends: ['plugin:vue/essential', 'airbnb-base'],
|
extends: ['plugin:vue/essential', 'airbnb-base'],
|
||||||
globals: {
|
globals: {
|
||||||
Atomics: 'readonly',
|
Atomics: 'readonly',
|
||||||
SharedArrayBuffer: 'readonly',
|
SharedArrayBuffer: 'readonly'
|
||||||
},
|
},
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: 2018,
|
ecmaVersion: 2018
|
||||||
},
|
},
|
||||||
plugins: ['vue'],
|
plugins: ['vue', 'prettier'],
|
||||||
rules: {
|
rules: {
|
||||||
semi: ['warn', 'never'],
|
semi: ['warn', 'never'],
|
||||||
'no-console': 0,
|
'no-console': 0,
|
||||||
camelcase: 0,
|
camelcase: 0,
|
||||||
'no-restricted-syntax': 0,
|
'no-restricted-syntax': 0,
|
||||||
'no-shadow': 0,
|
'no-shadow': 0,
|
||||||
'class-methods-use-this': 0,
|
'class-methods-use-this': 0,
|
||||||
'prefer-promise-reject-errors': 0,
|
'prefer-promise-reject-errors': 0,
|
||||||
'no-underscore-dangle': 0,
|
'no-underscore-dangle': 0,
|
||||||
'no-param-reassign': 0,
|
'no-param-reassign': 0,
|
||||||
'no-unused-vars': 0,
|
'no-unused-vars': 0,
|
||||||
'indent': 0
|
indent: 0,
|
||||||
},
|
'comma-dangle': 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
7
.prettierrc
Normal file
7
.prettierrc
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
18
Dockerfile
18
Dockerfile
|
@ -1,18 +0,0 @@
|
||||||
# build stage
|
|
||||||
FROM node:10-slim as build-stage
|
|
||||||
# Create app directory
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
COPY package*.json ./
|
|
||||||
RUN npm install -D
|
|
||||||
COPY . .
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
# production stage
|
|
||||||
FROM node:10-slim as production-stage
|
|
||||||
WORKDIR /usr/src/app
|
|
||||||
COPY --from=build-stage /usr/src/app ./
|
|
||||||
RUN rm -r node_modules && rm -r src && npm install
|
|
||||||
|
|
||||||
#serve
|
|
||||||
EXPOSE 3001
|
|
||||||
CMD ["node", "server/server.js"]
|
|
291
README.md
291
README.md
|
@ -1,220 +1,71 @@
|
||||||
|
# VueTorrent
|
||||||
|
|
||||||
|
The sleekest looking WEBUI for qBittorrent made with Vuejs!
|
||||||
# VueTorrent
|
|
||||||
|
> Vue, qBitorrent, Vuetify
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
A modern looking WEBUI for qBittorrent made with Vuejs & express!
|
|
||||||
|
<a href="https://i.imgur.com/vPBcrK4.png"><img src="https://i.imgur.com/vPBcrK4.png" title="Desktop" alt="Desktop Screenshot" ></a>
|
||||||
|
|
||||||
|
</p>
|
||||||
(support for more clients coming in the future)
|
|
||||||
|
<p align="center">
|
||||||
|
|
||||||
|
<a href="https://i.imgur.com/SUOEyy9.png"><img src="https://i.imgur.com/SUOEyy9.png" title="Mobile" alt="Mobile Screenshot" width="320" height="540"></a>
|
||||||
> Vue, Node, Express, qBitorrent
|
|
||||||
|
</p>
|
||||||
|
|
||||||
PS : This is NOT an alternate WEBUI, this is a webapp that interacts with your existing qBitorrent client.
|
## Installation
|
||||||
|
|
||||||
|
- Download & Unzip the latest release
|
||||||
## Update
|
|
||||||
|
- Point your Alternate WEBUI location to it
|
||||||
|
|
||||||
|
## Development
|
||||||
now using this 'https://github.com/TheFlow95/node-qbittorrent-api-v2' qbittorrent-api
|
|
||||||
|
- clone the repo
|
||||||
|
|
||||||
|
- npm install
|
||||||
& deleting & adding don't quite work yet :/
|
|
||||||
|
- npm run serve
|
||||||
|
|
||||||
### With the updates it has become too unstable to use unfortunately, I'll update it when I find a fully working library or maybe write one myself if I find the time.
|
## Features
|
||||||
|
|
||||||
## Screenshots
|
- viewing sessions stats ( down / upload speed, session uploaded / downloaded )
|
||||||
|
- adding / removing / pausing / resuming torrents
|
||||||
|
|
||||||
|
- sorting by every property shown!
|
||||||
<p align="center">
|
|
||||||
|
* mobile friendly! (maybe not for thousands of torrents...)
|
||||||
|
|
||||||
|
- works on QBittorrent V4.2 and later
|
||||||
<a href="https://i.imgur.com/vPBcrK4.png"><img src="https://i.imgur.com/vPBcrK4.png" title="Desktop" alt="Desktop Screenshot" ></a>
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
I'll gladly accept help/pull requests & advice! (this is my first project of this nature, pls be kind 😛 ).
|
||||||
</p>
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
- **Why build this??**
|
||||||
<p align="center">
|
|
||||||
|
* Why not? Most WebUI's look very dated and now it's no longer necessary to search for a remote control app!
|
||||||
|
|
||||||
|
## Support
|
||||||
<a href="https://i.imgur.com/SUOEyy9.png"><img src="https://i.imgur.com/SUOEyy9.png" title="Mobile" alt="Mobile Screenshot" width="320" height="540"></a>
|
|
||||||
|
Reach out to me at one of the following places!
|
||||||
|
|
||||||
|
- <a href="https://m.me/WijnsDaan" target="_blank">`Facebook Messenger`</a>
|
||||||
</p>
|
|
||||||
|
* 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')
|
||||||
|
|
||||||
|
## Credits
|
||||||
## Installation
|
|
||||||
|
- Dashboard design heavily inspired by: '[Net Ninja - Vuetify](https://github.com/iamshaunjp/vuetify-playlist)'.
|
||||||
Easiest way is with docker-compose:
|
Also check out The Net Ninja's Youtube Channel.
|
||||||
|
|
||||||
```
|
* This repo '[CzBiX qb-web ](https://github.com/CzBiX/qb-web)'
|
||||||
|
|
||||||
vuetorrent:
|
|
||||||
|
|
||||||
image: wdaan/vuetorrent
|
|
||||||
|
|
||||||
container_name: vuetorrent
|
|
||||||
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
ports:
|
|
||||||
|
|
||||||
- "4000:3000"
|
|
||||||
|
|
||||||
environment:
|
|
||||||
|
|
||||||
- VUE_APP_WEB_USER=vuetr
|
|
||||||
|
|
||||||
- VUE_APP_WEB_PASS=vuetr
|
|
||||||
|
|
||||||
- QBIT_USER=qbit
|
|
||||||
|
|
||||||
- QBIT_PASS=qbit
|
|
||||||
|
|
||||||
- QBIT_HOST=https://qbit.example.com
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
With Docker run
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
docker run --name=vuetorrent -d --env VUE_APP_WEB_USER=vuetr --env VUE_APP_WEB_PASS=vuetr --env QBIT_USER=admin --env QBIT_PASS=adminadmin --env QBIT_HOST=http://10.0.0.10:8080 --restart unless-stopped -p 3000:3000 wdaan/vuetorrent:latest
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
FRONTEND : Git clone & npm run serve!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SERVER : npm run build & npm run start!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- viewing sessions stats ( down / upload speed, session uploaded / downloaded )
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- adding / removing / pausing / resuming torrents
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- sorting by every property shown!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
- mobile friendly! (maybe not for thousands of torrents...)
|
|
||||||
|
|
||||||
|
|
||||||
- works on QBittorrent V4.2 and later
|
|
||||||
|
|
||||||
|
|
||||||
## 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!
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 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 😛
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Dashboard design heavily inspired by: 'https://github.com/iamshaunjp/vuetify-playlist'
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Also check out The Net Ninja's Youtube Channel.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
|
||||||
|
|
27222
package-lock.json
generated
27222
package-lock.json
generated
File diff suppressed because it is too large
Load diff
143
package.json
143
package.json
|
@ -1,72 +1,77 @@
|
||||||
{
|
{
|
||||||
"name": "vuetr",
|
"name": "vuetr",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "./node_modules/\\@vue/cli-service/bin/vue-cli-service.js build",
|
"build": "./node_modules/\\@vue/cli-service/bin/vue-cli-service.js build",
|
||||||
"lint": "vue-cli-service lint",
|
"lint": "vue-cli-service lint",
|
||||||
"start": "nodemon server/server.js"
|
"format": "pretty-quick"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"apexcharts": "^3.17.0",
|
|
||||||
"axios": "^0.18.1",
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"date-fns": "^1.30.1",
|
|
||||||
"dotenv": "^8.2.0",
|
|
||||||
"express": "^4.17.1",
|
|
||||||
"filepond": "^4.13.0",
|
|
||||||
"filepond-plugin-file-validate-size": "^2.2.0",
|
|
||||||
"filepond-plugin-file-validate-type": "^1.2.4",
|
|
||||||
"filepond-plugin-image-preview": "^4.6.1",
|
|
||||||
"multer": "^1.4.2",
|
|
||||||
"qbittorrent-api-v2": "^1.2.0",
|
|
||||||
"register-service-worker": "^1.7.1",
|
|
||||||
"vue": "^2.6.11",
|
|
||||||
"vue-apexcharts": "^1.5.2",
|
|
||||||
"vue-filepond": "^5.1.3",
|
|
||||||
"vue-router": "^3.1.6",
|
|
||||||
"vuetify": "^1.5.24",
|
|
||||||
"vuex": "^3.1.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@vue/cli-plugin-babel": "^3.12.1",
|
|
||||||
"@vue/cli-plugin-eslint": "^3.12.1",
|
|
||||||
"@vue/cli-plugin-pwa": "^3.12.1",
|
|
||||||
"@vue/cli-service": "^3.12.1",
|
|
||||||
"eslint": "^6.8.0",
|
|
||||||
"eslint-config-airbnb-base": "^14.1.0",
|
|
||||||
"eslint-plugin-import": "^2.20.1",
|
|
||||||
"eslint-plugin-vue": "^5.2.3",
|
|
||||||
"stylus": "^0.54.7",
|
|
||||||
"stylus-loader": "^3.0.1",
|
|
||||||
"vue-cli-plugin-vuetify": "^0.4.6",
|
|
||||||
"vue-template-compiler": "^2.6.11",
|
|
||||||
"vuetify-loader": "^1.4.3",
|
|
||||||
"webpack": "^4.42.0",
|
|
||||||
"webpack-cli": "^3.3.11"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"root": true,
|
|
||||||
"env": {
|
|
||||||
"node": true
|
|
||||||
},
|
},
|
||||||
"extends": [
|
"dependencies": {
|
||||||
"plugin:vue/essential"
|
"apexcharts": "^3.17.0",
|
||||||
],
|
"axios": "^0.18.1",
|
||||||
"rules": {},
|
"cors": "^2.8.5",
|
||||||
"parserOptions": {
|
"date-fns": "^1.30.1",
|
||||||
"parser": "babel-eslint"
|
"dotenv": "^8.2.0",
|
||||||
}
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
},
|
"express": "^4.17.1",
|
||||||
"postcss": {
|
"filepond": "^4.13.0",
|
||||||
"plugins": {
|
"filepond-plugin-file-validate-size": "^2.2.0",
|
||||||
"autoprefixer": {}
|
"filepond-plugin-file-validate-type": "^1.2.4",
|
||||||
}
|
"filepond-plugin-image-preview": "^4.6.1",
|
||||||
},
|
"lodash": "^4.17.15",
|
||||||
"browserslist": [
|
"multer": "^1.4.2",
|
||||||
"> 1%",
|
"prettier": "^2.0.5",
|
||||||
"last 2 versions",
|
"pretty-quick": "^2.0.1",
|
||||||
"not ie <= 8"
|
"qbittorrent-api-v2": "^1.2.0",
|
||||||
]
|
"register-service-worker": "^1.7.1",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-apexcharts": "^1.5.2",
|
||||||
|
"vue-filepond": "^5.1.3",
|
||||||
|
"vue-router": "^3.1.6",
|
||||||
|
"vue-toastification": "^1.7.1",
|
||||||
|
"vuetify": "^1.5.24",
|
||||||
|
"vuex": "^3.1.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vue/cli-plugin-babel": "^3.12.1",
|
||||||
|
"@vue/cli-plugin-eslint": "^3.12.1",
|
||||||
|
"@vue/cli-plugin-pwa": "^3.12.1",
|
||||||
|
"@vue/cli-service": "^3.12.1",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-config-airbnb-base": "^14.1.0",
|
||||||
|
"eslint-plugin-import": "^2.20.1",
|
||||||
|
"eslint-plugin-vue": "^5.2.3",
|
||||||
|
"stylus": "^0.54.7",
|
||||||
|
"stylus-loader": "^3.0.1",
|
||||||
|
"vue-cli-plugin-vuetify": "^0.4.6",
|
||||||
|
"vue-template-compiler": "^2.6.11",
|
||||||
|
"vuetify-loader": "^1.4.3",
|
||||||
|
"webpack": "^4.42.0",
|
||||||
|
"webpack-cli": "^3.3.11"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential"
|
||||||
|
],
|
||||||
|
"rules": {},
|
||||||
|
"parserOptions": {
|
||||||
|
"parser": "babel-eslint"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"postcss": {
|
||||||
|
"plugins": {
|
||||||
|
"autoprefixer": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not ie <= 8"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
module.exports = class Stat {
|
|
||||||
constructor(data) {
|
|
||||||
if (data != undefined && data != null) {
|
|
||||||
this.status = data.connection_status;
|
|
||||||
this.downloaded = this.formatBytes(data.dl_info_data, 1);
|
|
||||||
this.uploaded = this.formatBytes(data.up_info_data, 1);
|
|
||||||
this.dlspeed = this.formatBytes(data.dl_info_speed, 1);
|
|
||||||
this.upspeed = this.formatBytes(data.up_info_speed, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
formatBytes(a, b) {
|
|
||||||
if (0 == a) return '0 Bytes';
|
|
||||||
var c = 1024,
|
|
||||||
d = b || 2,
|
|
||||||
e = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
|
|
||||||
f = Math.floor(Math.log(a) / Math.log(c));
|
|
||||||
return parseFloat((a / Math.pow(c, f)).toFixed(d)) + ' ' + e[f];
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,52 +0,0 @@
|
||||||
module.exports = 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.dloaded = this.formatBytes(data.downloaded)
|
|
||||||
this.upspeed = this.formatBytes(data.upspeed, 1)
|
|
||||||
this.uploaded = this.formatBytes(data.uploaded)
|
|
||||||
this.eta = `${new Date(data.eta).getHours()
|
|
||||||
}h ${
|
|
||||||
new Date(data.eta).getMinutes()
|
|
||||||
}min`
|
|
||||||
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)
|
|
||||||
// hash is used to identify
|
|
||||||
this.hash = data.hash
|
|
||||||
// available seeds
|
|
||||||
this.available_seeds = data.num_complete
|
|
||||||
this.available_peers = data.num_incomplete
|
|
||||||
}
|
|
||||||
|
|
||||||
formatState(state) {
|
|
||||||
switch (state) {
|
|
||||||
case 'pausedDL':
|
|
||||||
return 'paused'
|
|
||||||
case 'downloading':
|
|
||||||
return 'busy'
|
|
||||||
case 'stalledDL':
|
|
||||||
return 'fail'
|
|
||||||
case 'pausedUP':
|
|
||||||
return 'done'
|
|
||||||
case 'missingFiles':
|
|
||||||
return 'fail'
|
|
||||||
case 'stalledUP':
|
|
||||||
return 'done'
|
|
||||||
default:
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
formatBytes(a, b) {
|
|
||||||
if (a == 0) return '0 Bytes'
|
|
||||||
const c = 1024
|
|
||||||
const d = b || 2
|
|
||||||
const e = ['Bytes', '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]}`
|
|
||||||
}
|
|
||||||
}
|
|
101
server/qbit.js
101
server/qbit.js
|
@ -1,101 +0,0 @@
|
||||||
const api = require('qbittorrent-api-v2')
|
|
||||||
const dotenv = require('dotenv')
|
|
||||||
const Torrent = require('./models/torrent.class.js')
|
|
||||||
const Stat = require('./models/stat.class.js')
|
|
||||||
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
const connection = api.connect(process.env.QBIT_HOST, process.env.QBIT_USER, process.env.QBIT_PASS)
|
|
||||||
|
|
||||||
|
|
||||||
class Qbit {
|
|
||||||
async get_all(prop) {
|
|
||||||
try {
|
|
||||||
const res = await (await connection).torrents('all', null, prop.name, String(prop.reverse), '20', null, null)
|
|
||||||
const torrents = []
|
|
||||||
res.forEach((el) => {
|
|
||||||
const t = new Torrent(el)
|
|
||||||
torrents.push(t)
|
|
||||||
})
|
|
||||||
return torrents
|
|
||||||
} catch (err) {
|
|
||||||
return `something went wrong:${err}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async get_session_stats() {
|
|
||||||
try {
|
|
||||||
const res = await (await connection).transferInfo()
|
|
||||||
return new Stat(res)
|
|
||||||
} catch (err) {
|
|
||||||
return `something went wrong:${err}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async pause_torrents(torrents) {
|
|
||||||
let _torrents = ''
|
|
||||||
torrents.forEach((el) => {
|
|
||||||
_torrents += `${el}|`
|
|
||||||
})
|
|
||||||
try {
|
|
||||||
return await (await connection).pauseTorrents(_torrents)
|
|
||||||
} catch (err) {
|
|
||||||
return `something went wrong:${err}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async pause_all() {
|
|
||||||
try {
|
|
||||||
return await (await connection).pauseTorrents('all')
|
|
||||||
} catch (err) {
|
|
||||||
return `something went wrong:${err}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async resume_torrents(torrents) {
|
|
||||||
let _torrents = ''
|
|
||||||
torrents.forEach((el) => {
|
|
||||||
_torrents += `${el}|`
|
|
||||||
})
|
|
||||||
console.log(_torrents)
|
|
||||||
try {
|
|
||||||
return await (await connection).resumeTorrents(_torrents)
|
|
||||||
} catch (err) {
|
|
||||||
return `something went wrong:${err}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async resume_all() {
|
|
||||||
try {
|
|
||||||
return await (await connection).resumeTorrents('all')
|
|
||||||
} catch (err) {
|
|
||||||
return `something went wrong:${err}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async remove_torrents(torrents) {
|
|
||||||
let _torrents = ''
|
|
||||||
torrents.forEach((el) => {
|
|
||||||
_torrents += `${el}|`
|
|
||||||
})
|
|
||||||
console.log(_torrents)
|
|
||||||
try {
|
|
||||||
return await (await connection).deleteTorrents(_torrents, 'true')
|
|
||||||
} catch (err) {
|
|
||||||
return `something went wrong:${err}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async add(torrent) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
connection.add(torrent.path, null, null, (err, res) => {
|
|
||||||
resolve(res)
|
|
||||||
reject(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const qbit = new Qbit()
|
|
||||||
|
|
||||||
module.exports = qbit
|
|
127
server/server.js
127
server/server.js
|
@ -1,127 +0,0 @@
|
||||||
const express = require('express')
|
|
||||||
const multer = require('multer')
|
|
||||||
const fs = require('fs')
|
|
||||||
const path = require('path')
|
|
||||||
const dotenv = require('dotenv')
|
|
||||||
const qbit = require('./qbit')
|
|
||||||
|
|
||||||
dotenv.config()
|
|
||||||
|
|
||||||
const PORT = process.env.PORT || 3000
|
|
||||||
|
|
||||||
const newest_torrent = {
|
|
||||||
name: '',
|
|
||||||
path: '',
|
|
||||||
}
|
|
||||||
|
|
||||||
const upload = multer({
|
|
||||||
dest: './src/tmp/',
|
|
||||||
})
|
|
||||||
|
|
||||||
// init express
|
|
||||||
const app = express()
|
|
||||||
|
|
||||||
app.use(express.json())
|
|
||||||
app.use(express.static('dist'))
|
|
||||||
|
|
||||||
// requests
|
|
||||||
// login
|
|
||||||
app.post('/login', (req, res) => {
|
|
||||||
console.log(req.body)
|
|
||||||
if (req.body.username !== process.env.VUE_APP_WEB_USER) {
|
|
||||||
return res.send('No such user')
|
|
||||||
} if (
|
|
||||||
req.body.username === process.env.VUE_APP_WEB_USER
|
|
||||||
&& req.body.password !== process.env.VUE_APP_WEB_PASS
|
|
||||||
) {
|
|
||||||
return res.send('Wrong password!')
|
|
||||||
} if (
|
|
||||||
req.body.username === process.env.VUE_APP_WEB_USER
|
|
||||||
&& req.body.password === process.env.VUE_APP_WEB_PASS
|
|
||||||
) {
|
|
||||||
return res.send('SUCCES')
|
|
||||||
}
|
|
||||||
return res.send('Something went wrong')
|
|
||||||
})
|
|
||||||
|
|
||||||
// get all torrents
|
|
||||||
// AAAND sort torrents
|
|
||||||
app.post('/all', async (req, res) => {
|
|
||||||
const torrents = await qbit.get_all(req.body)
|
|
||||||
res.set('Content-Type', 'application/json')
|
|
||||||
res.send(torrents)
|
|
||||||
})
|
|
||||||
|
|
||||||
// get session stats
|
|
||||||
app.get('/session', async (req, res) => {
|
|
||||||
const stats = await qbit.get_session_stats()
|
|
||||||
res.set('Content-Type', 'application/json')
|
|
||||||
res.send(stats)
|
|
||||||
})
|
|
||||||
|
|
||||||
// pause selected torrents
|
|
||||||
app.post('/pause', async (req, res) => {
|
|
||||||
// console.log(req.body);
|
|
||||||
const msg = await qbit.pause_torrents(req.body)
|
|
||||||
return res.send(msg)
|
|
||||||
})
|
|
||||||
|
|
||||||
// pause all torrents
|
|
||||||
app.post('/pause_all', async (req, res) => {
|
|
||||||
const msg = await qbit.pause_all()
|
|
||||||
return res.send(msg)
|
|
||||||
})
|
|
||||||
|
|
||||||
// resume selected torrents
|
|
||||||
app.post('/resume', async (req, res) => {
|
|
||||||
// console.log(req.body);
|
|
||||||
const msg = await qbit.resume_torrents(req.body)
|
|
||||||
return res.send(msg)
|
|
||||||
})
|
|
||||||
|
|
||||||
// resume all torrents
|
|
||||||
app.post('/resume_all', async (req, res) => {
|
|
||||||
const msg = await qbit.resume_all()
|
|
||||||
return res.send(msg)
|
|
||||||
})
|
|
||||||
|
|
||||||
// remove selected torrents
|
|
||||||
app.post('/remove', async (req, res) => {
|
|
||||||
// console.log(req.body);
|
|
||||||
const msg = await qbit.remove_torrents(req.body)
|
|
||||||
return res.send(msg)
|
|
||||||
})
|
|
||||||
|
|
||||||
// upload files to server
|
|
||||||
app.post('/upload', upload.single('file'), (req, res) => {
|
|
||||||
newest_torrent.name = req.file.filename
|
|
||||||
newest_torrent.path = req.file.path
|
|
||||||
return res.send('succes')
|
|
||||||
})
|
|
||||||
|
|
||||||
// add a torrent
|
|
||||||
app.post('/add', async (req, res) => {
|
|
||||||
const msg = await qbit.add(newest_torrent)
|
|
||||||
fs.unlinkSync(newest_torrent.path)
|
|
||||||
return res.send(msg)
|
|
||||||
})
|
|
||||||
|
|
||||||
// delete last uploaded file
|
|
||||||
app.delete('/upload', upload.single('file'), (req, res) => {
|
|
||||||
fs.unlinkSync(newest_torrent.path)
|
|
||||||
return res.send('deleted file')
|
|
||||||
})
|
|
||||||
|
|
||||||
app.listen(PORT, () => console.log(`Server listening on port ${PORT}!`))
|
|
||||||
|
|
||||||
// clear the tmp directory on every boot
|
|
||||||
const directory = path.resolve(`${__dirname}/tmp`)
|
|
||||||
|
|
||||||
fs.readdir(directory, (err, files) => {
|
|
||||||
if (err) console.log(err)
|
|
||||||
for (const file of files) {
|
|
||||||
fs.unlink(path.join(directory, file), (err) => {
|
|
||||||
if (err) console.log(err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
|
@ -1 +0,0 @@
|
||||||
Here will torrents files be stored temporarily
|
|
88
src/App.vue
88
src/App.vue
|
@ -1,63 +1,47 @@
|
||||||
<template>
|
<template>
|
||||||
<v-app class="grey lighten-4">
|
<v-app class="grey lighten-4">
|
||||||
<!--snackbar popup for torrent added -->
|
<div v-if="authenticated">
|
||||||
<v-snackbar :value="snackbar" :timeout="4000" top color="success">
|
<Navbar />
|
||||||
<span>{{succes_msg}}</span>
|
<v-content class="mx-4 mb-4">
|
||||||
<v-btn color="white" flat @click="snackbarClose">Close</v-btn>
|
<router-view></router-view>
|
||||||
</v-snackbar>
|
</v-content>
|
||||||
|
|
||||||
<!--snackbar popup for errors -->
|
|
||||||
<v-snackbar :value="snackbar_error" :timeout="4000" top color="error">
|
|
||||||
<span>{{error_msg}}</span>
|
|
||||||
<v-btn color="white" flat @click="snackbar_errorClose">Close</v-btn>
|
|
||||||
</v-snackbar>
|
|
||||||
|
|
||||||
<div v-if="authenticated">
|
|
||||||
<Navbar/>
|
|
||||||
<v-content class="mx-4 mb-4">
|
|
||||||
<router-view></router-view>
|
|
||||||
</v-content>
|
|
||||||
</div>
|
|
||||||
<v-container v-else fill-height>
|
|
||||||
<v-layout row wrap align-center class="justify-center" justify-center>
|
|
||||||
<div style="margin: 0 auto">
|
|
||||||
<Login/>
|
|
||||||
</div>
|
</div>
|
||||||
</v-layout>
|
<v-container v-else fill-height>
|
||||||
</v-container>
|
<v-layout
|
||||||
|
row
|
||||||
<v-spacer></v-spacer>
|
wrap
|
||||||
<p class="grey--text caption text-sm-center text-md-center text-xs-center">Made by Daan Wijns</p>
|
align-center
|
||||||
</v-app>
|
class="justify-center"
|
||||||
|
justify-center
|
||||||
|
>
|
||||||
|
<div style="margin: 0 auto;">
|
||||||
|
<Login />
|
||||||
|
</div>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<p
|
||||||
|
class="grey--text caption text-sm-center text-md-center text-xs-center"
|
||||||
|
>
|
||||||
|
Made by Daan Wijns
|
||||||
|
</p>
|
||||||
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import Navbar from './components/Navbar'
|
import Navbar from './components/Navbar.vue'
|
||||||
import Login from './components/Login'
|
import Login from './components/Login.vue'
|
||||||
|
import qbit from './services/qbit'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { Navbar, Login },
|
components: { Navbar, Login },
|
||||||
name: 'App',
|
name: 'App',
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState([
|
|
||||||
'authenticated',
|
|
||||||
'snackbar_error',
|
|
||||||
'error_msg',
|
|
||||||
'snackbar',
|
|
||||||
'succes_msg',
|
|
||||||
]),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
snackbar_errorClose() {
|
|
||||||
this.$store.state.snackbar_error = false
|
|
||||||
},
|
},
|
||||||
snackbarClose() {
|
computed: {
|
||||||
this.$store.state.snackbar = false
|
...mapState(['authenticated', 'rid', 'mainData', 'preferences'])
|
||||||
},
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,72 +1,75 @@
|
||||||
<template>
|
<template>
|
||||||
<v-container class="grey lighten-4">
|
<v-container class="grey lighten-4">
|
||||||
<v-card max-width="400" flat>
|
<v-card max-width="400" flat>
|
||||||
<v-container :class="`pa-3 project done`">
|
<v-container :class="`pa-3 project done`">
|
||||||
<v-card-title class="justify-center">
|
<v-card-title class="justify-center">
|
||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<div class="mr-5 ml-5"></div>
|
<div class="mr-5 ml-5"></div>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-form class="px-3" ref="form">
|
<v-form class="px-3" ref="form">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
flat
|
flat
|
||||||
solo
|
solo
|
||||||
background-color="grey lighten-4"
|
background-color="grey lighten-4"
|
||||||
label="username"
|
label="username"
|
||||||
prepend-icon="person"
|
prepend-icon="person"
|
||||||
v-model="username"
|
v-model="username"
|
||||||
:rules="inputRules"
|
:rules="inputRules"
|
||||||
@keyup.enter.native="Login"
|
@keyup.enter.native="Login"
|
||||||
></v-text-field>
|
autocomplete="current email"
|
||||||
<v-text-field
|
></v-text-field>
|
||||||
flat
|
<v-text-field
|
||||||
solo
|
flat
|
||||||
background-color="grey lighten-4"
|
solo
|
||||||
type="password"
|
background-color="grey lighten-4"
|
||||||
label="password"
|
type="password"
|
||||||
prepend-icon="lock"
|
label="password"
|
||||||
v-model="password"
|
prepend-icon="lock"
|
||||||
:rules="inputRules"
|
v-model="password"
|
||||||
@keyup.enter.native="Login"
|
:rules="inputRules"
|
||||||
></v-text-field>
|
@keyup.enter.native="Login"
|
||||||
<v-spacer></v-spacer>
|
autocomplete="current password"
|
||||||
<v-card-actions class="justify-center">
|
></v-text-field>
|
||||||
<v-btn
|
<v-spacer></v-spacer>
|
||||||
:loading="loading"
|
<v-card-actions class="justify-center">
|
||||||
flat
|
<v-btn
|
||||||
@click="Login"
|
:loading="loading"
|
||||||
class="blue_accent white--text mx-0 mt-3"
|
flat
|
||||||
>Login</v-btn>
|
@click="Login"
|
||||||
</v-card-actions>
|
class="blue_accent white--text mx-0 mt-3"
|
||||||
</v-form>
|
>Login</v-btn
|
||||||
</v-card-text>
|
>
|
||||||
</v-container>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-form>
|
||||||
</v-container>
|
</v-card-text>
|
||||||
|
</v-container>
|
||||||
|
</v-card>
|
||||||
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
username: '',
|
username: '',
|
||||||
password: '',
|
password: '',
|
||||||
inputRules: [(v) => v.length >= 1 || 'At least 1 character'],
|
inputRules: [v => v.length >= 1 || 'At least 1 character']
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
Login() {
|
|
||||||
this.$store.state.loading = true
|
|
||||||
this.$store.dispatch('LOGIN', {
|
|
||||||
username: this.username,
|
|
||||||
password: this.password,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
methods: {
|
||||||
computed: {
|
Login() {
|
||||||
...mapState(['loading']),
|
this.$store.state.loading = true
|
||||||
},
|
this.$store.dispatch('LOGIN', {
|
||||||
|
username: this.username,
|
||||||
|
password: this.password
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState(['loading'])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,108 +1,173 @@
|
||||||
<template>
|
<template>
|
||||||
<nav>
|
<nav>
|
||||||
<!--title-->
|
<!--title-->
|
||||||
<v-toolbar flat app>
|
<v-toolbar flat app>
|
||||||
<v-toolbar-side-icon @click="drawer = !drawer" class="grey--text"></v-toolbar-side-icon>
|
<v-toolbar-side-icon
|
||||||
<v-toolbar-title :class="['grey--text', {'subheading ml-0': $vuetify.breakpoint.smAndDown}]">
|
@click="drawer = !drawer"
|
||||||
<span class="font-weight-light">Vue</span>
|
class="grey--text"
|
||||||
<span>Torrent</span>
|
></v-toolbar-side-icon>
|
||||||
</v-toolbar-title>
|
<v-toolbar-title
|
||||||
<v-spacer></v-spacer>
|
:class="[
|
||||||
|
'grey--text',
|
||||||
|
{ 'subheading ml-0': $vuetify.breakpoint.smAndDown }
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span class="font-weight-light">Vue</span>
|
||||||
|
<span>Torrent</span>
|
||||||
|
</v-toolbar-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
|
||||||
<!--right corner functions-->
|
<!--right corner functions-->
|
||||||
<AddTorrent @torrentAdded="snackbar = true"/>
|
<AddTorrent @torrentAdded="snackbar = true" />
|
||||||
<v-btn small fab flat class="mr-0 ml-0" @click="removeTorrents">
|
<v-btn small fab flat class="mr-0 ml-0" @click="removeTorrents">
|
||||||
<v-icon color="grey">remove</v-icon>
|
<v-icon color="grey">remove</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn small fab flat class="mr-0 ml-0" @click="resumeTorrents">
|
<v-btn small fab flat class="mr-0 ml-0" @click="resumeTorrents">
|
||||||
<v-icon color="grey">play_arrow</v-icon>
|
<v-icon color="grey">play_arrow</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn small fab flat class="mr-0 ml-0" @click="pauseTorrents">
|
<v-btn small fab flat class="mr-0 ml-0" @click="pauseTorrents">
|
||||||
<v-icon color="grey">pause</v-icon>
|
<v-icon color="grey">pause</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn small fab flat class="mr-0 ml-0" @click="refreshTorrents">
|
<v-btn small fab flat class="mr-0 ml-0" @click="refreshTorrents">
|
||||||
<v-icon color="grey">autorenew</v-icon>
|
<v-icon color="grey">autorenew</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
<!--navigation drawer itself -->
|
<!--navigation drawer itself -->
|
||||||
<v-navigation-drawer app v-model="drawer" class="primary allow-spacer">
|
<v-navigation-drawer app v-model="drawer" class="primary allow-spacer">
|
||||||
<!--current download speeds -->
|
<!--current download speeds -->
|
||||||
<v-flex class="mt-3">
|
<v-flex class="mt-3">
|
||||||
<div class="secondary_lighter--text text-uppercase caption ml-4">current speed</div>
|
<div
|
||||||
<v-card color="secondary" flat class="mr-2 ml-2">
|
class="secondary_lighter--text text-uppercase caption ml-4"
|
||||||
<v-layout row wrap :class="`pa-3 project nav_download`">
|
>
|
||||||
<v-icon color="download">keyboard_arrow_down</v-icon>
|
current speed
|
||||||
<span class="download--text title">
|
</div>
|
||||||
{{stats.dlspeed.substring(0, stats.dlspeed.indexOf(' '))}}
|
<v-card color="secondary" flat class="mr-2 ml-2">
|
||||||
<span
|
<v-layout v-if="stats" row wrap :class="`pa-3 project nav_download`">
|
||||||
class="font-weight-light caption"
|
<v-icon color="download">keyboard_arrow_down</v-icon>
|
||||||
>{{stats.dlspeed.substring(stats.dlspeed.indexOf(' '))}}</span>
|
<span class="download--text title">
|
||||||
</span>
|
{{
|
||||||
<v-icon class="pl-5" color="upload">keyboard_arrow_up</v-icon>
|
stats.dlspeed.substring(
|
||||||
<span class="upload--text title">
|
0,
|
||||||
{{stats.upspeed.substring(0, stats.upspeed.indexOf(' '))}}
|
stats.dlspeed.indexOf(' ')
|
||||||
<span
|
)
|
||||||
class="font-weight-light caption"
|
}}
|
||||||
>{{stats.upspeed.substring(stats.upspeed.indexOf(' '))}}</span>
|
<span class="font-weight-light caption">{{
|
||||||
</span>
|
stats.dlspeed.substring(
|
||||||
</v-layout>
|
stats.dlspeed.indexOf(' ')
|
||||||
</v-card>
|
)
|
||||||
<!--speeds graph -->
|
}}</span>
|
||||||
<div class="mt-4">
|
</span>
|
||||||
<apexchart ref="chart" type="line" :options="chartOptions" :series="series"></apexchart>
|
<v-icon class="pl-5" color="upload"
|
||||||
</div>
|
>keyboard_arrow_up</v-icon
|
||||||
<div class="mt-4"></div>
|
>
|
||||||
<div class="secondary_lighter--text text-uppercase caption ml-4">session stats</div>
|
<span class="upload--text title">
|
||||||
<v-card flat color="secondary" class="mr-2 ml-2">
|
{{
|
||||||
<v-layout row wrap :class="`pa-3 project nav_download`">
|
stats.upspeed.substring(
|
||||||
<v-flex md6>
|
0,
|
||||||
<div class="download--text">Total downloaded</div>
|
stats.upspeed.indexOf(' ')
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
<span class="font-weight-light caption">{{
|
||||||
|
stats.upspeed.substring(
|
||||||
|
stats.upspeed.indexOf(' ')
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
</v-layout>
|
||||||
|
</v-card>
|
||||||
|
<!--speeds graph -->
|
||||||
|
<div class="mt-4">
|
||||||
|
<apexchart
|
||||||
|
ref="chart"
|
||||||
|
type="line"
|
||||||
|
:options="chartOptions"
|
||||||
|
:series="series"
|
||||||
|
></apexchart>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4"></div>
|
||||||
|
<div
|
||||||
|
class="secondary_lighter--text text-uppercase caption ml-4"
|
||||||
|
>
|
||||||
|
session stats
|
||||||
|
</div>
|
||||||
|
<v-card v-if="stats" flat color="secondary" class="mr-2 ml-2">
|
||||||
|
<v-layout row wrap :class="`pa-3 project nav_download`">
|
||||||
|
<v-flex md6>
|
||||||
|
<div class="download--text">Total downloaded</div>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex md5 class="mr-2">
|
||||||
|
<span class="download--text title pl-3">
|
||||||
|
{{
|
||||||
|
stats.downloaded.substring(
|
||||||
|
0,
|
||||||
|
stats.downloaded.indexOf(' ')
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
<span class="font-weight-light caption">{{
|
||||||
|
stats.downloaded.substring(
|
||||||
|
stats.downloaded.indexOf(' ')
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card>
|
||||||
|
<v-card v-if="stats" flat color="secondary" class="mr-2 ml-2 mt-1">
|
||||||
|
<v-layout row wrap :class="`pa-3 project nav_upload`">
|
||||||
|
<v-flex md6>
|
||||||
|
<div class="upload--text">Total uploaded</div>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex md5 class="mr-2">
|
||||||
|
<span class="upload--text title pl-3">
|
||||||
|
{{
|
||||||
|
stats.uploaded.substring(
|
||||||
|
0,
|
||||||
|
stats.uploaded.indexOf(' ')
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
<span class="font-weight-light caption">{{
|
||||||
|
stats.uploaded.substring(
|
||||||
|
stats.uploaded.indexOf(' ')
|
||||||
|
)
|
||||||
|
}}</span>
|
||||||
|
</span>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-card>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex md5 class="mr-2">
|
<v-spacer></v-spacer>
|
||||||
<span class="download--text title pl-3">
|
<v-layout class="align-end">
|
||||||
{{stats.downloaded.substring(0, stats.downloaded.indexOf(' '))}}
|
<Settings />
|
||||||
<span
|
<v-spacer></v-spacer>
|
||||||
class="font-weight-light caption"
|
<v-tooltip top v-if="paused">
|
||||||
>{{stats.downloaded.substring(stats.downloaded.indexOf(' '))}}</span>
|
<v-btn
|
||||||
</span>
|
small
|
||||||
</v-flex>
|
fab
|
||||||
</v-layout>
|
flat
|
||||||
</v-card>
|
class="mr-4"
|
||||||
<v-card flat color="secondary" class="mr-2 ml-2 mt-1">
|
@click="startInterval"
|
||||||
<v-layout row wrap :class="`pa-3 project nav_upload`">
|
slot="activator"
|
||||||
<v-flex md6>
|
>
|
||||||
<div class="upload--text">Total uploaded</div>
|
<v-icon color="green_accent">play_arrow</v-icon>
|
||||||
</v-flex>
|
</v-btn>
|
||||||
<v-flex md5 class="mr-2">
|
<span>Resumes connection to client</span>
|
||||||
<span class="upload--text title pl-3">
|
</v-tooltip>
|
||||||
{{stats.uploaded.substring(0, stats.uploaded.indexOf(' '))}}
|
<v-tooltip top v-else>
|
||||||
<span
|
<v-btn
|
||||||
class="font-weight-light caption"
|
small
|
||||||
>{{stats.uploaded.substring(stats.uploaded.indexOf(' '))}}</span>
|
fab
|
||||||
</span>
|
flat
|
||||||
</v-flex>
|
class="mr-4"
|
||||||
</v-layout>
|
@click="clearInterval"
|
||||||
</v-card>
|
slot="activator"
|
||||||
</v-flex>
|
>
|
||||||
<v-spacer></v-spacer>
|
<v-icon color="green_accent">pause</v-icon>
|
||||||
<v-layout class="align-end">
|
</v-btn>
|
||||||
<Settings/>
|
<span>Pauses connection to client</span>
|
||||||
<v-spacer></v-spacer>
|
</v-tooltip>
|
||||||
<v-tooltip top v-if="paused">
|
</v-layout>
|
||||||
<v-btn small fab flat class="mr-4" @click="startInterval" slot="activator">
|
</v-navigation-drawer>
|
||||||
<v-icon color="green_accent">play_arrow</v-icon>
|
</nav>
|
||||||
</v-btn>
|
|
||||||
<span>Resumes connection to client</span>
|
|
||||||
</v-tooltip>
|
|
||||||
<v-tooltip top v-else>
|
|
||||||
<v-btn small fab flat class="mr-4" @click="clearInterval" slot="activator">
|
|
||||||
<v-icon color="green_accent">pause</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
<span>Pauses connection to client</span>
|
|
||||||
</v-tooltip>
|
|
||||||
</v-layout>
|
|
||||||
</v-navigation-drawer>
|
|
||||||
</nav>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -112,105 +177,98 @@ import AddTorrent from './AddTorrent'
|
||||||
import Settings from './Settings'
|
import Settings from './Settings'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { AddTorrent, Settings },
|
components: { AddTorrent, Settings },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
drawer: false,
|
drawer: false,
|
||||||
paused: false,
|
paused: false,
|
||||||
links: [
|
links: [
|
||||||
{ icon: 'dashboard', text: 'Dashboard', route: '/' },
|
{ icon: 'dashboard', text: 'Dashboard', route: '/' },
|
||||||
{ icon: 'settings', text: 'Settings', route: '/settings' },
|
{ icon: 'settings', text: 'Settings', route: '/settings' }
|
||||||
],
|
],
|
||||||
chartOptions: {
|
chartOptions: {
|
||||||
chart: {
|
chart: {
|
||||||
sparkline: {
|
sparkline: {
|
||||||
enabled: true,
|
enabled: true
|
||||||
},
|
},
|
||||||
animations: {
|
animations: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
dynamicAnimation: {
|
dynamicAnimation: {
|
||||||
speed: 2000,
|
speed: 2000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors: ['#00b3fa', '#64CEAA'],
|
||||||
|
stroke: {
|
||||||
|
show: true,
|
||||||
|
curve: 'smooth',
|
||||||
|
lineCap: 'round',
|
||||||
|
width: 4
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: 'gradient',
|
||||||
|
gradient: {
|
||||||
|
shade: 'dark',
|
||||||
|
type: 'vertical',
|
||||||
|
shadeIntensity: 0.5,
|
||||||
|
opacityFrom: 0.6,
|
||||||
|
opacityTo: 0.5,
|
||||||
|
stops: [0, 50, 100]
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
},
|
series: [
|
||||||
|
{
|
||||||
|
name: 'upload',
|
||||||
|
type: 'area',
|
||||||
|
data: this.$store.state.upload_data
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'download',
|
||||||
|
type: 'area',
|
||||||
|
data: this.$store.state.download_data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
...mapMutations(['REFRESH_TORRENTS', 'CLEAR_INTERVALS']),
|
||||||
|
clearInterval() {
|
||||||
|
|
||||||
},
|
},
|
||||||
colors: ['#00b3fa', '#64CEAA'],
|
startInterval() {
|
||||||
stroke: {
|
|
||||||
show: true,
|
|
||||||
curve: 'smooth',
|
|
||||||
lineCap: 'round',
|
|
||||||
width: 4,
|
|
||||||
},
|
},
|
||||||
fill: {
|
pauseTorrents() {
|
||||||
type: 'gradient',
|
|
||||||
gradient: {
|
|
||||||
shade: 'dark',
|
|
||||||
type: 'vertical',
|
|
||||||
shadeIntensity: 0.5,
|
|
||||||
opacityFrom: 0.6,
|
|
||||||
opacityTo: 0.5,
|
|
||||||
stops: [0, 50, 100],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
resumeTorrents() {
|
||||||
series: [
|
|
||||||
{
|
|
||||||
name: 'upload',
|
|
||||||
type: 'area',
|
|
||||||
data: this.$store.state.upload_data,
|
|
||||||
},
|
},
|
||||||
{
|
removeTorrents() {
|
||||||
name: 'download',
|
|
||||||
type: 'area',
|
|
||||||
data: this.$store.state.download_data,
|
|
||||||
},
|
},
|
||||||
],
|
refreshTorrents() {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters(['getStats']),
|
||||||
|
stats() {
|
||||||
|
return this.getStats()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapMutations(['REFRESH_TORRENTS', 'CLEAR_INTERVALS']),
|
|
||||||
clearInterval() {
|
|
||||||
this.$store.commit('CLEAR_INTERVALS')
|
|
||||||
this.$data.paused = !this.$data.paused
|
|
||||||
},
|
|
||||||
startInterval() {
|
|
||||||
this.$store.dispatch('REFRESH_TORRENTS')
|
|
||||||
this.$store.dispatch('REFRESH_SESSION_STATS')
|
|
||||||
this.$data.paused = !this.$data.paused
|
|
||||||
},
|
|
||||||
pauseTorrents() {
|
|
||||||
this.$store.dispatch('PAUSE_TORRENTS')
|
|
||||||
},
|
|
||||||
resumeTorrents() {
|
|
||||||
this.$store.dispatch('RESUME_TORRENTS')
|
|
||||||
},
|
|
||||||
removeTorrents() {
|
|
||||||
this.$store.dispatch('REMOVE_TORRENTS')
|
|
||||||
},
|
|
||||||
refreshTorrents() {
|
|
||||||
this.$store.state.init_torrents = false
|
|
||||||
this.$store.dispatch('REFRESH_TORRENTS')
|
|
||||||
},
|
|
||||||
closeSnackbar() {
|
|
||||||
this.$store.state.snackbar = false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.$store.dispatch('REFRESH_SESSION_STATS')
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(['stats', 'snackbar_error', 'error_msg', 'snackbar']),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
.project.nav_upload {
|
.project.nav_upload {
|
||||||
border-left: 4px solid #00b3fa;
|
border-left: 4px solid #00b3fa;
|
||||||
}
|
}
|
||||||
.project.nav_download {
|
.project.nav_download {
|
||||||
border-left: 4px solid #64ceaa;
|
border-left: 4px solid #64ceaa;
|
||||||
}
|
}
|
||||||
.allow-spacer {
|
.allow-spacer {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
10
src/main.js
10
src/main.js
|
@ -1,20 +1,24 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import './plugins/vuetify'
|
import './plugins/vuetify'
|
||||||
import VueApexCharts from 'vue-apexcharts'
|
import VueApexCharts from 'vue-apexcharts'
|
||||||
|
import Toast from 'vue-toastification'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './services/store'
|
import store from './services/store'
|
||||||
import './registerServiceWorker'
|
import './registerServiceWorker'
|
||||||
|
|
||||||
|
import 'vue-toastification/dist/index.css'
|
||||||
|
|
||||||
Vue.use(VueApexCharts)
|
Vue.use(VueApexCharts)
|
||||||
|
|
||||||
Vue.component('apexchart', VueApexCharts)
|
Vue.component('apexchart', VueApexCharts)
|
||||||
|
|
||||||
|
Vue.use(Toast)
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
new Vue({
|
new Vue({
|
||||||
router,
|
router,
|
||||||
store,
|
store,
|
||||||
render: (h) => h(App),
|
render: h => h(App)
|
||||||
}).$mount('#app')
|
}).$mount('#app')
|
||||||
|
|
21
src/models/sessionStat.js
Normal file
21
src/models/sessionStat.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
export default class Stat {
|
||||||
|
constructor(data) {
|
||||||
|
if (data != undefined && data != null) {
|
||||||
|
this.status = data.connection_status
|
||||||
|
this.downloaded = this.formatBytes(data.dl_info_data, 1)
|
||||||
|
this.uploaded = this.formatBytes(data.up_info_data, 1)
|
||||||
|
this.dlspeed = this.formatBytes(data.dl_info_speed, 1)
|
||||||
|
this.upspeed = this.formatBytes(data.up_info_speed, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatBytes(a, b) {
|
||||||
|
if (a == 0) return '0 Bytes'
|
||||||
|
const c = 1024
|
||||||
|
const d = b || 2
|
||||||
|
const e = ['Bytes', '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]}`
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
52
src/models/torrent.js
Normal file
52
src/models/torrent.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
export default class Torrent {
|
||||||
|
constructor(data) {
|
||||||
|
this.id = data.id
|
||||||
|
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.dloaded = this.formatBytes(data.downloaded)
|
||||||
|
this.upspeed = this.formatBytes(data.upspeed, 1)
|
||||||
|
this.uploaded = this.formatBytes(data.uploaded)
|
||||||
|
this.eta = `${new Date(data.eta).getHours()}h ${new Date(
|
||||||
|
data.eta
|
||||||
|
).getMinutes()}min`
|
||||||
|
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)
|
||||||
|
// hash is used to identify
|
||||||
|
this.hash = data.hash
|
||||||
|
// available seeds
|
||||||
|
this.available_seeds = data.num_complete
|
||||||
|
this.available_peers = data.num_incomplete
|
||||||
|
}
|
||||||
|
|
||||||
|
formatState(state) {
|
||||||
|
switch (state) {
|
||||||
|
case 'pausedDL':
|
||||||
|
return 'paused'
|
||||||
|
case 'downloading':
|
||||||
|
return 'busy'
|
||||||
|
case 'stalledDL':
|
||||||
|
return 'fail'
|
||||||
|
case 'pausedUP':
|
||||||
|
return 'done'
|
||||||
|
case 'missingFiles':
|
||||||
|
return 'fail'
|
||||||
|
case 'stalledUP':
|
||||||
|
return 'done'
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatBytes(a, b) {
|
||||||
|
if (a == 0) return '0 Bytes'
|
||||||
|
const c = 1024
|
||||||
|
const d = b || 2
|
||||||
|
const e = ['Bytes', '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]}`
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,62 +1,294 @@
|
||||||
const axios = require('axios')
|
import axios from 'axios'
|
||||||
|
|
||||||
class Qbit {
|
class Qbit {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._axios = axios.create({
|
this.axios = axios.create({
|
||||||
timeout: 1000,
|
baseURL: 'api/v2'
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
async getAll(sort) {
|
this.axios.defaults.headers.post['Content-Type'] =
|
||||||
const res = await this._axios.post('/all', sort)
|
'application/x-www-form-urlencoded'
|
||||||
return res.data
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async get_sessions_stats() {
|
getAppVersion() {
|
||||||
const res = await this._axios.get('/session')
|
return this.axios.get('/app/version')
|
||||||
return res.data
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async pause_torrents(torrents) {
|
getApiVersion() {
|
||||||
const res = await this._axios.post('/pause', torrents)
|
return this.axios.get('/app/webapiVersion')
|
||||||
return res.data
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async pause_all() {
|
async login(params) {
|
||||||
const res = await this._axios.post('/pause_all')
|
const payload = new URLSearchParams(params)
|
||||||
return res.data
|
const { data } = await this.axios.post('/auth/login', payload, {
|
||||||
}
|
validateStatus(status) {
|
||||||
|
return status === 200 || status === 403
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
async resume_torrents(torrents) {
|
getGlobalTransferInfo() {
|
||||||
const res = await this._axios.post('/resume', torrents)
|
return this.axios.get('/transfer/info')
|
||||||
return res.data
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async resume_all() {
|
getAppPreferences() {
|
||||||
const res = await this._axios.post('/resume_all')
|
return this.axios.get('/app/preferences')
|
||||||
return res.data
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async add_torrent(torrent) {
|
getMainData(rid) {
|
||||||
const res = await this._axios.post('/add', torrent)
|
const params = {
|
||||||
return res
|
rid
|
||||||
}
|
}
|
||||||
|
return this.axios.get('/sync/maindata', {
|
||||||
|
params
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async remove_torrents(torrents) {
|
addTorrents(params, torrents) {
|
||||||
const res = await this._axios.post('/remove', torrents)
|
let data
|
||||||
return res.data
|
if (torrents) {
|
||||||
}
|
const formData = new FormData()
|
||||||
|
for (const [key, value] of Object.entries(params)) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
formData.append(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
async login(credentials) {
|
for (const torrent of torrents) {
|
||||||
let timeout = false
|
formData.append('torrents', torrent)
|
||||||
const res = await this._axios.post('/login', credentials).catch((error) => {
|
}
|
||||||
if (error.code === 'ECONNABORTED') timeout = true
|
|
||||||
else throw error
|
data = formData
|
||||||
})
|
} else {
|
||||||
return timeout ? 'timeout' : res.data
|
data = new URLSearchParams(params)
|
||||||
}
|
}
|
||||||
|
return this.axios.post('/torrents/add', data).then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
switchToOldUi() {
|
||||||
|
const params = {
|
||||||
|
alternative_webui_enabled: false
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.setPreferences(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
setPreferences(params) {
|
||||||
|
const data = new URLSearchParams({
|
||||||
|
json: JSON.stringify(params)
|
||||||
|
})
|
||||||
|
|
||||||
|
return this.axios.post('/app/setPreferences', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTorrentFilePriority(hash, idList, priority) {
|
||||||
|
const idListStr = idList.join('|')
|
||||||
|
const params = {
|
||||||
|
hash,
|
||||||
|
id: idListStr,
|
||||||
|
priority
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new URLSearchParams(params)
|
||||||
|
return this.axios
|
||||||
|
.post('/torrents/filePrio', data)
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
getLogs(lastId) {
|
||||||
|
const params = {
|
||||||
|
last_known_id: lastId
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.axios
|
||||||
|
.get('/log/main', {
|
||||||
|
params
|
||||||
|
})
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSpeedLimitsMode() {
|
||||||
|
return this.axios.post('/transfer/toggleSpeedLimitsMode')
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteTorrents(hashes, deleteFiles) {
|
||||||
|
return this.actionTorrents('delete', hashes, { deleteFiles })
|
||||||
|
}
|
||||||
|
|
||||||
|
pauseTorrents(hashes) {
|
||||||
|
return this.actionTorrents('pause', hashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
resumeTorrents(hashes) {
|
||||||
|
return this.actionTorrents('resume', hashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
reannounceTorrents(hashes) {
|
||||||
|
return this.actionTorrents('reannounce', hashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
recheckTorrents(hashes) {
|
||||||
|
return this.actionTorrents('recheck', hashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTorrentsCategory(hashes, category) {
|
||||||
|
return this.actionTorrents('setCategory', hashes, { category })
|
||||||
|
}
|
||||||
|
|
||||||
|
getTorrentTracker(hash) {
|
||||||
|
const params = {
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.axios
|
||||||
|
.get('/torrents/trackers', {
|
||||||
|
params
|
||||||
|
})
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
getTorrentPeers(hash, rid) {
|
||||||
|
const params = {
|
||||||
|
hash,
|
||||||
|
rid
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.axios
|
||||||
|
.get('/sync/torrentPeers', {
|
||||||
|
params
|
||||||
|
})
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
editTracker(hash, origUrl, newUrl) {
|
||||||
|
return this.actionTorrents('editTracker', [hash], { origUrl, newUrl })
|
||||||
|
}
|
||||||
|
|
||||||
|
setTorrentLocation(hashes, location) {
|
||||||
|
return this.actionTorrents('setLocation', hashes, { location })
|
||||||
|
}
|
||||||
|
|
||||||
|
getTorrentProperties(hash) {
|
||||||
|
const params = {
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.axios
|
||||||
|
.get('/torrents/properties', {
|
||||||
|
params
|
||||||
|
})
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
getTorrentPieceStates(hash) {
|
||||||
|
const params = {
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.axios
|
||||||
|
.get('/torrents/pieceStates', {
|
||||||
|
params
|
||||||
|
})
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
getTorrentFiles(hash) {
|
||||||
|
const params = {
|
||||||
|
hash
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.axios
|
||||||
|
.get('/torrents/files', {
|
||||||
|
params
|
||||||
|
})
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
getRssItems() {
|
||||||
|
const params = {
|
||||||
|
withData: true
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.axios
|
||||||
|
.get('/rss/items', {
|
||||||
|
params
|
||||||
|
})
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
addRssFeed(url, path = '') {
|
||||||
|
const params = {
|
||||||
|
url,
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new URLSearchParams(params)
|
||||||
|
return this.axios.post('/rss/addFeed', data).then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeRssFeed(path) {
|
||||||
|
const params = {
|
||||||
|
path
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new URLSearchParams(params)
|
||||||
|
return this.axios.post('/rss/removeItem', data).then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshRssFeed(path) {
|
||||||
|
const params = {
|
||||||
|
itemPath: path
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new URLSearchParams(params)
|
||||||
|
return this.axios
|
||||||
|
.post('/rss/refreshItem', data)
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
moveRssFeed(path, newPath) {
|
||||||
|
const params = {
|
||||||
|
itemPath: path,
|
||||||
|
destPath: newPath
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new URLSearchParams(params)
|
||||||
|
return this.axios.post('/rss/moveItem', data).then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
getRssRules() {
|
||||||
|
return this.axios.get('/rss/rules').then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
setRssRule(name, def) {
|
||||||
|
const params = {
|
||||||
|
ruleName: name,
|
||||||
|
ruleDef: JSON.stringify(def)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new URLSearchParams(params)
|
||||||
|
return this.axios.post('/rss/setRule', data).then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
removeRssRule(name) {
|
||||||
|
const params = {
|
||||||
|
ruleName: name
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = new URLSearchParams(params)
|
||||||
|
return this.axios.post('/rss/removeRule', data).then(Api.handleResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
actionTorrents(action, hashes, extra) {
|
||||||
|
const params = {
|
||||||
|
hashes: hashes.join('|'),
|
||||||
|
...extra
|
||||||
|
}
|
||||||
|
const data = new URLSearchParams(params)
|
||||||
|
return this.axios
|
||||||
|
.post(`/torrents/${action}`, data)
|
||||||
|
.then(Api.handleResponse)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const qbit = new Qbit()
|
export default new Qbit()
|
||||||
|
|
||||||
export default qbit
|
|
||||||
|
|
64
src/services/qbitOld.js
Normal file
64
src/services/qbitOld.js
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
const axios = require('axios')
|
||||||
|
|
||||||
|
class Qbit {
|
||||||
|
constructor() {
|
||||||
|
this._axios = axios.create({
|
||||||
|
timeout: 1000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(sort) {
|
||||||
|
const res = await this._axios.post('/all', sort)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
async get_sessions_stats() {
|
||||||
|
const res = await this._axios.get('/session')
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
async pause_torrents(torrents) {
|
||||||
|
const res = await this._axios.post('/pause', torrents)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
async pause_all() {
|
||||||
|
const res = await this._axios.post('/pause_all')
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
async resume_torrents(torrents) {
|
||||||
|
const res = await this._axios.post('/resume', torrents)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
async resume_all() {
|
||||||
|
const res = await this._axios.post('/resume_all')
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
async add_torrent(torrent) {
|
||||||
|
const res = await this._axios.post('/add', torrent)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove_torrents(torrents) {
|
||||||
|
const res = await this._axios.post('/remove', torrents)
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(credentials) {
|
||||||
|
let timeout = false
|
||||||
|
const res = await this._axios
|
||||||
|
.post('/login', credentials)
|
||||||
|
.catch(error => {
|
||||||
|
if (error.code === 'ECONNABORTED') timeout = true
|
||||||
|
else throw error
|
||||||
|
})
|
||||||
|
return timeout ? 'timeout' : res.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const qbit = new Qbit()
|
||||||
|
|
||||||
|
export default qbit
|
|
@ -1,208 +1,120 @@
|
||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
|
import { cloneDeep, merge, map, groupBy, sortBy } from 'lodash'
|
||||||
|
import Torrent from '../models/torrent'
|
||||||
|
import Stat from '../models/sessionStat'
|
||||||
|
|
||||||
import qbit from './qbit'
|
import qbit from './qbit'
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
intervals: [],
|
intervals: [],
|
||||||
stats: {
|
stats: null,
|
||||||
status: 'init',
|
upload_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
dlspeed: '6 Mbps',
|
download_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||||
upspeed: '1 Mbps',
|
torrents: [],
|
||||||
downloaded: '6.95 Gb',
|
selected_torrents: [],
|
||||||
uploaded: '1014 Mb',
|
authenticated: false,
|
||||||
|
loading: false,
|
||||||
|
sort_options: { sort: 'name', reverse: false },
|
||||||
|
rid: 0,
|
||||||
|
mainData: undefined,
|
||||||
|
preferences: null,
|
||||||
|
pasteUrl: null
|
||||||
},
|
},
|
||||||
upload_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
getters: {
|
||||||
download_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
CONTAINS_TORRENT: state => hash =>
|
||||||
torrents: [],
|
state.selected_torrents.includes(hash)
|
||||||
init_torrents: false,
|
|
||||||
selected_torrents: [],
|
|
||||||
network_error: false,
|
|
||||||
snackbar_error: false,
|
|
||||||
error_msg: '',
|
|
||||||
snackbar: false,
|
|
||||||
succes_msg: '',
|
|
||||||
authenticated: false,
|
|
||||||
loading: false,
|
|
||||||
sort_options: { sort: 'name', reverse: false },
|
|
||||||
},
|
|
||||||
getters: {
|
|
||||||
CONTAINS_TORRENT: (state) => (hash) => state.selected_torrents.includes(hash),
|
|
||||||
},
|
|
||||||
mutations: {
|
|
||||||
REFRESH_TORRENTS: async (state) => {
|
|
||||||
const torrents = await qbit.getAll(state.sort_options).catch(() => {
|
|
||||||
state.network_error = true
|
|
||||||
state.error_msg = 'Lost connection with server, reload page'
|
|
||||||
state.snackbar_error = true
|
|
||||||
})
|
|
||||||
if (torrents) {
|
|
||||||
state.torrents = torrents.map((a) => ({ ...a }))
|
|
||||||
state.init_torrents = true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
REFRESH_SESSION_STATS: async (state) => {
|
mutations: {
|
||||||
const _stats = await qbit.get_sessions_stats()
|
REMOVE_INTERVALS: state => {
|
||||||
// push in array for graph
|
state.intervals.forEach(el => clearInterval(el))
|
||||||
state.download_data.splice(0, 1)
|
},
|
||||||
if (_stats.dlspeed.indexOf('KB' > -1)) {
|
ADD_SELECTED: (state, payload) => {
|
||||||
state.download_data.push(
|
state.selected_torrents.push(payload)
|
||||||
_stats.dlspeed.substring(0, _stats.dlspeed.indexOf(' ')) / 1000,
|
},
|
||||||
)
|
REMOVE_SELECTED: (state, payload) => {
|
||||||
} else {
|
state.selected_torrents.splice(
|
||||||
state.download_data.push(
|
state.selected_torrents.indexOf(payload),
|
||||||
_stats.dlspeed(0, _stats.dlspeed.indexOf(' ')),
|
1
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
state.upload_data.splice(0, 1)
|
RESET_SELECTED: state => {
|
||||||
if (_stats.upspeed.indexOf('KB' > -1)) {
|
state.selected_torrents = []
|
||||||
state.upload_data.push(
|
},
|
||||||
_stats.upspeed.substring(0, _stats.upspeed.indexOf(' ')) / 1000,
|
PAUSE_TORRENTS: async state => {
|
||||||
)
|
let res
|
||||||
} else {
|
if (state.selected_torrents.length === 0) {
|
||||||
state.upload_data.push(
|
res = await qbit.pause_all()
|
||||||
_stats.upspeed.substring(0, _stats.upspeed.indexOf(' ')),
|
} else {
|
||||||
)
|
res = await qbit.pause_torrents(state.selected_torrents)
|
||||||
}
|
}
|
||||||
state.stats = _stats
|
},
|
||||||
},
|
RESUME_TORRENTS: async state => {
|
||||||
CLEAR_INTERVALS: (state) => {
|
let res
|
||||||
if (state.intervals.length > 1) { state.intervals.forEach((el) => clearInterval(el)) }
|
if (state.selected_torrents.length === 0) {
|
||||||
},
|
res = await qbit.resume_all()
|
||||||
ADD_SELECTED: (state, payload) => {
|
} else {
|
||||||
state.selected_torrents.push(payload)
|
res = await qbit.resume_torrents(state.selected_torrents)
|
||||||
},
|
}
|
||||||
REMOVE_SELECTED: (state, payload) => {
|
},
|
||||||
state.selected_torrents.splice(
|
ADD_TORRENT: async (state, payload) => {
|
||||||
state.selected_torrents.indexOf(payload),
|
const res = await qbit.add_torrent(payload)
|
||||||
1,
|
if (res.statusText === 'OK') {
|
||||||
)
|
state.snackbar = true
|
||||||
},
|
state.succes_msg = 'Awesome! You added a new Torrent.'
|
||||||
RESET_SELECTED: (state) => {
|
setTimeout(() => {
|
||||||
state.selected_torrents = []
|
state.snackbar = false
|
||||||
},
|
}, 4000)
|
||||||
PAUSE_TORRENTS: async (state) => {
|
} else {
|
||||||
let res
|
state.snackbar_error = true
|
||||||
if (state.selected_torrents.length === 0) {
|
state.error_msg = 'Something went wrong'
|
||||||
res = await qbit.pause_all()
|
setTimeout(() => {
|
||||||
} else {
|
state.snackbar_error = false
|
||||||
res = await qbit.pause_torrents(state.selected_torrents)
|
}, 4000)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RESUME_TORRENTS: async (state) => {
|
REMOVE_TORRENTS: async state => {
|
||||||
let res
|
if (state.selected_torrents.length !== 0) {
|
||||||
if (state.selected_torrents.length === 0) {
|
const res = await qbit.remove_torrents(state.selected_torrents)
|
||||||
res = await qbit.resume_all()
|
}
|
||||||
} else {
|
},
|
||||||
res = await qbit.resume_torrents(state.selected_torrents)
|
LOGIN: async (state, payload) => {
|
||||||
}
|
const res = await qbit.login(payload)
|
||||||
},
|
if (res === 'Ok.') {
|
||||||
ADD_TORRENT: async (state, payload) => {
|
state.loading = false
|
||||||
const res = await qbit.add_torrent(payload)
|
Vue.$toast.success('Successfully logged in!')
|
||||||
if (res.statusText === 'OK') {
|
state.authenticated = true
|
||||||
state.snackbar = true
|
}
|
||||||
state.succes_msg = 'Awesome! You added a new Torrent.'
|
},
|
||||||
setTimeout(() => {
|
updateMainData: async state => {
|
||||||
state.snackbar = false
|
const rid = state.rid ? state.rid : undefined
|
||||||
}, 4000)
|
const { data } = await qbit.getMainData(rid)
|
||||||
} else {
|
|
||||||
state.snackbar_error = true
|
// torrents
|
||||||
state.error_msg = 'Something went wrong'
|
state.torrents = []
|
||||||
setTimeout(() => {
|
for (const [key, value] of Object.entries(data.torrents)) {
|
||||||
state.snackbar_error = false
|
state.torrents.push(new Torrent({ id: key, ...value }))
|
||||||
}, 4000)
|
}
|
||||||
}
|
|
||||||
},
|
// download speed
|
||||||
REMOVE_TORRENTS: async (state) => {
|
state.stats = new Stat(data.server_state)
|
||||||
if (state.selected_torrents.length !== 0) {
|
|
||||||
const res = await qbit.remove_torrents(state.selected_torrents)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
LOGIN: async (state, payload) => {
|
|
||||||
const res = await qbit.login(payload)
|
|
||||||
if (res == 'timeout') {
|
|
||||||
state.loading = false
|
|
||||||
state.snackbar_error = true
|
|
||||||
state.error_msg = 'Express server timed out!'
|
|
||||||
setTimeout(() => {
|
|
||||||
state.snackbar_error = false
|
|
||||||
}, 4000)
|
|
||||||
} else {
|
|
||||||
switch (res) {
|
|
||||||
case 'No such user':
|
|
||||||
state.snackbar_error = true
|
|
||||||
state.error_msg = 'No such user!'
|
|
||||||
setTimeout(() => {
|
|
||||||
state.snackbar_error = false
|
|
||||||
}, 4000)
|
|
||||||
break
|
|
||||||
case 'Wrong password!':
|
|
||||||
state.snackbar_error = true
|
|
||||||
state.error_msg = 'Wrong password!'
|
|
||||||
setTimeout(() => {
|
|
||||||
state.snackbar_error = false
|
|
||||||
}, 4000)
|
|
||||||
break
|
|
||||||
case 'SUCCES':
|
|
||||||
state.snackbar = true
|
|
||||||
state.succes_msg = 'Succesfully logged in!'
|
|
||||||
state.authenticated = true
|
|
||||||
setTimeout(() => {
|
|
||||||
state.snackbar = false
|
|
||||||
}, 4000)
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
state.snackbar_error = true
|
|
||||||
state.error_msg = 'Something went wrong'
|
|
||||||
setTimeout(() => {
|
|
||||||
state.snackbar_error = false
|
|
||||||
}, 4000)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
state.loading = false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
actions: {
|
||||||
actions: {
|
INIT_INTERVALS: async (context) => {
|
||||||
REFRESH_TORRENTS: (context) => {
|
context.state.intervals[0] = setInterval(() => {
|
||||||
context.state.intervals[1] = setInterval(async () => {
|
context.commit('updateMainData')
|
||||||
context.commit('REFRESH_TORRENTS')
|
}, 2000)
|
||||||
if (context.state.network_error) {
|
},
|
||||||
context.commit('CLEAR_INTERVALS')
|
LOGIN: async (context, payload) => {
|
||||||
|
context.commit('LOGIN', payload)
|
||||||
|
context.commit('updateMainData')
|
||||||
}
|
}
|
||||||
}, 2000)
|
|
||||||
},
|
},
|
||||||
REFRESH_SESSION_STATS: (context) => {
|
getters: {
|
||||||
context.state.intervals[0] = setInterval(async () => {
|
getStats: state => () => state.stats
|
||||||
context.commit('REFRESH_SESSION_STATS')
|
}
|
||||||
}, 1000)
|
|
||||||
},
|
|
||||||
ADD_SELECTED: (context, payload) => {
|
|
||||||
context.commit('ADD_SELECTED', payload)
|
|
||||||
},
|
|
||||||
REMOVE_SELECTED: (context, payload) => {
|
|
||||||
context.commit('REMOVE_SELECTED', payload)
|
|
||||||
},
|
|
||||||
RESET_SELECTED: (context) => {
|
|
||||||
context.commit('RESET_SELECTED')
|
|
||||||
},
|
|
||||||
PAUSE_TORRENTS: (context) => {
|
|
||||||
context.commit('PAUSE_TORRENTS')
|
|
||||||
},
|
|
||||||
RESUME_TORRENTS: (context) => {
|
|
||||||
context.commit('RESUME_TORRENTS')
|
|
||||||
},
|
|
||||||
ADD_TORRENT: (context, payload) => {
|
|
||||||
context.commit('ADD_TORRENT', payload)
|
|
||||||
},
|
|
||||||
REMOVE_TORRENTS: (context) => {
|
|
||||||
context.commit('REMOVE_TORRENTS')
|
|
||||||
},
|
|
||||||
LOGIN: (context, payload) => {
|
|
||||||
context.commit('LOGIN', payload)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,273 +1,294 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="dashboard" @click.self="resetSelected">
|
<div class="dashboard" @click.self="resetSelected">
|
||||||
<h1 class="subheading grey--text">Dashboard</h1>
|
<h1 class="subheading grey--text">Dashboard</h1>
|
||||||
<v-container class="my-4" @click.self="resetSelected">
|
<v-container class="my-4" @click.self="resetSelected">
|
||||||
<!-- justify-center here in layout to center!! -->
|
<!-- justify-center here in layout to center!! -->
|
||||||
<v-flex xs12 sm6 md3 @click.self="resetSelected">
|
<v-flex xs12 sm6 md3 @click.self="resetSelected">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
flat
|
flat
|
||||||
label="type to sort..."
|
label="type to sort..."
|
||||||
height="50"
|
height="50"
|
||||||
clearable
|
clearable
|
||||||
solo
|
solo
|
||||||
hint="eg `size desc` + enter"
|
hint="eg `size desc` + enter"
|
||||||
background-color="grey lighten-3"
|
background-color="grey lighten-3"
|
||||||
v-model="sort_input"
|
v-model="sort_input"
|
||||||
@keyup.enter.native="sortBy"
|
@keyup.enter.native="sortBy"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-flex>
|
|
||||||
<v-container v-if="!init_torrents" fill-height>
|
|
||||||
<div style="margin: 150px auto;">
|
|
||||||
<v-progress-circular :size="100" indeterminate color="green_accent"></v-progress-circular>
|
|
||||||
</div>
|
|
||||||
</v-container>
|
|
||||||
<div v-if="torrents.length === 0 && init_torrents" class="mt-5 text-xs-center">
|
|
||||||
<p class="grey--text">No active Torrents!</p>
|
|
||||||
</div>
|
|
||||||
<div v-else>
|
|
||||||
<v-card
|
|
||||||
ripple
|
|
||||||
flat
|
|
||||||
v-for="torrent in torrents"
|
|
||||||
:key="torrent.name"
|
|
||||||
class="pointer"
|
|
||||||
:class=" containsTorrent(torrent.hash) ? 'grey lighten-3' : ''"
|
|
||||||
@click.native="selectTorrent(torrent.hash)"
|
|
||||||
>
|
|
||||||
<v-layout row wrap :class="`pa-3 project ${torrent.state}`">
|
|
||||||
<v-flex xs12 sm2 md3>
|
|
||||||
<div class="caption grey--text">Torrent title</div>
|
|
||||||
<div>{{ torrent.name }}</div>
|
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs6 sm1 md1 class="mr-2">
|
<div v-if="torrents.length === 0" class="mt-5 text-xs-center">
|
||||||
<div class="caption grey--text">Size</div>
|
<p class="grey--text">No active Torrents!</p>
|
||||||
<div>
|
</div>
|
||||||
{{ torrent.size.substring(0, torrent.size.indexOf(' '))}}
|
<div v-else>
|
||||||
<span
|
<v-card
|
||||||
class="caption grey--text"
|
ripple
|
||||||
>{{ torrent.size.substring(torrent.size.indexOf(' ')) }}</span>
|
flat
|
||||||
</div>
|
v-for="torrent in torrents"
|
||||||
</v-flex>
|
:key="torrent.name"
|
||||||
<v-flex xs5 sm1 md1 class="mr-2">
|
class="pointer"
|
||||||
<div class="caption grey--text">Done</div>
|
:class="
|
||||||
<div>
|
containsTorrent(torrent.hash) ? 'grey lighten-3' : ''
|
||||||
{{ torrent.dloaded.substring(0, torrent.dloaded.indexOf(' ')) }}
|
"
|
||||||
<span
|
@click.native="selectTorrent(torrent.hash)"
|
||||||
class="caption grey--text"
|
>
|
||||||
>{{ torrent.dloaded.substring(torrent.dloaded.indexOf(' ')) }}</span>
|
<v-layout row wrap :class="`pa-3 project ${torrent.state}`">
|
||||||
</div>
|
<v-flex xs12 sm2 md3>
|
||||||
</v-flex>
|
<div class="caption grey--text">Torrent title</div>
|
||||||
<v-flex xs6 sm1 md1 class="mr-2">
|
<div>{{ torrent.name }}</div>
|
||||||
<div class="caption grey--text">Download</div>
|
</v-flex>
|
||||||
<div>
|
<v-flex xs6 sm1 md1 class="mr-2">
|
||||||
{{ torrent.dlspeed.substring(0, torrent.dlspeed.indexOf(' ')) }}
|
<div class="caption grey--text">Size</div>
|
||||||
<span
|
<div>
|
||||||
class="caption grey--text"
|
{{
|
||||||
>{{ torrent.dlspeed.substring(torrent.dlspeed.indexOf(' ')) }}</span>
|
torrent.size.substring(
|
||||||
</div>
|
0,
|
||||||
</v-flex>
|
torrent.size.indexOf(' ')
|
||||||
<v-flex xs5 sm1 md1 class="mr-2">
|
)
|
||||||
<div class="caption grey--text">Upload</div>
|
}}
|
||||||
<div>
|
<span class="caption grey--text">{{
|
||||||
{{ torrent.upspeed.substring(0, torrent.upspeed.indexOf(' ')) }}
|
torrent.size.substring(
|
||||||
<span
|
torrent.size.indexOf(' ')
|
||||||
class="caption grey--text"
|
)
|
||||||
>{{ torrent.upspeed.substring(torrent.upspeed.indexOf(' ')) }}</span>
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs6 sm1 md1 class="mr-2">
|
<v-flex xs5 sm1 md1 class="mr-2">
|
||||||
<div class="caption grey--text">ETA</div>
|
<div class="caption grey--text">Done</div>
|
||||||
<div>{{ torrent.eta }}</div>
|
<div>
|
||||||
</v-flex>
|
{{
|
||||||
<v-flex xs5 sm1 md1 class="mr-2">
|
torrent.dloaded.substring(
|
||||||
<div class="caption grey--text">Peers</div>
|
0,
|
||||||
<div>
|
torrent.dloaded.indexOf(' ')
|
||||||
{{ torrent.num_leechs }}
|
)
|
||||||
<span
|
}}
|
||||||
class="grey--text caption"
|
<span class="caption grey--text">{{
|
||||||
>/{{torrent.available_peers}}</span>
|
torrent.dloaded.substring(
|
||||||
</div>
|
torrent.dloaded.indexOf(' ')
|
||||||
</v-flex>
|
)
|
||||||
<v-flex xs5 sm1 md1 class="mr-2">
|
}}</span>
|
||||||
<div class="caption grey--text">Seeds</div>
|
</div>
|
||||||
<div>
|
</v-flex>
|
||||||
{{ torrent.num_seeds }}
|
<v-flex xs6 sm1 md1 class="mr-2">
|
||||||
<span class="grey--text caption">/{{torrent.available_seeds}}</span>
|
<div class="caption grey--text">Download</div>
|
||||||
</div>
|
<div>
|
||||||
</v-flex>
|
{{
|
||||||
<v-flex xs4 sm12 md1>
|
torrent.dlspeed.substring(
|
||||||
<div class="right">
|
0,
|
||||||
<v-chip
|
torrent.dlspeed.indexOf(' ')
|
||||||
small
|
)
|
||||||
:class="`${torrent.state} white--text my-2 caption`"
|
}}
|
||||||
>{{ torrent.state }}</v-chip>
|
<span class="caption grey--text">{{
|
||||||
</div>
|
torrent.dlspeed.substring(
|
||||||
</v-flex>
|
torrent.dlspeed.indexOf(' ')
|
||||||
<v-flex xs12 sm12 md12>
|
)
|
||||||
<v-progress-linear
|
}}</span>
|
||||||
height="3"
|
</div>
|
||||||
color="cyan darken-1"
|
</v-flex>
|
||||||
background-color="cyan lighten-3"
|
<v-flex xs5 sm1 md1 class="mr-2">
|
||||||
:value="(torrent.dloaded/torrent.size)*100"
|
<div class="caption grey--text">Upload</div>
|
||||||
></v-progress-linear>
|
<div>
|
||||||
</v-flex>
|
{{
|
||||||
</v-layout>
|
torrent.upspeed.substring(
|
||||||
<v-divider></v-divider>
|
0,
|
||||||
</v-card>
|
torrent.upspeed.indexOf(' ')
|
||||||
</div>
|
)
|
||||||
</v-container>
|
}}
|
||||||
</div>
|
<span class="caption grey--text">{{
|
||||||
|
torrent.upspeed.substring(
|
||||||
|
torrent.upspeed.indexOf(' ')
|
||||||
|
)
|
||||||
|
}}</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 xs5 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 xs4 sm12 md1>
|
||||||
|
<div class="right">
|
||||||
|
<v-chip
|
||||||
|
small
|
||||||
|
:class="`${torrent.state} white--text my-2 caption`"
|
||||||
|
>{{ torrent.state }}</v-chip
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex xs12 sm12 md12>
|
||||||
|
<v-progress-linear
|
||||||
|
height="3"
|
||||||
|
color="cyan darken-1"
|
||||||
|
background-color="cyan lighten-3"
|
||||||
|
:value="(torrent.dloaded / torrent.size) * 100"
|
||||||
|
></v-progress-linear>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
<v-divider></v-divider>
|
||||||
|
</v-card>
|
||||||
|
</div>
|
||||||
|
</v-container>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapState, mapMutations, mapGetters } from 'vuex'
|
import { mapState, mapMutations, mapGetters } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
sort_input: '',
|
sort_input: ''
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
...mapState(['torrents', 'init_torrents']),
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
...mapMutations(['SORT_TORRENTS']),
|
|
||||||
sortBy() {
|
|
||||||
let name; let
|
|
||||||
reverse
|
|
||||||
// search if order was presented
|
|
||||||
const index = this.sort_input.indexOf(' ')
|
|
||||||
if (index > -1) {
|
|
||||||
name = this.sort_input.substring(0, index)
|
|
||||||
const temp = this.sort_input.substring(index)
|
|
||||||
if (temp.indexOf('asc') > -1) {
|
|
||||||
reverse = false
|
|
||||||
} else if (temp.indexOf('desc') > -1) {
|
|
||||||
reverse = true
|
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
// no order so we assume input is propname
|
computed: {
|
||||||
name = this.sort_input
|
...mapState(['mainData', 'torrents'])
|
||||||
reverse = false
|
},
|
||||||
}
|
methods: {
|
||||||
// prop names
|
...mapMutations(['SORT_TORRENTS']),
|
||||||
switch (name) {
|
sortBy() {
|
||||||
case 'title':
|
let name
|
||||||
case 'name':
|
let reverse
|
||||||
case 'Name':
|
// search if order was presented
|
||||||
case 'Title':
|
const index = this.sort_input.indexOf(' ')
|
||||||
name = 'name'
|
if (index > -1) {
|
||||||
break
|
name = this.sort_input.substring(0, index)
|
||||||
case 'size':
|
const temp = this.sort_input.substring(index)
|
||||||
case 'Size':
|
if (temp.indexOf('asc') > -1) {
|
||||||
name = 'size'
|
reverse = false
|
||||||
break
|
} else if (temp.indexOf('desc') > -1) {
|
||||||
case 'dlspeed':
|
reverse = true
|
||||||
case 'Dlspeed':
|
}
|
||||||
case 'Download':
|
} else {
|
||||||
case 'download':
|
// no order so we assume input is propname
|
||||||
case 'downloadspeed':
|
name = this.sort_input
|
||||||
name = 'dlspeed'
|
reverse = false
|
||||||
break
|
}
|
||||||
case 'upspeed':
|
// prop names
|
||||||
case 'upload':
|
switch (name) {
|
||||||
case 'Upload':
|
case 'title':
|
||||||
case 'Upspeed':
|
case 'name':
|
||||||
case 'uploadspeed':
|
case 'Name':
|
||||||
name = 'upspeed'
|
case 'Title':
|
||||||
break
|
name = 'name'
|
||||||
case 'leechs':
|
break
|
||||||
case 'leechers':
|
case 'size':
|
||||||
case 'leech':
|
case 'Size':
|
||||||
case 'peers':
|
name = 'size'
|
||||||
case 'Leechs':
|
break
|
||||||
case 'Leechers':
|
case 'dlspeed':
|
||||||
case 'Leech':
|
case 'Dlspeed':
|
||||||
case 'Peers':
|
case 'Download':
|
||||||
name = 'num_leechs'
|
case 'download':
|
||||||
break
|
case 'downloadspeed':
|
||||||
case 'seeds':
|
name = 'dlspeed'
|
||||||
case 'seeders':
|
break
|
||||||
case 'Seeds':
|
case 'upspeed':
|
||||||
case 'Seeders':
|
case 'upload':
|
||||||
name = 'num_seeds'
|
case 'Upload':
|
||||||
break
|
case 'Upspeed':
|
||||||
case 'remaining':
|
case 'uploadspeed':
|
||||||
case 'time':
|
name = 'upspeed'
|
||||||
case 'Time':
|
break
|
||||||
case 'ETA':
|
case 'leechs':
|
||||||
case 'eta':
|
case 'leechers':
|
||||||
name = 'eta'
|
case 'leech':
|
||||||
break
|
case 'peers':
|
||||||
case 'done':
|
case 'Leechs':
|
||||||
case 'downloaded':
|
case 'Leechers':
|
||||||
case 'dloaded':
|
case 'Leech':
|
||||||
case 'Done':
|
case 'Peers':
|
||||||
case 'Downloaded':
|
name = 'num_leechs'
|
||||||
case 'Dloaded':
|
break
|
||||||
name = 'downloaded'
|
case 'seeds':
|
||||||
break
|
case 'seeders':
|
||||||
case 'state':
|
case 'Seeds':
|
||||||
case 'status':
|
case 'Seeders':
|
||||||
case 'State':
|
name = 'num_seeds'
|
||||||
case 'Status':
|
break
|
||||||
name = 'state'
|
case 'remaining':
|
||||||
break
|
case 'time':
|
||||||
default:
|
case 'Time':
|
||||||
name = 'name'
|
case 'ETA':
|
||||||
break
|
case 'eta':
|
||||||
}
|
name = 'eta'
|
||||||
|
break
|
||||||
|
case 'done':
|
||||||
|
case 'downloaded':
|
||||||
|
case 'dloaded':
|
||||||
|
case 'Done':
|
||||||
|
case 'Downloaded':
|
||||||
|
case 'Dloaded':
|
||||||
|
name = 'downloaded'
|
||||||
|
break
|
||||||
|
case 'state':
|
||||||
|
case 'status':
|
||||||
|
case 'State':
|
||||||
|
case 'Status':
|
||||||
|
name = 'state'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
name = 'name'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
this.$store.state.sort_options = { name, reverse }
|
this.$store.state.sort_options = { name, reverse }
|
||||||
|
},
|
||||||
|
selectTorrent(hash) {},
|
||||||
|
containsTorrent(hash) {},
|
||||||
|
resetSelected() {}
|
||||||
},
|
},
|
||||||
selectTorrent(hash) {
|
created() {
|
||||||
if (this.containsTorrent(hash)) {
|
this.$store.dispatch('INIT_INTERVALS')
|
||||||
this.$store.dispatch('REMOVE_SELECTED', hash)
|
|
||||||
} else {
|
|
||||||
this.$store.dispatch('ADD_SELECTED', hash)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
containsTorrent(hash) {
|
beforeDestroy() {
|
||||||
return this.$store.getters.CONTAINS_TORRENT(hash)
|
this.$store.commit('REMOVE_INTERVALS')
|
||||||
},
|
}
|
||||||
resetSelected() {
|
|
||||||
this.$store.dispatch('RESET_SELECTED')
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.$store.dispatch('REFRESH_TORRENTS')
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.project.done {
|
.project.done {
|
||||||
border-left: 4px solid #3cd1c2;
|
border-left: 4px solid #3cd1c2;
|
||||||
}
|
}
|
||||||
.project.busy {
|
.project.busy {
|
||||||
border-left: 4px solid #ffaa2c;
|
border-left: 4px solid #ffaa2c;
|
||||||
}
|
}
|
||||||
.project.fail {
|
.project.fail {
|
||||||
border-left: 4px solid #f83e70;
|
border-left: 4px solid #f83e70;
|
||||||
}
|
}
|
||||||
.project.paused {
|
.project.paused {
|
||||||
border-left: 4px solid #cfd8dc;
|
border-left: 4px solid #cfd8dc;
|
||||||
}
|
}
|
||||||
.v-chip.done {
|
.v-chip.done {
|
||||||
background: #3cd1c2;
|
background: #3cd1c2;
|
||||||
}
|
}
|
||||||
.v-chip.busy {
|
.v-chip.busy {
|
||||||
background: #ffaa2c;
|
background: #ffaa2c;
|
||||||
}
|
}
|
||||||
.v-chip.fail {
|
.v-chip.fail {
|
||||||
background: #f83e70;
|
background: #f83e70;
|
||||||
}
|
}
|
||||||
.v-chip.paused {
|
.v-chip.paused {
|
||||||
background: #cfd8dc;
|
background: #cfd8dc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pointer {
|
.pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
devServer: {
|
outputDir: 'dist/public',
|
||||||
proxy: 'http://localhost:3001/',
|
publicPath: './',
|
||||||
},
|
|
||||||
|
devServer: {
|
||||||
|
port: 8000,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://127.0.0.1:8080'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue