- 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).
- 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.
-### Roadmap
-
-If 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)!
-Feel free to open an issue if you have any question.
# Ground Rules
### How to submit a contribution
The general process to submit a contribution is as follow:
-1. Create your own fork of the code
-2. Do the changes in your fork
-3. Make sure to fill the [pull request description](https://github.com/bastienwirtz/homer/blob/main/.github/PULL_REQUEST_TEMPLATE.md) properly.
+1. Take a look to the [development guideline](https://github.com/bastienwirtz/homer/blob/main/docs/development.md).
+2. Create your own fork of the code
+3. Do the changes in your fork
+4. Make sure to fill the [pull request description](https://github.com/bastienwirtz/homer/blob/main/.github/PULL_REQUEST_TEMPLATE.md) properly.
### Happy coding :metal:
# Development
+If you want to contribute to Homer, please read the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/main/CONTRIBUTING.md) first.
+
```sh
# Using yarn (recommended)
yarn install
npm run serve
```
+## Custom services
+
+Custom services are small VueJs component (see `src/components/services/`) that add little features to a classic, "static", dashboard item. It should be very simple.
+A dashboard can contain a lot of items, so performance is very important.
+
+The [`Generic`](https://github.com/bastienwirtz/homer/blob/main/src/components/services/Generic.vue) service provides a typical card layout which
+you 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`.
+Each one is **optional**, and will display the usual information if omitted.
+
+Each service must implement the `item` [property](https://vuejs.org/v2/guide/components-props.html) and bind it the Generic component if used.
+
+### Skeleton
+```Vue
+<template>
+ <Generic :item="item">
+ <template #icon>
+ <!-- left area containing the icon -->
+ </template>
+ <template #content>
+ <!-- main area containing the title, subtitle, ... -->
+ </template>
+ <template #indicator>
+ <!-- top right area, empty by default -->
+ </template>
+ </Generic>
+</template>
+
+<script>
+import Generic from "./Generic.vue";
+
+export default {
+ name: "MyNewService",
+ props: {
+ item: Object,
+ },
+ components: {
+ Generic,
+ }
+};
+</script>
+```
+
+
## Themes
Themes are meant to be simple customization (written in [scss](https://sass-lang.com/documentation/syntax)).
export default {
name: "Service",
- components: {
- Generic,
- },
props: {
item: Object,
},
computed: {
component() {
const type = this.item.type || "Generic";
- if (type == "Generic") {
+ if (type === "Generic") {
return Generic;
}
return () => import(`./services/${type}.vue`);
<template>
- <div>
- <div class="card" :class="item.class">
- <a :href="item.url" :target="item.target" rel="noreferrer">
- <div class="card-content">
- <div class="media">
- <div v-if="item.logo" class="media-left">
- <figure class="image is-48x48">
- <img :src="item.logo" :alt="`${item.name} logo`" />
- </figure>
- </div>
- <div v-if="item.icon" class="media-left">
- <figure class="image is-48x48">
- <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
- </figure>
- </div>
- <div class="media-content">
- <p class="title is-4">{{ item.name }}</p>
- <p class="subtitle is-6">
- <template v-if="item.subtitle">
- {{ item.subtitle }}
- </template>
- <template v-else-if="stats">
- {{ percentage }}% blocked
- </template>
- </p>
- </div>
- <div class="status" :class="protection">
- {{ protection }}
- </div>
- </div>
- <div class="tag" :class="item.tagstyle" v-if="item.tag">
- <strong class="tag-text">#{{ item.tag }}</strong>
- </div>
- </div>
- </a>
- </div>
- </div>
+ <Generic :item="item">
+ <template #content>
+ <p class="title is-4">{{ item.name }}</p>
+ <p class="subtitle is-6">
+ <template v-if="item.subtitle">
+ {{ item.subtitle }}
+ </template>
+ <template v-else-if="stats">
+ {{ percentage }}% blocked
+ </template>
+ </p>
+ </template>
+ <template #indicator>
+ <div class="status" :class="protection">
+ {{ protection }}
+ </div>
+ </template>
+ </Generic>
</template>
<script>
+import Generic from "./Generic.vue";
+
export default {
name: "AdGuardHome",
props: {
item: Object,
},
+ components: {
+ Generic,
+ },
data: () => {
return {
status: null,
<a :href="item.url" :target="item.target" rel="noreferrer">
<div class="card-content">
<div :class="mediaClass">
- <div v-if="item.logo" class="media-left">
- <figure class="image is-48x48">
- <img :src="item.logo" :alt="`${item.name} logo`" />
- </figure>
- </div>
- <div v-if="item.icon" class="media-left">
- <figure class="image is-48x48">
- <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
- </figure>
- </div>
+ <slot name="icon">
+ <div v-if="item.logo" class="media-left">
+ <figure class="image is-48x48">
+ <img :src="item.logo" :alt="`${item.name} logo`" />
+ </figure>
+ </div>
+ <div v-if="item.icon" class="media-left">
+ <figure class="image is-48x48">
+ <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
+ </figure>
+ </div>
+ </slot>
<div class="media-content">
- <p class="title is-4">{{ item.name }}</p>
- <p class="subtitle is-6" v-if="item.subtitle">
- {{ item.subtitle }}
- </p>
+ <slot name="content">
+ <p class="title is-4">{{ item.name }}</p>
+ <p class="subtitle is-6" v-if="item.subtitle">
+ {{ item.subtitle }}
+ </p>
+ </slot>
</div>
+ <slot name="indicator" class="indicator"></slot>
</div>
<div class="tag" :class="item.tagstyle" v-if="item.tag">
<strong class="tag-text">#{{ item.tag }}</strong>
<template>
- <div>
- <div class="card" :class="item.class">
- <a :href="item.url" :target="item.target" rel="noreferrer">
- <div class="card-content">
- <div class="media">
- <div v-if="item.logo" class="media-left">
- <figure class="image is-48x48">
- <img :src="item.logo" :alt="`${item.name} logo`" />
- </figure>
- </div>
- <div v-if="item.icon" class="media-left">
- <figure class="image is-48x48">
- <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
- </figure>
- </div>
- <div class="media-content">
- <p class="title is-4">{{ item.name }}</p>
- <p class="subtitle is-6">
- <template v-if="item.subtitle">
- {{ item.subtitle }}
- </template>
- </p>
- </div>
- <div v-if="status" class="status" :class="status">
- {{ status }}
- </div>
- </div>
- <div class="tag" :class="item.tagstyle" v-if="item.tag">
- <strong class="tag-text">#{{ item.tag }}</strong>
- </div>
- </div>
- </a>
- </div>
- </div>
+ <Generic :item="item">
+ <template #indicator>
+ <div v-if="status" class="status" :class="status">
+ {{ status }}
+ </div>
+ </template>
+ </Generic>
</template>
<script>
+import Generic from "./Generic.vue";
+
export default {
name: "Ping",
props: {
item: Object,
},
+ components: {
+ Generic,
+ },
data: () => ({
status: null,
}),
</script>
<style scoped lang="scss">
-.media-left img {
- max-height: 100%;
-}
.status {
font-size: 0.8rem;
color: var(--text-title);