aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRobin Schneider <45321827+robinschneider@users.noreply.github.com>2021-10-07 00:15:26 +0200
committerGitHub <noreply@github.com>2021-10-07 00:15:26 +0200
commit270e522e0ee2bb9bef795251796abbfc74efbef3 (patch)
treec8a22126b6393b48b6c3bd0365befd6fd695faaf
parent584f2b4b32e69865d9561f1537142791710f676d (diff)
parent220c60cba04e86e782e9610aa8ef0d77e221072c (diff)
downloadhomer-270e522e0ee2bb9bef795251796abbfc74efbef3.tar.gz
homer-270e522e0ee2bb9bef795251796abbfc74efbef3.tar.zst
homer-270e522e0ee2bb9bef795251796abbfc74efbef3.zip
Merge branch 'bastienwirtz:main' into hotkey
-rw-r--r--CONTRIBUTING.md11
-rw-r--r--Dockerfile3
-rw-r--r--Dockerfile.arm32v73
-rw-r--r--Dockerfile.arm64v83
-rw-r--r--README.md55
-rw-r--r--docs/configuration.md29
-rw-r--r--docs/customservices.md47
-rw-r--r--docs/development.md49
-rw-r--r--docs/tips-and-tricks.md18
-rw-r--r--docs/troubleshooting.md19
-rw-r--r--package.json6
-rw-r--r--src/assets/app.scss10
-rw-r--r--src/components/Service.vue5
-rw-r--r--src/components/services/AdGuardHome.vue98
-rw-r--r--src/components/services/Generic.vue33
-rw-r--r--src/components/services/Mealie.vue94
-rw-r--r--src/components/services/OpenWeather.vue135
-rw-r--r--src/components/services/PaperlessNG.vue1
-rw-r--r--src/components/services/PiHole.vue4
-rw-r--r--src/components/services/Ping.vue55
-rw-r--r--src/components/services/Radarr.vue8
-rw-r--r--src/components/services/Sonarr.vue8
-rw-r--r--yarn.lock21
23 files changed, 549 insertions, 166 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f29b7d8..de893b6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -12,10 +12,6 @@ UX and usability. If you are looking for a full featured dashboard, there is ton
12- Configuration is stored in a simple config file, avoiding the need for a backend/database while making possible to use versioning or [config template](https://docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html). 12- Configuration is stored in a simple config file, avoiding the need for a backend/database while making possible to use versioning or [config template](https://docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html).
13- Only modern browsers are supported, feel free to use any JS features without any polyfill as soon as the latest version of the major browsers supports them. 13- Only modern browsers are supported, feel free to use any JS features without any polyfill as soon as the latest version of the major browsers supports them.
14 14
15### Roadmap
16
17If you want to know more about the project direction or looking for something to work on, checkout the [roadmap](https://github.com/bastienwirtz/homer#Roadmap)!
18Feel free to open an issue if you have any question.
19 15
20# Ground Rules 16# Ground Rules
21 17
@@ -40,8 +36,9 @@ feel free to open an issue to present your idea.
40### How to submit a contribution 36### How to submit a contribution
41 37
42The general process to submit a contribution is as follow: 38The general process to submit a contribution is as follow:
431. Create your own fork of the code 391. Take a look to the [development guideline](https://github.com/bastienwirtz/homer/blob/main/docs/development.md).
442. Do the changes in your fork 402. Create your own fork of the code
453. Make sure to fill the [pull request description](https://github.com/bastienwirtz/homer/blob/main/.github/PULL_REQUEST_TEMPLATE.md) properly. 413. Do the changes in your fork
424. Make sure to fill the [pull request description](https://github.com/bastienwirtz/homer/blob/main/.github/PULL_REQUEST_TEMPLATE.md) properly.
46 43
47### Happy coding :metal: 44### Happy coding :metal:
diff --git a/Dockerfile b/Dockerfile
index 19d01f8..e31d97d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,6 +25,9 @@ COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist /www/
25COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist/assets /www/default-assets 25COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist/assets /www/default-assets
26COPY entrypoint.sh /entrypoint.sh 26COPY entrypoint.sh /entrypoint.sh
27 27
28HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
29 CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:${PORT}/ || exit 1
30
28EXPOSE ${PORT} 31EXPOSE ${PORT}
29VOLUME /www/assets 32VOLUME /www/assets
30ENTRYPOINT ["/bin/sh", "/entrypoint.sh"] 33ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]
diff --git a/Dockerfile.arm32v7 b/Dockerfile.arm32v7
index 3efb5aa..95a2db1 100644
--- a/Dockerfile.arm32v7
+++ b/Dockerfile.arm32v7
@@ -35,6 +35,9 @@ COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist /www/
35COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist/assets /www/default-assets 35COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist/assets /www/default-assets
36COPY entrypoint.sh /entrypoint.sh 36COPY entrypoint.sh /entrypoint.sh
37 37
38HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
39 CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:${PORT}/ || exit 1
40
38EXPOSE ${PORT} 41EXPOSE ${PORT}
39VOLUME /www/assets 42VOLUME /www/assets
40ENTRYPOINT ["/bin/sh", "/entrypoint.sh"] 43ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]
diff --git a/Dockerfile.arm64v8 b/Dockerfile.arm64v8
index e6d0a51..cd15e4a 100644
--- a/Dockerfile.arm64v8
+++ b/Dockerfile.arm64v8
@@ -35,6 +35,9 @@ COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist /www/
35COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist/assets /www/default-assets 35COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist/assets /www/default-assets
36COPY entrypoint.sh /entrypoint.sh 36COPY entrypoint.sh /entrypoint.sh
37 37
38HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
39 CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:${PORT}/ || exit 1
40
38EXPOSE ${PORT} 41EXPOSE ${PORT}
39VOLUME /www/assets 42VOLUME /www/assets
40ENTRYPOINT ["/bin/sh", "/entrypoint.sh"] 43ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]
diff --git a/README.md b/README.md
index 9441798..2dd2b42 100644
--- a/README.md
+++ b/README.md
@@ -1,67 +1,68 @@
1<h1 align="center"> 1<h1 align="center">
2 <img 2 <img
3 width="180" 3 width="180"
4 alt="Homer's donut" 4 alt="Homer's donut"
5 src="https://raw.githubusercontent.com//bastienwirtz/homer/main/public/logo.png"> 5 src="https://raw.githubusercontent.com//bastienwirtz/homer/main/public/logo.png">
6 <br/> 6 <br/>
7 Homer 7 Homer
8</h1> 8</h1>
9 9
10<h4 align="center"> 10<h4 align="center">
11 A dead simple static <strong>HOM</strong>epage for your serv<strong>ER</strong> to keep your services on hand, from a simple <code>yaml</code> configuration file. 11 A dead simple static <strong>HOM</strong>epage for your serv<strong>ER</strong> to keep your services on hand, from a simple <code>yaml</code> configuration file.
12</h4> 12</h4>
13 13
14<p align="center"> 14<p align="center">
15 <strong> 15 <strong>
16 <a href="https://homer-demo.netlify.app">Demo</a> 16 <a href="https://homer-demo.netlify.app">Demo</a>
17 17
18 <a href="https://gitter.im/homer-dashboard/community">Chat</a> 18 <a href="https://gitter.im/homer-dashboard/community">Chat</a>
19 19
20 <a href="#getting-started">Getting started</a> 20 <a href="#getting-started">Getting started</a>
21 </strong> 21 </strong>
22</p> 22</p>
23<p align="center"> 23<p align="center">
24 <a href="https://opensource.org/licenses/Apache-2.0"><img 24 <a href="https://opensource.org/licenses/Apache-2.0"><img
25 alt="License: Apache 2" 25 alt="License: Apache 2"
26 src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a> 26 src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
27 <a href="https://gitter.im/homer-dashboard/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge"><img 27 <a href="https://gitter.im/homer-dashboard/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge"><img
28 alt="Gitter chat" 28 alt="Gitter chat"
29 src="https://badges.gitter.im/homer-dashboard/community.svg"></a> 29 src="https://badges.gitter.im/homer-dashboard/community.svg"></a>
30 <a href="https://github.com/bastienwirtz/homer/releases/latest/download/homer.zip"><img 30 <a href="https://github.com/bastienwirtz/homer/releases/latest/download/homer.zip"><img
31 alt="Download homer static build" 31 alt="Download homer static build"
32 src="https://img.shields.io/badge/Download-homer.zip-orange"></a> 32 src="https://img.shields.io/badge/Download-homer.zip-orange"></a>
33 <a href="https://github.com/awesome-selfhosted/awesome-selfhosted"><img 33 <a href="https://github.com/awesome-selfhosted/awesome-selfhosted"><img
34 alt="Awesome" 34 alt="Awesome"
35 src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"></a> 35 src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"></a>
36</p> 36</p>
37 37
38<p align="center"> 38<p align="center">
39 <img src="https://raw.github.com/bastienwirtz/homer/main/docs/screenshot.png" width="100%"> 39 <img src="https://raw.github.com/bastienwirtz/homer/main/docs/screenshot.png" width="100%">
40</p> 40</p>
41 41
42## Table of Contents 42## Table of Contents
43
43- [Features](#features) 44- [Features](#features)
44- [Getting started](#getting-started) 45- [Getting started](#getting-started)
45- [Configuration](docs/configuration.md) 46- [Configuration](docs/configuration.md)
46- [Custom services](docs/customservices.md) 47- [Custom services](docs/customservices.md)
47- [Tips & tricks](docs/tips-and-tricks.md) 48- [Tips & tricks](docs/tips-and-tricks.md)
48- [Development](docs/development.md) 49- [Development](docs/development.md)
49 50- [Troubleshooting](docs/troubleshooting.md)
50 51
51## Features 52## Features
53
52- [yaml](http://yaml.org/) file configuration 54- [yaml](http://yaml.org/) file configuration
53- Installable (pwa) 55- Installable (pwa)
54- Search 56- Search
55- Grouping 57- Grouping
56- Theme customization 58- Theme customization
57- Offline heathcheck 59- Offline heath check
58- keyboard shortcuts: 60- keyboard shortcuts:
59 - `/` Start searching. 61 - `/` Start searching.
60 - `Escape` Stop searching. 62 - `Escape` Stop searching.
61 - `Enter` Open the first matching result (respects the bookmark's `_target` property). 63 - `Enter` Open the first matching result (respects the bookmark's `_target` property).
62 - `Alt`/`Option` + `Enter` Open the first matching result in a new tab. 64 - `Alt`/`Option` + `Enter` Open the first matching result in a new tab.
63 65
64
65## Getting started 66## Getting started
66 67
67Homer is a full static html/js dashboard, generated from the source in `/src` using webpack. It's meant to be served by an HTTP server, **it will not work if you open dist/index.html directly over file:// protocol**. 68Homer is a full static html/js dashboard, generated from the source in `/src` using webpack. It's meant to be served by an HTTP server, **it will not work if you open dist/index.html directly over file:// protocol**.
@@ -111,7 +112,7 @@ environment:
111 112
112### Using the release tarball (prebuilt, ready to use) 113### Using the release tarball (prebuilt, ready to use)
113 114
114Download and extract the latest release (`homer.zip`) from the [release page](https://github.com/bastienwirtz/homer/releases), rename the `assets/config.yml.dist` file to `assets/config.yml`, and put it behind a webserver. 115Download and extract the latest release (`homer.zip`) from the [release page](https://github.com/bastienwirtz/homer/releases), rename the `assets/config.yml.dist` file to `assets/config.yml`, and put it behind a web server.
115 116
116```sh 117```sh
117wget https://github.com/bastienwirtz/homer/releases/latest/download/homer.zip 118wget https://github.com/bastienwirtz/homer/releases/latest/download/homer.zip
diff --git a/docs/configuration.md b/docs/configuration.md
index 93abadb..807fa68 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -1,4 +1,4 @@
1## Configuration 1# Configuration
2 2
3Title, icons, links, colors, and services can be configured in the `config.yml` file (located in `/assets` directory once built, or in the `public/assets` directory in development mode), using [yaml](http://yaml.org/) format. 3Title, icons, links, colors, and services can be configured in the `config.yml` file (located in `/assets` directory once built, or in the `public/assets` directory in development mode), using [yaml](http://yaml.org/) format.
4 4
@@ -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
@@ -138,6 +138,8 @@ services:
138 # background: red # optional color for card to set color directly without custom stylesheet 138 # background: red # optional color for card to set color directly without custom stylesheet
139``` 139```
140 140
141View [Custom Services](customservices.md) for details about all available custom services (like PiHole) and how to configure them.
142
141If 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)): 143If 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 144
143```json 145```json
@@ -151,7 +153,7 @@ If you choose to fetch message information from an endpoint, the output format s
151`null` value or missing keys will be ignored and value from the `config.yml` will be used if available. 153`null` value or missing keys will be ignored and value from the `config.yml` will be used if available.
152Empty values (either in `config.yml` or the endpoint data) will hide the element (ex: set `"title": ""` to hide the title bar). 154Empty values (either in `config.yml` or the endpoint data) will hide the element (ex: set `"title": ""` to hide the title bar).
153 155
154### Style Options 156## Style Options
155 157
156Homer uses [bulma CSS](https://bulma.io/), which provides a [modifiers syntax](https://bulma.io/documentation/modifiers/syntax/). You'll notice in the config there is a `tagstyle` option. It can be set to any of the bulma modifiers. You'll probably want to use one of these 4 main colors: 158Homer uses [bulma CSS](https://bulma.io/), which provides a [modifiers syntax](https://bulma.io/documentation/modifiers/syntax/). You'll notice in the config there is a `tagstyle` option. It can be set to any of the bulma modifiers. You'll probably want to use one of these 4 main colors:
157 159
@@ -162,10 +164,29 @@ Homer uses [bulma CSS](https://bulma.io/), which provides a [modifiers syntax](h
162 164
163You can read the [bulma modifiers page](https://bulma.io/documentation/modifiers/syntax/) for other options regarding size, style, or state. 165You can read the [bulma modifiers page](https://bulma.io/documentation/modifiers/syntax/) for other options regarding size, style, or state.
164 166
165### PWA Icons 167## PWA Icons
166 168
167In order to easily generate all required icon preset for the PWA to work, a tool like [vue-pwa-asset-generator](https://www.npmjs.com/package/vue-pwa-asset-generator) can be used: 169In order to easily generate all required icon preset for the PWA to work, a tool like [vue-pwa-asset-generator](https://www.npmjs.com/package/vue-pwa-asset-generator) can be used:
168 170
169```bash 171```bash
170npx vue-pwa-asset-generator -a {your_512x512_source_png} -o {your_output_folder} 172npx vue-pwa-asset-generator -a {your_512x512_source_png} -o {your_output_folder}
171``` 173```
174
175## Supported services
176
177Currently the following services are supported for showing quick infos on the card. They can be used by setting the type to one of the following values at the item.
178
179- PiHole
180- AdGuardHome
181- PaperlessNG
182- Mealie
183
184## Additional configuration
185
186### Paperless
187
188For Paperless you need an API-Key which you have to store at the item in the field `apikey`.
189
190### Mealie
191
192First off make sure to remove an existing `subtitle` as it will take precedence if set. Setting `type: "Mealie"` will then show the number of recipes Mealie is keeping organized or the planned meal for today if one is planned. You will have to set an API key in the field `apikey` which can be created in your Mealie installation.
diff --git a/docs/customservices.md b/docs/customservices.md
index 43f45f4..f79428f 100644
--- a/docs/customservices.md
+++ b/docs/customservices.md
@@ -1,16 +1,18 @@
1# Custom Services 1# Custom Services
2 2
3Some service can use a specific a component that provides some extra features by adding a `type` key to the service yaml 3Some service can use a specific a component that provides some extra features by adding a `type` key to the service yaml
4configuration. Available services are in `src/components/`. Here is an overview of all custom services that are available 4configuration. Available services are in `src/components/`. Here is an overview of all custom services that are available
5within Homer. 5within Homer.
6 6
7If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page.
8
7## PiHole 9## PiHole
8 10
9Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard. 11Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard.
10 12
11The following configuration is available for the PiHole service. 13The following configuration is available for the PiHole service.
12 14
13``` 15```yaml
14 items: 16 items:
15 - name: "Pi-hole" 17 - name: "Pi-hole"
16 logo: "assets/tools/sample.png" 18 logo: "assets/tools/sample.png"
@@ -19,35 +21,64 @@ The following configuration is available for the PiHole service.
19 type: "PiHole" 21 type: "PiHole"
20``` 22```
21 23
24## OpenWeatherMap
25
26Using the OpenWeatherMap service you can display weather information about a given location.
27The following configuration is available for the OpenWeatherMap service:
28
29```yaml
30items:
31 - name: "Weather"
32 location: "Amsterdam" # your location.
33 locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy
34 apiKey: "<---insert-api-key-here--->" # insert your own API key here. Request one from https://openweathermap.org/api.
35 units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin.
36 background: "square" # choose which type of background you want behind the image. Can be one of: square, cicle, none. Defaults to none.
37 type: "OpenWeather"
38```
39
40**Remarks:**
41If 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).
42
22## Medusa 43## Medusa
23 44
24This service displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application. 45This service displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application.
25Two lines are needed in the config.yml : 46Two lines are needed in the config.yml :
26``` 47
48```yaml
27type: "Medusa" 49type: "Medusa"
28apikey: "01234deb70424befb1f4ef6a23456789" 50apikey: "01234deb70424befb1f4ef6a23456789"
29``` 51```
52
30The url must be the root url of Medusa application. 53The url must be the root url of Medusa application.
31The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API. 54The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API.
32 55
33
34## Sonarr/Radarr 56## Sonarr/Radarr
35 57
36This service displays Activity (blue), Warning (orange) or Error (red) notifications bubbles from the Radarr/Sonarr application. 58This service displays Activity (blue), Warning (orange) or Error (red) notifications bubbles from the Radarr/Sonarr application.
37Two lines are needed in the config.yml : 59Two lines are needed in the config.yml :
38``` 60
61```yaml
39type: "Radarr" or "Sonarr" 62type: "Radarr" or "Sonarr"
40apikey: "01234deb70424befb1f4ef6a23456789" 63apikey: "01234deb70424befb1f4ef6a23456789"
41``` 64```
65
42The url must be the root url of Radarr/Sonarr application. 66The url must be the root url of Radarr/Sonarr application.
43The Radarr/Sonarr API key can be found in Settings > General. It is needed to access the API. 67The Radarr/Sonarr API key can be found in Settings > General. It is needed to access the API.
44 68
45
46## PaperlessNG 69## PaperlessNG
47 70
48For Paperless you need an API-Key which you have to store at the item in the field `apikey`. 71For Paperless you need an API-Key which you have to store at the item in the field `apikey`.
49 72
50
51## Ping 73## Ping
52 74
53For Paperless you need an API-Key which you have to store at the item in the field `apikey`. 75For Ping you need to set the type to Ping and provide a url.
76
77```yaml
78items:
79 - name: "Awesome app"
80 type: Ping
81 logo: "assets/tools/sample.png"
82 subtitle: "Bookmark example" tag: "app"
83 url: "https://www.reddit.com/r/selfhosted/"
84```
diff --git a/docs/development.md b/docs/development.md
index f2e9020..a22ae0b 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -1,4 +1,6 @@
1## Development 1# Development
2
3If you want to contribute to Homer, please read the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/main/CONTRIBUTING.md) first.
2 4
3```sh 5```sh
4# Using yarn (recommended) 6# Using yarn (recommended)
@@ -10,7 +12,50 @@ npm install
10npm run serve 12npm run serve
11``` 13```
12 14
13### Themes 15## Custom services
16
17Custom services are small VueJs component (see `src/components/services/`) that add little features to a classic, "static", dashboard item. It should be very simple.
18A dashboard can contain a lot of items, so performance is very important.
19
20The [`Generic`](https://github.com/bastienwirtz/homer/blob/main/src/components/services/Generic.vue) service provides a typical card layout which
21you can extend to add specific features. Unless you want a completely different design, extended the generic service is the recommended way. It gives you 3 [slots](https://vuejs.org/v2/guide/components-slots.html#Named-Slots) to extend: `icon`, `content` and `indicator`.
22Each one is **optional**, and will display the usual information if omitted.
23
24Each service must implement the `item` [property](https://vuejs.org/v2/guide/components-props.html) and bind it the Generic component if used.
25
26### Skeleton
27```Vue
28<template>
29 <Generic :item="item">
30 <template #icon>
31 <!-- left area containing the icon -->
32 </template>
33 <template #content>
34 <!-- main area containing the title, subtitle, ... -->
35 </template>
36 <template #indicator>
37 <!-- top right area, empty by default -->
38 </template>
39 </Generic>
40</template>
41
42<script>
43import Generic from "./Generic.vue";
44
45export default {
46 name: "MyNewService",
47 props: {
48 item: Object,
49 },
50 components: {
51 Generic,
52 }
53};
54</script>
55```
56
57
58## Themes
14 59
15Themes are meant to be simple customization (written in [scss](https://sass-lang.com/documentation/syntax)). 60Themes are meant to be simple customization (written in [scss](https://sass-lang.com/documentation/syntax)).
16To add a new theme, just add a file in the theme directory, and put all style in the `body #app.theme-<name>` scope. Then import it in the main style file. 61To add a new theme, just add a file in the theme directory, and put all style in the `body #app.theme-<name>` scope. Then import it in the main style file.
diff --git a/docs/tips-and-tricks.md b/docs/tips-and-tricks.md
index 94167fb..17bba9a 100644
--- a/docs/tips-and-tricks.md
+++ b/docs/tips-and-tricks.md
@@ -3,6 +3,7 @@
3Here is a collection of neat tips and tricks that Homer users have come up with! 3Here is a collection of neat tips and tricks that Homer users have come up with!
4 4
5## Use Homer as a custom "new tab" page 5## Use Homer as a custom "new tab" page
6
6#### `by @vosdev` 7#### `by @vosdev`
7 8
8These extensions for [Firefox](https://addons.mozilla.org/firefox/addon/custom-new-tab-page) and [Chrome & Friends](https://chrome.google.com/webstore/detail/new-tab-changer/occbjkhimchkolibngmcefpjlbknggfh) allow you to have your homer dashboard in your new tab page, while leaving focus on the address bar meaning you can still type right away if you want to search or go to a page that is not on your homer dash. 9These extensions for [Firefox](https://addons.mozilla.org/firefox/addon/custom-new-tab-page) and [Chrome & Friends](https://chrome.google.com/webstore/detail/new-tab-changer/occbjkhimchkolibngmcefpjlbknggfh) allow you to have your homer dashboard in your new tab page, while leaving focus on the address bar meaning you can still type right away if you want to search or go to a page that is not on your homer dash.
@@ -22,11 +23,12 @@ The Firefox extension loads Homer in an iframe on your new tab page, meaning you
22``` 23```
23 24
24## YAML Anchors 25## YAML Anchors
26
25#### `by @JamiePhonic` 27#### `by @JamiePhonic`
26 28
27Since Homer is configured using YAML, it supports all of YAML's helpful features, such as anchoring! 29Since Homer is configured using YAML, it supports all of YAML's helpful features, such as anchoring!
28 30
29For example, you can define tags and tag styles for each "item" in a service. 31For example, you can define tags and tag styles for each "item" in a service.
30Using Anchoring, you can define all your tags and their styles once like this: (for example) 32Using Anchoring, you can define all your tags and their styles once like this: (for example)
31 33
32```yaml 34```yaml
@@ -70,6 +72,7 @@ The end result is that if you want to update the name or style of any particular
70Great if you have a lot of services or a lot of tags! 72Great if you have a lot of services or a lot of tags!
71 73
72## Remotely edit your config with Code Server 74## Remotely edit your config with Code Server
75
73#### `by @JamiePhonic` 76#### `by @JamiePhonic`
74 77
75Homer doesn't yet provide a way to edit your configuration from inside Homer itself, but that doesn't mean it can't be done! 78Homer doesn't yet provide a way to edit your configuration from inside Homer itself, but that doesn't mean it can't be done!
@@ -78,14 +81,17 @@ You can setup and use [Code-Server](https://github.com/cdr/code-server) to edit
78 81
79If you're running Homer in docker, you can setup a Code-Server container and pass your homer config directory into it. 82If you're running Homer in docker, you can setup a Code-Server container and pass your homer config directory into it.
80Simply pass your homer config directory as an extra -v parameter to your code-server container: 83Simply pass your homer config directory as an extra -v parameter to your code-server container:
81``` 84
85```sh
82-v '/your/local/homer/config-dir/':'/config/homer':'rw' 86-v '/your/local/homer/config-dir/':'/config/homer':'rw'
83``` 87```
88
84This will map your homer config directory (For example, /docker/appdata/homer/) into code-server's `/config/` directory, in a sub folder called `homer` 89This will map your homer config directory (For example, /docker/appdata/homer/) into code-server's `/config/` directory, in a sub folder called `homer`
85 90
86As a bonus, Code-Server puts the "current folder" as a parameter in the URL bar, so you could add a `links:` entry in Homer that points to your code-server instance with the directory pre-filled for essentially 1 click editing! 91As a bonus, Code-Server puts the "current folder" as a parameter in the URL bar, so you could add a `links:` entry in Homer that points to your code-server instance with the directory pre-filled for essentially 1 click editing!
87 92
88For example: 93For example:
94
89```yml 95```yml
90links: 96links:
91 - name: Edit config 97 - name: Edit config
@@ -93,9 +99,11 @@ links:
93 url: https://vscode.example.net/?folder=/config/homer 99 url: https://vscode.example.net/?folder=/config/homer
94 target: "_blank" # optional html tag target attribute 100 target: "_blank" # optional html tag target attribute
95``` 101```
102
96where the path after `?folder=` is the path to the folder where you mounted your homer config INSIDE the Code-Server container. 103where the path after `?folder=` is the path to the folder where you mounted your homer config INSIDE the Code-Server container.
97 104
98### Example Code-Server docker create command 105### Example Code-Server docker create command
106
99```sh 107```sh
100docker create \ 108docker create \
101 --name=code-server \ 109 --name=code-server \
@@ -111,13 +119,13 @@ docker create \
111 linuxserver/code-server 119 linuxserver/code-server
112``` 120```
113 121
114
115## Get the news headlines in Homer 122## Get the news headlines in Homer
116 123
117### Mapping Fields 124### Mapping Fields
125
118Most times, the url you're getting headlines from follows a different schema than the one expected by Homer. 126Most times, the url you're getting headlines from follows a different schema than the one expected by Homer.
119 127
120For example, if you would like to show jokes from ChuckNorris.io, you'll find that the url https://api.chucknorris.io/jokes/random is giving you info like this: 128For example, if you would like to show jokes from ChuckNorris.io, you'll find that the url <https://api.chucknorris.io/jokes/random> is giving you info like this:
121 129
122```json 130```json
123{ 131{
@@ -179,6 +187,6 @@ If the URL you specified returns a JSON object that defines a `title` and `conte
179 187
180So, using [Node-Red](https://nodered.org/docs/getting-started/) and a quick flow, you can process an RSS feed to replace the message with a news item! 188So, using [Node-Red](https://nodered.org/docs/getting-started/) and a quick flow, you can process an RSS feed to replace the message with a news item!
181 189
182To get started, simply import [this flow](https://flows.nodered.org/flow/4b6406c9a684c26ace0430dd1826e95d) into your Node-Red instance and change the RSS feed in the "Get News RSS Feed" node to one of your choosing! 190To get started, simply import [this flow](https://flows.nodered.org/flow/4b6406c9a684c26ace0430dd1826e95d) into your Node-Red instance and change the RSS feed in the "Get News RSS Feed" node to one of your choosing!
183 191
184So far, the flow has been tested with BBC News and Sky News, however it should be easy to modify the flow to work with other RSS feeds if they don't work out of the box! 192So far, the flow has been tested with BBC News and Sky News, however it should be easy to modify the flow to work with other RSS feeds if they don't work out of the box!
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
new file mode 100644
index 0000000..ed1f85d
--- /dev/null
+++ b/docs/troubleshooting.md
@@ -0,0 +1,19 @@
1# Troubleshooting
2
3## My custom service card doesn't work, nothing appears or offline status is displayed (pi-hole, sonarr, ping, ...)
4
5You might by facing a [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (Cross Origin Request Sharing) issue.
6It happens when the targeted service is hosted on a different domain or port.
7Web browsers will not allow to fetch information from a different site without explicit permissions (the targeted service
8must include a special `Access-Control-Allow-Origin: *` HTTP headers).
9If this happens your web console (`ctrl+shit+i` or `F12`) will be filled with this kind of errors:
10
11```text
12Access to fetch at 'https://<target-service>' from origin 'https://<homer>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
13```
14
15To resolve this, you can either:
16
17* Host all your target service under the same domain & port.
18* Modify the target sever configuration so that the response of the server included following header- `Access-Control-Allow-Origin: *` (<https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#simple_requests>). It might be an option in the targeted service, otherwise depending on how the service is hosted, the proxy or web server can seamlessly add it.
19* Use a cors proxy sever like [`cors-container`](https://github.com/imjacobclark/cors-container), [`cors-anywhere`](https://github.com/Rob--W/cors-anywhere) or many others.
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/Service.vue b/src/components/Service.vue
index 8686759..39a9ac4 100644
--- a/src/components/Service.vue
+++ b/src/components/Service.vue
@@ -7,16 +7,13 @@ import Generic from "./services/Generic.vue";
7 7
8export default { 8export default {
9 name: "Service", 9 name: "Service",
10 components: {
11 Generic,
12 },
13 props: { 10 props: {
14 item: Object, 11 item: Object,
15 }, 12 },
16 computed: { 13 computed: {
17 component() { 14 component() {
18 const type = this.item.type || "Generic"; 15 const type = this.item.type || "Generic";
19 if (type == "Generic") { 16 if (type === "Generic") {
20 return Generic; 17 return Generic;
21 } 18 }
22 return () => import(`./services/${type}.vue`); 19 return () => import(`./services/${type}.vue`);
diff --git a/src/components/services/AdGuardHome.vue b/src/components/services/AdGuardHome.vue
index d4a2b89..16881fa 100644
--- a/src/components/services/AdGuardHome.vue
+++ b/src/components/services/AdGuardHome.vue
@@ -1,59 +1,77 @@
1<template> 1<template>
2 <div> 2 <Generic :item="item">
3 <div class="card" :class="item.class"> 3 <template #content>
4 <a :href="item.url" :target="item.target" rel="noreferrer"> 4 <p class="title is-4">{{ item.name }}</p>
5 <div class="card-content"> 5 <p class="subtitle is-6">
6 <div class="media"> 6 <template v-if="item.subtitle">
7 <div v-if="item.logo" class="media-left"> 7 {{ item.subtitle }}
8 <figure class="image is-48x48"> 8 </template>
9 <img :src="item.logo" :alt="`${item.name} logo`" /> 9 <template v-else-if="stats">
10 </figure> 10 {{ percentage }}&percnt; blocked
11 </div> 11 </template>
12 <div v-if="item.icon" class="media-left"> 12 </p>
13 <figure class="image is-48x48"> 13 </template>
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> 14 <template #indicator>
15 </figure> 15 <div class="status" :class="protection">
16 </div> 16 {{ protection }}
17 <div class="media-content"> 17 </div>
18 <p class="title is-4">{{ item.name }}</p> 18 </template>
19 <p class="subtitle is-6">{{ item.subtitle }}</p> 19 </Generic>
20 </div>
21 <div
22 v-if="status"
23 class="status"
24 v-bind:class="status.protection_enabled ? 'enabled' : 'disabled'"
25 >
26 {{ status.protection_enabled }}
27 </div>
28 </div>
29 <div class="tag" :class="item.tagstyle" v-if="item.tag">
30 <strong class="tag-text">#{{ item.tag }}</strong>
31 </div>
32 </div>
33 </a>
34 </div>
35 </div>
36</template> 20</template>
37 21
38<script> 22<script>
23import Generic from "./Generic.vue";
24
39export default { 25export default {
40 name: "AdGuardHome", 26 name: "AdGuardHome",
41 props: { 27 props: {
42 item: Object, 28 item: Object,
43 }, 29 },
30 components: {
31 Generic,
32 },
44 data: () => { 33 data: () => {
45 return { 34 return {
46 status: null, 35 status: null,
36 stats: null,
47 }; 37 };
48 }, 38 },
39 computed: {
40 percentage: function () {
41 if (this.stats) {
42 return (
43 (this.stats.num_blocked_filtering * 100) /
44 this.stats.num_dns_queries
45 ).toFixed(2);
46 }
47 return "";
48 },
49 protection: function () {
50 if (this.status) {
51 return this.status.protection_enabled ? "enabled" : "disabled";
52 } else return "unknown";
53 },
54 },
49 created: function () { 55 created: function () {
50 this.fetchStatus(); 56 this.fetchStatus();
57 if (!this.item.subtitle) {
58 this.fetchStats();
59 }
51 }, 60 },
52 methods: { 61 methods: {
53 fetchStatus: async function () { 62 fetchStatus: async function () {
54 this.status = await fetch(`${this.item.url}/control/status`).then( 63 this.status = await fetch(`${this.item.url}/control/status`, {
55 (response) => response.json() 64 credentials: "include",
56 ); 65 })
66 .then((response) => response.json())
67 .catch((e) => console.log(e));
68 },
69 fetchStats: async function () {
70 this.stats = await fetch(`${this.item.url}/control/stats`, {
71 credentials: "include",
72 })
73 .then((response) => response.json())
74 .catch((e) => console.log(e));
57 }, 75 },
58 }, 76 },
59}; 77};
@@ -79,6 +97,12 @@ export default {
79 box-shadow: 0px 0px 4px 1px #c9404d; 97 box-shadow: 0px 0px 4px 1px #c9404d;
80 } 98 }
81 99
100 &.unknown:before {
101 background-color: #c9c740;
102 border-color: #ccc935;
103 box-shadow: 0px 0px 4px 1px #c9c740;
104 }
105
82 &:before { 106 &:before {
83 content: " "; 107 content: " ";
84 display: inline-block; 108 display: inline-block;
diff --git a/src/components/services/Generic.vue b/src/components/services/Generic.vue
index 08bd3f6..af65a8c 100644
--- a/src/components/services/Generic.vue
+++ b/src/components/services/Generic.vue
@@ -8,22 +8,27 @@
8 <a :href="item.url" :target="item.target" rel="noreferrer"> 8 <a :href="item.url" :target="item.target" rel="noreferrer">
9 <div class="card-content"> 9 <div class="card-content">
10 <div :class="mediaClass"> 10 <div :class="mediaClass">
11 <div v-if="item.logo" class="media-left"> 11 <slot name="icon">
12 <figure class="image is-48x48"> 12 <div v-if="item.logo" class="media-left">
13 <img :src="item.logo" :alt="`${item.name} logo`" /> 13 <figure class="image is-48x48">
14 </figure> 14 <img :src="item.logo" :alt="`${item.name} logo`" />
15 </div> 15 </figure>
16 <div v-if="item.icon" class="media-left"> 16 </div>
17 <figure class="image is-48x48"> 17 <div v-if="item.icon" class="media-left">
18 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> 18 <figure class="image is-48x48">
19 </figure> 19 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
20 </div> 20 </figure>
21 </div>
22 </slot>
21 <div class="media-content"> 23 <div class="media-content">
22 <p class="title is-4">{{ item.name }}</p> 24 <slot name="content">
23 <p class="subtitle is-6" v-if="item.subtitle"> 25 <p class="title is-4">{{ item.name }}</p>
24 {{ item.subtitle }} 26 <p class="subtitle is-6" v-if="item.subtitle">
25 </p> 27 {{ item.subtitle }}
28 </p>
29 </slot>
26 </div> 30 </div>
31 <slot name="indicator" class="indicator"></slot>
27 </div> 32 </div>
28 <div class="tag" :class="item.tagstyle" v-if="item.tag"> 33 <div class="tag" :class="item.tagstyle" v-if="item.tag">
29 <strong class="tag-text">#{{ item.tag }}</strong> 34 <strong class="tag-text">#{{ item.tag }}</strong>
diff --git a/src/components/services/Mealie.vue b/src/components/services/Mealie.vue
new file mode 100644
index 0000000..790a9b1
--- /dev/null
+++ b/src/components/services/Mealie.vue
@@ -0,0 +1,94 @@
1<template>
2 <div>
3 <div class="card" :class="item.class">
4 <a :href="item.url" :target="item.target" rel="noreferrer">
5 <div class="card-content">
6 <div class="media">
7 <div v-if="item.logo" class="media-left">
8 <figure class="image is-48x48">
9 <img :src="item.logo" :alt="`${item.name} logo`" />
10 </figure>
11 </div>
12 <div v-if="item.icon" class="media-left">
13 <figure class="image is-48x48">
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
15 </figure>
16 </div>
17 <div class="media-content">
18 <p class="title is-4">{{ item.name }}</p>
19 <p class="subtitle is-6">
20 <template v-if="item.subtitle">
21 {{ item.subtitle }}
22 </template>
23 <template v-else-if="meal"> Today: {{ meal.name }} </template>
24 <template v-else-if="stats">
25 happily keeping {{ stats.totalRecipes }} recipes organized
26 </template>
27 </p>
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>
40export default {
41 name: "Mealie",
42 props: {
43 item: Object,
44 },
45 data: () => ({
46 stats: null,
47 meal: null,
48 }),
49 created() {
50 this.fetchStatus();
51 },
52 methods: {
53 fetchStatus: async function () {
54 if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing
55 this.meal = await fetch(`${this.item.url}/api/meal-plans/today/`, {
56 headers: {
57 Authorization: "Bearer " + this.item.apikey,
58 Accept: "application/json",
59 },
60 })
61 .then(function (response) {
62 if (!response.ok) {
63 throw new Error("Not 2xx response");
64 } else {
65 if (response != null) {
66 return response.json();
67 }
68 }
69 })
70 .catch((e) => console.log(e));
71 this.stats = await fetch(`${this.item.url}/api/debug/statistics/`, {
72 headers: {
73 Authorization: "Bearer " + this.item.apikey,
74 Accept: "application/json",
75 },
76 })
77 .then(function (response) {
78 if (!response.ok) {
79 throw new Error("Not 2xx response");
80 } else {
81 return response.json();
82 }
83 })
84 .catch((e) => console.log(e));
85 },
86 },
87};
88</script>
89
90<style scoped lang="scss">
91.media-left img {
92 max-height: 100%;
93}
94</style>
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>
40export 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..6fd3ec5 100644
--- a/src/components/services/Ping.vue
+++ b/src/components/services/Ping.vue
@@ -1,46 +1,24 @@
1<template> 1<template>
2 <div> 2 <Generic :item="item">
3 <div class="card" :class="item.class"> 3 <template #indicator>
4 <a :href="item.url" :target="item.target" rel="noreferrer"> 4 <div v-if="status" class="status" :class="status">
5 <div class="card-content"> 5 {{ status }}
6 <div class="media"> 6 </div>
7 <div v-if="item.logo" class="media-left"> 7 </template>
8 <figure class="image is-48x48"> 8 </Generic>
9 <img :src="item.logo" :alt="`${item.name} logo`" />
10 </figure>
11 </div>
12 <div v-if="item.icon" class="media-left">
13 <figure class="image is-48x48">
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
15 </figure>
16 </div>
17 <div class="media-content">
18 <p class="title is-4">{{ item.name }}</p>
19 <p class="subtitle is-6">
20 <template v-if="item.subtitle">
21 {{ item.subtitle }}
22 </template>
23 </p>
24 </div>
25 <div v-if="status" class="status" :class="status">
26 {{ status }}
27 </div>
28 </div>
29 <div class="tag" :class="item.tagstyle" v-if="item.tag">
30 <strong class="tag-text">#{{ item.tag }}</strong>
31 </div>
32 </div>
33 </a>
34 </div>
35 </div>
36</template> 9</template>
37 10
38<script> 11<script>
12import Generic from "./Generic.vue";
13
39export default { 14export default {
40 name: "Ping", 15 name: "Ping",
41 props: { 16 props: {
42 item: Object, 17 item: Object,
43 }, 18 },
19 components: {
20 Generic,
21 },
44 data: () => ({ 22 data: () => ({
45 status: null, 23 status: null,
46 }), 24 }),
@@ -50,7 +28,11 @@ export default {
50 methods: { 28 methods: {
51 fetchStatus: async function () { 29 fetchStatus: async function () {
52 const url = `${this.item.url}`; 30 const url = `${this.item.url}`;
53 fetch(url, { method: "HEAD", cache: "no-cache" }) 31 fetch(url, {
32 method: "HEAD",
33 cache: "no-cache",
34 credentials: "include",
35 })
54 .then((response) => { 36 .then((response) => {
55 if (!response.ok) { 37 if (!response.ok) {
56 throw Error(response.statusText); 38 throw Error(response.statusText);
@@ -66,9 +48,6 @@ export default {
66</script> 48</script>
67 49
68<style scoped lang="scss"> 50<style scoped lang="scss">
69.media-left img {
70 max-height: 100%;
71}
72.status { 51.status {
73 font-size: 0.8rem; 52 font-size: 0.8rem;
74 color: var(--text-title); 53 color: var(--text-title);
diff --git a/src/components/services/Radarr.vue b/src/components/services/Radarr.vue
index 9d38292..a9cdedf 100644
--- a/src/components/services/Radarr.vue
+++ b/src/components/services/Radarr.vue
@@ -70,7 +70,9 @@ export default {
70 }, 70 },
71 methods: { 71 methods: {
72 fetchConfig: function () { 72 fetchConfig: function () {
73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`) 73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
74 credentials: "include",
75 })
74 .then((response) => { 76 .then((response) => {
75 if (response.status != 200) { 77 if (response.status != 200) {
76 throw new Error(response.statusText); 78 throw new Error(response.statusText);
@@ -92,7 +94,9 @@ export default {
92 console.error(e); 94 console.error(e);
93 this.serverError = true; 95 this.serverError = true;
94 }); 96 });
95 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`) 97 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
98 credentials: "include",
99 })
96 .then((response) => { 100 .then((response) => {
97 if (response.status != 200) { 101 if (response.status != 200) {
98 throw new Error(response.statusText); 102 throw new Error(response.statusText);
diff --git a/src/components/services/Sonarr.vue b/src/components/services/Sonarr.vue
index 7851b6b..0270255 100644
--- a/src/components/services/Sonarr.vue
+++ b/src/components/services/Sonarr.vue
@@ -70,7 +70,9 @@ export default {
70 }, 70 },
71 methods: { 71 methods: {
72 fetchConfig: function () { 72 fetchConfig: function () {
73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`) 73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
74 credentials: "include",
75 })
74 .then((response) => { 76 .then((response) => {
75 if (response.status != 200) { 77 if (response.status != 200) {
76 throw new Error(response.statusText); 78 throw new Error(response.statusText);
@@ -92,7 +94,9 @@ export default {
92 console.error(e); 94 console.error(e);
93 this.serverError = true; 95 this.serverError = true;
94 }); 96 });
95 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`) 97 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
98 credentials: "include",
99 })
96 .then((response) => { 100 .then((response) => {
97 if (response.status != 200) { 101 if (response.status != 200) {
98 throw new Error(response.statusText); 102 throw new Error(response.statusText);
diff --git a/yarn.lock b/yarn.lock
index c79c2fd..18906e1 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -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
2776core-js@^3.15.2, core-js@^3.6.5: 2776core-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
2781core-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==
@@ -8222,9 +8227,9 @@ url-loader@^2.2.0:
8222 schema-utils "^2.5.0" 8227 schema-utils "^2.5.0"
8223 8228
8224url-parse@^1.4.3, url-parse@^1.5.1: 8229url-parse@^1.4.3, url-parse@^1.5.1:
8225 version "1.5.1" 8230 version "1.5.3"
8226 resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.1.tgz#d5fa9890af8a5e1f274a2c98376510f6425f6e3b" 8231 resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862"
8227 integrity sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q== 8232 integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==
8228 dependencies: 8233 dependencies:
8229 querystringify "^2.1.1" 8234 querystringify "^2.1.1"
8230 requires-port "^1.0.0" 8235 requires-port "^1.0.0"