mirror of
https://github.com/CzBiX/qb-web.git
synced 2026-03-25 06:20:27 +08:00
Add settings
Add more chinese localization Add an option to display speed in page title
This commit is contained in:
12
src/Api.ts
12
src/Api.ts
@@ -1,6 +1,14 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import Axios, { AxiosInstance, AxiosPromise, AxiosResponse } from 'axios';
|
||||
import { RssNode, RssRule, SearchPlugin, ApiCategory, SearchTaskResponse, Preferences } from '@/types';
|
||||
import {
|
||||
RssNode,
|
||||
RssRule,
|
||||
SearchPlugin,
|
||||
ApiCategory,
|
||||
SearchTaskResponse,
|
||||
Preferences,
|
||||
MainData,
|
||||
} from '@/types'
|
||||
|
||||
const apiEndpoint = 'api/v2';
|
||||
|
||||
@@ -62,7 +70,7 @@ class Api {
|
||||
return this.axios.post('/app/shutdown');
|
||||
}
|
||||
|
||||
public getMainData(rid?: number) {
|
||||
public getMainData(rid?: number): AxiosPromise<MainData> {
|
||||
const params = {
|
||||
rid,
|
||||
};
|
||||
|
||||
14
src/App.vue
14
src/App.vue
@@ -82,6 +82,7 @@ import { Watch } from 'vue-property-decorator';
|
||||
import { MainData } from './types';
|
||||
import { Config } from './store/config';
|
||||
import Api from './Api';
|
||||
import {formatSize} from '@/filters'
|
||||
|
||||
let appWrapEl: HTMLElement;
|
||||
|
||||
@@ -205,7 +206,18 @@ export default class App extends Vue {
|
||||
const mainData = resp.data;
|
||||
|
||||
this.updateMainData(mainData);
|
||||
|
||||
if(this.config.displaySpeedInTitle) {
|
||||
const upInfoSpeed = mainData.server_state.up_info_speed
|
||||
const dlInfoSpeed = mainData.server_state.dl_info_speed
|
||||
let dl = '', up = ''
|
||||
if (dlInfoSpeed > 1024) {
|
||||
dl = `D ${formatSize(dlInfoSpeed)}/s`
|
||||
}
|
||||
if (upInfoSpeed > 1024) {
|
||||
up = `U ${formatSize(upInfoSpeed)}/s`
|
||||
}
|
||||
document.title = `[${up} ${dl}] qBittorrent Web UI`
|
||||
}
|
||||
this.task = setTimeout(this.getMainData, this.config.updateInterval);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,50 +68,50 @@ import { tr } from '@/locale';
|
||||
import { Torrent, Category } from '@/types';
|
||||
import FilterGroup from './drawer/FilterGroup.vue';
|
||||
import api from '../Api';
|
||||
import { formatSize } from '../filters';
|
||||
import { StateType } from '../consts';
|
||||
import { formatSize } from '@/filters';
|
||||
import { StateType } from '@/consts';
|
||||
import SiteMap from '@/sites'
|
||||
import Component from 'vue-class-component';
|
||||
import { Prop, Emit } from 'vue-property-decorator';
|
||||
|
||||
const stateList = [
|
||||
{
|
||||
title: tr('state.downloading'),
|
||||
title: tr('category_state.downloading'),
|
||||
state: StateType.Downloading,
|
||||
icon: 'download',
|
||||
},
|
||||
{
|
||||
title: tr('state.seeding'),
|
||||
title: tr('category_state.seeding'),
|
||||
state: StateType.Seeding,
|
||||
icon: 'upload',
|
||||
},
|
||||
{
|
||||
title: tr('state.completed'),
|
||||
title: tr('category_state.completed'),
|
||||
state: StateType.Completed,
|
||||
icon: 'check',
|
||||
},
|
||||
{
|
||||
title: tr('state.resumed'),
|
||||
title: tr('category_state.resumed'),
|
||||
state: StateType.Resumed,
|
||||
icon: 'play',
|
||||
},
|
||||
{
|
||||
title: tr('state.paused'),
|
||||
title: tr('category_state.paused'),
|
||||
state: StateType.Paused,
|
||||
icon: 'pause',
|
||||
},
|
||||
{
|
||||
title: tr('state.active'),
|
||||
title: tr('category_state.active'),
|
||||
state: StateType.Active,
|
||||
icon: 'filter',
|
||||
},
|
||||
{
|
||||
title: tr('state.inactive'),
|
||||
title: tr('category_state.inactive'),
|
||||
state: StateType.Inactive,
|
||||
icon: 'filter-outline',
|
||||
},
|
||||
{
|
||||
title: tr('state.errored'),
|
||||
title: tr('category_state.errored'),
|
||||
state: StateType.Errored,
|
||||
icon: 'alert',
|
||||
},
|
||||
@@ -249,7 +249,7 @@ export default class Drawer extends Vue {
|
||||
filterGroups.push({
|
||||
icon: 'mdi-menu-up',
|
||||
'icon-alt': 'mdi-menu-down',
|
||||
title: tr('state._'),
|
||||
title: tr('category_state._'),
|
||||
model: null,
|
||||
select: 'state',
|
||||
children: [
|
||||
|
||||
@@ -189,7 +189,7 @@ import api from '../Api';
|
||||
import buildInfo from '@/buildInfo';
|
||||
import Component from 'vue-class-component';
|
||||
import { Prop, Watch } from 'vue-property-decorator';
|
||||
import { Torrent, ServerState } from '../types';
|
||||
import { Torrent, ServerState } from '@/types';
|
||||
|
||||
|
||||
@Component({
|
||||
@@ -314,7 +314,7 @@ export default class Footer extends Vue {
|
||||
align-items: center;
|
||||
|
||||
.v-icon {
|
||||
margin-right: 4px;
|
||||
//margin-right: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
icon
|
||||
@click="setTorrentLocation"
|
||||
:title="$t('title.set_location')"
|
||||
:disabled="selectedRows.length == 0"
|
||||
:disabled="selectedRows.length === 0"
|
||||
>
|
||||
<v-icon>mdi-folder-marker</v-icon>
|
||||
</v-btn>
|
||||
@@ -128,7 +128,7 @@
|
||||
icon
|
||||
@click="recheckTorrents"
|
||||
:title="$t('recheck')"
|
||||
:disabled="selectedRows.length == 0"
|
||||
:disabled="selectedRows.length === 0"
|
||||
>
|
||||
<v-icon>mdi-backup-restore</v-icon>
|
||||
</v-btn>
|
||||
@@ -188,7 +188,7 @@
|
||||
</span>
|
||||
</v-progress-linear>
|
||||
</td>
|
||||
<td>{{ row.item.state }}</td>
|
||||
<td>{{ $t('torrent_state.' + 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 | formatNetworkSpeed }}</td>
|
||||
@@ -237,10 +237,10 @@ import ConfirmSetCategoryDialog from './dialogs/ConfirmSetCategoryDialog.vue'
|
||||
import EditTrackerDialog from './dialogs/EditTrackerDialog.vue'
|
||||
import InfoDialog from './dialogs/InfoDialog.vue'
|
||||
import api from '../Api'
|
||||
import { formatSize } from '../filters'
|
||||
import { DialogType, TorrentFilter, ConfigPayload, DialogConfig, SnackBarConfig } from '../store/types'
|
||||
import { formatSize } from '@/filters'
|
||||
import { DialogType, TorrentFilter, ConfigPayload, DialogConfig, SnackBarConfig } from '@/store/types'
|
||||
import Component from 'vue-class-component'
|
||||
import { Torrent, Category } from '../types'
|
||||
import { Torrent, Category } from '@/types'
|
||||
import { Watch } from 'vue-property-decorator'
|
||||
|
||||
function getStateInfo(state: string) {
|
||||
|
||||
@@ -76,17 +76,15 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { chunk, countBy } from 'lodash';
|
||||
import {chunk, countBy} from 'lodash'
|
||||
|
||||
import api from '../../Api';
|
||||
import {
|
||||
formatDuration, formatSize, formatTimestamp, toPrecision,
|
||||
} from '@/filters';
|
||||
import api from '../../Api'
|
||||
import {formatDuration, formatSize, formatTimestamp, toPrecision} from '@/filters'
|
||||
|
||||
import { TorrentProperties, Torrent } from '@/types'
|
||||
import Component from 'vue-class-component';
|
||||
import { Prop, Watch } from 'vue-property-decorator';
|
||||
import BaseTorrentInfo from './baseTorrentInfo';
|
||||
import {Torrent, TorrentProperties} from '@/types'
|
||||
import Component from 'vue-class-component'
|
||||
import {Prop, Watch} from 'vue-property-decorator'
|
||||
import BaseTorrentInfo from './baseTorrentInfo'
|
||||
|
||||
interface Item {
|
||||
label: string;
|
||||
@@ -153,8 +151,7 @@ export default class TorrentInfo extends BaseTorrentInfo {
|
||||
el.height = clientHeight;
|
||||
/* eslint-enable no-param-reassign */
|
||||
|
||||
const ctx = el.getContext('2d')!;
|
||||
return ctx;
|
||||
return el.getContext('2d')!;
|
||||
}
|
||||
|
||||
fetchInfo() {
|
||||
|
||||
@@ -1,35 +1,138 @@
|
||||
<template>
|
||||
<v-expansion-panel>
|
||||
<v-expansion-panel-header> Downloads </v-expansion-panel-header>
|
||||
<v-expansion-panel-content>
|
||||
<h4>When adding a torrent</h4>
|
||||
<v-divider />
|
||||
|
||||
<v-container
|
||||
class="px-0"
|
||||
fluid
|
||||
>
|
||||
<v-switch
|
||||
:input-value="preferences.create_subfolder_enabled"
|
||||
:label="`Create subfolder for torrents with multiple files`"
|
||||
@change="changeSettings('create_subfolder_enabled', !preferences.create_subfolder_enabled)"
|
||||
<v-container>
|
||||
<h4>{{ $t('preferences.adding_torrent') }}</h4>
|
||||
<v-divider />
|
||||
<v-container
|
||||
class="px-0"
|
||||
fluid
|
||||
>
|
||||
<v-switch
|
||||
:input-value="preferences.create_subfolder_enabled"
|
||||
:label="$t('preferences.create_subfolder_enabled')"
|
||||
@change="changeSettings('create_subfolder_enabled', !preferences.create_subfolder_enabled)"
|
||||
/>
|
||||
<v-switch
|
||||
:input-value="preferences.start_paused_enabled"
|
||||
:label="$t('preferences.start_paused_enabled')"
|
||||
@change="changeSettings('start_paused_enabled', !preferences.start_paused_enabled)"
|
||||
/>
|
||||
<v-switch
|
||||
:input-value="preferences.auto_delete_mode"
|
||||
:label="$t('preferences.auto_delete_mode')"
|
||||
@change="changeSettings('auto_delete_mode', !preferences.auto_delete_mode)"
|
||||
/>
|
||||
</v-container>
|
||||
<v-divider />
|
||||
<v-container
|
||||
class="px-0"
|
||||
fluid
|
||||
>
|
||||
<v-switch
|
||||
:input-value="preferences.preallocate_all"
|
||||
:label="$t('preferences.preallocate_all')"
|
||||
@change="changeSettings('preallocate_all', !preferences.preallocate_all)"
|
||||
/>
|
||||
<v-switch
|
||||
:input-value="preferences.incomplete_files_ext"
|
||||
:label="$t('preferences.incomplete_files_ext')"
|
||||
@change="changeSettings('incomplete_files_ext', !preferences.incomplete_files_ext)"
|
||||
/>
|
||||
</v-container>
|
||||
<h4>{{ $t('preferences.saving_management') }}</h4>
|
||||
<v-divider />
|
||||
<v-container
|
||||
class="px-0"
|
||||
fluid
|
||||
>
|
||||
<preference-row i18n-key="auto_tmm_enabled">
|
||||
<v-select
|
||||
:items="torrentMode"
|
||||
:value="preferences.auto_tmm_enabled ? torrentMode[0] : torrentMode[1]"
|
||||
@change="changeSettings('auto_tmm_enabled', $event == torrentMode[0])"
|
||||
/>
|
||||
</v-container>
|
||||
</v-expansion-panel-content>
|
||||
</v-expansion-panel>
|
||||
</preference-row>
|
||||
<preference-row i18n-key="torrent_changed_tmm_enabled">
|
||||
<v-select
|
||||
:items="torrentAction"
|
||||
:value="preferences.category_changed_tmm_enabled ? torrentAction[1] : torrentAction[0]"
|
||||
@change="changeSettings('torrent_changed_tmm_enabled', $event == torrentAction[1])"
|
||||
/>
|
||||
</preference-row>
|
||||
<preference-row i18n-key="save_path_changed_tmm_enabled">
|
||||
<v-select
|
||||
:items="torrentAction"
|
||||
:value="preferences.category_changed_tmm_enabled ? torrentAction[1] : torrentAction[0]"
|
||||
@change="changeSettings('save_path_changed_tmm_enabled', $event == torrentAction[1])"
|
||||
/>
|
||||
</preference-row>
|
||||
<preference-row i18n-key="category_changed_tmm_enabled">
|
||||
<v-select
|
||||
:items="torrentAction"
|
||||
:value="preferences.category_changed_tmm_enabled ? torrentAction[1] : torrentAction[0]"
|
||||
@change="changeSettings('category_changed_tmm_enabled', $event == torrentAction[1])"
|
||||
/>
|
||||
</preference-row>
|
||||
<preference-row i18n-key="save_path">
|
||||
<v-text-field
|
||||
:value="preferences.save_path"
|
||||
@change="changeSettings('save_path', $event)"
|
||||
lazy
|
||||
/>
|
||||
</preference-row>
|
||||
<preference-row i18n-key="temp_path">
|
||||
<template v-slot:header>
|
||||
<v-checkbox
|
||||
:value="preferences.temp_path_enabled"
|
||||
@change="changeSettings('temp_path_enabled', $event)"
|
||||
/>
|
||||
</template>
|
||||
<v-text-field
|
||||
:disabled="!preferences.temp_path_enabled"
|
||||
:value="preferences.temp_path"
|
||||
@change="changeSettings('temp_path', $event)"
|
||||
lazy
|
||||
/>
|
||||
</preference-row>
|
||||
<preference-row
|
||||
i18n-key="export_dir"
|
||||
can-be-enabled="true"
|
||||
>
|
||||
<v-text-field
|
||||
:value="preferences.export_dir"
|
||||
@change="changeSettings('export_dir', $event)"
|
||||
lazy
|
||||
clearable
|
||||
/>
|
||||
</preference-row>
|
||||
<preference-row
|
||||
i18n-key="export_dir_fin"
|
||||
can-be-enabled="true"
|
||||
>
|
||||
<v-text-field
|
||||
:value="preferences.export_dir_fin"
|
||||
@change="changeSettings('export_dir_fin', $event)"
|
||||
lazy
|
||||
clearable
|
||||
/>
|
||||
</preference-row>
|
||||
</v-container>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import { Preferences } from "@/types";
|
||||
import { Component } from "vue-property-decorator";
|
||||
import { mapActions, mapGetters } from "vuex";
|
||||
import Vue from 'vue'
|
||||
import {Preferences} from '@/types'
|
||||
import {Component} from 'vue-property-decorator'
|
||||
import {mapActions, mapGetters} from 'vuex'
|
||||
import PreferenceRow from './PreferenceRow'
|
||||
|
||||
@Component({
|
||||
components: {},
|
||||
components: {
|
||||
PreferenceRow,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
preferences: "allPreferences",
|
||||
preferences: 'allPreferences',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
@@ -39,12 +142,14 @@ import { mapActions, mapGetters } from "vuex";
|
||||
},
|
||||
})
|
||||
export default class DownloadSettings extends Vue {
|
||||
preferences!: Preferences;
|
||||
preferences!: Preferences
|
||||
torrentAction = [this.$t('preferences.switch_torrent_mode_to_manual'), this.$t('preferences.move_affected_torrent')]
|
||||
torrentMode = [this.$t('preferences.auto_mode'), this.$t('preferences.manual_mode')]
|
||||
|
||||
updatePreferencesRequest!: (_: any) => void;
|
||||
updatePreferencesRequest!: (_: any) => void
|
||||
|
||||
changeSettings(property: string, value: string | boolean) {
|
||||
this.updatePreferencesRequest({ [property]: value });
|
||||
this.updatePreferencesRequest({[property]: value})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -52,5 +157,14 @@ export default class DownloadSettings extends Vue {
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/assets/styles.scss";
|
||||
|
||||
h4 {
|
||||
margin-top: 8px;
|
||||
padding-left: 4px
|
||||
}
|
||||
|
||||
.v-input--switch {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
@include dialog-title;
|
||||
</style>
|
||||
|
||||
32
src/components/dialogs/settingsDialog/PreferenceRow.vue
Normal file
32
src/components/dialogs/settingsDialog/PreferenceRow.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<v-row
|
||||
align="center"
|
||||
dense
|
||||
>
|
||||
<v-col cols="3">
|
||||
<v-subheader v-text="$t('preferences.' + this.$props.i18nKey)" />
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<slot />
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="auto"
|
||||
v-if="$slots.header"
|
||||
>
|
||||
<slot name="header" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {Component} from 'vue-property-decorator'
|
||||
import Vue from 'vue'
|
||||
|
||||
@Component({
|
||||
props: {
|
||||
i18nKey: null,
|
||||
},
|
||||
})
|
||||
export default class PreferenceRow extends Vue {
|
||||
}
|
||||
</script>
|
||||
@@ -19,39 +19,84 @@
|
||||
</v-btn>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-expansion-panels
|
||||
v-model="panelsOpen"
|
||||
multiple
|
||||
>
|
||||
<download-settings />
|
||||
</v-expansion-panels>
|
||||
<v-tabs v-model="tab">
|
||||
<v-tab
|
||||
v-for="item of tabList"
|
||||
:key="item"
|
||||
>
|
||||
{{ $t('preferences.' + item) }}
|
||||
</v-tab>
|
||||
</v-tabs>
|
||||
<v-fade-transition>
|
||||
<v-alert
|
||||
dense
|
||||
text
|
||||
type="success"
|
||||
v-show="preferenceUpdated"
|
||||
>
|
||||
{{ $t('preferences.change_applied') }}
|
||||
</v-alert>
|
||||
</v-fade-transition>
|
||||
<v-tabs-items v-model="tab">
|
||||
<v-tab-item key="downloads">
|
||||
<download-settings />
|
||||
</v-tab-item>
|
||||
<v-tab-item key="speed">
|
||||
<speed-settings />
|
||||
</v-tab-item>
|
||||
<v-tab-item key="webui">
|
||||
<web-u-i-settings />
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
</v-card-text>
|
||||
<v-card-actions />
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from "vue";
|
||||
import { Component, Emit, Prop } from "vue-property-decorator";
|
||||
import DownloadSettings from "./DownloadSettings.vue";
|
||||
import Vue from 'vue'
|
||||
import {Component, Emit, Prop, Watch} from 'vue-property-decorator'
|
||||
import DownloadSettings from './DownloadSettings.vue'
|
||||
import SpeedSettings from './SpeedSettings.vue'
|
||||
import {mapGetters} from 'vuex'
|
||||
import {Preferences} from '@/types'
|
||||
import WebUISettings from '@/components/dialogs/settingsDialog/WebUISettings.vue'
|
||||
import {Config} from '@/store/config'
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
DownloadSettings,
|
||||
SpeedSettings,
|
||||
WebUISettings,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
config: ['config'],
|
||||
preferences: 'allPreferences',
|
||||
}),
|
||||
},
|
||||
methods: {},
|
||||
})
|
||||
export default class SearchDialog extends Vue {
|
||||
export default class SettingsDialog extends Vue {
|
||||
@Prop(Boolean)
|
||||
readonly value!: boolean;
|
||||
readonly value!: boolean
|
||||
preference!: Preferences
|
||||
config!: Config
|
||||
preferenceUpdated = false
|
||||
tabList = ['downloads', 'speed', 'webui', 'bittorrent', 'connection']
|
||||
tab = 'speed'
|
||||
|
||||
panelsOpen = [ 0 ];
|
||||
@Watch('preferences')
|
||||
@Watch('config')
|
||||
onPreferenceUpdate() {
|
||||
this.preferenceUpdated = true
|
||||
setTimeout(() => this.preferenceUpdated = false, 3000)
|
||||
}
|
||||
|
||||
@Emit("input")
|
||||
@Emit('input')
|
||||
closeDialog() {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
157
src/components/dialogs/settingsDialog/SpeedSettings.vue
Normal file
157
src/components/dialogs/settingsDialog/SpeedSettings.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-container
|
||||
fluid
|
||||
>
|
||||
<v-container>
|
||||
<v-row justify="center">
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<h4> {{ $t('preferences.global_rate_limits') }}</h4>
|
||||
<v-text-field
|
||||
@change="changeSettings('dl_limit', convertToBytes($event))"
|
||||
:label="$t('preferences.dl_limit')"
|
||||
:placeholder="convertToKB(preferences.dl_limit)"
|
||||
lazy
|
||||
/>
|
||||
<v-text-field
|
||||
@change="changeSettings('up_limit', convertToBytes($event))"
|
||||
:label="$t('preferences.up_limit')"
|
||||
:placeholder="convertToKB(preferences.up_limit)"
|
||||
lazy
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="12"
|
||||
md="4"
|
||||
>
|
||||
<h4> {{ $t('preferences.alternate_rate_limits') }}</h4>
|
||||
<v-text-field
|
||||
type="number"
|
||||
@change="changeSettings('alt_dl_limit', convertToBytes($event))"
|
||||
:label="$t('preferences.dl_limit')"
|
||||
:placeholder="convertToKB(preferences.alt_dl_limit)"
|
||||
lazy
|
||||
/>
|
||||
<v-text-field
|
||||
type="number"
|
||||
@change="changeSettings('alt_up_limit', convertToBytes($event))"
|
||||
:label="$t('preferences.up_limit')"
|
||||
:placeholder="convertToKB(preferences.alt_up_limit)"
|
||||
lazy
|
||||
/>
|
||||
<v-checkbox
|
||||
:label="$t('preferences.alternate_schedule_enable_time')"
|
||||
@change="changeSettings('scheduler_enabled', $event)"
|
||||
:input-value="preferences.scheduler_enabled"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
v-if="preferences.scheduler_enabled"
|
||||
class="justify-center"
|
||||
>
|
||||
<v-col
|
||||
cols="auto"
|
||||
>
|
||||
<v-time-picker
|
||||
:value="preferences.schedule_from_hour + ':' + preferences.schedule_from_min"
|
||||
color="green lighten-1"
|
||||
format="24hr"
|
||||
header-color="primary"
|
||||
@input="updateSchedulerFrom($event)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col
|
||||
cols="auto"
|
||||
>
|
||||
<v-time-picker
|
||||
:value="preferences.schedule_to_hour + ':' + preferences.schedule_to_min"
|
||||
color="green lighten-1"
|
||||
format="24hr"
|
||||
@input="updateSchedulerTo($event)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</v-container>
|
||||
<v-container
|
||||
class="px-0"
|
||||
fluid
|
||||
>
|
||||
<v-switch
|
||||
:input-value="preferences.limit_utp_rate"
|
||||
:label="$t('preferences.limit_utp_rate')"
|
||||
@change="changeSettings('limit_utp_rate', !preferences.limit_utp_rate)"
|
||||
/>
|
||||
<v-switch
|
||||
:input-value="preferences.limit_tcp_overhead"
|
||||
:label="$t('preferences.limit_tcp_overhead')"
|
||||
@change="changeSettings('limit_tcp_overhead', !preferences.limit_tcp_overhead)"
|
||||
/>
|
||||
<v-switch
|
||||
:input-value="preferences.limit_lan_peers"
|
||||
:label="$t('preferences.limit_lan_peers')"
|
||||
@change="changeSettings('limit_lan_peers', !preferences.limit_lan_peers)"
|
||||
/>
|
||||
</v-container>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import {Preferences} from '@/types'
|
||||
import {Component} from 'vue-property-decorator'
|
||||
import {mapActions, mapGetters} from 'vuex'
|
||||
|
||||
@Component({
|
||||
components: {},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
preferences: 'allPreferences',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
updatePreferencesRequest: 'updatePreferencesRequest',
|
||||
}),
|
||||
convertToKB(value: number): string {
|
||||
return (value / 1024).toString()
|
||||
},
|
||||
convertToBytes(value: number): number {
|
||||
return value * 1024
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class SpeedSettings extends Vue {
|
||||
preferences!: Preferences
|
||||
|
||||
updatePreferencesRequest!: (_: any) => void
|
||||
|
||||
changeSettings(property: string, value: string | boolean | number) {
|
||||
this.updatePreferencesRequest({[property]: value})
|
||||
}
|
||||
|
||||
updateSchedulerFrom(event: string) {
|
||||
const strings = event.split(':')
|
||||
this.updatePreferencesRequest({'schedule_from_hour': strings[0], 'schedule_from_min': strings[1]})
|
||||
}
|
||||
|
||||
updateSchedulerTo(event: string) {
|
||||
const strings = event.split(':')
|
||||
this.updatePreferencesRequest({'schedule_to_hour': strings[0], 'schedule_to_min': strings[1]})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "~@/assets/styles.scss";
|
||||
|
||||
.v-input--switch {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
@include dialog-title;
|
||||
</style>
|
||||
179
src/components/dialogs/settingsDialog/WebUISettings.vue
Normal file
179
src/components/dialogs/settingsDialog/WebUISettings.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<h4>{{ $t("preferences.webui_remote_control") }}}</h4>
|
||||
<v-divider />
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-col cols="2">
|
||||
<v-subheader>{{ $t("preferences.data_update_interval") }}</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<v-text-field
|
||||
:value="config.updateInterval"
|
||||
type="number"
|
||||
lazy
|
||||
@change="updateConfig({key: 'updateInterval', value: $event})"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-col cols="2">
|
||||
<v-subheader>{{ $t("preferences.ip_address") }}</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="4">
|
||||
<v-text-field
|
||||
:value="preferences.web_ui_address"
|
||||
@change="changeSettings('web_ui_address', $event)"
|
||||
lazy
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="1">
|
||||
<v-subheader>{{ $t("preferences.ip_port") }}</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="1">
|
||||
<v-text-field
|
||||
:value="preferences.web_ui_port"
|
||||
@change="changeSettings('web_ui_port', $event)"
|
||||
lazy
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col>
|
||||
<v-checkbox
|
||||
:label="$t('preferences.display_speed_in_title')"
|
||||
:input-value="config.displaySpeedInTitle"
|
||||
@change="updateTitleSpeedConfig($event)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<h4>{{ $t("preferences.authentication") }}</h4>
|
||||
<v-divider />
|
||||
<preference-row i18n-key="web_ui_username">
|
||||
<v-text-field
|
||||
:value="preferences.web_ui_username"
|
||||
@change="changeSettings('web_ui_username', $event)"
|
||||
lazy
|
||||
/>
|
||||
</preference-row>
|
||||
<preference-row i18n-key="web_ui_password">
|
||||
<v-text-field
|
||||
:value="preferences.web_ui_password"
|
||||
@change="changeSettings('web_ui_password', $event)"
|
||||
:placeholder="$t('preferences.new_password')"
|
||||
lazy
|
||||
/>
|
||||
</preference-row>
|
||||
<v-alert
|
||||
color="blue"
|
||||
type="error"
|
||||
dense
|
||||
>
|
||||
<v-row
|
||||
dense
|
||||
align="center"
|
||||
>
|
||||
<v-col cols="auto">
|
||||
<v-subheader>{{ $t("preferences.web_ui_max_auth_fail_count") }}</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="1">
|
||||
<v-text-field
|
||||
:value="preferences.web_ui_max_auth_fail_count"
|
||||
@change="changeSettings('web_ui_max_auth_fail_count', $event)"
|
||||
lazy
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="auto">
|
||||
<v-subheader>{{ $t("preferences.web_ui_ban_duration") }}</v-subheader>
|
||||
</v-col>
|
||||
<v-col cols="1">
|
||||
<v-text-field
|
||||
:value="preferences.web_ui_ban_duration"
|
||||
@change="changeSettings('web_ui_ban_duration', $event)"
|
||||
lazy
|
||||
/>
|
||||
</v-col>
|
||||
<v-col cols="1">
|
||||
{{ $t("preferences.web_ui_seconds") }}
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-alert>
|
||||
<v-row dense>
|
||||
<v-col>
|
||||
<v-checkbox
|
||||
:input-value="preferences.bypass_auth_subnet_whitelist_enabled"
|
||||
:label="$t('preferences.bypass_auth_subnet_whitelist')"
|
||||
@change="changeSettings('bypass_auth_subnet_whitelist_enabled', $event)"
|
||||
/>
|
||||
</v-col>
|
||||
<v-col>
|
||||
<v-checkbox
|
||||
:input-value="preferences.bypass_local_auth"
|
||||
:label="$t('preferences.bypass_local_auth')"
|
||||
@change="changeSettings('bypass_local_auth', $event)"
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-row dense>
|
||||
<v-col cols="4">
|
||||
<v-textarea
|
||||
:value="preferences.bypass_auth_subnet_whitelist"
|
||||
@change="changeSettings('bypass_auth_subnet_whitelist', $event)"
|
||||
lazy
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue'
|
||||
import {Preferences} from '@/types'
|
||||
import {Component} from 'vue-property-decorator'
|
||||
import {mapActions, mapGetters, mapMutations} from 'vuex'
|
||||
import {Config} from '@/store/config'
|
||||
import PreferenceRow from '@/components/dialogs/settingsDialog/PreferenceRow.vue'
|
||||
|
||||
@Component({
|
||||
components: {PreferenceRow},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
config: ['config'],
|
||||
preferences: 'allPreferences',
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
...mapMutations([
|
||||
'updateConfig',
|
||||
]),
|
||||
...mapActions({
|
||||
updatePreferencesRequest: 'updatePreferencesRequest',
|
||||
}),
|
||||
},
|
||||
})
|
||||
export default class WebUISettings extends Vue {
|
||||
preferences!: Preferences
|
||||
config!: Config
|
||||
|
||||
updatePreferencesRequest!: (_: any) => void
|
||||
|
||||
changeSettings(property: string, value: string | boolean) {
|
||||
this.updatePreferencesRequest({[property]: value})
|
||||
}
|
||||
|
||||
private updateTitleSpeedConfig(event: boolean) {
|
||||
this.updateConfig({
|
||||
key: 'displaySpeedInTitle',
|
||||
value: event,
|
||||
})
|
||||
if(!event) {
|
||||
document.title = 'qBittorrent Web UI'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -67,6 +67,67 @@ export default {
|
||||
plugin_manager: 'Plugin manager',
|
||||
update_plugins: 'Update plugins',
|
||||
|
||||
preferences: {
|
||||
change_applied: 'New preferences saved',
|
||||
downloads: 'Downloads',
|
||||
adding_torrent: 'When adding a torrent',
|
||||
create_subfolder_enabled: 'Create subfolder for torrents with multiple files',
|
||||
start_paused_enabled: 'Do not start the download automatically',
|
||||
auto_delete_mode: 'Delete .torrent files afterwards',
|
||||
preallocate_all: 'Pre-allocate disk space for all files',
|
||||
incomplete_files_ext: 'Append .!qB extension to incomplete files',
|
||||
saving_management: 'Saving Management',
|
||||
auto_tmm_enabled: 'Default Torrent Management Mode',
|
||||
torrent_changed_tmm_enabled: 'When Torrent Category changed',
|
||||
save_path_changed_tmm_enabled: 'When Default Save Path changed',
|
||||
category_changed_tmm_enabled: 'When Category Save Path changed',
|
||||
auto_mode: 'Automatic',
|
||||
manual_mode: 'Manual',
|
||||
switch_torrent_mode_to_manual: 'Switch affected torrent to manual mode',
|
||||
move_affected_torrent: 'Relocate affected torrents',
|
||||
save_path: 'Default Save Path',
|
||||
temp_path: 'Keep incomplete torrents in',
|
||||
export_dir: 'Copy .torrent files to',
|
||||
export_dir_fin: 'Copy .torrent files for finished downloads to',
|
||||
|
||||
speed: 'Speed',
|
||||
global_rate_limits: 'Global Rate Limits',
|
||||
alternate_rate_limits: 'Alternative Rate Limits',
|
||||
alternate_schedule_enable_time: 'Schedule the use of alternative rate limits',
|
||||
apply_speed_limit: 'Rate Limits Settings',
|
||||
dl_limit: 'Download (KiB/s)',
|
||||
up_limit: 'Upload (KiB/s)',
|
||||
zero_for_unlimited: '0 means unlimited',
|
||||
schedule_from: 'From',
|
||||
schedule_to: 'To',
|
||||
scheduler_days: 'When',
|
||||
limit_utp_rate: 'Apply rate limit to µTP protocol',
|
||||
limit_tcp_overhead: 'Apply rate limit to transport overhead',
|
||||
limit_lan_peers: 'Apply rate limit to peers on LAN',
|
||||
|
||||
connection: 'Connections',
|
||||
bittorrent: 'BitTorrent',
|
||||
|
||||
webui: 'Web UI',
|
||||
data_update_interval: 'Data Update Interval (ms)',
|
||||
webui_remote_control: 'Web User Interface (Remote control)',
|
||||
ip_address: 'IP address',
|
||||
ip_port: 'Port',
|
||||
enable_upnp: 'Use UPnP / NAT-PMP to forward the port from my router',
|
||||
authentication: 'Authentication',
|
||||
web_ui_username: 'Username',
|
||||
web_ui_password: 'Password',
|
||||
bypass_local_auth: 'Bypass authentication for clients on localhost',
|
||||
bypass_auth_subnet_whitelist: 'Bypass authentication for clients on localhost',
|
||||
web_ui_session_timeout: 'Session timeout',
|
||||
web_ui_max_auth_fail_count: 'Ban client after consecutive failures',
|
||||
web_ui_ban_duration: 'ban for',
|
||||
web_ui_seconds: 'seconds',
|
||||
new_password: 'Change current password...',
|
||||
|
||||
display_speed_in_title: 'Display download speed in page title',
|
||||
},
|
||||
|
||||
title: {
|
||||
_: 'Title',
|
||||
add_torrents: 'Add Torrents',
|
||||
@@ -157,7 +218,7 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
state: {
|
||||
category_state: {
|
||||
_: 'State',
|
||||
|
||||
downloading: 'Downloading',
|
||||
@@ -169,4 +230,26 @@ export default {
|
||||
inactive: 'Inactive',
|
||||
errored: 'Errored',
|
||||
},
|
||||
|
||||
torrent_state: {
|
||||
error: 'error',
|
||||
missingFiles: 'missingFiles',
|
||||
uploading: 'uploading',
|
||||
pausedUP: 'pausedUP',
|
||||
queuedUP: 'queuedUP',
|
||||
stalledUP: 'stalledUP',
|
||||
checkingUP: 'checkingUP',
|
||||
forcedUP: 'forcedUP',
|
||||
allocating: 'allocating',
|
||||
downloading: 'downloading',
|
||||
metaDL: 'metaDL',
|
||||
pausedDL: 'pausedDL',
|
||||
queuedDL: 'queuedDL',
|
||||
stalledDL: 'stalledDL',
|
||||
checkingDL: 'checkingDL',
|
||||
forceDL: 'forceDL',
|
||||
checkingResumeData: 'checkingResumeData',
|
||||
moving: 'moving',
|
||||
unknown: 'unknown',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ export default {
|
||||
added_on: '添加时间',
|
||||
|
||||
settings: '设置',
|
||||
|
||||
logs: '日志',
|
||||
light: '亮色',
|
||||
dark: '暗色',
|
||||
@@ -64,6 +65,67 @@ export default {
|
||||
action: '操作',
|
||||
search_engine: '搜索引擎',
|
||||
|
||||
preferences: {
|
||||
change_applied: '配置已保存',
|
||||
downloads: '下载',
|
||||
adding_torrent: '添加 torrent 时',
|
||||
create_subfolder_enabled: '为多个文件的 Torrent 创建子目录',
|
||||
start_paused_enabled: '不要自动开始下载',
|
||||
auto_delete_mode: '完成后删除 .torrent 文件',
|
||||
preallocate_all: '为所有文件预分配磁盘空间',
|
||||
incomplete_files_ext: '为不完整的文件添加扩展名 .!qB',
|
||||
saving_management: '保存管理',
|
||||
auto_tmm_enabled: '默认 Torrent 管理模式',
|
||||
torrent_changed_tmm_enabled: '当 Torrent 分类修改时',
|
||||
save_path_changed_tmm_enabled: '当默认保存路径修改时',
|
||||
category_changed_tmm_enabled: '当分类保存路径修改时',
|
||||
auto_mode: '自动',
|
||||
manual_mode: '手动',
|
||||
switch_torrent_mode_to_manual: '切换受影响的 Torrent 至手动模式',
|
||||
move_affected_torrent: '重新定位受影响的 Torrent',
|
||||
save_path: '默认保存路径',
|
||||
temp_path: '保存未完成的 torrent 到',
|
||||
export_dir: '复制 .torrent 文件到',
|
||||
export_dir_fin: '复制下载完成的 .torrent 文件到',
|
||||
|
||||
speed: '速度',
|
||||
global_rate_limits: '全局速度限制',
|
||||
alternate_rate_limits: '备用速度限制',
|
||||
alternate_schedule_enable_time: '设置备用速度限制的启用时间',
|
||||
apply_speed_limit: '设置速度限制',
|
||||
dl_limit: '下载 (KiB/s)',
|
||||
up_limit: '上传 (KiB/s)',
|
||||
zero_for_unlimited: '0 为无限制',
|
||||
schedule_from: '从',
|
||||
schedule_to: '到',
|
||||
scheduler_days: '时间',
|
||||
limit_utp_rate: '对 µTP 协议进行速度限制',
|
||||
limit_tcp_overhead: '对传送总开销进行速度限制',
|
||||
limit_lan_peers: '对本地网络用户进行速度限制',
|
||||
|
||||
connection: '连接',
|
||||
bittorrent: 'BitTorrent',
|
||||
|
||||
webui: 'Web UI',
|
||||
data_update_interval: '数据更新频率(ms)',
|
||||
webui_remote_control: 'Web 用户界面(远程控制)',
|
||||
ip_address: 'IP 地址',
|
||||
ip_port: '端口',
|
||||
enable_upnp: '使用我的路由器的 UPnP / NAT-PMP 功能来转发端口',
|
||||
authentication: '验证',
|
||||
web_ui_username: '用户名',
|
||||
web_ui_password: '密码',
|
||||
bypass_local_auth: '对本地主机上的客户端跳过身份验证',
|
||||
bypass_auth_subnet_whitelist: '对 IP 子网白名单中的客户端跳过身份验证',
|
||||
web_ui_session_timeout: '会话超时',
|
||||
web_ui_ban_duration: '禁止',
|
||||
web_ui_max_auth_fail_count: '连续失败后禁止客户端次数',
|
||||
web_ui_seconds: '秒',
|
||||
new_password: '更改当前的密码...',
|
||||
|
||||
display_speed_in_title: '在网页标题显示当前速度',
|
||||
},
|
||||
|
||||
title: {
|
||||
_: '标题',
|
||||
add_torrents: '添加种子',
|
||||
@@ -153,7 +215,7 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
state: {
|
||||
category_state: {
|
||||
_: '状态',
|
||||
|
||||
downloading: '下载',
|
||||
@@ -165,4 +227,26 @@ export default {
|
||||
inactive: '空闲',
|
||||
errored: '错误',
|
||||
},
|
||||
|
||||
torrent_state: {
|
||||
error: '错误',
|
||||
missingFiles: '文件丢失',
|
||||
uploading: '上传中',
|
||||
pausedUP: '完成',
|
||||
queuedUP: '排队上传',
|
||||
stalledUP: '上传',
|
||||
checkingUP: '上传校验',
|
||||
forcedUP: '强制上传',
|
||||
allocating: '分配空间',
|
||||
downloading: '下载中',
|
||||
metaDL: '获取信息',
|
||||
pausedDL: '暂停下载',
|
||||
queuedDL: '排队下载',
|
||||
stalledDL: '下载',
|
||||
checkingDL: '下载校验',
|
||||
forceDL: '强制下载',
|
||||
checkingResumeData: '快速校验',
|
||||
moving: '移动中',
|
||||
unknown: '未知',
|
||||
},
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ export interface Config {
|
||||
};
|
||||
locale: string | null;
|
||||
darkMode: string | null;
|
||||
displaySpeedInTitle: boolean | null;
|
||||
}
|
||||
|
||||
const defaultConfig = {
|
||||
@@ -33,6 +34,7 @@ const defaultConfig = {
|
||||
},
|
||||
locale: null,
|
||||
darkMode: null,
|
||||
displaySpeedInTitle: false,
|
||||
};
|
||||
|
||||
function saveConfig(obj: any) {
|
||||
|
||||
@@ -131,9 +131,10 @@ const store = new Vuex.Store<RootState>({
|
||||
actions: {
|
||||
async updatePreferencesRequest({ dispatch }, preferences) {
|
||||
try {
|
||||
const response = await api.setPreferences(preferences);
|
||||
|
||||
dispatch("updatePreferencesRequestSuccess", response.data);
|
||||
await api.setPreferences(preferences);
|
||||
//setPreference api return a empty response. Need to update preference by another request.
|
||||
const preferenceRes = await api.getAppPreferences();
|
||||
dispatch("updatePreferencesRequestSuccess", preferenceRes.data);
|
||||
} catch {
|
||||
dispatch("updatePreferencesRequestFailure");
|
||||
}
|
||||
|
||||
@@ -284,6 +284,9 @@ export interface Preferences {
|
||||
web_ui_port: number;
|
||||
web_ui_upnp: boolean;
|
||||
web_ui_username: string;
|
||||
web_ui_max_auth_fail_count: number;
|
||||
web_ui_ban_duration: number;
|
||||
web_ui_session_timeout: number;
|
||||
}
|
||||
|
||||
export interface SearchPlugin {
|
||||
|
||||
Reference in New Issue
Block a user