From a66636bde819eb9609c177e494f87e52591b8f20 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 4 Sep 2021 23:09:21 +0100 Subject: [PATCH 01/13] :zap: Improved error logging --- src/utils/CoolConsole.js | 15 ++++++++++----- src/utils/ErrorHandler.js | 9 ++++----- src/utils/ErrorReporting.js | 9 ++++++--- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/utils/CoolConsole.js b/src/utils/CoolConsole.js index 4bd32bd6d6..c5641c207e 100644 --- a/src/utils/CoolConsole.js +++ b/src/utils/CoolConsole.js @@ -1,14 +1,19 @@ -/* eslint no-console: ["error", { allow: ["log"] }] */ +/* eslint no-console: ["error", { allow: ["log", "info"] }] */ export const welcomeMsg = () => { const v = process.env.VUE_APP_VERSION ? `V${process.env.VUE_APP_VERSION}` : ''; - console.log(`%cDashy ${v} 🚀`, 'color:#00af87; background:#0b1021; font-size:36px; padding: 0.5rem 0.5rem 0; margin: 1rem auto; font-family: Rockwell; border: 2px solid #00af87; border-radius: 4px;font-weight: bold; text-shadow: 1px 1px 1px #00af87bf;'); + console.log(`%cDashy ${v} 🚀`, 'color:#00af87; background:#0b1021; font-size:1.5rem; padding: 0 0.5rem 0; margin: 1rem auto; font-family: Rockwell; border: 2px solid #00af87; border-radius: 4px;font-weight: bold; text-shadow: 1px 1px 1px #00af87bf;'); }; -export const warningMsg = () => { - console.log('%c⚠️ Error ⚠️', "background:#21bbca; color:#0b1021; font-size:20px; padding:0.25rem 0.5rem; margin: 1rem auto 0.25rem; font-family: 'Trebuchet MS', Helvetica; border: 2px solid yellow; border-radius: 4px; font-weight: bold;"); +export const warningMsg = (message) => { + console.info( + `%c⚠️ Warning ⚠️%c \n${message} \n\n%c🐛If you have found a bug, please open a ticket on GitHub, at: https://git.io/JukXk`, + "color:#ceb73f; background: #ceb73f33; font-size:1.2rem; padding:0.15rem; margin: 0.2rem auto 1rem auto; font-family: Rockwell, Tahoma, 'Trebuchet MS', Helvetica; border: 2px solid #ceb73f; border-radius: 4px; font-weight: bold; text-shadow: 1px 1px 1px #000000bf;", + 'font-weight: bold; font-size: 0.9rem;color: #ceb73f;', + "color: #ceb73f; font-size: 0.6rem; font-family: Tahoma, 'Trebuchet MS', Helvetica;", + ); }; export const raiseBug = () => { - console.log('%c🐛If you have found a bug, raise an issue on GitHub, at:\nhttps://git.io/JnqPR', "color:#dddd10; font-size: 14px; font-family: 'Trebuchet MS', Helvetica;"); + console.log('%c🐛If you have found a bug, raise an issue on GitHub, at:\nhttps://git.io/JukXk', "color:#dddd10; font-size: 14px; font-family: 'Trebuchet MS', Helvetica;"); }; diff --git a/src/utils/ErrorHandler.js b/src/utils/ErrorHandler.js index eb37160bf8..8406d6a61b 100644 --- a/src/utils/ErrorHandler.js +++ b/src/utils/ErrorHandler.js @@ -1,15 +1,14 @@ /* eslint no-console: ["error", { allow: ["warn", "error"] }] */ - -import { warningMsg, raiseBug } from '@/utils/CoolConsole'; +import * as Sentry from '@sentry/vue'; +import { warningMsg } from '@/utils/CoolConsole'; /** * Function called when an error happens * If you wish to use an error logging service, put code for it here */ const ErrorHandler = function handler(msg) { - warningMsg(); - console.warn(msg); - raiseBug(); + warningMsg(msg); + Sentry.captureMessage(msg); }; export default ErrorHandler; diff --git a/src/utils/ErrorReporting.js b/src/utils/ErrorReporting.js index c79c58347c..77cea66184 100644 --- a/src/utils/ErrorReporting.js +++ b/src/utils/ErrorReporting.js @@ -12,11 +12,13 @@ import ConfigAccumulator from '@/utils/ConfigAccumalator'; import { sentryDsn } from '@/utils/defaults'; -const ErrorTracking = (Vue, router) => { +const ErrorReporting = (Vue, router) => { // Fetch users config const appConfig = new ConfigAccumulator().appConfig() || {}; // Check if error reporting is enabled. Only proceed if user has turned it on. if (appConfig.enableErrorReporting) { + // Get current app version + const appVersion = process.env.VUE_APP_VERSION ? `Dashy@${process.env.VUE_APP_VERSION}` : ''; // Import Sentry const Sentry = require('@sentry/vue'); const { Integrations } = require('@sentry/tracing'); @@ -32,10 +34,11 @@ const ErrorTracking = (Vue, router) => { }), ], tracesSampleRate: 1.0, + release: appVersion, }); } else { - // Error reporting not enabled. Do Nothing. + // Error reporting has not been enabled by the user. Do Nothing. } }; -export default ErrorTracking; +export default ErrorReporting; From 48533f397a5cc56bfaa1d6c4ee3226042da8c497 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sat, 4 Sep 2021 23:07:50 +0100 Subject: [PATCH 02/13] :sparkles: Implements optional alphabetical sort for sections --- src/components/LinkItems/Section.vue | 15 ++++++++++++++- src/utils/defaults.js | 2 ++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/components/LinkItems/Section.vue b/src/components/LinkItems/Section.vue index c88ad459f7..9b7d7eb87b 100644 --- a/src/components/LinkItems/Section.vue +++ b/src/components/LinkItems/Section.vue @@ -17,7 +17,7 @@ :style="gridStyle" > diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 45b48aeabe..b6e37233a7 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -93,6 +93,7 @@ module.exports = { BACKUP_HASH: 'backupHash', HIDE_SETTINGS: 'hideSettings', USERNAME: 'username', + MOST_USED: 'mostUsed', }, /* Key names for cookie identifiers */ cookieKeys: { From 11f5f8c9df9ecf1663d247739cf36b1593aecf40 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 5 Sep 2021 01:00:44 +0100 Subject: [PATCH 04/13] :sparkles: Allows sorting items by last used --- src/components/LinkItems/Item.vue | 10 +++++++++- src/components/LinkItems/Section.vue | 10 ++++++++++ src/utils/defaults.js | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/components/LinkItems/Item.vue b/src/components/LinkItems/Item.vue index fc47ed76cc..8249815a1f 100644 --- a/src/components/LinkItems/Item.vue +++ b/src/components/LinkItems/Item.vue @@ -107,7 +107,9 @@ export default { } else { this.$emit('itemClicked'); } + // Update the most/ last used ledger, for smart-sorting this.incrementMostUsedCount(this.id); + this.incrementLastUsedCount(this.id); }, /* Open custom context menu, and set position */ openContextMenu(e) { @@ -200,7 +202,7 @@ export default { default: window.open(url, '_blank'); } }, - /* Used for smart-sort when sorting items by most/ last used */ + /* Used for smart-sort when sorting items by most used apps */ incrementMostUsedCount(itemId) { const mostUsed = JSON.parse(localStorage.getItem(localStorageKeys.MOST_USED) || '{}'); let counter = mostUsed[itemId] || 0; @@ -208,6 +210,12 @@ export default { mostUsed[itemId] = counter; localStorage.setItem(localStorageKeys.MOST_USED, JSON.stringify(mostUsed)); }, + /* Used for smart-sort when sorting by last used apps */ + incrementLastUsedCount(itemId) { + const lastUsed = JSON.parse(localStorage.getItem(localStorageKeys.LAST_USED) || '{}'); + lastUsed[itemId] = new Date().getTime(); + localStorage.setItem(localStorageKeys.LAST_USED, JSON.stringify(lastUsed)); + }, }, mounted() { // If ststus checking is enabled, then check service status diff --git a/src/components/LinkItems/Section.vue b/src/components/LinkItems/Section.vue index a1429d9092..c9977b6ef8 100644 --- a/src/components/LinkItems/Section.vue +++ b/src/components/LinkItems/Section.vue @@ -75,6 +75,7 @@ export default { sortOrder() { return this.displayData.sortBy || defaultSortOrder; }, + /* If the sortBy attribute is specified, then return sorted data */ sortedItems() { let { items } = this; if (this.sortOrder === 'alphabetical') { @@ -83,6 +84,8 @@ export default { items.sort((a, b) => (a.title < b.title ? 1 : -1)); } else if (this.sortOrder === 'most-used') { items = this.sortByMostUsed(items); + } else if (this.sortOrder === 'last-used') { + items = this.sortBLastUsed(items); } return items; }, @@ -134,6 +137,13 @@ export default { items.reverse().sort((a, b) => (gmu(a) < gmu(b) ? 1 : -1)); return items; }, + /* Sorts items by most recently used */ + sortBLastUsed(items) { + const usageCount = JSON.parse(localStorage.getItem(localStorageKeys.LAST_USED) || '{}'); + const glu = (item) => usageCount[this.makeId(item.title)] || 0; + items.reverse().sort((a, b) => (glu(a) < glu(b) ? 1 : -1)); + return items; + }, }, }; diff --git a/src/utils/defaults.js b/src/utils/defaults.js index b6e37233a7..5f169696d6 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -94,6 +94,7 @@ module.exports = { HIDE_SETTINGS: 'hideSettings', USERNAME: 'username', MOST_USED: 'mostUsed', + LAST_USED: 'lastUsed', }, /* Key names for cookie identifiers */ cookieKeys: { From 591b03420b45c4320d73ed0db8a9bc00650aa319 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 5 Sep 2021 01:01:45 +0100 Subject: [PATCH 05/13] :sparkles: Adds option for user to disable smart-sort --- src/components/LinkItems/Item.vue | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/components/LinkItems/Item.vue b/src/components/LinkItems/Item.vue index 8249815a1f..8572592c92 100644 --- a/src/components/LinkItems/Item.vue +++ b/src/components/LinkItems/Item.vue @@ -53,6 +53,7 @@ import { localStorageKeys } from '@/utils/defaults'; export default { name: 'Item', + inject: ['config'], props: { id: String, // The unique ID of a tile (e.g. 001) title: String, // The main text of tile, required @@ -108,8 +109,10 @@ export default { this.$emit('itemClicked'); } // Update the most/ last used ledger, for smart-sorting - this.incrementMostUsedCount(this.id); - this.incrementLastUsedCount(this.id); + if (!this.config.appConfig.disableSmartSort) { + this.incrementMostUsedCount(this.id); + this.incrementLastUsedCount(this.id); + } }, /* Open custom context menu, and set position */ openContextMenu(e) { From af4b41df39109c81865f794edbc0fff4429c29f9 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 5 Sep 2021 15:58:53 +0100 Subject: [PATCH 06/13] :memo: Adds disable features options to Privacy docs --- README.md | 34 +++++++++++++++++++--------------- docs/privacy.md | 18 ++++++++++++++++-- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a378b3006b..dfe9975a40 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ - 🛩️ A minimal view, for use as a fast-loading browser startpage - 🖱️ Choose how to launch apps, either new tab, same tab, a pop-up modal or in the workspace view - 🌎 Multi-language support, with more languages being added regularly -- 📏 Customizable layout, sizes, text, component visibility, behavior and colors etc +- 📏 Customizable layout, sizes, text, component visibility, sort order, behavior etc - 🖼️ Option for full-screen background image, custom nav-bar links, html footer, title, and more - 🚀 Easy to setup with Docker, or on bare metal, or with 1-Click cloud deployment - ⚙️ Easy single-file YAML-based configuration, with option to configure app directly through the UI @@ -214,7 +214,7 @@ Dashy comes with a number of built-in themes, but it's also easy to make you're > For full iconography documentation, see: [**Icons**](./docs/icons.md) -Both sections and items can have an icon associated with them, and defined under the `icon` attribute. There are many options for icons, including Font Awesome support, automatic fetching from favicon, programmatically generated icons and direct local or remote URLs. +Both sections and items can have an icon associated with them, and defined under the `icon` attribute. There are many options for icons, including Font Awesome support, automatic fetching from favicon, emojis, programmatically generated icons and direct local or remote URLs.

@@ -222,11 +222,12 @@ Both sections and items can have an icon associated with them, and defined under - **Favicon**: Set `icon: favicon` to fetch a services icon automatically from the URL of the corresponding application - **Font-Awesome**: To use any font-awesome icon, specify the category, followed by the icon name, e.g. `fas fa-rocket` or `fab fa-monero`. You can also use Pro icons if you have a license key, just set it under `appConfig.fontAwesomeKey` -- **Generative**: Setting `icon: generative`, will generate a unique for a given service, based on it's URL or IP -- **Emoji**: Use an emoji as a tile icon, by putting the emoji's code as the icon attribute. Emojis can be specified either as emojis (`🚀`), unicode (`'U+1F680'`) or shortcode (`':rocket:'`). -- **URL**: You can also pass in a URL to an icon asset, hosted either locally or using any CDN service. E.g. `icon: https://i.ibb.co/710B3Yc/space-invader-x256.png`. -- **Local Image**: To use a local image, store it in `./public/item-icons/` (or create a volume in Docker: `-v /local/image/directory:/app/public/item-icons/`) , and reference it by name and extension - e.g. set `icon: image.png` to use `./public/item-icon/image.png`. You can also use sub-folders here. -- **Material Design Icons**: You can also use any icon from [materialdesignicons.com](https://dev.materialdesignicons.com/icons) by setting the icon to `mdi-[icon-name]`. +- **Simple Icons**: Use any brand/ logo icon from [simpleicons.org](https://simpleicons.org/) by setting the icon to `si-[icon-name]` +- **Emoji**: Use an emoji as a tile icon, by putting the emoji's code as the icon attribute. Emojis can be specified either as emojis (`🚀`), unicode (`'U+1F680'`) or shortcode (`':rocket:'`) +- **Generative**: Setting `icon: generative`, will generate a unique logo for a given service, based on it's specified URL or IP +- **URL**: You can also pass in a URL to an icon asset, hosted either locally or using any CDN service. E.g. `icon: https://i.ibb.co/710B3Yc/space-invader-x256.png` +- **Local Image**: To use a local image, store it in `./public/item-icons/` (or create a volume in Docker: `-v /local/image/directory:/app/public/item-icons/`) , and reference it by name and extension - e.g. set `icon: image.png` to use `./public/item-icon/image.png`. You can also use sub-folders here +- **Material Design Icons**: You can also use any icon from [materialdesignicons.com](https://dev.materialdesignicons.com/icons) by setting the icon to `mdi-[icon-name]` **[⬆️ Back to Top](#dashy)** @@ -254,7 +255,7 @@ You can also specify an time interval in seconds under `appConfig.statusCheckInt > For full authentication documentation, see: [**Authentication**](./docs/authentication.md) -Dashy now has full support for [Keycloak](https://www.keycloak.org/)! +Dashy now has full support for secure single-sign-on using [Keycloak](https://www.keycloak.org/)! See [setup docs](/docs/authentication.md#keycloak) for a full usage guide There is also a simple login feature for basic access control, which doesn't require any additional setup. To enable this feature, add an `auth` attribute under `appConfig`, containing an array of `users`, each with a username, SHA-256 hashed password and optional user type. @@ -269,7 +270,10 @@ appConfig: **Guest Access**: By default, when authentication is configured no user can access your dashboard without first logging in. If you would like to allow for read-only access by unauthenticated users, then you can enable guest mode, by setting `appConfig.auth.enableGuestAccess: true`. -**Note**: Using the above method involves access control being handled on the frontend, and therefore in security-critical situations, it is recommended to use an alternate method for authentication. Keycloak is [natively supported](docs/authentication.md#keycloak), but you could also use [Authelia](https://www.authelia.com/), a VPN or web server and firewall rules. Instructions for all of these can be found [in the docs](docs/authentication.md#alternative-authentication-methods). +**Granular Controls**: With basic login, it is also possible to control which sections are visible to which users. Under the `displayData` property of a section, you can pass an array of usernames to one of the following attributes: +- `hideForUsers` - Section will be visible to all users, except for those specified in this list +- `showForUsers` - Section will be hidden from all users, except for those specified in this list +- `hideForGuests` - Section will be visible for all logged in users, but not for guests (if guest access is enabled)

-**Granular Controls**: With basic login, it is also possible to control which sections are visible to which users. Under the `displayData` property of a section, you can pass an array of usernames to one of the following attributes: -- `hideForUsers` - Section will be visible to all users, except for those specified in this list -- `showForUsers` - Section will be hidden from all users, except for those specified in this list -- `hideForGuests` - Section will be visible for all logged in users, but not for guests (if guest access is enabled) +**Note**: Using the above method involves access control being handled on the frontend, and therefore in security-critical situations, it is recommended to use an alternate method for authentication. Keycloak is [natively supported](docs/authentication.md#keycloak), but you could also use [Authelia](https://www.authelia.com/), a VPN or web server and firewall rules. Instructions for all of these can be found [in the docs](docs/authentication.md#alternative-authentication-methods). **[⬆️ Back to Top](#dashy)** @@ -382,14 +383,16 @@ Hit `Esc` at anytime to close any open apps, clear the search field, or hide any ## Config Editor ⚙️ > For full config documentation, see: [**Configuring**](./docs/configuring.md) -From the Settings Menu in Dashy, you can download, backup, edit and rest your config. An interactive editor makes editing the config file easy, it will tell you if you've got any errors. After making your changes, you can either apply them locally, or export into your main config file. After saving to the config file to the disk, the app will be rebuilt automatically, you can also manually trigger a rebuild from the Settings Menu. +From the Settings Menu in Dashy, you can download, backup, edit and rest your config. The editor will tell you if you've got any validation warnings. A full list of available config options can be found [here](./docs/configuring.md). It's recommend to make a backup of your configuration, as you can then restore it into a new instance of Dashy, without having to set it up again. [json2yaml](https://www.json2yaml.com/) is very useful for converting between YAML to JSON and visa versa.

- Workspace view demo + Config Editor demo

+**Update:** A new and improved drag-and-drop UI editor is in progress, and should be released within the next couple of weeks! + **[⬆️ Back to Top](#dashy)** --- @@ -637,6 +640,7 @@ The following features and tasks are planned for the near future. ## Alternatives 🙌 There are a few self-hosted web apps, that serve a similar purpose to Dashy. If you're looking for a dashboard, and Dashy doesn't meet your needs, I highly recommend you check these projects out! +- [Flame](https://github.com/pawelmalak/flame) by @pawelmalak (`MIT`) - [HomeDash2](https://lamarios.github.io/Homedash2) - [Homer](https://github.com/bastienwirtz/homer) (`Apache License 2.0`) - [Organizr](https://organizr.app/) (`GPL-3.0 License`) diff --git a/docs/privacy.md b/docs/privacy.md index 18bb15d722..a06c5f15cf 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -56,15 +56,17 @@ The following section outlines all data that is stored in the browsers, as cooki - `LAYOUT_ORIENTATION` - Preferred section layout, either horizontal, vertical or auto - `COLLAPSE_STATE` - Remembers which sections are collapsed - `ICON_SIZE` - Size of items, either small, medium or large -- `THEME: 'theme` - Users applied theme +- `THEME` - Users applied theme - `CUSTOM_COLORS` - Any color modifications made to a given theme - `BACKUP_ID` - If a backup has been made, the ID is stored here - `BACKUP_HASH` - A unique hash of the previous backups meta data - `HIDE_SETTINGS` - Lets user hide or show the settings menu -- `USERNAME` - If user logged in, store username in order to welcome them +- `USERNAME` - If user logged in, store username. Only used to show welcome message, not used for auth - `CONF_SECTIONS` - Array of sections, only used when user applies changes locally - `PAGE_INFO` - Config page info, only used when user applies changes locally - `APP_CONFIG` - App config, only used when user applies changes locally +- `MOST_USED` - If smart sort is used to order items by most used, store open count +- `LAST_USED` - If smart sort is used to order items by last used, store timestamps --- @@ -107,6 +109,18 @@ Dashy supports both basic auth, as well as server-based SSO using Keycloak. Full --- +## Disabling Features +You may wish to disable features that you don't want to use, if they involve storing data in the browser or making network requests. +- To disable update checks (makes external request to GH), set `appConfig.disableUpdateChecks: true` +- To disable the service worker (stores cache of app in browser data), set `appConfig.disableServiceWorker: true` +- To disable smart-sort (uses local storage), set `appConfig.disableSmartSort: true` +- To disable web search (redirect to external / internal content), set `appConfig.disableWebSearch: true` +- To keep font-awesome icons disabled (external requests), set `appConfig.enableFontAwesome: false` +- To keep error reporting disabled (external requests and data collection), set `appConfig.enableErrorReporting: false` +- To keep status checks disabled (external/ internal requests), set `appConfig.statusCheck: false` + +--- + ## Reporting a Security Issue If you think you've found a critical issue with Dashy, please send an email to `security@mail.alicia.omg.lol`. You can encrypt it, using [`0688 F8D3 4587 D954 E9E5 1FB8 FEDB 68F5 5C02 83A7`](https://keybase.io/aliciasykes/pgp_keys.asc?fingerprint=0688f8d34587d954e9e51fb8fedb68f55c0283a7). You should receive a response within 48 hours. From 2c4b4dc74a3de1cef42f59630379f7a1ccc48862 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 5 Sep 2021 16:48:19 +0100 Subject: [PATCH 07/13] :card_file_box: Adds sortBy option to schema and docs --- docs/configuring.md | 12 +++++++----- src/utils/ConfigSchema.json | 19 ++++++++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/docs/configuring.md b/docs/configuring.md index 49a1097410..7de66a199a 100644 --- a/docs/configuring.md +++ b/docs/configuring.md @@ -78,6 +78,7 @@ To disallow any changes from being written to disk via the UI config editor, set **`allowConfigEdit`** | `boolean` | _Optional_ | Should prevent / allow the user to write configuration changes to the conf.yml from the UI. When set to `false`, the user can only apply changes locally using the config editor within the app, whereas if set to `true` then changes can be written to disk directly through the UI. Defaults to `true`. Note that if authentication is enabled, the user must be of type `admin` in order to apply changes globally. **`enableErrorReporting`** | `boolean` | _Optional_ | Enable reporting of unexpected errors and crashes. This is off by default, and **no data will ever be captured unless you explicitly enable it**. Turning on error reporting helps previously unknown bugs get discovered and fixed. Dashy uses [Sentry](https://github.com/getsentry/sentry) for error reporting. Defaults to `false`. **`sentryDsn`** | `boolean` | _Optional_ | If you need to monitor errors in your instance, then you can use Sentry to collect and process bug reports. Sentry can be self-hosted, or used as SaaS, once your instance is setup, then all you need to do is pass in the DSN here, and enable error reporting. You can learn more on the [Sentry DSN Docs](https://docs.sentry.io/product/sentry-basics/dsn-explainer/). Note that this will only ever be used if `enableErrorReporting` is explicitly enabled. +**`disableSmartSort`** | `boolean` | _Optional_ | For the most-used and last-used app sort functions to work, a basic open-count is stored in local storage. If you do not want this to happen, then disable smart sort here, but you wil no longer be able to use these sort options. Defaults to `false`. **`disableUpdateChecks`** | `boolean` | _Optional_ | If set to true, Dashy will not check for updates. Defaults to `false`. **`disableServiceWorker`** | `boolean` | _Optional_ | Service workers cache web applications to improve load times and offer basic offline functionality, and are enabled by default in Dashy. The service worker can sometimes cause older content to be cached, requiring the app to be hard-refreshed. If you do not want SW functionality, or are having issues with caching, set this property to `true` to disable all service workers. **`disableContextMenu`** | `boolean` | _Optional_ | If set to `true`, the custom right-click context menu will be disabled. Defaults to `false`. @@ -166,7 +167,7 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)** **`statusCheck`** | `boolean` | _Optional_ | When set to `true`, Dashy will ping the URL associated with the current service, and display its status as a dot next to the item. The value here will override `appConfig.statusCheck` so you can turn off or on checks for a given service. Defaults to `appConfig.statusCheck`, falls back to `false` **`statusCheckUrl`** | `string` | _Optional_ | If you've enabled `statusCheck`, and want to use a different URL to what is defined under the item, then specify it here **`statusCheckHeaders`** | `object` | _Optional_ | If you're endpoint requires any specific headers for the status checking, then define them here -**`statusCheckAllowInsecure`** | `boolean` | By default, any request to insecure content will be blocked. Setting this option to `true` will disable the `rejectUnauthorized` option, enabling you to ping non-HTTPS services. Should only be used when needed, and for URLs that you know are safe. Defaults to `false` +**`statusCheckAllowInsecure`** | `boolean` | _Optional_ | By default, any request to insecure content will be blocked. Setting this option to `true` will disable the `rejectUnauthorized` option, enabling you to ping non-HTTPS services for the current item. Defaults to `false` **`color`** | `string` | _Optional_ | An optional color for the text and font-awesome icon to be displayed in. Note that this will override the current theme and so may not display well **`backgroundColor`** | `string` | _Optional_ | An optional background fill color for the that given item. Again, this will override the current theme and so might not display well against the background **`provider`** | `string` | _Optional_ | The name of the provider for a given service, useful for when including hosted apps. In some themes, this is visible under the item name @@ -177,12 +178,13 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)** **Field** | **Type** | **Required**| **Description** --- | --- | --- | --- +**`sortBy`** | `string` | _Optional_ | The sort order for items within the current section. By default items are displayed in the order in which they are listed in within the config. The following sort options are supported: `most-used` (most opened apps first), `last-used` (the most recently used apps), `alphabetical`, `reverse-alphabetical` and `default` **`collapsed`** | `boolean` | _Optional_ | If true, the section will be collapsed initially, and will need to be clicked to open. Useful for less regularly used, or very long sections. Defaults to `false` -**`color`** | `string` | _Optional_ | A custom accent color for the section, as a hex code or HTML color (e.g. `#fff`) -**`customStyles`** | `string` | _Optional_ | Custom CSS properties that should be applied to that section, e.g. `border: 2px dashed #ff0000;` -**`itemSize`** | `string` | _Optional_ | Specify the size for items within this group, either `small`, `medium` or `large`. Note that this will overide any settings specified through the UI **`rows`** | `number` | _Optional_ | Height of the section, specified as the number of rows it should span vertically, e.g. `2`. Defaults to `1`. Max is `5`. **`cols`** | `number` | _Optional_ | Width of the section, specified as the number of columns the section should span horizontally, e.g. `2`. Defaults to `1`. Max is `5`. +**`itemSize`** | `string` | _Optional_ | Specify the size for items within this group, either `small`, `medium` or `large`. Note that this will overide any settings specified through the UI +**`color`** | `string` | _Optional_ | A custom accent color for the section, as a hex code or HTML color (e.g. `#fff`) +**`customStyles`** | `string` | _Optional_ | Custom CSS properties that should be applied to that section, e.g. `border: 2px dashed #ff0000;` **`sectionLayout`** | `string` | _Optional_ | Specify which CSS layout will be used to responsivley place items. Can be either `auto` (which uses flex layout), or `grid`. If `grid` is selected, then `itemCountX` and `itemCountY` may also be set. Defaults to `auto` **`itemCountX`** | `number` | _Optional_ | The number of items to display per row / horizontally. If not set, it will be calculated automatically based on available space. Can only be set if `sectionLayout` is set to `grid`. Must be a whole number between `1` and `12` **`itemCountY`** | `number` | _Optional_ | The number of items to display per column / vertically. If not set, it will be calculated automatically based on available space. If `itemCountX` is set, then `itemCountY` can be calculated automatically. Can only be set if `sectionLayout` is set to `grid`. Must be a whole number between `1` and `12` @@ -196,7 +198,7 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)** **Field** | **Type** | **Required**| **Description** --- | --- | --- | --- -**`icon`** | `string` | _Optional_ | The icon for a given item or section. Can be a font-awesome icon, favicon, remote URL or local URL. If set to `favicon`, the icon will be automatically fetched from the items website URL. To use font-awesome, specify the category, followed by the icon name, e.g. `fas fa-rocket`, `fab fa-monero` or `fal fa-duck` - note that to use pro icons, you mut specify `appConfig.fontAwesomeKey`. Similarly, you can also use [simple-icons](https://simpleicons.org/) by setting icon to `si-[icon-name]` or [material-design-icons](https://dev.materialdesignicons.com/icons) by setting icon to `mdi-[icon-name]`. If set to `generative`, then a unique icon is generated from the apps URL or IP. You can also use hosted any by specifying it's URL, e.g. `https://i.ibb.co/710B3Yc/space-invader-x256.png`. To use a local image, first store it in `./public/item-icons/` (or `-v /app/public/item-icons/` in Docker) , and reference it by name and extension - e.g. set `image.png` to use `./public/item-icon/image.png`, you can also use sub-folders if you have a lot of icons, to keep them organised. +**`icon`** | `string` | _Optional_ | The icon for a given item or section. See [Icon Docs](/docs/icons.md) for all available supported icon types. To auto-fetch icon from a services URL, aet to `favicon`. To use font-awesome, specify the category, followed by the icon name, e.g. `fas fa-rocket`, `fab fa-monero` or `fal fa-duck`. Similarly, for branded icons, you can use [simple-icons](https://simpleicons.org/) by setting icon to `si-[icon-name]` or [material-design-icons](https://dev.materialdesignicons.com/icons) by setting icon to `mdi-[icon-name]`. If set to `generative`, then a unique icon is generated from the apps URL or IP. You can also use hosted any by specifying it's URL, e.g. `https://i.ibb.co/710B3Yc/space-invader-x256.png`. To use a local image, first store it in `./public/item-icons/` (or `-v /app/public/item-icons/` in Docker) , and reference it by name and extension - e.g. set `image.png` to use `./public/item-icon/image.png`, you can also use sub-folders if you have a lot of icons, to keep them organised. **[⬆️ Back to Top](#configuring)** diff --git a/src/utils/ConfigSchema.json b/src/utils/ConfigSchema.json index 1216a871a7..1e88e49cf6 100644 --- a/src/utils/ConfigSchema.json +++ b/src/utils/ConfigSchema.json @@ -361,6 +361,11 @@ "default": false, "description": "Prevents Dashy from checking for updates" }, + "disableSmartSort": { + "type": "boolean", + "default": false, + "description": "Prevents the app storing local click count, required for the last-used and most-used sort orders" + }, "enableErrorReporting": { "type": "boolean", "default": false, @@ -397,6 +402,18 @@ "additionalProperties": false, "description": "Optional meta data for customizing a section", "properties": { + "sortBy": { + "enum": [ + "default", + "most-used", + "last-used", + "alphabetical", + "reverse-alphabetical", + "random" + ], + "default": "default", + "description": "How to sort items within the section. By default items are displayed in the order in which they are listed in within the config" + }, "collapsed": { "type": "boolean", "default": false, @@ -559,4 +576,4 @@ } } } -} +} \ No newline at end of file From 96e9adf6f34cbc5e8d640ccbf006fb36f4608d4e Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 5 Sep 2021 16:49:19 +0100 Subject: [PATCH 08/13] :sparkles: Completes the sortBy functionality for items --- src/components/LinkItems/Section.vue | 35 +++++++++++++++++++++------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/components/LinkItems/Section.vue b/src/components/LinkItems/Section.vue index c9977b6ef8..51edd31729 100644 --- a/src/components/LinkItems/Section.vue +++ b/src/components/LinkItems/Section.vue @@ -18,8 +18,8 @@ > import { sortOrder as defaultSortOrder, localStorageKeys } from '@/utils/defaults'; +import ErrorHandler from '@/utils/ErrorHandler'; import Item from '@/components/LinkItems/Item.vue'; import Collapsable from '@/components/LinkItems/Collapsable.vue'; import IframeModal from '@/components/LinkItems/IframeModal.vue'; @@ -78,14 +79,19 @@ export default { /* If the sortBy attribute is specified, then return sorted data */ sortedItems() { let { items } = this; + if (this.config.appConfig.disableSmartSort) return items; if (this.sortOrder === 'alphabetical') { - items.sort((a, b) => (a.title > b.title ? 1 : -1)); + this.sortAlphabetically(items); } else if (this.sortOrder === 'reverse-alphabetical') { - items.sort((a, b) => (a.title < b.title ? 1 : -1)); + this.sortAlphabetically(items).reverse(); } else if (this.sortOrder === 'most-used') { items = this.sortByMostUsed(items); } else if (this.sortOrder === 'last-used') { items = this.sortBLastUsed(items); + } else if (this.sortOrder === 'random') { + items = this.sortRandomly(items); + } else if (this.sortOrder && this.sortOrder !== 'default') { + ErrorHandler(`Unknown Sort order '${this.sortOrder}' under '${this.title}'`); } return items; }, @@ -107,8 +113,9 @@ export default { }, methods: { /* Returns a unique lowercase string, based on name, for section ID */ - makeId(str) { - return str.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase(); + makeId(sectionStr, itemStr) { + const charSum = sectionStr.split('').map((a) => a.charCodeAt(0)).reduce((x, y) => x + y); + return `${charSum}_${itemStr.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase()}`; }, /* Opens the iframe modal */ triggerModal(url) { @@ -123,6 +130,7 @@ export default { const globalPreference = this.config.appConfig.statusCheck || false; return itemPreference !== undefined ? itemPreference : globalPreference; }, + /* Determine how often to re-fire status checks */ getStatusCheckInterval() { let interval = this.config.appConfig.statusCheckInterval; if (!interval) return 0; @@ -130,20 +138,31 @@ export default { if (interval < 1) interval = 0; return interval; }, + /* Sorts items alphabetically using the title attribute */ + sortAlphabetically(items) { + return items.sort((a, b) => (a.title > b.title ? 1 : -1)); + }, /* Sorts items by most used to least used, based on click-count */ sortByMostUsed(items) { const usageCount = JSON.parse(localStorage.getItem(localStorageKeys.MOST_USED) || '{}'); - const gmu = (item) => usageCount[this.makeId(item.title)] || 0; + const gmu = (item) => usageCount[this.makeId(this.title, item.title)] || 0; items.reverse().sort((a, b) => (gmu(a) < gmu(b) ? 1 : -1)); return items; }, /* Sorts items by most recently used */ sortBLastUsed(items) { const usageCount = JSON.parse(localStorage.getItem(localStorageKeys.LAST_USED) || '{}'); - const glu = (item) => usageCount[this.makeId(item.title)] || 0; + const glu = (item) => usageCount[this.makeId(this.title, item.title)] || 0; items.reverse().sort((a, b) => (glu(a) < glu(b) ? 1 : -1)); return items; }, + /* Sorts items randomly */ + sortRandomly(items) { + return items + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value); + }, }, }; From 6da4a237f13e7fc7b0b5f5e722003174a07fcb9c Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 5 Sep 2021 16:50:53 +0100 Subject: [PATCH 09/13] :see_no_evil: Updates console err msg, to say likely not a bug --- src/utils/CoolConsole.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/CoolConsole.js b/src/utils/CoolConsole.js index c5641c207e..1a7484979d 100644 --- a/src/utils/CoolConsole.js +++ b/src/utils/CoolConsole.js @@ -7,7 +7,7 @@ export const welcomeMsg = () => { export const warningMsg = (message) => { console.info( - `%c⚠️ Warning ⚠️%c \n${message} \n\n%c🐛If you have found a bug, please open a ticket on GitHub, at: https://git.io/JukXk`, + `%c⚠️ Warning ⚠️%c \n${message} \n\n%cThis is likely not an issue with Dashy, but rather your configuration. If you think it is a bug, please open a ticket on GitHub: https://git.io/JukXk`, "color:#ceb73f; background: #ceb73f33; font-size:1.2rem; padding:0.15rem; margin: 0.2rem auto 1rem auto; font-family: Rockwell, Tahoma, 'Trebuchet MS', Helvetica; border: 2px solid #ceb73f; border-radius: 4px; font-weight: bold; text-shadow: 1px 1px 1px #000000bf;", 'font-weight: bold; font-size: 0.9rem;color: #ceb73f;', "color: #ceb73f; font-size: 0.6rem; font-family: Tahoma, 'Trebuchet MS', Helvetica;", From 76a570cb6abe7b5b05db73132bc0085660040e15 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 5 Sep 2021 17:02:26 +0100 Subject: [PATCH 10/13] :adhesive_bandage: Updates default item sort order --- src/utils/defaults.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 5f169696d6..4ed1c35c58 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -26,7 +26,7 @@ module.exports = { /* Default API to use for fetching of user service favicon icons (if enabled) */ faviconApi: 'faviconkit', /* The default sort order for sections */ - sortOrder: 'auto', + sortOrder: 'default', /* The page paths for each route within the app for the router */ routePaths: { home: '/home', From 27dd60a812110d49c60feea34e02bdf823d188b2 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Sun, 5 Sep 2021 17:02:47 +0100 Subject: [PATCH 11/13] :adhesive_bandage: Fixes the UI for tooltip description --- src/components/LinkItems/Item.vue | 14 ++++++++------ src/styles/color-palette.scss | 2 ++ src/styles/color-themes.scss | 12 ++++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/LinkItems/Item.vue b/src/components/LinkItems/Item.vue index 8572592c92..dbe96ca99d 100644 --- a/src/components/LinkItems/Item.vue +++ b/src/components/LinkItems/Item.vue @@ -131,6 +131,7 @@ export default { }, /* Returns configuration object for the tooltip */ getTooltipOptions() { + if (!this.description) return {}; // If no description, then skip const hotkeyText = this.hotkey ? `\nPress '${this.hotkey}' to launch` : ''; return { disabled: !this.description, @@ -410,16 +411,18 @@ export default {