-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #6 from Lissy93/feature-cloud-backup-restore
Feature: Adds Cloud Backup and Restore
- Loading branch information
Showing
16 changed files
with
531 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,10 +19,11 @@ | |
- Quickly preview a website, by holding down the Alt key while clicking, to open it in a resizable pop-up modal | ||
- Many options for icons, including full Font-Awesome support and the ability to auto-fetch icon from URLs favicon | ||
- Additional info for each item visible on hover (including opening method icon and description as a tooltip) | ||
- Option for full-screen background image, custom nav-bar links, and custom footer | ||
- Preferences stored in local storage and applied on load | ||
- Easy YAML-based configuration | ||
- Small bundle size and a fully responsive UI makes the app easy to use on any device | ||
- Option for full-screen background image, custom nav-bar links, and custom footer text | ||
- User settings stored in local storage and applied on load | ||
- Encrypted cloud backup and restore feature available | ||
- Easy single-file YAML-based configuration | ||
- Small bundle size, fully responsive UI and PWA makes the app easy to use on any device | ||
- Plus lots more... | ||
|
||
**Live Demos**: [Demo 1](https://dashy-demo-1.as93.net) ┆ [Demo 2](https://dashy-demo-2.as93.net) ┆ [Demo 3](https://dashy-demo-3.as93.net) | ||
|
@@ -38,25 +39,29 @@ | |
--- | ||
|
||
## Running the App 🏃♂️ | ||
|
||
### Deploying 🚀 | ||
- Get Code: `git clone [email protected]:Lissy93/dashy.git` and `cd dashy` | ||
- Configuration: Fill in you're settings in `./public/conf.yml` | ||
- Install dependencies: `yarn` | ||
- Build: `yarn build` | ||
- Run: `yarn start` | ||
|
||
### Deploying with Docker 🐳 | ||
### Deploying with Docker from Source 🛳️ | ||
- Get Code: `git clone [email protected]:Lissy93/dashy.git` and `cd dashy` | ||
- Configuration: Fill in you're settings in `./public/conf.yml` | ||
- Build: `docker build -t lissy93/dashy .` | ||
- Start: `docker run -it -p 8080:80 --rm --name my-dashboard lissy93/dashy` | ||
- Start: `docker run -p 8080:80 --name my-dashboard lissy93/dashy` | ||
|
||
### Deploying from Docker Hub 🐳 | ||
- Get the Image: `docker pull lissy93/dashy` | ||
- Start the Container: `docker run -d -p 8080:80 --name my-dashboard lissy93/dashy` | ||
### Developing 🧱 | ||
- Get Code: `git clone [email protected]:Lissy93/dashy.git` and `cd dashy` | ||
- Install dependencies: `yarn` | ||
- Start dev server: `yarn dev` | ||
|
||
Note that although recommended, it is not required to use the conf.yml file- all settings can be specified through the UI, and backed up on the cloud. | ||
|
||
--- | ||
|
||
## Configuring 🔧 | ||
|
@@ -162,17 +167,24 @@ There are a few self-hosted web apps, that serve a similar purpose to Dashy. Inc | |
|
||
### Credits 🏆 | ||
|
||
And the app itself is built with [Vue.js](https://github.com/vuejs/vue) ![vue-logo](https://i.ibb.co/xqKW6h5/vue-logo.png) | ||
|
||
And wouldn't have been quite possible, without the following components, kudos to their respective authors | ||
This wouldn't have been quite so possible without the following components, kudos to their respective authors | ||
- [`vue-select`](https://github.com/sagalbot/vue-select) - Dropdown component by @sagalbot `MIT` | ||
- [`vue-js-modal`](https://github.com/euvl/vue-js-modal) - Modal component by @euvl `MIT` | ||
- [`v-tooltip`](https://github.com/Akryum/v-tooltip) - Tooltip component by @Akryum `MIT` | ||
- [`vue-material-tabs`](https://github.com/jairoblatt/vue-material-tabs) - Tab view component by @jairoblatt `MIT` | ||
- [`VJsoneditor`](https://github.com/yansenlei/VJsoneditor) - Interactive JSON editor component by @yansenlei `MIT` | ||
- Forked from [JsonEditor](https://github.com/josdejong/jsoneditor) by @josdejong `Apache-2.0 License` | ||
- Forked from [`JsonEditor`](https://github.com/josdejong/jsoneditor) by @josdejong `Apache-2.0 License` | ||
- And using [`ajv`](https://github.com/ajv-validator/ajv) `MIT` JSON schema Validator [`ace`](https://github.com/ajaxorg/ace) `BSD` code editor | ||
- [`vue-toasted`](https://github.com/shakee93/vue-toasted) - Toast notification component by @shakee93 `MIT` | ||
|
||
Utils: | ||
- [`crypto-js`](https://github.com/brix/crypto-js) - Encryption implementations by @evanvosberg and community `MIT` | ||
- [`axios`](https://github.com/axios/axios) - Promise based HTTP client by @mzabriskie and community `MIT` | ||
|
||
And the app itself is built with [Vue.js](https://github.com/vuejs/vue) ![vue-logo](https://i.ibb.co/xqKW6h5/vue-logo.png) | ||
|
||
Although the app is purely frontend, there is an optional cloud backup and restore feature. This is built as a serverless function on [Cloudflare workers](https://workers.cloudflare.com/) using [KV](https://developers.cloudflare.com/workers/runtime-apis/kv) and [web crypto](https://developers.cloudflare.com/workers/runtime-apis/web-crypto) | ||
|
||
### License 📜 | ||
|
||
``` | ||
|
@@ -194,3 +206,7 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRA | |
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWAREOR THE USE | ||
OR OTHER DEALINGS IN THE SOFTWARE. | ||
``` | ||
--- | ||
<a href="https://www.producthunt.com/posts/dashy" target="_blank" align="center"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=294872&theme=dark" alt="Dashy - A feature-rich dashboard for your homelab 🚀 | Product Hunt" width="250" height="54" /></a> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,238 @@ | ||
<template> | ||
<div class="cloud-backup-restore-wrapper"> | ||
<div class="section intro"> | ||
<h2>Cloud Backup & Restore</h2> | ||
<p class="intro"> | ||
Cloud backup and restore is an optional feature, that enabled you to upload your | ||
config to the internet, and then restore it on any other device or instance of Dashy. | ||
<br><br> | ||
All data is fully end-to-end encrypted with AES, using your password as the key. | ||
</p> | ||
</div> | ||
<div class="section backup-section"> | ||
<h3 v-if="backupId">Update Backup</h3> | ||
<h3 v-else>Make a Backup</h3> | ||
<Input | ||
v-model="backupPassword" | ||
name="backup-password" | ||
:label="backupId ? 'Enter your Password' : 'Choose a Password'" | ||
layout="vertical" | ||
type="password" | ||
/> | ||
<Button :click="checkPass"> | ||
<template v-slot:text>{{backupId ? 'Update Backup' : 'Backup'}}</template> | ||
<template v-slot:icon><IconBackup /></template> | ||
</Button> | ||
<div class="results-view" v-if="backupId"> | ||
<span class="backup-id-label">Your Backup ID: </span> | ||
<pre class="backup-id-value">{{ backupId }}</pre> | ||
<span class="backup-id-note"> | ||
This is used to restore from backups later. | ||
So keep it, along with your password somewhere safe. | ||
</span> | ||
</div> | ||
</div> | ||
<div class="section restore-section"> | ||
<h3>Restore a Backup</h3> | ||
<Input | ||
v-model="restoreCode" | ||
name="restore-code" | ||
label="Restore ID" | ||
/> | ||
<Input | ||
v-model="restorePassword" | ||
name="restore-password" | ||
label="Password" | ||
type="password" | ||
/> | ||
<Button :click="restoreBackup"> | ||
<template v-slot:text>Restore</template> | ||
<template v-slot:icon><IconRestore /></template> | ||
</Button> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script> | ||
import sha256 from 'crypto-js/sha256'; | ||
import Button from '@/components/FormElements/Button'; | ||
import Input from '@/components/FormElements/Input'; | ||
import IconBackup from '@/assets/interface-icons/config-backup.svg'; | ||
import IconRestore from '@/assets/interface-icons/config-restore.svg'; | ||
import { backup, update, restore } from '@/utils/CloudBackup'; | ||
import { localStorageKeys } from '@/utils/defaults'; | ||
export default { | ||
name: 'CloudBackupRestore', | ||
props: { | ||
config: Object, | ||
}, | ||
data() { | ||
return { | ||
backupPassword: '', | ||
restorePassword: '', | ||
restoreCode: '', | ||
backupId: localStorage[localStorageKeys.BACKUP_ID] || '', | ||
}; | ||
}, | ||
components: { | ||
Button, | ||
Input, | ||
IconBackup, | ||
IconRestore, | ||
}, | ||
methods: { | ||
restoreBackup() { | ||
restore(this.restoreCode, this.restorePassword) | ||
.then((response) => { | ||
this.restoreFromBackup(response, this.restoreCode); | ||
}).catch((msg) => { | ||
this.showErrorMsg(msg); | ||
}); | ||
}, | ||
checkPass() { | ||
const savedHash = localStorage[localStorageKeys.BACKUP_HASH] || undefined; | ||
if (!savedHash) { | ||
this.makeBackup(); | ||
} else if (savedHash === this.makeHash(this.backupPassword)) { | ||
this.makeUpdate(); | ||
} else { | ||
this.showErrorMsg('Incorrect password. Please enter your current password.'); | ||
} | ||
}, | ||
makeBackup() { | ||
backup(this.config, this.backupPassword) | ||
.then((response) => { | ||
if (!response.data || response.data.errorMsg || !response.data.backupId) { | ||
this.showErrorMsg(response.data.errorMsg || 'Error'); | ||
} else { // All clear, no error | ||
this.updateUiAfterBackup(response.data.backupId, false); | ||
} | ||
}).catch(() => { | ||
this.showErrorMsg('Unable to process request'); | ||
}); | ||
}, | ||
makeUpdate() { | ||
update(this.config, this.backupPassword, this.backupId) | ||
.then((response) => { | ||
if (!response.data || response.data.errorMsg || !response.data.backupId) { | ||
this.showErrorMsg(response.data.errorMsg || 'Error'); | ||
} else { // All clear, no error | ||
this.updateUiAfterBackup(response.data.backupId, true); | ||
} | ||
}).catch(() => { | ||
this.showErrorMsg('Unable to process request'); | ||
}); | ||
}, | ||
restoreFromBackup(config, backupId) { | ||
localStorage.setItem(localStorageKeys.CONF_SECTIONS, JSON.stringify(config.sections)); | ||
localStorage.setItem(localStorageKeys.APP_CONFIG, JSON.stringify(config.appConfig)); | ||
localStorage.setItem(localStorageKeys.PAGE_INFO, JSON.stringify(config.pageInfo)); | ||
if (config.appConfig.theme) { | ||
localStorage.setItem(localStorageKeys.THEME, config.appConfig.theme); | ||
} | ||
this.setBackupIdLocally(backupId, this.restorePassword); | ||
this.showSuccessMsg('Config Restored Succesfully'); | ||
setTimeout(() => { location.reload(); }, 1500); // eslint-disable-line no-restricted-globals | ||
}, | ||
updateUiAfterBackup(backupId, isUpdate = false) { | ||
this.setBackupIdLocally(backupId, this.backupPassword); | ||
this.showSuccessMsg(`${isUpdate ? 'Update' : 'Backup'} Completed Succesfully`); | ||
this.backupPassword = ''; | ||
}, | ||
showErrorMsg(errorMsg) { | ||
this.$toasted.show(errorMsg, { className: 'toast-error' }); | ||
}, | ||
showSuccessMsg(msg) { | ||
this.$toasted.show(msg, { className: 'toast-success' }); | ||
}, | ||
makeHash(pass) { | ||
return sha256(pass).toString(); | ||
}, | ||
setBackupIdLocally(backupId, pass) { | ||
this.backupId = backupId; | ||
const hash = this.makeHash(pass); | ||
localStorage.setItem(localStorageKeys.BACKUP_ID, backupId); | ||
localStorage.setItem(localStorageKeys.BACKUP_HASH, hash); | ||
}, | ||
}, | ||
}; | ||
</script> | ||
|
||
<style scoped lang="scss"> | ||
div.cloud-backup-restore-wrapper { | ||
display: flex; | ||
flex-direction: row; | ||
flex-wrap: wrap; | ||
text-align: center; | ||
height: 100%; | ||
background: var(--config-settings-background); | ||
color: var(--config-settings-color); | ||
.section { | ||
display: flex; | ||
flex-direction: column; | ||
width: fit-content; | ||
margin: 0 auto 1rem auto; | ||
padding: 0 0.5rem 1rem 0.5rem; | ||
&:first-child { | ||
border-bottom: 1px dashed var(--config-settings-color); | ||
} | ||
&.intro { | ||
width: 100%; | ||
height: fit-content; | ||
} | ||
} | ||
h2 { font-size: 2rem; } | ||
h3 { font-size: 1.6rem; } | ||
p.intro { | ||
text-align: left; | ||
font-size: 1rem; | ||
margin: 0.25rem; | ||
padding: 0.25rem; | ||
} | ||
} | ||
div.results-view { | ||
width: 16rem; | ||
margin: 0.5rem auto; | ||
padding: 0.5rem 0.75rem; | ||
box-sizing: border-box; | ||
border: 1px dashed var(--config-settings-color); | ||
border-radius: var(--curve-factor); | ||
text-align: left; | ||
.backup-id-label, .backup-id-value { | ||
display: inline; | ||
font-size: 1rem; | ||
margin-right: 0.5rem; | ||
} | ||
.backup-id-note { | ||
font-size: 0.8rem; | ||
display: block; | ||
opacity: 0.8; | ||
margin-top: 0.5rem; | ||
} | ||
} | ||
/* Overide form element colors, so that config menu can be themed by user */ | ||
input, button, { | ||
color: var(--config-settings-color); | ||
border: 1px solid var(--config-settings-color); | ||
background: none; | ||
width: 16rem; | ||
} | ||
input:focus { | ||
box-shadow: 1px 1px 6px var(--config-settings-color); | ||
} | ||
button:hover { | ||
color: var(--config-settings-background); | ||
border: 1px solid var(--config-settings-background); | ||
background: var(--config-settings-color); | ||
} | ||
h2, h3 { | ||
margin: 1rem; | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.