qbit-api-v2 begin

This commit is contained in:
Daan Wijns 2020-03-22 10:59:38 +01:00
parent f74d378895
commit 7a6ea22b91
16 changed files with 993 additions and 669 deletions

View file

@ -23,6 +23,7 @@ module.exports = {
'prefer-promise-reject-errors': 0,
'no-underscore-dangle': 0,
'no-param-reassign': 0,
'no-unused-vars': 0
'no-unused-vars': 0,
'indent': 4
},
}

View file

@ -1,14 +1,16 @@
FROM node:10-slim
# build stage
FROM node:10-slim as build-stage
# Create app directory
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# production stage
FROM node:10-slim as production-stage
COPY --from=build-stage /usr/src/app /usr/src/app
#serve
EXPOSE 3001
CMD ["node", "server/server.js"]

View file

@ -1,3 +1,3 @@
module.exports = {
presets: ['@vue/app']
};
presets: ['@vue/app'],
}

1004
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,42 +9,42 @@
"start": "nodemon server/server.js"
},
"dependencies": {
"apexcharts": "^3.10.1",
"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.8.0",
"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.5.0",
"filepond-plugin-image-preview": "^4.6.1",
"multer": "^1.4.2",
"qbittorrent-api": "^1.0.0",
"register-service-worker": "^1.6.2",
"vue": "^2.6.10",
"vue-apexcharts": "^1.5.1",
"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.3",
"vuetify": "^1.5.21",
"vuex": "^3.1.2"
"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.7.2",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-plugin-import": "^2.19.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.10",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.4.3",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10"
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
},
"eslintConfig": {
"root": true,

View file

@ -1,89 +1,101 @@
const api = require('qbittorrent-api')
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({ path: '../.env' })
dotenv.config()
const connection= api.connect(process.env.QBIT_HOST, process.env.QBIT_USER, process.env.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) => {
try {
const res = await (await connection).torrents('all', null, prop.name, String(prop.reverse), '20', null, null)
const torrents = []
res.forEach((el) => {
// console.log(el);
const t = new Torrent(el)
torrents.push(t)
})
// console.log(torrents[0]);
resolve(torrents)
reject(err)
})
})
return torrents
}
catch(err){
return `something went wrong:${err}`
}
}
async get_session_stats() {
return new Promise((resolve, reject) => {
qbt.transferInfo((err, res) => {
const stat = new Stat(res)
// console.log(stat);
resolve(stat)
reject(`something went wrong:${err}`)
})
})
try {
const res = await (await connection).transferInfo()
return new Stat(res)
}
catch(err){
return `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}`)
})
let _torrents = ''
torrents.forEach(el=> {
_torrents += el + '|'
})
try {
return await (await connection).pauseTorrents(_torrents)
}
catch(err){
return `something went wrong:${err}`
}
}
async pause_all() {
return new Promise((resolve, reject) => {
qbt.pauseAll((err, res) => {
resolve(res)
reject(`something went wrong:${err}`)
})
})
try {
return await (await connection).pauseTorrents('all')
}
catch(err){
return `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}`)
})
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() {
return new Promise((resolve, reject) => {
qbt.resumeAll((err, res) => {
resolve(res)
reject(`something went wrong:${err}`)
})
})
try {
return await (await connection).resumeTorrents('all')
}
catch(err){
return `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}`)
})
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) => {
qbt.add(torrent.path, null, null, (err, res) => {
connection.add(torrent.path, null, null, (err, res) => {
resolve(res)
reject(err)
})
@ -91,6 +103,6 @@ class Qbit {
}
}
const qbit = new Qbit()
const qbit = new Qbit();
module.exports = qbit

View file

@ -5,8 +5,7 @@ const path = require('path')
const dotenv = require('dotenv')
const qbit = require('./qbit')
dotenv.config({ path: '../.env' })
dotenv.config()
const PORT = process.env.PORT || 3000

View file

@ -32,32 +32,32 @@
</template>
<script>
import Navbar from "./components/Navbar";
import Login from "./components/Login";
import { mapState } from "vuex";
import { mapState } from 'vuex'
import Navbar from './components/Navbar'
import Login from './components/Login'
export default {
components: { Navbar, Login },
name: "App",
name: 'App',
data() {
return {};
return {}
},
computed: {
...mapState([
"authenticated",
"snackbar_error",
"error_msg",
"snackbar",
"succes_msg"
])
'authenticated',
'snackbar_error',
'error_msg',
'snackbar',
'succes_msg',
]),
},
methods: {
snackbar_errorClose() {
this.$store.state.snackbar_error = false;
this.$store.state.snackbar_error = false
},
snackbarClose() {
this.$store.state.snackbar = false;
this.$store.state.snackbar = false
},
},
}
}
};
</script>

View file

@ -47,71 +47,70 @@
<script>
// Import Vue FilePond
import vueFilePond from "vue-filepond";
import vueFilePond from 'vue-filepond'
// Import FilePond styles
import "filepond/dist/filepond.min.css";
import 'filepond/dist/filepond.min.css'
// Import image preview plugin styles
import "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css";
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css'
// Import image preview and file type validation plugins
import FilePondPluginFileValidateType from "filepond-plugin-file-validate-type";
import FilePondPluginImagePreview from "filepond-plugin-image-preview";
import FilePondPluginFileValidateSize from "filepond-plugin-file-validate-size";
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
import FilePondPluginImagePreview from 'filepond-plugin-image-preview'
import FilePondPluginFileValidateSize from 'filepond-plugin-file-validate-size'
// Create component
const FilePond = vueFilePond(
FilePondPluginFileValidateType,
FilePondPluginImagePreview,
FilePondPluginFileValidateSize
);
FilePondPluginFileValidateSize,
)
export default {
data() {
return {
filename: "",
directory: "",
filename: '',
directory: '',
inputRules: [
v =>
v.indexOf("magnet") > -1 ||
v.indexOf("http") > -1 ||
this.validFile ||
"Not a valid magnet link"
(v) => v.indexOf('magnet') > -1
|| v.indexOf('http') > -1
|| this.validFile
|| 'Not a valid magnet link',
],
loading: false,
dialog: false,
Files: []
};
Files: [],
}
},
methods: {
submit() {
if (this.$refs.form.validate()) {
this.loading = true;
this.loading = true
this.$store.dispatch("ADD_TORRENT", {
this.$store.dispatch('ADD_TORRENT', {
name: this.filename,
dir: this.directory
});
dir: this.directory,
})
// reset input
this.$refs.form.reset();
this.filename = "";
this.directory = "";
this.$refs.form.reset()
this.filename = ''
this.directory = ''
this.$refs.pond.removeFiles();
this.dialog = false;
this.loading = false;
}
this.$refs.pond.removeFiles()
this.dialog = false
this.loading = false
}
},
},
computed: {
validFile() {
return this.Files.length > 0;
}
return this.Files.length > 0
},
},
components: {
vueFilePond
vueFilePond,
},
}
};
</script>

View file

@ -46,26 +46,27 @@
</template>
<script>
import { mapState } from "vuex";
import { mapState } from 'vuex'
export default {
data() {
return {
username: "",
password: "",
inputRules: [v => v.length >= 1 || "At least 1 character"]
};
username: '',
password: '',
inputRules: [(v) => v.length >= 1 || 'At least 1 character'],
}
},
methods: {
Login() {
this.$store.state.loading = true;
this.$store.dispatch("LOGIN", {
this.$store.state.loading = true
this.$store.dispatch('LOGIN', {
username: this.username,
password: this.password
});
}
password: this.password,
})
},
},
computed: {
...mapState(["loading"])
...mapState(['loading']),
},
}
};
</script>

View file

@ -106,10 +106,11 @@
</template>
<script>
import AddTorrent from "./AddTorrent";
import Settings from "./Settings";
import { mapMutations, mapGetters, mapState } from "vuex";
import { setInterval } from "timers";
import { mapMutations, mapGetters, mapState } from 'vuex'
import { setInterval } from 'timers'
import AddTorrent from './AddTorrent'
import Settings from './Settings'
export default {
components: { AddTorrent, Settings },
data() {
@ -117,89 +118,89 @@ export default {
drawer: false,
paused: false,
links: [
{ icon: "dashboard", text: "Dashboard", route: "/" },
{ icon: "settings", text: "Settings", route: "/settings" }
{ icon: 'dashboard', text: 'Dashboard', route: '/' },
{ icon: 'settings', text: 'Settings', route: '/settings' },
],
chartOptions: {
chart: {
sparkline: {
enabled: true
enabled: true,
},
animations: {
enabled: false,
dynamicAnimation: {
speed: 2000
}
}
speed: 2000,
},
colors: ["#00b3fa", "#64CEAA"],
},
},
colors: ['#00b3fa', '#64CEAA'],
stroke: {
show: true,
curve: "smooth",
lineCap: "round",
width: 4
curve: 'smooth',
lineCap: 'round',
width: 4,
},
fill: {
type: "gradient",
type: 'gradient',
gradient: {
shade: "dark",
type: "vertical",
shade: 'dark',
type: 'vertical',
shadeIntensity: 0.5,
opacityFrom: 0.6,
opacityTo: 0.5,
stops: [0, 50, 100]
}
}
stops: [0, 50, 100],
},
},
},
series: [
{
name: "upload",
type: "area",
data: this.$store.state.upload_data
name: 'upload',
type: 'area',
data: this.$store.state.upload_data,
},
{
name: "download",
type: "area",
data: this.$store.state.download_data
name: 'download',
type: 'area',
data: this.$store.state.download_data,
},
],
}
]
};
},
methods: {
...mapMutations(["REFRESH_TORRENTS", "CLEAR_INTERVALS"]),
...mapMutations(['REFRESH_TORRENTS', 'CLEAR_INTERVALS']),
clearInterval() {
this.$store.commit("CLEAR_INTERVALS");
this.$data.paused = !this.$data.paused;
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;
this.$store.dispatch('REFRESH_TORRENTS')
this.$store.dispatch('REFRESH_SESSION_STATS')
this.$data.paused = !this.$data.paused
},
pauseTorrents() {
this.$store.dispatch("PAUSE_TORRENTS");
this.$store.dispatch('PAUSE_TORRENTS')
},
resumeTorrents() {
this.$store.dispatch("RESUME_TORRENTS");
this.$store.dispatch('RESUME_TORRENTS')
},
removeTorrents() {
this.$store.dispatch("REMOVE_TORRENTS");
this.$store.dispatch('REMOVE_TORRENTS')
},
refreshTorrents() {
this.$store.state.init_torrents = false;
this.$store.dispatch("REFRESH_TORRENTS");
this.$store.state.init_torrents = false
this.$store.dispatch('REFRESH_TORRENTS')
},
closeSnackbar() {
this.$store.state.snackbar = false;
}
this.$store.state.snackbar = false
},
created: function() {
this.$store.dispatch("REFRESH_SESSION_STATS");
},
created() {
this.$store.dispatch('REFRESH_SESSION_STATS')
},
computed: {
...mapState(["stats", "snackbar_error", "error_msg", "snackbar"])
...mapState(['stats', 'snackbar_error', 'error_msg', 'snackbar']),
},
}
};
</script>
<style>
.project.nav_upload {

View file

@ -1,18 +1,18 @@
import Vue from 'vue';
import Vuetify from 'vuetify/lib';
import 'vuetify/src/stylus/app.styl';
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import 'vuetify/src/stylus/app.styl'
Vue.use(Vuetify, {
iconfont: 'md',
theme: {
primary: '#35495e',
secondary: '#3e556d',
secondary_lighter: "#56718c",
secondary_lighter: '#56718c',
blue_accent: '#3cd1c2',
info: '#ffaa2c',
error: '#f83e70',
green_accent: '#3cd1c2',
download: '#64CEAA',
upload: '#00b3fa'
}
});
upload: '#00b3fa',
},
})

View file

@ -6,8 +6,8 @@ if (process.env.NODE_ENV === 'production') {
register(`${process.env.BASE_URL}service-worker.js`, {
ready() {
console.log(
'App is being served from cache by a service worker.\n' +
'For more details, visit https://goo.gl/AFskqB'
'App is being served from cache by a service worker.\n'
+ 'For more details, visit https://goo.gl/AFskqB',
)
},
registered() {
@ -27,6 +27,6 @@ if (process.env.NODE_ENV === 'production') {
},
error(error) {
console.error('Error during service worker registration:', error)
}
},
})
}

View file

@ -11,7 +11,7 @@ export default new Router({
{
path: '/',
name: 'dashboard',
component: Dashboard
}
]
component: Dashboard,
},
],
})

View file

@ -120,124 +120,125 @@
</template>
<script>
import { mapState, mapMutations, mapGetters } from "vuex";
import { mapState, mapMutations, mapGetters } from 'vuex'
export default {
data() {
return {
sort_input: ""
};
sort_input: '',
}
},
computed: {
...mapState(["torrents", "init_torrents"])
...mapState(['torrents', 'init_torrents']),
},
methods: {
...mapMutations(["SORT_TORRENTS"]),
...mapMutations(['SORT_TORRENTS']),
sortBy() {
let name, reverse;
let name; let
reverse
// search if order was presented
const index = this.sort_input.indexOf(" ");
const index = this.sort_input.indexOf(' ')
if (index > -1) {
name = this.sort_input.substring(0, index);
let temp = this.sort_input.substring(index);
if (temp.indexOf("asc") > -1) {
reverse = false;
} else if (temp.indexOf("desc") > -1) {
reverse = true;
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
name = this.sort_input;
reverse = false;
name = this.sort_input
reverse = false
}
// prop names
switch (name) {
case "title":
case "name":
case "Name":
case "Title":
name = "name";
break;
case "size":
case "Size":
name = "size";
break;
case "dlspeed":
case "Dlspeed":
case "Download":
case "download":
case "downloadspeed":
name = "dlspeed";
break;
case "upspeed":
case "upload":
case "Upload":
case "Upspeed":
case "uploadspeed":
name = "upspeed";
break;
case "leechs":
case "leechers":
case "leech":
case "peers":
case "Leechs":
case "Leechers":
case "Leech":
case "Peers":
name = "num_leechs";
break;
case "seeds":
case "seeders":
case "Seeds":
case "Seeders":
name = "num_seeds";
break;
case "remaining":
case "time":
case "Time":
case "ETA":
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;
case 'title':
case 'name':
case 'Name':
case 'Title':
name = 'name'
break
case 'size':
case 'Size':
name = 'size'
break
case 'dlspeed':
case 'Dlspeed':
case 'Download':
case 'download':
case 'downloadspeed':
name = 'dlspeed'
break
case 'upspeed':
case 'upload':
case 'Upload':
case 'Upspeed':
case 'uploadspeed':
name = 'upspeed'
break
case 'leechs':
case 'leechers':
case 'leech':
case 'peers':
case 'Leechs':
case 'Leechers':
case 'Leech':
case 'Peers':
name = 'num_leechs'
break
case 'seeds':
case 'seeders':
case 'Seeds':
case 'Seeders':
name = 'num_seeds'
break
case 'remaining':
case 'time':
case 'Time':
case 'ETA':
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;
name = 'name'
break
}
this.$store.state.sort_options = { name, reverse };
this.$store.state.sort_options = { name, reverse }
},
selectTorrent(hash) {
if (this.containsTorrent(hash)) {
this.$store.dispatch("REMOVE_SELECTED", hash);
this.$store.dispatch('REMOVE_SELECTED', hash)
} else {
this.$store.dispatch("ADD_SELECTED", hash);
this.$store.dispatch('ADD_SELECTED', hash)
}
},
containsTorrent(hash) {
return this.$store.getters["CONTAINS_TORRENT"](hash);
return this.$store.getters.CONTAINS_TORRENT(hash)
},
resetSelected() {
this.$store.dispatch("RESET_SELECTED");
}
this.$store.dispatch('RESET_SELECTED')
},
},
created() {
this.$store.dispatch('REFRESH_TORRENTS')
},
created: function() {
this.$store.dispatch("REFRESH_TORRENTS");
}
};
</script>
<style>

View file

@ -1,5 +1,5 @@
module.exports = {
devServer: {
proxy: 'http://localhost:3000/',
proxy: 'http://localhost:3001/',
},
}