]> git.immae.eu Git - github/bastienwirtz/homer.git/commitdiff
Merge pull request #329 from Panzer1119/main
authorBastien Wirtz <bastien.wirtz@gmail.com>
Mon, 13 Dec 2021 19:54:04 +0000 (11:54 -0800)
committerGitHub <noreply@github.com>
Mon, 13 Dec 2021 19:54:04 +0000 (11:54 -0800)
Add Lidarr service

15 files changed:
.github/workflows/integration.yml
Dockerfile
docs/configuration.md
docs/customservices.md
package.json
src/App.vue
src/components/SearchInput.vue
src/components/services/AdGuardHome.vue
src/components/services/Mealie.vue
src/components/services/Medusa.vue
src/components/services/PaperlessNG.vue
src/components/services/PiHole.vue
src/components/services/Radarr.vue
src/components/services/Sonarr.vue
yarn.lock

index 196113d1ae884f07e6dfa7346b95399c2f28025c..8d1a7ad0712b8055dbfbc81a73a29076b9f09780 100644 (file)
@@ -1,7 +1,7 @@
 # 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
+name: Integration
 
 on:
   push:
index e31d97d4af318a4c854f6d51d213c73aeae0fc34..0a43027dc75198b85e2b1302b3637842e88b698a 100644 (file)
@@ -10,7 +10,7 @@ COPY . .
 RUN yarn build
 
 # production stage
-FROM alpine:3.11
+FROM alpine:3.15
 
 ENV USER darkhttpd
 ENV GROUP darkhttpd
index e2b55060dd9a538857cc38c593d9f830af77f1d0..552b22a9b4993722bd10ab5eb3f42f7ed81f2311 100644 (file)
@@ -29,7 +29,6 @@ connectivityCheck: true # whether you want to display a message when the apps ar
 
 # 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
index 311a4f22709442b496087977baf2b56db5bc0e2e..7d475422b33f3bbe63b935a457dec73175a0a0d9 100644 (file)
@@ -18,8 +18,6 @@ If you experiencing any issue, please have a look to the [troubleshooting](troub
   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.
index c5486bbc9721fcaf484e41fd448371e90f873f31..8c8f219779ba7b6002d698c666dbd523a98438b3 100644 (file)
@@ -1,7 +1,6 @@
 {
   "name": "homer",
   "version": "21.09.1",
-  "license": "Apache-2.0",
   "scripts": {
     "serve": "vue-cli-service serve",
     "build": "vue-cli-service build",
   "dependencies": {
     "@fortawesome/fontawesome-free": "^5.15.4",
     "bulma": "^0.9.3",
-    "core-js": "^3.17.3",
+    "core-js": "^3.19.3",
     "js-yaml": "^4.1.0",
     "lodash.merge": "^4.6.2",
     "register-service-worker": "^1.7.2",
     "vue": "^2.6.14"
   },
   "devDependencies": {
-    "@vue/cli-plugin-babel": "~4.5.0",
-    "@vue/cli-plugin-eslint": "~4.5.0",
-    "@vue/cli-plugin-pwa": "~4.5.0",
-    "@vue/cli-service": "~4.5.0",
+    "@vue/cli-plugin-babel": "~4.5.15",
+    "@vue/cli-plugin-eslint": "~4.5.15",
+    "@vue/cli-plugin-pwa": "~4.5.15",
+    "@vue/cli-service": "~4.5.15",
     "@vue/eslint-config-prettier": "^6.0.0",
     "babel-eslint": "^10.1.0",
     "eslint": "^6.7.2",
@@ -31,5 +30,6 @@
     "sass": "^1.26.5",
     "sass-loader": "^8.0.2",
     "vue-template-compiler": "^2.6.12"
-  }
+  },
+  "license": "Apache-2.0"
 }
index 5c62a7fb409cfe65695870d8f54993d5c684e887..4eb112d4739dc1b60f7111dcbc65f978f2a3ee77 100644 (file)
@@ -41,7 +41,7 @@
 
         <SearchInput
           class="navbar-item is-inline-block-mobile"
-          :hotkey=searchHotkey()
+          :hotkey="searchHotkey()"
           @input="filterServices"
           @search-focus="showMenu = true"
           @search-open="navigateToFirstService"
index 586ff712886b775857608c27a7a39a684f0305d1..165c99299a552b7a893db4339aca64150805ac7d 100644 (file)
@@ -19,8 +19,8 @@ export default {
     value: String,
     hotkey: {
       type: String,
-      default: "/"
-    }
+      default: "/",
+    },
   },
   mounted() {
     this._keyListener = function (event) {
index b01f0f49a56c9f262bfc45d8409e0ae8fe464ec3..4c5339873f909e980a5fe3c5c3947ad5563bd42a 100644 (file)
@@ -76,9 +76,6 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.media-left img {
-  max-height: 100%;
-}
 .status {
   font-size: 0.8rem;
   color: var(--text-title);
index 790a9b11f05ec4a5e82f8d96e3c466a634ca41b1..b5b22558ee6a2f06dd3526ed4daf756a497448ba 100644 (file)
@@ -1,47 +1,33 @@
 <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="meal"> Today: {{ meal.name }} </template>
-                <template v-else-if="stats">
-                  happily keeping {{ stats.totalRecipes }} recipes organized
-                </template>
-              </p>
-            </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="meal"> Today: {{ meal.name }} </template>
+        <template v-else-if="stats">
+          happily keeping {{ stats.totalRecipes }} recipes organized
+        </template>
+      </p>
+    </template>
+  </Generic>
 </template>
 
 <script>
+import service from "@/mixins/service.js";
+import Generic from "./Generic.vue";
+
 export default {
   name: "Mealie",
+  mixins: [service],
   props: {
     item: Object,
   },
+  components: {
+    Generic,
+  },
   data: () => ({
     stats: null,
     meal: null,
@@ -51,44 +37,20 @@ 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/`, {
-        headers: {
-          Authorization: "Bearer " + this.item.apikey,
-          Accept: "application/json",
-        },
-      })
-        .then(function (response) {
-          if (!response.ok) {
-            throw new Error("Not 2xx response");
-          } else {
-            if (response != null) {
-              return response.json();
-            }
-          }
-        })
-        .catch((e) => console.log(e));
-      this.stats = await fetch(`${this.item.url}/api/debug/statistics/`, {
-        headers: {
-          Authorization: "Bearer " + this.item.apikey,
-          Accept: "application/json",
-        },
-      })
-        .then(function (response) {
-          if (!response.ok) {
-            throw new Error("Not 2xx response");
-          } else {
-            return response.json();
-          }
-        })
-        .catch((e) => console.log(e));
+      const headers = {
+        Authorization: "Bearer " + this.item.apikey,
+        Accept: "application/json",
+      };
+
+      if (this.item.subtitle != null) return;
+
+      this.meal = await this.fetch("/api/meal-plans/today/", { headers }).catch(
+        (e) => console.log(e)
+      );
+      this.stats = await this.fetch("/api/debug/statistics/", {
+        headers,
+      }).catch((e) => console.log(e));
     },
   },
 };
 </script>
-
-<style scoped lang="scss">
-.media-left img {
-  max-height: 100%;
-}
-</style>
index 57206493c867a525339fa797a5f963084ffb339a..43b5651fef189ee76c6013c60794f9087cf32f25 100644 (file)
@@ -1,65 +1,49 @@
 <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">{{ item.subtitle }}</p>
-            </div>
-            <div class="notifs">
-              <strong
-                v-if="config !== null && config.system.news.unread > 0"
-                class="notif news"
-                title="News"
-                >{{ config.system.news.unread }}</strong
-              >
-              <strong
-                v-if="config !== null && config.main.logs.numWarnings > 0"
-                class="notif warnings"
-                title="Warning"
-                >{{ config.main.logs.numWarnings }}</strong
-              >
-              <strong
-                v-if="config !== null && config.main.logs.numErrors > 0"
-                class="notif errors"
-                title="Error"
-                >{{ config.main.logs.numErrors }}</strong
-              >
-              <strong
-                v-if="serverError"
-                class="notif errors"
-                title="Connection error to Medusa API, check url and apikey in config.yml"
-                >?</strong
-              >
-            </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 class="notifs">
+        <strong
+          v-if="config !== null && config.system.news.unread > 0"
+          class="notif news"
+          title="News"
+          >{{ config.system.news.unread }}</strong
+        >
+        <strong
+          v-if="config !== null && config.main.logs.numWarnings > 0"
+          class="notif warnings"
+          title="Warning"
+          >{{ config.main.logs.numWarnings }}</strong
+        >
+        <strong
+          v-if="config !== null && config.main.logs.numErrors > 0"
+          class="notif errors"
+          title="Error"
+          >{{ config.main.logs.numErrors }}</strong
+        >
+        <strong
+          v-if="serverError"
+          class="notif errors"
+          title="Connection error to Medusa API, check url and apikey in config.yml"
+          >?</strong
+        >
+      </div>
+    </template>
+  </Generic>
 </template>
 
 <script>
+import service from "@/mixins/service.js";
+import Generic from "./Generic.vue";
+
 export default {
   name: "Medusa",
+  mixins: [service],
   props: {
     item: Object,
   },
+  components: {
+    Generic,
+  },
   data: () => {
     return {
       config: null,
@@ -71,16 +55,9 @@ export default {
   },
   methods: {
     fetchConfig: function () {
-      fetch(`${this.item.url}/api/v2/config`, {
-        credentials: "include",
-        headers: { "X-Api-Key": `${this.item.apikey}` },
+      this.fetch("/api/v2/config", {
+        headers: { "X-Api-Key": this.item.apikey },
       })
-        .then((response) => {
-          if (response.status != 200) {
-            throw new Error(response.statusText);
-          }
-          return response.json();
-        })
         .then((conf) => {
           this.config = conf;
         })
@@ -94,35 +71,32 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.media-left img {
-  max-height: 100%;
-}
 .notifs {
   position: absolute;
   color: white;
   font-family: sans-serif;
   top: 0.3em;
   right: 0.5em;
-}
-.notif {
-  padding-right: 0.35em;
-  padding-left: 0.35em;
-  padding-top: 0.2em;
-  padding-bottom: 0.2em;
-  border-radius: 0.25em;
-  position: relative;
-  margin-left: 0.3em;
-  font-size: 0.8em;
-}
-.news {
-  background-color: #777777;
-}
+  .notif {
+    padding-right: 0.35em;
+    padding-left: 0.35em;
+    padding-top: 0.2em;
+    padding-bottom: 0.2em;
+    border-radius: 0.25em;
+    position: relative;
+    margin-left: 0.3em;
+    font-size: 0.8em;
+    &.news {
+      background-color: #777777;
+    }
 
-.warnings {
-  background-color: #d08d2e;
-}
+    &.warnings {
+      background-color: #d08d2e;
+    }
 
-.errors {
-  background-color: #e51111;
+    &.errors {
+      background-color: #e51111;
+    }
+  }
 }
 </style>
index af13317859e1f7e1117cf29bcf9ec02adf9b9ce6..69f2437378a7249d1103269f94a190d35a124d56 100644 (file)
@@ -1,46 +1,32 @@
 <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="api">
-                  happily storing {{ api.count }} documents
-                </template>
-              </p>
-            </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="api">
+          happily storing {{ api.count }} documents
+        </template>
+      </p>
+    </template>
+  </Generic>
 </template>
 
 <script>
+import service from "@/mixins/service.js";
+import Generic from "./Generic.vue";
+
 export default {
   name: "Paperless",
+  mixins: [service],
   props: {
     item: Object,
   },
+  components: {
+    Generic,
+  },
   data: () => ({
     api: null,
   }),
@@ -49,36 +35,21 @@ export default {
   },
   methods: {
     fetchStatus: async function () {
-      if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing
-      var apikey = this.item.apikey;
+      if (this.item.subtitle != null) return;
+
+      const apikey = this.item.apikey;
       if (!apikey) {
         console.error(
           "apikey is not present in config.yml for the paperless entry!"
         );
         return;
       }
-      const url = `${this.item.url}/api/documents/`;
-      this.api = await fetch(url, {
-        credentials: "include",
+      this.api = await this.fetch("/api/documents/", {
         headers: {
           Authorization: "Token " + this.item.apikey,
         },
-      })
-        .then(function (response) {
-          if (!response.ok) {
-            throw new Error("Not 2xx response");
-          } else {
-            return response.json();
-          }
-        })
-        .catch((e) => console.log(e));
+      }).catch((e) => console.log(e));
     },
   },
 };
 </script>
-
-<style scoped lang="scss">
-.media-left img {
-  max-height: 100%;
-}
-</style>
index 87f7090d4ad8b16f47b5a2285db872f8102074ba..9aac01670009e5c704ecfe743fe7fe32045c5fbb 100644 (file)
@@ -1,59 +1,45 @@
 <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="api">
-                  {{ percentage }}&percnt; blocked
-                </template>
-              </p>
-            </div>
-            <div v-if="api" class="status" :class="api.status">
-              {{ api.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 #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="percentage">
+          {{ percentage }}&percnt; blocked
+        </template>
+      </p>
+    </template>
+    <template #indicator>
+      <div v-if="status" class="status" :class="status">
+        {{ status }}
+      </div>
+    </template>
+  </Generic>
 </template>
 
 <script>
+import service from "@/mixins/service.js";
+import Generic from "./Generic.vue";
+
 export default {
   name: "PiHole",
+  mixins: [service],
   props: {
     item: Object,
   },
+  components: {
+    Generic,
+  },
   data: () => ({
-    api: {
-      status: "",
-      ads_percentage_today: 0,
-    },
+    status: "",
+    ads_percentage_today: 0,
   }),
   computed: {
     percentage: function () {
-      if (this.api) {
-        return this.api.ads_percentage_today.toFixed(1);
+      if (this.ads_percentage_today) {
+        return this.ads_percentage_today.toFixed(1);
       }
       return "";
     },
@@ -63,21 +49,16 @@ export default {
   },
   methods: {
     fetchStatus: async function () {
-      const url = `${this.item.url}/api.php`;
-      this.api = await fetch(url, {
-        credentials: "include",
-      })
-        .then((response) => response.json())
-        .catch((e) => console.log(e));
+      const result = await this.fetch("/api.php").catch((e) => console.log(e));
+
+      this.status = result.status;
+      this.ads_percentage_today = result.ads_percentage_today;
     },
   },
 };
 </script>
 
 <style scoped lang="scss">
-.media-left img {
-  max-height: 100%;
-}
 .status {
   font-size: 0.8rem;
   color: var(--text-title);
index a9cdedf0defb4ee336f829d539395e902f2febde..a57c895f0e0e9bc9e10b48f5ef5c08c3df837e1b 100644 (file)
@@ -1,62 +1,40 @@
 <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">{{ item.subtitle }}</p>
-            </div>
-            <div class="notifs">
-              <strong
-                v-if="activity > 0"
-                class="notif activity"
-                title="Activity"
-                >{{ activity }}</strong
-              >
-              <strong
-                v-if="warnings > 0"
-                class="notif warnings"
-                title="Warning"
-                >{{ warnings }}</strong
-              >
-              <strong v-if="errors > 0" class="notif errors" title="Error">{{
-                errors
-              }}</strong>
-              <strong
-                v-if="serverError"
-                class="notif errors"
-                title="Connection error to Radarr API, check url and apikey in config.yml"
-                >?</strong
-              >
-            </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 class="notifs">
+        <strong v-if="activity > 0" class="notif activity" title="Activity">
+          {{ activity }}
+        </strong>
+        <strong v-if="warnings > 0" class="notif warnings" title="Warning">
+          {{ warnings }}
+        </strong>
+        <strong v-if="errors > 0" class="notif errors" title="Error">
+          {{ errors }}
+        </strong>
+        <strong
+          v-if="serverError"
+          class="notif errors"
+          title="Connection error to Radarr API, check url and apikey in config.yml"
+          >?</strong
+        >
+      </div>
+    </template>
+  </Generic>
 </template>
 
 <script>
+import service from "@/mixins/service.js";
+import Generic from "./Generic.vue";
+
 export default {
   name: "Radarr",
+  mixins: [service],
   props: {
     item: Object,
   },
+  components: {
+    Generic,
+  },
   data: () => {
     return {
       activity: null,
@@ -70,15 +48,7 @@ export default {
   },
   methods: {
     fetchConfig: function () {
-      fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
-        credentials: "include",
-      })
-        .then((response) => {
-          if (response.status != 200) {
-            throw new Error(response.statusText);
-          }
-          return response.json();
-        })
+      this.fetch(`/api/health?apikey=${this.item.apikey}`)
         .then((health) => {
           this.warnings = 0;
           this.errors = 0;
@@ -94,15 +64,7 @@ export default {
           console.error(e);
           this.serverError = true;
         });
-      fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
-        credentials: "include",
-      })
-        .then((response) => {
-          if (response.status != 200) {
-            throw new Error(response.statusText);
-          }
-          return response.json();
-        })
+      this.fetch(`/api/queue?apikey=${this.item.apikey}`)
         .then((queue) => {
           this.activity = 0;
           for (var i = 0; i < queue.length; i++) {
@@ -121,35 +83,30 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.media-left img {
-  max-height: 100%;
-}
 .notifs {
   position: absolute;
   color: white;
   font-family: sans-serif;
   top: 0.3em;
   right: 0.5em;
-}
-.notif {
-  padding-right: 0.35em;
-  padding-left: 0.35em;
-  padding-top: 0.2em;
-  padding-bottom: 0.2em;
-  border-radius: 0.25em;
-  position: relative;
-  margin-left: 0.3em;
-  font-size: 0.8em;
-}
-.activity {
-  background-color: #4fb5d6;
-}
+  .notif {
+    display: inline-block;
+    padding: 0.2em 0.35em;
+    border-radius: 0.25em;
+    position: relative;
+    margin-left: 0.3em;
+    font-size: 0.8em;
+    &.activity {
+      background-color: #4fb5d6;
+    }
 
-.warnings {
-  background-color: #d08d2e;
-}
+    &.warnings {
+      background-color: #d08d2e;
+    }
 
-.errors {
-  background-color: #e51111;
+    &.errors {
+      background-color: #e51111;
+    }
+  }
 }
 </style>
index 0270255995bfe9e35625d95c0aef81daf39c4053..f8dd0d1ac4e0e0db80bc19645161f6d79987b9d6 100644 (file)
@@ -1,62 +1,41 @@
 <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">{{ item.subtitle }}</p>
-            </div>
-            <div class="notifs">
-              <strong
-                v-if="activity > 0"
-                class="notif activity"
-                title="Activity"
-                >{{ activity }}</strong
-              >
-              <strong
-                v-if="warnings > 0"
-                class="notif warnings"
-                title="Warning"
-                >{{ warnings }}</strong
-              >
-              <strong v-if="errors > 0" class="notif errors" title="Error">{{
-                errors
-              }}</strong>
-              <strong
-                v-if="serverError"
-                class="notif errors"
-                title="Connection error to Sonarr API, check url and apikey in config.yml"
-                >?</strong
-              >
-            </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 class="notifs">
+        <strong v-if="activity > 0" class="notif activity" title="Activity">
+          {{ activity }}
+        </strong>
+        <strong v-if="warnings > 0" class="notif warnings" title="Warning">
+          {{ warnings }}
+        </strong>
+        <strong v-if="errors > 0" class="notif errors" title="Error">
+          {{ errors }}
+        </strong>
+        <strong
+          v-if="serverError"
+          class="notif errors"
+          title="Connection error to Sonarr API, check url and apikey in config.yml"
+        >
+          ?
+        </strong>
+      </div>
+    </template>
+  </Generic>
 </template>
 
 <script>
+import service from "@/mixins/service.js";
+import Generic from "./Generic.vue";
+
 export default {
   name: "Sonarr",
+  mixins: [service],
   props: {
     item: Object,
   },
+  components: {
+    Generic,
+  },
   data: () => {
     return {
       activity: null,
@@ -70,15 +49,7 @@ export default {
   },
   methods: {
     fetchConfig: function () {
-      fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
-        credentials: "include",
-      })
-        .then((response) => {
-          if (response.status != 200) {
-            throw new Error(response.statusText);
-          }
-          return response.json();
-        })
+      this.fetch(`/api/health?apikey=${this.item.apikey}`)
         .then((health) => {
           this.warnings = 0;
           this.errors = 0;
@@ -94,15 +65,7 @@ export default {
           console.error(e);
           this.serverError = true;
         });
-      fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
-        credentials: "include",
-      })
-        .then((response) => {
-          if (response.status != 200) {
-            throw new Error(response.statusText);
-          }
-          return response.json();
-        })
+      this.fetch(`/api/queue?apikey=${this.item.apikey}`)
         .then((queue) => {
           this.activity = 0;
           for (var i = 0; i < queue.length; i++) {
@@ -121,35 +84,32 @@ export default {
 </script>
 
 <style scoped lang="scss">
-.media-left img {
-  max-height: 100%;
-}
 .notifs {
   position: absolute;
   color: white;
   font-family: sans-serif;
   top: 0.3em;
   right: 0.5em;
-}
-.notif {
-  padding-right: 0.35em;
-  padding-left: 0.35em;
-  padding-top: 0.2em;
-  padding-bottom: 0.2em;
-  border-radius: 0.25em;
-  position: relative;
-  margin-left: 0.3em;
-  font-size: 0.8em;
-}
-.activity {
-  background-color: #4fb5d6;
-}
 
-.warnings {
-  background-color: #d08d2e;
-}
+  .notif {
+    display: inline-block;
+    padding: 0.2em 0.35em;
+    border-radius: 0.25em;
+    position: relative;
+    margin-left: 0.3em;
+    font-size: 0.8em;
+
+    &.activity {
+      background-color: #4fb5d6;
+    }
+
+    &.warnings {
+      background-color: #d08d2e;
+    }
 
-.errors {
-  background-color: #e51111;
+    &.errors {
+      background-color: #e51111;
+    }
+  }
 }
 </style>
index 18906e18338c1c2dd848f4d136588d25e20aa4f0..ca36eb70dbb937cdc521e2d8fced4e0ae9823de8 100644 (file)
--- a/yarn.lock
+++ b/yarn.lock
     lodash.kebabcase "^4.1.1"
     svg-tags "^1.0.0"
 
-"@vue/babel-preset-app@^4.5.13":
-  version "4.5.13"
-  resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.5.13.tgz#cb475321e4c73f7f110dac29a48c2a9cb80afeb6"
-  integrity sha512-pM7CR3yXB6L8Gfn6EmX7FLNE3+V/15I3o33GkSNsWvgsMp6HVGXKkXgojrcfUUauyL1LZOdvTmu4enU2RePGHw==
+"@vue/babel-preset-app@^4.5.15":
+  version "4.5.15"
+  resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.5.15.tgz#f6bc08f8f674e98a260004234cde18b966d72eb0"
+  integrity sha512-J+YttzvwRfV1BPczf8r3qCevznYk+jh531agVF+5EYlHF4Sgh/cGXTz9qkkiux3LQgvhEGXgmCteg1n38WuuKg==
   dependencies:
     "@babel/core" "^7.11.0"
     "@babel/helper-compilation-targets" "^7.9.6"
     "@vue/babel-plugin-transform-vue-jsx" "^1.2.1"
     camelcase "^5.0.0"
 
-"@vue/cli-overlay@^4.5.13":
-  version "4.5.13"
-  resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.5.13.tgz#4f1fd2161be8f69d6cba8079f3f0d7dc4dee47a7"
-  integrity sha512-jhUIg3klgi5Cxhs8dnat5hi/W2tQJvsqCxR0u6hgfSob0ORODgUBlN+F/uwq7cKIe/pzedVUk1y07F13GQvPqg==
+"@vue/cli-overlay@^4.5.15":
+  version "4.5.15"
+  resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.5.15.tgz#0700fd6bad39336d4189ba3ff7d25e638e818c9c"
+  integrity sha512-0zI0kANAVmjFO2LWGUIzdGPMeE3+9k+KeRDXsUqB30YfRF7abjfiiRPq5BU9pOzlJbVdpRkisschBrvdJqDuDg==
 
-"@vue/cli-plugin-babel@~4.5.0":
-  version "4.5.13"
-  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.13.tgz#a89c482edcc4ea1d135645cec502a7f5fd4c30e7"
-  integrity sha512-ykvEAfD8PgGs+dGMGqr7l/nRmIS39NRzWLhMluPLTvDV1L+IxcoB73HNLGA/aENDpl8CuWrTE+1VgydcOhp+wg==
+"@vue/cli-plugin-babel@~4.5.15":
+  version "4.5.15"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.15.tgz#ae4fb2ed54255fe3d84df381dab68509641179ed"
+  integrity sha512-hBLrwYfFkHldEe34op/YNgPhpOWI5n5DB2Qt9I/1Epeif4M4iFaayrgjvOR9AVM6WbD3Yx7WCFszYpWrQZpBzQ==
   dependencies:
     "@babel/core" "^7.11.0"
-    "@vue/babel-preset-app" "^4.5.13"
-    "@vue/cli-shared-utils" "^4.5.13"
+    "@vue/babel-preset-app" "^4.5.15"
+    "@vue/cli-shared-utils" "^4.5.15"
     babel-loader "^8.1.0"
     cache-loader "^4.1.0"
     thread-loader "^2.1.3"
     webpack "^4.0.0"
 
-"@vue/cli-plugin-eslint@~4.5.0":
-  version "4.5.13"
-  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.13.tgz#8baf22d0d96d76720c7506646b96f4f62c05bdfa"
-  integrity sha512-yc2uXX6aBiy3vEf5TwaueaDqQbdIXIhk0x0KzEtpPo23jBdLkpOSoU5NCgE06g/ZiGAcettpmBSv73Hfp4wHEw==
+"@vue/cli-plugin-eslint@~4.5.15":
+  version "4.5.15"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.15.tgz#5781824a941f34c26336a67b1f6584a06c6a24ff"
+  integrity sha512-/2Fl6wY/5bz3HD035oSnFRMsKNxDxU396KqBdpCQdwdvqk4mm6JAbXqihpcBRTNPeTO6w+LwGe6FE56PVbJdbg==
   dependencies:
-    "@vue/cli-shared-utils" "^4.5.13"
+    "@vue/cli-shared-utils" "^4.5.15"
     eslint-loader "^2.2.1"
     globby "^9.2.0"
     inquirer "^7.1.0"
     webpack "^4.0.0"
     yorkie "^2.0.0"
 
-"@vue/cli-plugin-pwa@~4.5.0":
-  version "4.5.13"
-  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.13.tgz#a800639814b6f62a38f04198c340cfaee7295c3f"
-  integrity sha512-uU5pp94VU0YscfKq/mNRsKOdxG+CTqVlZWaYkRc+HCcwkJ/m/CnxgaEqQFr0QpHC8zmlX4gILO1RVYygJoR9tw==
+"@vue/cli-plugin-pwa@~4.5.15":
+  version "4.5.15"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.15.tgz#eb800c418d96b496deec9d063a1798fe6e9c2db8"
+  integrity sha512-yQzsspaIkjeQyN6btF8ATgbJFU023q1HC8uUpmiBa4QE9EyBlR8fSrKFhcJ0EmT6KnU7PMwlnOJ/OqjguFnufA==
   dependencies:
-    "@vue/cli-shared-utils" "^4.5.13"
+    "@vue/cli-shared-utils" "^4.5.15"
     webpack "^4.0.0"
     workbox-webpack-plugin "^4.3.1"
 
-"@vue/cli-plugin-router@^4.5.13":
-  version "4.5.13"
-  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.13.tgz#0b67c8898a2bf132941919a2a2e5f3aacbd9ffbe"
-  integrity sha512-tgtMDjchB/M1z8BcfV4jSOY9fZSMDTPgF9lsJIiqBWMxvBIsk9uIZHxp62DibYME4CCKb/nNK61XHaikFp+83w==
+"@vue/cli-plugin-router@^4.5.15":
+  version "4.5.15"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.15.tgz#1e75c8c89df42c694f143b9f1028de3cf5d61e1e"
+  integrity sha512-q7Y6kP9b3k55Ca2j59xJ7XPA6x+iSRB+N4ac0ZbcL1TbInVQ4j5wCzyE+uqid40hLy4fUdlpl4X9fHJEwuVxPA==
   dependencies:
-    "@vue/cli-shared-utils" "^4.5.13"
+    "@vue/cli-shared-utils" "^4.5.15"
 
-"@vue/cli-plugin-vuex@^4.5.13":
-  version "4.5.13"
-  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.13.tgz#98646d8bc1e69cf6c6a6cba2fed3eace0356c360"
-  integrity sha512-I1S9wZC7iI0Wn8kw8Zh+A2Qkf6s1M6vTGBkx8boXjuzfwEEyEHRxadsVCecZc8Mkpydo0nykj+MyYF96TKFuVA==
+"@vue/cli-plugin-vuex@^4.5.15":
+  version "4.5.15"
+  resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.15.tgz#466c1f02777d02fef53a9bb49a36cc3a3bcfec4e"
+  integrity sha512-fqap+4HN+w+InDxlA3hZTOGE0tzBTgXhKLoDydhywqgmhQ1D9JA6Feh94ze6tG8DsWX58/ujYUqA8jAz17FJtg==
 
-"@vue/cli-service@~4.5.0":
-  version "4.5.13"
-  resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.5.13.tgz#a09e684a801684b6e24e5414ad30650970eec9ed"
-  integrity sha512-CKAZN4iokMMsaUyJRU22oUAz3oS/X9sVBSKAF2/shFBV5xh3jqAlKl8OXZYz4cXGFLA6djNuYrniuLAo7Ku97A==
+"@vue/cli-service@~4.5.15":
+  version "4.5.15"
+  resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.5.15.tgz#0e9a186d51550027d0e68e95042077eb4d115b45"
+  integrity sha512-sFWnLYVCn4zRfu45IcsIE9eXM0YpDV3S11vlM2/DVbIPAGoYo5ySpSof6aHcIvkeGsIsrHFpPHzNvDZ/efs7jA==
   dependencies:
     "@intervolga/optimize-cssnano-plugin" "^1.0.5"
     "@soda/friendly-errors-webpack-plugin" "^1.7.1"
     "@types/minimist" "^1.2.0"
     "@types/webpack" "^4.0.0"
     "@types/webpack-dev-server" "^3.11.0"
-    "@vue/cli-overlay" "^4.5.13"
-    "@vue/cli-plugin-router" "^4.5.13"
-    "@vue/cli-plugin-vuex" "^4.5.13"
-    "@vue/cli-shared-utils" "^4.5.13"
+    "@vue/cli-overlay" "^4.5.15"
+    "@vue/cli-plugin-router" "^4.5.15"
+    "@vue/cli-plugin-vuex" "^4.5.15"
+    "@vue/cli-shared-utils" "^4.5.15"
     "@vue/component-compiler-utils" "^3.1.2"
     "@vue/preload-webpack-plugin" "^1.1.0"
     "@vue/web-component-wrapper" "^1.2.0"
   optionalDependencies:
     vue-loader-v16 "npm:vue-loader@^16.1.0"
 
-"@vue/cli-shared-utils@^4.5.13":
-  version "4.5.13"
-  resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.13.tgz#acd40f31b4790f1634292bdaa5fca95dc1e0ff50"
-  integrity sha512-HpnOrkLg42RFUsQGMJv26oTG3J3FmKtO2WSRhKIIL+1ok3w9OjGCtA3nMMXN27f9eX14TqO64M36DaiSZ1fSiw==
+"@vue/cli-shared-utils@^4.5.15":
+  version "4.5.15"
+  resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.15.tgz#dba3858165dbe3465755f256a4890e69084532d6"
+  integrity sha512-SKaej9hHzzjKSOw1NlFmc6BSE0vcqUQMQiv1cxQ2DhVyy4QxZXBmzmiLBUBe+hYZZs1neXW7n//udeN9bCAY+Q==
   dependencies:
     "@hapi/joi" "^15.0.1"
     chalk "^2.4.2"