Allow setting api url from the login screen #264

Merged
konrad merged 8 commits from feature/change-api-url into master 2020-10-11 10:13:35 +00:00
10 changed files with 214 additions and 10 deletions

View File

@ -158,7 +158,6 @@
</a>
<aside class="menu namespaces-lists">
<div :class="{ 'is-loading': namespaceService.loading}" class="spinner"></div>
<template v-for="n in namespaces">
<div :key="n.id">
<router-link
@ -320,7 +319,6 @@
import router from './router'
import {mapState} from 'vuex'
import NamespaceService from './services/namespace'
import authTypes from './models/authTypes'
import Rights from './models/rights.json'
@ -337,7 +335,6 @@ export default {
},
data() {
return {
namespaceService: NamespaceService,
menuActive: true,
currentDate: new Date(),
userMenuActive: false,

View File

@ -0,0 +1,170 @@
<template>
<div class="api-config">
<div v-if="configureApi">
<label class="label" for="api-url">Vikunja URL</label>
<div class="field has-addons">
<div class="control is-expanded">
<input
class="input" id="api-url"
placeholder="eg. https://localhost:3456"
required
type="url"
v-focus
v-model="apiUrl"
@keyup.enter="setApiUrl"
/>
</div>
<div class="control">
<a class="button is-primary" @click="setApiUrl" :disabled="apiUrl === ''">
Change
</a>
</div>
</div>
</div>
<div class="api-url-info" v-else>
Sign in to your Vikunja account on <span v-tooltip="apiUrl">{{ apiDomain() }}</span><br/>
<a @click="() => configureApi = true">change</a>
</div>
<div class="notification is-success mt-2" v-if="successMsg !== '' && errorMsg === ''">
{{ successMsg }}
</div>
<div class="notification is-danger mt-2" v-if="errorMsg !== '' && successMsg === ''">
{{ errorMsg }}
</div>
</div>
</template>
<script>
export default {
name: 'apiConfig',
data() {
return {
configureApi: false,
apiUrl: '',
errorMsg: '',
successMsg: '',
}
},
created() {
this.apiUrl = window.API_URL
if (this.apiUrl === '') {
this.configureApi = true
}
},
methods: {
apiDomain() {
if (window.API_URL.startsWith('/api/v1')) {
return window.location.host
}
const urlParts = window.API_URL.replace('http://', '').replace('https://', '').split(/[/?#]/)
return urlParts[0]
},
setApiUrl() {
if (this.apiUrl === '') {
return
}
let urlToCheck = this.apiUrl
// Check if the url has an http prefix
if (!urlToCheck.startsWith('http://') && !urlToCheck.startsWith('https://')) {
urlToCheck = `http://${urlToCheck}`
}
urlToCheck = new URL(urlToCheck)
const origUrlToCheck = urlToCheck
const oldUrl = window.API_URL
window.API_URL = urlToCheck.toString()
// Check if the api is reachable at the provided url
this.$store.dispatch('config/update')
.catch(e => {
// Check if it is reachable at /api/v1 and http
if (!urlToCheck.pathname.endsWith('/api/v1') && !urlToCheck.pathname.endsWith('/api/v1/')) {
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch(e => {
// Check if it has a port and if not check if it is reachable at https
if (urlToCheck.protocol === 'http:') {
urlToCheck.protocol = 'https:'
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch(e => {
// Check if it is reachable at /api/v1 and https
urlToCheck.pathname = origUrlToCheck.pathname
if (!urlToCheck.pathname.endsWith('/api/v1') && !urlToCheck.pathname.endsWith('/api/v1/')) {
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch(e => {
// Check if it is reachable at port 3456 and https
if (urlToCheck.port !== 3456) {
urlToCheck.protocol = 'https:'
urlToCheck.port = 3456
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch(e => {
// Check if it is reachable at :3456 and /api/v1 and https
urlToCheck.pathname = origUrlToCheck.pathname
if (!urlToCheck.pathname.endsWith('/api/v1') && !urlToCheck.pathname.endsWith('/api/v1/')) {
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch(e => {
// Check if it is reachable at port 3456 and http
if (urlToCheck.port !== 3456) {
urlToCheck.protocol = 'http:'
urlToCheck.port = 3456
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch(e => {
// Check if it is reachable at :3456 and /api/v1 and http
urlToCheck.pathname = origUrlToCheck.pathname
if (!urlToCheck.pathname.endsWith('/api/v1') && !urlToCheck.pathname.endsWith('/api/v1/')) {
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch(() => {
// Still not found, url is still invalid
this.successMsg = ''
this.errorMsg = `Could not find or use Vikunja installation at "${this.apiDomain()}".`
window.API_URL = oldUrl
})
.then(r => {
if (typeof r !== 'undefined') {
// Set it + save it to local storage to save us the hoops
this.errorMsg = ''
this.successMsg = `Using Vikunja installation at "${this.apiDomain()}".`
localStorage.setItem('API_URL', window.API_URL)
this.configureApi = false
this.apiUrl = window.API_URL
}
})
},
},
}
</script>

View File

@ -1,5 +1,7 @@
import axios from 'axios'
export const HTTP = axios.create({
baseURL: window.API_URL,
})
export const HTTPFactory = () => {
return axios.create({
baseURL: window.API_URL,
})
}

View File

@ -73,6 +73,12 @@ import {store} from './store'
console.info(`Vikunja frontend version ${VERSION}`)
// Check if we have an api url in local storage and use it if that's the case
const apiUrlFromStorage = localStorage.getItem('API_URL')
if (apiUrlFromStorage !== null) {
window.API_URL = apiUrlFromStorage
}
// Make sure the api url does not contain a / at the end
if (window.API_URL.substr(window.API_URL.length - 1, window.API_URL.length) === '/') {
window.API_URL = window.API_URL.substr(0, window.API_URL.length - 1)

View File

@ -1,4 +1,4 @@
import {HTTP} from '@/http-common'
import {HTTPFactory} from '@/http-common'
import {ERROR_MESSAGE, LOADING} from '../mutation-types'
import UserModel from '../../models/user'
@ -32,6 +32,7 @@ export default {
actions: {
// Logs a user in with a set of credentials.
login(ctx, credentials) {
const HTTP = HTTPFactory()
ctx.commit(LOADING, true, {root: true})
// Delete an eventually preexisting old token
@ -78,6 +79,7 @@ export default {
// Registers a new user and logs them in.
// Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited.
register(ctx, credentials) {
const HTTP = HTTPFactory()
return HTTP.post('register', {
username: credentials.username,
email: credentials.email,
@ -98,6 +100,7 @@ export default {
},
linkShareAuth(ctx, hash) {
const HTTP = HTTPFactory()
return HTTP.post('/shares/' + hash + '/auth')
.then(r => {
localStorage.setItem('token', r.data.token)
@ -128,6 +131,7 @@ export default {
},
// Renews the api token and saves it to local storage
renewToken(ctx) {
const HTTP = HTTPFactory()
if (!ctx.state.authenticated) {
return
}

View File

@ -1,5 +1,5 @@
import {CONFIG} from '../mutation-types'
import {HTTP} from '@/http-common'
import {HTTPFactory} from '@/http-common'
export default {
namespaced: true,
@ -40,10 +40,14 @@ export default {
},
actions: {
update(ctx) {
HTTP.get('info')
const HTTP = HTTPFactory()
return HTTP.get('info')
.then(r => {
ctx.commit(CONFIG, r.data)
return Promise.resolve(r)
})
.catch(e => Promise.reject(e))
},
},
}

View File

@ -21,3 +21,4 @@
@import 'namespaces';
@import 'legal';
@import 'keyboard-shortcuts';
@import 'api-config';

View File

@ -0,0 +1,12 @@
.api-config {
margin-bottom: .75rem;
}
.api-url-info {
font-size: .9rem;
text-align: right;
span {
border-bottom: 1px dashed $primary;
}
}

View File

@ -76,6 +76,10 @@
}
}
.field.has-addons .button {
height: 2.5rem;
}
.input,
.textarea {
transition: all $transition;

View File

@ -5,6 +5,7 @@
<div class="notification is-success has-text-centered" v-if="confirmedEmailSuccess">
You successfully confirmed your email! You can log in now.
</div>
<api-config/>
<form @submit.prevent="submit" id="loginform">
<div class="field">
<label class="label" for="username">Username</label>
@ -76,13 +77,15 @@
import {mapState} from 'vuex'
import router from '../../router'
import {HTTP} from '@/http-common'
import {HTTPFactory} from '@/http-common'
import message from '../../message'
import {ERROR_MESSAGE, LOADING} from '@/store/mutation-types'
import legal from '../../components/misc/legal'
import ApiConfig from '@/components/misc/api-config'
export default {
components: {
ApiConfig,
legal,
},
data() {
@ -91,6 +94,7 @@ export default {
}
},
beforeMount() {
const HTTP = HTTPFactory()
// Try to verify the email
// FIXME: Why is this here? Can we find a better place for this?
let emailVerifyToken = localStorage.getItem('emailConfirmToken')