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 }}
11 <span class="up"> <i class="fas fa-upload"></i> {{ upRate }} </span>
16 <span v-if="!error" class="count"
18 <template v-if="count === 1">torrent</template>
19 <template v-else>torrents</template>
26 import Generic from "./Generic.vue";
28 // Units to add to download and upload rates.
29 const units = ["B", "kiB", "MiB", "GiB"];
31 // Take the rate in bytes and keep dividing it by 1k until the lowest
32 // value for which we have a unit is determined. Return the value with
33 // up to two decimals as a string and unit/s appended.
34 const displayRate = (rate) => {
37 while (rate > 1000 && i < units.length) {
43 Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }).format(
51 props: { item: Object },
52 components: { Generic },
53 // Properties for download, upload, torrent count and errors.
54 data: () => ({ dl: null, ul: null, count: null, error: null }),
55 // Computed properties for the rate labels.
57 downRate: function () {
58 return displayRate(this.dl);
61 return displayRate(this.ul);
65 // Set intervals if configured so the rates and/or torrent count
67 const rateInterval = parseInt(this.item.rateInterval, 10) || 0;
68 const torrentInterval = parseInt(this.item.torrentInterval, 10) || 0;
70 if (rateInterval > 0) {
71 setInterval(() => this.fetchRates(), rateInterval);
74 if (torrentInterval > 0) {
75 setInterval(() => this.fetchCount(), torrentInterval);
78 // Fetch the initial values.
83 // Perform two calls to the XML-RPC service and fetch download
84 // and upload rates. Values are saved to the `ul` and `dl`
86 fetchRates: async function () {
87 this.getRate("throttle.global_up.rate")
88 .then((ul) => (this.ul = ul))
89 .catch(() => (this.error = true));
91 this.getRate("throttle.global_down.rate")
92 .then((dl) => (this.dl = dl))
93 .catch(() => (this.error = true));
95 // Perform a call to the XML-RPC service to fetch the number of
97 fetchCount: async function () {
98 this.getCount().catch(() => (this.error = true));
100 // Fetch a numeric value from the XML-RPC service by requesting
101 // the specified method name and parsing the XML. The response
102 // is expected to adhere to the structure of a single numeric
104 getRate: async function (methodName) {
105 return this.getXml(methodName).then((xml) =>
107 xml.getElementsByTagName("value")[0].firstChild.textContent,
112 // Fetch the numer of torrents by requesting the download list
113 // and counting the number of entries therein.
114 getCount: async function () {
115 return this.getXml("download_list").then((xml) => {
116 const arrayEl = xml.getElementsByTagName("array");
118 ? arrayEl[0].getElementsByTagName("value").length
122 // Perform a call to the XML-RPC service and parse the response
123 // as XML, which is then returned.
124 getXml: async function (methodName) {
125 const headers = { "Content-Type": "text/xml" };
127 if (this.item.username && this.item.password) {
130 ] = `${this.item.username}:${this.item.password}`;
133 return fetch(`${this.item.xmlrpc.replace(/\/$/, "")}/RPC2`, {
136 body: `<methodCall><methodName>${methodName}</methodName></methodCall>`,
138 .then((response) => {
140 throw Error(response.statusText);
143 return response.text();
146 Promise.resolve(new DOMParser().parseFromString(text, "text/xml")),
153 <style scoped lang="scss">
155 color: #e51111 !important;