diff --git a/cmd/handlers.go b/cmd/handlers.go index 33c6ca195..d9a8e7947 100644 --- a/cmd/handlers.go +++ b/cmd/handlers.go @@ -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 { diff --git a/cmd/maintenance.go b/cmd/maintenance.go new file mode 100644 index 000000000..c11e3a854 --- /dev/null +++ b/cmd/maintenance.go @@ -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}) +} diff --git a/frontend/fontello/config.json b/frontend/fontello/config.json index d3ea0a2c9..48f004e8d 100755 --- a/frontend/fontello/config.json +++ b/frontend/fontello/config.json @@ -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", diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 99a20471c..112df6ec1 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -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 } }); diff --git a/frontend/src/assets/icons/fontello.css b/frontend/src/assets/icons/fontello.css index 9fb2be4b4..491fa8b58 100644 --- a/frontend/src/assets/icons/fontello.css +++ b/frontend/src/assets/icons/fontello.css @@ -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' */ diff --git a/frontend/src/assets/icons/fontello.woff2 b/frontend/src/assets/icons/fontello.woff2 index b2e48a99e..986cb8bbb 100755 Binary files a/frontend/src/assets/icons/fontello.woff2 and b/frontend/src/assets/icons/fontello.woff2 differ diff --git a/frontend/src/components/Navigation.vue b/frontend/src/components/Navigation.vue index 2ec4872e3..c23f347ab 100644 --- a/frontend/src/components/Navigation.vue +++ b/frontend/src/components/Navigation.vue @@ -69,6 +69,10 @@ data-cy="all-settings" icon="cog-outline" :label="$t('menu.settings')"> + + + diff --git a/frontend/src/constants.js b/frontend/src/constants.js index 51ebf27b2..9bccac291 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -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. diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 52d38af99..b6219d8e9 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -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({ diff --git a/frontend/src/views/Maintenance.vue b/frontend/src/views/Maintenance.vue new file mode 100644 index 000000000..2799caee3 --- /dev/null +++ b/frontend/src/views/Maintenance.vue @@ -0,0 +1,163 @@ + + + diff --git a/i18n/cs-cz.json b/i18n/cs-cz.json index f41a10026..f34ad844c 100644 --- a/i18n/cs-cz.json +++ b/i18n/cs-cz.json @@ -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í", diff --git a/i18n/de.json b/i18n/de.json index 868df67fe..54712bbc0 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -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", diff --git a/i18n/en.json b/i18n/en.json index 7237397da..050d82985 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -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", diff --git a/i18n/es.json b/i18n/es.json index dd8ef0109..e09a4fef7 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -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", diff --git a/i18n/fi.json b/i18n/fi.json index 015d4c1ff..8db37d9f6 100644 --- a/i18n/fi.json +++ b/i18n/fi.json @@ -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", diff --git a/i18n/fr.json b/i18n/fr.json index 81849788c..55b57aa44 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -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", diff --git a/i18n/hu.json b/i18n/hu.json index 1c75d3190..9c1d55d22 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -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", diff --git a/i18n/it.json b/i18n/it.json index 65cd2d7cf..d9d2de141 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -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", diff --git a/i18n/jp.json b/i18n/jp.json index 2ca3aa4fe..543b5b333 100644 --- a/i18n/jp.json +++ b/i18n/jp.json @@ -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": "設定", diff --git a/i18n/ml.json b/i18n/ml.json index 2828c0f6d..53339d8fb 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -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": "ക്രമീകരണങ്ങൾ", diff --git a/i18n/nl.json b/i18n/nl.json index 995249911..bda3b89d8 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -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", diff --git a/i18n/pl.json b/i18n/pl.json index b71a89c6a..d9506900c 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -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", diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index e0a15e60e..c9e57ae51 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -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", diff --git a/i18n/pt.json b/i18n/pt.json index 2b4ab1621..b437de269 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -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", diff --git a/i18n/ro.json b/i18n/ro.json index 774720114..38086d82a 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -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", diff --git a/i18n/ru.json b/i18n/ru.json index 03dafbe36..7089082b7 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -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": "Параметры", diff --git a/i18n/tr.json b/i18n/tr.json index ab2af004c..c35cb77e4 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -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", diff --git a/i18n/vi.json b/i18n/vi.json index bd0a77f85..d5536ef0b 100644 --- a/i18n/vi.json +++ b/i18n/vi.json @@ -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", diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index 248e4586a..db4087559 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -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": "设置", diff --git a/internal/core/campaigns.go b/internal/core/campaigns.go index de744af71..9254e8188 100644 --- a/internal/core/campaigns.go +++ b/internal/core/campaigns.go @@ -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 +} diff --git a/internal/core/subscribers.go b/internal/core/subscribers.go index bb4eea8ed..551387273 100644 --- a/internal/core/subscribers.go +++ b/internal/core/subscribers.go @@ -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 +} diff --git a/internal/core/subscriptions.go b/internal/core/subscriptions.go index db2e4cedd..35cbb22ee 100644 --- a/internal/core/subscriptions.go +++ b/internal/core/subscriptions.go @@ -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 +} diff --git a/models/queries.go b/models/queries.go index aeece0dc8..bf88db4ec 100644 --- a/models/queries.go +++ b/models/queries.go @@ -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"` diff --git a/queries.sql b/queries.sql index f780a31e5..ef186a976 100644 --- a/queries.sql +++ b/queries.sql @@ -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')