🔀 Merge pull request #261 from Lissy93/IMPROVMENTS/user-requested-modifications

[IMPROVMENTS] User-Requested Modifications
Fixes #255 
Fixes #254
This commit is contained in:
Alicia Sykes 2021-10-02 20:51:47 +01:00 committed by GitHub
commit 9b1c457ddd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 548 additions and 126 deletions

10
.github/CHANGELOG.md vendored
View File

@ -1,5 +1,15 @@
# Changelog
## 💄 1.8.5 - Lots of Requested UI Improvements [PR #261](https://github.com/Lissy93/dashy/pull/261)
- Adds an option for landing URL in workspace, Re: #255
- Switches to a new API for generative icons, Re: #163
- Adds new tab functionality to Workspace, Re: #254
- Remove CSS validation in style editor, Re: #259
- Cap item description at 2 lines, Re: #250
- Adds native support for common homelab icons, using dashboard-icons
- Improves general responsiveness of home page sections positioning
- Updates, fixes and adds a bunch of actions for easier repo management
## ✨ 1.8.4 - Custom Error Pages [PR #257](https://github.com/Lissy93/dashy/pull/257)
- Creates a 404 Not Found page
- Routes any missing views to the 404 page

10
.github/pr-branch-labeler.yml vendored Normal file
View File

@ -0,0 +1,10 @@
# PR labels and the branch patterns they should be auto-assigned to
🦋 Bug Fix: ['FIX/*', 'HOT-FIX/*', 'BUG-FIX/*']
✨ New Feature: ['FEATURE/*']
🚚 Refactor: ['IMPROVMENTS/*', 'REFACTOR/*']
💯 Showcase: ['SHOWCASE/*']
💄 Stylistic Changes: ['STYLES/*', 'THEME/*']
🛠️ Build Changes: ['ARCH/*', 'ARCHITECTURE/*', 'DOCKER/*', 'BUILD/*']
🤖 Auto: ['AUTO/*', 'BOT/*', 'snyk-upgrade-*', 'snyk-fix-*']
⛔ Don't Merge: ['WEBSITE/*', 'EXPERIMENT/*', 'DEPLOY/*', 'deploy_*', 'gh-pages', 'dev-demo']

14
.github/workflows/assign-reviewer.yml vendored Normal file
View File

@ -0,0 +1,14 @@
# Automatically assigns the author as a reviewer to opened PRs and issues
name: 💡 Auto-Assign Author to PR
on:
pull_request:
types: [opened]
issues:
types: [opened]
jobs:
assign-author:
runs-on: ubuntu-latest
steps:
- name: Assign author
uses: technote-space/assign-author@v1
GITHUB_TOKEN: ${{secrets.BOT_GITHUB_TOKEN}}

23
.github/workflows/auto-rebase-pr.yml vendored Normal file
View File

@ -0,0 +1,23 @@
# When a '/rebase' comment is added to a PR, it will be rebased from the main branch
name: 🏗️ Automatic PR Rebase
on:
issue_comment:
types: [created]
jobs:
rebase:
name: Rebase
if: >
github.event.issue.pull_request != ''
&& contains(github.event.comment.body, '/rebase')
&& github.event.comment.author_association == 'MEMBER'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
fetch-depth: 0
- name: Rebase
uses: cirrus-actions/rebase@1.4
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}

View File

@ -1,16 +1,27 @@
# Creates a new tag, whenever the app version (in package.json) is updated in master
name: 🏗️ Create Tag on Version Change
# And marks any relevant issues as fixed
name: 🏗️ Release Tag new Versions
on:
push:
branches:
- master
jobs:
build:
tag-pre-release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: butlerlogic/action-autotag@stable
with:
GITHUB_TOKEN: '${{ secrets.BOT_GITHUB_TOKEN }}'
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
strategy: package
commit_message_template: "🔖 {{number}} {{message}} (by {{author}})\nSHA: {{sha}}\n."
mark-issue-fixed:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: butlerlogic/action-autotag@stable
- name: Label Fixed Issues
uses: gh-bot/fix-labeler@master
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
label: '✅ Fixed'

View File

@ -0,0 +1,22 @@
# Attempts to auto-detect weather an issue is a duplicate, and adds a comment
name: 🎯 Issue Duplicate Check
on:
issues:
types: [opened, edited]
jobs:
check-duplicate:
runs-on: ubuntu-latest
steps:
- uses: wow-actions/potential-duplicates@v1
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
filter: ''
exclude: '[BUG] [QUESTION] [FEEDBACK] [SHOWCASE]'
label: '🕸️ Potential Duplicate'
state: all
threshold: 0.6
reactions: 'eyes'
comment: >
Potential duplicates: {{#issues}}
- [#{{ number }}] {{ title }} ({{ accuracy }}%)
{{/issues}}

View File

@ -13,7 +13,7 @@ jobs:
uses: actions/stale@v4
with:
repo-token: ${{ secrets.BOT_GITHUB_TOKEN }}
days-before-stale: 42
days-before-stale: 30
days-before-close: 5
operations-per-run: 30
remove-stale-when-updated: true
@ -41,9 +41,9 @@ jobs:
uses: actions/stale@v4
with:
repo-token: ${{ secrets.BOT_GITHUB_TOKEN }}
days-before-stale: 7
days-before-stale: 5
days-before-close: 3
operations-per-run: 10
operations-per-run: 30
remove-stale-when-updated: true
stale-issue-message: >
Hello! Looks like additional info is required for this issue to be addressed.
@ -55,3 +55,25 @@ jobs:
close-issue-label: '🕸️ Inactive'
exempt-issue-labels: '📌 Keep Open'
exempt-pr-labels: '📌 Keep Open'
# Comment on issues that I should have replied to
- name: Notify Repo Owner to Respond
uses: actions/stale@v4
with:
repo-token: ${{ secrets.BOT_GITHUB_TOKEN }}
days-before-stale: 7
days-before-close: 365
operations-per-run: 30
remove-stale-when-updated: true
stale-issue-message: Hey @Lissy93 - Don't forget to respond!
stale-pr-message: Hey @Lissy93 - Don't forget to respond!
only-labels: '👤 Awaiting Maintainer Response'
labels-to-remove-when-unstale: '👤 Awaiting Maintainer Response'
close-issue-message: 'Closed due to no response from repo author for over a year'
close-pr-message: 'Closed due to no response from repo author for over a year'
stale-issue-label: '👤 Awaiting Maintainer Response'
stale-pr-label: '👤 Awaiting Maintainer Response'
close-issue-label: '🕸️ Inactive'
close-pr-label: '🕸️ Inactive'
exempt-issue-labels: '📌 Keep Open'
exempt-pr-labels: '📌 Keep Open'

27
.github/workflows/docs-link-checker.yml vendored Normal file
View File

@ -0,0 +1,27 @@
# Checks for any broken links in the docs, and raises an issue if found
name: 🌈 Broken Link Checker
on:
repository_dispatch:
workflow_dispatch:
schedule:
- cron: '0 1 * * 0' # At 01:00 on Sunday.
jobs:
link-checker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Check for Broken Links
uses: lycheeverse/lychee-action@v1.0.8
with:
args: --verbose --no-progress **/*.md **/*.html
env:
GITHUB_TOKEN: ${{secrets.BOT_GITHUB_TOKEN}}
LYCHEE_OUT: .github/broken-link-report.md
- name: Raise an Issue with Results
uses: peter-evans/create-issue-from-file@v3
with:
title: '[DOCS] Broken Links found in Documentation'
content-filepath: .github/broken-link-report.md
labels: '📕 Docs, 👩‍💻 Good First Issue, 💤 Low Priority, 🤖 Auto'

View File

@ -0,0 +1,43 @@
name: 📕 Check Docs Domain Expiry
on:
workflow_dispatch:
schedule:
- cron: '0 1 * * 0' # At 01:00 on Sunday.
jobs:
check-domain:
runs-on: ubuntu-latest
name: Check domain
strategy:
matrix:
domain:
- https://dashy.to
steps:
- name: Check domain SSL and registry expire date
id: check-domain
uses: codex-team/action-check-domain@v1
with:
url: ${{ matrix.domain }}
- name: Raise issue if domain expiring soon
if: ${{ steps.check-domain.outputs.paid-till-days-left && steps.check-domain.outputs.paid-till-days-left < 30 }}
uses: rishabhgupta/git-action-issue@v2
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
assignees: Lissy93
title: '[WEBSITE] Domain Expiring Soon'
body: >
**Priority Notice**
Domain, ${{ matrix.domain }} will expire in ${{ steps.check-domain.outputs.paid-till-days-left }} days.
@Lissy93 - Please take action immediately to prevent any downtime
- name: Raise issue if SSL Cert expiring soon
if: ${{ steps.check-domain.outputs.ssl-expire-days-left && steps.check-domain.outputs.ssl-expire-days-left < 14 }}
uses: rishabhgupta/git-action-issue@v2
with:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
assignees: Lissy93
title: '[WEBSITE] SSL Cert Expiring Soon'
body: >
**Priority Notice**
The SSL Certificate for ${{ matrix.domain }} will expire in ${{ ssl-expire-days-left }} days, on ${{ steps.check-domain.outputs.ssl-expire-date }}.
@Lissy93 - Please take action immediately to prevent any downtime

View File

@ -65,4 +65,18 @@ jobs:
committer_username: liss-bot
committer_email: liss-bot@d0h.co
make-author-list:
runs-on: ubuntu-latest
steps:
- name: Checkout 🛎️
uses: actions/checkout@v2
with:
fetch-depth: 0
- uses: wow-actions/update-authors@v1
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
sort: commits
bots: true
path: .github/AUTHORS.txt
commit: ':blue_heart: Makes author list'
template: '{{name}} <{{email}}> - {{commits}} commits'

View File

@ -1,5 +1,5 @@
# Adds a comment to new PRs, showing the compressed size and size difference of new code
# And labels the PR based on the number of lines changes
# And also labels the PR based on the number of lines changes
name: 🌈 Check PR Size
on: [pull_request]
jobs:
@ -13,18 +13,26 @@ jobs:
uses: preactjs/compressed-size-action@v2
with:
repo-token: ${{ secrets.BOT_GITHUB_TOKEN }}
pattern: "./dist/**/**"
pattern: './dist/**/*.{js,css,html}'
strip-hash: '[-|.](\w{32}|\w{8}).'
exclude: '{./dist/manifest.json,**/*.map,**/node_modules/**}'
minimum-change-threshold: 100
# Check number of lines of code added
- name: Label based on Lines of Code
uses: codelytv/pr-size-labeler@v1
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
xs_max_size: '10'
s_max_size: '100'
m_max_size: '500'
l_max_size: '1000'
fail_if_xl: 'false'
message_if_xl: >
It looks like this PR is very large (over 1000 lines).
Try to avoid addressing multiple issues in a single PR, and
in the future consider breaking large tasks down into smaller steps.
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
xs_max_size: '10'
s_max_size: '100'
m_max_size: '500'
l_max_size: '1000'
s_label: '🟩 PR - Small'
m_label: '🟨 PR - Medium'
l_label: '🟧 PR - Large'
xl_label: '🟥 PR - XL'
fail_if_xl: 'false'
message_if_xl: >
It looks like this PR is very large (over 1000 lines).
Try to avoid addressing multiple issues in a single PR, and
in the future consider breaking large tasks down into smaller steps.
This it to make reviewing, testing, reverting and general quality management easier.

View File

@ -1,12 +1,12 @@
# Will add a comment and close new issues opened by users that may be spam, or have not starred
# Is still a work in progress, will also detect if user has previous activity in repo and check when joined GH
# Will add a comment and close any new issues opened by
# users who have not yet committed to, or starred the repo
name: 🎯 Issue Spam Control
on:
issues:
types: [opened, reopened]
jobs:
check:
if: ${{ ! contains( github.event.issue.labels.*.name, 'keep-open') }}
check-user:
if: ${{ ! contains( github.event.issue.labels.*.name, 'keep-open') && github.event.comment.author_association != 'CONTRIBUTOR' }}
runs-on: ubuntu-latest
name: Close issue opened by non-stargazer
steps:

View File

@ -2,8 +2,6 @@
# In order to allow their request can be prioritized
name: 🎯 Label sponsors
on:
pull_request:
types: [opened]
issues:
types: [opened]
jobs:

12
.github/workflows/pr-labler.yml vendored Normal file
View File

@ -0,0 +1,12 @@
# Labels pull requests based on their branch name
name: 💡 PR Branch Labeler
on: pull_request
jobs:
label-pr:
runs-on: ubuntu-latest
steps:
- name: Label PR
if: github.event.action == 'opened'
uses: ffittschen/pr-branch-labeler@v1
with:
repo-token: ${{ secrets.BOT_GITHUB_TOKEN }}

View File

@ -1,13 +1,13 @@
# When a new comment is added to an issue, if it had the Stale or Awaiting User Response labels,
# then those labels will be removed, providing it was not user lissy93 who added the commend.
name: 🎯 Remove Stale Label on Update
name: 🎯 Add/ Remove Awaiting Response Labels
on:
issue_comment:
types: [created]
jobs:
remove-stale:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.name != 'liss-bot' }}
if: ${{ github.event.inputs.name != 'liss-bot' && github.event.inputs.name != 'lissy93' }}
steps:
- name: Remove Stale labels when Updated
uses: actions-cool/issues-helper@v2
@ -16,3 +16,27 @@ jobs:
token: ${{ secrets.BOT_GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: '🚏 Awaiting User Response,⚰️ Stale'
add-awaiting-author:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.name != 'liss-bot' && github.event.inputs.name != 'lissy93' }}
steps:
- name: Add Awaiting Author labels when Updated
uses: actions-cool/issues-helper@v2
with:
actions: add-labels
token: ${{ secrets.BOT_GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: '👤 Awaiting Maintainer Response'
remove-awaiting-author:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.name == 'lissy93' }}
steps:
- name: Remove Awaiting Author labels when Updated
uses: actions-cool/issues-helper@v2
with:
actions: remove-labels
token: ${{ secrets.BOT_GITHUB_TOKEN }}
issue-number: ${{ github.event.issue.number }}
labels: '👤 Awaiting Maintainer Response'

View File

@ -1,16 +1,17 @@
# Generates diagram showing file breakdown
name: 📊 Generate Repo Visualization
# Generates series of diagrams and visualizations
name: 📊 Generate Repo Stats
on:
workflow_dispatch: # Manual dispatch
schedule:
- cron: '0 1 * * 0' # At 01:00 on Sunday.
jobs:
build:
# File structure chart
file-structure:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Generate File Structure Diagram
uses: githubocto/repo-visualizer@0.7.1
with:
@ -19,7 +20,28 @@ jobs:
excluded_paths: dist,node_modules
commit_message: ':yellow_heart: Updates repo diagram'
branch: master
# Hercules git branching stats
git-stats:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@master
with:
fetch-depth: 0
- name: Hercules
uses: src-d/hercules@master
- uses: actions/upload-artifact@master
with:
name: hercules_charts
path: hercules_charts.tar
# Lowlighter metrics community metrics
community-stats:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@master
- name: Generate Repo Metrics
uses: lowlighter/metrics@latest
with:
@ -32,7 +54,7 @@ jobs:
user: Lissy93
repo: dashy
delay: 5
- name: Generate License Metrics
uses: lowlighter/metrics@latest
with:
@ -67,5 +89,4 @@ jobs:
plugin_contributors_ignored: bot
plugin_contributors_contributions: yes
plugin_contributors_sections: contributors

View File

@ -0,0 +1,15 @@
name: 📊 Save Repo Analytics
on:
workflow_dispatch:
schedule:
- cron: '0 1 * * 0' # At 01:00 on Sunday.
jobs:
gen-stats:
runs-on: ubuntu-latest
steps:
- name: Repo Analytics
uses: jgehrcke/github-repo-stats@HEAD
with:
repository: lissy93/dashy
databranch: DATA/repo-stats
ghtoken: ${{ secrets.BOT_GITHUB_TOKEN }}

17
.github/workflows/unfurl-links.yml vendored Normal file
View File

@ -0,0 +1,17 @@
# Expands any raw pasted link in comments. Useful so people know what they're clicking
name: 🎯 Unfurl Links
on:
issues:
types: [opened, edited]
issue_comment:
types: [created, edited]
pull_request:
types: [opened, edited]
jobs:
run:
runs-on: ubuntu-latest
steps:
- uses: wow-actions/unfurl-links@v1
with:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
raw: true

View File

@ -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.
<p align="center">
<b>Example of Workspace View</b><br>

View File

@ -82,8 +82,9 @@ Tips:
**`fontAwesomeKey`** | `string` | _Optional_ | If you have a font-awesome key, then you can use it here and make use of premium icons. It is a 10-digit alpha-numeric string from you're FA kit URL (e.g. `13014ae648`)
**`faviconApi`** | `enum` | _Optional_ | Only applicable if you are using favicons for item icons. Specifies which service to use to resolve favicons. Set to `local` to do this locally, without using an API. Services running locally will use this option always. Available options are: `local`, `faviconkit`, `google`, `clearbit`, `webmasterapi` and `allesedv`. Defaults to `faviconkit`. See [Icons](/docs/icons.md#favicons) for more info
**`auth`** | `object` | _Optional_ | All settings relating to user authentication. See [`auth`](#appconfigauth-optional)
**`layout`** | `enum` | _Optional_ | App layout, either `horizontal`, `vertical`, `auto` or `sidebar`. Defaults to `auto`. This specifies the layout and direction of how sections are positioned on the home screen. This can also be modified from the UI.
**`layout`** | `enum` | _Optional_ | Layout for homepage, either `horizontal`, `vertical` or `auto`. Defaults to `auto`. This specifies the layout and direction of how sections are positioned on the home screen. This can also be modified and overridden from the UI.
**`iconSize`** | `enum` | _Optional_ | The size of link items / icons. Can be either `small`, `medium,` or `large`. Defaults to `medium`. This can also be set directly from the UI.
**`colCount`** | `number` | _Optional_ | The number of columns of sections displayed on the homepage, using the default view. Should be in integer between `1` and `8`. Note that by default this is applied responsively, based on current screen size, and specifying a value here will override this behavior, which may not be desirable.
**`theme`** | `string` | _Optional_ | The default theme for first load (you can change this later from the UI)
**`cssThemes`** | `string[]` | _Optional_ | An array of custom theme names which can be used in the theme switcher dropdown
**`customColors`** | `object`| _Optional_ | Enables you to apply a custom color palette to any given theme. Use the theme name (lowercase) as the key, for an object including key-value-pairs, with the color variable name as keys, and 6-digit hex code as value. See [Theming](/docs/theming.md#modifying-theme-colors) for more info
@ -91,6 +92,7 @@ Tips:
**`customCss`** | `string` | _Optional_ | Raw CSS that will be applied to the page. This can also be set from the UI. Please minify it first.
**`hideComponents`** | `object` | _Optional_ | A list of key page components (header, footer, search, settings, etc) that are present by default, but can be removed using this option. See [`appConfig.hideComponents`](#appconfighideComponents-optional)
**`enableMultiTasking`** | `boolean` | _Optional_ | If set to true, will keep apps open in the background when in the workspace view. Useful for quickly switching between multiple sites, and preserving their state, but comes at the cost of performance.
**`workspaceLandingUrl`** | `string` | _Optional_ | The URL or an app, service or website to launch when the workspace view is opened, before another service has been launched
**`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.

View File

@ -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`.
<p align="center">
<img width="400" src="https://i.ibb.co/qrNNNcm/generative-icons.png" />
<img width="500" src="https://i.ibb.co/b2pC2CL/generative-icons-2.png" />
</p>
---
@ -94,6 +94,32 @@ 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
```
<p align="center">
<img width="580" src="https://i.ibb.co/PQzYHmD/homelab-icons-2.png" />
</p>
---
## 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 +153,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.

View File

@ -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).

View File

@ -1,6 +1,6 @@
{
"name": "Dashy",
"version": "1.8.4",
"version": "1.8.5",
"license": "MIT",
"main": "server",
"scripts": {

View File

@ -4,7 +4,7 @@
<div class="css-wrapper">
<h2 class="css-input-title">Custom CSS</h2>
<textarea class="css-editor" v-model="customCss" />
<button class="save-button" @click="save()">{{ $t('config.css-save-btn') }}</button>
<Button class="save-button" :click="save">{{ $t('config.css-save-btn') }}</button>
<p class="quick-note">
<b>{{ $t('config.css-note-label') }}:</b>
{{ $t('config.css-note-l1') }} {{ $t('config.css-note-l2') }} {{ $t('config.css-note-l3') }}
@ -19,6 +19,7 @@
<script>
import CustomThemeMaker from '@/components/Settings/CustomThemeMaker';
import Button from '@/components/FormElements/Button';
import { getTheme } from '@/utils/ConfigHelpers';
import { localStorageKeys } from '@/utils/defaults';
import { InfoHandler } from '@/utils/ErrorHandler';
@ -29,6 +30,7 @@ export default {
config: Object,
},
components: {
Button,
CustomThemeMaker,
},
data() {
@ -38,26 +40,21 @@ export default {
};
},
methods: {
/* A regex to validate the users CSS */
validate(css) {
return css === '' || css.match(/([#.@]?[\w.:> ]+)[\s]{[\r\n]?([A-Za-z\- \r\n\t]+[:][\s]*[\w ./()\-!]+;[\r\n]*(?:[A-Za-z\- \r\n\t]+[:][\s]*[\w ./()\-!]+;[\r\n]*(2)*)*)}/gmi);
},
/* Save custom CSS in browser, call inject, and show success message */
save() {
let msg = '';
if (this.validate(this.customCss)) {
const appConfig = { ...this.config.appConfig };
appConfig.customCss = this.customCss;
localStorage.setItem(localStorageKeys.APP_CONFIG, JSON.stringify(appConfig));
msg = 'Changes saved successfully';
InfoHandler('User syles has been saved', 'Custom CSS Update');
this.inject(this.customCss);
if (this.customCss === '') setTimeout(() => { location.reload(); }, 1500); // eslint-disable-line no-restricted-globals
} else {
msg = 'Error - Invalid CSS';
InfoHandler(msg, 'Custom CSS Update');
}
this.$toasted.show(msg);
// Get, and sanitize users CSS
const css = this.customCss.replace(/<\/?[^>]+(>|$)/g, '');
// Update app config, and apply settings locally
const appConfig = { ...this.config.appConfig };
appConfig.customCss = css;
localStorage.setItem(localStorageKeys.APP_CONFIG, JSON.stringify(appConfig));
// Immidiatley inject new CSS
this.inject(css);
// If reseting styles, then refresh the page
if (css === '') setTimeout(() => { location.reload(); }, 1500); // eslint-disable-line no-restricted-globals
// Show status message
InfoHandler('User syles has been saved', 'Custom CSS Update');
this.$toasted.show('Changes saved successfully');
},
/* Formats CSS, and applies it to page */
inject(userStyles) {
@ -72,6 +69,7 @@ export default {
<style lang="scss">
// Main layout
div.css-editor-outer {
text-align: center;
padding-bottom: 1rem;
@ -87,22 +85,19 @@ div.css-editor-outer {
}
}
button.save-button {
padding: 0.5rem 1rem;
margin: 0.25rem auto;
font-size: 1.2rem;
// Save button
button.save-button{
background: var(--config-settings-color);
color: var(--config-settings-background);
border: 1px solid var(--config-settings-background);
border-radius: var(--curve-factor);
cursor: pointer;
&:hover {
&:hover:not(:disabled) {
background: var(--config-settings-background);
color: var(--config-settings-color);
border-color: var(--config-settings-color);
}
}
// CSS textarea input
.css-editor {
margin: 1rem auto;
padding: 0.5rem;
@ -121,6 +116,7 @@ button.save-button {
}
}
// Info note
p.quick-note {
text-align: left;
width: 80%;
@ -131,6 +127,7 @@ p.quick-note {
border-radius: var(--curve-factor);
}
// Theme editor
.color-config.theme-configurator-wrapper {
border: 1px solid var(--config-settings-color);
background: var(--config-settings-background);

View File

@ -1,5 +1,5 @@
<template ref="container">
<div class="item-wrapper">
<div :class="`item-wrapper wrap-size-${itemSize}`">
<a @click="itemOpened"
@mouseup.right="openContextMenu"
@contextmenu.prevent
@ -144,7 +144,7 @@ export default {
html: true,
placement: this.statusResponse ? 'left' : 'auto',
delay: { show: 600, hide: 200 },
classes: 'item-description-tooltip',
classes: `item-description-tooltip tooltip-is-${this.itemSize}`,
};
},
/* Used by certain themes, which display an icon with animated CSS */
@ -239,6 +239,10 @@ export default {
.item-wrapper {
flex-grow: 1;
flex-basis: 6rem;
&.wrap-size-large {
flex-basis: 12rem;
}
}
.item {
@ -392,11 +396,13 @@ export default {
width: 100%;
}
p.description {
display: block;
margin: 0;
display: block;
white-space: pre-wrap;
font-size: .9em;
text-overflow: ellipsis;
font-size: .9em;
line-height: 1rem;
height: 2rem;
}
}
}

View File

@ -1,5 +1,5 @@
<template>
<div class="item-icon">
<div :class="`item-icon wrapper-${size}`">
<!-- Font-Awesome Icon -->
<i v-if="iconType === 'font-awesome'" :class="`${icon} ${size}`" ></i>
<!-- Emoji Icon -->
@ -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) {
@ -135,6 +137,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.replace('{icon}', imageName);
},
/* Checks if the icon is from a local image, remote URL, SVG or font-awesome */
getIconPath(img, url) {
switch (this.determineImageType(img)) {
@ -145,6 +152,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 +167,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';
@ -180,6 +189,18 @@ export default {
</script>
<style lang="scss">
/* Icon wraper */
.item-icon {
&.wrapper-medium {
min-height: 2.5rem;
}
&.wrapper-large {
min-width: 3.5rem;
text-align: center;
}
}
/* Default Image Icon */
.tile-icon {
min-width: 1rem;

View File

@ -178,9 +178,9 @@ export default {
padding: 0.8rem;
text-align: center;
cursor: default;
border-radius: var(--curve-factor);
background: #607d8b33;
color: var(--primary);
background: var(--item-background);
border-radius: var(--curve-factor);
box-shadow: var(--item-shadow);
}
@ -192,12 +192,13 @@ export default {
display: grid;
overflow: auto;
@extend .scroll-bar;
@include phone { grid-template-columns: repeat(1, 1fr); }
@include tablet { grid-template-columns: repeat(2, 1fr); }
@include laptop { grid-template-columns: repeat(2, 1fr); }
@include monitor { grid-template-columns: repeat(3, 1fr); }
@include big-screen { grid-template-columns: repeat(4, 1fr); }
@include big-screen-up { grid-template-columns: repeat(5, 1fr); }
@include phone { --item-col-count: 1; }
@include tablet { --item-col-count: 2; }
@include laptop { --item-col-count: 2; }
@include monitor { --item-col-count: 3; }
@include big-screen { --item-col-count: 4; }
@include big-screen-up { --item-col-count: 5; }
grid-template-columns: repeat(var(--item-col-count, 2), minmax(0, 1fr));
}
}
.orientation-horizontal {
@ -205,13 +206,13 @@ export default {
flex-direction: column;
.there-are-items {
display: grid;
grid-template-columns: repeat(5, 1fr);
@include phone { grid-template-columns: repeat(2, 1fr); }
@include tablet { grid-template-columns: repeat(4, 1fr); }
@include laptop { grid-template-columns: repeat(6, 1fr); }
@include monitor { grid-template-columns: repeat(8, 1fr); }
@include big-screen { grid-template-columns: repeat(10, 1fr); }
@include big-screen-up { grid-template-columns: repeat(12, 1fr); }
@include phone { --item-col-count: 2; }
@include tablet { --item-col-count: 4; }
@include laptop { --item-col-count: 6; }
@include monitor { --item-col-count: 8; }
@include big-screen { --item-col-count: 10; }
@include big-screen-up { --item-col-count: 12; }
grid-template-columns: repeat(var(--item-col-count, 2), minmax(0, 1fr));
}
}

View File

@ -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));
},
launchApp(url) {
this.$emit('launch-app', url);
/* When item clicked, emit a launch event */
launchApp(options) {
this.$emit('launch-app', options);
},
/* 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();
},
};
</script>

View File

@ -17,6 +17,7 @@ export default {
icon: String,
title: String,
url: String,
target: String,
click: Function,
},
components: {
@ -24,7 +25,7 @@ export default {
},
methods: {
itemClicked() {
if (this.url) this.$emit('launch-app', this.url);
if (this.url) this.$emit('launch-app', { url: this.url, target: this.target });
},
},
data() {

View File

@ -6,6 +6,7 @@
:icon="item.icon"
:title="item.title"
:url="item.url"
:target="item.target"
@launch-app="launchApp"
/>
</div>
@ -26,8 +27,8 @@ export default {
SideBarItem,
},
methods: {
launchApp(url) {
this.$emit('launch-app', url);
launchApp(options) {
this.$emit('launch-app', options);
},
},
};

View File

@ -406,7 +406,7 @@ html[data-theme='material'], html[data-theme='material-dark'] {
}
}
}
.tooltip.item-description-tooltip {
.tooltip.item-description-tooltip:not(.tooltip-is-small) {
display: none !important;
}
.orientation-horizontal {

View File

@ -145,6 +145,12 @@
"default": "medium",
"description": "The size of each link item / icon"
},
"colCount": {
"type": "number",
"minimum": 1,
"maximum": 8,
"description": "Number of section columns for homepage. Leave blank for column count to be responsively calculated based on screen size"
},
"hideComponents": {
"type": "object",
"description": "Hide individual parts of the page. If not set, all components are visible by default",
@ -385,6 +391,10 @@
"sentryDsn": {
"type": "string",
"description": "The DSN to your self-hosted Sentry server, if you need to collect bug reports. Only used if enableErrorReporting is enabled"
},
"workspaceLandingUrl": {
"type": "string",
"description": "The URL of an app, service or website to render when the Workspace view is opened"
}
},
"additionalProperties": false

View File

@ -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);
};

View File

@ -173,9 +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://raw.githubusercontent.com/WalkxCode/dashboard-icons/master/png/{icon}.png',
},
/* URLs for web search engines */
searchEngineUrls: {

View File

@ -18,7 +18,11 @@
/>
<!-- Main content, section for each group of items -->
<div v-if="checkTheresData(sections)"
:class="`item-group-container orientation-${layout} item-size-${itemSizeBound}`">
:class="`item-group-container `
+ `orientation-${layout} `
+ `item-size-${itemSizeBound} `
+ (this.colCount ? `col-count-${this.colCount} ` : '')"
>
<Section
v-for="(section, index) in filteredTiles"
:key="index"
@ -67,6 +71,14 @@ export default {
modalOpen: false, // When true, keybindings are disabled
}),
computed: {
/* Get class for num columns, if specified by user */
colCount() {
let { colCount } = this.appConfig;
if (!colCount) return null;
if (colCount < 1) colCount = 1;
if (colCount > 8) colCount = 8;
return colCount;
},
/* Combines sections from config file, with those in local storage */
allSections() {
// If the user has stored sections in local storage, return those
@ -227,7 +239,6 @@ export default {
.home {
padding-bottom: 1px;
background: var(--background);
// min-height: calc(100vh - 126px);
min-height: calc(99.9vh - var(--footer-height));
}
@ -256,26 +267,27 @@ export default {
}
}
/* Specify number of columns, based on screen size */
@include phone {
grid-template-columns: repeat(1, 1fr);
}
@include tablet {
grid-template-columns: repeat(2, 1fr);
}
@include laptop {
grid-template-columns: repeat(2, 1fr);
}
@include monitor {
grid-template-columns: repeat(3, 1fr);
}
@include big-screen {
grid-template-columns: repeat(4, 1fr);
}
@include big-screen-up {
grid-template-columns: repeat(5, 1fr);
/* Specify number of columns, based on screen size or user preference */
@include phone { --col-count: 1; }
@include tablet { --col-count: 2; }
@include laptop { --col-count: 2; }
@include monitor { --col-count: 3; }
@include big-screen { --col-count: 4; }
@include big-screen-up { --col-count: 5; }
@include tablet-up {
&.col-count-1 { --col-count: 1; }
&.col-count-2 { --col-count: 2; }
&.col-count-3 { --col-count: 3; }
&.col-count-4 { --col-count: 4; }
&.col-count-5 { --col-count: 5; }
&.col-count-6 { --col-count: 6; }
&.col-count-7 { --col-count: 7; }
&.col-count-8 { --col-count: 8; }
}
grid-template-columns: repeat(var(--col-count, 2), minmax(0, 1fr));
/* Hide when search term returns nothing */
.no-results { display: none; }
}

View File

@ -1,6 +1,6 @@
<template>
<div class="work-space">
<SideBar :sections="sections" @launch-app="launchApp" />
<SideBar :sections="sections" @launch-app="launchApp" :initUrl="getInitialUrl()" />
<WebContent :url="url" v-if="!isMultiTaskingEnabled" />
<MultiTaskingWebComtent :url="url" v-else />
</div>
@ -21,7 +21,7 @@ export default {
appConfig: Object,
},
data: () => ({
url: '', // this.$route.query.url || '',
url: '',
GetTheme,
ApplyLocalTheme,
ApplyCustomVariables,
@ -37,8 +37,12 @@ export default {
MultiTaskingWebComtent,
},
methods: {
launchApp(url) {
this.url = url;
launchApp(options) {
if (options.target === 'newtab') {
window.open(options.url, '_blank');
} else {
this.url = options.url;
}
},
setTheme() {
const theme = this.GetTheme();
@ -51,16 +55,21 @@ export default {
fontAwesomeScript.setAttribute('src', `https://kit.fontawesome.com/${faKey}.js`);
document.head.appendChild(fontAwesomeScript);
},
repositionFooter() {
document.getElementsByTagName('footer')[0].style.position = 'fixed';
/* Returns a service URL, if set as a URL param, or if user has specified landing URL */
getInitialUrl() {
const route = this.$route;
if (route.query && route.query.url) {
return decodeURI(route.query.url);
} else if (this.appConfig.workspaceLandingUrl) {
return this.appConfig.workspaceLandingUrl;
}
return undefined;
},
},
mounted() {
const route = this.$route;
if (route.query && route.query.url) this.url = decodeURI(route.query.url);
this.setTheme();
this.initiateFontAwesome();
// this.repositionFooter();
this.url = this.getInitialUrl();
},
};