Merge pull request #338 from BeryJu/ui-improvements

Migrate to SPA
This commit is contained in:
Jens L 2020-11-23 18:02:07 +01:00 committed by GitHub
commit 2f43b5b5ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
165 changed files with 4809 additions and 2528 deletions

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
custom: ["https://www.paypal.me/beryju"]
github: [BeryJu]

1
.gitignore vendored
View File

@ -27,7 +27,6 @@ media
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/

View File

@ -13,9 +13,9 @@ passbook is an open-source Identity Provider focused on flexibility and versatil
## Installation
For small/test setups it is recommended to use docker-compose, see the [documentation](https://passbook.beryju.org/website/docs/installation/docker-compose/)
For small/test setups it is recommended to use docker-compose, see the [documentation](https://passbook.beryju.org/docs/installation/docker-compose/)
For bigger setups, there is a Helm Chart in the `helm/` directory. This is documented [here](https://passbook.beryju.org/website/docs/installation/kubernetes/)
For bigger setups, there is a Helm Chart in the `helm/` directory. This is documented [here](https://passbook.beryju.org/docs/installation/kubernetes/)
## Screenshots

View File

@ -261,7 +261,7 @@ stages:
script: |
sudo apt install -y libxmlsec1-dev pkg-config
sudo pip install -U wheel pipenv
pipenv install --dev
pipenv install --dev --python python3.9
- task: DockerCompose@0
displayName: Run ChromeDriver
inputs:

View File

@ -36,15 +36,15 @@ class CodeMirrorWidget(forms.Textarea):
# CodeMirror mode to enable
mode: str
template_name = "fields/codemirror.html"
def __init__(self, *args, mode="yaml", **kwargs):
super().__init__(*args, **kwargs)
self.mode = mode
def render(self, *args, **kwargs):
attrs = kwargs.setdefault("attrs", {})
attrs.setdefault("class", "")
attrs["class"] += " codemirror"
attrs["data-cm-mode"] = self.mode
attrs["mode"] = self.mode
return super().render(*args, **kwargs)

View File

@ -0,0 +1,18 @@
"""Forms for modals on overview page"""
from django import forms
class PolicyCacheClearForm(forms.Form):
"""Form to clear Policy cache"""
title = "Clear Policy cache"
body = """Are you sure you want to clear the policy cache?
This will cause all policies to be re-evaluated on their next usage."""
class FlowCacheClearForm(forms.Form):
"""Form to clear Flow cache"""
title = "Clear Flow cache"
body = """Are you sure you want to clear the flow cache?
This will cause all flows to be re-evaluated on their next usage."""

View File

@ -20,7 +20,15 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:application-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:application-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -29,6 +37,7 @@
<thead>
<tr role="row">
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Slug' %}</th>
<th role="columnheader" scope="col">{% trans 'Provider' %}</th>
<th role="columnheader" scope="col">{% trans 'Provider Type' %}</th>
<th role="cell"></th>
@ -45,6 +54,9 @@
{% endif %}
</div>
</th>
<td role="cell">
<code>{{ application.slug }}</span>
</td>
<td role="cell">
<span>
{{ application.get_provider }}
@ -56,8 +68,18 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:application-update' pk=application.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:application-delete' pk=application.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:application-update' pk=application.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:application-delete' pk=application.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</td>
</tr>
{% endfor %}
@ -85,7 +107,12 @@
{% trans 'Currently no applications exist. Click the button below to create one.' %}
{% endif %}
</div>
<a href="{% url 'passbook_admin:application-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:application-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}

View File

@ -1,180 +1,5 @@
{% extends "base/page.html" %}
{% load static %}
{% load i18n %}
{% load passbook_is_active %}
{% load passbook_utils %}
{% block head %}
{{ block.super }}
<script src="{% static 'node_modules/codemirror/lib/codemirror.js' %}"></script>
<script src="{% static 'node_modules/codemirror/addon/display/autorefresh.js' %}"></script>
<link rel="stylesheet" href="{% static 'node_modules/codemirror/lib/codemirror.css' %}">
<link rel="stylesheet" href="{% static 'node_modules/codemirror/theme/monokai.css' %}">
<script src="{% static 'node_modules/codemirror/mode/xml/xml.js' %}"></script>
<script src="{% static 'node_modules/codemirror/mode/yaml/yaml.js' %}"></script>
<script src="{% static 'node_modules/codemirror/mode/python/python.js' %}"></script>
{% endblock %}
{% block page_content %}
<div class="pf-c-page__sidebar">
<div class="pf-c-page__sidebar-body">
<nav class="pf-c-nav" id="page-default-nav-example-primary-nav" aria-label="Global">
<ul class="pf-c-nav__list">
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:overview' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:overview' %}">
{% trans 'Overview' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:applications' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:applications' 'passbook_admin:application-create' 'passbook_admin:application-update' 'passbook_admin:application-delete' %}">
{% trans 'Applications' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:sources' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:sources' 'passbook_admin:source-create' 'passbook_admin:source-update' 'passbook_admin:source-delete' %}">
{% trans 'Sources' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:providers' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:providers' 'passbook_admin:provider-create' 'passbook_admin:provider-update' 'passbook_admin:provider-delete' %}">
{% trans 'Providers' %}
</a>
</li>
<li class="pf-c-nav__item pf-m-expanded">
<a href="#" class="pf-c-nav__link" aria-expanded="true">{% trans 'Outposts' %}
<span class="pf-c-nav__toggle">
<i class="fas fa-angle-right" aria-hidden="true"></i>
</span>
</a>
<section class="pf-c-nav__subnav">
<ul class="pf-c-nav__simple-list">
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:outposts' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:outposts' 'passbook_admin:outpost-create' 'passbook_admin:outpost-update' 'passbook_admin:outpost-delete' %}">
{% trans 'Outposts' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:outpost-service-connections' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:outpost-service-connections' 'passbook_admin:outpost-service-connections-create' 'passbook_admin:outpost-service-connections-update' 'passbook_admin:outpost-service-connections-delete' %}">
{% trans 'Service Connections' %}
</a>
</li>
</ul>
</section>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:property-mappings' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:property-mappings' 'passbook_admin:property-mapping-create' 'passbook_admin:property-mapping-update' 'passbook_admin:property-mapping-delete' %}">
{% trans 'Property Mappings' %}
</a>
</li>
<li class="pf-c-nav__item pf-m-expanded">
<a href="#" class="pf-c-nav__link" aria-expanded="true">{% trans 'Flows' %}
<span class="pf-c-nav__toggle">
<i class="fas fa-angle-right" aria-hidden="true"></i>
</span>
</a>
<section class="pf-c-nav__subnav">
<ul class="pf-c-nav__simple-list">
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:flows' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:flows' 'passbook_admin:flow-create' 'passbook_admin:flow-update' 'passbook_admin:flow-delete' %}">
{% trans 'Flows' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:stage-bindings' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:stage-bindings' 'passbook_admin:stage-binding-create' 'passbook_admin:stage-binding-update' 'passbook_admin:stage-binding-delete' %}">
{% trans 'Bindings' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:stages' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:stages' 'passbook_admin:stage-create' 'passbook_admin:stage-update' 'passbook_admin:stage-delete' %}">
{% trans 'Stages' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:stage-prompts' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:stage-prompts' 'passbook_admin:stage-prompt-create' 'passbook_admin:stage-prompt-update' 'passbook_admin:stage-prompt-delete' %}">
{% trans 'Prompts' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:stage-invitations' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:stage-invitations' 'passbook_admin:stage-invitation-create' 'passbook_admin:stage-invitation-delete' %}">
{% trans 'Invitations' %}
</a>
</li>
</ul>
</section>
</li>
<li class="pf-c-nav__item pf-m-expanded">
<a href="#" class="pf-c-nav__link" aria-expanded="true">{% trans 'Policies' %}
<span class="pf-c-nav__toggle">
<i class="fas fa-angle-right" aria-hidden="true"></i>
</span>
</a>
<section class="pf-c-nav__subnav" aria-labelledby="subnav-title1">
<ul class="pf-c-nav__simple-list">
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:policies' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:policies' 'passbook_admin:policy-create' 'passbook_admin:policy-update' 'passbook_admin:policy-delete' 'passbook_admin:policy-test' %}">
{% trans 'Policies' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:policies-bindings' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:policies-bindings' 'passbook_admin:policy-binding-create' 'passbook_admin:policy-binding-update' 'passbook_admin:policy-binding-delete' %}">
{% trans 'Bindings' %}
</a>
</li>
</ul>
</section>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:certificate_key_pair' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:certificate_key_pair' 'passbook_admin:certificatekeypair-create' 'passbook_admin:certificatekeypair-update' 'passbook_admin:certificatekeypair-delete' %}">
{% trans 'Certificates' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:tokens' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:tokens' 'passbook_admin:token-delete' %}">
{% trans 'Tokens' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:users' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:users' 'passbook_admin:user-create' 'passbook_admin:user-update' 'passbook_admin:user-delete' %}">
{% trans 'Users' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:groups' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:groups' 'passbook_admin:group-create' 'passbook_admin:group-update' 'passbook_admin:group-delete' %}">
{% trans 'Groups' %}
</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_admin:tasks' %}"
class="pf-c-nav__link {% is_active 'passbook_admin:tasks' %}">
{% trans 'System Tasks' %}
</a>
</li>
</ul>
</nav>
</div>
</div>
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
{% block content %}
{% endblock %}
</main>
{% block content %}
{% endblock %}

View File

@ -20,7 +20,15 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:certificatekeypair-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:certificatekeypair-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -31,7 +39,6 @@
<th role="columnheader" scope="col">{% trans 'Name' %}</th>
<th role="columnheader" scope="col">{% trans 'Private Key available' %}</th>
<th role="columnheader" scope="col">{% trans 'Fingerprint' %}</th>
<th role="columnheader" scope="col">{% trans 'Provider Type' %}</th>
<th role="cell"></th>
</tr>
</thead>
@ -53,13 +60,21 @@
</span>
</td>
<td role="cell">
<span>
{{ kp.fingerprint }}
</span>
<code>{{ kp.fingerprint }}</code>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:certificatekeypair-update' pk=kp.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:certificatekeypair-delete' pk=kp.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:certificatekeypair-update' pk=kp.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:certificatekeypair-delete' pk=kp.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</td>
</tr>
{% endfor %}
@ -87,7 +102,12 @@
{% trans 'Currently no certificates exist. Click the button below to create one.' %}
{% endif %}
</div>
<a href="{% url 'passbook_admin:certificatekeypair-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:certificatekeypair-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}

View File

@ -20,8 +20,21 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:flow-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<a href="{% url 'passbook_admin:flow-import' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-secondary" type="button">{% trans 'Import' %}</a>
<pb-modal-button href="{% url 'passbook_admin:flow-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:flow-import' %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Import' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -41,7 +54,7 @@
<tr role="row">
<th role="columnheader">
<div>
<div>{{ flow.slug }}</div>
<div><code>{{ flow.slug }}</code></div>
<small>{{ flow.name }}</small>
</div>
</th>
@ -61,10 +74,20 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:flow-update' pk=flow.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:flow-delete' pk=flow.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:flow-execute' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Execute' %}</a>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:flow-export' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Export' %}</a>
<pb-modal-button href="{% url 'passbook_admin:flow-update' pk=flow.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:flow-delete' pk=flow.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<a class="pf-c-button pf-m-secondary pb-root-link" href="{% url 'passbook_admin:flow-execute' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Execute' %}</a>
<a class="pf-c-button pf-m-secondary pb-root-link" href="{% url 'passbook_admin:flow-export' pk=flow.pk %}?next={{ request.get_full_path }}">{% trans 'Export' %}</a>
</td>
</tr>
{% endfor %}
@ -92,8 +115,18 @@
{% trans 'Currently no flows exist. Click the button below to create one.' %}
{% endif %}
</div>
<a href="{% url 'passbook_admin:flow-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<a href="{% url 'passbook_admin:flow-import' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Import' %}</a>
<pb-modal-button href="{% url 'passbook_admin:flow-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:flow-import' %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Import' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}

View File

@ -1,7 +1,6 @@
{% extends "administration/base.html" %}
{% load i18n %}
{% load passbook_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
@ -21,8 +20,15 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:group-create' %}?back={{ request.get_full_path }}"
class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:group-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -55,8 +61,18 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:group-update' pk=group.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:group-delete' pk=group.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:group-update' pk=group.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:group-delete' pk=group.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</td>
</tr>
{% endfor %}
@ -84,7 +100,12 @@
{% trans 'Currently no group exist. Click the button below to create one.' %}
{% endif %}
</div>
<a href="{% url 'passbook_admin:group-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:group-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}

View File

@ -9,7 +9,7 @@
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="fas fa-map-marker"></i>
<i class="pf-icon pf-icon-zone"></i>
{% trans 'Outposts' %}
</h1>
<p>{% trans "Outposts are deployments of passbook components to support different environments and protocols, like reverse proxies." %}</p>
@ -22,7 +22,15 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:outpost-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:outpost-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -84,8 +92,18 @@
{% endif %}
{% endwith %}
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:outpost-update' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:outpost-delete' pk=outpost.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:outpost-update' pk=outpost.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:outpost-delete' pk=outpost.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
{% get_htmls outpost as htmls %}
{% for html in htmls %}
{{ html|safe }}
@ -117,7 +135,12 @@
{% trans 'Currently no outposts exist. Click the button below to create one.' %}
{% endif %}
</div>
<a href="{% url 'passbook_admin:outpost-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:outpost-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}

View File

@ -22,7 +22,7 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -30,16 +30,22 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:outpost-service-connection-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:outpost-service-connection-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -80,8 +86,18 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:outpost-service-connection-update' pk=sc.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:outpost-service-connection-delete' pk=sc.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:outpost-service-connection-update' pk=sc.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:outpost-service-connection-delete' pk=sc.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</td>
</tr>
{% endfor %}
@ -109,7 +125,7 @@
{% trans 'Currently no service connections exist. Click the button below to create one.' %}
{% endif %}
</div>
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -117,16 +133,19 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:outpost-service-connection-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:outpost-service-connection-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
</div>
</div>
{% endif %}

View File

@ -17,8 +17,8 @@
<i class="pf-icon pf-icon-server"></i> {% trans 'Logins over the last 24 hours' %}
</div>
</div>
<div class="pf-c-card__body" style="position: relative; height:100%; width:100%">
<canvas id="logins-last-metrics"></canvas>
<div class="pf-c-card__body">
<pb-admin-logins-chart url="{% url 'passbook_api:admin_metrics-list' %}"></pb-admin-logins-chart>
</div>
</div>
<div class="pf-c-card pf-c-card-aggregate pf-l-gallery__item pf-m-4-col" style="grid-column-end: span 2;grid-row-end: span 3;">
@ -179,9 +179,12 @@
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Policies' %}
</div>
<a data-target="modal" data-modal="clearPolicyCache">
<pb-modal-button href="{% url 'passbook_admin:overview-clear-policy-cache' %}">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</pb-modal-button>
</div>
<div class="pf-c-card__body">
{% if cached_policies < 1 %}
@ -202,9 +205,12 @@
<div class="pf-c-card__header-main">
<i class="pf-icon pf-icon-server"></i> {% trans 'Cached Flows' %}
</div>
<a data-target="modal" data-modal="clearFlowCache">
<pb-modal-button href="{% url 'passbook_admin:overview-clear-flow-cache' %}">
<a slot="trigger">
<i class="fa fa-trash"> </i>
</a>
<div slot="modal"></div>
</pb-modal-button>
</div>
<div class="pf-c-card__body">
{% if cached_flows < 1 %}
@ -221,117 +227,4 @@
</div>
</div>
</section>
<div class="pf-c-backdrop" id="clearPolicyCache" hidden>
<div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-sm" role="dialog">
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
<div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Policy Cache' %}?</h1>
</div>
<div class="pf-c-modal-box__body" id="modal-description">
<form method="post" id="clear_policies">
{% csrf_token %}
<input type="hidden" name="clear_policies">
<p>
{% blocktrans %}
Are you sure you want to clear the policy cache? This will cause all policies to be re-evaluated on their next usage.
{% endblocktrans %}
</p>
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<button form="clear_policies" class="pf-c-button pf-m-primary" type="submit">{% trans 'Clear' %}</button>
<button data-modal-close class="pf-c-button pf-m-link" type="button">{% trans 'Cancel' %}</button>
</footer>
</div>
</div>
</div>
<div class="pf-c-backdrop" id="clearFlowCache" hidden>
<div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-sm" role="dialog">
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
<div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Clear Flow Cache' %}?</h1>
</div>
<div class="pf-c-modal-box__body" id="modal-description">
<form method="post" id="clear_flows">
{% csrf_token %}
<input type="hidden" name="clear_flows">
<p>
{% blocktrans %}
Are you sure you want to clear the flow cache? This will cause all flows to be re-evaluated on their next usage.
{% endblocktrans %}
</p>
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<button form="clear_flows" class="pf-c-button pf-m-primary" type="submit">{% trans 'Clear' %}</button>
<button data-modal-close class="pf-c-button pf-m-link" type="button">{% trans 'Cancel' %}</button>
</footer>
</div>
</div>
</div>
<script src="{% static 'node_modules/chart.js/dist/Chart.bundle.min.js' %}"></script>
<script>
var ctx = document.getElementById('logins-last-metrics').getContext('2d');
fetch("{% url 'passbook_api:admin_metrics-list' %}").then(r => r.json()).then(r => {
var myChart = new Chart(ctx, {
type: 'bar',
data: {
datasets: [
{
label: 'Failed Logins',
backgroundColor: "rgba(201, 25, 11, .5)",
spanGaps: true,
data: r.logins_failed_per_1h,
},
{
label: 'Successful Logins',
backgroundColor: "rgba(189, 229, 184, .5)",
spanGaps: true,
data: r.logins_per_1h,
},
]
},
options: {
maintainAspectRatio: false,
spanGaps: true,
scales: {
xAxes: [{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
},
type: 'time',
offset: true,
ticks: {
callback: function (value, index, values) {
const date = new Date();
const delta = (date - values[index].value);
const ago = Math.round(delta / 1000 / 3600);
return `${ago} Hours ago`;
},
autoSkip: true,
maxTicksLimit: 8
}
}],
yAxes: [{
stacked: true,
gridLines: {
color: "rgba(0, 0, 0, 0)",
}
}]
}
}
});
});
</script>
{% endblock %}

View File

@ -20,7 +20,7 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -28,16 +28,19 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:policy-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:policy-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -71,9 +74,24 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:policy-update' pk=policy.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_admin:policy-test' pk=policy.pk %}?back={{ request.get_full_path }}">{% trans 'Test' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:policy-delete' pk=policy.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:policy-update' pk=policy.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:policy-test' pk=policy.pk %}">
<button slot="trigger" class="pf-c-button pf-m-tertiary">
{% trans 'Test' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:policy-delete' pk=policy.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</td>
</tr>
{% endfor %}
@ -101,7 +119,7 @@
{% trans 'Currently no policies exist. Click the button below to create one.' %}
{% endif %}
</div>
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -109,17 +127,19 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item"
href="{% url 'passbook_admin:policy-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:policy-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
</div>
</div>
{% endif %}

View File

@ -9,24 +9,3 @@
{% block action %}
{% trans 'Test' %}
{% endblock %}
{% block beneath_form %}
<div class="pf-c-form__group pf-m-action" style="display: none;" id="loading">
<div class="pf-c-form__horizontal-group">
<span class="pf-c-spinner" role="progressbar" aria-valuetext="Loading...">
<span class="pf-c-spinner__clipper"></span>
<span class="pf-c-spinner__lead-ball"></span>
<span class="pf-c-spinner__tail-ball"></span>
</span>
</div>
</div>
{% endblock %}
{% block scripts %}
{{ block.super }}
<script>
document.querySelector("form").addEventListener("submit", (e) => {
document.getElementById("loading").removeAttribute("style");
});
</script>
{% endblock %}

View File

@ -19,8 +19,15 @@
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:policy-binding-create' %}?back={{ request.get_full_path }}"
class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:policy-binding-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -66,9 +73,19 @@
<th role="cell">
<div>{{ binding.timeout }}</div>
</th>
<td class="pb-table-action" role="cell">
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:policy-binding-update' pk=binding.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:policy-binding-delete' pk=binding.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<td>
<pb-modal-button href="{% url 'passbook_admin:policy-binding-update' pk=binding.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:policy-binding-delete' pk=binding.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</td>
</tr>
{% endfor %}
@ -88,7 +105,12 @@
<div class="pf-c-empty-state__body">
{% trans 'Currently no policy bindings exist. Click the button below to create one.' %}
</div>
<a href="{% url 'passbook_admin:policy-binding-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:policy-binding-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}

View File

@ -21,7 +21,7 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -29,17 +29,22 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item"
href="{% url 'passbook_admin:property-mapping-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:property-mapping-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -66,8 +71,18 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:property-mapping-update' pk=property_mapping.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:property-mapping-delete' pk=property_mapping.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:property-mapping-update' pk=property_mapping.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:property-mapping-delete' pk=property_mapping.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</td>
</tr>
{% endfor %}
@ -95,7 +110,7 @@
{% trans 'Currently no property mappings exist. Click the button below to create one.' %}
{% endif %}
</div>
<div class="pf-c-dropdown">
<pb-dropdownpb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -103,17 +118,19 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item"
href="{% url 'passbook_admin:property-mapping-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:property-mapping-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
</div>
</div>
{% endif %}

View File

@ -22,7 +22,7 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -30,16 +30,22 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:provider-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:provider-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -77,11 +83,21 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:provider-update' pk=provider.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:provider-delete' pk=provider.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:provider-update' pk=provider.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:provider-delete' pk=provider.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
{% get_links provider as links %}
{% for name, href in links.items %}
<a class="pf-c-button pf-m-tertiary" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
<a class="pf-c-button pf-m-tertiary pb-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
{% endfor %}
{% get_htmls provider as htmls %}
{% for html in htmls %}
@ -114,7 +130,7 @@
{% trans 'Currently no providers exist. Click the button below to create one.' %}
{% endif %}
</div>
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -122,16 +138,19 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:provider-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:provider-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
</div>
</div>
{% endif %}

View File

@ -22,7 +22,7 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -30,16 +30,22 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:source-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:source-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -75,11 +81,21 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:source-update' pk=source.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:source-delete' pk=source.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:source-update' pk=source.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:source-delete' pk=source.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
{% get_links source as links %}
{% for name, href in links %}
<a class="pf-c-button pf-m-tertiary" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
<a class="pf-c-button pf-m-tertiary pb-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
{% endfor %}
</td>
</tr>
@ -108,7 +124,7 @@
{% trans 'Currently no sources exist. Click the button below to create one.' %}
{% endif %}
</div>
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -116,16 +132,19 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:source-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:source-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
</div>
</div>
{% endif %}

View File

@ -21,7 +21,7 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -29,16 +29,22 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item" href="{% url 'passbook_admin:stage-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:stage-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -63,18 +69,28 @@
<td role="cell">
<ul>
{% for flow in stage.flow_set.all %}
<li><a href="{% url 'passbook_admin:flow-update' pk=flow.pk %}">{{ flow.slug }}</a></li>
<li>{{ flow.slug }}<</li>
{% empty %}
<li>-</li>
{% endfor %}
</ul>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:stage-update' pk=stage.stage_uuid %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:stage-delete' pk=stage.stage_uuid %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:stage-update' pk=stage.stage_uuid %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:stage-delete' pk=stage.stage_uuid %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
{% get_links stage as links %}
{% for name, href in links.items %}
<a class="pf-c-button pf-m-tertiary" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
<a class="pf-c-button pf-m-tertiary pb-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
{% endfor %}
</td>
</tr>
@ -103,7 +119,7 @@
{% trans 'Currently no stages exist. Click the button below to create one.' %}
{% endif %}
</div>
<div class="pf-c-dropdown">
<pb-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">{% trans 'Create' %}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
@ -111,17 +127,19 @@
<ul class="pf-c-dropdown__menu" hidden>
{% for type, name in types.items %}
<li>
<a class="pf-c-dropdown__menu-item"
href="{% url 'passbook_admin:stage-create' %}?type={{ type }}&back={{ request.get_full_path }}">
<pb-modal-button href="{% url 'passbook_admin:stage-create' %}?type={{ type }}">
<button slot="trigger" class="pf-c-dropdown__menu-item">
{{ name|verbose_name }}<br>
<small>
{{ name|doc }}
</small>
</a>
</button>
<div slot="modal"></div>
</pb-modal-button>
</li>
{% endfor %}
</ul>
</div>
</pb-dropdown>
</div>
</div>
{% endif %}

View File

@ -19,8 +19,15 @@
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:stage-binding-create' %}?back={{ request.get_full_path }}"
class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:stage-binding-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -73,8 +80,18 @@
</div>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:stage-binding-update' pk=binding.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:stage-binding-delete' pk=binding.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:stage-binding-update' pk=binding.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Update' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:stage-binding-delete' pk=binding.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</td>
</tr>
{% endfor %}
@ -94,7 +111,12 @@
<div class="pf-c-empty-state__body">
{% trans 'Currently no flow-stage bindings exist. Click the button below to create one.' %}
</div>
<a href="{% url 'passbook_admin:certificatekeypair-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:stage-binding-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}

View File

@ -21,8 +21,15 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:stage-invitation-create' %}?back={{ request.get_full_path }}"
class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:stage-invitation-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -49,7 +56,12 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:stage-invitation-delete' pk=invitation.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:stage-invitation-delete' pk=invitation.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</td>
</tr>
{% endfor %}
@ -77,7 +89,12 @@
{% trans 'Currently no invitations exist. Click the button below to create one.' %}
{% endif %}
</div>
<a href="{% url 'passbook_admin:stage-invitation-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:stage-invitation-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}

View File

@ -21,7 +21,15 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:stage-prompt-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:stage-prompt-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -63,18 +71,28 @@
<td role="cell">
<ul>
{% for flow in prompt.flow_set.all %}
<li><a href="{% url 'passbook_admin:flow-update' pk=flow.pk %}">{{ flow.slug }}</a></li>
<li>{{ flow.slug }}</li>
{% empty %}
<li>-</li>
{% endfor %}
</ul>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:stage-prompt-update' pk=prompt.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:stage-prompt-delete' pk=prompt.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:stage-prompt-update' pk=prompt.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Update' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_admin:stage-prompt-delete' pk=prompt.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
{% get_links prompt as links %}
{% for name, href in links.items %}
<a class="pf-c-button pf-m-tertiary" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
<a class="pf-c-button pf-m-tertiary pb-root-link" href="{{ href }}?back={{ request.get_full_path }}">{% trans name %}</a>
{% endfor %}
</td>
</tr>

View File

@ -16,6 +16,13 @@
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
</div>
<table class="pf-c-table pf-m-compact pf-m-grid-xl" role="grid">
<thead>
<tr role="row">
@ -64,9 +71,9 @@
{% endfor %}
</td>
<td>
<button is="action-button" class="pf-c-button pf-m-primary" url="{% url 'passbook_api:admin_system_tasks-retry' pk=task.task_name %}">
<pb-action-button url="{% url 'passbook_api:admin_system_tasks-retry' pk=task.task_name %}">
{% trans 'Retry Task' %}
</button>
</pb-action-button>
</td>
</tr>
{% endfor %}

View File

@ -7,7 +7,7 @@
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="fas fa-key"></i>
<i class="pf-icon pf-icon-security"></i>
{% trans 'Tokens' %}
</h1>
<p>{% trans "Tokens are used throughout passbook for Email validation stages, Recovery keys and API access." %}</p>
@ -58,7 +58,15 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_admin:token-delete' pk=token.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_admin:token-delete' pk=token.pk %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-token-copy-button identifier="{{ token.identifier }}">
{% trans 'Copy token' %}
</pb-token-copy-button>
</td>
</tr>
{% endfor %}

View File

@ -19,7 +19,15 @@
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_admin:user-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:user-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -53,14 +61,29 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_admin:user-update' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<pb-modal-button href="{% url 'passbook_admin:user-update' pk=user.pk %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
{% if user.is_active %}
<a class="pf-c-button pf-m-warning" href="{% url 'passbook_admin:user-disable' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Disable' %}</a>
<pb-modal-button href="{% url 'passbook_admin:user-disable' pk=user.pk %}">
<button slot="trigger" class="pf-c-button pf-m-warning">
{% trans 'Disable' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
{% else %}
<a class="pf-c-button pf-m-primary" href="{% url 'passbook_admin:user-enable' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Enable' %}</a>
<pb-modal-button href="{% url 'passbook_admin:user-delete' pk=user.pk %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Enable' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
{% endif %}
<a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a>
<a class="pf-c-button pf-m-tertiary" href="{% url 'passbook_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a>
<a class="pf-c-button pf-m-tertiary pb-root-link" href="{% url 'passbook_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a>
<a class="pf-c-button pf-m-tertiary pb-root-link" href="{% url 'passbook_core:impersonate-init' user_id=user.pk %}">{% trans 'Impersonate' %}</a>
</td>
</tr>
{% endfor %}
@ -88,7 +111,12 @@
{% trans 'Currently no users exist. How did you even get here.' %}
{% endif %}
</div>
<a href="{% url 'passbook_admin:user-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_admin:user-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}

View File

@ -0,0 +1 @@
<pb-codemirror mode="{{ widget.attrs.mode }}"><textarea class="pf-c-form-control" name="{{ widget.name }}">{% if widget.value %}{{ widget.value }}{% endif %}</textarea></pb-codemirror>

View File

@ -30,26 +30,20 @@
<div class="pf-l-stack__item">
<div class="pf-c-card">
<div class="pf-c-card__body">
<form action="" method="post" class="pf-c-form pf-m-horizontal" enctype="multipart/form-data">
<form id="main-form" action="" method="post" class="pf-c-form pf-m-horizontal" enctype="multipart/form-data">
{% include 'partials/form_horizontal.html' with form=form %}
{% block beneath_form %}
{% endblock %}
<div class="pf-c-form__group pf-m-action">
<div class="pf-c-form__group-control">
<div class="pf-c-form__horizontal-group">
<div class="pf-c-form__actions">
<input class="pf-c-button pf-m-primary" type="submit" value="{% block action %}{% endblock %}" />
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
<footer class="pf-c-modal-box__footer">
<input class="pf-c-button pf-m-primary" type="submit" form="main-form" value="{% block action %}{% endblock %}" />
<a class="pf-c-button pf-m-secondary" href="{% back %}">{% trans "Cancel" %}</a>
</footer>
{% endblock %}
{% block scripts %}

View File

@ -0,0 +1,20 @@
{% extends base_template|default:"generic/form.html" %}
{% load passbook_utils %}
{% load i18n %}
{% block above_form %}
<h1>
{% trans form.title %}
</h1>
{% endblock %}
{% block beneath_form %}
<p>
{% trans form.body %}
</p>
{% endblock %}
{% block action %}
{% trans 'Confirm' %}
{% endblock %}

View File

@ -24,7 +24,17 @@ from passbook.admin.views import (
)
urlpatterns = [
path("", overview.AdministrationOverviewView.as_view(), name="overview"),
path(
"overview/cache/flow/",
overview.FlowCacheClearView.as_view(),
name="overview-clear-flow-cache",
),
path(
"overview/cache/policy/",
overview.PolicyCacheClearView.as_view(),
name="overview-clear-policy-cache",
),
path("overview/", overview.AdministrationOverviewView.as_view(), name="overview"),
# Applications
path(
"applications/", applications.ApplicationListView.as_view(), name="applications"

View File

@ -2,14 +2,20 @@
from typing import Union
from django.conf import settings
from django.contrib.messages.views import SuccessMessageMixin
from django.core.cache import cache
from django.db.models import Count
from django.db.models.fields.json import KeyTextTransform
from django.views.generic import TemplateView
from django.http.request import HttpRequest
from django.http.response import HttpResponse
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import FormView, TemplateView
from packaging.version import LegacyVersion, Version, parse
from structlog import get_logger
from passbook import __version__
from passbook.admin.forms.overview import FlowCacheClearForm, PolicyCacheClearForm
from passbook.admin.mixins import AdminRequiredMixin
from passbook.admin.tasks import VERSION_CACHE_KEY, update_latest_version
from passbook.audit.models import Event, EventAction
@ -24,18 +30,6 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
template_name = "administration/overview.html"
def post(self, *args, **kwargs):
"""Handle post (clear cache from modal)"""
if "clear_policies" in self.request.POST:
keys = cache.keys("policy_*")
cache.delete_many(keys)
LOGGER.debug("Cleared Policy cache", keys=len(keys))
if "clear_flows" in self.request.POST:
keys = cache.keys("flow_*")
cache.delete_many(keys)
LOGGER.debug("Cleared flow cache", keys=len(keys))
return self.get(*args, **kwargs)
def get_latest_version(self) -> Union[LegacyVersion, Version]:
"""Get latest version from cache"""
version_in_cache = cache.get(VERSION_CACHE_KEY)
@ -75,3 +69,35 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
kwargs["cached_policies"] = len(cache.keys("policy_*"))
kwargs["cached_flows"] = len(cache.keys("flow_*"))
return super().get_context_data(**kwargs)
class PolicyCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView):
"""View to clear Policy cache"""
form_class = PolicyCacheClearForm
template_name = "generic/form_non_model.html"
success_url = reverse_lazy("passbook_admin:overview")
success_message = _("Successfully cleared Policy cache")
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
keys = cache.keys("policy_*")
cache.delete_many(keys)
LOGGER.debug("Cleared Policy cache", keys=len(keys))
return super().post(request, *args, **kwargs)
class FlowCacheClearView(AdminRequiredMixin, SuccessMessageMixin, FormView):
"""View to clear Flow cache"""
form_class = FlowCacheClearForm
template_name = "generic/form_non_model.html"
success_url = reverse_lazy("passbook_admin:overview")
success_message = _("Successfully cleared Flow cache")
def post(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
keys = cache.keys("flow_*")
cache.delete_many(keys)
LOGGER.debug("Cleared flow cache", keys=len(keys))
return super().post(request, *args, **kwargs)

View File

@ -155,7 +155,7 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
def get(self, request: HttpRequest, *args, **kwargs) -> HttpResponse:
"""Create token for user and return link"""
super().get(request, *args, **kwargs)
token, _ = Token.objects.get_or_create(
token, __ = Token.objects.get_or_create(
identifier="password-reset-temp", user=self.object
)
querystring = urlencode({"token": token.key})

39
passbook/api/v2/config.py Normal file
View File

@ -0,0 +1,39 @@
"""core Configs API"""
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import ReadOnlyField, Serializer
from rest_framework.viewsets import ViewSet
from passbook.lib.config import CONFIG
class ConfigSerializer(Serializer):
"""Serialize passbook Config into DRF Object"""
branding_logo = ReadOnlyField()
branding_title = ReadOnlyField()
def create(self, request: Request) -> Response:
raise NotImplementedError
def update(self, request: Request) -> Response:
raise NotImplementedError
class ConfigsViewSet(ViewSet):
"""Read-only view set that returns the current session's Configs"""
permission_classes = [AllowAny]
@swagger_auto_schema(responses={200: ConfigSerializer(many=True)})
def list(self, request: Request) -> Response:
"""Retrive public configuration options"""
config = ConfigSerializer(
{
"branding_logo": CONFIG.y("passbook.branding.logo"),
"branding_title": CONFIG.y("passbook.branding.title"),
}
)
return Response(config.data)

View File

@ -8,6 +8,7 @@ from rest_framework.permissions import AllowAny
from passbook.admin.api.overview import AdministrationOverviewViewSet
from passbook.admin.api.overview_metrics import AdministrationMetricsViewSet
from passbook.admin.api.tasks import TaskViewSet
from passbook.api.v2.config import ConfigsViewSet
from passbook.api.v2.messages import MessagesViewSet
from passbook.audit.api import EventViewSet
from passbook.core.api.applications import ApplicationViewSet
@ -57,6 +58,7 @@ from passbook.stages.user_write.api import UserWriteStageViewSet
router = routers.DefaultRouter()
router.register("root/messages", MessagesViewSet, basename="messages")
router.register("root/config", ConfigsViewSet, basename="configs")
router.register(
"admin/overview", AdministrationOverviewViewSet, basename="admin_overview"

View File

@ -18,6 +18,9 @@
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<button role="pb-refresh" class="pf-c-button pf-m-primary">
{% trans 'Refresh' %}
</button>
{% include 'partials/pagination.html' %}
</div>
</div>

View File

@ -1,19 +1,33 @@
"""User API Views"""
from rest_framework.serializers import BooleanField, ModelSerializer
from drf_yasg2.utils import swagger_auto_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import (
BooleanField,
ModelSerializer,
SerializerMethodField,
)
from rest_framework.viewsets import ModelViewSet
from passbook.core.models import User
from passbook.lib.templatetags.passbook_utils import avatar
class UserSerializer(ModelSerializer):
"""User Serializer"""
is_superuser = BooleanField(read_only=True)
avatar = SerializerMethodField()
def get_avatar(self, user: User) -> str:
"""Add user's avatar as URL"""
return avatar(user)
class Meta:
model = User
fields = ["pk", "username", "name", "is_superuser", "email"]
fields = ["pk", "username", "name", "is_superuser", "email", "avatar"]
class UserViewSet(ModelViewSet):
@ -21,3 +35,10 @@ class UserViewSet(ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
@swagger_auto_schema(responses={200: UserSerializer(many=False)})
@action(detail=False)
# pylint: disable=invalid-name
def me(self, request: Request) -> Response:
"""Get information about current user"""
return Response(UserSerializer(request.user).data)

View File

@ -29,18 +29,17 @@ class ApplicationForm(forms.ModelForm):
]
widgets = {
"name": forms.TextInput(),
"meta_launch_url": forms.TextInput(
attrs={
"placeholder": _(
"meta_launch_url": forms.TextInput(),
"meta_icon_url": forms.TextInput(),
"meta_publisher": forms.TextInput(),
}
help_texts = {
"meta_launch_url": _(
(
"If left empty, passbook will try to extract the launch URL "
"based on the selected provider."
)
)
}
),
"meta_icon_url": forms.TextInput(),
"meta_publisher": forms.TextInput(),
}
field_classes = {"provider": GroupedModelChoiceField}
labels = {

View File

@ -1,6 +1,5 @@
"""passbook Core Group forms"""
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from passbook.admin.fields import CodeMirrorWidget, YAMLField
from passbook.core.models import Group, User
@ -12,7 +11,6 @@ class GroupForm(forms.ModelForm):
members = forms.ModelMultipleChoiceField(
User.objects.all(),
required=False,
widget=FilteredSelectMultiple("users", False),
)
def __init__(self, *args, **kwargs):

View File

@ -18,7 +18,7 @@ from structlog import get_logger
from passbook.core.exceptions import PropertyMappingExpressionException
from passbook.core.signals import password_changed
from passbook.core.types import UILoginButton, UIUserSettings
from passbook.core.types import UILoginButton
from passbook.flows.models import Flow
from passbook.lib.models import CreatedUpdatedModel
from passbook.policies.models import PolicyBindingModel
@ -249,9 +249,9 @@ class Source(PolicyBindingModel):
return None
@property
def ui_user_settings(self) -> Optional[UIUserSettings]:
def ui_user_settings(self) -> Optional[str]:
"""Entrypoint to integrate with User settings. Can either return None if no
user settings are available, or an instanace of UIUserSettings."""
user settings are available, or a string with the URL to fetch."""
return None
def __str__(self):

View File

@ -8,54 +8,8 @@
{% block body %}
<pb-messages url="{% url 'passbook_api:messages-list' %}"></pb-messages>
<div class="pf-c-page" id="page-default-nav-example">
<div class="pf-c-page">
<a class="pf-c-skip-to-content pf-c-button pf-m-primary" href="#main-content">{% trans 'Skip to content' %}</a>
<header role="banner" class="pf-c-page__header ws-page-header">
<div class="pf-c-page__header-brand">
<div class="pf-c-page__header-brand-toggle">
<button class="pf-c-button pf-m-plain" type="button" id="page-default-nav-example-nav-toggle"
aria-label="Global navigation" aria-expanded="true"
aria-controls="page-default-nav-example-primary-nav">
<i class="fas fa-bars" aria-hidden="true"></i>
</button>
</div>
<a href="{% url 'passbook_core:overview' %}" class="pf-c-page__header-brand-link">
<div class="pf-c-brand pb-brand">
<img src="{{ config.passbook.branding.logo }}" style="width: 100px;" alt="passbook icon">
{% if config.passbook.branding.title_show %}
<small><small>{{ config.passbook.branding.title }}</small></small>
{% endif %}
</div>
</a>
</div>
<div class="pf-c-page__header-nav">
<nav class="pf-c-nav pf-m-horizontal" aria-label="Nav">
<ul class="pf-c-nav__list ws-top-nav">
<li class="pf-c-nav__item"><a class="pf-c-nav__link {% is_active_url 'passbook_core:overview' %}"
href="{% url 'passbook_core:overview' %}">{% trans 'Access' %}</a></li>
{% if user.is_superuser %}
<li class="pf-c-nav__item"><a class="pf-c-nav__link {% is_active_app 'passbook_admin' %}"
href="{% url 'passbook_admin:overview' %}">{% trans 'Administrate' %}</a></li>
<li class="pf-c-nav__item"><a class="pf-c-nav__link {% is_active_url 'passbook_audit:log' %}"
href="{% url 'passbook_audit:log' %}">{% trans 'Monitor' %}</a></li>
{% endif %}
</ul>
</nav>
</div>
<div class="pf-c-page__header-tools">
<div class="pf-c-page__header-tools-group pf-m-icons">
<a href="{% url 'passbook_flows:default-invalidation' %}" class="pf-c-button pf-m-plain" type="button" id="logout">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i>
</a>
</div>
<div class="pf-c-page__header-tools-group">
<a href="{% url 'passbook_core:user-settings' %}" id="user-settings" class="pf-c-button">
{{ user.username }}
</a>
</div>
<img class="pf-c-avatar" src="{% avatar user %}" alt="">
</div>
</header>
{% block page_content %}
{% endblock %}
</div>

View File

@ -6,17 +6,18 @@
<html lang="en">
<head>
<link rel="preload" href="{% static 'passbook/fonts/DINEngschriftStd.woff2' %}" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="{% static 'passbook/fonts/DINEngschriftStd.woff' %}" as="font" type="font/woff" crossorigin>
<link rel="preload" href="{% static 'dist/assets/fonts/DINEngschriftStd.woff2' %}" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="{% static 'dist/assets/fonts/DINEngschriftStd.woff' %}" as="font" type="font/woff" crossorigin>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>{% block title %}{% trans title|default:config.passbook.branding.title %}{% endblock %}</title>
<link rel="icon" type="image/png" href="{% static 'passbook/logo.png' %}">
<link rel="shortcut icon" type="image/png" href="{% static 'passbook/logo.png' %}">
<link rel="icon" type="image/png" href="{% static 'dist/assets/images/logo.png' %}">
<link rel="shortcut icon" type="image/png" href="{% static 'dist/assets/images/logo.png' %}">
<link rel="stylesheet" type="text/css" href="{% static 'node_modules/@patternfly/patternfly/patternfly.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'node_modules/@patternfly/patternfly/patternfly-addons.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'node_modules/@fortawesome/fontawesome-free/css/fontawesome.min.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'passbook/passbook.css' %}">
<link rel="stylesheet" type="text/css" href="{% static 'dist/passbook.css' %}">
<script src="{% static 'dist/main.js' %}" type="module"></script>
{% block head %}
{% endblock %}
</head>
@ -37,6 +38,5 @@
{% endblock %}
{% block scripts %}
{% endblock %}
<script src="{% static 'passbook/passbook.js' %}"></script>
</body>
</html>

View File

@ -1,14 +0,0 @@
{% extends "base/page.html" %}
{% load static %}
{% load i18n %}
{% load passbook_is_active %}
{% load passbook_utils %}
{% block page_content %}
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
{% block content %}
{% endblock %}
</main>
{% endblock %}

View File

@ -1,27 +1,15 @@
{% extends "overview/base.html" %}
{% load i18n %}
{% block head %}
{{ block.super }}
<style>
img.app-icon {
max-height: 72px;
width: auto !important;
}
</style>
{% endblock %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-applications"></i>
{% trans 'Applications' %}
</h1>
</div>
</section>
<section class="pf-c-page__main-section">
</section>
<section class="pf-c-page__main-section">
{% if applications %}
<div class="pf-l-gallery pf-m-gutter">
{% for app in applications %}
@ -61,5 +49,5 @@
</div>
</div>
{% endif %}
</section>
{% endblock %}
</section>
</main>

View File

@ -17,7 +17,7 @@
<div class="pf-c-pagination__nav-control pf-m-prev">
<a class="pf-c-button pf-m-plain"
{% if page_obj.has_previous %}
href="?{% query_transform page=page_obj.previous_page_number %}"
href="{{ request.path }}?{% query_transform page=page_obj.previous_page_number %}"
{% else %}
disabled
{% endif %}
@ -28,7 +28,7 @@
<div class="pf-c-pagination__nav-control pf-m-next">
<a class="pf-c-button pf-m-plain"
{% if page_obj.has_next %}
href="?{% query_transform page=page_obj.next_page_number %}"
href="{{ request.path }}?{% query_transform page=page_obj.next_page_number %}"
{% else %}
disabled
{% endif %}

View File

@ -0,0 +1,14 @@
{% extends "base/page.html" %}
{% load static %}
{% load i18n %}
{% load passbook_is_active %}
{% load passbook_utils %}
{% block page_content %}
<pb-sidebar class="pf-c-page__sidebar">
</pb-sidebar>
<pb-router-outlet role="main" class="pf-c-page__main" tabindex="-1" id="main-content" defaultUrl="/-/overview/">
</pb-router-outlet>
{% endblock %}

View File

@ -1,71 +0,0 @@
{% extends "base/page.html" %}
{% load i18n %}
{% load passbook_is_active %}
{% load static %}
{% load passbook_user_settings %}
{% block page_content %}
<div class="pf-c-page__sidebar">
<div class="pf-c-page__sidebar-body">
<nav class="pf-c-nav" id="page-default-nav-example-primary-nav" aria-label="Global">
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'General Settings' %}</h2>
<ul class="pf-c-nav__list">
<li class="pf-c-nav__item">
<a href="{% url 'passbook_core:user-settings' %}"
class="pf-c-nav__link {% is_active 'passbook_core:user-settings' %}">{% trans 'User Details' %}</a>
</li>
<li class="pf-c-nav__item">
<a href="{% url 'passbook_core:user-tokens' %}"
class="pf-c-nav__link {% is_active 'passbook_core:user-tokens' 'passbook_core:user-tokens-create' 'passbook_core:user-tokens-update' 'passbook_core:user-tokens-delete' %}">{% trans 'Tokens' %}</a>
</li>
</ul>
</section>
{% user_stages as user_stages_loc %}
{% if user_stages_loc %}
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'Stages' %}</h2>
<ul class="pf-c-nav__list">
{% for stage in user_stages_loc %}
<li class="pf-c-nav__item">
<a href="{{ stage.url }}" class="pf-c-nav__link {% if stage.url == request.get_full_path %} pf-m-current {% endif %}">
{{ stage.name }}
</a>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
{% user_sources as user_sources_loc %}
{% if user_sources_loc %}
<section class="pf-c-nav__section">
<h2 class="pf-c-nav__section-title">{% trans 'Sources' %}</h2>
<ul class="pf-c-nav__list">
{% for source in user_sources_loc %}
<li class="pf-c-nav__item">
<a href="{{ source.url }}"
class="pf-c-nav__link {% if source.url == request.get_full_path %} pf-m-current {% endif %}">
{{ source.name }}
</a>
</li>
{% endfor %}
</ul>
</section>
{% endif %}
</nav>
</div>
</div>
<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
{% block content %}
<section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
{% block page %}
{% endblock %}
</div>
</div>
</section>
{% endblock %}
</main>
{% endblock %}

View File

@ -1,9 +1,21 @@
{% extends "user/base.html" %}
{% load i18n %}
{% load passbook_user_settings %}
{% block page %}
<div class="pf-c-card">
<div class="pf-c-page">
<main role="main" class="pf-c-page__main" tabindex="-1">
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-user"></i>
{% trans 'User Settings' %}
</h1>
<p>{% trans "Configure settings relevant to your user profile." %}</p>
</div>
</section>
<section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md">
{% trans 'Update details' %}
</div>
@ -17,12 +29,50 @@
<div class="pf-c-form__actions">
<input class="pf-c-button pf-m-primary" type="submit" value="{% trans 'Update' %}" />
{% if unenrollment_enabled %}
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_flows:default-unenrollment' %}?back={{ request.get_full_path }}">{% trans "Delete account" %}</a>
<a class="pf-c-button pf-m-danger"
href="{% url 'passbook_flows:default-unenrollment' %}?back={{ request.get_full_path }}">{% trans "Delete account" %}</a>
{% endif %}
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</section>
<section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<pb-site-shell url="{% url 'passbook_core:user-tokens' %}">
<div slot="body"></div>
</pb-site-shell>
</div>
</div>
</section>
{% user_stages as user_stages_loc %}
{% for stage in user_stages_loc %}
<section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<pb-site-shell url="{{ stage }}">
<div slot="body"></div>
</pb-site-shell>
</div>
</div>
</section>
{% endfor %}
{% user_sources as user_sources_loc %}
{% for source in user_sources_loc %}
<section class="pf-c-page__main-section">
<div class="pf-u-display-flex pf-u-justify-content-center">
<div class="pf-u-w-75">
<pb-site-shell url="{{ source }}">
<div slot="body"></div>
</pb-site-shell>
</div>
</div>
</section>
{% endfor %}
</main>
</div>
{% endblock %}

View File

@ -1,27 +1,20 @@
{% extends "user/base.html" %}
{% load i18n %}
{% load passbook_utils %}
{% block content %}
<section class="pf-c-page__main-section pf-m-light">
<div class="pf-c-content">
<h1>
<i class="pf-icon pf-icon-users"></i>
{% trans 'Tokens' %}
</h1>
<p>{% trans "Tokens can be used to access passbook's API." %}
</p>
<div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md">
<p>{% trans "Tokens can be used to access passbook's API." %}</p>
</div>
</section>
<section class="pf-c-page__main-section pf-m-no-padding-mobile">
<div class="pf-c-card">
{% if object_list %}
<div class="pf-c-toolbar">
<div class="pf-c-toolbar__content">
{% include 'partials/toolbar_search.html' %}
<div class="pf-c-toolbar__bulk-select">
<a href="{% url 'passbook_core:user-tokens-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_core:user-tokens-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
{% include 'partials/pagination.html' %}
</div>
@ -62,8 +55,21 @@
</span>
</td>
<td>
<a class="pf-c-button pf-m-secondary" href="{% url 'passbook_core:user-tokens-update' identifier=token.identifier %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a>
<a class="pf-c-button pf-m-danger" href="{% url 'passbook_core:user-tokens-delete' identifier=token.identifier %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a>
<pb-modal-button href="{% url 'passbook_core:user-tokens-update' identifier=token.identifier %}">
<button slot="trigger" class="pf-c-button pf-m-secondary">
{% trans 'Edit' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-modal-button href="{% url 'passbook_core:user-tokens-delete' identifier=token.identifier %}">
<button slot="trigger" class="pf-c-button pf-m-danger">
{% trans 'Delete' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
<pb-token-copy-button identifier="{{ token.identifier }}">
{% trans 'Copy token' %}
</pb-token-copy-button>
</td>
</tr>
{% endfor %}
@ -82,10 +88,13 @@
<div class="pf-c-empty-state__body">
{% trans 'Currently no tokens exist. Click the button below to create one.' %}
</div>
<a href="{% url 'passbook_core:user-tokens-create' %}?back={{ request.get_full_path }}" class="pf-c-button pf-m-primary" type="button">{% trans 'Create' %}</a>
<pb-modal-button href="{% url 'passbook_core:user-tokens-create' %}">
<button slot="trigger" class="pf-c-button pf-m-primary">
{% trans 'Create' %}
</button>
<div slot="modal"></div>
</pb-modal-button>
</div>
</div>
{% endif %}
</div>
</section>
{% endblock %}
</div>

View File

@ -1,11 +1,10 @@
"""passbook user settings template tags"""
from typing import Iterable, List
from typing import Iterable
from django import template
from django.template.context import RequestContext
from passbook.core.models import Source
from passbook.core.types import UIUserSettings
from passbook.flows.models import Stage
from passbook.policies.engine import PolicyEngine
@ -14,26 +13,26 @@ register = template.Library()
@register.simple_tag(takes_context=True)
# pylint: disable=unused-argument
def user_stages(context: RequestContext) -> List[UIUserSettings]:
def user_stages(context: RequestContext) -> list[str]:
"""Return list of all stages which apply to user"""
_all_stages: Iterable[Stage] = Stage.objects.all().select_subclasses()
matching_stages: List[UIUserSettings] = []
matching_stages: list[str] = []
for stage in _all_stages:
user_settings = stage.ui_user_settings
if not user_settings:
continue
matching_stages.append(user_settings)
return sorted(matching_stages, key=lambda x: x.name)
return matching_stages
@register.simple_tag(takes_context=True)
def user_sources(context: RequestContext) -> List[UIUserSettings]:
def user_sources(context: RequestContext) -> list[str]:
"""Return a list of all sources which are enabled for the user"""
user = context.get("request").user
_all_sources: Iterable[Source] = Source.objects.filter(
enabled=True
).select_subclasses()
matching_sources: List[UIUserSettings] = []
matching_sources: list[str] = []
for source in _all_sources:
user_settings = source.ui_user_settings
if not user_settings:
@ -42,4 +41,4 @@ def user_sources(context: RequestContext) -> List[UIUserSettings]:
policy_engine.build()
if policy_engine.passing:
matching_sources.append(user_settings)
return sorted(matching_sources, key=lambda x: x.name)
return matching_sources

View File

@ -23,13 +23,13 @@ class TestImpersonation(TestCase):
)
)
response = self.client.get(reverse("passbook_core:overview"))
response = self.client.get(reverse("passbook_api:user-me"))
self.assertIn(self.other_user.username, response.content.decode())
self.assertNotIn(self.pbadmin.username, response.content.decode())
self.client.get(reverse("passbook_core:impersonate-end"))
response = self.client.get(reverse("passbook_core:overview"))
response = self.client.get(reverse("passbook_api:user-me"))
self.assertNotIn(self.other_user.username, response.content.decode())
self.assertIn(self.pbadmin.username, response.content.decode())
@ -43,7 +43,7 @@ class TestImpersonation(TestCase):
)
)
response = self.client.get(reverse("passbook_core:overview"))
response = self.client.get(reverse("passbook_api:user-me"))
self.assertIn(self.other_user.username, response.content.decode())
self.assertNotIn(self.pbadmin.username, response.content.decode())
@ -52,4 +52,4 @@ class TestImpersonation(TestCase):
self.client.force_login(self.other_user)
response = self.client.get(reverse("passbook_core:impersonate-end"))
self.assertRedirects(response, reverse("passbook_core:overview"))
self.assertRedirects(response, reverse("passbook_core:shell"))

View File

@ -23,8 +23,20 @@ class TestOverviewViews(TestCase):
)
self.client.force_login(self.user)
def test_shell(self):
"""Test shell"""
self.assertEqual(
self.client.get(reverse("passbook_core:shell")).status_code, 200
)
def test_overview(self):
"""Test UserSettingsView"""
"""Test overview"""
self.assertEqual(
self.client.get(reverse("passbook_core:overview")).status_code, 200
)
def test_user_settings(self):
"""Test user settings"""
self.assertEqual(
self.client.get(reverse("passbook_core:user-settings")).status_code, 200
)

View File

@ -3,17 +3,9 @@ from dataclasses import dataclass
from typing import Optional
@dataclass
class UIUserSettings:
"""Dataclass for Stage and Source's user_settings"""
name: str
url: str
@dataclass
class UILoginButton:
"""Dataclass for Source's ui_ui_login_button"""
"""Dataclass for Source's ui_login_button"""
# Name, ran through i18n
name: str

View File

@ -1,7 +1,7 @@
"""passbook URL Configuration"""
from django.urls import path
from passbook.core.views import impersonate, overview, user
from passbook.core.views import impersonate, overview, shell, user
urlpatterns = [
# User views
@ -23,7 +23,8 @@ urlpatterns = [
name="user-tokens-delete",
),
# Overview
path("", overview.OverviewView.as_view(), name="overview"),
path("", shell.ShellView.as_view(), name="shell"),
path("-/overview/", overview.OverviewView.as_view(), name="overview"),
# Impersonation
path(
"-/impersonation/<int:user_id>/",

View File

@ -33,7 +33,7 @@ class ImpersonateInitView(View):
Event.new(EventAction.IMPERSONATION_STARTED).from_http(request, user_to_be)
return redirect("passbook_core:overview")
return redirect("passbook_core:shell")
class ImpersonateEndView(View):
@ -46,7 +46,7 @@ class ImpersonateEndView(View):
or SESSION_IMPERSONATE_ORIGINAL_USER not in request.session
):
LOGGER.debug("Can't end impersonation", user=request.user)
return redirect("passbook_core:overview")
return redirect("passbook_core:shell")
original_user = request.session[SESSION_IMPERSONATE_ORIGINAL_USER]
@ -55,4 +55,4 @@ class ImpersonateEndView(View):
Event.new(EventAction.IMPERSONATION_ENDED).from_http(request, original_user)
return redirect("passbook_core:overview")
return redirect("passbook_core:shell")

View File

@ -0,0 +1,9 @@
"""core shell view"""
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.base import TemplateView
class ShellView(LoginRequiredMixin, TemplateView):
"""core shell view"""
template_name = "shell.html"

View File

@ -79,7 +79,7 @@ class CertificateKeyPair(CreatedUpdatedModel):
)
def __str__(self) -> str:
return f"Certificate-Key Pair {self.name} {self.fingerprint}"
return f"Certificate-Key Pair {self.name}"
class Meta:

View File

@ -10,7 +10,6 @@ from model_utils.managers import InheritanceManager
from rest_framework.serializers import BaseSerializer
from structlog import get_logger
from passbook.core.types import UIUserSettings
from passbook.lib.models import InheritanceForeignKey, SerializerModel
from passbook.policies.models import PolicyBindingModel
@ -64,9 +63,9 @@ class Stage(SerializerModel):
raise NotImplementedError
@property
def ui_user_settings(self) -> Optional[UIUserSettings]:
def ui_user_settings(self) -> Optional[str]:
"""Entrypoint to integrate with User settings. Can either return None if no
user settings are available, or an instanace of UIUserSettings."""
user settings are available, or a string with the URL to fetch."""
return None
def __str__(self):

View File

@ -21,8 +21,8 @@
{% endblock %}
{% block main_container %}
<flow-shell-card
<pb-flow-shell-card
class="pf-c-login__main"
flowBodyUrl="{{ exec_url }}">
</flow-shell-card>
</pb-flow-shell-card>
{% endblock %}

View File

@ -207,7 +207,7 @@ class TestFlowExecutor(TestCase):
# We do this request without the patch, so the policy results in false
response = self.client.post(exec_url)
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, reverse("passbook_core:overview"))
self.assertEqual(response.url, reverse("passbook_core:shell"))
def test_reevaluate_remove_middle(self):
"""Test planner with re-evaluate (middle stage is removed)"""
@ -273,7 +273,7 @@ class TestFlowExecutor(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
def test_reevaluate_remove_consecutive(self):
@ -349,5 +349,5 @@ class TestFlowExecutor(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)

View File

@ -144,7 +144,7 @@ class FlowExecutorView(View):
# Since this is wrapped by the ExecutorShell, the next argument is saved in the session
# extract the next param before cancel as that cleans it
next_param = self.request.session.get(SESSION_KEY_GET, {}).get(
NEXT_ARG_NAME, "passbook_core:overview"
NEXT_ARG_NAME, "passbook_core:shell"
)
self.cancel()
return redirect_with_qs(next_param)
@ -248,7 +248,7 @@ class CancelView(View):
if SESSION_KEY_PLAN in request.session:
del request.session[SESSION_KEY_PLAN]
LOGGER.debug("Canceled current plan")
return redirect("passbook_core:overview")
return redirect("passbook_core:shell")
class ToDefaultFlow(View):

View File

@ -29,7 +29,7 @@ passbook:
branding:
title: passbook
title_show: true
logo: /static/passbook/logo.svg
logo: /static/dist/assets/images/logo.svg
# Optionally add links to the footer on the login page
footer_links:
- name: Documentation

View File

@ -1,13 +1,10 @@
{% load i18n %}
{% load static %}
<button class="pf-c-button pf-m-tertiary" data-target="modal" data-modal="saml-{{ provider.pk }}">{% trans 'View Deployment Info' %}</button>
<div class="pf-c-backdrop" id="saml-{{ provider.pk }}" hidden>
<div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-lg" role="dialog">
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
<i class="fas fa-times" aria-hidden="true"></i>
<pb-modal-button>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
{% trans 'View Deployment Info' %}
</button>
<div slot="modal">
<div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Outpost Deployment Info' %}</h1>
</div>
@ -24,8 +21,11 @@
<label class="pf-c-form__label" for="help-text-simple-form-name">
<span class="pf-c-form__label-text">PASSBOOK_TOKEN</span>
</label>
{# TODO: Only load key on modal open #}
<input class="pf-c-form-control" data-pb-fetch-key="key" data-pb-fetch-fill="{% url 'passbook_api:token-view-key' identifier=outpost.token_identifier %}" readonly type="text" value="" />
<div>
<pb-token-copy-button identifier="{{ outpost.token_identifier }}">
{% trans 'Click to copy token' %}
</pb-token-copy-button>
</div>
</div>
<h3>{% trans 'If your passbook Instance is using a self-signed certificate, set this value.' %}</h3>
<div class="pf-c-form__group">
@ -37,8 +37,7 @@
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<button data-modal-close class="pf-c-button pf-m-primary" type="button">{% trans 'Close' %}</button>
<a class="pf-c-button pf-m-primary">{% trans 'Close' %}</a>
</footer>
</div>
</div>
</div>
</pb-modal-button>

View File

@ -1,7 +1,6 @@
"""passbook PasswordExpiry Policy forms"""
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext as _
from passbook.policies.expiry.models import PasswordExpiryPolicy
@ -19,6 +18,5 @@ class PasswordExpiryPolicyForm(forms.ModelForm):
"name": forms.TextInput(),
"order": forms.NumberInput(),
"days": forms.NumberInput(),
"policies": FilteredSelectMultiple(_("policies"), False),
}
labels = {"deny_only": _("Only fail the policy, don't set user's password.")}

View File

@ -1,8 +1,6 @@
"""passbook HaveIBeenPwned Policy forms"""
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext as _
from passbook.policies.forms import GENERAL_FIELDS
from passbook.policies.hibp.models import HaveIBeenPwendPolicy
@ -18,5 +16,4 @@ class HaveIBeenPwnedPolicyForm(forms.ModelForm):
widgets = {
"name": forms.TextInput(),
"password_field": forms.TextInput(),
"policies": FilteredSelectMultiple(_("policies"), False),
}

View File

@ -22,7 +22,7 @@ You've logged out of {{ application }}.
{% endblocktrans %}
</p>
<a id="pb-back-home" href="{% url 'passbook_core:overview' %}" class="pf-c-button pf-m-primary">{% trans 'Go back to overview' %}</a>
<a id="pb-back-home" href="{% url 'passbook_core:shell' %}" class="pf-c-button pf-m-primary">{% trans 'Go back to overview' %}</a>
<a id="logout" href="{% url 'passbook_flows:default-invalidation' %}" class="pf-c-button pf-m-secondary">{% trans 'Log out of passbook' %}</a>

View File

@ -1,13 +1,10 @@
{% load i18n %}
<button class="pf-c-button pf-m-tertiary" data-target="modal" data-modal="oauth2-{{ provider.pk }}">{% trans 'View Setup URLs' %}</button>
<div class="pf-c-backdrop" id="oauth2-{{ provider.pk }}" hidden>
<div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-lg" role="dialog">
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
<i class="fas fa-times" aria-hidden="true"></i>
<pb-modal-button>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
{% trans 'View Setup URLs' %}
</button>
<div slot="modal">
<div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Setup URLs' %}</h1>
</div>
@ -47,8 +44,7 @@
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<button data-modal-close class="pf-c-button pf-m-primary" type="button">{% trans 'Close' %}</button>
<a class="pf-c-button pf-m-primary">{% trans 'Close' %}</a>
</footer>
</div>
</div>
</div>
</pb-modal-button>

View File

@ -1,7 +1,6 @@
"""passbook SAML IDP Forms"""
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.html import mark_safe
from django.utils.translation import gettext as _
@ -51,7 +50,6 @@ class SAMLProviderForm(forms.ModelForm):
"assertion_valid_not_before": forms.TextInput(),
"assertion_valid_not_on_or_after": forms.TextInput(),
"session_valid_not_on_or_after": forms.TextInput(),
"property_mappings": FilteredSelectMultiple(_("Property Mappings"), False),
}

View File

@ -105,4 +105,4 @@ class MetadataProcessor:
for binding in self.get_bindings():
idp_sso_descriptor.append(binding)
return tostring(entity_descriptor).decode()
return tostring(entity_descriptor, pretty_print=True).decode()

View File

@ -1,24 +1,22 @@
{% load i18n %}
{% load static %}
<button class="pf-c-button pf-m-tertiary" data-target="modal" data-modal="saml-{{ provider.pk }}">{% trans 'View Metadata' %}</button>
<div class="pf-c-backdrop" id="saml-{{ provider.pk }}" hidden>
<div class="pf-l-bullseye">
<div class="pf-c-modal-box pf-m-lg" role="dialog">
<button data-modal-close class="pf-c-button pf-m-plain" type="button" aria-label="Close dialog">
<i class="fas fa-times" aria-hidden="true"></i>
<pb-modal-button>
<button slot="trigger" class="pf-c-button pf-m-tertiary">
{% trans 'View Metadata' %}
</button>
<div slot="modal">
<div class="pf-c-modal-box__header">
<h1 class="pf-c-title pf-m-2xl" id="modal-title">{% trans 'Metadata' %}</h1>
</div>
<div class="pf-c-modal-box__body" id="modal-description">
<form method="post">
<textarea class="codemirror" readonly data-cm-mode="xml">{{ metadata }}</textarea>
<pb-codemirror mode="xml"><textarea class="pf-c-form-control"
readonly>{{ metadata }}</textarea></pb-codemirror>
</form>
</div>
<footer class="pf-c-modal-box__footer pf-m-align-left">
<button data-modal-close class="pf-c-button pf-m-primary" type="button">{% trans 'Close' %}</button>
<a class="pf-c-button pf-m-primary">{% trans 'Close' %}</a>
</footer>
</div>
</div>
</div>
</pb-modal-button>

View File

@ -21,4 +21,4 @@ class UseTokenView(View):
login(request, token.user, backend="django.contrib.auth.backends.ModelBackend")
token.delete()
messages.warning(request, _("Used recovery-link to authenticate."))
return redirect("passbook_core:overview")
return redirect("passbook_core:shell")

View File

@ -412,6 +412,7 @@ _LOGGING_HANDLER_MAP = {
"daphne": "WARNING",
"dbbackup": "ERROR",
"kubernetes": "INFO",
"asyncio": "WARNING",
}
for handler_name, level in _LOGGING_HANDLER_MAP.items():
# pyright: reportGeneralTypeIssues=false

View File

@ -1,7 +1,6 @@
"""passbook LDAP Forms"""
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext_lazy as _
from passbook.admin.fields import CodeMirrorWidget
@ -54,7 +53,6 @@ class LDAPSourceForm(forms.ModelForm):
"group_object_filter": forms.TextInput(),
"user_group_membership_field": forms.TextInput(),
"object_uniqueness_field": forms.TextInput(),
"property_mappings": FilteredSelectMultiple(_("Property Mappings"), False),
}

View File

@ -21,7 +21,12 @@ def ldap_sync_all():
@CELERY_APP.task(bind=True, base=MonitoredTask)
def ldap_sync(self: MonitoredTask, source_pk: int):
"""Synchronization of an LDAP Source"""
try:
source: LDAPSource = LDAPSource.objects.get(pk=source_pk)
except LDAPSource.DoesNotExist:
# Because the source couldn't be found, we don't have a UID
# to set the state with
return
self.set_uid(slugify(source.name))
try:
syncer = LDAPSynchronizer(source)

View File

@ -7,7 +7,7 @@ from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from passbook.core.models import Source, UserSourceConnection
from passbook.core.types import UILoginButton, UIUserSettings
from passbook.core.types import UILoginButton
class OAuthSource(Source):
@ -66,12 +66,9 @@ class OAuthSource(Source):
return f"Callback URL: <pre>{url}</pre>"
@property
def ui_user_settings(self) -> Optional[UIUserSettings]:
def ui_user_settings(self) -> Optional[str]:
view_name = "passbook_sources_oauth:oauth-client-user"
return UIUserSettings(
name=self.name,
url=reverse(view_name, kwargs={"source_slug": self.slug}),
)
return reverse(view_name, kwargs={"source_slug": self.slug})
def __str__(self) -> str:
return f"OAuth Source {self.name}"

View File

@ -1,9 +1,5 @@
{% extends "user/base.html" %}
{% load passbook_utils %}
{% load i18n %}
{% block page %}
<div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md">
{% blocktrans with source_name=source.name %}
@ -26,4 +22,3 @@
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -51,5 +51,5 @@ class TestCaptchaStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)

View File

@ -51,7 +51,7 @@ class TestConsentStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
self.assertFalse(UserConsent.objects.filter(user=self.user).exists())
@ -82,7 +82,7 @@ class TestConsentStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
self.assertTrue(
UserConsent.objects.filter(
@ -119,7 +119,7 @@ class TestConsentStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
self.assertTrue(
UserConsent.objects.filter(

View File

@ -49,7 +49,7 @@ class TestDummyStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
def test_form(self):

View File

@ -117,7 +117,7 @@ class TestEmailStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
session = self.client.session

View File

@ -59,7 +59,7 @@ class TestIdentificationStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
def test_invalid_with_username(self):

View File

@ -89,7 +89,7 @@ class TestUserLoginStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
self.stage.continue_flow_without_invitation = False
@ -128,5 +128,5 @@ class TestUserLoginStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)

View File

@ -8,7 +8,6 @@ from django.utils.translation import gettext_lazy as _
from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.core.types import UIUserSettings
from passbook.flows.models import ConfigurableStage, Stage
@ -36,13 +35,10 @@ class OTPStaticStage(ConfigurableStage, Stage):
return OTPStaticStageForm
@property
def ui_user_settings(self) -> Optional[UIUserSettings]:
return UIUserSettings(
name="Static OTP",
url=reverse(
def ui_user_settings(self) -> Optional[str]:
return reverse(
"passbook_stages_otp_static:user-settings",
kwargs={"stage_uuid": self.stage_uuid},
),
)
def __str__(self) -> str:

View File

@ -1,9 +1,5 @@
{% extends "user/base.html" %}
{% load passbook_utils %}
{% load i18n %}
{% block page %}
<div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md">
{% trans "Static One-Time Passwords" %}
@ -33,4 +29,3 @@
{% endif %}
</div>
</div>
{% endblock %}

View File

@ -8,7 +8,6 @@ from django.utils.translation import gettext_lazy as _
from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.core.types import UIUserSettings
from passbook.flows.models import ConfigurableStage, Stage
@ -43,13 +42,10 @@ class OTPTimeStage(ConfigurableStage, Stage):
return OTPTimeStageForm
@property
def ui_user_settings(self) -> Optional[UIUserSettings]:
return UIUserSettings(
name="Time-based OTP",
url=reverse(
def ui_user_settings(self) -> Optional[str]:
return reverse(
"passbook_stages_otp_time:user-settings",
kwargs={"stage_uuid": self.stage_uuid},
),
)
def __str__(self) -> str:

View File

@ -1,9 +1,5 @@
{% extends "user/base.html" %}
{% load passbook_utils %}
{% load i18n %}
{% block page %}
<div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md">
{% trans "Time-based One-Time Passwords" %}
@ -30,4 +26,3 @@
</p>
</div>
</div>
{% endblock %}

View File

@ -8,3 +8,4 @@ class PassbookStagePasswordConfig(AppConfig):
name = "passbook.stages.password"
label = "passbook_stages_password"
verbose_name = "passbook Stages.Password"
mountpoint = "-/user/password/"

View File

@ -1,6 +1,5 @@
"""passbook administration forms"""
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.utils.translation import gettext_lazy as _
from passbook.flows.models import Flow, FlowDesignation
@ -54,7 +53,5 @@ class PasswordStageForm(forms.ModelForm):
fields = ["name", "backends", "configure_flow", "failed_attempts_before_cancel"]
widgets = {
"name": forms.TextInput(),
"backends": FilteredSelectMultiple(
_("backends"), False, choices=get_authentication_backends()
),
"backends": forms.SelectMultiple(get_authentication_backends()),
}

View File

@ -5,14 +5,11 @@ from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.forms import ModelForm
from django.shortcuts import reverse
from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _
from django.views import View
from rest_framework.serializers import BaseSerializer
from passbook.core.types import UIUserSettings
from passbook.flows.models import ConfigurableStage, Stage
from passbook.flows.views import NEXT_ARG_NAME
class PasswordStage(ConfigurableStage, Stage):
@ -51,12 +48,12 @@ class PasswordStage(ConfigurableStage, Stage):
return PasswordStageForm
@property
def ui_user_settings(self) -> Optional[UIUserSettings]:
def ui_user_settings(self) -> Optional[str]:
if not self.configure_flow:
return None
base_url = reverse("passbook_flows:configure", kwargs={"stage_uuid": self.pk})
args = urlencode({NEXT_ARG_NAME: reverse("passbook_core:user-settings")})
return UIUserSettings(name=_("Change password"), url=f"{base_url}?{args}")
return reverse(
"passbook_stages_password:user-settings", kwargs={"stage_uuid": self.pk}
)
def __str__(self):
return f"Password Stage {self.name}"

View File

@ -52,7 +52,7 @@ class PasswordStageView(FormView, StageView):
"""Authentication stage which authenticates against django's AuthBackend"""
form_class = PasswordForm
template_name = "stages/password/backend.html"
template_name = "stages/password/flow-form.html"
def get_form(self, form_class=None) -> PasswordForm:
form = super().get_form(form_class=form_class)

View File

@ -0,0 +1,17 @@
{% extends "base/page.html" %}
{% load i18n %}
{% load passbook_utils %}
{% block body %}
<div class="pf-c-card">
<div class="pf-c-card__header pf-c-title pf-m-md">
{% trans 'Reset your password' %}
</div>
<div class="pf-c-card__body">
<a class="pf-c-button pf-m-primary" href="{{ url }}">
{% trans 'Change password' %}
</a>
</div>
</div>
{% endblock %}

View File

@ -110,7 +110,7 @@ class TestPasswordStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
def test_invalid_password(self):

View File

@ -0,0 +1,12 @@
"""Password stage urls"""
from django.urls import path
from passbook.stages.password.views import UserSettingsCardView
urlpatterns = [
path(
"<uuid:stage_uuid>/change-card/",
UserSettingsCardView.as_view(),
name="user-settings",
),
]

View File

@ -0,0 +1,25 @@
"""password stage user settings card"""
from typing import Any
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import reverse
from django.utils.http import urlencode
from django.views.generic import TemplateView
from passbook.flows.views import NEXT_ARG_NAME
class UserSettingsCardView(LoginRequiredMixin, TemplateView):
"""Card shown on user settings page to allow user to change their password"""
template_name = "stages/password/user-settings-card.html"
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
base_url = reverse(
"passbook_flows:configure", kwargs={"stage_uuid": self.kwargs["stage_uuid"]}
)
args = urlencode({NEXT_ARG_NAME: reverse("passbook_core:user-settings")})
kwargs = super().get_context_data(**kwargs)
kwargs["url"] = f"{base_url}?{args}"
return kwargs

View File

@ -4,7 +4,6 @@ from types import MethodType
from typing import Any, Callable, Iterator, List
from django import forms
from django.contrib.admin.widgets import FilteredSelectMultiple
from django.db.models.query import QuerySet
from django.http import HttpRequest
from django.utils.translation import gettext_lazy as _
@ -27,7 +26,6 @@ class PromptStageForm(forms.ModelForm):
fields = ["name", "fields", "validation_policies"]
widgets = {
"name": forms.TextInput(),
"fields": FilteredSelectMultiple(_("prompts"), False),
}

View File

@ -165,7 +165,7 @@ class TestPromptStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
# Check that valid data has been saved

View File

@ -89,7 +89,7 @@ class TestUserDeleteStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
self.assertFalse(User.objects.filter(username=self.username).exists())

View File

@ -55,7 +55,7 @@ class TestUserLoginStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
@patch(

View File

@ -51,7 +51,7 @@ class TestUserLogoutStage(TestCase):
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(
force_str(response.content),
{"type": "redirect", "to": reverse("passbook_core:overview")},
{"type": "redirect", "to": reverse("passbook_core:shell")},
)
def test_form(self):

Some files were not shown because too many files have changed in this diff Show More