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
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node.js CI
|
||||
name: Integration
|
||||
|
||||
on:
|
||||
push:
|
||||
|
@ -10,7 +10,7 @@ COPY . .
|
||||
RUN yarn build
|
||||
|
||||
# production stage
|
||||
FROM alpine:3.11
|
||||
FROM alpine:3.15
|
||||
|
||||
ENV USER darkhttpd
|
||||
ENV GROUP darkhttpd
|
||||
|
@ -56,7 +56,7 @@
|
||||
- Search
|
||||
- Grouping
|
||||
- Theme customization
|
||||
- Offline heath check
|
||||
- Offline health check
|
||||
- keyboard shortcuts:
|
||||
- `/` Start searching.
|
||||
- `Escape` Stop searching.
|
||||
|
@ -5,7 +5,7 @@ Title, icons, links, colors, and services can be configured in the `config.yml`
|
||||
```yaml
|
||||
---
|
||||
# Homepage configuration
|
||||
# See https://fontawesome.com/icons for icons options
|
||||
# See https://fontawesome.com/v5/search for icons options
|
||||
|
||||
# Optional: Use external configuration 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
|
||||
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.
|
||||
|
||||
# Set the default layout and color scheme
|
||||
defaults:
|
||||
layout: columns # Either 'columns', or 'list'
|
||||
colorTheme: auto # One of 'auto', 'light', or 'dark'
|
||||
|
||||
# Optional theming
|
||||
theme: default # 'default' or one of the themes available in 'src/assets/themes'.
|
||||
|
||||
|
@ -1,8 +1,22 @@
|
||||
# Custom Services
|
||||
|
||||
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
|
||||
within Homer.
|
||||
configuration and, where applicable, an apikey. Note that config.yml is exposed at /assets/config.yml via HTTP and any
|
||||
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.
|
||||
|
||||
@ -18,8 +32,6 @@ If you experiencing any issue, please have a look to the [troubleshooting](troub
|
||||
type: "<type>"
|
||||
```
|
||||
|
||||
⚠️🚧 `endpoint` & `useCredentials` new options are not yet supported by all custom services (but will be very soon).
|
||||
|
||||
## PiHole
|
||||
|
||||
Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard.
|
||||
@ -65,18 +77,28 @@ Two lines are needed in the config.yml :
|
||||
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.
|
||||
|
||||
## 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 :
|
||||
|
||||
```yaml
|
||||
type: "Radarr" or "Sonarr"
|
||||
type: "Lidarr", "Prowlarr", "Radarr" or "Sonarr"
|
||||
apikey: "01234deb70424befb1f4ef6a23456789"
|
||||
```
|
||||
|
||||
The url must be the root url of Radarr/Sonarr application.
|
||||
The Radarr/Sonarr API key can be found in Settings > General. It is needed to access the API.
|
||||
The url must be the root url of Lidarr, Prowlarr, Radarr or Sonarr application.
|
||||
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
|
||||
|
||||
@ -124,3 +146,36 @@ For AdGuard Home you need to set the type to AdGuard, if you have somes issues a
|
||||
target: "_blank"
|
||||
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"
|
||||
logo: "/assets/vscode.png"
|
||||
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/"
|
||||
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.
|
||||
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).
|
||||
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
|
||||
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:
|
||||
|
||||
* 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.
|
||||
* 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.
|
||||
* 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 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",
|
||||
"version": "21.09.1",
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
@ -10,17 +9,17 @@
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"bulma": "^0.9.3",
|
||||
"core-js": "^3.17.3",
|
||||
"core-js": "^3.21.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lodash.merge": "^4.6.2",
|
||||
"register-service-worker": "^1.7.2",
|
||||
"vue": "^2.6.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-pwa": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/cli-plugin-babel": "~4.5.15",
|
||||
"@vue/cli-plugin-eslint": "~4.5.15",
|
||||
"@vue/cli-plugin-pwa": "~4.5.15",
|
||||
"@vue/cli-service": "~4.5.15",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
@ -31,5 +30,6 @@
|
||||
"sass": "^1.26.5",
|
||||
"sass-loader": "^8.0.2",
|
||||
"vue-template-compiler": "^2.6.12"
|
||||
}
|
||||
},
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
# Homepage configuration
|
||||
# See https://fontawesome.com/icons for icons options
|
||||
# See https://fontawesome.com/v5/search for icons options
|
||||
|
||||
title: "Demo dashboard"
|
||||
subtitle: "Homer"
|
||||
@ -58,11 +58,11 @@ links:
|
||||
- name: "Wiki"
|
||||
icon: "fas fa-book"
|
||||
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
|
||||
# see url field and assets/additionnal-page.yml.dist used in this example:
|
||||
# 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/additional-page.yml.dist used in this example:
|
||||
- name: "another page!"
|
||||
icon: "fas fa-file-alt"
|
||||
url: "#additionnal-page"
|
||||
url: "#additional-page"
|
||||
|
||||
# Services
|
||||
# First level array represent a group.
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
# Homepage configuration
|
||||
# See https://fontawesome.com/icons for icons options
|
||||
# See https://fontawesome.com/v5/search for icons options
|
||||
|
||||
title: "Hello beautiful!"
|
||||
subtitle: "App dashboard"
|
||||
|
17
src/App.vue
17
src/App.vue
@ -30,18 +30,22 @@
|
||||
:links="config.links"
|
||||
@navbar-toggle="showMenu = !showMenu"
|
||||
>
|
||||
<DarkMode @updated="isDark = $event" />
|
||||
<DarkMode
|
||||
@updated="isDark = $event"
|
||||
:defaultValue="this.config.defaults.colorTheme"
|
||||
/>
|
||||
|
||||
<SettingToggle
|
||||
@updated="vlayout = $event"
|
||||
name="vlayout"
|
||||
icon="fa-list"
|
||||
iconAlt="fa-columns"
|
||||
:defaultValue="this.config.defaults.layout == 'columns'"
|
||||
/>
|
||||
|
||||
<SearchInput
|
||||
class="navbar-item is-inline-block-mobile"
|
||||
:hotkey=searchHotkey()
|
||||
:hotkey="searchHotkey()"
|
||||
@input="filterServices"
|
||||
@search-focus="showMenu = true"
|
||||
@search-open="navigateToFirstService"
|
||||
@ -56,6 +60,9 @@
|
||||
v-if="config.connectivityCheck"
|
||||
@network-status-update="offline = $event"
|
||||
/>
|
||||
|
||||
<GetStarted v-if="loaded && !services" />
|
||||
|
||||
<div v-if="!offline">
|
||||
<!-- Optional messages -->
|
||||
<Message :item="config.message" />
|
||||
@ -130,6 +137,7 @@ const jsyaml = require("js-yaml");
|
||||
const merge = require("lodash.merge");
|
||||
|
||||
import Navbar from "./components/Navbar.vue";
|
||||
import GetStarted from "./components/GetStarted.vue";
|
||||
import ConnectivityChecker from "./components/ConnectivityChecker.vue";
|
||||
import Service from "./components/Service.vue";
|
||||
import Message from "./components/Message.vue";
|
||||
@ -144,6 +152,7 @@ export default {
|
||||
name: "App",
|
||||
components: {
|
||||
Navbar,
|
||||
GetStarted,
|
||||
ConnectivityChecker,
|
||||
Service,
|
||||
Message,
|
||||
@ -154,6 +163,7 @@ export default {
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
loaded: false,
|
||||
config: null,
|
||||
services: null,
|
||||
offline: false,
|
||||
@ -166,6 +176,7 @@ export default {
|
||||
created: async function () {
|
||||
this.buildDashboard();
|
||||
window.onhashchange = this.buildDashboard;
|
||||
this.loaded = true;
|
||||
},
|
||||
methods: {
|
||||
searchHotkey() {
|
||||
@ -193,6 +204,7 @@ export default {
|
||||
}
|
||||
this.config = merge(defaults, config);
|
||||
this.services = this.config.services;
|
||||
|
||||
document.title =
|
||||
this.config.documentTitle ||
|
||||
`${this.config.title} | ${this.config.subtitle}`;
|
||||
@ -211,6 +223,7 @@ export default {
|
||||
window.location.href = response.url;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
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
|
||||
connectivityCheck: true
|
||||
|
||||
defaults:
|
||||
# columns, list
|
||||
layout: columns
|
||||
# auto, light, dark
|
||||
colorTheme: auto
|
||||
|
||||
theme: default
|
||||
colors:
|
||||
light:
|
||||
|
@ -15,6 +15,9 @@
|
||||
<script>
|
||||
export default {
|
||||
name: "Darkmode",
|
||||
props: {
|
||||
defaultValue: String,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
isDark: null,
|
||||
@ -30,6 +33,17 @@ export default {
|
||||
if ("overrideDark" in localStorage) {
|
||||
// Light theme is 1 and Dark theme is 2
|
||||
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.$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,
|
||||
hotkey: {
|
||||
type: String,
|
||||
default: "/"
|
||||
}
|
||||
default: "/",
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this._keyListener = function (event) {
|
||||
|
@ -12,6 +12,7 @@ export default {
|
||||
name: String,
|
||||
icon: String,
|
||||
iconAlt: String,
|
||||
defaultValue: Boolean,
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
@ -24,6 +25,8 @@ export default {
|
||||
|
||||
if (this.name in localStorage) {
|
||||
this.value = JSON.parse(localStorage[this.name]);
|
||||
} else {
|
||||
this.value = this.defaultValue;
|
||||
}
|
||||
|
||||
this.$emit("updated", this.value);
|
||||
|
@ -76,9 +76,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.media-left img {
|
||||
max-height: 100%;
|
||||
}
|
||||
.status {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-title);
|
||||
|
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,47 +1,33 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card" :class="item.class">
|
||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div v-if="item.logo" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
||||
</figure>
|
||||
</div>
|
||||
<div v-if="item.icon" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">
|
||||
<template v-if="item.subtitle">
|
||||
{{ item.subtitle }}
|
||||
</template>
|
||||
<template v-else-if="meal"> Today: {{ meal.name }} </template>
|
||||
<template v-else-if="stats">
|
||||
happily keeping {{ stats.totalRecipes }} recipes organized
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Generic :item="item">
|
||||
<template #content>
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">
|
||||
<template v-if="item.subtitle">
|
||||
{{ item.subtitle }}
|
||||
</template>
|
||||
<template v-else-if="meal"> Today: {{ meal.name }} </template>
|
||||
<template v-else-if="stats">
|
||||
happily keeping {{ stats.totalRecipes }} recipes organized
|
||||
</template>
|
||||
</p>
|
||||
</template>
|
||||
</Generic>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import service from "@/mixins/service.js";
|
||||
import Generic from "./Generic.vue";
|
||||
|
||||
export default {
|
||||
name: "Mealie",
|
||||
mixins: [service],
|
||||
props: {
|
||||
item: Object,
|
||||
},
|
||||
components: {
|
||||
Generic,
|
||||
},
|
||||
data: () => ({
|
||||
stats: null,
|
||||
meal: null,
|
||||
@ -51,44 +37,20 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
fetchStatus: async function () {
|
||||
if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing
|
||||
this.meal = await fetch(`${this.item.url}/api/meal-plans/today/`, {
|
||||
headers: {
|
||||
Authorization: "Bearer " + this.item.apikey,
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error("Not 2xx response");
|
||||
} else {
|
||||
if (response != null) {
|
||||
return response.json();
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
this.stats = await fetch(`${this.item.url}/api/debug/statistics/`, {
|
||||
headers: {
|
||||
Authorization: "Bearer " + this.item.apikey,
|
||||
Accept: "application/json",
|
||||
},
|
||||
})
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error("Not 2xx response");
|
||||
} else {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
const headers = {
|
||||
Authorization: "Bearer " + this.item.apikey,
|
||||
Accept: "application/json",
|
||||
};
|
||||
|
||||
if (this.item.subtitle != null) return;
|
||||
|
||||
this.meal = await this.fetch("/api/meal-plans/today/", { headers }).catch(
|
||||
(e) => console.log(e)
|
||||
);
|
||||
this.stats = await this.fetch("/api/debug/statistics/", {
|
||||
headers,
|
||||
}).catch((e) => console.log(e));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.media-left img {
|
||||
max-height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,65 +1,49 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card" :class="item.class">
|
||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div v-if="item.logo" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
||||
</figure>
|
||||
</div>
|
||||
<div v-if="item.icon" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">{{ item.subtitle }}</p>
|
||||
</div>
|
||||
<div class="notifs">
|
||||
<strong
|
||||
v-if="config !== null && config.system.news.unread > 0"
|
||||
class="notif news"
|
||||
title="News"
|
||||
>{{ config.system.news.unread }}</strong
|
||||
>
|
||||
<strong
|
||||
v-if="config !== null && config.main.logs.numWarnings > 0"
|
||||
class="notif warnings"
|
||||
title="Warning"
|
||||
>{{ config.main.logs.numWarnings }}</strong
|
||||
>
|
||||
<strong
|
||||
v-if="config !== null && config.main.logs.numErrors > 0"
|
||||
class="notif errors"
|
||||
title="Error"
|
||||
>{{ config.main.logs.numErrors }}</strong
|
||||
>
|
||||
<strong
|
||||
v-if="serverError"
|
||||
class="notif errors"
|
||||
title="Connection error to Medusa API, check url and apikey in config.yml"
|
||||
>?</strong
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Generic :item="item">
|
||||
<template #indicator>
|
||||
<div class="notifs">
|
||||
<strong
|
||||
v-if="config !== null && config.system.news.unread > 0"
|
||||
class="notif news"
|
||||
title="News"
|
||||
>{{ config.system.news.unread }}</strong
|
||||
>
|
||||
<strong
|
||||
v-if="config !== null && config.main.logs.numWarnings > 0"
|
||||
class="notif warnings"
|
||||
title="Warning"
|
||||
>{{ config.main.logs.numWarnings }}</strong
|
||||
>
|
||||
<strong
|
||||
v-if="config !== null && config.main.logs.numErrors > 0"
|
||||
class="notif errors"
|
||||
title="Error"
|
||||
>{{ config.main.logs.numErrors }}</strong
|
||||
>
|
||||
<strong
|
||||
v-if="serverError"
|
||||
class="notif errors"
|
||||
title="Connection error to Medusa API, check url and apikey in config.yml"
|
||||
>?</strong
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</Generic>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import service from "@/mixins/service.js";
|
||||
import Generic from "./Generic.vue";
|
||||
|
||||
export default {
|
||||
name: "Medusa",
|
||||
mixins: [service],
|
||||
props: {
|
||||
item: Object,
|
||||
},
|
||||
components: {
|
||||
Generic,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
config: null,
|
||||
@ -71,16 +55,9 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
fetchConfig: function () {
|
||||
fetch(`${this.item.url}/api/v2/config`, {
|
||||
credentials: "include",
|
||||
headers: { "X-Api-Key": `${this.item.apikey}` },
|
||||
this.fetch("/api/v2/config", {
|
||||
headers: { "X-Api-Key": this.item.apikey },
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status != 200) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then((conf) => {
|
||||
this.config = conf;
|
||||
})
|
||||
@ -94,35 +71,32 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.media-left img {
|
||||
max-height: 100%;
|
||||
}
|
||||
.notifs {
|
||||
position: absolute;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
top: 0.3em;
|
||||
right: 0.5em;
|
||||
}
|
||||
.notif {
|
||||
padding-right: 0.35em;
|
||||
padding-left: 0.35em;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
border-radius: 0.25em;
|
||||
position: relative;
|
||||
margin-left: 0.3em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.news {
|
||||
background-color: #777777;
|
||||
}
|
||||
.notif {
|
||||
padding-right: 0.35em;
|
||||
padding-left: 0.35em;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
border-radius: 0.25em;
|
||||
position: relative;
|
||||
margin-left: 0.3em;
|
||||
font-size: 0.8em;
|
||||
&.news {
|
||||
background-color: #777777;
|
||||
}
|
||||
|
||||
.warnings {
|
||||
background-color: #d08d2e;
|
||||
}
|
||||
&.warnings {
|
||||
background-color: #d08d2e;
|
||||
}
|
||||
|
||||
.errors {
|
||||
background-color: #e51111;
|
||||
&.errors {
|
||||
background-color: #e51111;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,46 +1,32 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card" :class="item.class">
|
||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div v-if="item.logo" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
||||
</figure>
|
||||
</div>
|
||||
<div v-if="item.icon" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">
|
||||
<template v-if="item.subtitle">
|
||||
{{ item.subtitle }}
|
||||
</template>
|
||||
<template v-else-if="api">
|
||||
happily storing {{ api.count }} documents
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Generic :item="item">
|
||||
<template #content>
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">
|
||||
<template v-if="item.subtitle">
|
||||
{{ item.subtitle }}
|
||||
</template>
|
||||
<template v-else-if="api">
|
||||
happily storing {{ api.count }} documents
|
||||
</template>
|
||||
</p>
|
||||
</template>
|
||||
</Generic>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import service from "@/mixins/service.js";
|
||||
import Generic from "./Generic.vue";
|
||||
|
||||
export default {
|
||||
name: "Paperless",
|
||||
mixins: [service],
|
||||
props: {
|
||||
item: Object,
|
||||
},
|
||||
components: {
|
||||
Generic,
|
||||
},
|
||||
data: () => ({
|
||||
api: null,
|
||||
}),
|
||||
@ -49,36 +35,21 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
fetchStatus: async function () {
|
||||
if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing
|
||||
var apikey = this.item.apikey;
|
||||
if (this.item.subtitle != null) return;
|
||||
|
||||
const apikey = this.item.apikey;
|
||||
if (!apikey) {
|
||||
console.error(
|
||||
"apikey is not present in config.yml for the paperless entry!"
|
||||
);
|
||||
return;
|
||||
}
|
||||
const url = `${this.item.url}/api/documents/`;
|
||||
this.api = await fetch(url, {
|
||||
credentials: "include",
|
||||
this.api = await this.fetch("/api/documents/", {
|
||||
headers: {
|
||||
Authorization: "Token " + this.item.apikey,
|
||||
},
|
||||
})
|
||||
.then(function (response) {
|
||||
if (!response.ok) {
|
||||
throw new Error("Not 2xx response");
|
||||
} else {
|
||||
return response.json();
|
||||
}
|
||||
})
|
||||
.catch((e) => console.log(e));
|
||||
}).catch((e) => console.log(e));
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.media-left img {
|
||||
max-height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,59 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card" :class="item.class">
|
||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div v-if="item.logo" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
||||
</figure>
|
||||
</div>
|
||||
<div v-if="item.icon" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">
|
||||
<template v-if="item.subtitle">
|
||||
{{ item.subtitle }}
|
||||
</template>
|
||||
<template v-else-if="api">
|
||||
{{ percentage }}% blocked
|
||||
</template>
|
||||
</p>
|
||||
</div>
|
||||
<div v-if="api" class="status" :class="api.status">
|
||||
{{ api.status }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Generic :item="item">
|
||||
<template #content>
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">
|
||||
<template v-if="item.subtitle">
|
||||
{{ item.subtitle }}
|
||||
</template>
|
||||
<template v-else-if="percentage">
|
||||
{{ percentage }}% blocked
|
||||
</template>
|
||||
</p>
|
||||
</template>
|
||||
<template #indicator>
|
||||
<div v-if="status" class="status" :class="status">
|
||||
{{ status }}
|
||||
</div>
|
||||
</template>
|
||||
</Generic>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import service from "@/mixins/service.js";
|
||||
import Generic from "./Generic.vue";
|
||||
|
||||
export default {
|
||||
name: "PiHole",
|
||||
mixins: [service],
|
||||
props: {
|
||||
item: Object,
|
||||
},
|
||||
components: {
|
||||
Generic,
|
||||
},
|
||||
data: () => ({
|
||||
api: {
|
||||
status: "",
|
||||
ads_percentage_today: 0,
|
||||
},
|
||||
status: "",
|
||||
ads_percentage_today: 0,
|
||||
}),
|
||||
computed: {
|
||||
percentage: function () {
|
||||
if (this.api) {
|
||||
return this.api.ads_percentage_today.toFixed(1);
|
||||
if (this.ads_percentage_today) {
|
||||
return this.ads_percentage_today.toFixed(1);
|
||||
}
|
||||
return "";
|
||||
},
|
||||
@ -63,21 +49,16 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
fetchStatus: async function () {
|
||||
const url = `${this.item.url}/api.php`;
|
||||
this.api = await fetch(url, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.catch((e) => console.log(e));
|
||||
const result = await this.fetch("/api.php").catch((e) => console.log(e));
|
||||
|
||||
this.status = result.status;
|
||||
this.ads_percentage_today = result.ads_percentage_today;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.media-left img {
|
||||
max-height: 100%;
|
||||
}
|
||||
.status {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-title);
|
||||
|
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,62 +1,43 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card" :class="item.class">
|
||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div v-if="item.logo" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
||||
</figure>
|
||||
</div>
|
||||
<div v-if="item.icon" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">{{ item.subtitle }}</p>
|
||||
</div>
|
||||
<div class="notifs">
|
||||
<strong
|
||||
v-if="activity > 0"
|
||||
class="notif activity"
|
||||
title="Activity"
|
||||
>{{ activity }}</strong
|
||||
>
|
||||
<strong
|
||||
v-if="warnings > 0"
|
||||
class="notif warnings"
|
||||
title="Warning"
|
||||
>{{ warnings }}</strong
|
||||
>
|
||||
<strong v-if="errors > 0" class="notif errors" title="Error">{{
|
||||
errors
|
||||
}}</strong>
|
||||
<strong
|
||||
v-if="serverError"
|
||||
class="notif errors"
|
||||
title="Connection error to Radarr API, check url and apikey in config.yml"
|
||||
>?</strong
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Generic :item="item">
|
||||
<template #indicator>
|
||||
<div class="notifs">
|
||||
<strong v-if="activity > 0" class="notif activity" title="Activity">
|
||||
{{ activity }}
|
||||
</strong>
|
||||
<strong v-if="warnings > 0" class="notif warnings" title="Warning">
|
||||
{{ warnings }}
|
||||
</strong>
|
||||
<strong v-if="errors > 0" class="notif errors" title="Error">
|
||||
{{ errors }}
|
||||
</strong>
|
||||
<strong
|
||||
v-if="serverError"
|
||||
class="notif errors"
|
||||
title="Connection error to Radarr API, check url and apikey in config.yml"
|
||||
>?</strong
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
</Generic>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import service from "@/mixins/service.js";
|
||||
import Generic from "./Generic.vue";
|
||||
|
||||
const V3_API = "/api/v3";
|
||||
const LEGACY_API = "/api";
|
||||
|
||||
export default {
|
||||
name: "Radarr",
|
||||
mixins: [service],
|
||||
props: {
|
||||
item: Object,
|
||||
},
|
||||
components: {
|
||||
Generic,
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
activity: null,
|
||||
@ -68,17 +49,14 @@ export default {
|
||||
created: function () {
|
||||
this.fetchConfig();
|
||||
},
|
||||
computed: {
|
||||
apiPath() {
|
||||
return this.item.legacyApi ? LEGACY_API : V3_API;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
fetchConfig: function () {
|
||||
fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status != 200) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
this.fetch(`${this.apiPath}/health?apikey=${this.item.apikey}`)
|
||||
.then((health) => {
|
||||
this.warnings = 0;
|
||||
this.errors = 0;
|
||||
@ -94,21 +72,18 @@ export default {
|
||||
console.error(e);
|
||||
this.serverError = true;
|
||||
});
|
||||
fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status != 200) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
this.fetch(`${this.apiPath}/queue?apikey=${this.item.apikey}`)
|
||||
.then((queue) => {
|
||||
this.activity = 0;
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
if (queue[i].movie) {
|
||||
this.activity++;
|
||||
|
||||
if (this.item.legacyApi) {
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
if (queue[i].movie) {
|
||||
this.activity++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.activity = queue.totalRecords;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
@ -121,35 +96,30 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.media-left img {
|
||||
max-height: 100%;
|
||||
}
|
||||
.notifs {
|
||||
position: absolute;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
top: 0.3em;
|
||||
right: 0.5em;
|
||||
}
|
||||
.notif {
|
||||
padding-right: 0.35em;
|
||||
padding-left: 0.35em;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
border-radius: 0.25em;
|
||||
position: relative;
|
||||
margin-left: 0.3em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.activity {
|
||||
background-color: #4fb5d6;
|
||||
}
|
||||
.notif {
|
||||
display: inline-block;
|
||||
padding: 0.2em 0.35em;
|
||||
border-radius: 0.25em;
|
||||
position: relative;
|
||||
margin-left: 0.3em;
|
||||
font-size: 0.8em;
|
||||
&.activity {
|
||||
background-color: #4fb5d6;
|
||||
}
|
||||
|
||||
.warnings {
|
||||
background-color: #d08d2e;
|
||||
}
|
||||
&.warnings {
|
||||
background-color: #d08d2e;
|
||||
}
|
||||
|
||||
.errors {
|
||||
background-color: #e51111;
|
||||
&.errors {
|
||||
background-color: #e51111;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,62 +1,49 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="card" :class="item.class">
|
||||
<a :href="item.url" :target="item.target" rel="noreferrer">
|
||||
<div class="card-content">
|
||||
<div class="media">
|
||||
<div v-if="item.logo" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img :src="item.logo" :alt="`${item.name} logo`" />
|
||||
</figure>
|
||||
</div>
|
||||
<div v-if="item.icon" class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<p class="title is-4">{{ item.name }}</p>
|
||||
<p class="subtitle is-6">{{ item.subtitle }}</p>
|
||||
</div>
|
||||
<div class="notifs">
|
||||
<strong
|
||||
v-if="activity > 0"
|
||||
class="notif activity"
|
||||
title="Activity"
|
||||
>{{ activity }}</strong
|
||||
>
|
||||
<strong
|
||||
v-if="warnings > 0"
|
||||
class="notif warnings"
|
||||
title="Warning"
|
||||
>{{ warnings }}</strong
|
||||
>
|
||||
<strong v-if="errors > 0" class="notif errors" title="Error">{{
|
||||
errors
|
||||
}}</strong>
|
||||
<strong
|
||||
v-if="serverError"
|
||||
class="notif errors"
|
||||
title="Connection error to Sonarr API, check url and apikey in config.yml"
|
||||
>?</strong
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tag" :class="item.tagstyle" v-if="item.tag">
|
||||
<strong class="tag-text">#{{ item.tag }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<Generic :item="item">
|
||||
<template #indicator>
|
||||
<div class="notifs">
|
||||
<strong v-if="activity > 0" class="notif activity" title="Activity">
|
||||
{{ activity }}
|
||||
</strong>
|
||||
<strong v-if="warnings > 0" class="notif warnings" title="Warning">
|
||||
{{ warnings }}
|
||||
</strong>
|
||||
<strong v-if="errors > 0" class="notif errors" title="Error">
|
||||
{{ errors }}
|
||||
</strong>
|
||||
<strong
|
||||
v-if="serverError"
|
||||
class="notif errors"
|
||||
title="Connection error to Sonarr API, check url and apikey in config.yml"
|
||||
>
|
||||
?
|
||||
</strong>
|
||||
</div>
|
||||
</template>
|
||||
</Generic>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import service from "@/mixins/service.js";
|
||||
import Generic from "./Generic.vue";
|
||||
|
||||
const V3_API = "/api/v3";
|
||||
const LEGACY_API = "/api";
|
||||
|
||||
export default {
|
||||
name: "Sonarr",
|
||||
mixins: [service],
|
||||
props: {
|
||||
item: Object,
|
||||
},
|
||||
components: {
|
||||
Generic,
|
||||
},
|
||||
computed: {
|
||||
apiPath() {
|
||||
return this.item.legacyApi ? LEGACY_API : V3_API;
|
||||
},
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
activity: null,
|
||||
@ -70,15 +57,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
fetchConfig: function () {
|
||||
fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status != 200) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
this.fetch(`${this.apiPath}/health?apikey=${this.item.apikey}`)
|
||||
.then((health) => {
|
||||
this.warnings = 0;
|
||||
this.errors = 0;
|
||||
@ -94,21 +73,17 @@ export default {
|
||||
console.error(e);
|
||||
this.serverError = true;
|
||||
});
|
||||
fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => {
|
||||
if (response.status != 200) {
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
this.fetch(`${this.apiPath}/queue?apikey=${this.item.apikey}`)
|
||||
.then((queue) => {
|
||||
this.activity = 0;
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
if (queue[i].series) {
|
||||
this.activity++;
|
||||
if (this.item.legacyApi) {
|
||||
for (var i = 0; i < queue.length; i++) {
|
||||
if (queue[i].series) {
|
||||
this.activity++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.activity = queue.totalRecords;
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
@ -121,35 +96,32 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.media-left img {
|
||||
max-height: 100%;
|
||||
}
|
||||
.notifs {
|
||||
position: absolute;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
top: 0.3em;
|
||||
right: 0.5em;
|
||||
}
|
||||
.notif {
|
||||
padding-right: 0.35em;
|
||||
padding-left: 0.35em;
|
||||
padding-top: 0.2em;
|
||||
padding-bottom: 0.2em;
|
||||
border-radius: 0.25em;
|
||||
position: relative;
|
||||
margin-left: 0.3em;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
.activity {
|
||||
background-color: #4fb5d6;
|
||||
}
|
||||
|
||||
.warnings {
|
||||
background-color: #d08d2e;
|
||||
}
|
||||
.notif {
|
||||
display: inline-block;
|
||||
padding: 0.2em 0.35em;
|
||||
border-radius: 0.25em;
|
||||
position: relative;
|
||||
margin-left: 0.3em;
|
||||
font-size: 0.8em;
|
||||
|
||||
.errors {
|
||||
background-color: #e51111;
|
||||
&.activity {
|
||||
background-color: #4fb5d6;
|
||||
}
|
||||
|
||||
&.warnings {
|
||||
background-color: #d08d2e;
|
||||
}
|
||||
|
||||
&.errors {
|
||||
background-color: #e51111;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -31,7 +31,13 @@ export default {
|
||||
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) {
|
||||
throw new Error("Not 2xx response");
|
||||
}
|
||||
|
122
yarn.lock
122
yarn.lock
@ -1173,10 +1173,10 @@
|
||||
lodash.kebabcase "^4.1.1"
|
||||
svg-tags "^1.0.0"
|
||||
|
||||
"@vue/babel-preset-app@^4.5.13":
|
||||
version "4.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.5.13.tgz#cb475321e4c73f7f110dac29a48c2a9cb80afeb6"
|
||||
integrity sha512-pM7CR3yXB6L8Gfn6EmX7FLNE3+V/15I3o33GkSNsWvgsMp6HVGXKkXgojrcfUUauyL1LZOdvTmu4enU2RePGHw==
|
||||
"@vue/babel-preset-app@^4.5.15":
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@vue/babel-preset-app/-/babel-preset-app-4.5.15.tgz#f6bc08f8f674e98a260004234cde18b966d72eb0"
|
||||
integrity sha512-J+YttzvwRfV1BPczf8r3qCevznYk+jh531agVF+5EYlHF4Sgh/cGXTz9qkkiux3LQgvhEGXgmCteg1n38WuuKg==
|
||||
dependencies:
|
||||
"@babel/core" "^7.11.0"
|
||||
"@babel/helper-compilation-targets" "^7.9.6"
|
||||
@ -1258,61 +1258,61 @@
|
||||
"@vue/babel-plugin-transform-vue-jsx" "^1.2.1"
|
||||
camelcase "^5.0.0"
|
||||
|
||||
"@vue/cli-overlay@^4.5.13":
|
||||
version "4.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.5.13.tgz#4f1fd2161be8f69d6cba8079f3f0d7dc4dee47a7"
|
||||
integrity sha512-jhUIg3klgi5Cxhs8dnat5hi/W2tQJvsqCxR0u6hgfSob0ORODgUBlN+F/uwq7cKIe/pzedVUk1y07F13GQvPqg==
|
||||
"@vue/cli-overlay@^4.5.15":
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-overlay/-/cli-overlay-4.5.15.tgz#0700fd6bad39336d4189ba3ff7d25e638e818c9c"
|
||||
integrity sha512-0zI0kANAVmjFO2LWGUIzdGPMeE3+9k+KeRDXsUqB30YfRF7abjfiiRPq5BU9pOzlJbVdpRkisschBrvdJqDuDg==
|
||||
|
||||
"@vue/cli-plugin-babel@~4.5.0":
|
||||
version "4.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.13.tgz#a89c482edcc4ea1d135645cec502a7f5fd4c30e7"
|
||||
integrity sha512-ykvEAfD8PgGs+dGMGqr7l/nRmIS39NRzWLhMluPLTvDV1L+IxcoB73HNLGA/aENDpl8CuWrTE+1VgydcOhp+wg==
|
||||
"@vue/cli-plugin-babel@~4.5.15":
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-babel/-/cli-plugin-babel-4.5.15.tgz#ae4fb2ed54255fe3d84df381dab68509641179ed"
|
||||
integrity sha512-hBLrwYfFkHldEe34op/YNgPhpOWI5n5DB2Qt9I/1Epeif4M4iFaayrgjvOR9AVM6WbD3Yx7WCFszYpWrQZpBzQ==
|
||||
dependencies:
|
||||
"@babel/core" "^7.11.0"
|
||||
"@vue/babel-preset-app" "^4.5.13"
|
||||
"@vue/cli-shared-utils" "^4.5.13"
|
||||
"@vue/babel-preset-app" "^4.5.15"
|
||||
"@vue/cli-shared-utils" "^4.5.15"
|
||||
babel-loader "^8.1.0"
|
||||
cache-loader "^4.1.0"
|
||||
thread-loader "^2.1.3"
|
||||
webpack "^4.0.0"
|
||||
|
||||
"@vue/cli-plugin-eslint@~4.5.0":
|
||||
version "4.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.13.tgz#8baf22d0d96d76720c7506646b96f4f62c05bdfa"
|
||||
integrity sha512-yc2uXX6aBiy3vEf5TwaueaDqQbdIXIhk0x0KzEtpPo23jBdLkpOSoU5NCgE06g/ZiGAcettpmBSv73Hfp4wHEw==
|
||||
"@vue/cli-plugin-eslint@~4.5.15":
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-eslint/-/cli-plugin-eslint-4.5.15.tgz#5781824a941f34c26336a67b1f6584a06c6a24ff"
|
||||
integrity sha512-/2Fl6wY/5bz3HD035oSnFRMsKNxDxU396KqBdpCQdwdvqk4mm6JAbXqihpcBRTNPeTO6w+LwGe6FE56PVbJdbg==
|
||||
dependencies:
|
||||
"@vue/cli-shared-utils" "^4.5.13"
|
||||
"@vue/cli-shared-utils" "^4.5.15"
|
||||
eslint-loader "^2.2.1"
|
||||
globby "^9.2.0"
|
||||
inquirer "^7.1.0"
|
||||
webpack "^4.0.0"
|
||||
yorkie "^2.0.0"
|
||||
|
||||
"@vue/cli-plugin-pwa@~4.5.0":
|
||||
version "4.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.13.tgz#a800639814b6f62a38f04198c340cfaee7295c3f"
|
||||
integrity sha512-uU5pp94VU0YscfKq/mNRsKOdxG+CTqVlZWaYkRc+HCcwkJ/m/CnxgaEqQFr0QpHC8zmlX4gILO1RVYygJoR9tw==
|
||||
"@vue/cli-plugin-pwa@~4.5.15":
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-pwa/-/cli-plugin-pwa-4.5.15.tgz#eb800c418d96b496deec9d063a1798fe6e9c2db8"
|
||||
integrity sha512-yQzsspaIkjeQyN6btF8ATgbJFU023q1HC8uUpmiBa4QE9EyBlR8fSrKFhcJ0EmT6KnU7PMwlnOJ/OqjguFnufA==
|
||||
dependencies:
|
||||
"@vue/cli-shared-utils" "^4.5.13"
|
||||
"@vue/cli-shared-utils" "^4.5.15"
|
||||
webpack "^4.0.0"
|
||||
workbox-webpack-plugin "^4.3.1"
|
||||
|
||||
"@vue/cli-plugin-router@^4.5.13":
|
||||
version "4.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.13.tgz#0b67c8898a2bf132941919a2a2e5f3aacbd9ffbe"
|
||||
integrity sha512-tgtMDjchB/M1z8BcfV4jSOY9fZSMDTPgF9lsJIiqBWMxvBIsk9uIZHxp62DibYME4CCKb/nNK61XHaikFp+83w==
|
||||
"@vue/cli-plugin-router@^4.5.15":
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-router/-/cli-plugin-router-4.5.15.tgz#1e75c8c89df42c694f143b9f1028de3cf5d61e1e"
|
||||
integrity sha512-q7Y6kP9b3k55Ca2j59xJ7XPA6x+iSRB+N4ac0ZbcL1TbInVQ4j5wCzyE+uqid40hLy4fUdlpl4X9fHJEwuVxPA==
|
||||
dependencies:
|
||||
"@vue/cli-shared-utils" "^4.5.13"
|
||||
"@vue/cli-shared-utils" "^4.5.15"
|
||||
|
||||
"@vue/cli-plugin-vuex@^4.5.13":
|
||||
version "4.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.13.tgz#98646d8bc1e69cf6c6a6cba2fed3eace0356c360"
|
||||
integrity sha512-I1S9wZC7iI0Wn8kw8Zh+A2Qkf6s1M6vTGBkx8boXjuzfwEEyEHRxadsVCecZc8Mkpydo0nykj+MyYF96TKFuVA==
|
||||
"@vue/cli-plugin-vuex@^4.5.15":
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-plugin-vuex/-/cli-plugin-vuex-4.5.15.tgz#466c1f02777d02fef53a9bb49a36cc3a3bcfec4e"
|
||||
integrity sha512-fqap+4HN+w+InDxlA3hZTOGE0tzBTgXhKLoDydhywqgmhQ1D9JA6Feh94ze6tG8DsWX58/ujYUqA8jAz17FJtg==
|
||||
|
||||
"@vue/cli-service@~4.5.0":
|
||||
version "4.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.5.13.tgz#a09e684a801684b6e24e5414ad30650970eec9ed"
|
||||
integrity sha512-CKAZN4iokMMsaUyJRU22oUAz3oS/X9sVBSKAF2/shFBV5xh3jqAlKl8OXZYz4cXGFLA6djNuYrniuLAo7Ku97A==
|
||||
"@vue/cli-service@~4.5.15":
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-service/-/cli-service-4.5.15.tgz#0e9a186d51550027d0e68e95042077eb4d115b45"
|
||||
integrity sha512-sFWnLYVCn4zRfu45IcsIE9eXM0YpDV3S11vlM2/DVbIPAGoYo5ySpSof6aHcIvkeGsIsrHFpPHzNvDZ/efs7jA==
|
||||
dependencies:
|
||||
"@intervolga/optimize-cssnano-plugin" "^1.0.5"
|
||||
"@soda/friendly-errors-webpack-plugin" "^1.7.1"
|
||||
@ -1320,10 +1320,10 @@
|
||||
"@types/minimist" "^1.2.0"
|
||||
"@types/webpack" "^4.0.0"
|
||||
"@types/webpack-dev-server" "^3.11.0"
|
||||
"@vue/cli-overlay" "^4.5.13"
|
||||
"@vue/cli-plugin-router" "^4.5.13"
|
||||
"@vue/cli-plugin-vuex" "^4.5.13"
|
||||
"@vue/cli-shared-utils" "^4.5.13"
|
||||
"@vue/cli-overlay" "^4.5.15"
|
||||
"@vue/cli-plugin-router" "^4.5.15"
|
||||
"@vue/cli-plugin-vuex" "^4.5.15"
|
||||
"@vue/cli-shared-utils" "^4.5.15"
|
||||
"@vue/component-compiler-utils" "^3.1.2"
|
||||
"@vue/preload-webpack-plugin" "^1.1.0"
|
||||
"@vue/web-component-wrapper" "^1.2.0"
|
||||
@ -1372,10 +1372,10 @@
|
||||
optionalDependencies:
|
||||
vue-loader-v16 "npm:vue-loader@^16.1.0"
|
||||
|
||||
"@vue/cli-shared-utils@^4.5.13":
|
||||
version "4.5.13"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.13.tgz#acd40f31b4790f1634292bdaa5fca95dc1e0ff50"
|
||||
integrity sha512-HpnOrkLg42RFUsQGMJv26oTG3J3FmKtO2WSRhKIIL+1ok3w9OjGCtA3nMMXN27f9eX14TqO64M36DaiSZ1fSiw==
|
||||
"@vue/cli-shared-utils@^4.5.15":
|
||||
version "4.5.15"
|
||||
resolved "https://registry.yarnpkg.com/@vue/cli-shared-utils/-/cli-shared-utils-4.5.15.tgz#dba3858165dbe3465755f256a4890e69084532d6"
|
||||
integrity sha512-SKaej9hHzzjKSOw1NlFmc6BSE0vcqUQMQiv1cxQ2DhVyy4QxZXBmzmiLBUBe+hYZZs1neXW7n//udeN9bCAY+Q==
|
||||
dependencies:
|
||||
"@hapi/joi" "^15.0.1"
|
||||
chalk "^2.4.2"
|
||||
@ -2339,9 +2339,9 @@ caniuse-api@^3.0.0:
|
||||
lodash.uniq "^4.5.0"
|
||||
|
||||
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219:
|
||||
version "1.0.30001245"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001245.tgz#45b941bbd833cb0fa53861ff2bae746b3c6ca5d4"
|
||||
integrity sha512-768fM9j1PKXpOCKws6eTo3RHmvTUsG9UrpT4WoREFeZgJBTi4/X9g565azS/rVUGtqb8nt7FjLeF5u4kukERnA==
|
||||
version "1.0.30001311"
|
||||
resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001311.tgz"
|
||||
integrity sha512-mleTFtFKfykEeW34EyfhGIFjGCqzhh38Y0LhdQ9aWF+HorZTtdgKV/1hEE0NlFkG2ubvisPV6l400tlbPys98A==
|
||||
|
||||
case-sensitive-paths-webpack-plugin@^2.3.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"
|
||||
integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
|
||||
|
||||
core-js@^3.17.3:
|
||||
version "3.17.3"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.17.3.tgz#8e8bd20e91df9951e903cabe91f9af4a0895bc1e"
|
||||
integrity sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw==
|
||||
core-js@^3.21.1:
|
||||
version "3.21.1"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.21.1.tgz#f2e0ddc1fc43da6f904706e8e955bc19d06a0d94"
|
||||
integrity sha512-FRq5b/VMrWlrmCzwRrpDYNxyHP9BcAZC+xHJaqTgIE5091ZV1NTmyh0sGOg5XqpnHvR0svdy0sv1gWA1zmhxig==
|
||||
|
||||
core-js@^3.6.5:
|
||||
version "3.15.2"
|
||||
@ -3999,9 +3999,9 @@ flush-write-stream@^1.0.0:
|
||||
readable-stream "^2.3.6"
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
|
||||
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
|
||||
version "1.14.8"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
|
||||
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
|
||||
|
||||
for-in@^1.0.2:
|
||||
version "1.0.2"
|
||||
@ -5590,9 +5590,9 @@ minimatch@^3.0.4:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.5:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
|
||||
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
|
||||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
|
||||
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
|
||||
|
||||
minipass@^3.1.1:
|
||||
version "3.1.3"
|
||||
@ -8227,9 +8227,9 @@ url-loader@^2.2.0:
|
||||
schema-utils "^2.5.0"
|
||||
|
||||
url-parse@^1.4.3, url-parse@^1.5.1:
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862"
|
||||
integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ==
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
Loading…
Reference in New Issue
Block a user