You've already forked snikket-web-portal
6
snikket_web/templates/admin_app.html
Normal file
6
snikket_web/templates/admin_app.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{% extends "app.html" %}
|
||||
{% block topbar_classes %}{{ super() }} admin{% endblock %}
|
||||
{% block topbar_left %}
|
||||
{{ super() }}
|
||||
<div class="admin-note">Admin area</div>
|
||||
{% endblock %}
|
||||
27
snikket_web/templates/admin_delete_user.html
Normal file
27
snikket_web/templates/admin_delete_user.html
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% from "library.j2" import box %}
|
||||
{% block content %}
|
||||
<h1>{% trans user_name=target_user.localpart %}Delete user {{ user_name }}{% endtrans %}</h1>
|
||||
<div class="form layout-expanded"><form method="POST">
|
||||
<h2 class="form-title">{% trans %}Delete user{% endtrans %}</h2>
|
||||
{{ form.csrf_token }}
|
||||
<p class="form-descr">{% trans %}Are you sure you want to delete the following user?{% endtrans %}</p>
|
||||
<dl>
|
||||
<dt>{% trans %}Login name{% endtrans %}</dt>
|
||||
<dd>{{ target_user.localpart }}</dd>
|
||||
<dt>{% trans %}Display name{% endtrans %}</dt>
|
||||
<dd>{{ target_user.display_name }}</dd>
|
||||
<dt>{% trans %}Email address{% endtrans %}</dt>
|
||||
<dd>{{ target_user.email }}</dd>
|
||||
<dt>{% trans %}Display name{% endtrans %}</dt>
|
||||
<dd>{{ target_user.phone }}</dd>
|
||||
</dl>
|
||||
{% call box("alert", _("Danger")) %}
|
||||
<p>The user and their data will be deleted irrevocably, permanently and immediately upon pushing thre below button. <strong>There is no way back!</strong></p>
|
||||
{% endcall %}
|
||||
<div class="f-bbox">
|
||||
<a class="button secondary" href="{{ url_for('.users') }}">Back</a>
|
||||
{{ form.action_delete(class="primary danger") }}
|
||||
</div>
|
||||
</form></div>
|
||||
{% endblock %}
|
||||
44
snikket_web/templates/admin_edit_invite.html
Normal file
44
snikket_web/templates/admin_edit_invite.html
Normal file
@@ -0,0 +1,44 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% block head_lead %}
|
||||
{{ super() }}
|
||||
{% include "copy-snippet.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% macro clipboard_button(caller=None) -%}
|
||||
{%- set text = caller() -%}
|
||||
<a title="Copy "{{ text }}" to clipboard" aria-label="Copy "{{ text }}" to clipboard" class="copy-to-clipboard" onclick="copy_to_clipboard(this); return false;" data-cliptext="{{ text }}" href="#">📋</a>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro showuri(uri, caller=None) %}
|
||||
{%- if uri is none -%}
|
||||
<em>—</em>
|
||||
{%- else -%}
|
||||
<a href="{{ uri }}" target="_blank">{{ uri }}</a> {% call clipboard_button() %}{{ uri }}{% endcall %}
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans %}View invitation{% endtrans %}</h1>
|
||||
<form method="POST">
|
||||
{{ form.csrf_token }}
|
||||
<div class="form layout-expanded">
|
||||
<dl>
|
||||
<dt>Created</dt>
|
||||
<dd>{{ invite.created_at | format_date }}</dd>
|
||||
<dt>Valid until</dt>
|
||||
<dd>{{ invite.expires | format_date }}</dd>
|
||||
<dt>Landing page</dt>
|
||||
<dd>{% call showuri(invite.landing_page) %}{% endcall %}</dd>
|
||||
<dt>XMPP URI</dt>
|
||||
<dd>{% call showuri(invite.xmpp_uri) %}{% endcall %}</dd>
|
||||
</dl>
|
||||
<div class="f-bbox">
|
||||
{#- -#}
|
||||
{{ form.action_revoke(class="button secondary danger") }}
|
||||
{#- -#}
|
||||
<a href="{{ url_for(".invitations") }}" class="button primary">{% trans %}Back{% endtrans %}</a>
|
||||
{#- -#}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
32
snikket_web/templates/admin_edit_user.html
Normal file
32
snikket_web/templates/admin_edit_user.html
Normal file
@@ -0,0 +1,32 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% block content %}
|
||||
<h1>{% trans user_name=target_user.localpart %}Edit user {{ user_name }}{% endtrans %}</h1>
|
||||
<div class="form layout-expanded"><form method="POST">
|
||||
<h2 class="form-title">{% trans %}User information{% endtrans %}</h2>
|
||||
{{ form.csrf_token }}
|
||||
<div class="f-ebox">
|
||||
{{ form.username.label }}
|
||||
{{ form.username(readonly="readonly") }}
|
||||
</div>
|
||||
<div class="f-ebox">
|
||||
{{ form.nickname.label }}
|
||||
{{ form.nickname(readonly="readonly") }}
|
||||
</div>
|
||||
<div class="f-ebox">
|
||||
{{ form.email.label }}
|
||||
{{ form.email(readonly="readonly") }}
|
||||
</div>
|
||||
<div class="f-ebox">
|
||||
{{ form.phone.label }}
|
||||
{{ form.phone(readonly="readonly") }}
|
||||
</div>
|
||||
{{ form.action_save(class="primary") }}
|
||||
<input type="submit" class="a11y-only">
|
||||
<h2 class="form-title">{% trans %}Password reset{% endtrans %}</h2>
|
||||
<p>{% trans %}If the user has forgotten their password, use the below button to create a password reset link. The password reset link can be used once to change the password of the account. Transmit the link to the user via a secure channel.{% endtrans %}</p>
|
||||
{{ form.action_create_reset_link(class="secondary accent") }}
|
||||
<h2 class="form-title">{% trans %}Delete user{% endtrans %}</h2>
|
||||
<p>{% trans %}{% endtrans %}</p>
|
||||
{{ form.action_create_reset_link(class="secondary accent") }}
|
||||
</form></div>
|
||||
{% endblock %}
|
||||
19
snikket_web/templates/admin_home.html
Normal file
19
snikket_web/templates/admin_home.html
Normal file
@@ -0,0 +1,19 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Welcome to the administration dashboard!{% endtrans %}</h1>
|
||||
<p>{% trans user_name=user_info.display_name %}At your service, {{ user_name }}.{% endtrans %}</p>
|
||||
<div class="welcome-cards">
|
||||
<a class="card" href="{{ url_for('.users') }}">
|
||||
<h2>{% trans %}Manage users{% endtrans %}</h2>
|
||||
<p>{% trans %}Modify administrative user information or delete users.{% endtrans %}</p>
|
||||
</a>
|
||||
<a class="card" href="{{ url_for('.invitations') }}">
|
||||
<h2>{% trans %}Manage invitations{% endtrans %}</h2>
|
||||
<p>{% trans %}Create, revoke or view invitations.{% endtrans %}</p>
|
||||
</a>
|
||||
<a class="card" href="{{ url_for('user.index') }}">
|
||||
<h2>{% trans %}Back to the main view{% endtrans %}</h2>
|
||||
<p>{% trans %}Go back to your user’s web portal page.{% endtrans %}</p>
|
||||
</a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
42
snikket_web/templates/admin_invites.html
Normal file
42
snikket_web/templates/admin_invites.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Manage invitations{% endtrans %}</h1>
|
||||
<form method="POST">{{ form.csrf_token }}
|
||||
<div class="form layout-expanded">
|
||||
<h2 class="form-title">{% trans %}Create new invitation{% endtrans %}</h2>
|
||||
<p class="form-descr weak">{% trans %}Create a new invitation link to invite more users to your Snikket instance by clicking the button below.{% endtrans %}</p>
|
||||
<div class="f-bbox">
|
||||
{{ form.action_create_invite(class="primary") }}
|
||||
</div>
|
||||
</div>
|
||||
<h2>{% trans %}Pending invitations{% endtrans %}</h2>
|
||||
{% if invites %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Created</th>
|
||||
<th>Expires</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for invite in invites %}
|
||||
<tr>
|
||||
<td>{{ invite.created_at | format_date }}</td>
|
||||
<td>{{ (invite.expires - now) | format_timedelta(add_direction=True) }}</td>
|
||||
<td>
|
||||
{#- -#}
|
||||
<a href="{{ url_for(".edit_invite", id_=invite.id_) }}" class="button primary btn-more" title="{% trans %}Show invite details{% endtrans %}"><span class="a11y-only">{% trans %}Show invite details{% endtrans %}</span></a>
|
||||
{#- -#}
|
||||
<button type="submit" class="secondary danger btn-delete" name="{{ form.action_revoke.name }}" value="{{ invite.id_ }}"><span class="a11y-only">{% trans %}Delete invitation{% endtrans %}</span></button>
|
||||
{#- -#}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p>{% trans %}Currently, there are no pending invitations.{% endtrans %}</p>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% endblock %}
|
||||
36
snikket_web/templates/admin_users.html
Normal file
36
snikket_web/templates/admin_users.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends "admin_app.html" %}
|
||||
{% macro value_or_hint(v, caller=None) %}
|
||||
{%- if v is not none -%}
|
||||
{{- v -}}
|
||||
{%- else -%}
|
||||
—
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
{% block content %}
|
||||
<h1>{% trans %}Manage users{% endtrans %}</h1>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans %}Login name{% endtrans %}</th>
|
||||
<th>{% trans %}Display name{% endtrans %}</th>
|
||||
<th class="collapsible">{% trans %}Email address{% endtrans %}</th>
|
||||
<th class="collapsible">{% trans %}Phone number{% endtrans %}</th>
|
||||
<th>{% trans %}Actions{% endtrans %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.localpart }}</td>
|
||||
<td>{% call value_or_hint(user.display_name) %}{% endcall %}</td>
|
||||
<td class="collapsible">{% call value_or_hint(user.email) %}{% endcall %}</td>
|
||||
<td class="collapsible">{% call value_or_hint(user.phone) %}{% endcall %}</td>
|
||||
<td>
|
||||
{#- -#}<a class="button secondary btn-delete" href="{{ url_for(".delete_user", localpart=user.localpart) }}" title="{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %}"><span class="a11y-only">{% trans user_name=user.localpart %}Delete user {{ user_name }}{% endtrans %}</span></a>
|
||||
{#- -#}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endblock %}
|
||||
@@ -8,9 +8,11 @@
|
||||
{{ super() }}
|
||||
{% endblock %}
|
||||
{% block body %}
|
||||
<div id="topbar">
|
||||
<header><a href="{{ url_for('user.index') }}"><span>{{ config["SNIKKET_DOMAIN"] }}</span></a></header>
|
||||
<div id="topbar" class="{% block topbar_classes %}{% endblock %}">
|
||||
<header><a href="{{ url_for('.index') }}"><span>{{ config["SNIKKET_DOMAIN"] }}</span></a></header>
|
||||
{% block topbar_left %}{% endblock %}
|
||||
<div class="filler"></div>
|
||||
{% block topbar_right %}{% endblock %}
|
||||
<nav class="usermenu">{{ user_info.display_name }}{% call avatar(user_info.address, user_info.avatar_hash ) %}{% endcall %}</nav>
|
||||
</div>
|
||||
<main>{% block content %}{% endblock %}</main>
|
||||
|
||||
71
snikket_web/templates/copy-snippet.html
Normal file
71
snikket_web/templates/copy-snippet.html
Normal file
@@ -0,0 +1,71 @@
|
||||
<script async type="text/javascript">
|
||||
/* https://stackoverflow.com/a/30810322/1248008 */
|
||||
|
||||
function fallbackCopyTextToClipboard(text, context) {
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.value = text;
|
||||
context.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
var result = false;
|
||||
try {
|
||||
var successful = document.execCommand('copy');
|
||||
var msg = successful ? 'successful' : 'unsuccessful';
|
||||
console.log('Fallback: Copying text command was ' + msg);
|
||||
result = true;
|
||||
} catch (err) {
|
||||
console.error('Fallback: Oops, unable to copy', err);
|
||||
}
|
||||
|
||||
context.removeChild(textArea);
|
||||
return result;
|
||||
}
|
||||
function copyTextToClipboard(text, context, callback) {
|
||||
if (!navigator.clipboard) {
|
||||
callback(fallbackCopyTextToClipboard(text, context));
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
console.log('Async: Copying to clipboard was successful!');
|
||||
callback(true);
|
||||
}, function(err) {
|
||||
console.error('Async: Could not copy text: ', err);
|
||||
callback(false);
|
||||
});
|
||||
}
|
||||
/* end of https://stackoverflow.com/a/30810322/1248008 */
|
||||
|
||||
var copy_to_clipboard = function(el) {
|
||||
var text = el.dataset.cliptext;
|
||||
if (!text) {
|
||||
console.error('copy_to_clipboard used on element without text to copy');
|
||||
}
|
||||
copyTextToClipboard(text, el, function(success) {
|
||||
var existing_result_el = document.getElementById("clipboard-result");
|
||||
if (existing_result_el !== null) {
|
||||
existing_result_el.parentNode.removeChild(existing_result_el);
|
||||
}
|
||||
|
||||
var result_el = document.createElement("span");
|
||||
result_el.id = "clipboard-result";
|
||||
if (success) {
|
||||
result_el.classList.add("success");
|
||||
result_el.innerText = "Copied!";
|
||||
} else {
|
||||
result_el.classList.add("error");
|
||||
result_el.innerText = "Clipboard operation failed!";
|
||||
}
|
||||
el.appendChild(result_el);
|
||||
setTimeout(function() {
|
||||
el.removeChild(result_el);
|
||||
el.blur();
|
||||
}, 1500);
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
document.body.classList.remove("no-copy");
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -10,6 +10,12 @@
|
||||
<a class="card" href="{{ url_for('user.change_pw') }}">
|
||||
<h2>{% trans %}Change password{% endtrans %}</h2>
|
||||
</a>
|
||||
{% if user_info.is_admin %}
|
||||
<a class="card" href="{{ url_for('admin.index') }}">
|
||||
<h2>{% trans %}Admin dashboard{% endtrans %}</h2>
|
||||
<p>{% trans %}Manage users and invitations of this Snikket instance.{% endtrans %}</p>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="card" href="{{ url_for('user.logout') }}">
|
||||
<h2>{% trans %}Log out{% endtrans %}</h2>
|
||||
<p>{% trans %}Exit the Snikket Web Portal, without logging out your other devices.{% endtrans %}</p>
|
||||
|
||||
Reference in New Issue
Block a user