mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-10-28 05:05:41 +00:00
[Web][Dovecot][SOGo][DockerApi] Add mailbox groups, shared mailboxes and identity provider sync
This commit is contained in:
82 files changed
+6488
-1027
No files matched your search
@@ -0,0 +1,49 @@
|
||||
{# One share row of the group edit page: kind = imap|sogo, grant = {} for the add template #}
|
||||
{% set level_key = kind == 'imap' ? 'rights' : 'access' %}
|
||||
{# new IMAP shares are subscribed by default, like automapping in Exchange #}
|
||||
{% set subscribed = grant.owner is defined ? grant.subscribe|default(0) == 1 : kind == 'imap' %}
|
||||
<tr class="group-grant">
|
||||
<td class="{{ kind == 'imap' ? 'w-50' : 'w-25' }}">
|
||||
<select data-grant="owner" data-live-search="true" data-width="100%" class="form-control" title="{{ lang.groups.grant_owner }}"{% if ro %} disabled{% endif %}>
|
||||
{% for mailbox in mailboxes %}
|
||||
<option{% if mailbox == grant.owner %} selected{% endif %}>{{ mailbox }}</option>
|
||||
{% endfor %}
|
||||
{% if grant.owner and grant.owner not in mailboxes %}
|
||||
<option selected>{{ grant.owner }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</td>
|
||||
{% if kind == 'sogo' %}
|
||||
<td>
|
||||
<select data-grant="type" data-width="100%" class="form-control"{% if ro %} disabled{% endif %}>
|
||||
<option value="Calendar"{% if grant.type == 'Calendar' %} selected{% endif %}>{{ lang.groups.type_calendar }}</option>
|
||||
<option value="Contacts"{% if grant.type == 'Contacts' %} selected{% endif %}>{{ lang.groups.type_contacts }}</option>
|
||||
</select>
|
||||
</td>
|
||||
<td class="w-25">
|
||||
<input type="text" data-grant="folder" class="form-control" value="{{ grant.folder ?? 'personal' }}" maxlength="100" title="{{ lang.groups.grant_folder }}"{% if ro %} disabled{% endif %}>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="{{ kind == 'imap' ? 'w-25' : '' }}">
|
||||
<select data-grant="{{ level_key }}" data-width="100%" class="form-control"{% if ro %} disabled{% endif %}>
|
||||
{% for level in (kind == 'imap' ? ['read', 'write', 'full'] : ['read', 'write']) %}
|
||||
<option value="{{ level }}"{% if level == attribute(grant, level_key)|default('read') %} selected{% endif %}>{{ attribute(lang.groups, 'rights_' ~ level) }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</td>
|
||||
<td class="text-nowrap">
|
||||
<div class="form-check mb-0" title="{{ attribute(lang.groups, 'grant_subscribe_' ~ kind ~ '_hint') }}">
|
||||
<label><input type="checkbox" class="form-check-input" data-grant="subscribe" value="1"{% if subscribed %} checked{% endif %}{% if ro %} disabled{% endif %}> {{ lang.groups.grant_subscribe }}</label>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
{% if grant.state %}
|
||||
<span class="badge {{ grant.state == 'applied' ? 'bg-success' : (grant.state == 'failed' ? 'bg-danger' : 'bg-secondary') }}"{% if grant.last_error %} title="{{ grant.last_error }}"{% endif %}>{{ attribute(lang.groups, 'grant_state_' ~ grant.state) }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
{% if not ro %}
|
||||
<button class="btn btn-sm btn-secondary group-grant-del" title="{{ lang.mailbox.remove }}"><i class="bi bi-x-lg"></i></button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@@ -0,0 +1,24 @@
|
||||
{# Shares of a group (edit page, add modal): kind = imap|sogo, grants, id = table id, ro; rows go to the hidden JSON field #}
|
||||
{% set field = kind ~ '_grants' %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2{{ label_class|default('') }}">{{ attribute(lang.groups, field) }}</label>
|
||||
<div class="col-sm-10">
|
||||
{% if not ro %}
|
||||
<input type="hidden" name="{{ field }}" value="{{ grants|json_encode }}">
|
||||
{% endif %}
|
||||
<table class="table table-sm align-middle group-grants mb-1" id="{{ id }}" data-field="{{ field }}">
|
||||
<tbody>
|
||||
{% for grant in grants %}
|
||||
{% include 'edit/mailbox-group-grant.twig' with {'kind': kind, 'grant': grant} %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% if not ro %}
|
||||
<template id="{{ id }}_template">
|
||||
{% include 'edit/mailbox-group-grant.twig' with {'kind': kind, 'grant': {}} %}
|
||||
</template>
|
||||
<button class="btn btn-sm btn-secondary group-grant-add" data-table="{{ id }}"><i class="bi bi-plus-lg"></i> {{ lang.groups.grant_add }}</button>
|
||||
{% endif %}
|
||||
<small class="text-muted d-block">{{ attribute(lang.groups, field ~ '_hint') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,142 @@
|
||||
{% extends 'edit.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
{% if result %}
|
||||
{% set ro = not result.can_edit %}
|
||||
{% set editable_members = [] %}
|
||||
{% set fixed_members = [] %}
|
||||
{% for member in result.members %}
|
||||
{% if member.source == 'idp' or member.username not in mailboxes %}
|
||||
{% set fixed_members = fixed_members|merge([member]) %}
|
||||
{% else %}
|
||||
{% set editable_members = editable_members|merge([member.username]) %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<h4>{{ lang.groups.group }}: {{ result.name }}</h4>
|
||||
{% if result.idp_ref %}
|
||||
<div class="alert alert-secondary" role="alert"><span class="badge bg-info">{{ lang.groups.idp }}</span> {{ lang.groups.idp_ref }}: <code>{{ result.idp_ref }}</code></div>
|
||||
{% endif %}
|
||||
<form data-id="edit_mailbox_group" class="form-horizontal" role="form" method="post" autocapitalize="none" autocorrect="off">
|
||||
<input type="hidden" value="0" name="active">
|
||||
<input type="hidden" value="0" name="sender_allowed">
|
||||
<input type="hidden" value="0" name="sogo_visible">
|
||||
<input type="hidden" value="0" name="internal">
|
||||
<input type="hidden" value="0" name="reapply_template">
|
||||
{% if not ro %}
|
||||
{# an empty selection still sends the field, so all editable members can be removed #}
|
||||
<input type="hidden" value="" name="members">
|
||||
{% endif %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="group_domain">{{ lang.groups.domain }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="group_domain" value="{{ result.domain }}" disabled>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="group_name">{{ lang.groups.name }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="name" id="group_name" value="{{ result.name }}" maxlength="100" required{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="group_description">{{ lang.groups.description }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="description" id="group_description" value="{{ result.description }}" maxlength="255"{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="group_address">{{ lang.groups.address }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="address" id="group_address" value="{{ result.address }}" placeholder="name@{{ result.domain }}"{% if ro %} disabled{% endif %}>
|
||||
<small class="text-muted">{{ lang.groups.address_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="group_members">{{ lang.groups.members }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select data-live-search="true" data-width="100%" name="members" id="group_members" class="form-control" multiple{% if ro %} disabled{% endif %}>
|
||||
{% for mailbox in mailboxes %}
|
||||
<option{% if mailbox in editable_members %} selected{% endif %}>{{ mailbox }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if fixed_members %}
|
||||
<div class="mt-2">
|
||||
{% for member in fixed_members %}
|
||||
<span class="badge bg-secondary"{% if member.source == 'idp' %} title="{{ lang.groups.idp_member }}"{% endif %}>{% if member.source == 'idp' %}<i class="bi bi-cloud"></i> {% endif %}{{ member.username }}</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="group_send_as">{{ lang.groups.send_as }}</label>
|
||||
<div class="col-sm-10">
|
||||
<textarea class="form-control" rows="3" name="send_as" id="group_send_as"{% if ro %} disabled{% endif %}>{{ result.send_as|join("\n") }}</textarea>
|
||||
<small class="text-muted">{{ lang.groups.send_as_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'edit/mailbox-group-grants.twig' with {'kind': 'imap', 'grants': result.imap_grants, 'id': 'group_imap_grants'} %}
|
||||
{% if not skip_sogo %}
|
||||
{% include 'edit/mailbox-group-grants.twig' with {'kind': 'sogo', 'grants': result.sogo_grants, 'id': 'group_sogo_grants'} %}
|
||||
{% endif %}
|
||||
<div class="row mb-2">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="sender_allowed"{% if result.sender_allowed == 1 %} checked{% endif %}{% if ro %} disabled{% endif %}> {{ lang.groups.sender_allowed }}</label>
|
||||
</div>
|
||||
{% if not skip_sogo %}
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="sogo_visible"{% if result.sogo_visible == 1 %} checked{% endif %}{% if ro %} disabled{% endif %}> {{ lang.groups.sogo_visible }}</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="internal"{% if result.internal == 1 %} checked{% endif %}{% if ro %} disabled{% endif %}> {{ lang.groups.internal }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="group_template">{{ lang.groups.template }}</label>
|
||||
{% if mailcow_cc_role == 'admin' %}
|
||||
<div class="col-sm-6">
|
||||
<select name="template_id" id="group_template" class="form-control">
|
||||
<option value="">{{ lang.groups.template_none }}</option>
|
||||
{% for template in mbox_templates %}
|
||||
<option value="{{ template.id }}"{% if template.id == result.template_id %} selected{% endif %}>{{ template.template }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<label class="control-label col-sm-2" for="group_priority">{{ lang.groups.priority }}</label>
|
||||
<div class="col-sm-2">
|
||||
<input type="number" class="form-control" name="priority" id="group_priority" value="{{ result.priority }}">
|
||||
</div>
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="reapply_template"> {{ lang.groups.reapply_template }}</label>
|
||||
</div>
|
||||
<small class="text-muted">{{ lang.groups.template_hint }}</small>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" id="group_template" value="{% if result.template %}{{ result.template }} ({{ lang.groups.priority }} {{ result.priority }}){% else %}{{ lang.groups.template_none }}{% endif %}" disabled>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="active"{% if result.active == 1 %} checked{% endif %}{% if ro %} disabled{% endif %}> {{ lang.groups.active }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not ro %}
|
||||
<div class="row">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<button class="btn btn-xs-lg d-block d-sm-inline btn-success" data-action="edit_selected" data-id="edit_mailbox_group" data-item="{{ result.id }}" data-api-url='edit/mailbox-group' data-api-attr='{}' href="#"><i class="bi bi-check-lg"></i> {{ lang.admin.save }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% else %}
|
||||
{{ parent() }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -24,6 +24,7 @@
|
||||
<form class="form-horizontal" data-id="editmailbox" role="form" method="post">
|
||||
<input type="hidden" value="default" name="sender_acl">
|
||||
<input type="hidden" value="0" name="force_pw_update">
|
||||
<input type="hidden" value="0" name="shared">
|
||||
<input type="hidden" value="0" name="force_tfa">
|
||||
<input type="hidden" value="0" name="sogo_access">
|
||||
<input type="hidden" value="0" name="protocol_access">
|
||||
@@ -46,6 +47,20 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% if result.authsource != 'mailcow' and identity_link %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2">{{ lang.edit.iam_link }}</label>
|
||||
<div class="col-sm-10">
|
||||
{% if identity_link.external_id %}<code>{{ identity_link.external_id }}</code>{% else %}<span class="text-muted">{{ lang.edit.iam_not_linked }}</span>{% endif %}
|
||||
{% if identity_link.idp_disabled == 1 %}<span class="badge bg-warning text-dark">{{ lang.edit.iam_idp_disabled }}</span>{% endif %}
|
||||
{% if identity_link.missing_since %}<span class="badge bg-danger">{{ lang.edit.iam_missing_since }} {{ identity_link.missing_since }}</span>{% endif %}
|
||||
{% if mailcow_cc_role == 'admin' and identity_link.external_id %}
|
||||
<a href="#" class="btn btn-sm btn-secondary ms-2" data-action="delete_selected" data-id="identity_link" data-item="{{ mailbox }}" data-api-url="delete/identity-link"><i class="bi bi-link-45deg"></i> {{ lang.edit.iam_unlink }}</a>
|
||||
{% endif %}
|
||||
<small class="text-muted d-block">{{ lang.edit.iam_link_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="name">{{ lang.edit.full_name }}</label>
|
||||
<div class="col-sm-10">
|
||||
@@ -121,6 +136,22 @@
|
||||
<small class="text-muted d-block">{{ lang.edit.sender_acl_info|raw }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% if mailbox_groups.groups %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2">{{ lang.groups.memberships }}</label>
|
||||
<div class="col-sm-10">
|
||||
{% for group in mailbox_groups.groups %}
|
||||
<a href="/edit/mailbox-group/{{ group.id }}" class="badge bg-secondary text-decoration-none">{% if group.source == 'idp' %}<i class="bi bi-cloud" title="{{ lang.groups.idp_member }}"></i> {% endif %}{{ group.name }}{% if group.address %} <{{ group.address }}>{% endif %}</a>
|
||||
{% endfor %}
|
||||
{% if mailbox_groups.template %}
|
||||
<small class="text-muted d-block">{{ lang.groups.effective_template }}: {{ mailbox_groups.template }}</small>
|
||||
{% endif %}
|
||||
{% if mailbox_groups.send_as %}
|
||||
<small class="text-muted d-block">{{ lang.groups.send_as_groups }}: {{ mailbox_groups.send_as|join(', ') }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if acl.mailbox_relayhost == '1' %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="relayhost">{{ lang.edit.relayhost }}</label>
|
||||
@@ -310,6 +341,14 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="shared"{% if result.attributes.shared == '1' %} checked{% endif %}> {{ lang.mailbox.shared_mailbox }}</label>
|
||||
<small class="text-muted">{{ lang.mailbox.shared_mailbox_info }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="form-check">
|
||||
|
||||
Reference in new issue
Block a user