mirror of
https://github.com/CzBiX/qb-web.git
synced 2026-04-10 06:28:01 +08:00
Add actions to torrents
This commit is contained in:
28
src/Api.ts
28
src/Api.ts
@@ -38,10 +38,11 @@ class Api {
|
||||
}
|
||||
|
||||
public getMainData(rid?: number) {
|
||||
const params = {
|
||||
rid,
|
||||
};
|
||||
return this.axios.get('/sync/maindata', {
|
||||
params: {
|
||||
rid,
|
||||
},
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,6 +67,27 @@ class Api {
|
||||
return this.axios.get('/log/main').then(this.handleResponse);
|
||||
}
|
||||
|
||||
public deleteTorrents(hashes: string[], deleteFiles: boolean) {
|
||||
return this.actionTorrents('delete', hashes, {deleteFiles});
|
||||
}
|
||||
|
||||
public pauseTorrents(hashes: string[]) {
|
||||
return this.actionTorrents('pause', hashes);
|
||||
}
|
||||
|
||||
public resumeTorrents(hashes: string[]) {
|
||||
return this.actionTorrents('resume', hashes);
|
||||
}
|
||||
|
||||
private actionTorrents(action: string, hashes: string[], extra?: any) {
|
||||
const params: any = {
|
||||
hashes: hashes.join('|'),
|
||||
...extra,
|
||||
};
|
||||
const data = new URLSearchParams(params);
|
||||
return this.axios.post('/torrents/' + action, data).then(this.handleResponse);
|
||||
}
|
||||
|
||||
private handleResponse(resp: AxiosResponse) {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</v-navigation-drawer>
|
||||
<v-toolbar
|
||||
:clipped-left="$vuetify.breakpoint.lgAndUp"
|
||||
:scroll-toolbar-off-screen="!$vuetify.breakpoint.lgAndUp"
|
||||
app
|
||||
>
|
||||
<v-toolbar-title class="headline">
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
:rules="[v => !!v || 'URLs is required']"
|
||||
:rows="$vuetify.breakpoint.xsOnly ? 1 : 3"
|
||||
required
|
||||
autofocus
|
||||
/>
|
||||
</v-flex>
|
||||
<v-flex>
|
||||
@@ -128,6 +129,7 @@ export default Vue.extend({
|
||||
|
||||
this.submitting = false;
|
||||
this.dialog = false;
|
||||
this.params.urls = null;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -48,11 +48,11 @@
|
||||
<v-btn
|
||||
@click="submit"
|
||||
color="primary"
|
||||
:disabled="!valid"
|
||||
:disabled="!valid || submitting"
|
||||
>
|
||||
<v-progress-circular
|
||||
v-if="submitting"
|
||||
:indeterminate="true"
|
||||
indeterminate
|
||||
/>
|
||||
<template v-else>
|
||||
Submit
|
||||
|
||||
@@ -1,52 +1,79 @@
|
||||
<template>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="torrents"
|
||||
item-key="hash"
|
||||
:hide-actions="torrents.length <= pagination.rowsPerPage"
|
||||
:pagination.sync="pagination"
|
||||
>
|
||||
<template v-slot:items="row">
|
||||
<td>
|
||||
<v-checkbox
|
||||
:input-value="row.selected"
|
||||
hide-details
|
||||
/>
|
||||
</td>
|
||||
<td>{{ row.item.name }}</td>
|
||||
<td>{{ row.item.size | formatSize }}</td>
|
||||
<td>
|
||||
<v-progress-linear
|
||||
height="1.5em"
|
||||
:value="row.item.progress * 100"
|
||||
class="text-xs-center ma-0"
|
||||
>
|
||||
<span :class="row.item.progress | progressColorClass">{{ row.item.progress | progressText }}</span>
|
||||
</v-progress-linear>
|
||||
</td>
|
||||
<td>{{ row.item.state }}</td>
|
||||
<td>{{ row.item.num_seeds }}/{{ row.item.num_complete }}</td>
|
||||
<td>{{ row.item.num_leechs }}/{{ row.item.num_incomplete }}</td>
|
||||
<td>{{ row.item.dlspeed | formatSize }}/s</td>
|
||||
<td>{{ row.item.upspeed | formatSize }}/s</td>
|
||||
<td>{{ row.item.eta | formatDuration }}</td>
|
||||
<td>{{ row.item.ratio.toFixed(2) }}</td>
|
||||
<td>{{ row.item.added_on | formatTimestamp }}</td>
|
||||
</template>
|
||||
</v-data-table>
|
||||
<div>
|
||||
<v-toolbar
|
||||
flat
|
||||
dense
|
||||
color="white"
|
||||
>
|
||||
<v-btn icon :disabled="!hasSelected" @click="confirmDelete">
|
||||
<v-icon>mdi-delete</v-icon>
|
||||
</v-btn>
|
||||
<v-divider vertical inset />
|
||||
<v-btn icon :disabled="!hasSelected" @click="resumeTorrents">
|
||||
<v-icon>mdi-play</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon :disabled="!hasSelected" @click="pauseTorrents">
|
||||
<v-icon>mdi-pause</v-icon>
|
||||
</v-btn>
|
||||
</v-toolbar>
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="torrents"
|
||||
item-key="hash"
|
||||
:hide-actions="torrents.length <= pagination.rowsPerPage"
|
||||
select-all
|
||||
:pagination.sync="pagination"
|
||||
v-model="selectedRows"
|
||||
>
|
||||
<template v-slot:items="row">
|
||||
<td>
|
||||
<v-checkbox
|
||||
v-model="row.selected"
|
||||
hide-details
|
||||
/>
|
||||
</td>
|
||||
<td>{{ row.item.name }}</td>
|
||||
<td>{{ row.item.size | formatSize }}</td>
|
||||
<td>
|
||||
<v-progress-linear
|
||||
height="1.4em"
|
||||
:value="row.item.progress * 100"
|
||||
class="text-xs-center ma-0"
|
||||
>
|
||||
<span :class="row.item.progress | progressColorClass">{{ row.item.progress | progressText }}</span>
|
||||
</v-progress-linear>
|
||||
</td>
|
||||
<td>{{ row.item.state }}</td>
|
||||
<td>{{ row.item.num_seeds }}/{{ row.item.num_complete }}</td>
|
||||
<td>{{ row.item.num_leechs }}/{{ row.item.num_incomplete }}</td>
|
||||
<td>{{ row.item.dlspeed | formatSize }}/s</td>
|
||||
<td>{{ row.item.upspeed | formatSize }}/s</td>
|
||||
<td>{{ row.item.eta | formatDuration }}</td>
|
||||
<td>{{ row.item.ratio.toFixed(2) }}</td>
|
||||
<td>{{ row.item.added_on | formatTimestamp }}</td>
|
||||
</template>
|
||||
</v-data-table>
|
||||
|
||||
<confirm-delete-dialog v-if="deleteDialog" v-model="deleteDialog" :torrents="selectedRows" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import ConfirmDeleteDialog from './dialogs/ConfirmDeleteDialog.vue';
|
||||
import { mapState, mapGetters } from 'vuex';
|
||||
import _ from 'lodash';
|
||||
import { api } from '../Api';
|
||||
|
||||
export default Vue.extend({
|
||||
name: 'torrents',
|
||||
|
||||
components: {
|
||||
ConfirmDeleteDialog,
|
||||
},
|
||||
|
||||
data() {
|
||||
const headers = [
|
||||
{ text: '', value: '#', sortable: false },
|
||||
{ text: 'Name', value: 'name', class: 'th-name' },
|
||||
{ text: 'Size', value: 'size' },
|
||||
{ text: 'Progress', value: 'progress' },
|
||||
@@ -62,6 +89,8 @@ export default Vue.extend({
|
||||
|
||||
return {
|
||||
headers,
|
||||
selectedRows: [],
|
||||
deleteDialog: false,
|
||||
pagination: {
|
||||
rowsPerPage: 100,
|
||||
},
|
||||
@@ -78,6 +107,12 @@ export default Vue.extend({
|
||||
'torrentGroupByCategory',
|
||||
'torrentGroupBySite',
|
||||
]),
|
||||
hasSelected() {
|
||||
return this.selectedRows.length;
|
||||
},
|
||||
selectedHashes() {
|
||||
return this.selectedRows.map(_.property('hash'));
|
||||
},
|
||||
torrents() {
|
||||
if (!this.isDataReady) {
|
||||
return [];
|
||||
@@ -96,10 +131,6 @@ export default Vue.extend({
|
||||
|
||||
filters: {
|
||||
progressText(progress: number) {
|
||||
// if (progress >= 1) {
|
||||
// return null;
|
||||
// }
|
||||
|
||||
return Math.floor(progress * 100) + '%';
|
||||
},
|
||||
progressColorClass(progress: number) {
|
||||
@@ -107,6 +138,18 @@ export default Vue.extend({
|
||||
return color + '--text';
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
confirmDelete() {
|
||||
this.deleteDialog = true;
|
||||
},
|
||||
async resumeTorrents() {
|
||||
await api.resumeTorrents(this.selectedHashes);
|
||||
},
|
||||
async pauseTorrents() {
|
||||
await api.pauseTorrents(this.selectedHashes);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
78
src/components/dialogs/ConfirmDeleteDialog.vue
Normal file
78
src/components/dialogs/ConfirmDeleteDialog.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<v-dialog :value="value" @input="$emit('input', $event)" width="40em">
|
||||
<v-card>
|
||||
<v-card-title
|
||||
class="headline grey lighten-4"
|
||||
>
|
||||
<v-icon class="mr-2">mdi-delete</v-icon>
|
||||
<span>Delete torrents</span>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
Are you sure you want to delete the selected torrents from the transfer list?
|
||||
<ol class="torrents pt-4">
|
||||
<li v-for="(row, i) in torrents" :key="i">
|
||||
{{ row.name }}
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<v-checkbox
|
||||
v-model="deleteFiles"
|
||||
prepend-icon="mdi-file-cancel"
|
||||
label="Also delete the files on the hard disk"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn flat @click="closeDialog">Cancel</v-btn>
|
||||
<v-btn
|
||||
@click="submit"
|
||||
color="warning"
|
||||
:disabled="submitting"
|
||||
>
|
||||
<v-progress-circular
|
||||
v-if="submitting"
|
||||
indeterminate
|
||||
/>
|
||||
<template v-else>
|
||||
Delete
|
||||
</template>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import { api } from '@/Api';
|
||||
|
||||
export default Vue.extend({
|
||||
props: {
|
||||
torrents: Array,
|
||||
value: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
deleteFiles: false,
|
||||
submitting: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
closeDialog() {
|
||||
this.$emit('input', false);
|
||||
},
|
||||
async submit() {
|
||||
if (this.submitting) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.submitting = true;
|
||||
|
||||
const hashed = this.torrents.map((t: any) => t.hash);
|
||||
await api.deleteTorrents(hashed, this.deleteFiles);
|
||||
|
||||
this.closeDialog();
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -24,7 +24,14 @@ export default new Vuex.Store({
|
||||
if (payload.full_update) {
|
||||
state.mainData = payload;
|
||||
} else {
|
||||
state.mainData = _.merge({}, state.mainData, payload);
|
||||
const tmp: any = _.cloneDeep(state.mainData);
|
||||
if (payload.torrents_removed) {
|
||||
for (const hash of payload.torrents_removed) {
|
||||
delete tmp.torrents[hash];
|
||||
}
|
||||
delete payload.torrents_removed;
|
||||
}
|
||||
state.mainData = _.merge(tmp, payload);
|
||||
}
|
||||
},
|
||||
updatePreferences(state, payload) {
|
||||
|
||||
Reference in New Issue
Block a user