diff --git a/docs/privacy.md b/docs/privacy.md
index 1df38c0d..22909bf1 100644
--- a/docs/privacy.md
+++ b/docs/privacy.md
@@ -109,6 +109,8 @@ Dashy supports [Widgets](/docs/widgets.md) for displaying dynamic content. Below
- **[IP Address](/docs/widgets.md#public-ip)**: `https://ipapi.co/json` or `http://ip-api.com/json`
- [IPGeoLocation Privacy Policy](https://ipgeolocation.io/privacy.html)
- [IP-API Privacy Policy](https://ip-api.com/docs/legal)
+- **[IP Blacklist](/docs/widgets.md#ip-blacklist)**: `https://api.blacklistchecker.com`
+ - [Blacklist Checker Privacy Policy](https://blacklistchecker.com/privacy)
- **[Crypto Watch List](/docs/widgets.md#crypto-watch-list)** and **[Token Price History](/docs/widgets.md#crypto-token-price-history)**: `https://api.coingecko.com`
- [CoinGecko Privacy Policy](https://www.coingecko.com/en/privacy)
- **[Wallet Balance](/docs/widgets.md#wallet-balance)**: `https://api.blockcypher.com/`
@@ -129,6 +131,8 @@ Dashy supports [Widgets](/docs/widgets.md) for displaying dynamic content. Below
- No Policy Availible
- **[News Headlines](/docs/widgets.md#news-headlines)**: `https://api.currentsapi.services`
- [CurrentsAPI Privacy Policy](https://currentsapi.services/privacy)
+- **[Mullvad Status](/docs/widgets.md#mullvad-status)**: `https://am.i.mullvad.net`
+ - [Mullvad Privacy Policy](https://mullvad.net/en/help/privacy-policy/)
- **[TFL Status](/docs/widgets.md#tfl-status)**: `https://api.tfl.gov.uk`
- [TFL Privacy Policy](https://tfl.gov.uk/corporate/privacy-and-cookies/)
- **[Stock Price History](/docs/widgets.md#stock-price-history)**: `https://alphavantage.co`
diff --git a/docs/widgets.md b/docs/widgets.md
index 0919e8f0..bbfff1d8 100644
--- a/docs/widgets.md
+++ b/docs/widgets.md
@@ -14,10 +14,12 @@ Dashy has support for displaying dynamic content in the form of widgets. There a
- [RSS Feed](#rss-feed)
- [Image](#image)
- [Public IP Address](#public-ip)
+ - [IP Blacklist Checker](#ip-blacklist)
- [Crypto Watch List](#crypto-watch-list)
- [Crypto Price History](#crypto-token-price-history)
- [Crypto Wallet Balance](#wallet-balance)
- [Code Stats](#code-stats)
+ - [Mullvad Status](#mullvad-status)
- [Email Aliases (AnonAddy)](#anonaddy)
- [Vulnerability Feed](#vulnerability-feed)
- [Exchange Rates](#exchange-rates)
@@ -285,6 +287,37 @@ Or
---
+### IP Blacklist
+
+Notice certain web pages aren't loading? This widget quickly shows which blacklists your IP address (or host, or email) appears on, using data from [blacklistchecker.com](https://blacklistchecker.com/).
+
+

+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`ipAddress`** | `string` | _Optional_ | The IP to check. This can also be a domain/ host name or even an email address. If left blank, Dashy will use your current public IP address.
+**`apiKey`** | `string` | Required | You can get your free API key from [blacklistchecker.com](https://blacklistchecker.com/keys)
+
+##### Example
+
+```yaml
+- type: blacklist-check
+ options:
+ apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ ipAddress: 1.1.1.1
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🔴 Required
+- **Price**: 🟠Free Plan
+- **Host**: Managed Instance Only
+- **Privacy**: _See [BlacklistChecker Privacy Policy](https://blacklistchecker.com/privacy)_
+
+---
+
### Crypto Watch List
Keep track of price changes of your favorite crypto assets. Data is fetched from [CoinGecko](https://www.coingecko.com/). All fields are optional.
@@ -433,6 +466,31 @@ Display your coding summary. [Code::Stats](https://codestats.net/) is a free and
---
+### Mullvad Status
+
+Shows your Mullvad VPN connection status, as well as server info. Fetched from [am.i.mullvad.net](https://mullvad.net/en/check/)
+
+
+
+##### Options
+
+_No Options_
+
+##### Example
+
+```yaml
+- type: mullvad-status
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Managed
+- **Privacy**: _See [Mullvad Privacy Policy](https://mullvad.net/en/help/privacy-policy/)_
+
+---
+
### AnonAddy
[AnonAddy](https://anonaddy.com/) is a free and open source mail forwarding service. Use it to protect your real email address, by using a different alias for each of your online accounts, and have all emails land in your normal inbox(es). Supports custom domains, email replies, PGP-encryption, multiple recipients and more
diff --git a/src/components/LinkItems/SubItemGroup.vue b/src/components/LinkItems/SubItemGroup.vue
index 71b78f62..2b4a2fbd 100644
--- a/src/components/LinkItems/SubItemGroup.vue
+++ b/src/components/LinkItems/SubItemGroup.vue
@@ -58,7 +58,6 @@ export default {
border-radius: var(--curve-factor);
text-decoration: none;
transition: all 0.2s ease-in-out 0s;
- color: var(--item-text-color);
p.sub-item-group-title {
margin: 0 auto;
cursor: default;
diff --git a/src/components/Widgets/BlacklistCheck.vue b/src/components/Widgets/BlacklistCheck.vue
new file mode 100644
index 00000000..edf5e419
--- /dev/null
+++ b/src/components/Widgets/BlacklistCheck.vue
@@ -0,0 +1,137 @@
+
+
+
{{ message }}
+
+
+ ✘
+ ✔
+ {{ blacklist.name }}
+
+
+
+
No Detections Found
+
✔
+
+
+ {{ showAll ? $t('widgets.general.show-less') : $t('widgets.general.show-more') }}
+
+
+
+
+
+
+
diff --git a/src/components/Widgets/MullvadStatus.vue b/src/components/Widgets/MullvadStatus.vue
new file mode 100644
index 00000000..9246e2a1
--- /dev/null
+++ b/src/components/Widgets/MullvadStatus.vue
@@ -0,0 +1,111 @@
+
+
+
✔ Connected
+
✘ Not Connected
+
+
IP{{ mullvadInfo.ip }}
+
+ Host{{ mullvadInfo.host }}
+
+
Owner{{ mullvadInfo.ownedBy }}
+
+ Type{{ mullvadInfo.serverType }}
+
+
Location{{ mullvadInfo.location }}
+
+ Blacklisted?
+ {{ mullvadInfo.isBlacklisted ? '✘ Yes' : '✔ No' }}
+
+
+
+
+
+
+
+
diff --git a/src/components/Widgets/WidgetBase.vue b/src/components/Widgets/WidgetBase.vue
index bc406ddf..ac288d4f 100644
--- a/src/components/Widgets/WidgetBase.vue
+++ b/src/components/Widgets/WidgetBase.vue
@@ -34,6 +34,13 @@
@error="handleError"
:ref="widgetRef"
/>
+
+
import('@/components/Widgets/AnonAddy.vue'),
Apod: () => import('@/components/Widgets/Apod.vue'),
+ BlacklistCheck: () => import('@/components/Widgets/BlacklistCheck.vue'),
Clock: () => import('@/components/Widgets/Clock.vue'),
CodeStats: () => import('@/components/Widgets/CodeStats.vue'),
CovidStats: () => import('@/components/Widgets/CovidStats.vue'),
@@ -432,6 +447,7 @@ export default {
IframeWidget: () => import('@/components/Widgets/IframeWidget.vue'),
ImageWidget: () => import('@/components/Widgets/ImageWidget.vue'),
Jokes: () => import('@/components/Widgets/Jokes.vue'),
+ MullvadStatus: () => import('@/components/Widgets/MullvadStatus.vue'),
NdCpuHistory: () => import('@/components/Widgets/NdCpuHistory.vue'),
NdLoadHistory: () => import('@/components/Widgets/NdLoadHistory.vue'),
NdRamHistory: () => import('@/components/Widgets/NdRamHistory.vue'),
@@ -476,7 +492,7 @@ export default {
/* Returns users specified widget options, or empty object */
widgetOptions() {
const options = this.widget.options || {};
- const timeout = this.widget.timeout || 2500;
+ const timeout = this.widget.timeout || null;
const useProxy = this.appConfig.widgetsAlwaysUseProxy || !!this.widget.useProxy;
const updateInterval = this.widget.updateInterval !== undefined
? this.widget.updateInterval : null;
diff --git a/src/mixins/WidgetMixin.js b/src/mixins/WidgetMixin.js
index 48e74c85..c0dd4a3a 100644
--- a/src/mixins/WidgetMixin.js
+++ b/src/mixins/WidgetMixin.js
@@ -20,6 +20,7 @@ const WidgetMixin = {
overrideUpdateInterval: null,
disableLoader: false, // Prevent ever showing the loader
updater: null, // Stores interval
+ defaultTimeout: 1000,
}),
/* When component mounted, fetch initial data */
mounted() {
@@ -106,7 +107,7 @@ const WidgetMixin = {
const CustomHeaders = options || null;
const headers = this.useProxy
? { 'Target-URL': endpoint, CustomHeaders: JSON.stringify(CustomHeaders) } : CustomHeaders;
- const timeout = this.options.timeout || 500;
+ const timeout = this.options.timeout || this.defaultTimeout;
const requestConfig = {
method, url, headers, data, timeout,
};
diff --git a/src/utils/MiscHelpers.js b/src/utils/MiscHelpers.js
index cc26403e..35a54a03 100644
--- a/src/utils/MiscHelpers.js
+++ b/src/utils/MiscHelpers.js
@@ -46,6 +46,12 @@ export const timestampToDateTime = (timestamp) => {
return `${timestampToDate(timestamp)} at ${timestampToTime(timestamp)}`;
};
+/* Given a 2-letter country ISO code, return the countries name */
+export const getCountryFromIso = (iso) => {
+ const regionNames = new Intl.DisplayNames(['en'], { type: 'region' });
+ return regionNames.of(iso);
+};
+
/* Given a 2-digit country code, return path to flag image from Flagpedia */
export const getCountryFlag = (countryCode, dimens) => {
const protocol = 'https';
diff --git a/src/utils/defaults.js b/src/utils/defaults.js
index 57a495c5..7ee4be7a 100644
--- a/src/utils/defaults.js
+++ b/src/utils/defaults.js
@@ -209,6 +209,7 @@ module.exports = {
widgetApiEndpoints: {
anonAddy: 'https://app.anonaddy.com',
astronomyPictureOfTheDay: 'https://apodapi.herokuapp.com/api',
+ blacklistCheck: 'https://api.blacklistchecker.com/check',
codeStats: 'https://codestats.net/',
covidStats: 'https://disease.sh/v3/covid-19',
cryptoPrices: 'https://api.coingecko.com/api/v3/coins/',
@@ -223,6 +224,7 @@ module.exports = {
holidays: 'https://kayaposoft.com/enrico/json/v2.0/?action=getHolidaysForDateRange',
jokes: 'https://v2.jokeapi.dev/joke/',
news: 'https://api.currentsapi.services/v1/latest-news',
+ mullvad: 'https://am.i.mullvad.net/json',
publicIp: 'https://ipapi.co/json',
publicIp2: 'https://api.ipgeolocation.io/ipgeo',
publicIp3: 'http://ip-api.com/json',