Add maintenance options.

- Add new maintenance UI with options to garbage collect (delete)
  orphan subscriber and analytics records.
This commit is contained in:
Kailash Nadh 2022-09-03 13:18:02 +05:30
parent 8ace25849e
commit 6d820f4f6e
34 changed files with 553 additions and 14 deletions

View file

@ -139,6 +139,10 @@ func initHTTPHandlers(e *echo.Echo, app *App) {
g.PUT("/api/templates/:id/default", handleTemplateSetDefault)
g.DELETE("/api/templates/:id", handleDeleteTemplate)
g.DELETE("/api/maintenance/subscribers/:type", handleGCSubscribers)
g.DELETE("/api/maintenance/analytics/:type", handleGCCampaignAnalytics)
g.DELETE("/api/maintenance/subscriptions/unconfirmed", handleGCSubscriptions)
g.POST("/api/tx", handleSendTxMessage)
if app.constants.BounceWebhooksEnabled {

92
cmd/maintenance.go Normal file
View file

@ -0,0 +1,92 @@
package main
import (
"net/http"
"time"
"github.com/labstack/echo/v4"
)
// handleGCSubscribers garbage collects (deletes) orphaned or blocklisted subscribers.
func handleGCSubscribers(c echo.Context) error {
var (
app = c.Get("app").(*App)
typ = c.Param("type")
)
var (
n int
err error
)
switch typ {
case "blocklisted":
n, err = app.core.DeleteBlocklistedSubscribers()
case "orphan":
n, err = app.core.DeleteOrphanSubscribers()
default:
err = echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData"))
}
if err != nil {
return err
}
return c.JSON(http.StatusOK, okResp{struct {
Count int `json:"count"`
}{n}})
}
// handleGCSubscriptions garbage collects (deletes) orphaned or blocklisted subscribers.
func handleGCSubscriptions(c echo.Context) error {
var (
app = c.Get("app").(*App)
)
t, err := time.Parse(time.RFC3339, c.FormValue("before_date"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData"))
}
n, err := app.core.DeleteUnconfirmedSubscriptions(t)
if err != nil {
return err
}
return c.JSON(http.StatusOK, okResp{struct {
Count int `json:"count"`
}{n}})
}
// handleGCCampaignAnalytics garbage collects (deletes) campaign analytics.
func handleGCCampaignAnalytics(c echo.Context) error {
var (
app = c.Get("app").(*App)
typ = c.Param("type")
)
t, err := time.Parse(time.RFC3339, c.FormValue("before_date"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData"))
}
switch typ {
case "all":
if err := app.core.DeleteCampaignViews(t); err != nil {
return err
}
err = app.core.DeleteCampaignLinkClicks(t)
case "views":
err = app.core.DeleteCampaignViews(t)
case "clicks":
err = app.core.DeleteCampaignLinkClicks(t)
default:
err = echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData"))
}
if err != nil {
return err
}
return c.JSON(http.StatusOK, okResp{true})
}

View file

@ -566,6 +566,20 @@
"logout-variant"
]
},
{
"uid": "10098901a143c53df6eeaeb317ae3da6",
"css": "wrench-outline",
"code": 986080,
"src": "custom_icons",
"selected": true,
"svg": {
"path": "M941.4 791L564.5 412.1Q593.8 337.9 578.1 258.8T503.9 121.1Q431.6 50.8 332 43T152.3 93.8L312.5 253.9 253.9 312.5 93.8 154.3Q35.2 234.4 42 334T121.1 503.9Q177.7 562.5 255.9 578.1T408.2 566.4L787.1 945.3Q798.8 959 816.4 959T845.7 945.3L941.4 849.6Q955.1 837.9 955.1 821.3T941.4 791ZM816.4 857.4L423.8 462.9Q384.8 492.2 340.8 498T253.9 491.2 180.7 447.3 136.7 378.9 125 302.7L253.9 431.6 429.7 253.9 300.8 125Q384.8 121.1 445.3 179.7 478.5 212.9 491.2 256.8T496.1 344.7 460.9 427.7L853.5 820.3Z",
"width": 1000
},
"search": [
"wrench-outline"
]
},
{
"uid": "f4ad3f6d071a0bfb3a8452b514ed0892",
"css": "vector-square",
@ -42748,20 +42762,6 @@
"wrap-disabled"
]
},
{
"uid": "10098901a143c53df6eeaeb317ae3da6",
"css": "wrench-outline",
"code": 986080,
"src": "custom_icons",
"selected": false,
"svg": {
"path": "M941.4 791L564.5 412.1Q593.8 337.9 578.1 258.8T503.9 121.1Q431.6 50.8 332 43T152.3 93.8L312.5 253.9 253.9 312.5 93.8 154.3Q35.2 234.4 42 334T121.1 503.9Q177.7 562.5 255.9 578.1T408.2 566.4L787.1 945.3Q798.8 959 816.4 959T845.7 945.3L941.4 849.6Q955.1 837.9 955.1 821.3T941.4 791ZM816.4 857.4L423.8 462.9Q384.8 492.2 340.8 498T253.9 491.2 180.7 447.3 136.7 378.9 125 302.7L253.9 431.6 429.7 253.9 300.8 125Q384.8 121.1 445.3 179.7 478.5 212.9 491.2 256.8T496.1 344.7 460.9 427.7L853.5 820.3Z",
"width": 1000
},
"search": [
"wrench-outline"
]
},
{
"uid": "d670b0f395ba3a61b975a6387f8a2471",
"css": "access-point-network-off",

View file

@ -289,3 +289,12 @@ export const getLang = async (lang) => http.get(`/api/lang/${lang}`,
export const logout = async () => http.get('/api/logout', {
auth: { username: 'wrong', password: 'wrong' },
});
export const deleteGCCampaignAnalytics = async (typ, beforeDate) => http.delete(`/api/maintenance/analytics/${typ}`,
{ loading: models.maintenance, params: { before_date: beforeDate } });
export const deleteGCSubscribers = async (typ) => http.delete(`/api/maintenance/subscribers/${typ}`,
{ loading: models.maintenance });
export const deleteGCSubscriptions = async (beforeDate) => http.delete('/api/maintenance/subscriptions/unconfirmed',
{ loading: models.maintenance, params: { before_date: beforeDate } });

View file

@ -40,6 +40,7 @@
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.mdi-view-dashboard-variant-outline:before { content: '\e800'; } /* '' */
.mdi-format-list-bulleted-square:before { content: '\e801'; } /* '' */
.mdi-newspaper-variant-outline:before { content: '\e802'; } /* '' */
@ -80,3 +81,4 @@
.mdi-email-bounce:before { content: '\e825'; } /* '' */
.mdi-speedometer:before { content: '\e826'; } /* '' */
.mdi-logout-variant:before { content: '󰗽'; } /* '\f05fd' */
.mdi-wrench-outline:before { content: '󰯠'; } /* '\f0be0' */

View file

@ -69,6 +69,10 @@
data-cy="all-settings" icon="cog-outline" :label="$t('menu.settings')">
</b-menu-item>
<b-menu-item :to="{name: 'maintenance'}" tag="router-link" :active="activeItem.maintenance"
data-cy="maintenance" icon="wrench-outline" :label="$t('menu.maintenance')">
</b-menu-item>
<b-menu-item :to="{name: 'logs'}" tag="router-link" :active="activeItem.logs"
data-cy="logs" icon="newspaper-variant-outline" :label="$t('menu.logs')">
</b-menu-item>

View file

@ -10,6 +10,7 @@ export const models = Object.freeze({
bounces: 'bounces',
settings: 'settings',
logs: 'logs',
maintenance: 'maintenance',
});
// Ad-hoc URIs that are used outside of vuex requests.

View file

@ -107,6 +107,12 @@ const routes = [
meta: { title: 'logs.title', group: 'settings' },
component: () => import(/* webpackChunkName: "main" */ '../views/Logs.vue'),
},
{
path: '/settings/maintenance',
name: 'maintenance',
meta: { title: 'logs.title', group: 'settings' },
component: () => import(/* webpackChunkName: "main" */ '../views/Maintenance.vue'),
},
];
const router = new VueRouter({

View file

@ -0,0 +1,163 @@
<template>
<section class="maintenance wrap">
<h1 class="title is-4">{{ $t('maintenance.title') }}</h1>
<hr />
<p class="has-text-grey">
{{ $t('maintenance.help') }}
</p>
<br />
<div class="box">
<h4 class="is-size-5">{{ $t('globals.terms.subscribers') }}</h4><br />
<div class="columns">
<div class="column is-4">
<b-field label="Data" message="Orpans = subscribers with no lists">
<b-select v-model="subscriberType" expanded>
<option value="orphan">{{ $t('dashboard.orphanSubs') }}</option>
<option value="blocklisted">{{ $t('subscribers.status.blocklisted') }}</option>
</b-select>
</b-field>
</div>
<div class="column is-5"></div>
<div class="column">
<b-field label=".">
<b-button class="is-primary" :loading="loading.maintenance"
@click="deleteSubscribers" expanded>{{ $t('globals.buttons.delete') }}</b-button>
</b-field>
</div>
</div>
</div><!-- subscribers -->
<div class="box">
<h4 class="is-size-5">{{ $tc('globals.terms.subscriptions', 2) }}</h4><br />
<div class="columns">
<div class="column is-4">
<b-field label="Data">
<b-select v-model="subscriptionType" expanded>
<option value="optin">{{ $t('maintenance.maintenance.unconfirmedOptins') }}</option>
</b-select>
</b-field>
</div>
<div class="column is-4">
<b-field :label="$t('maintenance.olderThan')">
<b-datepicker
v-model="subscriptionDate"
required expanded
icon="calendar-clock"
:date-formatter="formatDateTime">
</b-datepicker>
</b-field>
</div>
<div class="column is-1"></div>
<div class="column">
<b-field label=".">
<b-button class="is-primary" :loading="loading.maintenance"
@click="deleteSubscriptions" expanded>{{ $t('globals.buttons.delete') }}</b-button>
</b-field>
</div>
</div>
</div><!-- subscriptions -->
<div class="box mt-6">
<h4 class="is-size-5">{{ $t('globals.terms.analytics') }}</h4><br />
<div class="columns">
<div class="column is-4">
<b-field label="Data">
<b-select v-model="analyticsType" expanded>
<option selected value="all">{{ $t('globals.terms.all') }}</option>
<option value="views">{{ $t('dashboard.campaignViews') }}</option>
<option value="clicks">{{ $t('dashboard.linkClicks') }}</option>
</b-select>
</b-field>
</div>
<div class="column is-4">
<b-field :label="$t('maintenance.olderThan')">
<b-datepicker
v-model="analyticsDate"
required expanded
icon="calendar-clock"
:date-formatter="formatDateTime">
</b-datepicker>
</b-field>
</div>
<div class="column is-1"></div>
<div class="column">
<b-field label=".">
<b-button expanded class="is-primary" :loading="loading.maintenance"
@click="deleteAnalytics">{{ $t('globals.buttons.delete') }}</b-button>
</b-field>
</div>
</div>
</div><!-- analytics -->
</section>
</template>
<script>
import Vue from 'vue';
import { mapState } from 'vuex';
import dayjs from 'dayjs';
export default Vue.extend({
components: {
},
data() {
return {
subscriberType: 'orphan',
analyticsType: 'all',
subscriptionType: 'optin',
analyticsDate: dayjs().subtract(7, 'day').toDate(),
subscriptionDate: dayjs().subtract(7, 'day').toDate(),
};
},
methods: {
formatDateTime(s) {
return dayjs(s).format('YYYY-MM-DD');
},
deleteSubscribers() {
this.$utils.confirm(
null,
() => {
this.$api.deleteGCSubscribers(this.subscriberType).then((data) => {
this.$utils.toast(this.$t('globals.messages.deletedCount',
{ name: this.$tc('globals.terms.subscribers', 2), num: data.count }));
});
},
);
},
deleteSubscriptions() {
this.$utils.confirm(
null,
() => {
this.$api.deleteGCSubscriptions(this.subscriptionDate).then((data) => {
this.$utils.toast(this.$t('globals.messages.deletedCount',
{ name: this.$tc('globals.terms.subscriptions', 2), num: data.count }));
});
},
);
},
deleteAnalytics() {
this.$utils.confirm(
null,
() => {
this.$api.deleteGCCampaignAnalytics(this.analyticsType, this.analyticsDate)
.then(() => {
this.$utils.toast(this.$t('globals.messages.done'));
});
},
);
},
},
computed: {
...mapState(['loading']),
},
});
</script>

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" vytvořen",
"globals.messages.deleted": "\"{name}\" odstraněn",
"globals.messages.deletedCount": "{name} ({num}) odstraněn",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Nic zde není",
"globals.messages.errorCreating": "Chyba při vytváření {name}: {error}",
"globals.messages.errorDeleting": "Chyba při odstraňování {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Srp",
"globals.months.9": "Zář",
"globals.states.off": "Vypnout",
"globals.terms.all": "All",
"globals.terms.analytics": "Analytika",
"globals.terms.bounce": "Nedoručitelnost | Případy nedoručitelnosti",
"globals.terms.bounces": "Případy nedoručitelnosti",
@ -202,6 +204,7 @@
"globals.terms.settings": "Nastavení",
"globals.terms.subscriber": "Odběratel | Odběratelé",
"globals.terms.subscribers": "Odběratelé",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Značka | Značky",
"globals.terms.tags": "Značky",
"globals.terms.template": "Šablona | Šablony",
@ -252,6 +255,11 @@
"lists.types.private": "Soukromý",
"lists.types.public": "Veřejný",
"logs.title": "Protokoly",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Chyba při čtení souboru: {error}",
"media.errorResizing": "Chyba při změně velikosti obrázku: {error}",
"media.errorSavingThumbnail": "Chyba při ukládání miniatury: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formuláře",
"menu.import": "Import",
"menu.logs": "Protokoly",
"menu.maintenance": "Maintenance",
"menu.media": "Médium",
"menu.newCampaign": "Vytvořit nový",
"menu.settings": "Nastavení",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" erstellt",
"globals.messages.deleted": "\"{name}\" gelöscht",
"globals.messages.deletedCount": "{name} ({num}) gelöscht",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Hier ist nichts",
"globals.messages.errorCreating": "Fehler beim Erstellen von {name}: {error}",
"globals.messages.errorDeleting": "Fehler beim Löschen von {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Statistiken",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
@ -202,6 +204,7 @@
"globals.terms.settings": "Einstellungen",
"globals.terms.subscriber": "Abonnent | Abonnenten",
"globals.terms.subscribers": "Abonnenten",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Tag | Tags",
"globals.terms.tags": "Tags",
"globals.terms.template": "Vorlage | Vorlagen",
@ -252,6 +255,11 @@
"lists.types.private": "Privat",
"lists.types.public": "Öffentlich",
"logs.title": "Logs",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Fehler beim Lesen der Datei: {error}",
"media.errorResizing": "Fehler beim Anpassen der Größe des Bildes: {error}",
"media.errorSavingThumbnail": "Fehler beim Speichern des Thumbnails: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formulare",
"menu.import": "Importieren",
"menu.logs": "Logs",
"menu.maintenance": "Maintenance",
"menu.media": "Medien",
"menu.newCampaign": "Neu Anlegen",
"menu.settings": "Einstellungen",

View file

@ -154,6 +154,7 @@
"globals.messages.created": "\"{name}\" created",
"globals.messages.deleted": "\"{name}\" deleted",
"globals.messages.deletedCount": "{name} ({num}) deleted",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Nothing here",
"globals.messages.errorCreating": "Error creating {name}: {error}",
"globals.messages.errorDeleting": "Error deleting {name}: {error}",
@ -182,6 +183,7 @@
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
@ -201,6 +203,7 @@
"globals.terms.settings": "Settings",
"globals.terms.subscriber": "Subscriber | Subscribers",
"globals.terms.subscribers": "Subscribers",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Tag | Tags",
"globals.terms.tags": "Tags",
"globals.terms.template": "Template | Templates",
@ -251,6 +254,11 @@
"lists.types.private": "Private",
"lists.types.public": "Public",
"logs.title": "Logs",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Error reading file: {error}",
"media.errorResizing": "Error resizing image: {error}",
"media.errorSavingThumbnail": "Error saving thumbnail: {error}",
@ -268,6 +276,7 @@
"menu.forms": "Forms",
"menu.import": "Import",
"menu.logs": "Logs",
"menu.maintenance": "Maintenance",
"menu.media": "Media",
"menu.newCampaign": "Create new",
"menu.settings": "Settings",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" creado",
"globals.messages.deleted": "\"{name}\" eliminado",
"globals.messages.deletedCount": "{name} ({num}) eliminado(s)",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Vacío",
"globals.messages.errorCreating": "Error creando {name}: {error}",
"globals.messages.errorDeleting": "Error eliminando {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Agosto",
"globals.months.9": "Setiembre",
"globals.states.off": "Apagado",
"globals.terms.all": "All",
"globals.terms.analytics": "Analitica",
"globals.terms.bounce": "Rebote | Rebotes",
"globals.terms.bounces": "Rebotes",
@ -202,6 +204,7 @@
"globals.terms.settings": "Configuraciones",
"globals.terms.subscriber": "Subscriptor | Subscriptores",
"globals.terms.subscribers": "Subscriptores",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Etiqueta | Etiquetas",
"globals.terms.tags": "Etiqueta",
"globals.terms.template": "Plantilla | Plantillas",
@ -252,6 +255,11 @@
"lists.types.private": "Privada",
"lists.types.public": "Pública",
"logs.title": "Registros",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Error leyendo archivo: {error}",
"media.errorResizing": "Error cambiando tamaño de imagen: {error}",
"media.errorSavingThumbnail": "Error guardando miniatura: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formularios",
"menu.import": "Importar",
"menu.logs": "Registros (logs)",
"menu.maintenance": "Maintenance",
"menu.media": "Multimedia",
"menu.newCampaign": "Crear nueva",
"menu.settings": "Configuraciones",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" created",
"globals.messages.deleted": "\"{name}\" deleted",
"globals.messages.deletedCount": "{name} ({num}) deleted",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Nothing here",
"globals.messages.errorCreating": "Error creating {name}: {error}",
"globals.messages.errorDeleting": "Error deleting {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
@ -202,6 +204,7 @@
"globals.terms.settings": "Settings",
"globals.terms.subscriber": "Subscriber | Subscribers",
"globals.terms.subscribers": "Subscribers",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Tag | Tags",
"globals.terms.tags": "Tags",
"globals.terms.template": "Template | Templates",
@ -252,6 +255,11 @@
"lists.types.private": "Private",
"lists.types.public": "Public",
"logs.title": "Logs",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Error reading file: {error}",
"media.errorResizing": "Error resizing image: {error}",
"media.errorSavingThumbnail": "Error saving thumbnail: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Forms",
"menu.import": "Import",
"menu.logs": "Logs",
"menu.maintenance": "Maintenance",
"menu.media": "Media",
"menu.newCampaign": "Create new",
"menu.settings": "Settings",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "Création de « {name} »",
"globals.messages.deleted": "Suppression de « {name} »",
"globals.messages.deletedCount": "{name} ({num}) effacé(s)",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Rien",
"globals.messages.errorCreating": "Erreur lors de la création de {name} : {error}",
"globals.messages.errorDeleting": "Erreur lors de la suppression de {name} : {error}",
@ -183,6 +184,7 @@
"globals.months.8": "août",
"globals.months.9": "sept.",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analyses",
"globals.terms.bounce": "Rebond | Rebonds",
"globals.terms.bounces": "Rebonds",
@ -202,6 +204,7 @@
"globals.terms.settings": "Paramètres",
"globals.terms.subscriber": "Abonné·e | Abonné·es",
"globals.terms.subscribers": "Abonné·es",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Étiquette | Étiquettes",
"globals.terms.tags": "Étiquettes",
"globals.terms.template": "Modèle | Modèles",
@ -252,6 +255,11 @@
"lists.types.private": "Privée",
"lists.types.public": "Publique",
"logs.title": "Journalisations",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Erreur de lecture du fichier : {error}",
"media.errorResizing": "Erreur lors du redimensionnement de l'image : {error}",
"media.errorSavingThumbnail": "Erreur lors de l'enregistrement de la miniature : {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formulaires",
"menu.import": "Importer",
"menu.logs": "Journalisations",
"menu.maintenance": "Maintenance",
"menu.media": "Fichiers",
"menu.newCampaign": "Nouvelle campagne",
"menu.settings": "Paramètres",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" készítette",
"globals.messages.deleted": "\"{name}\" törölte",
"globals.messages.deletedCount": "{name} ({num}) törölve",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Semmi sincs itt",
"globals.messages.errorCreating": "Hiba a létrehozásnál {name}: {error}",
"globals.messages.errorDeleting": "Hiba a törléskor {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analitika",
"globals.terms.bounce": "Visszapattanó | Visszapattanók",
"globals.terms.bounces": "Visszapattanók",
@ -202,6 +204,7 @@
"globals.terms.settings": "Beállítások",
"globals.terms.subscriber": "Feliratkozó | Feliratkozók",
"globals.terms.subscribers": "Feliratkozók",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Címke | Címkék",
"globals.terms.tags": "Címkék",
"globals.terms.template": "Sablon | Sablonok",
@ -252,6 +255,11 @@
"lists.types.private": "Privát",
"lists.types.public": "Nyilvános",
"logs.title": "Logok",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Hiba a fájl olvasásakor : {error}",
"media.errorResizing": "Hiba a kép átméretezésekor : {error}",
"media.errorSavingThumbnail": "Hiba az indexkép mentésekor : {error}",
@ -269,6 +277,7 @@
"menu.forms": "Űrlapok",
"menu.import": "Importálás",
"menu.logs": "Logok",
"menu.maintenance": "Maintenance",
"menu.media": "Média",
"menu.newCampaign": "Új készítése",
"menu.settings": "Beállítások",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" creato",
"globals.messages.deleted": "\"{name}\" cancellato",
"globals.messages.deletedCount": "{name} ({num}) deleted",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Niente da visualizzare",
"globals.messages.errorCreating": "Errore durante la creazione di {name}: {error}",
"globals.messages.errorDeleting": "Errore durante la cancellazione di {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Ago",
"globals.months.9": "Set",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
@ -202,6 +204,7 @@
"globals.terms.settings": "Impostazioni",
"globals.terms.subscriber": "Iscritto | Iscritti",
"globals.terms.subscribers": "Iscritti",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Etichetta | Etichette",
"globals.terms.tags": "Etichette",
"globals.terms.template": "Modello | Modelli",
@ -252,6 +255,11 @@
"lists.types.private": "Privata",
"lists.types.public": "Pubblico",
"logs.title": "Logs",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Errore di lettura del file: {error}",
"media.errorResizing": "Errore di ridimensionamento dell'immagine: {error}",
"media.errorSavingThumbnail": "Errore durante il salvataggio dell'immagine: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formulari",
"menu.import": "Importare",
"menu.logs": "Logs",
"menu.maintenance": "Maintenance",
"menu.media": "Media",
"menu.newCampaign": "Creare nuovo",
"menu.settings": "Impostazioni",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" が作成されました",
"globals.messages.deleted": "\"{name}\" が削除されました",
"globals.messages.deletedCount": "{name} ({num}) が削除されました。",
"globals.messages.done": "Done",
"globals.messages.emptyState": "ここには何もありません",
"globals.messages.errorCreating": "{name}作成エラー: {error}",
"globals.messages.errorDeleting": "{name}削除エラー: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "8月",
"globals.months.9": "9月",
"globals.states.off": "オフ",
"globals.terms.all": "All",
"globals.terms.analytics": "分析",
"globals.terms.bounce": "バウンス | バウンス",
"globals.terms.bounces": "バウンス",
@ -202,6 +204,7 @@
"globals.terms.settings": "設定",
"globals.terms.subscriber": "加入者 | 加入者",
"globals.terms.subscribers": "加入者",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "タグ | タグ",
"globals.terms.tags": "タグ",
"globals.terms.template": "テンプレート | テンプレート",
@ -252,6 +255,11 @@
"lists.types.private": "プライベート",
"lists.types.public": "パブリック",
"logs.title": "ログ",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "ファイル読み込みエラー: {error}",
"media.errorResizing": "画像のリサイズエラー: {error}",
"media.errorSavingThumbnail": "サムネイル保存エラー: {error}",
@ -269,6 +277,7 @@
"menu.forms": "フォーム",
"menu.import": "インポート",
"menu.logs": "ログ",
"menu.maintenance": "Maintenance",
"menu.media": "メディア",
"menu.newCampaign": "新規作成",
"menu.settings": "設定",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" നിർമ്മിച്ചു",
"globals.messages.deleted": "\"{name}\" നീക്കം ചെയ്തു",
"globals.messages.deletedCount": "{name} ({num}) deleted",
"globals.messages.done": "Done",
"globals.messages.emptyState": "ഇവിടൊന്നുമില്ല",
"globals.messages.errorCreating": "{name} നിർമ്മിക്കുന്നതിൽ പിശകുണ്ടായി: {error}",
"globals.messages.errorDeleting": "{name} നീക്കം ചെയ്യുന്നതിൽ പിശകുണ്ടായി: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "ഓഗസ്റ്റ്",
"globals.months.9": "സെപ്റ്റംബർ",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
@ -202,6 +204,7 @@
"globals.terms.settings": "ക്രമീകരണങ്ങൾ",
"globals.terms.subscriber": "വരിക്കാരൻ | വരിക്കാർ",
"globals.terms.subscribers": "വരിക്കാർ",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "ടാഗ് | ടാഗുകൾ",
"globals.terms.tags": "ടാഗുകൾ",
"globals.terms.template": "ടെംപ്ലേറ്റ് | ടെംപ്ലേറ്റുകൾ",
@ -252,6 +255,11 @@
"lists.types.private": "സ്വകാര്യം",
"lists.types.public": "പൊതു",
"logs.title": "ലോഗുകൾ",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "ഫയൽ വായിക്കാനായില്ല: {error}",
"media.errorResizing": "ചിത്രത്തിന്റ വലിപ്പം മാറ്റാനായില്ല: {error}",
"media.errorSavingThumbnail": "തമ്പ്നെയിൽ സേവ് ചെയ്യാനായില്ല: {error}",
@ -269,6 +277,7 @@
"menu.forms": "ഫോമുകൾ",
"menu.import": "ഇമ്പോർട്ട്",
"menu.logs": "ലോഗുകൾ",
"menu.maintenance": "Maintenance",
"menu.media": "മീഡിയ",
"menu.newCampaign": "പുതിയത് തുടങ്ങുക",
"menu.settings": "ക്രമീകരണങ്ങൾ",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" aangemaakt",
"globals.messages.deleted": "\"{name}\" verwijderd",
"globals.messages.deletedCount": "{name} ({num}) verwijderd",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Niks te zien hier",
"globals.messages.errorCreating": "Fout bij aanmaken {name}: {error}",
"globals.messages.errorDeleting": "Fout bij verwijderen {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Uit",
"globals.terms.all": "All",
"globals.terms.analytics": "Analyse",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
@ -202,6 +204,7 @@
"globals.terms.settings": "Instellingen",
"globals.terms.subscriber": "Abonnee | Abonnees",
"globals.terms.subscribers": "Abonnees",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Label | Labels",
"globals.terms.tags": "Labels",
"globals.terms.template": "Sjabloon | Sjablonen",
@ -252,6 +255,11 @@
"lists.types.private": "Privé",
"lists.types.public": "Publiek",
"logs.title": "Logboeken",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Fout bij lezen bestand: {error}",
"media.errorResizing": "Fout bij wijzigen formaat afbeelding: {error}",
"media.errorSavingThumbnail": "Fout bij opslaan thumbnail: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formulieren",
"menu.import": "Importeer",
"menu.logs": "Logboeken",
"menu.maintenance": "Maintenance",
"menu.media": "Media",
"menu.newCampaign": "Nieuwe aanmaken",
"menu.settings": "Instellingen",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" utworzono",
"globals.messages.deleted": "\"{name}\" usunięto",
"globals.messages.deletedCount": "{name} ({num}) deleted",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Nic tutaj nie ma",
"globals.messages.errorCreating": "Błąd podczas tworzenia {name}: {error}",
"globals.messages.errorDeleting": "Błąd podczas usuwania {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Sie",
"globals.months.9": "Wrz",
"globals.states.off": "Wyłączone",
"globals.terms.all": "All",
"globals.terms.analytics": "Analityka",
"globals.terms.bounce": "Odbicie | Obicia",
"globals.terms.bounces": "Odbicia",
@ -202,6 +204,7 @@
"globals.terms.settings": "Ustawienia",
"globals.terms.subscriber": "Subskrypcja | Subskrypcje",
"globals.terms.subscribers": "Subskrypcje",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Tag | Tagi",
"globals.terms.tags": "Tagi",
"globals.terms.template": "Szablon | Szablony",
@ -252,6 +255,11 @@
"lists.types.private": "Prywatna",
"lists.types.public": "Publiczna",
"logs.title": "Logi",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Błąd odczytu pliku: {error}",
"media.errorResizing": "Błąd zmiany rozmiaru obrazu: {error}",
"media.errorSavingThumbnail": "Błąd zapisywania miniaturki: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formularze",
"menu.import": "Import",
"menu.logs": "Logi",
"menu.maintenance": "Maintenance",
"menu.media": "Media",
"menu.newCampaign": "Utwórz nową",
"menu.settings": "Ustawienia",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" criado",
"globals.messages.deleted": "\"{name}\" excluído",
"globals.messages.deletedCount": "{name} ({num}) deletado",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Nada por aqui",
"globals.messages.errorCreating": "Erro ao criar {name}: {error}",
"globals.messages.errorDeleting": "Erro ao excluir {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Ago",
"globals.months.9": "Set",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
@ -202,6 +204,7 @@
"globals.terms.settings": "Configurações",
"globals.terms.subscriber": "Assinante | Assinantes",
"globals.terms.subscribers": "Assinantes",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Tag | Tags",
"globals.terms.tags": "Tags",
"globals.terms.template": "Modelo | Modelos",
@ -252,6 +255,11 @@
"lists.types.private": "Privada",
"lists.types.public": "Pública",
"logs.title": "Logs",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Erro ao ler arquivo: {error}",
"media.errorResizing": "Erro ao redimensionar imagem: {error}",
"media.errorSavingThumbnail": "Erro ao salvar miniatura: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formulários",
"menu.import": "Importação",
"menu.logs": "Logs",
"menu.maintenance": "Maintenance",
"menu.media": "Mídia",
"menu.newCampaign": "Criar nova",
"menu.settings": "Configurações",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" criado",
"globals.messages.deleted": "\"{name}\" eliminado",
"globals.messages.deletedCount": "{name} ({num}) deleted",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Não há nada aqui",
"globals.messages.errorCreating": "Erro ao criar {name}: {error}",
"globals.messages.errorDeleting": "Erro ao eliminar {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Ago",
"globals.months.9": "Set",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
@ -202,6 +204,7 @@
"globals.terms.settings": "Definições",
"globals.terms.subscriber": "Subscritor | Subcritores",
"globals.terms.subscribers": "Subscritores",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Etiqueta | Etiquetas",
"globals.terms.tags": "Etiquetas",
"globals.terms.template": "Modelo | Modelos",
@ -252,6 +255,11 @@
"lists.types.private": "Privado",
"lists.types.public": "Público",
"logs.title": "Logs (Histórico)",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Erro ao ler ficheiro: {error}",
"media.errorResizing": "Erro ao alterar tamanho da imagem: {error}",
"media.errorSavingThumbnail": "Erro ao guardar miniatura: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formulários",
"menu.import": "Importar",
"menu.logs": "Logs",
"menu.maintenance": "Maintenance",
"menu.media": "Mídia",
"menu.newCampaign": "Criar nova",
"menu.settings": "Definições",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{nume}\" creat",
"globals.messages.deleted": "\"{nume}\" șters",
"globals.messages.deletedCount": "{nume} ({număr}) șters",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Nimic aici",
"globals.messages.errorCreating": "Eroare creare {nume}: {eroare}",
"globals.messages.errorDeleting": "Eroare ștergere {nume}: {eroare}",
@ -183,6 +184,7 @@
"globals.months.8": "Aug",
"globals.months.9": "Sep",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analitice",
"globals.terms.bounce": "Respins | Respinse",
"globals.terms.bounces": "Respinse",
@ -202,6 +204,7 @@
"globals.terms.settings": "Setări",
"globals.terms.subscriber": "Abonat | Abonați",
"globals.terms.subscribers": "Abonați",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Etichetă | Etichete",
"globals.terms.tags": "Etichete",
"globals.terms.template": "Șablon | Șabloane",
@ -252,6 +255,11 @@
"lists.types.private": "Privat",
"lists.types.public": "Public",
"logs.title": "Jurnale",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Eroare citire fisier: {eroare}",
"media.errorResizing": "Eroare redimensionare imagine: {eroare}",
"media.errorSavingThumbnail": "Eroare la salvarea miniaturii: {eroare}",
@ -269,6 +277,7 @@
"menu.forms": "Formulare",
"menu.import": "Import",
"menu.logs": "Jurnale",
"menu.maintenance": "Maintenance",
"menu.media": "Media",
"menu.newCampaign": "Creaza nou",
"menu.settings": "Setări",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" создано",
"globals.messages.deleted": "\"{name}\" удалено",
"globals.messages.deletedCount": "{name} ({num}) удалено",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Ничего нет",
"globals.messages.errorCreating": "Ошибка создания {name}: {error}",
"globals.messages.errorDeleting": "Ошибка удаления {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Авг",
"globals.months.9": "Сен",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Аналитика",
"globals.terms.bounce": "Отскок | Отскоки",
"globals.terms.bounces": "Отскоки",
@ -202,6 +204,7 @@
"globals.terms.settings": "Параметры",
"globals.terms.subscriber": "Подписчик | Подписчики",
"globals.terms.subscribers": "Подписчики",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Тег | Теги",
"globals.terms.tags": "Теги",
"globals.terms.template": "Шаблон | Шаблоны",
@ -252,6 +255,11 @@
"lists.types.private": "Приватный",
"lists.types.public": "Публичный",
"logs.title": "Логи",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Ошибка чтения файла: {error}",
"media.errorResizing": "Ошибка изменения размера изображения: {error}",
"media.errorSavingThumbnail": "Ошибка сохранения миниатюры: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Формы",
"menu.import": "Импорт",
"menu.logs": "Логи",
"menu.maintenance": "Maintenance",
"menu.media": "Медиа",
"menu.newCampaign": "Создать новую",
"menu.settings": "Параметры",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" oluşturma",
"globals.messages.deleted": "\"{name}\" silme",
"globals.messages.deletedCount": "{name} ({num}) deleted",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Burası Boş",
"globals.messages.errorCreating": "Hata oluşturma {name}: {error}",
"globals.messages.errorDeleting": "Hata silme {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Aug",
"globals.months.9": "Eyl",
"globals.states.off": "Off",
"globals.terms.all": "All",
"globals.terms.analytics": "Analytics",
"globals.terms.bounce": "Bounce | Bounces",
"globals.terms.bounces": "Bounces",
@ -202,6 +204,7 @@
"globals.terms.settings": "Ayarlar",
"globals.terms.subscriber": "Üye | Üyeler",
"globals.terms.subscribers": "Üyeler",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Tag | Tag(lar)",
"globals.terms.tags": "Tag(lar)",
"globals.terms.template": "Taslak | Taslaklar",
@ -252,6 +255,11 @@
"lists.types.private": "Kişisel",
"lists.types.public": "Erişime açık",
"logs.title": "Loglar",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Hata, dosya okurken: {error}",
"media.errorResizing": "Hata, resim büyüklüğü değişirken: {error}",
"media.errorSavingThumbnail": "Hata, önizleme oluşurken: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Formlar",
"menu.import": "İçeri aktar",
"menu.logs": "Loglar",
"menu.maintenance": "Maintenance",
"menu.media": "Medya",
"menu.newCampaign": "Yeni oluştur",
"menu.settings": "Ayarlar",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "\"{name}\" đã tạo",
"globals.messages.deleted": "\"{name}\" đã xóa",
"globals.messages.deletedCount": "{name} ({num}) đã xóa",
"globals.messages.done": "Done",
"globals.messages.emptyState": "Không có gì ở đây",
"globals.messages.errorCreating": "Lỗi khi tạo {name}: {error}",
"globals.messages.errorDeleting": "Lỗi khi xóa {name}: {error}",
@ -183,6 +184,7 @@
"globals.months.8": "Tháng 8",
"globals.months.9": "Tháng 9",
"globals.states.off": "Tắt",
"globals.terms.all": "All",
"globals.terms.analytics": "phân tích",
"globals.terms.bounce": "Bounces | Bounces",
"globals.terms.bounces": "Bị trả lại",
@ -202,6 +204,7 @@
"globals.terms.settings": "Cài đặt",
"globals.terms.subscriber": "Subscriber | Subscribers",
"globals.terms.subscribers": "Người đăng ký",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "Tag | Tags",
"globals.terms.tags": "Thẻ",
"globals.terms.template": "Template | Templates",
@ -252,6 +255,11 @@
"lists.types.private": "Riêng tư",
"lists.types.public": "Công cộng",
"logs.title": "Nhật ký",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "Lỗi khi đọc tệp: {error}",
"media.errorResizing": "Lỗi khi thay đổi kích thước hình ảnh: {error}",
"media.errorSavingThumbnail": "Lỗi khi lưu hình thu nhỏ: {error}",
@ -269,6 +277,7 @@
"menu.forms": "Biểu mẫu",
"menu.import": "Nhập",
"menu.logs": "Nhật kí",
"menu.maintenance": "Maintenance",
"menu.media": "Dữ liệu truyền thông",
"menu.newCampaign": "Tạo mới",
"menu.settings": "Cài đặt",

View file

@ -155,6 +155,7 @@
"globals.messages.created": "“{name}”已创建",
"globals.messages.deleted": "“{name}”已删除",
"globals.messages.deletedCount": "{name} ({num}) 个已删除",
"globals.messages.done": "Done",
"globals.messages.emptyState": "这里没有什么",
"globals.messages.errorCreating": "创建 {name} 时出错:{error}",
"globals.messages.errorDeleting": "删除 {name} 时出错:{error}",
@ -183,6 +184,7 @@
"globals.months.8": "八月",
"globals.months.9": "九月",
"globals.states.off": "关闭",
"globals.terms.all": "All",
"globals.terms.analytics": "统计",
"globals.terms.bounce": "反弹 | 多个反弹",
"globals.terms.bounces": "反弹",
@ -202,6 +204,7 @@
"globals.terms.settings": "设置",
"globals.terms.subscriber": "订阅者 | 多个订阅者",
"globals.terms.subscribers": "订阅者",
"globals.terms.subscriptions": "Subscription | Subscriptions",
"globals.terms.tag": "标签 | 多个标签",
"globals.terms.tags": "标签",
"globals.terms.template": "模板 | 多个模板",
@ -252,6 +255,11 @@
"lists.types.private": "私人的",
"lists.types.public": "公开",
"logs.title": "日志",
"maintenance.help": "Some actions may take a while to complete depending on the amount of data.",
"maintenance.maintenance.unconfirmedOptins": "Unconfirmed opt-in subscriptions",
"maintenance.olderThan": "Older than",
"maintenance.title": "Maintenance",
"maintenance.unconfirmedSubs": "Unconfirmed subscriptions older than {name} days.",
"media.errorReadingFile": "读取文件时出错:{error}",
"media.errorResizing": "调整图像大小时出错:{error}",
"media.errorSavingThumbnail": "保存缩略图时出错:{error}",
@ -269,6 +277,7 @@
"menu.forms": "表格",
"menu.import": "导入",
"menu.logs": "日志",
"menu.maintenance": "Maintenance",
"menu.media": "媒体",
"menu.newCampaign": "创建新的",
"menu.settings": "设置",

View file

@ -3,6 +3,7 @@ package core
import (
"database/sql"
"net/http"
"time"
"github.com/gofrs/uuid"
"github.com/jmoiron/sqlx"
@ -342,3 +343,23 @@ func (c *Core) RegisterCampaignLinkClick(linkUUID, campUUID, subUUID string) (st
return url, nil
}
// DeleteCampaignViews deletes campaign views older than a given date.
func (c *Core) DeleteCampaignViews(before time.Time) error {
if _, err := c.q.DeleteCampaignViews.Exec(before); err != nil {
c.log.Printf("error deleting campaign views: %s", err)
return echo.NewHTTPError(http.StatusInternalServerError, c.i18n.Ts("public.errorProcessingRequest"))
}
return nil
}
// DeleteCampaignLinkClicks deletes campaign views older than a given date.
func (c *Core) DeleteCampaignLinkClicks(before time.Time) error {
if _, err := c.q.DeleteCampaignLinkClicks.Exec(before); err != nil {
c.log.Printf("error deleting campaign link clicks: %s", err)
return echo.NewHTTPError(http.StatusInternalServerError, c.i18n.Ts("public.errorProcessingRequest"))
}
return nil
}

View file

@ -432,3 +432,29 @@ func (c *Core) DeleteSubscriberBounces(id int, uuid string) error {
return nil
}
// DeleteOrphanSubscribers deletes orphan subscriber records (subscribers without lists).
func (c *Core) DeleteOrphanSubscribers() (int, error) {
res, err := c.q.DeleteOrphanSubscribers.Exec()
if err != nil {
c.log.Printf("error deleting orphan subscribers: %v", err)
return 0, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorDeleting", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err)))
}
n, _ := res.RowsAffected()
return int(n), nil
}
// DeleteBlocklistedSubscribers deletes blocklisted subscribers.
func (c *Core) DeleteBlocklistedSubscribers() (int, error) {
res, err := c.q.DeleteBlocklistedSubscribers.Exec()
if err != nil {
c.log.Printf("error deleting blocklisted subscribers: %v", err)
return 0, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorDeleting", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err)))
}
n, _ := res.RowsAffected()
return int(n), nil
}

View file

@ -2,6 +2,7 @@ package core
import (
"net/http"
"time"
"github.com/labstack/echo/v4"
"github.com/lib/pq"
@ -91,3 +92,17 @@ func (c *Core) UnsubscribeListsByQuery(query string, sourceListIDs, targetListID
return nil
}
// DeleteUnconfirmedSubscriptions sets list subscriptions to 'ubsubscribed' by a given arbitrary query expression.
// sourceListIDs is the list of list IDs to filter the subscriber query with.
func (c *Core) DeleteUnconfirmedSubscriptions(beforeDate time.Time) (int, error) {
res, err := c.q.DeleteUnconfirmedSubscriptions.Exec(beforeDate)
if err != nil {
c.log.Printf("error deleting unconfirmed subscribers: %v", err)
return 0, echo.NewHTTPError(http.StatusInternalServerError,
c.i18n.Ts("globals.messages.errorDeleting", "name", "{globals.terms.subscribers}", "error", pqErrMsg(err)))
}
n, _ := res.RowsAffected()
return int(n), nil
}

View file

@ -25,9 +25,12 @@ type Queries struct {
BlocklistSubscribers *sqlx.Stmt `query:"blocklist-subscribers"`
AddSubscribersToLists *sqlx.Stmt `query:"add-subscribers-to-lists"`
DeleteSubscriptions *sqlx.Stmt `query:"delete-subscriptions"`
DeleteUnconfirmedSubscriptions *sqlx.Stmt `query:"delete-unconfirmed-subscriptions"`
ConfirmSubscriptionOptin *sqlx.Stmt `query:"confirm-subscription-optin"`
UnsubscribeSubscribersFromLists *sqlx.Stmt `query:"unsubscribe-subscribers-from-lists"`
DeleteSubscribers *sqlx.Stmt `query:"delete-subscribers"`
DeleteBlocklistedSubscribers *sqlx.Stmt `query:"delete-blocklisted-subscribers"`
DeleteOrphanSubscribers *sqlx.Stmt `query:"delete-orphan-subscribers"`
UnsubscribeByCampaign *sqlx.Stmt `query:"unsubscribe-by-campaign"`
ExportSubscriberData *sqlx.Stmt `query:"export-subscriber-data"`
@ -65,6 +68,8 @@ type Queries struct {
GetCampaignClickCounts *sqlx.Stmt `query:"get-campaign-click-counts"`
GetCampaignLinkCounts *sqlx.Stmt `query:"get-campaign-link-counts"`
GetCampaignBounceCounts *sqlx.Stmt `query:"get-campaign-bounce-counts"`
DeleteCampaignViews *sqlx.Stmt `query:"delete-campaign-views"`
DeleteCampaignLinkClicks *sqlx.Stmt `query:"delete-campaign-link-clicks"`
NextCampaigns *sqlx.Stmt `query:"next-campaigns"`
NextCampaignSubscribers *sqlx.Stmt `query:"next-campaign-subscribers"`

View file

@ -147,6 +147,13 @@ INSERT INTO subscriber_lists (subscriber_id, list_id, status)
-- Delete one or more subscribers by ID or UUID.
DELETE FROM subscribers WHERE CASE WHEN ARRAY_LENGTH($1::INT[], 1) > 0 THEN id = ANY($1) ELSE uuid = ANY($2::UUID[]) END;
-- name: delete-blocklisted-subscribers
DELETE FROM subscribers WHERE status = 'blocklisted';
-- name: delete-orphan-subscribers
DELETE FROM subscribers a WHERE NOT EXISTS
(SELECT 1 FROM subscriber_lists b WHERE b.subscriber_id = a.id);
-- name: blocklist-subscribers
WITH b AS (
UPDATE subscribers SET status='blocklisted', updated_at=NOW()
@ -196,6 +203,13 @@ UPDATE subscriber_lists SET status = 'unsubscribed' WHERE
-- If $3 is false, unsubscribe from the campaign's lists, otherwise all lists.
CASE WHEN $3 IS FALSE THEN list_id = ANY(SELECT list_id FROM lists) ELSE list_id != 0 END;
-- name: delete-unconfirmed-subscriptions
WITH optins AS (
SELECT id FROM lists WHERE optin = 'double'
)
DELETE FROM subscriber_lists
WHERE status = 'unconfirmed' AND list_id IN (SELECT id FROM optins) AND created_at < $1;
-- privacy
-- name: export-subscriber-data
WITH prof AS (
@ -670,6 +684,12 @@ u AS (
)
SELECT * FROM subs;
-- name: delete-campaign-views
DELETE FROM campaign_views WHERE created_at < $1;
-- name: delete-campaign-link-clicks
DELETE FROM link_clicks WHERE created_at < $1;
-- name: get-one-campaign-subscriber
SELECT * FROM subscribers
LEFT JOIN subscriber_lists ON (subscribers.id = subscriber_lists.subscriber_id AND subscriber_lists.status != 'unsubscribed')