forked from knadh/listmonk
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Add new maintenance UI with options to garbage collect (delete) orphan subscriber and analytics records.
- Loading branch information
Showing
34 changed files
with
553 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
Oops, something went wrong.