Skip to content

Commit 40d1236

Browse files
authored
🔀 Merge pull request #617 from Lissy93/FEATURE/multi-page-support-2
[FEATURE] Multi-Page Support
2 parents e454f6e + c87e13c commit 40d1236

38 files changed

+887
-259
lines changed

.github/CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## ✨ 2.0.8 Adds Multi-Page Support [PR #617](https://github.com/Lissy93/dashy/pull/617)
4+
- Adds support for multiple pages per-dashboard
5+
- Adds new attribute at root of main config file: `pages`
6+
- Updates router and nav-bar to automatically create paths for both local and remote configs
7+
38
## ⚡️ 2.0.7 Improves handling of Sections and Items [PR #595](https://github.com/Lissy93/dashy/pull/595)
49
- Adds functionality for sub-items / item-groups
510
- Creates an item mixin, for reusing functionality

.github/LATEST_CHANGELOG.md

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
## ⚡️ 2.0.7 Improves handling of Sections and Items [PR #595](https://github.com/Lissy93/dashy/pull/595)
2-
- Adds functionality for sub-items / item-groups
3-
- Creates an item mixin, for reusing functionality
4-
- Item width calculated based on parent section width
5-
- Improved mobile support, long-press for right-click
6-
- Adds 2 new themes (`lissy` and `charry-blossom`)
7-
- Adds 2 new widgets (`mullvad-status`, and `blacklist-check`)
1+
## ✨ 2.0.8 Adds Multi-Page Support [PR #617](https://github.com/Lissy93/dashy/pull/617)
2+
- Adds support for multiple pages per-dashboard
3+
- Adds new attribute at root of main config file: `pages`
4+
- Updates router and nav-bar to automatically create paths for both local and remote configs

README.md

+37-8
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
- [⚙️ Config Editor](#config-editor-)
4949
- [☁ Cloud Backup & Sync](#cloud-backup--sync-)
5050
- [🌎 Language Switching](#language-switching-)
51+
- [📃 Multi-Page Support](#multi-page-support-)
5152
- **Community**
5253
- [📊 System Requirements](#system-requirements-)
5354
- [🙋‍♀️ Support](#support-)
@@ -64,18 +65,18 @@
6465
</details>
6566

6667
## Features 🌈
67-
68+
- 📃 Support for multiple pages
69+
- 🚦 Real-time status monitoring for each of your apps/links
70+
- 📊 Use widgets to display info and dynamic content from self-hosted services
6871
- 🔎 Instant search by name, domain, or tags + customizable hotkeys & keyboard shortcuts
6972
- 🎨 Many built-in color themes, with UI color editor and support for custom CSS
7073
- 🧸 Many icon options - Font-Awesome, homelab icons, auto-fetching Favicon, images, emojis, etc.
71-
- 🚦 Status monitoring for each of your apps/links for basic availability and uptime checking
72-
- 📊 Use widgets to display info and dynamic content from self-hosted services
7374
- 💂 Optional authentication with multi-user access, configurable privileges, and SSO support
7475
- 🌎 Multi-language support, with 10+ human-translated languages, and more on the way
7576
- ☁ Optional, encrypted, free off-site cloud backup and restore feature available
7677
- 💼 A workspace view, for easily switching between multiple apps simultaneously
7778
- 🛩️ A minimal view, for use as a fast-loading browser Startpage
78-
- 🖱️ Choose app launch method, either new tab, same tab, a pop-up modal, or in the workspace view
79+
- 🖱️ Choose app launch methods: new tab, same tab, clipboard, pop-up modal, or open in workspace view
7980
- 📏 Customizable layout, sizes, text, component visibility, sort order, behavior, etc.
8081
- 🖼️ Options for a full-screen background image, custom nav-bar links, HTML footer, title, etc.
8182
- 🚀 Easy to setup with Docker, or on bare metal, or with 1-Click cloud deployment
@@ -413,11 +414,11 @@ Dashy supports multiple languages and locales. When available, your language sho
413414
- 🇸🇮 **Slovenian**: `sl` - Contributed by **[@UrekD](https://github.com/UrekD)**
414415
- 🇸🇪 **Swedish**: `sv` - Contributed by **[@BOZG](https://github.com/BOZG)**
415416
- 🇮🇹 **Italian**: `it` - Contributed by **[@alexdelprete](https://github.com/alexdelprete)**
416-
- 🇵🇹 **Portuguese**: `pt` - Machine Translated *(awaiting human review)*
417+
- 🇵🇹 **Portuguese**: `pt` - Contributed by **[@LeoColman](https://github.com/LeoColman)**
417418
- 🇷🇺 **Russian**: `ru` - Contributed by Anon
418-
- 🇦🇪 **Arabic**: `ar` - Contributed by Anon
419-
- 🇮🇳 **Hindi**: `hi` - Contributed by Anon
420-
- 🇯🇵 **Japanese**: `ja` - Contributed by Anon
419+
- 🇦🇪 **Arabic**: `ar`
420+
- 🇮🇳 **Hindi**: `hi`
421+
- 🇯🇵 **Japanese**: `ja`
421422

422423
#### Add your Language
423424
I would love Dashy to be available to everyone without language being a barrier to entry. If you've got a few minutes to spare, consider adding translations for your language. It's a quick task, and all text is in [a single JSON file](https://github.com/Lissy93/dashy/tree/master/src/assets/locales). Since any missing text will fall back to English, you don't need to translate it all.
@@ -426,6 +427,34 @@ I would love Dashy to be available to everyone without language being a barrier
426427

427428
---
428429

430+
## Multi-Page Support 📃
431+
432+
> For full multi-page documentation, see: [**Pages & Sections**](./docs/pages-and-sections.md)
433+
434+
Within your dashboard, you can have as many sub-pages as you require. To load additional pages, specify a name, and path to a config file under `pages`. The config file can be either local (stored in `/public`), or remote (located anywhere accessible).
435+
436+
```yaml
437+
pages:
438+
- name: Networking Services
439+
path: 'networking.yml'
440+
- name: Work Stuff
441+
path: 'work.yml'
442+
```
443+
444+
Or
445+
446+
```yaml
447+
pages:
448+
- name: Getting Started
449+
path: 'https://snippet.host/tvcw/raw'
450+
- name: Homelab
451+
path: 'https://snippet.host/tetp/raw'
452+
- name: Browser Startpage
453+
path: 'https://snippet.host/zcom/raw'
454+
```
455+
456+
---
457+
429458
## System Requirements 📊
430459

431460
If running on bare metal, Dashy requires [Node](https://nodejs.org/en/) V 16.0.0 or later, LTS (16.13.2) is recommended.

docs/configuring.md

+11
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ The following file provides a reference of all supported configuration options.
2727

2828
- [**`pageInfo`**](#pageinfo) - Header text, footer, title, navigation, etc
2929
- [`navLinks`](#pageinfonavlinks-optional) - Links to display in the navigation bar
30+
- [**`pages`**](#pages-optional) - List of additional config files, for multi-page dashboards
3031
- [**`appConfig`**](#appconfig-optional) - Main application settings
3132
- [`webSearch`](#appconfigwebsearch-optional) - Configure web search engine options
3233
- [`hideComponents`](#appconfighidecomponents-optional) - Show/ hide page components
@@ -56,6 +57,7 @@ The following file provides a reference of all supported configuration options.
5657
**`pageInfo`** | `object` | Required | Basic meta data like title, description, nav bar links, footer text. See [`pageInfo`](#pageinfo)
5758
**`appConfig`** | `object` | _Optional_ | Settings related to how the app functions, including API keys and global styles. See [`appConfig`](#appconfig-optional)
5859
**`sections`** | `array` | Required | An array of sections, each containing an array of items, which will be displayed as links. See [`section`](#section)
60+
**`pages`** | `array` | _Optional_ | An array additional config files, used for multi-page dashboards. See [`pages`](#pages-optional)
5961

6062
**[⬆️ Back to Top](#configuring)**
6163

@@ -81,6 +83,15 @@ The following file provides a reference of all supported configuration options.
8183

8284
**[⬆️ Back to Top](#configuring)**
8385

86+
### `pages[]` _(optional)_
87+
88+
**Field** | **Type** | **Required**| **Description**
89+
--- | --- | --- | ---
90+
**`name`** | `string` | Required | A unique name for that page
91+
**`path`** | `string` | Required | The path (local or remote) to the config file to use.<br>For files located within `/public`, you only need to specify filename, for externally hosted files you must include the full URL
92+
93+
**[⬆️ Back to Top](#configuring)**
94+
8495
### `appConfig` _(optional)_
8596

8697
**Field** | **Type** | **Required**| **Description**

docs/pages-and-sections.md

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Pages and Sections
2+
3+
## Multi-Page Support
4+
5+
You can have additional pages within your dashboard, with each having it's own config file. The config files for sub-pages can either be stored locally, or hosted separately. A link to each additional page will be displayed in the navigation bar.
6+
7+
You can edit additional pages using the interactive editor, exactly the same was as your primary page (so long as it's local). But please save changes to one page, before you start editing the next.
8+
9+
### Using Local Sub-Pages
10+
11+
To get started, create a new `.yml` config file for your sub-page, placing it within `/app/public`. Then within your primary `conf.yml`, choose a name, and specify the path to the new file.
12+
13+
For example:
14+
15+
```yaml
16+
pages:
17+
- name: Networking Services
18+
path: 'networking.yml'
19+
- name: Work Stuff
20+
path: 'work.yml'
21+
```
22+
23+
If you're sub-page is located within `/app/public`, then you only need to specify the filename, but if it's anywhere else, then the full path is required.
24+
25+
### Using Remote Sub-Pages
26+
27+
Config files don't need to be local, you can store them anywhere, and data will be imported as sub-pages on page load.
28+
29+
For example:
30+
31+
```yaml
32+
pages:
33+
- name: Getting Started
34+
path: 'https://snippet.host/tvcw/raw'
35+
- name: Homelab
36+
path: 'https://snippet.host/tetp/raw'
37+
- name: Browser Startpage
38+
path: 'https://snippet.host/zcom/raw'
39+
```
40+
41+
There are many options of how this can be used. You could store your config within a Git repository, in order to easily track and rollback changes. Or host your config on your NAS, to have it backed up with the rest of your files. Or use a hosted paste service, for example [snippet.host](https://snippet.host/), which supports never-expiring CORS-enabled pastes, which can also be edited later.
42+
43+
You will obviously not be able to write updates to remote configs directly through the UI editor, but you can still make and preview changes, then use the export menu to get a copy of the new config, which can then be pasted to the remote source manually.
44+
The config file must, of course be accessible from within Dashy. If your config contains sensitive info (like API keys, credentials, secret URLs, etc), take care not to expose it to the internet.
45+
46+
The following example shows creating a config, publishing it as a [Gist](https://gist.github.com/), copying the URL to the raw file, and using it within your dashboard.
47+
48+
<p align="center">
49+
<img width="700" alt="Public config in a gist demo"
50+
src="https://i.ibb.co/55jm3LG/how-to-use-remote-config-sub-page.gif"
51+
/>
52+
</p>
53+
54+
### Restrictions
55+
56+
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.
57+
58+
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).

docs/readme.md

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
- [Backup & Restore](/docs/backup-restore.md) - Guide to backing up config with Dashy's cloud sync feature
2222
- [Icons](/docs/icons.md) - Outline of all available icon types for sections and items, with examples
2323
- [Language Switching](/docs/multi-language-support.md) - Details on how to switch language, or add a new locale
24+
- [Pages and Sections](/docs/pages-and-sections.md) - Multi-page support, sections, items and sub-items
2425
- [Status Indicators](/docs/status-indicators.md) - Using Dashy to monitor uptime and status of your apps
2526
- [Searching & Shortcuts](/docs/searching.md) - Searching, launching methods + keyboard shortcuts
2627
- [Theming](/docs/theming.md) - Complete guide to applying, writing and modifying themes + styles

docs/theming.md

+10
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,16 @@ Custom CSS can be developed, tested and applied directly through the UI. Althoug
6969

7070
This can be done from the Config menu (spanner icon in the top-right), under the Custom Styles tab. This is then associated with `appConfig.customCss` in local storage. Styles can also be directly applied to this attribute in the config file, but this may get messy very quickly if you have a lot of CSS.
7171

72+
### Page-Specific Styles
73+
74+
If you've got multiple pages within your dashboard, you can choose to target certain styles to specific pages. The top-most element within `<body>` will have a class name specific to the current sub-page. This is usually the page's name, all lowercase, with dashes instead of spaces, but you can easily check this yourself within the dev tools.
75+
76+
For example, if the pages name was "CFT Toolbox", and you wanted to target `.item`s, you would do:
77+
78+
```css
79+
.cft-toolbox .item { border: 4px solid yellow; }
80+
```
81+
7282
### Loading External Stylesheets
7383

7484
The URI of a stylesheet, either local or hosted on a remote CDN can be passed into the config file. The attribute `appConfig.externalStyleSheet` accepts either a string, or an array of strings. You can also pass custom font stylesheets here, they must be in a CSS format (for example, `https://fonts.googleapis.com/css2?family=Cutive+Mono`).

docs/troubleshooting.md

+16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [Refused to Connect in Web Content View](#refused-to-connect-in-modal-or-workspace-view)
99
- [404 On Static Hosting](#404-on-static-hosting)
1010
- [Yarn Build or Run Error](#yarn-error)
11+
- [Remote Config Not Loading](#remote-config-not-loading)
1112
- [Auth Validation Error: "should be object"](#auth-validation-error-should-be-object)
1213
- [App Not Starting After Update to 2.0.4](#app-not-starting-after-update-to-204)
1314
- [Keycloak Redirect Error](#keycloak-redirect-error)
@@ -104,6 +105,21 @@ Alternatively, as a workaround, you have several options:
104105

105106
---
106107

108+
## Remote Config Not Loading
109+
110+
If you've got a multi-page dashboard, and are hosting the additional config files yourself, then CORS rules will apply. A CORS error will look something like:
111+
112+
```
113+
Access to XMLHttpRequest at 'https://example.com/raw/my-config.yml' from origin 'http://dashy.local' has been blocked by CORS policy:
114+
No 'Access-Control-Allow-Origin' header is present on the requested resource.
115+
```
116+
117+
The solution is to add the appropriate headers onto the target server, to allow it to accept requests from the origin where you're running Dashy.
118+
119+
If it is a remote service, that you do not have admin access to, then another option is to proxy the request. Either host your own, or use a publicly accessible service, like [allorigins.win](https://allorigins.win), e.g: `https://api.allorigins.win/raw?url=https://pastebin.com/raw/4tZpaJV5`. For git-based services specifically, there's [raw.githack.com](https://raw.githack.com/)
120+
121+
---
122+
107123
## Auth Validation Error: "should be object"
108124

109125
In V 1.6.5 an update was made that in the future will become a breaking change. You will need to update you config to reflect this before V 2.0.0 is released. In the meantime, your previous config will continue to function normally, but you will see a validation warning. The change means that the structure of the `appConfig.auth` object is now an object, which has a `users` property.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "Dashy",
3-
"version": "2.0.7",
3+
"version": "2.0.8",
44
"license": "MIT",
55
"main": "server",
66
"author": "Alicia Sykes <[email protected]> (https://aliciasykes.com)",

public/index.html

+2-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@
88
<!-- Favicon + App Icon -->
99
<link rel="icon" type="image/png" sizes="64x64" href="<%= BASE_URL %>/web-icons/favicon-64x64.png">
1010
<link rel="icon" type="image/png" sizes="32x32" href="web-icons/favicon-32x32.png">
11-
<link rel="icon" href="/favicon.ico" />
12-
<link rel="icon" type="image/png" href="<%= BASE_URL %>favicon.ico" />
13-
<link rel="stylesheet" type="text/css" href="<%= BASE_URL %>loading-screen.css" />
11+
<link rel="icon" type="image/png" href="/favicon.ico" />
12+
<link rel="stylesheet" type="text/css" href="/loading-screen.css" />
1413
<!-- Default Page Title -->
1514
<title>Dashy</title>
1615
</head>

server.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ const app = express()
7070
.use(sslServer.middleware)
7171
// Serves up static files
7272
.use(express.static(path.join(__dirname, 'dist')))
73-
.use(express.static(path.join(__dirname, 'public')))
73+
.use(express.static(path.join(__dirname, 'public'), { index: 'initialization.html' }))
7474
// Load middlewares for parsing JSON, and supporting HTML5 history routing
7575
.use(express.json({ limit: '1mb' }))
7676
.use(history())

services/save-config.js

+16-10
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
const fsPromises = require('fs').promises;
88

99
module.exports = async (newConfig, render) => {
10+
/* Either returns nothing (if using default path), or strips navigational characters from path */
11+
const makeSafeFileName = (configObj) => {
12+
if (!configObj || !configObj.filename) return undefined;
13+
return configObj.filename.replaceAll('/', '').replaceAll('..', '');
14+
};
15+
16+
const usersFileName = makeSafeFileName(newConfig);
17+
1018
// Define constants for the config file
1119
const settings = {
1220
defaultLocation: './public/',
@@ -16,11 +24,11 @@ module.exports = async (newConfig, render) => {
1624
};
1725

1826
// Make the full file name and path to save the backup config file
19-
const backupFilePath = `${settings.defaultLocation}${settings.filename}-`
27+
const backupFilePath = `${settings.defaultLocation}${usersFileName || settings.filename}-`
2028
+ `${Math.round(new Date() / 1000)}${settings.backupDenominator}`;
2129

2230
// The path where the main conf.yml should be read and saved to
23-
const defaultFilePath = settings.defaultLocation + settings.defaultFile;
31+
const defaultFilePath = settings.defaultLocation + (usersFileName || settings.defaultFile);
2432

2533
// Returns a string confirming successful job
2634
const getSuccessMessage = () => `Successfully backed up ${settings.defaultFile} to`
@@ -36,16 +44,14 @@ module.exports = async (newConfig, render) => {
3644
});
3745

3846
// Makes a backup of the existing config file
39-
await fsPromises.copyFile(defaultFilePath, backupFilePath)
40-
.catch((error) => {
41-
render(getRenderMessage(false, `Unable to backup conf.yml: ${error}`));
42-
});
47+
await fsPromises
48+
.copyFile(defaultFilePath, backupFilePath)
49+
.catch((error) => render(getRenderMessage(false, `Unable to backup conf.yml: ${error}`)));
4350

4451
// Writes the new content to the conf.yml file
45-
await fsPromises.writeFile(defaultFilePath, newConfig.config.toString(), writeFileOptions)
46-
.catch((error) => {
47-
render(getRenderMessage(false, `Unable to write changes to conf.yml: ${error}`));
48-
});
52+
await fsPromises
53+
.writeFile(defaultFilePath, newConfig.config.toString(), writeFileOptions)
54+
.catch((error) => render(getRenderMessage(false, `Unable to write to conf.yml: ${error}`)));
4955

5056
// If successful, then render hasn't yet been called- call it
5157
await render(getRenderMessage(true));

src/App.vue

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div id="dashy" :style="topLevelStyleModifications">
2+
<div id="dashy" :style="topLevelStyleModifications" :class="subPageClassName">
33
<EditModeTopBanner v-if="isEditMode" />
44
<LoadingScreen :isLoading="isLoading" v-if="shouldShowSplash" />
55
<Header :pageInfo="pageInfo" />
@@ -72,6 +72,10 @@ export default {
7272
isEditMode() {
7373
return this.$store.state.editMode;
7474
},
75+
subPageClassName() {
76+
const currentSubPage = this.$store.state.currentConfigInfo;
77+
return (currentSubPage && currentSubPage.pageId) ? currentSubPage.pageId : '';
78+
},
7579
topLevelStyleModifications() {
7680
const vc = this.visibleComponents;
7781
if (!vc.footer && !vc.pageTitle) {
Loading

0 commit comments

Comments
 (0)