mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-10-28 12:13:24 +00:00
[Web][Dovecot] Rework imapsync: sync sources, OAuth2 (XOAUTH2), worker pool, Python runner
This commit is contained in:
39 files changed
+3582
-1358
No files matched your search
@@ -0,0 +1,233 @@
|
||||
{% extends 'edit.twig' %}
|
||||
|
||||
{% block inner_content %}
|
||||
{% if result %}
|
||||
{# XOAUTH2 sources are managed by admins and domain admins only #}
|
||||
{% set ro = not result.can_edit or (mailcow_cc_role == 'user' and result.auth_type == 'XOAUTH2') %}
|
||||
{% set show_oauth = mailcow_cc_role != 'user' or result.auth_type == 'XOAUTH2' %}
|
||||
{% set is_cc = result.auth_type == 'XOAUTH2' and result.oauth_flow != 'authorization_code' %}
|
||||
<h4 class="mb-4">{{ lang.syncjobs.source }}: {{ result.name }}</h4>
|
||||
{% if ro %}
|
||||
<div class="alert alert-info" role="alert"><i class="bi bi-eye"></i> {{ lang.syncjobs.source_readonly_hint }}</div>
|
||||
{% endif %}
|
||||
<form class="form-horizontal" data-id="edit_imapsync_source" role="form" method="post">
|
||||
<input type="hidden" value="0" name="active">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="name">{{ lang.syncjobs.source_name }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="name" id="name" maxlength="100" value="{{ result.name }}" required{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="description">{{ lang.syncjobs.source_description }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="description" id="description" maxlength="255" value="{{ result.description }}"{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
{% if result.created_by %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="created_by_display">{{ lang.syncjobs.source_created_by }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" readonly class="form-control-plaintext" id="created_by_display" value="{{ result.created_by }}">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if (mailcow_cc_role == 'admin' or mailcow_cc_role == 'domainadmin') and not ro %}
|
||||
<div class="imapsync-source-scope-block"{% if is_cc %} style="display:none;"{% endif %}>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="scope">{{ lang.syncjobs.source_scope }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control imapsync-source-scope" name="scope" id="scope" data-width="100%">
|
||||
{% if mailcow_cc_role == 'admin' %}
|
||||
<option value="all"{% if result.scope == 'all' %} selected{% endif %}>{{ lang.syncjobs.source_scope_all }}</option>
|
||||
{% endif %}
|
||||
<option value="domain"{% if result.scope == 'domain' %} selected{% endif %}>{{ lang.syncjobs.source_scope_domain }}</option>
|
||||
<option value="user"{% if result.scope == 'user' %} selected{% endif %}>{{ lang.syncjobs.source_scope_user }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 imapsync-source-domains-row" style="display:{% if result.scope == 'domain' %}flex{% else %}none{% endif %};">
|
||||
<label class="control-label col-sm-2" for="domains">{{ lang.syncjobs.source_domains }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control imapsync-source-domains" name="domains" id="domains" data-live-search="true" data-width="100%" title="{{ lang.syncjobs.source_domains }}" multiple>
|
||||
{% for d in result.domains %}<option value="{{ d }}" selected>{{ d }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 imapsync-source-users-row" style="display:{% if result.scope == 'user' %}flex{% else %}none{% endif %};">
|
||||
<label class="control-label col-sm-2" for="users">{{ lang.syncjobs.source_users }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control imapsync-source-users" name="users" id="users" data-live-search="true" data-width="100%" title="{{ lang.syncjobs.source_users }}" multiple>
|
||||
{% for u in result.users %}<option value="{{ u }}" selected>{{ u }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 imapsync-source-scope-cc-hint"{% if not is_cc %} style="display:none;"{% endif %}>
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<small class="text-muted"><i class="bi bi-lock"></i> {{ lang.syncjobs.source_scope_cc_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="scope_display">{{ lang.syncjobs.source_scope }}</label>
|
||||
<div class="col-sm-10">
|
||||
{% set scope_targets = result.scope == 'domain' ? result.domains : (result.scope == 'user' ? result.users : []) %}
|
||||
<input type="text" readonly class="form-control-plaintext" id="scope_display" value="{{ lang.syncjobs['source_scope_' ~ result.scope] }}{% if scope_targets is not empty %}: {{ scope_targets|join(', ') }}{% endif %}">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="host">{{ lang.add.hostname }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="host" id="host" maxlength="253" value="{{ result.host1 }}" required{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="port">{{ lang.add.port }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="port" id="port" min="1" max="65535" value="{{ result.port1 }}" required{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="enc">{{ lang.add.enc_method }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select name="enc" id="enc" data-width="100%"{% if ro %} disabled{% endif %}>
|
||||
<option value="SSL"{% if result.enc1 == 'SSL' %} selected{% endif %}>SSL</option>
|
||||
<option value="TLS"{% if result.enc1 == 'TLS' %} selected{% endif %}>STARTTLS</option>
|
||||
<option value="PLAIN"{% if result.enc1 == 'PLAIN' %} selected{% endif %}>PLAIN</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2" for="auth_type">{{ lang.syncjobs.source_auth_type }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control imapsync-source-auth-type" name="auth_type" id="auth_type" data-width="100%" required{% if ro %} disabled{% endif %}>
|
||||
<option value="PLAIN"{% if result.auth_type == 'PLAIN' %} selected{% endif %}>PLAIN</option>
|
||||
<option value="LOGIN"{% if result.auth_type == 'LOGIN' %} selected{% endif %}>LOGIN</option>
|
||||
<option value="CRAM-MD5"{% if result.auth_type == 'CRAM-MD5' %} selected{% endif %}>CRAM-MD5</option>
|
||||
{% if show_oauth %}
|
||||
<option value="XOAUTH2"{% if result.auth_type == 'XOAUTH2' %} selected{% endif %}>XOAUTH2 (OAuth2)</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% if show_oauth %}
|
||||
<fieldset class="imapsync-source-oauth-block" style="display:{% if result.auth_type == 'XOAUTH2' %}block{% else %}none{% endif %};">
|
||||
<legend class="fs-5">{{ lang.syncjobs.source_oauth_block }}</legend>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="oauth_flow">{{ lang.syncjobs.source_oauth_flow }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select class="form-control imapsync-source-oauth-flow" name="oauth_flow" id="oauth_flow" data-width="100%"{% if ro %} disabled{% endif %}>
|
||||
<option value="client_credentials"{% if result.oauth_flow != 'authorization_code' %} selected{% endif %}>{{ lang.syncjobs.source_oauth_flow_app }}</option>
|
||||
<option value="authorization_code"{% if result.oauth_flow == 'authorization_code' %} selected{% endif %}>{{ lang.syncjobs.source_oauth_flow_user }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 imapsync-source-authcode-block" style="display:{% if result.oauth_flow == 'authorization_code' %}flex{% else %}none{% endif %};">
|
||||
<label class="control-label col-sm-2" for="oauth_authorize_endpoint">{{ lang.syncjobs.source_authorize_endpoint }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="url" class="form-control" name="oauth_authorize_endpoint" id="oauth_authorize_endpoint" value="{{ result.oauth_authorize_endpoint }}"{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="oauth_token_endpoint">{{ lang.syncjobs.source_oauth_token_endpoint }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="url" class="form-control" name="oauth_token_endpoint" id="oauth_token_endpoint" value="{{ result.oauth_token_endpoint }}"{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="imapsync-source-authcode-block" style="display:{% if result.oauth_flow == 'authorization_code' %}block{% else %}none{% endif %};">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="oauth_userinfo_endpoint">{{ lang.syncjobs.source_userinfo_endpoint }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="url" class="form-control" name="oauth_userinfo_endpoint" id="oauth_userinfo_endpoint" value="{{ result.oauth_userinfo_endpoint }}"{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="redirect_uri">{{ lang.syncjobs.source_redirect_uri }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" id="redirect_uri" readonly value="{{ result.oauth_redirect_uri ?: imapsync_redirect_uri }}">
|
||||
<button type="button" class="btn btn-secondary imapsync-copy-btn" data-copy-target="redirect_uri" title="{{ lang.syncjobs.copy }}"><i class="bi bi-clipboard" aria-hidden="true"></i> {{ lang.syncjobs.copy }}</button>
|
||||
</div>
|
||||
{% if not ro %}
|
||||
<small class="text-muted">{{ lang.syncjobs.source_redirect_uri_hint }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="oauth_client_id">{{ lang.syncjobs.source_oauth_client_id }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="oauth_client_id" id="oauth_client_id" value="{{ result.oauth_client_id }}"{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
{% if not ro %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="oauth_client_secret">{{ lang.syncjobs.source_oauth_client_secret }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" name="oauth_client_secret" id="oauth_client_secret" autocomplete="new-password"{% if result.has_client_secret %} placeholder="••••••••"{% endif %}>
|
||||
<small class="text-muted">{{ lang.syncjobs.source_secret_unchanged_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="oauth_scope">{{ lang.syncjobs.source_oauth_scope }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="oauth_scope" id="oauth_scope" value="{{ result.oauth_scope }}"{% if ro %} disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
{% if not ro %}
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2" for="oauth_extra_params">{{ lang.syncjobs.source_oauth_extra_params }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="oauth_extra_params" id="oauth_extra_params" value="{{ result.oauth_extra_params }}" placeholder='{"resource":"https://provider.example.com"}'>
|
||||
<small class="text-muted">{{ lang.syncjobs.source_oauth_extra_params_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if is_cc %}
|
||||
{# token state of the stored client credentials token (authorization_code tokens live per sync job) #}
|
||||
<div class="row mb-4 imapsync-source-cc-block">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<p class="mb-1">
|
||||
{% if result.oauth_last_refresh_error %}
|
||||
<span class="badge bg-danger">{{ lang.syncjobs.token_state_failed }}</span>
|
||||
{% elseif result.has_access_token %}
|
||||
<span class="badge bg-success">{{ lang.syncjobs.token_state_issued }}</span>
|
||||
{% if result.oauth_token_expires %}{{ lang.syncjobs.source_token_status }}: <code>{{ result.oauth_token_expires|date('Y-m-d H:i:s') }}</code>{% endif %}
|
||||
{% else %}
|
||||
<span class="badge bg-warning">{{ lang.syncjobs.token_state_none }}</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
{% if result.oauth_last_refresh_error %}
|
||||
<p class="text-danger">{{ lang.syncjobs.source_token_error }}: <code>{{ result.oauth_last_refresh_error }}</code></p>
|
||||
{% endif %}
|
||||
{% if not ro %}
|
||||
{# own data-id: must not harvest/post the form fields #}
|
||||
<button type="button" class="btn btn-sm btn-secondary" data-action="edit_selected" data-id="imapsync_source_refresh" data-item="{{ result.id }}" data-api-url='edit/syncjob_source/refresh_token' data-api-attr='{}'><i class="bi bi-arrow-repeat" aria-hidden="true"></i> {{ lang.syncjobs.source_refresh_now }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
{% endif %}
|
||||
<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.edit.active }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if not ro %}
|
||||
<div class="row mb-2">
|
||||
<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_imapsync_source" data-item="{{ result.id }}" data-api-url='edit/syncjob_source' data-api-attr='{}' href="#">{{ lang.edit.save }}</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% else %}
|
||||
{{ parent() }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
{% block inner_content %}
|
||||
{% if result %}
|
||||
<h4 class="mb-4">{{ lang.edit.syncjob }}</h4>
|
||||
<form class="form-horizontal" data-id="editsyncjob" role="form" method="post">
|
||||
<h4 class="mb-4">{{ lang.syncjobs.edit }}</h4>
|
||||
<form class="form-horizontal" data-id="editsyncjob" data-mailbox="{{ result.user2 }}" role="form" method="post">
|
||||
<input type="hidden" value="0" name="delete2duplicates">
|
||||
<input type="hidden" value="0" name="delete1">
|
||||
<input type="hidden" value="0" name="delete2">
|
||||
@@ -13,139 +13,180 @@
|
||||
<input type="hidden" value="0" name="subscribeall">
|
||||
<input type="hidden" value="0" name="dry">
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="host1">{{ lang.edit.hostname }}</label>
|
||||
<label class="control-label col-sm-2" for="user2_display">{{ lang.syncjobs.user2_label }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="host1" id="host1" value="{{ result.host1 }}">
|
||||
<input type="text" readonly class="form-control-plaintext" id="user2_display" value="{{ result.user2 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2" for="port1">Port</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="port1" id="port1" min="1" max="65535" value="{{ result.port1 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="user1">{{ lang.edit.username }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="user1" id="user1" value="{{ result.user1 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2" for="password1">{{ lang.edit.password }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" name="password1" id="password1" value="{{ result.password1 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="enc1">{{ lang.edit.encryption }}</label>
|
||||
<div class="col-sm-10">
|
||||
<select id="enc1" name="enc1">
|
||||
<option value="SSL"{% if result.enc1 == 'SSL' %} selected{% endif %}>SSL</option>
|
||||
<option value="TLS"{% if result.enc1 == 'TLS' %} selected{% endif %}>STARTTLS</option>
|
||||
<option value="PLAIN"{% if result.enc1 == 'PLAIN' %} selected{% endif %}>PLAIN</option>
|
||||
<label class="control-label col-sm-2" for="source_id">{{ lang.syncjobs.source }}</label>
|
||||
<div class="col-sm-10 imapsync-select-wrap">
|
||||
<select class="imapsync-source-select" name="source_id" id="source_id"
|
||||
data-live-search="true" data-show-subtext="true" data-width="100%"
|
||||
data-current-source-id="{{ result.source_id }}"
|
||||
data-current-source-name="{{ result.source_name }}"
|
||||
data-current-source-host="{{ result.source_host }}"
|
||||
data-current-source-port="{{ result.source_port }}"
|
||||
data-current-source-auth-type="{{ result.source_auth_type }}"
|
||||
data-current-source-oauth-flow="{{ result.source_oauth_flow }}" title="{{ lang.syncjobs.source_select }}" required>
|
||||
{% if result.source_id %}
|
||||
<option value="{{ result.source_id }}" data-auth-type="{{ result.source_auth_type }}" data-oauth-flow="{{ result.source_oauth_flow }}" data-subtext="{{ result.source_host }}:{{ result.source_port }} · {{ result.source_auth_type }}" selected>{{ result.source_name }}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
{% if not result.source_id %}
|
||||
<div class="alert alert-warning mt-2 mb-0" role="alert"><i class="bi bi-exclamation-triangle" aria-hidden="true"></i> {{ lang.syncjobs.source_missing }}</div>
|
||||
{% endif %}
|
||||
<div class="imapsync-no-source d-none mt-2"><small class="text-muted">{{ lang.syncjobs.no_sources }}</small></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 imapsync-user1-row" style="display:{% if result.source_oauth_flow == 'authorization_code' %}none{% else %}flex{% endif %};">
|
||||
<label class="control-label col-sm-2" for="user1">{{ lang.syncjobs.user1_label }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="user1" id="user1" value="{{ result.user1 }}" autocomplete="off">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2 imapsync-oauth-connect-row" style="display:{% if result.source_oauth_flow == 'authorization_code' %}flex{% else %}none{% endif %};">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<button type="button" class="btn btn-sm btn-primary imapsync-oauth-connect-btn"><i class="bi bi-box-arrow-up-right" aria-hidden="true"></i> {{ lang.syncjobs.syncjob_oauth_connect }}</button>
|
||||
{% if result.user_token_exists == 1 and not result.user_token_error %}
|
||||
<span class="imapsync-oauth-connect-status ms-2 text-success" role="status" aria-live="polite">{{ lang.syncjobs.syncjob_oauth_connected }}: {{ result.user1 }}</span>
|
||||
{% elseif result.user_token_error %}
|
||||
<span class="imapsync-oauth-connect-status ms-2 text-danger" role="status" aria-live="polite">{{ lang.syncjobs.token_state_failed }}: {{ result.user1 }}</span>
|
||||
{% else %}
|
||||
<span class="imapsync-oauth-connect-status ms-2 text-warning" role="status" aria-live="polite">{{ lang.syncjobs.token_state_pending }}</span>
|
||||
{% endif %}
|
||||
<div><small class="text-muted">{{ lang.syncjobs.oauth_hint }}</small></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4 password1-row" style="display:{% if result.source_auth_type == 'XOAUTH2' %}none{% else %}flex{% endif %};">
|
||||
<label class="control-label col-sm-2" for="password1">{{ lang.syncjobs.password1_label }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" name="password1" id="password1" value="" placeholder="{{ lang.edit.unchanged_if_empty }}" autocomplete="new-password">
|
||||
<small class="text-muted">{{ lang.edit.unchanged_if_empty }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="mins_interval">{{ lang.edit.mins_interval }}</label>
|
||||
<label class="control-label col-sm-2" for="mins_interval">{{ lang.syncjobs.mins_interval }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="mins_interval" min="1" max="43800" value="{{ result.mins_interval }}" required>
|
||||
<input type="number" class="form-control" name="mins_interval" id="mins_interval" min="1" max="43800" value="{{ result.mins_interval }}" required>
|
||||
<small class="text-muted">1-43800</small>
|
||||
</div>
|
||||
</div>
|
||||
{% if mailcow_cc_role == 'admin' %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="subfolder2">{{ lang.edit.subfolder2|raw }}</label>
|
||||
<label class="control-label col-sm-2" for="prio">{{ lang.syncjobs.prio }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="prio" id="prio" min="0" value="{{ result.prio }}">
|
||||
<small class="text-muted">{{ lang.syncjobs.prio_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="subfolder2">{{ lang.syncjobs.subfolder2|raw }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="subfolder2" id="subfolder2" value="{{ result.subfolder2 }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="maxage">{{ lang.edit.maxage|raw }}</label>
|
||||
<label class="control-label col-sm-2" for="maxage">{{ lang.syncjobs.maxage|raw }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="maxage" id="maxage" min="0" max="32000" value="{{ result.maxage }}">
|
||||
<small class="text-muted">0-32000</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="maxbytespersecond">{{ lang.edit.maxbytespersecond|raw }}</label>
|
||||
<label class="control-label col-sm-2" for="maxbytespersecond">{{ lang.syncjobs.maxbytespersecond|raw }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="maxbytespersecond" id="maxbytespersecond" min="0" max="125000000" value="{{ result.maxbytespersecond }}">
|
||||
<small class="text-muted">0-125000000</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="timeout1">{{ lang.add.timeout1 }}</label>
|
||||
<label class="control-label col-sm-2" for="timeout1">{{ lang.syncjobs.timeout1 }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="timeout1" id="timeout1" min="1" max="32000" value="{{ result.timeout1 }}">
|
||||
<small class="text-muted">1-32000</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="timeout2">{{ lang.add.timeout2 }}</label>
|
||||
<label class="control-label col-sm-2" for="timeout2">{{ lang.syncjobs.timeout2 }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="number" class="form-control" name="timeout2" id="timeout2" min="1" max="32000" value="{{ result.timeout2 }}">
|
||||
<small class="text-muted">1-32000</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<label class="control-label col-sm-2" for="exclude">{{ lang.edit.exclude }}</label>
|
||||
<label class="control-label col-sm-2" for="exclude">{{ lang.syncjobs.exclude }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="exclude" id="exclude" value="{{ result.exclude }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2" for="custom_params">{{ lang.add.custom_params }}</label>
|
||||
<label class="control-label col-sm-2" id="custom_params_label">{{ lang.syncjobs.custom_params }}</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="custom_params" id="custom_params" value="{{ result.custom_params }}" placeholder="--some-param=xy --other-param=yx">
|
||||
<small class="text-muted">{{ lang.add.custom_params_hint }}</small>
|
||||
<div class="imapsync-cp-editor" role="group" aria-labelledby="custom_params_label">
|
||||
<div class="imapsync-cp-rows"></div>
|
||||
<button type="button" class="btn btn-sm btn-secondary imapsync-cp-add"><i class="bi bi-plus-lg" aria-hidden="true"></i> {{ lang.syncjobs.custom_param_add }}</button>
|
||||
</div>
|
||||
<input type="hidden" name="custom_params" class="imapsync-cp-value" value="{{ result.custom_params }}">
|
||||
<small class="text-muted d-block mt-1">{{ lang.syncjobs.custom_params_hint }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% if result.custom_params_legacy %}
|
||||
<div class="row mb-4">
|
||||
<label class="control-label col-sm-2" for="custom_params_legacy_display">{{ lang.syncjobs.custom_params_legacy }}</label>
|
||||
<div class="col-sm-10">
|
||||
<div class="alert alert-warning mb-2"><i class="bi bi-exclamation-triangle"></i> {{ lang.syncjobs.custom_params_legacy_hint }}</div>
|
||||
<textarea class="form-control font-monospace" id="custom_params_legacy_display" rows="2" readonly>{{ result.custom_params_legacy }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
{% 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="delete2duplicates"{% if result.delete2duplicates == '1' %} checked{% endif %}> {{ lang.edit.delete2duplicates }} (--delete2duplicates)</label>
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete2duplicates"{% if result.delete2duplicates == '1' %} checked{% endif %}> {{ lang.syncjobs.delete2duplicates }} (--delete2duplicates)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="delete1"{% if result.delete1 == '1' %} checked{% endif %}> {{ lang.edit.delete1 }} (--delete1)</label>
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete1"{% if result.delete1 == '1' %} checked{% endif %}> {{ lang.syncjobs.delete1 }} (--delete1)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="delete2"{% if result.delete2 == '1' %} checked{% endif %}> {{ lang.edit.delete2 }} (--delete2)</label>
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="delete2"{% if result.delete2 == '1' %} checked{% endif %}> {{ lang.syncjobs.delete2 }} (--delete2)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="automap"{% if result.automap == '1' %} checked{% endif %}> {{ lang.edit.automap }} (--automap)</label>
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="automap"{% if result.automap == '1' %} checked{% endif %}> {{ lang.syncjobs.automap }} (--automap)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="skipcrossduplicates"{% if result.skipcrossduplicates == '1' %} checked{% endif %}> {{ lang.edit.skipcrossduplicates }} (--skipcrossduplicates)</label>
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="skipcrossduplicates"{% if result.skipcrossduplicates == '1' %} checked{% endif %}> {{ lang.syncjobs.skipcrossduplicates }} (--skipcrossduplicates)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="subscribeall"{% if result.subscribeall == '1' %} checked{% endif %}> {{ lang.add.subscribeall }} (--subscribeall)</label>
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="subscribeall"{% if result.subscribeall == '1' %} checked{% endif %}> {{ lang.syncjobs.subscribeall }} (--subscribeall)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<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="dry"{% if result.dry == '1' %} checked{% endif %}> {{ lang.add.dry }} (--dry)</label>
|
||||
<label><input type="checkbox" class="form-check-input" value="1" name="dry"{% if result.dry == '1' %} checked{% endif %}> {{ lang.syncjobs.dry }} (--dry)</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user