2020-07-08 13:00:14 +02:00
|
|
|
<template>
|
|
|
|
<section class="settings">
|
|
|
|
<b-loading :is-full-page="true" v-if="isLoading" active />
|
|
|
|
<header class="columns">
|
|
|
|
<div class="column is-half">
|
|
|
|
<h1 class="title is-4">Settings</h1>
|
|
|
|
</div>
|
|
|
|
<div class="column has-text-right">
|
|
|
|
<b-button :disabled="!hasFormChanged"
|
|
|
|
type="is-primary" icon-left="content-save-outline"
|
|
|
|
@click="onSubmit" class="isSaveEnabled">Save changes</b-button>
|
|
|
|
</div>
|
|
|
|
</header>
|
|
|
|
<hr />
|
|
|
|
|
|
|
|
<section class="wrap-small">
|
|
|
|
<form @submit.prevent="onSubmit">
|
|
|
|
<b-tabs type="is-boxed" :animated="false">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-tab-item label="General" label-position="on-border">
|
2020-07-08 13:00:14 +02:00
|
|
|
<div class="items">
|
2020-08-01 13:23:29 +02:00
|
|
|
<b-field label="Root URL" label-position="on-border"
|
|
|
|
message="Public URL of the installation (no trailing slash).">
|
|
|
|
<b-input v-model="form['app.root_url']" name="app.root_url"
|
|
|
|
placeholder='https://listmonk.yoursite.com' :maxlength="300" />
|
|
|
|
</b-field>
|
|
|
|
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Logo URL" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="(Optional) full URL to the static logo to be displayed on
|
|
|
|
user facing view such as the unsubscription page.">
|
|
|
|
<b-input v-model="form['app.logo_url']" name="app.logo_url"
|
|
|
|
placeholder='https://listmonk.yoursite.com/logo.png' :maxlength="300" />
|
|
|
|
</b-field>
|
|
|
|
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Favicon URL" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="(Optional) full URL to the static favicon to be displayed on
|
|
|
|
user facing view such as the unsubscription page.">
|
|
|
|
<b-input v-model="form['app.favicon_url']" name="app.favicon_url"
|
|
|
|
placeholder='https://listmonk.yoursite.com/favicon.png' :maxlength="300" />
|
|
|
|
</b-field>
|
|
|
|
|
|
|
|
<hr />
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Default 'from' email" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="(Optional) full URL to the static logo to be displayed on
|
|
|
|
user facing view such as the unsubscription page.">
|
|
|
|
<b-input v-model="form['app.from_email']" name="app.from_email"
|
|
|
|
placeholder='Listmonk <noreply@listmonk.yoursite.com>'
|
|
|
|
pattern="(.+?)\s<(.+?)@(.+?)>" :maxlength="300" />
|
|
|
|
</b-field>
|
|
|
|
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Admin notification e-mails" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Comma separated list of e-mail addresses to which admin
|
|
|
|
notifications such as import updates, campaign completion,
|
|
|
|
failure etc. should be sent.">
|
|
|
|
<b-taginput v-model="form['app.notify_emails']" name="app.notify_emails"
|
|
|
|
:before-adding="(v) => v.match(/(.+?)@(.+?)/)"
|
|
|
|
placeholder='you@yoursite.com' />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</b-tab-item><!-- general -->
|
|
|
|
|
|
|
|
<b-tab-item label="Performance">
|
|
|
|
<div class="items">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Concurrency" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Maximum concurrent worker (threads) that will attempt to send messages
|
|
|
|
simultaneously.">
|
|
|
|
<b-numberinput v-model="form['app.concurrency']"
|
|
|
|
name="app.concurrency" type="is-light"
|
|
|
|
placeholder="5" min="1" max="10000" />
|
|
|
|
</b-field>
|
|
|
|
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Message rate" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Maximum number of messages to be sent out per second
|
|
|
|
per worker in a second. If concurrency = 10 and message_rate = 10,
|
|
|
|
then up to 10x10=100 messages may be pushed out every second.
|
|
|
|
This, along with concurrency, should be tweaked to keep the
|
|
|
|
net messages going out per second under the target
|
|
|
|
message servers rate limits if any.">
|
|
|
|
<b-numberinput v-model="form['app.message_rate']"
|
|
|
|
name="app.message_rate" type="is-light"
|
|
|
|
placeholder="5" min="1" max="100000" />
|
|
|
|
</b-field>
|
|
|
|
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Batch size" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="The number of subscribers to pull from the databse in a single iteration.
|
|
|
|
Each iteration pulls subscribers from the database, sends messages to them,
|
|
|
|
and then moves on to the next iteration to pull the next batch.
|
|
|
|
This should ideally be higher than the maximum achievable
|
|
|
|
throughput (concurrency * message_rate).">
|
|
|
|
<b-numberinput v-model="form['app.batch_size']"
|
|
|
|
name="app.batch_size" type="is-light"
|
|
|
|
placeholder="1000" min="1" max="100000" />
|
|
|
|
</b-field>
|
|
|
|
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Maximum error threshold" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="The number of errors (eg: SMTP timeouts while e-mailing) a running
|
|
|
|
campaign should tolerate before it is paused for manual
|
|
|
|
investigation or intervention. Set to 0 to never pause.">
|
|
|
|
<b-numberinput v-model="form['app.max_send_errors']"
|
|
|
|
name="app.max_send_errors" type="is-light"
|
|
|
|
placeholder="1999" min="0" max="100000" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</b-tab-item><!-- performance -->
|
|
|
|
|
|
|
|
<b-tab-item label="Privacy">
|
|
|
|
<div class="items">
|
2020-08-01 14:24:51 +02:00
|
|
|
<b-field label="Include `List-Unsubscribe` header"
|
|
|
|
message="Include unsubscription headers that allow e-mail clients to
|
|
|
|
allow users to unsubscribe in a single click.">
|
|
|
|
<b-switch v-model="form['privacy.unsubscribe_header']"
|
|
|
|
name="privacy.unsubscribe_header" />
|
|
|
|
</b-field>
|
|
|
|
|
2020-08-01 13:15:29 +02:00
|
|
|
<b-field label="Allow blocklisting"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Allow subscribers to unsubscribe from all mailing lists and mark
|
2020-08-01 13:15:29 +02:00
|
|
|
themselves as blocklisted?">
|
|
|
|
<b-switch v-model="form['privacy.allow_blocklist']"
|
|
|
|
name="privacy.allow_blocklist" />
|
2020-07-08 13:00:14 +02:00
|
|
|
</b-field>
|
|
|
|
|
|
|
|
<b-field label="Allow exporting"
|
|
|
|
message="Allow subscribers to export data colected on them?">
|
|
|
|
<b-switch v-model="form['privacy.allow_export']"
|
|
|
|
name="privacy.allow_export" />
|
|
|
|
</b-field>
|
|
|
|
|
|
|
|
<b-field label="Allow wiping"
|
2020-08-01 14:24:51 +02:00
|
|
|
message="Allow subscribers to delete themselves including their
|
|
|
|
subscriptions and all other data from the database.
|
|
|
|
Campaign views and link clicks are also
|
2020-07-08 13:00:14 +02:00
|
|
|
removed while views and click counts remain (with no subscriber
|
|
|
|
associated to them) so that stats and analytics aren't affected.">
|
|
|
|
<b-switch v-model="form['privacy.allow_wipe']"
|
|
|
|
name="privacy.allow_wipe" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</b-tab-item><!-- privacy -->
|
|
|
|
|
|
|
|
<b-tab-item label="Media uploads">
|
|
|
|
<div class="items">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Provider" label-position="on-border">
|
2020-07-08 13:00:14 +02:00
|
|
|
<b-select v-model="form['upload.provider']" name="upload.provider">
|
|
|
|
<option value="filesystem">filesystem</option>
|
|
|
|
<option value="s3">s3</option>
|
|
|
|
</b-select>
|
|
|
|
</b-field>
|
|
|
|
|
|
|
|
<div class="block" v-if="form['upload.provider'] === 'filesystem'">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Upload path" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Path to the directory where media will be uploaded.">
|
|
|
|
<b-input v-model="form['upload.filesystem.upload_path']"
|
|
|
|
name="app.upload_path" placeholder='/home/listmonk/uploads'
|
|
|
|
:maxlength="200" />
|
|
|
|
</b-field>
|
|
|
|
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Upload URI" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Upload URI that's visible to the outside world.
|
|
|
|
The media uploaded to upload_path will be publicly accessible
|
|
|
|
under {root_url}/{}, for instance, https://listmonk.yoursite.com/uploads.">
|
|
|
|
<b-input v-model="form['upload.filesystem.upload_uri']"
|
|
|
|
name="app.upload_uri" placeholder='/uploads' :maxlength="200" />
|
|
|
|
</b-field>
|
|
|
|
</div><!-- filesystem -->
|
|
|
|
|
|
|
|
<div class="block" v-if="form['upload.provider'] === 's3'">
|
2020-07-26 16:13:43 +02:00
|
|
|
<div class="columns">
|
|
|
|
<div class="column is-3">
|
|
|
|
<b-field label="Region" label-position="on-border" expanded>
|
|
|
|
<b-input v-model="form['upload.s3.aws_default_region']"
|
|
|
|
name="upload.s3.aws_default_region"
|
|
|
|
:maxlength="200" placeholder="ap-south-1" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
<div class="column">
|
|
|
|
<b-field grouped>
|
|
|
|
<b-field label="AWS access key" label-position="on-border" expanded>
|
|
|
|
<b-input v-model="form['upload.s3.aws_access_key_id']"
|
|
|
|
name="upload.s3.aws_access_key_id" :maxlength="200" />
|
|
|
|
</b-field>
|
|
|
|
<b-field label="AWS access secret" label-position="on-border" expanded>
|
|
|
|
<b-input v-model="form['upload.s3.aws_secret_access_key']"
|
|
|
|
name="upload.s3.aws_secret_access_key" type="password"
|
|
|
|
:maxlength="200" />
|
|
|
|
</b-field>
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="columns">
|
|
|
|
<div class="column is-3">
|
|
|
|
<b-field label="Bucket type" label-position="on-border">
|
|
|
|
<b-select v-model="form['upload.s3.bucket_type']"
|
|
|
|
name="upload.s3.bucket_type" expanded>
|
|
|
|
<option value="private">private</option>
|
|
|
|
<option value="public">public</option>
|
|
|
|
</b-select>
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
<div class="column">
|
|
|
|
<b-field grouped>
|
|
|
|
<b-field label="Bucket" label-position="on-border" expanded>
|
|
|
|
<b-input v-model="form['upload.s3.bucket']"
|
|
|
|
name="upload.s3.bucket" :maxlength="200" placeholder="" />
|
|
|
|
</b-field>
|
|
|
|
<b-field label="Bucket path" label-position="on-border"
|
|
|
|
message="Path inside the bucket to upload files. Default is /" expanded>
|
|
|
|
<b-input v-model="form['upload.s3.bucket_path']"
|
|
|
|
name="upload.s3.bucket_path" :maxlength="200" placeholder="/" />
|
|
|
|
</b-field>
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="columns">
|
|
|
|
<div class="column is-3">
|
|
|
|
<b-field label="Upload expiry" label-position="on-border"
|
|
|
|
message="(Optional) Specify TTL (in seconds) for the generated presigned URL.
|
|
|
|
Only applicable for private buckets
|
|
|
|
(s, m, h, d for seconds, minutes, hours, days)." expanded>
|
|
|
|
<b-input v-model="form['upload.s3.expiry']"
|
|
|
|
name="upload.s3.expiry"
|
|
|
|
placeholder="14d" :pattern="regDuration" :maxlength="10" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-07-08 13:00:14 +02:00
|
|
|
</div><!-- s3 -->
|
|
|
|
</div>
|
|
|
|
</b-tab-item><!-- media -->
|
|
|
|
|
|
|
|
<b-tab-item label="SMTP">
|
|
|
|
<div class="items mail-servers">
|
|
|
|
<div class="block box" v-for="(item, n) in form.smtp" :key="n">
|
|
|
|
<div class="columns">
|
|
|
|
<div class="column is-2">
|
|
|
|
<b-field label="Enabled">
|
|
|
|
<b-switch v-model="item.enabled" name="enabled"
|
|
|
|
:native-value="true" />
|
|
|
|
</b-field>
|
|
|
|
<b-field v-if="form.smtp.length > 1">
|
|
|
|
<a @click.prevent="$utils.confirm(null, () => removeSMTP(n))"
|
|
|
|
href="#" class="is-size-7">
|
|
|
|
<b-icon icon="trash-can-outline" size="is-small" /> Delete
|
|
|
|
</a>
|
|
|
|
</b-field>
|
|
|
|
</div><!-- first column -->
|
|
|
|
|
|
|
|
<div class="column" :class="{'disabled': !item.enabled}">
|
|
|
|
<div class="columns">
|
|
|
|
<div class="column is-8">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Host" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="SMTP server's host address.">
|
|
|
|
<b-input v-model="item.host" name="host"
|
|
|
|
placeholder='smtp.yourmailserver.net' :maxlength="200" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
<div class="column">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Port" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="SMTP server's port.">
|
|
|
|
<b-numberinput v-model="item.port" name="port" type="is-light"
|
|
|
|
controls-position="compact"
|
|
|
|
placeholder="25" min="1" max="65535" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</div><!-- host -->
|
|
|
|
|
|
|
|
<div class="columns">
|
|
|
|
<div class="column is-2">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Auth protocol" label-position="on-border">
|
2020-07-08 13:00:14 +02:00
|
|
|
<b-select v-model="item.auth_protocol" name="auth_protocol">
|
|
|
|
<option value="none">none</option>
|
|
|
|
<option value="cram">cram</option>
|
|
|
|
<option value="plain">plain</option>
|
|
|
|
<option value="login">login</option>
|
|
|
|
</b-select>
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
<div class="column">
|
|
|
|
<b-field grouped>
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Username" label-position="on-border" expanded>
|
2020-07-08 13:00:14 +02:00
|
|
|
<b-input v-model="item.username"
|
|
|
|
:disabled="item.auth_protocol === 'none'"
|
|
|
|
name="username" placeholder="mysmtp" :maxlength="200" />
|
|
|
|
</b-field>
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Password" label-position="on-border" expanded
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Enter a value to change. Otherwise, leave empty.">
|
|
|
|
<b-input v-model="item.password"
|
|
|
|
:disabled="item.auth_protocol === 'none'"
|
|
|
|
name="password" type="password" placeholder="Enter to change"
|
|
|
|
:maxlength="200" />
|
|
|
|
</b-field>
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</div><!-- auth -->
|
|
|
|
<hr />
|
|
|
|
|
|
|
|
<div class="columns">
|
|
|
|
<div class="column is-6">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="HELO hostname" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Optional. Some SMTP servers require a FQDN in the hostname.
|
|
|
|
By default, HELLOs go with 'localhost'. Set this if a custom
|
|
|
|
hostname should be used.">
|
|
|
|
<b-input v-model="item.hello_hostname"
|
|
|
|
name="hello_hostname" placeholder="" :maxlength="200" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
<div class="column">
|
|
|
|
<b-field grouped>
|
|
|
|
<b-field label="TLS" expanded
|
|
|
|
message="Enable STARTTLS.">
|
|
|
|
<b-switch v-model="item.tls_enabled" name="item.tls_enabled" />
|
|
|
|
</b-field>
|
|
|
|
<b-field label="Skip TLS verification" expanded
|
|
|
|
message="Skip hostname check on the TLS certificate.">
|
|
|
|
<b-switch v-model="item.tls_skip_verify"
|
|
|
|
:disabled="!item.tls_enabled" name="item.tls_skip_verify" />
|
|
|
|
</b-field>
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</div><!-- TLS -->
|
|
|
|
<hr />
|
|
|
|
|
|
|
|
<div class="columns">
|
|
|
|
<div class="column is-3">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Max. connections" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Maximum concurrent connections to the SMTP server.">
|
|
|
|
<b-numberinput v-model="item.max_conns" name="max_conns" type="is-light"
|
|
|
|
controls-position="compact"
|
|
|
|
placeholder="25" min="1" max="65535" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
<div class="column is-3">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Retries" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="The number of times a message should be retried
|
|
|
|
if sending fails.">
|
|
|
|
<b-numberinput v-model="item.max_msg_retries" name="max_msg_retries"
|
|
|
|
type="is-light"
|
|
|
|
controls-position="compact"
|
|
|
|
placeholder="2" min="1" max="1000" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
<div class="column is-3">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Idle timeout" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Time to wait for new activity on a connection before closing
|
|
|
|
it and removing it from the pool (s for second, m for minute).">
|
|
|
|
<b-input v-model="item.idle_timeout" name="idle_timeout"
|
|
|
|
placeholder="15s" :pattern="regDuration" :maxlength="10" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
<div class="column is-3">
|
2020-07-26 16:13:43 +02:00
|
|
|
<b-field label="Wait timeout" label-position="on-border"
|
2020-07-08 13:00:14 +02:00
|
|
|
message="Time to wait for new activity on a connection before closing
|
|
|
|
it and removing it from the pool (s for second, m for minute).">
|
|
|
|
<b-input v-model="item.wait_timeout" name="wait_timeout"
|
|
|
|
placeholder="5s" :pattern="regDuration" :maxlength="10" />
|
|
|
|
</b-field>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div><!-- second container column -->
|
|
|
|
</div><!-- block -->
|
|
|
|
</div><!-- mail-servers -->
|
|
|
|
|
|
|
|
<b-button @click="addSMTP" icon-left="plus" type="is-primary">Add new</b-button>
|
|
|
|
</b-tab-item><!-- mail servers -->
|
|
|
|
</b-tabs>
|
|
|
|
</form>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
import Vue from 'vue';
|
|
|
|
import { mapState } from 'vuex';
|
|
|
|
import store from '../store';
|
|
|
|
import { models } from '../constants';
|
|
|
|
|
|
|
|
export default Vue.extend({
|
|
|
|
data() {
|
|
|
|
return {
|
2020-07-26 16:13:43 +02:00
|
|
|
regDuration: '[0-9]+(ms|s|m|h|d)',
|
2020-07-08 13:00:14 +02:00
|
|
|
isLoading: true,
|
|
|
|
|
|
|
|
// formCopy is a stringified copy of the original settings against which
|
|
|
|
// form is compared to detect changes.
|
|
|
|
formCopy: '',
|
|
|
|
form: {},
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
methods: {
|
|
|
|
addSMTP() {
|
|
|
|
const [data] = JSON.parse(JSON.stringify(this.form.smtp.slice(-1)));
|
|
|
|
this.form.smtp.push(data);
|
|
|
|
},
|
|
|
|
|
|
|
|
removeSMTP(i) {
|
|
|
|
this.form.smtp.splice(i, 1);
|
|
|
|
},
|
|
|
|
|
|
|
|
onSubmit() {
|
|
|
|
this.isLoading = true;
|
|
|
|
this.$api.updateSettings(this.form).then((data) => {
|
|
|
|
if (data.needsRestart) {
|
|
|
|
// Update the 'needsRestart' flag on the global serverConfig state
|
|
|
|
// as there are running campaigns and the app couldn't auto-restart.
|
|
|
|
store.commit('setModelResponse',
|
|
|
|
{ model: models.serverConfig, data: { ...this.serverConfig, needsRestart: true } });
|
|
|
|
this.getSettings();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.$utils.toast('Settings saved. Reloading app ...');
|
|
|
|
|
|
|
|
// Poll until there's a 200 response, waiting for the app
|
|
|
|
// to restart and come back up.
|
|
|
|
const pollId = setInterval(() => {
|
|
|
|
this.$api.getHealth().then(() => {
|
|
|
|
clearInterval(pollId);
|
|
|
|
this.getSettings();
|
|
|
|
});
|
|
|
|
}, 500);
|
|
|
|
}, () => {
|
|
|
|
this.isLoading = false;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
getSettings() {
|
|
|
|
this.$api.getSettings().then((data) => {
|
|
|
|
this.form = data;
|
|
|
|
this.formCopy = JSON.stringify(data);
|
|
|
|
this.isLoading = false;
|
|
|
|
});
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
computed: {
|
|
|
|
...mapState(['serverConfig', 'loading']),
|
|
|
|
|
|
|
|
hasFormChanged() {
|
|
|
|
if (!this.formCopy) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return JSON.stringify(this.form) !== this.formCopy;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
beforeRouteLeave(to, from, next) {
|
|
|
|
if (this.hasFormChanged) {
|
|
|
|
this.$utils.confirm('Discard changes?', () => next(true));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
next(true);
|
|
|
|
},
|
|
|
|
|
|
|
|
mounted() {
|
|
|
|
this.getSettings();
|
|
|
|
},
|
|
|
|
});
|
|
|
|
</script>
|