forked from GithubBackups/healthchecks
Split "Account Settings" page into subpages.
This commit is contained in:
parent
ec7fa1124f
commit
4906a5247c
20
hc/accounts/tests/test_badges.py
Normal file
20
hc/accounts/tests/test_badges.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from hc.test import BaseTestCase
|
||||||
|
from hc.api.models import Check
|
||||||
|
|
||||||
|
|
||||||
|
class BadgesTestCase(BaseTestCase):
|
||||||
|
|
||||||
|
def test_it_shows_badges(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
Check.objects.create(user=self.alice, tags="foo a-B_1 baz@")
|
||||||
|
Check.objects.create(user=self.bob, tags="bobs-tag")
|
||||||
|
|
||||||
|
r = self.client.get("/accounts/profile/badges/")
|
||||||
|
self.assertContains(r, "foo.svg")
|
||||||
|
self.assertContains(r, "a-B_1.svg")
|
||||||
|
|
||||||
|
# Expect badge URLs only for tags that match \w+
|
||||||
|
self.assertNotContains(r, "baz@.svg")
|
||||||
|
|
||||||
|
# Expect only Alice's tags
|
||||||
|
self.assertNotContains(r, "bobs-tag.svg")
|
24
hc/accounts/tests/test_notifications.py
Normal file
24
hc/accounts/tests/test_notifications.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class NotificationsTestCase(BaseTestCase):
|
||||||
|
|
||||||
|
def test_it_saves_reports_allowed_true(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
|
form = {"reports_allowed": "on"}
|
||||||
|
r = self.client.post("/accounts/profile/notifications/", form)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
self.alice.profile.refresh_from_db()
|
||||||
|
self.assertTrue(self.alice.profile.reports_allowed)
|
||||||
|
|
||||||
|
def test_it_saves_reports_allowed_false(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
|
form = {}
|
||||||
|
r = self.client.post("/accounts/profile/notifications/", form)
|
||||||
|
assert r.status_code == 200
|
||||||
|
|
||||||
|
self.alice.profile.refresh_from_db()
|
||||||
|
self.assertFalse(self.alice.profile.reports_allowed)
|
@ -123,18 +123,3 @@ class ProfileTestCase(BaseTestCase):
|
|||||||
# to user's default team.
|
# to user's default team.
|
||||||
self.bobs_profile.refresh_from_db()
|
self.bobs_profile.refresh_from_db()
|
||||||
self.assertEqual(self.bobs_profile.current_team, self.bobs_profile)
|
self.assertEqual(self.bobs_profile.current_team, self.bobs_profile)
|
||||||
|
|
||||||
def test_it_shows_badges(self):
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
|
||||||
Check.objects.create(user=self.alice, tags="foo a-B_1 baz@")
|
|
||||||
Check.objects.create(user=self.bob, tags="bobs-tag")
|
|
||||||
|
|
||||||
r = self.client.get("/accounts/profile/")
|
|
||||||
self.assertContains(r, "foo.svg")
|
|
||||||
self.assertContains(r, "a-B_1.svg")
|
|
||||||
|
|
||||||
# Expect badge URLs only for tags that match \w+
|
|
||||||
self.assertNotContains(r, "baz@.svg")
|
|
||||||
|
|
||||||
# Expect only Alice's tags
|
|
||||||
self.assertNotContains(r, "bobs-tag.svg")
|
|
||||||
|
@ -14,6 +14,8 @@ urlpatterns = [
|
|||||||
views.check_token, name="hc-check-token"),
|
views.check_token, name="hc-check-token"),
|
||||||
|
|
||||||
url(r'^profile/$', views.profile, name="hc-profile"),
|
url(r'^profile/$', views.profile, name="hc-profile"),
|
||||||
|
url(r'^profile/notifications/$', views.notifications, name="hc-notifications"),
|
||||||
|
url(r'^profile/badges/$', views.badges, name="hc-badges"),
|
||||||
|
|
||||||
url(r'^unsubscribe_reports/([\w-]+)/$',
|
url(r'^unsubscribe_reports/([\w-]+)/$',
|
||||||
views.unsubscribe_reports, name="hc-unsubscribe-reports"),
|
views.unsubscribe_reports, name="hc-unsubscribe-reports"),
|
||||||
|
@ -169,12 +169,6 @@ def profile(request):
|
|||||||
messages.info(request, "The API key has been revoked!")
|
messages.info(request, "The API key has been revoked!")
|
||||||
elif "show_api_key" in request.POST:
|
elif "show_api_key" in request.POST:
|
||||||
show_api_key = True
|
show_api_key = True
|
||||||
elif "update_reports_allowed" in request.POST:
|
|
||||||
form = ReportSettingsForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
profile.reports_allowed = form.cleaned_data["reports_allowed"]
|
|
||||||
profile.save()
|
|
||||||
messages.success(request, "Your settings have been updated!")
|
|
||||||
elif "invite_team_member" in request.POST:
|
elif "invite_team_member" in request.POST:
|
||||||
if not profile.team_access_allowed:
|
if not profile.team_access_allowed:
|
||||||
return HttpResponseForbidden()
|
return HttpResponseForbidden()
|
||||||
@ -213,6 +207,48 @@ def profile(request):
|
|||||||
profile.save()
|
profile.save()
|
||||||
messages.success(request, "Team Name updated!")
|
messages.success(request, "Team Name updated!")
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
"page": "profile",
|
||||||
|
"profile": profile,
|
||||||
|
"show_api_key": show_api_key
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, "accounts/profile.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def notifications(request):
|
||||||
|
profile = request.user.profile
|
||||||
|
# Switch user back to its default team
|
||||||
|
if profile.current_team_id != profile.id:
|
||||||
|
request.team = profile
|
||||||
|
profile.current_team_id = profile.id
|
||||||
|
profile.save()
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = ReportSettingsForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
profile.reports_allowed = form.cleaned_data["reports_allowed"]
|
||||||
|
profile.save()
|
||||||
|
messages.success(request, "Your settings have been updated!")
|
||||||
|
|
||||||
|
ctx = {
|
||||||
|
"page": "profile",
|
||||||
|
"profile": profile,
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, "accounts/notifications.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def badges(request):
|
||||||
|
profile = request.user.profile
|
||||||
|
# Switch user back to its default team
|
||||||
|
if profile.current_team_id != profile.id:
|
||||||
|
request.team = profile
|
||||||
|
profile.current_team_id = profile.id
|
||||||
|
profile.save()
|
||||||
|
|
||||||
tags = set()
|
tags = set()
|
||||||
for check in Check.objects.filter(user=request.team.user):
|
for check in Check.objects.filter(user=request.team.user):
|
||||||
tags.update(check.tags_list())
|
tags.update(check.tags_list())
|
||||||
@ -228,11 +264,9 @@ def profile(request):
|
|||||||
ctx = {
|
ctx = {
|
||||||
"page": "profile",
|
"page": "profile",
|
||||||
"badge_urls": badge_urls,
|
"badge_urls": badge_urls,
|
||||||
"profile": profile,
|
|
||||||
"show_api_key": show_api_key
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "accounts/profile.html", ctx)
|
return render(request, "accounts/badges.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@ -286,11 +320,11 @@ def switch_team(request, target_username):
|
|||||||
# Superuser can switch to any team.
|
# Superuser can switch to any team.
|
||||||
access_ok = request.user.is_superuser
|
access_ok = request.user.is_superuser
|
||||||
|
|
||||||
# Users can switch to teams they are members of.
|
# Users can switch to their own teams.
|
||||||
if not access_ok and other_user.id == request.user.id:
|
if not access_ok and other_user.id == request.user.id:
|
||||||
access_ok = True
|
access_ok = True
|
||||||
|
|
||||||
# Users can switch to their own teams.
|
# Users can switch to teams they are members of.
|
||||||
if not access_ok:
|
if not access_ok:
|
||||||
for membership in request.user.member_set.all():
|
for membership in request.user.member_set.all():
|
||||||
if membership.team.user.id == other_user.id:
|
if membership.team.user.id == other_user.id:
|
||||||
|
@ -79,3 +79,7 @@ body {
|
|||||||
pre {
|
pre {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-pills > li > a {
|
||||||
|
color: #888;
|
||||||
|
}
|
||||||
|
4
static/css/bootstrap.css
vendored
4
static/css/bootstrap.css
vendored
@ -3274,8 +3274,8 @@ select[multiple].input-group-sm > .input-group-btn > .btn {
|
|||||||
.nav-pills > li.active > a,
|
.nav-pills > li.active > a,
|
||||||
.nav-pills > li.active > a:hover,
|
.nav-pills > li.active > a:hover,
|
||||||
.nav-pills > li.active > a:focus {
|
.nav-pills > li.active > a:focus {
|
||||||
color: #ffffff;
|
color: #888888;
|
||||||
background-color: #22bc66;
|
background-color: #eeeeee;
|
||||||
}
|
}
|
||||||
.nav-stacked > li {
|
.nav-stacked > li {
|
||||||
float: none;
|
float: none;
|
||||||
|
@ -440,8 +440,8 @@
|
|||||||
|
|
||||||
//== Pills
|
//== Pills
|
||||||
@nav-pills-border-radius: @border-radius-base;
|
@nav-pills-border-radius: @border-radius-base;
|
||||||
@nav-pills-active-link-hover-bg: @component-active-bg;
|
@nav-pills-active-link-hover-bg: #EEE;
|
||||||
@nav-pills-active-link-hover-color: @component-active-color;
|
@nav-pills-active-link-hover-color: #888;
|
||||||
|
|
||||||
|
|
||||||
//== Pagination
|
//== Pagination
|
||||||
|
63
templates/accounts/badges.html
Normal file
63
templates/accounts/badges.html
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load compress staticfiles hc_extras %}
|
||||||
|
|
||||||
|
{% block title %}Account Settings - {% site_name %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1 class="settings-title">Settings</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<ul class="nav nav-pills nav-stacked">
|
||||||
|
<li><a href="{% url 'hc-profile' %}">Security</a></li>
|
||||||
|
<li><a href="{% url 'hc-notifications' %}">Notifications</a></li>
|
||||||
|
<li class="active"><a href="{% url 'hc-badges' %}">Badges</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body settings-block">
|
||||||
|
<h2 class="settings-title">Status Badges</h2>
|
||||||
|
<p id="badges-description">
|
||||||
|
healthchecks.io provides status badges for each of the tags
|
||||||
|
you have used. The badges have public, but hard-to-guess
|
||||||
|
URLs. If you wish, you can add them to your READMEs,
|
||||||
|
dashboards or status pages.
|
||||||
|
</p>
|
||||||
|
{% if badge_urls %}
|
||||||
|
<table class="badges table">
|
||||||
|
{% for badge_url in badge_urls %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<img src="{{ badge_url }}" alt="" />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<code>{{ badge_url }}</code>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
To get started with status badges, add some tags to
|
||||||
|
your checks!
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% compress js %}
|
||||||
|
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
||||||
|
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||||
|
{% endcompress %}
|
||||||
|
{% endblock %}
|
58
templates/accounts/notifications.html
Normal file
58
templates/accounts/notifications.html
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load compress staticfiles hc_extras %}
|
||||||
|
|
||||||
|
{% block title %}Account Settings - {% site_name %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<h1 class="settings-title">Settings</h1>
|
||||||
|
</div>
|
||||||
|
{% if messages %}
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{% for message in messages %}
|
||||||
|
<p class="alert alert-{{ message.tags }}">{{ message }}</p>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<ul class="nav nav-pills nav-stacked">
|
||||||
|
<li><a href="{% url 'hc-profile' %}">Account</a></li>
|
||||||
|
<li class="active"><a href="{% url 'hc-notifications' %}">Notifications</a></li>
|
||||||
|
<li><a href="{% url 'hc-badges' %}">Badges</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body settings-block">
|
||||||
|
<h2>Monthly Reports</h2>
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<label>
|
||||||
|
<input
|
||||||
|
name="reports_allowed"
|
||||||
|
type="checkbox"
|
||||||
|
{% if profile.reports_allowed %} checked {% endif %}>
|
||||||
|
Each month send me a summary of my checks
|
||||||
|
</label>
|
||||||
|
<button
|
||||||
|
name="update_reports_allowed"
|
||||||
|
type="submit"
|
||||||
|
class="btn btn-default pull-right">Save</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% compress js %}
|
||||||
|
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
|
||||||
|
<script src="{% static 'js/bootstrap.min.js' %}"></script>
|
||||||
|
{% endcompress %}
|
||||||
|
{% endblock %}
|
@ -19,26 +19,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="panel panel-default">
|
<div class="col-sm-3">
|
||||||
<div class="panel-body settings-block">
|
<ul class="nav nav-pills nav-stacked">
|
||||||
<form method="post">
|
<li class="active"><a href="{% url 'hc-profile' %}">Account</a></li>
|
||||||
{% csrf_token %}
|
<li><a href="{% url 'hc-notifications' %}">Notifications</a></li>
|
||||||
<h2>Monthly Reports</h2>
|
<li><a href="{% url 'hc-badges' %}">Badges</a></li>
|
||||||
<label>
|
</ul>
|
||||||
<input
|
|
||||||
name="reports_allowed"
|
|
||||||
type="checkbox"
|
|
||||||
{% if profile.reports_allowed %} checked {% endif %}>
|
|
||||||
Each month send me a summary of my checks
|
|
||||||
</label>
|
|
||||||
<button
|
|
||||||
name="update_reports_allowed"
|
|
||||||
type="submit"
|
|
||||||
class="btn btn-default pull-right">Save</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
@ -55,9 +42,44 @@
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="col-sm-6">
|
<div class="panel panel-default">
|
||||||
|
<div class="panel-body settings-block">
|
||||||
|
<h2>API Access</h2>
|
||||||
|
{% if profile.api_key %}
|
||||||
|
{% if show_api_key %}
|
||||||
|
API key: <code>{{ profile.api_key }}</code>
|
||||||
|
<button
|
||||||
|
data-toggle="modal"
|
||||||
|
data-target="#revoke-api-key-modal"
|
||||||
|
class="btn btn-danger pull-right">Revoke</button>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<form method="post">
|
||||||
|
<span class="icon-ok"></span>
|
||||||
|
API access is enabled.
|
||||||
|
{% csrf_token %}
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="show_api_key"
|
||||||
|
class="btn btn-default pull-right">Show API key</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
<span class="icon-cancel"></span>
|
||||||
|
API access is disabled.
|
||||||
|
<form method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
name="create_api_key"
|
||||||
|
class="btn btn-default pull-right">Create API key</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-body settings-block">
|
<div class="panel-body settings-block">
|
||||||
<h2>Team Access</h2>
|
<h2>Team Access</h2>
|
||||||
@ -115,78 +137,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-6">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body settings-block">
|
|
||||||
<h2>API Access</h2>
|
|
||||||
{% if profile.api_key %}
|
|
||||||
{% if show_api_key %}
|
|
||||||
API key: <code>{{ profile.api_key }}</code>
|
|
||||||
<button
|
|
||||||
data-toggle="modal"
|
|
||||||
data-target="#revoke-api-key-modal"
|
|
||||||
class="btn btn-danger pull-right">Revoke</button>
|
|
||||||
|
|
||||||
{% else %}
|
|
||||||
<span class="icon-ok"></span>
|
|
||||||
API access is enabled.
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
name="show_api_key"
|
|
||||||
class="btn btn-default pull-right">Show API key</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
{% else %}
|
|
||||||
<span class="icon-cancel"></span>
|
|
||||||
API access is disabled.
|
|
||||||
<form method="post">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
name="create_api_key"
|
|
||||||
class="btn btn-default pull-right">Create API key</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if badge_urls %}
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<div class="panel panel-default">
|
|
||||||
<div class="panel-body settings-block">
|
|
||||||
<h2 class="settings-title">Status Badges</h2>
|
|
||||||
<p id="badges-description">
|
|
||||||
Here are status badges for each of the tags you have used. The
|
|
||||||
badges have public, but hard-to-guess URLs. If you wish, you can
|
|
||||||
add them to your READMEs, dashboards or status pages.
|
|
||||||
</p>
|
|
||||||
<table class="badges table">
|
|
||||||
{% for badge_url in badge_urls %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<img src="{{ badge_url }}" alt="" />
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<code>{{ badge_url }}</code>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div id="revoke-api-key-modal" class="modal">
|
<div id="revoke-api-key-modal" class="modal">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<form id="revoke-api-key-form" method="post">
|
<form id="revoke-api-key-form" method="post">
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
<meta name="keywords" content="monitor cron jobs daemon background worker service cronjob monitoring crontab alert notify cronitor deadmanssnitch webhook">
|
<meta name="keywords" content="monitor cron jobs daemon background worker service cronjob monitoring crontab alert notify cronitor deadmanssnitch webhook">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
<link href='//fonts.googleapis.com/css?family=Open+Sans:400,300,600' rel='stylesheet' type='text/css'>
|
<!-- <link href='//fonts.googleapis.com/css?family=Open+Sans:400,300,600' rel='stylesheet' type='text/css'>
|
||||||
{% load compress staticfiles %}
|
--> {% load compress staticfiles %}
|
||||||
<link rel="icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}">
|
<link rel="icon" type="image/x-icon" href="{% static 'img/favicon.ico' %}">
|
||||||
|
|
||||||
{% compress css %}
|
{% compress css %}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user