vuefy login page

This commit is contained in:
Johannes Zellner
2024-12-14 01:42:23 +01:00
parent 7438576bb1
commit a071cef46a
4 changed files with 306 additions and 253 deletions

View 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>