mirror of
https://github.com/Lissy93/dashy.git
synced 2025-04-08 17:06:18 +02:00
🔀 Merge pull request #663 from Lissy93/FIX/user-suggestions-fixes-2.0.9
[FIX] User Suggestions & Bug Fixes Fixes #564 Fixes #590 Fixes #626 Fixes #629 Fixes #632 Fixes #640 Fixes #644 Fixes #645 Fixes #646 Fixes #651 Fixes #654 Fixes #655 Fixes #657 Fixes #659 Fixes #660
This commit is contained in:
commit
78bed0fb92
.github
README.mddocs
package.jsonsrc
18
.github/CHANGELOG.md
vendored
18
.github/CHANGELOG.md
vendored
@ -1,5 +1,23 @@
|
||||
# Changelog
|
||||
|
||||
## ✨ 2.0.9 Adds Multi-Page Support [PR #663](https://github.com/Lissy93/dashy/pull/663)
|
||||
- Fix KeyCloak API URL (#564)
|
||||
- Fix guest has config access (#590)
|
||||
- Fix collapsible content in multi-page support (#626)
|
||||
- Fix layout and item size buttons ( #629)
|
||||
- Refactor make request in RSS widget (#632)
|
||||
- Fix material-design-icons header in schema (#640)
|
||||
- Add option to hide seconds in clock widget (#644)
|
||||
- Fix pageInfo not being read in router (#645)
|
||||
- Fix startingView not honored (#646)
|
||||
- Fix Status Check default (#651)
|
||||
- Add option to hide image in SportsScores Widget (#654)
|
||||
- Add Adventure-basic theme (#655)
|
||||
- Write docs for sub-items (#657)
|
||||
- Add Font-Awesome displaying as square to troubleshooting guide (#659)
|
||||
- Show expand / collapse in context menu (#660)
|
||||
- Only deploy new release when relevant files have changed
|
||||
|
||||
## ✨ 2.0.8 Adds Multi-Page Support [PR #617](https://github.com/Lissy93/dashy/pull/617)
|
||||
- Adds support for multiple pages per-dashboard
|
||||
- Adds new attribute at root of main config file: `pages`
|
||||
|
21
.github/LATEST_CHANGELOG.md
vendored
21
.github/LATEST_CHANGELOG.md
vendored
@ -1,4 +1,17 @@
|
||||
## ✨ 2.0.8 Adds Multi-Page Support [PR #617](https://github.com/Lissy93/dashy/pull/617)
|
||||
- Adds support for multiple pages per-dashboard
|
||||
- Adds new attribute at root of main config file: `pages`
|
||||
- Updates router and nav-bar to automatically create paths for both local and remote configs
|
||||
## ✨ 2.0.9 Adds Multi-Page Support [PR #663](https://github.com/Lissy93/dashy/pull/663)
|
||||
- Fix KeyCloak API URL (#564)
|
||||
- Fix guest has config access (#590)
|
||||
- Fix collapsible content in multi-page support (#626)
|
||||
- Fix layout and item size buttons ( #629)
|
||||
- Refactor make request in RSS widget (#632)
|
||||
- Fix material-design-icons header in schema (#640)
|
||||
- Add option to hide seconds in clock widget (#644)
|
||||
- Fix pageInfo not being read in router (#645)
|
||||
- Fix startingView not honored (#646)
|
||||
- Fix Status Check default (#651)
|
||||
- Add option to hide image in SportsScores Widget (#654)
|
||||
- Add Adventure-basic theme (#655)
|
||||
- Write docs for sub-items (#657)
|
||||
- Add Font-Awesome displaying as square to troubleshooting guide (#659)
|
||||
- Show expand / collapse in context menu (#660)
|
||||
- Only deploy new release when relevant files have changed
|
||||
|
5
.github/workflows/docker-build-publish.yml
vendored
5
.github/workflows/docker-build-publish.yml
vendored
@ -6,6 +6,11 @@ on:
|
||||
push:
|
||||
branches: ['master']
|
||||
tags: [v*]
|
||||
paths:
|
||||
- '**.js'
|
||||
- 'src/**'
|
||||
- 'public/**'
|
||||
- 'services/**'
|
||||
|
||||
env:
|
||||
DH_IMAGE: ${{ secrets.DOCKER_REPO }}
|
||||
|
@ -408,14 +408,15 @@ Dashy supports multiple languages and locales. When available, your language sho
|
||||
- 🇳🇱 **Dutch**: `nl` - Contributed by **[@evroon](https://github.com/evroon)**
|
||||
- 🇲🇫 **French**: `fr` - Contributed by **[@EVOTk](https://github.com/EVOTk)**
|
||||
- 🇩🇪 **German**: `de` - Contributed by **[@Niklashere](https://github.com/Niklashere)**
|
||||
- 🇮🇹 **Italian**: `it` - Contributed by **[@alexdelprete](https://github.com/alexdelprete)**
|
||||
- 🇳🇴 **Norwegian Bokmål**: `nb` - Contributed by **[@rubjo](https://github.com/rubjo)**
|
||||
- 🇵🇱 **Polish**: `pl` - Contributed by **[@skaarj1989](https://github.com/skaarj1989)**
|
||||
- 🇵🇹 **Portuguese**: `pt` - Contributed by **[@LeoColman](https://github.com/LeoColman)**
|
||||
- 🇪🇸 **Spanish**: `es` - Contributed by **[@lu4t](https://github.com/lu4t)**
|
||||
- 🇸🇮 **Slovenian**: `sl` - Contributed by **[@UrekD](https://github.com/UrekD)**
|
||||
- 🇸🇪 **Swedish**: `sv` - Contributed by **[@BOZG](https://github.com/BOZG)**
|
||||
- 🇮🇹 **Italian**: `it` - Contributed by **[@alexdelprete](https://github.com/alexdelprete)**
|
||||
- 🇵🇹 **Portuguese**: `pt` - Contributed by **[@LeoColman](https://github.com/LeoColman)**
|
||||
- 🇷🇺 **Russian**: `ru` - Contributed by Anon
|
||||
- 🇹🇼 **Traditional Chinese**: `zh-TW` - Contributed by **[@stanly0726](https://github.com/stanly0726)**
|
||||
- 🇷🇺 **Russian**: `ru`
|
||||
- 🇦🇪 **Arabic**: `ar`
|
||||
- 🇮🇳 **Hindi**: `hi`
|
||||
- 🇯🇵 **Japanese**: `ja`
|
||||
|
@ -146,6 +146,8 @@ appConfig:
|
||||
clientId: 'dashy'
|
||||
```
|
||||
|
||||
Note that if you are using Keycloak V 17 or older, you will also need to set `legacySupport: true` (also under `appConfig.auth.keycloak`). This is because the API endpoint was updated in later versions.
|
||||
|
||||
### 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.
|
||||
|
@ -163,6 +163,7 @@ For more info, see the **[Authentication Docs](/docs/authentication.md)**
|
||||
**`serverUrl`** | `string` | Required | The URL (or URL/ IP + Port) where your keycloak server is running
|
||||
**`realm`** | `string` | Required | The name of the realm (must already be created) that you want to use
|
||||
**`clientId`** | `string` | Required | The Client ID of the client you created for use with Dashy
|
||||
**`legacySupport`** | `boolean` | _Optional_ | If using Keycloak 17 or older, then set this to `true`
|
||||
|
||||
**[⬆️ Back to Top](#configuring)**
|
||||
|
||||
|
@ -56,3 +56,43 @@ The following example shows creating a config, publishing it as a [Gist](https:/
|
||||
Only top-level fields supported by sub-pages are `pageInfo` and `sections`. The `appConfig` and `pages` will always be inherited from your main `conf.yml` file. Other than that, sub-pages behave exactly the same as your default view, and can contain sections, items, widgets and page info like nav links, title and logo.
|
||||
|
||||
Note that since page paths are required by the router, they are set at build-time, not run-time, and so a rebuild (happens automatically) is required for changes to page paths to take effect (this only applies to changes to the `pages` array, rebuild isn't required for editing page content).
|
||||
|
||||
|
||||
## Sub-Items
|
||||
|
||||
A normal section will contain zero or more items, for example:
|
||||
|
||||
```yaml
|
||||
- name: Coding
|
||||
icon: far fa-code
|
||||
items:
|
||||
- title: GitHub
|
||||
url: https://github.com/
|
||||
- title: StackOverflow
|
||||
url: http://stackoverflow.com/
|
||||
```
|
||||
|
||||
But items can also be grouped together, referred to as sub-items. This is useful for a group of less frequently used items, which you don't want to take up too much space, or for action buttons (_coming soon_).
|
||||
|
||||
Item groups may also have an optional title.
|
||||
|
||||
```yaml
|
||||
- name: Coding
|
||||
icon: far fa-code
|
||||
items:
|
||||
- title: Normal Item 1
|
||||
- title: Normal Item 2
|
||||
- subItems:
|
||||
- title: JavaScript
|
||||
url: https://developer.mozilla.org
|
||||
icon: si-javascript
|
||||
- title: TypeScript
|
||||
url: https://www.typescriptlang.org/docs
|
||||
icon: si-typescript
|
||||
- title: Svelt
|
||||
url: https://svelte.dev/docs
|
||||
icon: si-svelte
|
||||
- title: Go
|
||||
url: https://go.dev/doc
|
||||
icon: si-go
|
||||
```
|
||||
|
@ -30,12 +30,13 @@
|
||||
- [Diagnosing Widget Errors](#widget-errors)
|
||||
- [Fixing Widget CORS Errors](#widget-cors-errors)
|
||||
- [Weather Forecast Widget 401](#weather-forecast-widget-401)
|
||||
- [Keycloak Redirect Error](#keycloak-redirect-error)
|
||||
- [Font Awesome Icons not Displaying](#font-awesome-icons-not-displaying)
|
||||
- [How-To Open Browser Console](#how-to-open-browser-console)
|
||||
- [Git Contributions not Displaying](#git-contributions-not-displaying)
|
||||
|
||||
|
||||
---
|
||||
|
||||
## `Refused to Connect` in Modal or Workspace View
|
||||
|
||||
This is not an issue with Dashy, but instead caused by the target app preventing direct access through embedded elements.
|
||||
@ -214,6 +215,13 @@ You should also ensure that Keycloak is correctly configured, with a user, realm
|
||||
|
||||
For more details on how to set headers, see the [Example Headers](/docs/management.md#setting-headers) in the management docs, or reference the documentation for your proxy.
|
||||
|
||||
If you're running in Kubernetes, you will need to enable CORS ingress rules, see [docs](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#enable-cors), e.g:
|
||||
|
||||
```
|
||||
nginx.ingress.kubernetes.io/cors-allow-origin: "https://dashy.example.com"
|
||||
nginx.ingress.kubernetes.io/enable-cors: "true"
|
||||
```
|
||||
|
||||
See also: #479, #409, #507, #491, #341, #520
|
||||
|
||||
---
|
||||
@ -412,22 +420,19 @@ A future update will be pushed out, to use a free weather forecasting API.
|
||||
|
||||
---
|
||||
|
||||
## Keycloak Redirect Error
|
||||
## Font Awesome Icons not Displaying
|
||||
|
||||
Firstly, ensure that in your Keycloak instance you have populated the Valid Redirect URIs field ([screenshot](https://user-images.githubusercontent.com/1862727/148599768-db4ee4f8-72c5-402d-8f00-051d999e6267.png)) with the URL to your Dashy instance.
|
||||
Usually, Font Awesome will be automatically enabled if one or more of your icons are using Font-Awesome. If this is not happening, then you can always manually enable (or disable) Font Awesome by setting: [`appConfig`](/docs/configuring.md#appconfig-optional).`enableFontAwesome` to `true`.
|
||||
|
||||
You may need to specify CORS headers on your Keycloak instance, to allow requests coming from Dashy, e.g:
|
||||
If you are trying to use a premium icon, then you must have a [Pro License](https://fontawesome.com/plans). You'll then need to specify your Pro plan API key under `appConfig.fontAwesomeKey`. You can find this key, by logging into your FA account, navigate to Account → [Kits](https://fontawesome.com/kits) → New Kit → Copy Kit Code. The code is a 10-digit alpha-numeric code, and is also visible within the new kit's URL, for example: `81e48ce079`.
|
||||
|
||||
```
|
||||
Access-Control-Allow-Origin: https://dashy.example.com
|
||||
```
|
||||
Be sure that you're specifying the icon category and name correctly. You're icon should look be `[category] fa-[icon-name]`. The following categories are supported: `far` _(regular)_, `fas` _(solid)_, `fal`_(light)_, `fad` _(duo-tone)_ and `fab`_(brands)_. With the exception of brands, you'll usually want all your icons to be in from same category, so they look uniform.
|
||||
|
||||
If you're running in Kubernetes, you will need to enable CORS ingress rules, see [docs](https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#enable-cors), e.g:
|
||||
Ensure the icon you are trying to use, is available within [FontAwesome Version 5](https://fontawesome.com/v5/search).
|
||||
|
||||
```
|
||||
nginx.ingress.kubernetes.io/cors-allow-origin: "https://dashy.example.com"
|
||||
nginx.ingress.kubernetes.io/enable-cors: "true"
|
||||
```
|
||||
Examples: `fab fa-raspberry-pi`, `fas fa-database`, `fas fa-server`, `fas fa-ethernet`
|
||||
|
||||
Finally, check the [browser console](#how-to-open-browser-console) for any error messages, and raise a ticket if the issue persists.
|
||||
|
||||
---
|
||||
|
||||
|
@ -97,6 +97,7 @@ A simple, live-updating time and date widget with time-zone support. All fields
|
||||
**`format`** | `string` | _Optional_ | A country code for displaying the date and time in local format.<br>Specified as `[ISO-3166]-[ISO-639]`, for example: `en-AU`. See [here](https://www.fincher.org/Utilities/CountryLanguageList.shtml) for a full list of locales. Defaults to the browser / device's region
|
||||
**`customCityName`** | `string` | _Optional_ | By default the city from the time-zone is shown, but setting this value will override that text
|
||||
**`hideDate`** | `boolean` | _Optional_ | If set to `true`, the date and city will not be shown. Defaults to `false`
|
||||
**`hideSeconds`** | `boolean` | _Optional_ | If set to `true`, seconds will not be shown. Defaults to `false`
|
||||
|
||||
##### Example
|
||||
|
||||
@ -713,6 +714,7 @@ Show recent scores and upcoming matches from your favourite sports team. Data is
|
||||
**`pastOrFuture`** | `string` | __Optional__ | Set to `past` to show scores for recent games, or `future` to show upcoming games. Defaults to `past`. You can change this within the UI
|
||||
**`apiKey`** | `string` | __Optional__ | Optionally specify your API key, which you can sign up for at [TheSportsDB.com](https://www.thesportsdb.com/)
|
||||
**`limit`** | `number` | __Optional__ | To limit output to a certain number of matches, defaults to `15`
|
||||
**`hideImage`** | `boolean` | __Optional__ | Set to `true` to not render the team / match banner image, defaults to `false`
|
||||
|
||||
##### Example
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Dashy",
|
||||
"version": "2.0.8",
|
||||
"version": "2.0.9",
|
||||
"license": "MIT",
|
||||
"main": "server",
|
||||
"author": "Alicia Sykes <alicia@omg.lol> (https://aliciasykes.com)",
|
||||
@ -47,6 +47,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@architect/sandbox": "^4.5.2",
|
||||
"@babel/preset-env": "^7.17.10",
|
||||
"@vue/cli-plugin-babel": "^4.5.15",
|
||||
"@vue/cli-plugin-eslint": "^4.5.15",
|
||||
"@vue/cli-plugin-pwa": "^4.5.15",
|
||||
@ -87,6 +88,12 @@
|
||||
"parser": "babel-eslint"
|
||||
}
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
"@vue/app",
|
||||
"@babel/preset-env"
|
||||
]
|
||||
},
|
||||
"postcss": {
|
||||
"plugins": {
|
||||
"autoprefixer": {}
|
||||
|
1
src/assets/interface-icons/section-expand-collapse.svg
Normal file
1
src/assets/interface-icons/section-expand-collapse.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M296 391.1c-6.141 0-12.28 2.344-16.97 7.031L248 430.1v-86.06c0-13.25-10.75-24-24-24s-24 10.75-24 24v86.06l-31.03-31.03C164.3 394.3 158.1 391.1 152 391.1c-12.82 0-24 10.33-24 24c0 6.141 2.344 12.28 7.031 16.97l72 72.01C209.6 507.5 215.5 512 224 512s14.4-4.461 16.97-7.031l72-72.01C317.7 428.3 320 422.1 320 415.1C320 402.3 308.8 391.1 296 391.1zM152 119.1c6.141 0 12.28-2.344 16.97-7.031L200 81.91v86.07c0 13.25 10.75 24 24 24s24-10.75 24-24V81.91l31.03 31.03C283.7 117.6 289.8 119.1 296 119.1c18.79 0 24-17.2 24-23.1c0-6.141-2.344-12.28-7.031-16.97l-72-72.01C234.9 .9766 227.7 0 223.1 0C220.3 0 213.1 .9687 207 7l-72 72.01C130.3 83.7 128 89.84 128 95.98C128 109.7 139.2 119.1 152 119.1zM424 232H24C10.75 232 0 242.7 0 255.1S10.75 280 24 280h400c13.25 0 24-10.76 24-24.01S437.3 232 424 232z"/></svg>
|
After (image error) Size: 1.0 KiB |
@ -195,6 +195,7 @@
|
||||
"section": {
|
||||
"open-section": "Open Section",
|
||||
"edit-section": "Edit",
|
||||
"expand-collapse": "Expand / Collapse",
|
||||
"move-section": "Move To",
|
||||
"remove-section": "Remove"
|
||||
}
|
||||
|
@ -1,14 +1,16 @@
|
||||
<template>
|
||||
<div
|
||||
:class="`collapsable ${rowColSpanClass} ${collapseClass} ${!cutToHeight ? 'full-height' : ''}`"
|
||||
v-bind:class="[
|
||||
{ 'is-open': isExpanded, 'full-height': cutToHeight },
|
||||
`collapsable ${rowColSpanClass}`
|
||||
]"
|
||||
:style="`${color ? 'background: '+color : ''}; ${sanitizeCustomStyles(customStyles)};`"
|
||||
>
|
||||
<input
|
||||
:id="sectionKey"
|
||||
class="toggle"
|
||||
type="checkbox"
|
||||
:checked="isExpanded"
|
||||
@change="collapseChanged"
|
||||
v-model="checkboxState"
|
||||
tabIndex="-1"
|
||||
>
|
||||
<label :for="sectionKey" class="lbl-toggle" tabindex="-1"
|
||||
@ -72,14 +74,42 @@ export default {
|
||||
const { rows, cols, checkSpanNum } = this;
|
||||
return `${checkSpanNum(cols, 'col')} ${checkSpanNum(rows, 'row')}`;
|
||||
},
|
||||
/* Used to fetch initial collapse state, and set new collapse state on change */
|
||||
isExpanded: {
|
||||
get() {
|
||||
if (this.collapsed !== undefined) return !this.collapsed;
|
||||
const collapseStateObject = this.locallyStoredCollapseStates();
|
||||
if (collapseStateObject[this.uniqueKey] !== undefined) {
|
||||
return collapseStateObject[this.uniqueKey];
|
||||
}
|
||||
return true;
|
||||
},
|
||||
set(newState) {
|
||||
const collapseState = this.locallyStoredCollapseStates();
|
||||
collapseState[this.uniqueKey] = newState;
|
||||
localStorage.setItem(localStorageKeys.COLLAPSE_STATE, JSON.stringify(collapseState));
|
||||
},
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
isExpanded: false,
|
||||
checkboxState: true,
|
||||
}),
|
||||
mounted() {
|
||||
this.isExpanded = this.getCollapseState();
|
||||
this.checkboxState = this.isExpanded;
|
||||
},
|
||||
watch: {
|
||||
checkboxState(newState) {
|
||||
this.isExpanded = newState;
|
||||
},
|
||||
uniqueKey() {
|
||||
this.checkboxState = this.isExpanded;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/* Either expand or collapse section, based on it's current state */
|
||||
toggle() {
|
||||
this.checkboxState = !this.checkboxState;
|
||||
},
|
||||
/* Check that row & column span is valid, and not over the max */
|
||||
checkSpanNum(span, classPrefix) {
|
||||
const maxSpan = 6;
|
||||
@ -92,44 +122,14 @@ export default {
|
||||
return userCss ? userCss.replace(/[^a-zA-Z0-9- :;.]/g, '') : '';
|
||||
},
|
||||
/* Returns local storage collapse state data, and if not yet set then initialized is */
|
||||
initialiseStorage() {
|
||||
const storageKey = localStorageKeys.COLLAPSE_STATE;
|
||||
/* Initialize function will create and set a blank object to storage */
|
||||
const initStorage = () => localStorage.setItem(storageKey, JSON.stringify({}));
|
||||
locallyStoredCollapseStates() {
|
||||
// If not yet set, then call initialize
|
||||
if (!localStorage[storageKey]) {
|
||||
initStorage();
|
||||
if (!localStorage[localStorageKeys.COLLAPSE_STATE]) {
|
||||
localStorage.setItem(localStorageKeys.COLLAPSE_STATE, JSON.stringify({}));
|
||||
return {};
|
||||
}
|
||||
// Otherwise, return value of local storage
|
||||
return JSON.parse(localStorage[storageKey]);
|
||||
},
|
||||
/* If specified by user, return conf collapse state, otherwise check local storage */
|
||||
getCollapseState() {
|
||||
if (this.collapsed !== undefined) return !this.collapsed; // Check users config
|
||||
const collapseStateObject = this.initialiseStorage(); // Check local storage
|
||||
if (collapseStateObject[this.uniqueKey] !== undefined) {
|
||||
return collapseStateObject[this.uniqueKey];
|
||||
}
|
||||
// Nothing specified, return Open
|
||||
return true;
|
||||
},
|
||||
/* When section collapsed, update local storage, to remember for next time */
|
||||
setCollapseState(id, newState) {
|
||||
// Get the current localstorage collapse state object
|
||||
const collapseState = JSON.parse(localStorage[localStorageKeys.COLLAPSE_STATE]);
|
||||
// Add the new state to it
|
||||
collapseState[id] = newState;
|
||||
// Stringify, and set the new object into local storage
|
||||
localStorage.setItem(localStorageKeys.COLLAPSE_STATE, JSON.stringify(collapseState));
|
||||
},
|
||||
/* Called when collapse state changes, trigger local storage update if needed */
|
||||
collapseChanged(whatChanged) {
|
||||
this.isExpanded = whatChanged.srcElement.checked;
|
||||
if (this.collapseState === undefined) { // Only run, if user hasn't manually set prop
|
||||
this.initialiseStorage();
|
||||
this.setCollapseState(this.uniqueKey.toString(), this.isExpanded);
|
||||
}
|
||||
return JSON.parse(localStorage[localStorageKeys.COLLAPSE_STATE]);
|
||||
},
|
||||
openEditModal() {
|
||||
this.$emit('openEditSection');
|
||||
@ -216,7 +216,8 @@ export default {
|
||||
vertical-align: middle;
|
||||
margin-right: .7rem;
|
||||
transform: translateY(-2px);
|
||||
transition: transform .2s ease-out;
|
||||
opacity: 0.3;
|
||||
transition: all 0.4s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,6 +255,16 @@ export default {
|
||||
top: 0.5rem;
|
||||
margin-left: 0.2rem;
|
||||
margin-right: 0.2rem;
|
||||
opacity: 0.3;
|
||||
transition: all 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
/* On section hover, set interface icons to full visible */
|
||||
&:hover {
|
||||
.edit-mode-item, label.lbl-toggle::before {
|
||||
opacity: 1;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
}
|
||||
|
||||
/* Makes sections fill available space */
|
||||
|
@ -94,6 +94,7 @@
|
||||
v-click-outside="closeContextMenu"
|
||||
@openEditSection="openEditSection"
|
||||
@navigateToSection="navigateToSection"
|
||||
@expandCollapseSection="expandCollapseSection"
|
||||
@removeSection="removeSection"
|
||||
/>
|
||||
</Collapsable>
|
||||
@ -250,6 +251,12 @@ export default {
|
||||
router.push({ path: `/home/${sectionIdentifier}` });
|
||||
this.closeContextMenu();
|
||||
},
|
||||
/* Toggle sections collapse state */
|
||||
expandCollapseSection() {
|
||||
const secElem = this.$refs[this.sectionRef];
|
||||
if (secElem) secElem.toggle();
|
||||
this.closeContextMenu();
|
||||
},
|
||||
/* Open the Section Edit Menu */
|
||||
openEditSection() {
|
||||
this.editMenuOpen = true;
|
||||
|
@ -12,6 +12,10 @@
|
||||
<EditIcon />
|
||||
<span>{{ $t('context-menus.section.edit-section') }}</span>
|
||||
</li>
|
||||
<li @click="expandCollapseSection">
|
||||
<ExpandCollapseIcon />
|
||||
<span>{{ $t('context-menus.section.expand-collapse') }}</span>
|
||||
</li>
|
||||
<li v-if="isEditMode" @click="removeSection">
|
||||
<BinIcon />
|
||||
<span>{{ $t('context-menus.section.remove-section') }}</span>
|
||||
@ -26,6 +30,7 @@
|
||||
import EditIcon from '@/assets/interface-icons/config-edit-json.svg';
|
||||
import BinIcon from '@/assets/interface-icons/interactive-editor-remove.svg';
|
||||
import SameTabOpenIcon from '@/assets/interface-icons/open-current-tab.svg';
|
||||
import ExpandCollapseIcon from '@/assets/interface-icons/section-expand-collapse.svg';
|
||||
|
||||
export default {
|
||||
name: 'ContextMenu',
|
||||
@ -33,6 +38,7 @@ export default {
|
||||
EditIcon,
|
||||
BinIcon,
|
||||
SameTabOpenIcon,
|
||||
ExpandCollapseIcon,
|
||||
},
|
||||
props: {
|
||||
posX: Number, // The X coordinate for positioning
|
||||
@ -56,6 +62,9 @@ export default {
|
||||
openEditSectionMenu() {
|
||||
this.$emit('openEditSection');
|
||||
},
|
||||
expandCollapseSection() {
|
||||
this.$emit('expandCollapseSection');
|
||||
},
|
||||
removeSection() {
|
||||
this.$emit('removeSection');
|
||||
},
|
||||
|
@ -20,7 +20,6 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<KeyboardShortcutInfo />
|
||||
<AppInfoModal />
|
||||
</section>
|
||||
</template>
|
||||
@ -32,7 +31,6 @@ import ThemeSelector from '@/components/Settings/ThemeSelector';
|
||||
import LayoutSelector from '@/components/Settings/LayoutSelector';
|
||||
import ItemSizeSelector from '@/components/Settings/ItemSizeSelector';
|
||||
import AuthButtons from '@/components/Settings/AuthButtons';
|
||||
import KeyboardShortcutInfo from '@/components/Settings/KeyboardShortcutInfo';
|
||||
import AppInfoModal from '@/components/Configuration/AppInfoModal';
|
||||
import IconOpen from '@/assets/interface-icons/config-open-settings.svg';
|
||||
import IconClose from '@/assets/interface-icons/config-close.svg';
|
||||
@ -57,7 +55,6 @@ export default {
|
||||
LayoutSelector,
|
||||
ItemSizeSelector,
|
||||
AuthButtons,
|
||||
KeyboardShortcutInfo,
|
||||
AppInfoModal,
|
||||
IconOpen,
|
||||
IconClose,
|
||||
|
@ -106,15 +106,17 @@ export default {
|
||||
mounted() {
|
||||
const initialTheme = this.getInitialTheme();
|
||||
this.selectedTheme = initialTheme;
|
||||
// Pass all user custom stylesheets to the themehelper
|
||||
const added = Object.keys(this.externalThemes).map(
|
||||
name => this.themeHelper.add(name, this.externalThemes[name]),
|
||||
);
|
||||
// Quicker loading, if the theme is local we can apply it immidiatley
|
||||
if (this.isThemeLocal(initialTheme)) {
|
||||
this.updateTheme(initialTheme);
|
||||
}
|
||||
|
||||
// If it's an external stylesheet, then wait for promise to resolve
|
||||
} else if (initialTheme !== Defaults.theme) {
|
||||
if (this.externalThemes && Object.entries(this.externalThemes).length > 0) {
|
||||
const added = Object.keys(this.externalThemes).map(
|
||||
name => this.themeHelper.add(name, this.externalThemes[name]),
|
||||
);
|
||||
// Once, added, then apply users initial theme
|
||||
Promise.all(added).then(() => {
|
||||
this.updateTheme(initialTheme);
|
||||
});
|
||||
|
@ -58,7 +58,7 @@ export default {
|
||||
if (!this.ipAddress) {
|
||||
this.getUsersIpAddress(); return;
|
||||
}
|
||||
this.defaultTimeout = 20000;
|
||||
this.defaultTimeout = 200000;
|
||||
const options = { Authorization: `Basic ${this.apiKey}` };
|
||||
this.makeRequest(this.endpoint, options).then(this.processData);
|
||||
},
|
||||
|
@ -36,6 +36,9 @@ export default {
|
||||
if (this.options.customCityName) return this.options.customCityName;
|
||||
return this.timeZone.split('/')[1].replaceAll('_', ' ');
|
||||
},
|
||||
showSeconds() {
|
||||
return !this.options.hideSeconds;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
update() {
|
||||
@ -48,7 +51,7 @@ export default {
|
||||
timeZone: this.timeZone,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
...(this.showSeconds && { second: 'numeric' }),
|
||||
}).format();
|
||||
},
|
||||
/* Get and format the date */
|
||||
|
@ -31,7 +31,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import WidgetMixin from '@/mixins/WidgetMixin';
|
||||
import { widgetApiEndpoints } from '@/utils/defaults';
|
||||
|
||||
@ -91,16 +90,7 @@ export default {
|
||||
methods: {
|
||||
/* Make GET request to Rss2Json */
|
||||
fetchData() {
|
||||
axios.get(this.endpoint)
|
||||
.then((response) => {
|
||||
this.processData(response.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.error('Unable to RSS feed', error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.finishLoading();
|
||||
});
|
||||
this.makeRequest(this.endpoint).then(this.processData);
|
||||
},
|
||||
/* Assign data variables to the returned data */
|
||||
processData(data) {
|
||||
|
@ -28,7 +28,7 @@
|
||||
</div>
|
||||
<div class="match-row" v-for="match in matches" :key="match.id">
|
||||
<!-- Banner Image -->
|
||||
<div class="match-thumbnail-wrap">
|
||||
<div class="match-thumbnail-wrap" v-if="!hideImage">
|
||||
<img :src="match.thumbnail" :alt="`${match.title} Banner Image`" class="match-thumbnail" />
|
||||
</div>
|
||||
<!-- Team Scores -->
|
||||
@ -101,6 +101,9 @@ export default {
|
||||
pastOrFuture() {
|
||||
return this.options.pastOrFuture || 'past';
|
||||
},
|
||||
hideImage() {
|
||||
return this.options.hideImage || false;
|
||||
},
|
||||
endpoint() {
|
||||
this.initiate();
|
||||
const endpoint = widgetApiEndpoints.sportsScores;
|
||||
|
@ -26,6 +26,9 @@ const HomeMixin = {
|
||||
modalOpen() {
|
||||
return this.$store.state.modalOpen;
|
||||
},
|
||||
pageId() {
|
||||
return (this.subPageInfo && this.subPageInfo.pageId) ? this.subPageInfo.pageId : 'home';
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
searchValue: '',
|
||||
|
@ -41,13 +41,13 @@ export default {
|
||||
size() {
|
||||
const validSizes = ['small', 'medium', 'large'];
|
||||
if (this.itemSize && validSizes.includes(this.itemSize)) return this.itemSize;
|
||||
return this.appConfig.iconSize || defaultSize;
|
||||
return this.$store.getters.iconSize || defaultSize;
|
||||
},
|
||||
/* Determines if user has enabled online status checks */
|
||||
enableStatusCheck() {
|
||||
const globalPref = this.appConfig.statusCheck || false;
|
||||
const itemPref = this.item.statusCheck || false;
|
||||
return itemPref || globalPref;
|
||||
const itemPref = this.item.statusCheck;
|
||||
return typeof itemPref === 'boolean' ? itemPref : globalPref;
|
||||
},
|
||||
/* Determine how often to re-fire status checks */
|
||||
statusCheckInterval() {
|
||||
|
@ -20,7 +20,7 @@ const WidgetMixin = {
|
||||
overrideUpdateInterval: null,
|
||||
disableLoader: false, // Prevent ever showing the loader
|
||||
updater: null, // Stores interval
|
||||
defaultTimeout: 1000,
|
||||
defaultTimeout: 10000,
|
||||
}),
|
||||
/* When component mounted, fetch initial data */
|
||||
mounted() {
|
||||
|
@ -11,7 +11,6 @@ import { Progress } from 'rsup-progress';
|
||||
|
||||
// Import views, that are not lazy-loaded
|
||||
import Home from '@/views/Home.vue';
|
||||
import ConfigAccumulator from '@/utils/ConfigAccumalator';
|
||||
|
||||
// Import helper functions, config data and defaults
|
||||
import { isAuthEnabled, isLoggedIn, isGuestAccessEnabled } from '@/utils/Auth';
|
||||
@ -19,7 +18,8 @@ import { makePageSlug, makePageName } from '@/utils/ConfigHelpers';
|
||||
import { metaTagData, startingView, routePaths } from '@/utils/defaults';
|
||||
import ErrorHandler from '@/utils/ErrorHandler';
|
||||
|
||||
import { pages } from '../public/conf.yml';
|
||||
// Import data from users conf file. Note that rebuild is required for this to update.
|
||||
import { pages, pageInfo, appConfig } from '../public/conf.yml';
|
||||
|
||||
Vue.use(Router);
|
||||
const progress = new Progress({ color: 'var(--progress-bar)' });
|
||||
@ -32,17 +32,6 @@ const isAuthenticated = () => {
|
||||
return (!authEnabled || userLoggedIn || guestEnabled);
|
||||
};
|
||||
|
||||
const getConfig = () => {
|
||||
const Accumulator = new ConfigAccumulator();
|
||||
return {
|
||||
appConfig: Accumulator.appConfig(),
|
||||
pageInfo: Accumulator.pageInfo(),
|
||||
pages: Accumulator.pages(),
|
||||
};
|
||||
};
|
||||
|
||||
const { appConfig, pageInfo } = getConfig();
|
||||
|
||||
/* Get the users chosen starting view from app config, or return default */
|
||||
const getStartingView = () => appConfig.startingView || startingView;
|
||||
|
||||
@ -61,7 +50,7 @@ const getStartingComponent = () => {
|
||||
|
||||
/* Returns the meta tags for each route */
|
||||
const makeMetaTags = (defaultTitle) => ({
|
||||
title: pageInfo.title || defaultTitle,
|
||||
title: pageInfo && pageInfo.title ? pageInfo.title : defaultTitle,
|
||||
metaTags: metaTagData,
|
||||
});
|
||||
|
||||
@ -73,10 +62,12 @@ const makeSubConfigPath = (rawPath) => {
|
||||
|
||||
/* For each additional config file, create routes for home, minimal and workspace views */
|
||||
const makeMultiPageRoutes = (userPages) => {
|
||||
if (!userPages) return [];
|
||||
// If no multi pages specified, or is not array, then return nothing
|
||||
if (!userPages || !Array.isArray(userPages)) return [];
|
||||
const multiPageRoutes = [];
|
||||
// For each user page, create an additional route
|
||||
userPages.forEach((page) => {
|
||||
if (!page.name || !page.path) {
|
||||
if (!page.name || !page.path) { // Sumin not right, show warning
|
||||
ErrorHandler('Additional pages must have both a `name` and `path`');
|
||||
}
|
||||
// Props to be passed to home mixin
|
||||
|
@ -89,7 +89,7 @@ const store = new Vuex.Store({
|
||||
perms.allowSaveLocally = false;
|
||||
}
|
||||
// Disable saving changes to disk, only
|
||||
if (appConfig.preventWriteToDisk || !isUserAdmin) {
|
||||
if (appConfig.preventWriteToDisk || !isUserAdmin()) {
|
||||
perms.allowWriteToDisk = false;
|
||||
}
|
||||
// Legacy Option: Will be removed in V 2.1.0
|
||||
|
@ -1357,7 +1357,7 @@ html[data-theme="dashy-docs"] {
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme="adventure"] {
|
||||
html[data-theme="adventure"], html[data-theme="adventure-basic"] {
|
||||
// Main colors
|
||||
--primary: #ffffffe6;
|
||||
--background: #0b1021;
|
||||
@ -1376,11 +1376,6 @@ html[data-theme="adventure"] {
|
||||
--item-group-shadow: none;
|
||||
--item-group-background: none;
|
||||
--item-group-outer-background: none;
|
||||
// Background Image
|
||||
body {
|
||||
background: url('https://i.ibb.co/wdqSsGh/adventure-bg.jpg');
|
||||
background-size: cover;
|
||||
}
|
||||
// Remove background from certain components
|
||||
div.home, div.options-outer, div.options-container, section.filter-container,
|
||||
section.settings-outer, div.show-hide-container.hide-btn, div.show-hide-container.show-btn {
|
||||
@ -1401,6 +1396,14 @@ html[data-theme="adventure"] {
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme="adventure"] {
|
||||
// Background Image
|
||||
body {
|
||||
background: url('https://i.ibb.co/wdqSsGh/adventure-bg.jpg');
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
|
||||
html[data-theme="color-block"] {
|
||||
// Main colors
|
||||
--primary: #E94560;
|
||||
|
@ -40,11 +40,11 @@ export default class ConfigAccumulator {
|
||||
usersAppConfig = appConfigFile;
|
||||
}
|
||||
// Some settings have their own local storage keys, apply them here
|
||||
usersAppConfig.layout = appConfigFile.layout
|
||||
|| localStorage[localStorageKeys.LAYOUT_ORIENTATION]
|
||||
usersAppConfig.layout = localStorage[localStorageKeys.LAYOUT_ORIENTATION]
|
||||
|| appConfigFile.layout
|
||||
|| defaultLayout;
|
||||
usersAppConfig.iconSize = appConfigFile.iconSize
|
||||
|| localStorage[localStorageKeys.ICON_SIZE]
|
||||
usersAppConfig.iconSize = localStorage[localStorageKeys.ICON_SIZE]
|
||||
|| appConfigFile.iconSize
|
||||
|| defaultIconSize;
|
||||
// Don't let users modify users locally
|
||||
if (appConfigFile.auth) usersAppConfig.auth = appConfigFile.auth;
|
||||
@ -60,7 +60,7 @@ export default class ConfigAccumulator {
|
||||
try { localPageInfo = JSON.parse(localStorage[localStorageKeys.PAGE_INFO]); }
|
||||
catch (e) { ErrorHandler('Malformed pageInfo data in local storage'); }
|
||||
}
|
||||
const filePageInfo = this.conf ? this.conf.pageInfo || {} : {};
|
||||
const filePageInfo = (this.conf && this.conf.pageInfo) ? this.conf.pageInfo : {};
|
||||
return { ...defaultPageInfo, ...filePageInfo, ...localPageInfo };
|
||||
}
|
||||
|
||||
|
@ -322,7 +322,7 @@
|
||||
"description": "If true, font-awesome will be loaded, if false they will not be. If left empty, icons will only be loaded if needed"
|
||||
},
|
||||
"enableMaterialDesignIcons": {
|
||||
"title": "Enable Font-Awesome?",
|
||||
"title": "Enable Material Design Icons?",
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "If true, material-design-icons will be loaded, if false they will not be. If left empty, icons will only be loaded if needed"
|
||||
@ -475,6 +475,11 @@
|
||||
"title": "Client ID",
|
||||
"type": "string",
|
||||
"description": "The Client ID of the client you created for use with Dashy"
|
||||
},
|
||||
"legacySupport": {
|
||||
"title": "Legacy Support",
|
||||
"type": "boolean",
|
||||
"description": "If using Keycloak 17 or older, then set this to true"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -849,6 +854,15 @@
|
||||
"title": "Item ID",
|
||||
"type": "string",
|
||||
"description": "Unique ID for each item. Generated automatically, shouldn't need to be set manually."
|
||||
},
|
||||
"subItems": {
|
||||
"title": "Sub-Items",
|
||||
"type": "array",
|
||||
"description": "List of sub-items",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,12 @@ const getAppConfig = () => {
|
||||
class KeycloakAuth {
|
||||
constructor() {
|
||||
const { auth } = getAppConfig();
|
||||
const { serverUrl, realm, clientId } = auth.keycloak;
|
||||
const {
|
||||
serverUrl, realm, clientId, legacySupport,
|
||||
} = auth.keycloak;
|
||||
const url = legacySupport ? `${serverUrl}/auth` : serverUrl;
|
||||
const initOptions = {
|
||||
url: `${serverUrl}`, realm, clientId, onLoad: 'login-required',
|
||||
url, realm, clientId, onLoad: 'login-required',
|
||||
};
|
||||
|
||||
this.keycloakClient = Keycloak(initOptions);
|
||||
|
@ -82,6 +82,7 @@ module.exports = {
|
||||
'material-dark-original',
|
||||
'high-contrast-dark',
|
||||
'high-contrast-light',
|
||||
'adventure-basic',
|
||||
'basic',
|
||||
],
|
||||
/* Default color options for the theme configurator swatches */
|
||||
|
@ -34,7 +34,7 @@
|
||||
:title="section.name"
|
||||
:icon="section.icon || undefined"
|
||||
:displayData="getDisplayData(section)"
|
||||
:groupId="`section-${index}`"
|
||||
:groupId="`${pageId}-section-${index}`"
|
||||
:items="filterTiles(section.items, searchValue)"
|
||||
:widgets="section.widgets"
|
||||
:searchTerm="searchValue"
|
||||
|
Loading…
x
Reference in New Issue
Block a user