Add settings

Add more chinese localization
Add an option to display speed in page title
This commit is contained in:
emo64
2021-03-09 23:58:44 +08:00
committed by CzBiX
parent 809d5bab4b
commit 933413eaee
16 changed files with 796 additions and 79 deletions

View File

@@ -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,
};

View File

@@ -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);
}

View File

@@ -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: [

View File

@@ -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;
}
}

View File

@@ -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) {

View File

@@ -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() {

View File

@@ -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>

View 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>

View File

@@ -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>

View 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>

View 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>

View File

@@ -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',
},
}

View File

@@ -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: '未知',
},
}

View File

@@ -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) {

View File

@@ -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");
}

View File

@@ -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 {