mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2025-03-14 12:10:18 +03:00
fixed deployment (finally)
This commit is contained in:
parent
3f35340c0d
commit
9374f5bab8
18 changed files with 3855 additions and 3793 deletions
|
@ -1,6 +1,7 @@
|
|||
nodemodules
|
||||
src
|
||||
LICENSE.md
|
||||
package-lock.json
|
||||
package.json
|
||||
README.md
|
||||
README.md
|
||||
babel.config.js
|
||||
.gitignore
|
||||
.env
|
7
.env.example
Normal file
7
.env.example
Normal file
|
@ -0,0 +1,7 @@
|
|||
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
|
28
.eslintrc.js
Normal file
28
.eslintrc.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
},
|
||||
extends: ['plugin:vue/essential', 'airbnb-base'],
|
||||
globals: {
|
||||
Atomics: 'readonly',
|
||||
SharedArrayBuffer: 'readonly',
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 2018,
|
||||
},
|
||||
plugins: ['vue'],
|
||||
rules: {
|
||||
semi: ['warn', 'never'],
|
||||
'no-console' : 0,
|
||||
camelcase: 0,
|
||||
'no-restricted-syntax': 0,
|
||||
'no-shadow': 0,
|
||||
'class-methods-use-this': 0,
|
||||
'prefer-promise-reject-errors': 0,
|
||||
'no-underscore-dangle': 0,
|
||||
'no-param-reassign' : 0,
|
||||
'no-unused-vars': 0
|
||||
},
|
||||
}
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -6,6 +6,7 @@ server/dist
|
|||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.env
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
|
@ -20,7 +21,3 @@ yarn-error.log*
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw*
|
||||
|
||||
#config files
|
||||
server/config/config.json
|
||||
src/config/config.json
|
13
Dockerfile
13
Dockerfile
|
@ -1,15 +1,14 @@
|
|||
FROM node:10-slim
|
||||
|
||||
WORKDIR /app
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY package.json /app
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install
|
||||
|
||||
COPY server /app
|
||||
COPY . .
|
||||
|
||||
COPY dist /app
|
||||
EXPOSE 3001
|
||||
|
||||
EXPOSE 3009
|
||||
|
||||
CMD ["npm", "start"]
|
||||
CMD ["node", "server/server.js"]
|
96
README.md
96
README.md
|
@ -1,58 +1,112 @@
|
|||
|
||||
# VueTorrent
|
||||
|
||||
A modern looking WEBUI for qBittorrent made with Vuejs & express!
|
||||
|
||||
(support for more clients coming in the future)
|
||||
|
||||
> Vue, Node, Express, qBitorrent
|
||||
|
||||
## Screenshots
|
||||
|
||||
<p align="center">
|
||||
<a href="https://i.imgur.com/vPBcrK4.png"><img src="https://i.imgur.com/vPBcrK4.png" title="Desktop" alt="Desktop Screenshot" ></a>
|
||||
|
||||
|
||||
A modern looking WEBUI for qBittorrent made with Vuejs & express!
|
||||
|
||||
(support for more clients coming in the future)
|
||||
|
||||
> Vue, Node, Express, qBitorrent
|
||||
|
||||
## Screenshots
|
||||
|
||||
<p align="center">
|
||||
|
||||
<a href="https://i.imgur.com/vPBcrK4.png"><img src="https://i.imgur.com/vPBcrK4.png" title="Desktop" alt="Desktop Screenshot" ></a>
|
||||
|
||||
</p>
|
||||
|
||||
<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="500"></a>
|
||||
|
||||
<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>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
- Probably a Docker image but I haven't properly figured out this part yet...
|
||||
Easiest way is with docker-compose:
|
||||
```
|
||||
vuetorrent:
|
||||
image: wdaan/vuetorrent
|
||||
container_name: vuetorrent
|
||||
restart: always
|
||||
ports:
|
||||
- "4000:4000"
|
||||
environment:
|
||||
- VUE_APP_DOMAIN=http://172.18.0.4:4000
|
||||
- VUE_APP_WEB_USER=vuetr
|
||||
- VUE_APP_WEB_PASS=vuetr
|
||||
- QBIT_USER=qbit
|
||||
- QBIT_PASS=qbit
|
||||
- QBIT_HOST=http://10.0.0.8:8080
|
||||
- PORT=4000
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
FRONTEND : Git clone & npm run serve!
|
||||
FRONTEND : Git clone & npm run serve!
|
||||
|
||||
SERVER : npm run build & npm run start!
|
||||
|
||||
SERVER : (npm run build, copy dist to /server (optional)) & nodemon server.js!
|
||||
|
||||
## 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...)
|
||||
|
||||
|
||||
|
||||
## 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!
|
||||
|
||||
|
||||
- **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>
|
||||
|
||||
|
||||
- <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)
|
||||
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
6746
package-lock.json
generated
6746
package-lock.json
generated
File diff suppressed because it is too large
Load diff
54
package.json
54
package.json
|
@ -6,39 +6,45 @@
|
|||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint",
|
||||
"start" : "nodemon server/server.js"
|
||||
"start": "nodemon server/server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"apexcharts": "^3.6.5",
|
||||
"axios": "^0.18.0",
|
||||
"date-fns": "^1.29.0",
|
||||
"express": "^4.16.4",
|
||||
"filepond": "^4.3.9",
|
||||
"filepond-plugin-file-validate-size": "^2.1.3",
|
||||
"apexcharts": "^3.8.4",
|
||||
"axios": "^0.18.1",
|
||||
"cors": "^2.8.5",
|
||||
"date-fns": "^1.30.1",
|
||||
"dotenv": "^8.0.0",
|
||||
"express": "^4.17.1",
|
||||
"filepond": "^4.4.12",
|
||||
"filepond-plugin-file-validate-size": "^2.2.0",
|
||||
"filepond-plugin-file-validate-type": "^1.2.4",
|
||||
"filepond-plugin-image-preview": "^4.0.8",
|
||||
"multer": "^1.4.1",
|
||||
"filepond-plugin-image-preview": "^4.2.1",
|
||||
"multer": "^1.4.2",
|
||||
"qbittorrent-api": "^1.0.0",
|
||||
"register-service-worker": "^1.6.2",
|
||||
"vue": "^2.5.17",
|
||||
"vue-apexcharts": "^1.3.2",
|
||||
"vue-filepond": "^5.1.0",
|
||||
"vue-router": "^3.0.1",
|
||||
"vuetify": "^1.3.0",
|
||||
"vuex": "^3.1.0"
|
||||
"vue": "^2.6.10",
|
||||
"vue-apexcharts": "^1.4.0",
|
||||
"vue-filepond": "^5.1.3",
|
||||
"vue-router": "^3.1.2",
|
||||
"vuetify": "^1.5.16",
|
||||
"vuex": "^3.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.5.5",
|
||||
"@vue/cli-plugin-eslint": "^3.0.5",
|
||||
"@vue/cli-plugin-pwa": "^3.6.0",
|
||||
"@vue/cli-service": "^3.5.3",
|
||||
"@vue/cli-plugin-babel": "^3.10.0",
|
||||
"@vue/cli-plugin-eslint": "^3.10.0",
|
||||
"@vue/cli-plugin-pwa": "^3.10.0",
|
||||
"@vue/cli-service": "^3.10.0",
|
||||
"eslint": "^6.1.0",
|
||||
"eslint-config-airbnb-base": "^14.0.0",
|
||||
"eslint-plugin-import": "^2.18.2",
|
||||
"eslint-plugin-vue": "^5.2.3",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.1",
|
||||
"vue-cli-plugin-vuetify": "^0.4.5",
|
||||
"vue-template-compiler": "^2.5.17",
|
||||
"vuetify-loader": "^1.0.5",
|
||||
"webpack": "^4.30.0",
|
||||
"webpack-cli": "^3.3.0"
|
||||
"vue-cli-plugin-vuetify": "^0.4.6",
|
||||
"vue-template-compiler": "^2.6.10",
|
||||
"vuetify-loader": "^1.3.0",
|
||||
"webpack": "^4.39.1",
|
||||
"webpack-cli": "^3.3.6"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"qbit_user": "admin",
|
||||
"qbit_pass": "adminadmin",
|
||||
"qbit_host": "https://qbittorrent.mydomain.com",
|
||||
"web_user": "username",
|
||||
"web_pass": " password",
|
||||
"web_host": "https://vuetorrent.mydomain.com"
|
||||
}
|
|
@ -1,53 +1,52 @@
|
|||
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;
|
||||
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';
|
||||
return 'paused'
|
||||
case 'downloading':
|
||||
return 'busy';
|
||||
return 'busy'
|
||||
case 'stalledDL':
|
||||
return 'fail';
|
||||
return 'fail'
|
||||
case 'pausedUP':
|
||||
return 'done';
|
||||
return 'done'
|
||||
case 'missingFiles':
|
||||
return 'fail';
|
||||
case 'stalledUP':
|
||||
return 'done';
|
||||
return 'fail'
|
||||
case 'stalledUP':
|
||||
return 'done'
|
||||
default:
|
||||
return undefined;
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
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,95 +1,96 @@
|
|||
const api = require('qbittorrent-api');
|
||||
const Torrent = require('./models/torrent.class.js');
|
||||
const Stat = require('./models/stat.class.js');
|
||||
const api = require('qbittorrent-api')
|
||||
const dotenv = require('dotenv')
|
||||
const Torrent = require('./models/torrent.class.js')
|
||||
const Stat = require('./models/stat.class.js')
|
||||
|
||||
const info = require('./config/config.json');
|
||||
dotenv.config()
|
||||
|
||||
//server
|
||||
const qbt = api.connect(info.qbit_host, info.qbit_user, info.qbit_pass);
|
||||
// server
|
||||
const qbt = api.connect(process.env.QBIT_HOST, process.env.QBIT_USER, process.env.QBIT_PASS)
|
||||
|
||||
class Qbit {
|
||||
async get_all(prop) {
|
||||
return new Promise((resolve, reject) => {
|
||||
qbt.all({sort: prop.name, reverse: prop.reverse}, (err, res) => {
|
||||
let torrents = [];
|
||||
res.forEach(el => {
|
||||
//console.log(el);
|
||||
const t = new Torrent(el);
|
||||
torrents.push(t);
|
||||
});
|
||||
//console.log(torrents[0]);
|
||||
resolve(torrents);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
qbt.all({ sort: prop.name, reverse: prop.reverse }, (err, res) => {
|
||||
const torrents = []
|
||||
res.forEach((el) => {
|
||||
// console.log(el);
|
||||
const t = new Torrent(el)
|
||||
torrents.push(t)
|
||||
})
|
||||
// console.log(torrents[0]);
|
||||
resolve(torrents)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async get_session_stats() {
|
||||
return new Promise((resolve, reject) => {
|
||||
qbt.transferInfo((err, res) => {
|
||||
let stat = new Stat(res);
|
||||
//console.log(stat);
|
||||
resolve(stat);
|
||||
reject('something went wrong:' + err);
|
||||
});
|
||||
});
|
||||
const stat = new Stat(res)
|
||||
// console.log(stat);
|
||||
resolve(stat)
|
||||
reject(`something went wrong:${err}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async pause_torrents(torrents) {
|
||||
return new Promise((resolve, reject) => {
|
||||
qbt.pause(torrents, (err, res) => {
|
||||
resolve(res);
|
||||
reject('something went wrong:' + err);
|
||||
});
|
||||
});
|
||||
resolve(res)
|
||||
reject(`something went wrong:${err}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async pause_all() {
|
||||
return new Promise((resolve, reject) => {
|
||||
qbt.pauseAll((err, res) => {
|
||||
resolve(res);
|
||||
reject('something went wrong:' + err);
|
||||
});
|
||||
});
|
||||
resolve(res)
|
||||
reject(`something went wrong:${err}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async resume_torrents(torrents) {
|
||||
return new Promise((resolve, reject) => {
|
||||
qbt.resume(torrents, (err, res) => {
|
||||
resolve(res);
|
||||
reject('something went wrong:' + err);
|
||||
});
|
||||
});
|
||||
resolve(res)
|
||||
reject(`something went wrong:${err}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async resume_all() {
|
||||
return new Promise((resolve, reject) => {
|
||||
qbt.resumeAll((err, res) => {
|
||||
resolve(res);
|
||||
reject('something went wrong:' + err);
|
||||
});
|
||||
});
|
||||
resolve(res)
|
||||
reject(`something went wrong:${err}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async remove_torrents(torrents) {
|
||||
return new Promise((resolve, reject) => {
|
||||
qbt.delete(torrents, (err, res) => {
|
||||
resolve(res);
|
||||
reject('something went wrong:' + err);
|
||||
});
|
||||
});
|
||||
resolve(res)
|
||||
reject(`something went wrong:${err}`)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async add(torrent) {
|
||||
return new Promise((resolve, reject) => {
|
||||
qbt.add(torrent.path, null, null, (err, res) => {
|
||||
resolve(res);
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
resolve(res)
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const qbit = new Qbit();
|
||||
const qbit = new Qbit()
|
||||
|
||||
module.exports = qbit;
|
||||
module.exports = qbit
|
||||
|
|
189
server/server.js
189
server/server.js
|
@ -1,135 +1,130 @@
|
|||
const express = require('express');
|
||||
const qbit = require('./qbit');
|
||||
const multer = require('multer');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const info = require('./config/config.json');
|
||||
const express = require('express')
|
||||
const multer = require('multer')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const qbit = require('./qbit')
|
||||
|
||||
let newest_torrent = {
|
||||
const PORT = process.env.PORT || 3000
|
||||
|
||||
const newest_torrent = {
|
||||
name: '',
|
||||
path: ''
|
||||
};
|
||||
path: '',
|
||||
}
|
||||
|
||||
const upload = multer({
|
||||
dest: './src/tmp/'
|
||||
});
|
||||
dest: './src/tmp/',
|
||||
})
|
||||
|
||||
//init express
|
||||
const app = express();
|
||||
// init express
|
||||
const app = express()
|
||||
|
||||
app.use(express.json());
|
||||
app.use(express.static('dist'));
|
||||
app.use(express.json())
|
||||
app.use(express.static('dist'))
|
||||
|
||||
app.use(function(req, res, next) {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header(
|
||||
'Access-Control-Allow-Headers',
|
||||
'Origin, X-Requested-With, Content-Type, Accept'
|
||||
);
|
||||
res.header('Access-Control-Allow-Methods', 'DELETE');
|
||||
next();
|
||||
});
|
||||
|
||||
//requests
|
||||
|
||||
//login
|
||||
// requests
|
||||
// login
|
||||
app.post('/login', (req, res) => {
|
||||
if (req.body.username !== info.web_user) {
|
||||
return res.send('No such user');
|
||||
} else if (
|
||||
req.body.username === info.web_user &&
|
||||
req.body.password !== info.web_pass
|
||||
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!');
|
||||
} else if (
|
||||
req.body.username === info.web_user &&
|
||||
req.body.password === info.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');
|
||||
} else {
|
||||
return res.send('Something went wrong');
|
||||
return res.send('SUCCES')
|
||||
}
|
||||
});
|
||||
return res.send('Something went wrong')
|
||||
})
|
||||
|
||||
//get all torrents
|
||||
//AAAND sort torrents
|
||||
// get all torrents
|
||||
// AAAND sort torrents
|
||||
app.post('/all', async (req, res) => {
|
||||
let torrents = await qbit.get_all(req.body);
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send(torrents);
|
||||
});
|
||||
const torrents = await qbit.get_all(req.body)
|
||||
res.set('Content-Type', 'application/json')
|
||||
res.send(torrents)
|
||||
})
|
||||
|
||||
//get session stats
|
||||
// get session stats
|
||||
app.get('/session', async (req, res) => {
|
||||
let stats = await qbit.get_session_stats();
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send(stats);
|
||||
});
|
||||
const stats = await qbit.get_session_stats()
|
||||
res.set('Content-Type', 'application/json')
|
||||
res.send(stats)
|
||||
})
|
||||
|
||||
//pause selected torrents
|
||||
// pause selected torrents
|
||||
app.post('/pause', async (req, res) => {
|
||||
// console.log(req.body);
|
||||
let msg = await qbit.pause_torrents(req.body);
|
||||
return res.send(msg);
|
||||
});
|
||||
const msg = await qbit.pause_torrents(req.body)
|
||||
return res.send(msg)
|
||||
})
|
||||
|
||||
//pause all torrents
|
||||
// pause all torrents
|
||||
app.post('/pause_all', async (req, res) => {
|
||||
let msg = await qbit.pause_all();
|
||||
return res.send(msg);
|
||||
});
|
||||
const msg = await qbit.pause_all()
|
||||
return res.send(msg)
|
||||
})
|
||||
|
||||
//resume selected torrents
|
||||
// resume selected torrents
|
||||
app.post('/resume', async (req, res) => {
|
||||
//console.log(req.body);
|
||||
let msg = await qbit.resume_torrents(req.body);
|
||||
return res.send(msg);
|
||||
});
|
||||
// console.log(req.body);
|
||||
const msg = await qbit.resume_torrents(req.body)
|
||||
return res.send(msg)
|
||||
})
|
||||
|
||||
//resume all torrents
|
||||
// resume all torrents
|
||||
app.post('/resume_all', async (req, res) => {
|
||||
let msg = await qbit.resume_all();
|
||||
return res.send(msg);
|
||||
});
|
||||
const msg = await qbit.resume_all()
|
||||
return res.send(msg)
|
||||
})
|
||||
|
||||
//remove selected torrents
|
||||
// remove selected torrents
|
||||
app.post('/remove', async (req, res) => {
|
||||
//console.log(req.body);
|
||||
let msg = await qbit.remove_torrents(req.body);
|
||||
return res.send(msg);
|
||||
});
|
||||
// console.log(req.body);
|
||||
const msg = await qbit.remove_torrents(req.body)
|
||||
return res.send(msg)
|
||||
})
|
||||
|
||||
//upload files to server
|
||||
// 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');
|
||||
});
|
||||
newest_torrent.name = req.file.filename
|
||||
newest_torrent.path = req.file.path
|
||||
return res.send('succes')
|
||||
})
|
||||
|
||||
//add a torrent
|
||||
// add a torrent
|
||||
app.post('/add', async (req, res) => {
|
||||
let msg = await qbit.add(newest_torrent);
|
||||
fs.unlinkSync(newest_torrent.path);
|
||||
return res.send(msg);
|
||||
});
|
||||
const msg = await qbit.add(newest_torrent)
|
||||
fs.unlinkSync(newest_torrent.path)
|
||||
return res.send(msg)
|
||||
})
|
||||
|
||||
//delete last uploaded file
|
||||
// delete last uploaded file
|
||||
app.delete('/upload', upload.single('file'), (req, res) => {
|
||||
fs.unlinkSync(newest_torrent.path);
|
||||
return res.send('deleted file');
|
||||
});
|
||||
fs.unlinkSync(newest_torrent.path)
|
||||
return res.send('deleted file')
|
||||
})
|
||||
|
||||
app.listen(3009, () => console.log(`Server listening on port 3009!`));
|
||||
app.listen(PORT, () => console.log(`Server listening on port ${PORT}!`))
|
||||
|
||||
//clear the tmp directory on every boot
|
||||
const directory = path.resolve(__dirname + '/tmp');
|
||||
|
||||
app.get('/test', (req, res) => {
|
||||
res.set('Content-Type', 'application/json')
|
||||
res.send('test')
|
||||
})
|
||||
|
||||
|
||||
// clear the tmp directory on every boot
|
||||
const directory = path.resolve(`${__dirname}/tmp`)
|
||||
|
||||
fs.readdir(directory, (err, files) => {
|
||||
if (err) throw err;
|
||||
|
||||
if (err) console.log(err)
|
||||
for (const file of files) {
|
||||
fs.unlink(path.join(directory, file), err => {
|
||||
if (err) throw err;
|
||||
});
|
||||
fs.unlink(path.join(directory, file), (err) => {
|
||||
if (err) console.log(err)
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
label-idle="Drop file here..."
|
||||
accepted-file-types="application/x-bittorrent"
|
||||
data-max-file-size="1MB"
|
||||
server="http://localhost:3000/upload"
|
||||
:server="uploaddomain"
|
||||
v-model="Files"
|
||||
/>
|
||||
</div>
|
||||
|
@ -81,7 +81,8 @@ export default {
|
|||
],
|
||||
loading: false,
|
||||
dialog: false,
|
||||
Files: []
|
||||
Files: [],
|
||||
uploaddomain: this.$store.getters["GET_UPLOAD_DOMAIN"]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -17,11 +17,3 @@
|
|||
</v-container>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"qbit_user": "admin",
|
||||
"qbit_pass": "adminadmin",
|
||||
"qbit_host": "https://qbittorrent.mydomain.com",
|
||||
"web_user": "username",
|
||||
"web_pass": " password",
|
||||
"web_host": "https://vuetorrent.mydomain.com"
|
||||
}
|
23
src/main.js
23
src/main.js
|
@ -1,19 +1,20 @@
|
|||
import Vue from 'vue';
|
||||
import './plugins/vuetify';
|
||||
import App from './App.vue';
|
||||
import router from './router';
|
||||
import VueApexCharts from 'vue-apexcharts';
|
||||
import store from './services/store';
|
||||
import Vue from 'vue'
|
||||
import './plugins/vuetify'
|
||||
import VueApexCharts from 'vue-apexcharts'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import store from './services/store'
|
||||
import './registerServiceWorker'
|
||||
|
||||
Vue.use(VueApexCharts);
|
||||
|
||||
Vue.component('apexchart', VueApexCharts);
|
||||
Vue.use(VueApexCharts)
|
||||
|
||||
Vue.config.productionTip = false;
|
||||
Vue.component('apexchart', VueApexCharts)
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app');
|
||||
render: (h) => h(App),
|
||||
}).$mount('#app')
|
||||
|
|
|
@ -1,64 +1,63 @@
|
|||
const axios = require('axios');
|
||||
|
||||
const info = require('../config/config.json');
|
||||
const axios = require('axios')
|
||||
|
||||
class Qbit {
|
||||
constructor() {
|
||||
this._axios = axios.create({
|
||||
baseURL: info.web_host,
|
||||
timeout: 1000
|
||||
});
|
||||
// baseURL: process.env.VUE_APP_DOMAIN,
|
||||
timeout: 1000,
|
||||
})
|
||||
}
|
||||
|
||||
async getAll(sort) {
|
||||
let res = await this._axios.post('/all', sort);
|
||||
return res.data;
|
||||
const res = await this._axios.post('/all', sort)
|
||||
return res.data
|
||||
}
|
||||
|
||||
async get_sessions_stats() {
|
||||
let res = await this._axios.get('/session');
|
||||
return res.data;
|
||||
const res = await this._axios.get('/session')
|
||||
return res.data
|
||||
}
|
||||
|
||||
async pause_torrents(torrents) {
|
||||
let res = await this._axios.post('/pause', torrents);
|
||||
return res.data;
|
||||
const res = await this._axios.post('/pause', torrents)
|
||||
return res.data
|
||||
}
|
||||
|
||||
async pause_all() {
|
||||
let res = await this._axios.post('/pause_all');
|
||||
return res.data;
|
||||
const res = await this._axios.post('/pause_all')
|
||||
return res.data
|
||||
}
|
||||
|
||||
async resume_torrents(torrents) {
|
||||
let res = await this._axios.post('/resume', torrents);
|
||||
return res.data;
|
||||
const res = await this._axios.post('/resume', torrents)
|
||||
return res.data
|
||||
}
|
||||
|
||||
async resume_all() {
|
||||
let res = await this._axios.post('/resume_all');
|
||||
return res.data;
|
||||
const res = await this._axios.post('/resume_all')
|
||||
return res.data
|
||||
}
|
||||
|
||||
async add_torrent(torrent) {
|
||||
let res = await this._axios.post('/add', torrent);
|
||||
return res;
|
||||
const res = await this._axios.post('/add', torrent)
|
||||
return res
|
||||
}
|
||||
|
||||
async remove_torrents(torrents) {
|
||||
let res = await this._axios.post('/remove', torrents)
|
||||
return res.data;
|
||||
const res = await this._axios.post('/remove', torrents)
|
||||
return res.data
|
||||
}
|
||||
|
||||
async login(credentials) {
|
||||
let timeout = false;
|
||||
let res = await this._axios.post('/login', credentials).catch(error => {
|
||||
if (error.code === 'ECONNABORTED') timeout = true;
|
||||
else throw error;
|
||||
});
|
||||
return timeout ? 'timeout' : res.data;
|
||||
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();
|
||||
const qbit = new Qbit()
|
||||
|
||||
export default qbit;
|
||||
export default qbit
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import Vue from 'vue';
|
||||
import Vuex from 'vuex';
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
|
||||
import qbit from './qbit';
|
||||
import qbit from './qbit'
|
||||
|
||||
Vue.use(Vuex);
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
|
@ -13,12 +13,12 @@ export default new Vuex.Store({
|
|||
dlspeed: '6 Mbps',
|
||||
upspeed: '1 Mbps',
|
||||
downloaded: '6.95 Gb',
|
||||
uploaded: '1014 Mb'
|
||||
uploaded: '1014 Mb',
|
||||
},
|
||||
upload_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
download_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
torrents: [],
|
||||
init_torrents : false,
|
||||
init_torrents: false,
|
||||
selected_torrents: [],
|
||||
network_error: false,
|
||||
snackbar_error: false,
|
||||
|
@ -27,183 +27,183 @@ export default new Vuex.Store({
|
|||
succes_msg: '',
|
||||
authenticated: false,
|
||||
loading: false,
|
||||
sort_options: {sort: 'name', reverse: false}
|
||||
sort_options: { sort: 'name', reverse: false },
|
||||
serverdomain: process.env.VUE_APP_DOMAIN,
|
||||
},
|
||||
getters: {
|
||||
CONTAINS_TORRENT: state => hash => {
|
||||
return state.selected_torrents.includes(hash);
|
||||
}
|
||||
CONTAINS_TORRENT: (state) => (hash) => state.selected_torrents.includes(hash),
|
||||
GET_DOMAIN: (state) => state.serverdomain,
|
||||
GET_UPLOAD_DOMAIN: (state) => `${state.serverdomain}/upload`,
|
||||
},
|
||||
mutations: {
|
||||
REFRESH_TORRENTS: async state => {
|
||||
let 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;
|
||||
});
|
||||
state.torrents = torrents.map(a => ({ ...a }));
|
||||
state.init_torrents = true;
|
||||
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
|
||||
})
|
||||
state.torrents = torrents.map((a) => ({ ...a }))
|
||||
state.init_torrents = true
|
||||
},
|
||||
REFRESH_SESSION_STATS: async state => {
|
||||
let _stats = await qbit.get_sessions_stats();
|
||||
//push in array for graph
|
||||
state.download_data.splice(0, 1);
|
||||
REFRESH_SESSION_STATS: async (state) => {
|
||||
const _stats = await qbit.get_sessions_stats()
|
||||
// push in array for graph
|
||||
state.download_data.splice(0, 1)
|
||||
if (_stats.dlspeed.indexOf('KB' > -1)) {
|
||||
state.download_data.push(
|
||||
_stats.dlspeed.substring(0, _stats.dlspeed.indexOf(' ')) / 1000
|
||||
);
|
||||
_stats.dlspeed.substring(0, _stats.dlspeed.indexOf(' ')) / 1000,
|
||||
)
|
||||
} else {
|
||||
state.download_data.push(
|
||||
_stats.dlspeed(0, _stats.dlspeed.indexOf(' '))
|
||||
);
|
||||
_stats.dlspeed(0, _stats.dlspeed.indexOf(' ')),
|
||||
)
|
||||
}
|
||||
state.upload_data.splice(0, 1);
|
||||
state.upload_data.splice(0, 1)
|
||||
if (_stats.upspeed.indexOf('KB' > -1)) {
|
||||
state.upload_data.push(
|
||||
_stats.upspeed.substring(0, _stats.upspeed.indexOf(' ')) / 1000
|
||||
);
|
||||
_stats.upspeed.substring(0, _stats.upspeed.indexOf(' ')) / 1000,
|
||||
)
|
||||
} else {
|
||||
state.upload_data.push(
|
||||
_stats.upspeed.substring(0, _stats.upspeed.indexOf(' '))
|
||||
);
|
||||
_stats.upspeed.substring(0, _stats.upspeed.indexOf(' ')),
|
||||
)
|
||||
}
|
||||
state.stats = _stats;
|
||||
state.stats = _stats
|
||||
},
|
||||
CLEAR_INTERVALS: state => {
|
||||
if (state.intervals.length > 1)
|
||||
state.intervals.forEach(el => clearInterval(el));
|
||||
CLEAR_INTERVALS: (state) => {
|
||||
if (state.intervals.length > 1) { state.intervals.forEach((el) => clearInterval(el)) }
|
||||
},
|
||||
ADD_SELECTED: (state, payload) => {
|
||||
state.selected_torrents.push(payload);
|
||||
state.selected_torrents.push(payload)
|
||||
},
|
||||
REMOVE_SELECTED: (state, payload) => {
|
||||
state.selected_torrents.splice(
|
||||
state.selected_torrents.indexOf(payload),
|
||||
1
|
||||
);
|
||||
1,
|
||||
)
|
||||
},
|
||||
RESET_SELECTED: state => {
|
||||
state.selected_torrents = [];
|
||||
RESET_SELECTED: (state) => {
|
||||
state.selected_torrents = []
|
||||
},
|
||||
PAUSE_TORRENTS: async state => {
|
||||
let res;
|
||||
PAUSE_TORRENTS: async (state) => {
|
||||
let res
|
||||
if (state.selected_torrents.length === 0) {
|
||||
res = await qbit.pause_all();
|
||||
res = await qbit.pause_all()
|
||||
} else {
|
||||
res = await qbit.pause_torrents(state.selected_torrents);
|
||||
res = await qbit.pause_torrents(state.selected_torrents)
|
||||
}
|
||||
},
|
||||
RESUME_TORRENTS: async state => {
|
||||
let res;
|
||||
RESUME_TORRENTS: async (state) => {
|
||||
let res
|
||||
if (state.selected_torrents.length === 0) {
|
||||
res = await qbit.resume_all();
|
||||
res = await qbit.resume_all()
|
||||
} else {
|
||||
res = await qbit.resume_torrents(state.selected_torrents);
|
||||
res = await qbit.resume_torrents(state.selected_torrents)
|
||||
}
|
||||
},
|
||||
ADD_TORRENT: async (state, payload) => {
|
||||
let res = await qbit.add_torrent(payload);
|
||||
const res = await qbit.add_torrent(payload)
|
||||
if (res.statusText === 'OK') {
|
||||
state.snackbar = true;
|
||||
state.succes_msg = 'Awesome! You added a new Torrent.';
|
||||
state.snackbar = true
|
||||
state.succes_msg = 'Awesome! You added a new Torrent.'
|
||||
setTimeout(() => {
|
||||
state.snackbar = false;
|
||||
}, 4000);
|
||||
state.snackbar = false
|
||||
}, 4000)
|
||||
} else {
|
||||
state.snackbar_error = true;
|
||||
state.error_msg = 'Something went wrong';
|
||||
state.snackbar_error = true
|
||||
state.error_msg = 'Something went wrong'
|
||||
setTimeout(() => {
|
||||
state.snackbar_error = false;
|
||||
}, 4000);
|
||||
state.snackbar_error = false
|
||||
}, 4000)
|
||||
}
|
||||
},
|
||||
REMOVE_TORRENTS: async state => {
|
||||
REMOVE_TORRENTS: async (state) => {
|
||||
if (state.selected_torrents.length !== 0) {
|
||||
let res = await qbit.remove_torrents(state.selected_torrents);
|
||||
const res = await qbit.remove_torrents(state.selected_torrents)
|
||||
}
|
||||
},
|
||||
LOGIN: async (state, payload) => {
|
||||
let res = await qbit.login(payload);
|
||||
const res = await qbit.login(payload)
|
||||
if (res == 'timeout') {
|
||||
state.loading = false;
|
||||
state.snackbar_error = true;
|
||||
state.error_msg = 'Express server timed out!';
|
||||
state.loading = false
|
||||
state.snackbar_error = true
|
||||
state.error_msg = 'Express server timed out!'
|
||||
setTimeout(() => {
|
||||
state.snackbar_error = false;
|
||||
}, 4000);
|
||||
state.snackbar_error = false
|
||||
}, 4000)
|
||||
} else {
|
||||
switch (res) {
|
||||
case 'No such user':
|
||||
state.snackbar_error = true;
|
||||
state.error_msg = 'No such user!';
|
||||
state.snackbar_error = true
|
||||
state.error_msg = 'No such user!'
|
||||
setTimeout(() => {
|
||||
state.snackbar_error = false;
|
||||
}, 4000);
|
||||
break;
|
||||
state.snackbar_error = false
|
||||
}, 4000)
|
||||
break
|
||||
case 'Wrong password!':
|
||||
state.snackbar_error = true;
|
||||
state.error_msg = 'Wrong password!';
|
||||
state.snackbar_error = true
|
||||
state.error_msg = 'Wrong password!'
|
||||
setTimeout(() => {
|
||||
state.snackbar_error = false;
|
||||
}, 4000);
|
||||
break;
|
||||
state.snackbar_error = false
|
||||
}, 4000)
|
||||
break
|
||||
case 'SUCCES':
|
||||
state.snackbar = true;
|
||||
state.succes_msg = 'Succesfully logged in!';
|
||||
state.authenticated = true;
|
||||
state.snackbar = true
|
||||
state.succes_msg = 'Succesfully logged in!'
|
||||
state.authenticated = true
|
||||
setTimeout(() => {
|
||||
state.snackbar = false;
|
||||
}, 4000);
|
||||
break;
|
||||
state.snackbar = false
|
||||
}, 4000)
|
||||
break
|
||||
default:
|
||||
state.snackbar_error = true;
|
||||
state.error_msg = 'Something went wrong';
|
||||
state.snackbar_error = true
|
||||
state.error_msg = 'Something went wrong'
|
||||
setTimeout(() => {
|
||||
state.snackbar_error = false;
|
||||
}, 4000);
|
||||
break;
|
||||
state.snackbar_error = false
|
||||
}, 4000)
|
||||
break
|
||||
}
|
||||
state.loading = false;
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
REFRESH_TORRENTS: context => {
|
||||
REFRESH_TORRENTS: (context) => {
|
||||
context.state.intervals[1] = setInterval(async () => {
|
||||
context.commit('REFRESH_TORRENTS');
|
||||
context.commit('REFRESH_TORRENTS')
|
||||
if (context.state.network_error) {
|
||||
context.commit('CLEAR_INTERVALS');
|
||||
context.commit('CLEAR_INTERVALS')
|
||||
}
|
||||
}, 2000);
|
||||
}, 2000)
|
||||
},
|
||||
REFRESH_SESSION_STATS: context => {
|
||||
REFRESH_SESSION_STATS: (context) => {
|
||||
context.state.intervals[0] = setInterval(async () => {
|
||||
context.commit('REFRESH_SESSION_STATS');
|
||||
}, 1000);
|
||||
context.commit('REFRESH_SESSION_STATS')
|
||||
}, 1000)
|
||||
},
|
||||
ADD_SELECTED: (context, payload) => {
|
||||
context.commit('ADD_SELECTED', payload);
|
||||
context.commit('ADD_SELECTED', payload)
|
||||
},
|
||||
REMOVE_SELECTED: (context, payload) => {
|
||||
context.commit('REMOVE_SELECTED', payload);
|
||||
context.commit('REMOVE_SELECTED', payload)
|
||||
},
|
||||
RESET_SELECTED: context => {
|
||||
context.commit('RESET_SELECTED');
|
||||
RESET_SELECTED: (context) => {
|
||||
context.commit('RESET_SELECTED')
|
||||
},
|
||||
PAUSE_TORRENTS: context => {
|
||||
context.commit('PAUSE_TORRENTS');
|
||||
PAUSE_TORRENTS: (context) => {
|
||||
context.commit('PAUSE_TORRENTS')
|
||||
},
|
||||
RESUME_TORRENTS: context => {
|
||||
context.commit('RESUME_TORRENTS');
|
||||
RESUME_TORRENTS: (context) => {
|
||||
context.commit('RESUME_TORRENTS')
|
||||
},
|
||||
ADD_TORRENT: (context, payload) => {
|
||||
context.commit('ADD_TORRENT', payload);
|
||||
context.commit('ADD_TORRENT', payload)
|
||||
},
|
||||
REMOVE_TORRENTS: context => {
|
||||
context.commit('REMOVE_TORRENTS');
|
||||
REMOVE_TORRENTS: (context) => {
|
||||
context.commit('REMOVE_TORRENTS')
|
||||
},
|
||||
LOGIN: (context, payload) => {
|
||||
context.commit('LOGIN', payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
context.commit('LOGIN', payload)
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue