@@ -33,7 +33,7 @@ | |||
</router-link> | |||
</div> | |||
<div v-else class="d-flex justify-content-center align-items-center"> | |||
<router-link to="/bubbles" class="d-flex align-items-center"> | |||
<router-link to="/bubble" class="d-flex align-items-center"> | |||
<Button headerLink> | |||
{{ messages.label_menu_network }} | |||
</Button> | |||
@@ -104,7 +104,7 @@ | |||
</router-link> | |||
</div> | |||
<div v-else> | |||
<router-link class="dropdown-item" to="/bubbles"> | |||
<router-link class="dropdown-item" to="/bubble"> | |||
{{ messages.label_menu_network }} | |||
</router-link> | |||
<router-link | |||
@@ -14,7 +14,7 @@ | |||
:class="{ 'menu-invisible': !menuVisible }" | |||
> | |||
<!-- Profile --> | |||
<router-link to="/me"> | |||
<router-link to="/me" @click.native="closeMenu()"> | |||
<div class="profile"> | |||
<img | |||
:src="`https://www.gravatar.com/avatar/${userHash}?r=pg`" | |||
@@ -31,30 +31,61 @@ | |||
<!-- Navigation --> | |||
<div class="navigation"> | |||
<div class="navigation-item active"> | |||
<router-link to="/bubble" class="navigation-item" @click.native="closeMenu()"> | |||
<i class="fa fa-home icon icon-home"></i> | |||
<span>{{ messages.label_menu_network }}</span> | |||
</div> | |||
<div class="navigation-item"> | |||
</router-link> | |||
<router-link | |||
to="/devices" | |||
v-if="!configs.sageLauncher" | |||
class="navigation-item" | |||
> | |||
<i class="fa fa-tablet icon icon-devices"></i> | |||
<span>{{ messages.label_menu_devices }}</span> | |||
</div> | |||
<div class="navigation-item"> | |||
</router-link> | |||
<router-link | |||
to="/apps" | |||
v-if="!configs.sageLauncher" | |||
class="navigation-item" | |||
> | |||
<i class="fa fa-smile icon icon-apps"></i> | |||
<span>{{ messages.label_menu_apps }}</span> | |||
</router-link> | |||
<router-link | |||
to="/admin/accounts" | |||
v-if="!configs.sageLauncher" | |||
class="navigation-item" | |||
> | |||
<i class="fa fa-users icon icon-users"></i> | |||
<span> | |||
{{ messages.label_menu_admin_users }} | |||
</span> | |||
</router-link> | |||
<!-- settings --> | |||
<!-- <router-link class="navigation-item"> | |||
<i class="fa fa-cog icon icon-settings"></i> | |||
<span>{{ messages.label_menu_settings }}</span> | |||
</div> | |||
<div class="navigation-item"> | |||
</router-link> --> | |||
<router-link to="/support" class="navigation-item" @click.native="closeMenu()"> | |||
<i class="fa fa-question-circle icon icon-help"></i> | |||
<span>{{ messages.label_menu_help }}</span> | |||
</div> | |||
</router-link> | |||
</div> | |||
<!-- Upgrade Plan --> | |||
<div class="flex-grow-1"></div> | |||
<!-- Logout --> | |||
<Button color="outline" class="logout-button"> | |||
{{ messages.log_out }} | |||
</Button> | |||
<router-link to="/logout" class="logout-button" @click.native="closeMenu()"> | |||
<Button color="outline" block> | |||
{{ messages.log_out }} | |||
</Button> | |||
</router-link> | |||
</div> | |||
</aside> | |||
</template> | |||
@@ -171,7 +202,7 @@ | |||
margin-left: 20px; | |||
} | |||
&.active { | |||
&.router-link-active { | |||
background-color: #66cda4; | |||
color: white; | |||
border-radius: 20px; | |||
@@ -195,6 +226,7 @@ | |||
.icon { | |||
font-size: 20px; | |||
width: 20px; | |||
} | |||
.icon-home { | |||
@@ -203,6 +235,12 @@ | |||
.icon-devices { | |||
color: #4b53df; | |||
} | |||
.icon-apps { | |||
color: #9916df; | |||
} | |||
.icon-users { | |||
color: #2ed1a1; | |||
} | |||
.icon-settings { | |||
color: #e6458a; | |||
} | |||
@@ -229,7 +267,7 @@ export default { | |||
}), | |||
computed: { | |||
...mapState('system', ['messages']), | |||
...mapState('system', ['messages', 'configs']), | |||
userHash() { | |||
return md5(this.currentUser.email); | |||
@@ -240,6 +278,10 @@ export default { | |||
toggleMenu() { | |||
this.menuVisible = !this.menuVisible; | |||
}, | |||
closeMenu() { | |||
console.log('click.nativeed'); | |||
this.menuVisible = false; | |||
} | |||
}, | |||
}; | |||
</script> |
@@ -1,6 +1,6 @@ | |||
<!-- 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"> | |||
<div> | |||
<em v-if="loading && loading.devices">{{ messages.loading_devices }}</em> | |||
<div v-if="devices && devices.length > 0"> | |||
<h2>{{ messages.table_title_devices }}</h2> | |||
@@ -260,7 +260,7 @@ export default { | |||
newNodeNotification(nn) { | |||
if (nn && nn.uuid) { | |||
this.$router.push({ | |||
path: '/launching-bubble/' + nn.networkName, | |||
path: '/bubble/' + nn.networkName, | |||
}); | |||
this.submitted = false; | |||
} | |||
@@ -281,7 +281,7 @@ export default { | |||
network(net) { | |||
if (net) { | |||
if (net.uuid === 'Not Found') | |||
this.$router.replace({ path: '/bubbles' }); | |||
this.$router.replace({ path: '/bubble' }); | |||
this.networkUuid = net.uuid; | |||
if (net.state !== 'stopping') | |||
this.clearRefresherInterval(this.stopRefresher); | |||
@@ -327,7 +327,7 @@ export default { | |||
deletedNetworkUuid(uuid) { | |||
if (uuid && this.networkUuid && uuid === this.networkUuid) { | |||
this.$router.replace({ path: '/bubbles' }); | |||
this.$router.replace({ path: '/bubble' }); | |||
} | |||
}, | |||
@@ -0,0 +1,72 @@ | |||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | |||
<template> | |||
<div> | |||
<em v-if="loading && loading.networks">{{ messages.loading_networks }}</em> | |||
<div v-if="networks && networks.length > 0"> | |||
<!-- Show network list --> | |||
<NetworkList /> | |||
</div> | |||
<div v-else> | |||
<!-- Show Launch Bubble Page --> | |||
<LaunchBubble /> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { mapState, mapActions, mapGetters } from 'vuex'; | |||
import { util } from '~/_helpers'; | |||
import NetworkList from './Networks'; | |||
import LaunchBubble from './LaunchBubble'; | |||
export default { | |||
components: { | |||
NetworkList, | |||
LaunchBubble, | |||
}, | |||
computed: { | |||
...mapState('networks', ['networks']), | |||
...mapState('system', ['messages', 'configs']), | |||
...mapState('users', ['policy']), | |||
}, | |||
created() { | |||
if (!this.configs.sageLauncher) { | |||
this.$router.replace({ path: '/bubble/' + this.configs.networkUuid }); | |||
} | |||
const user = util.currentUser(); | |||
const selectedLocale = | |||
user !== null && | |||
typeof user.locale !== 'undefined' && | |||
user.locale !== null | |||
? user.locale | |||
: 'detect'; | |||
this.getAllNetworks({ | |||
userId: user.uuid, | |||
messages: this.messages, | |||
errors: this.errors, | |||
}); | |||
this.loadMessages('post_auth', selectedLocale); | |||
this.loadMessages('apps', selectedLocale); | |||
}, | |||
methods: { | |||
...mapActions('networks', [ | |||
'getAllNetworks', | |||
'stopNetwork', | |||
'deleteNetwork', | |||
]), | |||
...mapGetters('networks', ['loading']), | |||
...mapActions('system', ['loadMessages']), | |||
}, | |||
watch: { | |||
networks(nets) { | |||
if (nets && nets.length === 1 && util.currentUser().admin !== true) { | |||
this.$router.replace({ path: '/bubble/' + nets[0].name }); | |||
} | |||
}, | |||
}, | |||
}; | |||
</script> |
@@ -1,6 +1,6 @@ | |||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | |||
<template> | |||
<div v-if="network" class="bubble-form"> | |||
<div v-if="network"> | |||
<h4> | |||
{{ network.nickname }} - | |||
<i>{{ messages['msg_network_state_' + network.state] }}</i> | |||
@@ -12,8 +12,7 @@ | |||
<h6 | |||
v-if=" | |||
!isSelfNet && | |||
(isInReadyToRestoreState || network.state === 'running') | |||
!isSelfNet && (isInReadyToRestoreState || network.state === 'running') | |||
" | |||
> | |||
<Button | |||
@@ -42,6 +41,8 @@ | |||
network.state === 'restoring') | |||
" | |||
> | |||
<div ref="lottie" class="lottie"></div> | |||
<!-- adapted from: https://code-boxx.com/simple-vanilla-javascript-progress-bar/ --> | |||
<div v-if="stats.percent" class="progress-wrap"> | |||
<div | |||
@@ -80,7 +81,8 @@ | |||
<table border="0" width="100%"> | |||
<tr> | |||
<td | |||
v-for="deviceType in addableDeviceTypes" | |||
v-for="(deviceType, key) in addableDeviceTypes" | |||
:key="key" | |||
align="center" | |||
:width="addableDeviceWidth + '%'" | |||
> | |||
@@ -394,12 +396,14 @@ | |||
@import '../../../_scss/components/form'; | |||
@import '../../../_scss/breakpoints'; | |||
.bubble-form { | |||
width: 800px; | |||
.lottie { | |||
width: 100%; | |||
max-width: 500px; | |||
} | |||
</style> | |||
<script> | |||
import Lottie from 'lottie-web'; | |||
import { mapState, mapActions, mapGetters } from 'vuex'; | |||
import { util } from '~/_helpers'; | |||
import { loadingImgSrc } from '~/_store'; | |||
@@ -427,8 +431,16 @@ export default { | |||
upgradeRefresher: null, | |||
logsExpirationDays: null, | |||
backupDownloadRefresher: null, | |||
lottie: null, | |||
timerID: null, | |||
frameNumber: 0, | |||
targetFrameNumber: 0, | |||
}; | |||
}, | |||
mounted() {}, | |||
computed: { | |||
...mapState('networks', [ | |||
'network', | |||
@@ -577,6 +589,23 @@ export default { | |||
'upgrade', | |||
]), | |||
refreshStatus(userId) { | |||
if (!this.lottie && this.$refs.lottie) { | |||
this.lottie = Lottie.loadAnimation({ | |||
container: this.$refs.lottie, | |||
renderer: '', | |||
loop: false, | |||
autoplay: false, | |||
path: '/launching-bubble.json', | |||
}); | |||
this.frameNumber = 0; | |||
this.targetFrameNumber = 20; | |||
this.timerID = setInterval(() => { | |||
if (this.frameNumber < this.targetFrameNumber) { | |||
this.frameNumber++; | |||
} | |||
}, 100); | |||
} | |||
this.getNetworkById({ | |||
userId: userId, | |||
networkId: this.networkId, | |||
@@ -757,6 +786,7 @@ export default { | |||
this.upgrade(); | |||
}, | |||
}, | |||
created() { | |||
const user = util.currentUser(); | |||
this.refreshStatus(user.uuid); | |||
@@ -765,17 +795,18 @@ export default { | |||
this.getAppLinks(user.locale); | |||
this.loadSystemConfigs(); | |||
}, | |||
beforeDestroy() { | |||
this.clearRefresherInterval(this.refresher); | |||
this.clearRefresherInterval(this.stopRefresher); | |||
this.clearRefresherInterval(this.backupDownloadRefresher); | |||
this.resetRestoreKey(); | |||
}, | |||
watch: { | |||
network(net) { | |||
network(net, oldNet) { | |||
if (net) { | |||
if (net.uuid === 'Not Found') | |||
this.$router.replace({ path: '/bubbles' }); | |||
if (net.uuid === 'Not Found') this.$router.replace({ path: '/bubble' }); | |||
this.networkUuid = net.uuid; | |||
if (net.state !== 'stopping') | |||
this.clearRefresherInterval(this.stopRefresher); | |||
@@ -814,10 +845,22 @@ export default { | |||
// status not found for our network | |||
this.clearRefresherInterval(this.refresher); | |||
} | |||
if (this.stats) { | |||
this.targetFrameNumber = 20 + (30 / 100) * this.stats.percent; | |||
} | |||
}, | |||
frameNumber() { | |||
this.lottie.goToAndStop(this.frameNumber, true); | |||
if (this.frameNumber >= 50) { | |||
clearInterval(this.timerID); | |||
} | |||
}, | |||
deletedNetworkUuid(uuid) { | |||
if (uuid && this.networkUuid && uuid === this.networkUuid) { | |||
this.$router.replace({ path: '/bubbles' }); | |||
this.$router.replace({ path: '/bubble' }); | |||
} | |||
}, | |||
configs(c) { | |||
@@ -0,0 +1,117 @@ | |||
<!-- Copyright (c) 2020 Bubble, Inc. All rights reserved. For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ --> | |||
<template> | |||
<div> | |||
<div v-if="networks && networks.length > 0"> | |||
<h2>{{ messages.table_title_networks }}</h2> | |||
<table border="1"> | |||
<thead> | |||
<tr> | |||
<th nowrap="nowrap">{{ messages.label_field_networks_name }}</th> | |||
<th nowrap="nowrap">{{ messages.label_field_networks_locale }}</th> | |||
<th nowrap="nowrap"> | |||
{{ messages.label_field_networks_timezone }} | |||
</th> | |||
<th nowrap="nowrap"> | |||
{{ messages.label_field_networks_object_state }} | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<tr v-for="network in networks" :key="network.uuid"> | |||
<td> | |||
<router-link :to="{ path: '/bubble/' + network.name }" | |||
>{{ network.name }}.{{ network.domainName }}</router-link | |||
> | |||
</td> | |||
<td nowrap="nowrap"> | |||
{{ messages['locale_' + network.locale] || network.locale }} | |||
</td> | |||
<td nowrap="nowrap"> | |||
{{ messages['tz_name_' + network.timezone] || network.timezone }} | |||
</td> | |||
<td>{{ messages['msg_network_state_' + network.state] }}</td> | |||
</tr> | |||
</tbody> | |||
</table> | |||
<hr /> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { mapState, mapActions, mapGetters } from 'vuex'; | |||
import { util } from '~/_helpers'; | |||
export default { | |||
data() { | |||
return { | |||
verifiedContacts: null, | |||
}; | |||
}, | |||
computed: { | |||
...mapState('networks', ['networks']), | |||
...mapState('system', ['messages', 'configs']), | |||
...mapState('users', ['policy']), | |||
}, | |||
created() { | |||
if (!this.configs.sageLauncher) { | |||
this.$router.replace({ path: '/bubble/' + this.configs.networkUuid }); | |||
} | |||
const user = util.currentUser(); | |||
const selectedLocale = | |||
user !== null && | |||
typeof user.locale !== 'undefined' && | |||
user.locale !== null | |||
? user.locale | |||
: 'detect'; | |||
this.getAllNetworks({ | |||
userId: user.uuid, | |||
messages: this.messages, | |||
errors: this.errors, | |||
}); | |||
this.loadMessages('post_auth', selectedLocale); | |||
this.loadMessages('apps', selectedLocale); | |||
this.getPolicyByUserId({ | |||
userId: user.uuid, | |||
messages: this.messages, | |||
errors: this.errors, | |||
}); | |||
}, | |||
methods: { | |||
...mapActions('users', ['getPolicyByUserId']), | |||
...mapActions('networks', [ | |||
'getAllNetworks', | |||
'stopNetwork', | |||
'deleteNetwork', | |||
]), | |||
...mapGetters('networks', ['loading']), | |||
...mapActions('system', ['loadMessages']), | |||
hasVerifiedContact(policy) { | |||
if (policy && policy.accountContacts) { | |||
const contacts = policy.accountContacts; | |||
for (let i = 0; i < contacts.length; i++) { | |||
if (contacts[i].verified && isNotAuthenticator(contacts[i])) | |||
return true; | |||
} | |||
return false; | |||
} | |||
return false; | |||
}, | |||
}, | |||
watch: { | |||
networks(nets) { | |||
if (nets && nets.length) { | |||
if (nets.length === 0) { | |||
this.$router.replace({ path: '/new_bubble' }); | |||
} else if (nets.length === 1 && util.currentUser().admin !== true) { | |||
this.$router.replace({ path: '/bubble/' + nets[0].name }); | |||
} | |||
} | |||
}, | |||
policy(p) { | |||
this.verifiedContacts = this.hasVerifiedContact(p); | |||
}, | |||
}, | |||
}; | |||
</script> |
@@ -90,6 +90,8 @@ export const router = new Router({ | |||
// { path: '/restore', component: RestorePage }, | |||
// new route | |||
{ path: '', redirect: '/bubble', exact: true }, | |||
{ | |||
path: '', | |||
component: () => import('~/_pages/Layout'), | |||
@@ -122,22 +124,13 @@ export const router = new Router({ | |||
}, | |||
{ path: 'notifications', component: NotificationsPage }, | |||
{ | |||
path: 'bubbles', | |||
component: NetworksPage, | |||
children: [ | |||
{ | |||
path: '', | |||
component: NewNetworkPage, | |||
children: newNetworkChildren, | |||
}, | |||
], | |||
path: 'bubble', | |||
component: () => import('~/_pages/main/bubble/MyBubble'), | |||
}, | |||
{ | |||
path: 'new_bubble', | |||
component: NewNetworkPage, | |||
children: newNetworkChildren, | |||
path: 'bubble/:id', | |||
component: () => import('~/_pages/main/bubble/Network'), | |||
}, | |||
{ path: 'bubble/:id', component: NetworkPage }, | |||
{ path: 'action', component: ActionPage }, | |||
{ path: 'resetPassword/:code', component: SetPasswordPage }, | |||
@@ -214,14 +207,6 @@ export const router = new Router({ | |||
path: 'support', | |||
component: () => import('~/_pages/main/account/Support'), | |||
}, | |||
{ | |||
path: 'launch-bubble', | |||
component: () => import('~/_pages/main/bubble/LaunchBubble'), | |||
}, | |||
{ | |||
path: 'launching-bubble/:id', | |||
component: () => import('~/_pages/main/bubble/LaunchingBubble'), | |||
}, | |||
], | |||
}, | |||
{ | |||
@@ -99,19 +99,19 @@ const actions = { | |||
systemConfigs.bubbleNode === false || | |||
systemConfigs.sageLauncher === true | |||
) { | |||
router.replace('/bubbles'); | |||
router.replace('/bubble'); | |||
} else if (systemConfigs.bubbleNode === true) { | |||
router.replace('/devices'); | |||
} else { | |||
router.replace('/'); | |||
} | |||
} else { | |||
console.log( | |||
'account.login: landing page==' + | |||
JSON.stringify(landing) + | |||
', systemConfigs.bubbleNode==' + | |||
systemConfigs.bubbleNode | |||
); | |||
// console.log( | |||
// 'account.login: landing page==' + | |||
// JSON.stringify(landing) + | |||
// ', systemConfigs.bubbleNode==' + | |||
// systemConfigs.bubbleNode | |||
// ); | |||
util.resetLandingPage(); | |||
router.replace(landing.fullPath); | |||
} | |||
@@ -246,7 +246,7 @@ const getters = { | |||
}); | |||
if (configs.sageLauncher) { | |||
dashApps.push({ | |||
href: '/bubbles', | |||
href: '/bubble', | |||
title: messages.label_menu_networks, | |||
icon: messages.label_menu_networks_icon, | |||
index: 1, | |||
@@ -295,7 +295,7 @@ const getters = { | |||
index: 2, | |||
}); | |||
adminApps.push({ | |||
href: '/admin/bubbles', | |||
href: '/admin/bubble', | |||
title: messages.label_menu_admin_networks, | |||
icon: messages.label_menu_admin_networks_icon, | |||
index: 3, | |||
@@ -447,7 +447,7 @@ | |||
watch: { | |||
network (net) { | |||
if (net) { | |||
if (net.uuid === 'Not Found') this.$router.replace({path: '/bubbles'}); | |||
if (net.uuid === 'Not Found') this.$router.replace({path: '/bubble'}); | |||
this.networkUuid = net.uuid; | |||
if (net.state !== 'stopping') this.clearRefresherInterval(this.stopRefresher); | |||
} | |||
@@ -483,7 +483,7 @@ | |||
}, | |||
deletedNetworkUuid (uuid) { | |||
if (uuid && this.networkUuid && (uuid === this.networkUuid)) { | |||
this.$router.replace({path: '/bubbles'}); | |||
this.$router.replace({path: '/bubble'}); | |||
} | |||
}, | |||
configs (c) { | |||
@@ -72,8 +72,8 @@ | |||
console.log('ActionPage.watch.actionStatus: still requesting, doing nothing'); | |||
} else { | |||
if (status.success) { | |||
console.log('ActionPage.watch.actionStatus: sending to /bubbles with success'); | |||
this.$router.push({path: '/bubbles', query: {action: this.actionType, ok: 'true'}}); | |||
console.log('ActionPage.watch.actionStatus: sending to /bubble with success'); | |||
this.$router.push({path: '/bubble', query: {action: this.actionType, ok: 'true'}}); | |||
} else if (status.error !== null && status.type === 'approve' | |||
&& this.errors && this.errors.items && this.errors.items.length === 1 | |||
@@ -174,28 +174,6 @@ export default { | |||
isInRestoringMode() { | |||
return this.configs ? this.configs.awaitingRestore : undefined; | |||
}, | |||
isNewPage() { | |||
const newPages = [ | |||
'/login', | |||
'/forgotPassword', | |||
'/register', | |||
'/appLogin', | |||
'/verifyEmail', | |||
'/payment', | |||
'/me', | |||
'/devices', | |||
'/launch-bubble', | |||
'/launching-bubble', | |||
'/restore', | |||
'/support', | |||
'/legal', | |||
]; | |||
return ( | |||
newPages.includes(this.$route.path) || | |||
newPages.filter((p) => this.$route.path.startsWith(p)).length | |||
); | |||
}, | |||
}, | |||
methods: { | |||
...mapActions({ clearAlert: 'alert/clear' }), | |||
@@ -1,19 +0,0 @@ | |||
/** | |||
* Copyright (c) 2020 Bubble, Inc. All rights reserved. | |||
* For personal (non-commercial) use, see license: https://getbubblenow.com/bubble-license/ | |||
*/ | |||
// ------------------------------------------------------------- | |||
// Copy this file to webpack.config.dev.js to use in development | |||
// ------------------------------------------------------------- | |||
const webpackMerge = require('webpack-merge'); | |||
module.exports = webpackMerge.merge(require('./webpack.config.js'), { | |||
mode: 'development', | |||
devServer: { | |||
proxy: { | |||
'/api': 'http://example.com:8888' // change this to wherever your bubble API is running | |||
} | |||
}, | |||
}); |