Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optional fetch with credentials #301

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ services:
tag: "other"
url: "http://192.168.0.151/admin"
type: "PiHole" # optional, loads a specific component that provides extra features. MUST MATCH a file name (without file extension) available in `src/components/services`
# fetchWithCredentials: true # optional adds cookies to fetch calls for external data. Useful for services running behind SSO clients e.g. Authelia
target: "_blank" # optional html a tag target attribute
# class: "green" # optional custom CSS class for card, useful with custom stylesheet
# background: red # optional color for card to set color directly without custom stylesheet
Expand Down
3 changes: 0 additions & 3 deletions docs/customservices.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@ If you experiencing any issue, please have a look to the [troubleshooting](troub
- 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.
Expand Down
27 changes: 27 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

If you want to contribute to Homer, please read the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/main/CONTRIBUTING.md) first.

## Local development config

When developing locally, put your `config.yml` file at `public/assets/config.yml`. The config file is included in the `.gitignore`

## Serve locally

```sh
# Using yarn (recommended)
yarn install
Expand Down Expand Up @@ -72,3 +78,24 @@ body #app.theme-my-awesome-theme. { ... }
...
@import "./themes/my-awesome-theme.scss";
```

## Fetching data with `this.fetch`

Homer wraps the standard `fetch` with a mixin that allows us to added global and per service header options to standard fetch calls. This should be transparent to development as long as you use `this.fetch` instead of the standard `fetch`.

### Basic example
```js
this.fetch(
`${this.item.url}/api/health?apikey=${this.item.apikey}`
)
```

### Example with other fetch options
```js
this.fetch(
`${this.item.url}/api/v2/config`,
{
headers: { "X-Api-Key": `${this.item.apikey}` },
}
)
```
15 changes: 15 additions & 0 deletions docs/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,18 @@ To resolve this, you can either:
* Host all your target service under the same domain & port.
* 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.
* 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.


## I use a single sign on tool, like Authelia. How do I get the service calls to not hit the login page?

All the services use the JS `fetch` function. By default, a `fetch` call doesn't include the browser cookies that store your SSO session credentials. If you want to include cookie info in the API calls, you can add the global `fetchWithCredentials` attribute to your service item. Here's an example below for PiHole.

```yml
- name: "Pi-Hole"
logo: "assets/icons/pihole.svg"
url: "https://pihole.mydomain.com/admin"
type: "PiHole"
fetchWithCredentials: true
```

> **Note for developers:** Read the information in `docs/development.md` for how to make sure your service template works with `fetchWithCredentials`
8 changes: 5 additions & 3 deletions src/assets/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
text-overflow: ellipsis;
}

html, body, body #app {
html,
body,
body #app {
height: 100%;
background-color: var(--background);
}
Expand Down Expand Up @@ -153,8 +155,8 @@ body {
background-color: var(--highlight-hover);
}
}
.navbar-menu {
background-color: inherit;
.navbar-menu {
background-color: inherit;
}
}
.navbar-end {
Expand Down
2 changes: 0 additions & 2 deletions src/components/services/AdGuardHome.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,10 @@
</template>

<script>
import service from "@/mixins/service.js";
import Generic from "./Generic.vue";

export default {
name: "AdGuardHome",
mixins: [service],
props: {
item: Object,
},
Expand Down
4 changes: 2 additions & 2 deletions src/components/services/Mealie.vue
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default {
methods: {
fetchStatus: async function () {
if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing
this.meal = await fetch(`${this.item.url}/api/meal-plans/today/`, {
this.meal = await this.fetch(`${this.item.url}/api/meal-plans/today/`, {
headers: {
Authorization: "Bearer " + this.item.apikey,
Accept: "application/json",
Expand All @@ -68,7 +68,7 @@ export default {
}
})
.catch((e) => console.log(e));
this.stats = await fetch(`${this.item.url}/api/debug/statistics/`, {
this.stats = await this.fetch(`${this.item.url}/api/debug/statistics/`, {
headers: {
Authorization: "Bearer " + this.item.apikey,
Accept: "application/json",
Expand Down
3 changes: 1 addition & 2 deletions src/components/services/Medusa.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ export default {
},
methods: {
fetchConfig: function () {
fetch(`${this.item.url}/api/v2/config`, {
credentials: "include",
this.fetch(`${this.item.url}/api/v2/config`, {
headers: { "X-Api-Key": `${this.item.apikey}` },
})
.then((response) => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/services/OpenWeather.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default {

const apiKey = this.item.apikey || this.item.apiKey;
const url = `https://api.openweathermap.org/data/2.5/weather?${locationQuery}&appid=${apiKey}&units=${this.item.units}`;
fetch(url)
this.fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
Expand Down
3 changes: 1 addition & 2 deletions src/components/services/PaperlessNG.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ export default {
return;
}
const url = `${this.item.url}/api/documents/`;
this.api = await fetch(url, {
credentials: "include",
this.api = await this.fetch(url, {
headers: {
Authorization: "Token " + this.item.apikey,
},
Expand Down
4 changes: 1 addition & 3 deletions src/components/services/PiHole.vue
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,7 @@ export default {
methods: {
fetchStatus: async function () {
const url = `${this.item.url}/api.php`;
this.api = await fetch(url, {
credentials: "include",
})
this.api = await this.fetch(url)
.then((response) => response.json())
.catch((e) => console.log(e));
},
Expand Down
2 changes: 0 additions & 2 deletions src/components/services/Ping.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
</template>

<script>
import service from "@/mixins/service.js";
import Generic from "./Generic.vue";

export default {
name: "Ping",
mixins: [service],
props: {
item: Object,
},
Expand Down
2 changes: 0 additions & 2 deletions src/components/services/Prometheus.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
</template>

<script>
import service from "@/mixins/service.js";
import Generic from "./Generic.vue";

const AlertsStatus = Object.freeze({
Expand All @@ -29,7 +28,6 @@ const AlertsStatus = Object.freeze({

export default {
name: "Prometheus",
mixins: [service],
props: {
item: Object,
},
Expand Down
8 changes: 2 additions & 6 deletions src/components/services/Radarr.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ export default {
},
methods: {
fetchConfig: function () {
fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
credentials: "include",
})
this.fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`)
.then((response) => {
if (response.status != 200) {
throw new Error(response.statusText);
Expand All @@ -94,9 +92,7 @@ export default {
console.error(e);
this.serverError = true;
});
fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
credentials: "include",
})
this.fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`)
.then((response) => {
if (response.status != 200) {
throw new Error(response.statusText);
Expand Down
8 changes: 2 additions & 6 deletions src/components/services/Sonarr.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ export default {
},
methods: {
fetchConfig: function () {
fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
credentials: "include",
})
this.fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`)
.then((response) => {
if (response.status != 200) {
throw new Error(response.statusText);
Expand All @@ -94,9 +92,7 @@ export default {
console.error(e);
this.serverError = true;
});
fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
credentials: "include",
})
this.fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`)
.then((response) => {
if (response.status != 200) {
throw new Error(response.statusText);
Expand Down
3 changes: 3 additions & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "./registerServiceWorker";

import "@fortawesome/fontawesome-free/css/all.css";

import service from "./mixins/service.js";
import "./assets/app.scss";

Vue.config.productionTip = false;
Expand All @@ -14,6 +15,8 @@ Vue.component("DynamicStyle", {
},
});

Vue.mixin(service);

new Vue({
render: (h) => h(App),
}).$mount("#app");
25 changes: 3 additions & 22 deletions src/mixins/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,8 @@ 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) {
fetch: function (path, init = {}) {
let options = {};

if (this.proxy?.useCredentials) {
Expand All @@ -26,18 +17,8 @@ export default {
}

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;
});
console.log(options);
return fetch(path, options);
},
},
};
3 changes: 3 additions & 0 deletions vue.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ module.exports = {
.loader("raw-loader")
.end();
},
configureWebpack: {
devtool: "source-map",
},
publicPath: "",
pwa: {
manifestPath: "assets/manifest.json",
Expand Down