]>
Commit | Line | Data |
---|---|---|
51ba5ff5 | 1 | <template> |
2 | <Generic :item="item"> | |
3 | <template #content> | |
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> | |
7 | <template v-else> | |
8 | <span class="down"> | |
9 | <i class="fas fa-download"></i> {{ downRate }} | |
10 | </span> | |
bdad8933 | 11 | <span class="up"> <i class="fas fa-upload"></i> {{ upRate }} </span> |
51ba5ff5 | 12 | </template> |
13 | </p> | |
14 | </template> | |
15 | <template #indicator> | |
bdad8933 BW |
16 | <span v-if="!error" class="count" |
17 | >{{ count }} | |
51ba5ff5 | 18 | <template v-if="count === 1">torrent</template> |
19 | <template v-else>torrents</template> | |
20 | </span> | |
21 | </template> | |
22 | </Generic> | |
23 | </template> | |
24 | ||
25 | <script> | |
bdad8933 | 26 | import Generic from "./Generic.vue"; |
51ba5ff5 | 27 | |
28 | // Units to add to download and upload rates. | |
bdad8933 | 29 | const units = ["B", "kiB", "MiB", "GiB"]; |
51ba5ff5 | 30 | |
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) => { | |
35 | let i = 0; | |
bdad8933 | 36 | |
51ba5ff5 | 37 | while (rate > 1000 && i < units.length) { |
38 | rate /= 1000; | |
39 | i++; | |
40 | } | |
41 | ||
bdad8933 BW |
42 | return ( |
43 | Intl.NumberFormat(undefined, { maximumFractionDigits: 2 }).format( | |
44 | rate || 0 | |
45 | ) + ` ${units[i]}/s` | |
46 | ); | |
47 | }; | |
51ba5ff5 | 48 | |
49 | export default { | |
bdad8933 BW |
50 | name: "rTorrent", |
51 | props: { item: Object }, | |
52 | components: { Generic }, | |
51ba5ff5 | 53 | // Properties for download, upload, torrent count and errors. |
bdad8933 | 54 | data: () => ({ dl: null, ul: null, count: null, error: null }), |
51ba5ff5 | 55 | // Computed properties for the rate labels. |
56 | computed: { | |
bdad8933 | 57 | downRate: function () { |
51ba5ff5 | 58 | return displayRate(this.dl); |
59 | }, | |
bdad8933 | 60 | upRate: function () { |
51ba5ff5 | 61 | return displayRate(this.ul); |
62 | }, | |
63 | }, | |
64 | created() { | |
65 | // Set intervals if configured so the rates and/or torrent count | |
66 | // will be updated. | |
67 | const rateInterval = parseInt(this.item.rateInterval, 10) || 0; | |
68 | const torrentInterval = parseInt(this.item.torrentInterval, 10) || 0; | |
69 | ||
70 | if (rateInterval > 0) { | |
71 | setInterval(() => this.fetchRates(), rateInterval); | |
72 | } | |
73 | ||
74 | if (torrentInterval > 0) { | |
75 | setInterval(() => this.fetchCount(), torrentInterval); | |
76 | } | |
bdad8933 | 77 | |
51ba5ff5 | 78 | // Fetch the initial values. |
79 | this.fetchRates(); | |
80 | this.fetchCount(); | |
81 | }, | |
82 | methods: { | |
83 | // Perform two calls to the XML-RPC service and fetch download | |
84 | // and upload rates. Values are saved to the `ul` and `dl` | |
85 | // properties. | |
bdad8933 BW |
86 | fetchRates: async function () { |
87 | this.getRate("throttle.global_up.rate") | |
88 | .then((ul) => (this.ul = ul)) | |
89 | .catch(() => (this.error = true)); | |
51ba5ff5 | 90 | |
bdad8933 BW |
91 | this.getRate("throttle.global_down.rate") |
92 | .then((dl) => (this.dl = dl)) | |
93 | .catch(() => (this.error = true)); | |
51ba5ff5 | 94 | }, |
95 | // Perform a call to the XML-RPC service to fetch the number of | |
96 | // torrents. | |
bdad8933 BW |
97 | fetchCount: async function () { |
98 | this.getCount().catch(() => (this.error = true)); | |
51ba5ff5 | 99 | }, |
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 | |
103 | // value. | |
bdad8933 BW |
104 | getRate: async function (methodName) { |
105 | return this.getXml(methodName).then((xml) => | |
106 | parseInt( | |
107 | xml.getElementsByTagName("value")[0].firstChild.textContent, | |
108 | 10 | |
109 | ) | |
110 | ); | |
51ba5ff5 | 111 | }, |
112 | // Fetch the numer of torrents by requesting the download list | |
113 | // and counting the number of entries therein. | |
bdad8933 BW |
114 | getCount: async function () { |
115 | return this.getXml("download_list").then((xml) => { | |
116 | const arrayEl = xml.getElementsByTagName("array"); | |
117 | this.count = arrayEl | |
118 | ? arrayEl[0].getElementsByTagName("value").length | |
119 | : 0; | |
120 | }); | |
51ba5ff5 | 121 | }, |
122 | // Perform a call to the XML-RPC service and parse the response | |
123 | // as XML, which is then returned. | |
bdad8933 BW |
124 | getXml: async function (methodName) { |
125 | const headers = { "Content-Type": "text/xml" }; | |
51ba5ff5 | 126 | |
127 | if (this.item.username && this.item.password) { | |
bdad8933 BW |
128 | headers[ |
129 | "Authorization" | |
130 | ] = `${this.item.username}:${this.item.password}`; | |
51ba5ff5 | 131 | } |
132 | ||
bdad8933 BW |
133 | return fetch(`${this.item.xmlrpc.replace(/\/$/, "")}/RPC2`, { |
134 | method: "POST", | |
51ba5ff5 | 135 | headers, |
bdad8933 | 136 | body: `<methodCall><methodName>${methodName}</methodName></methodCall>`, |
51ba5ff5 | 137 | }) |
bdad8933 BW |
138 | .then((response) => { |
139 | if (!response.ok) { | |
140 | throw Error(response.statusText); | |
141 | } | |
51ba5ff5 | 142 | |
bdad8933 BW |
143 | return response.text(); |
144 | }) | |
145 | .then((text) => | |
146 | Promise.resolve(new DOMParser().parseFromString(text, "text/xml")) | |
147 | ); | |
148 | }, | |
149 | }, | |
150 | }; | |
51ba5ff5 | 151 | </script> |
152 | ||
153 | <style scoped lang="scss"> | |
154 | .error { | |
155 | color: #e51111 !important; | |
156 | } | |
157 | .down { | |
158 | margin-right: 1em; | |
159 | } | |
160 | .count { | |
161 | color: var(--text); | |
162 | font-size: 0.8em; | |
163 | } | |
bdad8933 | 164 | </style> |