Browse Source

jwt auth

readme
Mayank 4 years ago
parent
commit
fddbe94e99
No known key found for this signature in database GPG Key ID: D037D60476CE748C
  1. 3
      .env
  2. 3
      .env.production
  3. 3
      .env.staging
  4. 138
      src/App.vue
  5. 8
      src/components/Channels/Channel.vue
  6. 92
      src/helpers/api.js
  7. 283
      src/layouts/DashboardLayout.vue
  8. 2
      src/router/index.js
  9. 22
      src/store/modules/user.js
  10. 20
      src/views/Login.vue
  11. 24
      src/views/Settings.vue

3
.env

@ -1 +1,2 @@
VUE_APP_API_URL=http://localhost:3005 VUE_APP_API_URL=http://localhost:3005
VUE_APP_SYSTEM_API_URL=http://localhost:3333

3
.env.production

@ -1 +1,2 @@
VUE_APP_API_URL=/api VUE_APP_API_URL=/api
VUE_APP_SYSTEM_API_URL=/manager-api

3
.env.staging

@ -1 +1,2 @@
VUE_APP_API_URL=/api VUE_APP_API_URL=/api
VUE_APP_SYSTEM_API_URL=/manager-api

138
src/App.vue

@ -1,15 +1,8 @@
<template> <template>
<div id="app"> <div id="app">
<transition name="loading" mode> <!-- component matched by the route will render here -->
<loading
v-if="loading"
:text="loadingText"
:progress="loadingProgress"
></loading>
<!-- component matched by the route will render here -->
<router-view v-else></router-view> <router-view></router-view>
</transition>
</div> </div>
</template> </template>
@ -18,29 +11,12 @@
</style> </style>
<script> <script>
import { mapState } from "vuex";
import Loading from "@/components/Loading";
export default { export default {
name: "App", name: "App",
data() { data() {
return { return {};
loading: false,
loadingText: "Loading...",
loadingProgress: 0,
polling: null,
pollInProgress: false
};
},
computed: {
...mapState({
isApiOperational: state => state.system.api.operational,
isBitcoinOperational: state => state.bitcoin.operational,
isBitcoinCalibrating: state => state.bitcoin.calibrating,
isLndOperational: state => state.lightning.operational,
isLndUnlocked: state => state.lightning.unlocked
})
}, },
computed: {},
methods: { methods: {
//to do: move this to the specific layout that needs this 100vh fix //to do: move this to the specific layout that needs this 100vh fix
updateViewPortHeightCSS() { updateViewPortHeightCSS() {
@ -48,79 +24,12 @@ export default {
"--vh100", "--vh100",
`${window.innerHeight}px` `${window.innerHeight}px`
); );
},
async checkIfLoading() {
//prevent multiple polls if previous poll already in progress
if (this.pollInProgress) {
return;
}
this.pollInProgress = true;
//First check if API is active
await this.$store.dispatch("system/getApi");
if (!this.isApiOperational) {
this.loading = true;
this.loadingText = "Starting API...";
this.loadingProgress = 20;
this.pollInProgress = false;
return;
}
// Then check if bitcoind is operational
await this.$store.dispatch("bitcoin/getStatus");
if (!this.isBitcoinOperational) {
this.loading = true;
this.loadingText = "Starting Bitcoin Core...";
this.loadingProgress = 40;
this.pollInProgress = false;
return;
}
// Then check if bitcoind is calibrating
if (this.isBitcoinCalibrating) {
this.loading = true;
this.loadingText = "Calibrating Bitcoin Core...";
this.loadingProgress = 50;
this.pollInProgress = false;
return;
}
// Then check if lnd is operational
await this.$store.dispatch("lightning/getStatus");
if (!this.isLndOperational) {
this.loading = true;
this.loadingText = "Starting LND...";
this.loadingProgress = 70;
this.pollInProgress = false;
return;
}
// Then check if lnd is unlocked
if (!this.isLndUnlocked) {
this.loading = true;
this.loadingText = "Starting LND...";
this.loadingProgress = 90;
this.pollInProgress = false;
return;
}
this.loading = false;
this.loadingProgress = 100;
this.pollInProgress = false;
} }
}, },
created() { created() {
this.updateViewPortHeightCSS(); this.updateViewPortHeightCSS();
//for 100vh consistency //for 100vh consistency
window.addEventListener("resize", this.updateViewPortHeightCSS); window.addEventListener("resize", this.updateViewPortHeightCSS);
//trigger loading watcher
this.loading = true;
//immediately check loading on first load, watcher's interval takes care of polling
this.checkIfLoading();
}, },
mounted() { mounted() {
const isDarkMode = this.$store.getters.isDarkMode; const isDarkMode = this.$store.getters.isDarkMode;
@ -130,49 +39,14 @@ export default {
// document.body.style.background = isDarkMode ? "#1C1C26" : "#F7F9FB"; // document.body.style.background = isDarkMode ? "#1C1C26" : "#F7F9FB";
document.body.style.background = isDarkMode ? "#F7F9FB" : "#F7F9FB"; document.body.style.background = isDarkMode ? "#F7F9FB" : "#F7F9FB";
}, },
watch: { watch: {},
loading: function(nowLoading) {
window.clearInterval(this.polling);
//if loading, check every second
if (nowLoading) {
this.polling = window.setInterval(this.checkIfLoading, 1000);
} else {
//else check every 10s
this.polling = window.setInterval(this.checkIfLoading, 20000);
}
}
},
beforeDestroy() { beforeDestroy() {
window.removeEventListener("resize", this.updateViewPortHeightCSS); window.removeEventListener("resize", this.updateViewPortHeightCSS);
window.clearInterval(this.polling); window.clearInterval(this.polling);
}, },
components: { components: {}
Loading
}
}; };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// Loading transitions
.loading-enter-active,
.loading-leave-active {
transition: opacity 0.4s ease;
}
.loading-enter {
opacity: 0;
// filter: blur(70px);
}
.loading-enter-to {
opacity: 1;
// filter: blur(0);
}
.loading-leave {
opacity: 1;
// filter: blur(0);
}
.loading-leave-to {
opacity: 0;
// filter: blur(70px);
}
</style> </style>

8
src/components/Channels/Channel.vue

@ -53,7 +53,7 @@
</template> </template>
<script> <script>
import axios from "axios"; import API from "@/helpers/api";
import Status from "@/components/Utility/Status"; import Status from "@/components/Utility/Status";
import Bar from "@/components/Channels/Bar"; import Bar from "@/components/Channels/Bar";
@ -119,11 +119,11 @@ export default {
}, },
async updateNodeAlias() { async updateNodeAlias() {
if (this.channel.remotePubkey) { if (this.channel.remotePubkey) {
const nodeAlias = await axios.get( const nodeAlias = await API.get(
`${process.env.VUE_APP_API_URL}/v1/lnd/info/alias?pubkey=${this.channel.remotePubkey}` `${process.env.VUE_APP_API_URL}/v1/lnd/info/alias?pubkey=${this.channel.remotePubkey}`
); );
if (nodeAlias && nodeAlias.data) { if (nodeAlias) {
this.alias = nodeAlias.data.alias; this.alias = nodeAlias.alias;
} }
} }
} }

92
src/helpers/api.js

@ -1,4 +1,6 @@
import axios from "axios"; import axios from "axios";
import store from "@/store";
import router from "@/router";
// An object to store the response time of completed API requests // An object to store the response time of completed API requests
const responseTime = {}; const responseTime = {};
@ -6,9 +8,56 @@ const responseTime = {};
// An object to store pending API requests // An object to store pending API requests
const responsePending = {}; const responsePending = {};
// Interceptor to refresh JWT or logout user based on 401 requests
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, async function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Return any error which is not due to authentication back to the calling service
if (!error.response || error.response.status !== 401) {
return Promise.reject(error);
}
// Return the same 401 back if user is trying to login with incorrect password
if (error.config.url === `${process.env.VUE_APP_SYSTEM_API_URL}/v1/account/login`) {
return Promise.reject(error);
}
// Logout user if token refresh didn't work
if (error.config.url === `${process.env.VUE_APP_SYSTEM_API_URL}/v1/account/refresh` || error.config.message === "Invalid JWT") {
store.dispatch('user/logout')
router.push('/');
return Promise.reject(error);
}
// Try request again with new token
try {
await store.dispatch('user/refreshJWT');
} catch (error) {
return Promise.reject(error);
}
// New request with new token
const config = error.config;
config.headers['Authorization'] = `JWT ${store.state.user.jwt}`;
return new Promise((resolve, reject) => {
axios.request(config).then(response => {
resolve(response);
}).catch((error) => {
reject(error);
})
});
});
// Helper methods for making API requests // Helper methods for making API requests
const API = { const API = {
async get(url, config = {}) { async get(url, data = {}) {
let response; let response;
if (responsePending[url] === undefined || responsePending[url] === false) { if (responsePending[url] === undefined || responsePending[url] === false) {
@ -17,7 +66,17 @@ const API = {
try { try {
const startTime = new Date(); const startTime = new Date();
// await new Promise(resolve => setTimeout(resolve, 2000)) //2s API delay // await new Promise(resolve => setTimeout(resolve, 2000)) //2s API delay
response = (await axios.get(url, config)).data;
const requestOptions = {
method: "get",
url
};
if (store.state.user.jwt) {
requestOptions.headers = { 'Authorization': `JWT ${store.state.user.jwt}` };
}
response = (await axios(requestOptions, data)).data;
const endTime = new Date(); const endTime = new Date();
responseTime[url] = (endTime.getTime() - startTime.getTime()) / 1000; responseTime[url] = (endTime.getTime() - startTime.getTime()) / 1000;
@ -42,28 +101,35 @@ const API = {
// Wrap a post call // Wrap a post call
async post(url, data) { async post(url, data) {
return axios({
const requestOptions = {
method: "post", method: "post",
url, url,
data data
}); };
// If an instance of axios is passed, use it to make the call. Otherwise, use a fresh instance. if (store.state.user.jwt) {
// if (options.axios) { requestOptions.headers = { 'Authorization': `JWT ${store.state.user.jwt}` };
// return await options.axios.post(options.url, options.data, options.auth); }
// } else {
// return axios.post(options.url, options.data, options.auth); return axios(requestOptions);
// }
}, },
// Wrap a delete call // Wrap a delete call
async delete(url, data) { async delete(url, data) {
return axios({
const requestOptions = {
method: "delete", method: "delete",
url, url,
data data
}); };
if (store.state.user.jwt) {
requestOptions.headers = { 'Authorization': `JWT ${store.state.user.jwt}` };
}
return axios(requestOptions);
}, },
// Return the response time if this URL has already been fetched // Return the response time if this URL has already been fetched

283
src/layouts/DashboardLayout.vue

@ -1,16 +1,19 @@
<template> <template>
<div> <div>
<b-navbar type="light" variant="default" class="nav-horizontal"> <transition name="loading" mode>
<div> <loading v-if="loading" :text="loadingText" :progress="loadingProgress"></loading>
<b-navbar-brand to="/dashboard"> <div v-else>
<img src="@/assets/logo.svg" alt="Umbrel" height="50" /> <b-navbar type="light" variant="default" class="nav-horizontal">
</b-navbar-brand> <div>
</div> <b-navbar-brand to="/dashboard">
<img src="@/assets/logo.svg" alt="Umbrel" height="50" />
</b-navbar-brand>
</div>
<!-- <b-navbar-toggle target="nav-collapse"></b-navbar-toggle> --> <!-- <b-navbar-toggle target="nav-collapse"></b-navbar-toggle> -->
<!-- Search Bar --> <!-- Search Bar -->
<!-- <b-navbar-nav> <!-- <b-navbar-nav>
<div class="px-lg-4 px-xl-5 mx-xl-4"></div> <div class="px-lg-4 px-xl-5 mx-xl-4"></div>
<b-nav-form class="input-search-form"> <b-nav-form class="input-search-form">
<b-form-input <b-form-input
@ -20,88 +23,99 @@
></b-form-input> ></b-form-input>
<div class="input-search-icon"></div> <div class="input-search-icon"></div>
</b-nav-form> </b-nav-form>
</b-navbar-nav>--> </b-navbar-nav>-->
<!-- Right aligned nav items -->
<b-navbar-nav class="ml-auto">
<!-- Chain badge -->
<b-badge
variant="success"
v-if="chain !== 'main'"
class="align-self-center mr-2 text-capitalize"
pill
>{{ chain === "test" ? "testnet" : chain }}</b-badge>
<div
class="nav-hamburger-icon d-lg-none d-xl-none ml-1"
:class="{ active: isMobileMenuOpen }"
@click="toggleMobileMenu"
>
<div></div>
</div>
<b-nav-item-dropdown class="d-none d-lg-block d-xl-block" right no-caret>
<!-- Using 'button-content' slot -->
<template v-slot:button-content>Satoshi</template>
<b-dropdown-item @click="logout">Log out</b-dropdown-item>
</b-nav-item-dropdown>
</b-navbar-nav>
</b-navbar>
<!-- Mobile menu -->
<transition name="mobile-vertical-menu">
<div class="mobile-vertical-menu d-lg-none d-xl-none" v-if="isMobileMenuOpen">
<authenticated-vertical-navbar :isMobileMenu="true" />
</div>
</transition>
<transition name="mobile-vertical-menu-fader"> <!-- Right aligned nav items -->
<div <b-navbar-nav class="ml-auto">
class="mobile-vertical-menu-fader d-lg-none d-xl-none" <!-- Chain badge -->
v-if="isMobileMenuOpen" <b-badge
@click="toggleMobileMenu" variant="success"
></div> v-if="chain !== 'main'"
</transition> class="align-self-center mr-2 text-capitalize"
pill
>{{ chain === "test" ? "testnet" : chain }}</b-badge>
<div
class="nav-hamburger-icon d-lg-none d-xl-none ml-1"
:class="{ active: isMobileMenuOpen }"
@click="toggleMobileMenu"
>
<div></div>
</div>
<b-nav-item-dropdown class="d-none d-lg-block d-xl-block" right no-caret>
<!-- Using 'button-content' slot -->
<template v-slot:button-content>Satoshi</template>
<b-dropdown-item @click="logout">Log out</b-dropdown-item>
</b-nav-item-dropdown>
</b-navbar-nav>
</b-navbar>
<b-row class="mx-0"> <!-- Mobile menu -->
<b-col col lg="3" xl="2" class="d-none d-lg-block d-xl-block pl-0 pr-0 pr-xl-2"> <transition name="mobile-vertical-menu">
<authenticated-vertical-navbar /> <div class="mobile-vertical-menu d-lg-none d-xl-none" v-if="isMobileMenuOpen">
</b-col> <authenticated-vertical-navbar :isMobileMenu="true" />
</div>
<b-col col lg="9" xl="10"> </transition>
<div class="pr-xl-2">
<transition name="change-page" mode="out-in"> <transition name="mobile-vertical-menu-fader">
<!-- Content --> <div
<router-view></router-view> class="mobile-vertical-menu-fader d-lg-none d-xl-none"
</transition> v-if="isMobileMenuOpen"
</div> @click="toggleMobileMenu"
></div>
<!-- Footer --> </transition>
<footer class="d-flex justify-content-end text-muted pr-sm-2 pr-xl-3">
<p> <b-row class="mx-0">
<small> <b-col col lg="3" xl="2" class="d-none d-lg-block d-xl-block pl-0 pr-0 pr-xl-2">
<a href="https://getumbrel.com" target="_blank">getumbrel.com</a> <authenticated-vertical-navbar />
</small> </b-col>
</p>
</footer> <b-col col lg="9" xl="10">
</b-col> <div class="pr-xl-2">
</b-row> <transition name="change-page" mode="out-in">
<!-- Content -->
<router-view></router-view>
</transition>
</div>
<!-- Footer -->
<footer class="d-flex justify-content-end text-muted pr-sm-2 pr-xl-3">
<p>
<small>
<a href="https://getumbrel.com" target="_blank">getumbrel.com</a>
</small>
</p>
</footer>
</b-col>
</b-row>
</div>
</transition>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from "vuex";
import Loading from "@/components/Loading";
import AuthenticatedVerticalNavbar from "@/components/AuthenticatedVerticalNavbar"; import AuthenticatedVerticalNavbar from "@/components/AuthenticatedVerticalNavbar";
export default { export default {
data() { data() {
return {}; return {
loading: false,
loadingText: "Loading...",
loadingProgress: 0,
pollInProgress: false
};
}, },
computed: { computed: {
chain() { ...mapState({
return this.$store.state.bitcoin.chain; isApiOperational: state => state.system.api.operational,
}, isBitcoinOperational: state => state.bitcoin.operational,
isDarkMode() { isBitcoinCalibrating: state => state.bitcoin.calibrating,
return this.$store.getters.isDarkMode; isLndOperational: state => state.lightning.operational,
}, isLndUnlocked: state => state.lightning.unlocked,
chain: state => state.bitcoin.chain
}),
isMobileMenuOpen() { isMobileMenuOpen() {
return this.$store.getters.isMobileMenuOpen; return this.$store.getters.isMobileMenuOpen;
} }
@ -118,7 +132,73 @@ export default {
toggleMobileMenu() { toggleMobileMenu() {
this.$store.commit("toggleMobileMenu"); this.$store.commit("toggleMobileMenu");
}, },
fetchData() { async fetchData() {
//prevent multiple polls if previous poll already in progress
// if (this.pollInProgress) {
// return;
// }
// this.pollInProgress = true;
// //First check if API is active
// await this.$store.dispatch("system/getApi");
// if (!this.isApiOperational) {
// this.loading = true;
// this.loadingText = "Starting API...";
// this.loadingProgress = 20;
// this.pollInProgress = false;
// return;
// }
// // Then check if bitcoind is operational
// if (!this.isBitcoinOperational) {
// this.loading = true;
// this.loadingText = "Starting Bitcoin Core...";
// this.loadingProgress = 40;
// this.pollInProgress = false;
// return;
// }
// // Then check if bitcoind is calibrating
// if (this.isBitcoinCalibrating) {
// this.loading = true;
// this.loadingText = "Calibrating Bitcoin Core...";
// this.loadingProgress = 50;
// this.pollInProgress = false;
// return;
// }
// // Then check if lnd is operational
// await this.$store.dispatch("lightning/getStatus");
// if (!this.isLndOperational) {
// this.loading = true;
// this.loadingText = "Starting LND...";
// this.loadingProgress = 70;
// this.pollInProgress = false;
// return;
// }
// // Then check if lnd is unlocked
// if (!this.isLndUnlocked) {
// this.loading = true;
// this.loadingText = "Starting LND...";
// this.loadingProgress = 90;
// this.pollInProgress = false;
// return;
// }
// this.loading = false;
// this.loadingProgress = 100;
// this.pollInProgress = false;
await this.$store.dispatch("bitcoin/getStatus");
await this.$store.dispatch("system/getApi");
await this.$store.dispatch("lightning/getStatus");
//fetch data
this.$store.dispatch("system/fetchUnit");
this.$store.dispatch("bitcoin/getSync"); this.$store.dispatch("bitcoin/getSync");
this.$store.dispatch("bitcoin/getBalance"); this.$store.dispatch("bitcoin/getBalance");
this.$store.dispatch("bitcoin/getTransactions"); this.$store.dispatch("bitcoin/getTransactions");
@ -128,8 +208,9 @@ export default {
} }
}, },
created() { created() {
//fetch user's peferences //trigger loading watcher
this.$store.dispatch("system/fetchUnit"); // this.loading = true;
// start polling data every 20s // start polling data every 20s
this.fetchData(); this.fetchData();
this.interval = window.setInterval(this.fetchData, 20000); this.interval = window.setInterval(this.fetchData, 20000);
@ -137,8 +218,21 @@ export default {
beforeDestroy() { beforeDestroy() {
window.clearInterval(this.interval); window.clearInterval(this.interval);
}, },
watch: {
loading: function(nowLoading) {
window.clearInterval(this.interval);
//if loading, check every second
if (nowLoading) {
this.interval = window.setInterval(this.fetchData, 1000);
} else {
//else check every 10s
this.interval = window.setInterval(this.fetchData, 20000);
}
}
},
components: { components: {
AuthenticatedVerticalNavbar AuthenticatedVerticalNavbar,
Loading
} }
}; };
</script> </script>
@ -299,4 +393,27 @@ export default {
transform: translate3d(40px, 0, 0); transform: translate3d(40px, 0, 0);
opacity: 0; opacity: 0;
} }
// Loading transitions
.loading-enter-active,
.loading-leave-active {
transition: opacity 0.4s ease;
}
.loading-enter {
opacity: 0;
// filter: blur(70px);
}
.loading-enter-to {
opacity: 1;
// filter: blur(0);
}
.loading-leave {
opacity: 1;
// filter: blur(0);
}
.loading-leave-to {
opacity: 0;
// filter: blur(70px);
}
</style> </style>

2
src/router/index.js

@ -1,7 +1,7 @@
import Vue from "vue"; import Vue from "vue";
import VueRouter from "vue-router"; import VueRouter from "vue-router";
import store from "../store"; import store from "@/store";
import TransitionWrapperLayout from "../layouts/TransitionWrapperLayout.vue"; import TransitionWrapperLayout from "../layouts/TransitionWrapperLayout.vue";
import SimpleLayout from "../layouts/SimpleLayout.vue"; import SimpleLayout from "../layouts/SimpleLayout.vue";

22
src/store/modules/user.js

@ -1,7 +1,9 @@
import API from "@/helpers/api";
// Initial state // Initial state
const state = () => ({ const state = () => ({
name: "Satoshi", name: "Satoshi",
jwt: window.localStorage.getItem("jwt") ? window.localStorage.getItem("jwt") : "" jwt: window.localStorage.getItem("jwt") || ""
}); });
// Functions to update the state directly // Functions to update the state directly
@ -14,11 +16,25 @@ const mutations = {
// Functions to get data from the API // Functions to get data from the API
const actions = { const actions = {
login({ commit }) { async login({ commit }, password) {
commit("setJWT", "true");
const { data } = await API.post(
`${process.env.VUE_APP_SYSTEM_API_URL}/v1/account/login`,
{ password }
);
if (data && data.jwt) {
commit("setJWT", data.jwt);
}
}, },
logout({ commit }) { logout({ commit }) {
commit("setJWT", ""); commit("setJWT", "");
},
async refreshJWT({ commit }) {
const { data } = await API.post(`${process.env.VUE_APP_SYSTEM_API_URL}/v1/account/refresh`);
if (data && data.jwt) {
commit("setJWT", data.jwt);
}
} }
}; };

20
src/views/Login.vue

@ -67,11 +67,11 @@ export default {
methods: { methods: {
authenticateUser() { authenticateUser() {
this.isLoggingIn = true; this.isLoggingIn = true;
window.setTimeout(() => { window.setTimeout(async () => {
//if testnet, password is "printergobrrr" //if testnet, password is "printergobrrr"
if (window.location.host === "testnet.getumbrel.com") { if (window.location.host === "testnet.getumbrel.com") {
if (this.password === "printergobrrr") { if (this.password === "printergobrrr") {
this.$store.dispatch("user/login"); this.$store.dispatch("user/login", this.password);
return this.$router.push( return this.$router.push(
this.$router.history.current.query.redirect || "/dashboard" this.$router.history.current.query.redirect || "/dashboard"
); );
@ -83,13 +83,21 @@ export default {
//if locally, then any password will work, except "incorrect" //if locally, then any password will work, except "incorrect"
if (this.password !== "incorrect") { if (this.password !== "incorrect") {
this.$store.dispatch("user/login"); try {
await this.$store.dispatch("user/login", this.password);
} catch (error) {
if (
error.response &&
error.response.data === "Incorrect password"
) {
this.isIncorrectPassword = true;
this.isLoggingIn = false;
return;
}
}
return this.$router.push( return this.$router.push(
this.$router.history.current.query.redirect || "/dashboard" this.$router.history.current.query.redirect || "/dashboard"
); );
} else {
this.isIncorrectPassword = true;
return (this.isLoggingIn = false);
} }
}, 1000); }, 1000);
} }

24
src/views/Settings.vue

@ -17,27 +17,21 @@
<div class="d-flex w-100 justify-content-between px-3 px-lg-4 mb-4"> <div class="d-flex w-100 justify-content-between px-3 px-lg-4 mb-4">
<div> <div>
<span class="d-block">Bitcoin</span> <span class="d-block">Bitcoin</span>
<small class="d-block" style="opacity: 0.4" <small class="d-block" style="opacity: 0.4">Run Bitcoin Core on Tor</small>
>Run Bitcoin Core on Tor</small
>
</div> </div>
<toggle-switch class="align-self-center"></toggle-switch> <toggle-switch class="align-self-center"></toggle-switch>
</div> </div>
<div class="d-flex w-100 justify-content-between px-3 px-lg-4 mb-4"> <div class="d-flex w-100 justify-content-between px-3 px-lg-4 mb-4">
<div> <div>
<span class="d-block">Lightning Network</span> <span class="d-block">Lightning Network</span>
<small class="d-block" style="opacity: 0.4" <small class="d-block" style="opacity: 0.4">Run Lightning on Tor</small>
>Run Lightning on Tor</small
>
</div> </div>
<toggle-switch class="align-self-center"></toggle-switch> <toggle-switch class="align-self-center"></toggle-switch>
</div> </div>
<div class="d-flex w-100 justify-content-between px-3 px-lg-4 mb-4"> <div class="d-flex w-100 justify-content-between px-3 px-lg-4 mb-4">
<div> <div>
<span class="d-block">Remote Access</span> <span class="d-block">Remote Access</span>
<small class="d-block" style="opacity: 0.4" <small class="d-block" style="opacity: 0.4">Remotely access your Umbrel on Tor</small>
>Remotely access your Umbrel on Tor</small
>
</div> </div>
<toggle-switch class="align-self-center"></toggle-switch> <toggle-switch class="align-self-center"></toggle-switch>
</div> </div>
@ -50,6 +44,7 @@
</template> </template>
<script> <script>
import API from "@/helpers/api";
import CardWidget from "@/components/CardWidget"; import CardWidget from "@/components/CardWidget";
import ToggleSwitch from "@/components/ToggleSwitch"; import ToggleSwitch from "@/components/ToggleSwitch";
@ -58,6 +53,17 @@ export default {
return {}; return {};
}, },
computed: {}, computed: {},
async created() {
// const seed = await API.post(
// `${process.env.VUE_APP_SYSTEM_API_URL}/v1/account/seed`
// );
const seed = await API.post(
`${process.env.VUE_APP_SYSTEM_API_URL}/v1/account/seed`,
{ password: "abcdef123456" }
);
console.log(seed);
},
methods: {}, methods: {},
components: { components: {
CardWidget, CardWidget,

Loading…
Cancel
Save