forked from extern/homer
Merge branch 'main' into feature/adguard-home-customservices-doc
This commit is contained in:
commit
7341d7634b
2
.github/workflows/integration.yml
vendored
2
.github/workflows/integration.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
# This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||||
|
|
||||||
name: Node.js CI
|
name: Integration
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
@ -10,7 +10,7 @@ COPY . .
|
|||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
# production stage
|
# production stage
|
||||||
FROM alpine:3.11
|
FROM alpine:3.15
|
||||||
|
|
||||||
ENV USER darkhttpd
|
ENV USER darkhttpd
|
||||||
ENV GROUP darkhttpd
|
ENV GROUP darkhttpd
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
- Search
|
- Search
|
||||||
- Grouping
|
- Grouping
|
||||||
- Theme customization
|
- Theme customization
|
||||||
- Offline heath check
|
- Offline health check
|
||||||
- keyboard shortcuts:
|
- keyboard shortcuts:
|
||||||
- `/` Start searching.
|
- `/` Start searching.
|
||||||
- `Escape` Stop searching.
|
- `Escape` Stop searching.
|
||||||
|
@ -5,7 +5,7 @@ Title, icons, links, colors, and services can be configured in the `config.yml`
|
|||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
# Homepage configuration
|
# Homepage configuration
|
||||||
# See https://fontawesome.com/icons for icons options
|
# See https://fontawesome.com/v5/search for icons options
|
||||||
|
|
||||||
# Optional: Use external configuration file.
|
# Optional: Use external configuration file.
|
||||||
# Using this will ignore remaining config in this file
|
# 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
|
|||||||
|
|
||||||
# Optional: Proxy / hosting option
|
# Optional: Proxy / hosting option
|
||||||
proxy:
|
proxy:
|
||||||
# NOT All custom services implements this new option YET. Support will be extended very soon.
|
|
||||||
useCredentials: false # send cookies & authorization headers when fetching service specific data. Set to `true` if you use an authentication proxy. Can be overrided on service level.
|
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.
|
||||||
|
|
||||||
|
# Set the default layout and color scheme
|
||||||
|
defaults:
|
||||||
|
layout: columns # Either 'columns', or 'list'
|
||||||
|
colorTheme: auto # One of 'auto', 'light', or 'dark'
|
||||||
|
|
||||||
# Optional theming
|
# Optional theming
|
||||||
theme: default # 'default' or one of the themes available in 'src/assets/themes'.
|
theme: default # 'default' or one of the themes available in 'src/assets/themes'.
|
||||||
|
|
||||||
|
@ -1,8 +1,22 @@
|
|||||||
# Custom Services
|
# Custom Services
|
||||||
|
|
||||||
Some service can use a specific a component that provides some extra features by adding a `type` key to the service yaml
|
Some service can use a specific a component that provides some extra features by adding a `type` key to the service yaml
|
||||||
configuration. Available services are in `src/components/`. Here is an overview of all custom services that are available
|
configuration and, where applicable, an apikey. Note that config.yml is exposed at /assets/config.yml via HTTP and any
|
||||||
within Homer.
|
apikey included in the configuration file is exposed to anyone who can access the homer instance. Only include an apikey
|
||||||
|
if your homer instance is secured behind some form of authentication or access restriction.
|
||||||
|
|
||||||
|
Available services are in `src/components/`. Here is an overview of all custom services that are available
|
||||||
|
within Homer:
|
||||||
|
+ [PiHole](#pihole)
|
||||||
|
+ [OpenWeatherMap](#openweathermap)
|
||||||
|
+ [Medusa](#medusa)
|
||||||
|
+ [Lidarr, Prowlarr, Sonarr and Radarr](#lidarr-prowlarr-sonarr-and-radarr)
|
||||||
|
+ [PaperlessNG](#paperlessng)
|
||||||
|
+ [Ping](#ping)
|
||||||
|
+ [Prometheus](#prometheus)
|
||||||
|
+ [AdGuard Home](#adguard-home)
|
||||||
|
+ [Portainer](#portainer)
|
||||||
|
+ [Emby](#emby)
|
||||||
|
|
||||||
If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page.
|
If you experiencing any issue, please have a look to the [troubleshooting](troubleshooting.md) page.
|
||||||
|
|
||||||
@ -18,8 +32,6 @@ If you experiencing any issue, please have a look to the [troubleshooting](troub
|
|||||||
type: "<type>"
|
type: "<type>"
|
||||||
```
|
```
|
||||||
|
|
||||||
⚠️🚧 `endpoint` & `useCredentials` new options are not yet supported by all custom services (but will be very soon).
|
|
||||||
|
|
||||||
## PiHole
|
## PiHole
|
||||||
|
|
||||||
Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard.
|
Using 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 :
|
|||||||
The url must be the root url of Medusa application.
|
The url must be the root url of Medusa application.
|
||||||
The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API.
|
The Medusa API key can be found in General configuration > Interface. It is needed to access Medusa API.
|
||||||
|
|
||||||
## Sonarr/Radarr
|
## Lidarr, Prowlarr, Sonarr and Radarr
|
||||||
|
|
||||||
This service displays Activity (blue), Warning (orange) or Error (red) notifications bubbles from the Radarr/Sonarr application.
|
This service displays Activity (blue), Warning (orange) or Error (red) notifications bubbles from the Lidarr, Radarr or Sonarr application.
|
||||||
Two lines are needed in the config.yml :
|
Two lines are needed in the config.yml :
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
type: "Radarr" or "Sonarr"
|
type: "Lidarr", "Prowlarr", "Radarr" or "Sonarr"
|
||||||
apikey: "01234deb70424befb1f4ef6a23456789"
|
apikey: "01234deb70424befb1f4ef6a23456789"
|
||||||
```
|
```
|
||||||
|
|
||||||
The url must be the root url of Radarr/Sonarr application.
|
The url must be the root url of Lidarr, Prowlarr, Radarr or Sonarr application.
|
||||||
The Radarr/Sonarr API key can be found in Settings > General. It is needed to access the API.
|
The Lidarr, Prowlarr, Radarr or Sonarr API key can be found in Settings > General. It is needed to access the API.
|
||||||
|
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:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: "Radarr"
|
||||||
|
type: "Radarr"
|
||||||
|
url: "http://localhost:7878/"
|
||||||
|
apikey: "MY-SUPER-SECRET-API-KEY"
|
||||||
|
target: "_blank"
|
||||||
|
legacyApi: true
|
||||||
|
```
|
||||||
|
|
||||||
## PaperlessNG
|
## PaperlessNG
|
||||||
|
|
||||||
@ -124,3 +146,36 @@ For AdGuard Home you need to set the type to AdGuard, if you have somes issues a
|
|||||||
target: "_blank"
|
target: "_blank"
|
||||||
type: "AdGuardHome"
|
type: "AdGuardHome"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Portainer
|
||||||
|
|
||||||
|
This service displays info about the total number of containers managed by your Portainer instance.
|
||||||
|
In order to use it, you must be using Portainer version 1.11 or later. Generate an access token from the UI and pass
|
||||||
|
it to the apikey field.
|
||||||
|
By 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.
|
||||||
|
|
||||||
|
See https://docs.portainer.io/v/ce-2.11/user/account-settings#access-tokens
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: "Portainer"
|
||||||
|
logo: "assets/tools/sample.png"
|
||||||
|
url: "http://192.168.0.151/"
|
||||||
|
type: "Portainer"
|
||||||
|
apikey: "MY-SUPER-SECRET-API-KEY"
|
||||||
|
# environments:
|
||||||
|
# - "raspberry"
|
||||||
|
# - "local"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Emby
|
||||||
|
|
||||||
|
You need to set the type to Emby, provide an api key and choose which stats to show if the subtitle is disabled.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: "Emby"
|
||||||
|
logo: "assets/tools/sample.png"
|
||||||
|
url: "http://192.168.0.151/"
|
||||||
|
type: "Emby"
|
||||||
|
apikey: "MY-SUPER-SECRET-API-KEY"
|
||||||
|
libraryType: "music" #Choose which stats to show. Can be one of: music, series or movies.
|
||||||
|
```
|
||||||
|
@ -51,7 +51,7 @@ and then simply reference these pre-defined (anchored) tags in each item like so
|
|||||||
- name: "VS Code"
|
- name: "VS Code"
|
||||||
logo: "/assets/vscode.png"
|
logo: "/assets/vscode.png"
|
||||||
subtitle: "Develop Code Anywhere, On Anything!"
|
subtitle: "Develop Code Anywhere, On Anything!"
|
||||||
<<: *App # Reference to the predefined "App" Tag
|
<<: *Apps # Reference to the predefined "App" Tag
|
||||||
url: "https://vscode.example.com/"
|
url: "https://vscode.example.com/"
|
||||||
target: "_blank" # optional html tag target attribute
|
target: "_blank" # optional html tag target attribute
|
||||||
````
|
````
|
||||||
|
@ -6,7 +6,7 @@ You might by facing a [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/C
|
|||||||
It happens when the targeted service is hosted on a different domain or port.
|
It happens when the targeted service is hosted on a different domain or port.
|
||||||
Web browsers will not allow to fetch information from a different site without explicit permissions (the targeted service
|
Web browsers will not allow to fetch information from a different site without explicit permissions (the targeted service
|
||||||
must include a special `Access-Control-Allow-Origin: *` HTTP headers).
|
must include a special `Access-Control-Allow-Origin: *` HTTP headers).
|
||||||
If this happens your web console (`ctrl+shit+i` or `F12`) will be filled with this kind of errors:
|
If this happens your web console (`ctrl+shift+i` or `F12`) will be filled with this kind of errors:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
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.
|
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
|
|||||||
To resolve this, you can either:
|
To resolve this, you can either:
|
||||||
|
|
||||||
* Host all your target service under the same domain & port.
|
* Host all your target service under the same domain & port.
|
||||||
* 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.
|
* 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.
|
||||||
* 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.
|
* 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.
|
||||||
|
14
package.json
14
package.json
@ -1,7 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "homer",
|
"name": "homer",
|
||||||
"version": "21.09.1",
|
"version": "21.09.1",
|
||||||
"license": "Apache-2.0",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"serve": "vue-cli-service serve",
|
"serve": "vue-cli-service serve",
|
||||||
"build": "vue-cli-service build",
|
"build": "vue-cli-service build",
|
||||||
@ -10,17 +9,17 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||||
"bulma": "^0.9.3",
|
"bulma": "^0.9.3",
|
||||||
"core-js": "^3.17.3",
|
"core-js": "^3.21.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lodash.merge": "^4.6.2",
|
"lodash.merge": "^4.6.2",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"vue": "^2.6.14"
|
"vue": "^2.6.14"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vue/cli-plugin-babel": "~4.5.0",
|
"@vue/cli-plugin-babel": "~4.5.15",
|
||||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
"@vue/cli-plugin-eslint": "~4.5.15",
|
||||||
"@vue/cli-plugin-pwa": "~4.5.0",
|
"@vue/cli-plugin-pwa": "~4.5.15",
|
||||||
"@vue/cli-service": "~4.5.0",
|
"@vue/cli-service": "~4.5.15",
|
||||||
"@vue/eslint-config-prettier": "^6.0.0",
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
"babel-eslint": "^10.1.0",
|
"babel-eslint": "^10.1.0",
|
||||||
"eslint": "^6.7.2",
|
"eslint": "^6.7.2",
|
||||||
@ -31,5 +30,6 @@
|
|||||||
"sass": "^1.26.5",
|
"sass": "^1.26.5",
|
||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"vue-template-compiler": "^2.6.12"
|
"vue-template-compiler": "^2.6.12"
|
||||||
}
|
},
|
||||||
|
"license": "Apache-2.0"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
# Additionnal page configuration
|
# Additional page configuration
|
||||||
|
|
||||||
# Additionnal configurations are loaded using its file name, minus the extension, as an anchor (https://<mydashboad>#<config>).
|
# Additional configurations are loaded using its file name, minus the extension, as an anchor (https://<mydashboad>#<config>).
|
||||||
# `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
|
# `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
|
||||||
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
# Homepage configuration
|
# Homepage configuration
|
||||||
# See https://fontawesome.com/icons for icons options
|
# See https://fontawesome.com/v5/search for icons options
|
||||||
|
|
||||||
title: "Demo dashboard"
|
title: "Demo dashboard"
|
||||||
subtitle: "Homer"
|
subtitle: "Homer"
|
||||||
@ -58,11 +58,11 @@ links:
|
|||||||
- name: "Wiki"
|
- name: "Wiki"
|
||||||
icon: "fas fa-book"
|
icon: "fas fa-book"
|
||||||
url: "https://www.wikipedia.org/"
|
url: "https://www.wikipedia.org/"
|
||||||
# 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
|
# 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
|
||||||
# see url field and assets/additionnal-page.yml.dist used in this example:
|
# see url field and assets/additional-page.yml.dist used in this example:
|
||||||
- name: "another page!"
|
- name: "another page!"
|
||||||
icon: "fas fa-file-alt"
|
icon: "fas fa-file-alt"
|
||||||
url: "#additionnal-page"
|
url: "#additional-page"
|
||||||
|
|
||||||
# Services
|
# Services
|
||||||
# First level array represent a group.
|
# First level array represent a group.
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
# Homepage configuration
|
# Homepage configuration
|
||||||
# See https://fontawesome.com/icons for icons options
|
# See https://fontawesome.com/v5/search for icons options
|
||||||
|
|
||||||
title: "Hello beautiful!"
|
title: "Hello beautiful!"
|
||||||
subtitle: "App dashboard"
|
subtitle: "App dashboard"
|
||||||
|
17
src/App.vue
17
src/App.vue
@ -30,18 +30,22 @@
|
|||||||
:links="config.links"
|
:links="config.links"
|
||||||
@navbar-toggle="showMenu = !showMenu"
|
@navbar-toggle="showMenu = !showMenu"
|
||||||
>
|
>
|
||||||
<DarkMode @updated="isDark = $event" />
|
<DarkMode
|
||||||
|
@updated="isDark = $event"
|
||||||
|
:defaultValue="this.config.defaults.colorTheme"
|
||||||
|
/>
|
||||||
|
|
||||||
<SettingToggle
|
<SettingToggle
|
||||||
@updated="vlayout = $event"
|
@updated="vlayout = $event"
|
||||||
name="vlayout"
|
name="vlayout"
|
||||||
icon="fa-list"
|
icon="fa-list"
|
||||||
iconAlt="fa-columns"
|
iconAlt="fa-columns"
|
||||||
|
:defaultValue="this.config.defaults.layout == 'columns'"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<SearchInput
|
<SearchInput
|
||||||
class="navbar-item is-inline-block-mobile"
|
class="navbar-item is-inline-block-mobile"
|
||||||
:hotkey=searchHotkey()
|
:hotkey="searchHotkey()"
|
||||||
@input="filterServices"
|
@input="filterServices"
|
||||||
@search-focus="showMenu = true"
|
@search-focus="showMenu = true"
|
||||||
@search-open="navigateToFirstService"
|
@search-open="navigateToFirstService"
|
||||||
@ -56,6 +60,9 @@
|
|||||||
v-if="config.connectivityCheck"
|
v-if="config.connectivityCheck"
|
||||||
@network-status-update="offline = $event"
|
@network-status-update="offline = $event"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<GetStarted v-if="loaded && !services" />
|
||||||
|
|
||||||
<div v-if="!offline">
|
<div v-if="!offline">
|
||||||
<!-- Optional messages -->
|
<!-- Optional messages -->
|
||||||
<Message :item="config.message" />
|
<Message :item="config.message" />
|
||||||
@ -130,6 +137,7 @@ const jsyaml = require("js-yaml");
|
|||||||
const merge = require("lodash.merge");
|
const merge = require("lodash.merge");
|
||||||
|
|
||||||
import Navbar from "./components/Navbar.vue";
|
import Navbar from "./components/Navbar.vue";
|
||||||
|
import GetStarted from "./components/GetStarted.vue";
|
||||||
import ConnectivityChecker from "./components/ConnectivityChecker.vue";
|
import ConnectivityChecker from "./components/ConnectivityChecker.vue";
|
||||||
import Service from "./components/Service.vue";
|
import Service from "./components/Service.vue";
|
||||||
import Message from "./components/Message.vue";
|
import Message from "./components/Message.vue";
|
||||||
@ -144,6 +152,7 @@ export default {
|
|||||||
name: "App",
|
name: "App",
|
||||||
components: {
|
components: {
|
||||||
Navbar,
|
Navbar,
|
||||||
|
GetStarted,
|
||||||
ConnectivityChecker,
|
ConnectivityChecker,
|
||||||
Service,
|
Service,
|
||||||
Message,
|
Message,
|
||||||
@ -154,6 +163,7 @@ export default {
|
|||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
|
loaded: false,
|
||||||
config: null,
|
config: null,
|
||||||
services: null,
|
services: null,
|
||||||
offline: false,
|
offline: false,
|
||||||
@ -166,6 +176,7 @@ export default {
|
|||||||
created: async function () {
|
created: async function () {
|
||||||
this.buildDashboard();
|
this.buildDashboard();
|
||||||
window.onhashchange = this.buildDashboard;
|
window.onhashchange = this.buildDashboard;
|
||||||
|
this.loaded = true;
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
searchHotkey() {
|
searchHotkey() {
|
||||||
@ -193,6 +204,7 @@ export default {
|
|||||||
}
|
}
|
||||||
this.config = merge(defaults, config);
|
this.config = merge(defaults, config);
|
||||||
this.services = this.config.services;
|
this.services = this.config.services;
|
||||||
|
|
||||||
document.title =
|
document.title =
|
||||||
this.config.documentTitle ||
|
this.config.documentTitle ||
|
||||||
`${this.config.title} | ${this.config.subtitle}`;
|
`${this.config.title} | ${this.config.subtitle}`;
|
||||||
@ -211,6 +223,7 @@ export default {
|
|||||||
window.location.href = response.url;
|
window.location.href = response.url;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw Error(`${response.statusText}: ${response.body}`);
|
throw Error(`${response.statusText}: ${response.body}`);
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,12 @@ footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a hre
|
|||||||
columns: 3
|
columns: 3
|
||||||
connectivityCheck: true
|
connectivityCheck: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
# columns, list
|
||||||
|
layout: columns
|
||||||
|
# auto, light, dark
|
||||||
|
colorTheme: auto
|
||||||
|
|
||||||
theme: default
|
theme: default
|
||||||
colors:
|
colors:
|
||||||
light:
|
light:
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: "Darkmode",
|
name: "Darkmode",
|
||||||
|
props: {
|
||||||
|
defaultValue: String,
|
||||||
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
isDark: null,
|
isDark: null,
|
||||||
@ -30,6 +33,17 @@ export default {
|
|||||||
if ("overrideDark" in localStorage) {
|
if ("overrideDark" in localStorage) {
|
||||||
// Light theme is 1 and Dark theme is 2
|
// Light theme is 1 and Dark theme is 2
|
||||||
this.mode = JSON.parse(localStorage.overrideDark) ? 2 : 1;
|
this.mode = JSON.parse(localStorage.overrideDark) ? 2 : 1;
|
||||||
|
} else {
|
||||||
|
switch (this.defaultValue) {
|
||||||
|
case "light":
|
||||||
|
this.mode = 1;
|
||||||
|
break;
|
||||||
|
case "dark":
|
||||||
|
this.mode = 2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.mode = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.isDark = this.getIsDark();
|
this.isDark = this.getIsDark();
|
||||||
this.$emit("updated", this.isDark);
|
this.$emit("updated", this.isDark);
|
||||||
|
35
src/components/GetStarted.vue
Normal file
35
src/components/GetStarted.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<template>
|
||||||
|
<article>
|
||||||
|
<div class="m-6 has-text-centered py-6">
|
||||||
|
<p class="is-size-5 mb-0">No configured service found!</p>
|
||||||
|
<p>Check out the documentation to start building your Homer dashboard.</p>
|
||||||
|
<p>
|
||||||
|
<a
|
||||||
|
class="button is-primary mt-5 has-text-weight-bold"
|
||||||
|
href="https://github.com/bastienwirtz/homer/blob/main/README.md#getting-started"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Get started
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "GetStarted",
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
p {
|
||||||
|
color: #4a4a4a;
|
||||||
|
}
|
||||||
|
|
||||||
|
body #app a {
|
||||||
|
font-weight: 900;
|
||||||
|
color: #ffffff;
|
||||||
|
font-family: "Lato", sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
@ -19,8 +19,8 @@ export default {
|
|||||||
value: String,
|
value: String,
|
||||||
hotkey: {
|
hotkey: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "/"
|
default: "/",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this._keyListener = function (event) {
|
this._keyListener = function (event) {
|
||||||
|
@ -12,6 +12,7 @@ export default {
|
|||||||
name: String,
|
name: String,
|
||||||
icon: String,
|
icon: String,
|
||||||
iconAlt: String,
|
iconAlt: String,
|
||||||
|
defaultValue: Boolean,
|
||||||
},
|
},
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
@ -24,6 +25,8 @@ export default {
|
|||||||
|
|
||||||
if (this.name in localStorage) {
|
if (this.name in localStorage) {
|
||||||
this.value = JSON.parse(localStorage[this.name]);
|
this.value = JSON.parse(localStorage[this.name]);
|
||||||
|
} else {
|
||||||
|
this.value = this.defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit("updated", this.value);
|
this.$emit("updated", this.value);
|
||||||
|
@ -76,9 +76,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.media-left img {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
.status {
|
.status {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: var(--text-title);
|
color: var(--text-title);
|
||||||
|
118
src/components/services/Emby.vue
Normal file
118
src/components/services/Emby.vue
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<Generic :item="item">
|
||||||
|
<template #content>
|
||||||
|
<p class="title is-4">{{ item.name }}</p>
|
||||||
|
<p class="subtitle is-6">
|
||||||
|
<template v-if="item.subtitle">
|
||||||
|
{{ item.subtitle }}
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
{{ embyCount }}
|
||||||
|
</template>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #indicator>
|
||||||
|
<div v-if="status" class="status" :class="status">
|
||||||
|
{{ status }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Generic>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Emby",
|
||||||
|
mixins: [service],
|
||||||
|
props: {
|
||||||
|
item: Object,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
status: "",
|
||||||
|
albumCount: 0,
|
||||||
|
songCount: 0,
|
||||||
|
movieCount: 0,
|
||||||
|
seriesCount: 0,
|
||||||
|
episodeCount: 0,
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
embyCount: function () {
|
||||||
|
if (this.item.libraryType === "music")
|
||||||
|
return `${this.songCount} songs, ${this.albumCount} albums`;
|
||||||
|
else if (this.item.libraryType === "movies")
|
||||||
|
return `${this.movieCount} movies`;
|
||||||
|
else if (this.item.libraryType === "series")
|
||||||
|
return `${this.episodeCount} eps, ${this.seriesCount} series`;
|
||||||
|
else return `wrong library type 💀`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchServerStatus();
|
||||||
|
|
||||||
|
if (!this.item.subtitle && this.status !== "dead")
|
||||||
|
this.fetchServerMediaStats();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchServerStatus: async function () {
|
||||||
|
this.fetch("/System/info/public")
|
||||||
|
.then((response) => {
|
||||||
|
if (response.Id) this.status = "running";
|
||||||
|
else throw new Error();
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
this.status = "dead";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
fetchServerMediaStats: async function () {
|
||||||
|
const headers = {
|
||||||
|
"X-Emby-Token": this.item.apikey,
|
||||||
|
};
|
||||||
|
|
||||||
|
var data = await this.fetch("/items/counts", { headers }).catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.albumCount = data.AlbumCount;
|
||||||
|
this.songCount = data.SongCount;
|
||||||
|
this.movieCount = data.MovieCount;
|
||||||
|
this.seriesCount = data.SeriesCount;
|
||||||
|
this.episodeCount = data.EpisodeCount;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.status {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--text-title);
|
||||||
|
|
||||||
|
&.running:before {
|
||||||
|
background-color: #94e185;
|
||||||
|
border-color: #78d965;
|
||||||
|
box-shadow: 0 0 5px 1px #94e185;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dead:before {
|
||||||
|
background-color: #c9404d;
|
||||||
|
border-color: #c42c3b;
|
||||||
|
box-shadow: 0 0 5px 1px #c9404d;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: " ";
|
||||||
|
display: inline-block;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border: 1px solid #000;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
110
src/components/services/Lidarr.vue
Normal file
110
src/components/services/Lidarr.vue
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<template>
|
||||||
|
<Generic :item="item">
|
||||||
|
<template #indicator>
|
||||||
|
<div class="notifs">
|
||||||
|
<strong v-if="activity > 0" class="notif activity" title="Activity">
|
||||||
|
{{ activity }}
|
||||||
|
</strong>
|
||||||
|
<strong v-if="warnings > 0" class="notif warnings" title="Warning">
|
||||||
|
{{ warnings }}
|
||||||
|
</strong>
|
||||||
|
<strong v-if="errors > 0" class="notif errors" title="Error">
|
||||||
|
{{ errors }}
|
||||||
|
</strong>
|
||||||
|
<strong
|
||||||
|
v-if="serverError"
|
||||||
|
class="notif errors"
|
||||||
|
title="Connection error to Lidarr API, check url and apikey in config.yml"
|
||||||
|
>?</strong
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Generic>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Lidarr",
|
||||||
|
mixins: [service],
|
||||||
|
props: {
|
||||||
|
item: Object,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
activity: null,
|
||||||
|
warnings: null,
|
||||||
|
errors: null,
|
||||||
|
serverError: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
this.fetchConfig();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchConfig: function () {
|
||||||
|
this.fetch(`/api/v1/health?apikey=${this.item.apikey}`)
|
||||||
|
.then((health) => {
|
||||||
|
this.warnings = 0;
|
||||||
|
this.errors = 0;
|
||||||
|
for (var i = 0; i < health.length; i++) {
|
||||||
|
if (health[i].type == "warning") {
|
||||||
|
this.warnings++;
|
||||||
|
} else if (health[i].type == "error") {
|
||||||
|
this.errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
this.serverError = true;
|
||||||
|
});
|
||||||
|
this.fetch(`/api/v1/queue/status?apikey=${this.item.apikey}`)
|
||||||
|
.then((queue) => {
|
||||||
|
this.activity = queue.totalCount;
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
this.serverError = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.notifs {
|
||||||
|
position: absolute;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
top: 0.3em;
|
||||||
|
right: 0.5em;
|
||||||
|
.notif {
|
||||||
|
display: inline-block;
|
||||||
|
padding-right: 0.35em;
|
||||||
|
padding-left: 0.35em;
|
||||||
|
padding-top: 0.2em;
|
||||||
|
padding-bottom: 0.2em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 0.3em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
&.activity {
|
||||||
|
background-color: #4fb5d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.warnings {
|
||||||
|
background-color: #d08d2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.errors {
|
||||||
|
background-color: #e51111;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,20 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<Generic :item="item">
|
||||||
<div class="card" :class="item.class">
|
<template #content>
|
||||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div v-if="item.logo" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.icon" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-4">{{ item.name }}</p>
|
<p class="title is-4">{{ item.name }}</p>
|
||||||
<p class="subtitle is-6">
|
<p class="subtitle is-6">
|
||||||
<template v-if="item.subtitle">
|
<template v-if="item.subtitle">
|
||||||
@ -25,23 +11,23 @@
|
|||||||
happily keeping {{ stats.totalRecipes }} recipes organized
|
happily keeping {{ stats.totalRecipes }} recipes organized
|
||||||
</template>
|
</template>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</Generic>
|
||||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
|
||||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Mealie",
|
name: "Mealie",
|
||||||
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
stats: null,
|
stats: null,
|
||||||
meal: null,
|
meal: null,
|
||||||
@ -51,44 +37,20 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchStatus: async function () {
|
fetchStatus: async function () {
|
||||||
if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing
|
const headers = {
|
||||||
this.meal = await fetch(`${this.item.url}/api/meal-plans/today/`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: "Bearer " + this.item.apikey,
|
Authorization: "Bearer " + this.item.apikey,
|
||||||
Accept: "application/json",
|
Accept: "application/json",
|
||||||
},
|
};
|
||||||
})
|
|
||||||
.then(function (response) {
|
if (this.item.subtitle != null) return;
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Not 2xx response");
|
this.meal = await this.fetch("/api/meal-plans/today/", { headers }).catch(
|
||||||
} else {
|
(e) => console.log(e)
|
||||||
if (response != null) {
|
);
|
||||||
return response.json();
|
this.stats = await this.fetch("/api/debug/statistics/", {
|
||||||
}
|
headers,
|
||||||
}
|
}).catch((e) => console.log(e));
|
||||||
})
|
|
||||||
.catch((e) => console.log(e));
|
|
||||||
this.stats = await fetch(`${this.item.url}/api/debug/statistics/`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: "Bearer " + this.item.apikey,
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(function (response) {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Not 2xx response");
|
|
||||||
} else {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => console.log(e));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.media-left img {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,23 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<Generic :item="item">
|
||||||
<div class="card" :class="item.class">
|
<template #indicator>
|
||||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div v-if="item.logo" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.icon" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-4">{{ item.name }}</p>
|
|
||||||
<p class="subtitle is-6">{{ item.subtitle }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="notifs">
|
<div class="notifs">
|
||||||
<strong
|
<strong
|
||||||
v-if="config !== null && config.system.news.unread > 0"
|
v-if="config !== null && config.system.news.unread > 0"
|
||||||
@ -44,22 +27,23 @@
|
|||||||
>?</strong
|
>?</strong
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
</Generic>
|
||||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Medusa",
|
name: "Medusa",
|
||||||
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
config: null,
|
config: null,
|
||||||
@ -71,15 +55,8 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchConfig: function () {
|
fetchConfig: function () {
|
||||||
fetch(`${this.item.url}/api/v2/config`, {
|
this.fetch("/api/v2/config", {
|
||||||
credentials: "include",
|
headers: { "X-Api-Key": this.item.apikey },
|
||||||
headers: { "X-Api-Key": `${this.item.apikey}` },
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status != 200) {
|
|
||||||
throw new Error(response.statusText);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
})
|
||||||
.then((conf) => {
|
.then((conf) => {
|
||||||
this.config = conf;
|
this.config = conf;
|
||||||
@ -94,16 +71,12 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.media-left img {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
.notifs {
|
.notifs {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: white;
|
color: white;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
top: 0.3em;
|
top: 0.3em;
|
||||||
right: 0.5em;
|
right: 0.5em;
|
||||||
}
|
|
||||||
.notif {
|
.notif {
|
||||||
padding-right: 0.35em;
|
padding-right: 0.35em;
|
||||||
padding-left: 0.35em;
|
padding-left: 0.35em;
|
||||||
@ -113,16 +86,17 @@ export default {
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin-left: 0.3em;
|
margin-left: 0.3em;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
&.news {
|
||||||
.news {
|
|
||||||
background-color: #777777;
|
background-color: #777777;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warnings {
|
&.warnings {
|
||||||
background-color: #d08d2e;
|
background-color: #d08d2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.errors {
|
&.errors {
|
||||||
background-color: #e51111;
|
background-color: #e51111;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,20 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<Generic :item="item">
|
||||||
<div class="card" :class="item.class">
|
<template #content>
|
||||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div v-if="item.logo" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.icon" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-4">{{ item.name }}</p>
|
<p class="title is-4">{{ item.name }}</p>
|
||||||
<p class="subtitle is-6">
|
<p class="subtitle is-6">
|
||||||
<template v-if="item.subtitle">
|
<template v-if="item.subtitle">
|
||||||
@ -24,23 +10,23 @@
|
|||||||
happily storing {{ api.count }} documents
|
happily storing {{ api.count }} documents
|
||||||
</template>
|
</template>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</Generic>
|
||||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
|
||||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Paperless",
|
name: "Paperless",
|
||||||
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
api: null,
|
api: null,
|
||||||
}),
|
}),
|
||||||
@ -49,36 +35,21 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchStatus: async function () {
|
fetchStatus: async function () {
|
||||||
if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing
|
if (this.item.subtitle != null) return;
|
||||||
var apikey = this.item.apikey;
|
|
||||||
|
const apikey = this.item.apikey;
|
||||||
if (!apikey) {
|
if (!apikey) {
|
||||||
console.error(
|
console.error(
|
||||||
"apikey is not present in config.yml for the paperless entry!"
|
"apikey is not present in config.yml for the paperless entry!"
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const url = `${this.item.url}/api/documents/`;
|
this.api = await this.fetch("/api/documents/", {
|
||||||
this.api = await fetch(url, {
|
|
||||||
credentials: "include",
|
|
||||||
headers: {
|
headers: {
|
||||||
Authorization: "Token " + this.item.apikey,
|
Authorization: "Token " + this.item.apikey,
|
||||||
},
|
},
|
||||||
})
|
}).catch((e) => console.log(e));
|
||||||
.then(function (response) {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error("Not 2xx response");
|
|
||||||
} else {
|
|
||||||
return response.json();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((e) => console.log(e));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.media-left img {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
@ -1,59 +1,45 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<Generic :item="item">
|
||||||
<div class="card" :class="item.class">
|
<template #content>
|
||||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div v-if="item.logo" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.icon" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-4">{{ item.name }}</p>
|
<p class="title is-4">{{ item.name }}</p>
|
||||||
<p class="subtitle is-6">
|
<p class="subtitle is-6">
|
||||||
<template v-if="item.subtitle">
|
<template v-if="item.subtitle">
|
||||||
{{ item.subtitle }}
|
{{ item.subtitle }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="api">
|
<template v-else-if="percentage">
|
||||||
{{ percentage }}% blocked
|
{{ percentage }}% blocked
|
||||||
</template>
|
</template>
|
||||||
</p>
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #indicator>
|
||||||
|
<div v-if="status" class="status" :class="status">
|
||||||
|
{{ status }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="api" class="status" :class="api.status">
|
</template>
|
||||||
{{ api.status }}
|
</Generic>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
|
||||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "PiHole",
|
name: "PiHole",
|
||||||
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
api: {
|
|
||||||
status: "",
|
status: "",
|
||||||
ads_percentage_today: 0,
|
ads_percentage_today: 0,
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
computed: {
|
computed: {
|
||||||
percentage: function () {
|
percentage: function () {
|
||||||
if (this.api) {
|
if (this.ads_percentage_today) {
|
||||||
return this.api.ads_percentage_today.toFixed(1);
|
return this.ads_percentage_today.toFixed(1);
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
@ -63,21 +49,16 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchStatus: async function () {
|
fetchStatus: async function () {
|
||||||
const url = `${this.item.url}/api.php`;
|
const result = await this.fetch("/api.php").catch((e) => console.log(e));
|
||||||
this.api = await fetch(url, {
|
|
||||||
credentials: "include",
|
this.status = result.status;
|
||||||
})
|
this.ads_percentage_today = result.ads_percentage_today;
|
||||||
.then((response) => response.json())
|
|
||||||
.catch((e) => console.log(e));
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.media-left img {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
.status {
|
.status {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
color: var(--text-title);
|
color: var(--text-title);
|
||||||
|
139
src/components/services/Portainer.vue
Normal file
139
src/components/services/Portainer.vue
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
<template>
|
||||||
|
<Generic :item="item">
|
||||||
|
<template #indicator>
|
||||||
|
<div class="notifs">
|
||||||
|
<strong v-if="running > 0" class="notif running" title="Running">
|
||||||
|
{{ running }}
|
||||||
|
</strong>
|
||||||
|
<strong v-if="dead > 0" class="notif dead" title="Dead">
|
||||||
|
{{ dead }}
|
||||||
|
</strong>
|
||||||
|
<strong
|
||||||
|
v-if="misc > 0"
|
||||||
|
class="notif misc"
|
||||||
|
title="Other (creating, paused, exited, etc.)"
|
||||||
|
>
|
||||||
|
{{ misc }}
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Generic>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Portainer",
|
||||||
|
mixins: [service],
|
||||||
|
props: {
|
||||||
|
item: Object,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
|
data: () => ({
|
||||||
|
endpoints: null,
|
||||||
|
containers: null,
|
||||||
|
}),
|
||||||
|
computed: {
|
||||||
|
running: function () {
|
||||||
|
if (!this.containers) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.containers.filter((container) => {
|
||||||
|
return container.State.toLowerCase() === "running";
|
||||||
|
}).length;
|
||||||
|
},
|
||||||
|
dead: function () {
|
||||||
|
if (!this.containers) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.containers.filter((container) => {
|
||||||
|
return container.State.toLowerCase() === "dead";
|
||||||
|
}).length;
|
||||||
|
},
|
||||||
|
misc: function () {
|
||||||
|
if (!this.containers) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return this.containers.filter((container) => {
|
||||||
|
return (
|
||||||
|
container.State.toLowerCase() !== "running" &&
|
||||||
|
container.State.toLowerCase() !== "dead"
|
||||||
|
);
|
||||||
|
}).length;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.fetchStatus();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchStatus: async function () {
|
||||||
|
const headers = {
|
||||||
|
"X-Api-Key": this.item.apikey,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.endpoints = await this.fetch("/api/endpoints", { headers }).catch(
|
||||||
|
(e) => {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let containers = [];
|
||||||
|
for (let endpoint of this.endpoints) {
|
||||||
|
if (
|
||||||
|
this.item.environments &&
|
||||||
|
!this.item.environments.includes(endpoint.Name)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const uri = `/api/endpoints/${endpoint.Id}/docker/containers/json?all=1`;
|
||||||
|
const endpointContainers = await this.fetch(uri, { headers }).catch(
|
||||||
|
(e) => {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (endpointContainers) {
|
||||||
|
containers = containers.concat(endpointContainers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.containers = containers;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.notifs {
|
||||||
|
position: absolute;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
top: 0.3em;
|
||||||
|
right: 0.5em;
|
||||||
|
|
||||||
|
.notif {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.2em 0.35em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 0.3em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
|
||||||
|
&.running {
|
||||||
|
background-color: #4fd671;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.dead {
|
||||||
|
background-color: #e51111;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.misc {
|
||||||
|
background-color: #2ed0c8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
94
src/components/services/Prowlarr.vue
Normal file
94
src/components/services/Prowlarr.vue
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<Generic :item="item">
|
||||||
|
<template #indicator>
|
||||||
|
<div class="notifs">
|
||||||
|
<strong v-if="warnings > 0" class="notif warnings" title="Warning">
|
||||||
|
{{ warnings }}
|
||||||
|
</strong>
|
||||||
|
<strong v-if="errors > 0" class="notif errors" title="Error">
|
||||||
|
{{ errors }}
|
||||||
|
</strong>
|
||||||
|
<strong
|
||||||
|
v-if="serverError"
|
||||||
|
class="notif errors"
|
||||||
|
title="Connection error to Prowlarr API, check url and apikey in config.yml"
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</strong>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Generic>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "Prowlarr",
|
||||||
|
mixins: [service],
|
||||||
|
props: {
|
||||||
|
item: Object,
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
|
data: () => {
|
||||||
|
return {
|
||||||
|
warnings: null,
|
||||||
|
errors: null,
|
||||||
|
serverError: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
this.fetchConfig();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchConfig: function () {
|
||||||
|
this.fetch(`/api/v1/health?apikey=${this.item.apikey}`)
|
||||||
|
.then((health) => {
|
||||||
|
this.warnings = 0;
|
||||||
|
this.errors = 0;
|
||||||
|
for (var i = 0; i < health.length; i++) {
|
||||||
|
if (health[i].type == "warning") {
|
||||||
|
this.warnings++;
|
||||||
|
} else if (health[i].type == "error") {
|
||||||
|
this.errors++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
this.serverError = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.notifs {
|
||||||
|
position: absolute;
|
||||||
|
color: white;
|
||||||
|
font-family: sans-serif;
|
||||||
|
top: 0.3em;
|
||||||
|
right: 0.5em;
|
||||||
|
|
||||||
|
.notif {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.2em 0.35em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
position: relative;
|
||||||
|
margin-left: 0.3em;
|
||||||
|
font-size: 0.8em;
|
||||||
|
|
||||||
|
&.warnings {
|
||||||
|
background-color: #d08d2e;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.errors {
|
||||||
|
background-color: #e51111;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,39 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<Generic :item="item">
|
||||||
<div class="card" :class="item.class">
|
<template #indicator>
|
||||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div v-if="item.logo" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.icon" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-4">{{ item.name }}</p>
|
|
||||||
<p class="subtitle is-6">{{ item.subtitle }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="notifs">
|
<div class="notifs">
|
||||||
<strong
|
<strong v-if="activity > 0" class="notif activity" title="Activity">
|
||||||
v-if="activity > 0"
|
{{ activity }}
|
||||||
class="notif activity"
|
</strong>
|
||||||
title="Activity"
|
<strong v-if="warnings > 0" class="notif warnings" title="Warning">
|
||||||
>{{ activity }}</strong
|
{{ warnings }}
|
||||||
>
|
</strong>
|
||||||
<strong
|
<strong v-if="errors > 0" class="notif errors" title="Error">
|
||||||
v-if="warnings > 0"
|
{{ errors }}
|
||||||
class="notif warnings"
|
</strong>
|
||||||
title="Warning"
|
|
||||||
>{{ warnings }}</strong
|
|
||||||
>
|
|
||||||
<strong v-if="errors > 0" class="notif errors" title="Error">{{
|
|
||||||
errors
|
|
||||||
}}</strong>
|
|
||||||
<strong
|
<strong
|
||||||
v-if="serverError"
|
v-if="serverError"
|
||||||
class="notif errors"
|
class="notif errors"
|
||||||
@ -41,22 +18,26 @@
|
|||||||
>?</strong
|
>?</strong
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
</Generic>
|
||||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
|
const V3_API = "/api/v3";
|
||||||
|
const LEGACY_API = "/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Radarr",
|
name: "Radarr",
|
||||||
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
activity: null,
|
activity: null,
|
||||||
@ -68,17 +49,14 @@ export default {
|
|||||||
created: function () {
|
created: function () {
|
||||||
this.fetchConfig();
|
this.fetchConfig();
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
apiPath() {
|
||||||
|
return this.item.legacyApi ? LEGACY_API : V3_API;
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchConfig: function () {
|
fetchConfig: function () {
|
||||||
fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
|
this.fetch(`${this.apiPath}/health?apikey=${this.item.apikey}`)
|
||||||
credentials: "include",
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status != 200) {
|
|
||||||
throw new Error(response.statusText);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((health) => {
|
.then((health) => {
|
||||||
this.warnings = 0;
|
this.warnings = 0;
|
||||||
this.errors = 0;
|
this.errors = 0;
|
||||||
@ -94,22 +72,19 @@ export default {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
this.serverError = true;
|
this.serverError = true;
|
||||||
});
|
});
|
||||||
fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
|
this.fetch(`${this.apiPath}/queue?apikey=${this.item.apikey}`)
|
||||||
credentials: "include",
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status != 200) {
|
|
||||||
throw new Error(response.statusText);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((queue) => {
|
.then((queue) => {
|
||||||
this.activity = 0;
|
this.activity = 0;
|
||||||
|
|
||||||
|
if (this.item.legacyApi) {
|
||||||
for (var i = 0; i < queue.length; i++) {
|
for (var i = 0; i < queue.length; i++) {
|
||||||
if (queue[i].movie) {
|
if (queue[i].movie) {
|
||||||
this.activity++;
|
this.activity++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.activity = queue.totalRecords;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@ -121,35 +96,30 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.media-left img {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
.notifs {
|
.notifs {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: white;
|
color: white;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
top: 0.3em;
|
top: 0.3em;
|
||||||
right: 0.5em;
|
right: 0.5em;
|
||||||
}
|
|
||||||
.notif {
|
.notif {
|
||||||
padding-right: 0.35em;
|
display: inline-block;
|
||||||
padding-left: 0.35em;
|
padding: 0.2em 0.35em;
|
||||||
padding-top: 0.2em;
|
|
||||||
padding-bottom: 0.2em;
|
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-left: 0.3em;
|
margin-left: 0.3em;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
&.activity {
|
||||||
.activity {
|
|
||||||
background-color: #4fb5d6;
|
background-color: #4fb5d6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warnings {
|
&.warnings {
|
||||||
background-color: #d08d2e;
|
background-color: #d08d2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.errors {
|
&.errors {
|
||||||
background-color: #e51111;
|
background-color: #e51111;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,62 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<Generic :item="item">
|
||||||
<div class="card" :class="item.class">
|
<template #indicator>
|
||||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="media">
|
|
||||||
<div v-if="item.logo" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div v-if="item.icon" class="media-left">
|
|
||||||
<figure class="image is-48x48">
|
|
||||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<div class="media-content">
|
|
||||||
<p class="title is-4">{{ item.name }}</p>
|
|
||||||
<p class="subtitle is-6">{{ item.subtitle }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="notifs">
|
<div class="notifs">
|
||||||
<strong
|
<strong v-if="activity > 0" class="notif activity" title="Activity">
|
||||||
v-if="activity > 0"
|
{{ activity }}
|
||||||
class="notif activity"
|
</strong>
|
||||||
title="Activity"
|
<strong v-if="warnings > 0" class="notif warnings" title="Warning">
|
||||||
>{{ activity }}</strong
|
{{ warnings }}
|
||||||
>
|
</strong>
|
||||||
<strong
|
<strong v-if="errors > 0" class="notif errors" title="Error">
|
||||||
v-if="warnings > 0"
|
{{ errors }}
|
||||||
class="notif warnings"
|
</strong>
|
||||||
title="Warning"
|
|
||||||
>{{ warnings }}</strong
|
|
||||||
>
|
|
||||||
<strong v-if="errors > 0" class="notif errors" title="Error">{{
|
|
||||||
errors
|
|
||||||
}}</strong>
|
|
||||||
<strong
|
<strong
|
||||||
v-if="serverError"
|
v-if="serverError"
|
||||||
class="notif errors"
|
class="notif errors"
|
||||||
title="Connection error to Sonarr API, check url and apikey in config.yml"
|
title="Connection error to Sonarr API, check url and apikey in config.yml"
|
||||||
>?</strong
|
|
||||||
>
|
>
|
||||||
|
?
|
||||||
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
</Generic>
|
||||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import service from "@/mixins/service.js";
|
||||||
|
import Generic from "./Generic.vue";
|
||||||
|
|
||||||
|
const V3_API = "/api/v3";
|
||||||
|
const LEGACY_API = "/api";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "Sonarr",
|
name: "Sonarr",
|
||||||
|
mixins: [service],
|
||||||
props: {
|
props: {
|
||||||
item: Object,
|
item: Object,
|
||||||
},
|
},
|
||||||
|
components: {
|
||||||
|
Generic,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
apiPath() {
|
||||||
|
return this.item.legacyApi ? LEGACY_API : V3_API;
|
||||||
|
},
|
||||||
|
},
|
||||||
data: () => {
|
data: () => {
|
||||||
return {
|
return {
|
||||||
activity: null,
|
activity: null,
|
||||||
@ -70,15 +57,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
fetchConfig: function () {
|
fetchConfig: function () {
|
||||||
fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
|
this.fetch(`${this.apiPath}/health?apikey=${this.item.apikey}`)
|
||||||
credentials: "include",
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status != 200) {
|
|
||||||
throw new Error(response.statusText);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((health) => {
|
.then((health) => {
|
||||||
this.warnings = 0;
|
this.warnings = 0;
|
||||||
this.errors = 0;
|
this.errors = 0;
|
||||||
@ -94,22 +73,18 @@ export default {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
this.serverError = true;
|
this.serverError = true;
|
||||||
});
|
});
|
||||||
fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
|
this.fetch(`${this.apiPath}/queue?apikey=${this.item.apikey}`)
|
||||||
credentials: "include",
|
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (response.status != 200) {
|
|
||||||
throw new Error(response.statusText);
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then((queue) => {
|
.then((queue) => {
|
||||||
this.activity = 0;
|
this.activity = 0;
|
||||||
|
if (this.item.legacyApi) {
|
||||||
for (var i = 0; i < queue.length; i++) {
|
for (var i = 0; i < queue.length; i++) {
|
||||||
if (queue[i].series) {
|
if (queue[i].series) {
|
||||||
this.activity++;
|
this.activity++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.activity = queue.totalRecords;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
@ -121,35 +96,32 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.media-left img {
|
|
||||||
max-height: 100%;
|
|
||||||
}
|
|
||||||
.notifs {
|
.notifs {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: white;
|
color: white;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
top: 0.3em;
|
top: 0.3em;
|
||||||
right: 0.5em;
|
right: 0.5em;
|
||||||
}
|
|
||||||
.notif {
|
.notif {
|
||||||
padding-right: 0.35em;
|
display: inline-block;
|
||||||
padding-left: 0.35em;
|
padding: 0.2em 0.35em;
|
||||||
padding-top: 0.2em;
|
|
||||||
padding-bottom: 0.2em;
|
|
||||||
border-radius: 0.25em;
|
border-radius: 0.25em;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-left: 0.3em;
|
margin-left: 0.3em;
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
}
|
|
||||||
.activity {
|
&.activity {
|
||||||
background-color: #4fb5d6;
|
background-color: #4fb5d6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warnings {
|
&.warnings {
|
||||||
background-color: #d08d2e;
|
background-color: #d08d2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.errors {
|
&.errors {
|
||||||
background-color: #e51111;
|
background-color: #e51111;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -31,7 +31,13 @@ export default {
|
|||||||
path = path.slice(1);
|
path = path.slice(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(`${this.endpoint}/${path}`, options).then((response) => {
|
let url = this.endpoint;
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
url = `${this.endpoint}/${path}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(url, options).then((response) => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error("Not 2xx response");
|
throw new Error("Not 2xx response");
|
||||||
}
|
}
|
||||||
|
122
yarn.lock
122
yarn.lock
@ -1173,10 +1173,10 @@
|
|||||||
lodash.kebabcase "^4.1.1"
|
lodash.kebabcase "^4.1.1"
|
||||||
svg-tags "^1.0.0"
|
svg-tags "^1.0.0"
|
||||||
|
|
||||||
"@vue/babel-preset-app@^4.5.13":
|
"@vue/babel-preset-app@^4.5.15":
|
||||||
version "4.5.13"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.5.13.tgz#cb475321e4c73f7f110dac29a48c2a9cb80afeb6"
|
resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.5.15.tgz#f6bc08f8f674e98a260004234cde18b966d72eb0"
|
||||||
integrity sha512-pM7CR3yXB6L8Gfn6EmX7FLNE3+V/15I3o33GkSNsWvgsMp6HVGXKkXgojrcfUUauyL1LZOdvTmu4enU2RePGHw==
|
integrity sha512-J+YttzvwRfV1BPczf8r3qCevznYk+jh531agVF+5EYlHF4Sgh/cGXTz9qkkiux3LQgvhEGXgmCteg1n38WuuKg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.11.0"
|
"@babel/core" "^7.11.0"
|
||||||
"@babel/helper-compilation-targets" "^7.9.6"
|
"@babel/helper-compilation-targets" "^7.9.6"
|
||||||
@ -1258,61 +1258,61 @@
|
|||||||
"@vue/babel-plugin-transform-vue-jsx" "^1.2.1"
|
"@vue/babel-plugin-transform-vue-jsx" "^1.2.1"
|
||||||
camelcase "^5.0.0"
|
camelcase "^5.0.0"
|
||||||
|
|
||||||
"@vue/cli-overlay@^4.5.13":
|
"@vue/cli-overlay@^4.5.15":
|
||||||
version "4.5.13"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.5.13.tgz#4f1fd2161be8f69d6cba8079f3f0d7dc4dee47a7"
|
resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.5.15.tgz#0700fd6bad39336d4189ba3ff7d25e638e818c9c"
|
||||||
integrity sha512-jhUIg3klgi5Cxhs8dnat5hi/W2tQJvsqCxR0u6hgfSob0ORODgUBlN+F/uwq7cKIe/pzedVUk1y07F13GQvPqg==
|
integrity sha512-0zI0kANAVmjFO2LWGUIzdGPMeE3+9k+KeRDXsUqB30YfRF7abjfiiRPq5BU9pOzlJbVdpRkisschBrvdJqDuDg==
|
||||||
|
|
||||||
"@vue/cli-plugin-babel@~4.5.0":
|
"@vue/cli-plugin-babel@~4.5.15":
|
||||||
version "4.5.13"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.13.tgz#a89c482edcc4ea1d135645cec502a7f5fd4c30e7"
|
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.15.tgz#ae4fb2ed54255fe3d84df381dab68509641179ed"
|
||||||
integrity sha512-ykvEAfD8PgGs+dGMGqr7l/nRmIS39NRzWLhMluPLTvDV1L+IxcoB73HNLGA/aENDpl8CuWrTE+1VgydcOhp+wg==
|
integrity sha512-hBLrwYfFkHldEe34op/YNgPhpOWI5n5DB2Qt9I/1Epeif4M4iFaayrgjvOR9AVM6WbD3Yx7WCFszYpWrQZpBzQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@babel/core" "^7.11.0"
|
"@babel/core" "^7.11.0"
|
||||||
"@vue/babel-preset-app" "^4.5.13"
|
"@vue/babel-preset-app" "^4.5.15"
|
||||||
"@vue/cli-shared-utils" "^4.5.13"
|
"@vue/cli-shared-utils" "^4.5.15"
|
||||||
babel-loader "^8.1.0"
|
babel-loader "^8.1.0"
|
||||||
cache-loader "^4.1.0"
|
cache-loader "^4.1.0"
|
||||||
thread-loader "^2.1.3"
|
thread-loader "^2.1.3"
|
||||||
webpack "^4.0.0"
|
webpack "^4.0.0"
|
||||||
|
|
||||||
"@vue/cli-plugin-eslint@~4.5.0":
|
"@vue/cli-plugin-eslint@~4.5.15":
|
||||||
version "4.5.13"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.13.tgz#8baf22d0d96d76720c7506646b96f4f62c05bdfa"
|
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.15.tgz#5781824a941f34c26336a67b1f6584a06c6a24ff"
|
||||||
integrity sha512-yc2uXX6aBiy3vEf5TwaueaDqQbdIXIhk0x0KzEtpPo23jBdLkpOSoU5NCgE06g/ZiGAcettpmBSv73Hfp4wHEw==
|
integrity sha512-/2Fl6wY/5bz3HD035oSnFRMsKNxDxU396KqBdpCQdwdvqk4mm6JAbXqihpcBRTNPeTO6w+LwGe6FE56PVbJdbg==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/cli-shared-utils" "^4.5.13"
|
"@vue/cli-shared-utils" "^4.5.15"
|
||||||
eslint-loader "^2.2.1"
|
eslint-loader "^2.2.1"
|
||||||
globby "^9.2.0"
|
globby "^9.2.0"
|
||||||
inquirer "^7.1.0"
|
inquirer "^7.1.0"
|
||||||
webpack "^4.0.0"
|
webpack "^4.0.0"
|
||||||
yorkie "^2.0.0"
|
yorkie "^2.0.0"
|
||||||
|
|
||||||
"@vue/cli-plugin-pwa@~4.5.0":
|
"@vue/cli-plugin-pwa@~4.5.15":
|
||||||
version "4.5.13"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.13.tgz#a800639814b6f62a38f04198c340cfaee7295c3f"
|
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.15.tgz#eb800c418d96b496deec9d063a1798fe6e9c2db8"
|
||||||
integrity sha512-uU5pp94VU0YscfKq/mNRsKOdxG+CTqVlZWaYkRc+HCcwkJ/m/CnxgaEqQFr0QpHC8zmlX4gILO1RVYygJoR9tw==
|
integrity sha512-yQzsspaIkjeQyN6btF8ATgbJFU023q1HC8uUpmiBa4QE9EyBlR8fSrKFhcJ0EmT6KnU7PMwlnOJ/OqjguFnufA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/cli-shared-utils" "^4.5.13"
|
"@vue/cli-shared-utils" "^4.5.15"
|
||||||
webpack "^4.0.0"
|
webpack "^4.0.0"
|
||||||
workbox-webpack-plugin "^4.3.1"
|
workbox-webpack-plugin "^4.3.1"
|
||||||
|
|
||||||
"@vue/cli-plugin-router@^4.5.13":
|
"@vue/cli-plugin-router@^4.5.15":
|
||||||
version "4.5.13"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.13.tgz#0b67c8898a2bf132941919a2a2e5f3aacbd9ffbe"
|
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.15.tgz#1e75c8c89df42c694f143b9f1028de3cf5d61e1e"
|
||||||
integrity sha512-tgtMDjchB/M1z8BcfV4jSOY9fZSMDTPgF9lsJIiqBWMxvBIsk9uIZHxp62DibYME4CCKb/nNK61XHaikFp+83w==
|
integrity sha512-q7Y6kP9b3k55Ca2j59xJ7XPA6x+iSRB+N4ac0ZbcL1TbInVQ4j5wCzyE+uqid40hLy4fUdlpl4X9fHJEwuVxPA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/cli-shared-utils" "^4.5.13"
|
"@vue/cli-shared-utils" "^4.5.15"
|
||||||
|
|
||||||
"@vue/cli-plugin-vuex@^4.5.13":
|
"@vue/cli-plugin-vuex@^4.5.15":
|
||||||
version "4.5.13"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.13.tgz#98646d8bc1e69cf6c6a6cba2fed3eace0356c360"
|
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.15.tgz#466c1f02777d02fef53a9bb49a36cc3a3bcfec4e"
|
||||||
integrity sha512-I1S9wZC7iI0Wn8kw8Zh+A2Qkf6s1M6vTGBkx8boXjuzfwEEyEHRxadsVCecZc8Mkpydo0nykj+MyYF96TKFuVA==
|
integrity sha512-fqap+4HN+w+InDxlA3hZTOGE0tzBTgXhKLoDydhywqgmhQ1D9JA6Feh94ze6tG8DsWX58/ujYUqA8jAz17FJtg==
|
||||||
|
|
||||||
"@vue/cli-service@~4.5.0":
|
"@vue/cli-service@~4.5.15":
|
||||||
version "4.5.13"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.5.13.tgz#a09e684a801684b6e24e5414ad30650970eec9ed"
|
resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.5.15.tgz#0e9a186d51550027d0e68e95042077eb4d115b45"
|
||||||
integrity sha512-CKAZN4iokMMsaUyJRU22oUAz3oS/X9sVBSKAF2/shFBV5xh3jqAlKl8OXZYz4cXGFLA6djNuYrniuLAo7Ku97A==
|
integrity sha512-sFWnLYVCn4zRfu45IcsIE9eXM0YpDV3S11vlM2/DVbIPAGoYo5ySpSof6aHcIvkeGsIsrHFpPHzNvDZ/efs7jA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@intervolga/optimize-cssnano-plugin" "^1.0.5"
|
"@intervolga/optimize-cssnano-plugin" "^1.0.5"
|
||||||
"@soda/friendly-errors-webpack-plugin" "^1.7.1"
|
"@soda/friendly-errors-webpack-plugin" "^1.7.1"
|
||||||
@ -1320,10 +1320,10 @@
|
|||||||
"@types/minimist" "^1.2.0"
|
"@types/minimist" "^1.2.0"
|
||||||
"@types/webpack" "^4.0.0"
|
"@types/webpack" "^4.0.0"
|
||||||
"@types/webpack-dev-server" "^3.11.0"
|
"@types/webpack-dev-server" "^3.11.0"
|
||||||
"@vue/cli-overlay" "^4.5.13"
|
"@vue/cli-overlay" "^4.5.15"
|
||||||
"@vue/cli-plugin-router" "^4.5.13"
|
"@vue/cli-plugin-router" "^4.5.15"
|
||||||
"@vue/cli-plugin-vuex" "^4.5.13"
|
"@vue/cli-plugin-vuex" "^4.5.15"
|
||||||
"@vue/cli-shared-utils" "^4.5.13"
|
"@vue/cli-shared-utils" "^4.5.15"
|
||||||
"@vue/component-compiler-utils" "^3.1.2"
|
"@vue/component-compiler-utils" "^3.1.2"
|
||||||
"@vue/preload-webpack-plugin" "^1.1.0"
|
"@vue/preload-webpack-plugin" "^1.1.0"
|
||||||
"@vue/web-component-wrapper" "^1.2.0"
|
"@vue/web-component-wrapper" "^1.2.0"
|
||||||
@ -1372,10 +1372,10 @@
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vue-loader-v16 "npm:vue-loader@^16.1.0"
|
vue-loader-v16 "npm:vue-loader@^16.1.0"
|
||||||
|
|
||||||
"@vue/cli-shared-utils@^4.5.13":
|
"@vue/cli-shared-utils@^4.5.15":
|
||||||
version "4.5.13"
|
version "4.5.15"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.13.tgz#acd40f31b4790f1634292bdaa5fca95dc1e0ff50"
|
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.15.tgz#dba3858165dbe3465755f256a4890e69084532d6"
|
||||||
integrity sha512-HpnOrkLg42RFUsQGMJv26oTG3J3FmKtO2WSRhKIIL+1ok3w9OjGCtA3nMMXN27f9eX14TqO64M36DaiSZ1fSiw==
|
integrity sha512-SKaej9hHzzjKSOw1NlFmc6BSE0vcqUQMQiv1cxQ2DhVyy4QxZXBmzmiLBUBe+hYZZs1neXW7n//udeN9bCAY+Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/joi" "^15.0.1"
|
"@hapi/joi" "^15.0.1"
|
||||||
chalk "^2.4.2"
|
chalk "^2.4.2"
|
||||||
@ -2339,9 +2339,9 @@ caniuse-api@^3.0.0:
|
|||||||
lodash.uniq "^4.5.0"
|
lodash.uniq "^4.5.0"
|
||||||
|
|
||||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219:
|
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219:
|
||||||
version "1.0.30001245"
|
version "1.0.30001311"
|
||||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001245.tgz#45b941bbd833cb0fa53861ff2bae746b3c6ca5d4"
|
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001311.tgz"
|
||||||
integrity sha512-768fM9j1PKXpOCKws6eTo3RHmvTUsG9UrpT4WoREFeZgJBTi4/X9g565azS/rVUGtqb8nt7FjLeF5u4kukERnA==
|
integrity sha512-mleTFtFKfykEeW34EyfhGIFjGCqzhh38Y0LhdQ9aWF+HorZTtdgKV/1hEE0NlFkG2ubvisPV6l400tlbPys98A==
|
||||||
|
|
||||||
case-sensitive-paths-webpack-plugin@^2.3.0:
|
case-sensitive-paths-webpack-plugin@^2.3.0:
|
||||||
version "2.4.0"
|
version "2.4.0"
|
||||||
@ -2773,10 +2773,10 @@ core-js@^2.4.0:
|
|||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
|
||||||
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
||||||
|
|
||||||
core-js@^3.17.3:
|
core-js@^3.21.1:
|
||||||
version "3.17.3"
|
version "3.21.1"
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.17.3.tgz#8e8bd20e91df9951e903cabe91f9af4a0895bc1e"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94"
|
||||||
integrity sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw==
|
integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==
|
||||||
|
|
||||||
core-js@^3.6.5:
|
core-js@^3.6.5:
|
||||||
version "3.15.2"
|
version "3.15.2"
|
||||||
@ -3999,9 +3999,9 @@ flush-write-stream@^1.0.0:
|
|||||||
readable-stream "^2.3.6"
|
readable-stream "^2.3.6"
|
||||||
|
|
||||||
follow-redirects@^1.0.0:
|
follow-redirects@^1.0.0:
|
||||||
version "1.14.1"
|
version "1.14.8"
|
||||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
|
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
|
||||||
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
|
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
|
||||||
|
|
||||||
for-in@^1.0.2:
|
for-in@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
@ -5590,9 +5590,9 @@ minimatch@^3.0.4:
|
|||||||
brace-expansion "^1.1.7"
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
minimist@^1.2.0, minimist@^1.2.5:
|
minimist@^1.2.0, minimist@^1.2.5:
|
||||||
version "1.2.5"
|
version "1.2.6"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||||
|
|
||||||
minipass@^3.1.1:
|
minipass@^3.1.1:
|
||||||
version "3.1.3"
|
version "3.1.3"
|
||||||
@ -8227,9 +8227,9 @@ url-loader@^2.2.0:
|
|||||||
schema-utils "^2.5.0"
|
schema-utils "^2.5.0"
|
||||||
|
|
||||||
url-parse@^1.4.3, url-parse@^1.5.1:
|
url-parse@^1.4.3, url-parse@^1.5.1:
|
||||||
version "1.5.3"
|
version "1.5.10"
|
||||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862"
|
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||||
integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==
|
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
querystringify "^2.1.1"
|
querystringify "^2.1.1"
|
||||||
requires-port "^1.0.0"
|
requires-port "^1.0.0"
|
||||||
|
Loading…
Reference in New Issue
Block a user