diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index e3b95055..b22797a4 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -1,5 +1,10 @@
# Changelog
+## ✨ 1.9.4 - Widget Support [PR #382](https://github.com/Lissy93/dashy/pull/382)
+- Adds support for dynamic content, through widgets
+- Adds 30+ pre-built widgets for general info and self-hosted services
+- Writes docs on widget usage
+
## ⚡️ 1.9.2 - Native SSL Support + Performance Improvements [PR #326](https://github.com/Lissy93/dashy/pull/326)
- Updates the server to use Express, removing serve-static, connect and body-parser
- Adds native support for passing in self-signed SSL certificates and updates docs
diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml
index 0346fb30..e6c71472 100644
--- a/.github/ISSUE_TEMPLATE/question.yml
+++ b/.github/ISSUE_TEMPLATE/question.yml
@@ -1,62 +1,64 @@
-name: Question 🤷♂️
-description: Got a question about Dashy, deployment, development or usage?
-title: '[QUESTION]
'
-labels: ['🤷♂️ Question']
-
-body:
- # Filed 1 - Intro Text
- - type: markdown
- attributes:
- value: >
- Thanks for using Dashy! Questions are welcome, but in the future will be moving over to
- [Discussions](https://github.com/Lissy93/dashy/discussions) page.
- Quick questions should be asked [here](https://github.com/Lissy93/dashy/discussions/148) instead.
- validations:
- required: false
-
- # Field 2 - The actual question
- - type: textarea
- id: question
- attributes:
- label: Question
- description: Outline your question in a clear and concise manner
- validations:
- required: true
-
- # Field 3 - Category
- - type: dropdown
- id: category
- attributes:
- label: Category
- description: What part of the application does this relate to?
- options:
- - Setup and Deployment
- - Configuration
- - App Usage
- - Development
- - Documentation
- - Alternate Views
- - Authentication
- - Using Icons
- - Language Support
- - Search & Shortcuts
- - Status Checking
- - Theming & Layout
- validations:
- required: true
-
- # Field 4 - User has RTFM first, and agrees to code of conduct, etc
- - type: checkboxes
- id: idiot-check
- attributes:
- label: Please tick the boxes
- description: Before submitting, please ensure that
- options:
- - label: You are using a [supported](https://github.com/Lissy93/dashy/blob/master/.github/SECURITY.md#supported-versions) version of Dashy (check the first two digits of the version number)
- required: true
- - label: You've checked that this [question hasn't already been raised](https://github.com/Lissy93/dashy/issues?q=is%3Aissue)
- required: true
- - label: You've checked the [docs](https://github.com/Lissy93/dashy/tree/master/docs#readme) and [troubleshooting](https://github.com/Lissy93/dashy/blob/master/docs/troubleshooting.md#troubleshooting) guide
- required: true
- - label: You agree to the [code of conduct](https://github.com/Lissy93/dashy/blob/master/.github/CODE_OF_CONDUCT.md#contributor-covenant-code-of-conduct)
- required: true
+name: Question 🤷♂️
+description: Got a question about Dashy, deployment, development or usage?
+title: '[QUESTION] '
+labels: ['🤷♂️ Question']
+
+body:
+ # Filed 1 - Intro Text
+ - type: markdown
+ attributes:
+ value: >
+ Thanks for using Dashy! Questions are welcome, but in the future will be moving over to
+ [Discussions](https://github.com/Lissy93/dashy/discussions) page.
+ Quick questions should be asked [here](https://github.com/Lissy93/dashy/discussions/148) instead.
+ validations:
+ required: false
+
+ # Field 2 - The actual question
+ - type: textarea
+ id: question
+ attributes:
+ label: Question
+ description: Outline your question in a clear and concise manner
+ validations:
+ required: true
+
+ # Field 3 - Category
+ - type: dropdown
+ id: category
+ attributes:
+ label: Category
+ description: What part of the application does this relate to?
+ options:
+ - Setup and Deployment
+ - Configuration
+ - App Usage
+ - Development
+ - Documentation
+ - Alternate Views
+ - Authentication
+ - Using Icons
+ - Widgets
+ - Actions
+ - Language Support
+ - Search & Shortcuts
+ - Status Checking
+ - Theming & Layout
+ validations:
+ required: true
+
+ # Field 4 - User has RTFM first, and agrees to code of conduct, etc
+ - type: checkboxes
+ id: idiot-check
+ attributes:
+ label: Please tick the boxes
+ description: Before submitting, please ensure that
+ options:
+ - label: You are using a [supported](https://github.com/Lissy93/dashy/blob/master/.github/SECURITY.md#supported-versions) version of Dashy (check the first two digits of the version number)
+ required: true
+ - label: You've checked that this [question hasn't already been raised](https://github.com/Lissy93/dashy/issues?q=is%3Aissue)
+ required: true
+ - label: You've checked the [docs](https://github.com/Lissy93/dashy/tree/master/docs#readme) and [troubleshooting](https://github.com/Lissy93/dashy/blob/master/docs/troubleshooting.md#troubleshooting) guide
+ required: true
+ - label: You agree to the [code of conduct](https://github.com/Lissy93/dashy/blob/master/.github/CODE_OF_CONDUCT.md#contributor-covenant-code-of-conduct)
+ required: true
diff --git a/README.md b/README.md
index 90ba97d5..e1629d1d 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@
- [🎨 Theming](#theming-)
- [🧸 Icons](#icons-)
- [🚦 Status Indicators](#status-indicators-)
+ - [📊 Widgets](#widgets-)
- [💂 Authentication](#authentication-)
- [🖱️ Opening Methods](#opening-methods-%EF%B8%8F)
- [👓 Alternate Views](#alternate-views-)
@@ -70,6 +71,7 @@
- 🎨 Multiple built-in color themes, with UI color editor and support for custom CSS
- 🧸 Many icon options - Font-Awesome, homelab icons, auto-fetching Favicon, images, emojis, etc.
- 🚦 Status monitoring for each of your apps/links for basic availability and uptime checking
+- 📊 Use widgets to display info and dynamic content from self-hosted services
- 💂 Optional authentication with multi-user access, configurable privileges, and SSO support
- 🌎 Multi-language support, with 10+ human-translated languages, and more on the way
- ☁ Optional, encrypted, free off-site cloud backup and restore feature available
@@ -235,6 +237,22 @@ Status indicators can be globally enabled by setting `appConfig.statusCheck: tru
+**[⬆️ Back to Top](#dashy)**
+
+---
+
+## Widgets 📊
+
+> For full widget documentation, see: [**Widgets**](./docs/widgets.md)
+
+You can display dynamic content from services in the form of widgets. There are several pre-built widgets availible for showing useful info, and integrations with commonly self-hosted services, but you can also easily create your own for almost any app.
+
+
+
+
+
+
+
**[⬆️ Back to Top](#dashy)**
---
diff --git a/docs/development-guides.md b/docs/development-guides.md
index 5547654a..e41911b9 100644
--- a/docs/development-guides.md
+++ b/docs/development-guides.md
@@ -1,221 +1,431 @@
-# Development Guides
-
-A series of short tutorials, to guide you through the most common development tasks.
-
-Sections:
-- [Creating a new theme](#creating-a-new-theme)
-- [Writing Translations](#writing-translations)
-- [Adding a new option in the config file](#adding-a-new-option-in-the-config-file)
-- [Updating Dependencies](#updating-dependencies)
-
-## Creating a new theme
-
-Adding a new theme is really easy. There's two things you need to do: Pass the theme name to Dashy, so that it can be added to the theme selector dropdown menu, and then write some styles!
-
-##### 1. Add Theme Name
-Choose a snappy name for you're theme, and add it to the `builtInThemes` array inside [`defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js#L27).
-
-##### 2. Write some Styles!
-Put your theme's styles inside [`color-themes.scss`](https://github.com/Lissy93/dashy/blob/master/src/styles/color-themes.scss).
-Create a new block, and make sure that `data-theme` matches the theme name you chose above. For example:
-
-```css
-html[data-theme='tiger'] {
- --primary: #f58233;
- --background: #0b1021;
-}
-```
-
-Then you can go ahead and write you're own custom CSS. Although all CSS is supported here, the best way to define you're theme is by setting the CSS variables. You can find a [list of all CSS variables, here](https://github.com/Lissy93/dashy/blob/master/docs/theming.md#css-variables).
-
-For a full guide on styling, see [Theming Docs](./theming.md).
-
-Note that if you're theme is just for yourself, and you're not submitting a PR, then you can instead just pass it under `appConfig.cssThemes` inside your config file. And then put your theme in your own stylesheet, and pass it into the Docker container - [see how](https://github.com/Lissy93/dashy/blob/master/docs/theming.md#adding-your-own-theme).
-
-## Writing Translations
-
-For full docs about Dashy's multi-language support, see [Multi-Language Support](./multi-language-support.md)
-
-Dashy is using [vue-i18n](https://vue-i18n.intlify.dev/guide/) to manage multi-language support.
-
-Adding a new language is pretty straightforward, with just three steps:
-
-##### 1. Create a new Language File
-Create a new JSON file in `./src/assets/locales` name is a 2-digit [ISO-639 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language, E.g. for German `de.json`, French `fr.json` or Spanish `es.json` - You can find a list of all ISO codes at [iso.org](https://www.iso.org/obp/ui).
-
-##### 2. Translate!
-Using [`en.json`](https://github.com/Lissy93/dashy/tree/master/src/assets/locales/en.json) as an example, translate the JSON values to your language, while leaving the keys as they are. It's fine to leave out certain items, as if they're missing they will fall-back to English. If you see any attribute which include curly braces (`{xxx}`), then leave the inner value of these braces as is, as this is for variables.
-
-```json
-{
- "theme-maker": {
- "export-button": "Benutzerdefinierte Variablen exportieren",
- "reset-button": "Stile zurücksetzen für",
- "show-all-button": "Alle Variablen anzeigen",
- "save-button": "Speichern",
- "cancel-button": "Abbrechen",
- "saved-toast": "{theme} Erfolgreich aktualisiert",
- "reset-toast": "Benutzerdefinierte Farben für {theme} entfernt"
- },
-}
-```
-
-##### 3. Add your file to the app
-
-In [`./src/utils/languages.js`](https://github.com/Lissy93/dashy/tree/master/src/utils/languages.js), you need to do 2 small things:
-
-First import your new translation file, do this at the top of the page.
-E.g. `import de from '@/assets/locales/de.json';`
-
-Second, add it to the array of languages, e.g:
-```javascript
-export const languages = [
- {
- name: 'English',
- code: 'en',
- locale: en,
- flag: '🇬🇧',
- },
- {
- name: 'German', // The name of your language
- code: 'de', // The ISO code of your language
- locale: de, // The name of the file you imported (no quotes)
- flag: '🇩🇪', // An optional flag emoji
- },
-];
-```
-You can also add your new language to the readme, under the [Language Switching](https://github.com/Lissy93/dashy#language-switching-) section, and optionally include your name/ username if you'd like to be credited for your work. Done!
-
-If you are not comfortable with making pull requests, or do not want to modify the code, then feel free to instead send the translated file to me, and I can add it into the application. I will be sure to credit you appropriately.
-
-# Adding a new option in the config file
-
-This section is for, if you're adding a new component or setting, that requires an additional item to be added to the users config file.
-
-All of the users config is specified in `./public/conf.yml` - see [Configuring Docs](./configuring.md) for info.
-Before adding a new option in the config file, first ensure that there is nothing similar available, that is is definitely necessary, it will not conflict with any other options and most importantly that it will not cause any breaking changes. Ensure that you choose an appropriate and relevant section to place it under.
-
-Next decide the most appropriate place for your attribute:
-- Application settings should be located under `appConfig`
-- Page info (such as text and metadata) should be under `pageInfo`
-- Data relating to specific sections should be under `section[n].displayData`
-- And for setting applied to specific items, it should be under `item[n]`
-
-In order for the user to be able to add your new attribute using the Config Editor, and for the build validation to pass, your attribute must be included within the [ConfigSchema](https://github.com/Lissy93/dashy/blob/master/src/utils/ConfigSchema.js). You can read about how to do this on the [ajv docs](https://ajv.js.org/json-schema.html). Give your property a type and a description, as well as any other optional fields that you feel are relevant. For example:
-
-```json
-"fontAwesomeKey": {
- "type": "string",
- "pattern": "^[a-z0-9]{10}$",
- "description": "API key for font-awesome",
- "example": "0821c65656"
-}
-```
-or
-```json
-"iconSize": {
- "enum": [ "small", "medium", "large" ],
- "default": "medium",
- "description": "The size of each link item / icon"
-}
-```
-
-Next, if you're property should have a default value, then add it to [`defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js). This ensures that nothing will break if the user does not use your property, and having all defaults together keeps things organised and easy to manage.
-
-If your property needs additional logic for fetching, setting or processing, then you can add a helper function within [`ConfigHelpers.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/ConfigHelpers.js).
-
-Finally, add your new property to the [`configuring.md`](./configuring.md) API docs. Put it under the relevant section, and be sure to include field name, data type, a description and mention that it is optional. If your new feature needs more explaining, then you can also document it under the relevant section elsewhere in the documentation.
-
-Checklist:
-- [ ] Ensure the new attribute is actually necessary, and nothing similar already exists
-- [ ] Update the [Schema](https://github.com/Lissy93/dashy/blob/master/src/utils/ConfigSchema.js) with the parameters for your new option
-- [ ] Set a default value (if required) within [`defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js)
-- [ ] Document the new value in [`configuring.md`](./configuring.md)
-- [ ] Test that the reading of the new attribute is properly handled, and will not cause any errors when it is missing or populated with an unexpected value
-
----
-
-## Updating Dependencies
-
-Running `yarn upgrade` will updated all dependencies based on the ranges specified in the `package.json`. The `yarn.lock` file will be updated, as will the contents of `./node_modules`, for more info, see the [yarn upgrade documentation](https://classic.yarnpkg.com/en/docs/cli/upgrade/). It is important to thoroughly test after any big dependency updates.
-
----
-
-## Developing Netlify Cloud Functions
-
-When Dashy is deployed to Netlify, it is effectively running as a static app, and therefore the server-side code for the Node.js endpoints is not available. However Netlify now supports serverless cloud lambda functions, which can be used to replace most functionality.
-
-#### 1. Run Netlify Dev Server
-
-First off, install the Netlify CLI: `npm install netlify-cli -g`
-Then, from within the root of Dashy's directory, start the server, by running: `netlify dev`
-
-#### 2. Create a lambda function
-
-This should be saved it in the [`./services/serverless-functions`](https://github.com/Lissy93/dashy/tree/master/services/serverless-functions) directory
-
-```javascript
-exports.handler = async () => ({
- statusCode: 200,
- body: 'Return some data here...',
-});
-```
-
-#### 3. Redirect the Node endpoint to the function
-
-In the [`netlify.toml`](https://github.com/Lissy93/dashy/blob/FEATURE/serverless-functions/netlify.toml) file, add a 301 redirect, with the path to the original Node.js endpoint, and the name of your cloud function
-
-```toml
-[[redirects]]
- from = "/status-check"
- to = "/.netlify/functions/cloud-status-check"
- status = 301
- force = true
-```
-
----
-
-## Hiding Page Furniture on Certain Routes
-For some pages (such as the login page, the minimal start page, etc) the basic page furniture, (like header, footer, nav, etc) is not needed. This section explains how you can hide furniture on a new view (step 1), or add a component that should be hidden on certain views (step 2).
-
-##### 1. Add the route name to the should hide array
-
-In [`./src/utils/defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js), there's an array called `hideFurnitureOn`. Append the name of the route (the same as it appears in [`router.js`](https://github.com/Lissy93/dashy/blob/master/src/router.js)) here.
-
-##### 2. Add the conditional to the structural component to hide
-
-First, import the helper function:
-```javascript
-import { shouldBeVisible } from '@/utils/MiscHelpers';
-```
-
-Then you can create a computed value, that calls this function, passing in the route name:
-```javascript
-export default {
- ...
- computed: {
- ...
- isVisible() {
- return shouldBeVisible(this.$route.name);
- },
- },
-};
-```
-
-Finally, in the markup of your component, just add a `v-if` statement, referencing your computed value
-```vue
-
- ...
-
-```
-
----
-
-## Adding / Using Environmental Variables
-All environmental variables are optional. Currently there are not many environmental variables used, as most of the user preferences are stored under `appConfig` in the `conf.yml` file.
-
-You can set variables either in your environment, or using the [`.env`](https://github.com/Lissy93/dashy/blob/master/.env) file.
-
-Any environmental variables used by the frontend are preceded with `VUE_APP_`. Vue will merge the contents of your `.env` file into the app in a similar way to the ['dotenv'](https://github.com/motdotla/dotenv) package, where any variables that you set on your system will always take preference over the contents of any `.env` file.
-
-If add any new variables, ensure that there is always a fallback (define it in [`defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js)), so as to not cause breaking changes. Don't commit the contents of your `.env` file to git, but instead take a few moments to document what you've added under the appropriate section. Try and follow the concepts outlined in the [12 factor app](https://12factor.net/config).
+# Development Guides
+
+A series of short tutorials, to guide you through the most common development tasks.
+
+Sections:
+- [Creating a new theme](#creating-a-new-theme)
+- [Writing Translations](#writing-translations)
+- [Adding a new option in the config file](#adding-a-new-option-in-the-config-file)
+- [Updating Dependencies](#updating-dependencies)
+- [Writing Netlify Cloud Functions](#developing-netlify-cloud-functions)
+- [Hiding Page Furniture](#hiding-page-furniture-on-certain-routes)
+- [Adding / Using Environmental Variables](#adding--using-environmental-variables)
+- [Building a Widget](#building-a-widget)
+
+## Creating a new theme
+
+Adding a new theme is really easy. There's two things you need to do: Pass the theme name to Dashy, so that it can be added to the theme selector dropdown menu, and then write some styles!
+
+##### 1. Add Theme Name
+Choose a snappy name for you're theme, and add it to the `builtInThemes` array inside [`defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js#L27).
+
+##### 2. Write some Styles!
+Put your theme's styles inside [`color-themes.scss`](https://github.com/Lissy93/dashy/blob/master/src/styles/color-themes.scss).
+Create a new block, and make sure that `data-theme` matches the theme name you chose above. For example:
+
+```css
+html[data-theme='tiger'] {
+ --primary: #f58233;
+ --background: #0b1021;
+}
+```
+
+Then you can go ahead and write you're own custom CSS. Although all CSS is supported here, the best way to define you're theme is by setting the CSS variables. You can find a [list of all CSS variables, here](https://github.com/Lissy93/dashy/blob/master/docs/theming.md#css-variables).
+
+For a full guide on styling, see [Theming Docs](./theming.md).
+
+Note that if you're theme is just for yourself, and you're not submitting a PR, then you can instead just pass it under `appConfig.cssThemes` inside your config file. And then put your theme in your own stylesheet, and pass it into the Docker container - [see how](https://github.com/Lissy93/dashy/blob/master/docs/theming.md#adding-your-own-theme).
+
+## Writing Translations
+
+For full docs about Dashy's multi-language support, see [Multi-Language Support](./multi-language-support.md)
+
+Dashy is using [vue-i18n](https://vue-i18n.intlify.dev/guide/) to manage multi-language support.
+
+Adding a new language is pretty straightforward, with just three steps:
+
+##### 1. Create a new Language File
+Create a new JSON file in `./src/assets/locales` name is a 2-digit [ISO-639 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language, E.g. for German `de.json`, French `fr.json` or Spanish `es.json` - You can find a list of all ISO codes at [iso.org](https://www.iso.org/obp/ui).
+
+##### 2. Translate!
+Using [`en.json`](https://github.com/Lissy93/dashy/tree/master/src/assets/locales/en.json) as an example, translate the JSON values to your language, while leaving the keys as they are. It's fine to leave out certain items, as if they're missing they will fall-back to English. If you see any attribute which include curly braces (`{xxx}`), then leave the inner value of these braces as is, as this is for variables.
+
+```json
+{
+ "theme-maker": {
+ "export-button": "Benutzerdefinierte Variablen exportieren",
+ "reset-button": "Stile zurücksetzen für",
+ "show-all-button": "Alle Variablen anzeigen",
+ "save-button": "Speichern",
+ "cancel-button": "Abbrechen",
+ "saved-toast": "{theme} Erfolgreich aktualisiert",
+ "reset-toast": "Benutzerdefinierte Farben für {theme} entfernt"
+ },
+}
+```
+
+##### 3. Add your file to the app
+
+In [`./src/utils/languages.js`](https://github.com/Lissy93/dashy/tree/master/src/utils/languages.js), you need to do 2 small things:
+
+First import your new translation file, do this at the top of the page.
+E.g. `import de from '@/assets/locales/de.json';`
+
+Second, add it to the array of languages, e.g:
+```javascript
+export const languages = [
+ {
+ name: 'English',
+ code: 'en',
+ locale: en,
+ flag: '🇬🇧',
+ },
+ {
+ name: 'German', // The name of your language
+ code: 'de', // The ISO code of your language
+ locale: de, // The name of the file you imported (no quotes)
+ flag: '🇩🇪', // An optional flag emoji
+ },
+];
+```
+You can also add your new language to the readme, under the [Language Switching](https://github.com/Lissy93/dashy#language-switching-) section, and optionally include your name/ username if you'd like to be credited for your work. Done!
+
+If you are not comfortable with making pull requests, or do not want to modify the code, then feel free to instead send the translated file to me, and I can add it into the application. I will be sure to credit you appropriately.
+
+# Adding a new option in the config file
+
+This section is for, if you're adding a new component or setting, that requires an additional item to be added to the users config file.
+
+All of the users config is specified in `./public/conf.yml` - see [Configuring Docs](./configuring.md) for info.
+Before adding a new option in the config file, first ensure that there is nothing similar available, that is is definitely necessary, it will not conflict with any other options and most importantly that it will not cause any breaking changes. Ensure that you choose an appropriate and relevant section to place it under.
+
+Next decide the most appropriate place for your attribute:
+- Application settings should be located under `appConfig`
+- Page info (such as text and metadata) should be under `pageInfo`
+- Data relating to specific sections should be under `section[n].displayData`
+- And for setting applied to specific items, it should be under `item[n]`
+
+In order for the user to be able to add your new attribute using the Config Editor, and for the build validation to pass, your attribute must be included within the [ConfigSchema](https://github.com/Lissy93/dashy/blob/master/src/utils/ConfigSchema.js). You can read about how to do this on the [ajv docs](https://ajv.js.org/json-schema.html). Give your property a type and a description, as well as any other optional fields that you feel are relevant. For example:
+
+```json
+"fontAwesomeKey": {
+ "type": "string",
+ "pattern": "^[a-z0-9]{10}$",
+ "description": "API key for font-awesome",
+ "example": "0821c65656"
+}
+```
+or
+```json
+"iconSize": {
+ "enum": [ "small", "medium", "large" ],
+ "default": "medium",
+ "description": "The size of each link item / icon"
+}
+```
+
+Next, if you're property should have a default value, then add it to [`defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js). This ensures that nothing will break if the user does not use your property, and having all defaults together keeps things organised and easy to manage.
+
+If your property needs additional logic for fetching, setting or processing, then you can add a helper function within [`ConfigHelpers.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/ConfigHelpers.js).
+
+Finally, add your new property to the [`configuring.md`](./configuring.md) API docs. Put it under the relevant section, and be sure to include field name, data type, a description and mention that it is optional. If your new feature needs more explaining, then you can also document it under the relevant section elsewhere in the documentation.
+
+Checklist:
+- [ ] Ensure the new attribute is actually necessary, and nothing similar already exists
+- [ ] Update the [Schema](https://github.com/Lissy93/dashy/blob/master/src/utils/ConfigSchema.js) with the parameters for your new option
+- [ ] Set a default value (if required) within [`defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js)
+- [ ] Document the new value in [`configuring.md`](./configuring.md)
+- [ ] Test that the reading of the new attribute is properly handled, and will not cause any errors when it is missing or populated with an unexpected value
+
+---
+
+## Updating Dependencies
+
+Running `yarn upgrade` will updated all dependencies based on the ranges specified in the `package.json`. The `yarn.lock` file will be updated, as will the contents of `./node_modules`, for more info, see the [yarn upgrade documentation](https://classic.yarnpkg.com/en/docs/cli/upgrade/). It is important to thoroughly test after any big dependency updates.
+
+---
+
+## Developing Netlify Cloud Functions
+
+When Dashy is deployed to Netlify, it is effectively running as a static app, and therefore the server-side code for the Node.js endpoints is not available. However Netlify now supports serverless cloud lambda functions, which can be used to replace most functionality.
+
+#### 1. Run Netlify Dev Server
+
+First off, install the Netlify CLI: `npm install netlify-cli -g`
+Then, from within the root of Dashy's directory, start the server, by running: `netlify dev`
+
+#### 2. Create a lambda function
+
+This should be saved it in the [`./services/serverless-functions`](https://github.com/Lissy93/dashy/tree/master/services/serverless-functions) directory
+
+```javascript
+exports.handler = async () => ({
+ statusCode: 200,
+ body: 'Return some data here...',
+});
+```
+
+#### 3. Redirect the Node endpoint to the function
+
+In the [`netlify.toml`](https://github.com/Lissy93/dashy/blob/FEATURE/serverless-functions/netlify.toml) file, add a 301 redirect, with the path to the original Node.js endpoint, and the name of your cloud function
+
+```toml
+[[redirects]]
+ from = "/status-check"
+ to = "/.netlify/functions/cloud-status-check"
+ status = 301
+ force = true
+```
+
+---
+
+## Hiding Page Furniture on Certain Routes
+For some pages (such as the login page, the minimal start page, etc) the basic page furniture, (like header, footer, nav, etc) is not needed. This section explains how you can hide furniture on a new view (step 1), or add a component that should be hidden on certain views (step 2).
+
+##### 1. Add the route name to the should hide array
+
+In [`./src/utils/defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js), there's an array called `hideFurnitureOn`. Append the name of the route (the same as it appears in [`router.js`](https://github.com/Lissy93/dashy/blob/master/src/router.js)) here.
+
+##### 2. Add the conditional to the structural component to hide
+
+First, import the helper function:
+```javascript
+import { shouldBeVisible } from '@/utils/SectionHelpers';
+```
+
+Then you can create a computed value, that calls this function, passing in the route name:
+```javascript
+export default {
+ ...
+ computed: {
+ ...
+ isVisible() {
+ return shouldBeVisible(this.$route.name);
+ },
+ },
+};
+```
+
+Finally, in the markup of your component, just add a `v-if` statement, referencing your computed value
+```vue
+
+ ...
+
+```
+
+---
+
+## Adding / Using Environmental Variables
+All environmental variables are optional. Currently there are not many environmental variables used, as most of the user preferences are stored under `appConfig` in the `conf.yml` file.
+
+You can set variables either in your environment, or using the [`.env`](https://github.com/Lissy93/dashy/blob/master/.env) file.
+
+Any environmental variables used by the frontend are preceded with `VUE_APP_`. Vue will merge the contents of your `.env` file into the app in a similar way to the ['dotenv'](https://github.com/motdotla/dotenv) package, where any variables that you set on your system will always take preference over the contents of any `.env` file.
+
+If add any new variables, ensure that there is always a fallback (define it in [`defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js)), so as to not cause breaking changes. Don't commit the contents of your `.env` file to git, but instead take a few moments to document what you've added under the appropriate section. Try and follow the concepts outlined in the [12 factor app](https://12factor.net/config).
+
+---
+
+## Building a Widget
+
+### Step 0 - Prerequisites
+
+If this is your first time working on Dashy, then the [Developing Docs](https://github.com/Lissy93/dashy/blob/master/docs/developing.md) instructions for project setup and running. In short, you just need to clone the project, cd into it, install dependencies (`yarn`) and then start the development server (`yarn dev`).
+
+To build a widget, you'll also need some basic knowledge of Vue.js. The [official Vue docs](https://vuejs.org/v2/guide/) provides a good starting point, as does [this guide](https://www.taniarascia.com/getting-started-with-vue/) by Tania Rascia
+
+If you just want to jump straight in, then [here](https://github.com/Lissy93/dashy/commit/3da76ce2999f57f76a97454c0276301e39957b8e) is a complete implementation of a new example widget, or take a look at the [`XkcdComic.vue`](https://github.com/Lissy93/dashy/blob/master/src/components/Widgets/XkcdComic.vue) widget, which is pretty simple.
+
+
+### Step 1 - Create Widget
+
+Firstly, create a new `.vue` file under [`./src/components/Widgets`](https://github.com/Lissy93/dashy/tree/master/src/components/Widgets).
+
+
+```vue
+
+
+
+
+
+
+
+
+```
+
+All widgets extend from the [Widget](https://github.com/Lissy93/dashy/blob/master/src/mixins/WidgetMixin.js) mixin. This provides some basic functionality that is shared by all widgets. The mixin includes the following `options`, `startLoading()`, `finishLoading()`, `error()` and `update()`.
+- **Getting user options: `options`**
+ - Any user-specific config can be accessed with `this.options.something` (where something is the data key your accessing)
+- **Loading state: `startLoading()` and `finishLoading()`**
+ - You can show the loader with `this.startLoading()`, then when your data request completes, hide it again with `this.finishLoading()`
+- **Error handling: `error()`**
+ - If something goes wrong (such as API error, or missing user parameters), then call `this.error()` to show message to user
+- **Updating data: `update()`**
+ - When the user clicks the update button, or if continuous updates are enabled, then the `update()` method within your widget will be called
+
+### Step 2 - Adding Functionality
+
+**Accessing User Options**
+
+If your widget is going to accept any parameters from the user, then we can access these with `this.options.[parmName]`. It's best to put these as computed properties, which will enable us to check it exists, is valid, and if needed format it. For example, if we have an optional property called `count` (to determine number of results), we can do the following, and then reference it within our component with `this.count`
+
+```javascript
+computed: {
+ count() {
+ if (!this.options.count) {
+ return 5;
+ }
+ return this.options.count;
+ },
+ ...
+},
+```
+
+**Adding an API Endpoint**
+
+If your widget makes a data request, then add the URL for the API under point to the `widgetApiEndpoints` array in [`defaults.js`](https://github.com/Lissy93/dashy/blob/master/src/utils/defaults.js#L207)
+
+```javascript
+widgetApiEndpoints: {
+ ...
+ exampleEndpoint: 'https://hub.dummyapis.com/ImagesList',
+},
+```
+
+Then in your widget file:
+
+```javascript
+import { widgetApiEndpoints } from '@/utils/defaults';
+```
+
+For GET requests, you may need to add some parameters onto the end of the URL. We can use another computed property for this, for example:
+
+```javascript
+endpoint() {
+ return `${widgetApiEndpoints.exampleEndpoint}?count=${this.count}`;
+},
+```
+
+**Making an API Request**
+
+Axios is used for making data requests, so import it into your component: `import axios from 'axios';`
+
+Under the `methods` block, we'll create a function called `fetchData`, here we can use Axios to make a call to our endpoint.
+
+```javascript
+fetchData() {
+ axios.get(this.endpoint)
+ .then((response) => {
+ this.processData(response.data);
+ })
+ .catch((dataFetchError) => {
+ this.error('Unable to fetch data', dataFetchError);
+ })
+ .finally(() => {
+ this.finishLoading();
+ });
+},
+```
+
+There are three things happening here:
+- If the response completes successfully, we'll pass the results to another function that will handle them
+- If there's an error, then we call `this.error()`, which will show a message to the user
+- Whatever the result, once the request has completed, we call `this.finishLoading()`, which will hide the loader
+
+**Processing Response**
+
+In the above example, we call the `processData()` method with the result from the API, so we need to create that under the `methods` section. How you handle this data will vary depending on what's returned by the API, and what you want to render to the user. But however you do it, you will likely need to create a data variable to store the response, so that it can be easily displayed in the HTML.
+
+```javascript
+data() {
+ return {
+ myResults: null,
+ };
+},
+```
+
+And then, inside your `processData()` method, you can set the value of this, with:
+
+```javascript
+`this.myResults = 'whatever'`
+```
+
+**Rendering Response**
+
+Now that the results are in the correct format, and stored as data variables, we can use them within the `` to render results to the user. Again, how you do this will depend on the structure of your data, and what you want to display, but at it's simplest, it might look something like this:
+
+```vue
+
{{ myResults }}
+```
+
+**Styling**
+
+Styles can be written your your widget within the `
+```
+
+For examples of finished widget components, see the [Widgets](https://github.com/Lissy93/dashy/tree/master/src/components/Widgets) directory. Specifically, the [`XkcdComic.vue`](https://github.com/Lissy93/dashy/blob/master/src/components/Widgets/XkcdComic.vue) widget is quite minimal, so would make a good example, as will [this example implementation](https://github.com/Lissy93/dashy/commit/3da76ce2999f57f76a97454c0276301e39957b8e).
+
+
+### Step 3 - Register
+
+Next, import and register your new widget, in [`WidgetBase.vue`](https://github.com/Lissy93/dashy/blob/master/src/components/Widgets/WidgetBase.vue). In this file, you'll need to add the following:
+
+Import your widget file
+```javascript
+import ExampleWidget from '@/components/Widgets/ExampleWidget.vue';
+```
+
+Then register the component
+```javascript
+components: {
+ ...
+ ExampleWidget,
+},
+```
+
+Finally, add the markup to render it. The only attribute you need to change here is, setting `widgetType === 'example'` to your widget's name.
+```vue
+
+```
+
+### Step 4 - Docs
+
+Finally, add some documentation for your widget in the [Widget Docs](https://github.com/Lissy93/dashy/blob/master/docs/widgets.md), so that others know hoe to use it. Include the following information: Title, short description, screenshot, config options and some example YAML.
+
+
+**Summary**: For a complete example of everything discussed here, see: [`3da76ce`](https://github.com/Lissy93/dashy/commit/3da76ce2999f57f76a97454c0276301e39957b8e)
diff --git a/docs/readme.md b/docs/readme.md
index ee2e2184..32df91d6 100644
--- a/docs/readme.md
+++ b/docs/readme.md
@@ -18,12 +18,13 @@
### Feature Docs
- [Authentication](/docs/authentication.md) - Guide to setting up authentication to protect your dashboard
- [Alternate Views](/docs/alternate-views.md) - Outline of available pages / views and item opening methods
-- [Backup & Restore](/docs/backup-restore.md) - Guide to Dashy's cloud sync feature
-- [Icons](/docs/icons.md) - Outline of all available icon types for sections and items
+- [Backup & Restore](/docs/backup-restore.md) - Guide to backing up config with Dashy's cloud sync feature
+- [Icons](/docs/icons.md) - Outline of all available icon types for sections and items, with examples
- [Language Switching](/docs/multi-language-support.md) - Details on how to switch language, or add a new locale
- [Status Indicators](/docs/status-indicators.md) - Using Dashy to monitor uptime and status of your apps
-- [Searching & Shortcuts](/docs/searching.md) - Finding and launching your apps, and using keyboard shortcuts
-- [Theming](/docs/theming.md) - Complete guide to applying, writing and modifying themes and styles
+- [Searching & Shortcuts](/docs/searching.md) - Searching, launching methods + keyboard shortcuts
+- [Theming](/docs/theming.md) - Complete guide to applying, writing and modifying themes + styles
+- [Widgets](/docs/widgets.md) - List of all dynamic content widgets, with usage guides and examples
### Misc
- [Privacy & Security](/docs/privacy.md) - List of requests, potential issues, and security resources
diff --git a/docs/showcase.md b/docs/showcase.md
index f52ebe1c..a5dd55fd 100644
--- a/docs/showcase.md
+++ b/docs/showcase.md
@@ -1,139 +1,147 @@
-# *Dashy Showcase* 🌟
-
-| 💗 Do you use Dashy? Got a sweet dashboard? Submit it to the showcase! 👉 [See How](#submitting-your-dashboard) |
-|-|
-
-### Home Lab 2.0
-
-![screenshot-homelab](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/1-home-lab-material.png)
-
----
-
-### Ratty222
-> By [@ratty222](https://github.com/ratty222) ([#384](https://github.com/Lissy93/dashy/discussions/384))
-
-![screenshot-ratty222-dashy](https://user-images.githubusercontent.com/1862727/147582551-4c655d37-8bcc-4f95-ab41-164a9d0d6a07.png)
-
----
-
-### Networking Services
-> By [@Lissy93](https://github.com/lissy93)
-
-![screenshot-networking-services](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/2-networking-services-minimal-dark.png)
-
----
-
-### Homelab & VPS dashboard
-> By [@shadowking001](https://github.com/shadowking001)
-
-![screenshot-shadowking001-dashy](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/8-shadowking001s-dashy.png)
-
----
-
-### EVO Dashboard
-
-> By [@EVOTk](https://github.com/EVOTk)
-
-![screenshot-evo-dashboard](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/12-evo-dashboard.png)
-
----
-
-### NAS Home Dashboard
-> By [@cerealconyogurt](https://github.com/cerealconyogurt)
-
-![screenshot-networking-services](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/6-nas-home-dashboard.png)
-
----
-
-### Dashy Live
-> By [@Lissy93](https://github.com/lissy93)
-
-> A dashboard I made to manage all project development links from one place. View demo at [live.dashy.to](https://live.dashy.to/).
-
-![screenshot-dashy-live](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/10-dashy-live.png)
-
-### CFT Toolbox
-
-![screenshot-cft-toolbox](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/3-cft-toolbox.png)
-
----
-
-### Bookmarks
-
-![screenshot-bookmarks](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/4-bookmarks-colourful.png)
-
----
-
-### Project Management
-
-![screenshot-project-managment](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/5-project-managment.png)
-
----
-
-### Dashy Example
-
-> An example dashboard, by [@Lissy93](https://github.com/lissy93). View live at [demo.dashy.to](https://demo.dashy.to/).
-
-![screenshot-dashy-example](https://i.ibb.co/YbzqPK7/demo-dashy.png)
-
----
-
-### First Week of Self-Hosting
-> By [u//RickyCZ](https://www.reddit.com/user/RickyCZ)
-
-![screenshot-week-of-self-hosting](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/11-ricky-cz.png)
-
----
-
-### HomeLAb 3.0
-
-> By [@skoogee](https://github.com/skoogee) (http://zhrn.cc)
-
-> Dashy, is the most complete dashboard I ever tried, has all the features, and it sets itself apart from the rest. It is my default homepage now. I am thankful to the developer @Lissy93 for sharing such a wonderful creation.
-
-[![screenshot-12-skoogee-homelab-3](https://i.ibb.co/F5yBTsT/12-skoogee-homelab-3.png)](https://ibb.co/album/ynSwzm)
-
----
-
-### Ground Control
-> By [@dtctek](https://github.com/dtctek)
-
-![screenshot-ground-control](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/7-ground-control-dtctek.png)
-
----
-
-### Yet Another Homelab
-
-![screenshot-yet-another-homelab](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/9-home-lab-oblivion.png)
-
----
-
-## Submitting your Dashboard
-
-#### How to Submit
-- [Open an Issue](https://git.io/JEtgM)
-- [Open a PR](https://github.com/Lissy93/dashy/compare)
-
-#### What to Include
-Please include the following information:
-- A single high-quality screenshot of your Dashboard
-- A short title (it doesn't have to be particularly imaginative)
-- An optional description, you could include details on anything interesting or unique about your dashboard, or say how you use it, and why it's awesome
-- Optionally leave your name or username, with a link to your GitHub, Twitter or Website
-
-#### Template
-
-If you're submitting a pull request, please use a format similar to this:
-
-```
-### [Dashboard Name] (required)
-
-> Submitted by [@username](https://github.com/user) (optional)
-
-![dashboard-screenshot](/docs/showcase/screenshot-name.jpg) (required)
-
-[An optional text description, or any interesting details] (optional)
-
----
-
-```
+# *Dashy Showcase* 🌟
+
+| 💗 Do you use Dashy? Got a sweet dashboard? Submit it to the showcase! 👉 [See How](#submitting-your-dashboard) |
+|-|
+
+### Home Lab 2.0
+
+![screenshot-homelab](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/1-home-lab-material.png)
+
+---
+
+### Ratty222
+> By [@ratty222](https://github.com/ratty222) ([#384](https://github.com/Lissy93/dashy/discussions/384))
+
+![screenshot-ratty222-dashy](https://user-images.githubusercontent.com/1862727/147582551-4c655d37-8bcc-4f95-ab41-164a9d0d6a07.png)
+
+---
+
+### Networking Services
+> By [@Lissy93](https://github.com/lissy93)
+
+![screenshot-networking-services](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/2-networking-services-minimal-dark.png)
+
+---
+
+### Homelab & VPS dashboard
+> By [@shadowking001](https://github.com/shadowking001)
+
+![screenshot-shadowking001-dashy](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/8-shadowking001s-dashy.png)
+
+---
+
+### EVO Dashboard
+
+> By [@EVOTk](https://github.com/EVOTk)
+
+![screenshot-evo-dashboard](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/12-evo-dashboard.png)
+
+---
+
+### NAS Home Dashboard
+> By [@cerealconyogurt](https://github.com/cerealconyogurt)
+
+![screenshot-networking-services](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/6-nas-home-dashboard.png)
+
+---
+
+### Dashy Live
+> By [@Lissy93](https://github.com/lissy93)
+
+> A dashboard I made to manage all project development links from one place. View demo at [live.dashy.to](https://live.dashy.to/).
+
+![screenshot-dashy-live](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/10-dashy-live.png)
+
+### CFT Toolbox
+
+![screenshot-cft-toolbox](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/3-cft-toolbox.png)
+
+---
+
+### Bookmarks
+
+![screenshot-bookmarks](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/4-bookmarks-colourful.png)
+
+---
+
+### Project Management
+
+![screenshot-project-managment](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/5-project-managment.png)
+
+---
+
+### Dashy Example
+
+> An example dashboard, by [@Lissy93](https://github.com/lissy93). View live at [demo.dashy.to](https://demo.dashy.to/).
+
+![screenshot-dashy-example](https://i.ibb.co/YbzqPK7/demo-dashy.png)
+
+---
+
+### First Week of Self-Hosting
+> By [u//RickyCZ](https://www.reddit.com/user/RickyCZ)
+
+![screenshot-week-of-self-hosting](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/11-ricky-cz.png)
+
+---
+
+### HomeLAb 3.0
+
+> By [@skoogee](https://github.com/skoogee) (http://zhrn.cc)
+
+> Dashy, is the most complete dashboard I ever tried, has all the features, and it sets itself apart from the rest. It is my default homepage now. I am thankful to the developer @Lissy93 for sharing such a wonderful creation.
+
+[![screenshot-12-skoogee-homelab-3](https://i.ibb.co/F5yBTsT/12-skoogee-homelab-3.png)](https://ibb.co/album/ynSwzm)
+
+---
+
+### Ground Control
+> By [@dtctek](https://github.com/dtctek)
+
+![screenshot-ground-control](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/7-ground-control-dtctek.png)
+
+---
+
+### Crypto Dash
+> Example usage of widgets to monitor cryptocurrencies news, prices and data. Config is [available here](https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10#file-example-8-dashy-crypto-widgets-conf-yml)
+
+
+![screenshot-crypto-dash](https://user-images.githubusercontent.com/1862727/147394584-352fe3bf-740d-4624-a01b-9003a97bc832.png)
+
+---
+
+### Yet Another Homelab
+
+![screenshot-yet-another-homelab](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/9-home-lab-oblivion.png)
+
+---
+
+## Submitting your Dashboard
+
+#### How to Submit
+- [Open an Issue](https://git.io/JEtgM)
+- [Open a PR](https://github.com/Lissy93/dashy/compare)
+
+#### What to Include
+Please include the following information:
+- A single high-quality screenshot of your Dashboard
+- A short title (it doesn't have to be particularly imaginative)
+- An optional description, you could include details on anything interesting or unique about your dashboard, or say how you use it, and why it's awesome
+- Optionally leave your name or username, with a link to your GitHub, Twitter or Website
+
+#### Template
+
+If you're submitting a pull request, please use a format similar to this:
+
+```
+### [Dashboard Name] (required)
+
+> Submitted by [@username](https://github.com/user) (optional)
+
+![dashboard-screenshot](/docs/showcase/screenshot-name.jpg) (required)
+
+[An optional text description, or any interesting details] (optional)
+
+---
+
+```
diff --git a/docs/widgets.md b/docs/widgets.md
new file mode 100644
index 00000000..dc962be8
--- /dev/null
+++ b/docs/widgets.md
@@ -0,0 +1,1256 @@
+# Widgets
+
+Dashy has support for displaying dynamic content in the form of widgets. There are several built-in widgets available out-of-the-box as well as support for custom widgets to display stats from almost any service with an API.
+
+> ℹ️ **Note**: Widgets are still in the Alpha-phase of development.
+> If you find a bug, please raise it.
+> Adding / editing widgets through the UI isn't yet supported, you will need to do this in the YAML config file.
+
+##### Contents
+- [General Widgets](#general-widgets)
+ - [Clock](#clock)
+ - [Weather](#weather)
+ - [Weather Forecast](#weather-forecast)
+ - [Crypto Watch List](#crypto-watch-list)
+ - [Crypto Price History](#crypto-token-price-history)
+ - [RSS Feed](#rss-feed)
+ - [Code Stats](#code-stats)
+ - [Vulnerability Feed](#vulnerability-feed)
+ - [Sports Scores](#sports-scores)
+ - [Exchange Rates](#exchange-rates)
+ - [Public Holidays](#public-holidays)
+ - [TFL Status](#tfl-status)
+ - [Stock Price History](#stock-price-history)
+ - [Joke of the Day](#joke)
+ - [XKCD Comics](#xkcd-comics)
+ - [News Headlines](#news-headlines)
+ - [Flight Data](#flight-data)
+ - [NASA APOD](#astronomy-picture-of-the-day)
+ - [GitHub Trending](#github-trending)
+ - [GitHub Profile Stats](#github-profile-stats)
+ - [Public IP Address](#public-ip)
+- [Self-Hosted Services Widgets](#self-hosted-services-widgets)
+ - [System Info](#system-info)
+ - [Cron Monitoring](#cron-monitoring-health-checks)
+ - [CPU History](#cpu-history-netdata)
+ - [Memory History](#memory-history-netdata)
+ - [System Load History](#load-history-netdata)
+ - [Pi Hole Stats](#pi-hole-stats)
+ - [Pi Hole Queries](#pi-hole-queries)
+ - [Recent Traffic](#recent-traffic)
+ - [Stat Ping Statuses](#stat-ping-statuses)
+- [Dynamic Widgets](#dynamic-widgets)
+ - [Iframe Widget](#iframe-widget)
+ - [HTML Embed Widget](#html-embedded-widget)
+ - [API Response](#api-response)
+ - [Prometheus Data](#prometheus-data)
+ - [Data Feed](#data-feed)
+- [Usage & Customizations](#usage--customizations)
+ - [Widget Usage Guide](#widget-usage-guide)
+ - [Continuous Updates](#continuous-updates)
+ - [Custom CSS Styling](#widget-styling)
+ - [Language Translations](#language-translations)
+ - [Widget UI Options](#widget-ui-options)
+ - [Building a Widget](#build-your-own-widget)
+ - [Requesting a Widget](#requesting-a-widget)
+
+## General Widgets
+
+### Clock
+
+A simple, live-updating time and date widget with time-zone support. All fields are optional.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`timeZone`** | `string` | _Optional_ | The time zone to display date and time in. Specified as Region/City, for example: `Australia/Melbourne`. See the [Time Zone DB](https://timezonedb.com/time-zones) for a full list of supported TZs. Defaults to the browser / device's local time
+**`format`** | `string` | _Optional_ | A country code for displaying the date and time in local format. Specified as `[ISO-3166]-[ISO-639]`, for example: `en-AU`. See [here](https://www.fincher.org/Utilities/CountryLanguageList.shtml) for a full list of locales. Defaults to the browser / device's region
+**`hideDate`** | `boolean` | _Optional_ | If set to `true`, the date and city will not be shown. Defaults to `false`
+
+##### Example
+
+```yaml
+- type: clock
+ options:
+ timeZone: Europe/London
+ format: en-GB
+ hideDate: false
+```
+
+##### Info
+_No external data requests_
+
+---
+
+### Weather
+
+A simple, live-updating local weather component, showing temperature, conditions and more info.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`apiKey`** | `string` | Required | Your OpenWeatherMap API key. You can get one for free at [openweathermap.org](https://openweathermap.org/)
+**`city`** | `string` | Required | A city name to use for fetching weather. This can also be a state code or country code, following the ISO-3166 format
+**`units`** | `string` | _Optional_ | The units to use for displaying data, can be either `metric` or `imperial`. Defaults to `metric`
+**`hideDetails`** | `boolean` | _Optional_ | If set to `true`, the additional details (wind, humidity, pressure, etc) will not be shown. Defaults to `false`
+
+##### Example
+
+```yaml
+- type: weather
+ options:
+ apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ city: London
+ units: metric
+ hideDetails: false
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🔴 Required
+- **Price**: 🟠 Free plan
+- **Privacy**: _See [OWM Privacy Policy](https://openweather.co.uk/privacy-policy)_
+
+---
+
+### Weather Forecast
+
+Displays the weather (temperature and conditions) for the next few days for a given location. Note that this requires either the free [OpenWeatherMap Student Plan](https://home.openweathermap.org/students), or the Premium Plan.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`apiKey`** | `string` | Required | Your OpenWeatherMap API key. You can get one for free at [openweathermap.org](https://openweathermap.org/)
+**`city`** | `string` | Required | A city name to use for fetching weather. This can also be a state code or country code, following the ISO-3166 format
+**`numDays`** | `number` | _Optional_ | The number of days to display of forecast info to display. Defaults to `4`, max `16` days
+**`units`** | `string` | _Optional_ | The units to use for displaying data, can be either `metric` or `imperial`. Defaults to `metric`
+**`hideDetails`** | `boolean` | _Optional_ | If set to `true`, the additional details (wind, humidity, pressure, etc) will not be shown. Defaults to `false`
+
+##### Example
+
+```yaml
+- type: weather-forecast
+ options:
+ city: California
+ numDays: 6
+ apiKey: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+ units: imperial
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🔴 Required
+- **Price**: 🔴 Premium (free for personal use only)
+- **Privacy**: _See [OWM Privacy Policy](https://openweather.co.uk/privacy-policy)_
+
+---
+
+
+### 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.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`assets`** | `string` | _Optional_ | An array of cryptocurrencies, coins and tokens. See [list of supported assets](https://api.coingecko.com/api/v3/asset_platforms). If none are specified, then the top coins by `sortBy` (defaults to market cap) will be returned
+**`currency`** | `string` | _Optional_ | The fiat currency to display price in, expressed as an ISO-4217 alpha code (see [list of currencies](https://www.iban.com/currency-codes)). Defaults to `USD`
+**`sortBy`** | `string` | _Optional_ | The method of sorting results. Can be `marketCap`, `volume` or `alphabetical`. Defaults to `marketCap`.
+**`limit`** | `number` | _Optional_ | Number of results to return, useful when no assets are specified. Defaults to either `all` or `100`
+
+##### Example
+
+```yaml
+- type: crypto-watch-list
+ options:
+ limit: 10
+```
+
+Or
+
+```yaml
+ - type: crypto-watch-list
+ options:
+ currency: GBP
+ sortBy: marketCap
+ assets:
+ - bitcoin
+ - ethereum
+ - monero
+ - cosmos
+ - polkadot
+ - dogecoin
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Privacy**: _See [CoinGecko Privacy Policy](https://www.coingecko.com/en/privacy)_
+
+---
+
+### Crypto Token Price History
+
+Shows recent price history for a given crypto asset, using price data fetched from [CoinGecko](https://www.coingecko.com/)
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`asset`** | `string` | Required | Name of a crypto asset, coin or token to fetch price data for, see [list of supported assets](https://api.coingecko.com/api/v3/asset_platforms)
+**`currency`** | `string` | _Optional_ | The fiat currency to display results in, expressed as an ISO-4217 alpha code (see [list of currencies](https://www.iban.com/currency-codes)). Defaults to `USD`
+**`numDays`** | `number` | _Optional_ | The number of days of price history to render. Defaults to `7`, min: `1`, max: `30` days
+**`chartColor`** | `string` | _Optional_ | Color of the chart value. Defaults to `--widget-text-color` which inherits dashboard primary color
+**`chartHeight`** | `number` | _Optional_ | The height of rendered chart in px. Defaults to `300`
+
+##### Example
+
+```yaml
+- type: crypto-price-chart
+ options:
+ asset: bitcoin
+ currency: GBP
+ numDays: 7
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Privacy**: _See [CoinGecko Privacy Policy](https://www.coingecko.com/en/privacy)_
+
+---
+
+### RSS Feed
+
+Display news and updates from any RSS-enabled service.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`rssUrl`** | `string` | Required | The URL location of your RSS feed
+**`apiKey`** | `string` | _Optional_ | An API key for [rss2json](https://rss2json.com/). It's free, and will allow you to make 10,000 requests per day, you can sign up [here](https://rss2json.com/sign-up)
+**`limit`** | `number` | _Optional_ | The number of posts to return. If you haven't specified an API key, this will be limited to 10
+**`orderBy`** | `string` | _Optional_ | How results should be sorted. Can be either `pubDate`, `author` or `title`. Defaults to `pubDate`
+**`orderDirection`** | `string` | _Optional_ | Order direction of feed items to return. Can be either `asc` or `desc`. Defaults to `desc`
+
+##### Example
+
+```yaml
+- type: rss-feed
+ options:
+ rssUrl: https://www.schneier.com/blog/atom.xml
+ apiKey: xxxx
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟠 Optional
+- **Price**: 🟠 Free Plan (up to 10,000 requests / day)
+- **Privacy**: _See [Rss2Json Privacy Policy](https://rss2json.com/privacy-policy)_
+
+---
+
+### Code Stats
+
+Display your coding summary. [Code::Stats](https://codestats.net/) is a free and open source app that aggregates statistics about your programming activity. Dashy supports both the public instance, as well as self-hosted versions.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`username`** | `string` | Required | Your CodeStats username
+**`hostname`** | `string` | _Optional_ | If your self-hosting CodeStats, then supply the host name. By default it will use the public hosted instance
+**`monthsToShow`** | `number` | _Optional_ | Specify the number of months to render in the historical data chart. Defaults to `6`
+**`hideMeta`** | `boolean` | _Optional_ | Optionally hide the meta section (username, level, all-time and recent XP)
+**`hideHistory`** | `boolean` | _Optional_ | Optionally hide the historical calendar heat map
+**`hideLanguages`** | `boolean` | _Optional_ | Optionally hide the programming languages pie chart
+**`hideMachines`** | `boolean` | _Optional_ | Optionally hide the machines percentage chart
+
+##### Example
+
+```yaml
+- type: code-stats
+ options:
+ username: alicia
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Self-Hosted or Managed
+- **Privacy**: _See [Code::Stats Privacy Policy](https://codestats.net/tos#privacy)_
+
+---
+
+### Vulnerability Feed
+
+Keep track of recent security advisories and vulnerabilities, with optional filtering by score, exploits, vendor and product. All fields are optional.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`sortBy`** | `string` | _Optional_ | The sorting method. Can be either `publish-date`, `last-update` or `cve-code`. Defaults to `publish-date`
+**`limit`** | `number` | _Optional_ | The number of results to fetch. Can be between `5` and `30`, defaults to `10`
+**`minScore`** | `number` | _Optional_ | If set, will only display results with a CVE score higher than the number specified. Can be a number between `0` and `9.9`. By default, vulnerabilities of all CVE scores are shown
+**`hasExploit`** | `boolean` | _Optional_ | If set to `true`, will only show results with active exploits. Defaults to `false`
+**`vendorId`** | `number` | _Optional_ | Only show results from a specific vendor, specified by ID. See [Vendor Search](https://www.cvedetails.com/vendor-search.php) for list of vendors. E.g. `23` (Debian), `26` (Microsoft), `23682` (CloudFlare)
+**`productId`** | `number` | _Optional_ | Only show results from a specific app or product, specified by ID. See [Product Search](https://www.cvedetails.com/product-search.php) for list of products. E.g. `13534` (Docker), `15913` (NextCloud), `19294` (Portainer), `17908` (ProtonMail)
+
+
+##### Example
+
+```yaml
+- type: cve-vulnerabilities
+```
+
+or
+
+```yaml
+- type: cve-vulnerabilities
+ options:
+ sortBy: publish-date
+ productId: 28125
+ hasExploit: true
+ minScore: 5
+ limit: 30
+```
+
+##### Info
+- **CORS**: 🟠 Proxied
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Managed
+- **Privacy**: _See [CVE Details Privacy Policy](https://www.cvedetails.com/privacy.php)_
+
+---
+
+### Sports Scores
+
+Show recent scores and upcoming matches from your favourite sports team. Data is fetched from [TheSportsDB.com](https://www.thesportsdb.com/). From the UI, you can click any other team to view their scores and upcoming games, or click a league name to see all teams.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`teamId`** | `string` | __Optional__ | The ID of a team to fetch scores from. You can search for your team on the [Teams Page](https://www.thesportsdb.com/teams_main.php)
+**`leagueId`** | `string` | __Optional__ | Alternatively, provide a league ID to fetch all games from. You can find the ID on the [Leagues Page](https://www.thesportsdb.com/Sport/Leagues)
+**`pastOrFuture`** | `string` | __Optional__ | Set to `past` to show scores for recent games, or `future` to show upcoming games. Defaults to `past`. You can change this within the UI
+**`apiKey`** | `string` | __Optional__ | Optionally specify your API key, which you can sign up for at [TheSportsDB.com](https://www.thesportsdb.com/)
+**`limit`** | `number` | __Optional__ | To limit output to a certain number of matches, defaults to `15`
+
+##### Example
+
+```yaml
+- type: sports-scores
+ options:
+ teamId: 133636
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟠 Optional
+- **Price**: 🟠 Free plan (upto 30 requests / minute, limited endpoints)
+- **Host**: Managed Instance Only
+- **Privacy**: ⚫ No Policy Available
+
+---
+
+### Exchange Rates
+
+Display current FX rates in your native currency. Hover over a row to view more info, or click to show rates in that currency.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`inputCurrency`** | `string` | Required | The base currency to show results in. Specified as a 3-letter ISO-4217 code, see [here](https://www.exchangerate-api.com/docs/supported-currencies) for the full list of supported currencies, and their symbols
+**`outputCurrencies`** | `array` | Required | List or currencies to show results for. Specified as a 3-letter ISO-4217 code, see [here](https://www.exchangerate-api.com/docs/supported-currencies) for the full list of supported currencies, and their symbols
+**`apiKey`** | `string` | Required | API key for [exchangerate-api.com](https://www.exchangerate-api.com/), usually a 24-digit alpha-numeric string. You can sign up for a free account [here](https://app.exchangerate-api.com/sign-up)
+
+##### Example
+
+```yaml
+- type: exchange-rates
+ options:
+ apiKey: xxxxxxxxxxxxxxxxxxxxxxxx
+ inputCurrency: GBP
+ outputCurrencies:
+ - USD
+ - JPY
+ - HKD
+ - KPW
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🔴 Required
+- **Price**: 🟠 Free plan (upto 100,000 requests/ month)
+- **Host**: Managed Instance Only
+- **Privacy**: _See [ExchangeRateAPI Privacy Policy](https://www.exchangerate-api.com/terms)_
+
+---
+
+### Public Holidays
+
+Counting down to the next day off work? This widget displays upcoming public holidays for your country. Data is fetched from [Enrico](http://kayaposoft.com/enrico/)
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`country`** | `string` | Required | The region to fetch holiday data for, specified as a country code, e.g. `GB` or `US`
+**`holidayType`** | `string` | __Optional__ | The type of holidays to fetch. Can be: `all`, `public_holiday`, `observance`, `school_holiday`, `other_day` or `extra_working_day`. Defaults to `public_holiday`
+**`monthsToShow`** | `number` | __Optional__ | The number of months in advance to show. Min: `1`, max: `24`. Defaults to `12`
+
+##### Example
+
+```yaml
+- type: public-holidays
+ options:
+ country: GB
+ holidayType: all
+ monthsToShow: 12
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Self-Hosted (see [jurajmajer/enrico](https://github.com/jurajmajer/enrico)) or Managed
+- **Privacy**: ⚫ No Policy Available
+
+---
+
+### TFL Status
+
+Shows real-time tube status of the London Underground. All fields are optional.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`showAll`** | `boolean` | _Optional_ | By default, details for lines with a Good Service are not visible, but you can click More Details to see all. Setting this option to `true` will show all lines on initial page load
+**`sortAlphabetically`** | `boolean` | _Optional_ | By default lines are sorted by current status, set this option to `true` to instead sort them alphabetically
+**`linesToShow`** | `array` | _Optional_ | By default all lines are shown. If you're only interested in the status of a few lines, then pass in an array of lines to show, specified by name
+
+##### Example
+
+```yaml
+- type: tfl-status
+```
+
+```yaml
+ - type: tfl-status
+ options:
+ showAll: true
+ sortAlphabetically: true
+ linesToShow:
+ - District
+ - Jubilee
+ - Central
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Managed Instance Only
+- **Privacy**: _See [TFL Privacy Policy](https://tfl.gov.uk/corporate/privacy-and-cookies/)_
+
+---
+
+### Stock Price History
+
+Shows recent price history for a given publicly-traded stock or share
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`apiKey`** | `string` | Required | API key for [Alpha Vantage](https://www.alphavantage.co/), you can get a free API key [here](https://www.alphavantage.co/support/#api-key)
+**`stock`** | `string` | Required | The stock symbol for the asset to fetch data for
+**`priceTime`** | `string` | _Optional_ | The time to fetch price for. Can be `high`, `low`, `open` or `close`. Defaults to `high`
+**`chartColor`** | `string` | _Optional_ | Color of the chart value. Defaults to `--widget-text-color` which inherits dashboard primary color
+**`chartHeight`** | `number` | _Optional_ | The height of rendered chart in px. Defaults to `300`
+
+##### Example
+
+```yaml
+- type: stock-price-chart
+ options:
+ stock: NET
+ apiKey: PGUWSWD6CZTXMT8N
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🔴 Required
+- **Price**: 🟠 Free plan (upto 500 requests/day)
+- **Host**: Managed Instance Only
+- **Privacy**: _See [AlphaVantage Privacy Policy](https://www.alphavantage.co/privacy/)_
+
+---
+
+### Joke
+
+Renders a programming or generic joke. Data is fetched from the [JokesAPI](https://github.com/Sv443/JokeAPI) by @Sv443. All fields are optional.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`category`** | `string` | _Optional_ | Set the category of jokes to return. Use a string to specify a single category, or an array to pass in multiple options. Available options are: `all`, `programming`, `pun`, `dark`, `spooky`, `christmas` and `misc`. An up-to-date list of supported categories can be found [here](https://v2.jokeapi.dev/categories). Defaults to `all`
+**`safeMode`** | `boolean` | _Optional_ | Set to `true`, to prevent the fetching of any NSFW jokes. Defaults to `false`
+**`language`** | `string` | _Optional_ | Specify the language for returned jokes. The following languages are supported: `en`, `cs`, `de`, `es`, `fr` and `pt`, and an up-to-date list of supported languages can be found [here](https://v2.jokeapi.dev/languages). By default, your system language will be used, if it's supported, otherwise English
+
+##### Example
+
+```yaml
+- type: joke
+ options:
+ safeMode: true
+ language: en
+ category: Programming
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Managed Instance or Self-Hosted (see [Sv443/JokeAPI](https://github.com/Sv443/JokeAPI))
+- **Privacy**: _See [SV443's Privacy Policy](https://sv443.net/privacypolicy/en)_
+
+---
+
+### XKCD Comics
+
+Have a laugh with the daily comic from [XKCD](https://xkcd.com/). A classic webcomic website covering everything from Linux, math, romance, science and language. All fields are optional.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`comic`** | `string / number` | _Optional_ | Choose which comic to display. Set to either `random`, `latest` or the series number of a specific comic, like `627`. Defaults to `latest`
+
+##### Example
+
+```yaml
+- type: xkcd-comic
+ options:
+ comic: latest
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Privacy**: ⚫ No Policy Available
+
+---
+
+### News Headlines
+
+Displays the latest news, click to read full article. Date is fetched from various news sources using [Currents API](https://currentsapi.services/en)
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`apiKey`** | `string` | Required | Your API key for CurrentsAPI. This is free, and you can [get one here](https://currentsapi.services/en/register)
+**`country`** | `string` | _Optional_ | Fetch news only from a certain country or region. Specified as a country code, e.g. `GB` or `US`. See [here](https://api.currentsapi.services/v1/available/regions) for a list of supported regions
+**`category`** | `string` | _Optional_ | Only return news from within a given category, e.g. `sports`, `programming`, `world`, `science`. The [following categories](https://api.currentsapi.services/v1/available/categories) are supported
+**`lang`** | `string` | _Optional_ | Specify the language for returned articles as a 2-digit ISO code (limited article support). The [following languages](https://api.currentsapi.services/v1/available/languages) are supported, defaults to `en`
+**`count`** | `number` | _Optional_ | Limit the number of results. Can be between `1` and `200`, defaults to `10`
+**`keywords`** | `string` | _Optional_ | Only return articles that contain an exact match within their title or description
+
+##### Example
+
+```yaml
+- type: news-headlines
+ options:
+ apiKey: xxxxxxx
+ category: world
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🔴 Required
+- **Price**: 🟠 Free plan (upto 600 requests / day)
+- **Host**: Managed Instance Only
+- **Privacy**: _See [CurrentsAPI Privacy Policy](https://currentsapi.services/privacy)_
+
+---
+
+### Flight Data
+
+Displays airport departure and arrival flights, using data from [AeroDataBox](https://www.aerodatabox.com/). Useful if you live near an airport and often wonder where the flight overhead is going to. Hover over a row for more flight data.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`airport`** | `string` | Required | The airport to show flight data from. Should be specified as a 4-character ICAO-code, a full list of which can be found [here](https://en.wikipedia.org/wiki/ICAO_airport_code) (example: `KBJC` or `EGKK`)
+**`apiKey`** | `string` | Required | A valid [RapidAPI](https://rapidapi.com/) Key, with [AeroDataBox](https://rapidapi.com/aerodatabox/api/aerodatabox/) enabled (check in your [Subscription Dashboard](https://rapidapi.com/developer/billing/subscriptions-and-usage)). This API is free to sign up for and use
+**`limit`** | `number` | _Optional_ | For busy airports, you may wish to limit the number of results visible
+**`direction`** | `string` | _Optional_ | By default, both departure and arrival flights will be fetched, if you would like to only show flights in one direction, set this to wither `departure` or `arrival`
+
+##### Example
+
+```yaml
+- type: flight-data
+ options:
+ airport: EGLC
+ apiKey: XXXXX
+ limit: 12
+ direction: all
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🔴 Required
+- **Price**: 🟠 Free plan (upto 150 requests / month)
+- **Host**: Managed Instance Only
+- **Privacy**: _See [AeroDataBox](https://www.aerodatabox.com/#h.p_CXtIYZWF_WQd) and [RapidAPI Policy](https://rapidapi.com/privacy/)_
+
+---
+
+### Astronomy Picture of the Day
+
+Show the NASA Astronomy Pictore of the Day. Data is fetched from [APOD](https://apod.nasa.gov/apod/) using [PawelPleskaczynski/apod_api](https://github.com/PawelPleskaczynski/apod_api).
+
+
+
+##### Options
+
+_No config options._
+
+##### Example
+
+```yaml
+- type: apod
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Managed Instance or Self-Hosted (see [PawelPleskaczynski/apod_api](https://github.com/PawelPleskaczynski/apod_api))
+- **Privacy**: _See [NASA's Privacy Policy](https://www.nasa.gov/about/highlights/HP_Privacy.html)_
+
+---
+
+### GitHub Trending
+
+Displays currently trending projects on GitHub. Optionally specify a language and time-frame. Data is fetched from [Lissy93/gh-trending-no-cors](https://github.com/Lissy93/gh-trending-no-cors) using the GitHub API. All fields are optional.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`lang`** | `string` | _Optional_ | A programming language to fetch trending repos from that category. E.g. `javascript` or `go`
+**`since`** | `string` | _Optional_ | The timeframe to use when calculating trends. Can be either `daily`, `weekly` or `monthly`. Defaults to `daily`
+**`limit`** | `number` | _Optional_ | Optionally limit the number of results. Max `25`, default is `10`
+
+##### Example
+
+```yaml
+- type: github-trending-repos
+ options:
+ limit: 8
+ since: weekly
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Managed Instance or Self-Hosted (see [Lissy93/gh-trending-no-cors](https://github.com/Lissy93/gh-trending-no-cors))
+- **Privacy**: _See [GitHub's Privacy Policy](https://docs.github.com/en/github/site-policy/github-privacy-statement)_
+
+---
+
+### GitHub Profile Stats
+
+Display stats from your GitHub profile, using embedded cards from [anuraghazra/github-readme-stats](https://github.com/anuraghazra/github-readme-stats)
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`username`** | `string` | Required | The GitHub username to fetch info for. E.g. `lissy93`. (Not required if `hideProfileCard` and `hideLanguagesCard` are both set to `true`)
+**`hideProfileCard`** | `boolean` | _Optional_ | If set to `true`, the users profile card will not be shown. Defaults to `false`
+**`hideLanguagesCard`** | `boolean` | _Optional_ | If set to `true`, the users top languages card will not be shown. Defaults to `false`
+**`repos`** | `array` | _Optional_ | If you'd like to also display stats for some GitHub reposotories, then add an array or repo names here. Specified as `[username]/[repo-name]`, e.g. `lissy93/dashy`
+
+
+##### Example
+
+
+```yaml
+- type: github-profile-stats
+ options:
+ username: Lissy93
+ hideLanguagesCard: true
+ repos:
+ - lissy93/dashy
+ - lissy93/personal-security-checklist
+ - lissy93/twitter-sentiment-visualisation
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Managed Instance or Self-Hosted (see [anuraghazra/github-readme-stats](https://github.com/anuraghazra/github-readme-stats))
+- **Privacy**: _See [GitHub's Privacy Policy](https://docs.github.com/en/github/site-policy/github-privacy-statement)_
+
+---
+
+### Public IP
+
+Often find yourself searching "What's my IP", just so you can check your VPN is still connected? This widget displays your public IP address, along with ISP name and approx location. Data is fetched from [IP-API.com](https://ip-api.com/).
+
+
+
+##### Options
+
+_No config options._
+
+##### Example
+
+```yaml
+- type: public-ip
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟠 Optional
+- **Price**: 🟢 Free
+- **Host**: Managed Instance Only
+- **Privacy**: _See [IP-API Privacy Policy](https://ip-api.com/docs/legal)_
+
+---
+
+## Self-Hosted Services Widgets
+
+
+### System Info
+
+Displays info about the server which Dashy is hosted on. Includes user + host, operating system, uptime and basic memory & load data.
+
+
+
+##### Options
+
+_No config options._
+
+##### Example
+
+```yaml
+- type: system-info
+```
+
+##### Info
+No external data requests made
+
+---
+
+### Cron Monitoring (Health Checks)
+
+Cron job monitoring using [Health Checks](https://github.com/healthchecks/healthchecks). Both managed and self-hosted instances are supported.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`apiKey`** | `string` | Required | A read-only API key for the project to monitor. You can generate this by selecting a Project --> Settings --> API Access. Note that you must generate a separate key for each project
+**`host`** | `string` | _Optional_ | If you're self-hosting, or using any instance other than the official (healthchecks.io), you will need to specify the host address. E.g. `https://healthchecks.example.com` or `http://cron-monitoing.local`
+
+##### Example
+
+```yaml
+- type: health-checks
+ options:
+ apiKey: XXXXXXXXX
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🔴 Required
+- **Price**: 🟠 Free plan (upto 20 services, or self-host for unlimited)
+- **Host**: Managed Instance or Self-Hosted (see [GitHub - HealthChecks](https://github.com/healthchecks/healthchecks))
+- **Privacy**: _See [Health-Checks Privacy Policy](https://healthchecks.io/privacy/)_
+
+---
+
+### CPU History (NetData)
+
+Pull recent CPU usage history from NetData.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`host`** | `string` | Required | The URL to your NetData instance
+**`chartHeight`** | `number` | _Optional_ | The height of rendered chart in px. Defaults to `300`
+**`chartColor`** / **`chartColors`** | `string` / `array`| _Optional_ | Color of the chart value(s) as hex codes. `chartColor` is a single value (defaults to `--widget-text-color`), whereas `chartColors` is an array of colors
+
+##### Example
+
+```yaml
+- type: nd-cpu-history
+ options:
+ host: http://192.168.1.1:19999
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Self-Hosted (see [GitHub - NetData](https://github.com/netdata/netdata))
+- **Privacy**: _See [NetData Privacy Policy](https://www.netdata.cloud/data-privacy/)_
+
+---
+
+
+### Memory History (NetData)
+
+Pull recent system RAM usage from NetData, and show as a breakdown of different categories.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`host`** | `string` | Required | The URL to your NetData instance
+**`chartHeight`** | `number` | _Optional_ | The height of rendered chart in px. Defaults to `300`
+**`chartColor`** / **`chartColors`** | `string` / `array`| _Optional_ | Color of the chart value(s) as hex codes. `chartColor` is a single value (defaults to `--widget-text-color`), whereas `chartColors` is an array of colors
+
+##### Example
+
+```yaml
+- type: nd-ram-history
+ options:
+ host: http://192.168.1.1:19999
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Self-Hosted (see [GitHub - NetData](https://github.com/netdata/netdata))
+- **Privacy**: _See [NetData Privacy Policy](https://www.netdata.cloud/data-privacy/)_
+
+---
+
+### Load History (NetData)
+
+Pull recent load usage in 1, 5 and 15 minute intervals, from NetData.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`host`** | `string` | Required | The URL to your NetData instance
+**`chartHeight`** | `number` | _Optional_ | The height of rendered chart in px. Defaults to `300`
+**`chartColor`** / **`chartColors`** | `string` / `array`| _Optional_ | Color of the chart value(s) as hex codes. `chartColor` is a single value (defaults to `--widget-text-color`), whereas `chartColors` is an array of colors
+
+##### Example
+
+```yaml
+- type: nd-load-history
+ options:
+ host: http://192.168.1.1:19999
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Self-Hosted (see [GitHub - NetData](https://github.com/netdata/netdata))
+- **Privacy**: _See [NetData Privacy Policy](https://www.netdata.cloud/data-privacy/)_
+
+---
+
+### Pi Hole Stats
+
+Displays the number of queries blocked by [Pi-Hole](https://pi-hole.net/).
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`hostname`** | `string` | Required | The URL to your Pi-Hole instance
+**`hideStatus`** / **`hideChart`** / **`hideInfo`** | `boolean` | _Optional_ | Optionally hide any of the three parts of the widget
+
+##### Example
+
+```yaml
+- type: pi-hole-stats
+ options:
+ hostname: http://192.168.130.1
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Self-Hosted (see [GitHub - Pi-hole](https://github.com/pi-hole/pi-hole))
+- **Privacy**: _See [Pi-Hole Privacy Guide](https://pi-hole.net/privacy/)_
+
+---
+
+### Pi Hole Queries
+
+Shows top queries that were blocked and allowed by [Pi-Hole](https://pi-hole.net/).
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`hostname`** | `string` | Required | The URL to your Pi-Hole instance
+**`apiKey`** | `string` | Required | Your Pi-Hole web password. It is **NOT** your pi-hole admin interface or server password. It can be found in `/etc/pihole/setupVars.conf`, and is a 64-character located on the line that starts with `WEBPASSWORD`
+**`count`** | `number` | _Optional_ | The number of queries to display. Defaults to `10`
+
+##### Example
+
+```yaml
+- type: pi-hole-top-queries
+ options:
+ hostname: https://pi-hole.local
+ apiKey: xxxxxxxxxxxxxxxxxxxxxxx
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🔴 Required
+- **Price**: 🟢 Free
+- **Host**: Self-Hosted (see [GitHub - Pi-hole](https://github.com/pi-hole/pi-hole))
+- **Privacy**: _See [Pi-Hole Privacy Guide](https://pi-hole.net/privacy/)_
+
+---
+
+### Recent Traffic
+
+Shows number of recent traffic, using allowed and blocked queries from [Pi-Hole](https://pi-hole.net/)
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`hostname`** | `string` | Required | The URL to your Pi-Hole instance
+
+##### Example
+
+```yaml
+- type: pi-hole-traffic
+ options:
+ hostname: https://pi-hole.local
+```
+
+##### Info
+- **CORS**: 🟢 Enabled
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Self-Hosted (see [GitHub - Pi-hole](https://github.com/pi-hole/pi-hole))
+- **Privacy**: _See [Pi-Hole Privacy Guide](https://pi-hole.net/privacy/)_
+
+---
+
+### Stat Ping Statuses
+
+Displays the current and recent uptime of your running services, via a self-hosted instance of [StatPing](https://github.com/statping/statping)
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`hostname`** | `string` | Required | The URL to your StatPing instance, without a trailing slash
+
+##### Example
+
+```yaml
+- type: stat-ping
+ options:
+ hostname: http://192.168.130.1:8080
+```
+
+##### Info
+- **CORS**: 🟠 Proxied
+- **Auth**: 🟢 Not Required
+- **Price**: 🟢 Free
+- **Host**: Self-Hosted (see [GitHub - StatPing](https://github.com/statping/statping))
+- **Privacy**: _See [StatPing Docs](https://docs.statping.com/)_
+
+---
+
+## Dynamic Widgets
+
+### Iframe Widget
+
+Embed any webpage into your dashboard as a widget.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`url`** | `string` | Required | The URL to the webpage to embed
+**`frameHeight`** | `number` | _Optional_ | If needed, specify height of iframe in `px`. E.g. `400`, defaults to auto
+
+##### Example
+
+```yaml
+- type: iframe
+ options:
+ url: https://fiatleak.com/
+```
+
+---
+
+### HTML Embedded Widget
+
+Many websites and apps provide their own embeddable widgets. These can be used with Dashy using the Embed widget, which lets you dynamically embed and HTML, CSS or JavaScript contents.
+
+⚠️ **NOTE:** Use with extreme caution. Embedding a script from an untrustworthy source may have serious unintended consequences.
+
+
+
+##### Options
+
+**Field** | **Type** | **Required** | **Description**
+--- | --- | --- | ---
+**`html`** | `string` | _Optional_ | HTML contents to render in the widget
+**`script`** | `string` | _Optional_ | Raw JavaScript code to execute (caution)
+**`scriptSrc`** | `string` | _Optional_ | A URL to JavaScript content (caution)
+**`css`** | `string` | _Optional_ | Any stylings for widget contents
+
+##### Example
+
+```yaml
+- type: embed
+ options:
+ scriptSrc: https://cdn.speedcheck.org/basic/scbjs.min.js
+ html: |
+
+```
+
+Or
+
+```yaml
+- type: embed
+ options:
+ css: '.coinmarketcap-currency-widget { color: var(--widget-text-color); }'
+ html: ''
+ scriptSrc: 'https://files.coinmarketcap.com/static/widget/currency.js'
+```
+
+---
+
+### API Response
+
+Directly output plain-text response from any API-enabled service.
+
+// Coming soon...
+
+---
+
+### Prometheus Data
+
+Display data from any service with a Prometheus exporter.
+
+// Coming soon...
+
+---
+
+### Data Feed
+
+Show live data from an RSS-enabled service. The only required parameter is `rssUrl`, which is the URL to the ATOM feed. See [RSS Widget](#rss-feed) for full list of available options.
+
+
+
+##### Example
+
+```yaml
+- type: rss-feed
+ options:
+ rssUrl: https://notes.aliciasykes.com/feed
+```
+
+---
+
+## Usage & Customizations
+
+### Widget Usage Guide
+
+Like items, widgets are placed under sections. You may have one or more widgets per section.
+
+In your YAML config file, this will look something like:
+
+```yaml
+sections:
+- name: Today
+ icon: far fa-calendar-day
+ widgets:
+ - type: clock
+ options:
+ format: en-GB
+ - type: weather
+ options:
+ apiKey: 6e29c7d514cf890f846d58178b6d418f
+ city: London
+ units: metric
+```
+
+> In this example, there is a single section, named "Today", using a Calendar icon from Font-Awesome. It has 2 widgets, a clock and the current weather.
+
+---
+
+### Continuous Updates
+
+By default, a widget which displays dynamic data from an external source, will only fetch results on page load. If you would like to keep data updated at all times, you can enable **Continuous Updates**. This is done by setting a time value in the `updateInterval` field.
+
+The value of `updateInterval` is optional, and is specified and seconds. It must be more than `10` and less than `7200`.
+
+For example, the following widget displaying stats from Pi-Hole will update ever 20 seconds.
+
+```yaml
+widgets:
+- type: pi-hole-stats
+ updateInterval: 20
+ options:
+ hostname: http://192.168.130.2
+```
+
+Note that if you have many widgets, and set them to continuously update frequently, you will notice a hit to performance. A widget that relies on data from an external API, will also consume your usage quota faster, if set to keep updating.
+
+---
+
+### Widget Styling
+
+Like elsewhere in Dashy, all colours can be easily modified with CSS variables.
+
+Widgets use the following color variables, which can be overridden if desired:
+- `--widget-text-color` - Text color, defaults to `--primary`
+- `--widget-background-color` - Background color, defaults to `--background-darker`
+- `--widget-accent-color` - Accent color, defaults to `--background`
+
+For more info on how to apply custom variables, see the [Theming Docs](/docs/theming.md#setting-custom-css-in-the-ui)
+
+---
+
+### Language Translations
+
+Since most of the content displayed within widgets is fetched from an external API, unless that API supports multiple languages, translating dynamic content is not possible.
+
+However, any hard-coded content is translatable, and all dates and times will display in your local format.
+
+For more info about multi-language support, see the [Internationalization Docs](/docs/multi-language-support.md).
+
+---
+
+### Widget UI Options
+
+Widgets can be opened in full-page view, by clicking the Arrow icon (top-right). The URL in your address bar will also update, and visiting that web address will take you straight to the selected widget.
+
+You can reload the data of any widget, by clicking the Refresh Data icon (also in top-right). This will only affect the widget where the action was triggered from.
+
+All [config options](/docs/configuring.md#section) that can be applied to sections, can also be applied to widget sections. For example, to make a widget span multiple columns, set `displayData.cols: 2` within the parent section. You can collapse a widget (by clicking the section title), and collapse state will be saved locally.
+
+Widgets cannot currently be edited through the UI. This feature is in development, and will be released soon. In the meantime, you can either use the JSON config editor, or use VS Code or SSH into your box to edit the conf.yml file directly.
+
+---
+
+### Build your own Widget
+
+Widgets are built in a modular fashion, making it easy for anyone to create their own custom components.
+
+For a full tutorial on creating your own widget, you can follow [this guide](/docs/development-guides.md#building-a-widget), or take a look at [here](https://github.com/Lissy93/dashy/commit/3da76ce2999f57f76a97454c0276301e39957b8e) for a code example.
+
+Alternatively, for displaying simple data, you could also just use the either the [iframe](#iframe-widget), [embed](#html-embedded-widget), [Data Feed](#data-feed) or [API response](#api-response) widgets.
+
+---
+
+### Requesting a Widget
+
+Suggestions for widget ideas are welcome. But there is no guarantee that I will build your widget idea.
+
+You can suggest a widget [here](https://git.io/Jygo3), please star the repo before submitting a ticket.
+
+Please only request widgets for services that:
+- Have a publicly accessible API
+- Are CORS and HTTPS enabled
+- Are free to use, or have a free plan
+- Allow for use in their Terms of Service
+- Would be useful for other users
+
+For services that are not officially supported, it is likely still possible to display data using either the [iframe](#iframe-widget), [embed](#html-embedded-widget) or [API response](#api-response) widgets. For more advanced features, like charts and action buttons, you could also build your own widget, using [this tutorial](/docs/development-guides.md#building-a-widget), it's fairly straight forward, and you can use an [existing widget](https://github.com/Lissy93/dashy/tree/master/src/components/Widgets) (or [this example](https://git.io/JygKI)) as a template.
diff --git a/package.json b/package.json
index 59685373..5018f6c7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "Dashy",
- "version": "1.9.3",
+ "version": "1.9.4",
"license": "MIT",
"main": "server",
"author": "Alicia Sykes (https://aliciasykes.com)",
@@ -25,6 +25,7 @@
"connect-history-api-fallback": "^1.6.0",
"crypto-js": "^4.1.1",
"express": "^4.17.1",
+ "frappe-charts": "^1.6.2",
"js-yaml": "^4.1.0",
"keycloak-js": "^15.0.2",
"register-service-worker": "^1.6.2",
@@ -95,4 +96,4 @@
"> 1%",
"last 2 versions"
]
-}
\ No newline at end of file
+}
diff --git a/public/fonts/Digital-Regular.ttf b/public/fonts/Digital-Regular.ttf
new file mode 100644
index 00000000..5dbe6f90
Binary files /dev/null and b/public/fonts/Digital-Regular.ttf differ
diff --git a/public/widget-resources/WeatherIcons.eot b/public/widget-resources/WeatherIcons.eot
new file mode 100644
index 00000000..30cde10b
Binary files /dev/null and b/public/widget-resources/WeatherIcons.eot differ
diff --git a/public/widget-resources/WeatherIcons.svg b/public/widget-resources/WeatherIcons.svg
new file mode 100644
index 00000000..a6bebbf0
--- /dev/null
+++ b/public/widget-resources/WeatherIcons.svg
@@ -0,0 +1,69 @@
+
+
+
diff --git a/public/widget-resources/WeatherIcons.ttf b/public/widget-resources/WeatherIcons.ttf
new file mode 100644
index 00000000..55925b45
Binary files /dev/null and b/public/widget-resources/WeatherIcons.ttf differ
diff --git a/public/widget-resources/WeatherIcons.woff b/public/widget-resources/WeatherIcons.woff
new file mode 100644
index 00000000..05b3b406
Binary files /dev/null and b/public/widget-resources/WeatherIcons.woff differ
diff --git a/public/widget-resources/WeatherIcons.woff2 b/public/widget-resources/WeatherIcons.woff2
new file mode 100644
index 00000000..e7176427
Binary files /dev/null and b/public/widget-resources/WeatherIcons.woff2 differ
diff --git a/server.js b/server.js
index f3455f46..bc66efc3 100644
--- a/server.js
+++ b/server.js
@@ -24,7 +24,9 @@ require('./services/config-validator'); // Include and kicks off the config file
const statusCheck = require('./services/status-check'); // Used by the status check feature, uses GET
const saveConfig = require('./services/save-config'); // Saves users new conf.yml to file-system
const rebuild = require('./services/rebuild-app'); // A script to programmatically trigger a build
-const sslServer = require('./services/ssl-server');
+const systemInfo = require('./services/system-info'); // Basic system info, for resource widget
+const sslServer = require('./services/ssl-server'); // TLS-enabled web server
+const corsProxy = require('./services/cors-proxy'); // Enables API requests to CORS-blocked services
/* Helper functions, and default config */
const printMessage = require('./services/print-message'); // Function to print welcome msg on start
@@ -91,6 +93,24 @@ const app = express()
}).catch((response) => {
res.end(JSON.stringify(response));
});
+ })
+ // GET endpoint to return system info, for widget
+ .use(ENDPOINTS.systemInfo, (req, res) => {
+ try {
+ const results = systemInfo();
+ systemInfo.success = true;
+ res.end(JSON.stringify(results));
+ } catch (e) {
+ res.end(JSON.stringify({ success: false, message: e }));
+ }
+ })
+ // GET for accessing non-CORS API services
+ .use(ENDPOINTS.corsProxy, (req, res) => {
+ try {
+ corsProxy(req, res);
+ } catch (e) {
+ res.end(JSON.stringify({ success: false, message: e }));
+ }
});
/* Create HTTP server from app on port, and print welcome message */
diff --git a/services/cors-proxy.js b/services/cors-proxy.js
new file mode 100644
index 00000000..cb2d264f
--- /dev/null
+++ b/services/cors-proxy.js
@@ -0,0 +1,47 @@
+/**
+ * A simple CORS proxy, for accessing API services which aren't CORS-enabled.
+ * Receives requests from frontend, applies correct access control headers,
+ * makes request to endpoint, then responds to the frontend with the response
+ */
+
+const axios = require('axios');
+
+module.exports = (req, res) => {
+ // Apply allow-all response headers
+ res.header('Access-Control-Allow-Origin', '*');
+ res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE');
+ if (req.header('access-control-request-headers')) {
+ res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers'));
+ }
+
+ // Pre-flight
+ if (req.method === 'OPTIONS') {
+ res.send();
+ return;
+ }
+
+ // Get desired URL, from Target-URL header
+ const targetURL = req.header('Target-URL');
+ if (!targetURL) {
+ res.send(500, { error: 'There is no Target-Endpoint header in the request' });
+ return;
+ }
+ // Apply any custom headers, if needed
+ const headers = req.header('CustomHeaders') ? JSON.parse(req.header('CustomHeaders')) : {};
+
+ // Prepare the request
+ const requestConfig = {
+ method: req.method,
+ url: targetURL + req.url,
+ json: req.body,
+ headers,
+ };
+
+ // Make the request, and respond with result
+ axios.request(requestConfig)
+ .then((response) => {
+ res.send(200, response.data);
+ }).catch((error) => {
+ res.send(500, { error });
+ });
+};
diff --git a/services/system-info.js b/services/system-info.js
new file mode 100644
index 00000000..baf10012
--- /dev/null
+++ b/services/system-info.js
@@ -0,0 +1,24 @@
+/**
+ * Gets basic system info, for the resource usage widget
+ */
+const os = require('os');
+
+module.exports = () => {
+ const meta = {
+ timestamp: new Date(),
+ uptime: os.uptime(),
+ hostname: os.hostname(),
+ username: os.userInfo().username,
+ system: `${os.version()} (${os.platform()})`,
+ };
+
+ const memory = {
+ total: `${Math.round(os.totalmem() / (1024 * 1024 * 1024))} GB`,
+ freePercent: (os.freemem() / os.totalmem()).toFixed(2),
+ };
+
+ const loadAv = os.loadavg();
+ const load = { one: loadAv[0], five: loadAv[1], fifteen: loadAv[2] };
+
+ return { meta, memory, load };
+};
diff --git a/src/assets/interface-icons/widget-update.svg b/src/assets/interface-icons/widget-update.svg
new file mode 100644
index 00000000..29e37fbe
--- /dev/null
+++ b/src/assets/interface-icons/widget-update.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json
index 50d58465..f8e05fea 100644
--- a/src/assets/locales/en.json
+++ b/src/assets/locales/en.json
@@ -1,248 +1,275 @@
-{
- "home": {
- "no-results": "No Search Results",
- "no-data": "No Data Configured"
- },
- "search": {
- "search-label": "Search",
- "search-placeholder": "Start typing to filter",
- "clear-search-tooltip": "Clear Search",
- "enter-to-search-web": "Press enter to search the web"
- },
- "login": {
- "title": "Dashy",
- "username-label": "Username",
- "password-label": "Password",
- "login-button": "Login",
- "remember-me-label": "Remember me for",
- "remember-me-never": "Never",
- "remember-me-hour": "4 Hours",
- "remember-me-day": "1 Day",
- "remember-me-week": "1 Week",
- "remember-me-long-time": "A long time",
- "error-missing-username": "Missing Username",
- "error-missing-password": "Missing Password",
- "error-incorrect-username": "User not found",
- "error-incorrect-password": "Incorrect Password",
- "success-message": "Logging in...",
- "logout-message": "Logged Out",
- "already-logged-in-title": "Already Logged In",
- "already-logged-in-text": "You're logged in as",
- "proceed-to-dashboard": "Proceed to Dashboard",
- "log-out-button": "Logout",
- "proceed-guest-button": "Proceed as Guest"
- },
- "config": {
- "main-tab": "Main Menu",
- "view-config-tab": "View Config",
- "edit-config-tab": "Edit Config",
- "custom-css-tab": "Custom Styles",
- "heading": "Configuration Options",
- "download-config-button": "View / Export Config",
- "edit-config-button": "Edit Config",
- "edit-css-button": "Edit Custom CSS",
- "cloud-sync-button": "Enable Cloud Sync",
- "edit-cloud-sync-button": "Edit Cloud Sync",
- "rebuild-app-button": "Rebuild Application",
- "change-language-button": "Change App Language",
- "reset-settings-button": "Reset Local Settings",
- "app-info-button": "App Info",
- "backup-note": "It is recommend to make a backup of your configuration before making changes.",
- "reset-config-msg-l1": "This will remove all user settings from local storage, but won't effect your 'conf.yml' file.",
- "reset-config-msg-l2": "You should first backup any changes you've made locally, if you want to use them in the future.",
- "reset-config-msg-l3": "Are you sure you want to proceed?",
- "data-cleared-msg": "Data cleared successfully",
- "actions-label": "Actions",
- "copy-config-label": "Copy Config",
- "data-copied-msg": "Config has been copied to clipboard",
- "reset-config-label": "Reset Config",
- "css-save-btn": "Save Changes",
- "css-note-label": "Note",
- "css-note-l1": "You will need to refresh the page for your changes to take effect.",
- "css-note-l2": "Styles overrides are only stored locally, so it is recommended to make a copy of your CSS.",
- "css-note-l3": "To remove all custom styles, delete the contents and hit Save Changes"
- },
- "alternate-views": {
- "alternate-view-heading": "Switch View",
- "default": "Default",
- "workspace": "Workspace",
- "minimal": "Minimal"
- },
- "settings": {
- "theme-label": "Theme",
- "layout-label": "Layout",
- "layout-auto": "Auto",
- "layout-horizontal": "Horizontal",
- "layout-vertical": "Vertical",
- "item-size-label": "Item Size",
- "item-size-small": "Small",
- "item-size-medium": "Medium",
- "item-size-large": "Large",
- "config-launcher-label": "Config",
- "config-launcher-tooltip": "Update Configuration",
- "sign-out-tooltip": "Sign Out",
- "sign-in-tooltip": "Log In",
- "sign-in-welcome": "Hello {username}!"
- },
- "updates": {
- "app-version-note": "Dashy version",
- "up-to-date": "Up-to-Date",
- "out-of-date": "Update Available",
- "unsupported-version-l1": "You are using an unsupported version of Dashy",
- "unsupported-version-l2": "For the best experience, and recent security patches, please update to"
- },
- "language-switcher": {
- "title": "Change Application Language",
- "dropdown-label": "Select a Language",
- "save-button": "Save",
- "success-msg": "Language Updated to"
- },
- "theme-maker": {
- "title": "Theme Configurator",
- "export-button": "Export Custom Variables",
- "reset-button": "Reset Styles for",
- "show-all-button": "Show All Variables",
- "change-fonts-button": "Change Fonts",
- "save-button": "Save",
- "cancel-button": "Cancel",
- "saved-toast": "{theme} Updated Successfully",
- "copied-toast": "Theme data for {theme} copied to clipboard",
- "reset-toast": "Custom Colors for {theme} Removed"
- },
- "config-editor": {
- "save-location-label": "Save Location",
- "location-local-label": "Apply Locally",
- "location-disk-label": "Write Changes to Config File",
- "save-button": "Save Changes",
- "preview-button": "Preview Changes",
- "valid-label": "Config is Valid",
- "status-success-msg": "Task Complete",
- "status-fail-msg": "Task Failed",
- "success-msg-disk": "Config file written to disk successfully",
- "success-msg-local": "Local changes saved successfully",
- "success-note-l1": "The app should rebuild automatically.",
- "success-note-l2": "This may take up to a minute.",
- "success-note-l3": "You will need to refresh the page for changes to take effect.",
- "error-msg-save-mode": "Please select a Save Mode: Local or File",
- "error-msg-cannot-save": "An error occurred saving config",
- "error-msg-bad-json": "Error in JSON, possibly malformed",
- "warning-msg-validation": "Validation Warning",
- "not-admin-note": "You cannot write changed to disk, because you are not logged in as an admin"
- },
- "app-rebuild": {
- "title": "Rebuild Application",
- "rebuild-note-l1": "A rebuild is required for changes written to the conf.yml file to take effect.",
- "rebuild-note-l2": "This should happen automatically, but if it hasn't, you can manually trigger it here.",
- "rebuild-note-l3": "This is not required for modifications stored locally.",
- "rebuild-button": "Start Build",
- "rebuilding-status-1": "Building...",
- "rebuilding-status-2": "This may take a few minutes",
- "error-permission": "You don't have permission to trigger this action",
- "success-msg": "Build completed successfully",
- "fail-msg": "Build operation failed",
- "reload-note": "A page reload is now required for changes to take effect",
- "reload-button": "Reload Page"
- },
- "cloud-sync": {
- "title": "Cloud Backup & Restore",
- "intro-l1": "Cloud backup and restore is an optional feature, that enables you to upload your config to the internet, and then restore it on any other device or instance of Dashy.",
- "intro-l2": "All data is fully end-to-end encrypted with AES, using your password as the key.",
- "intro-l3": "For more info, please see the",
- "backup-title-setup": "Make a Backup",
- "backup-title-update": "Update Backup",
- "password-label-setup": "Choose a Password",
- "password-label-update": "Enter your Password",
- "backup-button-setup": "Backup",
- "backup-button-update": "Update Backup",
- "backup-id-label": "Your Backup ID",
- "backup-id-note": "This is used to restore from backups later. So keep it, along with your password somewhere safe.",
- "restore-title": "Restore a Backup",
- "restore-id-label": "Restore ID",
- "restore-password-label": "Password",
- "restore-button": "Restore",
- "backup-missing-password": "Missing Password",
- "backup-error-unknown": "Unable to process request",
- "backup-error-password": "Incorrect password. Please enter your current password.",
- "backup-success-msg": "Completed Successfully",
- "restore-success-msg": "Config Restored Successfully"
- },
- "menu": {
- "open-section-title": "Open In",
- "sametab": "Current Tab",
- "newtab": "New Tab",
- "modal": "Pop-Up Modal",
- "workspace": "Workspace View",
- "options-section-title": "Options",
- "edit-item": "Edit",
- "move-item": "Copy or Move",
- "remove-item": "Remove"
- },
- "context-menus": {
- "item": {
- "open-section-title": "Open In",
- "sametab": "Current Tab",
- "newtab": "New Tab",
- "modal": "Pop-Up Modal",
- "workspace": "Workspace View",
- "options-section-title": "Options",
- "edit-item": "Edit",
- "move-item": "Copy or Move",
- "remove-item": "Remove"
- },
- "section": {
- "open-section": "Open Section",
- "edit-section": "Edit",
- "move-section": "Move To",
- "remove-section": "Remove"
- }
- },
- "interactive-editor": {
- "menu": {
- "start-editing-tooltip": "Enter the Interactive Editor",
- "edit-site-data-subheading": "Edit Site Data",
- "edit-page-info-btn": "Edit Page Info",
- "edit-page-info-tooltip": "App title, description, nav links, footer text, etc",
- "edit-app-config-btn": "Edit App Config",
- "edit-app-config-tooltip": "All other app configuration options",
- "config-save-methods-subheading": "Config Saving Options",
- "save-locally-btn": "Save Locally",
- "save-locally-tooltip": "Save config locally, to browser storage. This will not affect your config file, but changes will only be saved on this device",
- "save-disk-btn": "Save to Disk",
- "save-disk-tooltip": "Save config to the conf.yml file on disk. This will backup, and then over-write your existing config",
- "export-config-btn": "Export Config",
- "export-config-tooltip": "View and export new config, either to a file, or to clipboard",
- "cloud-backup-btn": "Backup to Cloud",
- "cloud-backup-tooltip": "Save encrypted backup of configuration to cloud",
- "edit-raw-config-btn": "Edit Raw Config",
- "edit-raw-config-tooltip": "View and modify raw config via JSON editor",
- "cancel-changes-btn": "Cancel Edit",
- "cancel-changes-tooltip": "Reset current modifications, and exit Edit Mode. This will not affect your saved config",
- "edit-mode-name": "Edit Mode",
- "edit-mode-subtitle": "You are in Edit Mode",
- "edit-mode-description": "This means you can make modifications to your config, and preview the results, but until you save, none of your changes will be preserved.",
- "save-stage-btn": "Save",
- "cancel-stage-btn": "Cancel"
- },
- "edit-section": {
- "edit-section-title": "Edit Section",
- "add-section-title": "Add New Section",
- "edit-tooltip": "Click to Edit, or right-click for more options",
- "remove-confirm": "Are you sure you want to remove this section? This action can be undone later."
- },
- "edit-app-config": {
- "warning-msg-title": "Proceed with Caution",
- "warning-msg-l1": "The following options are for advanced app configuration.",
- "warning-msg-l2": "If you are unsure about any of the fields, please reference the",
- "warning-msg-docs": "documentation",
- "warning-msg-l3": "to avoid unintended consequences."
- },
- "export": {
- "export-title": "Export Config",
- "copy-clipboard-btn": "Copy to Clipboard",
- "copy-clipboard-tooltip": "Copy all app config to system clipboard, in YAML format",
- "download-file-btn": "Download as File",
- "download-file-tooltip": "Download all app config to your device, in a YAML file",
- "view-title": "View Config"
- }
- }
-}
\ No newline at end of file
+{
+ "home": {
+ "no-results": "No Search Results",
+ "no-data": "No Data Configured",
+ "no-items-section": "No Items to Show Yet"
+ },
+ "search": {
+ "search-label": "Search",
+ "search-placeholder": "Start typing to filter",
+ "clear-search-tooltip": "Clear Search",
+ "enter-to-search-web": "Press enter to search the web"
+ },
+ "login": {
+ "title": "Dashy",
+ "username-label": "Username",
+ "password-label": "Password",
+ "login-button": "Login",
+ "remember-me-label": "Remember me for",
+ "remember-me-never": "Never",
+ "remember-me-hour": "4 Hours",
+ "remember-me-day": "1 Day",
+ "remember-me-week": "1 Week",
+ "remember-me-long-time": "A long time",
+ "error-missing-username": "Missing Username",
+ "error-missing-password": "Missing Password",
+ "error-incorrect-username": "User not found",
+ "error-incorrect-password": "Incorrect Password",
+ "success-message": "Logging in...",
+ "logout-message": "Logged Out",
+ "already-logged-in-title": "Already Logged In",
+ "already-logged-in-text": "You're logged in as",
+ "proceed-to-dashboard": "Proceed to Dashboard",
+ "log-out-button": "Logout",
+ "proceed-guest-button": "Proceed as Guest"
+ },
+ "config": {
+ "main-tab": "Main Menu",
+ "view-config-tab": "View Config",
+ "edit-config-tab": "Edit Config",
+ "custom-css-tab": "Custom Styles",
+ "heading": "Configuration Options",
+ "download-config-button": "View / Export Config",
+ "edit-config-button": "Edit Config",
+ "edit-css-button": "Edit Custom CSS",
+ "cloud-sync-button": "Enable Cloud Sync",
+ "edit-cloud-sync-button": "Edit Cloud Sync",
+ "rebuild-app-button": "Rebuild Application",
+ "change-language-button": "Change App Language",
+ "reset-settings-button": "Reset Local Settings",
+ "app-info-button": "App Info",
+ "backup-note": "It is recommend to make a backup of your configuration before making changes.",
+ "reset-config-msg-l1": "This will remove all user settings from local storage, but won't effect your 'conf.yml' file.",
+ "reset-config-msg-l2": "You should first backup any changes you've made locally, if you want to use them in the future.",
+ "reset-config-msg-l3": "Are you sure you want to proceed?",
+ "data-cleared-msg": "Data cleared successfully",
+ "actions-label": "Actions",
+ "copy-config-label": "Copy Config",
+ "data-copied-msg": "Config has been copied to clipboard",
+ "reset-config-label": "Reset Config",
+ "css-save-btn": "Save Changes",
+ "css-note-label": "Note",
+ "css-note-l1": "You will need to refresh the page for your changes to take effect.",
+ "css-note-l2": "Styles overrides are only stored locally, so it is recommended to make a copy of your CSS.",
+ "css-note-l3": "To remove all custom styles, delete the contents and hit Save Changes"
+ },
+ "alternate-views": {
+ "alternate-view-heading": "Switch View",
+ "default": "Default",
+ "workspace": "Workspace",
+ "minimal": "Minimal"
+ },
+ "settings": {
+ "theme-label": "Theme",
+ "layout-label": "Layout",
+ "layout-auto": "Auto",
+ "layout-horizontal": "Horizontal",
+ "layout-vertical": "Vertical",
+ "item-size-label": "Item Size",
+ "item-size-small": "Small",
+ "item-size-medium": "Medium",
+ "item-size-large": "Large",
+ "config-launcher-label": "Config",
+ "config-launcher-tooltip": "Update Configuration",
+ "sign-out-tooltip": "Sign Out",
+ "sign-in-tooltip": "Log In",
+ "sign-in-welcome": "Hello {username}!"
+ },
+ "updates": {
+ "app-version-note": "Dashy version",
+ "up-to-date": "Up-to-Date",
+ "out-of-date": "Update Available",
+ "unsupported-version-l1": "You are using an unsupported version of Dashy",
+ "unsupported-version-l2": "For the best experience, and recent security patches, please update to"
+ },
+ "language-switcher": {
+ "title": "Change Application Language",
+ "dropdown-label": "Select a Language",
+ "save-button": "Save",
+ "success-msg": "Language Updated to"
+ },
+ "theme-maker": {
+ "title": "Theme Configurator",
+ "export-button": "Export Custom Variables",
+ "reset-button": "Reset Styles for",
+ "show-all-button": "Show All Variables",
+ "change-fonts-button": "Change Fonts",
+ "save-button": "Save",
+ "cancel-button": "Cancel",
+ "saved-toast": "{theme} Updated Successfully",
+ "copied-toast": "Theme data for {theme} copied to clipboard",
+ "reset-toast": "Custom Colors for {theme} Removed"
+ },
+ "config-editor": {
+ "save-location-label": "Save Location",
+ "location-local-label": "Apply Locally",
+ "location-disk-label": "Write Changes to Config File",
+ "save-button": "Save Changes",
+ "preview-button": "Preview Changes",
+ "valid-label": "Config is Valid",
+ "status-success-msg": "Task Complete",
+ "status-fail-msg": "Task Failed",
+ "success-msg-disk": "Config file written to disk successfully",
+ "success-msg-local": "Local changes saved successfully",
+ "success-note-l1": "The app should rebuild automatically.",
+ "success-note-l2": "This may take up to a minute.",
+ "success-note-l3": "You will need to refresh the page for changes to take effect.",
+ "error-msg-save-mode": "Please select a Save Mode: Local or File",
+ "error-msg-cannot-save": "An error occurred saving config",
+ "error-msg-bad-json": "Error in JSON, possibly malformed",
+ "warning-msg-validation": "Validation Warning",
+ "not-admin-note": "You cannot write changed to disk, because you are not logged in as an admin"
+ },
+ "app-rebuild": {
+ "title": "Rebuild Application",
+ "rebuild-note-l1": "A rebuild is required for changes written to the conf.yml file to take effect.",
+ "rebuild-note-l2": "This should happen automatically, but if it hasn't, you can manually trigger it here.",
+ "rebuild-note-l3": "This is not required for modifications stored locally.",
+ "rebuild-button": "Start Build",
+ "rebuilding-status-1": "Building...",
+ "rebuilding-status-2": "This may take a few minutes",
+ "error-permission": "You don't have permission to trigger this action",
+ "success-msg": "Build completed successfully",
+ "fail-msg": "Build operation failed",
+ "reload-note": "A page reload is now required for changes to take effect",
+ "reload-button": "Reload Page"
+ },
+ "cloud-sync": {
+ "title": "Cloud Backup & Restore",
+ "intro-l1": "Cloud backup and restore is an optional feature, that enables you to upload your config to the internet, and then restore it on any other device or instance of Dashy.",
+ "intro-l2": "All data is fully end-to-end encrypted with AES, using your password as the key.",
+ "intro-l3": "For more info, please see the",
+ "backup-title-setup": "Make a Backup",
+ "backup-title-update": "Update Backup",
+ "password-label-setup": "Choose a Password",
+ "password-label-update": "Enter your Password",
+ "backup-button-setup": "Backup",
+ "backup-button-update": "Update Backup",
+ "backup-id-label": "Your Backup ID",
+ "backup-id-note": "This is used to restore from backups later. So keep it, along with your password somewhere safe.",
+ "restore-title": "Restore a Backup",
+ "restore-id-label": "Restore ID",
+ "restore-password-label": "Password",
+ "restore-button": "Restore",
+ "backup-missing-password": "Missing Password",
+ "backup-error-unknown": "Unable to process request",
+ "backup-error-password": "Incorrect password. Please enter your current password.",
+ "backup-success-msg": "Completed Successfully",
+ "restore-success-msg": "Config Restored Successfully"
+ },
+ "menu": {
+ "open-section-title": "Open In",
+ "sametab": "Current Tab",
+ "newtab": "New Tab",
+ "modal": "Pop-Up Modal",
+ "workspace": "Workspace View",
+ "options-section-title": "Options",
+ "edit-item": "Edit",
+ "move-item": "Copy or Move",
+ "remove-item": "Remove"
+ },
+ "context-menus": {
+ "item": {
+ "open-section-title": "Open In",
+ "sametab": "Current Tab",
+ "newtab": "New Tab",
+ "modal": "Pop-Up Modal",
+ "workspace": "Workspace View",
+ "options-section-title": "Options",
+ "edit-item": "Edit",
+ "move-item": "Copy or Move",
+ "remove-item": "Remove"
+ },
+ "section": {
+ "open-section": "Open Section",
+ "edit-section": "Edit",
+ "move-section": "Move To",
+ "remove-section": "Remove"
+ }
+ },
+ "interactive-editor": {
+ "menu": {
+ "start-editing-tooltip": "Enter the Interactive Editor",
+ "edit-site-data-subheading": "Edit Site Data",
+ "edit-page-info-btn": "Edit Page Info",
+ "edit-page-info-tooltip": "App title, description, nav links, footer text, etc",
+ "edit-app-config-btn": "Edit App Config",
+ "edit-app-config-tooltip": "All other app configuration options",
+ "config-save-methods-subheading": "Config Saving Options",
+ "save-locally-btn": "Save Locally",
+ "save-locally-tooltip": "Save config locally, to browser storage. This will not affect your config file, but changes will only be saved on this device",
+ "save-disk-btn": "Save to Disk",
+ "save-disk-tooltip": "Save config to the conf.yml file on disk. This will backup, and then over-write your existing config",
+ "export-config-btn": "Export Config",
+ "export-config-tooltip": "View and export new config, either to a file, or to clipboard",
+ "cloud-backup-btn": "Backup to Cloud",
+ "cloud-backup-tooltip": "Save encrypted backup of configuration to cloud",
+ "edit-raw-config-btn": "Edit Raw Config",
+ "edit-raw-config-tooltip": "View and modify raw config via JSON editor",
+ "cancel-changes-btn": "Cancel Edit",
+ "cancel-changes-tooltip": "Reset current modifications, and exit Edit Mode. This will not affect your saved config",
+ "edit-mode-name": "Edit Mode",
+ "edit-mode-subtitle": "You are in Edit Mode",
+ "edit-mode-description": "This means you can make modifications to your config, and preview the results, but until you save, none of your changes will be preserved.",
+ "save-stage-btn": "Save",
+ "cancel-stage-btn": "Cancel"
+ },
+ "edit-section": {
+ "edit-section-title": "Edit Section",
+ "add-section-title": "Add New Section",
+ "edit-tooltip": "Click to Edit, or right-click for more options",
+ "remove-confirm": "Are you sure you want to remove this section? This action can be undone later."
+ },
+ "edit-app-config": {
+ "warning-msg-title": "Proceed with Caution",
+ "warning-msg-l1": "The following options are for advanced app configuration.",
+ "warning-msg-l2": "If you are unsure about any of the fields, please reference the",
+ "warning-msg-docs": "documentation",
+ "warning-msg-l3": "to avoid unintended consequences."
+ },
+ "export": {
+ "export-title": "Export Config",
+ "copy-clipboard-btn": "Copy to Clipboard",
+ "copy-clipboard-tooltip": "Copy all app config to system clipboard, in YAML format",
+ "download-file-btn": "Download as File",
+ "download-file-tooltip": "Download all app config to your device, in a YAML file",
+ "view-title": "View Config"
+ }
+ },
+ "widgets": {
+ "general": {
+ "loading": "Loading...",
+ "show-more": "Expand Details",
+ "show-less": "Show Less",
+ "open-link": "Continue Reading"
+ },
+ "pi-hole": {
+ "status-heading": "Status"
+ },
+ "stat-ping": {
+ "up": "Online",
+ "down": "Offline"
+ },
+ "system-info": {
+ "uptime": "Uptime"
+ },
+ "flight-data": {
+ "arrivals": "Arrivals",
+ "departures": "Departures"
+ },
+ "tfl-status": {
+ "good-service-all": "Good Service on all Lines",
+ "good-service-rest": "Good Service on all other Lines"
+ }
+ }
+}
diff --git a/src/components/Configuration/AppInfoModal.vue b/src/components/Configuration/AppInfoModal.vue
index 9d78a059..7182592c 100644
--- a/src/components/Configuration/AppInfoModal.vue
+++ b/src/components/Configuration/AppInfoModal.vue
@@ -7,16 +7,27 @@
{{ errorLog }}
No recent errors detected :)
-
+
Help & Support
For getting support with running or configuring Dashy, see the Discussions
+
Supporting Dashy
For ways that you can get involved, check out the Contributing page.
+
Report a Bug
If you think you've found a bug, then please raise an Issue.
+
+ For a break-down of how your data is managed by Dashy, see
+ the Privacy Policy.
+ For advise in securing your dashboard, you can reference the
+ Management Docs.
+ If you've found a potential security issue, report it following our
+ Security Policy