Merge pull request #6 from Lissy93/feature-cloud-backup-restore

Feature: Adds Cloud Backup and Restore
This commit is contained in:
Alicia Sykes 2021-05-24 20:48:39 +01:00 committed by GitHub
commit fcb260271b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 531 additions and 18 deletions

View File

@ -15,10 +15,12 @@ COPY . .
RUN yarn build
# Production Stage
ENV PORT 80
FROM nginx:1.15.7-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
EXPOSE ${PORT}
VOLUME /usr/share/nginx/html/item-icons
CMD ["nginx", "-g", "daemon off;"]

View File

@ -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,7 +39,6 @@
---
## Running the App 🏃‍♂️
### Deploying 🚀
- Get Code: `git clone git@github.com:Lissy93/dashy.git` and `cd dashy`
- Configuration: Fill in you're settings in `./public/conf.yml`
@ -46,17 +46,22 @@
- Build: `yarn build`
- Run: `yarn start`
### Deploying with Docker 🐳
### Deploying with Docker from Source 🛳️
- Get Code: `git clone git@github.com: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 git@github.com: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>

View File

@ -8,7 +8,9 @@
"lint": "vue-cli-service lint --fix"
},
"dependencies": {
"axios": "^0.21.1",
"connect": "^3.7.0",
"crypto-js": "^4.0.0",
"register-service-worker": "^1.6.2",
"remedial": "^1.0.8",
"serve-static": "^1.14.1",

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="far" data-icon="cloud-upload-alt" class="svg-inline--fa fa-cloud-upload-alt fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M395.5 267.5l-99-99c-4.7-4.7-12.3-4.7-17 0l-99 99c-7.6 7.6-2.2 20.5 8.5 20.5h67v84c0 6.6 5.4 12 12 12h40c6.6 0 12-5.4 12-12v-84h67c10.7 0 16.1-12.9 8.5-20.5zm148.2-67.4C539.7 142.1 491.4 96 432 96c-7.6 0-15.1.8-22.4 2.3C377.7 58.3 328.1 32 272 32c-84.6 0-155.5 59.7-172.3 139.8C39.9 196.1 0 254.4 0 320c0 88.4 71.6 160 160 160h336c79.5 0 144-64.5 144-144 0-61.8-39.2-115.8-96.3-135.9zM496 432H160c-61.9 0-112-50.1-112-112 0-56.4 41.7-103.1 96-110.9V208c0-70.7 57.3-128 128-128 53.5 0 99.3 32.8 118.4 79.4 11.2-9.6 25.7-15.4 41.6-15.4 35.3 0 64 28.7 64 64 0 11.8-3.2 22.9-8.8 32.4 2.9-.3 5.9-.4 8.8-.4 53 0 96 43 96 96s-43 96-96 96z"></path></svg>

After

Width:  |  Height:  |  Size: 883 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fal" data-icon="upload" class="svg-inline--fa fa-upload fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M452 432c0 11-9 20-20 20s-20-9-20-20 9-20 20-20 20 9 20 20zm-84-20c-11 0-20 9-20 20s9 20 20 20 20-9 20-20-9-20-20-20zm144-48v104c0 24.3-19.7 44-44 44H44c-24.3 0-44-19.7-44-44V364c0-24.3 19.7-44 44-44h124v-99.3h-52.7c-35.6 0-53.4-43.1-28.3-68.3L227.7 11.7c15.6-15.6 40.9-15.6 56.6 0L425 152.4c25.2 25.2 7.3 68.3-28.3 68.3H344V320h124c24.3 0 44 19.7 44 44zM200 188.7V376c0 4.4 3.6 8 8 8h96c4.4 0 8-3.6 8-8V188.7h84.7c7.1 0 10.7-8.6 5.7-13.7L261.7 34.3c-3.1-3.1-8.2-3.1-11.3 0L109.7 175c-5 5-1.5 13.7 5.7 13.7H200zM480 364c0-6.6-5.4-12-12-12H344v24c0 22.1-17.9 40-40 40h-96c-22.1 0-40-17.9-40-40v-24H44c-6.6 0-12 5.4-12 12v104c0 6.6 5.4 12 12 12h424c6.6 0 12-5.4 12-12V364z"></path></svg>

After

Width:  |  Height:  |  Size: 902 B

View File

@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fal" data-icon="download" class="svg-inline--fa fa-download fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M452 432c0 11-9 20-20 20s-20-9-20-20 9-20 20-20 20 9 20 20zm-84-20c-11 0-20 9-20 20s9 20 20 20 20-9 20-20-9-20-20-20zm144-48v104c0 24.3-19.7 44-44 44H44c-24.3 0-44-19.7-44-44V364c0-24.3 19.7-44 44-44h99.4L87 263.6c-25.2-25.2-7.3-68.3 28.3-68.3H168V40c0-22.1 17.9-40 40-40h96c22.1 0 40 17.9 40 40v155.3h52.7c35.6 0 53.4 43.1 28.3 68.3L368.6 320H468c24.3 0 44 19.7 44 44zm-261.7 17.7c3.1 3.1 8.2 3.1 11.3 0L402.3 241c5-5 1.5-13.7-5.7-13.7H312V40c0-4.4-3.6-8-8-8h-96c-4.4 0-8 3.6-8 8v187.3h-84.7c-7.1 0-10.7 8.6-5.7 13.7l140.7 140.7zM480 364c0-6.6-5.4-12-12-12H336.6l-52.3 52.3c-15.6 15.6-41 15.6-56.6 0L175.4 352H44c-6.6 0-12 5.4-12 12v104c0 6.6 5.4 12 12 12h424c6.6 0 12-5.4 12-12V364z"></path></svg>

After

Width:  |  Height:  |  Size: 920 B

View File

@ -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>

View File

@ -60,7 +60,9 @@ export default {
pageInfo.title = this.formElements.title;
pageInfo.description = this.formElements.description;
pageInfo.footerText = this.formElements.footerText;
pageInfo.navLinks = this.formElements.navLinks.filter(link => (link.title !== ''));
if (this.formElements.navLinks) {
pageInfo.navLinks = this.formElements.navLinks.filter(link => (link.title !== ''));
}
localStorage.setItem(localStorageKeys.PAGE_INFO, JSON.stringify(pageInfo));
this.$toasted.show('Changes saved succesfully');
setTimeout(() => { location.reload(); }, 1500); // eslint-disable-line no-restricted-globals

View File

@ -0,0 +1,53 @@
<template>
<button @click="click()">
<slot name="text"></slot>
<slot name="icon"></slot>
</button>
</template>
<script>
export default {
name: 'Button',
props: {
text: String,
click: Function,
},
};
</script>
<style scoped lang="scss">
/* Layout settings */
button {
display: flex;
justify-content: center;
flex-direction: row-reverse;
align-items: center;
padding: 0.5rem 0.75rem;
margin: 0.5rem auto;
font-size: 1.2rem;
min-width: 10rem;
cursor: pointer;
svg {
width: 1.2rem;
margin: 0 0.5rem;
path, g {
fill: currentColor;
}
}
}
/* Default visual settings, can be overridden when needed */
button {
color: var(--primary);
background: var(--background);
border: 1px solid var(--primary);
border-radius: var(--curve-factor);
&:hover {
color: var(--background);
background: var(--primary);
border-color: var(--background);
}
}
</style>

View File

@ -0,0 +1,73 @@
<template>
<div :class="`input-container ${layout}`">
<label v-if="label" for="name">{{label}}</label>
<input
:type="type"
:value="value"
v-on:input="updateValue($event.target.value)"
:name="name"
:id="name"
:placeholder="placeholder"
/>
</div>
</template>
<script>
export default {
name: 'Input',
props: {
value: String, // The value bound to v-model
label: String, // An optional label to display above
name: String, // Required unique ID value, for accessibility
placeholder: String, // Optional placeholder value
type: {
default: 'text', // Input type, e.g. text, password, number
type: String,
},
layout: { // Layout alignment direction, either horizonal or verical
validator: (value) => ['horizontal', 'vertical'].indexOf(value) !== -1,
type: String,
default: 'vertical',
},
},
methods: {
updateValue(value) {
this.$emit('input', value);
},
},
};
</script>
<style scoped lang="scss">
div.input-container {
margin: 0.25rem auto;
display: flex;
align-items: baseline;
&.vertical {
flex-direction: column;
}
&.horizontal {
flex-direction: row;
justify-content: space-between;
label { margin-right: 0.25rem; }
}
input {
min-width: 10rem;
padding: 0.5rem 0.75rem;
margin: 0.5rem auto;
font-size: 1.2rem;
box-sizing: border-box;
color: var(--primary);
background: var(--background);;
border: 1px solid var(--primary);
border-radius: var(--curve-factor);
&:focus {
box-shadow: 1px 1px 6px var(--config-settings-color);
outline: none;
}
}
}
</style>

View File

@ -4,32 +4,46 @@
<span>Config</span>
<div class="config-buttons">
<IconSpanner v-tooltip="tooltip('Update configuration locally')" @click="showEditor()" />
<IconCloud v-tooltip="tooltip('Backup / restore cloud config')" @click="showCloudModal()" />
</div>
<!-- Modal containing all the configuration options -->
<modal :name="modalName" :resizable="true" width="60%" height="80%"
<modal :name="modalNames.CONF_EDITOR" :resizable="true" width="60%" height="80%"
@closed="$emit('modalChanged', false)">
<ConfigContainer :config="combineConfig()" />
</modal>
<!-- Modal for cloud backup and restore options -->
<modal :name="modalNames.CLOUD_BACKUP" :resizable="true" width="65%" height="60%"
@closed="$emit('modalChanged', false)">
<CloudBackupRestore :config="combineConfig()" />
</modal>
</div>
</template>
<script>
import IconSpanner from '@/assets/interface-icons/config-editor.svg';
import IconCloud from '@/assets/interface-icons/cloud-backup-restore.svg';
import ConfigContainer from '@/components/Configuration/ConfigContainer';
import CloudBackupRestore from '@/components/Configuration/CloudBackupRestore';
import { topLevelConfKeys, localStorageKeys } from '@/utils/defaults';
export default {
name: 'ConfigLauncher',
data() {
return {
modalName: 'CONF-EDITOR',
modalNames: {
CONF_EDITOR: 'CONF_EDITOR',
CLOUD_BACKUP: 'CLOUD_BACKUP',
},
};
},
components: {
IconSpanner,
IconCloud,
ConfigContainer,
CloudBackupRestore,
},
props: {
sections: Array,
@ -38,7 +52,11 @@ export default {
},
methods: {
showEditor: function show() {
this.$modal.show(this.modalName);
this.$modal.show(this.modalNames.CONF_EDITOR);
this.$emit('modalChanged', true);
},
showCloudModal: function show() {
this.$modal.show(this.modalNames.CLOUD_BACKUP);
this.$emit('modalChanged', true);
},
combineConfig() {

View File

@ -5,6 +5,16 @@
--background: #0b1021; // Page background
--background-darker: #05070e; // Used for navigation bar, footer and fills
/* Action Colors */
--info: #04e4f4;
--success: #20e253;
--warning: #f6f000;
--danger: #f80363;
--neutral: #272f4d;
--white: #fff;
--black: #000;
/* Modified Colors */
--item-group-background: #0b1021cc;
--medium-grey: #5e6474;
@ -16,6 +26,11 @@
--transparent-50: #00000080;
--transparent-30: #0000004d;
/* Semi-Transparent White*/
--transparent-white-70: #ffffffb3;
--transparent-white-50: #ffffff80;
--transparent-white-30: #ffffff4d;
/* Other Variables */
--outline-color: none;
--curve-factor: 5px; // Border radius for most components

View File

@ -20,6 +20,14 @@ h1, h2, h3, h4, h5 {
font-family: 'Inconsolata', sans-serif;
}
.bold { font-weight: bold; }
.light { font-weight: lighter; }
.text-left { text-align: left;}
.text-right { text-align: right;}
.text-center { text-align: center;}
.horizontal-center { margin: 0 auto; }
.border-box { box-sizing: border-box; }
/* Overiding styles for the global toast component */
.toast-message {
background: var(--toast-background) !important;
@ -29,4 +37,15 @@ h1, h2, h3, h4, h5 {
font-size: 1.25rem !important;
}
.toast-error {
background: var(--danger) !important;
color: var(--white) !important;
font-size: 1.25rem !important;
}
.toast-success {
background: var(--success) !important;
color: var(--white) !important;
font-size: 1.25rem !important;
}

52
src/utils/CloudBackup.js Normal file
View File

@ -0,0 +1,52 @@
import sha256 from 'crypto-js/sha256';
import aes from 'crypto-js/aes';
import Utf8 from 'crypto-js/enc-utf8';
import axios from 'axios';
import { backupEndpoint } from '@/utils/defaults';
const ENDPOINT = backupEndpoint; // 'https://dashy-sync-service.as93.net';
/* Stringify, encrypt and encode data for transmission */
const encryptData = (data, password) => {
const stringifiedData = JSON.stringify(data);
const encryptedData = aes.encrypt(stringifiedData, password);
return encryptedData.toString();
};
/* Decrypt, decode and parse received data */
const decryptData = (data, password) => aes.decrypt(data, password).toString(Utf8);
/* Returns a splice of the hash of the users password */
const makeSubHash = (pass) => sha256(pass).toString().slice(0, 14);
/* Makes the backup */
export const backup = (data, password) => axios.post(ENDPOINT, {
userData: encryptData(data, password),
subHash: makeSubHash(password),
});
/* Updates and existing backup */
export const update = (data, password, backupId) => axios.put(ENDPOINT, {
backupId,
userData: encryptData(data, password),
subHash: makeSubHash(password),
});
const encodeGetParams = p => Object.entries(p).map(kv => kv.map(encodeURIComponent).join('=')).join('&');
/* Restores the backup */
export const restore = (backupId, password) => {
const params = encodeGetParams({ backupId, subHash: makeSubHash(password) });
console.log(makeSubHash(password));
const url = `${ENDPOINT}/?${params}`;
return new Promise((resolve, reject) => {
axios.get(url).then((response) => {
if (!response.data || response.data.errorMsg) {
reject(response.data.errorMsg || 'Error');
} else {
const decryptedData = decryptData(response.data.userData.userData, password);
try { resolve(JSON.parse(decryptedData)); } catch (e) { reject(e); }
}
});
});
};

View File

@ -47,6 +47,8 @@ module.exports = {
CONF_SECTIONS: 'confSections',
PAGE_INFO: 'pageInfo',
APP_CONFIG: 'appConfig',
BACKUP_ID: 'backupId',
BACKUP_HASH: 'backupHash',
},
topLevelConfKeys: {
PAGE_INFO: 'pageInfo',
@ -55,9 +57,10 @@ module.exports = {
},
toastedOptions: {
position: 'bottom-center',
duration: 2000,
duration: 2500,
keepOnHover: true,
className: 'toast-message',
iconPack: 'fontawesome',
},
backupEndpoint: 'https://dashy-sync-service.as93.net',
};

View File

@ -1904,6 +1904,13 @@ aws4@^1.8.0:
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
babel-eslint@^10.0.1:
version "10.1.0"
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232"
@ -2956,6 +2963,11 @@ crypto-browserify@^3.11.0:
randombytes "^2.0.0"
randomfill "^1.0.3"
crypto-js@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc"
integrity sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==
css-color-names@0.0.4, css-color-names@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@ -4167,6 +4179,11 @@ follow-redirects@^1.0.0:
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
follow-redirects@^1.10.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"