Finalized the email function

This commit is contained in:
Donald Zou 2025-01-16 00:44:22 +08:00
parent 5d84b61f18
commit 4f92a7edf3
5 changed files with 204 additions and 39 deletions

View File

@ -1883,7 +1883,8 @@ class DashboardConfig:
"encryption": "",
"username": "",
"email_password": "",
"send_from": ""
"send_from": "",
"email_template": ""
},
"WireGuardConfiguration": {
"autostart": ""
@ -3053,15 +3054,14 @@ def API_Email_Send():
data = request.get_json()
if "Receiver" not in data.keys():
return ResponseObject(False, "Please at least specify receiver")
body = data.get('Body', '')
download = None
if "ConfigurationName" in data.keys() and "Peer" in data.keys():
if data.get('ConfigurationName') in WireguardConfigurations.keys():
configuration = WireguardConfigurations.get(data.get('ConfigurationName'))
attachmentName = ""
if configuration is not None:
fp, p = configuration.searchPeer(data.get('Peer'))
print(fp)
if fp:
template = Template(body)
download = p.downloadPeer()
@ -3073,9 +3073,34 @@ def API_Email_Send():
attachmentName = f'{u}.conf'
s, m = EmailSender.send(data.get('Receiver'), data.get('Subject', ''), body,
data.get('IncludeAttachment', False), download['fileName'])
data.get('IncludeAttachment', False), (download.get('fileName', '') if download else ''))
return ResponseObject(s, m)
@app.post(f'{APP_PREFIX}/api/email/previewBody')
def API_Email_PreviewBody():
data = request.get_json()
body = data.get('Body', '')
if len(body) == 0:
return ResponseObject(False, "Nothing to preview")
if ("ConfigurationName" not in data.keys()
or "Peer" not in data.keys() or data.get('ConfigurationName') not in WireguardConfigurations.keys()):
return ResponseObject(False, "Please specify configuration and peer")
configuration = WireguardConfigurations.get(data.get('ConfigurationName'))
fp, p = configuration.searchPeer(data.get('Peer'))
if not fp:
return ResponseObject(False, "Peer does not exist")
try:
template = Template(body)
download = p.downloadPeer()
body = template.render(peer=p.toJson(), configurationFile=download)
return ResponseObject(data=body)
except Exception as e:
return ResponseObject(False, message=str(e))
@app.get(f'{APP_PREFIX}/api/systemStatus')
def API_SystemStatus():
cpu_percpu = psutil.cpu_percent(interval=0.5, percpu=True)

View File

@ -1,23 +1,28 @@
<script setup async>
import LocaleText from "@/components/text/localeText.vue";
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
import {reactive, ref} from "vue";
import {reactive, ref, watch} from "vue";
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
import PeerShareWithEmailBodyPreview
from "@/components/configurationComponents/peerShareLinkComponents/peerShareWithEmailBodyPreview.vue";
import {GetLocale} from "@/utilities/locale.js";
const props = defineProps(['dataCopy', 'selectedPeer'])
const emailIsReady = ref(false)
await fetchGet("/api/email/ready", {}, (res) => {
emailIsReady.value = res.status
})
const store = DashboardConfigurationStore()
const email = reactive({
Receiver: "",
Body: "",
Body: store.Configuration.Email.email_template,
Subject: "",
IncludeAttachment: false,
ConfigurationName: props.selectedPeer.configuration.Name,
Peer: props.selectedPeer.id
})
const store = DashboardConfigurationStore()
const livePreview = ref(false)
const sending = ref(false)
const sendEmail = async () => {
@ -32,6 +37,15 @@ const sendEmail = async () => {
})
}
const placeholderTranslate = (key) => {
return GetLocale(key)
}
const emits = defineEmits(['fullscreen'])
watch(livePreview, () => {
console.log(livePreview.value)
emits('fullscreen', livePreview.value)
})
</script>
<template>
@ -49,7 +63,7 @@ const sendEmail = async () => {
style="padding-left: calc( 0.75rem + 24px )"
v-model="email.Receiver"
:disabled="sending"
placeholder="Send to who?"
:placeholder="placeholderTranslate('Send to who?')"
required
id="email_receiver" aria-describedby="emailHelp">
</div>
@ -57,22 +71,48 @@ const sendEmail = async () => {
<i class="bi bi-hash" style="position: absolute; top: 0.4rem; left: 0.75rem;"></i>
<input type="text" class="form-control rounded-0 border-top-0 border-bottom-0"
style="padding-left: calc( 0.75rem + 24px )"
placeholder="Subject"
:placeholder="placeholderTranslate('Subject')"
:disabled="sending"
v-model="email.Subject"
id="email_subject" aria-describedby="emailHelp">
</div>
<textarea class="form-control rounded-bottom-3 rounded-top-0"
v-model="email.Body"
:disabled="sending"
placeholder="Body"
style="min-height: 300px"></textarea>
<div class="row g-0">
<div :class="[livePreview ? 'col-6' : 'col-12']">
<textarea class="form-control rounded-top-0 rounded-bottom-0 font-monospace border-bottom-0"
v-model="email.Body"
:disabled="sending"
:placeholder="placeholderTranslate('Body')"
style="height: 400px; max-height: 400px;"></textarea>
</div>
<div class="col-6" v-if="livePreview">
<PeerShareWithEmailBodyPreview
:body="email.Body"
:selectedPeer="selectedPeer"
>
</PeerShareWithEmailBodyPreview>
</div>
</div>
<div class="card border-top-0 rounded-top-0 rounded-bottom-3 bg-body-tertiary"
style="border: var(--bs-border-width) solid var(--bs-border-color)">
<div class="card-body d-flex flex-column gap-2">
<div class="form-check form-switch ms-auto">
<input class="form-check-input" type="checkbox"
v-model="livePreview"
role="switch" id="livePreview">
<label class="form-check-label" for="livePreview">
<LocaleText t="Live Preview"></LocaleText>
</label>
</div>
</div>
</div>
</div>
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox"
v-model="email.IncludeAttachment"
role="switch" id="includeAttachment" checked>
role="switch" id="includeAttachment">
<label class="form-check-label" for="includeAttachment">
<LocaleText t="Include configuration file as an attachment"></LocaleText>
</label>
@ -102,5 +142,12 @@ const sendEmail = async () => {
</template>
<style scoped>
textarea:focus, input:focus{
box-shadow: none;
border-color: var(--bs-border-color) !important;
}
textarea{
padding: var(--bs-card-spacer-y) var(--bs-card-spacer-x);
}
</style>

View File

@ -0,0 +1,70 @@
<script setup>
import {fetchPost} from "@/utilities/fetch.js";
import {ref, watch} from "vue";
const props = defineProps(['body', 'selectedPeer'])
const preview = ref("")
const error = ref(false)
const errorMsg = ref("")
const getPreview = async () => {
if (props.body){
error.value = false;
preview.value = ("")
await fetchPost('/api/email/previewBody', {
Body: props.body,
ConfigurationName: props.selectedPeer.configuration.Name,
Peer: props.selectedPeer.id
}, (res) => {
if (res.status){
preview.value = res.data;
}else{
errorMsg.value = res.message
}
error.value = !res.status
})
}
}
await getPreview();
let timeout = undefined
watch(() => {
return props.body
}, async () => {
if (timeout === undefined){
timeout = setTimeout(async () => {
await getPreview();
}, 500)
}else{
clearTimeout(timeout)
timeout = setTimeout(async () => {
await getPreview();
}, 500)
}
})
</script>
<template>
<div class="card rounded-0 border-start-0 border-bottom-0 bg-body-secondary" style="height: 400px; overflow: scroll">
<div class="card-body">
<div class="alert alert-danger rounded-3" v-if="error && body">
<i class="bi bi-exclamation-triangle-fill me-2"></i>
<span class="font-monospace">
{{errorMsg}}
</span>
</div>
<div v-if="body"
:class="{'opacity-50': error}"
:innerText="preview"></div>
</div>
</div>
</template>
<style scoped>
.card{
border-color: var(--bs-border-color) !important;
}
</style>

View File

@ -21,7 +21,8 @@ export default {
data(){
return {
dataCopy: undefined,
loading: false
loading: false,
fullscreen: false
}
},
setup(){
@ -107,7 +108,7 @@ export default {
<template>
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll">
<div class="container d-flex h-100 w-100">
<div class="m-auto modal-dialog-centered dashboardModal" style="width: 700px">
<div class="m-auto modal-dialog-centered dashboardModal" :style="[ this.fullscreen ? 'width: 100%' : 'width: 700px']">
<div class="card rounded-3 shadow flex-grow-1">
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
<h4 class="mb-0">
@ -167,7 +168,9 @@ export default {
</button>
<hr>
<Suspense>
<PeerShareWithEmail :selectedPeer="selectedPeer" :dataCopy="dataCopy"></PeerShareWithEmail>
<PeerShareWithEmail
@fullscreen="(f) => { this.fullscreen = f; }"
:selectedPeer="selectedPeer" :dataCopy="dataCopy"></PeerShareWithEmail>
<template #fallback>
<h6 class="text-muted">
<span class="spinner-border me-2 spinner-border-sm" role="status"></span>

View File

@ -7,7 +7,7 @@ const store = DashboardConfigurationStore()
onMounted(() => {
checkEmailReady()
document.querySelectorAll("#emailAccount input, #emailAccount select").forEach(x => {
document.querySelectorAll("#emailAccount input, #emailAccount select, #email_template").forEach(x => {
x.addEventListener("change", async () => {
let id = x.attributes.getNamedItem('id').value;
await fetchPost("/api/updateDashboardConfigurationItem", {
@ -67,7 +67,7 @@ const sendTestEmail = async () => {
</span>
</h6>
</div>
<div class="card-body">
<div class="card-body d-flex flex-column gap-3">
<form @submit="(e) => e.preventDefault(e)" id="emailAccount">
<div class="row gx-2 gy-2">
<div class="col-12 col-lg-4">
@ -152,26 +152,46 @@ const sendTestEmail = async () => {
</div>
</form>
<hr v-if="emailIsReady">
<form
v-if="emailIsReady"
@submit="(e) => {e.preventDefault(); sendTestEmail()}"
class="input-group mb-3">
<input type="email" class="form-control rounded-start-3"
v-model="testEmailReceiver"
:disabled="testing"
placeholder="Test Email Receiver">
<button class="btn bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-end-3"
type="submit" value="Submit"
:disabled="testEmailReceiver.length === 0 || testing"
id="button-addon2">
<i class="bi bi-send me-2" v-if="!testing"></i>
<span class="spinner-border spinner-border-sm me-2" v-else>
<div v-if="emailIsReady">
<label class="text-muted mb-1" for="test_email">
<small class="fw-bold">
<LocaleText t="Send Test Email"></LocaleText>
</small>
</label>
<form
@submit="(e) => {e.preventDefault(); sendTestEmail()}"
class="input-group">
<input type="email" class="form-control rounded-start-3"
id="test_email"
placeholder="john@example.com"
v-model="testEmailReceiver"
:disabled="testing">
<button class="btn bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-end-3"
type="submit" value="Submit"
:disabled="testEmailReceiver.length === 0 || testing"
id="button-addon2">
<i class="bi bi-send me-2" v-if="!testing"></i>
<span class="spinner-border spinner-border-sm me-2" v-else>
<span class="visually-hidden">Loading...</span>
</span>
<LocaleText :t="!testing ? 'Send Test Email':'Sending...'"></LocaleText>
</button>
</form>
<LocaleText :t="!testing ? 'Send':'Sending...'"></LocaleText>
</button>
</form>
</div>
<hr>
<div>
<label class="text-muted mb-1" for="email_template">
<small class="fw-bold">
<LocaleText t="Email Body Template"></LocaleText>
</small>
</label>
<textarea class="form-control rounded-3 font-monospace"
v-model="store.Configuration.Email.email_template"
id="email_template"
style="min-height: 400px"></textarea>
</div>
</div>
</div>
</template>