vuefy login page
This commit is contained in:
259
dashboard/src/components/Login.vue
Normal file
259
dashboard/src/components/Login.vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<div class="layout-root hide">
|
||||
<div class="layout-content">
|
||||
<div class="card">
|
||||
<div class="row">
|
||||
<div class="col-md-12" style="text-align: center;">
|
||||
<img width="128" height="128" class="avatar" :src="iconUrl"/>
|
||||
<br/>
|
||||
<small>{{ $t('login.loginTo') }}</small>
|
||||
<h1>{{ name }}</h1>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div :html="note"></div>
|
||||
<form @submit.prevent="onSubmit">
|
||||
<button type="submit" style="display: none;"></button>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputUsername">{{ $t('login.username') }}</label>
|
||||
<input type="text" class="form-control" id="inputUsername" :model="username" autofocus required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputPassword">{{ $t('login.password') }}</label>
|
||||
<input type="password" class="form-control" :model="password" id="inputPassword" required password-reveal>
|
||||
<p class="has-error" v-show="passwordError">{{ $t('login.errorIncorrectCredentials') }}</p>
|
||||
<p class="has-error" v-show="internalError">{{ $t('login.errorInternal') }}</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label" for="inputTotpToken">{{ $t('login.2faToken') }}</label>
|
||||
<input type="text" class="form-control" :model="totpToken" id="inputTotpToken">
|
||||
<p class="has-error" v-show="totpError">{{ $t('login.errorIncorrect2FAToken') }}</p>
|
||||
</div>
|
||||
<div class="card-form-bottom-bar">
|
||||
<a href="/passwordreset.html">{{ $t('login.resetPasswordAction') }}</a>
|
||||
<Button id="loginSubmitButton" type="submit" @click.prevent="onSubmit" :loading="busy">{{ $t('login.signInAction') }}</Button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="text-center">
|
||||
<span class="text-muted" :html="footer"></span>
|
||||
</footer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { Button } from 'pankow';
|
||||
|
||||
export default {
|
||||
name: 'Login',
|
||||
components: {
|
||||
Button
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
busy: false,
|
||||
username: '',
|
||||
password: '',
|
||||
totpToken: '',
|
||||
// coming from login.html template
|
||||
name: window.cloudron.name,
|
||||
note: window.cloudron.note,
|
||||
iconUrl: window.cloudron.iconUrl,
|
||||
footer: window.cloudron.footer,
|
||||
submitUrl: window.cloudron.submitUrl
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async onSubmit() {
|
||||
this.busy = true;
|
||||
|
||||
console.log('-----submit')
|
||||
|
||||
this.passwordError = false;
|
||||
this.totpError = false;
|
||||
this.internalError = false;
|
||||
|
||||
const body = {
|
||||
username: this.username,
|
||||
password: this.password,
|
||||
totpToken: this.totpToken
|
||||
};
|
||||
|
||||
let res;
|
||||
fetch(this.submitUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
headers: { 'Content-type': 'application/json; charset=UTF-8' }
|
||||
}).then(function (response) {
|
||||
res = response;
|
||||
return res.json(); // we always return objects
|
||||
}).then((data) => {
|
||||
if (res.status === 410) {
|
||||
// the oidc login session is old
|
||||
window.location.reload();
|
||||
} else if (res.status === 401) {
|
||||
if (data.message.indexOf('totpToken') !== -1) {
|
||||
this.totpToken = '';
|
||||
this.totpError = true;
|
||||
document.getElementById('inputTotpToken').focus();
|
||||
} else {
|
||||
this.password.value = '';
|
||||
this.passwordError = true;
|
||||
document.getElementById('inputPassword').focus();
|
||||
}
|
||||
|
||||
return;
|
||||
} else if (res.status !== 200) {
|
||||
throw new Error('Something went wrong');
|
||||
}
|
||||
|
||||
if (data.redirectTo) window.location.href = data.redirectTo;
|
||||
else console.log('login success but missing redirectTo in data:', data);
|
||||
}).catch((error) => {
|
||||
this.internalError = true;
|
||||
console.warn(error, res);
|
||||
}).finally(() => {
|
||||
this.busy = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
var svgEye = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye" class="svg-inline--fa fa-eye fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="currentColor" d="M572.52 241.4C518.29 135.59 410.93 64 288 64S57.68 135.64 3.48 241.41a32.35 32.35 0 0 0 0 29.19C57.71 376.41 165.07 448 288 448s230.32-71.64 284.52-177.41a32.35 32.35 0 0 0 0-29.19zM288 400a144 144 0 1 1 144-144 143.93 143.93 0 0 1-144 144zm0-240a95.31 95.31 0 0 0-25.31 3.79 47.85 47.85 0 0 1-66.9 66.9A95.78 95.78 0 1 0 288 160z"></path></svg>';
|
||||
var svgEyeSlash = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="eye-slash" class="svg-inline--fa fa-eye-slash fa-w-20" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M320 400c-75.85 0-137.25-58.71-142.9-133.11L72.2 185.82c-13.79 17.3-26.48 35.59-36.72 55.59a32.35 32.35 0 0 0 0 29.19C89.71 376.41 197.07 448 320 448c26.91 0 52.87-4 77.89-10.46L346 397.39a144.13 144.13 0 0 1-26 2.61zm313.82 58.1l-110.55-85.44a331.25 331.25 0 0 0 81.25-102.07 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64a308.15 308.15 0 0 0-147.32 37.7L45.46 3.37A16 16 0 0 0 23 6.18L3.37 31.45A16 16 0 0 0 6.18 53.9l588.36 454.73a16 16 0 0 0 22.46-2.81l19.64-25.27a16 16 0 0 0-2.82-22.45zm-183.72-142l-39.3-30.38A94.75 94.75 0 0 0 416 256a94.76 94.76 0 0 0-121.31-92.21A47.65 47.65 0 0 1 304 192a46.64 46.64 0 0 1-1.54 10l-73.61-56.89A142.31 142.31 0 0 1 320 112a143.92 143.92 0 0 1 144 144c0 21.63-5.29 41.79-13.9 60.11z"></path></svg>';
|
||||
|
||||
document.querySelectorAll('[password-reveal]').forEach(function (element) {
|
||||
var eye = document.createElement('i');
|
||||
eye.innerHTML = svgEyeSlash;
|
||||
eye.style.width = '18px';
|
||||
eye.style.height = '18px';
|
||||
eye.style.position = 'relative';
|
||||
eye.style.float = 'right';
|
||||
eye.style.marginTop = '-24px';
|
||||
eye.style.marginRight = '10px';
|
||||
eye.style.cursor = 'pointer';
|
||||
|
||||
eye.addEventListener('click', function () {
|
||||
if (element.type === 'password') {
|
||||
element.type = 'text';
|
||||
eye.innerHTML = svgEye;
|
||||
} else {
|
||||
element.type = 'password';
|
||||
eye.innerHTML = svgEyeSlash;
|
||||
}
|
||||
});
|
||||
|
||||
element.parentNode.style.position = 'relative';
|
||||
element.parentNode.insertBefore(eye, element.nextSibling);
|
||||
});
|
||||
|
||||
// placed in local storage by setupaccount.js
|
||||
const autoLoginToken = localStorage.cloudronFirstTimeToken;
|
||||
if (autoLoginToken) {
|
||||
const apiUrl = this.submitUrl;
|
||||
|
||||
let res;
|
||||
fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ autoLoginToken }),
|
||||
headers: { 'Content-type': 'application/json; charset=UTF-8' }
|
||||
}).then(function (res) {
|
||||
localStorage.removeItem('cloudronFirstTimeToken');
|
||||
return res.json(); // we always return objects
|
||||
}).then(function (data) {
|
||||
if (data.redirectTo) window.location.href = data.redirectTo;
|
||||
else console.log('login success but missing redirectTo in data:', data);
|
||||
}).catch(function (error) {
|
||||
document.getElementsByClassName('layout-root')[0].classList.remove('hide');
|
||||
localStorage.removeItem('cloudronFirstTimeToken');
|
||||
document.getElementById('internalError').classList.remove('hide');
|
||||
document.getElementById('busyIndicator').classList.add('hide');
|
||||
console.warn(error, res);
|
||||
});
|
||||
} else {
|
||||
document.getElementsByClassName('layout-root')[0].classList.remove('hide');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
body {
|
||||
background-image: url('/api/v1/cloudron/background');
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 20px;
|
||||
margin-bottom: 0;
|
||||
max-width: 620px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media(min-width:620px) {
|
||||
.card {
|
||||
margin-bottom: 15px;
|
||||
margin-top: 100px;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
margin-top: -84px
|
||||
}
|
||||
}
|
||||
|
||||
.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#busyIndicator {
|
||||
display: inline-block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 1px solid rgba(255,255,255,.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: #fff;
|
||||
animation: spin 1s ease-in-out infinite;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding: 0;
|
||||
margin-top: 10px;
|
||||
text-overflow: ellipsis;
|
||||
overflow-x: hidden;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
small {
|
||||
display: block;
|
||||
margin-top: 25px;
|
||||
color: #777777;
|
||||
font-size: 20px;
|
||||
font-family: "Noto Sans Light", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user