aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--docs/configuration.md22
-rw-r--r--docs/customservices.md23
-rw-r--r--package.json6
-rw-r--r--src/assets/app.scss10
-rw-r--r--src/components/services/AdGuardHome.vue58
-rw-r--r--src/components/services/Mealie.vue97
-rw-r--r--src/components/services/OpenWeather.vue135
-rw-r--r--src/components/services/PaperlessNG.vue1
-rw-r--r--src/components/services/PiHole.vue4
-rw-r--r--src/components/services/Ping.vue6
-rw-r--r--src/components/services/Radarr.vue6
-rw-r--r--src/components/services/Sonarr.vue6
-rw-r--r--yarn.lock15
13 files changed, 355 insertions, 34 deletions
diff --git a/docs/configuration.md b/docs/configuration.md
index a472b41..68711ec 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -7,7 +7,7 @@ Title, icons, links, colors, and services can be configured in the `config.yml`
7# Homepage configuration 7# Homepage configuration
8# See https://fontawesome.com/icons for icons options 8# See https://fontawesome.com/icons for icons options
9 9
10# Optional: Use external configuration file. 10# Optional: Use external configuration file.
11# Using this will ignore remaining config in this file 11# Using this will ignore remaining config in this file
12# externalConfig: https://example.com/server-luci/config.yaml 12# externalConfig: https://example.com/server-luci/config.yaml
13 13
@@ -136,6 +136,9 @@ services:
136 # background: red # optional color for card to set color directly without custom stylesheet 136 # background: red # optional color for card to set color directly without custom stylesheet
137``` 137```
138 138
139
140View [Custom Services](customservices.md) for details about all available custom services (like PiHole) and how to configure them.
141
139If 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)): 142If 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)):
140 143
141```json 144```json
@@ -167,3 +170,20 @@ In order to easily generate all required icon preset for the PWA to work, a tool
167```bash 170```bash
168npx vue-pwa-asset-generator -a {your_512x512_source_png} -o {your_output_folder} 171npx vue-pwa-asset-generator -a {your_512x512_source_png} -o {your_output_folder}
169``` 172```
173
174### Supported services
175
176Currently 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.
177
178- PiHole
179- AdGuardHome
180- PaperlessNG
181- Mealie
182
183### Additional configuration
184
185#### Paperless
186For Paperless you need an API-Key which you have to store at the item in the field `apikey`.
187
188#### Mealie
189First 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.
diff --git a/docs/customservices.md b/docs/customservices.md
index 43f45f4..150e232 100644
--- a/docs/customservices.md
+++ b/docs/customservices.md
@@ -19,6 +19,27 @@ The following configuration is available for the PiHole service.
19 type: "PiHole" 19 type: "PiHole"
20``` 20```
21 21
22
23## OpenWeatherMap
24
25Using the OpenWeatherMap service you can display weather information about a given location.
26The following configuration is available for the OpenWeatherMap service:
27
28```
29items:
30 - name: "Weather"
31 location: "Amsterdam" # your location.
32 locationId: "2759794" # Optional: Specify OpenWeatherMap city ID for better accuracy
33 apiKey: "<---insert-api-key-here--->" # insert your own API key here. Request one from https://openweathermap.org/api.
34 units: "metric" # units to display temperature. Can be one of: metric, imperial, kelvin. Defaults to kelvin.
35 background: "square" # choose which type of background you want behind the image. Can be one of: square, cicle, none. Defaults to none.
36 type: "OpenWeather"
37```
38
39**Remarks:**
40If 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).
41
42
22## Medusa 43## Medusa
23 44
24This service displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application. 45This service displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application.
@@ -50,4 +71,4 @@ For Paperless you need an API-Key which you have to store at the item in the fie
50 71
51## Ping 72## Ping
52 73
53For Paperless you need an API-Key which you have to store at the item in the field `apikey`. 74For Ping you need an API-Key which you have to store at the item in the field `apikey`.
diff --git a/package.json b/package.json
index 61b3073..c5486bb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
1{ 1{
2 "name": "homer", 2 "name": "homer",
3 "version": "21.07.1", 3 "version": "21.09.1",
4 "license": "Apache-2.0", 4 "license": "Apache-2.0",
5 "scripts": { 5 "scripts": {
6 "serve": "vue-cli-service serve", 6 "serve": "vue-cli-service serve",
@@ -8,9 +8,9 @@
8 "lint": "vue-cli-service lint" 8 "lint": "vue-cli-service lint"
9 }, 9 },
10 "dependencies": { 10 "dependencies": {
11 "@fortawesome/fontawesome-free": "^5.15.3", 11 "@fortawesome/fontawesome-free": "^5.15.4",
12 "bulma": "^0.9.3", 12 "bulma": "^0.9.3",
13 "core-js": "^3.15.2", 13 "core-js": "^3.17.3",
14 "js-yaml": "^4.1.0", 14 "js-yaml": "^4.1.0",
15 "lodash.merge": "^4.6.2", 15 "lodash.merge": "^4.6.2",
16 "register-service-worker": "^1.7.2", 16 "register-service-worker": "^1.7.2",
diff --git a/src/assets/app.scss b/src/assets/app.scss
index 6bb5068..f2dfb37 100644
--- a/src/assets/app.scss
+++ b/src/assets/app.scss
@@ -106,7 +106,7 @@ body {
106 } 106 }
107 107
108 .first-line { 108 .first-line {
109 height: 100px; 109 min-height: 100px;
110 vertical-align: center; 110 vertical-align: center;
111 background-color: var(--highlight-primary); 111 background-color: var(--highlight-primary);
112 112
@@ -121,7 +121,7 @@ body {
121 } 121 }
122 122
123 .container { 123 .container {
124 height: 80px; 124 min-height: 80px;
125 padding: 10px 0; 125 padding: 10px 0;
126 } 126 }
127 127
@@ -140,8 +140,7 @@ body {
140 } 140 }
141 } 141 }
142 } 142 }
143 .navbar, 143 .navbar {
144 .navbar-menu {
145 background-color: var(--highlight-secondary); 144 background-color: var(--highlight-secondary);
146 145
147 a { 146 a {
@@ -153,6 +152,9 @@ body {
153 background-color: var(--highlight-hover); 152 background-color: var(--highlight-hover);
154 } 153 }
155 } 154 }
155 .navbar-menu {
156 background-color: inherit;
157 }
156 } 158 }
157 .navbar-end { 159 .navbar-end {
158 text-align: right; 160 text-align: right;
diff --git a/src/components/services/AdGuardHome.vue b/src/components/services/AdGuardHome.vue
index d4a2b89..61d4bed 100644
--- a/src/components/services/AdGuardHome.vue
+++ b/src/components/services/AdGuardHome.vue
@@ -16,14 +16,17 @@
16 </div> 16 </div>
17 <div class="media-content"> 17 <div class="media-content">
18 <p class="title is-4">{{ item.name }}</p> 18 <p class="title is-4">{{ item.name }}</p>
19 <p class="subtitle is-6">{{ item.subtitle }}</p> 19 <p class="subtitle is-6">
20 <template v-if="item.subtitle">
21 {{ item.subtitle }}
22 </template>
23 <template v-else-if="stats">
24 {{ percentage }}&percnt; blocked
25 </template>
26 </p>
20 </div> 27 </div>
21 <div 28 <div class="status" :class="protection">
22 v-if="status" 29 {{ protection }}
23 class="status"
24 v-bind:class="status.protection_enabled ? 'enabled' : 'disabled'"
25 >
26 {{ status.protection_enabled }}
27 </div> 30 </div>
28 </div> 31 </div>
29 <div class="tag" :class="item.tagstyle" v-if="item.tag"> 32 <div class="tag" :class="item.tagstyle" v-if="item.tag">
@@ -44,16 +47,45 @@ export default {
44 data: () => { 47 data: () => {
45 return { 48 return {
46 status: null, 49 status: null,
50 stats: null,
47 }; 51 };
48 }, 52 },
53 computed: {
54 percentage: function () {
55 if (this.stats) {
56 return (
57 (this.stats.num_blocked_filtering * 100) /
58 this.stats.num_dns_queries
59 ).toFixed(2);
60 }
61 return "";
62 },
63 protection: function () {
64 if (this.status) {
65 return this.status.protection_enabled ? "enabled" : "disabled";
66 } else return "unknown";
67 },
68 },
49 created: function () { 69 created: function () {
50 this.fetchStatus(); 70 this.fetchStatus();
71 if (!this.item.subtitle) {
72 this.fetchStats();
73 }
51 }, 74 },
52 methods: { 75 methods: {
53 fetchStatus: async function () { 76 fetchStatus: async function () {
54 this.status = await fetch(`${this.item.url}/control/status`).then( 77 this.status = await fetch(`${this.item.url}/control/status`, {
55 (response) => response.json() 78 credentials: "include",
56 ); 79 })
80 .then((response) => response.json())
81 .catch((e) => console.log(e));
82 },
83 fetchStats: async function () {
84 this.stats = await fetch(`${this.item.url}/control/stats`, {
85 credentials: "include",
86 })
87 .then((response) => response.json())
88 .catch((e) => console.log(e));
57 }, 89 },
58 }, 90 },
59}; 91};
@@ -79,6 +111,12 @@ export default {
79 box-shadow: 0px 0px 4px 1px #c9404d; 111 box-shadow: 0px 0px 4px 1px #c9404d;
80 } 112 }
81 113
114 &.unknown:before {
115 background-color: #c9c740;
116 border-color: #ccc935;
117 box-shadow: 0px 0px 4px 1px #c9c740;
118 }
119
82 &:before { 120 &:before {
83 content: " "; 121 content: " ";
84 display: inline-block; 122 display: inline-block;
diff --git a/src/components/services/Mealie.vue b/src/components/services/Mealie.vue
new file mode 100644
index 0000000..acff1fb
--- /dev/null
+++ b/src/components/services/Mealie.vue
@@ -0,0 +1,97 @@
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="meal">
24 Today: {{ meal.name }}
25 </template>
26 <template v-else-if="stats">
27 happily keeping {{ stats.totalRecipes }} recipes organized
28 </template>
29 </p>
30 </div>
31 </div>
32 <div class="tag" :class="item.tagstyle" v-if="item.tag">
33 <strong class="tag-text">#{{ item.tag }}</strong>
34 </div>
35 </div>
36 </a>
37 </div>
38 </div>
39</template>
40
41<script>
42export default {
43 name: "Mealie",
44 props: {
45 item: Object,
46 },
47 data: () => ({
48 stats: null,
49 meal: null,
50 }),
51 created() {
52 this.fetchStatus();
53 },
54 methods: {
55 fetchStatus: async function () {
56 if (this.item.subtitle != null) return; // omitting unnecessary ajax call as the subtitle is showing
57 var apikey = this.item.apikey;
58 this.meal = await fetch(`${this.item.url}/api/meal-plans/today/`, {
59 headers: {
60 "Authorization": "Bearer " + this.item.apikey,
61 "Accept": "application/json"
62 }
63 })
64 .then(function(response) {
65 if (!response.ok) {
66 throw new Error("Not 2xx response")
67 } else {
68 if (response != null) {
69 return response.json();
70 }
71 }
72 })
73 .catch((e) => console.log(e));
74 this.stats = await fetch(`${this.item.url}/api/debug/statistics/`, {
75 headers: {
76 "Authorization": "Bearer " + this.item.apikey,
77 "Accept": "application/json"
78 }
79 })
80 .then(function(response) {
81 if (!response.ok) {
82 throw new Error("Not 2xx response")
83 } else {
84 return response.json();
85 }
86 })
87 .catch((e) => console.log(e));
88 },
89 },
90};
91</script>
92
93<style scoped lang="scss">
94.media-left img {
95 max-height: 100%;
96}
97</style>
diff --git a/src/components/services/OpenWeather.vue b/src/components/services/OpenWeather.vue
new file mode 100644
index 0000000..09ff76a
--- /dev/null
+++ b/src/components/services/OpenWeather.vue
@@ -0,0 +1,135 @@
1<template>
2 <div>
3 <div class="card" :class="item.class">
4 <a
5 :href="`https://openweathermap.org/city/${id}`"
6 :target="item.target"
7 rel="noreferrer"
8 >
9 <div class="card-content">
10 <div class="media">
11 <div v-if="icon" class="media-left" :class="item.background">
12 <figure class="image is-48x48">
13 <img
14 :src="`https://openweathermap.org/img/wn/${icon}@2x.png`"
15 :alt="conditions"
16 :title="conditions"
17 />
18 </figure>
19 </div>
20 <div class="media-content">
21 <p v-if="error" class="error">Data could not be retrieved</p>
22 <div v-else>
23 <p class="title is-4">{{ name }}</p>
24 <p class="subtitle is-6">
25 {{ temp | tempSuffix(this.item.units) }}
26 </p>
27 </div>
28 </div>
29 </div>
30 <div class="tag" :class="item.tagstyle" v-if="item.tag">
31 <strong class="tag-text">#{{ item.tag }}</strong>
32 </div>
33 </div>
34 </a>
35 </div>
36 </div>
37</template>
38
39<script>
40export default {
41 name: "OpenWeather",
42 props: {
43 item: Object,
44 },
45 data: () => ({
46 id: null,
47 icon: null,
48 name: null,
49 temp: null,
50 conditions: null,
51 error: false,
52 }),
53 created() {
54 this.fetchWeather();
55 },
56 methods: {
57 fetchWeather: async function () {
58 let locationQuery;
59
60 // Use location ID if specified, otherwise retrieve value from location (name).
61 if (this.item.locationId) {
62 locationQuery = `id=${this.item.locationId}`;
63 } else {
64 locationQuery = `q=${this.item.location}`;
65 }
66
67 const url = `https://api.openweathermap.org/data/2.5/weather?${locationQuery}&appid=${this.item.apiKey}&units=${this.item.units}`;
68 fetch(url)
69 .then((response) => {
70 if (!response.ok) {
71 throw Error(response.statusText);
72 }
73 return response.json();
74 })
75 .then((weather) => {
76 this.id = weather.id;
77 this.name = weather.name;
78 this.temp = parseInt(weather.main.temp).toFixed(1);
79 this.icon = weather.weather[0].icon;
80 this.conditions = weather.weather[0].description;
81 })
82 .catch((e) => {
83 console.log(e);
84 this.error = true;
85 });
86 },
87 },
88 filters: {
89 tempSuffix: function (value, type) {
90 if (!value) return "";
91
92 let unit = "K";
93 if (type === "metric") {
94 unit = "°C";
95 } else if (type === "imperial") {
96 unit = "°F";
97 }
98 return `${value} ${unit}`;
99 },
100 },
101};
102</script>
103
104<style scoped lang="scss">
105// Add a border around the weather image.
106// Otherwise the image is not always distinguishable.
107.media-left {
108 &.circle,
109 &.square {
110 background-color: #e4e4e4;
111 }
112
113 &.circle {
114 border-radius: 90%;
115 }
116
117 img {
118 max-height: 100%;
119 }
120}
121
122.error {
123 color: #de0000;
124}
125
126// Change background color in dark mode.
127.is-dark {
128 .media-left {
129 &.circle,
130 &.square {
131 background-color: #909090;
132 }
133 }
134}
135</style>
diff --git a/src/components/services/PaperlessNG.vue b/src/components/services/PaperlessNG.vue
index 4fb31f8..af13317 100644
--- a/src/components/services/PaperlessNG.vue
+++ b/src/components/services/PaperlessNG.vue
@@ -59,6 +59,7 @@ export default {
59 } 59 }
60 const url = `${this.item.url}/api/documents/`; 60 const url = `${this.item.url}/api/documents/`;
61 this.api = await fetch(url, { 61 this.api = await fetch(url, {
62 credentials: "include",
62 headers: { 63 headers: {
63 Authorization: "Token " + this.item.apikey, 64 Authorization: "Token " + this.item.apikey,
64 }, 65 },
diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue
index 7042a7b..87f7090 100644
--- a/src/components/services/PiHole.vue
+++ b/src/components/services/PiHole.vue
@@ -64,7 +64,9 @@ export default {
64 methods: { 64 methods: {
65 fetchStatus: async function () { 65 fetchStatus: async function () {
66 const url = `${this.item.url}/api.php`; 66 const url = `${this.item.url}/api.php`;
67 this.api = await fetch(url) 67 this.api = await fetch(url, {
68 credentials: "include",
69 })
68 .then((response) => response.json()) 70 .then((response) => response.json())
69 .catch((e) => console.log(e)); 71 .catch((e) => console.log(e));
70 }, 72 },
diff --git a/src/components/services/Ping.vue b/src/components/services/Ping.vue
index 8a9b7a4..e693af4 100644
--- a/src/components/services/Ping.vue
+++ b/src/components/services/Ping.vue
@@ -50,7 +50,11 @@ export default {
50 methods: { 50 methods: {
51 fetchStatus: async function () { 51 fetchStatus: async function () {
52 const url = `${this.item.url}`; 52 const url = `${this.item.url}`;
53 fetch(url, { method: "HEAD", cache: "no-cache" }) 53 fetch(url, {
54 method: "HEAD",
55 cache: "no-cache",
56 credentials: "include",
57 })
54 .then((response) => { 58 .then((response) => {
55 if (!response.ok) { 59 if (!response.ok) {
56 throw Error(response.statusText); 60 throw Error(response.statusText);
diff --git a/src/components/services/Radarr.vue b/src/components/services/Radarr.vue
index 93831a7..a9cdedf 100644
--- a/src/components/services/Radarr.vue
+++ b/src/components/services/Radarr.vue
@@ -70,9 +70,8 @@ export default {
70 }, 70 },
71 methods: { 71 methods: {
72 fetchConfig: function () { 72 fetchConfig: function () {
73 fetch(`${this.item.url}/api/health`, { 73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
74 credentials: "include", 74 credentials: "include",
75 headers: { "X-Api-Key": `${this.item.apikey}` },
76 }) 75 })
77 .then((response) => { 76 .then((response) => {
78 if (response.status != 200) { 77 if (response.status != 200) {
@@ -95,9 +94,8 @@ export default {
95 console.error(e); 94 console.error(e);
96 this.serverError = true; 95 this.serverError = true;
97 }); 96 });
98 fetch(`${this.item.url}/api/queue`, { 97 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
99 credentials: "include", 98 credentials: "include",
100 headers: { "X-Api-Key": `${this.item.apikey}` },
101 }) 99 })
102 .then((response) => { 100 .then((response) => {
103 if (response.status != 200) { 101 if (response.status != 200) {
diff --git a/src/components/services/Sonarr.vue b/src/components/services/Sonarr.vue
index 8cebac4..0270255 100644
--- a/src/components/services/Sonarr.vue
+++ b/src/components/services/Sonarr.vue
@@ -70,9 +70,8 @@ export default {
70 }, 70 },
71 methods: { 71 methods: {
72 fetchConfig: function () { 72 fetchConfig: function () {
73 fetch(`${this.item.url}/api/health`, { 73 fetch(`${this.item.url}/api/health?apikey=${this.item.apikey}`, {
74 credentials: "include", 74 credentials: "include",
75 headers: { "X-Api-Key": `${this.item.apikey}` },
76 }) 75 })
77 .then((response) => { 76 .then((response) => {
78 if (response.status != 200) { 77 if (response.status != 200) {
@@ -95,9 +94,8 @@ export default {
95 console.error(e); 94 console.error(e);
96 this.serverError = true; 95 this.serverError = true;
97 }); 96 });
98 fetch(`${this.item.url}/api/queue`, { 97 fetch(`${this.item.url}/api/queue?apikey=${this.item.apikey}`, {
99 credentials: "include", 98 credentials: "include",
100 headers: { "X-Api-Key": `${this.item.apikey}` },
101 }) 99 })
102 .then((response) => { 100 .then((response) => {
103 if (response.status != 200) { 101 if (response.status != 200) {
diff --git a/yarn.lock b/yarn.lock
index c79c2fd..79ad871 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -903,10 +903,10 @@
903 "@babel/helper-validator-identifier" "^7.14.5" 903 "@babel/helper-validator-identifier" "^7.14.5"
904 to-fast-properties "^2.0.0" 904 to-fast-properties "^2.0.0"
905 905
906"@fortawesome/fontawesome-free@^5.15.3": 906"@fortawesome/fontawesome-free@^5.15.4":
907 version "5.15.3" 907 version "5.15.4"
908 resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz#c36ffa64a2a239bf948541a97b6ae8d729e09a9a" 908 resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz#ecda5712b61ac852c760d8b3c79c96adca5554e5"
909 integrity sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w== 909 integrity sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==
910 910
911"@hapi/address@2.x.x": 911"@hapi/address@2.x.x":
912 version "2.1.4" 912 version "2.1.4"
@@ -2773,7 +2773,12 @@ core-js@^2.4.0:
2773 resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" 2773 resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
2774 integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== 2774 integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
2775 2775
2776core-js@^3.15.2, core-js@^3.6.5: 2776core-js@^3.17.3:
2777 version "3.17.3"
2778 resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.17.3.tgz#8e8bd20e91df9951e903cabe91f9af4a0895bc1e"
2779 integrity sha512-lyvajs+wd8N1hXfzob1LdOCCHFU4bGMbqqmLn1Q4QlCpDqWPpGf+p0nj+LNrvDDG33j0hZXw2nsvvVpHysxyNw==
2780
2781core-js@^3.6.5:
2777 version "3.15.2" 2782 version "3.15.2"
2778 resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61" 2783 resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.15.2.tgz#740660d2ff55ef34ce664d7e2455119c5bdd3d61"
2779 integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q== 2784 integrity sha512-tKs41J7NJVuaya8DxIOCnl8QuPHx5/ZVbFo1oKgVl1qHFBBrDctzQGtuLjPpRdNTWmKPH6oEvgN/MUID+l485Q==