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.md8
-rw-r--r--docs/customservices.md73
-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.dist8
-rw-r--r--public/assets/config.yml.dist.sample-sui2
-rw-r--r--src/App.vue17
-rw-r--r--src/assets/defaults.yml6
-rw-r--r--src/components/DarkMode.vue14
-rw-r--r--src/components/GetStarted.vue35
-rw-r--r--src/components/SearchInput.vue4
-rw-r--r--src/components/SettingToggle.vue3
-rw-r--r--src/components/services/AdGuardHome.vue3
-rw-r--r--src/components/services/Emby.vue118
-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/PaperlessNG.vue79
-rw-r--r--src/components/services/PiHole.vue85
-rw-r--r--src/components/services/Portainer.vue139
-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.lock122
30 files changed, 973 insertions, 549 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 e2b5506..84a7ea7 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -5,7 +5,7 @@ Title, icons, links, colors, and services can be configured in the `config.yml`
5```yaml 5```yaml
6--- 6---
7# Homepage configuration 7# Homepage configuration
8# See https://fontawesome.com/icons for icons options 8# See https://fontawesome.com/v5/search for icons options
9 9
10# Optional: Use external configuration file. 10# Optional: Use external configuration file.
11# Using this will ignore remaining config in this file 11# Using this will ignore remaining config in this file
@@ -29,9 +29,13 @@ connectivityCheck: true # whether you want to display a message when the apps ar
29 29
30# Optional: Proxy / hosting option 30# Optional: Proxy / hosting option
31proxy: 31proxy:
32 # NOT All custom services implements this new option YET. Support will be extended very soon.
33 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.
34 33
34# Set the default layout and color scheme
35defaults:
36 layout: columns # Either 'columns', or 'list'
37 colorTheme: auto # One of 'auto', 'light', or 'dark'
38
35# Optional theming 39# Optional theming
36theme: default # 'default' or one of the themes available in 'src/assets/themes'. 40theme: default # 'default' or one of the themes available in 'src/assets/themes'.
37 41
diff --git a/docs/customservices.md b/docs/customservices.md
index 3fbd695..509278f 100644
--- a/docs/customservices.md
+++ b/docs/customservices.md
@@ -1,8 +1,22 @@
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
5within Homer. 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
9within Homer:
10+ [PiHole](#pihole)
11+ [OpenWeatherMap](#openweathermap)
12+ [Medusa](#medusa)
13+ [Lidarr, Prowlarr, Sonarr and Radarr](#lidarr-prowlarr-sonarr-and-radarr)
14+ [PaperlessNG](#paperlessng)
15+ [Ping](#ping)
16+ [Prometheus](#prometheus)
17+ [AdGuard Home](#adguard-home)
18+ [Portainer](#portainer)
19+ [Emby](#emby)
6 20
7If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page. 21If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page.
8 22
@@ -18,8 +32,6 @@ If you experiencing any issue, please have a look to the [troubleshooting](troub
18 type: "<type>" 32 type: "<type>"
19``` 33```
20 34
21⚠️🚧 `endpoint` & `useCredentials` new options are not yet supported by all custom services (but will be very soon).
22
23## PiHole 35## PiHole
24 36
25Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard. 37Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard.
@@ -65,18 +77,28 @@ Two lines are needed in the config.yml :
65The url must be the root url of Medusa application. 77The 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. 78The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API.
67 79
68## Sonarr/Radarr 80## Lidarr, Prowlarr, Sonarr and Radarr
69 81
70This service displays Activity (blue), Warning (orange) or Error (red) notifications bubbles from the Radarr/Sonarr application. 82This 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 : 83Two lines are needed in the config.yml :
72 84
73```yaml 85```yaml
74 type: "Radarr" or "Sonarr" 86 type: "Lidarr", "Prowlarr", "Radarr" or "Sonarr"
75 apikey: "01234deb70424befb1f4ef6a23456789" 87 apikey: "01234deb70424befb1f4ef6a23456789"
76``` 88```
77 89
78The url must be the root url of Radarr/Sonarr application. 90The 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. 91The Lidarr, Prowlarr, Radarr or Sonarr API key can be found in Settings > General. It is needed to access the API.
92If 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:
93
94```yaml
95- name: "Radarr"
96 type: "Radarr"
97 url: "http://localhost:7878/"
98 apikey: "MY-SUPER-SECRET-API-KEY"
99 target: "_blank"
100 legacyApi: true
101```
80 102
81## PaperlessNG 103## PaperlessNG
82 104
@@ -124,3 +146,36 @@ For AdGuard Home you need to set the type to AdGuard, if you have somes issues a
124 target: "_blank" 146 target: "_blank"
125 type: "AdGuardHome" 147 type: "AdGuardHome"
126``` 148```
149
150## Portainer
151
152This service displays info about the total number of containers managed by your Portainer instance.
153In order to use it, you must be using Portainer version 1.11 or later. Generate an access token from the UI and pass
154it to the apikey field.
155By default, every connected environments will be checked. To select specific ones,add an "environments" entry which can be a simple string or an array containing all the selected environments name.
156
157See https://docs.portainer.io/v/ce-2.11/user/account-settings#access-tokens
158
159```yaml
160- name: "Portainer"
161 logo: "assets/tools/sample.png"
162 url: "http://192.168.0.151/"
163 type: "Portainer"
164 apikey: "MY-SUPER-SECRET-API-KEY"
165 # environments:
166 # - "raspberry"
167 # - "local"
168```
169
170## Emby
171
172You need to set the type to Emby, provide an api key and choose which stats to show if the subtitle is disabled.
173
174```yaml
175- name: "Emby"
176 logo: "assets/tools/sample.png"
177 url: "http://192.168.0.151/"
178 type: "Emby"
179 apikey: "MY-SUPER-SECRET-API-KEY"
180 libraryType: "music" #Choose which stats to show. Can be one of: music, series or movies.
181```
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..03a8682 100644
--- a/public/assets/config.yml.dist
+++ b/public/assets/config.yml.dist
@@ -1,6 +1,6 @@
1--- 1---
2# Homepage configuration 2# Homepage configuration
3# See https://fontawesome.com/icons for icons options 3# See https://fontawesome.com/v5/search for icons options
4 4
5title: "Demo dashboard" 5title: "Demo dashboard"
6subtitle: "Homer" 6subtitle: "Homer"
@@ -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/public/assets/config.yml.dist.sample-sui b/public/assets/config.yml.dist.sample-sui
index ff7f80a..4ebf4a4 100644
--- a/public/assets/config.yml.dist.sample-sui
+++ b/public/assets/config.yml.dist.sample-sui
@@ -1,6 +1,6 @@
1--- 1---
2# Homepage configuration 2# Homepage configuration
3# See https://fontawesome.com/icons for icons options 3# See https://fontawesome.com/v5/search for icons options
4 4
5title: "Hello beautiful!" 5title: "Hello beautiful!"
6subtitle: "App dashboard" 6subtitle: "App dashboard"
diff --git a/src/App.vue b/src/App.vue
index 5c62a7f..fda13c3 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -30,18 +30,22 @@
30 :links="config.links" 30 :links="config.links"
31 @navbar-toggle="showMenu = !showMenu" 31 @navbar-toggle="showMenu = !showMenu"
32 > 32 >
33 <DarkMode @updated="isDark = $event" /> 33 <DarkMode
34 @updated="isDark = $event"
35 :defaultValue="this.config.defaults.colorTheme"
36 />
34 37
35 <SettingToggle 38 <SettingToggle
36 @updated="vlayout = $event" 39 @updated="vlayout = $event"
37 name="vlayout" 40 name="vlayout"
38 icon="fa-list" 41 icon="fa-list"
39 iconAlt="fa-columns" 42 iconAlt="fa-columns"
43 :defaultValue="this.config.defaults.layout == 'columns'"
40 /> 44 />
41 45
42 <SearchInput 46 <SearchInput
43 class="navbar-item is-inline-block-mobile" 47 class="navbar-item is-inline-block-mobile"
44 :hotkey=searchHotkey() 48 :hotkey="searchHotkey()"
45 @input="filterServices" 49 @input="filterServices"
46 @search-focus="showMenu = true" 50 @search-focus="showMenu = true"
47 @search-open="navigateToFirstService" 51 @search-open="navigateToFirstService"
@@ -56,6 +60,9 @@
56 v-if="config.connectivityCheck" 60 v-if="config.connectivityCheck"
57 @network-status-update="offline = $event" 61 @network-status-update="offline = $event"
58 /> 62 />
63
64 <GetStarted v-if="loaded && !services" />
65
59 <div v-if="!offline"> 66 <div v-if="!offline">
60 <!-- Optional messages --> 67 <!-- Optional messages -->
61 <Message :item="config.message" /> 68 <Message :item="config.message" />
@@ -130,6 +137,7 @@ const jsyaml = require("js-yaml");
130const merge = require("lodash.merge"); 137const merge = require("lodash.merge");
131 138
132import Navbar from "./components/Navbar.vue"; 139import Navbar from "./components/Navbar.vue";
140import GetStarted from "./components/GetStarted.vue";
133import ConnectivityChecker from "./components/ConnectivityChecker.vue"; 141import ConnectivityChecker from "./components/ConnectivityChecker.vue";
134import Service from "./components/Service.vue"; 142import Service from "./components/Service.vue";
135import Message from "./components/Message.vue"; 143import Message from "./components/Message.vue";
@@ -144,6 +152,7 @@ export default {
144 name: "App", 152 name: "App",
145 components: { 153 components: {
146 Navbar, 154 Navbar,
155 GetStarted,
147 ConnectivityChecker, 156 ConnectivityChecker,
148 Service, 157 Service,
149 Message, 158 Message,
@@ -154,6 +163,7 @@ export default {
154 }, 163 },
155 data: function () { 164 data: function () {
156 return { 165 return {
166 loaded: false,
157 config: null, 167 config: null,
158 services: null, 168 services: null,
159 offline: false, 169 offline: false,
@@ -166,6 +176,7 @@ export default {
166 created: async function () { 176 created: async function () {
167 this.buildDashboard(); 177 this.buildDashboard();
168 window.onhashchange = this.buildDashboard; 178 window.onhashchange = this.buildDashboard;
179 this.loaded = true;
169 }, 180 },
170 methods: { 181 methods: {
171 searchHotkey() { 182 searchHotkey() {
@@ -193,6 +204,7 @@ export default {
193 } 204 }
194 this.config = merge(defaults, config); 205 this.config = merge(defaults, config);
195 this.services = this.config.services; 206 this.services = this.config.services;
207
196 document.title = 208 document.title =
197 this.config.documentTitle || 209 this.config.documentTitle ||
198 `${this.config.title} | ${this.config.subtitle}`; 210 `${this.config.title} | ${this.config.subtitle}`;
@@ -211,6 +223,7 @@ export default {
211 window.location.href = response.url; 223 window.location.href = response.url;
212 return; 224 return;
213 } 225 }
226
214 if (!response.ok) { 227 if (!response.ok) {
215 throw Error(`${response.statusText}: ${response.body}`); 228 throw Error(`${response.statusText}: ${response.body}`);
216 } 229 }
diff --git a/src/assets/defaults.yml b/src/assets/defaults.yml
index ed1fbc9..85f2698 100644
--- a/src/assets/defaults.yml
+++ b/src/assets/defaults.yml
@@ -10,6 +10,12 @@ footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a hre
10columns: 3 10columns: 3
11connectivityCheck: true 11connectivityCheck: true
12 12
13defaults:
14 # columns, list
15 layout: columns
16 # auto, light, dark
17 colorTheme: auto
18
13theme: default 19theme: default
14colors: 20colors:
15 light: 21 light:
diff --git a/src/components/DarkMode.vue b/src/components/DarkMode.vue
index 80491fa..677238a 100644
--- a/src/components/DarkMode.vue
+++ b/src/components/DarkMode.vue
@@ -15,6 +15,9 @@
15<script> 15<script>
16export default { 16export default {
17 name: "Darkmode", 17 name: "Darkmode",
18 props: {
19 defaultValue: String,
20 },
18 data: function () { 21 data: function () {
19 return { 22 return {
20 isDark: null, 23 isDark: null,
@@ -30,6 +33,17 @@ export default {
30 if ("overrideDark" in localStorage) { 33 if ("overrideDark" in localStorage) {
31 // Light theme is 1 and Dark theme is 2 34 // Light theme is 1 and Dark theme is 2
32 this.mode = JSON.parse(localStorage.overrideDark) ? 2 : 1; 35 this.mode = JSON.parse(localStorage.overrideDark) ? 2 : 1;
36 } else {
37 switch (this.defaultValue) {
38 case "light":
39 this.mode = 1;
40 break;
41 case "dark":
42 this.mode = 2;
43 break;
44 default:
45 this.mode = 0;
46 }
33 } 47 }
34 this.isDark = this.getIsDark(); 48 this.isDark = this.getIsDark();
35 this.$emit("updated", this.isDark); 49 this.$emit("updated", this.isDark);
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 586ff71..165c992 100644
--- a/src/components/SearchInput.vue
+++ b/src/components/SearchInput.vue
@@ -19,8 +19,8 @@ export default {
19 value: String, 19 value: String,
20 hotkey: { 20 hotkey: {
21 type: String, 21 type: String,
22 default: "/" 22 default: "/",
23 } 23 },
24 }, 24 },
25 mounted() { 25 mounted() {
26 this._keyListener = function (event) { 26 this._keyListener = function (event) {
diff --git a/src/components/SettingToggle.vue b/src/components/SettingToggle.vue
index 985ca84..6c8a10f 100644
--- a/src/components/SettingToggle.vue
+++ b/src/components/SettingToggle.vue
@@ -12,6 +12,7 @@ export default {
12 name: String, 12 name: String,
13 icon: String, 13 icon: String,
14 iconAlt: String, 14 iconAlt: String,
15 defaultValue: Boolean,
15 }, 16 },
16 data: function () { 17 data: function () {
17 return { 18 return {
@@ -24,6 +25,8 @@ export default {
24 25
25 if (this.name in localStorage) { 26 if (this.name in localStorage) {
26 this.value = JSON.parse(localStorage[this.name]); 27 this.value = JSON.parse(localStorage[this.name]);
28 } else {
29 this.value = this.defaultValue;
27 } 30 }
28 31
29 this.$emit("updated", this.value); 32 this.$emit("updated", this.value);
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/Emby.vue b/src/components/services/Emby.vue
new file mode 100644
index 0000000..25a2612
--- /dev/null
+++ b/src/components/services/Emby.vue
@@ -0,0 +1,118 @@
1<template>
2 <Generic :item="item">
3 <template #content>
4 <p class="title is-4">{{ item.name }}</p>
5 <p class="subtitle is-6">
6 <template v-if="item.subtitle">
7 {{ item.subtitle }}
8 </template>
9 <template v-else>
10 {{ embyCount }}
11 </template>
12 </p>
13 </template>
14 <template #indicator>
15 <div v-if="status" class="status" :class="status">
16 {{ status }}
17 </div>
18 </template>
19 </Generic>
20</template>
21
22<script>
23import service from "@/mixins/service.js";
24import Generic from "./Generic.vue";
25
26export default {
27 name: "Emby",
28 mixins: [service],
29 props: {
30 item: Object,
31 },
32 components: {
33 Generic,
34 },
35 data: () => ({
36 status: "",
37 albumCount: 0,
38 songCount: 0,
39 movieCount: 0,
40 seriesCount: 0,
41 episodeCount: 0,
42 }),
43 computed: {
44 embyCount: function () {
45 if (this.item.libraryType === "music")
46 return `${this.songCount} songs, ${this.albumCount} albums`;
47 else if (this.item.libraryType === "movies")
48 return `${this.movieCount} movies`;
49 else if (this.item.libraryType === "series")
50 return `${this.episodeCount} eps, ${this.seriesCount} series`;
51 else return `wrong library type 💀`;
52 },
53 },
54 created() {
55 this.fetchServerStatus();
56
57 if (!this.item.subtitle && this.status !== "dead")
58 this.fetchServerMediaStats();
59 },
60 methods: {
61 fetchServerStatus: async function () {
62 this.fetch("/System/info/public")
63 .then((response) => {
64 if (response.Id) this.status = "running";
65 else throw new Error();
66 })
67 .catch((e) => {
68 console.log(e);
69 this.status = "dead";
70 });
71 },
72 fetchServerMediaStats: async function () {
73 const headers = {
74 "X-Emby-Token": this.item.apikey,
75 };
76
77 var data = await this.fetch("/items/counts", { headers }).catch((e) => {
78 console.log(e);
79 });
80
81 this.albumCount = data.AlbumCount;
82 this.songCount = data.SongCount;
83 this.movieCount = data.MovieCount;
84 this.seriesCount = data.SeriesCount;
85 this.episodeCount = data.EpisodeCount;
86 },
87 },
88};
89</script>
90
91<style scoped lang="scss">
92.status {
93 font-size: 0.8rem;
94 color: var(--text-title);
95
96 &.running:before {
97 background-color: #94e185;
98 border-color: #78d965;
99 box-shadow: 0 0 5px 1px #94e185;
100 }
101
102 &.dead:before {
103 background-color: #c9404d;
104 border-color: #c42c3b;
105 box-shadow: 0 0 5px 1px #c9404d;
106 }
107
108 &:before {
109 content: " ";
110 display: inline-block;
111 width: 7px;
112 height: 7px;
113 margin-right: 10px;
114 border: 1px solid #000;
115 border-radius: 7px;
116 }
117}
118</style>
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/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..d101ecc
--- /dev/null
+++ b/src/components/services/Portainer.vue
@@ -0,0 +1,139 @@
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 if (
87 this.item.environments &&
88 !this.item.environments.includes(endpoint.Name)
89 ) {
90 continue;
91 }
92 const uri = `/api/endpoints/${endpoint.Id}/docker/containers/json?all=1`;
93 const endpointContainers = await this.fetch(uri, { headers }).catch(
94 (e) => {
95 console.error(e);
96 }
97 );
98
99 if (endpointContainers) {
100 containers = containers.concat(endpointContainers);
101 }
102 }
103
104 this.containers = containers;
105 },
106 },
107};
108</script>
109
110<style scoped lang="scss">
111.notifs {
112 position: absolute;
113 color: white;
114 font-family: sans-serif;
115 top: 0.3em;
116 right: 0.5em;
117
118 .notif {
119 display: inline-block;
120 padding: 0.2em 0.35em;
121 border-radius: 0.25em;
122 position: relative;
123 margin-left: 0.3em;
124 font-size: 0.8em;
125
126 &.running {
127 background-color: #4fd671;
128 }
129
130 &.dead {
131 background-color: #e51111;
132 }
133
134 &.misc {
135 background-color: #2ed0c8;
136 }
137 }
138}
139</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..8139450 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"
@@ -5590,9 +5590,9 @@ minimatch@^3.0.4:
5590 brace-expansion "^1.1.7" 5590 brace-expansion "^1.1.7"
5591 5591
5592minimist@^1.2.0, minimist@^1.2.5: 5592minimist@^1.2.0, minimist@^1.2.5:
5593 version "1.2.5" 5593 version "1.2.6"
5594 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 5594 resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
5595 integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 5595 integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
5596 5596
5597minipass@^3.1.1: 5597minipass@^3.1.1:
5598 version "3.1.3" 5598 version "3.1.3"
@@ -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"