forked from GithubBackups/healthchecks
Improved layout & style, fixed hamburger menu in login page.
This commit is contained in:
parent
160aa191c8
commit
a58ce791c0
@ -6,9 +6,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
### Improvements
|
### Improvements
|
||||||
- Content updates in the "Welcome" page.
|
- Content updates in the "Welcome" page.
|
||||||
- Added "Docs > Third-Party Resources" page.
|
- Added "Docs > Third-Party Resources" page.
|
||||||
|
- Improved layout and styling in "Login" page.
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
- Timezones were missing in the "Change Schedule" dialog, fixed
|
- Timezones were missing in the "Change Schedule" dialog, fixed.
|
||||||
|
- Fix hamburger menu button in "Login" page.
|
||||||
|
|
||||||
|
|
||||||
## 1.1.0 - 2018-08-20
|
## 1.1.0 - 2018-08-20
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from datetime import timedelta as td
|
from datetime import timedelta as td
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import authenticate
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
|
||||||
@ -10,9 +12,37 @@ class LowercaseEmailField(forms.EmailField):
|
|||||||
return value.lower()
|
return value.lower()
|
||||||
|
|
||||||
|
|
||||||
|
class EmailForm(forms.Form):
|
||||||
|
email = LowercaseEmailField()
|
||||||
|
|
||||||
|
def clean_email(self):
|
||||||
|
v = self.cleaned_data["email"]
|
||||||
|
|
||||||
|
# If registration is not open then validate if an user
|
||||||
|
# account with this address exists-
|
||||||
|
if not settings.REGISTRATION_OPEN:
|
||||||
|
if not User.objects.filter(email=v).exists():
|
||||||
|
raise forms.ValidationError("Incorrect email address.")
|
||||||
|
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class EmailPasswordForm(forms.Form):
|
class EmailPasswordForm(forms.Form):
|
||||||
identity = LowercaseEmailField()
|
email = LowercaseEmailField()
|
||||||
password = forms.CharField(required=False)
|
password = forms.CharField()
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
username = self.cleaned_data.get('email')
|
||||||
|
password = self.cleaned_data.get('password')
|
||||||
|
|
||||||
|
if username and password:
|
||||||
|
self.user = authenticate(username=username, password=password)
|
||||||
|
if self.user is None:
|
||||||
|
raise forms.ValidationError("Incorrect email or password")
|
||||||
|
if not self.user.is_active:
|
||||||
|
raise forms.ValidationError("Account is inactive")
|
||||||
|
|
||||||
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class ReportSettingsForm(forms.Form):
|
class ReportSettingsForm(forms.Form):
|
||||||
|
@ -10,7 +10,7 @@ from django.conf import settings
|
|||||||
class LoginTestCase(TestCase):
|
class LoginTestCase(TestCase):
|
||||||
|
|
||||||
def test_it_sends_link(self):
|
def test_it_sends_link(self):
|
||||||
form = {"identity": "alice@example.org"}
|
form = {"email": "alice@example.org"}
|
||||||
|
|
||||||
r = self.client.post("/accounts/login/", form)
|
r = self.client.post("/accounts/login/", form)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
@ -32,10 +32,9 @@ class LoginTestCase(TestCase):
|
|||||||
self.client.get("/accounts/login/")
|
self.client.get("/accounts/login/")
|
||||||
assert "bad_link" not in self.client.session
|
assert "bad_link" not in self.client.session
|
||||||
|
|
||||||
|
|
||||||
@override_settings(REGISTRATION_OPEN=False)
|
@override_settings(REGISTRATION_OPEN=False)
|
||||||
def test_it_obeys_registration_open(self):
|
def test_it_obeys_registration_open(self):
|
||||||
form = {"identity": "dan@example.org"}
|
form = {"email": "dan@example.org"}
|
||||||
|
|
||||||
r = self.client.post("/accounts/login/", form)
|
r = self.client.post("/accounts/login/", form)
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
@ -45,7 +44,7 @@ class LoginTestCase(TestCase):
|
|||||||
alice = User(username="alice", email="alice@example.org")
|
alice = User(username="alice", email="alice@example.org")
|
||||||
alice.save()
|
alice.save()
|
||||||
|
|
||||||
form = {"identity": "ALICE@EXAMPLE.ORG"}
|
form = {"email": "ALICE@EXAMPLE.ORG"}
|
||||||
|
|
||||||
r = self.client.post("/accounts/login/", form)
|
r = self.client.post("/accounts/login/", form)
|
||||||
assert r.status_code == 302
|
assert r.status_code == 302
|
||||||
|
@ -17,7 +17,7 @@ from django.views.decorators.http import require_POST
|
|||||||
from hc.accounts.forms import (ChangeEmailForm, EmailPasswordForm,
|
from hc.accounts.forms import (ChangeEmailForm, EmailPasswordForm,
|
||||||
InviteTeamMemberForm, RemoveTeamMemberForm,
|
InviteTeamMemberForm, RemoveTeamMemberForm,
|
||||||
ReportSettingsForm, SetPasswordForm,
|
ReportSettingsForm, SetPasswordForm,
|
||||||
TeamNameForm)
|
TeamNameForm, EmailForm)
|
||||||
from hc.accounts.models import Profile, Member
|
from hc.accounts.models import Profile, Member
|
||||||
from hc.api.models import Channel, Check
|
from hc.api.models import Channel, Check
|
||||||
from hc.lib.badges import get_badge_url
|
from hc.lib.badges import get_badge_url
|
||||||
@ -57,44 +57,38 @@ def _ensure_own_team(request):
|
|||||||
request.profile.save()
|
request.profile.save()
|
||||||
|
|
||||||
|
|
||||||
def login(request, show_password=False):
|
def login(request):
|
||||||
bad_credentials = False
|
form = EmailPasswordForm()
|
||||||
|
magic_form = EmailForm()
|
||||||
|
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
form = EmailPasswordForm(request.POST)
|
if request.POST.get("action") == "login":
|
||||||
if form.is_valid():
|
form = EmailPasswordForm(request.POST)
|
||||||
email = form.cleaned_data["identity"]
|
if form.is_valid():
|
||||||
password = form.cleaned_data["password"]
|
auth_login(request, form.user)
|
||||||
if len(password):
|
return redirect("hc-checks")
|
||||||
user = authenticate(username=email, password=password)
|
|
||||||
if user is not None and user.is_active:
|
else:
|
||||||
auth_login(request, user)
|
magic_form = EmailForm(request.POST)
|
||||||
return redirect("hc-checks")
|
if magic_form.is_valid():
|
||||||
bad_credentials = True
|
email = magic_form.cleaned_data["email"]
|
||||||
show_password = True
|
|
||||||
else:
|
|
||||||
user = None
|
user = None
|
||||||
try:
|
try:
|
||||||
user = User.objects.get(email=email)
|
user = User.objects.get(email=email)
|
||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
if settings.REGISTRATION_OPEN:
|
if settings.REGISTRATION_OPEN:
|
||||||
user = _make_user(email)
|
user = _make_user(email)
|
||||||
else:
|
|
||||||
bad_credentials = True
|
|
||||||
|
|
||||||
if user:
|
if user:
|
||||||
profile = Profile.objects.for_user(user)
|
profile = Profile.objects.for_user(user)
|
||||||
profile.send_instant_login_link()
|
profile.send_instant_login_link()
|
||||||
return redirect("hc-login-link-sent")
|
return redirect("hc-login-link-sent")
|
||||||
|
|
||||||
else:
|
|
||||||
form = EmailPasswordForm()
|
|
||||||
|
|
||||||
bad_link = request.session.pop("bad_link", None)
|
bad_link = request.session.pop("bad_link", None)
|
||||||
ctx = {
|
ctx = {
|
||||||
|
"page": "login",
|
||||||
"form": form,
|
"form": form,
|
||||||
"bad_credentials": bad_credentials,
|
"magic_form": magic_form,
|
||||||
"bad_link": bad_link,
|
"bad_link": bad_link
|
||||||
"show_password": show_password
|
|
||||||
}
|
}
|
||||||
return render(request, "accounts/login.html", ctx)
|
return render(request, "accounts/login.html", ctx)
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ from django.urls import include, path
|
|||||||
from hc.accounts.views import login as hc_login
|
from hc.accounts.views import login as hc_login
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('admin/login/', hc_login, {"show_password": True}),
|
path('admin/login/', hc_login),
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
path('accounts/', include('hc.accounts.urls')),
|
path('accounts/', include('hc.accounts.urls')),
|
||||||
path('', include('hc.api.urls')),
|
path('', include('hc.api.urls')),
|
||||||
|
74
static/css/login.css
Normal file
74
static/css/login.css
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
.page-login h1 {
|
||||||
|
text-align: center;
|
||||||
|
margin: 40px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-login .alert {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-login form p {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-login form input {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
#magic-link-form {
|
||||||
|
margin-right: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-form {
|
||||||
|
margin-left: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#link-instruction {
|
||||||
|
color: #999;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-sep {
|
||||||
|
background:#ddd;
|
||||||
|
position: absolute;
|
||||||
|
top: 10%;
|
||||||
|
right: -1px;
|
||||||
|
height: 80%;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-sep div {
|
||||||
|
position: absolute;
|
||||||
|
top: 40%;
|
||||||
|
width: 40px;
|
||||||
|
left: -20px;
|
||||||
|
text-align: center;
|
||||||
|
background: #fff;
|
||||||
|
font-style: italic;
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#xs-login-sep {
|
||||||
|
text-align: center;
|
||||||
|
margin: 40px;
|
||||||
|
font-style: italic;
|
||||||
|
color: #666;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 1px;
|
||||||
|
background: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
#xs-login-sep div {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 30px;
|
||||||
|
top: -9px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
@ -3,77 +3,82 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6 col-sm-offset-3">
|
<div class="col-xs-10 col-xs-offset-1 col-sm-12 col-sm-offset-0 col-lg-8 col-lg-offset-2">
|
||||||
<div class="hc-dialog">
|
|
||||||
{% if bad_link %}
|
|
||||||
<h1>Incorrect Login Link</h1>
|
|
||||||
<div class="dialog-body">
|
|
||||||
<p>The login link you just used is either incorrect or expired.</p>
|
|
||||||
<p>Please use the form below to request a fresh login link:</p>
|
|
||||||
</div>
|
|
||||||
{% else %}
|
|
||||||
<h1>{% site_name %}</h1>
|
|
||||||
<div class="dialog-body">
|
|
||||||
<p>
|
|
||||||
{% if show_password %}
|
|
||||||
Please enter your email address and password.
|
|
||||||
{% else %}
|
|
||||||
Please enter your email address.
|
|
||||||
Next, we'll send you an email with log-in instructions!
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if bad_credentials %}
|
<h1>Sign In to {% site_name %}</h1>
|
||||||
<p class="alert alert-danger">Incorrect email or password.</p>
|
{% if bad_link %}
|
||||||
{% endif %}
|
<div class="alert alert-warning">
|
||||||
|
<p>The login link you just used is either incorrect or expired.</p>
|
||||||
|
<p>Please use the form below to request a fresh login link.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<form method="post">
|
<div class="row">
|
||||||
{% csrf_token %}
|
<div class="col-sm-6">
|
||||||
|
<form id="magic-link-form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
<div class="form-group">
|
{% if magic_form.email.errors %}
|
||||||
<div class="input-group input-group-lg">
|
<p class="text-danger">Incorrect email address.</p>
|
||||||
<div class="input-group-addon">
|
{% else %}
|
||||||
<span class="icon-mail"></span>
|
<p>Enter your <strong>email address</strong>.</p>
|
||||||
</div>
|
{% endif %}
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="form-control"
|
|
||||||
name="identity"
|
|
||||||
value="{{ form.identity.value|default:"" }}"
|
|
||||||
placeholder="you@example.org">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if not show_password %}
|
<input
|
||||||
<div class="checkbox" id="password-toggle">
|
type="text"
|
||||||
<label>
|
class="form-control input-lg"
|
||||||
<input type="checkbox"> I want to use a password
|
name="email"
|
||||||
</label>
|
value="{{ magic_form.email.value|default:"" }}"
|
||||||
</div>
|
placeholder="you@example.org">
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
<p id="link-instruction">
|
||||||
|
We will email you a magic sign in link.
|
||||||
|
</p>
|
||||||
|
|
||||||
<div id="password-block" class="form-group {% if not show_password %} hide {% endif %}">
|
<button type="submit" class="btn btn-lg btn-primary btn-block">
|
||||||
<div class="input-group input-group-lg">
|
Email Me a Link
|
||||||
<div class="input-group-addon">
|
|
||||||
<span class="icon-dots"></span>
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
class="form-control"
|
|
||||||
name="password"
|
|
||||||
placeholder="password">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="clearfix">
|
|
||||||
<button type="submit" class="btn btn-lg btn-primary pull-right">
|
|
||||||
Log In
|
|
||||||
</button>
|
</button>
|
||||||
|
</form>
|
||||||
|
<div id="login-sep" class="hidden-xs"><div>or</div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-xs-12 visible-xs-block">
|
||||||
|
<div id="xs-login-sep">
|
||||||
|
<div>or</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<form id="login-form" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="action" value="login" />
|
||||||
|
|
||||||
|
{% if form.non_field_errors %}
|
||||||
|
<p class="text-danger">Incorrect email or password.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
Enter your <strong>email address</strong> and <strong>password</strong>.
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
class="form-control input-lg"
|
||||||
|
name="email"
|
||||||
|
value="{{ form.email.value|default:"" }}"
|
||||||
|
placeholder="you@example.org">
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
class="form-control input-lg"
|
||||||
|
name="password"
|
||||||
|
placeholder="your password">
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-lg btn-primary btn-block">
|
||||||
|
Sign In
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -82,6 +87,7 @@
|
|||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
{% compress js %}
|
{% compress js %}
|
||||||
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
||||||
|
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||||
<script src="{% static 'js/login.js' %}"></script>
|
<script src="{% static 'js/login.js' %}"></script>
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -42,6 +42,7 @@
|
|||||||
<link rel="stylesheet" href="{% static 'css/checkbox.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/checkbox.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/radio.css' %}" type="text/css">
|
||||||
<link rel="stylesheet" href="{% static 'css/billing.css' %}" type="text/css">
|
<link rel="stylesheet" href="{% static 'css/billing.css' %}" type="text/css">
|
||||||
|
<link rel="stylesheet" href="{% static 'css/login.css' %}" type="text/css">
|
||||||
{% endcompress %}
|
{% endcompress %}
|
||||||
</head>
|
</head>
|
||||||
<body class="page-{{ page }}">
|
<body class="page-{{ page }}">
|
||||||
@ -109,14 +110,6 @@
|
|||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
|
||||||
{% else %}
|
|
||||||
<ul class="nav navbar-nav navbar-right">
|
|
||||||
<li><a href="{% url 'hc-login' %}">Log In</a></li>
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% if request.user.is_authenticated %}
|
{% if request.user.is_authenticated %}
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
@ -148,6 +141,10 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
{% elif page != "login" %}
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li><a href="{% url 'hc-login' %}">Log In</a></li>
|
||||||
|
</ul>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user