mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-07-07 05:37:00 +00:00
Finalized the email function
This commit is contained in:
parent
5d84b61f18
commit
4f92a7edf3
@ -1883,7 +1883,8 @@ class DashboardConfig:
|
|||||||
"encryption": "",
|
"encryption": "",
|
||||||
"username": "",
|
"username": "",
|
||||||
"email_password": "",
|
"email_password": "",
|
||||||
"send_from": ""
|
"send_from": "",
|
||||||
|
"email_template": ""
|
||||||
},
|
},
|
||||||
"WireGuardConfiguration": {
|
"WireGuardConfiguration": {
|
||||||
"autostart": ""
|
"autostart": ""
|
||||||
@ -3053,15 +3054,14 @@ def API_Email_Send():
|
|||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if "Receiver" not in data.keys():
|
if "Receiver" not in data.keys():
|
||||||
return ResponseObject(False, "Please at least specify receiver")
|
return ResponseObject(False, "Please at least specify receiver")
|
||||||
|
|
||||||
body = data.get('Body', '')
|
body = data.get('Body', '')
|
||||||
|
download = None
|
||||||
if "ConfigurationName" in data.keys() and "Peer" in data.keys():
|
if "ConfigurationName" in data.keys() and "Peer" in data.keys():
|
||||||
if data.get('ConfigurationName') in WireguardConfigurations.keys():
|
if data.get('ConfigurationName') in WireguardConfigurations.keys():
|
||||||
configuration = WireguardConfigurations.get(data.get('ConfigurationName'))
|
configuration = WireguardConfigurations.get(data.get('ConfigurationName'))
|
||||||
attachmentName = ""
|
attachmentName = ""
|
||||||
if configuration is not None:
|
if configuration is not None:
|
||||||
fp, p = configuration.searchPeer(data.get('Peer'))
|
fp, p = configuration.searchPeer(data.get('Peer'))
|
||||||
print(fp)
|
|
||||||
if fp:
|
if fp:
|
||||||
template = Template(body)
|
template = Template(body)
|
||||||
download = p.downloadPeer()
|
download = p.downloadPeer()
|
||||||
@ -3073,9 +3073,34 @@ def API_Email_Send():
|
|||||||
attachmentName = f'{u}.conf'
|
attachmentName = f'{u}.conf'
|
||||||
|
|
||||||
s, m = EmailSender.send(data.get('Receiver'), data.get('Subject', ''), body,
|
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)
|
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')
|
@app.get(f'{APP_PREFIX}/api/systemStatus')
|
||||||
def API_SystemStatus():
|
def API_SystemStatus():
|
||||||
cpu_percpu = psutil.cpu_percent(interval=0.5, percpu=True)
|
cpu_percpu = psutil.cpu_percent(interval=0.5, percpu=True)
|
||||||
|
@ -1,23 +1,28 @@
|
|||||||
<script setup async>
|
<script setup async>
|
||||||
import LocaleText from "@/components/text/localeText.vue";
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
|
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 {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 props = defineProps(['dataCopy', 'selectedPeer'])
|
||||||
const emailIsReady = ref(false)
|
const emailIsReady = ref(false)
|
||||||
await fetchGet("/api/email/ready", {}, (res) => {
|
await fetchGet("/api/email/ready", {}, (res) => {
|
||||||
emailIsReady.value = res.status
|
emailIsReady.value = res.status
|
||||||
})
|
})
|
||||||
|
const store = DashboardConfigurationStore()
|
||||||
const email = reactive({
|
const email = reactive({
|
||||||
Receiver: "",
|
Receiver: "",
|
||||||
Body: "",
|
Body: store.Configuration.Email.email_template,
|
||||||
Subject: "",
|
Subject: "",
|
||||||
IncludeAttachment: false,
|
IncludeAttachment: false,
|
||||||
ConfigurationName: props.selectedPeer.configuration.Name,
|
ConfigurationName: props.selectedPeer.configuration.Name,
|
||||||
Peer: props.selectedPeer.id
|
Peer: props.selectedPeer.id
|
||||||
})
|
})
|
||||||
const store = DashboardConfigurationStore()
|
|
||||||
|
const livePreview = ref(false)
|
||||||
|
|
||||||
const sending = ref(false)
|
const sending = ref(false)
|
||||||
|
|
||||||
const sendEmail = async () => {
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -49,7 +63,7 @@ const sendEmail = async () => {
|
|||||||
style="padding-left: calc( 0.75rem + 24px )"
|
style="padding-left: calc( 0.75rem + 24px )"
|
||||||
v-model="email.Receiver"
|
v-model="email.Receiver"
|
||||||
:disabled="sending"
|
:disabled="sending"
|
||||||
placeholder="Send to who?"
|
:placeholder="placeholderTranslate('Send to who?')"
|
||||||
required
|
required
|
||||||
id="email_receiver" aria-describedby="emailHelp">
|
id="email_receiver" aria-describedby="emailHelp">
|
||||||
</div>
|
</div>
|
||||||
@ -57,22 +71,48 @@ const sendEmail = async () => {
|
|||||||
<i class="bi bi-hash" style="position: absolute; top: 0.4rem; left: 0.75rem;"></i>
|
<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"
|
<input type="text" class="form-control rounded-0 border-top-0 border-bottom-0"
|
||||||
style="padding-left: calc( 0.75rem + 24px )"
|
style="padding-left: calc( 0.75rem + 24px )"
|
||||||
placeholder="Subject"
|
:placeholder="placeholderTranslate('Subject')"
|
||||||
:disabled="sending"
|
:disabled="sending"
|
||||||
v-model="email.Subject"
|
v-model="email.Subject"
|
||||||
id="email_subject" aria-describedby="emailHelp">
|
id="email_subject" aria-describedby="emailHelp">
|
||||||
</div>
|
</div>
|
||||||
<textarea class="form-control rounded-bottom-3 rounded-top-0"
|
<div class="row g-0">
|
||||||
v-model="email.Body"
|
<div :class="[livePreview ? 'col-6' : 'col-12']">
|
||||||
:disabled="sending"
|
<textarea class="form-control rounded-top-0 rounded-bottom-0 font-monospace border-bottom-0"
|
||||||
placeholder="Body"
|
v-model="email.Body"
|
||||||
style="min-height: 300px"></textarea>
|
: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>
|
||||||
<div class="form-check form-switch">
|
<div class="form-check form-switch">
|
||||||
<input class="form-check-input" type="checkbox"
|
<input class="form-check-input" type="checkbox"
|
||||||
v-model="email.IncludeAttachment"
|
v-model="email.IncludeAttachment"
|
||||||
role="switch" id="includeAttachment" checked>
|
role="switch" id="includeAttachment">
|
||||||
<label class="form-check-label" for="includeAttachment">
|
<label class="form-check-label" for="includeAttachment">
|
||||||
<LocaleText t="Include configuration file as an attachment"></LocaleText>
|
<LocaleText t="Include configuration file as an attachment"></LocaleText>
|
||||||
</label>
|
</label>
|
||||||
@ -102,5 +142,12 @@ const sendEmail = async () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<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>
|
</style>
|
@ -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>
|
@ -21,7 +21,8 @@ export default {
|
|||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
dataCopy: undefined,
|
dataCopy: undefined,
|
||||||
loading: false
|
loading: false,
|
||||||
|
fullscreen: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setup(){
|
setup(){
|
||||||
@ -107,7 +108,7 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div class="peerSettingContainer w-100 h-100 position-absolute top-0 start-0 overflow-y-scroll">
|
<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="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 rounded-3 shadow flex-grow-1">
|
||||||
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
|
<div class="card-header bg-transparent d-flex align-items-center gap-2 border-0 p-4">
|
||||||
<h4 class="mb-0">
|
<h4 class="mb-0">
|
||||||
@ -167,7 +168,9 @@ export default {
|
|||||||
</button>
|
</button>
|
||||||
<hr>
|
<hr>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<PeerShareWithEmail :selectedPeer="selectedPeer" :dataCopy="dataCopy"></PeerShareWithEmail>
|
<PeerShareWithEmail
|
||||||
|
@fullscreen="(f) => { this.fullscreen = f; }"
|
||||||
|
:selectedPeer="selectedPeer" :dataCopy="dataCopy"></PeerShareWithEmail>
|
||||||
<template #fallback>
|
<template #fallback>
|
||||||
<h6 class="text-muted">
|
<h6 class="text-muted">
|
||||||
<span class="spinner-border me-2 spinner-border-sm" role="status"></span>
|
<span class="spinner-border me-2 spinner-border-sm" role="status"></span>
|
||||||
|
@ -7,7 +7,7 @@ const store = DashboardConfigurationStore()
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
checkEmailReady()
|
checkEmailReady()
|
||||||
document.querySelectorAll("#emailAccount input, #emailAccount select").forEach(x => {
|
document.querySelectorAll("#emailAccount input, #emailAccount select, #email_template").forEach(x => {
|
||||||
x.addEventListener("change", async () => {
|
x.addEventListener("change", async () => {
|
||||||
let id = x.attributes.getNamedItem('id').value;
|
let id = x.attributes.getNamedItem('id').value;
|
||||||
await fetchPost("/api/updateDashboardConfigurationItem", {
|
await fetchPost("/api/updateDashboardConfigurationItem", {
|
||||||
@ -67,7 +67,7 @@ const sendTestEmail = async () => {
|
|||||||
</span>
|
</span>
|
||||||
</h6>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body d-flex flex-column gap-3">
|
||||||
<form @submit="(e) => e.preventDefault(e)" id="emailAccount">
|
<form @submit="(e) => e.preventDefault(e)" id="emailAccount">
|
||||||
<div class="row gx-2 gy-2">
|
<div class="row gx-2 gy-2">
|
||||||
<div class="col-12 col-lg-4">
|
<div class="col-12 col-lg-4">
|
||||||
@ -152,26 +152,46 @@ const sendTestEmail = async () => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<hr v-if="emailIsReady">
|
<hr v-if="emailIsReady">
|
||||||
<form
|
<div v-if="emailIsReady">
|
||||||
v-if="emailIsReady"
|
<label class="text-muted mb-1" for="test_email">
|
||||||
@submit="(e) => {e.preventDefault(); sendTestEmail()}"
|
<small class="fw-bold">
|
||||||
class="input-group mb-3">
|
<LocaleText t="Send Test Email"></LocaleText>
|
||||||
|
</small>
|
||||||
|
</label>
|
||||||
|
<form
|
||||||
|
|
||||||
<input type="email" class="form-control rounded-start-3"
|
@submit="(e) => {e.preventDefault(); sendTestEmail()}"
|
||||||
v-model="testEmailReceiver"
|
class="input-group">
|
||||||
:disabled="testing"
|
|
||||||
placeholder="Test Email Receiver">
|
<input type="email" class="form-control rounded-start-3"
|
||||||
<button class="btn bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-end-3"
|
id="test_email"
|
||||||
type="submit" value="Submit"
|
placeholder="john@example.com"
|
||||||
:disabled="testEmailReceiver.length === 0 || testing"
|
v-model="testEmailReceiver"
|
||||||
id="button-addon2">
|
:disabled="testing">
|
||||||
<i class="bi bi-send me-2" v-if="!testing"></i>
|
<button class="btn bg-primary-subtle text-primary-emphasis border-primary-subtle rounded-end-3"
|
||||||
<span class="spinner-border spinner-border-sm me-2" v-else>
|
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 class="visually-hidden">Loading...</span>
|
||||||
</span>
|
</span>
|
||||||
<LocaleText :t="!testing ? 'Send Test Email':'Sending...'"></LocaleText>
|
<LocaleText :t="!testing ? 'Send':'Sending...'"></LocaleText>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user