perf: full Typescript migration + restructure (#612) @Larsluph

This commit is contained in:
Rémi Marseault 2023-01-24 17:10:59 +01:00 committed by GitHub
parent 6cabef1b56
commit e09e8a0300
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
105 changed files with 2091 additions and 805 deletions

View file

@ -19,6 +19,6 @@
<strong>We're sorry but Vuetorrent doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

44
package-lock.json generated
View file

@ -19,6 +19,7 @@
"vue": "^2.7.14",
"vue-apexcharts": "^1.6.2",
"vue-i18n": "^8",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.6.5",
"vue-toastification": "^1.7.11",
"vuedraggable": "^2.24.3",
@ -30,6 +31,8 @@
"@faker-js/faker": "^7.6.0",
"@mdi/js": "^7",
"@types/jsdom": "^20.0.1",
"@types/lodash": "^4.14.191",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5",
"@typescript-eslint/parser": "^5",
"@vitejs/plugin-vue2": "^2",
@ -46,8 +49,9 @@
"vite": "^3",
"vite-plugin-eslint": "^1",
"vite-plugin-pwa": "^0.13",
"vitest": "^0.25",
"vue-template-compiler": "^2"
"vitest": "^0.25.8",
"vue-template-compiler": "^2",
"vue-typed-mixins": "^0.2.0"
}
},
"node_modules/@ampproject/remapping": {
@ -2260,6 +2264,12 @@
"integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.191",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
"integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==",
"dev": true
},
"node_modules/@types/node": {
"version": "18.11.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
@ -2293,6 +2303,12 @@
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==",
"dev": true
},
"node_modules/@types/uuid": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.0.tgz",
"integrity": "sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==",
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.48.0.tgz",
@ -7244,6 +7260,15 @@
"apexcharts": "^3.26.0"
}
},
"node_modules/vue-class-component": {
"version": "7.2.6",
"resolved": "https://registry.npmjs.org/vue-class-component/-/vue-class-component-7.2.6.tgz",
"integrity": "sha512-+eaQXVrAm/LldalI272PpDe3+i4mPis0ORiMYxF6Ae4hyuCh15W8Idet7wPUEs4N4YptgFHGys4UrgNQOMyO6w==",
"peer": true,
"peerDependencies": {
"vue": "^2.0.0"
}
},
"node_modules/vue-eslint-parser": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.1.0.tgz",
@ -7295,6 +7320,15 @@
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-8.28.2.tgz",
"integrity": "sha512-C5GZjs1tYlAqjwymaaCPDjCyGo10ajUphiwA922jKt9n7KPpqR7oM1PCwYzhB/E7+nT3wfdG3oRre5raIT1rKA=="
},
"node_modules/vue-property-decorator": {
"version": "9.1.2",
"resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz",
"integrity": "sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ==",
"peerDependencies": {
"vue": "*",
"vue-class-component": "*"
}
},
"node_modules/vue-router": {
"version": "3.6.5",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.6.5.tgz",
@ -7318,6 +7352,12 @@
"vue": "^2.0.0"
}
},
"node_modules/vue-typed-mixins": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/vue-typed-mixins/-/vue-typed-mixins-0.2.0.tgz",
"integrity": "sha512-0OxuinandPWv3nm5k/reYkuKtX3jjPZ40Sy9roJz0ih8PUzmI7zSRiXFEJ62LsyRegw9Tqy+qMkajk7ipKP8Vg==",
"dev": true
},
"node_modules/vuedraggable": {
"version": "2.24.3",
"resolved": "https://registry.npmjs.org/vuedraggable/-/vuedraggable-2.24.3.tgz",

View file

@ -22,6 +22,7 @@
"vue": "^2.7.14",
"vue-apexcharts": "^1.6.2",
"vue-i18n": "^8",
"vue-property-decorator": "^9.1.2",
"vue-router": "^3.6.5",
"vue-toastification": "^1.7.11",
"vuedraggable": "^2.24.3",
@ -33,6 +34,8 @@
"@faker-js/faker": "^7.6.0",
"@mdi/js": "^7",
"@types/jsdom": "^20.0.1",
"@types/lodash": "^4.14.191",
"@types/uuid": "^9.0.0",
"@typescript-eslint/eslint-plugin": "^5",
"@typescript-eslint/parser": "^5",
"@vitejs/plugin-vue2": "^2",
@ -49,8 +52,9 @@
"vite": "^3",
"vite-plugin-eslint": "^1",
"vite-plugin-pwa": "^0.13",
"vitest": "^0.25",
"vue-template-compiler": "^2"
"vitest": "^0.25.8",
"vue-template-compiler": "^2",
"vue-typed-mixins": "^0.2.0"
},
"browserslist": [
"> 1%",

View file

@ -1,24 +1,24 @@
import { formatBytes } from '@/helpers'
import store from '../store'
import store from '@/store'
export class DocumentTitle {
static setDefault() {
private static setDefault() {
this.set('qBittorrent')
}
static setGlobalSpeed() {
private static setGlobalSpeed() {
const status = store.getters.getStatus()
this.set(`[D: ${formatBytes(status.dlspeed)}/s, U: ${formatBytes(status.upspeed)}/s] VueTorrent`)
}
static setFirstTorrentStatus() {
private static setFirstTorrentStatus() {
const torrents = store.getters.getTorrents()
if (!torrents && !torrents.length) return
const torrent = torrents[0]
this.set(`[D: ${formatBytes(torrent.dlspeed)}/s, U: ${formatBytes(torrent.upspeed)}/s] ${torrent.progress}%`)
}
static update() {
public static update() {
const mode = store.getters.getWebuiSettings().title
switch (mode) {
case 'Default':
@ -32,7 +32,7 @@ export class DocumentTitle {
}
}
static set(title) {
private static set(title: string) {
document.title = title
}
}

View file

@ -1,7 +1,7 @@
import store from '../store'
import store from '@/store'
export class Graph {
static update() {
public static shiftValues() {
const state = store.state
state.download_data.shift()
state.download_data.push(state.status.dlspeedRaw || 0)

View file

@ -1,10 +0,0 @@
import store from '../store'
import Status from '@/models/Status'
export class ServerStatus {
static update(response) {
if (response.server_state) {
store.state.status = new Status(response.server_state)
}
}
}

View file

@ -0,0 +1,12 @@
import store from '@/store'
import {Status} from '@/models'
import type {ServerState} from "@/types/qbit/models";
import type {Optional} from "@/global";
export class ServerStatus {
static update(server_state: Optional<ServerState>) {
if (server_state) {
store.state.status = new Status(server_state)
}
}
}

View file

@ -1,22 +0,0 @@
import store from '../store'
import { ArrayHelper } from '@/helpers'
export class Tags {
static update(response) {
if (response?.full_update === true) {
store.state.tags = response.tags
return
}
if (response.tags_removed) {
store.state.tags = ArrayHelper.remove(store.state.tags, response.tags_removed)
return
}
if (response.tags) {
store.state.tags = ArrayHelper.concat(store.state.tags, response.tags)
}
}
}

23
src/actions/Tags.ts Normal file
View file

@ -0,0 +1,23 @@
import store from '@/store'
import {ArrayHelper} from '@/helpers'
import type {MainDataResponse} from "@/types/qbit/responses";
export class Tags {
static update(response: MainDataResponse) {
if (response?.fullUpdate === true) {
store.state.tags = response.tags || []
return
}
if (response.tags_removed) {
store.state.tags = ArrayHelper.remove(store.state.tags, ...response.tags_removed)
return
}
if (response.tags) {
store.state.tags = ArrayHelper.concat(store.state.tags, response.tags)
}
}
}

View file

@ -1,11 +1,12 @@
import store from '../store'
import store from '@/store'
import { Hostname } from '@/helpers'
import Torrent from '@/models/Torrent'
import {Torrent as VtTorrent} from '@/models'
import type {Torrent as QbitTorrent} from '@/types/qbit/models'
import { isProduction } from '@/utils'
import { generateMultiple } from '@/utils/faker'
export class Torrents {
static update(data) {
static update(data: QbitTorrent[]) {
if (store.state.webuiSettings.showTrackerFilter) {
// don't calculate trackers when disabled
@ -15,7 +16,7 @@ export class Torrents {
}
// update torrents
store.state.torrents = data.map(t => new Torrent(t, store.state.webuiSettings.dateFormat))
store.state.torrents = data.map(t => new VtTorrent(t, store.state.webuiSettings.dateFormat))
// load fake torrents if enabled
if (isProduction()) return

View file

@ -1,8 +1,9 @@
import store from '../store'
import store from '@/store'
import { Hostname } from '@/helpers'
import type {Torrent} from "@/types/qbit/models";
export class Trackers {
static update(data) {
static update(data: Torrent[]) {
if (store.state.webuiSettings.showTrackerFilter) {
store.state.trackers = data
.map(t => t.tracker)

View file

@ -70,7 +70,7 @@ export default {
this.dialog = false
},
edit() {
qbit.editfeed(this.feed)
qbit.editFeed(this.feed)
Vue.$toast.success(this.$t('toast.feedSaved'))
this.cancel()
}

View file

@ -110,7 +110,7 @@ export default {
async addTrackers() {
if (!this.newTrackers.length) return (this.trackerDialog = false)
qbit.addTorrenTrackers(this.hash, this.newTrackers)
qbit.addTorrentTrackers(this.hash, this.newTrackers)
this.newTrackers = ''
await this.getTorrentTrackers()
this.trackerDialog = false

View file

@ -0,0 +1,67 @@
export enum BitTorrentProtocol {
TCP_uTP,
TCP,
uTP
}
export enum DynDnsService {
USE_DYNDNS,
USE_NOIP
}
export enum Encryption {
PREFER_ENCRYPTION,
FORCE_ON,
FORCE_OFF
}
export enum MaxRatioAction {
PAUSE_TORRENT,
REMOVE_TORRENT,
REMOVE_TORRENT_AND_FILES,
ENABLE_SUPERSEEDING
}
export enum ProxyType {
DISABLED = -1,
HTTP_WITHOUT_AUTH = 1,
SOCKS5_WITHOUT_AUTH,
HTTP_WITH_AUTH,
SOCKS5_WITH_AUTH,
SOCKS4_WITHOUT_AUTH
}
enum ScanDirsEnum {
MONITORED_FOLDER,
DEFAULT_SAVE_PATH
}
export type ScanDirs = ScanDirsEnum | string
export enum SchedulerDays {
EVERY_DAY,
EVERY_WEEKDAY,
EVERY_WEEKEND,
EVERY_MONDAY,
EVERY_TUESDAY,
EVERY_WEDNESDAY,
EVERY_THURSDAY,
EVERY_FRIDAY,
EVERY_SATURDAY,
EVERY_SUNDAY
}
export enum UploadChokingAlgorithm {
ROUND_ROBIN,
FASTEST_UPLOAD,
ANTI_LEECH
}
export enum UploadSlotsBehavior {
FIXED_SLOTS,
UPLOAD_RATE_BASED
}
export enum UtpTcpMixedMode {
PREFER_TCP,
PEER_PROPORTIONAL
}

View file

@ -0,0 +1,5 @@
export enum ConnectionStatus {
CONNECTED = 'connected',
FIREWALLED = 'firewalled',
DISCONNECTED = 'disconnected'
}

View file

@ -0,0 +1,6 @@
export enum LogType {
NORMAL = 1,
INFO = 2,
WARNING = 4,
CRITICAL = 8
}

View file

@ -0,0 +1,6 @@
export enum Priority {
DO_NOT_DOWNLOAD = 0,
NORMAL = 1,
HIGH = 6,
MAXIMAL = 7
}

View file

@ -0,0 +1,40 @@
export enum TorrentState {
/** Some error occurred, applies to paused torrents */
ERROR = 'error',
/** Torrent data files is missing */
MISSING_FILES = 'missingFiles',
/** Torrent is being seeded and data is being transferred */
UPLOADING = 'uploading',
/** Torrent is paused and has finished downloading */
PAUSED_UP = 'pausedUP',
/** Queuing is enabled and torrent is queued for upload */
QUEUED_UP = 'queuedUP',
/** Torrent is being seeded, but no connection were made */
STALLED_UP = 'stalledUP',
/** Torrent has finished downloading and is being checked */
CHECKING_UP = 'checkingUP',
/** Torrent is forced to uploading and ignore queue limit */
FORCED_UP = 'forcedUP',
/** Torrent is allocating disk space for download */
ALLOCATING = 'allocating',
/** Torrent is being downloaded and data is being transferred */
DOWNLOADING = 'downloading',
/** Torrent has just started downloading and is fetching metadata */
META_DL = 'metaDL',
/** Torrent is paused and has NOT finished downloading */
PAUSED_DL = 'pausedDL',
/** Queuing is enabled and torrent is queued for download */
QUEUED_DL = 'queuedDL',
/** Torrent is being downloaded, but no connection were made */
STALLED_DL = 'stalledDL',
/** Same as checkingUP, but torrent has NOT finished downloading */
CHECKING_DL = 'checkingDL',
/** Torrent is forced to downloading to ignore queue limit */
FORCED_DL = 'forcedDL',
/** Checking resume data on qBt startup */
CHECKING_RESUME_DATA = 'checkingResumeData',
/** Torrent is moving to another location */
MOVING = 'moving',
/** Unknown status */
UNKNOWN = 'unknown'
}

View file

@ -0,0 +1,12 @@
export enum TrackerStatus {
/** Tracker is disabled (used for DHT, PeX, and LSD) */
DISABLED,
/** Tracker has not been contacted yet */
NOT_YET_CONTACTED,
/** Tracker has been contacted and is working */
WORKING,
/** Tracker is updating */
UPDATING,
/** Tracker has been contacted, but it is not working (or doesn't send proper replies) */
NOT_WORKING
}

8
src/enums/qbit/index.ts Normal file
View file

@ -0,0 +1,8 @@
import * as AppPreferences from './AppPreferences'
import { LogType } from './LogType'
import { Priority } from './Priority'
import { TrackerStatus } from './TrackerStatus'
import { ConnectionStatus } from './ConnectionStatus'
import {TorrentState} from './TorrentState'
export { AppPreferences, ConnectionStatus, LogType, Priority, TrackerStatus, TorrentState }

View file

@ -0,0 +1,5 @@
export enum TitleOptions {
DEFAULT = 'Default',
GLOBAL_SPEED = 'Global Speed',
FIRST_TORRENT_STATUS = 'First Torrent Status'
}

View file

@ -0,0 +1,12 @@
export enum TorrentState {
DOWNLOADING = 'Downloading',
SEEDING = 'Seeding',
PAUSED = 'Paused',
STALLED = 'Stalled',
METADATA = 'Metadata',
DONE = 'Done',
QUEUED = 'Queued',
CHECKING = 'Checking',
MOVING = 'Moving',
FAIL = 'Fail'
}

View file

@ -0,0 +1,4 @@
import {TorrentState} from "./TorrentState"
import {TitleOptions} from "./TitleOptions"
export {TorrentState, TitleOptions}

View file

@ -1,7 +1,6 @@
import Vue from 'vue'
/* eslint-disable no-param-reassign */
export function toPrecision(value, precision) {
export function toPrecision(value: number, precision: number): string {
if (value >= 10 ** precision) {
return value.toString()
}
@ -12,7 +11,7 @@ export function toPrecision(value, precision) {
return value.toFixed(precision - 1)
}
export function formatSize(value) {
export function formatSize(value: number): string {
const units = 'KMGTP'
let index = -1
@ -33,7 +32,7 @@ export function formatSize(value) {
Vue.filter('formatSize', formatSize)
Vue.filter('size', formatSize)
export function formatProgress(progress) {
export function formatProgress(progress: number): string {
progress *= 100
return `${toPrecision(progress, 3)}%`
@ -41,17 +40,7 @@ export function formatProgress(progress) {
Vue.filter('progress', formatProgress)
export function parseDate(str) {
if (!str) {
return null
}
return Date.parse(str) / 1000
}
Vue.filter('parseDate', parseDate)
export function formatNetworkSpeed(speed) {
export function formatNetworkSpeed(speed: number): string|null {
if (speed === 0) {
return null
}
@ -61,7 +50,7 @@ export function formatNetworkSpeed(speed) {
Vue.filter('networkSpeed', formatNetworkSpeed)
export function networkSize(size) {
export function networkSize(size: number) {
if (size === 0) {
return null
}
@ -71,42 +60,41 @@ export function networkSize(size) {
Vue.filter('networkSize', networkSize)
export function getDataUnit(a, b) {
if (a === -1) return null
if (!a) return 'B'
export function getDataUnit(data: number) {
if (data === -1) return null
if (!data) return 'B'
const c = 1024
const e = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const f = Math.floor(Math.log(a) / Math.log(c))
const f = Math.floor(Math.log(data) / Math.log(c))
return `${e[f]}`
}
Vue.filter('getDataUnit', getDataUnit)
export function getDataValue(a, b) {
if (a === -1) return 'None'
if (!a) return '0'
export function getDataValue(data: number, precision: number = 2) {
if (data === -1) return 'None'
if (!data) return '0'
const c = 1024
const d = b || 2
const f = Math.floor(Math.log(a) / Math.log(c))
const f = Math.floor(Math.log(data) / Math.log(c))
return `${parseFloat((a / Math.pow(c, f)).toFixed(d))}`
return `${parseFloat((data / Math.pow(c, f)).toFixed(precision))}`
}
Vue.filter('getDataValue', getDataValue)
export function titleCase(str) {
if (!str) return
export function titleCase(str: string): string {
if (str.length == 0) return str
return str
.split(' ')
.map(w => w[0].toUpperCase() + w.substr(1).toLowerCase())
.map(w => w[0] && w[0].toUpperCase() + w.substring(1).toLowerCase())
.join(' ')
}
Vue.filter('titleCase', titleCase)
export function limitToValue(value) {
export function limitToValue(value: number): string {
if (value === -2) {
return 'global'
}
@ -114,7 +102,7 @@ export function limitToValue(value) {
return 'unlimited'
}
return value
return value.toString()
}
Vue.filter('limitToValue', limitToValue)

4
src/global.d.ts vendored Normal file
View file

@ -0,0 +1,4 @@
export type Optional<T> = T | null | undefined
export type $TSFixMe = any
export type $TSFixMeFunction = (...args: any[]) => any

View file

@ -195,4 +195,4 @@ export class Hostname {
return ''
}
}
}
}

View file

@ -14,20 +14,20 @@ import vi from './vi.json'
import zh_hans from './zh-hans.json'
import zh_hant from './zh-hant.json'
export const messages = {
[Locales.EN]: en,
[Locales.ES]: es,
[Locales.FR]: fr,
[Locales.ID]: id,
[Locales.IT]: it,
[Locales.JA]: ja,
[Locales.NL]: nl,
[Locales.PT_BR]: pt_br,
[Locales.RU]: ru,
[Locales.UK]: uk,
[Locales.VI]: vi,
[Locales.ZH_HANS]: zh_hans,
[Locales.ZH_HANT]: zh_hant
export const messages: Record<Locales, any> = {
[Locales.EN]: en,
[Locales.ES]: es,
[Locales.FR]: fr,
[Locales.ID]: id,
[Locales.IT]: it,
[Locales.JA]: ja,
[Locales.NL]: nl,
[Locales.PT_BR]: pt_br,
[Locales.RU]: ru,
[Locales.UK]: uk,
[Locales.VI]: vi,
[Locales.ZH_HANS]: zh_hans,
[Locales.ZH_HANT]: zh_hant
}
export const defaultLocale = Locales.EN

View file

@ -16,11 +16,7 @@ Vue.use(toast, config)
// register modals
const components = import.meta.glob('./components/Modals/**/*.vue')
Object.entries(components).forEach(([path, definition]) => {
const componentName = path
.split('/')
.pop()
.replace(/\.\w+$/, '')
const componentName = (path.split('/').pop() as string).replace(/\.\w+$/, '')
Vue.component(componentName, definition)
})

View file

@ -1,17 +0,0 @@
export default {
computed: {
phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
},
dialogWidth() {
return this.phoneLayout ? '100%' : '80%'
}
},
watch: {
dialog(visible) {
if (!visible) {
this.tab = null
}
}
}
}

View file

@ -0,0 +1,18 @@
import {Component, Vue, Watch} from "vue-property-decorator";
@Component
export default class FullScreenModal extends Vue {
tab!: string|null
get phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
}
get dialogWidth() {
return this.phoneLayout ? '100%' : '80%'
}
@Watch('dialog')
onDialogChanged(visible: boolean) {
if (!visible) this.tab = null
}
}

View file

@ -1,24 +0,0 @@
import { v4 as uuidv4 } from 'uuid'
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['getTheme']),
theme() {
return this.getTheme()
},
isMobile() {
return this.$vuetify.breakpoint.smAndDown
}
},
methods: {
createModal(name, props) {
const component = {
component: name,
props,
guid: uuidv4()
}
this.$store.commit('ADD_MODAL', component)
}
}
}

27
src/mixins/General.ts Normal file
View file

@ -0,0 +1,27 @@
import { v4 as uuidv4 } from 'uuid'
import { mapGetters } from 'vuex'
import {Component, Vue} from "vue-property-decorator";
@Component({
computed: mapGetters(['getTheme'])
})
export default class General extends Vue {
getTheme!: () => string
get theme() {
return this.getTheme()
}
get isMobile() {
return this.$vuetify.breakpoint.smAndDown
}
createModal(name: string, props: any) {
const component = {
component: name,
props,
guid: uuidv4()
}
this.$store.commit('ADD_MODAL', component)
}
}

View file

@ -1,36 +0,0 @@
import { mapGetters } from 'vuex'
export default {
props: ['guid'],
data() {
return {
hndlDialog: true
}
},
computed: {
...mapGetters(['getModalState']),
dialog: {
get() {
return this.hndlDialog
},
set(val) {
this.hndlDialog = val
if (!val) this.deleteModal()
}
}
},
methods: {
deleteModal() {
//this.hndlDialog = false
setTimeout(
function () {
this.$store.commit('DELETE_MODAL', this.guid)
}.bind(this),
300
)
}
},
beforeDestroy() {
this.deleteModal()
}
}

35
src/mixins/Modal.ts Normal file
View file

@ -0,0 +1,35 @@
import { mapGetters } from 'vuex'
import {Component, Prop, Vue} from "vue-property-decorator";
@Component({
computed: mapGetters(['getModalState'])
})
export default class Modal extends Vue {
//data
hndlDialog = true
//props
@Prop() guid!: string
// mapGetters
getModalState!: () => any
// computed
get dialog() {
return this.hndlDialog
}
set dialog(val) {
this.hndlDialog = val
if (!val) this.deleteModal()
}
// methods
deleteModal() {
//this.hndlDialog = false
setTimeout(() => { this.$store.commit('DELETE_MODAL', this.guid) }, 300)
}
beforeDestroy() {
this.deleteModal()
}
}

View file

@ -1,28 +0,0 @@
import { mapGetters } from 'vuex'
import qbit from '@/services/qbit'
import Vue from 'vue'
export default {
computed: {
...mapGetters(['getSettings']),
settings() {
return this.getSettings()
}
},
methods: {
async saveSettings() {
qbit.setPreferences(this.getSettings()).then(() => {
Vue.$toast.success(this.$t('toast.settingsSaved'))
})
await this.$store.dispatch('FETCH_SETTINGS')
await this.$store.commit('SET_LANGUAGE')
this.close()
if (!this.settings.alternative_webui_enabled) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (const registration of registrations) {
registration.unregister()
}
})
}
}
}
}

33
src/mixins/SettingsTab.ts Normal file
View file

@ -0,0 +1,33 @@
import { mapGetters } from 'vuex'
import qbit from '@/services/qbit'
import {Component, Vue} from "vue-property-decorator";
import type {AppPreferences} from "@/types/qbit/models";
@Component({
computed: mapGetters(['getSettings'])
})
export default class SettingsTab extends Vue {
getSettings!: () => AppPreferences
get settings() {
return this.getSettings()
}
close!: () => void
async saveSettings() {
qbit.setPreferences(this.getSettings()).then(() => {
Vue.$toast.success(this.$t('toast.settingsSaved').toString())
})
await this.$store.dispatch('FETCH_SETTINGS')
await this.$store.commit('SET_LANGUAGE')
this.close()
if (!this.settings.alternative_webui_enabled) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (const registration of registrations) {
registration.unregister()
}
})
}
}
}

View file

@ -1,13 +0,0 @@
export default {
props: {
hash: String,
isActive: Boolean
},
watch: {
isActive(active) {
if (active) {
this.activeMethod()
}
}
}
}

14
src/mixins/Tab.ts Normal file
View file

@ -0,0 +1,14 @@
import {Component, Prop, Vue, Watch} from "vue-property-decorator";
@Component
export default class Tab extends Vue {
@Prop() hash!: string
@Prop() isActive!: boolean
activeMethod!: () => void
@Watch('isActive')
isActiveChanged(active: boolean) {
if (active) this.activeMethod()
}
}

View file

@ -1,16 +0,0 @@
import { mapGetters } from 'vuex'
export default {
computed: {
...mapGetters(['getTheme']),
phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
},
theme() {
return this.getTheme()
},
state() {
return this.torrent.state.toLowerCase()
}
}
}

View file

@ -0,0 +1,22 @@
import { mapGetters } from 'vuex'
import {Component, Prop, Vue} from "vue-property-decorator";
import type {Torrent} from "@/models";
@Component({
computed: mapGetters(['getTheme'])
})
export default class TorrentDashboardItem extends Vue {
getTheme!: () => string
@Prop() torrent!: Torrent
get phoneLayout() {
return this.$vuetify.breakpoint.xsOnly
}
get theme() {
return this.getTheme()
}
get state() {
return this.torrent.state.toLowerCase()
}
}

View file

@ -1,19 +0,0 @@
export default {
methods: {
isAlreadySelected(hash) {
return this.$store.getters.containsTorrent(hash)
},
selectTorrent(hash) {
if (!this.$store.state.selectMode) this.$store.state.selectMode = true
if (this.isAlreadySelected(hash)) {
this.$store.commit('SET_SELECTED', { type: 'remove', hash })
} else {
this.$store.commit('SET_SELECTED', { type: 'add', hash })
}
},
selectUntil(hash, index) {
if (!this.$store.state.selectMode) return
this.$store.commit('SET_SELECTED', { type: 'until', hash, index })
}
}
}

View file

@ -0,0 +1,20 @@
import {Component, Vue} from "vue-property-decorator";
@Component
export default class TorrentSelect extends Vue {
isAlreadySelected(hash: string) {
return this.$store.getters.containsTorrent(hash)
}
selectTorrent(hash: string) {
if (!this.$store.state.selectMode) this.$store.state.selectMode = true
if (this.isAlreadySelected(hash)) {
this.$store.commit('SET_SELECTED', { type: 'remove', hash })
} else {
this.$store.commit('SET_SELECTED', { type: 'add', hash })
}
}
selectUntil(hash: string, index: number) {
if (!this.$store.state.selectMode) return
this.$store.commit('SET_SELECTED', { type: 'until', hash, index })
}
}

View file

@ -1,28 +0,0 @@
import store from '../store'
export default class Status {
constructor({ connection_status, dl_info_data, up_info_data, alltime_dl, alltime_ul, dl_info_speed, up_info_speed, free_space_on_disk, use_alt_speed_limits }) {
const previous = store.state.status
this.status = connection_status || previous.status
this.sessionDownloaded = dl_info_data || previous.sessionDownloaded
this.sessionUploaded = up_info_data || previous.sessionUploaded
this.alltimeDownloaded = alltime_dl || previous.alltimeDownloaded
this.alltimeUploaded = alltime_ul || previous.alltimeUploaded
this.dlspeed = dl_info_speed || 0
this.upspeed = up_info_speed || 0
this.freeDiskSpace = free_space_on_disk || previous.freeDiskSpace
this.altSpeed = use_alt_speed_limits !== undefined ? use_alt_speed_limits : previous.altSpeed
this.dlspeedRaw = this.formatSpeed(dl_info_speed) || 0
this.upspeedRaw = this.formatSpeed(up_info_speed) || 0
Object.freeze(this)
}
formatSpeed(value) {
if (!value) {
return 0
}
return Math.round(value)
}
}

47
src/models/Status.ts Normal file
View file

@ -0,0 +1,47 @@
import store from '@/store'
import type { ServerState } from '@/types/qbit/models'
import { ConnectionStatus } from '@/enums/qbit'
export default class Status {
alltimeDownloaded: number = 0
alltimeUploaded: number = 0
altSpeed: boolean = false
dlspeed: number = 0
dlspeedRaw: number = 0
freeDiskSpace: number = 0
sessionDownloaded: number = 0
sessionUploaded: number = 0
status: ConnectionStatus = ConnectionStatus.DISCONNECTED
upspeed: number = 0
upspeedRaw: number = 0
constructor(in_state?: ServerState) {
if (!in_state) {
return
}
const previous = store.state.status
this.alltimeDownloaded = in_state.alltime_dl || previous.alltimeDownloaded
this.alltimeUploaded = in_state.alltime_ul || previous.alltimeUploaded
this.altSpeed = in_state.use_alt_speed_limits !== undefined ? in_state.use_alt_speed_limits : previous.altSpeed
this.dlspeed = in_state.dl_info_speed || 0
this.dlspeedRaw = this.formatSpeed(in_state.dl_info_speed) || 0
this.freeDiskSpace = in_state.free_space_on_disk || previous.freeDiskSpace
this.sessionDownloaded = in_state.dl_info_data || previous.sessionDownloaded
this.sessionUploaded = in_state.up_info_data || previous.sessionUploaded
this.status = in_state.connection_status || previous.status
this.upspeed = in_state.up_info_speed || 0
this.upspeedRaw = this.formatSpeed(in_state.up_info_speed) || 0
Object.freeze(this)
}
formatSpeed(value: number): number {
if (!value) {
return 0
}
return Math.round(value)
}
}

View file

@ -1,6 +1,9 @@
import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime'
import {TorrentState as QbitTorrentState} from "@/enums/qbit";
import {TorrentState as VtTorrentState} from "@/enums/vuetorrent";
import type {Torrent as QbitTorrent} from '@/types/qbit/models'
dayjs.extend(duration)
dayjs.extend(relativeTime)
@ -9,7 +12,45 @@ const durationFormat = 'D[d] H[h] m[m] s[s]'
export default class Torrent {
static computedValues = ['globalSpeed', 'globalVolume']
constructor(data, format = 'DD/MM/YYYY, HH:mm:ss') {
name: string
size: number
added_on: string
completed_on: string
dlspeed: number
dloaded: number
upspeed: number
uploaded: number
uploaded_session: number
eta: string
num_leechs: number
num_seeds: number
state: VtTorrentState
hash: string
available_seeds: number
available_peers: number
savePath: string
progress: number
ratio: number
tags: string[] | null
category: string
tracker: string
f_l_piece_prio: boolean
seq_dl: boolean
auto_tmm: boolean
dl_limit: number
up_limit: number
ratio_limit: number
ratio_time_limit: number
availability: number
forced: boolean
magnet: string
time_active: string
seeding_time: string | null
last_activity: string
globalSpeed: number
globalVolume: number
constructor(data: QbitTorrent, format = 'DD/MM/YYYY, HH:mm:ss') {
this.name = data.name
this.size = data.size
this.added_on = dayjs(data.added_on * 1000).format(format)
@ -22,7 +63,6 @@ export default class Torrent {
this.eta = this.formatEta(data.eta)
this.num_leechs = data.num_leechs
this.num_seeds = data.num_seeds
this.path = data.path === undefined ? '/downloads' : data.path
this.state = this.formatState(data.state)
this.hash = data.hash
this.available_seeds = data.num_complete
@ -54,42 +94,41 @@ export default class Torrent {
Object.freeze(this)
}
formatState(state) {
formatState(state: QbitTorrentState): VtTorrentState {
switch (state) {
case 'forcedDL':
case 'downloading':
return 'Downloading'
case 'metaDL':
return 'Metadata'
case 'forcedUP':
case 'uploading':
case 'stalledUP':
return 'Seeding'
case 'pausedDL':
return 'Paused'
case 'pausedUP':
return 'Done'
case 'queuedDL':
case 'queuedUP':
return 'Queued'
case 'allocating':
case 'checkingDL':
case 'checkingUP':
case 'checkingResumeData':
return 'Checking'
case 'moving':
return 'Moving'
case 'unknown':
case 'missingFiles':
return 'Fail'
case 'stalledDL':
return 'Stalled'
case QbitTorrentState.FORCED_DL:
case QbitTorrentState.DOWNLOADING:
return VtTorrentState.DOWNLOADING
case QbitTorrentState.META_DL:
return VtTorrentState.METADATA
case QbitTorrentState.FORCED_UP:
case QbitTorrentState.UPLOADING:
case QbitTorrentState.STALLED_UP:
return VtTorrentState.SEEDING
case QbitTorrentState.PAUSED_DL:
return VtTorrentState.PAUSED
case QbitTorrentState.PAUSED_UP:
return VtTorrentState.DONE
case QbitTorrentState.QUEUED_DL:
case QbitTorrentState.QUEUED_UP:
return VtTorrentState.QUEUED
case QbitTorrentState.ALLOCATING:
case QbitTorrentState.CHECKING_DL:
case QbitTorrentState.CHECKING_UP:
case QbitTorrentState.CHECKING_RESUME_DATA:
return VtTorrentState.CHECKING
case QbitTorrentState.MOVING:
return VtTorrentState.MOVING
case QbitTorrentState.UNKNOWN:
case QbitTorrentState.STALLED_DL:
return VtTorrentState.STALLED
case QbitTorrentState.MISSING_FILES:
default:
return 'Fail'
return VtTorrentState.FAIL
}
}
formatEta(value) {
formatEta(value: number): string {
const options = { dayLimit: 100 }
const minute = 60
const hour = minute * 60

4
src/models/index.ts Normal file
View file

@ -0,0 +1,4 @@
import Status from './Status'
import Torrent from './Torrent'
export {Status, Torrent}

View file

@ -1,4 +1,5 @@
import Vue from 'vue'
import type { Framework } from 'vuetify'
import Vuetify from 'vuetify/lib'
import { Ripple } from 'vuetify/lib/directives'
@ -46,45 +47,22 @@ export default new Vuetify({
background: colors.grey.lighten4,
selected: colors.grey.lighten2,
red: colors.red.accent2,
primary: '#35495e',
secondary: '#3e556d',
download: '#64CEAA',
upload: '#00b3fa',
// Torrent status colors
'torrent-done': '#16573e',
'torrent-downloading': '#5bb974',
'torrent-fail': '#f83e70',
'torrent-paused': '#9CA3AF',
'torrent-queued': '#2e5eaa',
'torrent-seeding': '#4ecde6',
'torrent-checking': '#ff7043',
'torrent-stalled': '#4ADE80',
'torrent-metadata': '#7e57c2',
'torrent-moving': '#ffaa2c',
...variables
},
dark: {
accent: '#64CEAA',
background: colors.black,
background: colors.shades.black,
selected: colors.grey.darken1,
red: colors.red.accent3,
primary: '#35495e',
secondary: '#3e556d',
download: '#64CEAA',
upload: '#00b3fa',
// Torrent status colors
'torrent-done': '#16573e',
'torrent-downloading': '#5bb974',
'torrent-fail': '#f83e70',
'torrent-paused': '#9CA3AF',
'torrent-queued': '#2e5eaa',
'torrent-seeding': '#4ecde6',
'torrent-checking': '#ff7043',
'torrent-stalled': '#4ADE80',
'torrent-metadata': '#7e57c2',
'torrent-moving': '#ffaa2c',
...variables
}
}
}
})
declare module 'vue/types/vue' {
// this.$vuetify inside Vue components
interface Vue {
$vuetify: Framework
}
}

View file

@ -1,4 +1,25 @@
import axios, { AxiosInstance } from 'axios'
import axios from 'axios'
import type { AxiosInstance } from 'axios'
import type {
ApplicationVersion,
AppPreferences,
Category,
Feed,
FeedRule,
SearchJob,
SearchPlugin,
SearchStatus,
TorrentFile,
TorrentProperties,
Tracker,
Torrent
} from '@/types/qbit/models'
import type { MainDataResponse, SearchResultsResponse, TorrentPeersResponse } from '@/types/qbit/responses'
import type { AddTorrentPayload, AppPreferencesPayload, LoginPayload } from '@/types/qbit/payloads'
import type { SortOptions } from '@/types/vuetorrent'
import type { Priority } from '@/enums/qbit'
type Parameters = Record<string, any>
export class QBitApi {
private axios: AxiosInstance
@ -11,92 +32,63 @@ export class QBitApi {
this.axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
}
execute(method, action, params) {
if (method === 'post') {
const data = new URLSearchParams(params)
return this.axios.post(action, data).then(res => res.data)
}
async execute(action: string, params?: Parameters): Promise<any> {
const data = new URLSearchParams(params)
return this.axios.post(action, data).then(res => res.data)
}
/** Begin General functions * */
getAppVersion(): Promise<string> {
async getAppVersion(): Promise<ApplicationVersion> {
return this.axios
.get('/app/version')
.then(res => res.data)
.then(version => (version.includes('v') ? version.substring(1) : version))
.get('/app/version')
.then(res => res.data)
.then(version => (version.includes('v') ? version.substring(1) : version))
}
getApiVersion() {
return this.axios.get('/app/webapiVersion')
}
async login(params) {
async login(params: LoginPayload): Promise<string> {
const payload = new URLSearchParams(params)
const { data } = await this.axios
.post('/auth/login', payload, {
validateStatus(status) {
return status === 200 || status === 403
}
})
.catch(err => console.log(err))
const res = await this.axios
.post('/auth/login', payload, {
validateStatus: (status: number) => status === 200 || status === 403
})
.catch(err => console.log(err))
return data
return res?.data
}
async getAuthenticationStatus() {
async getAuthenticationStatus(): Promise<boolean> {
return this.axios
.get('/app/version')
.then(() => true)
.catch(() => false)
.get('/app/version')
.then(() => true)
.catch(() => false)
}
async logout() {
this.axios.post('/auth/logout')
async logout(): Promise<void> {
await this.axios.post('/auth/logout')
}
getGlobalTransferInfo() {
return this.axios.get('/transfer/info')
async getAppPreferences(): Promise<AppPreferences> {
return this.axios.get('/app/preferences').then(r => r.data)
}
getAppPreferences() {
return this.axios.get('/app/preferences')
}
setPreferences(params) {
const data = new URLSearchParams({
async setPreferences(params: AppPreferencesPayload): Promise<void> {
const data = {
json: JSON.stringify(params)
})
}
return this.axios.post('/app/setPreferences', data)
await this.execute('/app/setPreferences', data)
}
getMainData(rid) {
async getMainData(rid?: number): Promise<MainDataResponse> {
return this.axios.get('/sync/maindata', { params: { rid } }).then(res => res.data)
}
switchToOldUi() {
return this.setPreferences({
alternative_webui_enabled: false
})
async toggleSpeedLimitsMode(): Promise<void> {
await this.execute('/transfer/toggleSpeedLimitsMode')
}
toggleSpeedLimitsMode() {
return this.axios.post('/transfer/toggleSpeedLimitsMode')
}
/** Begin Torrent functions * */
// Get
getLogs(lastId) {
return this.axios.get('/log/main', {
last_known_id: lastId
})
}
getTorrents(payload) {
const params = {
async getTorrents(payload: SortOptions): Promise<Torrent[]> {
const params: Parameters = {
sort: !payload.isCustomSortEnabled ? payload.sort : null,
reverse: !payload.isCustomSortEnabled ? payload.reverse : null,
hashes: payload.hashes.length > 0 ? payload.hashes.join('|') : null,
@ -110,104 +102,111 @@ export class QBitApi {
const data = new URLSearchParams(params)
return this.axios.get(`/torrents/info?${data.toString()}`)
return this.axios.get(`/torrents/info?${data.toString()}`).then(r => r.data)
}
getTorrentTrackers(hash) {
return this.axios.get('/torrents/trackers', {
params: { hash }
})
async getTorrentTrackers(hash: string): Promise<Tracker[]> {
return this.axios
.get('/torrents/trackers', {
params: { hash }
})
.then(r => r.data)
}
getTorrentPeers(hash, rid) {
async getTorrentPeers(hash: string, rid?: number): Promise<TorrentPeersResponse> {
return this.axios.get('/sync/torrentPeers', {
params: { hash, rid }
})
}
setTorrentName(hash, name) {
return this.execute('post', '/torrents/rename', { hash, name })
async setTorrentName(hash: string, name: string): Promise<void> {
await this.execute('/torrents/rename', { hash, name })
}
getTorrentPieceStates(hash) {
return this.axios.get('/torrents/pieceStates', {
params: { hash }
})
async getTorrentPieceStates(hash: string): Promise<number[]> {
return this.axios
.get('/torrents/pieceStates', {
params: { hash }
})
.then(res => res.data)
}
getTorrentFiles(hash) {
return this.axios.get('/torrents/files', {
params: { hash }
})
async getTorrentFiles(hash: string, indexes?: number[]): Promise<TorrentFile[]> {
return this.axios
.get('/torrents/files', {
params: { hash, indexes: indexes?.join('|') }
})
.then(res => res.data)
}
getAvailableTags() {
async getAvailableTags(): Promise<string[]> {
return this.axios.get('/torrents/tags').then(res => res.data)
}
getTorrentProperties(hash) {
async getTorrentProperties(hash: string): Promise<TorrentProperties> {
return this.axios
.get('/torrents/properties', {
params: { hash }
})
.then(res => res.data)
.get('/torrents/properties', {
params: { hash }
})
.then(res => res.data)
}
// RSS
createFeed(feed) {
return this.execute('post', '/rss/addFeed', {
url: feed.url,
path: feed.url
async createFeed(url: string, path?: string): Promise<void> {
await this.execute('/rss/addFeed', {
url: url,
path: path
})
}
createRule(ruleName, defs) {
return this.execute('post', '/rss/setRule', {
async createRule(ruleName: string, ruleDef: FeedRule) {
return this.execute('/rss/setRule', {
ruleName: ruleName,
ruleDef: JSON.stringify(defs)
ruleDef: JSON.stringify(ruleDef, ['enabled', 'mustContain', 'mustNotContain', 'useRegex', 'affectedFeeds'])
})
}
getFeeds() {
return this.axios
.get('/rss/items')
.then(res => res.data)
.then(data =>
Object.entries(data).map(feed => {
return { name: feed[0], ...feed[1] }
})
)
async getFeeds(): Promise<Record<string, Feed>> {
return this.axios.get('/rss/items').then(res => res.data)
}
getRules() {
return this.axios
.get('/rss/rules')
.then(res => res.data)
.then(data =>
Object.entries(data).map(rule => {
return { name: rule[0], ...rule[1] }
})
)
async getRules(): Promise<Record<string, FeedRule>> {
return this.axios.get('/rss/rules').then(res => res.data)
}
deleteRule(ruleName) {
return this.execute('post', 'rss/removeRule', {
async editFeed(itemPath: string, destPath: string): Promise<void> {
await this.execute('/rss/moveItem', {
itemPath,
destPath
})
}
async editRule(ruleName: string, newRuleName: string): Promise<void> {
await this.execute('/rss/renameRule', {
ruleName,
newRuleName
})
}
async deleteRule(ruleName: string): Promise<void> {
await this.execute('rss/removeRule', {
ruleName
})
}
deleteFeed(name) {
return this.execute('post', 'rss/removeItem', {
async deleteFeed(name: string): Promise<void> {
await this.execute('rss/removeItem', {
path: name
})
}
// Post
addTorrents(params, torrents) {
async addTorrents(params: AddTorrentPayload, torrents: File[]): Promise<void> {
let data
if (torrents) {
// torrent files
const formData = new FormData()
if (params) {
for (const [key, value] of Object.entries(params)) {
@ -221,283 +220,269 @@ export class QBitApi {
data = formData
} else {
data = new URLSearchParams(params)
// magnet links
data = new URLSearchParams(params as Parameters)
}
return this.axios.post('/torrents/add', data)
await this.axios.post('/torrents/add', data)
}
setTorrentFilePriority(hash, idList, priority) {
async setTorrentFilePriority(hash: string, idList: number[], priority: Priority): Promise<void> {
const params = {
hash,
id: idList.join('|'),
priority
}
return this.execute('post', '/torrents/filePrio', params)
await this.execute('/torrents/filePrio', params)
}
deleteTorrents(hashes, deleteFiles) {
async deleteTorrents(hashes: string[], deleteFiles: boolean): Promise<void> {
if (!hashes.length) return
return this.torrentAction('delete', hashes, { deleteFiles })
await this.torrentAction('delete', hashes, { deleteFiles })
}
pauseTorrents(hashes) {
return this.torrentAction('pause', hashes)
async pauseTorrents(hashes: string[]): Promise<void> {
await this.torrentAction('pause', hashes)
}
resumeTorrents(hashes) {
return this.torrentAction('resume', hashes)
async resumeTorrents(hashes: string[]): Promise<void> {
await this.torrentAction('resume', hashes)
}
forceStartTorrents(hashes) {
return this.torrentAction('setForceStart', hashes, { value: true })
async forceStartTorrents(hashes: string[]): Promise<void> {
await this.torrentAction('setForceStart', hashes, { value: true })
}
toggleSequentialDownload(hashes) {
return this.torrentAction('toggleSequentialDownload', hashes)
async toggleSequentialDownload(hashes: string[]): Promise<void> {
await this.torrentAction('toggleSequentialDownload', hashes)
}
toggleFirstLastPiecePriority(hashes) {
return this.torrentAction('toggleFirstLastPiecePrio', hashes)
async toggleFirstLastPiecePriority(hashes: string[]): Promise<void> {
await this.torrentAction('toggleFirstLastPiecePrio', hashes)
}
setAutoTMM(hashes, enable) {
return this.torrentAction('setAutoManagement', hashes, { enable })
async setAutoTMM(hashes: string[], enable: boolean): Promise<void> {
await this.torrentAction('setAutoManagement', hashes, { enable })
}
setDownloadLimit(hashes, limit) {
return this.torrentAction('setDownloadLimit', hashes, { limit })
async setDownloadLimit(hashes: string[], limit: number): Promise<void> {
await this.torrentAction('setDownloadLimit', hashes, { limit })
}
setUploadLimit(hashes, limit) {
return this.torrentAction('setUploadLimit', hashes, { limit })
async setUploadLimit(hashes: string[], limit: number): Promise<void> {
await this.torrentAction('setUploadLimit', hashes, { limit })
}
async getGlobalDownloadLimit() {
const { data } = await this.axios.get('/transfer/downloadLimit')
return data
/**
* @return current global download speed limit in bytes/second; this value will be zero if no limit is applied.
*/
async getGlobalDownloadLimit(): Promise<number> {
return this.axios.get('/transfer/downloadLimit').then(res => res.data)
}
async getGlobalUploadLimit() {
const { data } = await this.axios.get('/transfer/uploadLimit')
return data
/**
* @return current global upload speed limit in bytes/second; this value will be zero if no limit is applied.
*/
async getGlobalUploadLimit(): Promise<number> {
return this.axios.get('/transfer/uploadLimit').then(res => res.data)
}
setGlobalDownloadLimit(limit) {
const formData = new FormData()
formData.append('limit', limit)
/**
* @param limit - The global download speed limit to set in bytes/second
*/
async setGlobalDownloadLimit(limit: number): Promise<void> {
const data = {
limit
}
return this.axios.post('/transfer/setDownloadLimit', formData)
await this.execute('/transfer/setDownloadLimit', data)
}
setGlobalUploadLimit(limit) {
const formData = new FormData()
formData.append('limit', limit)
/**
* @param limit - The global upload speed limit to set in bytes/second
*/
async setGlobalUploadLimit(limit: number): Promise<void> {
const data = {
limit
}
return this.axios.post('/transfer/setUploadLimit', formData)
await this.execute('/transfer/setUploadLimit', data)
}
setShareLimit(hashes, ratioLimit, seedingTimeLimit) {
return this.torrentAction('setShareLimits', hashes, {
async setShareLimit(hashes: string[], ratioLimit: number, seedingTimeLimit: number): Promise<void> {
await this.torrentAction('setShareLimits', hashes, {
ratioLimit,
seedingTimeLimit
})
}
reannounceTorrents(hashes) {
return this.torrentAction('reannounce', hashes)
async reannounceTorrents(hashes: string[]): Promise<void> {
await this.torrentAction('reannounce', hashes)
}
recheckTorrents(hashes) {
return this.torrentAction('recheck', hashes)
async recheckTorrents(hashes: string[]): Promise<void> {
await this.torrentAction('recheck', hashes)
}
setTorrentsCategory(hashes, category) {
return this.torrentAction('setCategory', hashes, { category })
async setTorrentLocation(hashes: string[], location: string): Promise<void> {
await this.torrentAction('setLocation', hashes, { location })
}
editTracker(hash, origUrl, newUrl) {
return this.torrentAction('editTracker', [hash], { origUrl, newUrl })
async addTorrentTrackers(hash: string, trackers: string): Promise<void> {
await this.torrentAction('addTrackers', [hash], { urls: trackers })
}
setTorrentLocation(hashes, location) {
return this.torrentAction('setLocation', hashes, { location })
async removeTorrentTrackers(hash: string, trackers: string[]): Promise<void> {
await this.torrentAction('removeTrackers', [hash], { urls: trackers.join('|') })
}
addTorrenTrackers(hash, trackers) {
const params = {
hash,
urls: trackers
}
return this.execute('post', '/torrents/addTrackers', params)
async addTorrentPeers(hashes: string[], peers: string[]): Promise<void> {
await this.torrentAction('addPeers', hashes, { peers: peers.join('|') })
}
removeTorrentTrackers(hash, trackers) {
const params = {
hash,
urls: trackers.join('|')
}
return this.execute('post', '/torrents/removeTrackers', params)
}
addTorrentPeers(hashes: Array<string>, peers: Array<string>) {
const params = {
hashes: hashes.join('|'),
peers: peers.join('|')
}
return this.execute('post', '/torrents/addPeers', params)
}
banPeers(peers: Array<string>) {
async banPeers(peers: string[]): Promise<void> {
const params = {
peers: peers.join('|')
}
return this.execute('post', '/transfer/banPeers', params)
await this.execute('/transfer/banPeers', params)
}
torrentAction(action, hashes, extra) {
async torrentAction(action: string, hashes: string[], extra?: Record<string, any>): Promise<any> {
const params = {
hashes: hashes.length ? hashes.join('|') : 'all',
...extra
}
return this.execute('post', `/torrents/${action}`, params)
return this.execute(`/torrents/${action}`, params)
}
renameFile(hash, oldPath, newPath) {
async renameFile(hash: string, oldPath: string, newPath: string): Promise<void> {
const params = {
hash,
oldPath,
newPath
}
return this.execute('post', '/torrents/renameFile', params)
await this.execute('/torrents/renameFile', params)
}
renameFolder(hash, oldPath, newPath) {
async renameFolder(hash: string, oldPath: string, newPath: string): Promise<void> {
const params = {
hash,
oldPath,
newPath
}
return this.execute('post', '/torrents/renameFolder', params)
await this.execute('/torrents/renameFolder', params)
}
/** Torrent Priority **/
setTorrentPriority(hashes, priority) {
if (['increasePrio', 'decreasePrio', 'topPrio', 'bottomPrio'].includes(priority)) {
return this.execute('post', `/torrents/${priority}`, {
hashes: hashes.join('|')
})
}
async setTorrentPriority(hashes: string[], priority: 'increasePrio' | 'decreasePrio' | 'topPrio' | 'bottomPrio'): Promise<void> {
await this.execute(`/torrents/${priority}`, {
hashes: hashes.join('|')
})
}
/** Begin Torrent Tags **/
removeTorrentTag(hashes, tag) {
return this.execute('post', '/torrents/removeTags', {
hashes: hashes.join('|'),
tags: tag
async removeTorrentTag(hashes: string[], tags: string[]): Promise<void> {
await this.torrentAction('removeTags', hashes, { tags: tags.join('|') })
}
async addTorrentTag(hashes: string[], tags: string[]): Promise<void> {
await this.torrentAction('addTags', hashes, { tags: tags.join('|') })
}
async createTag(tags: string[]): Promise<void> {
await this.execute('/torrents/createTags', {
tags: tags.join(',')
})
}
addTorrentTag(hashes, tag) {
return this.execute('post', '/torrents/addTags ', {
hashes: hashes.join('|'),
tags: tag
})
}
createTag(tag) {
return this.execute('post', '/torrents/createTags ', {
tags: tag
})
}
deleteTag(tag) {
return this.execute('post', '/torrents/deleteTags', {
tags: tag
async deleteTag(tags: string[]): Promise<void> {
await this.execute('/torrents/deleteTags', {
tags: tags.join(',')
})
}
/** Begin Categories **/
getCategories() {
return this.axios.get('/torrents/categories').then(res => res.data)
async getCategories(): Promise<Category[]> {
return this.axios
.get('/torrents/categories')
.then(res => res.data)
.then(data => Object.values(data))
}
deleteCategory(categories) {
return this.execute('post', '/torrents/removeCategories', {
categories
async deleteCategory(categories: string[]): Promise<void> {
await this.execute('/torrents/removeCategories', {
categories: categories.join('\n')
})
}
createCategory(cat) {
return this.execute('post', '/torrents/createCategory', {
async createCategory(cat: Category): Promise<void> {
await this.execute('/torrents/createCategory', {
category: cat.name,
savePath: cat.savePath
})
}
setCategory(hashes, category) {
return this.torrentAction('setCategory', hashes, { category })
async setCategory(hashes: string[], category: string): Promise<void> {
await this.torrentAction('setCategory', hashes, { category })
}
editCategory(cat) {
async editCategory(cat: Category): Promise<void> {
const params = {
category: cat.name,
savePath: cat.savePath
}
return this.execute('post', '/torrents/editCategory', params)
await this.execute('/torrents/editCategory', params)
}
/** Search **/
getSearchPlugins() {
async getSearchPlugins(): Promise<SearchPlugin[]> {
return this.axios.get('/search/plugins').then(res => res.data)
}
updateSearchPlugins() {
return this.execute('post', '/search/updatePlugins')
async updateSearchPlugins(): Promise<void> {
await this.execute('/search/updatePlugins')
}
enableSearchPlugin(plugins, enable) {
async enableSearchPlugin(pluginNames: string[], enable: boolean): Promise<void> {
const params = {
names: plugins.join('|'),
names: pluginNames.join('|'),
enable
}
return this.execute('post', '/search/enablePlugin', params)
await this.execute('/search/enablePlugin', params)
}
startSearch(pattern, plugins) {
async startSearch(pattern: string, plugins: string[]): Promise<SearchJob> {
const params = {
pattern,
plugins: Array.isArray(plugins) ? plugins.join('|') : 'all',
plugins: plugins.length ? plugins.join('|') : 'enabled',
category: 'all'
}
return this.execute('post', '/search/start', params)
return this.execute('/search/start', params)
}
stopSearch(id) {
return this.execute('post', '/search/stop', { id })
async stopSearch(id: number): Promise<void> {
await this.execute('/search/stop', { id })
}
getSearchStatus(id) {
return this.execute('post', '/search/status', { id })
async getSearchStatus(id?: number): Promise<SearchStatus[]> {
const params = id !== undefined ? { id } : undefined
return this.execute('/search/status', params)
}
getSearchResults(id) {
return this.execute('post', '/search/results', {
id
async getSearchResults(id: number, limit?: number, offset?: number): Promise<SearchResultsResponse> {
return this.execute('/search/results', {
id,
limit,
offset
})
}
}

View file

@ -1,34 +0,0 @@
import Vue from 'vue'
import qbit from '../services/qbit'
import { i18n } from '@/plugins/i18n'
export default {
INIT_INTERVALS: async context => {
context.state.intervals[0] = setInterval(() => {
context.commit('updateMainData')
}, 2000)
},
LOGIN: async (context, payload) => {
const res = await qbit.login(payload)
console.log(res)
if (res === 'Ok.') {
Vue.$toast.success(i18n.t('toast.loginSuccess'))
context.commit('LOGIN', true)
context.commit('updateMainData')
await context.dispatch('FETCH_SETTINGS')
context.commit('FETCH_CATEGORIES')
context.commit('FETCH_TAGS')
return true
}
Vue.$toast.error(i18n.t('toast.loginFailed'))
return false
},
FETCH_SETTINGS: async context => {
const { data } = await qbit.getAppPreferences()
context.commit('FETCH_SETTINGS', data)
return data
}
}

37
src/store/actions.ts Normal file
View file

@ -0,0 +1,37 @@
import Vue from 'vue'
import qbit from '@/services/qbit'
import { i18n } from '@/plugins/i18n'
import type { StoreState } from '@/types/vuetorrent'
import type { Store } from 'vuex'
import type { LoginPayload } from '@/types/qbit/payloads'
export default {
INIT_INTERVALS: async (store: Store<StoreState>) => {
store.state.intervals[0] = setInterval(() => {
store.commit('updateMainData')
}, 2000)
},
LOGIN: async (store: Store<StoreState>, payload: LoginPayload) => {
const res = await qbit.login(payload)
console.log(res)
if (res === 'Ok.') {
Vue.$toast.success(i18n.t('toast.loginSuccess').toString())
store.commit('LOGIN', true)
store.commit('updateMainData')
await store.dispatch('FETCH_SETTINGS')
store.commit('FETCH_CATEGORIES')
store.commit('FETCH_TAGS')
return true
}
Vue.$toast.error(i18n.t('toast.loginFailed').toString())
return false
},
FETCH_SETTINGS: async (store: Store<StoreState>) => {
const data = await qbit.getAppPreferences()
store.commit('FETCH_SETTINGS', data)
return data
}
}

View file

@ -1,29 +0,0 @@
import { i18n } from '@/plugins/i18n'
export default {
getAppVersion: state => () => state.version,
containsTorrent: state => hash => state.selected_torrents.includes(hash),
isDarkMode: state => () => state.webuiSettings.darkTheme,
getTheme: state => () => state.webuiSettings.darkTheme ? 'dark' : 'light',
getModalState: state => guid => state.modals.filter(m => m.guid === guid)[0],
getSettings: state => () => state.settings,
getStatus: state => () => state.status,
getTorrent: state => hash => state.torrents.filter(el => el.hash === hash)[0],
getWebuiSettings: state => () => state.webuiSettings,
getAvailableTags: state => () => state.tags,
getCategories: state => () => state.categories,
getFeeds: state => () => state.rss.feeds,
getRules: state => () => state.rss.rules,
getModals: state => () => state.modals,
getTorrents: state => () => state.torrents,
getTrackers: state => () => state.trackers,
getAuthenticated: state => () => state.authenticated,
getTorrentCountString: state => () => {
if (state.selected_torrents && state.selected_torrents.length) {
return `${state.selected_torrents.length} ${i18n.t('of')} ${i18n.tc('navbar.torrentsCount', state.filteredTorrentsCount)}`
}
return i18n.tc('navbar.torrentsCount', state.filteredTorrentsCount)
},
getSearchPlugins: state => () => state.searchPlugins
}

30
src/store/getters.ts Normal file
View file

@ -0,0 +1,30 @@
import { i18n } from '@/plugins/i18n'
import type { StoreState } from '@/types/vuetorrent'
export default {
getAppVersion: (state: StoreState) => () => state.version,
containsTorrent: (state: StoreState) => (hash: string) => state.selected_torrents.includes(hash),
isDarkMode: (state: StoreState) => () => state.webuiSettings.darkTheme,
getTheme: (state: StoreState) => () => state.webuiSettings.darkTheme ? 'dark' : 'light',
getModalState: (state: StoreState) => (guid: string) => state.modals.filter(m => m.guid === guid)[0],
getSettings: (state: StoreState) => () => state.settings,
getStatus: (state: StoreState) => () => state.status,
getTorrent: (state: StoreState) => (hash: string) => state.torrents.filter(el => el.hash === hash)[0],
getWebuiSettings: (state: StoreState) => () => state.webuiSettings,
getAvailableTags: (state: StoreState) => () => state.tags,
getCategories: (state: StoreState) => () => state.categories,
getFeeds: (state: StoreState) => () => state.rss.feeds,
getRules: (state: StoreState) => () => state.rss.rules,
getModals: (state: StoreState) => () => state.modals,
getTorrents: (state: StoreState) => () => state.torrents,
getTrackers: (state: StoreState) => () => state.trackers,
getAuthenticated: (state: StoreState) => () => state.authenticated,
getTorrentCountString: (state: StoreState) => () => {
if (state.selected_torrents && state.selected_torrents.length) {
return `${state.selected_torrents.length} ${i18n.t('of')} ${i18n.tc('navbar.torrentsCount', state.filteredTorrentsCount)}`
}
return i18n.tc('navbar.torrentsCount', state.filteredTorrentsCount)
},
getSearchPlugins: (state: StoreState) => () => state.searchPlugins
}

View file

@ -4,8 +4,11 @@ import VuexPersist from 'vuex-persist'
import actions from './actions'
import getters from './getters'
import mutations from './mutations'
import type { StoreState } from '@/types/vuetorrent'
import { Status } from '@/models'
import {TitleOptions} from "@/enums/vuetorrent";
const vuexPersist = new VuexPersist({
const vuexPersist = new VuexPersist<StoreState>({
key: 'vuetorrent',
storage: window.localStorage,
reducer: state => ({
@ -17,6 +20,7 @@ const vuexPersist = new VuexPersist({
Vue.use(Vuex)
// noinspection DuplicatedCode
const propertiesTemplate = [
{ name: 'Size', active: true },
{ name: 'Progress', active: true },
@ -40,32 +44,29 @@ const propertiesTemplate = [
{ name: 'GlobalVolume', active: false }
]
export default new Vuex.Store({
export default new Vuex.Store<StoreState>({
plugins: [vuexPersist.plugin],
state: {
version: 0,
authenticated: false,
categories: [],
dashboard: {
currentPage: 1,
searchFilter: ''
},
download_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
filteredTorrentsCount: 0,
intervals: [],
latestSelectedTorrent: -1,
modals: [],
rid: 0,
rss: {
feeds: [],
rules: []
},
status: {
status: '',
downloaded: '',
uploaded: '',
dlspeed: '',
upspeed: '',
freeDiskSpace: '',
altSpeed: '',
dlspeedRaw: '',
upspeedRaw: '',
tags: ''
},
upload_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
download_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
torrents: [],
searchPlugins: [],
selectMode: false,
selected_torrents: [],
authenticated: false,
settings: null,
sort_options: {
isCustomSortEnabled: false,
sort: 'priority',
@ -76,10 +77,12 @@ export default new Vuex.Store({
tag: null,
tracker: null
},
rid: 0,
pasteUrl: null,
modals: [],
settings: {},
status: new Status(),
tags: [],
torrents: [],
trackers: [],
upload_data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
version: '',
webuiSettings: {
lang: 'en',
darkTheme: false,
@ -91,27 +94,17 @@ export default new Vuex.Store({
showTrackerFilter: false,
showSpeedInTitle: false,
deleteWithFiles: false,
title: 'Default',
title: TitleOptions.DEFAULT,
rightDrawer: false,
topPagination: false,
paginationSize: 15,
dateFormat: 'DD/MM/YYYY, HH:mm:ss',
openSideBarOnStart: true,
busyTorrentProperties: JSON.parse(JSON.stringify(propertiesTemplate)),
doneTorrentProperties: JSON.parse(JSON.stringify(propertiesTemplate))
},
categories: [],
trackers: [],
tags: [],
filteredTorrentsCount: 0,
latestSelectedTorrent: null,
selectMode: false,
searchPlugins: [],
dashboard: {
currentPage: 1,
searchFilter: ''
busyTorrentProperties: [...propertiesTemplate],
doneTorrentProperties: [...propertiesTemplate]
}
},
// @ts-expect-error
actions: {
...actions
},

View file

@ -1,84 +0,0 @@
import qbit from '../services/qbit'
import { DocumentTitle, Tags, Trackers, Torrents, Graph, ServerStatus } from '@/actions'
import { setLanguage } from '@/plugins/i18n'
import Torrent from "@/models/Torrent";
export default {
SET_APP_VERSION(state, version) {
state.version = version
},
REMOVE_INTERVALS: state => {
state.intervals.forEach(el => clearInterval(el))
},
ADD_MODAL(state, modal) {
state.modals.push(modal)
},
DELETE_MODAL(state, guid) {
state.modals = state.modals.filter(m => m.guid !== guid)
},
SET_SELECTED: (state, { type, hash, index }) => {
if (type === 'add') {
state.selected_torrents.push(hash)
state.latestSelectedTorrent = state.torrents.map(t => t.hash).indexOf(hash)
} else if (type === 'remove') {
state.selected_torrents.splice(state.selected_torrents.indexOf(hash), 1)
} else if (type === 'until') {
let from
let until
if (state.latestSelectedTorrent > index) {
from = index
until = state.latestSelectedTorrent + 1 // include latest selected
} else {
from = state.latestSelectedTorrent
until = index + 1
}
state.selected_torrents = state.torrents.map(t => t.hash).slice(from, until)
}
},
RESET_SELECTED: state => {
state.selected_torrents = []
},
TOGGLE_THEME(state) {
state.webuiSettings.darkTheme = !state.webuiSettings.darkTheme
},
LOGOUT: async state => {
await qbit.logout()
state.authenticated = false
},
LOGIN: async (state, payload) => {
state.authenticated = payload
},
updateMainData: async state => {
const response = await qbit.getMainData(state.rid || undefined)
state.rid = response.rid || undefined
ServerStatus.update(response)
Tags.update(response)
Graph.update()
// fetch torrent data
state.sort_options.isCustomSortEnabled = Torrent.computedValues.indexOf(state.sort_options.sort) !== -1
const { data } = await qbit.getTorrents(state.sort_options)
Trackers.update(data)
Torrents.update(data)
DocumentTitle.update()
},
FETCH_SETTINGS: async (state, settings) => {
state.settings = settings
},
UPDATE_SORT_OPTIONS: (state, { hashes = [], filter = null, category = null, tag = null, tracker = null }) => {
state.sort_options.hashes = hashes
state.sort_options.filter = filter
state.sort_options.category = category
state.sort_options.tag = tag
state.sort_options.tracker = tracker
},
FETCH_CATEGORIES: async state => (state.categories = Object.values(await qbit.getCategories())),
FETCH_TAGS: async state => (state.tags = await qbit.getAvailableTags()),
FETCH_FEEDS: async state => (state.rss.feeds = await qbit.getFeeds()),
FETCH_RULES: async state => (state.rss.rules = await qbit.getRules()),
FETCH_SEARCH_PLUGINS: async state => (state.searchPlugins = await qbit.getSearchPlugins()),
SET_CURRENT_ITEM_COUNT: (state, count) => (state.filteredTorrentsCount = count),
SET_LANGUAGE: async state => setLanguage(state.webuiSettings.lang)
}

86
src/store/mutations.ts Normal file
View file

@ -0,0 +1,86 @@
import qbit from '../services/qbit'
import { DocumentTitle, Tags, Trackers, Torrents, Graph, ServerStatus } 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'
export default {
SET_APP_VERSION(state: StoreState, version: string) {
state.version = version
},
REMOVE_INTERVALS: (state: StoreState) => {
state.intervals.forEach(el => clearInterval(el))
},
ADD_MODAL(state: StoreState, modal: ModalTemplate) {
state.modals.push(modal)
},
DELETE_MODAL(state: StoreState, guid: string) {
state.modals = state.modals.filter(m => m.guid !== guid)
},
SET_SELECTED: (state: StoreState, data: { type: string; hash: string; index: number }) => {
const { type, hash, index } = data
if (type === 'add') {
state.selected_torrents.push(hash)
state.latestSelectedTorrent = state.torrents.map(t => t.hash).indexOf(hash)
} else if (type === 'remove') {
state.selected_torrents.splice(state.selected_torrents.indexOf(hash), 1)
} else if (type === 'until') {
let from, until
if (state.latestSelectedTorrent > index) {
from = index
until = state.latestSelectedTorrent + 1 // include latest selected
} else {
from = state.latestSelectedTorrent
until = index + 1
}
state.selected_torrents = state.torrents.map(t => t.hash).slice(from, until)
}
},
RESET_SELECTED: (state: StoreState) => {
state.selected_torrents = []
},
TOGGLE_THEME(state: StoreState) {
state.webuiSettings.darkTheme = !state.webuiSettings.darkTheme
},
LOGOUT: async (state: StoreState) => {
await qbit.logout()
state.authenticated = false
},
LOGIN: async (state: StoreState, payload: boolean) => {
state.authenticated = payload
},
updateMainData: async (state: StoreState) => {
const response = await qbit.getMainData(state.rid || undefined)
state.rid = response.rid || undefined
ServerStatus.update(response.server_state)
Tags.update(response)
Graph.shiftValues()
// fetch torrent data
state.sort_options.isCustomSortEnabled = Torrent.computedValues.indexOf(state.sort_options.sort) !== -1
const data = await qbit.getTorrents(state.sort_options)
Trackers.update(data)
Torrents.update(data)
DocumentTitle.update()
},
FETCH_SETTINGS: async (state: StoreState, settings: AppPreferences) => {
state.settings = settings
},
UPDATE_SORT_OPTIONS: (state: StoreState, { hashes = [], filter = null, category = null, tag = null, tracker = null }) => {
state.sort_options.hashes = hashes
state.sort_options.filter = filter
state.sort_options.category = category
state.sort_options.tag = tag
state.sort_options.tracker = tracker
},
FETCH_CATEGORIES: async (state: StoreState) => (state.categories = Object.values(await qbit.getCategories())),
FETCH_TAGS: async (state: StoreState) => (state.tags = await qbit.getAvailableTags()),
FETCH_FEEDS: async (state: StoreState) => (state.rss.feeds = Object.entries(await qbit.getFeeds()).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()),
SET_CURRENT_ITEM_COUNT: (state: StoreState, count: number) => (state.filteredTorrentsCount = count),
SET_LANGUAGE: async (state: StoreState) => setLanguage(state.webuiSettings.lang)
}

View file

@ -0,0 +1,39 @@
import type {FeedRule as QbitFeedRule} from '@/types/qbit/models'
import type {FeedRule as VtFeedRule} from '@/types/vuetorrent'
import type {BaseMapper} from "@/types/mappers/index";
export default class FeedRuleMapper implements BaseMapper<QbitFeedRule, VtFeedRule> {
public toQbit(rule: VtFeedRule): QbitFeedRule {
return <QbitFeedRule>{
addPaused: rule.addPaused,
affectedFeeds: rule.affectedFeeds,
assignedCategory: rule.assignedCategory,
enabled: rule.enabled,
episodeFilter: rule.episodeFilter,
ignoreDays: rule.ignoreDays,
lastMatch: rule.lastMatch,
mustContain: rule.mustContain,
mustNotContain: rule.mustNotContain,
savePath: rule.savePath,
smartFilter: rule.smartFilter,
useRegex: rule.useRegex
}
}
public toVt(rule: QbitFeedRule): VtFeedRule {
return <VtFeedRule>{
addPaused: rule.addPaused,
affectedFeeds: rule.affectedFeeds,
assignedCategory: rule.assignedCategory,
enabled: rule.enabled,
episodeFilter: rule.episodeFilter,
ignoreDays: rule.ignoreDays,
lastMatch: rule.lastMatch,
mustContain: rule.mustContain,
mustNotContain: rule.mustNotContain,
savePath: rule.savePath,
smartFilter: rule.smartFilter,
useRegex: rule.useRegex
}
}
}

View file

@ -0,0 +1,32 @@
import type {Tracker as QbitTracker} from '@/types/qbit/models'
import type {Tracker as VtTracker} from '@/types/vuetorrent'
import type {BaseMapper} from "@/types/mappers/index"
export default class TrackerMapper implements BaseMapper<QbitTracker, VtTracker> {
public toVt(tracker: QbitTracker): VtTracker {
return {
isSelectable: tracker.tier >= 0,
msg: tracker.msg,
num_downloaded: tracker.num_downloaded,
num_leeches: tracker.num_leeches,
num_peers: tracker.num_peers,
num_seeds: tracker.num_seeds,
status: tracker.status,
tier: tracker.tier,
url: tracker.url
}
}
toQbit(tracker: VtTracker): QbitTracker {
return {
msg: tracker.msg,
num_downloaded: tracker.num_downloaded,
num_leeches: tracker.num_leeches,
num_peers: tracker.num_peers,
num_seeds: tracker.num_seeds,
status: tracker.status,
tier: tracker.tier,
url: tracker.url
}
}
}

View file

@ -0,0 +1,9 @@
import FeedRuleMapper from "./FeedRuleMapper"
import TrackerMapper from "./TrackerMapper"
export {FeedRuleMapper, TrackerMapper}
export interface BaseMapper<QbitType, VtType> {
toVt(data: QbitType): VtType
toQbit(data: VtType): QbitType
}

View file

@ -0,0 +1,309 @@
import type {
BitTorrentProtocol,
DynDnsService,
Encryption,
MaxRatioAction,
ProxyType,
ScanDirs,
SchedulerDays,
UploadChokingAlgorithm,
UploadSlotsBehavior,
UtpTcpMixedMode
} from '@/enums/qbit/AppPreferences'
export default interface AppPreferences {
/** List of trackers to add to new torrent */
add_trackers: string
/** Enable automatic adding of trackers to new torrents */
add_trackers_enabled: boolean
/** Alternative global download speed limit in KiB/s */
alt_dl_limit: number
/** Alternative global upload speed limit in KiB/s */
alt_up_limit: number
/** True if an alternative WebUI should be used */
alternative_webui_enabled: boolean
/** File path to the alternative WebUI */
alternative_webui_path: string
announce_ip: string
/** True always announce to all tiers */
announce_to_all_tiers: boolean
/** True always announce to all trackers in a tier */
announce_to_all_trackers: boolean
/** If true anonymous mode will be enabled; read more here; this option is only available in qBittorent built against libtorrent version 0.16.X and higher */
anonymous_mode: boolean
/** Number of asynchronous I/O threads */
async_io_threads: number
auto_delete_mode: number
/** True if Automatic Torrent Management is enabled by default */
auto_tmm_enabled: boolean
/** True if external program should be run after torrent has finished downloading */
autorun_enabled: boolean
/** Program path/name/arguments to run if autorun_enabled is enabled; path is separated by slashes; you can use %f and %n arguments, which will be expanded by qBittorent as path_to_torrent_file and torrent_name (from the GUI; not the .torrent file name) respectively */
autorun_program: string
/** True if external program should be run after torrent has been added */
autorun_on_torrent_added_enabled: boolean
/** Program path/name/arguments to run if autorun_on_torrent_added_enabled is enabled; path is separated by slashes; you can use %f and %n arguments, which will be expanded by qBittorent as path_to_torrent_file and torrent_name (from the GUI; not the .torrent file name) respectively */
autorun_on_torrent_added_program: string
/** List of banned IPs */
banned_IPs: string
/** Bittorrent Protocol to use (see list of possible values below) */
bittorrent_protocol: BitTorrentProtocol
/** (White)list of ipv4/ipv6 subnets for which webui authentication should be bypassed; list entries are separated by commas */
bypass_auth_subnet_whitelist: string
/** True if webui authentication should be bypassed for clients whose ip resides within (at least) one of the subnets on the whitelist */
bypass_auth_subnet_whitelist_enabled: boolean
/** True if authentication challenge for loopback address (127.0.0.1) should be disabled */
bypass_local_auth: boolean
/** True if torrent should be relocated when its Category's save path changes */
category_changed_tmm_enabled: boolean
/** Outstanding memory when checking torrents in MiB */
checking_memory_use: number
/** True if a subfolder should be created when adding a torrent */
create_subfolder_enabled: boolean
/** IP Address to bind to. Empty String means All addresses */
current_interface_address: string
/** Network Interface used */
current_network_interface: string
/** True if DHT is enabled */
dht: boolean
/** Disk cache used in MiB */
disk_cache: number
/** Disk cache expiry interval in seconds */
disk_cache_ttl: number
/** Global download speed limit in KiB/s; -1 means no limit is applied */
dl_limit: number
/** If true torrents w/o any activity (stalled ones) will not be counted towards max_active_* limits; see dont_count_slow_torrents for more information */
dont_count_slow_torrents: boolean
/** Your DDNS domain name */
dyndns_domain: string
/** True if server DNS should be updated dynamically */
dyndns_enabled: boolean
/** Password for DDNS service */
dyndns_password: string
/** See list of possible values here below */
dyndns_service: DynDnsService
/** Username for DDNS service */
dyndns_username: string
/** Port used for embedded tracker */
embedded_tracker_port: number
/** True enables coalesce reads & writes */
enable_coalesce_read_write: boolean
/** True enables embedded tracker */
enable_embedded_tracker: boolean
/** True allows multiple connections from the same IP address */
enable_multi_connections_from_same_ip: boolean
/** True enables os cache */
enable_os_cache: boolean
/** True if the advanced libtorrent option piece_extent_affinity is enabled */
enable_piece_extent_affinity: boolean
/** True enables sending of upload piece suggestions */
enable_upload_suggestions: boolean
/** See list of possible values here below */
encryption: Encryption
/** Path to directory to copy .torrent files to. Slashes are used as path separators */
export_dir: string
/** Path to directory to copy .torrent files of completed downloads to. Slashes are used as path separators */
export_dir_fin: string
/** File pool size */
file_pool_size: number
/** True if ".!qB" should be appended to incomplete files */
incomplete_files_ext: boolean
/** True if external IP filter should be enabled */
ip_filter_enabled: boolean
/** Path to IP filter file (.dat, .p2p, .p2b files are supported); path is separated by slashes */
ip_filter_path: string
/** True if IP filters are applied to trackers */
ip_filter_trackers: boolean
/** True if [du]l_limit should be applied to peers on the LAN */
limit_lan_peers: boolean
/** True if [du]l_limit should be applied to estimated TCP overhead (service data: e.g. packet headers) */
limit_tcp_overhead: boolean
/** True if [du]l_limit should be applied to uTP connections; this option is only available in qBittorent built against libtorrent version 0.16.X and higher */
limit_utp_rate: boolean
/** Port for incoming connections */
listen_port: number
/** Currently selected language (e.g. en_GB for English) */
locale: string
/** True if LSD is enabled */
lsd: boolean
/** True if smtp server requires authentication */
mail_notification_auth_enabled: boolean
/** e-mail to send notifications to */
mail_notification_email: string
/** True if e-mail notification should be enabled */
mail_notification_enabled: boolean
/** Password for smtp authentication */
mail_notification_password: string
/** e-mail where notifications should originate from */
mail_notification_sender: string
/** smtp server for e-mail notifications */
mail_notification_smtp: string
/** True if smtp server requires SSL connection */
mail_notification_ssl_enabled: boolean
/** Username for smtp authentication */
mail_notification_username: string
/** Maximum number of active simultaneous downloads */
max_active_downloads: number
/** Maximum number of active simultaneous downloads and uploads */
max_active_torrents: number
/** Maximum number of active simultaneous uploads */
max_active_uploads: number
/** Maximum global number of simultaneous connections */
max_connec: number
/** Maximum number of simultaneous connections per torrent */
max_connec_per_torrent: number
/** Get the global share ratio limit */
max_ratio: number
/** Action performed when a torrent reaches the maximum share ratio. See list of possible values here below. */
max_ratio_act: MaxRatioAction
/** True if share ratio limit is enabled */
max_ratio_enabled: boolean
/** Number of minutes to seed a torrent */
max_seeding_time: number
/** True enables max seeding time */
max_seeding_time_enabled: boolean
/** Maximum number of upload slots */
max_uploads: number
/** Maximum number of upload slots per torrent */
max_uploads_per_torrent: number
/** Maximal outgoing port (0: Disabled) */
outgoing_ports_max: number
/** Minimal outgoing port (0: Disabled) */
outgoing_ports_min: number
/** True if PeX is enabled */
pex: boolean
/** True if disk space should be pre-allocated for all files */
preallocate_all: boolean
/** True proxy requires authentication; doesn't apply to SOCKS4 proxies */
proxy_auth_enabled: boolean
/** Proxy IP address or domain name */
proxy_ip: string
/** Password for proxy authentication */
proxy_password: string
/** True if peer and web seed connections should be proxified; this option will have any effect only in qBittorent built against libtorrent version 0.16.X and higher */
proxy_peer_connections: boolean
/** Proxy port */
proxy_port: number
/** True if proxy is only used for torrents */
proxy_torrents_only: boolean
/** See list of possible values here below */
proxy_type: ProxyType
/** Username for proxy authentication */
proxy_username: string
/** True if torrent queuing is enabled */
queueing_enabled: boolean
/** True if the port is randomly selected */
random_port: boolean
/** True rechecks torrents on completion */
recheck_completed_torrents: boolean
/** True resolves peer countries */
resolve_peer_countries: boolean
/** Enable auto-downloading of torrents from the RSS feeds */
rss_auto_downloading_enabled: boolean
/** For API ≥ v2.5.1: Enable downloading of repack/proper Episodes */
rss_download_repack_proper_episodes: boolean
/** Max stored articles per RSS feed */
rss_max_articles_per_feed: number
/** Enable processing of RSS feeds */
rss_processing_enabled: boolean
/** RSS refresh interval */
rss_refresh_interval: number
/** For API ≥ v2.5.1: List of RSS Smart Episode Filters */
rss_smart_episode_filters: string
/** Default save path for torrents, separated by slashes */
save_path: string
/** True if torrent should be relocated when the default save path changes */
save_path_changed_tmm_enabled: boolean
/** Save resume data interval in min */
save_resume_data_interval: number
/** Property: directory to watch for torrent files, value: where torrents loaded from this directory should be downloaded to (see list of possible values below). Slashes are used as path separators; multiple key/value pairs can be specified */
scan_dirs: Record<string, ScanDirs>
/** Scheduler starting hour */
schedule_from_hour: number
/** Scheduler starting minute */
schedule_from_min: number
/** Scheduler ending hour */
schedule_to_hour: number
/** Scheduler ending minute */
schedule_to_min: number
/** Scheduler days. See possible values here below */
scheduler_days: SchedulerDays
/** True if alternative limits should be applied according to schedule */
scheduler_enabled: boolean
/** Send buffer low watermark in KiB */
send_buffer_low_watermark: number
/** Send buffer watermark in KiB */
send_buffer_watermark: number
/** Send buffer watermark factor in percent */
send_buffer_watermark_factor: number
/** Download rate in KiB/s for a torrent to be considered "slow" */
slow_torrent_dl_rate_threshold: number
/** Seconds a torrent should be inactive before considered "slow" */
slow_torrent_inactive_timer: number
/** Upload rate in KiB/s for a torrent to be considered "slow" */
slow_torrent_ul_rate_threshold: number
/** Socket backlog size */
socket_backlog_size: number
/** For API < v2.0.1: SSL certificate contents (this is a not a path) */
ssl_cert: string
/** For API < v2.0.1: SSL keyfile contents (this is a not a path) */
ssl_key: string
/** True if torrents should be added in a Paused state */
start_paused_enabled: boolean
/** Timeout in seconds for a stopped announce request to trackers */
stop_tracker_timeout: number
/** Path for incomplete torrents, separated by slashes */
temp_path: string
/** True if folder for incomplete torrents is enabled */
temp_path_enabled: boolean
/** True if torrent should be relocated when its Category changes */
torrent_changed_tmm_enabled: boolean
/** Global upload speed limit in KiB/s; -1 means no limit is applied */
up_limit: number
/** Upload choking algorithm used (see list of possible values below) */
upload_choking_algorithm: UploadChokingAlgorithm
/** Upload slots behavior used (see list of possible values below) */
upload_slots_behavior: UploadSlotsBehavior
/** True if UPnP/NAT-PMP is enabled */
upnp: boolean
/** UPnP lease duration (0: Permanent lease) */
upnp_lease_duration: number
/** True if WebUI HTTPS access is enabled */
use_https: boolean
/** μTP-TCP mixed mode algorithm (see list of possible values below) */
utp_tcp_mixed_mode: UtpTcpMixedMode
/** IP address to use for the WebUI */
web_ui_address: string
/** WebUI access ban duration in seconds */
web_ui_ban_duration: number
/** True if WebUI clickjacking protection is enabled */
web_ui_clickjacking_protection_enabled: boolean
/** True if WebUI CSRF protection is enabled */
web_ui_csrf_protection_enabled: boolean
/** For API ≥ v2.5.1: List of custom http headers */
web_ui_custom_http_headers: string
/** Comma-separated list of domains to accept when performing Host header validation */
web_ui_domain_list: string
/** True if WebUI host header validation is enabled */
web_ui_host_header_validation_enabled: boolean
/** For API ≥ v2.0.1: Path to SSL certificate */
web_ui_https_cert_path: string
/** For API ≥ v2.0.1: Path to SSL keyfile */
web_ui_https_key_path: string
/** Maximum number of authentication failures before WebUI access ban */
web_ui_max_auth_fail_count: number
/** For API ≥ v2.3.0: Plaintext WebUI password, not readable, write-only. For API < v2.3.0: MD5 hash of WebUI password, hash is generated from the following string: username:Web UI Access:plain_text_web_ui_password */
web_ui_password: string
/** WebUI port */
web_ui_port: number
/** True if WebUI cookie Secure flag is enabled */
web_ui_secure_cookie_enabled: boolean
/** Seconds until WebUI is automatically signed off */
web_ui_session_timeout: number
/** True if UPnP is used for the WebUI port */
web_ui_upnp: boolean
/** For API ≥ v2.5.1: Enable custom http headers */
web_ui_use_custom_http_headers_enabled: boolean
/** WebUI username */
web_ui_username: string
}

View file

@ -0,0 +1,4 @@
export default interface Category {
name: string
savePath: string
}

View file

@ -0,0 +1,4 @@
export default interface Feed {
uid: string
url: string
}

View file

@ -0,0 +1,29 @@
export default interface FeedRule {
/** Add matched torrent in paused mode */
addPaused: boolean
/** The feed URLs the rule applied to */
affectedFeeds: string[]
/** Assign category to the torrent */
assignedCategory: string
/** Whether the rule is enabled */
enabled: boolean
/** Episode filter definition */
episodeFilter: string
/** Ignore sunsequent rule matches */
ignoreDays: number
/** The rule last match time */
lastMatch: string
/** The substring that the torrent name must contain */
mustContain: string
/** The substring that the torrent name must not contain */
mustNotContain: string
/** The list of episode IDs already matched by smart filter */
previouslyMatchedEpisodes?: unknown[]
/** Save torrent to the given directory */
savePath: string
/** Enable smart episode filter */
smartFilter: boolean
torrentContentLayout?: unknown
/** Enable regex mode in "mustContain" and "mustNotContain" */
useRegex: boolean
}

View file

@ -0,0 +1,18 @@
export default interface Peer {
client: string
connection: string
country: string
country_code: string
dl_speed: number
downloaded: number
files: string
flags: string
flags_desc: string
ip: string
peer_id_client: string
port: number
progress: number
relevance: number
up_speed: number
uploaded: number
}

View file

@ -0,0 +1,4 @@
export default interface SearchJob {
/** ID of the search job */
id: number
}

View file

@ -0,0 +1,16 @@
type PluginCategory = { id: string; name: string }
export default interface SearchPlugin {
/** Whether the plugin is enabled */
enabled: boolean
/** Full name of the plugin */
fullName: string
/** Short name of the plugin */
name: string
/** List of category objects */
supportedCategories: PluginCategory[]
/** URL of the torrent site */
url: string
/** Installed version of the plugin */
version: string
}

View file

@ -0,0 +1,16 @@
export default interface SearchResult {
/** URL of the torrent's description page */
descrLink: string
/** Name of the file */
fileName: string
/** Size of the file in Bytes */
fileSize: number
/** Torrent download link (usually either .torrent file or magnet link) */
fileUrl: string
/** Number of leechers */
nbLeechers: number
/** Number of seeders */
nbSeeders: number
/** URL of the torrent site */
siteUrl: string
}

View file

@ -0,0 +1,8 @@
export default interface SearchStatus {
/** ID of the search job */
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
}

View file

@ -0,0 +1,28 @@
import type { ConnectionStatus } from '@/enums/qbit'
export default interface ServerState {
alltime_dl: number
alltime_ul: number
average_time_queue: number
connection_status: ConnectionStatus
dht_nodes: number
dl_info_data: number
dl_info_speed: number
dl_rate_limit: number
free_space_on_disk: number
global_ratio: string
queued_io_jobs: number
queueing: boolean
read_cache_hits: string
read_cache_overload: string
refresh_interval: number
total_buffers_size: number
total_peer_connections: number
total_queued_size: number
total_wasted_session: number
up_info_data: number
up_info_speed: number
up_rate_limit: number
use_alt_speed_limits: boolean
write_cache_overload: string
}

View file

@ -0,0 +1,92 @@
import type {TorrentState} from "@/enums/qbit";
export default interface Torrent {
// Time (Unix Epoch) when the torrent was added to the client
added_on: number
// Amount of data left to download (bytes)
amount_left: number
// Whether this torrent is managed by Automatic Torrent Management
auto_tmm: boolean
// Percentage of file pieces currently available
availability: number
// Category of the torrent
category: string
// Amount of transfer data completed (bytes)
completed: number
// Time (Unix Epoch) when the torrent completed
completion_on: number
// Absolute path of torrent content (root path for multifile torrents, absolute file path for singlefile torrents)
content_path: string
// Torrent download speed limit (bytes/s). -1 if ulimited.
dl_limit: number
// Torrent download speed (bytes/s)
dlspeed: number
// Amount of data downloaded
downloaded: number
// Amount of data downloaded this session
downloaded_session: number
// Torrent ETA (seconds)
eta: number
// True if first last piece are prioritized
f_l_piece_prio: boolean
// True if force start is enabled for this torrent
force_start: boolean
// Torrent hash
hash: string
// Last time (Unix Epoch) when a chunk was downloaded/uploaded
last_activity: number
// Magnet URI corresponding to this torrent
magnet_uri: string
// Maximum share ratio until torrent is stopped from seeding/uploading
max_ratio: number
// Maximum seeding time (seconds) until torrent is stopped from seeding
max_seeding_time: number
// Torrent name
name: string
// Number of seeds in the swarm
num_complete: number
// Number of leechers in the swarm
num_incomplete: number
// Number of leechers connected to
num_leechs: number
// Number of seeds connected to
num_seeds: number
// Torrent priority. Returns -1 if queuing is disabled or torrent is in seed mode
priority: number
// Torrent progress (percentage/100)
progress: number
// Torrent share ratio. Max ratio value: 9999.
ratio: number
ratio_limit: number
// Path where this torrent's data is stored
save_path: string
// Torrent elapsed time while complete (seconds)
seeding_time: number
seeding_time_limit: number
// Time (Unix Epoch) when this torrent was last seen complete
seen_complete: number
// True if sequential download is enabled
seq_dl: boolean
// Total size (bytes) of files selected for download
size: number
// Torrent state. See table here below for the possible values
state: TorrentState
// True if super seeding is enabled
super_seeding: boolean
// Comma-concatenated tag list of the torrent
tags: string
// Total active time (seconds)
time_active: number
// Total size (bytes) of all file in this torrent (including unselected ones)
total_size: number
// The first tracker with working status. Returns empty string if no tracker is working.
tracker: string
// Torrent upload speed limit (bytes/s). -1 if unlimited.
up_limit: number
// Amount of data uploaded
uploaded: number
// Amount of data uploaded this session
uploaded_session: number
// Torrent upload speed (bytes/s)
upspeed: number
}

View file

@ -0,0 +1,20 @@
import type { Priority } from '@/enums/qbit'
export default interface TorrentFile {
/** Percentage of file pieces currently available (percentage/100) */
availability: number
/** File index */
index: number
/** True if file is seeding/complete */
is_seed: boolean
/** File name (including relative path) */
name: string
/** The first number is the starting piece index and the second number is the ending piece index (inclusive) */
piece_range: [number, number]
/** File priority. See possible values here below */
priority: Priority
/** File progress (percentage/100) */
progress: number
/** File size (bytes) */
size: number
}

View file

@ -0,0 +1,68 @@
export default interface TorrentProperties {
/** When this torrent was added (unix timestamp) */
addition_date: number
/** Torrent comment */
comment: string
/** Torrent completion date (unix timestamp) */
completion_date: number
/** Torrent creator */
created_by: string
/** Torrent creation date (Unix timestamp) */
creation_date: number
/** Torrent download limit (bytes/s) */
dl_limit: number
/** Torrent download speed (bytes/second) */
dl_speed: number
/** Torrent average download speed (bytes/second) */
dl_speed_avg: number
/** Torrent ETA (seconds) */
eta: number
/** Last seen complete date (unix timestamp) */
last_seen: number
/** Torrent connection count */
nb_connections: number
/** Torrent connection count limit */
nb_connections_limit: number
/** Number of peers connected to */
peers: number
/** Number of peers in the swarm */
peers_total: number
/** Torrent piece size (bytes) */
piece_size: number
/** Number of pieces owned */
pieces_have: number
/** Number of pieces of the torrent */
pieces_num: number
/** Number of seconds until the next announce */
reannounce: number
/** Torrent save path */
save_path: string
/** Torrent elapsed time while complete (seconds) */
seeding_time: number
/** Number of seeds connected to */
seeds: number
/** Number of seeds in the swarm */
seeds_total: number
/** Torrent share ratio */
share_ratio: number
/** Torrent elapsed time (seconds) */
time_elapsed: number
/** Total data downloaded for torrent (bytes) */
total_downloaded: number
/** Total data downloaded this session (bytes) */
total_downloaded_session: number
/** Torrent total size (bytes) */
total_size: number
/** Total data uploaded for torrent (bytes) */
total_uploaded: number
/** Total data uploaded this session (bytes) */
total_uploaded_session: number
/** Total data wasted for torrent (bytes) */
total_wasted: number
/** Torrent upload limit (bytes/s) */
up_limit: number
/** Torrent upload speed (bytes/second) */
up_speed: number
/** Torrent average upload speed (bytes/second) */
up_speed_avg: number
}

View file

@ -0,0 +1,20 @@
import type { TrackerStatus } from '@/enums/qbit'
export default interface Tracker {
/** Tracker message (there is no way of knowing what this message is - it's up to tracker admins) */
msg: string
/** Number of completed downlods for current torrent, as reported by the tracker */
num_downloaded: number
/** Number of leeches for current torrent, as reported by the tracker */
num_leeches: number
/** Number of peers for current torrent, as reported by the tracker */
num_peers: number
/** Number of seeds for current torrent, asreported by the tracker */
num_seeds: number
/** Tracker status. See the table below for possible values */
status: TrackerStatus
/** Tracker priority tier. Lower tier trackers are tried before higher tiers. Tier numbers are valid when >= 0, < 0 is used as placeholder when tier does not exist for special entries (such as DHT). */
tier: number
/** Tracker url */
url: string
}

View file

@ -0,0 +1,34 @@
import type AppPreferences from './AppPreferences'
import type Category from './Category'
import type ServerState from './ServerState'
import type Torrent from './Torrent'
import type Tracker from './Tracker'
import type Peer from './Peer'
import type TorrentFile from './TorrentFile'
import type TorrentProperties from './TorrentProperties'
import type FeedRule from './FeedRule'
import type Feed from './Feed'
import type SearchPlugin from './SearchPlugin'
import type SearchJob from './SearchJob'
import type SearchStatus from './SearchStatus'
import type SearchResult from './SearchResult'
type ApplicationVersion = string
export type {
ApplicationVersion,
AppPreferences,
Category,
ServerState,
Tracker,
Torrent,
Peer,
TorrentFile,
TorrentProperties,
FeedRule,
Feed,
SearchPlugin,
SearchJob,
SearchStatus,
SearchResult
}

View file

@ -0,0 +1,36 @@
import type { BasePayload } from '.'
export default interface AddTorrentPayload extends BasePayload {
/** Whether Automatic Torrent Management should be used */
autoTMM?: boolean
/** Category for the torrent */
category?: string
/** Cookie sent to download the .torrent file */
cookie?: string
/** Set torrent download speed limit. Unit in bytes/second */
dlLimit?: number
/** Prioritize download first last piece. Possible values are true, false (default) */
firstLastPiecePrio?: boolean
/** Add torrents in the paused state. Possible values are true, false (default) */
paused?: boolean
/** Set torrent share ratio limit */
ratioLimit?: number
/** Rename torrent */
rename?: string
/** Create the root folder. Possible values are true, false, unset (default) */
root_folder?: boolean
/** Download folder */
savepath?: string
/** Set torrent seeding time limit. Unit in minutes */
seedingTimeLimit?: number
/** Enable sequential download. Possible values are true, false (default) */
sequentialDownload?: boolean
/** Skip hash checking. Possible values are true, false (default) */
skip_checking?: boolean
/** Tags for the torrent, split by ',' */
tags?: string
/** Set torrent upload speed limit. Unit in bytes/second */
upLimit?: number
/** URLs separated with newlines */
urls?: string
}

View file

@ -0,0 +1,3 @@
import type { AppPreferences } from '@/types/qbit/models'
export type AppPreferencesPayload = Partial<AppPreferences>

View file

@ -0,0 +1 @@
export default interface BasePayload extends Record<string, any> {}

View file

@ -0,0 +1,8 @@
import type { BasePayload } from '.'
export default interface LoginPayload extends BasePayload {
/** Username used to access the WebUI */
username: string
/** Password used to access the WebUI */
password: string
}

View file

@ -0,0 +1,7 @@
import type { Optional } from '@/global'
import type { BasePayload } from '.'
export default interface PeerLogPayload extends BasePayload {
// Exclude messages with "message id" <= last_known_id (default: -1)
last_known_id: Optional<number>
}

View file

@ -0,0 +1,7 @@
import type LoginPayload from './LoginPayload'
import type AddTorrentPayload from './AddTorrentPayload'
import type PeerLogPayload from './PeerLogPayload'
import type { AppPreferencesPayload } from './AppPreferencesPayload'
import type BasePayload from './BasePayload'
export { AppPreferencesPayload, LoginPayload, AddTorrentPayload, PeerLogPayload, BasePayload }

View file

@ -0,0 +1,17 @@
import type { Category, Torrent, ServerState } from '@/types/qbit/models'
import type { Optional } from '@/global'
export default interface MainDataResponse {
// Response ID
rid: number
// Whether the response contains all the data or partial data
fullUpdate: Optional<boolean>
torrents: Optional<Record<string, Torrent>>
torrents_removed: Optional<string[]>
categories: Optional<Record<string, Category>>
categories_removed: Optional<Category[]>
tags: Optional<string[]>
tags_removed: Optional<string[]>
trackers: Optional<Record<string, string[]>>
server_state?: Optional<ServerState>
}

View file

@ -0,0 +1,14 @@
interface PeerLog {
// ID of the peer
id: number
// IP of the peer
ip: string
// Milliseconds since epoch
timestamp: number
// Whether or not the peer was blocked
blocked: boolean
// Reason of the block
reason: string
}
export type PeerLogResponse = PeerLog[]

View file

@ -0,0 +1,10 @@
import type { SearchResult } from '@/types/qbit/models'
export default interface SearchResultsResponse {
/** Array of result objects- see table below */
results: SearchResult[]
/** 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 continue to increase */
total: number
}

View file

@ -0,0 +1,8 @@
import type { Peer } from '@/types/qbit/models'
export default interface TorrentPeersResponse {
full_update?: boolean
rid: number
peers: Record<string, Peer>
show_flags: boolean
}

View file

@ -0,0 +1,6 @@
import type MainDataResponse from './MainDataResponse'
import type { PeerLogResponse } from './PeerLogResponse'
import type TorrentPeersResponse from './TorrentPeersResponse'
import type SearchResultsResponse from './SearchResultsResponse'
export { MainDataResponse, PeerLogResponse, TorrentPeersResponse, SearchResultsResponse }

View file

@ -0,0 +1,4 @@
export default interface Category {
name: string
savePath: string
}

View file

@ -0,0 +1,5 @@
export default interface ModalTemplate {
component: string
props: Object
guid: string
}

View file

@ -0,0 +1,13 @@
import type { Optional } from '@/global'
import type {TorrentState} from "@/enums/vuetorrent"
export default interface SortOptions {
isCustomSortEnabled: boolean
sort: string
reverse: boolean
hashes: string[]
filter: Optional<TorrentState>
category: Optional<string>
tag: Optional<string>
tracker: Optional<string>
}

View file

@ -0,0 +1,42 @@
import type Feed from './rss/Feed'
import type FeedRule from './rss/FeedRule'
import type Category from '../qbit/models/Category'
import type Torrent from '@/models/Torrent'
import type SortOptions from './SortOptions'
import type { AppPreferences } from '../qbit/models'
import type { Optional } from '@/global'
import type ModalTemplate from './ModalTemplate'
import type {Status} from '@/models'
import type WebUISettings from '@/types/vuetorrent/WebUISettings'
import type {SearchPlugin} from "@/types/qbit/models";
export default interface StoreState {
authenticated: boolean
categories: Category[]
dashboard: {
currentPage: number
searchFilter: string
}
download_data: number[]
filteredTorrentsCount: number
intervals: NodeJS.Timer[]
latestSelectedTorrent: number
modals: ModalTemplate[]
rid?: number
rss: {
feeds: Feed[]
rules: FeedRule[]
}
searchPlugins: SearchPlugin[]
selectMode: boolean
selected_torrents: string[]
settings: Optional<AppPreferences>
sort_options: SortOptions
status: Status
tags: string[]
torrents: Torrent[]
trackers: string[]
upload_data: number[]
version: string
webuiSettings: WebUISettings
}

View file

@ -0,0 +1,5 @@
import type {Tracker as QbitTracker} from '../qbit/models'
export default interface Tracker extends QbitTracker {
isSelectable: boolean
}

View file

@ -0,0 +1,17 @@
export interface TreeNode {
name: string
fullName: string
children: TreeNode[]
}
export interface TreeFile extends TreeNode {
id: number
progress: number
size: string
icon: string
priority: number
}
export interface TreeFolder extends TreeNode {
type: 'directory'
}

View file

@ -0,0 +1,31 @@
import type {TitleOptions} from "@/enums/vuetorrent"
export interface TorrentProperty {
name: string
active: boolean
}
export interface TorrentPropertyLocalized extends TorrentProperty {
label: string
}
export default interface WebUISettings {
lang: string
darkTheme: boolean
showFreeSpace: boolean
showSpeedGraph: boolean
showSessionStat: boolean
showAlltimeStat: boolean
showCurrentSpeed: boolean
showTrackerFilter: boolean
showSpeedInTitle: boolean
deleteWithFiles: boolean
title: TitleOptions
rightDrawer: boolean
topPagination: boolean
paginationSize: number
dateFormat: string
openSideBarOnStart: boolean
busyTorrentProperties: TorrentProperty[]
doneTorrentProperties: TorrentProperty[]
}

View file

@ -0,0 +1,15 @@
import type Category from "./Category"
import type Feed from './rss/Feed'
import type FeedRule from './rss/FeedRule'
import type SearchStatus from "./search/SearchStatus"
import type SearchResult from "./search/SearchResult"
import type ModalTemplate from './ModalTemplate'
import type SortOptions from './SortOptions'
import type StoreState from './StoreState'
import type { TreeNode, TreeFile, TreeFolder } from './TreeObjects'
import type {TorrentProperty, TorrentPropertyLocalized} from "@/types/vuetorrent/WebUISettings"
import type Tracker from './Tracker'
export type ComponentRule = (value: string) => boolean|string
export { Category, Feed, FeedRule, SearchStatus, SearchResult, ModalTemplate, SortOptions, StoreState, TreeNode, TreeFile, TreeFolder, TorrentProperty, TorrentPropertyLocalized, Tracker }

View file

@ -0,0 +1,5 @@
export default interface Feed {
name: string
uid?: string
url: string
}

View file

@ -0,0 +1,16 @@
export default interface FeedRule {
addPaused?: boolean
affectedFeeds?: string[]
assignedCategory?: string
enabled: boolean
episodeFilter?: string
ignoreDays?: number
lastMatch?: string
mustContain?: string
mustNotContain?: string
name: string
previouslyMatchedEpisodes?: unknown[]
savePath?: string
smartFilter?: boolean
useRegex?: boolean
}

View file

@ -0,0 +1,9 @@
export default interface SearchResult {
descrLink: string
fileName: string
fileSize: number
fileUrl: string
nbLeechers: number
nbSeeders: number
siteUrl: string
}

View file

@ -0,0 +1,9 @@
import type {SearchResult} from "..";
import type {Optional} from "@/global";
export default interface SearchStatus {
id: number
status: 'Running' | 'Stopped'
interval: Optional<NodeJS.Timer>
results: SearchResult[]
}

Some files were not shown because too many files have changed in this diff Show more