Compare commits

...

198 Commits

Author SHA1 Message Date
a4c1f6a37d Added forwarder binding to Service.vue 2022-04-28 20:08:42 -04:00
76d30be8e3 Pass new forwarder config to services, give it a default value of empty 2022-04-19 20:37:38 -04:00
240e3f0e87 Changed config and header names as per @bastienwirtz feedback 2022-04-19 20:24:51 -04:00
33f75a798a Merge branch 'main' into proxy-api 2022-04-19 20:18:39 -04:00
9c370d3c5e Merge pull request #321 from Darkham42/feature/adguard-home-customservices-doc
doc: add doc for AdGuard Home
2022-04-07 22:33:58 +02:00
7341d7634b Merge branch 'main' into feature/adguard-home-customservices-doc 2022-04-07 22:33:20 +02:00
b2a4140054 Improve with @bastienwirtz comments 2022-04-07 22:06:26 +02:00
9e1e82b0f3 Merge pull request #402 from Zareix/feature/portainer-environments
Portainer service - Select environments
2022-04-07 21:45:32 +02:00
000a46ee88 Merge pull request #410 from espilioto/main
Emby integration
2022-04-07 21:27:37 +02:00
1275a8cce5 Update src/components/services/Emby.vue
Co-authored-by: Bastien Wirtz <bastien.wirtz@gmail.com>
2022-04-01 00:07:02 +03:00
cd1fc28f51 Removed await from api call 2022-03-31 23:55:32 +03:00
5c42d50d47 No authentication required for public endpoint 2022-03-31 23:54:26 +03:00
31027f4791 Merge pull request #409 from bastienwirtz/dependabot/npm_and_yarn/minimist-1.2.6
Bump minimist from 1.2.5 to 1.2.6
2022-03-31 21:59:47 +02:00
abfe72b9cf Fixed yarn lint errors 2022-03-29 11:39:15 +03:00
6dc8fa2026 Update customservices.md 2022-03-28 20:42:06 +03:00
345dd6c194 Update customservices.md 2022-03-28 20:41:19 +03:00
585844394d Initial Emby service commit 2022-03-28 20:00:17 +03:00
a25f317bee Bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-26 19:46:32 +00:00
a2dfffab68 Added url and apikey config options under proxy 2022-03-20 16:32:24 -04:00
775d0a8e86 FA note chages for new users (#404)
Update link to Font Awesome to reference v5 directly
2022-03-19 19:34:16 -04:00
6351bf973c Merge pull request #401 from Zareix/fix/portainer-service
Portainer service - Fix for down endpoints
2022-03-19 12:58:34 -04:00
4e953d7c81 prettier 2022-03-19 14:12:30 +01:00
e2ebf9973b select environments 2022-03-19 13:52:41 +01:00
a7cbcc7700 fix if endpoint not up 2022-03-19 12:59:05 +01:00
049610bc91 Merge pull request #311 from Aryess/main
Fix #121 - Change default theme and layout from config
2022-03-13 15:00:09 -04:00
f398006935 Add a get started link when no configuration is found 2022-03-11 22:47:26 +01:00
db2a2af3a4 Merge pull request #393 from jamesmacwhite/ping-endpoint-urls
Check if path has data before adding a trailing slash (/)
2022-03-06 22:30:10 +01:00
2ccadd578e Merge pull request #389 from bastienwirtz/dependabot/npm_and_yarn/url-parse-1.5.10
Bump url-parse from 1.5.7 to 1.5.10
2022-03-06 22:23:43 +01:00
120ee25bf5 Merge pull request #391 from Roundaround/portainer
Added Portainer custom service
2022-03-06 22:23:34 +01:00
1340a8e6d0 Check if path has data before adding / 2022-03-06 20:04:16 +00:00
edd2c9ce2d Removed number fudging used for testing 2022-03-01 21:05:14 -05:00
a1a70d4a3c Added Portainer custom service 2022-03-01 20:55:45 -05:00
1acdbe4920 Bump url-parse from 1.5.7 to 1.5.10
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.7 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.7...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-28 04:27:15 +00:00
ba2c7c5c57 Merge pull request #385 from saschabrockel/patch-1
Fix typos in troubleshooting
2022-02-23 22:09:34 +01:00
e4b077843c Fix typos in troubleshooting 2022-02-21 22:04:51 +01:00
5cd802d157 Merge pull request #380 from FinalDoom/main
Change Radarr/Sonarr v3 to use totalRecords
2022-02-19 09:10:19 +01:00
4a526f6e7f Merge pull request #371 from Wurzelmann/patch-2
fixed typos: "additionnal" -> "additional"
2022-02-19 09:08:40 +01:00
2c52f45048 Merge pull request #369 from Wurzelmann/patch-1
fixed typo: "additionnal" -> "additional"
2022-02-19 09:06:35 +01:00
7e81828b34 Merge pull request #377 from bastienwirtz/dependabot/npm_and_yarn/follow-redirects-1.14.8
Bump follow-redirects from 1.14.7 to 1.14.8
2022-02-19 09:04:15 +01:00
78d0fc5f1b Merge pull request #383 from bastienwirtz/dependabot/npm_and_yarn/url-parse-1.5.7
Bump url-parse from 1.5.3 to 1.5.7
2022-02-19 09:01:26 +01:00
dabcc0bae1 Bump url-parse from 1.5.3 to 1.5.7
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.3 to 1.5.7.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.3...1.5.7)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-19 02:57:57 +00:00
c6ec28f1c5 Change Radarr/Sonarr v3 to use totalRecords as counting paged records is
wrong number
2022-02-18 06:39:27 -07:00
9bfa95963d Bump follow-redirects from 1.14.7 to 1.14.8
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-15 13:19:33 +00:00
8b9ec8465f fixed typos: "Additionnal" -> "Additional"
Fixed all occurrences of typo "Additionnal" (including file name)
2022-02-11 11:44:32 +01:00
893690cf95 Lint fixes 2022-02-10 22:07:00 +01:00
dec7e466b9 Merge pull request #365 from nthduy-deevotech/fix/sonarr-radarr-api
Support for Radarr, Sonarr V3 API
2022-02-10 21:52:05 +01:00
096c7eda48 Merge branch 'main' into fix/sonarr-radarr-api 2022-02-10 21:50:53 +01:00
d92444ec19 Merge pull request #353 from spaceneb/patch-1
Fix Possible Typo (Heath -> Health)
2022-02-10 21:45:19 +01:00
5fdf790e2c Merge pull request #364 from Zareix/feature/add-prowlarr
Added Prowlarr custom services
2022-02-10 21:43:58 +01:00
5afd21a84c Merge pull request #354 from bastienwirtz/dependabot/npm_and_yarn/follow-redirects-1.14.7
Bump follow-redirects from 1.14.1 to 1.14.7
2022-02-10 21:38:00 +01:00
51829a85c4 fixed typo: "additionnal" -> "additional"
Fixed all occurrences of a typo (additionnal).
2022-02-05 20:50:46 +01:00
6c8f9f1c5b Fix radarr legacy api call 2022-02-01 23:01:09 +01:00
f7f4ebdf66 Parse new V3 api response 2022-02-01 18:32:25 +01:00
8ede30411e Radarr and Sonarr V3 api support optional 2022-02-01 17:05:23 +01:00
cb154a6818 Update Sonarr and Radarr API 2022-02-01 16:39:15 +01:00
50b3bddff1 Added Prowlarr doc 2022-01-31 13:14:40 +01:00
990606a38a Added Prowlarr custom service 2022-01-31 13:13:28 +01:00
0aa343d744 Bump follow-redirects from 1.14.1 to 1.14.7
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.1 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.1...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-14 10:47:08 +00:00
26dbed936a Fix Possible Typo (Heath -> Health) 2022-01-14 00:45:26 -05:00
68b10120c9 Merge pull request #342 from b-t-k/patch-1
Typo: should have "s" in alias
2021-12-23 07:41:59 -08:00
9f14de32fe Merge pull request #344 from michaelkrieger/patch-1
Warn about exposed apikeys
2021-12-22 07:34:07 -08:00
ad8efdc799 Warn about exposed apikeys 2021-12-20 16:11:15 -05:00
BTK
a9cdf57043 Typo: should have "s" in alias 2021-12-15 12:58:56 -07:00
8283b8da5a Make Lidaar component use the Generic base component 2021-12-13 21:07:30 +01:00
b1c8586441 Merge pull request #329 from Panzer1119/main
Add Lidarr service
2021-12-13 11:54:04 -08:00
9c77651692 Last batch of custom services refacto. 2021-12-12 22:20:10 +01:00
b4a2db6e37 Services refactoring 2021-12-12 16:28:20 +01:00
e6ba84d35a Upgrade dependancies 2021-12-12 09:45:54 +01:00
611fe797eb Merge pull request #338 from naioja/bump_alpine_version
Using a newer version of alpine
2021-12-08 12:32:28 -08:00
e961af8255 Using a newer version of alpine
Signed-off-by: Adrian Joian <ajoian@microsoft.com>
2021-12-04 01:35:55 +01:00
0121fa8036 docs: add Lidarr service to customservices.md 2021-11-02 18:28:47 +01:00
754372579e feat: add Lidarr service 2021-11-02 18:26:33 +01:00
6e6efc7d29 doc: add doc for AdGuard Home 2021-10-27 21:53:39 +02:00
b7480f632e Merge pull request #316 from robinschneider/apikey
Fixes apikey inconsistency
2021-10-25 13:17:30 -07:00
9fce0ce5a5 Update src/components/services/OpenWeather.vue
Co-authored-by: Bastien Wirtz <bastien.wirtz@gmail.com>
2021-10-25 14:08:31 +02:00
f2c901a1ec Merge branch 'bastienwirtz:main' into apikey 2021-10-25 14:06:41 +02:00
cf33747f42 Merge pull request #274 from robinschneider/hotkey
Added custom hotkey support
2021-10-25 04:46:58 -07:00
46c9a513e5 Merge pull request #319 from t-huyeng/fix-ping-doc
fixed Ping documentation yaml
2021-10-25 04:45:18 -07:00
400cdb8f6a fixed Ping documentation yaml 2021-10-21 16:52:06 +02:00
487f954a36 Fixes apikey inconsistency 2021-10-13 13:51:39 +02:00
446e78d2ab Merged main, fixed hotkey support 2021-10-12 14:36:22 +02:00
3668050ba3 Merge branch 'bastienwirtz:main' into hotkey 2021-10-12 14:16:13 +02:00
54c19bb5f0 Merge pull request #314 from AlexFullmoon/patch-1
Update customservices.md
2021-10-12 04:59:13 -07:00
168f157cf9 Update customservices.md
Added PaperlessNG configuration to be in line with other services.
2021-10-12 11:37:30 +03:00
5db2414d05 Fix #121 - Change default theme and layout from config 2021-10-12 11:37:24 +11:00
1c0bf7132a Added requested changes 2021-10-11 23:54:34 +02:00
2f19540400 Merge branch 'bastienwirtz:main' into hotkey 2021-10-11 23:48:05 +02:00
c72acd57d0 Merge pull request #310 from ArturBa/main
Add Prometheus custom component
2021-10-11 13:23:43 -07:00
80ba98cf66 Code review fixes 2021-10-11 18:41:28 +02:00
d31a9a79c2 Merge pull request #307 from robinschneider/icon-color
Added custom fontawesome icon color option with link property
2021-10-10 23:17:41 -07:00
277dafafa9 Update docs 2021-10-10 22:20:34 +02:00
8d9cfa98bd Add Prometheus custom component 2021-10-10 22:14:33 +02:00
7a4e78e8d0 Merge branch 'bastienwirtz:main' into icon-color 2021-10-10 21:41:23 +02:00
87aadbb6df Merge branch 'bastienwirtz:main' into hotkey 2021-10-10 21:40:42 +02:00
66a434e7db Implementation status warning 2021-10-10 20:57:04 +02:00
3acfb01d99 Custom services common options documentation 2021-10-10 10:47:23 +02:00
2fba043575 Allow non json reponse in fetch. 2021-10-10 10:37:20 +02:00
efc2bbb856 Allow any service to override the credentials option 2021-10-10 10:16:18 +02:00
fea0f09045 Proxy settings documentation 2021-10-10 09:46:51 +02:00
0a3be103dc Factorize fetch options 2021-10-10 09:26:02 +02:00
a25e1b1a70 Auto PR lint 2021-10-10 09:23:35 +02:00
cc26624f39 Merge pull request #308 from robinschneider/patch-1
Spelling fix
2021-10-08 12:39:32 -07:00
d7e17e6146 Spelling fix 2021-10-08 17:50:19 +02:00
3faeac7e9f Added custom fontawesome icon color option with link property 2021-10-08 17:42:23 +02:00
b64b17a4f9 Fixed spelling 2021-10-08 15:11:00 +02:00
270e522e0e Merge branch 'bastienwirtz:main' into hotkey 2021-10-07 00:15:26 +02:00
220c60cba0 Reduce docker healthcheck frequency 2021-10-06 22:55:53 +02:00
2ca4faad9c Extendable base service for easier development. 2021-10-06 22:55:09 +02:00
c7dc6bfd0d Merge pull request #304 from xconverge/patch-1
Update Ping entry in docs
2021-10-06 12:42:22 -07:00
b2f6da0382 Cleanup 2021-10-06 09:17:46 -07:00
e58461ffe3 Update customservices readme for ping
Fixes #249

Would be improved with https://github.com/bastienwirtz/homer/pull/255
2021-10-06 09:16:17 -07:00
451b1ac624 Merge pull request #277 from bastienwirtz/dependabot/npm_and_yarn/url-parse-1.5.3
Bump url-parse from 1.5.1 to 1.5.3
2021-09-25 04:37:00 -07:00
7129af3bda Add troubleshooting section 2021-09-25 12:19:32 +02:00
1d3287dcca Apply linters 2021-09-25 12:18:13 +02:00
6173d7df60 Merge pull request #260 from vosdev/main
Add healthcheck to Dockerfile
2021-09-25 01:19:52 -07:00
b2a31c0701 Merge pull request #291 from mcclurec/radarr-sonarr-sso-fix
Add credentials: "include" back to Radarr and Sonarr
2021-09-24 23:59:38 -07:00
d6d078132b Update Sonarr.vue 2021-09-22 10:47:10 -07:00
e32643056e Add credentials: "include" back to Sonarr 2021-09-22 10:37:52 -07:00
eecd0db92e Add credentials: "include" back to Radarr` 2021-09-22 10:23:24 -07:00
d489f6ef87 Merge pull request #243 from waschinski/patch-1
Improving Adguard Home service
2021-09-15 00:40:05 -07:00
b63add2f95 Status visibility no longer depending on subtitle 2021-09-14 22:44:42 +02:00
a6b72c97d0 Merge pull request #246 from waschinski/mealie-service
Adding Mealie service
2021-09-14 13:38:23 -07:00
4eeed6596b Merge branch 'main' into patch-1 2021-09-14 08:56:06 +02:00
f11b1c9dcf Weather service refactoring 2021-09-13 23:13:26 +02:00
fd18715085 Merge pull request #181 from dickwolff/main
Added OpenWeatherMap service
2021-09-13 14:08:08 -07:00
92d5b8d424 Merge branch 'main' into main 2021-09-13 13:09:40 -07:00
bcec0449ec Bump url-parse from 1.5.1 to 1.5.3
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.1 to 1.5.3.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.1...1.5.3)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-13 20:04:32 +00:00
55c3ea4d92 Deps updates & lint 2021-09-13 22:03:13 +02:00
33d60aa76a Merge pull request #271 from Sharknoon/main
Fixed the height of the Dashboard Title in case of a long string
2021-09-13 12:57:58 -07:00
afe6d34ced Merge pull request #258 from mcclurec/sso-compliant-fetch-calls
SSO Compliant Fetch Calls
2021-09-13 12:55:14 -07:00
1ed0a2f387 Merge pull request #213 from simonporte/main
Fix transparency level behind links
2021-09-13 12:45:58 -07:00
117fa7d3b9 Merge pull request #263 from jeremymeyers/patch-1
typo
2021-09-13 12:43:02 -07:00
584f2b4b32 Added custom hotkey support 2021-09-11 12:42:57 +02:00
6c834c24b6 Update app.scss
Fixed the height of the header in case of a longer dashboard title
2021-09-09 16:14:49 +02:00
cb325bd58a typo 2021-08-26 21:54:59 -04:00
bbe7149d58 Update PiHole.vue 2021-08-16 14:17:15 -07:00
7efcd282bb Update AdGuardHome.vue 2021-08-16 14:16:29 -07:00
addaf36c3d Fix transparency level behind links 2021-08-16 21:08:25 +02:00
6b54eedae7 Merge pull request #254 from rvankraaij/fix-cors-radarr-sonarr
Fix cors issue with radarr and sonarr services
2021-08-15 10:28:51 -07:00
6dd8342bf0 Add healthcheck to Dockerfile 2021-08-15 15:34:17 +02:00
4852ae6b85 Update AdGuardHome.vue 2021-08-11 15:41:33 -07:00
2f6d9e1b09 Update PaperlessNG.vue 2021-08-11 15:41:11 -07:00
0dc3cea15e Update PiHole.vue 2021-08-11 15:40:22 -07:00
76a46c3507 Update Ping.vue 2021-08-11 15:38:24 -07:00
cf2fb08dc7 Update PaperlessNG.vue 2021-08-11 15:37:17 -07:00
077be43473 Update AdGuardHome.vue 2021-08-11 15:35:41 -07:00
ecec695272 include credentials while making fetch calls 2021-08-11 15:25:51 -07:00
a74fa38302 Merge branch 'main' into patch-1 2021-08-08 11:18:46 +02:00
rvk
304362adfd Fix CORS issue for Radarr and Sonarr services 2021-08-04 21:26:15 +02:00
rvk
25f99adc6c Fix CORS issue for Radarr and Sonarr services 2021-08-04 21:19:28 +02:00
b2062fb60a Merge remote-tracking branch 'upstream/main' into mealie-service 2021-07-28 16:13:50 +02:00
4386cd094b Updating configuration 2021-07-28 16:12:19 +02:00
64ac4c48d5 Statistics now also need the token for authentication 2021-07-28 16:10:11 +02:00
35926e1e6e Version bump 2021-07-14 16:48:55 +02:00
f3b3b89b7c Simplify the connectivity checker 2021-07-14 16:41:00 +02:00
a6b7db5437 Merge pull request #186 from pdevq/connectivity_checker_status
Fix ConnectivityChecker to evaluate the response status codes
2021-07-14 07:37:53 -07:00
3a8fa151f4 Improve ping service 2021-07-14 15:49:19 +02:00
92d899bd48 regroup service documentation 2021-07-14 15:48:57 +02:00
c06c0cdf9b Lint & updates 2021-07-14 12:05:53 +02:00
73a102f3fa Merge branch 'main' into mealie-service 2021-07-14 11:48:54 +02:00
f9cc1d27cc Merge pull request #239 from azrikahar/patch-1
fix card border radius when it's the only child
2021-07-14 02:23:39 -07:00
f5b467f933 Merge pull request #245 from boerniee/paperlessng-integration
Added paperless service
2021-07-14 02:13:52 -07:00
ded5228972 Merge pull request #240 from stubbfel/stubbfel-add-ping-service
Add  Ping services
2021-07-14 02:07:35 -07:00
90d6bc67c0 Merge pull request #223 from bastienwirtz/dependabot/npm_and_yarn/browserslist-4.16.6
Bump browserslist from 4.16.4 to 4.16.6
2021-07-14 01:57:49 -07:00
5d642b3674 Merge pull request #225 from bastienwirtz/dependabot/npm_and_yarn/dns-packet-1.3.4
Bump dns-packet from 1.3.1 to 1.3.4
2021-07-14 01:57:40 -07:00
c98f88eaf2 Merge pull request #228 from bastienwirtz/dependabot/npm_and_yarn/ws-6.2.2
Bump ws from 6.2.1 to 6.2.2
2021-07-14 01:57:27 -07:00
b1ccd8d6b2 Merge pull request #233 from bastienwirtz/dependabot/npm_and_yarn/postcss-7.0.36
Bump postcss from 7.0.35 to 7.0.36
2021-07-14 01:57:14 -07:00
c4cae400d5 Merge pull request #219 from tpansino/support-message-icon-url
Support passing FA icon in message URL payload
2021-07-14 01:55:59 -07:00
9e1b1bd1d2 Merge pull request #178 from twolaw/healthcheck2
Create Radarr, Sonarr & Medusa services
2021-07-14 01:52:35 -07:00
c3878bca0b Adding Mealie service 2021-07-13 19:16:15 +02:00
bebb6953cb Adding status "unknown"
Changing code as per linter
2021-07-10 09:58:17 +02:00
3832025b0c Improving Adguard Home service
Showing "x.x% blocked" similar to how the PiHole service is doing it.
The status text will no longer be `true/false` but `enabled/disabled`.
Endpoints require to be logged in so fetch does now send cookies to prevent 403s.
2021-07-10 02:02:57 +02:00
68955dc1d3 Add Ping services
a  service (type) which check if the given url as available or not. if the service is  available then set the status to enable other to disable
2021-06-28 23:20:20 +02:00
f9ebff9311 fix card border radius when it's the only child 2021-06-24 09:05:13 +08:00
24229b5411 Added paperless service with documentation 2021-06-23 17:06:19 +02:00
adacb3c33f Bump postcss from 7.0.35 to 7.0.36
Bumps [postcss](https://github.com/postcss/postcss) from 7.0.35 to 7.0.36.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/7.0.35...7.0.36)

---
updated-dependencies:
- dependency-name: postcss
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-17 04:22:49 +00:00
0178d73f66 Bump ws from 6.2.1 to 6.2.2
Bumps [ws](https://github.com/websockets/ws) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/commits)

---
updated-dependencies:
- dependency-name: ws
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-06 05:50:18 +00:00
a2b59fb6c1 Bump dns-packet from 1.3.1 to 1.3.4
Bumps [dns-packet](https://github.com/mafintosh/dns-packet) from 1.3.1 to 1.3.4.
- [Release notes](https://github.com/mafintosh/dns-packet/releases)
- [Changelog](https://github.com/mafintosh/dns-packet/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mafintosh/dns-packet/compare/v1.3.1...v1.3.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-28 19:03:13 +00:00
764b470209 Bump browserslist from 4.16.4 to 4.16.6
Bumps [browserslist](https://github.com/browserslist/browserslist) from 4.16.4 to 4.16.6.
- [Release notes](https://github.com/browserslist/browserslist/releases)
- [Changelog](https://github.com/browserslist/browserslist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/browserslist/browserslist/compare/4.16.4...4.16.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-26 11:32:52 +00:00
742ae4eb52 Support passing FA icon in message URL payload 2021-05-15 02:20:51 -07:00
996011956b Merge pull request #214 from pkoenig10/patch-1
Add crossorigin use-credentials attribute to manifest tag
2021-04-30 09:28:27 -07:00
2428998313 Add crossorigin use-credentials attribute to manifest tag 2021-04-25 14:38:41 -07:00
7596bc527f Fix null error releated to refreshInterval + cleanup. Fix #210 2021-04-21 22:10:51 -07:00
aadd8b49cc Make sure dependencies are compatible with vue-cli. Fix #206 2021-04-21 21:37:51 -07:00
64f189c9dc Update deps 2021-04-08 22:06:20 -07:00
4399f5fade pihole, medusa, radarr/sonarr services help 2021-03-11 16:12:17 +01:00
275a335cce tag moved to bottom 2021-03-11 16:12:16 +01:00
b1de1f9e08 medusa service 2021-03-11 16:12:15 +01:00
0211da26c8 radarr, sonarr services 2021-03-11 16:12:15 +01:00
a5fe53beb2 Fix ConnectivityChecker to evaluate the response status codes. 2021-01-30 00:17:24 -05:00
97f0c43ccc Added doc for locationID 2021-01-28 13:02:18 +01:00
551e32e203 Added location id 2021-01-28 12:58:29 +01:00
fb158d4767 Error handling and fixed link to city. 2021-01-13 22:19:17 +01:00
9e0ef05efe Removed URL and made fixed. 2021-01-13 21:27:19 +01:00
dfb0b14626 Fixed some typo's. 2021-01-13 21:25:07 +01:00
fd12de9ebd Improvements 2021-01-13 21:22:35 +01:00
b79561bc9c Format file. 2021-01-12 11:39:27 +01:00
593f8afc90 Added OpenWeather service component. 2021-01-12 11:35:57 +01:00
45 changed files with 3470 additions and 1719 deletions

View File

@ -14,5 +14,5 @@ Fixes # (issue)
- [ ] I've read & comply with the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/main/CONTRIBUTING.md)
- [ ] I have tested my code for new features & regressions on both mobile & desktop devices, using the latest version of major browsers.
- [ ] I have made corresponding changes the documentation (README.md).
- [ ] I have made corresponding changes to the documentation (README.md).
- [ ] I've checked my modifications for any breaking changes, especially in the `config.yml` file

31
.github/workflows/integration.yml vendored Normal file
View File

@ -0,0 +1,31 @@
# 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: Integration
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'
- run: yarn install
- run: yarn lint

View File

@ -12,10 +12,6 @@ UX and usability. If you are looking for a full featured dashboard, there is ton
- Configuration is stored in a simple config file, avoiding the need for a backend/database while making possible to use versioning or [config template](https://docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html).
- Only modern browsers are supported, feel free to use any JS features without any polyfill as soon as the latest version of the major browsers supports them.
### Roadmap
If you want to know more about the project direction or looking for something to work on, checkout the [roadmap](https://github.com/bastienwirtz/homer#Roadmap)!
Feel free to open an issue if you have any question.
# Ground Rules
@ -40,8 +36,9 @@ feel free to open an issue to present your idea.
### How to submit a contribution
The general process to submit a contribution is as follow:
1. Create your own fork of the code
2. Do the changes in your fork
3. Make sure to fill the [pull request description](https://github.com/bastienwirtz/homer/blob/main/.github/PULL_REQUEST_TEMPLATE.md) properly.
1. Take a look to the [development guideline](https://github.com/bastienwirtz/homer/blob/main/docs/development.md).
2. Create your own fork of the code
3. Do the changes in your fork
4. Make sure to fill the [pull request description](https://github.com/bastienwirtz/homer/blob/main/.github/PULL_REQUEST_TEMPLATE.md) properly.
### Happy coding :metal:

View File

@ -10,7 +10,7 @@ COPY . .
RUN yarn build
# production stage
FROM alpine:3.11
FROM alpine:3.15
ENV USER darkhttpd
ENV GROUP darkhttpd
@ -25,6 +25,9 @@ COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist /www/
COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist/assets /www/default-assets
COPY entrypoint.sh /entrypoint.sh
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:${PORT}/ || exit 1
EXPOSE ${PORT}
VOLUME /www/assets
ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]

View File

@ -35,6 +35,9 @@ COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist /www/
COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist/assets /www/default-assets
COPY entrypoint.sh /entrypoint.sh
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:${PORT}/ || exit 1
EXPOSE ${PORT}
VOLUME /www/assets
ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]

View File

@ -35,6 +35,9 @@ COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist /www/
COPY --from=build-stage --chown=${USER}:${GROUP} /app/dist/assets /www/default-assets
COPY entrypoint.sh /entrypoint.sh
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:${PORT}/ || exit 1
EXPOSE ${PORT}
VOLUME /www/assets
ENTRYPOINT ["/bin/sh", "/entrypoint.sh"]

View File

@ -1,67 +1,68 @@
<h1 align="center">
<img
width="180"
alt="Homer's donut"
src="https://raw.githubusercontent.com//bastienwirtz/homer/main/public/logo.png">
<img
width="180"
alt="Homer's donut"
src="https://raw.githubusercontent.com//bastienwirtz/homer/main/public/logo.png">
<br/>
Homer
</h1>
<h4 align="center">
A dead simple static <strong>HOM</strong>epage for your serv<strong>ER</strong> to keep your services on hand, from a simple <code>yaml</code> configuration file.
A dead simple static <strong>HOM</strong>epage for your serv<strong>ER</strong> to keep your services on hand, from a simple <code>yaml</code> configuration file.
</h4>
<p align="center">
<strong>
<a href="https://homer-demo.netlify.app">Demo</a>
<a href="https://gitter.im/homer-dashboard/community">Chat</a>
<a href="#getting-started">Getting started</a>
</strong>
<strong>
<a href="https://homer-demo.netlify.app">Demo</a>
<a href="https://gitter.im/homer-dashboard/community">Chat</a>
<a href="#getting-started">Getting started</a>
</strong>
</p>
<p align="center">
<a href="https://opensource.org/licenses/Apache-2.0"><img
alt="License: Apache 2"
src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
<a href="https://opensource.org/licenses/Apache-2.0"><img
alt="License: Apache 2"
src="https://img.shields.io/badge/License-Apache%202.0-blue.svg"></a>
<a href="https://gitter.im/homer-dashboard/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge"><img
alt="Gitter chat"
src="https://badges.gitter.im/homer-dashboard/community.svg"></a>
alt="Gitter chat"
src="https://badges.gitter.im/homer-dashboard/community.svg"></a>
<a href="https://github.com/bastienwirtz/homer/releases/latest/download/homer.zip"><img
alt="Download homer static build"
src="https://img.shields.io/badge/Download-homer.zip-orange"></a>
<a href="https://github.com/awesome-selfhosted/awesome-selfhosted"><img
alt="Awesome"
src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"></a>
alt="Download homer static build"
src="https://img.shields.io/badge/Download-homer.zip-orange"></a>
<a href="https://github.com/awesome-selfhosted/awesome-selfhosted"><img
alt="Awesome"
src="https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg"></a>
</p>
<p align="center">
<img src="https://raw.github.com/bastienwirtz/homer/main/docs/screenshot.png" width="100%">
<img src="https://raw.github.com/bastienwirtz/homer/main/docs/screenshot.png" width="100%">
</p>
## Table of Contents
- [Features](#features)
- [Getting started](#getting-started)
- [Configuration](docs/configuration.md)
- [Custom services](docs/customservices.md)
- [Tips & tricks](docs/tips-and-tricks.md)
- [Roadmap](#roadmap)
- [Development](docs/development.md)
- [Troubleshooting](docs/troubleshooting.md)
## Features
- [yaml](http://yaml.org/) file configuration
- Installable (pwa)
- Search
- Grouping
- Theme customization
- Offline heathcheck
- Offline health check
- keyboard shortcuts:
- `/` Start searching.
- `Escape` Stop searching.
- `Enter` Open the first matching result (respects the bookmark's `_target` property).
- `Alt`/`Option` + `Enter` Open the first matching result in a new tab.
## Getting started
Homer is a full static html/js dashboard, generated from the source in `/src` using webpack. It's meant to be served by an HTTP server, **it will not work if you open dist/index.html directly over file:// protocol**.
@ -111,7 +112,7 @@ environment:
### Using the release tarball (prebuilt, ready to use)
Download and extract the latest release (`homer.zip`) from the [release page](https://github.com/bastienwirtz/homer/releases), rename the `assets/config.yml.dist` file to `assets/config.yml`, and put it behind a webserver.
Download and extract the latest release (`homer.zip`) from the [release page](https://github.com/bastienwirtz/homer/releases), rename the `assets/config.yml.dist` file to `assets/config.yml`, and put it behind a web server.
```sh
wget https://github.com/bastienwirtz/homer/releases/latest/download/homer.zip
@ -134,9 +135,3 @@ npm run build
```
Then your dashboard is ready to use in the `/dist` directory.
## Roadmap
- [ ] Add new themes.
- [ ] Add support for custom service card (add custom feature to some service / app link)

View File

@ -1,13 +1,13 @@
## Configuration
# Configuration
Title, icons, links, colors, and services can be configured in the `config.yml` file (located in `/assets` directory once built, or in the `public/assets` directory in development mode), using [yaml](http://yaml.org/) format.
```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.
# Optional: Use external configuration file.
# Using this will ignore remaining config in this file
# externalConfig: https://example.com/server-luci/config.yaml
@ -19,11 +19,23 @@ logo: "assets/logo.png"
# icon: "fas fa-skull-crossbones"
header: true # Set to false to hide the header
# Optional: Different hotkey for search, defaults to "/"
# hotkey:
# search: "Shift"
footer: '<p>Created with <span class="has-text-danger">❤️</span> with <a href="https://bulma.io/">bulma</a>, <a href="https://vuejs.org/">vuejs</a> & <a href="https://fontawesome.com/">font awesome</a> // Fork me on <a href="https://github.com/bastienwirtz/homer"><i class="fab fa-github-alt"></i></a></p>' # set false if you want to hide it.
columns: "3" # "auto" or number (must be a factor of 12: 1, 2, 3, 4, 6, 12)
connectivityCheck: true # whether you want to display a message when the apps are not accessible anymore (VPN disconnected for example)
# Optional: Proxy / hosting option
proxy:
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'.
@ -47,6 +59,7 @@ colors:
text-title: "#303030"
text-subtitle: "#424242"
card-shadow: rgba(0, 0, 0, 0.1)
link: "#3273dc"
link-hover: "#363636"
background-image: "assets/your/light/bg.png"
dark:
@ -60,6 +73,7 @@ colors:
text-title: "#fafafa"
text-subtitle: "#f5f5f5"
card-shadow: rgba(0, 0, 0, 0.4)
link: "#3273dc"
link-hover: "#ffdd57"
background-image: "assets/your/dark/bg.png"
@ -136,6 +150,8 @@ services:
# background: red # optional color for card to set color directly without custom stylesheet
```
View [Custom Services](customservices.md) for details about all available custom services (like PiHole) and how to configure them.
If you choose to fetch message information from an endpoint, the output format should be as follows (or you can [custom map fields as shown in tips-and-tricks](./tips-and-tricks.md#mapping-fields)):
```json
@ -149,7 +165,7 @@ If you choose to fetch message information from an endpoint, the output format s
`null` value or missing keys will be ignored and value from the `config.yml` will be used if available.
Empty values (either in `config.yml` or the endpoint data) will hide the element (ex: set `"title": ""` to hide the title bar).
### Style Options
## Style Options
Homer uses [bulma CSS](https://bulma.io/), which provides a [modifiers syntax](https://bulma.io/documentation/modifiers/syntax/). You'll notice in the config there is a `tagstyle` option. It can be set to any of the bulma modifiers. You'll probably want to use one of these 4 main colors:
@ -160,10 +176,29 @@ Homer uses [bulma CSS](https://bulma.io/), which provides a [modifiers syntax](h
You can read the [bulma modifiers page](https://bulma.io/documentation/modifiers/syntax/) for other options regarding size, style, or state.
### PWA Icons
## PWA Icons
In order to easily generate all required icon preset for the PWA to work, a tool like [vue-pwa-asset-generator](https://www.npmjs.com/package/vue-pwa-asset-generator) can be used:
```bash
npx vue-pwa-asset-generator -a {your_512x512_source_png} -o {your_output_folder}
```
## Supported services
Currently the following services are supported for showing quick infos on the card. They can be used by setting the type to one of the following values at the item.
- PiHole
- AdGuardHome
- PaperlessNG
- Mealie
## Additional configuration
### Paperless
For Paperless you need an API-Key which you have to store at the item in the field `apikey`.
### Mealie
First off make sure to remove an existing `subtitle` as it will take precedence if set. Setting `type: "Mealie"` will then show the number of recipes Mealie is keeping organized or the planned meal for today if one is planned. You will have to set an API key in the field `apikey` which can be created in your Mealie installation.

181
docs/customservices.md Normal file
View File

@ -0,0 +1,181 @@
# 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 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.
## Common options
```yaml
- name: "My Service"
logo: "assets/tools/sample.png"
url: "http://my-service-link"
endpoint: "http://my-service-endpoint" # Optional: alternative base URL used to fetch service data is necessary.
useCredentials: false # Optional: Override global proxy.useCredentials configuration.
type: "<type>"
```
## PiHole
Using the PiHole service you can display info about your local PiHole instance right on your Homer dashboard.
The following configuration is available for the PiHole service.
```yaml
- name: "Pi-hole"
logo: "assets/tools/sample.png"
# subtitle: "Network-wide Ad Blocking" # optional, if no subtitle is defined, PiHole statistics will be shown
url: "http://192.168.0.151/admin"
type: "PiHole"
```
## OpenWeatherMap
Using the OpenWeatherMap service you can display weather information about a given location.
The following configuration is available for the OpenWeatherMap service:
```yaml
- name: "Weather"
location: "Amsterdam" # your location.
locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy
apikey: "<---insert-api-key-here--->" # insert your own API key here. Request one from https://openweathermap.org/api.
units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin.
background: "square" # choose which type of background you want behind the image. Can be one of: square, cicle, none. Defaults to none.
type: "OpenWeather"
```
**Remarks:**
If for some reason your city can't be found by entering the name in the `location` property, you could also try to configure the OWM city ID in the `locationId` property. To retrieve your specific City ID, go to the [OWM website](https://openweathermap.org), search for your city and retrieve the ID from the URL (for example, the City ID of Amsterdam is 2759794).
## Medusa
This service displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application.
Two lines are needed in the config.yml :
```yaml
type: "Medusa"
apikey: "01234deb70424befb1f4ef6a23456789"
```
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.
## Lidarr, Prowlarr, Sonarr and Radarr
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: "Lidarr", "Prowlarr", "Radarr" or "Sonarr"
apikey: "01234deb70424befb1f4ef6a23456789"
```
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
This service displays total number of documents stored. Two lines are required:
```yaml
type: "PaperlessNG"
apikey: "0123456789abcdef123456789abcdef"
```
API key can be generated in Settings > Administration > Auth Tokens
## Ping
For Ping you need to set the type to Ping and provide a url.
```yaml
- name: "Awesome app"
type: Ping
logo: "assets/tools/sample.png"
subtitle: "Bookmark example"
tag: "app"
url: "https://www.reddit.com/r/selfhosted/"
```
## Prometheus
For Prometheus you need to set the type to Prometheus and provide a url.
```yaml
- name: "Prometheus"
type: Prometheus
logo: "assets/tools/sample.png"
url: "http://192.168.0.151/"
# subtitle: "Monitor data server"
```
## AdGuard Home
For AdGuard Home you need to set the type to AdGuard, if you have somes issues as 403 responses on requests you need to provide authentification in headers for locations needed as below.
```yaml
- name: "Adguard"
logo: "assets/tools/adguardhome.png"
url: "https://adguard.exemple.com"
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.
```

View File

@ -1,4 +1,6 @@
## Development
# Development
If you want to contribute to Homer, please read the [contributing guidelines](https://github.com/bastienwirtz/homer/blob/main/CONTRIBUTING.md) first.
```sh
# Using yarn (recommended)
@ -10,7 +12,50 @@ npm install
npm run serve
```
### Themes
## Custom services
Custom services are small VueJs component (see `src/components/services/`) that add little features to a classic, "static", dashboard item. It should be very simple.
A dashboard can contain a lot of items, so performance is very important.
The [`Generic`](https://github.com/bastienwirtz/homer/blob/main/src/components/services/Generic.vue) service provides a typical card layout which
you can extend to add specific features. Unless you want a completely different design, extended the generic service is the recommended way. It gives you 3 [slots](https://vuejs.org/v2/guide/components-slots.html#Named-Slots) to extend: `icon`, `content` and `indicator`.
Each one is **optional**, and will display the usual information if omitted.
Each service must implement the `item` [property](https://vuejs.org/v2/guide/components-props.html) and bind it the Generic component if used.
### Skeleton
```Vue
<template>
<Generic :item="item">
<template #icon>
<!-- left area containing the icon -->
</template>
<template #content>
<!-- main area containing the title, subtitle, ... -->
</template>
<template #indicator>
<!-- top right area, empty by default -->
</template>
</Generic>
</template>
<script>
import Generic from "./Generic.vue";
export default {
name: "MyNewService",
props: {
item: Object,
},
components: {
Generic,
}
};
</script>
```
## Themes
Themes are meant to be simple customization (written in [scss](https://sass-lang.com/documentation/syntax)).
To add a new theme, just add a file in the theme directory, and put all style in the `body #app.theme-<name>` scope. Then import it in the main style file.

View File

@ -3,6 +3,7 @@
Here is a collection of neat tips and tricks that Homer users have come up with!
## Use Homer as a custom "new tab" page
#### `by @vosdev`
These extensions for [Firefox](https://addons.mozilla.org/firefox/addon/custom-new-tab-page) and [Chrome & Friends](https://chrome.google.com/webstore/detail/new-tab-changer/occbjkhimchkolibngmcefpjlbknggfh) allow you to have your homer dashboard in your new tab page, while leaving focus on the address bar meaning you can still type right away if you want to search or go to a page that is not on your homer dash.
@ -22,11 +23,12 @@ The Firefox extension loads Homer in an iframe on your new tab page, meaning you
```
## YAML Anchors
#### `by @JamiePhonic`
Since Homer is configured using YAML, it supports all of YAML's helpful features, such as anchoring!
For example, you can define tags and tag styles for each "item" in a service.
For example, you can define tags and tag styles for each "item" in a service.
Using Anchoring, you can define all your tags and their styles once like this: (for example)
```yaml
@ -49,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
````
@ -70,6 +72,7 @@ The end result is that if you want to update the name or style of any particular
Great if you have a lot of services or a lot of tags!
## Remotely edit your config with Code Server
#### `by @JamiePhonic`
Homer doesn't yet provide a way to edit your configuration from inside Homer itself, but that doesn't mean it can't be done!
@ -78,14 +81,17 @@ You can setup and use [Code-Server](https://github.com/cdr/code-server) to edit
If you're running Homer in docker, you can setup a Code-Server container and pass your homer config directory into it.
Simply pass your homer config directory as an extra -v parameter to your code-server container:
```
```sh
-v '/your/local/homer/config-dir/':'/config/homer':'rw'
```
This will map your homer config directory (For example, /docker/appdata/homer/) into code-server's `/config/` directory, in a sub folder called `homer`
As a bonus, Code-Server puts the "current folder" as a parameter in the URL bar, so you could add a `links:` entry in Homer that points to your code-server instance with the directory pre-filled for essentially 1 click editing!
For example:
```yml
links:
- name: Edit config
@ -93,9 +99,11 @@ links:
url: https://vscode.example.net/?folder=/config/homer
target: "_blank" # optional html tag target attribute
```
where the path after `?folder=` is the path to the folder where you mounted your homer config INSIDE the Code-Server container.
### Example Code-Server docker create command
```sh
docker create \
--name=code-server \
@ -111,13 +119,13 @@ docker create \
linuxserver/code-server
```
## Get the news headlines in Homer
### Mapping Fields
Most times, the url you're getting headlines from follows a different schema than the one expected by Homer.
For example, if you would like to show jokes from ChuckNorris.io, you'll find that the url https://api.chucknorris.io/jokes/random is giving you info like this:
For example, if you would like to show jokes from ChuckNorris.io, you'll find that the url <https://api.chucknorris.io/jokes/random> is giving you info like this:
```json
{
@ -179,6 +187,6 @@ If the URL you specified returns a JSON object that defines a `title` and `conte
So, using [Node-Red](https://nodered.org/docs/getting-started/) and a quick flow, you can process an RSS feed to replace the message with a news item!
To get started, simply import [this flow](https://flows.nodered.org/flow/4b6406c9a684c26ace0430dd1826e95d) into your Node-Red instance and change the RSS feed in the "Get News RSS Feed" node to one of your choosing!
To get started, simply import [this flow](https://flows.nodered.org/flow/4b6406c9a684c26ace0430dd1826e95d) into your Node-Red instance and change the RSS feed in the "Get News RSS Feed" node to one of your choosing!
So far, the flow has been tested with BBC News and Sky News, however it should be easy to modify the flow to work with other RSS feeds if they don't work out of the box!

19
docs/troubleshooting.md Normal file
View File

@ -0,0 +1,19 @@
# Troubleshooting
## My custom service card doesn't work, nothing appears or offline status is displayed (pi-hole, sonarr, ping, ...)
You might by facing a [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) (Cross Origin Request Sharing) issue.
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+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.
```
To resolve this, you can either:
* Host all your target service under the same domain & port.
* 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.

View File

@ -1,36 +1,35 @@
{
"name": "homer",
"version": "20.06.1",
"license": "Apache-2.0",
"version": "21.09.1",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.2",
"bulma": "^0.9.2",
"core-js": "^3.8.3",
"js-yaml": "^4.0.0",
"@fortawesome/fontawesome-free": "^5.15.4",
"bulma": "^0.9.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.12"
"vue": "^2.6.14"
},
"devDependencies": {
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~4.5.11",
"@vue/cli-plugin-eslint": "~4.5.11",
"@vue/cli-plugin-pwa": "~4.5.11",
"@vue/cli-service": "~4.5.11",
"@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": "^7.16.0",
"eslint-plugin-prettier": "^3.3.0",
"eslint-plugin-vue": "^7.3.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^6.2.2",
"prettier": "^2.2.1",
"raw-loader": "^4.0.2",
"sass": "^1.30.0",
"sass-loader": "^10.1.0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"vue-template-compiler": "^2.6.12"
}
},
"license": "Apache-2.0"
}

View File

@ -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

View File

@ -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"
@ -24,6 +24,7 @@ colors:
text-title: "#303030"
text-subtitle: "#424242"
card-shadow: rgba(0, 0, 0, 0.1)
link: "#3273dc"
link-hover: "#363636"
dark:
highlight-primary: "#3367d6"
@ -36,6 +37,7 @@ colors:
text-title: "#fafafa"
text-subtitle: "#f5f5f5"
card-shadow: rgba(0, 0, 0, 0.4)
link: "#3273dc"
link-hover: "#ffdd57"
# Optional message
@ -56,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.

View File

@ -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"

View File

@ -30,17 +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()"
@input="filterServices"
@search-focus="showMenu = true"
@search-open="navigateToFirstService"
@ -55,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" />
@ -74,7 +82,9 @@
<Service
v-for="(item, index) in group.items"
:key="index"
v-bind:item="item"
:item="item"
:proxy="config.proxy"
:forwarder="config.forwarder"
:class="['column', `is-${12 / config.columns}`]"
/>
</template>
@ -102,7 +112,9 @@
<Service
v-for="(item, index) in group.items"
:key="index"
v-bind:item="item"
:item="item"
:proxy="config.proxy"
:forwarder="config.forwarder"
/>
</div>
</div>
@ -127,6 +139,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";
@ -141,6 +154,7 @@ export default {
name: "App",
components: {
Navbar,
GetStarted,
ConnectivityChecker,
Service,
Message,
@ -151,6 +165,7 @@ export default {
},
data: function () {
return {
loaded: false,
config: null,
services: null,
offline: false,
@ -163,8 +178,14 @@ export default {
created: async function () {
this.buildDashboard();
window.onhashchange = this.buildDashboard;
this.loaded = true;
},
methods: {
searchHotkey() {
if (this.config.hotkey && this.config.hotkey.search) {
return this.config.hotkey.search;
}
},
buildDashboard: async function () {
const defaults = jsyaml.load(defaultConfig);
let config;
@ -185,6 +206,7 @@ export default {
}
this.config = merge(defaults, config);
this.services = this.config.services;
document.title =
this.config.documentTitle ||
`${this.config.title} | ${this.config.subtitle}`;
@ -203,6 +225,7 @@ export default {
window.location.href = response.url;
return;
}
if (!response.ok) {
throw Error(`${response.statusText}: ${response.body}`);
}

View File

@ -31,6 +31,7 @@ body {
transition: background-color cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
a {
color: var(--link);
&:hover {
color: var(--link-hover);
}
@ -106,7 +107,7 @@ body {
}
.first-line {
height: 100px;
min-height: 100px;
vertical-align: center;
background-color: var(--highlight-primary);
@ -121,7 +122,7 @@ body {
}
.container {
height: 80px;
min-height: 80px;
padding: 10px 0;
}
@ -140,8 +141,7 @@ body {
}
}
}
.navbar,
.navbar-menu {
.navbar {
background-color: var(--highlight-secondary);
a {
@ -153,6 +153,9 @@ body {
background-color: var(--highlight-hover);
}
}
.navbar-menu {
background-color: inherit;
}
}
.navbar-end {
text-align: right;
@ -211,7 +214,7 @@ body {
color: var(--highlight-secondary);
background-color: var(--highlight-secondary);
position: absolute;
top: 1rem;
bottom: 1rem;
right: -0.2rem;
width: 3px;
overflow: hidden;
@ -224,7 +227,6 @@ body {
}
.card {
border-radius: 5px;
border: none;
box-shadow: 0 2px 15px 0 rgba(0, 0, 0, 0.1);
transition: cubic-bezier(0.165, 0.84, 0.44, 1) 300ms;
@ -260,11 +262,13 @@ body {
}
.column div:first-of-type .card {
border-radius: 5px 5px 0 0;
border-top-left-radius: 0.25rem;
border-top-right-radius: 0.25rem;
}
.column div:last-child .card {
border-radius: 0 0 5px 5px;
border-bottom-left-radius: 0.25rem;
border-bottom-right-radius: 0.25rem;
}
}
@ -348,4 +352,4 @@ body {
.group-logo {
float: left;
}
}

View File

@ -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:
@ -23,6 +29,7 @@ colors:
text-title: "#303030"
text-subtitle: "#424242"
card-shadow: rgba(0, 0, 0, 0.1)
link: "#3273dc"
link-hover: "#363636"
background-image: ""
dark:
@ -36,9 +43,14 @@ colors:
text-title: "#fafafa"
text-subtitle: "#f5f5f5"
card-shadow: rgba(0, 0, 0, 0.4)
link: "#3273dc"
link-hover: "#ffdd57"
background-image: ""
message: ~
links: []
services: []
proxy: ~
forwarder: ~

View File

@ -37,8 +37,8 @@ export default {
method: "HEAD",
cache: "no-store",
})
.then(function () {
that.offline = false;
.then(function (response) {
that.offline = !response.ok;
})
.catch(function () {
that.offline = true;

View File

@ -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);

View 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>

View File

@ -42,19 +42,29 @@ export default {
},
methods: {
getMessage: async function () {
if (this.item && this.item.url) {
if (!this.item) {
return;
}
if (this.item.url) {
let fetchedMessage = await this.downloadMessage(this.item.url);
if (this.item.mapping)
console.log("done");
if (this.item.mapping) {
fetchedMessage = this.mapRemoteMessage(fetchedMessage);
}
// keep the original config value if no value is provided by the endpoint
for (const prop of ["title", "style", "content"]) {
const message = this.message;
for (const prop of ["title", "style", "content", "icon"]) {
if (prop in fetchedMessage && fetchedMessage[prop] !== null) {
this.message[prop] = fetchedMessage[prop];
message[prop] = fetchedMessage[prop];
}
}
this.message = { ...message }; // Force computed property to re-evaluate
}
if (this.item.refreshInterval)
if (this.item.refreshInterval) {
setTimeout(this.getMessage, this.item.refreshInterval);
}
},
downloadMessage: function (url) {

View File

@ -15,10 +15,16 @@
<script>
export default {
name: "SearchInput",
props: ["value"],
props: {
value: String,
hotkey: {
type: String,
default: "/",
},
},
mounted() {
this._keyListener = function (event) {
if (event.key === "/") {
if (event.key === this.hotkey) {
event.preventDefault();
this.focus();
}
@ -28,7 +34,7 @@ export default {
};
document.addEventListener("keydown", this._keyListener.bind(this));
// fill seach from get parameter.
// fill search from get parameter.
const search = new URLSearchParams(window.location.search).get("search");
if (search) {
this.$refs.search.value = search;

View File

@ -1,5 +1,10 @@
<template>
<component v-bind:is="component" :item="item"></component>
<component
v-bind:is="component"
:item="item"
:proxy="proxy"
:forwarder="forwarder"
></component>
</template>
<script>
@ -7,16 +12,15 @@ import Generic from "./services/Generic.vue";
export default {
name: "Service",
components: {
Generic,
},
props: {
item: Object,
proxy: Object,
forwarder: Object,
},
computed: {
component() {
const type = this.item.type || "Generic";
if (type == "Generic") {
if (type === "Generic") {
return Generic;
}
return () => import(`./services/${type}.vue`);

View File

@ -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);

View File

@ -1,68 +1,81 @@
<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
v-if="status"
class="status"
v-bind:class="status.protection_enabled ? 'enabled' : 'disabled'"
>
{{ status.protection_enabled }}
</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="stats">
{{ percentage }}&percnt; blocked
</template>
</p>
</template>
<template #indicator>
<div class="status" :class="protection">
{{ protection }}
</div>
</template>
</Generic>
</template>
<script>
import service from "@/mixins/service.js";
import Generic from "./Generic.vue";
export default {
name: "AdGuardHome",
mixins: [service],
props: {
item: Object,
},
components: {
Generic,
},
data: () => {
return {
status: null,
stats: null,
};
},
computed: {
percentage: function () {
if (this.stats) {
return (
(this.stats.num_blocked_filtering * 100) /
this.stats.num_dns_queries
).toFixed(2);
}
return "";
},
protection: function () {
if (this.status) {
return this.status.protection_enabled ? "enabled" : "disabled";
} else return "unknown";
},
},
created: function () {
this.fetchStatus();
if (!this.item.subtitle) {
this.fetchStats();
}
},
methods: {
fetchStatus: async function () {
this.status = await fetch(
`${this.item.url}/control/status`
).then((response) => response.json());
this.status = await this.fetch("/control/status").catch((e) =>
console.log(e)
);
},
fetchStats: async function () {
this.stats = await this.fetch("/control/stats").catch((e) =>
console.log(e)
);
},
},
};
</script>
<style scoped lang="scss">
.media-left img {
max-height: 100%;
}
.status {
font-size: 0.8rem;
color: var(--text-title);
@ -79,6 +92,12 @@ export default {
box-shadow: 0px 0px 4px 1px #c9404d;
}
&.unknown:before {
background-color: #c9c740;
border-color: #ccc935;
box-shadow: 0px 0px 4px 1px #c9c740;
}
&:before {
content: " ";
display: inline-block;

View 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>

View File

@ -8,22 +8,27 @@
<a :href="item.url" :target="item.target" rel="noreferrer">
<div class="card-content">
<div :class="mediaClass">
<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>
<slot name="icon">
<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>
</slot>
<div class="media-content">
<p class="title is-4">{{ item.name }}</p>
<p class="subtitle is-6" v-if="item.subtitle">
{{ item.subtitle }}
</p>
<slot name="content">
<p class="title is-4">{{ item.name }}</p>
<p class="subtitle is-6" v-if="item.subtitle">
{{ item.subtitle }}
</p>
</slot>
</div>
<slot name="indicator" class="indicator"></slot>
</div>
<div class="tag" :class="item.tagstyle" v-if="item.tag">
<strong class="tag-text">#{{ item.tag }}</strong>

View 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>

View File

@ -0,0 +1,56 @@
<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-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,
}),
created() {
this.fetchStatus();
},
methods: {
fetchStatus: async function () {
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>

View File

@ -0,0 +1,102 @@
<template>
<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,
serverError: false,
};
},
created: function () {
this.fetchConfig();
},
methods: {
fetchConfig: function () {
this.fetch("/api/v2/config", {
headers: { "X-Api-Key": this.item.apikey },
})
.then((conf) => {
this.config = conf;
})
.catch((e) => {
console.log(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 {
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;
}
&.errors {
background-color: #e51111;
}
}
}
</style>

View File

@ -0,0 +1,136 @@
<template>
<div>
<div class="card" :class="item.class">
<a
:href="`https://openweathermap.org/city/${id}`"
:target="item.target"
rel="noreferrer"
>
<div class="card-content">
<div class="media">
<div v-if="icon" class="media-left" :class="item.background">
<figure class="image is-48x48">
<img
:src="`https://openweathermap.org/img/wn/${icon}@2x.png`"
:alt="conditions"
:title="conditions"
/>
</figure>
</div>
<div class="media-content">
<p v-if="error" class="error">Data could not be retrieved</p>
<div v-else>
<p class="title is-4">{{ name }}</p>
<p class="subtitle is-6">
{{ temp | tempSuffix(this.item.units) }}
</p>
</div>
</div>
</div>
<div class="tag" :class="item.tagstyle" v-if="item.tag">
<strong class="tag-text">#{{ item.tag }}</strong>
</div>
</div>
</a>
</div>
</div>
</template>
<script>
export default {
name: "OpenWeather",
props: {
item: Object,
},
data: () => ({
id: null,
icon: null,
name: null,
temp: null,
conditions: null,
error: false,
}),
created() {
this.fetchWeather();
},
methods: {
fetchWeather: async function () {
let locationQuery;
// Use location ID if specified, otherwise retrieve value from location (name).
if (this.item.locationId) {
locationQuery = `id=${this.item.locationId}`;
} else {
locationQuery = `q=${this.item.location}`;
}
const apiKey = this.item.apikey || this.item.apiKey;
const url = `https://api.openweathermap.org/data/2.5/weather?${locationQuery}&appid=${apiKey}&units=${this.item.units}`;
fetch(url)
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
return response.json();
})
.then((weather) => {
this.id = weather.id;
this.name = weather.name;
this.temp = parseInt(weather.main.temp).toFixed(1);
this.icon = weather.weather[0].icon;
this.conditions = weather.weather[0].description;
})
.catch((e) => {
console.log(e);
this.error = true;
});
},
},
filters: {
tempSuffix: function (value, type) {
if (!value) return "";
let unit = "K";
if (type === "metric") {
unit = "°C";
} else if (type === "imperial") {
unit = "°F";
}
return `${value} ${unit}`;
},
},
};
</script>
<style scoped lang="scss">
// Add a border around the weather image.
// Otherwise the image is not always distinguishable.
.media-left {
&.circle,
&.square {
background-color: #e4e4e4;
}
&.circle {
border-radius: 90%;
}
img {
max-height: 100%;
}
}
.error {
color: #de0000;
}
// Change background color in dark mode.
.is-dark {
.media-left {
&.circle,
&.square {
background-color: #909090;
}
}
}
</style>

View File

@ -0,0 +1,55 @@
<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-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,
}),
created() {
this.fetchStatus();
},
methods: {
fetchStatus: async function () {
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;
}
this.api = await this.fetch("/api/documents/", {
headers: {
Authorization: "Token " + this.item.apikey,
},
}).catch((e) => console.log(e));
},
},
};
</script>

View File

@ -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 }}&percnt; 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 }}&percnt; 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,19 +49,16 @@ export default {
},
methods: {
fetchStatus: async function () {
const url = `${this.item.url}/api.php`;
this.api = await fetch(url)
.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);
@ -83,13 +66,13 @@ export default {
&.enabled:before {
background-color: #94e185;
border-color: #78d965;
box-shadow: 0 0 4px 1px #94e185;
box-shadow: 0 0 5px 1px #94e185;
}
&.disabled:before {
background-color: #c9404d;
border-color: #c42c3b;
box-shadow: 0 0 4px 1px #c9404d;
box-shadow: 0 0 5px 1px #c9404d;
}
&:before {

View File

@ -0,0 +1,71 @@
<template>
<Generic :item="item">
<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: "Ping",
mixins: [service],
props: {
item: Object,
},
components: {
Generic,
},
data: () => ({
status: null,
}),
created() {
this.fetchStatus();
},
methods: {
fetchStatus: async function () {
this.fetch("/", { method: "HEAD", cache: "no-cache" }, false)
.then(() => {
this.status = "online";
})
.catch(() => {
this.status = "offline";
});
},
},
};
</script>
<style scoped lang="scss">
.status {
font-size: 0.8rem;
color: var(--text-title);
&.online:before {
background-color: #94e185;
border-color: #78d965;
box-shadow: 0 0 5px 1px #94e185;
}
&.offline: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>

View 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>

View File

@ -0,0 +1,143 @@
<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-if="api"> {{ count }} {{ level }} alerts </template>
</p>
</template>
<template #indicator>
<div v-if="api" class="status" :class="level">
{{ count }}
</div>
</template>
</Generic>
</template>
<script>
import service from "@/mixins/service.js";
import Generic from "./Generic.vue";
const AlertsStatus = Object.freeze({
firing: "firing",
pending: "pending",
inactive: "inactive",
});
export default {
name: "Prometheus",
mixins: [service],
props: {
item: Object,
},
components: {
Generic,
},
data: () => ({
api: {
status: "",
count: 0,
alerts: {
firing: 0,
inactive: 0,
pending: 0,
},
},
}),
computed: {
count: function () {
return (
this.countFiring() || this.countPending() || this.countInactive() || 0
);
},
level: function () {
if (this.countFiring()) {
return AlertsStatus.firing;
} else if (this.countPending()) {
return AlertsStatus.pending;
}
return AlertsStatus.inactive;
},
},
created() {
this.fetchStatus();
},
methods: {
fetchStatus: async function () {
this.api = await this.fetch("api/v1/alerts").catch((e) => console.log(e));
},
countFiring: function () {
if (this.api) {
return this.api.data?.alerts?.filter(
(alert) => alert.state === AlertsStatus.firing
).length;
}
return 0;
},
countPending: function () {
if (this.api) {
return this.api.data?.alerts?.filter(
(alert) => alert.state === AlertsStatus.pending
).length;
}
return 0;
},
countInactive: function () {
if (this.api) {
return this.api.data?.alerts?.filter(
(alert) => alert.state === AlertsStatus.pending
).length;
}
return 0;
},
},
};
</script>
<style scoped lang="scss">
.media-left {
.image {
display: flex;
align-items: center;
}
img {
max-height: 100%;
}
}
.status {
font-size: 0.8rem;
color: var(--text-title);
&.firing:before {
background-color: #d65c68;
border-color: #e87d88;
box-shadow: 0 0 5px 1px #d65c68;
}
&.pending:before {
background-color: #e8bb7d;
border-color: #d6a35c;
box-shadow: 0 0 5px 1px #e8bb7d;
}
&.inactive:before {
background-color: #8fe87d;
border-color: #70d65c;
box-shadow: 0 0 5px 1px #8fe87d;
}
&:before {
content: " ";
display: inline-block;
width: 7px;
height: 7px;
margin-right: 10px;
border: 1px solid #000;
border-radius: 7px;
}
}
</style>

View 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>

View File

@ -0,0 +1,125 @@
<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 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,
warnings: null,
errors: null,
serverError: false,
};
},
created: function () {
this.fetchConfig();
},
computed: {
apiPath() {
return this.item.legacyApi ? LEGACY_API : V3_API;
},
},
methods: {
fetchConfig: function () {
this.fetch(`${this.apiPath}/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(`${this.apiPath}/queue?apikey=${this.item.apikey}`)
.then((queue) => {
this.activity = 0;
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) => {
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;
&.activity {
background-color: #4fb5d6;
}
&.warnings {
background-color: #d08d2e;
}
&.errors {
background-color: #e51111;
}
}
}
</style>

View File

@ -0,0 +1,127 @@
<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 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,
warnings: null,
errors: null,
serverError: false,
};
},
created: function () {
this.fetchConfig();
},
methods: {
fetchConfig: function () {
this.fetch(`${this.apiPath}/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(`${this.apiPath}/queue?apikey=${this.item.apikey}`)
.then((queue) => {
this.activity = 0;
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) => {
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;
&.activity {
background-color: #4fb5d6;
}
&.warnings {
background-color: #d08d2e;
}
&.errors {
background-color: #e51111;
}
}
}
</style>

62
src/mixins/service.js Normal file
View File

@ -0,0 +1,62 @@
const merge = require("lodash.merge");
export default {
props: {
proxy: Object,
forwarder: Object,
},
created: function () {
// custom service often consume info from an API using the item link (url) as a base url,
// but sometimes the base url is different. An optional alternative URL can be provided with the "endpoint" key.
this.endpoint = this.item.endpoint || this.item.url;
if (this.endpoint.endsWith("/")) {
this.endpoint = this.endpoint.slice(0, -1);
}
},
methods: {
fetch: function (path, init, json = true) {
let options = {};
if (this.proxy?.useCredentials) {
options.credentials = "include";
}
// Each item can override the credential settings
if (this.item.useCredentials !== undefined) {
options.credentials =
this.item.useCredentials === true ? "include" : "omit";
}
if (this.forwarder?.apikey) {
options.headers = {
"X-Homer-Forwarder-Api-Key": this.forwarder.apikey,
};
}
if (path.startsWith("/")) {
path = path.slice(1);
}
let url = path ? `${this.endpoint}/${path}` : this.endpoint;
if (this.forwarder?.url) {
options.headers = {
...(options.headers || {}),
"X-Homer-Forwarder-Url": url,
};
url = this.forwarder.url;
}
options = merge(options, init);
return fetch(url, options).then((response) => {
if (!response.ok) {
throw new Error("Not 2xx response");
}
return json ? response.json() : response;
});
},
},
};

View File

@ -12,6 +12,7 @@ module.exports = {
publicPath: "",
pwa: {
manifestPath: "assets/manifest.json",
manifestCrossorigin: "use-credentials",
appleMobileWebAppStatusBarStyle: "black",
appleMobileWebAppCapable: "yes",
name: manifestOptions.name,

2983
yarn.lock

File diff suppressed because it is too large Load Diff