mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2025-03-14 12:10:18 +03:00
perf: Rework search engine (#819)
This commit is contained in:
parent
17aff9e034
commit
834827fed4
26 changed files with 551 additions and 422 deletions
|
@ -73,8 +73,8 @@ export default {
|
|||
this.newPath = this.torrents[0].savePath
|
||||
},
|
||||
methods: {
|
||||
setLocation() {
|
||||
qbit.setTorrentLocation(this.hashes, this.newPath)
|
||||
async setLocation() {
|
||||
await qbit.setTorrentLocation(this.hashes, this.newPath)
|
||||
this.close()
|
||||
},
|
||||
close() {
|
||||
|
|
|
@ -54,8 +54,8 @@ export default {
|
|||
close() {
|
||||
this.dialog = false
|
||||
},
|
||||
deleteTorrent() {
|
||||
qbit.deleteTorrents(this.selected_torrents, this.settings.deleteWithFiles)
|
||||
async deleteTorrent() {
|
||||
await qbit.deleteTorrents(this.selected_torrents, this.settings.deleteWithFiles)
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,16 +67,16 @@ export default defineComponent({
|
|||
document.removeEventListener('keydown', this.handleKeyboardShortcut)
|
||||
},
|
||||
methods: {
|
||||
create() {
|
||||
qbit.createFeed(this.feed)
|
||||
async create() {
|
||||
await qbit.createFeed(this.feed)
|
||||
this.cancel()
|
||||
},
|
||||
cancel() {
|
||||
this.$store.commit('FETCH_FEEDS')
|
||||
this.dialog = false
|
||||
},
|
||||
edit() {
|
||||
qbit.editFeed(this.initialFeed.name, this.feed.name)
|
||||
async edit() {
|
||||
await qbit.editFeed(this.initialFeed.name, this.feed.name)
|
||||
this.$toast.success(this.$t('toast.feedSaved'))
|
||||
this.cancel()
|
||||
},
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-btn @click="opened = true">
|
||||
<v-icon>{{ mdiCog }}</v-icon> {{ $t('modals.pluginManager.title') | titleCase }}
|
||||
</v-btn>
|
||||
<v-dialog v-model="opened" width="50%">
|
||||
<v-card class="pa-0">
|
||||
<v-card-title class="justify-center pa-1">
|
||||
<v-toolbar flat dense class="transparent">
|
||||
<v-toolbar-title>
|
||||
<v-icon>{{ mdiToyBrick }}</v-icon> Plugin manager
|
||||
</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-btn fab small class="transparent elevation-0" @click="close">
|
||||
<v-icon>{{ mdiClose }}</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-switch v-for="(plugin, key) in searchPlugins" :key="key" v-model="plugin.enabled" :label="plugin.fullName" @change="togglePlugin(plugin)" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
import qbit from '@/services/qbit'
|
||||
import { mdiCog, mdiToyBrick, mdiClose } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'PluginsManager',
|
||||
data: () => ({
|
||||
opened: false,
|
||||
mdiCog,
|
||||
mdiToyBrick,
|
||||
mdiClose
|
||||
}),
|
||||
computed: {
|
||||
...mapState(['searchPlugins'])
|
||||
},
|
||||
watch: {
|
||||
opened() {
|
||||
this.$store.commit('FETCH_SEARCH_PLUGINS')
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (!this.searchPlugins.length) {
|
||||
qbit.updateSearchPlugins()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
togglePlugin(plugin) {
|
||||
qbit.enableSearchPlugin([plugin.name], plugin.enabled)
|
||||
},
|
||||
close() {
|
||||
this.opened = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -1,195 +0,0 @@
|
|||
<template>
|
||||
<v-dialog v-model="dialog" scrollable :width="dialogWidth" :fullscreen="phoneLayout" :style="{ height: phoneLayout ? '100vh' : '' }">
|
||||
<v-card :style="{ height: phoneLayout ? '100vh' : '' }">
|
||||
<v-card-text class="pa-0">
|
||||
<v-form ref="form" v-model="searchForm.valid">
|
||||
<v-flex row class="my-1 py-1 px-2 mx-auto">
|
||||
<v-col class="pa-0" cols="8">
|
||||
<v-text-field
|
||||
v-model="searchForm.pattern"
|
||||
:prepend-inner-icon="mdiMagnify"
|
||||
label="Search"
|
||||
:rules="[v => !!v || 'Search term is required']"
|
||||
clearable
|
||||
style="width: 95%"
|
||||
autofocus
|
||||
@keydown.enter.prevent="$refs.searchButton.click"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col class="pa-0 mt-2" cols="3">
|
||||
<v-btn ref="searchButton" class="mt-2 mx-0" :disabled="!searchForm.valid" :color="loading ? 'warning' : 'primary'" @click="loading ? stopSearch() : startSearch()">
|
||||
{{ loading ? $t('modals.search.btnStopSearch') : $t('modals.search.btnStartSearch') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-flex>
|
||||
</v-form>
|
||||
<v-data-table
|
||||
id="searchTable"
|
||||
:headers="grid.headers"
|
||||
:items="search.results"
|
||||
:items-per-page="10"
|
||||
:loading="loading"
|
||||
:style="{ maxHeight: '60vh' }"
|
||||
:search="filter"
|
||||
:custom-filter="customFilter"
|
||||
:sort-by.sync="sortBy"
|
||||
:sort-desc.sync="sortDesc"
|
||||
>
|
||||
<template #top>
|
||||
<v-text-field ref="filterRef" v-model="filter" :label="$t('filter')" class="mx-4" />
|
||||
</template>
|
||||
<template #[`item.fileName`]="{ item }">
|
||||
<a :href="item.descrLink" target="_blank" v-text="item.fileName" />
|
||||
</template>
|
||||
<template #[`item.fileSize`]="{ item }">
|
||||
{{ item.fileSize | formatSize }}
|
||||
</template>
|
||||
<template #[`item.actions`]="{ item }">
|
||||
<v-icon @click="downloadTorrent(item)">
|
||||
{{ mdiDownload }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<PluginManager />
|
||||
</v-card-actions>
|
||||
<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 qbit from '@/services/qbit'
|
||||
import { Modal, FullScreenModal, General } from '@/mixins'
|
||||
import PluginManager from './PluginManager.vue'
|
||||
import { mdiClose, mdiMagnify, mdiDownload } from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'SearchModal',
|
||||
components: { PluginManager },
|
||||
mixins: [Modal, FullScreenModal, General],
|
||||
data() {
|
||||
return {
|
||||
search: {
|
||||
id: null,
|
||||
status: null,
|
||||
interval: null,
|
||||
results: []
|
||||
},
|
||||
loading: false,
|
||||
grid: {
|
||||
headers: [
|
||||
{ text: this.$t('modals.search.columnTitle.name'), value: 'fileName' },
|
||||
{ text: this.$t('modals.search.columnTitle.size'), value: 'fileSize' },
|
||||
{ text: this.$t('modals.search.columnTitle.seeds'), value: 'nbSeeders' },
|
||||
{ text: this.$t('modals.search.columnTitle.peers'), value: 'nbLeechers' },
|
||||
{
|
||||
text: this.$t('modals.search.columnTitle.search_engine'),
|
||||
value: 'siteUrl'
|
||||
},
|
||||
{
|
||||
text: this.$t('modals.search.columnTitle.action'),
|
||||
value: 'actions',
|
||||
sortable: false
|
||||
}
|
||||
]
|
||||
},
|
||||
searchForm: {
|
||||
valid: false,
|
||||
pattern: ''
|
||||
},
|
||||
filter: '',
|
||||
mdiClose,
|
||||
mdiMagnify,
|
||||
mdiDownload,
|
||||
sortBy: 'nbSeeders',
|
||||
sortDesc: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['getSearchPlugins']),
|
||||
dialogWidth() {
|
||||
return this.phoneLayout ? '100%' : '70%'
|
||||
},
|
||||
enabledSearchPlugins() {
|
||||
return this.getSearchPlugins().filter(p => p.enabled)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$store.commit('FETCH_SEARCH_PLUGINS')
|
||||
},
|
||||
methods: {
|
||||
async startSearch() {
|
||||
if (this.searchForm.pattern.length && !this.search.interval) {
|
||||
this.loading = true
|
||||
this.search.status = 'Running'
|
||||
this.search.results = []
|
||||
this.$refs.filterRef.reset()
|
||||
const data = await qbit.startSearch(
|
||||
this.searchForm.pattern,
|
||||
this.enabledSearchPlugins.map(p => p.name)
|
||||
)
|
||||
this.search.id = data.id
|
||||
await this.getStatus()
|
||||
this.search.interval = setInterval(async () => {
|
||||
const status = await this.getStatus()
|
||||
if (status === 'Stopped') {
|
||||
clearInterval(this.search.interval)
|
||||
this.search.interval = null
|
||||
}
|
||||
await this.getResults()
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
async getStatus() {
|
||||
if (this.search.id) {
|
||||
const data = await qbit.getSearchStatus(this.search.id)
|
||||
|
||||
return (this.search.status = data[0].status)
|
||||
}
|
||||
},
|
||||
async getResults() {
|
||||
const data = await qbit.getSearchResults(this.search.id)
|
||||
this.search.results = data.results
|
||||
},
|
||||
downloadTorrent(item) {
|
||||
this.createModal('addModal', { initialMagnet: item.fileUrl })
|
||||
},
|
||||
stopSearch() {
|
||||
qbit.stopSearch(this.search.id)
|
||||
this.loading = false
|
||||
},
|
||||
close() {
|
||||
this.dialog = false
|
||||
},
|
||||
customFilter(value, search, item) {
|
||||
const searchArr = search.trim().toLowerCase().split(' ')
|
||||
|
||||
return value != null && search != null && typeof value === 'string' && searchArr.every(i => value.toString().toLowerCase().indexOf(i) !== -1)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
#searchTable {
|
||||
.v-data-footer {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.v-data-footer__pagination {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.v-select__slot {
|
||||
width: 4em;
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
173
src/components/Modals/SearchPluginManager.vue
Normal file
173
src/components/Modals/SearchPluginManager.vue
Normal file
|
@ -0,0 +1,173 @@
|
|||
<template>
|
||||
<v-dialog v-model="dialog" scrollable :width="dialogWidth" :fullscreen="phoneLayout">
|
||||
<v-card>
|
||||
<v-card-title class="pa-0">
|
||||
<v-toolbar-title class="ma-4 primarytext--text">
|
||||
<h3>{{ $t('modals.searchPluginManager.title') }}</h3>
|
||||
</v-toolbar-title>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<v-dialog v-model="installDialog" max-width="500px">
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn class="primary white--text elevation-0 px-4 ma-4" v-on="on">Install</v-btn>
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<h5>{{ $t('modals.searchPluginManager.install.title') }}</h5>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-text-field v-model="installInput" :label="$t('modals.searchPluginManager.install.label')" required />
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn color="accent darken-1" text @click="closeInstallDialog">{{ $t('cancel') }}</v-btn>
|
||||
<v-btn color="accent darken-1" text @click="installNewPlugin">{{ $t('ok') }}</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-data-table
|
||||
style="width: 100%"
|
||||
v-model="enabledPlugins"
|
||||
item-key="name"
|
||||
:headers="headers"
|
||||
:items="searchPlugins"
|
||||
disable-pagination
|
||||
hide-default-footer
|
||||
show-select
|
||||
sort-by="name"
|
||||
:loading="loading"
|
||||
@item-selected="onTogglePlugin"
|
||||
@toggle-select-all="onToggleAllPlugins"
|
||||
>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<v-icon color="red" small @click="uninstallPlugin(item)">
|
||||
{{ mdiDelete }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn class="white--text elevation-0 px-4" @click="checkForPluginUpdates">
|
||||
{{ $t('modals.searchPluginManager.checkForUpdates') }}
|
||||
</v-btn>
|
||||
<v-btn class="accent white--text elevation-0 px-4" @click="close">
|
||||
{{ $t('close') }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { FullScreenModal, Modal } from '@/mixins'
|
||||
import { mapState } from 'vuex'
|
||||
import { SearchPlugin } from '@/types/qbit/models'
|
||||
import qbit from '@/services/qbit'
|
||||
import { mdiDelete } from '@mdi/js'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SearchPluginManager',
|
||||
mixins: [Modal, FullScreenModal],
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{ text: 'Name', value: 'fullName' },
|
||||
{ text: 'Version', value: 'version' },
|
||||
{ text: 'URL', value: 'url' },
|
||||
{ text: 'Actions', value: 'actions' }
|
||||
],
|
||||
enabledPlugins: [],
|
||||
loading: false,
|
||||
installDialog: false,
|
||||
installInput: '',
|
||||
mdiDelete
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['searchPlugins'])
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyboardShortcut)
|
||||
|
||||
this.loading = true
|
||||
this.updatePluginList()
|
||||
this.loading = false
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.handleKeyboardShortcut)
|
||||
},
|
||||
methods: {
|
||||
async updatePluginList() {
|
||||
await this.$store.dispatch('FETCH_SEARCH_PLUGINS')
|
||||
this.enabledPlugins = this.searchPlugins.filter((plugin: SearchPlugin) => plugin.enabled)
|
||||
},
|
||||
async onTogglePlugin(payload: { item: SearchPlugin; value: boolean }) {
|
||||
this.loading = true
|
||||
|
||||
await qbit.enableSearchPlugin([payload.item.name], payload.value)
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
async onToggleAllPlugins(payload: { items: SearchPlugin[]; value: boolean }) {
|
||||
this.loading = true
|
||||
|
||||
await qbit.enableSearchPlugin(
|
||||
payload.items.map(plugin => plugin.name),
|
||||
payload.value
|
||||
)
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
async installNewPlugin() {
|
||||
this.loading = true
|
||||
|
||||
this.closeInstallDialog()
|
||||
|
||||
await qbit.installSearchPlugin([this.installInput])
|
||||
this.installInput = ''
|
||||
|
||||
await this.updatePluginList()
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
async checkForPluginUpdates() {
|
||||
this.loading = true
|
||||
|
||||
await qbit.updateSearchPlugins()
|
||||
await this.updatePluginList()
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
async uninstallPlugin(plugin: SearchPlugin) {
|
||||
this.loading = true
|
||||
|
||||
await qbit.uninstallSearchPlugin([plugin.name])
|
||||
await this.updatePluginList()
|
||||
|
||||
this.loading = false
|
||||
},
|
||||
close() {
|
||||
this.dialog = false
|
||||
},
|
||||
closeInstallDialog() {
|
||||
this.installDialog = false
|
||||
},
|
||||
handleKeyboardShortcut(e: KeyboardEvent) {
|
||||
if (e.key === 'Escape') {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
|
@ -46,8 +46,8 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
save() {
|
||||
qbit.setShareLimit([this.hash], this.$refs.ratio.export(), this.$refs.time.export())
|
||||
async save() {
|
||||
await qbit.setShareLimit([this.hash], this.$refs.ratio.export(), this.$refs.time.export())
|
||||
this.close()
|
||||
},
|
||||
close() {
|
||||
|
|
|
@ -84,20 +84,20 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
setLimit() {
|
||||
async setLimit() {
|
||||
switch (this.mode) {
|
||||
case 'download':
|
||||
if (this.isGlobal()) {
|
||||
qbit.setGlobalDownloadLimit(this.exportLimit())
|
||||
await qbit.setGlobalDownloadLimit(this.exportLimit())
|
||||
} else {
|
||||
qbit.setDownloadLimit([this.hash], this.exportLimit())
|
||||
await qbit.setDownloadLimit([this.hash], this.exportLimit())
|
||||
}
|
||||
break
|
||||
case 'upload':
|
||||
if (this.isGlobal()) {
|
||||
qbit.setGlobalUploadLimit(this.exportLimit())
|
||||
await qbit.setGlobalUploadLimit(this.exportLimit())
|
||||
} else {
|
||||
qbit.setUploadLimit([this.hash], this.exportLimit())
|
||||
await qbit.setUploadLimit([this.hash], this.exportLimit())
|
||||
}
|
||||
break
|
||||
default:
|
||||
|
@ -107,7 +107,7 @@ export default {
|
|||
this.close()
|
||||
},
|
||||
isGlobal() {
|
||||
return this.torrent ? false : true
|
||||
return !this.torrent
|
||||
},
|
||||
formatLimit(limit) {
|
||||
return limit > 0 ? limit / 1024 : '∞'
|
||||
|
|
|
@ -69,16 +69,16 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
create() {
|
||||
qbit.createCategory(this.category)
|
||||
async create() {
|
||||
await qbit.createCategory(this.category)
|
||||
this.cancel()
|
||||
},
|
||||
cancel() {
|
||||
this.$store.commit('FETCH_CATEGORIES')
|
||||
this.dialog = false
|
||||
},
|
||||
edit() {
|
||||
qbit.editCategory(this.category)
|
||||
async edit() {
|
||||
await qbit.editCategory(this.category)
|
||||
Vue.$toast.success(this.$t('toast.categorySaved'))
|
||||
this.cancel()
|
||||
}
|
||||
|
|
|
@ -41,8 +41,8 @@ export default {
|
|||
this.$store.commit('FETCH_TAGS')
|
||||
},
|
||||
methods: {
|
||||
create() {
|
||||
qbit.createTag([this.tagname])
|
||||
async create() {
|
||||
await qbit.createTag([this.tagname])
|
||||
this.cancel()
|
||||
},
|
||||
cancel() {
|
||||
|
|
|
@ -101,8 +101,8 @@ export default {
|
|||
this.$store.commit('LOGOUT')
|
||||
this.$router.push({ name: 'login' })
|
||||
},
|
||||
toggleSpeed() {
|
||||
qbit.toggleSpeedLimitsMode()
|
||||
async toggleSpeed() {
|
||||
await qbit.toggleSpeedLimitsMode()
|
||||
},
|
||||
toggleTheme() {
|
||||
this.webuiSettings.darkTheme = !this.webuiSettings.darkTheme
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
</v-tooltip>
|
||||
<v-tooltip bottom open-delay="400">
|
||||
<template #activator="{ on }">
|
||||
<v-btn :text="!mobile" small fab color="grey--text" class="mr-0 ml-0" :aria-label="$t('navbar.topActions.searchNew')" v-on="on" @click="addModal('SearchModal')">
|
||||
<v-btn :text="!mobile" small fab color="grey--text" class="mr-0 ml-0" :aria-label="$t('navbar.topActions.searchNew')" v-on="on" @click="goToSearch">
|
||||
<v-icon color="grey">
|
||||
{{ mdiSearchWeb }}
|
||||
</v-icon>
|
||||
|
@ -103,11 +103,11 @@ export default {
|
|||
...mapState(['selected_torrents'])
|
||||
},
|
||||
methods: {
|
||||
pauseTorrents() {
|
||||
qbit.pauseTorrents(this.selected_torrents)
|
||||
async pauseTorrents() {
|
||||
await qbit.pauseTorrents(this.selected_torrents)
|
||||
},
|
||||
resumeTorrents() {
|
||||
qbit.resumeTorrents(this.selected_torrents)
|
||||
async resumeTorrents() {
|
||||
await qbit.resumeTorrents(this.selected_torrents)
|
||||
},
|
||||
removeTorrents() {
|
||||
if (!this.selected_torrents.length) return
|
||||
|
@ -117,6 +117,9 @@ export default {
|
|||
addModal(name) {
|
||||
this.createModal(name)
|
||||
},
|
||||
goToSearch() {
|
||||
if (this.$route.name !== 'search') this.$router.push({ name: 'search' })
|
||||
},
|
||||
goToRss() {
|
||||
if (this.$route.name !== 'rss') this.$router.push({ name: 'rss' })
|
||||
},
|
||||
|
|
|
@ -76,8 +76,8 @@ export default defineComponent({
|
|||
editFeed(item: Feed) {
|
||||
this.createModal('FeedForm', { initialFeed: { url: item.url, name: item.name } })
|
||||
},
|
||||
deleteFeed(item: Feed) {
|
||||
qbit.deleteFeed(item.name)
|
||||
async deleteFeed(item: Feed) {
|
||||
await qbit.deleteFeed(item.name)
|
||||
this.$store.commit('FETCH_FEEDS')
|
||||
},
|
||||
createFeed() {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<v-card flat>
|
||||
<v-row dense class="ma-0 pa-0">
|
||||
<v-col cols="12" sm="6" lg="3" v-for="(item, index) in availableRules" :key="item.uid">
|
||||
<v-col cols="12" sm="6" lg="3" v-for="item in availableRules" :key="item.uid">
|
||||
<v-list-item>
|
||||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.name" />
|
||||
|
@ -62,8 +62,8 @@ export default {
|
|||
activeMethod() {
|
||||
this.$store.commit('FETCH_RULES')
|
||||
},
|
||||
deleteRule(item) {
|
||||
qbit.deleteRule(item.name)
|
||||
async deleteRule(item) {
|
||||
await qbit.deleteRule(item.name)
|
||||
this.$store.commit('FETCH_RULES')
|
||||
},
|
||||
createRule() {
|
||||
|
|
|
@ -95,12 +95,12 @@ export default {
|
|||
createCategory() {
|
||||
this.createModal('CreateCategoryDialog')
|
||||
},
|
||||
deleteCategory(category) {
|
||||
qbit.deleteCategory([category.name])
|
||||
async deleteCategory(category) {
|
||||
await qbit.deleteCategory([category.name])
|
||||
this.$store.commit('FETCH_CATEGORIES')
|
||||
},
|
||||
deleteTag(item) {
|
||||
qbit.deleteTag([item])
|
||||
async deleteTag(item) {
|
||||
await qbit.deleteTag([item])
|
||||
this.$store.commit('FETCH_TAGS')
|
||||
},
|
||||
editCategory(cat) {
|
||||
|
|
|
@ -387,11 +387,11 @@ export default {
|
|||
detectRightside() {
|
||||
this.isRightside = document.documentElement.clientWidth < this.x + 380
|
||||
},
|
||||
resume() {
|
||||
qbit.resumeTorrents(this.hashes)
|
||||
async resume() {
|
||||
await qbit.resumeTorrents(this.hashes)
|
||||
},
|
||||
pause() {
|
||||
qbit.pauseTorrents(this.hashes)
|
||||
async pause() {
|
||||
await qbit.pauseTorrents(this.hashes)
|
||||
},
|
||||
location() {
|
||||
this.createModal('ChangeLocationModal', { hashes: this.multiple ? this.selected_torrents : [this.torrent.hash] })
|
||||
|
@ -399,22 +399,22 @@ export default {
|
|||
rename() {
|
||||
this.createModal('RenameModal', { hash: this.torrent.hash })
|
||||
},
|
||||
reannounce() {
|
||||
qbit.reannounceTorrents(this.hashes)
|
||||
async reannounce() {
|
||||
await qbit.reannounceTorrents(this.hashes)
|
||||
},
|
||||
removeTorrent() {
|
||||
this.$store.state.selected_torrents = this.hashes
|
||||
|
||||
return this.createModal('ConfirmDeleteModal')
|
||||
},
|
||||
recheck() {
|
||||
qbit.recheckTorrents(this.hashes)
|
||||
async recheck() {
|
||||
await qbit.recheckTorrents(this.hashes)
|
||||
},
|
||||
showInfo() {
|
||||
this.$router.push({ name: 'torrentDetail', params: { hash: this.torrent.hash } })
|
||||
},
|
||||
setPriority(priority) {
|
||||
qbit.setTorrentPriority(this.hashes, priority)
|
||||
async setPriority(priority) {
|
||||
await qbit.setTorrentPriority(this.hashes, priority)
|
||||
},
|
||||
setLimit(mode) {
|
||||
this.createModal('SpeedLimitModal', { hash: this.torrent.hash, mode })
|
||||
|
@ -422,30 +422,30 @@ export default {
|
|||
setShareLimit() {
|
||||
this.createModal('ShareLimitModal', { hash: this.torrent.hash })
|
||||
},
|
||||
forceResume() {
|
||||
qbit.forceStartTorrents(this.hashes)
|
||||
async forceResume() {
|
||||
await qbit.forceStartTorrents(this.hashes)
|
||||
},
|
||||
setCategory(cat) {
|
||||
qbit.setCategory(this.hashes, cat)
|
||||
async setCategory(cat) {
|
||||
await qbit.setCategory(this.hashes, cat)
|
||||
},
|
||||
setTag(tag) {
|
||||
if (this.torrent.tags && this.torrent.tags.includes(tag)) this.removeTag(tag)
|
||||
else this.addTag(tag)
|
||||
async setTag(tag) {
|
||||
if (this.torrent.tags && this.torrent.tags.includes(tag)) await this.removeTag(tag)
|
||||
else await this.addTag(tag)
|
||||
},
|
||||
addTag(tag) {
|
||||
qbit.addTorrentTag(this.hashes, [tag])
|
||||
async addTag(tag) {
|
||||
await qbit.addTorrentTag(this.hashes, [tag])
|
||||
},
|
||||
removeTag(tag) {
|
||||
qbit.removeTorrentTag(this.hashes, [tag])
|
||||
async removeTag(tag) {
|
||||
await qbit.removeTorrentTag(this.hashes, [tag])
|
||||
},
|
||||
toggleSeq() {
|
||||
qbit.toggleSequentialDownload(this.hashes)
|
||||
async toggleSeq() {
|
||||
await qbit.toggleSequentialDownload(this.hashes)
|
||||
},
|
||||
toggleFL() {
|
||||
qbit.toggleFirstLastPiecePriority(this.hashes)
|
||||
async toggleFL() {
|
||||
await qbit.toggleFirstLastPiecePriority(this.hashes)
|
||||
},
|
||||
toggleAutoTMM() {
|
||||
qbit.setAutoTMM(this.hashes, !this.torrent.auto_tmm)
|
||||
async toggleAutoTMM() {
|
||||
await qbit.setAutoTMM(this.hashes, !this.torrent.auto_tmm)
|
||||
},
|
||||
copyToClipBoard(text) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
|
|
|
@ -175,7 +175,7 @@ export default {
|
|||
item.newName = item.name
|
||||
this.toggleEditing(item)
|
||||
},
|
||||
renameFile(item) {
|
||||
async renameFile(item) {
|
||||
const lastPathSep = item.fullName.lastIndexOf('/')
|
||||
const args = [this.hash]
|
||||
|
||||
|
@ -185,12 +185,12 @@ export default {
|
|||
args.push(`${prefix}/${item.name}`, `${prefix}/${item.newName}`)
|
||||
}
|
||||
|
||||
qbit.renameFile(...args).catch(() => Vue.$toast.error(this.$t('toast.renameFileFailed')))
|
||||
await qbit.renameFile(...args).catch(() => Vue.$toast.error(this.$t('toast.renameFileFailed')))
|
||||
|
||||
item.name = item.newName
|
||||
this.toggleEditing(item)
|
||||
},
|
||||
renameFolder(item) {
|
||||
async renameFolder(item) {
|
||||
const lastPathSep = item.fullName.lastIndexOf('/')
|
||||
const args = [this.hash]
|
||||
|
||||
|
@ -200,13 +200,13 @@ export default {
|
|||
args.push(`${prefix}/${item.name}`, `${prefix}/${item.newName}`)
|
||||
}
|
||||
|
||||
qbit.renameFolder(...args).catch(() => Vue.$toast.error(this.$t('toast.renameFolderFailed')))
|
||||
await qbit.renameFolder(...args).catch(() => Vue.$toast.error(this.$t('toast.renameFolderFailed')))
|
||||
|
||||
item.name = item.newName
|
||||
this.toggleEditing(item)
|
||||
},
|
||||
setFilePrio(fileId, priority) {
|
||||
qbit.setTorrentFilePriority(this.hash, [fileId], priority).then(() => this.initFiles())
|
||||
async setFilePrio(fileId, priority) {
|
||||
await qbit.setTorrentFilePriority(this.hash, [fileId], priority).then(() => this.initFiles())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,25 +77,25 @@ export default {
|
|||
this.$store.commit('FETCH_TAGS')
|
||||
},
|
||||
methods: {
|
||||
addTag(tag) {
|
||||
async addTag(tag) {
|
||||
if (this.activeTags.includes(this.availableTags.indexOf(tag))) {
|
||||
return this.deleteTag(tag)
|
||||
await this.deleteTag(tag)
|
||||
}
|
||||
|
||||
return qbit.addTorrentTag([this.hash], [tag])
|
||||
await qbit.addTorrentTag([this.hash], [tag])
|
||||
},
|
||||
deleteTag(tag) {
|
||||
qbit.removeTorrentTag([this.hash], [tag])
|
||||
async deleteTag(tag) {
|
||||
await qbit.removeTorrentTag([this.hash], [tag])
|
||||
},
|
||||
setCategory(cat) {
|
||||
async setCategory(cat) {
|
||||
if (this.torrent.category === cat.name) {
|
||||
return this.deleteCategory()
|
||||
await this.deleteCategory()
|
||||
}
|
||||
|
||||
return qbit.setCategory([this.hash], cat.name)
|
||||
await qbit.setCategory([this.hash], cat.name)
|
||||
},
|
||||
deleteCategory() {
|
||||
qbit.setCategory([this.hash], '')
|
||||
async deleteCategory() {
|
||||
await qbit.setCategory([this.hash], '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,11 @@
|
|||
"downloaded": "Downloaded",
|
||||
"uploaded": "Uploaded"
|
||||
},
|
||||
"search": {
|
||||
"title": "Search torrents",
|
||||
"runNewSearch": "Search",
|
||||
"stopSearch": "Stop"
|
||||
},
|
||||
"navbar": {
|
||||
"currentSpeed": "Current Speed",
|
||||
"alltimeTitle": "All-Time Stats",
|
||||
|
@ -173,8 +178,13 @@
|
|||
"matchingArticles": {
|
||||
"title": "Matching RSS Articles"
|
||||
},
|
||||
"pluginManager": {
|
||||
"title": "Plugin manager"
|
||||
"searchPluginManager": {
|
||||
"title": "Plugin manager",
|
||||
"checkForUpdates": "Check for Updates",
|
||||
"install": {
|
||||
"title": "Install new plugin",
|
||||
"label": "URL or file path"
|
||||
}
|
||||
},
|
||||
"search": {
|
||||
"title": "Search",
|
||||
|
|
|
@ -24,6 +24,11 @@ const router = new Router({
|
|||
name: 'rss',
|
||||
component: () => import('./views/RssArticles.vue')
|
||||
},
|
||||
{
|
||||
path: '/search',
|
||||
name: 'search',
|
||||
component: () => import('./views/SearchEngine.vue')
|
||||
},
|
||||
{
|
||||
path: '/torrent/:hash',
|
||||
name: 'torrentDetail',
|
||||
|
|
|
@ -48,13 +48,10 @@ export class QBitApi {
|
|||
|
||||
async login(params: LoginPayload): Promise<string> {
|
||||
const payload = new URLSearchParams(params)
|
||||
const res = await this.axios
|
||||
.post('/auth/login', payload, {
|
||||
validateStatus: (status: number) => status === 200 || status === 403
|
||||
})
|
||||
.catch(err => console.log(err))
|
||||
|
||||
return res?.data
|
||||
return this.axios.post('/auth/login', payload, { validateStatus: (status: number) => status === 200 || status === 403 }).then(
|
||||
res => res.data,
|
||||
err => console.log(err)
|
||||
)
|
||||
}
|
||||
|
||||
async getAuthenticationStatus(): Promise<boolean> {
|
||||
|
@ -65,7 +62,7 @@ export class QBitApi {
|
|||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
await this.axios.post('/auth/logout')
|
||||
return this.axios.post('/auth/logout')
|
||||
}
|
||||
|
||||
async getAppPreferences(): Promise<AppPreferences> {
|
||||
|
@ -77,7 +74,7 @@ export class QBitApi {
|
|||
json: JSON.stringify(params)
|
||||
}
|
||||
|
||||
await this.execute('/app/setPreferences', data)
|
||||
return this.execute('/app/setPreferences', data)
|
||||
}
|
||||
|
||||
async getMainData(rid?: number): Promise<MainDataResponse> {
|
||||
|
@ -85,7 +82,7 @@ export class QBitApi {
|
|||
}
|
||||
|
||||
async toggleSpeedLimitsMode(): Promise<void> {
|
||||
await this.execute('/transfer/toggleSpeedLimitsMode')
|
||||
return this.execute('/transfer/toggleSpeedLimitsMode')
|
||||
}
|
||||
|
||||
async getTorrents(payload: SortOptions): Promise<Torrent[]> {
|
||||
|
@ -122,7 +119,7 @@ export class QBitApi {
|
|||
}
|
||||
|
||||
async setTorrentName(hash: string, name: string): Promise<void> {
|
||||
await this.execute('/torrents/rename', { hash, name })
|
||||
return this.execute('/torrents/rename', { hash, name })
|
||||
}
|
||||
|
||||
async getTorrentPieceStates(hash: string): Promise<number[]> {
|
||||
|
@ -156,7 +153,7 @@ export class QBitApi {
|
|||
// RSS
|
||||
|
||||
async createFeed(payload: CreateFeedPayload): Promise<void> {
|
||||
await this.execute('/rss/addFeed', {
|
||||
return this.execute('/rss/addFeed', {
|
||||
url: payload.url,
|
||||
path: payload.name
|
||||
})
|
||||
|
@ -178,40 +175,38 @@ export class QBitApi {
|
|||
}
|
||||
|
||||
async editFeed(itemPath: string, destPath: string): Promise<void> {
|
||||
await this.execute('/rss/moveItem', {
|
||||
return this.execute('/rss/moveItem', {
|
||||
itemPath,
|
||||
destPath
|
||||
})
|
||||
}
|
||||
|
||||
async renameRule(ruleName: string, newRuleName: string): Promise<void> {
|
||||
await this.execute('/rss/renameRule', {
|
||||
return this.execute('/rss/renameRule', {
|
||||
ruleName,
|
||||
newRuleName
|
||||
})
|
||||
}
|
||||
|
||||
async deleteRule(ruleName: string): Promise<void> {
|
||||
await this.execute('rss/removeRule', {
|
||||
ruleName
|
||||
})
|
||||
return this.execute('rss/removeRule', { ruleName })
|
||||
}
|
||||
|
||||
async deleteFeed(name: string): Promise<void> {
|
||||
await this.execute('rss/removeItem', {
|
||||
return this.execute('rss/removeItem', {
|
||||
path: name
|
||||
})
|
||||
}
|
||||
|
||||
async markAsRead(itemPath: string, articleId: string) {
|
||||
await this.execute('rss/markAsRead', {
|
||||
return this.execute('rss/markAsRead', {
|
||||
itemPath,
|
||||
articleId
|
||||
})
|
||||
}
|
||||
|
||||
async refreshFeed(itemPath: string) {
|
||||
await this.execute('rss/refreshItem', {
|
||||
return this.execute('rss/refreshItem', {
|
||||
itemPath
|
||||
})
|
||||
}
|
||||
|
@ -243,7 +238,7 @@ export class QBitApi {
|
|||
data = new URLSearchParams(params as Parameters)
|
||||
}
|
||||
|
||||
await this.axios.post('/torrents/add', data)
|
||||
return this.axios.post('/torrents/add', data)
|
||||
}
|
||||
|
||||
async setTorrentFilePriority(hash: string, idList: number[], priority: Priority): Promise<void> {
|
||||
|
@ -253,45 +248,45 @@ export class QBitApi {
|
|||
priority
|
||||
}
|
||||
|
||||
await this.execute('/torrents/filePrio', params)
|
||||
return this.execute('/torrents/filePrio', params)
|
||||
}
|
||||
|
||||
async deleteTorrents(hashes: string[], deleteFiles: boolean): Promise<void> {
|
||||
if (!hashes.length) return
|
||||
|
||||
await this.torrentAction('delete', hashes, { deleteFiles })
|
||||
return this.torrentAction('delete', hashes, { deleteFiles })
|
||||
}
|
||||
|
||||
async pauseTorrents(hashes: string[]): Promise<void> {
|
||||
await this.torrentAction('pause', hashes)
|
||||
return this.torrentAction('pause', hashes)
|
||||
}
|
||||
|
||||
async resumeTorrents(hashes: string[]): Promise<void> {
|
||||
await this.torrentAction('resume', hashes)
|
||||
return this.torrentAction('resume', hashes)
|
||||
}
|
||||
|
||||
async forceStartTorrents(hashes: string[]): Promise<void> {
|
||||
await this.torrentAction('setForceStart', hashes, { value: true })
|
||||
return this.torrentAction('setForceStart', hashes, { value: true })
|
||||
}
|
||||
|
||||
async toggleSequentialDownload(hashes: string[]): Promise<void> {
|
||||
await this.torrentAction('toggleSequentialDownload', hashes)
|
||||
return this.torrentAction('toggleSequentialDownload', hashes)
|
||||
}
|
||||
|
||||
async toggleFirstLastPiecePriority(hashes: string[]): Promise<void> {
|
||||
await this.torrentAction('toggleFirstLastPiecePrio', hashes)
|
||||
return this.torrentAction('toggleFirstLastPiecePrio', hashes)
|
||||
}
|
||||
|
||||
async setAutoTMM(hashes: string[], enable: boolean): Promise<void> {
|
||||
await this.torrentAction('setAutoManagement', hashes, { enable })
|
||||
return this.torrentAction('setAutoManagement', hashes, { enable })
|
||||
}
|
||||
|
||||
async setDownloadLimit(hashes: string[], limit: number): Promise<void> {
|
||||
await this.torrentAction('setDownloadLimit', hashes, { limit })
|
||||
return this.torrentAction('setDownloadLimit', hashes, { limit })
|
||||
}
|
||||
|
||||
async setUploadLimit(hashes: string[], limit: number): Promise<void> {
|
||||
await this.torrentAction('setUploadLimit', hashes, { limit })
|
||||
return this.torrentAction('setUploadLimit', hashes, { limit })
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -316,7 +311,7 @@ export class QBitApi {
|
|||
limit
|
||||
}
|
||||
|
||||
await this.execute('/transfer/setDownloadLimit', data)
|
||||
return this.execute('/transfer/setDownloadLimit', data)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -327,26 +322,26 @@ export class QBitApi {
|
|||
limit
|
||||
}
|
||||
|
||||
await this.execute('/transfer/setUploadLimit', data)
|
||||
return this.execute('/transfer/setUploadLimit', data)
|
||||
}
|
||||
|
||||
async setShareLimit(hashes: string[], ratioLimit: number, seedingTimeLimit: number): Promise<void> {
|
||||
await this.torrentAction('setShareLimits', hashes, {
|
||||
return this.torrentAction('setShareLimits', hashes, {
|
||||
ratioLimit,
|
||||
seedingTimeLimit
|
||||
})
|
||||
}
|
||||
|
||||
async reannounceTorrents(hashes: string[]): Promise<void> {
|
||||
await this.torrentAction('reannounce', hashes)
|
||||
return this.torrentAction('reannounce', hashes)
|
||||
}
|
||||
|
||||
async recheckTorrents(hashes: string[]): Promise<void> {
|
||||
await this.torrentAction('recheck', hashes)
|
||||
return this.torrentAction('recheck', hashes)
|
||||
}
|
||||
|
||||
async setTorrentLocation(hashes: string[], location: string): Promise<void> {
|
||||
await this.torrentAction('setLocation', hashes, { location })
|
||||
return this.torrentAction('setLocation', hashes, { location })
|
||||
}
|
||||
|
||||
async addTorrentTrackers(hash: string, trackers: string): Promise<void> {
|
||||
|
@ -355,7 +350,7 @@ export class QBitApi {
|
|||
urls: trackers
|
||||
}
|
||||
|
||||
await this.execute(`/torrents/addTrackers`, params)
|
||||
return this.execute(`/torrents/addTrackers`, params)
|
||||
}
|
||||
|
||||
async removeTorrentTrackers(hash: string, trackers: string[]): Promise<void> {
|
||||
|
@ -364,11 +359,11 @@ export class QBitApi {
|
|||
urls: trackers.join('|')
|
||||
}
|
||||
|
||||
await this.execute(`/torrents/removeTrackers`, params)
|
||||
return this.execute(`/torrents/removeTrackers`, params)
|
||||
}
|
||||
|
||||
async addTorrentPeers(hashes: string[], peers: string[]): Promise<void> {
|
||||
await this.torrentAction('addPeers', hashes, { peers: peers.join('|') })
|
||||
return this.torrentAction('addPeers', hashes, { peers: peers.join('|') })
|
||||
}
|
||||
|
||||
async banPeers(peers: string[]): Promise<void> {
|
||||
|
@ -376,7 +371,7 @@ export class QBitApi {
|
|||
peers: peers.join('|')
|
||||
}
|
||||
|
||||
await this.execute('/transfer/banPeers', params)
|
||||
return this.execute('/transfer/banPeers', params)
|
||||
}
|
||||
|
||||
async torrentAction(action: string, hashes: string[], extra?: Record<string, any>): Promise<any> {
|
||||
|
@ -395,7 +390,7 @@ export class QBitApi {
|
|||
newPath
|
||||
}
|
||||
|
||||
await this.execute('/torrents/renameFile', params)
|
||||
return this.execute('/torrents/renameFile', params)
|
||||
}
|
||||
|
||||
async renameFolder(hash: string, oldPath: string, newPath: string): Promise<void> {
|
||||
|
@ -405,33 +400,33 @@ export class QBitApi {
|
|||
newPath
|
||||
}
|
||||
|
||||
await this.execute('/torrents/renameFolder', params)
|
||||
return this.execute('/torrents/renameFolder', params)
|
||||
}
|
||||
|
||||
/** Torrent Priority **/
|
||||
async setTorrentPriority(hashes: string[], priority: 'increasePrio' | 'decreasePrio' | 'topPrio' | 'bottomPrio'): Promise<void> {
|
||||
await this.execute(`/torrents/${priority}`, {
|
||||
return this.execute(`/torrents/${priority}`, {
|
||||
hashes: hashes.join('|')
|
||||
})
|
||||
}
|
||||
|
||||
/** Begin Torrent Tags **/
|
||||
async removeTorrentTag(hashes: string[], tags: string[]): Promise<void> {
|
||||
await this.torrentAction('removeTags', hashes, { tags: tags.join('|') })
|
||||
return this.torrentAction('removeTags', hashes, { tags: tags.join('|') })
|
||||
}
|
||||
|
||||
async addTorrentTag(hashes: string[], tags: string[]): Promise<void> {
|
||||
await this.torrentAction('addTags', hashes, { tags: tags.join('|') })
|
||||
return this.torrentAction('addTags', hashes, { tags: tags.join('|') })
|
||||
}
|
||||
|
||||
async createTag(tags: string[]): Promise<void> {
|
||||
await this.execute('/torrents/createTags', {
|
||||
return this.execute('/torrents/createTags', {
|
||||
tags: tags.join(',')
|
||||
})
|
||||
}
|
||||
|
||||
async deleteTag(tags: string[]): Promise<void> {
|
||||
await this.execute('/torrents/deleteTags', {
|
||||
return this.execute('/torrents/deleteTags', {
|
||||
tags: tags.join(',')
|
||||
})
|
||||
}
|
||||
|
@ -445,20 +440,20 @@ export class QBitApi {
|
|||
}
|
||||
|
||||
async deleteCategory(categories: string[]): Promise<void> {
|
||||
await this.execute('/torrents/removeCategories', {
|
||||
return this.execute('/torrents/removeCategories', {
|
||||
categories: categories.join('\n')
|
||||
})
|
||||
}
|
||||
|
||||
async createCategory(cat: Category): Promise<void> {
|
||||
await this.execute('/torrents/createCategory', {
|
||||
return this.execute('/torrents/createCategory', {
|
||||
category: cat.name,
|
||||
savePath: cat.savePath
|
||||
})
|
||||
}
|
||||
|
||||
async setCategory(hashes: string[], category: string): Promise<void> {
|
||||
await this.torrentAction('setCategory', hashes, { category })
|
||||
return this.torrentAction('setCategory', hashes, { category })
|
||||
}
|
||||
|
||||
async editCategory(cat: Category): Promise<void> {
|
||||
|
@ -467,7 +462,7 @@ export class QBitApi {
|
|||
savePath: cat.savePath
|
||||
}
|
||||
|
||||
await this.execute('/torrents/editCategory', params)
|
||||
return this.execute('/torrents/editCategory', params)
|
||||
}
|
||||
|
||||
async exportTorrent(hash: string): Promise<Blob> {
|
||||
|
@ -483,40 +478,27 @@ export class QBitApi {
|
|||
}
|
||||
|
||||
/** Search **/
|
||||
async getSearchPlugins(): Promise<SearchPlugin[]> {
|
||||
return this.axios.get('/search/plugins').then(res => res.data)
|
||||
}
|
||||
|
||||
async updateSearchPlugins(): Promise<void> {
|
||||
await this.execute('/search/updatePlugins')
|
||||
}
|
||||
|
||||
async enableSearchPlugin(pluginNames: string[], enable: boolean): Promise<void> {
|
||||
const params = {
|
||||
names: pluginNames.join('|'),
|
||||
enable
|
||||
}
|
||||
|
||||
await this.execute('/search/enablePlugin', params)
|
||||
}
|
||||
|
||||
async startSearch(pattern: string, plugins: string[]): Promise<SearchJob> {
|
||||
async startSearch(pattern: string, category: string, plugins: string[]): Promise<SearchJob> {
|
||||
const params = {
|
||||
pattern,
|
||||
plugins: plugins.length ? plugins.join('|') : 'enabled',
|
||||
category: 'all'
|
||||
category,
|
||||
plugins: plugins.join('|')
|
||||
}
|
||||
|
||||
return this.execute('/search/start', params)
|
||||
}
|
||||
|
||||
async stopSearch(id: number): Promise<void> {
|
||||
await this.execute('/search/stop', { id })
|
||||
async stopSearch(id: number): Promise<boolean> {
|
||||
return this.execute('/search/stop', { id }).then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
}
|
||||
|
||||
async getSearchStatus(id?: number): Promise<SearchStatus[]> {
|
||||
const params = id !== undefined ? { id } : undefined
|
||||
return this.execute('/search/status', params)
|
||||
return this.execute('/search/status', {
|
||||
id: id !== undefined ? id : 0
|
||||
}).then(res => res.data)
|
||||
}
|
||||
|
||||
async getSearchResults(id: number, limit?: number, offset?: number): Promise<SearchResultsResponse> {
|
||||
|
@ -527,6 +509,41 @@ export class QBitApi {
|
|||
})
|
||||
}
|
||||
|
||||
async deleteSearchPlugin(id: number): Promise<boolean> {
|
||||
return this.execute('/search/delete', { id }).then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
}
|
||||
|
||||
async getSearchPlugins(): Promise<SearchPlugin[]> {
|
||||
return this.axios.get('/search/plugins').then(res => res.data)
|
||||
}
|
||||
|
||||
async installSearchPlugin(sources: string[]) {
|
||||
return this.execute('/search/installPlugin', { sources: sources.join('|') }).then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
}
|
||||
|
||||
async uninstallSearchPlugin(names: string[]) {
|
||||
return this.execute('/search/uninstallPlugin', { names: names.join('|') })
|
||||
}
|
||||
|
||||
async enableSearchPlugin(names: string[], enable: boolean): Promise<void> {
|
||||
const params = {
|
||||
names: names.join('|'),
|
||||
enable
|
||||
}
|
||||
|
||||
return this.execute('/search/enablePlugin', params)
|
||||
}
|
||||
|
||||
async updateSearchPlugins(): Promise<void> {
|
||||
return this.execute('/search/updatePlugins')
|
||||
}
|
||||
|
||||
async shutdownApp(): Promise<boolean> {
|
||||
return this.axios
|
||||
.post('/app/shutdown')
|
||||
|
|
|
@ -39,5 +39,8 @@ export default {
|
|||
|
||||
store.state.oldSettingsDetected = true
|
||||
Vue.$toast.error(i18n.t('toast.resetSettingsNeeded').toString(), { timeout: 2500 })
|
||||
},
|
||||
FETCH_SEARCH_PLUGINS: async (store: Store<StoreState>) => {
|
||||
await qbit.getSearchPlugins().then(plugins => store.commit('UPDATE_SEARCH_PLUGINS', plugins))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { DocumentTitle, Tags, Trackers, Torrents, Graph } from '@/actions'
|
|||
import { setLanguage } from '@/plugins/i18n'
|
||||
import type { ModalTemplate, StoreState } from '@/types/vuetorrent'
|
||||
import Torrent from '@/models/Torrent'
|
||||
import type { AppPreferences } from '@/types/qbit/models'
|
||||
import type { AppPreferences, SearchPlugin } from '@/types/qbit/models'
|
||||
import { Status } from '@/models'
|
||||
import router from '@/router'
|
||||
|
||||
|
@ -94,7 +94,7 @@ export default {
|
|||
FETCH_TAGS: async (state: StoreState) => (state.tags = await qbit.getAvailableTags()),
|
||||
FETCH_FEEDS: async (state: StoreState) => (state.rss.feeds = Object.entries(await qbit.getFeeds(true)).map(([key, value]) => ({ name: key, ...value }))),
|
||||
FETCH_RULES: async (state: StoreState) => (state.rss.rules = Object.entries(await qbit.getRules()).map(([key, value]) => ({ name: key, ...value }))),
|
||||
FETCH_SEARCH_PLUGINS: async (state: StoreState) => (state.searchPlugins = await qbit.getSearchPlugins()),
|
||||
UPDATE_SEARCH_PLUGINS: async (state: StoreState, plugins: SearchPlugin[]) => (state.searchPlugins = plugins),
|
||||
SET_CURRENT_ITEM_COUNT: (state: StoreState, count: number) => (state.filteredTorrentsCount = count),
|
||||
SET_LANGUAGE: async (state: StoreState) => setLanguage(state.webuiSettings.lang)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@ export default interface SearchStatus {
|
|||
id: number
|
||||
/** Current status of the search job (either Running or Stopped) */
|
||||
status: 'Running' | 'Stopped'
|
||||
/** Total number of results. If the status is Running this number may contineu to increase */
|
||||
/** Total number of results. If the status is Running this number may continue to increase */
|
||||
total: number
|
||||
}
|
||||
|
|
|
@ -81,13 +81,10 @@ export default defineComponent({
|
|||
headers: [
|
||||
{ text: this.$t('modals.rss.columnTitle.id'), value: 'id' },
|
||||
{ text: this.$t('modals.rss.columnTitle.title'), value: 'title' },
|
||||
// {text: this.$t('modals.rss.columnTitle.description'), value: 'description'},
|
||||
{ text: this.$t('modals.rss.columnTitle.category'), value: 'category' },
|
||||
{ text: this.$t('modals.rss.columnTitle.author'), value: 'author' },
|
||||
{ text: this.$t('modals.rss.columnTitle.date'), value: 'parsedDate' },
|
||||
{ text: this.$t('modals.rss.columnTitle.feedName'), value: 'feedName' },
|
||||
// {text: this.$t('modals.rss.columnTitle.link'), value: 'link'},
|
||||
// {text: this.$t('modals.rss.columnTitle.torrentURL'), value: 'torrentURL'},
|
||||
{ text: this.$t('modals.rss.columnTitle.actions'), value: 'actions', sortable: false }
|
||||
],
|
||||
filter: '',
|
||||
|
|
178
src/views/SearchEngine.vue
Normal file
178
src/views/SearchEngine.vue
Normal file
|
@ -0,0 +1,178 @@
|
|||
<template>
|
||||
<div class="px-1 px-sm-5 background noselect">
|
||||
<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">
|
||||
{{ $t('search.title') }}
|
||||
</h1>
|
||||
</v-col>
|
||||
<v-col class="align-center justify-center">
|
||||
<v-card-actions class="justify-end">
|
||||
<v-btn small elevation="0" color="primary" @click="openPluginManager">
|
||||
<v-icon>{{ mdiToyBrick }}</v-icon>
|
||||
</v-btn>
|
||||
<v-btn small elevation="0" @click="close">
|
||||
<v-icon>{{ mdiClose }}</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-card flat>
|
||||
<v-list-item class="searchCriterias">
|
||||
<v-row class="my-2">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field v-model="searchPattern" dense hide-details clearable :rules="[v => !!v || 'Search term is required']" label="Search pattern" />
|
||||
</v-col>
|
||||
<v-col cols="6" sm="5" md="2">
|
||||
<v-select v-model="searchCategory" height="1" flat dense hide-details outlined :items="categories" label="Search category" />
|
||||
</v-col>
|
||||
<v-col cols="6" sm="5" md="2">
|
||||
<v-select v-model="searchPlugin" height="1" flat dense hide-details outlined :items="plugins" label="Search plugins" />
|
||||
</v-col>
|
||||
<v-col cols="12" sm="2" class="d-flex align-center justify-center">
|
||||
<v-btn v-if="queryId === 0" class="mx-auto accent white--text elevation-0 px-4" @click="runNewSearch">
|
||||
{{ $t('search.runNewSearch') }}
|
||||
</v-btn>
|
||||
<v-btn v-else class="mx-auto warning white--text elevation-0 px-4" @click="stopSearch">
|
||||
{{ $t('search.stopSearch') }}
|
||||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-list-item>
|
||||
</v-card>
|
||||
|
||||
<v-card flat class="mt-5">
|
||||
<v-list-item class="searchResults">
|
||||
<v-data-table style="width: 100%" id="searchResultsTable" :headers="headers" :items="filteredResults" :search="resultFilter">
|
||||
<template v-slot:top>
|
||||
<v-row class="mt-2">
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field v-model="resultFilter" dense hide-details label="Filter" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
<template v-slot:item.fileSize="{ item }">
|
||||
{{ item.fileSize | formatSize }}
|
||||
</template>
|
||||
<template v-slot:item.actions="{ item }">
|
||||
<span class="search-actions">
|
||||
<v-icon @click="downloadTorrent(item)">{{ mdiDownload }}</v-icon>
|
||||
</span>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-list-item>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import { mapState } from 'vuex'
|
||||
import { mdiClose, mdiDownload, mdiToyBrick } from '@mdi/js'
|
||||
import qbit from '@/services/qbit'
|
||||
import { General } from '@/mixins'
|
||||
import { SearchPlugin, SearchResult } from '@/types/qbit/models'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SearchEngine',
|
||||
mixins: [General],
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{ text: 'Filename', value: 'fileName' },
|
||||
{ text: 'File Size', value: 'fileSize' },
|
||||
{ text: 'Seeders', value: 'nbSeeders' },
|
||||
{ text: 'Leechers', value: 'nbLeechers' },
|
||||
{ text: 'Site URL', value: 'siteUrl' },
|
||||
{ text: '', value: 'actions', sortable: false }
|
||||
],
|
||||
searchPattern: '',
|
||||
searchCategory: 'all',
|
||||
searchPlugin: 'enabled',
|
||||
queryId: 0,
|
||||
queryTimer: NaN as NodeJS.Timer,
|
||||
queryResults: [] as SearchResult[],
|
||||
resultFilter: '',
|
||||
mdiClose,
|
||||
mdiDownload,
|
||||
mdiToyBrick
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState(['searchPlugins']),
|
||||
categories() {
|
||||
const cats = [
|
||||
{ text: 'Movies', value: 'movies' },
|
||||
{ text: 'TV shows', value: 'tv' },
|
||||
{ text: 'Music', value: 'music' },
|
||||
{ text: 'Games', value: 'games' },
|
||||
{ text: 'Anime', value: 'anime' },
|
||||
{ text: 'Software', value: 'software' },
|
||||
{ text: 'Pictures', value: 'pictures' },
|
||||
{ text: 'Books', value: 'books' }
|
||||
]
|
||||
cats.sort((a, b) => a.text.localeCompare(b.text))
|
||||
|
||||
return [{ text: 'All categories', value: 'all' }, ...cats]
|
||||
},
|
||||
plugins() {
|
||||
const plugins = [
|
||||
{ text: 'All plugins', value: 'all' },
|
||||
{ text: 'Only enabled', value: 'enabled' }
|
||||
]
|
||||
|
||||
this.searchPlugins.filter((plugin: SearchPlugin) => plugin.enabled).forEach((plugin: SearchPlugin) => plugins.push({ text: plugin.fullName, value: plugin.name }))
|
||||
|
||||
return plugins
|
||||
},
|
||||
filteredResults() {
|
||||
return this.queryResults
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
await this.$store.dispatch('FETCH_SEARCH_PLUGINS')
|
||||
},
|
||||
async beforeDestroy() {
|
||||
await this.stopSearch()
|
||||
},
|
||||
methods: {
|
||||
openPluginManager() {
|
||||
this.createModal('SearchPluginManager')
|
||||
},
|
||||
close() {
|
||||
this.$router.back()
|
||||
},
|
||||
async runNewSearch() {
|
||||
const searchJob = await qbit.startSearch(this.searchPattern, this.searchCategory, [this.searchPlugin])
|
||||
this.queryId = searchJob.id
|
||||
this.queryTimer = setInterval(() => this.refreshResults(), 1000)
|
||||
},
|
||||
async stopSearch() {
|
||||
if (this.queryId !== 0) await qbit.stopSearch(this.queryId)
|
||||
|
||||
this.queryId = 0
|
||||
clearInterval(this.queryTimer)
|
||||
},
|
||||
async refreshResults() {
|
||||
const response = await qbit.getSearchResults(this.queryId, 100, this.queryResults.length)
|
||||
this.queryResults.push(...response.results)
|
||||
|
||||
if (response.status === 'Stopped') {
|
||||
this.queryId = 0
|
||||
await this.stopSearch()
|
||||
}
|
||||
},
|
||||
downloadTorrent(item: SearchResult) {
|
||||
this.createModal('AddModal', { initialMagnet: item.fileUrl })
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.search-actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
</style>
|
Loading…
Add table
Reference in a new issue