Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Adds ability for config file to be written to directly from the UI #43

Merged
merged 21 commits into from
Jun 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
856b9eb
Refactors the main server.js, and moves welcome msg to separate file
Lissy93 Jun 18, 2021
77ca662
Adds server endpoint for backing up and saving conf.yml. Still needs …
Lissy93 Jun 18, 2021
106103a
Syntactic improvments, and linting ping.js
Lissy93 Jun 19, 2021
760c464
Got the save config route working in the node server
Lissy93 Jun 19, 2021
a954f8c
Adds new property, `appConfig.allowConfigEdit`, in order to allow / p…
Lissy93 Jun 19, 2021
6337e5d
Merge branch 'master' of github.com:Lissy93/server-start-page into fe…
Lissy93 Jun 19, 2021
b0d5b63
Adds an endpoint for rebuilding the app, so that it can be triggered …
Lissy93 Jun 19, 2021
e75b0c7
✨ Implements frontend work for Rebuild App functionality
Lissy93 Jun 20, 2021
01af5fb
:building_construction: Sets environment in Docker, and adds recompil…
Lissy93 Jun 20, 2021
77bf770
:art: Refactor of stylesheets, making way for new changes
Lissy93 Jun 20, 2021
978064e
:speech_balloon: Sets name for compiled script
Lissy93 Jun 20, 2021
561c8a7
:art: Improves how modal styles are applied
Lissy93 Jun 20, 2021
20e12fe
✨ The user can now modify the conf.yml file through the UI :)
Lissy93 Jun 20, 2021
04708a4
:memo: Adds in-code docs to config accumalator
Lissy93 Jun 20, 2021
cd314cb
:art: Adds new styles used by config editor
Lissy93 Jun 20, 2021
f1f227d
:recycle: Fixed console formatting in welcome message, and increaes s…
Lissy93 Jun 20, 2021
6f80946
:passport_control: Prevent non-admin users from writing changes to disk
Lissy93 Jun 21, 2021
7ecb815
:bug: #36 - New method for downloading config
Lissy93 Jun 21, 2021
6d30b54
Adds w raw config view
Lissy93 Jun 21, 2021
46e6c23
:lock: Checks that the user has permission to write to disk before sa…
Lissy93 Jun 21, 2021
fcfb162
:memo: Updates docs, including new configuration changes
Lissy93 Jun 21, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ FROM node:lts-alpine
ENV PORT 80
ENV DIRECTORY /app
ENV IS_DOCKER true
ENV NODE_ENV production

# Create and set the working directory
WORKDIR ${DIRECTORY}
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,12 @@ Dashy supports 1-Click deployments on several popular cloud platforms (with more

Dashy is configured with a single [YAML](https://yaml.org/) file, located at `./public/conf.yml` (or `./app/public/conf.yml` for Docker). Any other optional user-customizable assets are also located in the `./public/` directory, e.g. `favicon.ico`, `manifest.json`, `robots.txt` and `web-icons/*`. If you are using Docker, the easiest way to method is to mount a Docker volume (e.g. `-v /root/my-local-conf.yml:/app/public/conf.yml`)

In the production environment, the app needs to be rebuilt in order for changes to take effect. This can be done with `yarn build`, or `docker exec -it [container-id] yarn build` if you are using Docker (where container ID can be found by running `docker ps`).
In the production environment, the app needs to be rebuilt in order for changes to take effect. This should happen automatically, but can also be triggered by running `yarn build`, or `docker exec -it [container-id] yarn build` if you are using Docker (where container ID can be found by running `docker ps`).

You can check that your config matches Dashy's [schema](https://github.com/Lissy93/dashy/blob/master/src/utils/ConfigSchema.json) before deploying, by running `yarn validate-config.`

It is now possible to update Dashy's config directly through the UI, and have changes written to disk. You can disable this feature by setting: `appConfig.allowConfigEdit: false`. If you are using users within Dashy, then you need to be logged in to a user of `type: admin` in order to modify the configuration globally. You can also trigger a rebuild of the app through the UI (Settings --> Rebuild). The current theme, and other visual preferences are only stored locally, unless otherwise specified in the config file. The option to only apply config changes locally is still available, and can be used in conjunction with the cloud backup feature to sync data between instances.

You may find these [example config](https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10) helpful for getting you started

**[⬆️ Back to Top](#dashy)**
Expand Down
7 changes: 6 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
version: "3.8"
services:
dashy:
# Set any environmental variables
environment:
NODE_ENV: production
# To build from source, replace 'image: lissy93/dashy' with 'build: .'
# build: .
image: lissy93/dashy
Expand All @@ -16,10 +19,12 @@ services:
# environment:
# - UID=1000
# - GID=1000
# Specify restart policy
restart: unless-stopped
# Configure healthchecks
healthcheck:
test: ['CMD', 'node', '/app/services/healthcheck']
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
start_period: 40s
13 changes: 11 additions & 2 deletions docs/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,17 @@ If you're new to YAML, it's pretty straight-forward. The format is exactly the s
You may find it helpful to look at some sample config files to get you started, a collection of which can be found [here](https://gist.github.com/Lissy93/000f712a5ce98f212817d20bc16bab10).

There's a couple of things to remember, before getting started:
- After modifying your config, you will need to run `yarn build` to recompile the application
- After modifying your config, the app needs to be recompiled, run `yarn build` (this happens automatically in newer versions)
- You can check that your config file fits the schema, by running `yarn validate-config`
- Any changes made locally through the UI need to be exported into this file, in order for them to persist across devices
- Any which are only saved locally through the UI need to be exported into this file, in order for them to persist across devices

#### Config Saving Methods
When updating the config through the JSON editor in the UI, you have two save options: **Local** or **Write to Disk**. Changes saved locally will only be applied to the current user through the browser, and to apply to other instances, you either need to use the cloud sync feature, or manually update the conf.yml file. On the other-hand, if you choose to write changes to disk, then your main `conf.yml` file will be updated, and changes will be applied to all users, and visible across all devices.

#### Preventing Changes being Written to Disk
To disallow any changes from being written to disk, then set `appConfig.allowConfigEdit: false`. If you are using users, and have setup `auth` within Dashy, then only users with `type: admin` will be able to write config changes to disk.

It is recommended to make a backup of your config file.

All fields are optional, unless otherwise stated.

Expand Down Expand Up @@ -58,6 +66,7 @@ All fields are optional, unless otherwise stated.
**`customCss`** | `string` | _Optional_ | Raw CSS that will be applied to the page. This can also be set from the UI. Please minify it first.
**`showSplashScreen`** | `boolean` | _Optional_ | Should display a splash screen while the app is loading. Defaults to false, except on first load
**`auth`** | `array` | _Optional_ | An array of objects containing usernames and hashed passwords. If this is not provided, then authentication will be off by default, and you will not need any credentials to access the app. Note authentication is done on the client side, and so if your instance of Dashy is exposed to the internet, it is recommend to configure your web server to handle this. See [`auth`](#appconfigauth-optional)
**`allowConfigEdit`** | `boolean` | _Optional_ | Should prevent / allow the user to write configuration changes to the conf.yml from the UI. When set to `false`, the user can only apply changes locally using the config editor within the app, whereas if set to `true` then changes can be written to disk directly through the UI. Defaults to `true`. Note that if authentication is enabled, the user must be of type `admin` in order to apply changes globally.

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

Expand Down
3 changes: 3 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ on how to create a pull request..
8. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
with a clear title and description.

You can use emojis in your commit message, to indicate the category of the task.
For a reference of what each emoji means in the context of commits, see [gitmoji.dev](https://gitmoji.dev/).

#### Testing the Production App

For larger pull requests, please also check that it works as expected in a production environment.
Expand Down
6 changes: 6 additions & 0 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,12 @@ Dashy ships with a pre-configured Node.js server, in [`server.js`](https://githu
If you wish to run Dashy from a sub page (e.g. `example.com/dashy`), then just set the `BASE_URL` environmental variable to that page name (in this example, `/dashy`), before building the app, and the path to all assets will then resolve to the new path, instead of `./`.

However, since Dashy is just a static web application, it can be served with whatever server you like. The following section outlines how you can configure a web server.

Note, that if you choose not to use `server.js` to serve up the app, you will loose access to the following features:
- Loading page, while the app is building
- Writing config file to disk from the UI
- Website status indicators, and ping checks

### NGINX

Create a new file in `/etc/nginx/sites-enabled/dashy`
Expand Down
11 changes: 10 additions & 1 deletion docs/developing.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ Note:
- If you are using Docker, precede each command with `docker exec -it [container-id]`. Container ID can be found by running `docker ps`

### Environmental Variables

- `PORT` - The port in which the application will run (defaults to `4000` for the Node.js server, and `80` within the Docker container)
- `NODE_ENV` - Which environment to use, either `production`, `development` or `test`
- `VUE_APP_DOMAIN` - The URL where Dashy is going to be accessible from. This should include the protocol, hostname and (if not 80 or 443), then the port too, e.g. `https://localhost:3000`, `http://192.168.1.2:4002` or `https://dashy.mydomain.com`

All environmental variables are optional. Currently there are not many environmental variables used, as most of the user preferences are stored under `appConfig` in the `conf.yml` file.
Expand All @@ -58,6 +58,15 @@ If you do add new variables, ensure that there is always a fallback (define it i

Any environmental variables used by the frontend are preceded with `VUE_APP_`. Vue will merge the contents of your `.env` file into the app in a similar way to the ['dotenv'](https://github.com/motdotla/dotenv) package, where any variables that you set on your system will always take preference over the contents of any `.env` file.

### Environment Modes
Both the Node app and Vue app supports several environments: `production`, `development` and `test`. You can set the environment using the `NODE_ENV` variable (either with your OS, in the Docker script or in an `.env` file - see [Environmental Variables](#environmental-variables) above).

The production environment will build the app in full, minifying and streamlining all assets. This means that building takes longer, but the app will then run faster. Whereas the dev environment creates a webpack configuration which enables HMR, doesn't hash assets or create vendor bundles in order to allow for fast re-builds when running a dev server. It supports sourcemaps and other debugging tools, re-compiles and reloads quickly but is not optimized, and so the app will not be as snappy as it could be. The test environment is intended for test running servers, it ignores assets that aren't needed for testing, and focuses on running all the E2E, regression and unit tests. For more information, see [Vue CLI Environment Modes](https://cli.vuejs.org/guide/mode-and-env.html#modes).

By default:
- `production` is used by `yarn build` (or `vue-cli-service build`) and `yarn build-and-start` and `yarn pm2-start`
- `development` is used by `yarn dev` (or `vue-cli-service serve`)
- `test` is used by `yarn test` (or `vue-cli-service test:unit`)
### Resources for Beginners
New to Web Development? Glad you're here! Dashy is a pretty simple app, so it should make a good candidate for your first PR. Presuming that you already have a basic knowledge of JavaScript, the following articles should point you in the right direction for getting up to speed with the technologies used in this project:
- [Introduction to Vue.js](https://v3.vuejs.org/guide/introduction.html)
Expand Down
4 changes: 4 additions & 0 deletions docs/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ You can target specific elements on the UI with these variables. All are optiona
- `--side-bar-color` - Color of icons and text within the sidebar. Defaults to `--primary`
- `--status-check-tooltip-background` - Background color for status check tooltips. Defaults to `--background-darker`
- `--status-check-tooltip-color` - Text color for the status check tooltips. Defaults to `--primary`
- `--code-editor-color` - Text color used within raw code editors. Defaults to `--black`
- `--code-editor-background` - Background color for raw code editors. Defaults to `--white`



#### Non-Color Variables
- `--outline-color` - Used to outline focused or selected elements
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@
"start": "node server",
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint --fix",
"lint": "vue-cli-service lint",
"pm2-start": "npx pm2 start server.js",
"build-watch": "vue-cli-service build --watch",
"build-and-start": "npm-run-all --parallel build start",
"build-watch": "vue-cli-service build --watch --mode production",
"build-and-start": "npm-run-all --parallel build-watch start",
"validate-config": "node src/utils/ConfigValidator",
"health-check": "node services/healthcheck"
},
"dependencies": {
"ajv": "^8.5.0",
"axios": "^0.21.1",
"body-parser": "^1.19.0",
"connect": "^3.7.0",
"crypto-js": "^4.0.0",
"highlight.js": "^11.0.0",
Expand Down
110 changes: 59 additions & 51 deletions server.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,92 @@
/* eslint-disable no-console */
/* This is a simple Node.js http server, that is used to serve up the contents of ./dist */
const connect = require('connect');
const serveStatic = require('serve-static');
/**
* Note: The app must first be built (yarn build) before this script is run
* This is the main entry point for the application, a simple server that
* runs some checks, and then serves up the app from the ./dist directory
* Also includes some routes for status checks/ ping and config saving
* */

/* Include required node dependencies */
const serveStatic = require('serve-static');
const connect = require('connect');
const util = require('util');
const dns = require('dns');
const os = require('os');
const bodyParser = require('body-parser');

require('./src/utils/ConfigValidator');

const pingUrl = require('./services/ping');
/* Include helper functions and route handlers */
const pingUrl = require('./services/ping'); // Used by the status check feature, to ping services
const saveConfig = require('./services/save-config'); // Saves users new conf.yml to file-system
const printMessage = require('./services/print-message'); // Function to print welcome msg on start
const rebuild = require('./services/rebuild-app'); // A script to programmatically trigger a build
require('./src/utils/ConfigValidator'); // Include and kicks off the config file validation script

/* Checks if app is running within a container, from env var */
const isDocker = !!process.env.IS_DOCKER;

/* Checks env var for port. If undefined, will use Port 80 for Docker, or 4000 for metal */
const port = process.env.PORT || (isDocker ? 80 : 4000);

/* Attempts to get the users local IP, used as part of welcome message */
const getLocalIp = () => {
const dnsLookup = util.promisify(dns.lookup);
return dnsLookup(os.hostname());
};

const overComplicatedMessage = (ip) => {
let msg = '';
const chars = {
RESET: '\x1b[0m',
CYAN: '\x1b[36m',
GREEN: '\x1b[32m',
BLUE: '\x1b[34m',
BRIGHT: '\x1b[1m',
BR: '\n',
};
const stars = (count) => new Array(count).fill('*').join('');
const line = (count) => new Array(count).fill('━').join('');
const blanks = (count) => new Array(count).fill(' ').join('');
if (isDocker) {
const containerId = process.env.HOSTNAME || undefined;
msg = `${chars.BLUE}${stars(91)}${chars.BR}${chars.RESET}`
+ `${chars.CYAN}Welcome to Dashy! 🚀${chars.RESET}${chars.BR}`
+ `${chars.GREEN}Your new dashboard is now up and running `
+ `${containerId ? `in container ID ${containerId}` : 'with Docker'}${chars.BR}`
+ `${chars.GREEN}After updating your config file, run `
+ `'${chars.BRIGHT}docker exec -it ${containerId || '[container-id]'} yarn build`
+ `${chars.RESET}${chars.GREEN}' to rebuild${chars.BR}`
+ `${chars.BLUE}${stars(91)}${chars.BR}${chars.RESET}`;
} else {
msg = `${chars.GREEN}┏${line(75)}┓${chars.BR}`
+ `┃ ${chars.CYAN}Welcome to Dashy! 🚀${blanks(55)}${chars.GREEN}┃${chars.BR}`
+ `┃ ${chars.CYAN}Your new dashboard is now up and running at ${chars.BRIGHT}`
+ `http://${ip}:${port}${chars.RESET}${blanks(18 - ip.length)}${chars.GREEN}┃${chars.BR}`
+ `┃ ${chars.CYAN}After updating your config file, run '${chars.BRIGHT}yarn build`
+ `${chars.RESET}${chars.CYAN}' to rebuild the app${blanks(6)}${chars.GREEN}┃${chars.BR}`
+ `┗${line(75)}┛${chars.BR}${chars.BR}`;
}
return msg;
};

/* eslint no-console: 0 */
/* Gets the users local IP and port, then calls to print welcome message */
const printWelcomeMessage = () => {
getLocalIp().then(({ address }) => {
const ip = address || 'localhost';
console.log(overComplicatedMessage(ip));
console.log(printMessage(ip, port, isDocker)); // eslint-disable-line no-console
});
};

/* Just console.warns an error */
const printWarning = (msg, error) => {
console.warn(`\x1b[103m\x1b[34m${msg}\x1b[0m\n`, error || ''); // eslint-disable-line no-console
};

/* A middleware function for Connect, that filters requests based on method type */
const method = (m, mw) => (req, res, next) => (req.method === m ? mw(req, res, next) : next());

try {
connect()
.use(serveStatic(`${__dirname}/dist`)) /* Serves up the main built application to the root */
.use(serveStatic(`${__dirname}/public`, { index: 'default.html' })) /* During build, a custom page will be served */
.use('/ping', (req, res) => { /* This root returns the status of a given service - used for uptime monitoring */
.use(bodyParser.json())
// Serves up the main built application to the root
.use(serveStatic(`${__dirname}/dist`))
// During build, a custom page will be served before the app is available
.use(serveStatic(`${__dirname}/public`, { index: 'default.html' }))
// This root returns the status of a given service - used for uptime monitoring
.use('/ping', (req, res) => {
try {
pingUrl(req.url, async (results) => {
await res.end(results);
});
// next();
} catch (e) { console.warn(`Error running ping check for ${req.url}\n`, e); }
} catch (e) {
printWarning(`Error running ping check for ${req.url}\n`, e);
}
})
// POST Endpoint used to save config, by writing conf.yml to disk
.use('/config-manager/save', method('POST', (req, res) => {
try {
saveConfig(req.body, (results) => {
res.end(results);
});
} catch (e) {
res.end(JSON.stringify({ success: false, message: e }));
}
}))
// GET endpoint to trigger a build, and respond with success status and output
.use('/config-manager/rebuild', (req, res) => {
rebuild().then((response) => {
res.end(JSON.stringify(response));
}).catch((response) => {
res.end(JSON.stringify(response));
});
})
// Finally, initialize the server then print welcome message
.listen(port, () => {
try { printWelcomeMessage(); } catch (e) { console.log('Dashy is Starting...'); }
try { printWelcomeMessage(); } catch (e) { printWarning('Dashy is Starting...'); }
});
} catch (error) {
console.log('Sorry, an error occurred ', error);
printWarning('Sorry, a critical error occurred ', error);
}
Loading