dashy/src/components/Widgets/AnonAddy.vue

401 lines
13 KiB
Vue

<template>
<div class="anonaddy-wrapper">
<!-- Account Info -->
<div class="account-info" v-if="meta && !hideMeta">
<PercentageChart title="Mail Stats"
:values="[
{ label: 'Forwarded', size: meta.forwardCount, color: '#20e253' },
{ label: 'Blocked', size: meta.blockedCount, color: '#f80363' },
{ label: 'Replies', size: meta.repliesCount, color: '#04e4f4' },
{ label: 'Sent', size: meta.sentCount, color: '#f6f000' },
]" />
<div class="meta-item">
<span class="lbl">Bandwidth</span>
<span class="val">
{{ meta.bandwidth | formatBytes }} out of
{{ meta.bandwidthLimit !== 100000000 ? (formatBytes(meta.bandwidthLimit)) : '∞'}}
</span>
</div>
<div class="meta-item">
<span class="lbl">Active Domains</span>
<span class="val">{{ meta.activeDomains }} out of {{ meta.activeDomainsLimit }}</span>
</div>
<div class="meta-item">
<span class="lbl">Shared Domains</span>
<span class="val">{{ meta.sharedDomains }} out of {{ meta.sharedDomainsLimit || '∞'}}</span>
</div>
<div class="meta-item">
<span class="lbl">Usernames</span>
<span class="val">{{ meta.usernamesCount }} out of {{ meta.usernamesLimit || '∞'}}</span>
</div>
</div>
<!-- Email List -->
<div class="email-list" v-if="aliases && !hideAliases">
<div class="email-row" v-for="alias in aliases" :key="alias.id">
<!-- Email address and status -->
<div class="row-1">
<Toggle v-if="!disableControls" @change="toggleAlias"
:defaultState="alias.active" :id="alias.id" :hideLabels="true" />
<span v-if="disableControls"
:class="`status ${alias.active ? 'active' : 'inactive'}`">●</span>
<div class="address-copy" @click="copyToClipboard(alias.fullEmail)" title="Click to Copy">
<span class="txt-email">{{ alias.email }}</span>
<span class="txt-at">@</span>
<span class="txt-domain">{{ alias.domain }}</span>
</div>
<ClipboardIcon class="copy-btn"
@click="copyToClipboard(alias.fullEmail)"
v-tooltip="tooltip('Copy alias to clipboard')"
/>
</div>
<!-- Optional description field -->
<div class="row-2" v-if="alias.description">
<span class="description">{{ alias.description }}</span>
</div>
<!-- Num emails sent + received -->
<div class="row-3">
<span class="lbl">Forwarded</span>
<span class="val">{{ alias.forwardCount }}</span>
<span class="lbl">Blocked</span>
<span class="val">{{ alias.blockedCount }}</span>
<span class="lbl">Replied</span>
<span class="val">{{ alias.repliesCount }}</span>
<span class="lbl">Sent</span>
<span class="val">{{ alias.sentCount }}</span>
</div>
<!-- Date created / updated -->
<div class="row-4">
<span class="lbl">Created</span>
<span class="val as-date">{{ alias.createdAt | formatDate }}</span>
<span class="val as-time-ago">{{ alias.createdAt | formatTimeAgo }}</span>
</div>
</div>
</div>
<!-- Pagination Page Numbers -->
<div class="pagination" v-if="numPages && !hideAliases">
<span class="page-num first" @click="goToFirst()">«</span>
<span class="page-num" v-if="paginationRange[0] !== 1" @click="goToPrevious()">...</span>
<span
v-for="pageNum in paginationRange" :key="pageNum"
@click="goToPage(pageNum)"
:class="`page-num ${pageNum === currentPage ? 'selected' : ''}`"
>{{ pageNum }}</span>
<span class="page-num" @click="goToNext()"
v-if="paginationRange[paginationRange.length - 1] < numPages">...</span>
<span class="page-num last" @click="goToLast()">»</span>
<p class="page-status">Page {{ currentPage }} of {{ numPages }}</p>
</div>
</div>
</template>
<script>
import Toggle from '@/components/FormElements/Toggle';
import PercentageChart from '@/components/Charts/PercentageChart';
import WidgetMixin from '@/mixins/WidgetMixin';
import { widgetApiEndpoints } from '@/utils/defaults';
import { timestampToDate, getTimeAgo, convertBytes } from '@/utils/MiscHelpers';
import ClipboardIcon from '@/assets/interface-icons/open-clipboard.svg';
export default {
mixins: [WidgetMixin],
components: {
Toggle,
PercentageChart,
ClipboardIcon,
},
data() {
return {
aliases: null,
meta: null,
numPages: null,
currentPage: 1,
};
},
computed: {
hostname() {
return this.parseAsEnvVar(this.options.hostname) || widgetApiEndpoints.anonAddy;
},
apiVersion() {
return this.options.apiVersion || 'v1';
},
limit() {
return this.options.limit || '10';
},
sortBy() {
return this.options.sortBy || 'updated_at';
},
searchTerm() {
return this.options.searchTerm || '';
},
disableControls() {
return this.options.disableControls || false;
},
apiKey() {
if (!this.options.apiKey) this.error('An apiKey is required');
return this.parseAsEnvVar(this.options.apiKey);
},
hideMeta() {
return this.options.hideMeta;
},
hideAliases() {
return this.options.hideAliases;
},
endpoint() {
return `${this.hostname}/api/${this.apiVersion}/aliases?`
+ `sort=${this.sortBy}&filter[search]=${this.searchTerm}`
+ `&page[number]=${this.currentPage}&page[size]=${this.limit}`;
},
aliasCountEndpoint() {
return `${this.hostname}/api/${this.apiVersion}/aliases?filter[search]=${this.searchTerm}`;
},
accountInfoEndpoint() {
return `${this.hostname}/api/${this.apiVersion}/account-details`;
},
headers() {
return {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
Authorization: `Bearer ${this.apiKey}`,
};
},
paginationRange() {
const arrOfRange = (start, end) => Array(end - start + 1).fill().map((_, idx) => start + idx);
const maxNumbers = this.numPages > 10 ? 10 : this.numPages;
if (this.currentPage > maxNumbers) {
return arrOfRange(this.currentPage - maxNumbers, this.currentPage);
}
return arrOfRange(1, maxNumbers);
},
},
filters: {
formatDate(timestamp) {
return timestampToDate(timestamp);
},
formatTimeAgo(timestamp) {
return getTimeAgo(timestamp);
},
formatBytes(bytes) {
return convertBytes(bytes);
},
},
created() {
this.fetchAccountInfo();
},
methods: {
copyToClipboard(text) {
navigator.clipboard.writeText(text);
this.$toasted.show('Email address copied to clipboard');
},
fetchData() {
this.makeRequest(this.endpoint, this.headers).then(this.processData);
},
fetchAccountInfo() {
// Get account info
this.makeRequest(this.accountInfoEndpoint, this.headers).then(this.processAccountInfo);
// Get number of pages of results (in the most inefficient way possible...)
this.makeRequest(this.aliasCountEndpoint, this.headers).then((response) => {
this.numPages = Math.floor(response.data.length / this.limit);
});
},
processData(data) {
// this.numPages = 14; // data.meta.to;
this.currentPage = data.meta.current_page;
const aliases = [];
data.data.forEach((alias) => {
aliases.push({
id: alias.id,
active: alias.active,
domain: alias.domain,
email: alias.local_part,
recipients: alias.recipients,
description: alias.description,
forwardCount: alias.emails_forwarded,
blockedCount: alias.emails_blocked,
repliesCount: alias.emails_replied,
sentCount: alias.emails_sent,
createdAt: alias.created_at,
updatedAt: alias.updated_at,
deletedAt: alias.deleted_at,
fullEmail: alias.email,
});
});
this.aliases = aliases;
},
processAccountInfo(data) {
const res = data.data;
this.meta = {
name: data.username || res.from_name,
bandwidth: res.bandwidth,
bandwidthLimit: res.bandwidth_limit || 100000000,
activeDomains: res.active_domain_count,
activeDomainsLimit: res.active_domain_limit,
sharedDomains: res.active_shared_domain_alias_count,
sharedDomainsLimit: res.active_shared_domain_alias_limit,
usernamesCount: res.username_count,
usernamesLimit: res.username_limit,
forwardCount: res.total_emails_forwarded,
blockedCount: res.total_emails_blocked,
repliesCount: res.total_emails_replied,
sentCount: res.total_emails_sent,
};
},
toggleAlias(state, id) {
if (this.disableControls) {
this.$toasted.show('Error, controls disabled', { className: 'toast-error' });
} else {
const method = state ? 'POST' : 'DELETE';
const path = state ? 'active-aliases' : `active-aliases/${id}`;
const body = state ? { id } : {};
const endpoint = `${this.hostname}/api/${this.apiVersion}/${path}`;
this.makeRequest(endpoint, this.headers, method, body).then(() => {
const successMsg = `Alias successfully ${state ? 'enabled' : 'disabled'}`;
this.$toasted.show(successMsg, { className: 'toast-success' });
});
}
},
goToPage(page) {
this.progress.start();
this.currentPage = page;
this.fetchData();
},
goToFirst() {
this.goToPage(1);
},
goToLast() {
this.goToPage(this.numPages);
},
goToPrevious() {
if (this.currentPage > 1) this.goToPage(this.currentPage - 1);
},
goToNext() {
if (this.currentPage < this.numPages) this.goToPage(this.currentPage + 1);
},
},
};
</script>
<style scoped lang="scss">
@import '@/styles/style-helpers.scss';
.anonaddy-wrapper {
.account-info {
background: var(--widget-accent-color);
border-radius: var(--curve-factor);
padding: 0.5rem;
.meta-item span {
font-size: 0.8rem;
margin: 0.25rem 0;
opacity: var(--dimming-factor);
color: var(--widget-text-color);
font-family: var(--font-monospace);
&.lbl {
font-weight: bold;
margin-right: 0.25rem;
&::after { content: ':'; }
}
}
p.username {
margin: 0.25rem 0;
}
}
.email-list {
span.lbl {
&::after { content: ':'; }
}
span.val {
font-family: var(--font-monospace);
margin: 0 0.5rem 0 0.25rem;
}
.email-row {
color: var(--widget-text-color);
padding: 0.5rem 0;
.row-1 {
@extend .svg-button;
.address-copy {
cursor: copy;
display: inline;
}
span.txt-email {
font-weight: bold;
}
span.txt-at {
margin: 0 0.1rem;
opacity: var(--dimming-factor);
}
span.status {
font-size: 1.5rem;
line-height: 1rem;
margin-right: 0.25rem;
vertical-align: middle;
&.active { color: var(--success); }
&.inactive { color: var(--danger); }
}
.copy-btn {
float: right;
border: none;
color: var(--widget-text-color);
background: var(--widget-accent-color);
}
}
.row-2 {
max-width: 90%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
opacity: var(--dimming-factor);
span.description {
font-size: 0.8rem;
font-style: italic;
}
}
.row-3, .row-4 {
font-size: 0.8rem;
opacity: var(--dimming-factor);
}
.row-4 {
span.as-time-ago {
display: none;
}
}
&:hover {
.row-4 {
.as-date { display: none; }
.as-time-ago { display: inline; }
}
}
&:not(:last-child) { border-bottom: 1px dashed var(--widget-text-color); }
}
}
.pagination {
text-align: center;
p.page-status {
color: var(--widget-text-color);
opacity: var(--dimming-factor);
margin: 0.25rem 0;
font-size: 0.85rem;
font-family: var(--font-monospace);
}
span.page-num {
width: 1rem;
cursor: pointer;
padding: 0 0.15rem 0.1rem 0.15rem;
margin: 0;
color: var(--widget-text-color);
border-radius: 0.25rem;
border: 1px solid transparent;
display: inline-block;
&.selected {
font-weight: bold;
color: var(--widget-background-color);
background: var(--widget-text-color);
border: 1px solid var(--widget-background-color);
}
&:hover {
border: 1px solid var(--widget-text-color);
}
}
}
}
</style>