diff options
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 | ||
4 | name: Node.js CI | 4 | name: Integration |
5 | 5 | ||
6 | on: | 6 | on: |
7 | push: | 7 | push: |
@@ -10,7 +10,7 @@ COPY . . | |||
10 | RUN yarn build | 10 | RUN yarn build |
11 | 11 | ||
12 | # production stage | 12 | # production stage |
13 | FROM alpine:3.11 | 13 | FROM alpine:3.15 |
14 | 14 | ||
15 | ENV USER darkhttpd | 15 | ENV USER darkhttpd |
16 | ENV GROUP darkhttpd | 16 | ENV GROUP darkhttpd |
@@ -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 | ||
21 | header: true # Set to false to hide the header | 21 | header: true # Set to false to hide the header |
22 | # Optional: Different hotkey for search, defaults to "/" | ||
23 | # hotkey: | ||
24 | # search: "Shift" | ||
22 | footer: '<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. | 25 | footer: '<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 | ||
24 | columns: "3" # "auto" or number (must be a factor of 12: 1, 2, 3, 4, 6, 12) | 27 | columns: "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 |
28 | proxy: | 31 | proxy: |
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 | ||
3 | Some service can use a specific a component that provides some extra features by adding a `type` key to the service yaml | 3 | Some service can use a specific a component that provides some extra features by adding a `type` key to the service yaml |
4 | configuration. Available services are in `src/components/`. Here is an overview of all custom services that are available | 4 | configuration and, where applicable, an apikey. Note that config.yml is exposed at /assets/config.yml via HTTP and any |
5 | apikey included in the configuration file is exposed to anyone who can access the homer instance. Only include an apikey | ||
6 | if your homer instance is secured behind some form of authentication or access restriction. | ||
7 | |||
8 | Available services are in `src/components/`. Here is an overview of all custom services that are available | ||
5 | within Homer. | 9 | within Homer. |
6 | 10 | ||
7 | If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page. | 11 | If 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 | ||
25 | Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard. | 27 | Using 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 : | |||
65 | The url must be the root url of Medusa application. | 67 | The url must be the root url of Medusa application. |
66 | The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API. | 68 | The 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 | ||
70 | This service displays Activity (blue), Warning (orange) or Error (red) notifications bubbles from the Radarr/Sonarr application. | 72 | This service displays Activity (blue), Warning (orange) or Error (red) notifications bubbles from the Lidarr, Radarr or Sonarr application. |
71 | Two lines are needed in the config.yml : | 73 | Two 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 | ||
78 | The url must be the root url of Radarr/Sonarr application. | 80 | The url must be the root url of Lidarr, Prowlarr, Radarr or Sonarr application. |
79 | The Radarr/Sonarr API key can be found in Settings > General. It is needed to access the API. | 81 | The Lidarr, Prowlarr, Radarr or Sonarr API key can be found in Settings > General. It is needed to access the API. |
82 | If 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 | ||
83 | For Paperless you need an API-Key which you have to store at the item in the field `apikey`. | 95 | This service displays total number of documents stored. Two lines are required: |
96 | |||
97 | ```yaml | ||
98 | type: "PaperlessNG" | ||
99 | apikey: "0123456789abcdef123456789abcdef" | ||
100 | ``` | ||
101 | |||
102 | API 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 | |||
131 | This service displays info about the total number of containers managed by your Portainer instance. | ||
132 | In order to use it, you must be using Portainer version 1.11 or later. Generate an access token from the UI and pass | ||
133 | it to the apikey field. | ||
134 | |||
135 | See 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 | |||
6 | It happens when the targeted service is hosted on a different domain or port. | 6 | It happens when the targeted service is hosted on a different domain or port. |
7 | Web browsers will not allow to fetch information from a different site without explicit permissions (the targeted service | 7 | Web browsers will not allow to fetch information from a different site without explicit permissions (the targeted service |
8 | must include a special `Access-Control-Allow-Origin: *` HTTP headers). | 8 | must include a special `Access-Control-Allow-Origin: *` HTTP headers). |
9 | If this happens your web console (`ctrl+shit+i` or `F12`) will be filled with this kind of errors: | 9 | If this happens your web console (`ctrl+shift+i` or `F12`) will be filled with this kind of errors: |
10 | 10 | ||
11 | ```text | 11 | ```text |
12 | Access 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. | 12 | Access 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 | |||
15 | To resolve this, you can either: | 15 | To 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"); | |||
133 | const merge = require("lodash.merge"); | 137 | const merge = require("lodash.merge"); |
134 | 138 | ||
135 | import Navbar from "./components/Navbar.vue"; | 139 | import Navbar from "./components/Navbar.vue"; |
140 | import GetStarted from "./components/GetStarted.vue"; | ||
136 | import ConnectivityChecker from "./components/ConnectivityChecker.vue"; | 141 | import ConnectivityChecker from "./components/ConnectivityChecker.vue"; |
137 | import Service from "./components/Service.vue"; | 142 | import Service from "./components/Service.vue"; |
138 | import Message from "./components/Message.vue"; | 143 | import 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> | ||
20 | export default { | ||
21 | name: "GetStarted", | ||
22 | }; | ||
23 | </script> | ||
24 | |||
25 | <style lang="scss" scoped> | ||
26 | p { | ||
27 | color: #4a4a4a; | ||
28 | } | ||
29 | |||
30 | body #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> |
16 | export default { | 16 | export 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> | ||
26 | import service from "@/mixins/service.js"; | ||
27 | import Generic from "./Generic.vue"; | ||
28 | |||
29 | export 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> |
19 | import service from "@/mixins/service.js"; | ||
20 | import Generic from "./Generic.vue"; | ||
21 | |||
40 | export default { | 22 | export 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> |
35 | import service from "@/mixins/service.js"; | ||
36 | import Generic from "./Generic.vue"; | ||
37 | |||
58 | export default { | 38 | export 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> |
18 | import service from "@/mixins/service.js"; | ||
19 | import Generic from "./Generic.vue"; | ||
20 | |||
39 | export default { | 21 | export 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 }}% 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 }}% 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> |
23 | import service from "@/mixins/service.js"; | ||
24 | import Generic from "./Generic.vue"; | ||
25 | |||
42 | export default { | 26 | export 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> | ||
24 | import service from "@/mixins/service.js"; | ||
25 | import Generic from "./Generic.vue"; | ||
26 | |||
27 | export 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> | ||
24 | import service from "@/mixins/service.js"; | ||
25 | import Generic from "./Generic.vue"; | ||
26 | |||
27 | export 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> |
26 | import service from "@/mixins/service.js"; | ||
27 | import Generic from "./Generic.vue"; | ||
28 | |||
29 | const V3_API = "/api/v3"; | ||
30 | const LEGACY_API = "/api"; | ||
31 | |||
55 | export default { | 32 | export 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> |
27 | import service from "@/mixins/service.js"; | ||
28 | import Generic from "./Generic.vue"; | ||
29 | |||
30 | const V3_API = "/api/v3"; | ||
31 | const LEGACY_API = "/api"; | ||
32 | |||
55 | export default { | 33 | export 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 | } |
@@ -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 | ||
2341 | caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219: | 2341 | caniuse-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 | ||
2346 | case-sensitive-paths-webpack-plugin@^2.3.0: | 2346 | case-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 | ||
2776 | core-js@^3.17.3: | 2776 | core-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 | ||
2781 | core-js@^3.6.5: | 2781 | core-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 | ||
4001 | follow-redirects@^1.0.0: | 4001 | follow-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 | ||
4006 | for-in@^1.0.2: | 4006 | for-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 | ||
8229 | url-parse@^1.4.3, url-parse@^1.5.1: | 8229 | url-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" |