diff options
Diffstat (limited to 'src/components')
-rw-r--r-- | src/components/DarkMode.vue | 14 | ||||
-rw-r--r-- | src/components/GetStarted.vue | 35 | ||||
-rw-r--r-- | src/components/SearchInput.vue | 4 | ||||
-rw-r--r-- | src/components/SettingToggle.vue | 3 | ||||
-rw-r--r-- | src/components/services/AdGuardHome.vue | 3 | ||||
-rw-r--r-- | src/components/services/Emby.vue | 118 | ||||
-rw-r--r-- | src/components/services/Lidarr.vue | 110 | ||||
-rw-r--r-- | src/components/services/Mealie.vue | 106 | ||||
-rw-r--r-- | src/components/services/Medusa.vue | 142 | ||||
-rw-r--r-- | src/components/services/PaperlessNG.vue | 79 | ||||
-rw-r--r-- | src/components/services/PiHole.vue | 85 | ||||
-rw-r--r-- | src/components/services/Portainer.vue | 139 | ||||
-rw-r--r-- | src/components/services/Prowlarr.vue | 94 | ||||
-rw-r--r-- | src/components/services/Radarr.vue | 156 | ||||
-rw-r--r-- | src/components/services/Sonarr.vue | 158 |
15 files changed, 793 insertions, 453 deletions
diff --git a/src/components/DarkMode.vue b/src/components/DarkMode.vue index 80491fa..677238a 100644 --- a/src/components/DarkMode.vue +++ b/src/components/DarkMode.vue | |||
@@ -15,6 +15,9 @@ | |||
15 | <script> | 15 | <script> |
16 | export default { | 16 | export default { |
17 | name: "Darkmode", | 17 | name: "Darkmode", |
18 | props: { | ||
19 | defaultValue: String, | ||
20 | }, | ||
18 | data: function () { | 21 | data: function () { |
19 | return { | 22 | return { |
20 | isDark: null, | 23 | isDark: null, |
@@ -30,6 +33,17 @@ export default { | |||
30 | if ("overrideDark" in localStorage) { | 33 | if ("overrideDark" in localStorage) { |
31 | // Light theme is 1 and Dark theme is 2 | 34 | // Light theme is 1 and Dark theme is 2 |
32 | this.mode = JSON.parse(localStorage.overrideDark) ? 2 : 1; | 35 | this.mode = JSON.parse(localStorage.overrideDark) ? 2 : 1; |
36 | } else { | ||
37 | switch (this.defaultValue) { | ||
38 | case "light": | ||
39 | this.mode = 1; | ||
40 | break; | ||
41 | case "dark": | ||
42 | this.mode = 2; | ||
43 | break; | ||
44 | default: | ||
45 | this.mode = 0; | ||
46 | } | ||
33 | } | 47 | } |
34 | this.isDark = this.getIsDark(); | 48 | this.isDark = this.getIsDark(); |
35 | this.$emit("updated", this.isDark); | 49 | this.$emit("updated", this.isDark); |
diff --git a/src/components/GetStarted.vue b/src/components/GetStarted.vue new file mode 100644 index 0000000..dcabc02 --- /dev/null +++ b/src/components/GetStarted.vue | |||
@@ -0,0 +1,35 @@ | |||
1 | <template> | ||
2 | <article> | ||
3 | <div class="m-6 has-text-centered py-6"> | ||
4 | <p class="is-size-5 mb-0">No configured service found!</p> | ||
5 | <p>Check out the documentation to start building your Homer dashboard.</p> | ||
6 | <p> | ||
7 | <a | ||
8 | class="button is-primary mt-5 has-text-weight-bold" | ||
9 | href="https://github.com/bastienwirtz/homer/blob/main/README.md#getting-started" | ||
10 | target="_blank" | ||
11 | > | ||
12 | Get started | ||
13 | </a> | ||
14 | </p> | ||
15 | </div> | ||
16 | </article> | ||
17 | </template> | ||
18 | |||
19 | <script> | ||
20 | export default { | ||
21 | name: "GetStarted", | ||
22 | }; | ||
23 | </script> | ||
24 | |||
25 | <style lang="scss" scoped> | ||
26 | p { | ||
27 | color: #4a4a4a; | ||
28 | } | ||
29 | |||
30 | body #app a { | ||
31 | font-weight: 900; | ||
32 | color: #ffffff; | ||
33 | font-family: "Lato", sans-serif; | ||
34 | } | ||
35 | </style> | ||
diff --git a/src/components/SearchInput.vue b/src/components/SearchInput.vue index 586ff71..165c992 100644 --- a/src/components/SearchInput.vue +++ b/src/components/SearchInput.vue | |||
@@ -19,8 +19,8 @@ export default { | |||
19 | value: String, | 19 | value: String, |
20 | hotkey: { | 20 | hotkey: { |
21 | type: String, | 21 | type: String, |
22 | default: "/" | 22 | default: "/", |
23 | } | 23 | }, |
24 | }, | 24 | }, |
25 | mounted() { | 25 | mounted() { |
26 | this._keyListener = function (event) { | 26 | this._keyListener = function (event) { |
diff --git a/src/components/SettingToggle.vue b/src/components/SettingToggle.vue index 985ca84..6c8a10f 100644 --- a/src/components/SettingToggle.vue +++ b/src/components/SettingToggle.vue | |||
@@ -12,6 +12,7 @@ export default { | |||
12 | name: String, | 12 | name: String, |
13 | icon: String, | 13 | icon: String, |
14 | iconAlt: String, | 14 | iconAlt: String, |
15 | defaultValue: Boolean, | ||
15 | }, | 16 | }, |
16 | data: function () { | 17 | data: function () { |
17 | return { | 18 | return { |
@@ -24,6 +25,8 @@ export default { | |||
24 | 25 | ||
25 | if (this.name in localStorage) { | 26 | if (this.name in localStorage) { |
26 | this.value = JSON.parse(localStorage[this.name]); | 27 | this.value = JSON.parse(localStorage[this.name]); |
28 | } else { | ||
29 | this.value = this.defaultValue; | ||
27 | } | 30 | } |
28 | 31 | ||
29 | this.$emit("updated", this.value); | 32 | this.$emit("updated", this.value); |
diff --git a/src/components/services/AdGuardHome.vue b/src/components/services/AdGuardHome.vue index b01f0f4..4c53398 100644 --- a/src/components/services/AdGuardHome.vue +++ b/src/components/services/AdGuardHome.vue | |||
@@ -76,9 +76,6 @@ export default { | |||
76 | </script> | 76 | </script> |
77 | 77 | ||
78 | <style scoped lang="scss"> | 78 | <style scoped lang="scss"> |
79 | .media-left img { | ||
80 | max-height: 100%; | ||
81 | } | ||
82 | .status { | 79 | .status { |
83 | font-size: 0.8rem; | 80 | font-size: 0.8rem; |
84 | color: var(--text-title); | 81 | color: var(--text-title); |
diff --git a/src/components/services/Emby.vue b/src/components/services/Emby.vue new file mode 100644 index 0000000..25a2612 --- /dev/null +++ b/src/components/services/Emby.vue | |||
@@ -0,0 +1,118 @@ | |||
1 | <template> | ||
2 | <Generic :item="item"> | ||
3 | <template #content> | ||
4 | <p class="title is-4">{{ item.name }}</p> | ||
5 | <p class="subtitle is-6"> | ||
6 | <template v-if="item.subtitle"> | ||
7 | {{ item.subtitle }} | ||
8 | </template> | ||
9 | <template v-else> | ||
10 | {{ embyCount }} | ||
11 | </template> | ||
12 | </p> | ||
13 | </template> | ||
14 | <template #indicator> | ||
15 | <div v-if="status" class="status" :class="status"> | ||
16 | {{ status }} | ||
17 | </div> | ||
18 | </template> | ||
19 | </Generic> | ||
20 | </template> | ||
21 | |||
22 | <script> | ||
23 | import service from "@/mixins/service.js"; | ||
24 | import Generic from "./Generic.vue"; | ||
25 | |||
26 | export default { | ||
27 | name: "Emby", | ||
28 | mixins: [service], | ||
29 | props: { | ||
30 | item: Object, | ||
31 | }, | ||
32 | components: { | ||
33 | Generic, | ||
34 | }, | ||
35 | data: () => ({ | ||
36 | status: "", | ||
37 | albumCount: 0, | ||
38 | songCount: 0, | ||
39 | movieCount: 0, | ||
40 | seriesCount: 0, | ||
41 | episodeCount: 0, | ||
42 | }), | ||
43 | computed: { | ||
44 | embyCount: function () { | ||
45 | if (this.item.libraryType === "music") | ||
46 | return `${this.songCount} songs, ${this.albumCount} albums`; | ||
47 | else if (this.item.libraryType === "movies") | ||
48 | return `${this.movieCount} movies`; | ||
49 | else if (this.item.libraryType === "series") | ||
50 | return `${this.episodeCount} eps, ${this.seriesCount} series`; | ||
51 | else return `wrong library type 💀`; | ||
52 | }, | ||
53 | }, | ||
54 | created() { | ||
55 | this.fetchServerStatus(); | ||
56 | |||
57 | if (!this.item.subtitle && this.status !== "dead") | ||
58 | this.fetchServerMediaStats(); | ||
59 | }, | ||
60 | methods: { | ||
61 | fetchServerStatus: async function () { | ||
62 | this.fetch("/System/info/public") | ||
63 | .then((response) => { | ||
64 | if (response.Id) this.status = "running"; | ||
65 | else throw new Error(); | ||
66 | }) | ||
67 | .catch((e) => { | ||
68 | console.log(e); | ||
69 | this.status = "dead"; | ||
70 | }); | ||
71 | }, | ||
72 | fetchServerMediaStats: async function () { | ||
73 | const headers = { | ||
74 | "X-Emby-Token": this.item.apikey, | ||
75 | }; | ||
76 | |||
77 | var data = await this.fetch("/items/counts", { headers }).catch((e) => { | ||
78 | console.log(e); | ||
79 | }); | ||
80 | |||
81 | this.albumCount = data.AlbumCount; | ||
82 | this.songCount = data.SongCount; | ||
83 | this.movieCount = data.MovieCount; | ||
84 | this.seriesCount = data.SeriesCount; | ||
85 | this.episodeCount = data.EpisodeCount; | ||
86 | }, | ||
87 | }, | ||
88 | }; | ||
89 | </script> | ||
90 | |||
91 | <style scoped lang="scss"> | ||
92 | .status { | ||
93 | font-size: 0.8rem; | ||
94 | color: var(--text-title); | ||
95 | |||
96 | &.running:before { | ||
97 | background-color: #94e185; | ||
98 | border-color: #78d965; | ||
99 | box-shadow: 0 0 5px 1px #94e185; | ||
100 | } | ||
101 | |||
102 | &.dead:before { | ||
103 | background-color: #c9404d; | ||
104 | border-color: #c42c3b; | ||
105 | box-shadow: 0 0 5px 1px #c9404d; | ||
106 | } | ||
107 | |||
108 | &:before { | ||
109 | content: " "; | ||
110 | display: inline-block; | ||
111 | width: 7px; | ||
112 | height: 7px; | ||
113 | margin-right: 10px; | ||
114 | border: 1px solid #000; | ||
115 | border-radius: 7px; | ||
116 | } | ||
117 | } | ||
118 | </style> | ||
diff --git a/src/components/services/Lidarr.vue b/src/components/services/Lidarr.vue new file mode 100644 index 0000000..cbe5516 --- /dev/null +++ b/src/components/services/Lidarr.vue | |||
@@ -0,0 +1,110 @@ | |||
1 | <template> | ||
2 | <Generic :item="item"> | ||
3 | <template #indicator> | ||
4 | <div class="notifs"> | ||
5 | <strong v-if="activity > 0" class="notif activity" title="Activity"> | ||
6 | {{ activity }} | ||
7 | </strong> | ||
8 | <strong v-if="warnings > 0" class="notif warnings" title="Warning"> | ||
9 | {{ warnings }} | ||
10 | </strong> | ||
11 | <strong v-if="errors > 0" class="notif errors" title="Error"> | ||
12 | {{ errors }} | ||
13 | </strong> | ||
14 | <strong | ||
15 | v-if="serverError" | ||
16 | class="notif errors" | ||
17 | title="Connection error to Lidarr API, check url and apikey in config.yml" | ||
18 | >?</strong | ||
19 | > | ||
20 | </div> | ||
21 | </template> | ||
22 | </Generic> | ||
23 | </template> | ||
24 | |||
25 | <script> | ||
26 | import service from "@/mixins/service.js"; | ||
27 | import Generic from "./Generic.vue"; | ||
28 | |||
29 | export default { | ||
30 | name: "Lidarr", | ||
31 | mixins: [service], | ||
32 | props: { | ||
33 | item: Object, | ||
34 | }, | ||
35 | components: { | ||
36 | Generic, | ||
37 | }, | ||
38 | data: () => { | ||
39 | return { | ||
40 | activity: null, | ||
41 | warnings: null, | ||
42 | errors: null, | ||
43 | serverError: false, | ||
44 | }; | ||
45 | }, | ||
46 | created: function () { | ||
47 | this.fetchConfig(); | ||
48 | }, | ||
49 | methods: { | ||
50 | fetchConfig: function () { | ||
51 | this.fetch(`/api/v1/health?apikey=${this.item.apikey}`) | ||
52 | .then((health) => { | ||
53 | this.warnings = 0; | ||
54 | this.errors = 0; | ||
55 | for (var i = 0; i < health.length; i++) { | ||
56 | if (health[i].type == "warning") { | ||
57 | this.warnings++; | ||
58 | } else if (health[i].type == "error") { | ||
59 | this.errors++; | ||
60 | } | ||
61 | } | ||
62 | }) | ||
63 | .catch((e) => { | ||
64 | console.error(e); | ||
65 | this.serverError = true; | ||
66 | }); | ||
67 | this.fetch(`/api/v1/queue/status?apikey=${this.item.apikey}`) | ||
68 | .then((queue) => { | ||
69 | this.activity = queue.totalCount; | ||
70 | }) | ||
71 | .catch((e) => { | ||
72 | console.error(e); | ||
73 | this.serverError = true; | ||
74 | }); | ||
75 | }, | ||
76 | }, | ||
77 | }; | ||
78 | </script> | ||
79 | |||
80 | <style scoped lang="scss"> | ||
81 | .notifs { | ||
82 | position: absolute; | ||
83 | color: white; | ||
84 | font-family: sans-serif; | ||
85 | top: 0.3em; | ||
86 | right: 0.5em; | ||
87 | .notif { | ||
88 | display: inline-block; | ||
89 | padding-right: 0.35em; | ||
90 | padding-left: 0.35em; | ||
91 | padding-top: 0.2em; | ||
92 | padding-bottom: 0.2em; | ||
93 | border-radius: 0.25em; | ||
94 | position: relative; | ||
95 | margin-left: 0.3em; | ||
96 | font-size: 0.8em; | ||
97 | &.activity { | ||
98 | background-color: #4fb5d6; | ||
99 | } | ||
100 | |||
101 | &.warnings { | ||
102 | background-color: #d08d2e; | ||
103 | } | ||
104 | |||
105 | &.errors { | ||
106 | background-color: #e51111; | ||
107 | } | ||
108 | } | ||
109 | } | ||
110 | </style> | ||
diff --git a/src/components/services/Mealie.vue b/src/components/services/Mealie.vue index 790a9b1..b5b2255 100644 --- a/src/components/services/Mealie.vue +++ b/src/components/services/Mealie.vue | |||
@@ -1,47 +1,33 @@ | |||
1 | <template> | 1 | <template> |
2 | <div> | 2 | <Generic :item="item"> |
3 | <div class="card" :class="item.class"> | 3 | <template #content> |
4 | <a :href="item.url" :target="item.target" rel="noreferrer"> | 4 | <p class="title is-4">{{ item.name }}</p> |
5 | <div class="card-content"> | 5 | <p class="subtitle is-6"> |
6 | <div class="media"> | 6 | <template v-if="item.subtitle"> |
7 | <div v-if="item.logo" class="media-left"> | 7 | {{ item.subtitle }} |
8 | <figure class="image is-48x48"> | 8 | </template> |
9 | <img :src="item.logo" :alt="`${item.name} logo`" /> | 9 | <template v-else-if="meal"> Today: {{ meal.name }} </template> |
10 | </figure> | 10 | <template v-else-if="stats"> |
11 | </div> | 11 | happily keeping {{ stats.totalRecipes }} recipes organized |
12 | <div v-if="item.icon" class="media-left"> | 12 | </template> |
13 | <figure class="image is-48x48"> | 13 | </p> |
14 | <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> | 14 | </template> |
15 | </figure> | 15 | </Generic> |
16 | </div> | ||
17 | <div class="media-content"> | ||
18 | <p class="title is-4">{{ item.name }}</p> | ||
19 | <p class="subtitle is-6"> | ||
20 | <template v-if="item.subtitle"> | ||
21 | {{ item.subtitle }} | ||
22 | </template> | ||
23 | <template v-else-if="meal"> Today: {{ meal.name }} </template> | ||
24 | <template v-else-if="stats"> | ||
25 | happily keeping {{ stats.totalRecipes }} recipes organized | ||
26 | </template> | ||
27 | </p> | ||
28 | </div> | ||
29 | </div> | ||
30 | <div class="tag" :class="item.tagstyle" v-if="item.tag"> | ||
31 | <strong class="tag-text">#{{ item.tag }}</strong> | ||
32 | </div> | ||
33 | </div> | ||
34 | </a> | ||
35 | </div> | ||
36 | </div> | ||
37 | </template> | 16 | </template> |
38 | 17 | ||
39 | <script> | 18 | <script> |
19 | import service from "@/mixins/service.js"; | ||
20 | import Generic from "./Generic.vue"; | ||
21 | |||
40 | export default { | 22 | export default { |
41 | name: "Mealie", | 23 | name: "Mealie", |
24 | mixins: [service], | ||
42 | props: { | 25 | props: { |
43 | item: Object, | 26 | item: Object, |
44 | }, | 27 | }, |
28 | components: { | ||
29 | Generic, | ||
30 | }, | ||
45 | data: () => ({ | 31 | data: () => ({ |
46 | stats: null, | 32 | stats: null, |
47 | meal: null, | 33 | meal: null, |
@@ -51,44 +37,20 @@ export default { | |||
51 | }, | 37 | }, |
52 | methods: { | 38 | methods: { |
53 | fetchStatus: async function () { | 39 | fetchStatus: async function () { |
54 | if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing | 40 | const headers = { |
55 | this.meal = await fetch(`${this.item.url}/api/meal-plans/today/`, { | 41 | Authorization: "Bearer " + this.item.apikey, |
56 | headers: { | 42 | Accept: "application/json", |
57 | Authorization: "Bearer " + this.item.apikey, | 43 | }; |
58 | Accept: "application/json", | 44 | |
59 | }, | 45 | if (this.item.subtitle != null) return; |
60 | }) | 46 | |
61 | .then(function (response) { | 47 | this.meal = await this.fetch("/api/meal-plans/today/", { headers }).catch( |
62 | if (!response.ok) { | 48 | (e) => console.log(e) |
63 | throw new Error("Not 2xx response"); | 49 | ); |
64 | } else { | 50 | this.stats = await this.fetch("/api/debug/statistics/", { |
65 | if (response != null) { | 51 | headers, |
66 | return response.json(); | 52 | }).catch((e) => console.log(e)); |
67 | } | ||
68 | } | ||
69 | }) | ||
70 | .catch((e) => console.log(e)); | ||
71 | this.stats = await fetch(`${this.item.url}/api/debug/statistics/`, { | ||
72 | headers: { | ||
73 | Authorization: "Bearer " + this.item.apikey, | ||
74 | Accept: "application/json", | ||
75 | }, | ||
76 | }) | ||
77 | .then(function (response) { | ||
78 | if (!response.ok) { | ||
79 | throw new Error("Not 2xx response"); | ||
80 | } else { | ||
81 | return response.json(); | ||
82 | } | ||
83 | }) | ||
84 | .catch((e) => console.log(e)); | ||
85 | }, | 53 | }, |
86 | }, | 54 | }, |
87 | }; | 55 | }; |
88 | </script> | 56 | </script> |
89 | |||
90 | <style scoped lang="scss"> | ||
91 | .media-left img { | ||
92 | max-height: 100%; | ||
93 | } | ||
94 | </style> | ||
diff --git a/src/components/services/Medusa.vue b/src/components/services/Medusa.vue index 5720649..43b5651 100644 --- a/src/components/services/Medusa.vue +++ b/src/components/services/Medusa.vue | |||
@@ -1,65 +1,49 @@ | |||
1 | <template> | 1 | <template> |
2 | <div> | 2 | <Generic :item="item"> |
3 | <div class="card" :class="item.class"> | 3 | <template #indicator> |
4 | <a :href="item.url" :target="item.target" rel="noreferrer"> | 4 | <div class="notifs"> |
5 | <div class="card-content"> | 5 | <strong |
6 | <div class="media"> | 6 | v-if="config !== null && config.system.news.unread > 0" |
7 | <div v-if="item.logo" class="media-left"> | 7 | class="notif news" |
8 | <figure class="image is-48x48"> | 8 | title="News" |
9 | <img :src="item.logo" :alt="`${item.name} logo`" /> | 9 | >{{ config.system.news.unread }}</strong |
10 | </figure> | 10 | > |
11 | </div> | 11 | <strong |
12 | <div v-if="item.icon" class="media-left"> | 12 | v-if="config !== null && config.main.logs.numWarnings > 0" |
13 | <figure class="image is-48x48"> | 13 | class="notif warnings" |
14 | <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> | 14 | title="Warning" |
15 | </figure> | 15 | >{{ config.main.logs.numWarnings }}</strong |
16 | </div> | 16 | > |
17 | <div class="media-content"> | 17 | <strong |
18 | <p class="title is-4">{{ item.name }}</p> | 18 | v-if="config !== null && config.main.logs.numErrors > 0" |
19 | <p class="subtitle is-6">{{ item.subtitle }}</p> | 19 | class="notif errors" |
20 | </div> | 20 | title="Error" |
21 | <div class="notifs"> | 21 | >{{ config.main.logs.numErrors }}</strong |
22 | <strong | 22 | > |
23 | v-if="config !== null && config.system.news.unread > 0" | 23 | <strong |
24 | class="notif news" | 24 | v-if="serverError" |
25 | title="News" | 25 | class="notif errors" |
26 | >{{ config.system.news.unread }}</strong | 26 | title="Connection error to Medusa API, check url and apikey in config.yml" |
27 | > | 27 | >?</strong |
28 | <strong | 28 | > |
29 | v-if="config !== null && config.main.logs.numWarnings > 0" | 29 | </div> |
30 | class="notif warnings" | 30 | </template> |
31 | title="Warning" | 31 | </Generic> |
32 | >{{ config.main.logs.numWarnings }}</strong | ||
33 | > | ||
34 | <strong | ||
35 | v-if="config !== null && config.main.logs.numErrors > 0" | ||
36 | class="notif errors" | ||
37 | title="Error" | ||
38 | >{{ config.main.logs.numErrors }}</strong | ||
39 | > | ||
40 | <strong | ||
41 | v-if="serverError" | ||
42 | class="notif errors" | ||
43 | title="Connection error to Medusa API, check url and apikey in config.yml" | ||
44 | >?</strong | ||
45 | > | ||
46 | </div> | ||
47 | </div> | ||
48 | <div class="tag" :class="item.tagstyle" v-if="item.tag"> | ||
49 | <strong class="tag-text">#{{ item.tag }}</strong> | ||
50 | </div> | ||
51 | </div> | ||
52 | </a> | ||
53 | </div> | ||
54 | </div> | ||
55 | </template> | 32 | </template> |
56 | 33 | ||
57 | <script> | 34 | <script> |
35 | import service from "@/mixins/service.js"; | ||
36 | import Generic from "./Generic.vue"; | ||
37 | |||
58 | export default { | 38 | export default { |
59 | name: "Medusa", | 39 | name: "Medusa", |
40 | mixins: [service], | ||
60 | props: { | 41 | props: { |
61 | item: Object, | 42 | item: Object, |
62 | }, | 43 | }, |
44 | components: { | ||
45 | Generic, | ||
46 | }, | ||
63 | data: () => { | 47 | data: () => { |
64 | return { | 48 | return { |
65 | config: null, | 49 | config: null, |
@@ -71,16 +55,9 @@ export default { | |||
71 | }, | 55 | }, |
72 | methods: { | 56 | methods: { |
73 | fetchConfig: function () { | 57 | fetchConfig: function () { |
74 | fetch(`${this.item.url}/api/v2/config`, { | 58 | this.fetch("/api/v2/config", { |
75 | credentials: "include", | 59 | headers: { "X-Api-Key": this.item.apikey }, |
76 | headers: { "X-Api-Key": `${this.item.apikey}` }, | ||
77 | }) | 60 | }) |
78 | .then((response) => { | ||
79 | if (response.status != 200) { | ||
80 | throw new Error(response.statusText); | ||
81 | } | ||
82 | return response.json(); | ||
83 | }) | ||
84 | .then((conf) => { | 61 | .then((conf) => { |
85 | this.config = conf; | 62 | this.config = conf; |
86 | }) | 63 | }) |
@@ -94,35 +71,32 @@ export default { | |||
94 | </script> | 71 | </script> |
95 | 72 | ||
96 | <style scoped lang="scss"> | 73 | <style scoped lang="scss"> |
97 | .media-left img { | ||
98 | max-height: 100%; | ||
99 | } | ||
100 | .notifs { | 74 | .notifs { |
101 | position: absolute; | 75 | position: absolute; |
102 | color: white; | 76 | color: white; |
103 | font-family: sans-serif; | 77 | font-family: sans-serif; |
104 | top: 0.3em; | 78 | top: 0.3em; |
105 | right: 0.5em; | 79 | right: 0.5em; |
106 | } | 80 | .notif { |
107 | .notif { | 81 | padding-right: 0.35em; |
108 | padding-right: 0.35em; | 82 | padding-left: 0.35em; |
109 | padding-left: 0.35em; | 83 | padding-top: 0.2em; |
110 | padding-top: 0.2em; | 84 | padding-bottom: 0.2em; |
111 | padding-bottom: 0.2em; | 85 | border-radius: 0.25em; |
112 | border-radius: 0.25em; | 86 | position: relative; |
113 | position: relative; | 87 | margin-left: 0.3em; |
114 | margin-left: 0.3em; | 88 | font-size: 0.8em; |
115 | font-size: 0.8em; | 89 | &.news { |
116 | } | 90 | background-color: #777777; |
117 | .news { | 91 | } |
118 | background-color: #777777; | ||
119 | } | ||
120 | 92 | ||
121 | .warnings { | 93 | &.warnings { |
122 | background-color: #d08d2e; | 94 | background-color: #d08d2e; |
123 | } | 95 | } |
124 | 96 | ||
125 | .errors { | 97 | &.errors { |
126 | background-color: #e51111; | 98 | background-color: #e51111; |
99 | } | ||
100 | } | ||
127 | } | 101 | } |
128 | </style> | 102 | </style> |
diff --git a/src/components/services/PaperlessNG.vue b/src/components/services/PaperlessNG.vue index af13317..69f2437 100644 --- a/src/components/services/PaperlessNG.vue +++ b/src/components/services/PaperlessNG.vue | |||
@@ -1,46 +1,32 @@ | |||
1 | <template> | 1 | <template> |
2 | <div> | 2 | <Generic :item="item"> |
3 | <div class="card" :class="item.class"> | 3 | <template #content> |
4 | <a :href="item.url" :target="item.target" rel="noreferrer"> | 4 | <p class="title is-4">{{ item.name }}</p> |
5 | <div class="card-content"> | 5 | <p class="subtitle is-6"> |
6 | <div class="media"> | 6 | <template v-if="item.subtitle"> |
7 | <div v-if="item.logo" class="media-left"> | 7 | {{ item.subtitle }} |
8 | <figure class="image is-48x48"> | 8 | </template> |
9 | <img :src="item.logo" :alt="`${item.name} logo`" /> | 9 | <template v-else-if="api"> |
10 | </figure> | 10 | happily storing {{ api.count }} documents |
11 | </div> | 11 | </template> |
12 | <div v-if="item.icon" class="media-left"> | 12 | </p> |
13 | <figure class="image is-48x48"> | 13 | </template> |
14 | <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> | 14 | </Generic> |
15 | </figure> | ||
16 | </div> | ||
17 | <div class="media-content"> | ||
18 | <p class="title is-4">{{ item.name }}</p> | ||
19 | <p class="subtitle is-6"> | ||
20 | <template v-if="item.subtitle"> | ||
21 | {{ item.subtitle }} | ||
22 | </template> | ||
23 | <template v-else-if="api"> | ||
24 | happily storing {{ api.count }} documents | ||
25 | </template> | ||
26 | </p> | ||
27 | </div> | ||
28 | </div> | ||
29 | <div class="tag" :class="item.tagstyle" v-if="item.tag"> | ||
30 | <strong class="tag-text">#{{ item.tag }}</strong> | ||
31 | </div> | ||
32 | </div> | ||
33 | </a> | ||
34 | </div> | ||
35 | </div> | ||
36 | </template> | 15 | </template> |
37 | 16 | ||
38 | <script> | 17 | <script> |
18 | import service from "@/mixins/service.js"; | ||
19 | import Generic from "./Generic.vue"; | ||
20 | |||
39 | export default { | 21 | export default { |
40 | name: "Paperless", | 22 | name: "Paperless", |
23 | mixins: [service], | ||
41 | props: { | 24 | props: { |
42 | item: Object, | 25 | item: Object, |
43 | }, | 26 | }, |
27 | components: { | ||
28 | Generic, | ||
29 | }, | ||
44 | data: () => ({ | 30 | data: () => ({ |
45 | api: null, | 31 | api: null, |
46 | }), | 32 | }), |
@@ -49,36 +35,21 @@ export default { | |||
49 | }, | 35 | }, |
50 | methods: { | 36 | methods: { |
51 | fetchStatus: async function () { | 37 | fetchStatus: async function () { |
52 | if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing | 38 | if (this.item.subtitle != null) return; |
53 | var apikey = this.item.apikey; | 39 | |
40 | const apikey = this.item.apikey; | ||
54 | if (!apikey) { | 41 | if (!apikey) { |
55 | console.error( | 42 | console.error( |
56 | "apikey is not present in config.yml for the paperless entry!" | 43 | "apikey is not present in config.yml for the paperless entry!" |
57 | ); | 44 | ); |
58 | return; | 45 | return; |
59 | } | 46 | } |
60 | const url = `${this.item.url}/api/documents/`; | 47 | this.api = await this.fetch("/api/documents/", { |
61 | this.api = await fetch(url, { | ||
62 | credentials: "include", | ||
63 | headers: { | 48 | headers: { |
64 | Authorization: "Token " + this.item.apikey, | 49 | Authorization: "Token " + this.item.apikey, |
65 | }, | 50 | }, |
66 | }) | 51 | }).catch((e) => console.log(e)); |
67 | .then(function (response) { | ||
68 | if (!response.ok) { | ||
69 | throw new Error("Not 2xx response"); | ||
70 | } else { | ||
71 | return response.json(); | ||
72 | } | ||
73 | }) | ||
74 | .catch((e) => console.log(e)); | ||
75 | }, | 52 | }, |
76 | }, | 53 | }, |
77 | }; | 54 | }; |
78 | </script> | 55 | </script> |
79 | |||
80 | <style scoped lang="scss"> | ||
81 | .media-left img { | ||
82 | max-height: 100%; | ||
83 | } | ||
84 | </style> | ||
diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index 87f7090..9aac016 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue | |||
@@ -1,59 +1,45 @@ | |||
1 | <template> | 1 | <template> |
2 | <div> | 2 | <Generic :item="item"> |
3 | <div class="card" :class="item.class"> | 3 | <template #content> |
4 | <a :href="item.url" :target="item.target" rel="noreferrer"> | 4 | <p class="title is-4">{{ item.name }}</p> |
5 | <div class="card-content"> | 5 | <p class="subtitle is-6"> |
6 | <div class="media"> | 6 | <template v-if="item.subtitle"> |
7 | <div v-if="item.logo" class="media-left"> | 7 | {{ item.subtitle }} |
8 | <figure class="image is-48x48"> | 8 | </template> |
9 | <img :src="item.logo" :alt="`${item.name} logo`" /> | 9 | <template v-else-if="percentage"> |
10 | </figure> | 10 | {{ percentage }}% blocked |
11 | </div> | 11 | </template> |
12 | <div v-if="item.icon" class="media-left"> | 12 | </p> |
13 | <figure class="image is-48x48"> | 13 | </template> |
14 | <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> | 14 | <template #indicator> |
15 | </figure> | 15 | <div v-if="status" class="status" :class="status"> |
16 | </div> | 16 | {{ status }} |
17 | <div class="media-content"> | 17 | </div> |
18 | <p class="title is-4">{{ item.name }}</p> | 18 | </template> |
19 | <p class="subtitle is-6"> | 19 | </Generic> |
20 | <template v-if="item.subtitle"> | ||
21 | {{ item.subtitle }} | ||
22 | </template> | ||
23 | <template v-else-if="api"> | ||
24 | {{ percentage }}% blocked | ||
25 | </template> | ||
26 | </p> | ||
27 | </div> | ||
28 | <div v-if="api" class="status" :class="api.status"> | ||
29 | {{ api.status }} | ||
30 | </div> | ||
31 | </div> | ||
32 | <div class="tag" :class="item.tagstyle" v-if="item.tag"> | ||
33 | <strong class="tag-text">#{{ item.tag }}</strong> | ||
34 | </div> | ||
35 | </div> | ||
36 | </a> | ||
37 | </div> | ||
38 | </div> | ||
39 | </template> | 20 | </template> |
40 | 21 | ||
41 | <script> | 22 | <script> |
23 | import service from "@/mixins/service.js"; | ||
24 | import Generic from "./Generic.vue"; | ||
25 | |||
42 | export default { | 26 | export default { |
43 | name: "PiHole", | 27 | name: "PiHole", |
28 | mixins: [service], | ||
44 | props: { | 29 | props: { |
45 | item: Object, | 30 | item: Object, |
46 | }, | 31 | }, |
32 | components: { | ||
33 | Generic, | ||
34 | }, | ||
47 | data: () => ({ | 35 | data: () => ({ |
48 | api: { | 36 | status: "", |
49 | status: "", | 37 | ads_percentage_today: 0, |
50 | ads_percentage_today: 0, | ||
51 | }, | ||
52 | }), | 38 | }), |
53 | computed: { | 39 | computed: { |
54 | percentage: function () { | 40 | percentage: function () { |
55 | if (this.api) { | 41 | if (this.ads_percentage_today) { |
56 | return this.api.ads_percentage_today.toFixed(1); | 42 | return this.ads_percentage_today.toFixed(1); |
57 | } | 43 | } |
58 | return ""; | 44 | return ""; |
59 | }, | 45 | }, |
@@ -63,21 +49,16 @@ export default { | |||
63 | }, | 49 | }, |
64 | methods: { | 50 | methods: { |
65 | fetchStatus: async function () { | 51 | fetchStatus: async function () { |
66 | const url = `${this.item.url}/api.php`; | 52 | const result = await this.fetch("/api.php").catch((e) => console.log(e)); |
67 | this.api = await fetch(url, { | 53 | |
68 | credentials: "include", | 54 | this.status = result.status; |
69 | }) | 55 | this.ads_percentage_today = result.ads_percentage_today; |
70 | .then((response) => response.json()) | ||
71 | .catch((e) => console.log(e)); | ||
72 | }, | 56 | }, |
73 | }, | 57 | }, |
74 | }; | 58 | }; |
75 | </script> | 59 | </script> |
76 | 60 | ||
77 | <style scoped lang="scss"> | 61 | <style scoped lang="scss"> |
78 | .media-left img { | ||
79 | max-height: 100%; | ||
80 | } | ||
81 | .status { | 62 | .status { |
82 | font-size: 0.8rem; | 63 | font-size: 0.8rem; |
83 | color: var(--text-title); | 64 | color: var(--text-title); |
diff --git a/src/components/services/Portainer.vue b/src/components/services/Portainer.vue new file mode 100644 index 0000000..d101ecc --- /dev/null +++ b/src/components/services/Portainer.vue | |||
@@ -0,0 +1,139 @@ | |||
1 | <template> | ||
2 | <Generic :item="item"> | ||
3 | <template #indicator> | ||
4 | <div class="notifs"> | ||
5 | <strong v-if="running > 0" class="notif running" title="Running"> | ||
6 | {{ running }} | ||
7 | </strong> | ||
8 | <strong v-if="dead > 0" class="notif dead" title="Dead"> | ||
9 | {{ dead }} | ||
10 | </strong> | ||
11 | <strong | ||
12 | v-if="misc > 0" | ||
13 | class="notif misc" | ||
14 | title="Other (creating, paused, exited, etc.)" | ||
15 | > | ||
16 | {{ misc }} | ||
17 | </strong> | ||
18 | </div> | ||
19 | </template> | ||
20 | </Generic> | ||
21 | </template> | ||
22 | |||
23 | <script> | ||
24 | import service from "@/mixins/service.js"; | ||
25 | import Generic from "./Generic.vue"; | ||
26 | |||
27 | export default { | ||
28 | name: "Portainer", | ||
29 | mixins: [service], | ||
30 | props: { | ||
31 | item: Object, | ||
32 | }, | ||
33 | components: { | ||
34 | Generic, | ||
35 | }, | ||
36 | data: () => ({ | ||
37 | endpoints: null, | ||
38 | containers: null, | ||
39 | }), | ||
40 | computed: { | ||
41 | running: function () { | ||
42 | if (!this.containers) { | ||
43 | return ""; | ||
44 | } | ||
45 | return this.containers.filter((container) => { | ||
46 | return container.State.toLowerCase() === "running"; | ||
47 | }).length; | ||
48 | }, | ||
49 | dead: function () { | ||
50 | if (!this.containers) { | ||
51 | return ""; | ||
52 | } | ||
53 | return this.containers.filter((container) => { | ||
54 | return container.State.toLowerCase() === "dead"; | ||
55 | }).length; | ||
56 | }, | ||
57 | misc: function () { | ||
58 | if (!this.containers) { | ||
59 | return ""; | ||
60 | } | ||
61 | return this.containers.filter((container) => { | ||
62 | return ( | ||
63 | container.State.toLowerCase() !== "running" && | ||
64 | container.State.toLowerCase() !== "dead" | ||
65 | ); | ||
66 | }).length; | ||
67 | }, | ||
68 | }, | ||
69 | created() { | ||
70 | this.fetchStatus(); | ||
71 | }, | ||
72 | methods: { | ||
73 | fetchStatus: async function () { | ||
74 | const headers = { | ||
75 | "X-Api-Key": this.item.apikey, | ||
76 | }; | ||
77 | |||
78 | this.endpoints = await this.fetch("/api/endpoints", { headers }).catch( | ||
79 | (e) => { | ||
80 | console.error(e); | ||
81 | } | ||
82 | ); | ||
83 | |||
84 | let containers = []; | ||
85 | for (let endpoint of this.endpoints) { | ||
86 | if ( | ||
87 | this.item.environments && | ||
88 | !this.item.environments.includes(endpoint.Name) | ||
89 | ) { | ||
90 | continue; | ||
91 | } | ||
92 | const uri = `/api/endpoints/${endpoint.Id}/docker/containers/json?all=1`; | ||
93 | const endpointContainers = await this.fetch(uri, { headers }).catch( | ||
94 | (e) => { | ||
95 | console.error(e); | ||
96 | } | ||
97 | ); | ||
98 | |||
99 | if (endpointContainers) { | ||
100 | containers = containers.concat(endpointContainers); | ||
101 | } | ||
102 | } | ||
103 | |||
104 | this.containers = containers; | ||
105 | }, | ||
106 | }, | ||
107 | }; | ||
108 | </script> | ||
109 | |||
110 | <style scoped lang="scss"> | ||
111 | .notifs { | ||
112 | position: absolute; | ||
113 | color: white; | ||
114 | font-family: sans-serif; | ||
115 | top: 0.3em; | ||
116 | right: 0.5em; | ||
117 | |||
118 | .notif { | ||
119 | display: inline-block; | ||
120 | padding: 0.2em 0.35em; | ||
121 | border-radius: 0.25em; | ||
122 | position: relative; | ||
123 | margin-left: 0.3em; | ||
124 | font-size: 0.8em; | ||
125 | |||
126 | &.running { | ||
127 | background-color: #4fd671; | ||
128 | } | ||
129 | |||
130 | &.dead { | ||
131 | background-color: #e51111; | ||
132 | } | ||
133 | |||
134 | &.misc { | ||
135 | background-color: #2ed0c8; | ||
136 | } | ||
137 | } | ||
138 | } | ||
139 | </style> | ||
diff --git a/src/components/services/Prowlarr.vue b/src/components/services/Prowlarr.vue new file mode 100644 index 0000000..abaa0f0 --- /dev/null +++ b/src/components/services/Prowlarr.vue | |||
@@ -0,0 +1,94 @@ | |||
1 | <template> | ||
2 | <Generic :item="item"> | ||
3 | <template #indicator> | ||
4 | <div class="notifs"> | ||
5 | <strong v-if="warnings > 0" class="notif warnings" title="Warning"> | ||
6 | {{ warnings }} | ||
7 | </strong> | ||
8 | <strong v-if="errors > 0" class="notif errors" title="Error"> | ||
9 | {{ errors }} | ||
10 | </strong> | ||
11 | <strong | ||
12 | v-if="serverError" | ||
13 | class="notif errors" | ||
14 | title="Connection error to Prowlarr API, check url and apikey in config.yml" | ||
15 | > | ||
16 | ? | ||
17 | </strong> | ||
18 | </div> | ||
19 | </template> | ||
20 | </Generic> | ||
21 | </template> | ||
22 | |||
23 | <script> | ||
24 | import service from "@/mixins/service.js"; | ||
25 | import Generic from "./Generic.vue"; | ||
26 | |||
27 | export default { | ||
28 | name: "Prowlarr", | ||
29 | mixins: [service], | ||
30 | props: { | ||
31 | item: Object, | ||
32 | }, | ||
33 | components: { | ||
34 | Generic, | ||
35 | }, | ||
36 | data: () => { | ||
37 | return { | ||
38 | warnings: null, | ||
39 | errors: null, | ||
40 | serverError: false, | ||
41 | }; | ||
42 | }, | ||
43 | created: function () { | ||
44 | this.fetchConfig(); | ||
45 | }, | ||
46 | methods: { | ||
47 | fetchConfig: function () { | ||
48 | this.fetch(`/api/v1/health?apikey=${this.item.apikey}`) | ||
49 | .then((health) => { | ||
50 | this.warnings = 0; | ||
51 | this.errors = 0; | ||
52 | for (var i = 0; i < health.length; i++) { | ||
53 | if (health[i].type == "warning") { | ||
54 | this.warnings++; | ||
55 | } else if (health[i].type == "error") { | ||
56 | this.errors++; | ||
57 | } | ||
58 | } | ||
59 | }) | ||
60 | .catch((e) => { | ||
61 | console.error(e); | ||
62 | this.serverError = true; | ||
63 | }); | ||
64 | }, | ||
65 | }, | ||
66 | }; | ||
67 | </script> | ||
68 | |||
69 | <style scoped lang="scss"> | ||
70 | .notifs { | ||
71 | position: absolute; | ||
72 | color: white; | ||
73 | font-family: sans-serif; | ||
74 | top: 0.3em; | ||
75 | right: 0.5em; | ||
76 | |||
77 | .notif { | ||
78 | display: inline-block; | ||
79 | padding: 0.2em 0.35em; | ||
80 | border-radius: 0.25em; | ||
81 | position: relative; | ||
82 | margin-left: 0.3em; | ||
83 | font-size: 0.8em; | ||
84 | |||
85 | &.warnings { | ||
86 | background-color: #d08d2e; | ||
87 | } | ||
88 | |||
89 | &.errors { | ||
90 | background-color: #e51111; | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | </style> | ||
diff --git a/src/components/services/Radarr.vue b/src/components/services/Radarr.vue index a9cdedf..5c40aaa 100644 --- a/src/components/services/Radarr.vue +++ b/src/components/services/Radarr.vue | |||
@@ -1,62 +1,43 @@ | |||
1 | <template> | 1 | <template> |
2 | <div> | 2 | <Generic :item="item"> |
3 | <div class="card" :class="item.class"> | 3 | <template #indicator> |
4 | <a :href="item.url" :target="item.target" rel="noreferrer"> | 4 | <div class="notifs"> |
5 | <div class="card-content"> | 5 | <strong v-if="activity > 0" class="notif activity" title="Activity"> |
6 | <div class="media"> | 6 | {{ activity }} |
7 | <div v-if="item.logo" class="media-left"> | 7 | </strong> |
8 | <figure class="image is-48x48"> | 8 | <strong v-if="warnings > 0" class="notif warnings" title="Warning"> |
9 | <img :src="item.logo" :alt="`${item.name} logo`" /> | 9 | {{ warnings }} |
10 | </figure> | 10 | </strong> |
11 | </div> | 11 | <strong v-if="errors > 0" class="notif errors" title="Error"> |
12 | <div v-if="item.icon" class="media-left"> | 12 | {{ errors }} |
13 | <figure class="image is-48x48"> | 13 | </strong> |
14 | <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> | 14 | <strong |
15 | </figure> | 15 | v-if="serverError" |
16 | </div> | 16 | class="notif errors" |
17 | <div class="media-content"> | 17 | title="Connection error to Radarr API, check url and apikey in config.yml" |
18 | <p class="title is-4">{{ item.name }}</p> | 18 | >?</strong |
19 | <p class="subtitle is-6">{{ item.subtitle }}</p> | 19 | > |
20 | </div> | 20 | </div> |
21 | <div class="notifs"> | 21 | </template> |
22 | <strong | 22 | </Generic> |
23 | v-if="activity > 0" | ||
24 | class="notif activity" | ||
25 | title="Activity" | ||
26 | >{{ activity }}</strong | ||
27 | > | ||
28 | <strong | ||
29 | v-if="warnings > 0" | ||
30 | class="notif warnings" | ||
31 | title="Warning" | ||
32 | >{{ warnings }}</strong | ||
33 | > | ||
34 | <strong v-if="errors > 0" class="notif errors" title="Error">{{ | ||
35 | errors | ||
36 | }}</strong> | ||
37 | <strong | ||
38 | v-if="serverError" | ||
39 | class="notif errors" | ||
40 | title="Connection error to Radarr API, check url and apikey in config.yml" | ||
41 | >?</strong | ||
42 | > | ||
43 | </div> | ||
44 | </div> | ||
45 | <div class="tag" :class="item.tagstyle" v-if="item.tag"> | ||
46 | <strong class="tag-text">#{{ item.tag }}</strong> | ||
47 | </div> | ||
48 | </div> | ||
49 | </a> | ||
50 | </div> | ||
51 | </div> | ||
52 | </template> | 23 | </template> |
53 | 24 | ||
54 | <script> | 25 | <script> |
26 | import service from "@/mixins/service.js"; | ||
27 | import Generic from "./Generic.vue"; | ||
28 | |||
29 | const V3_API = "/api/v3"; | ||
30 | const LEGACY_API = "/api"; | ||
31 | |||
55 | export default { | 32 | export default { |
56 | name: "Radarr", | 33 | name: "Radarr", |
34 | mixins: [service], | ||
57 | props: { | 35 | props: { |
58 | item: Object, | 36 | item: Object, |
59 | }, | 37 | }, |
38 | components: { | ||
39 | Generic, | ||
40 | }, | ||
60 | data: () => { | 41 | data: () => { |
61 | return { | 42 | return { |
62 | activity: null, | 43 | activity: null, |
@@ -68,17 +49,14 @@ export default { | |||
68 | created: function () { | 49 | created: function () { |
69 | this.fetchConfig(); | 50 | this.fetchConfig(); |
70 | }, | 51 | }, |
52 | computed: { | ||
53 | apiPath() { | ||
54 | return this.item.legacyApi ? LEGACY_API : V3_API; | ||
55 | }, | ||
56 | }, | ||
71 | methods: { | 57 | methods: { |
72 | fetchConfig: function () { | 58 | fetchConfig: function () { |
73 | fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, { | 59 | this.fetch(`${this.apiPath}/health?apikey=${this.item.apikey}`) |
74 | credentials: "include", | ||
75 | }) | ||
76 | .then((response) => { | ||
77 | if (response.status != 200) { | ||
78 | throw new Error(response.statusText); | ||
79 | } | ||
80 | return response.json(); | ||
81 | }) | ||
82 | .then((health) => { | 60 | .then((health) => { |
83 | this.warnings = 0; | 61 | this.warnings = 0; |
84 | this.errors = 0; | 62 | this.errors = 0; |
@@ -94,21 +72,18 @@ export default { | |||
94 | console.error(e); | 72 | console.error(e); |
95 | this.serverError = true; | 73 | this.serverError = true; |
96 | }); | 74 | }); |
97 | fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, { | 75 | this.fetch(`${this.apiPath}/queue?apikey=${this.item.apikey}`) |
98 | credentials: "include", | ||
99 | }) | ||
100 | .then((response) => { | ||
101 | if (response.status != 200) { | ||
102 | throw new Error(response.statusText); | ||
103 | } | ||
104 | return response.json(); | ||
105 | }) | ||
106 | .then((queue) => { | 76 | .then((queue) => { |
107 | this.activity = 0; | 77 | this.activity = 0; |
108 | for (var i = 0; i < queue.length; i++) { | 78 | |
109 | if (queue[i].movie) { | 79 | if (this.item.legacyApi) { |
110 | this.activity++; | 80 | for (var i = 0; i < queue.length; i++) { |
81 | if (queue[i].movie) { | ||
82 | this.activity++; | ||
83 | } | ||
111 | } | 84 | } |
85 | } else { | ||
86 | this.activity = queue.totalRecords; | ||
112 | } | 87 | } |
113 | }) | 88 | }) |
114 | .catch((e) => { | 89 | .catch((e) => { |
@@ -121,35 +96,30 @@ export default { | |||
121 | </script> | 96 | </script> |
122 | 97 | ||
123 | <style scoped lang="scss"> | 98 | <style scoped lang="scss"> |
124 | .media-left img { | ||
125 | max-height: 100%; | ||
126 | } | ||
127 | .notifs { | 99 | .notifs { |
128 | position: absolute; | 100 | position: absolute; |
129 | color: white; | 101 | color: white; |
130 | font-family: sans-serif; | 102 | font-family: sans-serif; |
131 | top: 0.3em; | 103 | top: 0.3em; |
132 | right: 0.5em; | 104 | right: 0.5em; |
133 | } | 105 | .notif { |
134 | .notif { | 106 | display: inline-block; |
135 | padding-right: 0.35em; | 107 | padding: 0.2em 0.35em; |
136 | padding-left: 0.35em; | 108 | border-radius: 0.25em; |
137 | padding-top: 0.2em; | 109 | position: relative; |
138 | padding-bottom: 0.2em; | 110 | margin-left: 0.3em; |
139 | border-radius: 0.25em; | 111 | font-size: 0.8em; |
140 | position: relative; | 112 | &.activity { |
141 | margin-left: 0.3em; | 113 | background-color: #4fb5d6; |
142 | font-size: 0.8em; | 114 | } |
143 | } | ||
144 | .activity { | ||
145 | background-color: #4fb5d6; | ||
146 | } | ||
147 | 115 | ||
148 | .warnings { | 116 | &.warnings { |
149 | background-color: #d08d2e; | 117 | background-color: #d08d2e; |
150 | } | 118 | } |
151 | 119 | ||
152 | .errors { | 120 | &.errors { |
153 | background-color: #e51111; | 121 | background-color: #e51111; |
122 | } | ||
123 | } | ||
154 | } | 124 | } |
155 | </style> | 125 | </style> |
diff --git a/src/components/services/Sonarr.vue b/src/components/services/Sonarr.vue index 0270255..bb83b6b 100644 --- a/src/components/services/Sonarr.vue +++ b/src/components/services/Sonarr.vue | |||
@@ -1,62 +1,49 @@ | |||
1 | <template> | 1 | <template> |
2 | <div> | 2 | <Generic :item="item"> |
3 | <div class="card" :class="item.class"> | 3 | <template #indicator> |
4 | <a :href="item.url" :target="item.target" rel="noreferrer"> | 4 | <div class="notifs"> |
5 | <div class="card-content"> | 5 | <strong v-if="activity > 0" class="notif activity" title="Activity"> |
6 | <div class="media"> | 6 | {{ activity }} |
7 | <div v-if="item.logo" class="media-left"> | 7 | </strong> |
8 | <figure class="image is-48x48"> | 8 | <strong v-if="warnings > 0" class="notif warnings" title="Warning"> |
9 | <img :src="item.logo" :alt="`${item.name} logo`" /> | 9 | {{ warnings }} |
10 | </figure> | 10 | </strong> |
11 | </div> | 11 | <strong v-if="errors > 0" class="notif errors" title="Error"> |
12 | <div v-if="item.icon" class="media-left"> | 12 | {{ errors }} |
13 | <figure class="image is-48x48"> | 13 | </strong> |
14 | <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i> | 14 | <strong |
15 | </figure> | 15 | v-if="serverError" |
16 | </div> | 16 | class="notif errors" |
17 | <div class="media-content"> | 17 | title="Connection error to Sonarr API, check url and apikey in config.yml" |
18 | <p class="title is-4">{{ item.name }}</p> | 18 | > |
19 | <p class="subtitle is-6">{{ item.subtitle }}</p> | 19 | ? |
20 | </div> | 20 | </strong> |
21 | <div class="notifs"> | 21 | </div> |
22 | <strong | 22 | </template> |
23 | v-if="activity > 0" | 23 | </Generic> |
24 | class="notif activity" | ||
25 | title="Activity" | ||
26 | >{{ activity }}</strong | ||
27 | > | ||
28 | <strong | ||
29 | v-if="warnings > 0" | ||
30 | class="notif warnings" | ||
31 | title="Warning" | ||
32 | >{{ warnings }}</strong | ||
33 | > | ||
34 | <strong v-if="errors > 0" class="notif errors" title="Error">{{ | ||
35 | errors | ||
36 | }}</strong> | ||
37 | <strong | ||
38 | v-if="serverError" | ||
39 | class="notif errors" | ||
40 | title="Connection error to Sonarr API, check url and apikey in config.yml" | ||
41 | >?</strong | ||
42 | > | ||
43 | </div> | ||
44 | </div> | ||
45 | <div class="tag" :class="item.tagstyle" v-if="item.tag"> | ||
46 | <strong class="tag-text">#{{ item.tag }}</strong> | ||
47 | </div> | ||
48 | </div> | ||
49 | </a> | ||
50 | </div> | ||
51 | </div> | ||
52 | </template> | 24 | </template> |
53 | 25 | ||
54 | <script> | 26 | <script> |
27 | import service from "@/mixins/service.js"; | ||
28 | import Generic from "./Generic.vue"; | ||
29 | |||
30 | const V3_API = "/api/v3"; | ||
31 | const LEGACY_API = "/api"; | ||
32 | |||
55 | export default { | 33 | export default { |
56 | name: "Sonarr", | 34 | name: "Sonarr", |
35 | mixins: [service], | ||
57 | props: { | 36 | props: { |
58 | item: Object, | 37 | item: Object, |
59 | }, | 38 | }, |
39 | components: { | ||
40 | Generic, | ||
41 | }, | ||
42 | computed: { | ||
43 | apiPath() { | ||
44 | return this.item.legacyApi ? LEGACY_API : V3_API; | ||
45 | }, | ||
46 | }, | ||
60 | data: () => { | 47 | data: () => { |
61 | return { | 48 | return { |
62 | activity: null, | 49 | activity: null, |
@@ -70,15 +57,7 @@ export default { | |||
70 | }, | 57 | }, |
71 | methods: { | 58 | methods: { |
72 | fetchConfig: function () { | 59 | fetchConfig: function () { |
73 | fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, { | 60 | this.fetch(`${this.apiPath}/health?apikey=${this.item.apikey}`) |
74 | credentials: "include", | ||
75 | }) | ||
76 | .then((response) => { | ||
77 | if (response.status != 200) { | ||
78 | throw new Error(response.statusText); | ||
79 | } | ||
80 | return response.json(); | ||
81 | }) | ||
82 | .then((health) => { | 61 | .then((health) => { |
83 | this.warnings = 0; | 62 | this.warnings = 0; |
84 | this.errors = 0; | 63 | this.errors = 0; |
@@ -94,21 +73,17 @@ export default { | |||
94 | console.error(e); | 73 | console.error(e); |
95 | this.serverError = true; | 74 | this.serverError = true; |
96 | }); | 75 | }); |
97 | fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, { | 76 | this.fetch(`${this.apiPath}/queue?apikey=${this.item.apikey}`) |
98 | credentials: "include", | ||
99 | }) | ||
100 | .then((response) => { | ||
101 | if (response.status != 200) { | ||
102 | throw new Error(response.statusText); | ||
103 | } | ||
104 | return response.json(); | ||
105 | }) | ||
106 | .then((queue) => { | 77 | .then((queue) => { |
107 | this.activity = 0; | 78 | this.activity = 0; |
108 | for (var i = 0; i < queue.length; i++) { | 79 | if (this.item.legacyApi) { |
109 | if (queue[i].series) { | 80 | for (var i = 0; i < queue.length; i++) { |
110 | this.activity++; | 81 | if (queue[i].series) { |
82 | this.activity++; | ||
83 | } | ||
111 | } | 84 | } |
85 | } else { | ||
86 | this.activity = queue.totalRecords; | ||
112 | } | 87 | } |
113 | }) | 88 | }) |
114 | .catch((e) => { | 89 | .catch((e) => { |
@@ -121,35 +96,32 @@ export default { | |||
121 | </script> | 96 | </script> |
122 | 97 | ||
123 | <style scoped lang="scss"> | 98 | <style scoped lang="scss"> |
124 | .media-left img { | ||
125 | max-height: 100%; | ||
126 | } | ||
127 | .notifs { | 99 | .notifs { |
128 | position: absolute; | 100 | position: absolute; |
129 | color: white; | 101 | color: white; |
130 | font-family: sans-serif; | 102 | font-family: sans-serif; |
131 | top: 0.3em; | 103 | top: 0.3em; |
132 | right: 0.5em; | 104 | right: 0.5em; |
133 | } | ||
134 | .notif { | ||
135 | padding-right: 0.35em; | ||
136 | padding-left: 0.35em; | ||
137 | padding-top: 0.2em; | ||
138 | padding-bottom: 0.2em; | ||
139 | border-radius: 0.25em; | ||
140 | position: relative; | ||
141 | margin-left: 0.3em; | ||
142 | font-size: 0.8em; | ||
143 | } | ||
144 | .activity { | ||
145 | background-color: #4fb5d6; | ||
146 | } | ||
147 | 105 | ||
148 | .warnings { | 106 | .notif { |
149 | background-color: #d08d2e; | 107 | display: inline-block; |
150 | } | 108 | padding: 0.2em 0.35em; |
109 | border-radius: 0.25em; | ||
110 | position: relative; | ||
111 | margin-left: 0.3em; | ||
112 | font-size: 0.8em; | ||
113 | |||
114 | &.activity { | ||
115 | background-color: #4fb5d6; | ||
116 | } | ||
117 | |||
118 | &.warnings { | ||
119 | background-color: #d08d2e; | ||
120 | } | ||
151 | 121 | ||
152 | .errors { | 122 | &.errors { |
153 | background-color: #e51111; | 123 | background-color: #e51111; |
124 | } | ||
125 | } | ||
154 | } | 126 | } |
155 | </style> | 127 | </style> |