Merge branch 'master' of github.com:Lissy93/dashy into FEATURE/more-widgets

This commit is contained in:
Alicia Sykes 2022-01-20 16:37:01 +00:00
commit 7953ccf3c8
30 changed files with 727 additions and 368 deletions

22
.github/AUTHORS.txt vendored
View File

@ -1,37 +1,41 @@
Alicia <liss-bot@d0h.co> - 1 commits Alicia <liss-bot@d0h.co> - 1 commits
BOZG <sr@bozg.se> - 1 commits
Begin <support@begin.com> - 1 commits Begin <support@begin.com> - 1 commits
David <skaarj1989@gmail.com> - 1 commits David <skaarj1989@gmail.com> - 1 commits
DeepSource <o> - 1 commits DeepSource <o> - 1 commits
Devin <uh> - 1 commits Devin <uh> - 1 commits
FormatToday <616099456@qq.com> - 1 commits FormatToday <616099456@qq.com> - 1 commits
Iaroslav <ronski> - 1 commits Iaroslav <ronski> - 1 commits
Kieren <onnel> - 1 commits
Rune <jørnerå> - 1 commits Rune <jørnerå> - 1 commits
Ryan <urne> - 1 commits Ryan <urne> - 1 commits
Shreya <o> - 1 commits Shreya <o> - 1 commits
Xert <xertdev@gmail.com> - 1 commits
deepsource-io[bot] <deepsource-io[bot]@users.noreply.github.com> - 1 commits deepsource-io[bot] <deepsource-io[bot]@users.noreply.github.com> - 1 commits
jnach <33467747+jnach@users.noreply.github.com> - 1 commits jnach <33467747+jnach@users.noreply.github.com> - 1 commits
BOZG <sr@bozg.se> - 2 commits
Brendan <&#39;Lear> - 2 commits Brendan <&#39;Lear> - 2 commits
Dan <ilber> - 2 commits Dan <ilber> - 2 commits
liss-bot <87835202+liss-bot@users.noreply.github.com> - 2 commits liss-bot <87835202+liss-bot@users.noreply.github.com> - 2 commits
ᗪєνιη <υн> - 2 commits ᗪєνιη <υн> - 2 commits
Walkx <71191962+walkxcode@users.noreply.github.com> - 3 commits Walkx <71191962+walkxcode@users.noreply.github.com> - 3 commits
Niklas <abe> - 4 commits Niklas <abe> - 4 commits
Alicie <gh@d0h.co> - 5 commits
UrekD <urek.denis@gmail.com> - 5 commits UrekD <urek.denis@gmail.com> - 5 commits
Erik <roo> - 6 commits Erik <roo> - 6 commits
Leonardo <ovarrubia> - 6 commits
liss-bot <liss-bot@users.noreply.github.com> - 6 commits liss-bot <liss-bot@users.noreply.github.com> - 6 commits
Kashif <ohai> - 9 commits Kashif <ohai> - 9 commits
Alicia <yke> - 10 commits Alicia <yke> - 16 commits
github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> - 16 commits github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> - 16 commits
repo-visualizer <repo-visualizer@users.noreply.github.com> - 17 commits
snyk-bot <snyk-bot@users.noreply.github.com> - 18 commits snyk-bot <snyk-bot@users.noreply.github.com> - 18 commits
snyk-bot <snyk-bot@snyk.io> - 20 commits repo-visualizer <repo-visualizer@users.noreply.github.com> - 20 commits
EVOTk <45015615+EVOTk@users.noreply.github.com> - 22 commits EVOTk <45015615+EVOTk@users.noreply.github.com> - 22 commits
Alicia <yke> - 23 commits snyk-bot <snyk-bot@snyk.io> - 24 commits
Alicia <yke> - 28 commits Alicia <yke> - 28 commits
Alicia <o> - 34 commits Alicia <o> - 39 commits
liss-bot <liss-bot@d0h.co> - 46 commits liss-bot <liss-bot@d0h.co> - 54 commits
Alicia <yke> - 60 commits
Lissy93 <gh@d0h.co> - 78 commits Lissy93 <gh@d0h.co> - 78 commits
Lissy93 <Lissy93@users.noreply.github.com> - 202 commits Lissy93 <Lissy93@users.noreply.github.com> - 202 commits
Alicia <yke> - 304 commits Alicia <yke> - 314 commits
Alicia <yke> - 1249 commits Alicia <yke> - 1270 commits

View File

@ -1,5 +1,10 @@
# Changelog # Changelog
## 🐛 1.9.7 - Minor UI Editor Bug fixes [PR #416](https://github.com/Lissy93/dashy/pull/416)
- Fixes unable to edit item bug (#415)
- Fixes unable to add new app bug (#390)
- Fixes nav links visibility (#389)
## ⚡️ 1.9.6 - Adds Proxy Support for Widget Requests [PR #392](https://github.com/Lissy93/dashy/pull/392) ## ⚡️ 1.9.6 - Adds Proxy Support for Widget Requests [PR #392](https://github.com/Lissy93/dashy/pull/392)
- Refactors widget mixin to include data requests, so that code can be shared between widgets - Refactors widget mixin to include data requests, so that code can be shared between widgets
- Adds a Node endpoint for proxying requests server-side, used for APIs that are not CORS enabled - Adds a Node endpoint for proxying requests server-side, used for APIs that are not CORS enabled

View File

@ -328,7 +328,7 @@ For apps that you use regularly, you can set a custom keybinding. Use the `hotke
You can also add custom tags to a given item to make finding them based on keywords easier. For example, in the following example, searching for 'Movies' will show 'Plex' You can also add custom tags to a given item to make finding them based on keywords easier. For example, in the following example, searching for 'Movies' will show 'Plex'
"`yaml ```yaml
items: items:
- title: Plex - title: Plex
hotkey: 8 hotkey: 8
@ -501,6 +501,20 @@ Huge thanks to the sponsors helping to support Dashy's development!
<br /> <br />
<sub><b>Vlad Timofeev</b></sub> <sub><b>Vlad Timofeev</b></sub>
</a> </a>
</td>
<td align="center">
<a href="https://github.com/KierenConnell">
<img src="https://avatars.githubusercontent.com/u/46445781?u=5502f8fb780938e2825735d7bbb9236642d212c0&v=4" width="80;" alt="KierenConnell"/>
<br />
<sub><b>Kieren Connell</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ratty222">
<img src="https://avatars.githubusercontent.com/u/92832598?v=4" width="80;" alt="ratty222"/>
<br />
<sub><b>Ratty222</b></sub>
</a>
</td></tr> </td></tr>
</table> </table>
<!-- readme: sponsors -end --> <!-- readme: sponsors -end -->

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 79 KiB

View File

@ -143,9 +143,25 @@ appConfig:
realm: 'alicia-homelab' realm: 'alicia-homelab'
clientId: 'dashy' clientId: 'dashy'
``` ```
### 4. Add groups and roles (Optional)
Keycloak allows you to assign users roles and groups. You can use these values to configure who can access various sections in Dashy.
Keycloak server administration and configuration is a deep topic; please refer to the [server admin guide](https://www.keycloak.org/docs/latest/server_admin/index.html#assigning-permissions-and-access-using-roles-and-groups) to see details about creating and assigning roles and groups.
Once you have groups or roles assigned to users you can configure access under each sections `displayData.showForKeycloakUser` and `displayData.hideForKeycloakUser`.
Both show and hide configurations accept a list of `groups` and `roles` that limit access. If a users data matches one or more items in these lists they will be allowed or excluded as defined.
```yaml
sections:
- name: DeveloperResources
displayData:
showForKeycloakUsers:
roles: ['canViewDevResources']
hideForKeycloakUsers:
groups: ['ProductTeam']
```
Your app is now secured :) When you load Dashy, it will redirect to your Keycloak login page, and any user without valid credentials will be prevented from accessing your dashboard. Your app is now secured :) When you load Dashy, it will redirect to your Keycloak login page, and any user without valid credentials will be prevented from accessing your dashboard.
From within the Keycloak console, you can then configure things like user permissions, time outs, password policies, access, etc. You can also backup your full Keycloak config, and it is recommended to do this, along with your Dashy config. You can spin up both Dashy and Keycloak simultaneously and restore both applications configs using a `docker-compose.yml` file, and this is recommended. From within the Keycloak console, you can then configure things like time-outs, password policies, etc. You can also backup your full Keycloak config, and it is recommended to do this, along with your Dashy config. You can spin up both Dashy and Keycloak simultaneously and restore both applications configs using a `docker-compose.yml` file, and this is recommended.
--- ---

View File

@ -77,7 +77,7 @@ Tips:
--- | --- | --- | --- --- | --- | --- | ---
**`language`** | `string` | _Optional_ | The 2 (or 4-digit) [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language, e.g. `en` or `en-GB`. This must be a language that the app has already been [translated](https://github.com/Lissy93/dashy/tree/master/src/assets/locales) into. If your language is unavailable, Dashy will fallback to English. By default Dashy will attempt to auto-detect your language, although this may not work on some privacy browsers. **`language`** | `string` | _Optional_ | The 2 (or 4-digit) [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language, e.g. `en` or `en-GB`. This must be a language that the app has already been [translated](https://github.com/Lissy93/dashy/tree/master/src/assets/locales) into. If your language is unavailable, Dashy will fallback to English. By default Dashy will attempt to auto-detect your language, although this may not work on some privacy browsers.
**`startingView`** | `enum` | _Optional_ | Which page to load by default, and on the base page or domain root. You can still switch to different views from within the UI. Can be either `default`, `minimal` or `workspace`. Defaults to `default` **`startingView`** | `enum` | _Optional_ | Which page to load by default, and on the base page or domain root. You can still switch to different views from within the UI. Can be either `default`, `minimal` or `workspace`. Defaults to `default`
**`defaultOpeningMethod`** | `enum` | _Optional_ | The default opening method for items, if no `target` is specified for a given item. Can be either `newtab`, `sametab`, `top`, `parent`, `modal` or `workspace`. Defaults to `newtab` **`defaultOpeningMethod`** | `enum` | _Optional_ | The default opening method for items, if no `target` is specified for a given item. Can be either `newtab`, `sametab`, `modal`, `workspace`, `clipboard`, `top` or `parent`. Defaults to `newtab`
**`statusCheck`** | `boolean` | _Optional_ | When set to `true`, Dashy will ping each of your services and display their status as a dot next to each item. This can be overridden by setting `statusCheck` under each item. Defaults to `false` **`statusCheck`** | `boolean` | _Optional_ | When set to `true`, Dashy will ping each of your services and display their status as a dot next to each item. This can be overridden by setting `statusCheck` under each item. Defaults to `false`
**`statusCheckInterval`** | `boolean` | _Optional_ | The number of seconds between checks. If set to `0` then service will only be checked on initial page load, which is usually the desired functionality. If value is less than `10` you may experience a hit in performance. Defaults to `0` **`statusCheckInterval`** | `boolean` | _Optional_ | The number of seconds between checks. If set to `0` then service will only be checked on initial page load, which is usually the desired functionality. If value is less than `10` you may experience a hit in performance. Defaults to `0`
**`webSearch`** | `object` | _Optional_ | Configuration options for the web search feature, set your default search engine, opening method or disable web search. See [`webSearch`](#appconfigwebsearch-optional) **`webSearch`** | `object` | _Optional_ | Configuration options for the web search feature, set your default search engine, opening method or disable web search. See [`webSearch`](#appconfigwebsearch-optional)
@ -186,7 +186,7 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
**`description`** | `string` | _Optional_ | Additional info about an item, which is shown in the tooltip on hover, or visible on large tiles **`description`** | `string` | _Optional_ | Additional info about an item, which is shown in the tooltip on hover, or visible on large tiles
**`url`** | `string` | Required | The URL / location of web address for when the item is clicked **`url`** | `string` | Required | The URL / location of web address for when the item is clicked
**`icon`** | `string` | _Optional_ | The icon for a given item. Can be a font-awesome icon, favicon, remote URL or local URL. See [`item.icon`](#sectionicon-and-sectionitemicon) **`icon`** | `string` | _Optional_ | The icon for a given item. Can be a font-awesome icon, favicon, remote URL or local URL. See [`item.icon`](#sectionicon-and-sectionitemicon)
**`target`** | `string` | _Optional_ | The opening method for when the item is clicked, either `newtab`, `sametab`, `top`, `parent`, `modal` or `workspace`. Where `newtab` will open the link in a new tab, `sametab` will open it in the current tab, and `modal` will open a pop-up modal and `workspace` will open in the Workspace view. Defaults to `newtab` **`target`** | `string` | _Optional_ | The opening method for when the item is clicked, either `newtab`, `sametab`, `modal`, `workspace`, `clipboard`, `top` or `parent`. Where `newtab` will open the link in a new tab, `sametab` will open it in the current tab, and `modal` will open a pop-up modal, `workspace` will open in the Workspace view and `clipboard` will copy the URL to system clipboard (but not launch app). Defaults to `newtab`
**`hotkey`** | `number` | _Optional_ | Give frequently opened applications a numeric hotkey, between `0 - 9`. You can then just press that key to launch that application. **`hotkey`** | `number` | _Optional_ | Give frequently opened applications a numeric hotkey, between `0 - 9`. You can then just press that key to launch that application.
**`tags`** | `string[]` | _Optional_ | A list of tags, which can be used for improved search **`tags`** | `string[]` | _Optional_ | A list of tags, which can be used for improved search
**`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` **`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`
@ -228,6 +228,8 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
**`hideForUsers`** | `string[]` | _Optional_ | Current section will be visible to all users, except for those specified in this list **`hideForUsers`** | `string[]` | _Optional_ | Current section will be visible to all users, except for those specified in this list
**`showForUsers`** | `string[]` | _Optional_ | Current section will be hidden from all users, except for those specified in this list **`showForUsers`** | `string[]` | _Optional_ | Current section will be hidden from all users, except for those specified in this list
**`hideForGuests`** | `boolean` | _Optional_ | Current section will be visible for logged in users, but not for guests (see `appConfig.enableGuestAccess`). Defaults to `false` **`hideForGuests`** | `boolean` | _Optional_ | Current section will be visible for logged in users, but not for guests (see `appConfig.enableGuestAccess`). Defaults to `false`
**`hideForKeycloakUsers`** | `object` | _Optional_ | Current section will be visible to all keycloak users, except for those configured via these groups and roles. See `hideForKeycloakUsers`
**`showForKeycloakUsers`** | `object` | _Optional_ | Current section will be hidden from all keyclaok users, except for those configured via these groups and roles. See `showForKeycloakUsers`
**[⬆️ Back to Top](#configuring)** **[⬆️ Back to Top](#configuring)**
@ -239,6 +241,15 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
**[⬆️ Back to Top](#configuring)** **[⬆️ Back to Top](#configuring)**
### `section.displayData.hideForKeycloakUsers` and `section.displayData.showForKeycloakUsers`
**Field** | **Type** | **Required**| **Description**
--- |------------| --- | ---
**`groups`** | `string[]` | _Optional_ | Current Section will be hidden or shown based on the user having any of the groups in this list
**`roles`** | `string[]` | _Optional_ | Current Section will be hidden or shown based on the user having any of the roles in this list
**[⬆️ Back to Top](#configuring)**
--- ---
## Notes ## Notes

View File

@ -24,6 +24,20 @@
<br /> <br />
<sub><b>Vlad Timofeev</b></sub> <sub><b>Vlad Timofeev</b></sub>
</a> </a>
</td>
<td align="center">
<a href="https://github.com/KierenConnell">
<img src="https://avatars.githubusercontent.com/u/46445781?u=5502f8fb780938e2825735d7bbb9236642d212c0&v=4" width="80;" alt="KierenConnell"/>
<br />
<sub><b>Kieren Connell</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ratty222">
<img src="https://avatars.githubusercontent.com/u/92832598?v=4" width="80;" alt="ratty222"/>
<br />
<sub><b>Ratty222</b></sub>
</a>
</td></tr> </td></tr>
</table> </table>
<!-- readme: sponsors -end --> <!-- readme: sponsors -end -->
@ -89,13 +103,6 @@
<sub><b>ᗪєνιη ᗷυнʟ</b></sub> <sub><b>ᗪєνιη ᗷυнʟ</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/daentech">
<img src="https://avatars.githubusercontent.com/u/358678?v=4" width="80;" alt="daentech"/>
<br />
<sub><b>Dan Gilbert</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/BOZG"> <a href="https://github.com/BOZG">
<img src="https://avatars.githubusercontent.com/u/6022344?v=4" width="80;" alt="BOZG"/> <img src="https://avatars.githubusercontent.com/u/6022344?v=4" width="80;" alt="BOZG"/>
@ -103,6 +110,13 @@
<sub><b>Stephen Rigney</b></sub> <sub><b>Stephen Rigney</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/daentech">
<img src="https://avatars.githubusercontent.com/u/358678?v=4" width="80;" alt="daentech"/>
<br />
<sub><b>Dan Gilbert</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/BeginCI"> <a href="https://github.com/BeginCI">
<img src="https://avatars.githubusercontent.com/u/57495754?v=4" width="80;" alt="BeginCI"/> <img src="https://avatars.githubusercontent.com/u/57495754?v=4" width="80;" alt="BeginCI"/>
@ -139,6 +153,13 @@
<sub><b>Iaroslav Dronskii</b></sub> <sub><b>Iaroslav Dronskii</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/KierenConnell">
<img src="https://avatars.githubusercontent.com/u/46445781?v=4" width="80;" alt="KierenConnell"/>
<br />
<sub><b>Kieren Connell</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/rubjo"> <a href="https://github.com/rubjo">
<img src="https://avatars.githubusercontent.com/u/42270947?v=4" width="80;" alt="rubjo"/> <img src="https://avatars.githubusercontent.com/u/42270947?v=4" width="80;" alt="rubjo"/>
@ -152,15 +173,22 @@
<br /> <br />
<sub><b>Ryan Turner</b></sub> <sub><b>Ryan Turner</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/royshreyaaa"> <a href="https://github.com/royshreyaaa">
<img src="https://avatars.githubusercontent.com/u/88572557?v=4" width="80;" alt="royshreyaaa"/> <img src="https://avatars.githubusercontent.com/u/88572557?v=4" width="80;" alt="royshreyaaa"/>
<br /> <br />
<sub><b>Shreya Roy</b></sub> <sub><b>Shreya Roy</b></sub>
</a> </a>
</td></tr> </td>
<tr> <td align="center">
<a href="https://github.com/XertDev">
<img src="https://avatars.githubusercontent.com/u/16572811?v=4" width="80;" alt="XertDev"/>
<br />
<sub><b>Xert</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/jnach"> <a href="https://github.com/jnach">
<img src="https://avatars.githubusercontent.com/u/33467747?v=4" width="80;" alt="jnach"/> <img src="https://avatars.githubusercontent.com/u/33467747?v=4" width="80;" alt="jnach"/>

View File

@ -16,6 +16,8 @@ Once you've got Dashy up and running, you'll want to configure it with your own
- [Deploy with Docker](#deploy-with-docker) - [Deploy with Docker](#deploy-with-docker)
- [Using Docker Compose](#using-docker-compose) - [Using Docker Compose](#using-docker-compose)
- [Unraid](#unraid)
- [Synology NAS](#synology-nas)
- [Build from Source](#build-from-source) - [Build from Source](#build-from-source)
- [Hosting with CDN](#hosting-with-cdn) - [Hosting with CDN](#hosting-with-cdn)
- [Run as executable](#run-as-executable) - [Run as executable](#run-as-executable)
@ -23,7 +25,9 @@ Once you've got Dashy up and running, you'll want to configure it with your own
- [Deploy to cloud service](#deploy-to-cloud-service) - [Deploy to cloud service](#deploy-to-cloud-service)
- [Use managed instance](#use-managed-instance) - [Use managed instance](#use-managed-instance)
### Deploy with Docker ---
## Deploy with Docker
**Container Info**: [ **Container Info**: [
![Docker Supported Architecture](https://img.shields.io/badge/Architectures-amd64%20|%20arm32v7%20|%20arm64v8-6ba6e5) ![Docker Supported Architecture](https://img.shields.io/badge/Architectures-amd64%20|%20arm32v7%20|%20arm64v8-6ba6e5)
@ -66,7 +70,9 @@ If you're deploying Dashy on a modern ARM-based board, such as a Raspberry Pi (2
The image defaults to `:latest`, but you can instead specify a specific version, e.g. `docker pull lissy93/dashy:release-1.5.0` The image defaults to `:latest`, but you can instead specify a specific version, e.g. `docker pull lissy93/dashy:release-1.5.0`
### Using Docker Compose ---
## Using Docker Compose
Using Docker Compose can be useful for saving your specific config in files, without having to type out a long run command each time. Save compose config as a YAML file, and then run `docker compose up -d` (optionally use the `-f` flag to specify file location, if it isn't located at `./docker-compose.yml`), `-d` is detached mode (not running in the foreground of your terminal). Compose is also useful if you are using clusters, as the format is very similar to stack files, used with Docker Swarm. Using Docker Compose can be useful for saving your specific config in files, without having to type out a long run command each time. Save compose config as a YAML file, and then run `docker compose up -d` (optionally use the `-f` flag to specify file location, if it isn't located at `./docker-compose.yml`), `-d` is detached mode (not running in the foreground of your terminal). Compose is also useful if you are using clusters, as the format is very similar to stack files, used with Docker Swarm.
@ -106,7 +112,21 @@ You can use a different tag, by for example setting `image: lissy93/dashy:arm64v
If you are building from source, and would like to use one of the [other Dockerfiles](https://github.com/Lissy93/dashy/tree/master/docker), then under `services.dashy` first set `context: .`, then specify the the path to the dockerfile, e.g. `dockerfile: ./docker/Dockerfile-arm32v7` If you are building from source, and would like to use one of the [other Dockerfiles](https://github.com/Lissy93/dashy/tree/master/docker), then under `services.dashy` first set `context: .`, then specify the the path to the dockerfile, e.g. `dockerfile: ./docker/Dockerfile-arm32v7`
### Build from Source ---
## Unraid
// TODO
---
## Synology NAS
// TODO
---
## Build from Source
If you do not want to use Docker, you can run Dashy directly on your host system. For this, you will need both [git](https://git-scm.com/downloads) and the latest or LTS version of [Node.js](https://nodejs.org/) installed, and optionally [yarn](https://yarnpkg.com/) If you do not want to use Docker, you can run Dashy directly on your host system. For this, you will need both [git](https://git-scm.com/downloads) and the latest or LTS version of [Node.js](https://nodejs.org/) installed, and optionally [yarn](https://yarnpkg.com/)
@ -116,6 +136,8 @@ If you do not want to use Docker, you can run Dashy directly on your host system
4. Build: `yarn build` 4. Build: `yarn build`
5. Run: `yarn start` 5. Run: `yarn start`
---
### Deploy to cloud service ### Deploy to cloud service
If you don't have a home server, then fear not - Dashy can be deployed to pretty much any cloud provider. The above Docker and NPM guides will work exactly the same on a VPS, but I've also setup some 1-Click deploy links for 10+ of the most common cloud providers, to make things easier. Note that if your instance is exposed to the internet, it will be your responsibility to adequately secure it. If you don't have a home server, then fear not - Dashy can be deployed to pretty much any cloud provider. The above Docker and NPM guides will work exactly the same on a VPS, but I've also setup some 1-Click deploy links for 10+ of the most common cloud providers, to make things easier. Note that if your instance is exposed to the internet, it will be your responsibility to adequately secure it.
@ -236,13 +258,16 @@ yarn build
surge ./dist surge ./dist
``` ```
---
### Hosting with CDN ## Hosting with CDN
Once Dashy has been built, it is effectivley just a static web app. This means that it can be served up with pretty much any static host, CDN or web server. To host Dashy through a CDN, the steps are very similar to building from source: clone the project, cd into it, install dependencies, write your config file and build the app. Once build is complete you will have a `./dist` directory within Dashy's root, and this is the build application which is ready to be served up. Once Dashy has been built, it is effectivley just a static web app. This means that it can be served up with pretty much any static host, CDN or web server. To host Dashy through a CDN, the steps are very similar to building from source: clone the project, cd into it, install dependencies, write your config file and build the app. Once build is complete you will have a `./dist` directory within Dashy's root, and this is the build application which is ready to be served up.
However without Dashy's node server, there are a couple of features that will be unavailible to you, including: Writing config changes to disk through the UI, triggering a rebuild through the UI and application status checks. Everything else will work fine. However without Dashy's node server, there are a couple of features that will be unavailible to you, including: Writing config changes to disk through the UI, triggering a rebuild through the UI and application status checks. Everything else will work fine.
---
## Requirements ## Requirements

View File

@ -285,6 +285,7 @@ Styleguides:
│ ├── InitServiceWorker.js # Initializes and manages service worker, if enabled │ ├── InitServiceWorker.js # Initializes and manages service worker, if enabled
│ ├── Search.js # Helper functions for searching/ filtering items in all views │ ├── Search.js # Helper functions for searching/ filtering items in all views
│ ├── JsonToYaml.js # Function that parses and converts raw JSON into valid YAML │ ├── JsonToYaml.js # Function that parses and converts raw JSON into valid YAML
│ ├── KeycloakAuth.js # Singleton class to manage Keycloak authentication
│ ├── languages.js # Handles fetching, switching and validating languages │ ├── languages.js # Handles fetching, switching and validating languages
│ ╰── ThemeHelper.js # Function that handles the fetching and setting of user themes │ ╰── ThemeHelper.js # Function that handles the fetching and setting of user themes
╰── views # Directory of available pages, corresponding to available routes ╰── views # Directory of available pages, corresponding to available routes

View File

@ -1,6 +1,6 @@
# *Dashy Showcase* 🌟 # *Dashy Showcase* 🌟
| 💗 Do you use Dashy? Got a sweet dashboard? Submit it to the showcase! 👉 [See How](#submitting-your-dashboard) | | 💗 Got a sweet dashboard? Submit it to the showcase! 👉 [See How](#submitting-your-dashboard) |
|-| |-|
### Home Lab 2.0 ### Home Lab 2.0
@ -10,7 +10,7 @@
--- ---
### Ratty222 ### Ratty222
> By [@ratty222](https://github.com/ratty222) ([#384](https://github.com/Lissy93/dashy/discussions/384)) > By [@ratty222](https://github.com/ratty222) <sup>[#384](https://github.com/Lissy93/dashy/discussions/384)</sup>
![screenshot-ratty222-dashy](https://user-images.githubusercontent.com/1862727/147582551-4c655d37-8bcc-4f95-ab41-164a9d0d6a07.png) ![screenshot-ratty222-dashy](https://user-images.githubusercontent.com/1862727/147582551-4c655d37-8bcc-4f95-ab41-164a9d0d6a07.png)
@ -38,6 +38,14 @@
--- ---
### The Private Dashboard
> By [@DylanBeMe](https://github.com/DylanBeMe) <sup>[#419](https://github.com/Lissy93/dashy/issues/419)</sup>
![screenshot-evo-dashboard](https://i.ibb.co/hKS483T/private-dashboard-Dylan-Be-Me.png)
---
### NAS Home Dashboard ### NAS Home Dashboard
> By [@cerealconyogurt](https://github.com/cerealconyogurt) > By [@cerealconyogurt](https://github.com/cerealconyogurt)
@ -52,6 +60,8 @@
![screenshot-dashy-live](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/10-dashy-live.png) ![screenshot-dashy-live](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/10-dashy-live.png)
---
### CFT Toolbox ### CFT Toolbox
![screenshot-cft-toolbox](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/3-cft-toolbox.png) ![screenshot-cft-toolbox](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/3-cft-toolbox.png)
@ -91,14 +101,21 @@
> Dashy, is the most complete dashboard I ever tried, has all the features, and it sets itself apart from the rest. It is my default homepage now. I am thankful to the developer @Lissy93 for sharing such a wonderful creation. > Dashy, is the most complete dashboard I ever tried, has all the features, and it sets itself apart from the rest. It is my default homepage now. I am thankful to the developer @Lissy93 for sharing such a wonderful creation.
[![screenshot-12-skoogee-homelab-3](https://i.ibb.co/F5yBTsT/12-skoogee-homelab-3.png)](https://ibb.co/album/ynSwzm) [![screenshot-12-skoogee-homelab-3](https://i.ibb.co/F5yBTsT/12-skoogee-homelab-3.png?)](https://ibb.co/album/ynSwzm)
--- ---
### Ground Control ### Ground Control
> By [@dtctek](https://github.com/dtctek) > By [@dtctek](https://github.com/dtctek)
![screenshot-ground-control](https://raw.githubusercontent.com/Lissy93/dashy/master/docs/showcase/7-ground-control-dtctek.png) ![screenshot-ground-control](https://user-images.githubusercontent.com/1862727/149821995-e9b41dab-186c-42e6-b5b3-e233259b241d.png)
---
### System Monitor
> An aggregated board for monitoring system resource usage from a single view
![screenshot-monitor](https://i.ibb.co/xfK6BGb/system-monitor-board.png)
--- ---
@ -138,10 +155,10 @@ If you're submitting a pull request, please use a format similar to this:
> Submitted by [@username](https://github.com/user) (optional) > Submitted by [@username](https://github.com/user) (optional)
![dashboard-screenshot](/docs/showcase/screenshot-name.jpg) (required)
[An optional text description, or any interesting details] (optional) [An optional text description, or any interesting details] (optional)
![dashboard-screenshot](https://example.com/url-to-screenshot.png) (required)
--- ---
``` ```

View File

@ -1,6 +1,6 @@
{ {
"name": "Dashy", "name": "Dashy",
"version": "1.9.6", "version": "1.9.7",
"license": "MIT", "license": "MIT",
"main": "server", "main": "server",
"author": "Alicia Sykes <alicia@omg.lol> (https://aliciasykes.com)", "author": "Alicia Sykes <alicia@omg.lol> (https://aliciasykes.com)",
@ -96,4 +96,4 @@
"> 1%", "> 1%",
"last 2 versions" "last 2 versions"
] ]
} }

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="paste" class="svg-inline--fa fa-paste fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M433.941 193.941l-51.882-51.882A48 48 0 0 0 348.118 128H320V80c0-26.51-21.49-48-48-48h-61.414C201.582 13.098 182.294 0 160 0s-41.582 13.098-50.586 32H48C21.49 32 0 53.49 0 80v288c0 26.51 21.49 48 48 48h80v48c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48V227.882a48 48 0 0 0-14.059-33.941zm-84.066-16.184l48.368 48.368a6 6 0 0 1 1.757 4.243V240h-64v-64h9.632a6 6 0 0 1 4.243 1.757zM160 38c9.941 0 18 8.059 18 18s-8.059 18-18 18-18-8.059-18-18 8.059-18 18-18zm-32 138v192H54a6 6 0 0 1-6-6V86a6 6 0 0 1 6-6h55.414c9.004 18.902 28.292 32 50.586 32s41.582-13.098 50.586-32H266a6 6 0 0 1 6 6v42h-96c-26.51 0-48 21.49-48 48zm266 288H182a6 6 0 0 1-6-6V182a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v170a6 6 0 0 1-6 6z"></path></svg>

After

Width:  |  Height:  |  Size: 949 B

View File

@ -185,10 +185,12 @@
"newtab": "New Tab", "newtab": "New Tab",
"modal": "Pop-Up Modal", "modal": "Pop-Up Modal",
"workspace": "Workspace View", "workspace": "Workspace View",
"clipboard": "Copy to Clipboard",
"options-section-title": "Options", "options-section-title": "Options",
"edit-item": "Edit", "edit-item": "Edit",
"move-item": "Copy or Move", "move-item": "Copy or Move",
"remove-item": "Remove" "remove-item": "Remove",
"copied-toast": "URL has been copied to clipboard"
}, },
"section": { "section": {
"open-section": "Open Section", "open-section": "Open Section",

View File

@ -1,248 +1,284 @@
{ {
"home": { "home":{
"no-results": "Inga sökresultat", "no-results":"Inga sökresultat",
"no-data": "Ingen data konfigurerad" "no-data":"Ingen data konfigurerad",
"no-items-section":"Inga objekt att visa än"
},
"search":{
"search-label":"Sök",
"search-placeholder":"Börja skriva för att filtrera",
"clear-search-tooltip":"Rensa sök",
"enter-to-search-web":"Tryck på Retur för att söka på webben"
},
"login":{
"title":"Dashy",
"username-label":"Användarnamn",
"password-label":"Lösenord",
"login-button":"Logga in",
"remember-me-label":"Kom ihåg mig",
"remember-me-never":"Aldrig",
"remember-me-hour":"4 Timmar",
"remember-me-day":"1 Dag",
"remember-me-week":"1 Vecka",
"remember-me-long-time":"Länge",
"error-missing-username":"Användarnamn saknas",
"error-missing-password":"Lösenord saknas",
"error-incorrect-username":"Användaren hittas inte",
"error-incorrect-password":"Fel lösenord",
"success-message":"Loggar in...",
"logout-message":"Utloggad",
"already-logged-in-title":"Redan inloggad",
"already-logged-in-text":"Du är inloggad som",
"proceed-to-dashboard":"Fortsätt till Dashboard",
"log-out-button":"Logga ut",
"proceed-guest-button":"Fortsätt som Gäst"
},
"config":{
"main-tab":"Huvudmeny",
"view-config-tab":"Visa konfiguration",
"edit-config-tab":"Redigera konfiguration",
"custom-css-tab":"Egna stilmallar",
"heading":"Konfigurationsalternativ",
"download-config-button":"Visa / Exportera konfiguration",
"edit-config-button":"Redigera konfiguration",
"edit-css-button":"Redigera Custom CSS",
"cloud-sync-button":"Aktivera molnsynk",
"edit-cloud-sync-button":"Redigera molnsynk",
"rebuild-app-button":"Återuppbygga appen",
"change-language-button":"Ändra appspråk",
"reset-settings-button":"Återställ lokala inställningar",
"app-info-button":"Appinfo",
"backup-note":"Det rekommenderas att du gör en säkerhetskopia av din konfiguration innan du gör ändringar.",
"reset-config-msg-l1":"Detta tar bort alla användarinställningar från lokal lagring, men påverkar inte din 'conf.yml'-fil",
"reset-config-msg-l2":"Du bör först göra en säkerhetskopia av alla ändringar du har gjort lokalt, om du vill använda dem i framtiden.",
"reset-config-msg-l3":"Är du säker på att du vill fortsätta?",
"data-cleared-msg":"Datarensning har lyckats",
"actions-label":"Åtgärder",
"copy-config-label":"Kopiera konfiguration",
"data-copied-msg":"Konfiguration har kopierats till urklipp",
"reset-config-label":"Återställ konfiguration",
"css-save-btn":"Spara ändringar",
"css-note-label":"Not",
"css-note-l1":"Du måste uppdatera sidan för att dina ändringar ska gälla.",
"css-note-l2":"Styles overrides lagras bara lokalt, så det rekommenderas att du gör en kopia av din CSS.",
"css-note-l3":"För att ta bort alla egna stilmallar, radera innehållet och tryck på Spara ändringar"
},
"alternate-views":{
"alternate-view-heading":"Ändra vy",
"default":"Standard",
"workspace":"Workspace",
"minimal":"Minimal"
},
"settings":{
"theme-label":"Tema",
"layout-label":"Layout",
"layout-auto":"Auto",
"layout-horizontal":"Vågrät",
"layout-vertical":"Lodrät",
"item-size-label":"Storlek",
"item-size-small":"Liten",
"item-size-medium":"Mellan",
"item-size-large":"Stor",
"config-launcher-label":"Konfig",
"config-launcher-tooltip":"Uppdatera konfiguration",
"sign-out-tooltip":"Logga ut",
"sign-in-tooltip":"Logga in",
"sign-in-welcome":"Hej {username}!"
},
"updates":{
"app-version-note":"Dashy-version",
"up-to-date":"Uppdaterat",
"out-of-date":"Uppdatering finns",
"unsupported-version-l1":"Du använder en icke-stödd version av Dashy",
"unsupported-version-l2":"För den bästa upplevelsen och de senaste säkerhetskorrigeringarna, uppdatera till"
},
"language-switcher":{
"title":"Ändra appspråk",
"dropdown-label":"Välj språk",
"save-button":"Spara",
"success-msg":"Språket har ändrats till"
},
"theme-maker":{
"title":"Temakonfigurator",
"export-button":"Exportera egendefinierade variabler",
"reset-button":"Återställ stilmallar för",
"show-all-button":"Vissa alla variabler",
"change-fonts-button":"Ändra typsnitt",
"save-button":"Spara",
"cancel-button":"Avbryt",
"saved-toast":"{theme} har uppdaterats",
"copied-toast":"Temadatan för {theme} har kopierats till urklipp",
"reset-toast":"Egna färger för {theme} har tagits bort"
},
"config-editor":{
"save-location-label":"Sparningsplats",
"location-local-label":"Tillämpa lokalt",
"location-disk-label":"Skriv ändringar till konfigurationsfil",
"save-button":"Spara ändringar",
"preview-button":"Förhandsgranska ändringar",
"valid-label":"Konfigurationen är giltig",
"status-success-msg":"Åtgärden slutförts",
"status-fail-msg":"Åtgärden misslyckats",
"success-msg-disk":"Konfigurationsfil har skrivits till disk utan problem",
"success-msg-local":"Lokala ändringar har sparats utan problem",
"success-note-l1":"Återskapa",
"success-note-l2":"Detta kan ta upp till en minut.",
"success-note-l3":"Du måste uppdatera sidan för att ändringar ska gälla",
"error-msg-save-mode":"Välj Lagringsläge: Lokalt eller Fil",
"error-msg-cannot-save":"Ett fel uppstod när konfigurationen skulle sparas",
"error-msg-bad-json":"Fel i JSON, möjligen felformaterat",
"warning-msg-validation":"Valideringsvarning",
"not-admin-note":"Du kan inte skriva ändringar till disk, eftersom du inte är inloggad som admin"
},
"app-rebuild":{
"title":"Återskapa appen",
"rebuild-note-l1":"Appen måste återskapas för att ändringar som skrivits till filen conf.yml ska gälla.",
"rebuild-note-l2":"Detta bör ske automatiskt, men om det inte har gjort det kan du aktivera det manuellt här.",
"rebuild-note-l3":"Detta krävs inte för ändringar som lagras lokalt.",
"rebuild-button":"Återskapa",
"rebuilding-status-1":"Återskapar...",
"rebuilding-status-2":"Detta kan ta några minuter",
"error-permission":"Du har inte behörighet att utföra denna åtgärd",
"success-msg":"Återskapning lyckats",
"fail-msg":"Återskapning misslyckats",
"reload-note":"En omladdning av sidan krävs nu för att ändringarna ska gälla",
"reload-button":"Ladda om sidan"
},
"cloud-sync":{
"title":"Molnsäkerhetskopiering och återställning",
"intro-l1":"Molnsäkerhetskopiering och återställning är en valfri funktion som gör att du kan ladda upp din konfiguration till internet och sedan återställa den på någon annan enhet eller instans av Dashy.",
"intro-l2":"All data är fullständigt end-to-end krypterad med AES, med ditt lösenord som nyckel.",
"intro-l3":"För mer information, vänligen se",
"backup-title-setup":"Gör en säkerhetskopia",
"backup-title-update":"Uppdatera säkerhetskopia",
"password-label-setup":"Välj lösenord",
"password-label-update":"Ange ditt lösenord",
"backup-button-setup":"Säkerhetskopiering",
"backup-button-update":"Uppdatera säkerhetskopia",
"backup-id-label":"Ditt säkerhetskopierings-ID",
"backup-id-note":"Detta används för att återställa från säkerhetskopior senare. Så förvara det tillsammans med ditt lösenord någonstans säkert.",
"restore-title":"Återställ en säkerhetskopia",
"restore-id-label":"Återställ ID",
"restore-password-label":"Lösenord",
"restore-button":"Återställ",
"backup-missing-password":"Lösenord saknas",
"backup-error-unknown":"Begäran kan inte behandlas",
"backup-error-password":"Fel lösenord. Vänligen ange ditt aktuella lösenord.",
"backup-success-msg":"Slutfört utan problem",
"restore-success-msg":"Konfigurationen har återställts utan problem"
},
"menu":{
"open-section-title":"Öppna i",
"sametab":"Denna flik",
"newtab":"Ny flik",
"modal":"Pop-Up Modal",
"workspace":"Workspace-vy",
"options-section-title":"Alternativ",
"edit-item":"Redigera",
"move-item":"Kopiera eller flytta",
"remove-item":"Ta bort"
},
"context-menus":{
"item":{
"open-section-title":"Öppna i",
"sametab":"Denna flik",
"newtab":"Ny flik",
"modal":"Pop-Up Modal",
"workspace":"Workspace View",
"options-section-title":"Alternativ",
"edit-item":"Redigera",
"move-item":"Kopiera eller flytta",
"remove-item":"Ta bort"
}, },
"search": { "section":{
"search-label": "Sök", "open-section":"Öppna sektion",
"search-placeholder": "Börja skriva för att filtrera", "edit-section":"Redigera",
"clear-search-tooltip": "Rensa sök", "move-section":"Flytta till",
"enter-to-search-web": "Tryck på Retur för att söka på webben" "remove-section":"Ta bort"
},
"login": {
"title": "Dashy",
"username-label": "Användarnamn",
"password-label": "Lösenord",
"login-button": "Logga in",
"remember-me-label": "Kom ihåg mig",
"remember-me-never": "Aldrig",
"remember-me-hour": "4 Timmar",
"remember-me-day": "1 Dag",
"remember-me-week": "1 Vecka",
"remember-me-long-time": "Länge",
"error-missing-username": "Användarnamn saknas",
"error-missing-password": "Lösenord saknas",
"error-incorrect-username": "Användaren hittas inte",
"error-incorrect-password": "Fel lösenord",
"success-message": "Loggar in...",
"logout-message": "Utloggad",
"already-logged-in-title": "Redan inloggad",
"already-logged-in-text": "Du är inloggad som",
"proceed-to-dashboard": "Fortsätt till Dashboard",
"log-out-button": "Logga ut",
"proceed-guest-button": "Fortsätt som Gäst"
},
"config": {
"main-tab": "Huvudmeny",
"view-config-tab": "Visa konfiguration",
"edit-config-tab": "Redigera konfiguration",
"custom-css-tab": "Egendefinierade stilmallar",
"heading": "Konfigurationsalternativ",
"download-config-button": "Visa / Exportera konfiguration",
"edit-config-button": "Redigera konfiguration",
"edit-css-button": "Redigera egendefinierad CSS",
"cloud-sync-button": "Aktivera molnsynkronisering",
"edit-cloud-sync-button": "Redigera molnsynkronisering",
"rebuild-app-button": "Återuppbygga appen",
"change-language-button": "Ändra appspråk",
"reset-settings-button": "Återställ lokala inställningar",
"app-info-button": "Appinfo",
"backup-note": "Det rekommenderas att du gör en säkerhetskopia av din konfiguration innan du gör ändringar.",
"reset-config-msg-l1": "Detta tar bort alla användarinställningar från lokal lagring, men påverkar inte din 'conf.yml'-fil",
"reset-config-msg-l2": "Du bör först göra en säkerhetskopia av alla ändringar du har gjort lokalt, om du vill använda dem i framtiden.",
"reset-config-msg-l3": "Är du säker på att du vill fortsätta?",
"data-cleared-msg": "Datarensning har lyckats",
"actions-label": "Åtgärder",
"copy-config-label": "Kopiera konfiguration",
"data-copied-msg": "Konfiguration har kopierats till urklipp",
"reset-config-label": "Återställ konfiguration",
"css-save-btn": "Spara ändringar",
"css-note-label": "Not",
"css-note-l1": "Du måste uppdatera sidan för att dina ändringar ska gälla.",
"css-note-l2": "Styles overrides lagras bara lokalt, så det rekommenderas att du gör en kopia av din CSS.",
"css-note-l3": "För att ta bort alla egendefinierade stilmallar, radera innehållet och tryck på Spara ändringar"
},
"alternate-views": {
"alternate-view-heading": "Ändra vy",
"default": "Standard",
"workspace": "Workspace",
"minimal": "Minimal"
},
"settings": {
"theme-label": "Tema",
"layout-label": "Layout",
"layout-auto": "Auto",
"layout-horizontal": "Vågrät",
"layout-vertical": "Lodrät",
"item-size-label": "Storlek",
"item-size-small": "Liten",
"item-size-medium": "Mellan",
"item-size-large": "Stor",
"config-launcher-label": "Konfig",
"config-launcher-tooltip": "Uppdatera konfiguration",
"sign-out-tooltip": "Logga ut",
"sign-in-tooltip": "Logga in",
"sign-in-welcome": "Hej {username}!"
},
"updates": {
"app-version-note": "Dashy-version",
"up-to-date": "Uppdaterat",
"out-of-date": "Uppdatering finns",
"unsupported-version-l1": "Du använder en icke-stödd version av Dashy",
"unsupported-version-l2": "För den bästa upplevelsen och de senaste säkerhetskorrigeringarna, uppdatera till"
},
"language-switcher": {
"title": "Ändra appspråk",
"dropdown-label": "Välj språk",
"save-button": "Spara",
"success-msg": "Språket har ändrats till"
},
"theme-maker": {
"title": "Temakonfigurator",
"export-button": "Exportera egendefinierade variabler",
"reset-button": "Återställ stilmallar för",
"show-all-button": "Visa alla variabler",
"change-fonts-button": "Ändra typsnitt",
"save-button": "Spara",
"cancel-button": "Avbryt",
"saved-toast": "Uppdatering av {theme} har lyckats",
"copied-toast": "Temadatan för {theme} har kopierats till urklipp",
"reset-toast": "Egendefinierade färger för {theme} har tagits bort"
},
"config-editor": {
"save-location-label": "Sparningsplats",
"location-local-label": "Tillämpa lokalt",
"location-disk-label": "Skriv ändringar till konfigurationsfil",
"save-button": "Spara ändringar",
"preview-button": "Förhandsgranska ändringar",
"valid-label": "Konfigurationen är giltig",
"status-success-msg": "Åtgärden slutförts",
"status-fail-msg": "Åtgärden misslyckats",
"success-msg-disk": "Konfigurationsfil har skrivits till disk utan problem",
"success-msg-local": "Lokala ändringar har sparats utan problem",
"success-note-l1": "Återskapa",
"success-note-l2": "Detta kan ta upp till en minut.",
"success-note-l3": "Du måste uppdatera sidan för att ändringar ska gälla",
"error-msg-save-mode": "Välj Lagringsläge: Lokalt eller Fil",
"error-msg-cannot-save": "Ett fel uppstod när konfigurationen skulle sparas",
"error-msg-bad-json": "Fel i JSON, möjligen felformaterat",
"warning-msg-validation": "Valideringsvarning",
"not-admin-note": "Du kan inte skriva ändringar till disk, eftersom du inte är inloggad som admin"
},
"app-rebuild": {
"title": "Återskapa appen",
"rebuild-note-l1": "Appen måste återskapas för att ändringar som skrivits till filen conf.yml ska gälla.",
"rebuild-note-l2": "Detta bör ske automatiskt, men om det inte har gjort det kan du aktivera det manuellt här.",
"rebuild-note-l3": "Detta krävs inte för ändringar som lagras lokalt.",
"rebuild-button": "Återskapa",
"rebuilding-status-1": "Återskapar...",
"rebuilding-status-2": "Detta kan ta några minuter",
"error-permission": "Du har inte behörighet att utföra denna åtgärd",
"success-msg": "Återskapning lyckats",
"fail-msg": "Återskapning misslyckats",
"reload-note": "En omladdning av sidan krävs nu för att ändringarna ska gälla",
"reload-button": "Ladda om sidan"
},
"cloud-sync": {
"title": "Molnsäkerhetskopiering och återställning",
"intro-l1": "Molnsäkerhetskopiering och återställning är en valfri funktion som gör att du kan ladda upp din konfiguration till internet och sedan återställa den på någon annan enhet eller instans av Dashy.",
"intro-l2": "All data är fullständigt end-to-end krypterad med AES, med ditt lösenord som nyckel.",
"intro-l3": "För mer information, vänligen se",
"backup-title-setup": "Gör en säkerhetskopia",
"backup-title-update": "Uppdatera säkerhetskopia",
"password-label-setup": "Välj lösenord",
"password-label-update": "Ange ditt lösenord",
"backup-button-setup": "Säkerhetskopiering",
"backup-button-update": "Uppdatera säkerhetskopia",
"backup-id-label": "Ditt säkerhetskopierings-ID",
"backup-id-note": "Detta används för att återställa från säkerhetskopior senare. Så förvara det tillsammans med ditt lösenord någonstans säkert.",
"restore-title": "Återställ en säkerhetskopia",
"restore-id-label": "Återställ ID",
"restore-password-label": "Lösenord",
"restore-button": "Återställ",
"backup-missing-password": "Lösenord saknas",
"backup-error-unknown": "Begäran kan inte behandlas",
"backup-error-password": "Fel lösenord. Vänligen ange ditt aktuella lösenord.",
"backup-success-msg": "Slutfört utan problem",
"restore-success-msg": "Konfigurationen har återställts utan problem"
},
"menu": {
"open-section-title": "Öppna i",
"sametab": "Denna flik",
"newtab": "Ny flik",
"modal": "Pop-Up Modal",
"workspace": "Workspace-vy",
"options-section-title": "Alternativ",
"edit-item": "Redigera",
"move-item": "Kopiera eller flytta",
"remove-item": "Ta bort"
},
"context-menus": {
"item": {
"open-section-title": "Öppna i",
"sametab": "Denna flik",
"newtab": "Ny flik",
"modal": "Pop-Up Modal",
"workspace": "Workspace View",
"options-section-title": "Alternativ",
"edit-item": "Redigera",
"move-item": "Kopiera eller flytta",
"remove-item": "Ta bort"
},
"section": {
"open-section": "Öppna sektion",
"edit-section": "Redigera",
"move-section": "Flytta till",
"remove-section": "Ta bort"
}
},
"interactive-editor": {
"menu": {
"start-editing-tooltip": "Öppna den interaktiva redigeraren",
"edit-site-data-subheading": "Redigera webbplatsinformation",
"edit-page-info-btn": "Redigera sidinformation",
"edit-page-info-tooltip": "Appnamn, beskrivning, navigeringslänkar, sidfotstext, etc",
"edit-app-config-btn": "Redigera appkonfiguration",
"edit-app-config-tooltip": "Övriga appkonfigurationsalternativ",
"config-save-methods-subheading": "Alternativ för konfigurationssparande",
"save-locally-btn": "Spara lokalt",
"save-locally-tooltip": "Spara konfigurationen lokalt, till webbläsarens lagring. Detta påverkar inte din konfigurationsfil, men ändringarna sparas bara på denna enhet",
"save-disk-btn": "Spara till disk",
"save-disk-tooltip": "Spara konfiguration to conf.yml-filen på disk. Detta kommer att säkerhetskopiera och sedan skriva över din befintliga konfiguration",
"export-config-btn": "Exportera konfiguration",
"export-config-tooltip": "Visa och exportera den nya konfigurationen, antingen till fil eller urklipp",
"cloud-backup-btn": "Säkerhetskopiera till molnet",
"cloud-backup-tooltip": "Spara krypterad säkerhetskopia av konfigurationen i molnet",
"edit-raw-config-btn": "Redigera raw-konfiguration",
"edit-raw-config-tooltip": "Visa och redigera raw-konfiguration via JSON-redigeraren",
"cancel-changes-btn": "Avbryt redigering",
"cancel-changes-tooltip": "Radera nuvarande ändringar och lämna Redigeringsläge. Detta kommer in påverka din sparade konfiguration.",
"edit-mode-name": "Redigeringsläge",
"edit-mode-subtitle": "Du är i Redigeringsläge",
"edit-mode-description": "Detta innebär att du kan göra ändringar i din konfiguration och förhandsgranska resultaten, men tills du sparar kommer inga av dina ändringar att bevaras.",
"save-stage-btn": "Spara",
"cancel-stage-btn": "Avbryt"
},
"edit-section": {
"edit-section-title": "Redigera sektion",
"add-section-title": "Lägg till ny sektion",
"edit-tooltip": "Tryck för att redigera, eller högerklicka för fler alternativ",
"remove-confirm": "Är du säker på att du vill ta bort denna sektion? Denna åtgärd kan ångras senare."
},
"edit-app-config": {
"warning-msg-title": "Fortsätt med försiktighet",
"warning-msg-l1": "Följande alternativ är för avancerade appkonfigurationer.",
"warning-msg-l2": "Om du är osäker på något av fälten, vänligen kolla",
"warning-msg-docs": "dokumentationen",
"warning-msg-l3": "för att undvika oavsiktliga konsekvenser."
},
"export": {
"export-title": "Exportera konfiguration",
"copy-clipboard-btn": "Kopiera till urklipp",
"copy-clipboard-tooltip": "Kopiera alla appkonfigurationer till systemets urklipp i YAML-format",
"download-file-btn": "Ladda ned som fil",
"download-file-tooltip": "Ladda ner alla appkonfigurationer till din enhet som en YAML-fil",
"view-title": "Visa konfiguration"
}
} }
} },
"interactive-editor":{
"menu":{
"start-editing-tooltip":"Öppna den interaktiva redigeraren",
"edit-site-data-subheading":"Redigera webbplatsinformation",
"edit-page-info-btn":"Redigera sidinformation",
"edit-page-info-tooltip":"Appnamn, beskrivning, navigeringslänkar, sidfotstext, etc",
"edit-app-config-btn":"Redigera appkonfiguration",
"edit-app-config-tooltip":"Övriga appkonfigurationsalternativ",
"config-save-methods-subheading":"Alternativ för konfigurationssparande",
"save-locally-btn":"Spara lokalt",
"save-locally-tooltip":"Spara konfigurationen lokalt, till webbläsarens lagring. Detta påverkar inte din konfigurationsfil, men ändringarna sparas bara på denna enhet",
"save-disk-btn":"Spara till disk",
"save-disk-tooltip":"Spara konfiguration to conf.yml-filen på disk. Detta kommer att säkerhetskopiera och sedan skriva över din befintliga konfiguration",
"export-config-btn":"Exportera konfiguration",
"export-config-tooltip":"Visa och exportera den nya konfigurationen, antingen till fil eller urklipp",
"cloud-backup-btn":"Säkerhetskopiera till molnet",
"cloud-backup-tooltip":"Spara krypterad säkerhetskopia av konfigurationen i molnet",
"edit-raw-config-btn":"Redigera raw-konfiguration",
"edit-raw-config-tooltip":"Visa och redigera raw-konfiguration via JSON-redigeraren",
"cancel-changes-btn":"Avbryt redigering",
"cancel-changes-tooltip":"Radera nuvarande ändringar och lämna Redigeringsläge. Detta kommer in påverka din sparade konfiguration.",
"edit-mode-name":"Redigeringsläge",
"edit-mode-subtitle":"Du är i Redigeringsläge",
"edit-mode-description":"Detta innebär att du kan göra ändringar i din konfiguration och förhandsgranska resultaten, men tills du sparar kommer inga av dina ändringar att bevaras.",
"save-stage-btn":"Spara",
"cancel-stage-btn":"Avbryt"
},
"edit-item":{
"missing-title-err":"Objektet måste ha en titel"
},
"edit-section":{
"edit-section-title":"Redigera sektion",
"add-section-title":"Lägg till ny sektion",
"edit-tooltip":"Tryck för att redigera, eller högerklicka för fler alternativ",
"remove-confirm":"Är du säker på att du vill ta bort denna sektion? Denna åtgärd kan ångras senare."
},
"edit-app-config":{
"warning-msg-title":"Fortsätt med försiktighet",
"warning-msg-l1":"Följande alternativ är för avancerade appkonfigurationer.",
"warning-msg-l2":"Om du är osäker på något av fälten, vänligen kolla",
"warning-msg-docs":"dokumentationen",
"warning-msg-l3":"för att undvika oavsiktliga konsekvenser."
},
"export":{
"export-title":"Exportera konfiguration",
"copy-clipboard-btn":"Kopiera till urklipp",
"copy-clipboard-tooltip":"Kopiera alla appkonfigurationer till systemets urklipp i YAML-format",
"download-file-btn":"Ladda ned som fil",
"download-file-tooltip":"Ladda ner alla appkonfigurationer till din enhet som en YAML-fil",
"view-title":"Visa konfiguration"
}
},
"widgets":{
"general":{
"loading":"Laddar...",
"show-more":"Visa mer info",
"show-less":"Visa mindre",
"open-link":"Läs mer"
},
"pi-hole":{
"status-heading":"Status"
},
"stat-ping":{
"up":"Online",
"down":"Offline"
},
"net-data":{
"cpu-chart-title":"CPU History",
"mem-chart-title":"Memory Usage",
"mem-breakdown-title":"Memory Breakdown",
"load-chart-title":"System Load"
},
"system-info":{
"uptime":"Uptime"
},
"flight-data":{
"arrivals":"Ankomster",
"departures":"Avgångar"
},
"tfl-status":{
"good-service-all":"Good Service på alla linjer",
"good-service-rest":"Good Service på alla övriga linjer"
}
}
}

View File

@ -141,7 +141,7 @@ export default {
hyperLinkHref() { hyperLinkHref() {
const nothing = '#'; const nothing = '#';
if (this.isEditMode) return nothing; if (this.isEditMode) return nothing;
const noAnchorNeeded = ['modal', 'workspace']; const noAnchorNeeded = ['modal', 'workspace', 'clipboard'];
return noAnchorNeeded.includes(this.accumulatedTarget) ? nothing : this.url; return noAnchorNeeded.includes(this.accumulatedTarget) ? nothing : this.url;
}, },
}, },
@ -174,6 +174,9 @@ export default {
this.$emit('triggerModal', this.url); this.$emit('triggerModal', this.url);
} else if (this.accumulatedTarget === 'workspace') { } else if (this.accumulatedTarget === 'workspace') {
router.push({ name: 'workspace', query: { url: this.url } }); router.push({ name: 'workspace', query: { url: this.url } });
} else if (this.accumulatedTarget === 'clipboard') {
navigator.clipboard.writeText(this.url);
this.$toasted.show(this.$t('context-menus.item.copied-toast'));
} else { } else {
this.$emit('itemClicked'); this.$emit('itemClicked');
} }
@ -226,6 +229,7 @@ export default {
case 'top': return '"\\f102"'; case 'top': return '"\\f102"';
case 'modal': return '"\\f2d0"'; case 'modal': return '"\\f2d0"';
case 'workspace': return '"\\f0b1"'; case 'workspace': return '"\\f0b1"';
case 'clipboard': return '"\\f0ea"';
default: return '"\\f054"'; default: return '"\\f054"';
} }
}, },
@ -279,6 +283,10 @@ export default {
case 'workspace': case 'workspace':
router.push({ name: 'workspace', query: { url } }); router.push({ name: 'workspace', query: { url } });
break; break;
case 'clipboard':
navigator.clipboard.writeText(url);
this.$toasted.show(this.$t('context-menus.item.copied-toast'));
break;
default: window.open(url, '_blank'); default: window.open(url, '_blank');
} }
}, },
@ -546,4 +554,7 @@ a.item.is-edit-mode {
.disabled-link { .disabled-link {
pointer-events: none; pointer-events: none;
} }
.tooltip.item-description-tooltip {
z-index: 7;
}
</style> </style>

View File

@ -23,6 +23,10 @@
<WorkspaceOpenIcon /> <WorkspaceOpenIcon />
<span>{{ $t('context-menus.item.workspace') }}</span> <span>{{ $t('context-menus.item.workspace') }}</span>
</li> </li>
<li @click="launch('clipboard')">
<ClipboardOpenIcon />
<span>{{ $t('context-menus.item.clipboard') }}</span>
</li>
</ul> </ul>
<!-- Edit Options --> <!-- Edit Options -->
<ul class="menu-section"> <ul class="menu-section">
@ -55,6 +59,7 @@ import SameTabOpenIcon from '@/assets/interface-icons/open-current-tab.svg';
import NewTabOpenIcon from '@/assets/interface-icons/open-new-tab.svg'; import NewTabOpenIcon from '@/assets/interface-icons/open-new-tab.svg';
import IframeOpenIcon from '@/assets/interface-icons/open-iframe.svg'; import IframeOpenIcon from '@/assets/interface-icons/open-iframe.svg';
import WorkspaceOpenIcon from '@/assets/interface-icons/open-workspace.svg'; import WorkspaceOpenIcon from '@/assets/interface-icons/open-workspace.svg';
import ClipboardOpenIcon from '@/assets/interface-icons/open-clipboard.svg';
export default { export default {
name: 'ContextMenu', name: 'ContextMenu',
@ -66,6 +71,7 @@ export default {
NewTabOpenIcon, NewTabOpenIcon,
IframeOpenIcon, IframeOpenIcon,
WorkspaceOpenIcon, WorkspaceOpenIcon,
ClipboardOpenIcon,
}, },
props: { props: {
posX: Number, // The X coordinate for positioning posX: Number, // The X coordinate for positioning

View File

@ -7,6 +7,7 @@
<WorkspaceOpenIcon v-else-if="openingMethod === 'workspace'" /> <WorkspaceOpenIcon v-else-if="openingMethod === 'workspace'" />
<ParentOpenIcon v-else-if="openingMethod === 'parent'" /> <ParentOpenIcon v-else-if="openingMethod === 'parent'" />
<TopOpenIcon v-else-if="openingMethod === 'top'" /> <TopOpenIcon v-else-if="openingMethod === 'top'" />
<ClipboardOpenIcon v-else-if="openingMethod === 'clipboard'" />
<UnknownIcon v-else /> <UnknownIcon v-else />
</div> </div>
<div v-if="hotkey" :class="`hotkey-denominator ${makeClass(position, isSmall, isTransparent)}`"> <div v-if="hotkey" :class="`hotkey-denominator ${makeClass(position, isSmall, isTransparent)}`">
@ -25,6 +26,7 @@ import IframeOpenIcon from '@/assets/interface-icons/open-iframe.svg';
import WorkspaceOpenIcon from '@/assets/interface-icons/open-workspace.svg'; import WorkspaceOpenIcon from '@/assets/interface-icons/open-workspace.svg';
import ParentOpenIcon from '@/assets/interface-icons/open-parent.svg'; import ParentOpenIcon from '@/assets/interface-icons/open-parent.svg';
import TopOpenIcon from '@/assets/interface-icons/open-top.svg'; import TopOpenIcon from '@/assets/interface-icons/open-top.svg';
import ClipboardOpenIcon from '@/assets/interface-icons/open-clipboard.svg';
import UnknownIcon from '@/assets/interface-icons/unknown-icon.svg'; import UnknownIcon from '@/assets/interface-icons/unknown-icon.svg';
export default { export default {
@ -52,6 +54,7 @@ export default {
WorkspaceOpenIcon, WorkspaceOpenIcon,
ParentOpenIcon, ParentOpenIcon,
TopOpenIcon, TopOpenIcon,
ClipboardOpenIcon,
UnknownIcon, UnknownIcon,
}, },
}; };

View File

@ -12,11 +12,11 @@
@openContextMenu="openContextMenu" @openContextMenu="openContextMenu"
> >
<!-- If no items, show message --> <!-- If no items, show message -->
<div v-if="sectionType === 'empty'" class="no-items"> <div v-if="isEmpty" class="no-items">
{{ $t('home.no-items-section') }} {{ $t('home.no-items-section') }}
</div> </div>
<!-- Item Container --> <!-- Item Container -->
<div v-else-if="sectionType === 'item'" <div v-if="hasItems"
:class="`there-are-items ${isGridLayout? 'item-group-grid': ''} inner-size-${itemSize}`" :class="`there-are-items ${isGridLayout? 'item-group-grid': ''} inner-size-${itemSize}`"
:style="gridStyle" :id="`section-${groupId}`" :style="gridStyle" :id="`section-${groupId}`"
> <!-- Show for each item --> > <!-- Show for each item -->
@ -58,7 +58,7 @@
/> />
</div> </div>
<div <div
v-else-if="sectionType === 'widget'" v-if="hasWidgets"
:class="`widget-list ${isWide? 'wide' : ''}`"> :class="`widget-list ${isWide? 'wide' : ''}`">
<WidgetBase <WidgetBase
v-for="(widget, widgetIndx) in widgets" v-for="(widget, widgetIndx) in widgets"
@ -154,11 +154,15 @@ export default {
sortOrder() { sortOrder() {
return this.displayData.sortBy || defaultSortOrder; return this.displayData.sortBy || defaultSortOrder;
}, },
/* A section can contain either items or widgets */ hasItems() {
sectionType() { if (this.isEditMode) return true;
if (this.widgets && this.widgets.length > 0) return 'widget'; return this.items && this.items.length > 0;
if (this.items && this.items.length > 0) return 'item'; },
return 'empty'; hasWidgets() {
return this.widgets && this.widgets.length > 0;
},
isEmpty() {
return !this.hasItems && !this.hasWidgets;
}, },
/* If the sortBy attribute is specified, then return sorted data */ /* If the sortBy attribute is specified, then return sorted data */
sortedItems() { sortedItems() {

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<!-- If auth configured, show status text --> <!-- If auth configured, show status text -->
<span class="user-type-note">{{ makeText() }}</span> <span class="user-type-note">{{ makeUserGreeting() }}</span>
<div class="display-options"> <div class="display-options">
<!-- If user logged in, show logout button --> <!-- If user logged in, show logout button -->
<IconLogout <IconLogout
@ -17,6 +17,13 @@
v-tooltip="tooltip($t('settings.sign-in-tooltip'))" v-tooltip="tooltip($t('settings.sign-in-tooltip'))"
class="layout-icon" tabindex="-2" class="layout-icon" tabindex="-2"
/> />
<!-- If user logged in via keycloak, show keycloak logout button -->
<IconLogout
v-if="userType == userStateEnum.keycloakEnabled"
@click="keycloakLogout()"
v-tooltip="tooltip($t('settings.sign-out-tooltip'))"
class="layout-icon" tabindex="-2"
/>
</div> </div>
</div> </div>
</template> </template>
@ -24,6 +31,7 @@
<script> <script>
import router from '@/router'; import router from '@/router';
import { logout as registerLogout } from '@/utils/Auth'; import { logout as registerLogout } from '@/utils/Auth';
import { getKeycloakAuth } from '@/utils/KeycloakAuth';
import { localStorageKeys, userStateEnum } from '@/utils/defaults'; import { localStorageKeys, userStateEnum } from '@/utils/defaults';
import IconLogout from '@/assets/interface-icons/user-logout.svg'; import IconLogout from '@/assets/interface-icons/user-logout.svg';
@ -48,14 +56,22 @@ export default {
router.push({ path: '/login' }); router.push({ path: '/login' });
}, 500); }, 500);
}, },
keycloakLogout() {
const keycloak = getKeycloakAuth();
this.$toasted.show(this.$t('login.logout-message'));
setTimeout(() => {
keycloak.logout();
}, 500);
},
goToLogin() { goToLogin() {
router.push({ path: '/login' }); router.push({ path: '/login' });
}, },
tooltip(content) { tooltip(content) {
return { content, trigger: 'hover focus', delay: 250 }; return { content, trigger: 'hover focus', delay: 250 };
}, },
makeText() { makeUserGreeting() {
if (this.userType === userStateEnum.loggedIn) { if (this.userType === userStateEnum.loggedIn
|| this.userType === userStateEnum.keycloakEnabled) {
const username = localStorage[localStorageKeys.USERNAME]; const username = localStorage[localStorageKeys.USERNAME];
return username ? this.$t('settings.sign-in-welcome', { username }) : ''; return username ? this.$t('settings.sign-in-welcome', { username }) : '';
} }
@ -73,7 +89,6 @@ export default {
span.user-type-note { span.user-type-note {
color: var(--settings-text-color); color: var(--settings-text-color);
text-transform: capitalize;
margin-right: 0.5rem; margin-right: 0.5rem;
} }

View File

@ -10,7 +10,7 @@
<LayoutSelector :displayLayout="displayLayout" /> <LayoutSelector :displayLayout="displayLayout" />
<ItemSizeSelector :iconSize="iconSize" /> <ItemSizeSelector :iconSize="iconSize" />
<ConfigLauncher /> <ConfigLauncher />
<AuthButtons v-if="userState != 'noone'" :userType="userState" /> <AuthButtons v-if="userState !== 0" :userType="userState" />
</div> </div>
<div :class="`show-hide-container ${settingsVisible? 'hide-btn' : 'show-btn'}`"> <div :class="`show-hide-container ${settingsVisible? 'hide-btn' : 'show-btn'}`">
<button @click="toggleSettingsVisibility()" <button @click="toggleSettingsVisibility()"
@ -80,7 +80,7 @@ export default {
/** /**
* Determines which button should display, based on the user type * Determines which button should display, based on the user type
* 0 = Auth not configured, don't show anything * 0 = Auth not configured, don't show anything
* 1 = Auth condifured, and user logged in, show logout button * 1 = Auth configured, and user logged in, show logout button
* 2 = Auth configured, guest access enabled, and not logged in, show login * 2 = Auth configured, guest access enabled, and not logged in, show login
* Note that if auth is enabled, but not guest access, and user not logged in, * Note that if auth is enabled, but not guest access, and user not logged in,
* then they will never be able to view the homepage, so no button needed * then they will never be able to view the homepage, so no button needed

View File

@ -47,12 +47,12 @@ export default {
}, },
startDate() { startDate() {
const now = new Date(); const now = new Date();
return `${now.getDate()}-${now.getMonth()}-${now.getFullYear()}`; return `${now.getDate()}-${now.getMonth() + 1}-${now.getFullYear()}`;
}, },
endDate() { endDate() {
const now = new Date(); const now = new Date();
const then = new Date((now.setMonth(now.getMonth() + this.monthsToShow))); const then = new Date((now.setMonth(now.getMonth() + this.monthsToShow)));
return `${then.getDate()}-${then.getMonth()}-${then.getFullYear()}`; return `${then.getDate()}-${then.getMonth() + 1}-${then.getFullYear()}`;
}, },
endpoint() { endpoint() {
return `${widgetApiEndpoints.holidays}` return `${widgetApiEndpoints.holidays}`

View File

@ -2,7 +2,6 @@
// Import core framework and essential utils // Import core framework and essential utils
import Vue from 'vue'; import Vue from 'vue';
import VueI18n from 'vue-i18n'; // i18n for localization import VueI18n from 'vue-i18n'; // i18n for localization
import Keycloak from 'keycloak-js';
// Import component Vue plugins, used throughout the app // Import component Vue plugins, used throughout the app
import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js, tooltip component import VTooltip from 'v-tooltip'; // A Vue directive for Popper.js, tooltip component
@ -21,7 +20,7 @@ import clickOutside from '@/utils/ClickOutside'; // Directive for closing p
import { messages } from '@/utils/languages'; // Language texts import { messages } from '@/utils/languages'; // Language texts
import ErrorReporting from '@/utils/ErrorReporting'; // Error reporting initializer (off) import ErrorReporting from '@/utils/ErrorReporting'; // Error reporting initializer (off)
import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/utils/defaults'; import { toastedOptions, tooltipOptions, language as defaultLanguage } from '@/utils/defaults';
import { isKeycloakEnabled, getKeycloakConfig } from '@/utils/Auth'; // Keycloak auth config import { initKeycloakAuth, isKeycloakEnabled } from '@/utils/KeycloakAuth';
// Initialize global Vue components // Initialize global Vue components
Vue.use(VueI18n); Vue.use(VueI18n);
@ -63,18 +62,7 @@ const mount = () => new Vue({
if (!isKeycloakEnabled()) { if (!isKeycloakEnabled()) {
mount(); mount();
} else { // Keycloak is enabled, redirect to KC login page } else { // Keycloak is enabled, redirect to KC login page
const { serverUrl, realm, clientId } = getKeycloakConfig(); initKeycloakAuth()
const initOptions = { .then(() => mount())
url: `${serverUrl}/auth`, realm, clientId, onLoad: 'login-required', .catch(() => window.location.reload());
};
const keycloak = Keycloak(initOptions);
keycloak.init({ onLoad: initOptions.onLoad }).then((auth) => {
if (!auth) {
// Not authenticated, reload to Keycloak login page
window.location.reload();
} else {
// Yay - user successfully authenticated with Keycloak, render the app!
mount();
}
});
} }

View File

@ -70,8 +70,10 @@ const store = new Vuex.Store({
getItemById: (state, getters) => (id) => { getItemById: (state, getters) => (id) => {
let item; let item;
getters.sections.forEach(sec => { getters.sections.forEach(sec => {
const foundItem = sec.items.find((itm) => itm.id === id); if (sec.items) {
if (foundItem) item = foundItem; const foundItem = sec.items.find((itm) => itm.id === id);
if (foundItem) item = foundItem;
}
}); });
return item; return item;
}, },

View File

@ -2,6 +2,7 @@ import sha256 from 'crypto-js/sha256';
import ConfigAccumulator from '@/utils/ConfigAccumalator'; import ConfigAccumulator from '@/utils/ConfigAccumalator';
import ErrorHandler from '@/utils/ErrorHandler'; import ErrorHandler from '@/utils/ErrorHandler';
import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults'; import { cookieKeys, localStorageKeys, userStateEnum } from '@/utils/defaults';
import { isKeycloakEnabled } from '@/utils/KeycloakAuth';
/* Uses config accumulator to get and return app config */ /* Uses config accumulator to get and return app config */
const getAppConfig = () => { const getAppConfig = () => {
@ -19,26 +20,6 @@ const printWarning = () => {
ErrorHandler('From V 1.6.5 onwards, the structure of the users object has changed.'); ErrorHandler('From V 1.6.5 onwards, the structure of the users object has changed.');
}; };
/* Returns true if keycloak is enabled */
export const isKeycloakEnabled = () => {
const appConfig = getAppConfig();
if (!appConfig.auth) return false;
return appConfig.auth.enableKeycloak || false;
};
/* Returns the users keycloak config */
export const getKeycloakConfig = () => {
const appConfig = getAppConfig();
if (!isKeycloakEnabled()) return false;
const { keycloak } = appConfig.auth;
const { serverUrl, realm, clientId } = keycloak;
if (!serverUrl || !realm || !clientId) {
ErrorHandler('Keycloak config missing- please ensure you specify: serverUrl, realm, clientId');
return false;
}
return keycloak;
};
/* Returns array of users from appConfig.auth, if available, else an empty array */ /* Returns array of users from appConfig.auth, if available, else an empty array */
const getUsers = () => { const getUsers = () => {
const appConfig = getAppConfig(); const appConfig = getAppConfig();
@ -65,7 +46,6 @@ const generateUserToken = (user) => {
/** /**
* Checks if the user is currently authenticated * Checks if the user is currently authenticated
* @param {Array[Object]} users An array of user objects pulled from the config
* @returns {Boolean} Will return true if the user is logged in, else false * @returns {Boolean} Will return true if the user is logged in, else false
*/ */
export const isLoggedIn = () => { export const isLoggedIn = () => {
@ -95,7 +75,7 @@ export const isAuthEnabled = () => {
/* Returns true if guest access is enabled */ /* Returns true if guest access is enabled */
export const isGuestAccessEnabled = () => { export const isGuestAccessEnabled = () => {
const appConfig = getAppConfig(); const appConfig = getAppConfig();
if (appConfig.auth && typeof appConfig.auth === 'object') { if (appConfig.auth && typeof appConfig.auth === 'object' && !isKeycloakEnabled()) {
return appConfig.auth.enableGuestAccess || false; return appConfig.auth.enableGuestAccess || false;
} }
return false; return false;
@ -108,6 +88,7 @@ export const isGuestAccessEnabled = () => {
* @param {String} username The username entered by the user * @param {String} username The username entered by the user
* @param {String} pass The password entered by the user * @param {String} pass The password entered by the user
* @param {String[]} users An array of valid user objects * @param {String[]} users An array of valid user objects
* @param {Object} messages A static message template object
* @returns {Object} An object containing a boolean result and a message * @returns {Object} An object containing a boolean result and a message
*/ */
export const checkCredentials = (username, pass, users, messages) => { export const checkCredentials = (username, pass, users, messages) => {
@ -146,7 +127,7 @@ export const login = (username, pass, timeout) => {
}; };
/** /**
* Removed the browsers cookie, causing user to be logged out * Removed the browsers' cookie, causing user to be logged out
*/ */
export const logout = () => { export const logout = () => {
document.cookie = 'authenticationToken=null'; document.cookie = 'authenticationToken=null';
@ -164,7 +145,7 @@ export const getCurrentUser = () => {
if (!username) return false; // No username if (!username) return false; // No username
let foundUserObject = false; // Value to return let foundUserObject = false; // Value to return
getUsers().forEach((user) => { getUsers().forEach((user) => {
// If current logged in user found, then return that user // If current logged-in user found, then return that user
if (user.user === username) foundUserObject = user; if (user.user === username) foundUserObject = user;
}); });
return foundUserObject; return foundUserObject;
@ -182,11 +163,10 @@ export const isLoggedInAsGuest = () => {
/** /**
* Checks if the current user has admin privileges. * Checks if the current user has admin privileges.
* If no users are setup, then function will always return true * If no users are set up, then function will always return true
* But if auth is configured, then will verify user is correctly * But if auth is configured, then will verify user is correctly
* logged in and then check weather they are of type admin, and * logged in and then check weather they are of type admin, and
* return false if any conditions fail * return false if any conditions fail
* @param {String[]} - Array of users
* @returns {Boolean} - True if admin privileges * @returns {Boolean} - True if admin privileges
*/ */
export const isUserAdmin = () => { export const isUserAdmin = () => {
@ -212,7 +192,13 @@ export const isUserAdmin = () => {
* then they will never be able to view the homepage, so no button needed * then they will never be able to view the homepage, so no button needed
*/ */
export const getUserState = () => { export const getUserState = () => {
const { notConfigured, loggedIn, guestAccess } = userStateEnum; // Numeric enum options const {
notConfigured,
loggedIn,
guestAccess,
keycloakEnabled,
} = userStateEnum; // Numeric enum options
if (isKeycloakEnabled()) return keycloakEnabled; // Keycloak auth configured
if (!isAuthEnabled()) return notConfigured; // No auth enabled if (!isAuthEnabled()) return notConfigured; // No auth enabled
if (isLoggedIn()) return loggedIn; // User is logged in if (isLoggedIn()) return loggedIn; // User is logged in
if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing if (isGuestAccessEnabled()) return guestAccess; // Guest is viewing

View File

@ -6,24 +6,32 @@
// Import helper functions from auth, to get current user, and check if guest // Import helper functions from auth, to get current user, and check if guest
import { getCurrentUser, isLoggedInAsGuest } from '@/utils/Auth'; import { getCurrentUser, isLoggedInAsGuest } from '@/utils/Auth';
import { localStorageKeys } from '@/utils/defaults';
/* Helper function, checks if a given username appears in a user array */ /* Helper function, checks if a given testValue is found in the visibility list */
const determineVisibility = (visibilityList, cUsername) => { const determineVisibility = (visibilityList, testValue) => {
let isFound = false; let isFound = false;
visibilityList.forEach((userInList) => { visibilityList.forEach((visibilityItem) => {
if (userInList.toLowerCase() === cUsername) isFound = true; if (visibilityItem.toLowerCase() === testValue.toLowerCase()) isFound = true;
}); });
return isFound; return isFound;
}; };
/* Helper function, determines if two arrays have any intersecting elements
(one or more items that are the same) */
const determineIntersection = (source = [], target = []) => {
const intersections = source.filter(item => target.indexOf(item) !== -1);
return intersections.length > 0;
};
/* Returns false if this section should not be rendered for the current user/ guest */ /* Returns false if this section should not be rendered for the current user/ guest */
const isSectionVisibleToUser = (displayData, currentUser, isGuest) => { const isSectionVisibleToUser = (displayData, currentUser, isGuest) => {
// Checks if user explicitly has access to a certain section // Checks if user explicitly has access to a certain section
const checkVisiblity = () => { const checkVisibility = () => {
if (!currentUser) return true; if (!currentUser) return true;
const hideFor = displayData.hideForUsers || []; const hideForUsers = displayData.hideForUsers || [];
const cUsername = currentUser.user.toLowerCase(); const cUsername = currentUser.user.toLowerCase();
return !determineVisibility(hideFor, cUsername); return !determineVisibility(hideForUsers, cUsername);
}; };
// Checks if user is explicitly prevented from viewing a certain section // Checks if user is explicitly prevented from viewing a certain section
const checkHiddenability = () => { const checkHiddenability = () => {
@ -33,12 +41,36 @@ const isSectionVisibleToUser = (displayData, currentUser, isGuest) => {
if (showForUsers.length < 1) return true; if (showForUsers.length < 1) return true;
return determineVisibility(showForUsers, cUsername); return determineVisibility(showForUsers, cUsername);
}; };
const checkKeycloakVisibility = () => {
if (!displayData.hideForKeycloakUsers) return true;
const { groups, roles } = JSON.parse(localStorage.getItem(localStorageKeys.KEYCLOAK_INFO) || '{}');
const hideForGroups = displayData.hideForKeycloakUsers.groups || [];
const hideForRoles = displayData.hideForKeycloakUsers.roles || [];
return !(determineIntersection(hideForRoles, roles)
|| determineIntersection(hideForGroups, groups));
};
const checkKeycloakHiddenability = () => {
if (!displayData.showForKeycloakUsers) return true;
const { groups, roles } = JSON.parse(localStorage.getItem(localStorageKeys.KEYCLOAK_INFO) || '{}');
const showForGroups = displayData.showForKeycloakUsers.groups || [];
const showForRoles = displayData.showForKeycloakUsers.roles || [];
return determineIntersection(showForRoles, roles)
|| determineIntersection(showForGroups, groups);
};
// Checks if the current user is a guest, and if section allows for guests // Checks if the current user is a guest, and if section allows for guests
const checkIfHideForGuest = () => { const checkIfHideForGuest = () => {
const hideForGuest = displayData.hideForGuests; const hideForGuest = displayData.hideForGuests;
return !(hideForGuest && isGuest); return !(hideForGuest && isGuest);
}; };
return checkVisiblity() && checkHiddenability() && checkIfHideForGuest(); return checkVisibility()
&& checkHiddenability()
&& checkIfHideForGuest()
&& checkKeycloakVisibility()
&& checkKeycloakHiddenability();
}; };
/* Putting it all together, the function to export */ /* Putting it all together, the function to export */

View File

@ -84,7 +84,8 @@
"parent", "parent",
"top", "top",
"modal", "modal",
"workspace" "workspace",
"clipboard"
], ],
"default": "newtab", "default": "newtab",
"description": "The default opening method for items. Only used if no item.target is specified" "description": "The default opening method for items. Only used if no item.target is specified"
@ -613,6 +614,58 @@
"type": "boolean", "type": "boolean",
"default": false, "default": false,
"description": "If set to true, section will be visible for logged in users, but not for guests" "description": "If set to true, section will be visible for logged in users, but not for guests"
},
"showForKeycloakUsers": {
"title": "Show for select Keycloak groups or roles",
"type": "object",
"description": "Configure the Keycloak groups or roles that will have access to this section",
"additionalProperties": false,
"properties": {
"groups": {
"title": "Show for Groups",
"type": "array",
"description": "Section will be hidden from all users except those with one or more of these groups",
"items": {
"type": "string",
"description": "Name of the group that will be able to view this section"
}
},
"roles": {
"title": "Show for Roles",
"type": "array",
"description": "Section will be hidden from all users except those with one or more of these roles",
"items": {
"type": "string",
"description": "Name of the role that will be able to view this section"
}
}
}
},
"hideForKeycloakUsers": {
"title": "Hide for select Keycloak groups or roles",
"type": "object",
"description": "Configure the Keycloak groups or roles that will not have access to this section",
"additionalProperties": false,
"properties": {
"groups": {
"title": "Hide for Groups",
"type": "array",
"description": "Section will be hidden from users with any of these groups",
"items": {
"type": "string",
"description": "name of the group that will not be able to view this section"
}
},
"roles": {
"title": "Hide for Roles",
"type": "array",
"description": "Section will be hidden from users with any of roles",
"items": {
"type": "string",
"description": "name of the role that will not be able to view this section"
}
}
}
} }
} }
}, },
@ -658,7 +711,8 @@
"parent", "parent",
"top", "top",
"modal", "modal",
"workspace" "workspace",
"clipboard"
], ],
"default": "newtab", "default": "newtab",
"description": "Where / how the item is opened when it's clicked" "description": "Where / how the item is opened when it's clicked"
@ -742,4 +796,4 @@
} }
} }
} }
} }

92
src/utils/KeycloakAuth.js Normal file
View File

@ -0,0 +1,92 @@
import Keycloak from 'keycloak-js';
import ConfigAccumulator from '@/utils/ConfigAccumalator';
import { localStorageKeys } from '@/utils/defaults';
import ErrorHandler from '@/utils/ErrorHandler';
const getAppConfig = () => {
const Accumulator = new ConfigAccumulator();
const config = Accumulator.config();
return config.appConfig || {};
};
class KeycloakAuth {
constructor() {
const { auth } = getAppConfig();
const { serverUrl, realm, clientId } = auth.keycloak;
const initOptions = {
url: `${serverUrl}/auth`, realm, clientId, onLoad: 'login-required',
};
this.keycloakClient = Keycloak(initOptions);
}
login() {
return new Promise((resolve, reject) => {
this.keycloakClient.init({ onLoad: 'login-required' })
.then((auth) => {
if (auth) {
this.storeKeycloakInfo();
return resolve();
} else {
return reject(new Error('Not authenticated'));
}
})
.catch((reason) => reject(reason));
});
}
logout() {
localStorage.removeItem(localStorageKeys.USERNAME);
localStorage.removeItem(localStorageKeys.KEYCLOAK_INFO);
this.keycloakClient.logout();
}
storeKeycloakInfo() {
if (this.keycloakClient.tokenParsed && typeof this.keycloakClient.tokenParsed === 'object') {
const {
groups,
realm_access: realmAccess,
resource_access: resourceAccess,
azp: clientId,
preferred_username: preferredUsername,
} = this.keycloakClient.tokenParsed;
const realmRoles = realmAccess.roles || [];
let clientRoles = [];
if (Object.hasOwn(resourceAccess, clientId)) {
clientRoles = resourceAccess[clientId].roles || [];
}
const roles = [...realmRoles, ...clientRoles];
const info = {
groups,
roles,
};
localStorage.setItem(localStorageKeys.KEYCLOAK_INFO, JSON.stringify(info));
localStorage.setItem(localStorageKeys.USERNAME, preferredUsername);
}
}
}
export const isKeycloakEnabled = () => {
const { auth } = getAppConfig();
if (!auth) return false;
return auth.enableKeycloak || false;
};
let keycloak;
export const initKeycloakAuth = () => {
keycloak = new KeycloakAuth();
return keycloak.login();
};
export const getKeycloakAuth = () => {
if (!keycloak) {
ErrorHandler("Keycloak not initialized, can't get instance of class");
}
return keycloak;
};

View File

@ -8,7 +8,8 @@ export const shouldBeVisible = (routeName) => !hideFurnitureOn.includes(routeNam
/* Based on section title, item name and index, return a string value for ID */ /* Based on section title, item name and index, return a string value for ID */
const makeItemId = (sectionStr, itemStr, index) => { const makeItemId = (sectionStr, itemStr, index) => {
const charSum = sectionStr.split('').map((a) => a.charCodeAt(0)).reduce((x, y) => x + y); const charSum = sectionStr.split('').map((a) => a.charCodeAt(0)).reduce((x, y) => x + y);
const itemTitleStr = itemStr.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase(); const newItemStr = itemStr || `unknown_${Math.random()}`;
const itemTitleStr = newItemStr.replace(/\s+/g, '-').replace(/[^a-zA-Z ]/g, '').toLowerCase();
return `${index}_${charSum}_${itemTitleStr}`; return `${index}_${charSum}_${itemTitleStr}`;
}; };

View File

@ -3,10 +3,7 @@ module.exports = {
pageInfo: { pageInfo: {
title: 'Dashy', title: 'Dashy',
description: '', description: '',
navLinks: [ navLinks: [],
{ title: 'Home', path: '/' },
{ title: 'Source', path: 'https://github.com/Lissy93/dashy' },
],
footerText: '', footerText: '',
}, },
/* Default appConfig to be used, if user does not specify their own */ /* Default appConfig to be used, if user does not specify their own */
@ -124,6 +121,7 @@ module.exports = {
USERNAME: 'username', USERNAME: 'username',
MOST_USED: 'mostUsed', MOST_USED: 'mostUsed',
LAST_USED: 'lastUsed', LAST_USED: 'lastUsed',
KEYCLOAK_INFO: 'keycloakInfo',
}, },
/* Key names for cookie identifiers */ /* Key names for cookie identifiers */
cookieKeys: { cookieKeys: {
@ -284,6 +282,7 @@ module.exports = {
loggedIn: 1, loggedIn: 1,
guestAccess: 2, guestAccess: 2,
notLoggedIn: 3, notLoggedIn: 3,
keycloakEnabled: 4,
}, },
/* Progressive Web App settings, used by Vue Config */ /* Progressive Web App settings, used by Vue Config */
pwa: { pwa: {