mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2024-11-28 13:08:53 +03:00
0.5.5 (#125)
This commit is contained in:
parent
c4329ce2b9
commit
2e334ca909
20 changed files with 507 additions and 185 deletions
|
@ -6,7 +6,7 @@ The sleekest looking WebUI for qBittorrent made with Vue.js!
|
|||
|
||||
## Screenshots
|
||||
|
||||
![Desktop screenshot](https://imgur.com/BgqO5Zp.png)
|
||||
![Desktop screenshot](https://imgur.com/IUkaDnI.png)
|
||||
|
||||
| | | |
|
||||
| :--------------------------------: | :--------------------------------: | :--------------------------------: |
|
||||
|
|
109
package-lock.json
generated
109
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vuetorrent",
|
||||
"version": "0.5.4",
|
||||
"version": "0.5.5",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -3634,6 +3634,12 @@
|
|||
"jest-diff": "^24.3.0"
|
||||
}
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz",
|
||||
"integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||
|
@ -6352,6 +6358,12 @@
|
|||
"caller-callsite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"callsite": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
||||
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=",
|
||||
"dev": true
|
||||
},
|
||||
"callsites": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
|
||||
|
@ -7595,6 +7607,15 @@
|
|||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"decache": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/decache/-/decache-4.6.0.tgz",
|
||||
"integrity": "sha512-PppOuLiz+DFeaUvFXEYZjLxAkKiMYH/do/b/MxpDe/8AgKBi5GhZxridoVIbBq72GDbL36e4p0Ce2jTGUwwU+w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"callsite": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
|
@ -17366,9 +17387,9 @@
|
|||
"integrity": "sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g=="
|
||||
},
|
||||
"vue-cli-plugin-vuetify": {
|
||||
"version": "2.0.9",
|
||||
"resolved": "https://registry.npmjs.org/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.0.9.tgz",
|
||||
"integrity": "sha512-J4fzpz27OmCCAA3CI56ulYsUrZ859dQAh58Z9XZilY03kd/M+svLlPkK45cBIrGGfjSqQ40oyWezA3NiPBEG8g==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-cli-plugin-vuetify/-/vue-cli-plugin-vuetify-2.1.0.tgz",
|
||||
"integrity": "sha512-cvJR2+6U1PS4UUP7NnuylWfxM3LrzKnusOgrCZUyzr5abyDxf/t0TZy5EqfJwAa9/TsIO0W4gOoaoy/f4Yw0aQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"null-loader": "^3.0.0",
|
||||
|
@ -17524,15 +17545,10 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"vue-observe-visibility": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/vue-observe-visibility/-/vue-observe-visibility-0.4.6.tgz",
|
||||
"integrity": "sha512-xo0CEVdkjSjhJoDdLSvoZoQrw/H2BlzB5jrCBKGZNXN2zdZgMuZ9BKrxXDjNP2AxlcCoKc8OahI3F3r3JGLv2Q=="
|
||||
},
|
||||
"vue-router": {
|
||||
"version": "3.4.9",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.4.9.tgz",
|
||||
"integrity": "sha512-CGAKWN44RqXW06oC+u4mPgHLQQi2t6vLD/JbGRDAXm0YpMv0bgpKuU5bBd7AvMgfTz9kXVRIWKHqRwGEb8xFkA=="
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz",
|
||||
"integrity": "sha512-RRQNLT8Mzr8z7eL4p7BtKvRaTSGdCbTy2+Mm5HTJvLGYSSeG9gDzNasJPP/yOYKLy+/cLG/ftrqq5fvkFwBJEw=="
|
||||
},
|
||||
"vue-style-loader": {
|
||||
"version": "4.1.2",
|
||||
|
@ -17597,19 +17613,72 @@
|
|||
"integrity": "sha512-i2/Df0U0sedlaCbft4NMbna7WXbTCBhKVYTMjBrLVzrYTTWqzSO7ZCxLuDRY7MjwQhn7AOec7ent9U/NyIICqA=="
|
||||
},
|
||||
"vuetify-loader": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/vuetify-loader/-/vuetify-loader-1.6.0.tgz",
|
||||
"integrity": "sha512-1bx3YeZ712dT1+QMX+XSFlP0O5k5O5Ui9ysBBmUZ9bWkAEHWZJQI9soI+qG5qmeFxUC0L9QYMCIKP0hOL/pf3Q==",
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/vuetify-loader/-/vuetify-loader-1.7.1.tgz",
|
||||
"integrity": "sha512-zRfgNxi/SeE8Nh4Vhw3aIJftYrcJWd3PqPn8+cB/F9CgBVhJo5qp2BuFL70k33G1kTaBvcjYgM+vZc9nvvU3xg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"file-loader": "^4.0.0",
|
||||
"loader-utils": "^1.2.0"
|
||||
"decache": "^4.6.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"loader-utils": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ajv": {
|
||||
"version": "6.12.6",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
}
|
||||
},
|
||||
"ajv-keywords": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
|
||||
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"file-loader": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz",
|
||||
"integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loader-utils": "^2.0.0",
|
||||
"schema-utils": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"loader-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz",
|
||||
"integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"big.js": "^5.2.2",
|
||||
"emojis-list": "^3.0.0",
|
||||
"json5": "^2.1.2"
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz",
|
||||
"integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-schema": "^7.0.6",
|
||||
"ajv": "^6.12.5",
|
||||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"vuex": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.0.tgz",
|
||||
"integrity": "sha512-W74OO2vCJPs9/YjNjW8lLbj+jzT24waTo2KShI8jLvJW8OaIkgb3wuAMA7D+ZiUxDOx3ubwSZTaJBip9G8a3aQ=="
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.6.2.tgz",
|
||||
"integrity": "sha512-ETW44IqCgBpVomy520DT5jf8n0zoCac+sxWnn+hMe/CzaSejb/eVw2YToiXYX+Ex/AuHHia28vWTq4goAexFbw=="
|
||||
},
|
||||
"vuex-persist": {
|
||||
"version": "3.1.3",
|
||||
|
|
10
package.json
10
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vuetorrent",
|
||||
"version": "0.5.4",
|
||||
"version": "0.5.5",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
|
@ -24,12 +24,12 @@
|
|||
"vue": "^2.6.12",
|
||||
"vue-apexcharts": "^1.6.0",
|
||||
"vue-context": "^5.2.0",
|
||||
"vue-router": "^3.4.9",
|
||||
"vue-router": "^3.5.1",
|
||||
"vue-toastification": "^1.7.11",
|
||||
"vue2-perfect-scrollbar": "^1.5.0",
|
||||
"vuedraggable": "^2.24.3",
|
||||
"vuetify": "^2.4.3",
|
||||
"vuex": "^3.6.0",
|
||||
"vuex": "^3.6.2",
|
||||
"vuex-persist": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -51,9 +51,9 @@
|
|||
"node-sass": "^4.14.1",
|
||||
"sass": "^1.32.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-cli-plugin-vuetify": "^2.0.9",
|
||||
"vue-cli-plugin-vuetify": "^2.1.0",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
"vuetify-loader": "^1.6.0"
|
||||
"vuetify-loader": "^1.7.1"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
|
|
|
@ -11,16 +11,16 @@
|
|||
{{ icon }}
|
||||
</v-icon>
|
||||
</v-flex>
|
||||
<v-flex xs7 class="text-center font-weight-bold robot-mono">
|
||||
<v-flex xs6 class="text-center font-weight-bold robot-mono">
|
||||
<span data-testid="SpeedCard-value">
|
||||
{{ value | getDataValue(2) }}
|
||||
{{ value | getSpeedValue }}
|
||||
</span>
|
||||
</v-flex>
|
||||
<v-flex
|
||||
xs3
|
||||
xs4
|
||||
class="caption robot-mono text-right mt-1"
|
||||
>
|
||||
<span class="speedUnits" data-testid="SpeedCard-unit">
|
||||
<span data-testid="SpeedCard-unit">
|
||||
{{ value | getDataUnit(1) }}/s
|
||||
</span>
|
||||
</v-flex>
|
||||
|
@ -31,6 +31,16 @@
|
|||
<script>
|
||||
export default {
|
||||
name: 'SpeedCard',
|
||||
filters: {
|
||||
getSpeedValue(value) {
|
||||
if (!value) return '0'
|
||||
const c = 1024
|
||||
const d = value > 1048576 ? 2 : 0 // 2 decimals when MB
|
||||
const f = Math.floor(Math.log(value) / Math.log(c))
|
||||
|
||||
return `${parseFloat((value / Math.pow(c, f)).toFixed(d))}`
|
||||
}
|
||||
},
|
||||
props: ['color', 'icon', 'value']
|
||||
}
|
||||
</script>
|
||||
|
@ -40,8 +50,4 @@ export default {
|
|||
padding: 32px 16px !important;
|
||||
font-size: 1.05em;
|
||||
}
|
||||
|
||||
.speedUnits {
|
||||
font-size: .8em !important;
|
||||
}
|
||||
</style>
|
|
@ -13,7 +13,7 @@
|
|||
<v-flex md5 class="ml-4">
|
||||
<span data-testid="StorageCard-Wrapper" :class="color + '--text title'">
|
||||
<span data-testid="StorageCard-value"> {{ value | getDataValue(2) }} </span>
|
||||
<span data-testid="StorageCard-unit" class="font-weight-light caption">
|
||||
<span data-testid="StorageCard-unit" class="caption">
|
||||
{{ value | getDataUnit }}
|
||||
</span>
|
||||
</span>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<v-card style="overflow: hidden !important">
|
||||
<v-container :style="{ height: phoneLayout ? '100vh' : '' }">
|
||||
<v-card-title class="pb-0 justify-center">
|
||||
<h2>Change Torrent Location</h2>
|
||||
<h2>Change Location</h2>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div>
|
||||
|
@ -18,13 +18,13 @@
|
|||
<v-text-field
|
||||
v-model="torrent.name"
|
||||
label="Torrent Name"
|
||||
prepend-icon="insert_drive_file"
|
||||
:prepend-icon="mdiFile"
|
||||
readonly
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="newPath"
|
||||
label="Directory"
|
||||
prepend-icon="folder"
|
||||
:prepend-icon="mdiFolder"
|
||||
@keydown.enter="setLocation"
|
||||
/>
|
||||
</v-col>
|
||||
|
@ -49,7 +49,7 @@
|
|||
right
|
||||
@click="close"
|
||||
>
|
||||
<v-icon>close</v-icon>
|
||||
<v-icon>{{ mdiClose }}</v-icon>
|
||||
</v-btn>
|
||||
</v-fab-transition>
|
||||
</v-card>
|
||||
|
@ -58,9 +58,10 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
|
||||
import { mdiFile, mdiFolder, mdiClose } from '@mdi/js'
|
||||
import { Modal, FullScreenModal } from '@/mixins'
|
||||
import qbit from '@/services/qbit'
|
||||
|
||||
export default {
|
||||
name: 'ChangeLocationModal',
|
||||
mixins: [Modal, FullScreenModal],
|
||||
|
@ -69,7 +70,8 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
newPath: ''
|
||||
newPath: '',
|
||||
mdiFile, mdiFolder, mdiClose
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
|
@ -12,7 +12,10 @@
|
|||
:key="t.hash"
|
||||
>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title class="truncate" v-text="t.name" />
|
||||
<v-list-item-title
|
||||
class="text-wrap"
|
||||
v-text="t.name"
|
||||
/>
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
|
@ -20,21 +23,21 @@
|
|||
<v-card-actions class="justify-center pb-5">
|
||||
<v-btn
|
||||
text
|
||||
class="error white--text mt-3"
|
||||
class="accent white--text mt-3"
|
||||
@click="close()"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
text
|
||||
class="accent white--text mt-3"
|
||||
class="error white--text mt-3"
|
||||
@click="deleteWithoutFiles()"
|
||||
>
|
||||
Delete
|
||||
</v-btn>
|
||||
<v-btn
|
||||
text
|
||||
class="accent white--text mt-3"
|
||||
class="error white--text mt-3"
|
||||
@click="deleteWithFiles()"
|
||||
>
|
||||
Delete with files
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<v-card style="overflow: hidden !important">
|
||||
<v-container :style="{ height: phoneLayout ? '100vh' : '' }">
|
||||
<v-card-title class="pb-0 justify-center">
|
||||
<h2>Rename Torrent</h2>
|
||||
<h2>Rename</h2>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div>
|
||||
|
@ -18,7 +18,7 @@
|
|||
<v-text-field
|
||||
v-model="name"
|
||||
label="Torrent Name"
|
||||
prepend-icon="insert_drive_file"
|
||||
:prepend-icon="mdiFile"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
@ -42,7 +42,7 @@
|
|||
right
|
||||
@click="close"
|
||||
>
|
||||
<v-icon>close</v-icon>
|
||||
<v-icon>{{ mdiClose }}</v-icon>
|
||||
</v-btn>
|
||||
</v-fab-transition>
|
||||
</v-card>
|
||||
|
@ -51,6 +51,7 @@
|
|||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { mdiFile, mdiClose } from '@mdi/js'
|
||||
import { Modal, FullScreenModal } from '@/mixins'
|
||||
import qbit from '@/services/qbit'
|
||||
export default {
|
||||
|
@ -61,7 +62,8 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
name: ''
|
||||
name: '',
|
||||
mdiFile, mdiClose
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
112
src/components/Modals/SpeedLimitModal.vue
Normal file
112
src/components/Modals/SpeedLimitModal.vue
Normal file
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<v-dialog
|
||||
v-model="dialog"
|
||||
scrollable
|
||||
max-width="500px"
|
||||
:fullscreen="phoneLayout"
|
||||
>
|
||||
<v-card style="overflow: hidden !important">
|
||||
<v-container :style="{ height: phoneLayout ? '100vh' : '' }">
|
||||
<v-card-title class="pb-0 justify-center">
|
||||
<h2 class="text-capitalize">
|
||||
Limit {{ mode }}
|
||||
</h2>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<div>
|
||||
<v-container>
|
||||
<v-row>
|
||||
<v-col>
|
||||
<v-text-field
|
||||
v-model="limit"
|
||||
label="Speed Limit"
|
||||
:prepend-icon="mdiSpeedometer"
|
||||
suffix="KB/s"
|
||||
clearable
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</v-card-text>
|
||||
<div>
|
||||
<v-card-actions class="justify-center">
|
||||
<v-btn color="success" @click="setLimit">
|
||||
Save
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</div>
|
||||
</v-container>
|
||||
<v-fab-transition v-if="phoneLayout">
|
||||
<v-btn
|
||||
color="red"
|
||||
dark
|
||||
absolute
|
||||
bottom
|
||||
right
|
||||
@click="close"
|
||||
>
|
||||
<v-icon>{{ mdiClose }}</v-icon>
|
||||
</v-btn>
|
||||
</v-fab-transition>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import { mdiSpeedometer, mdiClose } from '@mdi/js'
|
||||
import { Modal, FullScreenModal } from '@/mixins'
|
||||
import qbit from '@/services/qbit'
|
||||
export default {
|
||||
name: 'SpeedLimitModal',
|
||||
mixins: [Modal, FullScreenModal],
|
||||
props: {
|
||||
mode: String,
|
||||
hash: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
limit: '',
|
||||
mdiSpeedometer, mdiClose
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getTorrent']),
|
||||
torrent() {
|
||||
return this.getTorrent(this.hash)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
switch (this.mode) {
|
||||
case 'download':
|
||||
this.limit = this.torrent.dl_limit / 1024
|
||||
break
|
||||
case 'upload':
|
||||
this.limit = this.torrent.up_limit / 1024
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
setLimit() {
|
||||
switch (this.mode) {
|
||||
case 'download':
|
||||
qbit.setDownloadLimit([this.hash], this.limit * 1024 ?? -1)
|
||||
break
|
||||
case 'upload':
|
||||
qbit.setUploadLimit([this.hash], this.limit * 1024 ?? -1)
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
this.close()
|
||||
},
|
||||
close() {
|
||||
this.$store.commit('DELETE_MODAL', this.guid)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -170,6 +170,30 @@
|
|||
{{ torrent.auto_tmm }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">
|
||||
Share Limit
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.ratio_limit }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">
|
||||
Download Limit
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.dl_limit | getDataValue }} {{ torrent.dl_limit | getDataUnit }}/s
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="grey--text">
|
||||
Upload Limit
|
||||
</td>
|
||||
<td>
|
||||
{{ torrent.up_limit | getDataValue }} {{ torrent.up_limit | getDataUnit }}/s
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-simple-table>
|
||||
</v-card-text>
|
||||
|
|
|
@ -1,60 +1,5 @@
|
|||
<template>
|
||||
<div :class="mobile ? '' : 'flex-shrink-0 ml-0'">
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
:text="!mobile"
|
||||
small
|
||||
fab
|
||||
class="mr-0 ml-0"
|
||||
aria-label="Select Mode"
|
||||
v-on="on"
|
||||
@click="toggleSelectMode()"
|
||||
>
|
||||
<v-icon color="grey">
|
||||
{{ $store.state.selectMode ? mdiCheckboxMarked : mdiCheckboxBlankOutline }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Select Mode</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
:text="!mobile"
|
||||
small
|
||||
fab
|
||||
class="mr-0 ml-0"
|
||||
aria-label="Sort Torrents"
|
||||
v-on="on"
|
||||
@click="addModal('SortModal')"
|
||||
>
|
||||
<v-icon color="grey">
|
||||
{{ mdiSort }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Sort Torrents</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
:text="!mobile"
|
||||
small
|
||||
fab
|
||||
color="grey--text"
|
||||
class="mr-0 ml-0"
|
||||
aria-label="Search New Torrent"
|
||||
v-on="on"
|
||||
@click="addModal('SearchModal')"
|
||||
>
|
||||
<v-icon color="grey">
|
||||
{{ mdiSearchWeb }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Search new Torrent</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
|
@ -74,24 +19,6 @@
|
|||
</template>
|
||||
<span> Add Torrent</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
small
|
||||
fab
|
||||
:text="!mobile"
|
||||
class="mr-0 ml-0"
|
||||
aria-label="Remove Selected Torrents"
|
||||
v-on="on"
|
||||
@click="removeTorrents"
|
||||
>
|
||||
<v-icon color="grey">
|
||||
{{ mdiDelete }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Remove Selected Torrents</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
|
@ -128,6 +55,43 @@
|
|||
</template>
|
||||
<span>Pause Selected Torrents</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
small
|
||||
fab
|
||||
:text="!mobile"
|
||||
class="mr-0 ml-0"
|
||||
aria-label="Remove Selected Torrents"
|
||||
v-on="on"
|
||||
@click="removeTorrents"
|
||||
>
|
||||
<v-icon color="grey">
|
||||
{{ mdiDelete }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Remove Selected Torrents</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
:text="!mobile"
|
||||
small
|
||||
fab
|
||||
color="grey--text"
|
||||
class="mr-0 ml-0"
|
||||
aria-label="Search New Torrent"
|
||||
v-on="on"
|
||||
@click="addModal('SearchModal')"
|
||||
>
|
||||
<v-icon color="grey">
|
||||
{{ mdiSearchWeb }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Search new Torrent</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
|
@ -193,14 +157,6 @@ export default {
|
|||
},
|
||||
addModal(name) {
|
||||
this.createModal(name)
|
||||
},
|
||||
toggleSelectMode() {
|
||||
if (this.$store.state.selectMode) {
|
||||
this.$store.state.selected_torrents = []
|
||||
|
||||
return this.$store.state.selectMode = false
|
||||
}
|
||||
this.$store.state.selectMode = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<v-layout
|
||||
row
|
||||
wrap
|
||||
class="ma-0 pa-4 ml-0 "
|
||||
class="ma-0 px-4 py-2 ml-0 "
|
||||
:class="style"
|
||||
>
|
||||
<v-flex xs12>
|
||||
|
|
|
@ -193,13 +193,42 @@
|
|||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-menu
|
||||
v-if="!multiple"
|
||||
open-on-hover
|
||||
top
|
||||
>
|
||||
<template #activator="{ on }">
|
||||
<v-list-item link v-on="on">
|
||||
<v-icon>{{ mdiSpeedometerSlow }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
Set Limit
|
||||
<v-icon>{{ mdiChevronRight }}</v-icon>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<v-list dense rounded>
|
||||
<v-list-item @click="setLimit('download')">
|
||||
<v-icon>{{ mdiChevronDown }}</v-icon>
|
||||
<v-list-item-title class="ml-2">
|
||||
Download
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item @click="setLimit('upload')">
|
||||
<v-icon>{{ mdiChevronUp }}</v-icon>
|
||||
<v-list-item-title class="ml-2">
|
||||
Upload
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
<v-divider v-if="!multiple" />
|
||||
<v-list-item v-if="!multiple" link @click="showInfo">
|
||||
<v-icon>{{ mdiInformation }}</v-icon>
|
||||
<v-list-item-title
|
||||
class="ml-2"
|
||||
style="font-size: 1em"
|
||||
>
|
||||
<v-list-item-title class="ml-2">
|
||||
Show Info
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
@ -222,7 +251,8 @@ import {
|
|||
mdiBullhorn, mdiPlaylistCheck, mdiArrowUp, mdiArrowDown, mdiPriorityLow,
|
||||
mdiInformation, mdiDeleteForever, mdiRenameBox, mdiFolder, mdiDelete,
|
||||
mdiPlay, mdiPause, mdiSelect, mdiPriorityHigh, mdiChevronRight,
|
||||
mdiFastForward, mdiShape, mdiHeadCog, mdiCheckboxMarked, mdiCheckboxBlankOutline
|
||||
mdiFastForward, mdiShape, mdiHeadCog, mdiCheckboxMarked, mdiCheckboxBlankOutline,
|
||||
mdiSpeedometerSlow, mdiChevronUp, mdiChevronDown
|
||||
} from '@mdi/js'
|
||||
|
||||
export default {
|
||||
|
@ -241,7 +271,8 @@ export default {
|
|||
mdiDelete, mdiPlay, mdiPause, mdiSelect, mdiFastForward,
|
||||
mdiFolder, mdiRenameBox, mdiDeleteForever, mdiInformation,
|
||||
mdiPlaylistCheck, mdiPriorityHigh, mdiBullhorn, mdiChevronRight,
|
||||
mdiShape, mdiHeadCog, mdiCheckboxMarked, mdiCheckboxBlankOutline
|
||||
mdiShape, mdiHeadCog, mdiCheckboxMarked, mdiCheckboxBlankOutline,
|
||||
mdiSpeedometerSlow, mdiChevronUp, mdiChevronDown
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters(['getCategories']),
|
||||
|
@ -293,7 +324,10 @@ export default {
|
|||
this.createModal('TorrentDetailModal', { hash: this.torrent.hash })
|
||||
},
|
||||
setPriority(priority) {
|
||||
qbit.setTorrentPriority(this.hash, priority)
|
||||
qbit.setTorrentPriority(this.hashes, priority)
|
||||
},
|
||||
setLimit(mode) {
|
||||
this.createModal('SpeedLimitModal', { hash: this.torrent.hash, mode })
|
||||
},
|
||||
forceResume() {
|
||||
qbit.forceStartTorrents(this.hashes)
|
||||
|
|
|
@ -85,7 +85,7 @@ export function networkSize(size) {
|
|||
Vue.filter('networkSize', networkSize)
|
||||
|
||||
function getDataUnit(a, b) {
|
||||
if (a == 0) return 'B'
|
||||
if (!a) return 'B'
|
||||
const c = 1024
|
||||
const e = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||
const f = Math.floor(Math.log(a) / Math.log(c))
|
||||
|
|
|
@ -26,6 +26,9 @@ export default class Torrent {
|
|||
this.f_l_piece_prio = data.f_l_piece_prio
|
||||
this.seq_dl = data.seq_dl
|
||||
this.auto_tmm = data.auto_tmm
|
||||
this.dl_limit = data.dl_limit
|
||||
this.up_limit = data.up_limit
|
||||
this.ratio_limit = data.ratio_limit
|
||||
|
||||
Object.freeze(this)
|
||||
}
|
||||
|
|
|
@ -211,6 +211,18 @@ class Qbit {
|
|||
return this.torrentAction('setAutoManagement', hashes, { enable })
|
||||
}
|
||||
|
||||
setDownloadLimit(hashes, limit) {
|
||||
return this.torrentAction('setDownloadLimit', hashes, { limit })
|
||||
}
|
||||
|
||||
setUploadLimit(hashes, limit) {
|
||||
return this.torrentAction('setUploadLimit', hashes, { limit })
|
||||
}
|
||||
|
||||
setShareLimit(hashes, ratioLimit, seedingTimeLimit) {
|
||||
return this.torrentAction('setShareLimits', hashes, { ratioLimit, seedingTimeLimit })
|
||||
}
|
||||
|
||||
reannounceTorrents(hashes) {
|
||||
return this.torrentAction('reannounce', hashes)
|
||||
}
|
||||
|
@ -269,10 +281,10 @@ class Qbit {
|
|||
}
|
||||
|
||||
/** Torrent Priority **/
|
||||
setTorrentPriority(hash, priority) {
|
||||
setTorrentPriority(hashes, priority) {
|
||||
if (['increasePrio', 'decreasePrio', 'topPrio', 'bottomPrio'].includes(priority)) {
|
||||
return this.execute('post', `/torrents/${priority}`, {
|
||||
hashes: hash
|
||||
hashes: hashes.join('|')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,14 +95,17 @@ export default {
|
|||
UPDATE_SORT_OPTIONS: (state, {
|
||||
reverse = false,
|
||||
hashes = [],
|
||||
filter = null, category = null,
|
||||
tracker = null
|
||||
filter = null,
|
||||
category = null,
|
||||
tracker = null,
|
||||
sort = null
|
||||
}) => {
|
||||
state.sort_options.reverse = reverse
|
||||
state.sort_options.hashes = hashes
|
||||
state.sort_options.filter = filter
|
||||
state.sort_options.category = category
|
||||
state.sort_options.tracker = tracker
|
||||
state.sort_options.sort = sort
|
||||
},
|
||||
FETCH_CATEGORIES: async state => state.categories = Object.values(await (qbit.getCategories())),
|
||||
FETCH_SEARCH_PLUGINS: async state => state.searchPlugins = await qbit.getSearchPlugins(),
|
||||
|
|
|
@ -7,7 +7,7 @@ $upload: #00b3fa;
|
|||
$torrent-done: #3cd1c2;
|
||||
$torrent-downloading: #5bb974;
|
||||
$torrent-fail: #f83e70;
|
||||
$torrent-paused: #cfd8dc;
|
||||
$torrent-paused: #9CA3AF;
|
||||
$torrent-queued: #2e5eaa;
|
||||
$torrent-seeding: #4ecde6;
|
||||
$torrent-checking: #ff7043;
|
||||
|
|
|
@ -1,36 +1,105 @@
|
|||
<template>
|
||||
<div class="px-1 px-sm-5 pt-4 background" @click.self="resetSelected">
|
||||
<v-row no-gutters class="grey--text">
|
||||
<v-row
|
||||
no-gutters
|
||||
class="grey--text"
|
||||
align="center"
|
||||
justify="center"
|
||||
>
|
||||
<v-col>
|
||||
<h1 style="font-size: 1.6em !important" class="subtitle-1 ml-2">
|
||||
Dashboard
|
||||
</h1>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<p style="float: right; font-size: 0.8em" class="mr-2 text-uppercase">
|
||||
<v-col class="align-center justify-center">
|
||||
<span style="float: right; font-size: 0.8em" class="mr-2 text-uppercase">
|
||||
{{ torrentCountString }}
|
||||
</p>
|
||||
</span>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<div class="my-2 px-2" @click.self="resetSelected">
|
||||
<v-flex
|
||||
xs12
|
||||
sm6
|
||||
md3
|
||||
@click.self="resetSelected"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="input"
|
||||
flat
|
||||
label="Filter"
|
||||
outlined
|
||||
clearable
|
||||
solo
|
||||
:append-outer-icon="mdiFilter"
|
||||
@click:clear="resetInput()"
|
||||
/>
|
||||
</v-flex>
|
||||
<v-row class="my-2 mx-1" @click.self="resetSelected">
|
||||
<v-expand-x-transition>
|
||||
<v-card
|
||||
v-show="searchFilterEnabled"
|
||||
id="searchFilter"
|
||||
flat
|
||||
xs7
|
||||
md3
|
||||
class="ma-0 pa-0 mt-1 transparent"
|
||||
>
|
||||
<v-text-field
|
||||
v-model="input"
|
||||
flat
|
||||
label="Search"
|
||||
dense
|
||||
outlined
|
||||
clearable
|
||||
solo
|
||||
height="50px"
|
||||
width="100px"
|
||||
@click:clear="resetInput()"
|
||||
/>
|
||||
</v-card>
|
||||
</v-expand-x-transition>
|
||||
<v-row style="margin-top: 10px" class="mb-1 mx-1">
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
small
|
||||
fab
|
||||
class="mr-0 ml-0"
|
||||
aria-label="Select Mode"
|
||||
v-on="on"
|
||||
@click="searchFilterEnabled = !searchFilterEnabled"
|
||||
>
|
||||
<v-icon color="grey">
|
||||
{{ mdiFilter }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Toggle Search Filter</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
small
|
||||
fab
|
||||
class="mr-0 ml-0"
|
||||
aria-label="Select Mode"
|
||||
v-on="on"
|
||||
@click="toggleSelectMode()"
|
||||
>
|
||||
<v-icon color="grey">
|
||||
{{ $store.state.selectMode ? mdiCheckboxMarked : mdiCheckboxBlankOutline }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Select Mode</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom>
|
||||
<template #activator="{ on }">
|
||||
<v-btn
|
||||
text
|
||||
small
|
||||
fab
|
||||
class="mr-0 ml-0"
|
||||
aria-label="Sort Torrents"
|
||||
v-on="on"
|
||||
@click="addModal('SortModal')"
|
||||
>
|
||||
<v-icon color="grey">
|
||||
{{ mdiSort }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>Sort Torrents</span>
|
||||
</v-tooltip>
|
||||
</v-row>
|
||||
</v-row>
|
||||
|
||||
<div v-if="torrents.length === 0" class="mt-5 text-xs-center">
|
||||
<p class="grey--text">
|
||||
|
@ -42,17 +111,22 @@
|
|||
<v-list-item
|
||||
v-for="(torrent, index) in paginatedData"
|
||||
:key="torrent.hash"
|
||||
class="pa-0 mb-1"
|
||||
class="pa-0"
|
||||
:class="isMobile ? 'mb-1' : 'mb-2'"
|
||||
@contextmenu.prevent="$refs.menu.open($event, { torrent })"
|
||||
>
|
||||
<template #default>
|
||||
<v-list-item-action v-if="selectMode">
|
||||
<v-checkbox
|
||||
color="grey"
|
||||
:input-value="selected_torrents.indexOf(torrent.hash) !== -1"
|
||||
@click="selectTorrent(torrent.hash)"
|
||||
/>
|
||||
</v-list-item-action>
|
||||
<v-expand-x-transition>
|
||||
<v-card v-show="selectMode" flat class="transparent">
|
||||
<v-list-item-action>
|
||||
<v-checkbox
|
||||
color="grey"
|
||||
:input-value="selected_torrents.indexOf(torrent.hash) !== -1"
|
||||
@click="selectTorrent(torrent.hash)"
|
||||
/>
|
||||
</v-list-item-action>
|
||||
</v-card>
|
||||
</v-expand-x-transition>
|
||||
<v-list-item-content class="pa-0">
|
||||
<Torrent :torrent="torrent" />
|
||||
<v-divider
|
||||
|
@ -86,7 +160,7 @@
|
|||
<script>
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import Fuse from 'fuse.js'
|
||||
import { mdiFilter } from '@mdi/js'
|
||||
import { mdiFilter, mdiCheckboxMarked, mdiCheckboxBlankOutline, mdiSort } from '@mdi/js'
|
||||
|
||||
import { VueContext } from 'vue-context'
|
||||
import 'vue-context/src/sass/vue-context.scss'
|
||||
|
@ -103,8 +177,9 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
input: '',
|
||||
searchFilterEnabled: false,
|
||||
pageNumber: 1,
|
||||
mdiFilter
|
||||
mdiFilter, mdiCheckboxBlankOutline, mdiCheckboxMarked, mdiSort
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -176,6 +251,17 @@ export default {
|
|||
toTop() {
|
||||
this.$vuetify.goTo(0)
|
||||
},
|
||||
toggleSelectMode() {
|
||||
if (this.$store.state.selectMode) {
|
||||
this.$store.state.selected_torrents = []
|
||||
|
||||
return this.$store.state.selectMode = false
|
||||
}
|
||||
this.$store.state.selectMode = true
|
||||
},
|
||||
addModal(name) {
|
||||
this.createModal(name)
|
||||
},
|
||||
handleKeyboardShortcut(e) {
|
||||
// 'ctrl + A' => select torrents
|
||||
if (e.keyCode === 65 && e.ctrlKey) {
|
||||
|
@ -204,13 +290,9 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.v-context {
|
||||
&,
|
||||
& ul {
|
||||
border-radius: 0.3rem;
|
||||
padding: 0;
|
||||
}
|
||||
<style lang="scss">
|
||||
#searchFilter .v-text-field__details {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
@ -18,12 +18,26 @@ describe('SpeedCard.vue', () => {
|
|||
expect(wrapper.find('[data-testid="SpeedCard-icon"]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('should render value and unit & be formatted', () => {
|
||||
const wrapper = setup(SpeedCard, { value: 10000 })
|
||||
it('should render with 0 as value', () => {
|
||||
const wrapper = setup(SpeedCard)
|
||||
expect(wrapper.find('[data-testid="SpeedCard-value"]').exists()).toBe(true)
|
||||
expect(wrapper.find('[data-testid="SpeedCard-value"]').text()).toBe('9.77')
|
||||
expect(wrapper.find('[data-testid="SpeedCard-value"]').text()).toBe('0')
|
||||
|
||||
expect(wrapper.find('[data-testid="SpeedCard-unit"]').exists()).toBe(true)
|
||||
expect(wrapper.find('[data-testid="SpeedCard-unit"]').text()).toBe('B/s')
|
||||
})
|
||||
|
||||
it('should render value (0 decimals) and unit & be formatted', () => {
|
||||
const wrapper = setup(SpeedCard, { value: 26823 })
|
||||
expect(wrapper.find('[data-testid="SpeedCard-value"]').text()).toBe('26')
|
||||
|
||||
expect(wrapper.find('[data-testid="SpeedCard-unit"]').text()).toBe('KB/s')
|
||||
})
|
||||
|
||||
it('should render value (2 decimals) and unit & be formatted', () => {
|
||||
const wrapper = setup(SpeedCard, { value: 10899700 })
|
||||
expect(wrapper.find('[data-testid="SpeedCard-value"]').text()).toBe('10.39')
|
||||
|
||||
expect(wrapper.find('[data-testid="SpeedCard-unit"]').text()).toBe('MB/s')
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue