feat: update header feat: change header to have more links Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: implement Restore page Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: implement my bubble page Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: replace old pages with new pages feat: implement devices page Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: implement account policy page Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: implement payment and bills page Merge branch 'feat/ui-layout' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: change config to accept env files Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: make a stripe element component Merge branch 'master' into feat/ui-layout Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: implement manage ssh key page feat: set password page feat: implement change password page fix: change design feat: implement my account page feat: implement animation control for launching bubble screen fix: navigating to network page feat: implement Launching bubble page feat: integrate launch bubble api Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: implement MFA in login Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: implement adding ssh key Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout fix: showing default values Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout feat: implement selector placeholders and default values Merge branch 'feat/ui-layout' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout fix: getting user information after setting payment method Merge branch 'master' into feat/ui-layout feat: implement setting payment plan in payment page feat: populate options for advanced settings modal fix: remove showing advanced settings modal when loading the page feat: implement Advanced Settings Modal layout feat: implement modal and selector components feat: implement launch bubble screen Merge branch 'master' of git.bubblev.org:bubblev/bubble-web into feat/ui-layout Co-authored-by: Tyler <everdev0923@gmail.com> Co-authored-by: jonathan <jonathan@noreply.git.bubblev.org> Reviewed-on: https://git.bubblev.org/bubblev/bubble-web/pulls/47pull/56/head
@@ -4,7 +4,7 @@ | |||
<div class="d-flex justify-content-center align-items-center container "> | |||
<img src="/small-BubbleLogo-Horizontal-BlackText.png" height="40" /> | |||
<div class="flex-grow-1"></div> | |||
<div class="navbar d-none d-md-flex"> | |||
<div class="navbar d-none d-lg-flex"> | |||
<!--- If not logged in ---> | |||
<div | |||
v-if="status.loggedIn !== true" | |||
@@ -41,6 +41,38 @@ | |||
{{ messages.label_menu_network }} | |||
</Button> | |||
</router-link> | |||
<router-link | |||
to="/devices" | |||
class="d-flex align-items-center" | |||
v-if="!configs.sageLauncher" | |||
> | |||
<Button headerLink> | |||
{{ messages.label_menu_devices }} | |||
</Button> | |||
</router-link> | |||
<router-link | |||
to="/apps" | |||
class="d-flex align-items-center" | |||
v-if="!configs.sageLauncher" | |||
> | |||
<Button headerLink> | |||
{{ messages.label_menu_apps }} | |||
</Button> | |||
</router-link> | |||
<router-link | |||
to="/admin/accounts" | |||
class="d-flex align-items-center" | |||
v-if="!configs.sageLauncher" | |||
> | |||
<Button headerLink> | |||
{{ messages.label_menu_admin_users }} | |||
</Button> | |||
</router-link> | |||
<router-link to="/me" class="d-flex align-items-center"> | |||
<Button headerLink> | |||
{{ messages.label_menu_account }} | |||
</Button> | |||
</router-link> | |||
<router-link to="/help" class="d-flex align-items-center"> | |||
<Button headerLink> | |||
{{ messages.button_label_help }} | |||
@@ -53,14 +85,14 @@ | |||
</router-link> | |||
</div> | |||
</div> | |||
<div class="navbar d-md-none" @click="toggleNavbar()"> | |||
<div class="navbar d-lg-none" @click="toggleNavbar()"> | |||
<i class="fas fa-bars"></i> | |||
</div> | |||
</div> | |||
<div | |||
class="dropdown-menu dropdown-menu-right d-md-none w-100 my-0" | |||
:class="{ show: menuVisible }" | |||
class="dropdown-menu dropdown-menu-right w-100 my-0" | |||
v-click-outside="hide" | |||
> | |||
<div v-if="status.loggedIn === false"> | |||
@@ -81,6 +113,30 @@ | |||
<router-link class="dropdown-item" to="/bubbles"> | |||
{{ messages.label_menu_network }} | |||
</router-link> | |||
<router-link | |||
to="/devices" | |||
class="dropdown-item" | |||
v-if="!configs.sageLauncher" | |||
> | |||
{{ messages.label_menu_devices }} | |||
</router-link> | |||
<router-link | |||
to="/apps" | |||
class="dropdown-item" | |||
v-if="!configs.sageLauncher" | |||
> | |||
{{ messages.label_menu_apps }} | |||
</router-link> | |||
<router-link | |||
to="/admin/accounts" | |||
class="dropdown-item" | |||
v-if="!configs.sageLauncher" | |||
> | |||
{{ messages.label_menu_admin_users }} | |||
</router-link> | |||
<router-link to="/me" class="dropdown-item"> | |||
{{ messages.label_menu_account }} | |||
</router-link> | |||
<router-link class="dropdown-item" to="/help"> | |||
{{ messages.button_label_help }} | |||
</router-link> | |||
@@ -135,7 +191,7 @@ export default { | |||
data() { | |||
return { | |||
menuVisible: false, | |||
prevVisibleState: false | |||
timerId: null, | |||
}; | |||
}, | |||
@@ -146,11 +202,15 @@ export default { | |||
methods: { | |||
toggleNavbar() { | |||
this.menuVisible = !this.prevVisibleState; | |||
this.menuVisible = !this.menuVisible; | |||
this.timerId = setTimeout(() => { | |||
this.timerId = null; | |||
}, 100); | |||
}, | |||
hide() { | |||
this.prevVisibleState = this.menuVisible; | |||
this.menuVisible = false; | |||
if (!this.timerId) { | |||
this.menuVisible = false; | |||
} | |||
}, | |||
}, | |||
}; | |||
@@ -0,0 +1,213 @@ | |||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | |||
<template> | |||
<div class="bubble-form"> | |||
<h2>{{ messages.form_title_restore }}</h2> | |||
<div v-if="!configs"><img :src="loadingImgSrc" /></div> | |||
<div v-else-if="configs.restoreInProgress" class="alert alert-info"> | |||
{{ messages.message_restore_in_progress }} | |||
<hr /> | |||
<a href="/">{{ messages.message_back_to_root }}</a> | |||
</div> | |||
<div v-else> | |||
<form @submit.prevent="handleSubmit"> | |||
<fieldset :disabled="status.uploadingRestoreRequestData"> | |||
<div class="form-group"> | |||
<Input | |||
type="text" | |||
v-model="restoreShortKey" | |||
name="restoreShortKey" | |||
class="form-control" | |||
:class="{ 'is-invalid': submitted && !restoreShortKey }" | |||
:placeholder="messages.field_label_restore_short_key" | |||
/> | |||
<div | |||
v-show="submitted && !restoreShortKey" | |||
class="invalid-feedback" | |||
> | |||
{{ messages.err_restoreShortKey_required }} | |||
</div> | |||
<div | |||
v-if="submitted && errors.has('restoreShortKey')" | |||
class="invalid-feedback d-block" | |||
> | |||
{{ errors.first('restoreShortKey') }} | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<div style="border: 2px solid #000;"> | |||
<label htmlFor="restoreLongNetworkKeyFile"> | |||
{{ messages.field_label_restore_long_key }} | |||
</label> | |||
<input | |||
type="file" | |||
ref="restoreLongNetworkKeyFile" | |||
@change="readUploadedKeyFile" | |||
class="form-control" | |||
:class="{ 'is-invalid': fileFieldsConstaintFailed }" | |||
/> | |||
<div | |||
v-if="submitted && errors.has('restoreLongNetworkKey')" | |||
class="invalid-feedback d-block" | |||
> | |||
{{ errors.first('restoreLongNetworkKey') }} | |||
</div> | |||
<div style="text-align: center">{{ messages.label_or }}</div> | |||
<label htmlFor="restoreBackupPackageFile"> | |||
{{ messages.field_label_restore_backup_package }} | |||
</label> | |||
<input | |||
type="file" | |||
ref="restoreBackupPackageFile" | |||
@change="setBackupFileForUpload" | |||
class="form-control" | |||
:class="{ 'is-invalid': fileFieldsConstaintFailed }" | |||
/> | |||
<div | |||
v-if="submitted && errors.has('restoreBackupPackageFileRef')" | |||
class="invalid-feedback d-block" | |||
> | |||
{{ errors.first('restoreBackupPackageFileRef') }} | |||
</div> | |||
</div> | |||
<div | |||
class="invalid-feedback" | |||
:class="{ 'd-block': fileFieldsConstaintFailed }" | |||
> | |||
{{ messages.err_restoreFile_constraint }} | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<Input | |||
type="password" | |||
v-model="password" | |||
name="password" | |||
class="form-control" | |||
:class="{ 'is-invalid': submitted && !password }" | |||
:placeholder="messages.field_label_password" | |||
/> | |||
<div v-show="submitted && !password" class="invalid-feedback"> | |||
{{ messages.err_password_required }} | |||
</div> | |||
<div | |||
v-if="submitted && errors.has('password')" | |||
class="invalid-feedback d-block" | |||
> | |||
{{ errors.first('password') }} | |||
</div> | |||
</div> | |||
<div class="form-group"> | |||
<Button | |||
color="default" | |||
class="btn btn-primary" | |||
:disabled="status.uploadingRestoreRequestData" | |||
> | |||
{{ messages.button_label_restore }} | |||
</Button> | |||
<img | |||
v-show="status.uploadingRestoreRequestData" | |||
:src="loadingImgSrc" | |||
/> | |||
</div> | |||
</fieldset> | |||
</form> | |||
</div> | |||
</div> | |||
</template> | |||
<style lang="scss" scoped> | |||
@import '../../../_scss/components/form'; | |||
@import '../../../_scss/breakpoints'; | |||
.bubble-form { | |||
width: 800px; | |||
} | |||
</style> | |||
<script> | |||
import { mapState, mapActions } from 'vuex'; | |||
import { loadingImgSrc } from '~/_store'; | |||
import { Input, Button } from '~/_components/shared'; | |||
export default { | |||
components: { | |||
Input, | |||
Button, | |||
}, | |||
data() { | |||
return { | |||
restoreShortKey: | |||
this.$route.query && this.$route.query.k ? this.$route.query.k : null, | |||
restoreLongNetworkKey: null, | |||
restoreBackupPackageFileRef: null, | |||
password: '', | |||
submitted: false, | |||
loadingImgSrc: loadingImgSrc, | |||
}; | |||
}, | |||
created() { | |||
this.loadSystemConfigs(); | |||
}, | |||
computed: { | |||
...mapState('account', ['status']), | |||
...mapState('system', ['configs', 'messages']), | |||
fileFieldsConstaintFailed() { | |||
// only and exactly 1 file may be uploaded - either file including long network key, or full backup package | |||
return ( | |||
this.submitted && | |||
((!this.restoreLongNetworkKey && !this.restoreBackupPackageFileRef) || | |||
(this.restoreLongNetworkKey && this.restoreBackupPackageFileRef)) | |||
); | |||
}, | |||
}, | |||
methods: { | |||
...mapActions('account', ['restore', 'restoreFromPackage']), | |||
...mapActions('system', ['loadSystemConfigs']), | |||
handleSubmit(e) { | |||
this.errors.clear(); | |||
const { restoreShortKey, password } = this; | |||
this.submitted = true; | |||
if (!this.fileFieldsConstaintFailed) { | |||
if (this.restoreLongNetworkKey) { | |||
this.restore({ | |||
shortKey: restoreShortKey, | |||
longKey: this.restoreLongNetworkKey, | |||
password: password, | |||
systemConfigs: this.configs, | |||
messages: this.messages, | |||
errors: this.errors, | |||
}); | |||
} else if (this.restoreBackupPackageFileRef) { | |||
this.restoreFromPackage({ | |||
shortKey: restoreShortKey, | |||
backupFileRef: this.restoreBackupPackageFileRef, | |||
password: password, | |||
systemConfigs: this.configs, | |||
messages: this.messages, | |||
errors: this.errors, | |||
}); | |||
} | |||
} | |||
}, | |||
readUploadedKeyFile() { | |||
var uploadedFile = this.$refs.restoreLongNetworkKeyFile.files[0]; | |||
var reader = new FileReader(); | |||
reader.onload = (event) => | |||
(this.restoreLongNetworkKey = event.target.result); | |||
reader.readAsText(uploadedFile); | |||
}, | |||
setBackupFileForUpload() { | |||
this.restoreBackupPackageFileRef = this.$refs.restoreBackupPackageFile.files[0]; | |||
}, | |||
}, | |||
}; | |||
</script> |
@@ -79,65 +79,16 @@ export const router = new Router({ | |||
path: '', | |||
component: () => import('~/_pages/main/account/Layout'), | |||
children: [ | |||
{ | |||
path: 'verify-email', | |||
component: () => import('~/_pages/main/account/VerifyEmail'), | |||
}, | |||
{ | |||
path: 'payment', | |||
component: () => import('~/_pages/main/account/Payment'), | |||
}, | |||
{ | |||
path: 'me', | |||
exact: true, | |||
component: () => import('~/_pages/main/account/MyAccount'), | |||
}, | |||
{ | |||
path: 'me/change-password', | |||
component: () => | |||
import('~/_pages/main/account/ChangePassword'), | |||
}, | |||
{ | |||
path: 'me/set-password/:code', | |||
component: () => import('~/_pages/main/account/SetPassword'), | |||
}, | |||
{ | |||
path: 'me/keys', | |||
component: () => import('~/_pages/main/account/ManageSSH'), | |||
}, | |||
{ | |||
path: 'me/payment', | |||
component: () => | |||
import('~/_pages/main/account/PaymentMethods'), | |||
}, | |||
{ | |||
path: 'me/bills', | |||
component: () => import('~/_pages/main/account/Bills'), | |||
}, | |||
{ | |||
path: 'me/policy', | |||
component: () => import('~/_pages/main/account/Policy'), | |||
}, | |||
{ | |||
path: 'devices', | |||
component: () => import('~/_pages/main/account/Devices'), | |||
}, | |||
{ | |||
path: 'bubble/:id', | |||
component: () => import('~/_pages/main/bubble/Network'), | |||
}, | |||
{ | |||
path: 'restore', | |||
component: () => import('~/_pages/main/bubble/Restore'), | |||
}, | |||
], | |||
}, | |||
{ | |||
path: 'launch-bubble', | |||
component: () => import('~/_pages/main/bubble/LaunchBubble'), | |||
}, | |||
{ | |||
path: 'launching-bubble/:id', | |||
component: () => import('~/_pages/main/bubble/LaunchingBubble'), | |||
}, | |||
{ | |||
path: 'test', | |||
component: () => import('~/_pages/main/Test'), | |||