4 <p class="title is-4">{{ item.name }}</p>
5 <p class="subtitle is-6">
6 <span v-if="error" class="error">An error has occurred.</span>
9 <i class="fas fa-download"></i> {{ downRate }}
12 <i class="fas fa-upload"></i> {{ upRate }}
18 <span v-if="!error" class="count">{{ count }}
19 <template v-if="count === 1">torrent</template>
20 <template v-else>torrents</template>
27 import Generic from './Generic.vue';
29 // Units to add to download and upload rates.
30 const units = ['B', 'kiB', 'MiB', 'GiB'];
32 // Take the rate in bytes and keep dividing it by 1k until the lowest
33 // value for which we have a unit is determined. Return the value with
34 // up to two decimals as a string and unit/s appended.
35 const displayRate = (rate) => {
38 while (rate > 1000 && i < units.length) {
43 return Intl.NumberFormat(undefined, {maximumFractionDigits: 2})
44 .format(rate || 0) + ` ${units[i]}/s`;
49 props: {item: Object},
50 components: {Generic},
51 // Properties for download, upload, torrent count and errors.
52 data: () => ({dl: null, ul: null, count: null, error: null}),
53 // Computed properties for the rate labels.
55 downRate: function() {
56 return displayRate(this.dl);
59 return displayRate(this.ul);
63 // Set intervals if configured so the rates and/or torrent count
65 const rateInterval = parseInt(this.item.rateInterval, 10) || 0;
66 const torrentInterval = parseInt(this.item.torrentInterval, 10) || 0;
68 if (rateInterval > 0) {
69 setInterval(() => this.fetchRates(), rateInterval);
72 if (torrentInterval > 0) {
73 setInterval(() => this.fetchCount(), torrentInterval);
76 // Fetch the initial values.
81 // Perform two calls to the XML-RPC service and fetch download
82 // and upload rates. Values are saved to the `ul` and `dl`
84 fetchRates: async function() {
85 this.getRate('throttle.global_up.rate')
86 .then((ul) => this.ul = ul)
87 .catch(() => this.error = true);
89 this.getRate('throttle.global_down.rate')
90 .then((dl) => this.dl = dl)
91 .catch(() => this.error = true);
93 // Perform a call to the XML-RPC service to fetch the number of
95 fetchCount: async function() {
96 this.getCount().catch(() => this.error = true);
98 // Fetch a numeric value from the XML-RPC service by requesting
99 // the specified method name and parsing the XML. The response
100 // is expected to adhere to the structure of a single numeric
102 getRate: async function(methodName) {
103 return this.getXml(methodName)
104 .then((xml) => parseInt(xml.getElementsByTagName('value')[0].firstChild.textContent, 10));
106 // Fetch the numer of torrents by requesting the download list
107 // and counting the number of entries therein.
108 getCount: async function() {
109 return this.getXml('download_list')
111 const arrayEl = xml.getElementsByTagName('array');
112 this.count = arrayEl ? arrayEl[0].getElementsByTagName('value').length : 0;
115 // Perform a call to the XML-RPC service and parse the response
116 // as XML, which is then returned.
117 getXml: async function(methodName) {
118 const headers = {'Content-Type': 'text/xml'};
120 if (this.item.username && this.item.password) {
121 headers['Authorization'] = `${this.item.username}:${this.item.password}`;
124 return fetch(`${this.item.xmlrpc.replace(/\/$/, '')}/RPC2`, {
127 body: `<methodCall><methodName>${methodName}</methodName></methodCall>`
129 .then((response) => {
131 throw Error(response.statusText);
134 return response.text();
136 .then((text) => Promise.resolve(new DOMParser().parseFromString(text, 'text/xml')));
142 <style scoped lang="scss">
144 color: #e51111 !important;