aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/integration.yml2
-rw-r--r--Dockerfile2
-rw-r--r--README.md2
-rw-r--r--docs/configuration.md4
-rw-r--r--docs/customservices.md60
-rw-r--r--docs/tips-and-tricks.md2
-rw-r--r--docs/troubleshooting.md6
-rw-r--r--package.json14
-rw-r--r--public/assets/additional-page.yml.dist (renamed from public/assets/additionnal-page.yml.dist)4
-rw-r--r--public/assets/config.yml.dist6
-rw-r--r--src/App.vue15
-rw-r--r--src/components/GetStarted.vue35
-rw-r--r--src/components/SearchInput.vue12
-rw-r--r--src/components/services/AdGuardHome.vue3
-rw-r--r--src/components/services/Lidarr.vue110
-rw-r--r--src/components/services/Mealie.vue106
-rw-r--r--src/components/services/Medusa.vue142
-rw-r--r--src/components/services/OpenWeather.vue3
-rw-r--r--src/components/services/PaperlessNG.vue79
-rw-r--r--src/components/services/PiHole.vue85
-rw-r--r--src/components/services/Portainer.vue131
-rw-r--r--src/components/services/Prowlarr.vue94
-rw-r--r--src/components/services/Radarr.vue156
-rw-r--r--src/components/services/Sonarr.vue158
-rw-r--r--src/mixins/service.js8
-rw-r--r--yarn.lock116
26 files changed, 809 insertions, 546 deletions
diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml
index 196113d..8d1a7ad 100644
--- a/.github/workflows/integration.yml
+++ b/.github/workflows/integration.yml
@@ -1,7 +1,7 @@
1# 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 1# 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
2# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 2# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 3
4name: Node.js CI 4name: Integration
5 5
6on: 6on:
7 push: 7 push:
diff --git a/Dockerfile b/Dockerfile
index e31d97d..0a43027 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -10,7 +10,7 @@ COPY . .
10RUN yarn build 10RUN yarn build
11 11
12# production stage 12# production stage
13FROM alpine:3.11 13FROM alpine:3.15
14 14
15ENV USER darkhttpd 15ENV USER darkhttpd
16ENV GROUP darkhttpd 16ENV GROUP darkhttpd
diff --git a/README.md b/README.md
index 2dd2b42..5ea80fb 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@
56- Search 56- Search
57- Grouping 57- Grouping
58- Theme customization 58- Theme customization
59- Offline heath check 59- Offline health check
60- keyboard shortcuts: 60- keyboard shortcuts:
61 - `/` Start searching. 61 - `/` Start searching.
62 - `Escape` Stop searching. 62 - `Escape` Stop searching.
diff --git a/docs/configuration.md b/docs/configuration.md
index 65fd018..2083be3 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -19,6 +19,9 @@ logo: "assets/logo.png"
19# icon: "fas fa-skull-crossbones" 19# icon: "fas fa-skull-crossbones"
20 20
21header: true # Set to false to hide the header 21header: true # Set to false to hide the header
22# Optional: Different hotkey for search, defaults to "/"
23# hotkey:
24# search: "Shift"
22footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a href="https://vuejs.org/">vuejs</a> & <a href="https://fontawesome.com/">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p>' # set false if you want to hide it. 25footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a href="https://vuejs.org/">vuejs</a> & <a href="https://fontawesome.com/">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p>' # set false if you want to hide it.
23 26
24columns: "3" # "auto" or number (must be a factor of 12: 1, 2, 3, 4, 6, 12) 27columns: "3" # "auto" or number (must be a factor of 12: 1, 2, 3, 4, 6, 12)
@@ -26,7 +29,6 @@ connectivityCheck: true # whether you want to display a message when the apps ar
26 29
27# Optional: Proxy / hosting option 30# Optional: Proxy / hosting option
28proxy: 31proxy:
29 # NOT All custom services implements this new option YET. Support will be extended very soon.
30 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. 32 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.
31 33
32# Set the default layout and color scheme 34# Set the default layout and color scheme
diff --git a/docs/customservices.md b/docs/customservices.md
index 7a151d1..cb68e1d 100644
--- a/docs/customservices.md
+++ b/docs/customservices.md
@@ -1,7 +1,11 @@
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 and, where applicable, an apikey. Note that config.yml is exposed at /assets/config.yml via HTTP and any
5apikey included in the configuration file is exposed to anyone who can access the homer instance. Only include an apikey
6if your homer instance is secured behind some form of authentication or access restriction.
7
8Available services are in `src/components/`. Here is an overview of all custom services that are available
5within Homer. 9within Homer.
6 10
7If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page. 11If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page.
@@ -18,8 +22,6 @@ If you experiencing any issue, please have a look to the [troubleshooting](troub
18 type: "<type>" 22 type: "<type>"
19``` 23```
20 24
21⚠️🚧 `endpoint` & `useCredentials` new options are not yet supported by all custom services (but will be very soon).
22
23## PiHole 25## PiHole
24 26
25Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard. 27Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard.
@@ -43,7 +45,7 @@ The following configuration is available for the OpenWeatherMap service:
43- name: "Weather" 45- name: "Weather"
44 location: "Amsterdam" # your location. 46 location: "Amsterdam" # your location.
45 locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy 47 locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy
46 apiKey: "<---insert-api-key-here--->" # insert your own API key here. Request one from https://openweathermap.org/api. 48 apikey: "<---insert-api-key-here--->" # insert your own API key here. Request one from https://openweathermap.org/api.
47 units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin. 49 units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin.
48 background: "square" # choose which type of background you want behind the image. Can be one of: square, cicle, none. Defaults to none. 50 background: "square" # choose which type of background you want behind the image. Can be one of: square, cicle, none. Defaults to none.
49 type: "OpenWeather" 51 type: "OpenWeather"
@@ -65,22 +67,39 @@ Two lines are needed in the config.yml :
65The url must be the root url of Medusa application. 67The url must be the root url of Medusa application.
66The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API. 68The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API.
67 69
68## Sonarr/Radarr 70## Lidarr, Prowlarr, Sonarr and Radarr
69 71
70This service displays Activity (blue), Warning (orange) or Error (red) notifications bubbles from the Radarr/Sonarr application. 72This service displays Activity (blue), Warning (orange) or Error (red) notifications bubbles from the Lidarr, Radarr or Sonarr application.
71Two lines are needed in the config.yml : 73Two lines are needed in the config.yml :
72 74
73```yaml 75```yaml
74 type: "Radarr" or "Sonarr" 76 type: "Lidarr", "Prowlarr", "Radarr" or "Sonarr"
75 apikey: "01234deb70424befb1f4ef6a23456789" 77 apikey: "01234deb70424befb1f4ef6a23456789"
76``` 78```
77 79
78The url must be the root url of Radarr/Sonarr application. 80The url must be the root url of Lidarr, Prowlarr, Radarr or Sonarr application.
79The Radarr/Sonarr API key can be found in Settings > General. It is needed to access the API. 81The Lidarr, Prowlarr, Radarr or Sonarr API key can be found in Settings > General. It is needed to access the API.
82If you are using an older version of Radarr or Sonarr which don't support the new V3 api endpoints, add the following line to your service config "legacyApi: true", example:
83
84```yaml
85- name: "Radarr"
86 type: "Radarr"
87 url: "http://localhost:7878/"
88 apikey: "MY-SUPER-SECRET-API-KEY"
89 target: "_blank"
90 legacyApi: true
91```
80 92
81## PaperlessNG 93## PaperlessNG
82 94
83For Paperless you need an API-Key which you have to store at the item in the field `apikey`. 95This service displays total number of documents stored. Two lines are required:
96
97```yaml
98 type: "PaperlessNG"
99 apikey: "0123456789abcdef123456789abcdef"
100```
101
102API key can be generated in Settings > Administration > Auth Tokens
84 103
85## Ping 104## Ping
86 105
@@ -90,8 +109,9 @@ For Ping you need to set the type to Ping and provide a url.
90- name: "Awesome app" 109- name: "Awesome app"
91 type: Ping 110 type: Ping
92 logo: "assets/tools/sample.png" 111 logo: "assets/tools/sample.png"
93 subtitle: "Bookmark example" tag: "app" 112 subtitle: "Bookmark example"
94 url: "https://www.reddit.com/r/selfhosted/" 113 tag: "app"
114 url: "https://www.reddit.com/r/selfhosted/"
95``` 115```
96 116
97## Prometheus 117## Prometheus
@@ -105,3 +125,19 @@ For Prometheus you need to set the type to Prometheus and provide a url.
105 url: "http://192.168.0.151/" 125 url: "http://192.168.0.151/"
106 # subtitle: "Monitor data server" 126 # subtitle: "Monitor data server"
107``` 127```
128
129## Portainer
130
131This service displays info about the total number of containers managed by your Portainer instance.
132In order to use it, you must be using Portainer version 1.11 or later. Generate an access token from the UI and pass
133it to the apikey field.
134
135See https://docs.portainer.io/v/ce-2.11/user/account-settings#access-tokens
136
137```yaml
138- name: "Portainer"
139 logo: "assets/tools/sample.png"
140 url: "http://192.168.0.151/"
141 type: "Portainer"
142 apikey: "MY-SUPER-SECRET-API-KEY"
143```
diff --git a/docs/tips-and-tricks.md b/docs/tips-and-tricks.md
index 17bba9a..6f1b995 100644
--- a/docs/tips-and-tricks.md
+++ b/docs/tips-and-tricks.md
@@ -51,7 +51,7 @@ and then simply reference these pre-defined (anchored) tags in each item like so
51- name: "VS Code" 51- name: "VS Code"
52 logo: "/assets/vscode.png" 52 logo: "/assets/vscode.png"
53 subtitle: "Develop Code Anywhere, On Anything!" 53 subtitle: "Develop Code Anywhere, On Anything!"
54 <<: *App # Reference to the predefined "App" Tag 54 <<: *Apps # Reference to the predefined "App" Tag
55 url: "https://vscode.example.com/" 55 url: "https://vscode.example.com/"
56 target: "_blank" # optional html tag target attribute 56 target: "_blank" # optional html tag target attribute
57```` 57````
diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md
index ed1f85d..649e5a6 100644
--- a/docs/troubleshooting.md
+++ b/docs/troubleshooting.md
@@ -6,7 +6,7 @@ You might by facing a [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/C
6It happens when the targeted service is hosted on a different domain or port. 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 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). 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: 9If this happens your web console (`ctrl+shift+i` or `F12`) will be filled with this kind of errors:
10 10
11```text 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. 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.
@@ -15,5 +15,5 @@ Access to fetch at 'https://<target-service>' from origin 'https://<homer>' has
15To resolve this, you can either: 15To resolve this, you can either:
16 16
17* Host all your target service under the same domain & port. 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. 18* Modify the target server 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. 19* Use a cors proxy server 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 c5486bb..41a96fe 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,6 @@
1{ 1{
2 "name": "homer", 2 "name": "homer",
3 "version": "21.09.1", 3 "version": "21.09.1",
4 "license": "Apache-2.0",
5 "scripts": { 4 "scripts": {
6 "serve": "vue-cli-service serve", 5 "serve": "vue-cli-service serve",
7 "build": "vue-cli-service build", 6 "build": "vue-cli-service build",
@@ -10,17 +9,17 @@
10 "dependencies": { 9 "dependencies": {
11 "@fortawesome/fontawesome-free": "^5.15.4", 10 "@fortawesome/fontawesome-free": "^5.15.4",
12 "bulma": "^0.9.3", 11 "bulma": "^0.9.3",
13 "core-js": "^3.17.3", 12 "core-js": "^3.21.1",
14 "js-yaml": "^4.1.0", 13 "js-yaml": "^4.1.0",
15 "lodash.merge": "^4.6.2", 14 "lodash.merge": "^4.6.2",
16 "register-service-worker": "^1.7.2", 15 "register-service-worker": "^1.7.2",
17 "vue": "^2.6.14" 16 "vue": "^2.6.14"
18 }, 17 },
19 "devDependencies": { 18 "devDependencies": {
20 "@vue/cli-plugin-babel": "~4.5.0", 19 "@vue/cli-plugin-babel": "~4.5.15",
21 "@vue/cli-plugin-eslint": "~4.5.0", 20 "@vue/cli-plugin-eslint": "~4.5.15",
22 "@vue/cli-plugin-pwa": "~4.5.0", 21 "@vue/cli-plugin-pwa": "~4.5.15",
23 "@vue/cli-service": "~4.5.0", 22 "@vue/cli-service": "~4.5.15",
24 "@vue/eslint-config-prettier": "^6.0.0", 23 "@vue/eslint-config-prettier": "^6.0.0",
25 "babel-eslint": "^10.1.0", 24 "babel-eslint": "^10.1.0",
26 "eslint": "^6.7.2", 25 "eslint": "^6.7.2",
@@ -31,5 +30,6 @@
31 "sass": "^1.26.5", 30 "sass": "^1.26.5",
32 "sass-loader": "^8.0.2", 31 "sass-loader": "^8.0.2",
33 "vue-template-compiler": "^2.6.12" 32 "vue-template-compiler": "^2.6.12"
34 } 33 },
34 "license": "Apache-2.0"
35} 35}
diff --git a/public/assets/additionnal-page.yml.dist b/public/assets/additional-page.yml.dist
index f918dc1..5d377ee 100644
--- a/public/assets/additionnal-page.yml.dist
+++ b/public/assets/additional-page.yml.dist
@@ -1,7 +1,7 @@
1--- 1---
2# Additionnal page configuration 2# Additional page configuration
3 3
4# Additionnal configurations are loaded using its file name, minus the extension, as an anchor (https://<mydashboad>#<config>). 4# Additional configurations are loaded using its file name, minus the extension, as an anchor (https://<mydashboad>#<config>).
5# `config.yml` is still used as a base configuration, and all values here will overwrite it, so you don't have to re-defined everything 5# `config.yml` is still used as a base configuration, and all values here will overwrite it, so you don't have to re-defined everything
6 6
7 7
diff --git a/public/assets/config.yml.dist b/public/assets/config.yml.dist
index acdd32c..63d022a 100644
--- a/public/assets/config.yml.dist
+++ b/public/assets/config.yml.dist
@@ -58,11 +58,11 @@ links:
58 - name: "Wiki" 58 - name: "Wiki"
59 icon: "fas fa-book" 59 icon: "fas fa-book"
60 url: "https://www.wikipedia.org/" 60 url: "https://www.wikipedia.org/"
61 # this will link to a second homer page that will load config from additionnal-page.yml and keep default config values as in config.yml file 61 # this will link to a second homer page that will load config from additional-page.yml and keep default config values as in config.yml file
62 # see url field and assets/additionnal-page.yml.dist used in this example: 62 # see url field and assets/additional-page.yml.dist used in this example:
63 - name: "another page!" 63 - name: "another page!"
64 icon: "fas fa-file-alt" 64 icon: "fas fa-file-alt"
65 url: "#additionnal-page" 65 url: "#additional-page"
66 66
67# Services 67# Services
68# First level array represent a group. 68# First level array represent a group.
diff --git a/src/App.vue b/src/App.vue
index 1f1791c..fda13c3 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -45,6 +45,7 @@
45 45
46 <SearchInput 46 <SearchInput
47 class="navbar-item is-inline-block-mobile" 47 class="navbar-item is-inline-block-mobile"
48 :hotkey="searchHotkey()"
48 @input="filterServices" 49 @input="filterServices"
49 @search-focus="showMenu = true" 50 @search-focus="showMenu = true"
50 @search-open="navigateToFirstService" 51 @search-open="navigateToFirstService"
@@ -59,6 +60,9 @@
59 v-if="config.connectivityCheck" 60 v-if="config.connectivityCheck"
60 @network-status-update="offline = $event" 61 @network-status-update="offline = $event"
61 /> 62 />
63
64 <GetStarted v-if="loaded && !services" />
65
62 <div v-if="!offline"> 66 <div v-if="!offline">
63 <!-- Optional messages --> 67 <!-- Optional messages -->
64 <Message :item="config.message" /> 68 <Message :item="config.message" />
@@ -133,6 +137,7 @@ const jsyaml = require("js-yaml");
133const merge = require("lodash.merge"); 137const merge = require("lodash.merge");
134 138
135import Navbar from "./components/Navbar.vue"; 139import Navbar from "./components/Navbar.vue";
140import GetStarted from "./components/GetStarted.vue";
136import ConnectivityChecker from "./components/ConnectivityChecker.vue"; 141import ConnectivityChecker from "./components/ConnectivityChecker.vue";
137import Service from "./components/Service.vue"; 142import Service from "./components/Service.vue";
138import Message from "./components/Message.vue"; 143import Message from "./components/Message.vue";
@@ -147,6 +152,7 @@ export default {
147 name: "App", 152 name: "App",
148 components: { 153 components: {
149 Navbar, 154 Navbar,
155 GetStarted,
150 ConnectivityChecker, 156 ConnectivityChecker,
151 Service, 157 Service,
152 Message, 158 Message,
@@ -157,6 +163,7 @@ export default {
157 }, 163 },
158 data: function () { 164 data: function () {
159 return { 165 return {
166 loaded: false,
160 config: null, 167 config: null,
161 services: null, 168 services: null,
162 offline: false, 169 offline: false,
@@ -169,8 +176,14 @@ export default {
169 created: async function () { 176 created: async function () {
170 this.buildDashboard(); 177 this.buildDashboard();
171 window.onhashchange = this.buildDashboard; 178 window.onhashchange = this.buildDashboard;
179 this.loaded = true;
172 }, 180 },
173 methods: { 181 methods: {
182 searchHotkey() {
183 if (this.config.hotkey && this.config.hotkey.search) {
184 return this.config.hotkey.search;
185 }
186 },
174 buildDashboard: async function () { 187 buildDashboard: async function () {
175 const defaults = jsyaml.load(defaultConfig); 188 const defaults = jsyaml.load(defaultConfig);
176 let config; 189 let config;
@@ -191,6 +204,7 @@ export default {
191 } 204 }
192 this.config = merge(defaults, config); 205 this.config = merge(defaults, config);
193 this.services = this.config.services; 206 this.services = this.config.services;
207
194 document.title = 208 document.title =
195 this.config.documentTitle || 209 this.config.documentTitle ||
196 `${this.config.title} | ${this.config.subtitle}`; 210 `${this.config.title} | ${this.config.subtitle}`;
@@ -209,6 +223,7 @@ export default {
209 window.location.href = response.url; 223 window.location.href = response.url;
210 return; 224 return;
211 } 225 }
226
212 if (!response.ok) { 227 if (!response.ok) {
213 throw Error(`${response.statusText}: ${response.body}`); 228 throw Error(`${response.statusText}: ${response.body}`);
214 } 229 }
diff --git a/src/components/GetStarted.vue b/src/components/GetStarted.vue
new file mode 100644
index 0000000..dcabc02
--- /dev/null
+++ b/src/components/GetStarted.vue
@@ -0,0 +1,35 @@
1<template>
2 <article>
3 <div class="m-6 has-text-centered py-6">
4 <p class="is-size-5 mb-0">No configured service found!</p>
5 <p>Check out the documentation to start building your Homer dashboard.</p>
6 <p>
7 <a
8 class="button is-primary mt-5 has-text-weight-bold"
9 href="https://github.com/bastienwirtz/homer/blob/main/README.md#getting-started"
10 target="_blank"
11 >
12 Get started
13 </a>
14 </p>
15 </div>
16 </article>
17</template>
18
19<script>
20export default {
21 name: "GetStarted",
22};
23</script>
24
25<style lang="scss" scoped>
26p {
27 color: #4a4a4a;
28}
29
30body #app a {
31 font-weight: 900;
32 color: #ffffff;
33 font-family: "Lato", sans-serif;
34}
35</style>
diff --git a/src/components/SearchInput.vue b/src/components/SearchInput.vue
index 40c5a1d..165c992 100644
--- a/src/components/SearchInput.vue
+++ b/src/components/SearchInput.vue
@@ -15,10 +15,16 @@
15<script> 15<script>
16export default { 16export default {
17 name: "SearchInput", 17 name: "SearchInput",
18 props: ["value"], 18 props: {
19 value: String,
20 hotkey: {
21 type: String,
22 default: "/",
23 },
24 },
19 mounted() { 25 mounted() {
20 this._keyListener = function (event) { 26 this._keyListener = function (event) {
21 if (event.key === "/") { 27 if (event.key === this.hotkey) {
22 event.preventDefault(); 28 event.preventDefault();
23 this.focus(); 29 this.focus();
24 } 30 }
@@ -28,7 +34,7 @@ export default {
28 }; 34 };
29 document.addEventListener("keydown", this._keyListener.bind(this)); 35 document.addEventListener("keydown", this._keyListener.bind(this));
30 36
31 // fill seach from get parameter. 37 // fill search from get parameter.
32 const search = new URLSearchParams(window.location.search).get("search"); 38 const search = new URLSearchParams(window.location.search).get("search");
33 if (search) { 39 if (search) {
34 this.$refs.search.value = search; 40 this.$refs.search.value = search;
diff --git a/src/components/services/AdGuardHome.vue b/src/components/services/AdGuardHome.vue
index b01f0f4..4c53398 100644
--- a/src/components/services/AdGuardHome.vue
+++ b/src/components/services/AdGuardHome.vue
@@ -76,9 +76,6 @@ export default {
76</script> 76</script>
77 77
78<style scoped lang="scss"> 78<style scoped lang="scss">
79.media-left img {
80 max-height: 100%;
81}
82.status { 79.status {
83 font-size: 0.8rem; 80 font-size: 0.8rem;
84 color: var(--text-title); 81 color: var(--text-title);
diff --git a/src/components/services/Lidarr.vue b/src/components/services/Lidarr.vue
new file mode 100644
index 0000000..cbe5516
--- /dev/null
+++ b/src/components/services/Lidarr.vue
@@ -0,0 +1,110 @@
1<template>
2 <Generic :item="item">
3 <template #indicator>
4 <div class="notifs">
5 <strong v-if="activity > 0" class="notif activity" title="Activity">
6 {{ activity }}
7 </strong>
8 <strong v-if="warnings > 0" class="notif warnings" title="Warning">
9 {{ warnings }}
10 </strong>
11 <strong v-if="errors > 0" class="notif errors" title="Error">
12 {{ errors }}
13 </strong>
14 <strong
15 v-if="serverError"
16 class="notif errors"
17 title="Connection error to Lidarr API, check url and apikey in config.yml"
18 >?</strong
19 >
20 </div>
21 </template>
22 </Generic>
23</template>
24
25<script>
26import service from "@/mixins/service.js";
27import Generic from "./Generic.vue";
28
29export default {
30 name: "Lidarr",
31 mixins: [service],
32 props: {
33 item: Object,
34 },
35 components: {
36 Generic,
37 },
38 data: () => {
39 return {
40 activity: null,
41 warnings: null,
42 errors: null,
43 serverError: false,
44 };
45 },
46 created: function () {
47 this.fetchConfig();
48 },
49 methods: {
50 fetchConfig: function () {
51 this.fetch(`/api/v1/health?apikey=${this.item.apikey}`)
52 .then((health) => {
53 this.warnings = 0;
54 this.errors = 0;
55 for (var i = 0; i < health.length; i++) {
56 if (health[i].type == "warning") {
57 this.warnings++;
58 } else if (health[i].type == "error") {
59 this.errors++;
60 }
61 }
62 })
63 .catch((e) => {
64 console.error(e);
65 this.serverError = true;
66 });
67 this.fetch(`/api/v1/queue/status?apikey=${this.item.apikey}`)
68 .then((queue) => {
69 this.activity = queue.totalCount;
70 })
71 .catch((e) => {
72 console.error(e);
73 this.serverError = true;
74 });
75 },
76 },
77};
78</script>
79
80<style scoped lang="scss">
81.notifs {
82 position: absolute;
83 color: white;
84 font-family: sans-serif;
85 top: 0.3em;
86 right: 0.5em;
87 .notif {
88 display: inline-block;
89 padding-right: 0.35em;
90 padding-left: 0.35em;
91 padding-top: 0.2em;
92 padding-bottom: 0.2em;
93 border-radius: 0.25em;
94 position: relative;
95 margin-left: 0.3em;
96 font-size: 0.8em;
97 &.activity {
98 background-color: #4fb5d6;
99 }
100
101 &.warnings {
102 background-color: #d08d2e;
103 }
104
105 &.errors {
106 background-color: #e51111;
107 }
108 }
109}
110</style>
diff --git a/src/components/services/Mealie.vue b/src/components/services/Mealie.vue
index 790a9b1..b5b2255 100644
--- a/src/components/services/Mealie.vue
+++ b/src/components/services/Mealie.vue
@@ -1,47 +1,33 @@
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="meal"> Today: {{ meal.name }} </template>
10 </figure> 10 <template v-else-if="stats">
11 </div> 11 happily keeping {{ stats.totalRecipes }} recipes organized
12 <div v-if="item.icon" class="media-left"> 12 </template>
13 <figure class="image is-48x48"> 13 </p>
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> 14 </template>
15 </figure> 15 </Generic>
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> 16</template>
38 17
39<script> 18<script>
19import service from "@/mixins/service.js";
20import Generic from "./Generic.vue";
21
40export default { 22export default {
41 name: "Mealie", 23 name: "Mealie",
24 mixins: [service],
42 props: { 25 props: {
43 item: Object, 26 item: Object,
44 }, 27 },
28 components: {
29 Generic,
30 },
45 data: () => ({ 31 data: () => ({
46 stats: null, 32 stats: null,
47 meal: null, 33 meal: null,
@@ -51,44 +37,20 @@ export default {
51 }, 37 },
52 methods: { 38 methods: {
53 fetchStatus: async function () { 39 fetchStatus: async function () {
54 if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing 40 const headers = {
55 this.meal = await fetch(`${this.item.url}/api/meal-plans/today/`, { 41 Authorization: "Bearer " + this.item.apikey,
56 headers: { 42 Accept: "application/json",
57 Authorization: "Bearer " + this.item.apikey, 43 };
58 Accept: "application/json", 44
59 }, 45 if (this.item.subtitle != null) return;
60 }) 46
61 .then(function (response) { 47 this.meal = await this.fetch("/api/meal-plans/today/", { headers }).catch(
62 if (!response.ok) { 48 (e) => console.log(e)
63 throw new Error("Not 2xx response"); 49 );
64 } else { 50 this.stats = await this.fetch("/api/debug/statistics/", {
65 if (response != null) { 51 headers,
66 return response.json(); 52 }).catch((e) => console.log(e));
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 }, 53 },
86 }, 54 },
87}; 55};
88</script> 56</script>
89
90<style scoped lang="scss">
91.media-left img {
92 max-height: 100%;
93}
94</style>
diff --git a/src/components/services/Medusa.vue b/src/components/services/Medusa.vue
index 5720649..43b5651 100644
--- a/src/components/services/Medusa.vue
+++ b/src/components/services/Medusa.vue
@@ -1,65 +1,49 @@
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 class="notifs">
5 <div class="card-content"> 5 <strong
6 <div class="media"> 6 v-if="config !== null && config.system.news.unread > 0"
7 <div v-if="item.logo" class="media-left"> 7 class="notif news"
8 <figure class="image is-48x48"> 8 title="News"
9 <img :src="item.logo" :alt="`${item.name} logo`" /> 9 >{{ config.system.news.unread }}</strong
10 </figure> 10 >
11 </div> 11 <strong
12 <div v-if="item.icon" class="media-left"> 12 v-if="config !== null && config.main.logs.numWarnings > 0"
13 <figure class="image is-48x48"> 13 class="notif warnings"
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> 14 title="Warning"
15 </figure> 15 >{{ config.main.logs.numWarnings }}</strong
16 </div> 16 >
17 <div class="media-content"> 17 <strong
18 <p class="title is-4">{{ item.name }}</p> 18 v-if="config !== null && config.main.logs.numErrors > 0"
19 <p class="subtitle is-6">{{ item.subtitle }}</p> 19 class="notif errors"
20 </div> 20 title="Error"
21 <div class="notifs"> 21 >{{ config.main.logs.numErrors }}</strong
22 <strong 22 >
23 v-if="config !== null && config.system.news.unread > 0" 23 <strong
24 class="notif news" 24 v-if="serverError"
25 title="News" 25 class="notif errors"
26 >{{ config.system.news.unread }}</strong 26 title="Connection error to Medusa API, check url and apikey in config.yml"
27 > 27 >?</strong
28 <strong 28 >
29 v-if="config !== null && config.main.logs.numWarnings > 0" 29 </div>
30 class="notif warnings" 30 </template>
31 title="Warning" 31 </Generic>
32 >{{ config.main.logs.numWarnings }}</strong
33 >
34 <strong
35 v-if="config !== null && config.main.logs.numErrors > 0"
36 class="notif errors"
37 title="Error"
38 >{{ config.main.logs.numErrors }}</strong
39 >
40 <strong
41 v-if="serverError"
42 class="notif errors"
43 title="Connection error to Medusa API, check url and apikey in config.yml"
44 >?</strong
45 >
46 </div>
47 </div>
48 <div class="tag" :class="item.tagstyle" v-if="item.tag">
49 <strong class="tag-text">#{{ item.tag }}</strong>
50 </div>
51 </div>
52 </a>
53 </div>
54 </div>
55</template> 32</template>
56 33
57<script> 34<script>
35import service from "@/mixins/service.js";
36import Generic from "./Generic.vue";
37
58export default { 38export default {
59 name: "Medusa", 39 name: "Medusa",
40 mixins: [service],
60 props: { 41 props: {
61 item: Object, 42 item: Object,
62 }, 43 },
44 components: {
45 Generic,
46 },
63 data: () => { 47 data: () => {
64 return { 48 return {
65 config: null, 49 config: null,
@@ -71,16 +55,9 @@ export default {
71 }, 55 },
72 methods: { 56 methods: {
73 fetchConfig: function () { 57 fetchConfig: function () {
74 fetch(`${this.item.url}/api/v2/config`, { 58 this.fetch("/api/v2/config", {
75 credentials: "include", 59 headers: { "X-Api-Key": this.item.apikey },
76 headers: { "X-Api-Key": `${this.item.apikey}` },
77 }) 60 })
78 .then((response) => {
79 if (response.status != 200) {
80 throw new Error(response.statusText);
81 }
82 return response.json();
83 })
84 .then((conf) => { 61 .then((conf) => {
85 this.config = conf; 62 this.config = conf;
86 }) 63 })
@@ -94,35 +71,32 @@ export default {
94</script> 71</script>
95 72
96<style scoped lang="scss"> 73<style scoped lang="scss">
97.media-left img {
98 max-height: 100%;
99}
100.notifs { 74.notifs {
101 position: absolute; 75 position: absolute;
102 color: white; 76 color: white;
103 font-family: sans-serif; 77 font-family: sans-serif;
104 top: 0.3em; 78 top: 0.3em;
105 right: 0.5em; 79 right: 0.5em;
106} 80 .notif {
107.notif { 81 padding-right: 0.35em;
108 padding-right: 0.35em; 82 padding-left: 0.35em;
109 padding-left: 0.35em; 83 padding-top: 0.2em;
110 padding-top: 0.2em; 84 padding-bottom: 0.2em;
111 padding-bottom: 0.2em; 85 border-radius: 0.25em;
112 border-radius: 0.25em; 86 position: relative;
113 position: relative; 87 margin-left: 0.3em;
114 margin-left: 0.3em; 88 font-size: 0.8em;
115 font-size: 0.8em; 89 &.news {
116} 90 background-color: #777777;
117.news { 91 }
118 background-color: #777777;
119}
120 92
121.warnings { 93 &.warnings {
122 background-color: #d08d2e; 94 background-color: #d08d2e;
123} 95 }
124 96
125.errors { 97 &.errors {
126 background-color: #e51111; 98 background-color: #e51111;
99 }
100 }
127} 101}
128</style> 102</style>
diff --git a/src/components/services/OpenWeather.vue b/src/components/services/OpenWeather.vue
index 09ff76a..79d5e37 100644
--- a/src/components/services/OpenWeather.vue
+++ b/src/components/services/OpenWeather.vue
@@ -64,7 +64,8 @@ export default {
64 locationQuery = `q=${this.item.location}`; 64 locationQuery = `q=${this.item.location}`;
65 } 65 }
66 66
67 const url = `https://api.openweathermap.org/data/2.5/weather?${locationQuery}&appid=${this.item.apiKey}&units=${this.item.units}`; 67 const apiKey = this.item.apikey || this.item.apiKey;
68 const url = `https://api.openweathermap.org/data/2.5/weather?${locationQuery}&appid=${apiKey}&units=${this.item.units}`;
68 fetch(url) 69 fetch(url)
69 .then((response) => { 70 .then((response) => {
70 if (!response.ok) { 71 if (!response.ok) {
diff --git a/src/components/services/PaperlessNG.vue b/src/components/services/PaperlessNG.vue
index af13317..69f2437 100644
--- a/src/components/services/PaperlessNG.vue
+++ b/src/components/services/PaperlessNG.vue
@@ -1,46 +1,32 @@
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="api">
10 </figure> 10 happily storing {{ api.count }} documents
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 </Generic>
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="api">
24 happily storing {{ api.count }} documents
25 </template>
26 </p>
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> 15</template>
37 16
38<script> 17<script>
18import service from "@/mixins/service.js";
19import Generic from "./Generic.vue";
20
39export default { 21export default {
40 name: "Paperless", 22 name: "Paperless",
23 mixins: [service],
41 props: { 24 props: {
42 item: Object, 25 item: Object,
43 }, 26 },
27 components: {
28 Generic,
29 },
44 data: () => ({ 30 data: () => ({
45 api: null, 31 api: null,
46 }), 32 }),
@@ -49,36 +35,21 @@ export default {
49 }, 35 },
50 methods: { 36 methods: {
51 fetchStatus: async function () { 37 fetchStatus: async function () {
52 if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing 38 if (this.item.subtitle != null) return;
53 var apikey = this.item.apikey; 39
40 const apikey = this.item.apikey;
54 if (!apikey) { 41 if (!apikey) {
55 console.error( 42 console.error(
56 "apikey is not present in config.yml for the paperless entry!" 43 "apikey is not present in config.yml for the paperless entry!"
57 ); 44 );
58 return; 45 return;
59 } 46 }
60 const url = `${this.item.url}/api/documents/`; 47 this.api = await this.fetch("/api/documents/", {
61 this.api = await fetch(url, {
62 credentials: "include",
63 headers: { 48 headers: {
64 Authorization: "Token " + this.item.apikey, 49 Authorization: "Token " + this.item.apikey,
65 }, 50 },
66 }) 51 }).catch((e) => console.log(e));
67 .then(function (response) {
68 if (!response.ok) {
69 throw new Error("Not 2xx response");
70 } else {
71 return response.json();
72 }
73 })
74 .catch((e) => console.log(e));
75 }, 52 },
76 }, 53 },
77}; 54};
78</script> 55</script>
79
80<style scoped lang="scss">
81.media-left img {
82 max-height: 100%;
83}
84</style>
diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue
index 87f7090..9aac016 100644
--- a/src/components/services/PiHole.vue
+++ b/src/components/services/PiHole.vue
@@ -1,59 +1,45 @@
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="percentage">
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 v-if="status" class="status" :class="status">
16 </div> 16 {{ status }}
17 <div class="media-content"> 17 </div>
18 <p class="title is-4">{{ item.name }}</p> 18 </template>
19 <p class="subtitle is-6"> 19 </Generic>
20 <template v-if="item.subtitle">
21 {{ item.subtitle }}
22 </template>
23 <template v-else-if="api">
24 {{ percentage }}&percnt; blocked
25 </template>
26 </p>
27 </div>
28 <div v-if="api" class="status" :class="api.status">
29 {{ api.status }}
30 </div>
31 </div>
32 <div class="tag" :class="item.tagstyle" v-if="item.tag">
33 <strong class="tag-text">#{{ item.tag }}</strong>
34 </div>
35 </div>
36 </a>
37 </div>
38 </div>
39</template> 20</template>
40 21
41<script> 22<script>
23import service from "@/mixins/service.js";
24import Generic from "./Generic.vue";
25
42export default { 26export default {
43 name: "PiHole", 27 name: "PiHole",
28 mixins: [service],
44 props: { 29 props: {
45 item: Object, 30 item: Object,
46 }, 31 },
32 components: {
33 Generic,
34 },
47 data: () => ({ 35 data: () => ({
48 api: { 36 status: "",
49 status: "", 37 ads_percentage_today: 0,
50 ads_percentage_today: 0,
51 },
52 }), 38 }),
53 computed: { 39 computed: {
54 percentage: function () { 40 percentage: function () {
55 if (this.api) { 41 if (this.ads_percentage_today) {
56 return this.api.ads_percentage_today.toFixed(1); 42 return this.ads_percentage_today.toFixed(1);
57 } 43 }
58 return ""; 44 return "";
59 }, 45 },
@@ -63,21 +49,16 @@ export default {
63 }, 49 },
64 methods: { 50 methods: {
65 fetchStatus: async function () { 51 fetchStatus: async function () {
66 const url = `${this.item.url}/api.php`; 52 const result = await this.fetch("/api.php").catch((e) => console.log(e));
67 this.api = await fetch(url, { 53
68 credentials: "include", 54 this.status = result.status;
69 }) 55 this.ads_percentage_today = result.ads_percentage_today;
70 .then((response) => response.json())
71 .catch((e) => console.log(e));
72 }, 56 },
73 }, 57 },
74}; 58};
75</script> 59</script>
76 60
77<style scoped lang="scss"> 61<style scoped lang="scss">
78.media-left img {
79 max-height: 100%;
80}
81.status { 62.status {
82 font-size: 0.8rem; 63 font-size: 0.8rem;
83 color: var(--text-title); 64 color: var(--text-title);
diff --git a/src/components/services/Portainer.vue b/src/components/services/Portainer.vue
new file mode 100644
index 0000000..c7e9962
--- /dev/null
+++ b/src/components/services/Portainer.vue
@@ -0,0 +1,131 @@
1<template>
2 <Generic :item="item">
3 <template #indicator>
4 <div class="notifs">
5 <strong v-if="running > 0" class="notif running" title="Running">
6 {{ running }}
7 </strong>
8 <strong v-if="dead > 0" class="notif dead" title="Dead">
9 {{ dead }}
10 </strong>
11 <strong
12 v-if="misc > 0"
13 class="notif misc"
14 title="Other (creating, paused, exited, etc.)"
15 >
16 {{ misc }}
17 </strong>
18 </div>
19 </template>
20 </Generic>
21</template>
22
23<script>
24import service from "@/mixins/service.js";
25import Generic from "./Generic.vue";
26
27export default {
28 name: "Portainer",
29 mixins: [service],
30 props: {
31 item: Object,
32 },
33 components: {
34 Generic,
35 },
36 data: () => ({
37 endpoints: null,
38 containers: null,
39 }),
40 computed: {
41 running: function () {
42 if (!this.containers) {
43 return "";
44 }
45 return this.containers.filter((container) => {
46 return container.State.toLowerCase() === "running";
47 }).length;
48 },
49 dead: function () {
50 if (!this.containers) {
51 return "";
52 }
53 return this.containers.filter((container) => {
54 return container.State.toLowerCase() === "dead";
55 }).length;
56 },
57 misc: function () {
58 if (!this.containers) {
59 return "";
60 }
61 return this.containers.filter((container) => {
62 return (
63 container.State.toLowerCase() !== "running" &&
64 container.State.toLowerCase() !== "dead"
65 );
66 }).length;
67 },
68 },
69 created() {
70 this.fetchStatus();
71 },
72 methods: {
73 fetchStatus: async function () {
74 const headers = {
75 "X-Api-Key": this.item.apikey,
76 };
77
78 this.endpoints = await this.fetch("/api/endpoints", { headers }).catch(
79 (e) => {
80 console.error(e);
81 }
82 );
83
84 let containers = [];
85 for (let endpoint of this.endpoints) {
86 const uri = `/api/endpoints/${endpoint.Id}/docker/containers/json?all=1`;
87 const endpointContainers = await this.fetch(uri, { headers }).catch(
88 (e) => {
89 console.error(e);
90 }
91 );
92
93 containers = containers.concat(endpointContainers);
94 }
95
96 this.containers = containers;
97 },
98 },
99};
100</script>
101
102<style scoped lang="scss">
103.notifs {
104 position: absolute;
105 color: white;
106 font-family: sans-serif;
107 top: 0.3em;
108 right: 0.5em;
109
110 .notif {
111 display: inline-block;
112 padding: 0.2em 0.35em;
113 border-radius: 0.25em;
114 position: relative;
115 margin-left: 0.3em;
116 font-size: 0.8em;
117
118 &.running {
119 background-color: #4fd671;
120 }
121
122 &.dead {
123 background-color: #e51111;
124 }
125
126 &.misc {
127 background-color: #2ed0c8;
128 }
129 }
130}
131</style>
diff --git a/src/components/services/Prowlarr.vue b/src/components/services/Prowlarr.vue
new file mode 100644
index 0000000..abaa0f0
--- /dev/null
+++ b/src/components/services/Prowlarr.vue
@@ -0,0 +1,94 @@
1<template>
2 <Generic :item="item">
3 <template #indicator>
4 <div class="notifs">
5 <strong v-if="warnings > 0" class="notif warnings" title="Warning">
6 {{ warnings }}
7 </strong>
8 <strong v-if="errors > 0" class="notif errors" title="Error">
9 {{ errors }}
10 </strong>
11 <strong
12 v-if="serverError"
13 class="notif errors"
14 title="Connection error to Prowlarr API, check url and apikey in config.yml"
15 >
16 ?
17 </strong>
18 </div>
19 </template>
20 </Generic>
21</template>
22
23<script>
24import service from "@/mixins/service.js";
25import Generic from "./Generic.vue";
26
27export default {
28 name: "Prowlarr",
29 mixins: [service],
30 props: {
31 item: Object,
32 },
33 components: {
34 Generic,
35 },
36 data: () => {
37 return {
38 warnings: null,
39 errors: null,
40 serverError: false,
41 };
42 },
43 created: function () {
44 this.fetchConfig();
45 },
46 methods: {
47 fetchConfig: function () {
48 this.fetch(`/api/v1/health?apikey=${this.item.apikey}`)
49 .then((health) => {
50 this.warnings = 0;
51 this.errors = 0;
52 for (var i = 0; i < health.length; i++) {
53 if (health[i].type == "warning") {
54 this.warnings++;
55 } else if (health[i].type == "error") {
56 this.errors++;
57 }
58 }
59 })
60 .catch((e) => {
61 console.error(e);
62 this.serverError = true;
63 });
64 },
65 },
66};
67</script>
68
69<style scoped lang="scss">
70.notifs {
71 position: absolute;
72 color: white;
73 font-family: sans-serif;
74 top: 0.3em;
75 right: 0.5em;
76
77 .notif {
78 display: inline-block;
79 padding: 0.2em 0.35em;
80 border-radius: 0.25em;
81 position: relative;
82 margin-left: 0.3em;
83 font-size: 0.8em;
84
85 &.warnings {
86 background-color: #d08d2e;
87 }
88
89 &.errors {
90 background-color: #e51111;
91 }
92 }
93}
94</style>
diff --git a/src/components/services/Radarr.vue b/src/components/services/Radarr.vue
index a9cdedf..5c40aaa 100644
--- a/src/components/services/Radarr.vue
+++ b/src/components/services/Radarr.vue
@@ -1,62 +1,43 @@
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 class="notifs">
5 <div class="card-content"> 5 <strong v-if="activity > 0" class="notif activity" title="Activity">
6 <div class="media"> 6 {{ activity }}
7 <div v-if="item.logo" class="media-left"> 7 </strong>
8 <figure class="image is-48x48"> 8 <strong v-if="warnings > 0" class="notif warnings" title="Warning">
9 <img :src="item.logo" :alt="`${item.name} logo`" /> 9 {{ warnings }}
10 </figure> 10 </strong>
11 </div> 11 <strong v-if="errors > 0" class="notif errors" title="Error">
12 <div v-if="item.icon" class="media-left"> 12 {{ errors }}
13 <figure class="image is-48x48"> 13 </strong>
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> 14 <strong
15 </figure> 15 v-if="serverError"
16 </div> 16 class="notif errors"
17 <div class="media-content"> 17 title="Connection error to Radarr API, check url and apikey in config.yml"
18 <p class="title is-4">{{ item.name }}</p> 18 >?</strong
19 <p class="subtitle is-6">{{ item.subtitle }}</p> 19 >
20 </div> 20 </div>
21 <div class="notifs"> 21 </template>
22 <strong 22 </Generic>
23 v-if="activity > 0"
24 class="notif activity"
25 title="Activity"
26 >{{ activity }}</strong
27 >
28 <strong
29 v-if="warnings > 0"
30 class="notif warnings"
31 title="Warning"
32 >{{ warnings }}</strong
33 >
34 <strong v-if="errors > 0" class="notif errors" title="Error">{{
35 errors
36 }}</strong>
37 <strong
38 v-if="serverError"
39 class="notif errors"
40 title="Connection error to Radarr API, check url and apikey in config.yml"
41 >?</strong
42 >
43 </div>
44 </div>
45 <div class="tag" :class="item.tagstyle" v-if="item.tag">
46 <strong class="tag-text">#{{ item.tag }}</strong>
47 </div>
48 </div>
49 </a>
50 </div>
51 </div>
52</template> 23</template>
53 24
54<script> 25<script>
26import service from "@/mixins/service.js";
27import Generic from "./Generic.vue";
28
29const V3_API = "/api/v3";
30const LEGACY_API = "/api";
31
55export default { 32export default {
56 name: "Radarr", 33 name: "Radarr",
34 mixins: [service],
57 props: { 35 props: {
58 item: Object, 36 item: Object,
59 }, 37 },
38 components: {
39 Generic,
40 },
60 data: () => { 41 data: () => {
61 return { 42 return {
62 activity: null, 43 activity: null,
@@ -68,17 +49,14 @@ export default {
68 created: function () { 49 created: function () {
69 this.fetchConfig(); 50 this.fetchConfig();
70 }, 51 },
52 computed: {
53 apiPath() {
54 return this.item.legacyApi ? LEGACY_API : V3_API;
55 },
56 },
71 methods: { 57 methods: {
72 fetchConfig: function () { 58 fetchConfig: function () {
73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, { 59 this.fetch(`${this.apiPath}/health?apikey=${this.item.apikey}`)
74 credentials: "include",
75 })
76 .then((response) => {
77 if (response.status != 200) {
78 throw new Error(response.statusText);
79 }
80 return response.json();
81 })
82 .then((health) => { 60 .then((health) => {
83 this.warnings = 0; 61 this.warnings = 0;
84 this.errors = 0; 62 this.errors = 0;
@@ -94,21 +72,18 @@ export default {
94 console.error(e); 72 console.error(e);
95 this.serverError = true; 73 this.serverError = true;
96 }); 74 });
97 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, { 75 this.fetch(`${this.apiPath}/queue?apikey=${this.item.apikey}`)
98 credentials: "include",
99 })
100 .then((response) => {
101 if (response.status != 200) {
102 throw new Error(response.statusText);
103 }
104 return response.json();
105 })
106 .then((queue) => { 76 .then((queue) => {
107 this.activity = 0; 77 this.activity = 0;
108 for (var i = 0; i < queue.length; i++) { 78
109 if (queue[i].movie) { 79 if (this.item.legacyApi) {
110 this.activity++; 80 for (var i = 0; i < queue.length; i++) {
81 if (queue[i].movie) {
82 this.activity++;
83 }
111 } 84 }
85 } else {
86 this.activity = queue.totalRecords;
112 } 87 }
113 }) 88 })
114 .catch((e) => { 89 .catch((e) => {
@@ -121,35 +96,30 @@ export default {
121</script> 96</script>
122 97
123<style scoped lang="scss"> 98<style scoped lang="scss">
124.media-left img {
125 max-height: 100%;
126}
127.notifs { 99.notifs {
128 position: absolute; 100 position: absolute;
129 color: white; 101 color: white;
130 font-family: sans-serif; 102 font-family: sans-serif;
131 top: 0.3em; 103 top: 0.3em;
132 right: 0.5em; 104 right: 0.5em;
133} 105 .notif {
134.notif { 106 display: inline-block;
135 padding-right: 0.35em; 107 padding: 0.2em 0.35em;
136 padding-left: 0.35em; 108 border-radius: 0.25em;
137 padding-top: 0.2em; 109 position: relative;
138 padding-bottom: 0.2em; 110 margin-left: 0.3em;
139 border-radius: 0.25em; 111 font-size: 0.8em;
140 position: relative; 112 &.activity {
141 margin-left: 0.3em; 113 background-color: #4fb5d6;
142 font-size: 0.8em; 114 }
143}
144.activity {
145 background-color: #4fb5d6;
146}
147 115
148.warnings { 116 &.warnings {
149 background-color: #d08d2e; 117 background-color: #d08d2e;
150} 118 }
151 119
152.errors { 120 &.errors {
153 background-color: #e51111; 121 background-color: #e51111;
122 }
123 }
154} 124}
155</style> 125</style>
diff --git a/src/components/services/Sonarr.vue b/src/components/services/Sonarr.vue
index 0270255..bb83b6b 100644
--- a/src/components/services/Sonarr.vue
+++ b/src/components/services/Sonarr.vue
@@ -1,62 +1,49 @@
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 class="notifs">
5 <div class="card-content"> 5 <strong v-if="activity > 0" class="notif activity" title="Activity">
6 <div class="media"> 6 {{ activity }}
7 <div v-if="item.logo" class="media-left"> 7 </strong>
8 <figure class="image is-48x48"> 8 <strong v-if="warnings > 0" class="notif warnings" title="Warning">
9 <img :src="item.logo" :alt="`${item.name} logo`" /> 9 {{ warnings }}
10 </figure> 10 </strong>
11 </div> 11 <strong v-if="errors > 0" class="notif errors" title="Error">
12 <div v-if="item.icon" class="media-left"> 12 {{ errors }}
13 <figure class="image is-48x48"> 13 </strong>
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> 14 <strong
15 </figure> 15 v-if="serverError"
16 </div> 16 class="notif errors"
17 <div class="media-content"> 17 title="Connection error to Sonarr API, check url and apikey in config.yml"
18 <p class="title is-4">{{ item.name }}</p> 18 >
19 <p class="subtitle is-6">{{ item.subtitle }}</p> 19 ?
20 </div> 20 </strong>
21 <div class="notifs"> 21 </div>
22 <strong 22 </template>
23 v-if="activity > 0" 23 </Generic>
24 class="notif activity"
25 title="Activity"
26 >{{ activity }}</strong
27 >
28 <strong
29 v-if="warnings > 0"
30 class="notif warnings"
31 title="Warning"
32 >{{ warnings }}</strong
33 >
34 <strong v-if="errors > 0" class="notif errors" title="Error">{{
35 errors
36 }}</strong>
37 <strong
38 v-if="serverError"
39 class="notif errors"
40 title="Connection error to Sonarr API, check url and apikey in config.yml"
41 >?</strong
42 >
43 </div>
44 </div>
45 <div class="tag" :class="item.tagstyle" v-if="item.tag">
46 <strong class="tag-text">#{{ item.tag }}</strong>
47 </div>
48 </div>
49 </a>
50 </div>
51 </div>
52</template> 24</template>
53 25
54<script> 26<script>
27import service from "@/mixins/service.js";
28import Generic from "./Generic.vue";
29
30const V3_API = "/api/v3";
31const LEGACY_API = "/api";
32
55export default { 33export default {
56 name: "Sonarr", 34 name: "Sonarr",
35 mixins: [service],
57 props: { 36 props: {
58 item: Object, 37 item: Object,
59 }, 38 },
39 components: {
40 Generic,
41 },
42 computed: {
43 apiPath() {
44 return this.item.legacyApi ? LEGACY_API : V3_API;
45 },
46 },
60 data: () => { 47 data: () => {
61 return { 48 return {
62 activity: null, 49 activity: null,
@@ -70,15 +57,7 @@ export default {
70 }, 57 },
71 methods: { 58 methods: {
72 fetchConfig: function () { 59 fetchConfig: function () {
73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, { 60 this.fetch(`${this.apiPath}/health?apikey=${this.item.apikey}`)
74 credentials: "include",
75 })
76 .then((response) => {
77 if (response.status != 200) {
78 throw new Error(response.statusText);
79 }
80 return response.json();
81 })
82 .then((health) => { 61 .then((health) => {
83 this.warnings = 0; 62 this.warnings = 0;
84 this.errors = 0; 63 this.errors = 0;
@@ -94,21 +73,17 @@ export default {
94 console.error(e); 73 console.error(e);
95 this.serverError = true; 74 this.serverError = true;
96 }); 75 });
97 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, { 76 this.fetch(`${this.apiPath}/queue?apikey=${this.item.apikey}`)
98 credentials: "include",
99 })
100 .then((response) => {
101 if (response.status != 200) {
102 throw new Error(response.statusText);
103 }
104 return response.json();
105 })
106 .then((queue) => { 77 .then((queue) => {
107 this.activity = 0; 78 this.activity = 0;
108 for (var i = 0; i < queue.length; i++) { 79 if (this.item.legacyApi) {
109 if (queue[i].series) { 80 for (var i = 0; i < queue.length; i++) {
110 this.activity++; 81 if (queue[i].series) {
82 this.activity++;
83 }
111 } 84 }
85 } else {
86 this.activity = queue.totalRecords;
112 } 87 }
113 }) 88 })
114 .catch((e) => { 89 .catch((e) => {
@@ -121,35 +96,32 @@ export default {
121</script> 96</script>
122 97
123<style scoped lang="scss"> 98<style scoped lang="scss">
124.media-left img {
125 max-height: 100%;
126}
127.notifs { 99.notifs {
128 position: absolute; 100 position: absolute;
129 color: white; 101 color: white;
130 font-family: sans-serif; 102 font-family: sans-serif;
131 top: 0.3em; 103 top: 0.3em;
132 right: 0.5em; 104 right: 0.5em;
133}
134.notif {
135 padding-right: 0.35em;
136 padding-left: 0.35em;
137 padding-top: 0.2em;
138 padding-bottom: 0.2em;
139 border-radius: 0.25em;
140 position: relative;
141 margin-left: 0.3em;
142 font-size: 0.8em;
143}
144.activity {
145 background-color: #4fb5d6;
146}
147 105
148.warnings { 106 .notif {
149 background-color: #d08d2e; 107 display: inline-block;
150} 108 padding: 0.2em 0.35em;
109 border-radius: 0.25em;
110 position: relative;
111 margin-left: 0.3em;
112 font-size: 0.8em;
113
114 &.activity {
115 background-color: #4fb5d6;
116 }
117
118 &.warnings {
119 background-color: #d08d2e;
120 }
151 121
152.errors { 122 &.errors {
153 background-color: #e51111; 123 background-color: #e51111;
124 }
125 }
154} 126}
155</style> 127</style>
diff --git a/src/mixins/service.js b/src/mixins/service.js
index 17fa6fc..abc708c 100644
--- a/src/mixins/service.js
+++ b/src/mixins/service.js
@@ -31,7 +31,13 @@ export default {
31 path = path.slice(1); 31 path = path.slice(1);
32 } 32 }
33 33
34 return fetch(`${this.endpoint}/${path}`, options).then((response) => { 34 let url = this.endpoint;
35
36 if (path) {
37 url = `${this.endpoint}/${path}`;
38 }
39
40 return fetch(url, options).then((response) => {
35 if (!response.ok) { 41 if (!response.ok) {
36 throw new Error("Not 2xx response"); 42 throw new Error("Not 2xx response");
37 } 43 }
diff --git a/yarn.lock b/yarn.lock
index 18906e1..a5e1e00 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1173,10 +1173,10 @@
1173 lodash.kebabcase "^4.1.1" 1173 lodash.kebabcase "^4.1.1"
1174 svg-tags "^1.0.0" 1174 svg-tags "^1.0.0"
1175 1175
1176"@vue/babel-preset-app@^4.5.13": 1176"@vue/babel-preset-app@^4.5.15":
1177 version "4.5.13" 1177 version "4.5.15"
1178 resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.5.13.tgz#cb475321e4c73f7f110dac29a48c2a9cb80afeb6" 1178 resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.5.15.tgz#f6bc08f8f674e98a260004234cde18b966d72eb0"
1179 integrity sha512-pM7CR3yXB6L8Gfn6EmX7FLNE3+V/15I3o33GkSNsWvgsMp6HVGXKkXgojrcfUUauyL1LZOdvTmu4enU2RePGHw== 1179 integrity sha512-J+YttzvwRfV1BPczf8r3qCevznYk+jh531agVF+5EYlHF4Sgh/cGXTz9qkkiux3LQgvhEGXgmCteg1n38WuuKg==
1180 dependencies: 1180 dependencies:
1181 "@babel/core" "^7.11.0" 1181 "@babel/core" "^7.11.0"
1182 "@babel/helper-compilation-targets" "^7.9.6" 1182 "@babel/helper-compilation-targets" "^7.9.6"
@@ -1258,61 +1258,61 @@
1258 "@vue/babel-plugin-transform-vue-jsx" "^1.2.1" 1258 "@vue/babel-plugin-transform-vue-jsx" "^1.2.1"
1259 camelcase "^5.0.0" 1259 camelcase "^5.0.0"
1260 1260
1261"@vue/cli-overlay@^4.5.13": 1261"@vue/cli-overlay@^4.5.15":
1262 version "4.5.13" 1262 version "4.5.15"
1263 resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.5.13.tgz#4f1fd2161be8f69d6cba8079f3f0d7dc4dee47a7" 1263 resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.5.15.tgz#0700fd6bad39336d4189ba3ff7d25e638e818c9c"
1264 integrity sha512-jhUIg3klgi5Cxhs8dnat5hi/W2tQJvsqCxR0u6hgfSob0ORODgUBlN+F/uwq7cKIe/pzedVUk1y07F13GQvPqg== 1264 integrity sha512-0zI0kANAVmjFO2LWGUIzdGPMeE3+9k+KeRDXsUqB30YfRF7abjfiiRPq5BU9pOzlJbVdpRkisschBrvdJqDuDg==
1265 1265
1266"@vue/cli-plugin-babel@~4.5.0": 1266"@vue/cli-plugin-babel@~4.5.15":
1267 version "4.5.13" 1267 version "4.5.15"
1268 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.13.tgz#a89c482edcc4ea1d135645cec502a7f5fd4c30e7" 1268 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.15.tgz#ae4fb2ed54255fe3d84df381dab68509641179ed"
1269 integrity sha512-ykvEAfD8PgGs+dGMGqr7l/nRmIS39NRzWLhMluPLTvDV1L+IxcoB73HNLGA/aENDpl8CuWrTE+1VgydcOhp+wg== 1269 integrity sha512-hBLrwYfFkHldEe34op/YNgPhpOWI5n5DB2Qt9I/1Epeif4M4iFaayrgjvOR9AVM6WbD3Yx7WCFszYpWrQZpBzQ==
1270 dependencies: 1270 dependencies:
1271 "@babel/core" "^7.11.0" 1271 "@babel/core" "^7.11.0"
1272 "@vue/babel-preset-app" "^4.5.13" 1272 "@vue/babel-preset-app" "^4.5.15"
1273 "@vue/cli-shared-utils" "^4.5.13" 1273 "@vue/cli-shared-utils" "^4.5.15"
1274 babel-loader "^8.1.0" 1274 babel-loader "^8.1.0"
1275 cache-loader "^4.1.0" 1275 cache-loader "^4.1.0"
1276 thread-loader "^2.1.3" 1276 thread-loader "^2.1.3"
1277 webpack "^4.0.0" 1277 webpack "^4.0.0"
1278 1278
1279"@vue/cli-plugin-eslint@~4.5.0": 1279"@vue/cli-plugin-eslint@~4.5.15":
1280 version "4.5.13" 1280 version "4.5.15"
1281 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.13.tgz#8baf22d0d96d76720c7506646b96f4f62c05bdfa" 1281 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.15.tgz#5781824a941f34c26336a67b1f6584a06c6a24ff"
1282 integrity sha512-yc2uXX6aBiy3vEf5TwaueaDqQbdIXIhk0x0KzEtpPo23jBdLkpOSoU5NCgE06g/ZiGAcettpmBSv73Hfp4wHEw== 1282 integrity sha512-/2Fl6wY/5bz3HD035oSnFRMsKNxDxU396KqBdpCQdwdvqk4mm6JAbXqihpcBRTNPeTO6w+LwGe6FE56PVbJdbg==
1283 dependencies: 1283 dependencies:
1284 "@vue/cli-shared-utils" "^4.5.13" 1284 "@vue/cli-shared-utils" "^4.5.15"
1285 eslint-loader "^2.2.1" 1285 eslint-loader "^2.2.1"
1286 globby "^9.2.0" 1286 globby "^9.2.0"
1287 inquirer "^7.1.0" 1287 inquirer "^7.1.0"
1288 webpack "^4.0.0" 1288 webpack "^4.0.0"
1289 yorkie "^2.0.0" 1289 yorkie "^2.0.0"
1290 1290
1291"@vue/cli-plugin-pwa@~4.5.0": 1291"@vue/cli-plugin-pwa@~4.5.15":
1292 version "4.5.13" 1292 version "4.5.15"
1293 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.13.tgz#a800639814b6f62a38f04198c340cfaee7295c3f" 1293 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.15.tgz#eb800c418d96b496deec9d063a1798fe6e9c2db8"
1294 integrity sha512-uU5pp94VU0YscfKq/mNRsKOdxG+CTqVlZWaYkRc+HCcwkJ/m/CnxgaEqQFr0QpHC8zmlX4gILO1RVYygJoR9tw== 1294 integrity sha512-yQzsspaIkjeQyN6btF8ATgbJFU023q1HC8uUpmiBa4QE9EyBlR8fSrKFhcJ0EmT6KnU7PMwlnOJ/OqjguFnufA==
1295 dependencies: 1295 dependencies:
1296 "@vue/cli-shared-utils" "^4.5.13" 1296 "@vue/cli-shared-utils" "^4.5.15"
1297 webpack "^4.0.0" 1297 webpack "^4.0.0"
1298 workbox-webpack-plugin "^4.3.1" 1298 workbox-webpack-plugin "^4.3.1"
1299 1299
1300"@vue/cli-plugin-router@^4.5.13": 1300"@vue/cli-plugin-router@^4.5.15":
1301 version "4.5.13" 1301 version "4.5.15"
1302 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.13.tgz#0b67c8898a2bf132941919a2a2e5f3aacbd9ffbe" 1302 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.15.tgz#1e75c8c89df42c694f143b9f1028de3cf5d61e1e"
1303 integrity sha512-tgtMDjchB/M1z8BcfV4jSOY9fZSMDTPgF9lsJIiqBWMxvBIsk9uIZHxp62DibYME4CCKb/nNK61XHaikFp+83w== 1303 integrity sha512-q7Y6kP9b3k55Ca2j59xJ7XPA6x+iSRB+N4ac0ZbcL1TbInVQ4j5wCzyE+uqid40hLy4fUdlpl4X9fHJEwuVxPA==
1304 dependencies: 1304 dependencies:
1305 "@vue/cli-shared-utils" "^4.5.13" 1305 "@vue/cli-shared-utils" "^4.5.15"
1306 1306
1307"@vue/cli-plugin-vuex@^4.5.13": 1307"@vue/cli-plugin-vuex@^4.5.15":
1308 version "4.5.13" 1308 version "4.5.15"
1309 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.13.tgz#98646d8bc1e69cf6c6a6cba2fed3eace0356c360" 1309 resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.15.tgz#466c1f02777d02fef53a9bb49a36cc3a3bcfec4e"
1310 integrity sha512-I1S9wZC7iI0Wn8kw8Zh+A2Qkf6s1M6vTGBkx8boXjuzfwEEyEHRxadsVCecZc8Mkpydo0nykj+MyYF96TKFuVA== 1310 integrity sha512-fqap+4HN+w+InDxlA3hZTOGE0tzBTgXhKLoDydhywqgmhQ1D9JA6Feh94ze6tG8DsWX58/ujYUqA8jAz17FJtg==
1311 1311
1312"@vue/cli-service@~4.5.0": 1312"@vue/cli-service@~4.5.15":
1313 version "4.5.13" 1313 version "4.5.15"
1314 resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.5.13.tgz#a09e684a801684b6e24e5414ad30650970eec9ed" 1314 resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.5.15.tgz#0e9a186d51550027d0e68e95042077eb4d115b45"
1315 integrity sha512-CKAZN4iokMMsaUyJRU22oUAz3oS/X9sVBSKAF2/shFBV5xh3jqAlKl8OXZYz4cXGFLA6djNuYrniuLAo7Ku97A== 1315 integrity sha512-sFWnLYVCn4zRfu45IcsIE9eXM0YpDV3S11vlM2/DVbIPAGoYo5ySpSof6aHcIvkeGsIsrHFpPHzNvDZ/efs7jA==
1316 dependencies: 1316 dependencies:
1317 "@intervolga/optimize-cssnano-plugin" "^1.0.5" 1317 "@intervolga/optimize-cssnano-plugin" "^1.0.5"
1318 "@soda/friendly-errors-webpack-plugin" "^1.7.1" 1318 "@soda/friendly-errors-webpack-plugin" "^1.7.1"
@@ -1320,10 +1320,10 @@
1320 "@types/minimist" "^1.2.0" 1320 "@types/minimist" "^1.2.0"
1321 "@types/webpack" "^4.0.0" 1321 "@types/webpack" "^4.0.0"
1322 "@types/webpack-dev-server" "^3.11.0" 1322 "@types/webpack-dev-server" "^3.11.0"
1323 "@vue/cli-overlay" "^4.5.13" 1323 "@vue/cli-overlay" "^4.5.15"
1324 "@vue/cli-plugin-router" "^4.5.13" 1324 "@vue/cli-plugin-router" "^4.5.15"
1325 "@vue/cli-plugin-vuex" "^4.5.13" 1325 "@vue/cli-plugin-vuex" "^4.5.15"
1326 "@vue/cli-shared-utils" "^4.5.13" 1326 "@vue/cli-shared-utils" "^4.5.15"
1327 "@vue/component-compiler-utils" "^3.1.2" 1327 "@vue/component-compiler-utils" "^3.1.2"
1328 "@vue/preload-webpack-plugin" "^1.1.0" 1328 "@vue/preload-webpack-plugin" "^1.1.0"
1329 "@vue/web-component-wrapper" "^1.2.0" 1329 "@vue/web-component-wrapper" "^1.2.0"
@@ -1372,10 +1372,10 @@
1372 optionalDependencies: 1372 optionalDependencies:
1373 vue-loader-v16 "npm:vue-loader@^16.1.0" 1373 vue-loader-v16 "npm:vue-loader@^16.1.0"
1374 1374
1375"@vue/cli-shared-utils@^4.5.13": 1375"@vue/cli-shared-utils@^4.5.15":
1376 version "4.5.13" 1376 version "4.5.15"
1377 resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.13.tgz#acd40f31b4790f1634292bdaa5fca95dc1e0ff50" 1377 resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.15.tgz#dba3858165dbe3465755f256a4890e69084532d6"
1378 integrity sha512-HpnOrkLg42RFUsQGMJv26oTG3J3FmKtO2WSRhKIIL+1ok3w9OjGCtA3nMMXN27f9eX14TqO64M36DaiSZ1fSiw== 1378 integrity sha512-SKaej9hHzzjKSOw1NlFmc6BSE0vcqUQMQiv1cxQ2DhVyy4QxZXBmzmiLBUBe+hYZZs1neXW7n//udeN9bCAY+Q==
1379 dependencies: 1379 dependencies:
1380 "@hapi/joi" "^15.0.1" 1380 "@hapi/joi" "^15.0.1"
1381 chalk "^2.4.2" 1381 chalk "^2.4.2"
@@ -2339,9 +2339,9 @@ caniuse-api@^3.0.0:
2339 lodash.uniq "^4.5.0" 2339 lodash.uniq "^4.5.0"
2340 2340
2341caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219: 2341caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219:
2342 version "1.0.30001245" 2342 version "1.0.30001311"
2343 resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001245.tgz#45b941bbd833cb0fa53861ff2bae746b3c6ca5d4" 2343 resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001311.tgz"
2344 integrity sha512-768fM9j1PKXpOCKws6eTo3RHmvTUsG9UrpT4WoREFeZgJBTi4/X9g565azS/rVUGtqb8nt7FjLeF5u4kukERnA== 2344 integrity sha512-mleTFtFKfykEeW34EyfhGIFjGCqzhh38Y0LhdQ9aWF+HorZTtdgKV/1hEE0NlFkG2ubvisPV6l400tlbPys98A==
2345 2345
2346case-sensitive-paths-webpack-plugin@^2.3.0: 2346case-sensitive-paths-webpack-plugin@^2.3.0:
2347 version "2.4.0" 2347 version "2.4.0"
@@ -2773,10 +2773,10 @@ 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.17.3: 2776core-js@^3.21.1:
2777 version "3.17.3" 2777 version "3.21.1"
2778 resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.17.3.tgz#8e8bd20e91df9951e903cabe91f9af4a0895bc1e" 2778 resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94"
2779 integrity sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw== 2779 integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==
2780 2780
2781core-js@^3.6.5: 2781core-js@^3.6.5:
2782 version "3.15.2" 2782 version "3.15.2"
@@ -3999,9 +3999,9 @@ flush-write-stream@^1.0.0:
3999 readable-stream "^2.3.6" 3999 readable-stream "^2.3.6"
4000 4000
4001follow-redirects@^1.0.0: 4001follow-redirects@^1.0.0:
4002 version "1.14.1" 4002 version "1.14.8"
4003 resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43" 4003 resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
4004 integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg== 4004 integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
4005 4005
4006for-in@^1.0.2: 4006for-in@^1.0.2:
4007 version "1.0.2" 4007 version "1.0.2"
@@ -8227,9 +8227,9 @@ url-loader@^2.2.0:
8227 schema-utils "^2.5.0" 8227 schema-utils "^2.5.0"
8228 8228
8229url-parse@^1.4.3, url-parse@^1.5.1: 8229url-parse@^1.4.3, url-parse@^1.5.1:
8230 version "1.5.3" 8230 version "1.5.10"
8231 resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" 8231 resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
8232 integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== 8232 integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
8233 dependencies: 8233 dependencies:
8234 querystringify "^2.1.1" 8234 querystringify "^2.1.1"
8235 requires-port "^1.0.0" 8235 requires-port "^1.0.0"