diff options
-rw-r--r-- | docs/configuration.md | 5 | ||||
-rw-r--r-- | docs/customservices.md | 23 | ||||
-rw-r--r-- | package.json | 6 | ||||
-rw-r--r-- | src/assets/app.scss | 10 | ||||
-rw-r--r-- | src/components/services/AdGuardHome.vue | 6 | ||||
-rw-r--r-- | src/components/services/OpenWeather.vue | 135 | ||||
-rw-r--r-- | src/components/services/PaperlessNG.vue | 1 | ||||
-rw-r--r-- | src/components/services/PiHole.vue | 4 | ||||
-rw-r--r-- | src/components/services/Ping.vue | 6 | ||||
-rw-r--r-- | src/components/services/Radarr.vue | 10 | ||||
-rw-r--r-- | src/components/services/Sonarr.vue | 10 | ||||
-rw-r--r-- | yarn.lock | 15 |
12 files changed, 196 insertions, 35 deletions
diff --git a/docs/configuration.md b/docs/configuration.md index f160ae0..68711ec 100644 --- a/docs/configuration.md +++ b/docs/configuration.md | |||
@@ -7,7 +7,7 @@ Title, icons, links, colors, and services can be configured in the `config.yml` | |||
7 | # Homepage configuration | 7 | # Homepage configuration |
8 | # See https://fontawesome.com/icons for icons options | 8 | # See https://fontawesome.com/icons for icons options |
9 | 9 | ||
10 | # Optional: Use external configuration file. | 10 | # Optional: Use external configuration file. |
11 | # Using this will ignore remaining config in this file | 11 | # Using this will ignore remaining config in this file |
12 | # externalConfig: https://example.com/server-luci/config.yaml | 12 | # externalConfig: https://example.com/server-luci/config.yaml |
13 | 13 | ||
@@ -136,6 +136,9 @@ services: | |||
136 | # background: red # optional color for card to set color directly without custom stylesheet | 136 | # background: red # optional color for card to set color directly without custom stylesheet |
137 | ``` | 137 | ``` |
138 | 138 | ||
139 | |||
140 | View [Custom Services](customservices.md) for details about all available custom services (like PiHole) and how to configure them. | ||
141 | |||
139 | If you choose to fetch message information from an endpoint, the output format should be as follows (or you can [custom map fields as shown in tips-and-tricks](./tips-and-tricks.md#mapping-fields)): | 142 | If you choose to fetch message information from an endpoint, the output format should be as follows (or you can [custom map fields as shown in tips-and-tricks](./tips-and-tricks.md#mapping-fields)): |
140 | 143 | ||
141 | ```json | 144 | ```json |
diff --git a/docs/customservices.md b/docs/customservices.md index 43f45f4..150e232 100644 --- a/docs/customservices.md +++ b/docs/customservices.md | |||
@@ -19,6 +19,27 @@ The following configuration is available for the PiHole service. | |||
19 | type: "PiHole" | 19 | type: "PiHole" |
20 | ``` | 20 | ``` |
21 | 21 | ||
22 | |||
23 | ## OpenWeatherMap | ||
24 | |||
25 | Using the OpenWeatherMap service you can display weather information about a given location. | ||
26 | The following configuration is available for the OpenWeatherMap service: | ||
27 | |||
28 | ``` | ||
29 | items: | ||
30 | - name: "Weather" | ||
31 | location: "Amsterdam" # your location. | ||
32 | locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy | ||
33 | apiKey: "<---insert-api-key-here--->" # insert your own API key here. Request one from https://openweathermap.org/api. | ||
34 | units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin. | ||
35 | background: "square" # choose which type of background you want behind the image. Can be one of: square, cicle, none. Defaults to none. | ||
36 | type: "OpenWeather" | ||
37 | ``` | ||
38 | |||
39 | **Remarks:** | ||
40 | If for some reason your city can't be found by entering the name in the `location` property, you could also try to configure the OWM city ID in the `locationId` property. To retrieve your specific City ID, go to the [OWM website](https://openweathermap.org), search for your city and retrieve the ID from the URL (for example, the City ID of Amsterdam is 2759794). | ||
41 | |||
42 | |||
22 | ## Medusa | 43 | ## Medusa |
23 | 44 | ||
24 | This service displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application. | 45 | This service displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application. |
@@ -50,4 +71,4 @@ For Paperless you need an API-Key which you have to store at the item in the fie | |||
50 | 71 | ||
51 | ## Ping | 72 | ## Ping |
52 | 73 | ||
53 | For Paperless you need an API-Key which you have to store at the item in the field `apikey`. | 74 | For Ping you need an API-Key which you have to store at the item in the field `apikey`. |
diff --git a/package.json b/package.json index 61b3073..c5486bb 100644 --- a/package.json +++ b/package.json | |||
@@ -1,6 +1,6 @@ | |||
1 | { | 1 | { |
2 | "name": "homer", | 2 | "name": "homer", |
3 | "version": "21.07.1", | 3 | "version": "21.09.1", |
4 | "license": "Apache-2.0", | 4 | "license": "Apache-2.0", |
5 | "scripts": { | 5 | "scripts": { |
6 | "serve": "vue-cli-service serve", | 6 | "serve": "vue-cli-service serve", |
@@ -8,9 +8,9 @@ | |||
8 | "lint": "vue-cli-service lint" | 8 | "lint": "vue-cli-service lint" |
9 | }, | 9 | }, |
10 | "dependencies": { | 10 | "dependencies": { |
11 | "@fortawesome/fontawesome-free": "^5.15.3", | 11 | "@fortawesome/fontawesome-free": "^5.15.4", |
12 | "bulma": "^0.9.3", | 12 | "bulma": "^0.9.3", |
13 | "core-js": "^3.15.2", | 13 | "core-js": "^3.17.3", |
14 | "js-yaml": "^4.1.0", | 14 | "js-yaml": "^4.1.0", |
15 | "lodash.merge": "^4.6.2", | 15 | "lodash.merge": "^4.6.2", |
16 | "register-service-worker": "^1.7.2", | 16 | "register-service-worker": "^1.7.2", |
diff --git a/src/assets/app.scss b/src/assets/app.scss index 6bb5068..f2dfb37 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss | |||
@@ -106,7 +106,7 @@ body { | |||
106 | } | 106 | } |
107 | 107 | ||
108 | .first-line { | 108 | .first-line { |
109 | height: 100px; | 109 | min-height: 100px; |
110 | vertical-align: center; | 110 | vertical-align: center; |
111 | background-color: var(--highlight-primary); | 111 | background-color: var(--highlight-primary); |
112 | 112 | ||
@@ -121,7 +121,7 @@ body { | |||
121 | } | 121 | } |
122 | 122 | ||
123 | .container { | 123 | .container { |
124 | height: 80px; | 124 | min-height: 80px; |
125 | padding: 10px 0; | 125 | padding: 10px 0; |
126 | } | 126 | } |
127 | 127 | ||
@@ -140,8 +140,7 @@ body { | |||
140 | } | 140 | } |
141 | } | 141 | } |
142 | } | 142 | } |
143 | .navbar, | 143 | .navbar { |
144 | .navbar-menu { | ||
145 | background-color: var(--highlight-secondary); | 144 | background-color: var(--highlight-secondary); |
146 | 145 | ||
147 | a { | 146 | a { |
@@ -153,6 +152,9 @@ body { | |||
153 | background-color: var(--highlight-hover); | 152 | background-color: var(--highlight-hover); |
154 | } | 153 | } |
155 | } | 154 | } |
155 | .navbar-menu { | ||
156 | background-color: inherit; | ||
157 | } | ||
156 | } | 158 | } |
157 | .navbar-end { | 159 | .navbar-end { |
158 | text-align: right; | 160 | text-align: right; |
diff --git a/src/components/services/AdGuardHome.vue b/src/components/services/AdGuardHome.vue index d4a2b89..19a2f7d 100644 --- a/src/components/services/AdGuardHome.vue +++ b/src/components/services/AdGuardHome.vue | |||
@@ -51,9 +51,9 @@ export default { | |||
51 | }, | 51 | }, |
52 | methods: { | 52 | methods: { |
53 | fetchStatus: async function () { | 53 | fetchStatus: async function () { |
54 | this.status = await fetch(`${this.item.url}/control/status`).then( | 54 | this.status = await fetch(`${this.item.url}/control/status`, { |
55 | (response) => response.json() | 55 | credentials: "include", |
56 | ); | 56 | }).then((response) => response.json()); |
57 | }, | 57 | }, |
58 | }, | 58 | }, |
59 | }; | 59 | }; |
diff --git a/src/components/services/OpenWeather.vue b/src/components/services/OpenWeather.vue new file mode 100644 index 0000000..09ff76a --- /dev/null +++ b/src/components/services/OpenWeather.vue | |||
@@ -0,0 +1,135 @@ | |||
1 | <template> | ||
2 | <div> | ||
3 | <div class="card" :class="item.class"> | ||
4 | <a | ||
5 | :href="`https://openweathermap.org/city/${id}`" | ||
6 | :target="item.target" | ||
7 | rel="noreferrer" | ||
8 | > | ||
9 | <div class="card-content"> | ||
10 | <div class="media"> | ||
11 | <div v-if="icon" class="media-left" :class="item.background"> | ||
12 | <figure class="image is-48x48"> | ||
13 | <img | ||
14 | :src="`https://openweathermap.org/img/wn/${icon}@2x.png`" | ||
15 | :alt="conditions" | ||
16 | :title="conditions" | ||
17 | /> | ||
18 | </figure> | ||
19 | </div> | ||
20 | <div class="media-content"> | ||
21 | <p v-if="error" class="error">Data could not be retrieved</p> | ||
22 | <div v-else> | ||
23 | <p class="title is-4">{{ name }}</p> | ||
24 | <p class="subtitle is-6"> | ||
25 | {{ temp | tempSuffix(this.item.units) }} | ||
26 | </p> | ||
27 | </div> | ||
28 | </div> | ||
29 | </div> | ||
30 | <div class="tag" :class="item.tagstyle" v-if="item.tag"> | ||
31 | <strong class="tag-text">#{{ item.tag }}</strong> | ||
32 | </div> | ||
33 | </div> | ||
34 | </a> | ||
35 | </div> | ||
36 | </div> | ||
37 | </template> | ||
38 | |||
39 | <script> | ||
40 | export default { | ||
41 | name: "OpenWeather", | ||
42 | props: { | ||
43 | item: Object, | ||
44 | }, | ||
45 | data: () => ({ | ||
46 | id: null, | ||
47 | icon: null, | ||
48 | name: null, | ||
49 | temp: null, | ||
50 | conditions: null, | ||
51 | error: false, | ||
52 | }), | ||
53 | created() { | ||
54 | this.fetchWeather(); | ||
55 | }, | ||
56 | methods: { | ||
57 | fetchWeather: async function () { | ||
58 | let locationQuery; | ||
59 | |||
60 | // Use location ID if specified, otherwise retrieve value from location (name). | ||
61 | if (this.item.locationId) { | ||
62 | locationQuery = `id=${this.item.locationId}`; | ||
63 | } else { | ||
64 | locationQuery = `q=${this.item.location}`; | ||
65 | } | ||
66 | |||
67 | const url = `https://api.openweathermap.org/data/2.5/weather?${locationQuery}&appid=${this.item.apiKey}&units=${this.item.units}`; | ||
68 | fetch(url) | ||
69 | .then((response) => { | ||
70 | if (!response.ok) { | ||
71 | throw Error(response.statusText); | ||
72 | } | ||
73 | return response.json(); | ||
74 | }) | ||
75 | .then((weather) => { | ||
76 | this.id = weather.id; | ||
77 | this.name = weather.name; | ||
78 | this.temp = parseInt(weather.main.temp).toFixed(1); | ||
79 | this.icon = weather.weather[0].icon; | ||
80 | this.conditions = weather.weather[0].description; | ||
81 | }) | ||
82 | .catch((e) => { | ||
83 | console.log(e); | ||
84 | this.error = true; | ||
85 | }); | ||
86 | }, | ||
87 | }, | ||
88 | filters: { | ||
89 | tempSuffix: function (value, type) { | ||
90 | if (!value) return ""; | ||
91 | |||
92 | let unit = "K"; | ||
93 | if (type === "metric") { | ||
94 | unit = "°C"; | ||
95 | } else if (type === "imperial") { | ||
96 | unit = "°F"; | ||
97 | } | ||
98 | return `${value} ${unit}`; | ||
99 | }, | ||
100 | }, | ||
101 | }; | ||
102 | </script> | ||
103 | |||
104 | <style scoped lang="scss"> | ||
105 | // Add a border around the weather image. | ||
106 | // Otherwise the image is not always distinguishable. | ||
107 | .media-left { | ||
108 | &.circle, | ||
109 | &.square { | ||
110 | background-color: #e4e4e4; | ||
111 | } | ||
112 | |||
113 | &.circle { | ||
114 | border-radius: 90%; | ||
115 | } | ||
116 | |||
117 | img { | ||
118 | max-height: 100%; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | .error { | ||
123 | color: #de0000; | ||
124 | } | ||
125 | |||
126 | // Change background color in dark mode. | ||
127 | .is-dark { | ||
128 | .media-left { | ||
129 | &.circle, | ||
130 | &.square { | ||
131 | background-color: #909090; | ||
132 | } | ||
133 | } | ||
134 | } | ||
135 | </style> | ||
diff --git a/src/components/services/PaperlessNG.vue b/src/components/services/PaperlessNG.vue index 4fb31f8..af13317 100644 --- a/src/components/services/PaperlessNG.vue +++ b/src/components/services/PaperlessNG.vue | |||
@@ -59,6 +59,7 @@ export default { | |||
59 | } | 59 | } |
60 | const url = `${this.item.url}/api/documents/`; | 60 | const url = `${this.item.url}/api/documents/`; |
61 | this.api = await fetch(url, { | 61 | this.api = await fetch(url, { |
62 | credentials: "include", | ||
62 | headers: { | 63 | headers: { |
63 | Authorization: "Token " + this.item.apikey, | 64 | Authorization: "Token " + this.item.apikey, |
64 | }, | 65 | }, |
diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index 7042a7b..87f7090 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue | |||
@@ -64,7 +64,9 @@ export default { | |||
64 | methods: { | 64 | methods: { |
65 | fetchStatus: async function () { | 65 | fetchStatus: async function () { |
66 | const url = `${this.item.url}/api.php`; | 66 | const url = `${this.item.url}/api.php`; |
67 | this.api = await fetch(url) | 67 | this.api = await fetch(url, { |
68 | credentials: "include", | ||
69 | }) | ||
68 | .then((response) => response.json()) | 70 | .then((response) => response.json()) |
69 | .catch((e) => console.log(e)); | 71 | .catch((e) => console.log(e)); |
70 | }, | 72 | }, |
diff --git a/src/components/services/Ping.vue b/src/components/services/Ping.vue index 8a9b7a4..e693af4 100644 --- a/src/components/services/Ping.vue +++ b/src/components/services/Ping.vue | |||
@@ -50,7 +50,11 @@ export default { | |||
50 | methods: { | 50 | methods: { |
51 | fetchStatus: async function () { | 51 | fetchStatus: async function () { |
52 | const url = `${this.item.url}`; | 52 | const url = `${this.item.url}`; |
53 | fetch(url, { method: "HEAD", cache: "no-cache" }) | 53 | fetch(url, { |
54 | method: "HEAD", | ||
55 | cache: "no-cache", | ||
56 | credentials: "include", | ||
57 | }) | ||
54 | .then((response) => { | 58 | .then((response) => { |
55 | if (!response.ok) { | 59 | if (!response.ok) { |
56 | throw Error(response.statusText); | 60 | throw Error(response.statusText); |
diff --git a/src/components/services/Radarr.vue b/src/components/services/Radarr.vue index 93831a7..9d38292 100644 --- a/src/components/services/Radarr.vue +++ b/src/components/services/Radarr.vue | |||
@@ -70,10 +70,7 @@ export default { | |||
70 | }, | 70 | }, |
71 | methods: { | 71 | methods: { |
72 | fetchConfig: function () { | 72 | fetchConfig: function () { |
73 | fetch(`${this.item.url}/api/health`, { | 73 | fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`) |
74 | credentials: "include", | ||
75 | headers: { "X-Api-Key": `${this.item.apikey}` }, | ||
76 | }) | ||
77 | .then((response) => { | 74 | .then((response) => { |
78 | if (response.status != 200) { | 75 | if (response.status != 200) { |
79 | throw new Error(response.statusText); | 76 | throw new Error(response.statusText); |
@@ -95,10 +92,7 @@ export default { | |||
95 | console.error(e); | 92 | console.error(e); |
96 | this.serverError = true; | 93 | this.serverError = true; |
97 | }); | 94 | }); |
98 | fetch(`${this.item.url}/api/queue`, { | 95 | fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`) |
99 | credentials: "include", | ||
100 | headers: { "X-Api-Key": `${this.item.apikey}` }, | ||
101 | }) | ||
102 | .then((response) => { | 96 | .then((response) => { |
103 | if (response.status != 200) { | 97 | if (response.status != 200) { |
104 | throw new Error(response.statusText); | 98 | throw new Error(response.statusText); |
diff --git a/src/components/services/Sonarr.vue b/src/components/services/Sonarr.vue index 8cebac4..7851b6b 100644 --- a/src/components/services/Sonarr.vue +++ b/src/components/services/Sonarr.vue | |||
@@ -70,10 +70,7 @@ export default { | |||
70 | }, | 70 | }, |
71 | methods: { | 71 | methods: { |
72 | fetchConfig: function () { | 72 | fetchConfig: function () { |
73 | fetch(`${this.item.url}/api/health`, { | 73 | fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`) |
74 | credentials: "include", | ||
75 | headers: { "X-Api-Key": `${this.item.apikey}` }, | ||
76 | }) | ||
77 | .then((response) => { | 74 | .then((response) => { |
78 | if (response.status != 200) { | 75 | if (response.status != 200) { |
79 | throw new Error(response.statusText); | 76 | throw new Error(response.statusText); |
@@ -95,10 +92,7 @@ export default { | |||
95 | console.error(e); | 92 | console.error(e); |
96 | this.serverError = true; | 93 | this.serverError = true; |
97 | }); | 94 | }); |
98 | fetch(`${this.item.url}/api/queue`, { | 95 | fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`) |
99 | credentials: "include", | ||
100 | headers: { "X-Api-Key": `${this.item.apikey}` }, | ||
101 | }) | ||
102 | .then((response) => { | 96 | .then((response) => { |
103 | if (response.status != 200) { | 97 | if (response.status != 200) { |
104 | throw new Error(response.statusText); | 98 | throw new Error(response.statusText); |
@@ -903,10 +903,10 @@ | |||
903 | "@babel/helper-validator-identifier" "^7.14.5" | 903 | "@babel/helper-validator-identifier" "^7.14.5" |
904 | to-fast-properties "^2.0.0" | 904 | to-fast-properties "^2.0.0" |
905 | 905 | ||
906 | "@fortawesome/fontawesome-free@^5.15.3": | 906 | "@fortawesome/fontawesome-free@^5.15.4": |
907 | version "5.15.3" | 907 | version "5.15.4" |
908 | resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz#c36ffa64a2a239bf948541a97b6ae8d729e09a9a" | 908 | resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz#ecda5712b61ac852c760d8b3c79c96adca5554e5" |
909 | integrity sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w== | 909 | integrity sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg== |
910 | 910 | ||
911 | "@hapi/address@2.x.x": | 911 | "@hapi/address@2.x.x": |
912 | version "2.1.4" | 912 | version "2.1.4" |
@@ -2773,7 +2773,12 @@ core-js@^2.4.0: | |||
2773 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" | 2773 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" |
2774 | integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== | 2774 | integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== |
2775 | 2775 | ||
2776 | core-js@^3.15.2, core-js@^3.6.5: | 2776 | core-js@^3.17.3: |
2777 | version "3.17.3" | ||
2778 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.17.3.tgz#8e8bd20e91df9951e903cabe91f9af4a0895bc1e" | ||
2779 | integrity sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw== | ||
2780 | |||
2781 | core-js@^3.6.5: | ||
2777 | version "3.15.2" | 2782 | version "3.15.2" |
2778 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61" | 2783 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61" |
2779 | integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q== | 2784 | integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q== |