- [ ] I've read & comply with the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/main/CONTRIBUTING.md)
- [ ] I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
-- [ ] I have made corresponding changes the documentation (README.md).
+- [ ] I have made corresponding changes to the documentation (README.md).
- [ ] I've checked my modifications for any breaking changes, especially in the `config.yml` file
--- /dev/null
+# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
+# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
+
+name: Node.js CI
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ branches: [ main ]
+
+jobs:
+ build:
+
+ runs-on: ubuntu-latest
+
+ strategy:
+ matrix:
+ node-version: [16.x]
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
+
+ steps:
+ - uses: actions/checkout@v2
+ - name: Use Node.js ${{ matrix.node-version }}
+ uses: actions/setup-node@v2
+ with:
+ node-version: ${{ matrix.node-version }}
+ cache: 'yarn'
+ - run: yarn install
+ - run: yarn lint
+
columns: "3" # "auto" or number (must be a factor of 12: 1, 2, 3, 4, 6, 12)
connectivityCheck: true # whether you want to display a message when the apps are not accessible anymore (VPN disconnected for example)
+# Optional: Proxy / hosting option
+proxy:
+ # NOT All custom services implements this new option YET. Support will be extended very soon.
+ useCredentials: false # send cookies & authorization headers when fetching service specific data. Set to `true` if you use an authentication proxy. Can be overrided on service level.
+
# Optional theming
theme: default # 'default' or one of the themes available in 'src/assets/themes'.
If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page.
+
+## Common options
+
+```yaml
+- name: "My Service"
+ logo: "assets/tools/sample.png"
+ url: "http://my-service-link"
+ endpoint: "http://my-service-endpoint" # Optional: alternative base URL used to fetch service data is necessary.
+ useCredentials: false # Optional: Override global proxy.useCredentials configuration.
+ type: "<type>"
+```
+
+⚠️🚧 `endpoint` & `useCredentials` new options are not yet supported by all custom services (but will be very soon).
+
## PiHole
Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard.
The following configuration is available for the PiHole service.
```yaml
- items:
- - name: "Pi-hole"
- logo: "assets/tools/sample.png"
- # subtitle: "Network-wide Ad Blocking" # optional, if no subtitle is defined, PiHole statistics will be shown
- url: "http://192.168.0.151/admin"
- type: "PiHole"
+- name: "Pi-hole"
+ logo: "assets/tools/sample.png"
+ # subtitle: "Network-wide Ad Blocking" # optional, if no subtitle is defined, PiHole statistics will be shown
+ url: "http://192.168.0.151/admin"
+ type: "PiHole"
```
## OpenWeatherMap
The following configuration is available for the OpenWeatherMap service:
```yaml
-items:
- - name: "Weather"
- location: "Amsterdam" # your location.
- locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy
- apiKey: "<---insert-api-key-here--->" # insert your own API key here. Request one from https://openweathermap.org/api.
- units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin.
- background: "square" # choose which type of background you want behind the image. Can be one of: square, cicle, none. Defaults to none.
- type: "OpenWeather"
+- name: "Weather"
+ location: "Amsterdam" # your location.
+ locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy
+ apiKey: "<---insert-api-key-here--->" # insert your own API key here. Request one from https://openweathermap.org/api.
+ units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin.
+ background: "square" # choose which type of background you want behind the image. Can be one of: square, cicle, none. Defaults to none.
+ type: "OpenWeather"
```
**Remarks:**
Two lines are needed in the config.yml :
```yaml
-type: "Medusa"
-apikey: "01234deb70424befb1f4ef6a23456789"
+ type: "Medusa"
+ apikey: "01234deb70424befb1f4ef6a23456789"
```
The url must be the root url of Medusa application.
Two lines are needed in the config.yml :
```yaml
-type: "Radarr" or "Sonarr"
-apikey: "01234deb70424befb1f4ef6a23456789"
+ type: "Radarr" or "Sonarr"
+ apikey: "01234deb70424befb1f4ef6a23456789"
```
The url must be the root url of Radarr/Sonarr application.
For Ping you need to set the type to Ping and provide a url.
```yaml
-items:
- - name: "Awesome app"
- type: Ping
- logo: "assets/tools/sample.png"
- subtitle: "Bookmark example" tag: "app"
- url: "https://www.reddit.com/r/selfhosted/"
+- name: "Awesome app"
+ type: Ping
+ logo: "assets/tools/sample.png"
+ subtitle: "Bookmark example" tag: "app"
+ url: "https://www.reddit.com/r/selfhosted/"
```
<Service
v-for="(item, index) in group.items"
:key="index"
- v-bind:item="item"
+ :item="item"
+ :proxy="config.proxy"
:class="['column', `is-${12 / config.columns}`]"
/>
</template>
<Service
v-for="(item, index) in group.items"
:key="index"
- v-bind:item="item"
+ :item="item"
+ :proxy="config.proxy"
/>
</div>
</div>
message: ~
links: []
services: []
+
+
+proxy: ~
\ No newline at end of file
<template>
- <component v-bind:is="component" :item="item"></component>
+ <component v-bind:is="component" :item="item" :proxy="proxy"></component>
</template>
<script>
name: "Service",
props: {
item: Object,
+ proxy: Object,
},
computed: {
component() {
</template>
<script>
+import service from "@/mixins/service.js";
import Generic from "./Generic.vue";
export default {
name: "AdGuardHome",
+ mixins: [service],
props: {
item: Object,
},
},
methods: {
fetchStatus: async function () {
- this.status = await fetch(`${this.item.url}/control/status`, {
- credentials: "include",
- })
- .then((response) => response.json())
- .catch((e) => console.log(e));
+ this.status = await this.fetch("/control/status").catch((e) =>
+ console.log(e)
+ );
},
fetchStats: async function () {
- this.stats = await fetch(`${this.item.url}/control/stats`, {
- credentials: "include",
- })
- .then((response) => response.json())
- .catch((e) => console.log(e));
+ this.stats = await this.fetch("/control/stats").catch((e) =>
+ console.log(e)
+ );
},
},
};
</template>
<script>
+import service from "@/mixins/service.js";
import Generic from "./Generic.vue";
export default {
name: "Ping",
+ mixins: [service],
props: {
item: Object,
},
},
methods: {
fetchStatus: async function () {
- const url = `${this.item.url}`;
- fetch(url, {
- method: "HEAD",
- cache: "no-cache",
- credentials: "include",
- })
- .then((response) => {
- if (!response.ok) {
- throw Error(response.statusText);
- }
+ this.fetch("/", { method: "HEAD", cache: "no-cache" }, false)
+ .then(() => {
this.status = "online";
})
.catch(() => {
--- /dev/null
+export default {
+ props: {
+ proxy: Object,
+ },
+ created: function () {
+ // custom service often consume info from an API using the item link (url) as a base url,
+ // but sometimes the base url is different. An optional alternative URL can be provided with the "endpoint" key.
+ this.endpoint = this.item.endpoint || this.item.url;
+
+ if (this.endpoint.endsWith("/")) {
+ this.endpoint = this.endpoint.slice(0, -1);
+ }
+ },
+ methods: {
+ fetch: function (path, init, json = true) {
+ let options = {};
+
+ if (this.proxy?.useCredentials) {
+ options.credentials = "include";
+ }
+
+ // Each item can override the credential settings
+ if (this.item.useCredentials !== undefined) {
+ options.credentials =
+ this.item.useCredentials === true ? "include" : "omit";
+ }
+
+ options = Object.assign(options, init);
+
+ if (path.startsWith("/")) {
+ path = path.slice(1);
+ }
+
+ return fetch(`${this.endpoint}/${path}`, options).then((response) => {
+ if (!response.ok) {
+ throw new Error("Not 2xx response");
+ }
+
+ return json ? response.json() : response;
+ });
+ },
+ },
+};