From 5ae100b44f8cbd1c5455eb296030c1e232a2008f Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Tue, 28 Sep 2021 21:37:48 +0100 Subject: [PATCH 01/29] :sparkles: Adds support for home-lab icons --- docs/icons.md | 38 +++++++++++++++++++++++++++ src/components/LinkItems/ItemIcon.vue | 7 +++++ src/utils/defaults.js | 1 + 3 files changed, 46 insertions(+) diff --git a/docs/icons.md b/docs/icons.md index 2b4bcd530..7267dc81a 100644 --- a/docs/icons.md +++ b/docs/icons.md @@ -94,6 +94,27 @@ For example, these will all render the same rocket (πŸš€) emoji: `icon: ':rocket --- +## Home-Lab Icons + +The [dashboard-icons](https://github.com/WalkxCode/dashboard-icons) repo by [@WalkxCode](https://github.com/WalkxCode) provides a comprehensive collection of 360+ high-quality PNG icons for commonly self-hosted services. Dashy natively supports these icons, and you can use them just by specifying the icon name (without extension) preceded by `hl-`. You can see a full list of all available icons [here](https://github.com/WalkxCode/dashboard-icons/tree/master/png). + +For example: +```yaml +sections: +- name: Home Lab Icons Example + items: + - title: AdGuard Home + icon: hl-adguardhome + - title: Long Horn + icon: hl-longhorn + - title: Nagios + icon: hl-nagios + - title: Whoogle Search + icon: hl-whooglesearch +``` + +--- + ## Icons by URL You can also set an icon by passing in a valid URL pointing to the icons location. For example `icon: https://i.ibb.co/710B3Yc/space-invader-x256.png`, this can be in .png, .jpg or .svg format, and hosted anywhere- so long as it's accessible from where you are hosting Dashy. The icon will be automatically scaled to fit, however loading in a lot of large icons may have a negative impact on performance, especially if you visit Dashy from new devices often. @@ -127,3 +148,20 @@ sections: ## No Icon If you don't wish for a given item or section to have an icon, just leave out the `icon` attribute. + + +--- + +## Icon Collections and Resources + +The following website provide good-quality, free icon sets. To use any of these icons, just copy the link to the raw icon (it should end in `.svg` or `.png`) and paste it as your `icon`, or download and save the icons in `/public/item-icons` or pass through with a Docker volume as described above. +Full credit to the authors, please see the licenses for each service for usage and copyright information. + +- [dashboard-icons](https://github.com/WalkxCode/dashboard-icons) - 350+ high-quality icons for commonly self-hosted services, maintained by [@WalkxCode](https://github.com/WalkxCode) +- [SVG Box](https://svgbox.net/iconsets/) - Cryptocurrency, social media apps and flag icons +- [Simple Icons](https://simpleicons.org/) - Free SVG brand icons, with easy API access +- [Icons8](https://icons8.com/icons) - Thousands of icons, all with free 64x64 versions +- [Flat Icon](https://www.flaticon.com/) - Wide variety of icon sets, almost all free to use + +If you are a student, then you can get free access to premium icons on [Icon Scout](https://education.github.com/pack/redeem/iconscout-student) or [Icons8](https://icons8.com/github-students) using the GitHub Student Pack. + diff --git a/src/components/LinkItems/ItemIcon.vue b/src/components/LinkItems/ItemIcon.vue index b8c3f9c6d..0696e5d8d 100644 --- a/src/components/LinkItems/ItemIcon.vue +++ b/src/components/LinkItems/ItemIcon.vue @@ -135,6 +135,11 @@ export default { const icon = simpleIcons.Get(imageName); return icon.path; }, + /* Gets home-lab icon from GitHub */ + getHomeLabIcon(img) { + const imageName = img.replace('hl-', '').toLocaleLowerCase(); + return `${iconCdns.homeLabIcons}/png/${imageName}.png`; + }, /* Checks if the icon is from a local image, remote URL, SVG or font-awesome */ getIconPath(img, url) { switch (this.determineImageType(img)) { @@ -145,6 +150,7 @@ export default { case 'generative': return this.getGenerativeIcon(url); case 'mdi': return img; // Material design icons case 'simple-icons': return this.getSimpleIcon(img); + case 'home-lab-icons': return this.getHomeLabIcon(img); case 'svg': return img; // Local SVG icon case 'emoji': return img; // Emoji/ unicode default: return ''; @@ -159,6 +165,7 @@ export default { else if (img.includes('fa-')) imgType = 'font-awesome'; else if (img.includes('mdi-')) imgType = 'mdi'; else if (img.includes('si-')) imgType = 'si'; + else if (img.includes('hl-')) imgType = 'home-lab-icons'; else if (img.includes('favicon-')) imgType = 'custom-favicon'; else if (img === 'favicon') imgType = 'favicon'; else if (img === 'generative') imgType = 'generative'; diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 49fa263c1..387d26e40 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -176,6 +176,7 @@ module.exports = { generative: 'https://ipsicon.io', localPath: '/item-icons', faviconName: 'favicon.ico', + homeLabIcons: 'https://github.com/raw/WalkxCode/dashboard-icons/master/', }, /* URLs for web search engines */ searchEngineUrls: { From 66e4ada28f0bfd077bc5fcbedd5b8400efe86de1 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Wed, 29 Sep 2021 19:43:58 +0100 Subject: [PATCH 02/29] :memo: Updates privacy docs, re Generative icons --- docs/privacy.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/privacy.md b/docs/privacy.md index 76d3c2719..434fac1d9 100644 --- a/docs/privacy.md +++ b/docs/privacy.md @@ -18,6 +18,10 @@ If an item's icon is set to `favicon`, then it will be auto-fetched from the cor The default favicon API is [Favicon Kit](https://faviconkit.com/), but this can be changed by setting `appConfig.faviconApi` to an alternate source (`google`, `clearbit`, `webmasterapi` and `allesedv` are supported). If you do not want to use any API, then you can set this property to `local`, and the favicon will be fetched from the default path. For hosted services, this will still incur an external request. +### Generative Icons +If an item has the icon set to `generative`, then an external request it made to [Dice Bear](https://dicebear.com/) to fetch the uniquely generated icon. The URL of a given service is used as the key for generating the icon, but it is first hashed and encoded for basic privacy. For more info, please reference the [Dicebear Privacy Policy](https://avatars.dicebear.com/legal/privacy-policy) + + ### Other Icons Section icons, item icons and app icons are able to accept a URL to a raw image, if the image is hosted online then an external request will be made. To avoid the need to make external requests for icon assets, you can either use a self-hosted CDN, or store your images within `./public/item-icons` (which can be mounted as a volume if you're using Docker). From 5ec2abcf81c11dcd43e7da172dd7633878b5f291 Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Wed, 29 Sep 2021 19:46:04 +0100 Subject: [PATCH 03/29] :alien: New API for generative icons, Re: #163 --- docs/icons.md | 9 +++++++-- src/components/LinkItems/ItemIcon.vue | 8 +++++--- src/utils/MiscHelpers.js | 9 ++++++++- src/utils/defaults.js | 4 ++-- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/docs/icons.md b/docs/icons.md index 7267dc81a..df0b58503 100644 --- a/docs/icons.md +++ b/docs/icons.md @@ -75,10 +75,10 @@ If for a given service none of the APIs work in your situation, and nor does loc --- ## Generative Icons -Uses a unique and programmatically generated icon for a given service. This is particularly useful when you have a lot of similar services with a different IP or port, and no specific icon. These icons are generated with [ipsicon.io](https://ipsicon.io/). To use this option, just set an item's to: `icon: generative`. +Uses a unique and programmatically generated icon for a given service. This is particularly useful when you have a lot of similar services with a different IP or port, and no specific icon. These icons are generated with [DiceBear](https://avatars.dicebear.com/), and use a hash of the services domain/ ip for entropy, so each domain will always have the same icon. To use this option, just set an item's to: `icon: generative`.

- +

--- @@ -113,6 +113,11 @@ sections: icon: hl-whooglesearch ``` + +

+ +

+ --- ## Icons by URL diff --git a/src/components/LinkItems/ItemIcon.vue b/src/components/LinkItems/ItemIcon.vue index 0696e5d8d..45aa5f1a9 100644 --- a/src/components/LinkItems/ItemIcon.vue +++ b/src/components/LinkItems/ItemIcon.vue @@ -23,9 +23,10 @@ import simpleIcons from 'simple-icons'; import BrokenImage from '@/assets/interface-icons/broken-icon.svg'; import ErrorHandler from '@/utils/ErrorHandler'; -import { faviconApi as defaultFaviconApi, faviconApiEndpoints, iconCdns } from '@/utils/defaults'; import EmojiUnicodeRegex from '@/utils/EmojiUnicodeRegex'; import emojiLookup from '@/utils/emojis.json'; +import { faviconApi as defaultFaviconApi, faviconApiEndpoints, iconCdns } from '@/utils/defaults'; +import { asciiHash } from '@/utils/MiscHelpers'; export default { name: 'Icon', @@ -127,7 +128,8 @@ export default { }, /* Formats the URL for fetching the generative icons */ getGenerativeIcon(url) { - return `${iconCdns.generative}/${this.getHostName(url)}.svg`; + const host = encodeURI(url) || Math.random().toString(); + return iconCdns.generative.replace('{icon}', asciiHash(host)); }, /* Returns the SVG path content */ getSimpleIcon(img) { @@ -138,7 +140,7 @@ export default { /* Gets home-lab icon from GitHub */ getHomeLabIcon(img) { const imageName = img.replace('hl-', '').toLocaleLowerCase(); - return `${iconCdns.homeLabIcons}/png/${imageName}.png`; + return iconCdns.homeLabIcons.replace('{icon}', imageName); }, /* Checks if the icon is from a local image, remote URL, SVG or font-awesome */ getIconPath(img, url) { diff --git a/src/utils/MiscHelpers.js b/src/utils/MiscHelpers.js index 212e74957..add6f457c 100644 --- a/src/utils/MiscHelpers.js +++ b/src/utils/MiscHelpers.js @@ -3,4 +3,11 @@ import { hideFurnitureOn } from '@/utils/defaults'; /* Returns false if page furniture should be hidden on said route */ export const shouldBeVisible = (routeName) => !hideFurnitureOn.includes(routeName); -export const x = () => null; +/* Very rudimentary hash function for generative icons */ +export const asciiHash = (input) => { + const str = (!input || input.length === 0) ? Math.random().toString() : input; + const reducer = (previousHash, char) => (previousHash || 0) + char.charCodeAt(0); + const asciiSum = str.split('').reduce(reducer).toString(); + const shortened = asciiSum.slice(0, 30) + asciiSum.slice(asciiSum.length - 30); + return window.btoa(shortened); +}; diff --git a/src/utils/defaults.js b/src/utils/defaults.js index 387d26e40..2a475d458 100644 --- a/src/utils/defaults.js +++ b/src/utils/defaults.js @@ -173,10 +173,10 @@ module.exports = { fa: 'https://kit.fontawesome.com', mdi: 'https://cdn.jsdelivr.net/npm/@mdi/font@5.9.55/css/materialdesignicons.min.css', si: 'https://unpkg.com/simple-icons@v5/icons', - generative: 'https://ipsicon.io', + generative: 'https://avatars.dicebear.com/api/identicon/{icon}.svg', localPath: '/item-icons', faviconName: 'favicon.ico', - homeLabIcons: 'https://github.com/raw/WalkxCode/dashboard-icons/master/', + homeLabIcons: 'https://github.com/raw/WalkxCode/dashboard-icons/master/png/{icon}.png', }, /* URLs for web search engines */ searchEngineUrls: { From 2f1dd2a9fa002e22e4735fe07303a0ebed65703c Mon Sep 17 00:00:00 2001 From: Alicia Sykes Date: Wed, 29 Sep 2021 20:26:24 +0100 Subject: [PATCH 04/29] :sparkles: Re: #255 - Adds an option for landing URL in workspace --- docs/alternate-views.md | 4 +++- src/components/Workspace/SideBar.vue | 14 ++++++++++++++ src/views/Workspace.vue | 19 ++++++++++++------- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/docs/alternate-views.md b/docs/alternate-views.md index de72cd1a5..29dc032cb 100644 --- a/docs/alternate-views.md +++ b/docs/alternate-views.md @@ -14,7 +14,9 @@ This is the main page that you will land on when you first launch the applicatio ### Workspace The workspace view displays your links in a sidebar on the left-hand side, and apps are launched within Dashy. This enables you to use all of your self-hosted apps from one place, and makes multi-tasking easy. -In the workspace view, you can keep previously opened websites/ apps open in the background, by setting `appConfig.enableMultiTasking: true`. This comes at the cost of performance, but does mean that your session with each app is preserved, enabling you to quickly switch between your apps. +In the workspace view, you can opt to keep previously opened websites/ apps open in the background, by setting `appConfig.enableMultiTasking: true`. This comes at the cost of performance, but does mean that your session with each app is preserved, enabling you to quickly switch between your apps. + +You can also specify a default app to be opened when you land on the workspace, by setting `appConfig.workspaceLandingUrl: https://app-to-open/`. If this app exists within your sections.items, then the corresponding section will also be expanded.

Example of Workspace View
diff --git a/src/components/Workspace/SideBar.vue b/src/components/Workspace/SideBar.vue index b75fba72e..e3115d17a 100644 --- a/src/components/Workspace/SideBar.vue +++ b/src/components/Workspace/SideBar.vue @@ -39,6 +39,7 @@ export default { inject: ['config'], props: { sections: Array, + initUrl: String, }, data() { return { @@ -56,9 +57,22 @@ export default { openSection(index) { this.isOpen = this.isOpen.map((val, ind) => (ind !== index ? false : !val)); }, + /* When item clicked, emit a launch event */ launchApp(url) { this.$emit('launch-app', url); }, + /* If an initial URL is specified, then open relevant section */ + openDefaultSection() { + if (!this.initUrl) return; + const process = (url) => url.replace(/[^\w\s]/gi, '').toLowerCase(); + const compare = (item) => (process(item.url) === process(this.initUrl)); + this.sections.forEach((section, sectionIndex) => { + if (section.items.findIndex(compare) !== -1) this.openSection(sectionIndex); + }); + }, + }, + mounted() { + this.openDefaultSection(); }, }; diff --git a/src/views/Workspace.vue b/src/views/Workspace.vue index 2878885c5..bebbba1e4 100644 --- a/src/views/Workspace.vue +++ b/src/views/Workspace.vue @@ -1,6 +1,6 @@