aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets/app.scss11
-rw-r--r--src/components/ConnectivityChecker.vue4
-rw-r--r--src/components/Message.vue2
-rw-r--r--src/components/services/AdGuardHome.vue6
-rw-r--r--src/components/services/Medusa.vue128
-rw-r--r--src/components/services/PaperlessNG.vue83
-rw-r--r--src/components/services/PiHole.vue4
-rw-r--r--src/components/services/Ping.vue98
-rw-r--r--src/components/services/Radarr.vue151
-rw-r--r--src/components/services/Sonarr.vue151
10 files changed, 625 insertions, 13 deletions
diff --git a/src/assets/app.scss b/src/assets/app.scss
index 52d8857..bdbe441 100644
--- a/src/assets/app.scss
+++ b/src/assets/app.scss
@@ -213,7 +213,7 @@ body {
213 color: var(--highlight-secondary); 213 color: var(--highlight-secondary);
214 background-color: var(--highlight-secondary); 214 background-color: var(--highlight-secondary);
215 position: absolute; 215 position: absolute;
216 top: 1rem; 216 bottom: 1rem;
217 right: -0.2rem; 217 right: -0.2rem;
218 width: 3px; 218 width: 3px;
219 overflow: hidden; 219 overflow: hidden;
@@ -226,7 +226,6 @@ body {
226 } 226 }
227 227
228 .card { 228 .card {
229 border-radius: 5px;
230 border: none; 229 border: none;
231 box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1); 230 box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1);
232 transition: cubic-bezier(0.165, 0.84, 0.44, 1) 300ms; 231 transition: cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
@@ -262,11 +261,13 @@ body {
262 } 261 }
263 262
264 .column div:first-of-type .card { 263 .column div:first-of-type .card {
265 border-radius: 5px 5px 0 0; 264 border-top-left-radius: 0.25rem;
265 border-top-right-radius: 0.25rem;
266 } 266 }
267 267
268 .column div:last-child .card { 268 .column div:last-child .card {
269 border-radius: 0 0 5px 5px; 269 border-bottom-left-radius: 0.25rem;
270 border-bottom-right-radius: 0.25rem;
270 } 271 }
271 } 272 }
272 273
@@ -350,4 +351,4 @@ body {
350 351
351.group-logo { 352.group-logo {
352 float: left; 353 float: left;
353} \ No newline at end of file 354}
diff --git a/src/components/ConnectivityChecker.vue b/src/components/ConnectivityChecker.vue
index d41c443..02cbd7f 100644
--- a/src/components/ConnectivityChecker.vue
+++ b/src/components/ConnectivityChecker.vue
@@ -37,8 +37,8 @@ export default {
37 method: "HEAD", 37 method: "HEAD",
38 cache: "no-store", 38 cache: "no-store",
39 }) 39 })
40 .then(function () { 40 .then(function (response) {
41 that.offline = false; 41 that.offline = !response.ok;
42 }) 42 })
43 .catch(function () { 43 .catch(function () {
44 that.offline = true; 44 that.offline = true;
diff --git a/src/components/Message.vue b/src/components/Message.vue
index 6cc649a..00ce158 100644
--- a/src/components/Message.vue
+++ b/src/components/Message.vue
@@ -54,7 +54,7 @@ export default {
54 54
55 // keep the original config value if no value is provided by the endpoint 55 // keep the original config value if no value is provided by the endpoint
56 const message = this.message; 56 const message = this.message;
57 for (const prop of ["title", "style", "content"]) { 57 for (const prop of ["title", "style", "content", "icon"]) {
58 if (prop in fetchedMessage && fetchedMessage[prop] !== null) { 58 if (prop in fetchedMessage && fetchedMessage[prop] !== null) {
59 message[prop] = fetchedMessage[prop]; 59 message[prop] = fetchedMessage[prop];
60 } 60 }
diff --git a/src/components/services/AdGuardHome.vue b/src/components/services/AdGuardHome.vue
index 6ef5302..d4a2b89 100644
--- a/src/components/services/AdGuardHome.vue
+++ b/src/components/services/AdGuardHome.vue
@@ -51,9 +51,9 @@ export default {
51 }, 51 },
52 methods: { 52 methods: {
53 fetchStatus: async function () { 53 fetchStatus: async function () {
54 this.status = await fetch( 54 this.status = await fetch(`${this.item.url}/control/status`).then(
55 `${this.item.url}/control/status` 55 (response) => response.json()
56 ).then((response) => response.json()); 56 );
57 }, 57 },
58 }, 58 },
59}; 59};
diff --git a/src/components/services/Medusa.vue b/src/components/services/Medusa.vue
new file mode 100644
index 0000000..5720649
--- /dev/null
+++ b/src/components/services/Medusa.vue
@@ -0,0 +1,128 @@
1<template>
2 <div>
3 <div class="card" :class="item.class">
4 <a :href="item.url" :target="item.target" rel="noreferrer">
5 <div class="card-content">
6 <div class="media">
7 <div v-if="item.logo" class="media-left">
8 <figure class="image is-48x48">
9 <img :src="item.logo" :alt="`${item.name} logo`" />
10 </figure>
11 </div>
12 <div v-if="item.icon" class="media-left">
13 <figure class="image is-48x48">
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
15 </figure>
16 </div>
17 <div class="media-content">
18 <p class="title is-4">{{ item.name }}</p>
19 <p class="subtitle is-6">{{ item.subtitle }}</p>
20 </div>
21 <div class="notifs">
22 <strong
23 v-if="config !== null && config.system.news.unread > 0"
24 class="notif news"
25 title="News"
26 >{{ config.system.news.unread }}</strong
27 >
28 <strong
29 v-if="config !== null && config.main.logs.numWarnings > 0"
30 class="notif warnings"
31 title="Warning"
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>
56
57<script>
58export default {
59 name: "Medusa",
60 props: {
61 item: Object,
62 },
63 data: () => {
64 return {
65 config: null,
66 serverError: false,
67 };
68 },
69 created: function () {
70 this.fetchConfig();
71 },
72 methods: {
73 fetchConfig: function () {
74 fetch(`${this.item.url}/api/v2/config`, {
75 credentials: "include",
76 headers: { "X-Api-Key": `${this.item.apikey}` },
77 })
78 .then((response) => {
79 if (response.status != 200) {
80 throw new Error(response.statusText);
81 }
82 return response.json();
83 })
84 .then((conf) => {
85 this.config = conf;
86 })
87 .catch((e) => {
88 console.log(e);
89 this.serverError = true;
90 });
91 },
92 },
93};
94</script>
95
96<style scoped lang="scss">
97.media-left img {
98 max-height: 100%;
99}
100.notifs {
101 position: absolute;
102 color: white;
103 font-family: sans-serif;
104 top: 0.3em;
105 right: 0.5em;
106}
107.notif {
108 padding-right: 0.35em;
109 padding-left: 0.35em;
110 padding-top: 0.2em;
111 padding-bottom: 0.2em;
112 border-radius: 0.25em;
113 position: relative;
114 margin-left: 0.3em;
115 font-size: 0.8em;
116}
117.news {
118 background-color: #777777;
119}
120
121.warnings {
122 background-color: #d08d2e;
123}
124
125.errors {
126 background-color: #e51111;
127}
128</style>
diff --git a/src/components/services/PaperlessNG.vue b/src/components/services/PaperlessNG.vue
new file mode 100644
index 0000000..4fb31f8
--- /dev/null
+++ b/src/components/services/PaperlessNG.vue
@@ -0,0 +1,83 @@
1<template>
2 <div>
3 <div class="card" :class="item.class">
4 <a :href="item.url" :target="item.target" rel="noreferrer">
5 <div class="card-content">
6 <div class="media">
7 <div v-if="item.logo" class="media-left">
8 <figure class="image is-48x48">
9 <img :src="item.logo" :alt="`${item.name} logo`" />
10 </figure>
11 </div>
12 <div v-if="item.icon" class="media-left">
13 <figure class="image is-48x48">
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
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>
37
38<script>
39export default {
40 name: "Paperless",
41 props: {
42 item: Object,
43 },
44 data: () => ({
45 api: null,
46 }),
47 created() {
48 this.fetchStatus();
49 },
50 methods: {
51 fetchStatus: async function () {
52 if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing
53 var apikey = this.item.apikey;
54 if (!apikey) {
55 console.error(
56 "apikey is not present in config.yml for the paperless entry!"
57 );
58 return;
59 }
60 const url = `${this.item.url}/api/documents/`;
61 this.api = await fetch(url, {
62 headers: {
63 Authorization: "Token " + this.item.apikey,
64 },
65 })
66 .then(function (response) {
67 if (!response.ok) {
68 throw new Error("Not 2xx response");
69 } else {
70 return response.json();
71 }
72 })
73 .catch((e) => console.log(e));
74 },
75 },
76};
77</script>
78
79<style scoped lang="scss">
80.media-left img {
81 max-height: 100%;
82}
83</style>
diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue
index a9fd369..7042a7b 100644
--- a/src/components/services/PiHole.vue
+++ b/src/components/services/PiHole.vue
@@ -83,13 +83,13 @@ export default {
83 &.enabled:before { 83 &.enabled:before {
84 background-color: #94e185; 84 background-color: #94e185;
85 border-color: #78d965; 85 border-color: #78d965;
86 box-shadow: 0 0 4px 1px #94e185; 86 box-shadow: 0 0 5px 1px #94e185;
87 } 87 }
88 88
89 &.disabled:before { 89 &.disabled:before {
90 background-color: #c9404d; 90 background-color: #c9404d;
91 border-color: #c42c3b; 91 border-color: #c42c3b;
92 box-shadow: 0 0 4px 1px #c9404d; 92 box-shadow: 0 0 5px 1px #c9404d;
93 } 93 }
94 94
95 &:before { 95 &:before {
diff --git a/src/components/services/Ping.vue b/src/components/services/Ping.vue
new file mode 100644
index 0000000..8a9b7a4
--- /dev/null
+++ b/src/components/services/Ping.vue
@@ -0,0 +1,98 @@
1<template>
2 <div>
3 <div class="card" :class="item.class">
4 <a :href="item.url" :target="item.target" rel="noreferrer">
5 <div class="card-content">
6 <div class="media">
7 <div v-if="item.logo" class="media-left">
8 <figure class="image is-48x48">
9 <img :src="item.logo" :alt="`${item.name} logo`" />
10 </figure>
11 </div>
12 <div v-if="item.icon" class="media-left">
13 <figure class="image is-48x48">
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
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 </p>
24 </div>
25 <div v-if="status" class="status" :class="status">
26 {{ status }}
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>
37
38<script>
39export default {
40 name: "Ping",
41 props: {
42 item: Object,
43 },
44 data: () => ({
45 status: null,
46 }),
47 created() {
48 this.fetchStatus();
49 },
50 methods: {
51 fetchStatus: async function () {
52 const url = `${this.item.url}`;
53 fetch(url, { method: "HEAD", cache: "no-cache" })
54 .then((response) => {
55 if (!response.ok) {
56 throw Error(response.statusText);
57 }
58 this.status = "online";
59 })
60 .catch(() => {
61 this.status = "offline";
62 });
63 },
64 },
65};
66</script>
67
68<style scoped lang="scss">
69.media-left img {
70 max-height: 100%;
71}
72.status {
73 font-size: 0.8rem;
74 color: var(--text-title);
75
76 &.online:before {
77 background-color: #94e185;
78 border-color: #78d965;
79 box-shadow: 0 0 5px 1px #94e185;
80 }
81
82 &.offline:before {
83 background-color: #c9404d;
84 border-color: #c42c3b;
85 box-shadow: 0 0 5px 1px #c9404d;
86 }
87
88 &:before {
89 content: " ";
90 display: inline-block;
91 width: 7px;
92 height: 7px;
93 margin-right: 10px;
94 border: 1px solid #000;
95 border-radius: 7px;
96 }
97}
98</style>
diff --git a/src/components/services/Radarr.vue b/src/components/services/Radarr.vue
new file mode 100644
index 0000000..9d38292
--- /dev/null
+++ b/src/components/services/Radarr.vue
@@ -0,0 +1,151 @@
1<template>
2 <div>
3 <div class="card" :class="item.class">
4 <a :href="item.url" :target="item.target" rel="noreferrer">
5 <div class="card-content">
6 <div class="media">
7 <div v-if="item.logo" class="media-left">
8 <figure class="image is-48x48">
9 <img :src="item.logo" :alt="`${item.name} logo`" />
10 </figure>
11 </div>
12 <div v-if="item.icon" class="media-left">
13 <figure class="image is-48x48">
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
15 </figure>
16 </div>
17 <div class="media-content">
18 <p class="title is-4">{{ item.name }}</p>
19 <p class="subtitle is-6">{{ item.subtitle }}</p>
20 </div>
21 <div class="notifs">
22 <strong
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>
53
54<script>
55export default {
56 name: "Radarr",
57 props: {
58 item: Object,
59 },
60 data: () => {
61 return {
62 activity: null,
63 warnings: null,
64 errors: null,
65 serverError: false,
66 };
67 },
68 created: function () {
69 this.fetchConfig();
70 },
71 methods: {
72 fetchConfig: function () {
73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`)
74 .then((response) => {
75 if (response.status != 200) {
76 throw new Error(response.statusText);
77 }
78 return response.json();
79 })
80 .then((health) => {
81 this.warnings = 0;
82 this.errors = 0;
83 for (var i = 0; i < health.length; i++) {
84 if (health[i].type == "warning") {
85 this.warnings++;
86 } else if (health[i].type == "error") {
87 this.errors++;
88 }
89 }
90 })
91 .catch((e) => {
92 console.error(e);
93 this.serverError = true;
94 });
95 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`)
96 .then((response) => {
97 if (response.status != 200) {
98 throw new Error(response.statusText);
99 }
100 return response.json();
101 })
102 .then((queue) => {
103 this.activity = 0;
104 for (var i = 0; i < queue.length; i++) {
105 if (queue[i].movie) {
106 this.activity++;
107 }
108 }
109 })
110 .catch((e) => {
111 console.error(e);
112 this.serverError = true;
113 });
114 },
115 },
116};
117</script>
118
119<style scoped lang="scss">
120.media-left img {
121 max-height: 100%;
122}
123.notifs {
124 position: absolute;
125 color: white;
126 font-family: sans-serif;
127 top: 0.3em;
128 right: 0.5em;
129}
130.notif {
131 padding-right: 0.35em;
132 padding-left: 0.35em;
133 padding-top: 0.2em;
134 padding-bottom: 0.2em;
135 border-radius: 0.25em;
136 position: relative;
137 margin-left: 0.3em;
138 font-size: 0.8em;
139}
140.activity {
141 background-color: #4fb5d6;
142}
143
144.warnings {
145 background-color: #d08d2e;
146}
147
148.errors {
149 background-color: #e51111;
150}
151</style>
diff --git a/src/components/services/Sonarr.vue b/src/components/services/Sonarr.vue
new file mode 100644
index 0000000..7851b6b
--- /dev/null
+++ b/src/components/services/Sonarr.vue
@@ -0,0 +1,151 @@
1<template>
2 <div>
3 <div class="card" :class="item.class">
4 <a :href="item.url" :target="item.target" rel="noreferrer">
5 <div class="card-content">
6 <div class="media">
7 <div v-if="item.logo" class="media-left">
8 <figure class="image is-48x48">
9 <img :src="item.logo" :alt="`${item.name} logo`" />
10 </figure>
11 </div>
12 <div v-if="item.icon" class="media-left">
13 <figure class="image is-48x48">
14 <i style="font-size: 35px" :class="['fa-fw', item.icon]"></i>
15 </figure>
16 </div>
17 <div class="media-content">
18 <p class="title is-4">{{ item.name }}</p>
19 <p class="subtitle is-6">{{ item.subtitle }}</p>
20 </div>
21 <div class="notifs">
22 <strong
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 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>
53
54<script>
55export default {
56 name: "Sonarr",
57 props: {
58 item: Object,
59 },
60 data: () => {
61 return {
62 activity: null,
63 warnings: null,
64 errors: null,
65 serverError: false,
66 };
67 },
68 created: function () {
69 this.fetchConfig();
70 },
71 methods: {
72 fetchConfig: function () {
73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`)
74 .then((response) => {
75 if (response.status != 200) {
76 throw new Error(response.statusText);
77 }
78 return response.json();
79 })
80 .then((health) => {
81 this.warnings = 0;
82 this.errors = 0;
83 for (var i = 0; i < health.length; i++) {
84 if (health[i].type == "warning") {
85 this.warnings++;
86 } else if (health[i].type == "error") {
87 this.errors++;
88 }
89 }
90 })
91 .catch((e) => {
92 console.error(e);
93 this.serverError = true;
94 });
95 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`)
96 .then((response) => {
97 if (response.status != 200) {
98 throw new Error(response.statusText);
99 }
100 return response.json();
101 })
102 .then((queue) => {
103 this.activity = 0;
104 for (var i = 0; i < queue.length; i++) {
105 if (queue[i].series) {
106 this.activity++;
107 }
108 }
109 })
110 .catch((e) => {
111 console.error(e);
112 this.serverError = true;
113 });
114 },
115 },
116};
117</script>
118
119<style scoped lang="scss">
120.media-left img {
121 max-height: 100%;
122}
123.notifs {
124 position: absolute;
125 color: white;
126 font-family: sans-serif;
127 top: 0.3em;
128 right: 0.5em;
129}
130.notif {
131 padding-right: 0.35em;
132 padding-left: 0.35em;
133 padding-top: 0.2em;
134 padding-bottom: 0.2em;
135 border-radius: 0.25em;
136 position: relative;
137 margin-left: 0.3em;
138 font-size: 0.8em;
139}
140.activity {
141 background-color: #4fb5d6;
142}
143
144.warnings {
145 background-color: #d08d2e;
146}
147
148.errors {
149 background-color: #e51111;
150}
151</style>