mirror of
https://github.com/donaldzou/WGDashboard
synced 2025-02-26 05:58:47 +00:00
Email functionality is done
This commit is contained in:
parent
eb7dee013d
commit
40463d9831
68
src/Email.py
Normal file
68
src/Email.py
Normal file
@ -0,0 +1,68 @@
|
||||
import os.path
|
||||
import smtplib
|
||||
from email import encoders
|
||||
from email.header import Header
|
||||
from email.mime.base import MIMEBase
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
from email.mime.text import MIMEText
|
||||
from email.utils import formataddr
|
||||
|
||||
class EmailSender:
|
||||
def __init__(self, DashboardConfig):
|
||||
self.smtp = None
|
||||
self.DashboardConfig = DashboardConfig
|
||||
if not os.path.exists('./attachments'):
|
||||
os.mkdir('./attachments')
|
||||
|
||||
def Server(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "server")[1]
|
||||
|
||||
def Port(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "port")[1]
|
||||
|
||||
def Encryption(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "encryption")[1]
|
||||
|
||||
def Username(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "username")[1]
|
||||
|
||||
def Password(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "email_password")[1]
|
||||
|
||||
def SendFrom(self):
|
||||
return self.DashboardConfig.GetConfig("Email", "send_from")[1]
|
||||
|
||||
def ready(self):
|
||||
return len(self.Server()) > 0 and len(self.Port()) > 0 and len(self.Encryption()) > 0 and len(self.Username()) > 0 and len(self.Password()) > 0
|
||||
|
||||
def send(self, receiver, subject, body, includeAttachment = False, attachmentName = ""):
|
||||
if self.ready():
|
||||
try:
|
||||
self.smtp = smtplib.SMTP(self.Server(), port=int(self.Port()))
|
||||
self.smtp.ehlo()
|
||||
if self.Encryption() == "STARTTLS":
|
||||
self.smtp.starttls()
|
||||
self.smtp.login(self.Username(), self.Password())
|
||||
message = MIMEMultipart()
|
||||
message['Subject'] = subject
|
||||
message['From'] = formataddr((Header(self.SendFrom()).encode(), self.Username()))
|
||||
message["To"] = receiver
|
||||
message.attach(MIMEText(body, "html"))
|
||||
|
||||
if includeAttachment and len(attachmentName) > 0:
|
||||
attachmentPath = os.path.join('./attachments', attachmentName)
|
||||
if os.path.exists(attachmentPath):
|
||||
attachment = MIMEBase("application", "octet-stream")
|
||||
with open(os.path.join('attachments', attachmentName), 'rb') as f:
|
||||
attachment.set_payload(f.read())
|
||||
encoders.encode_base64(attachment)
|
||||
attachment.add_header("Content-Disposition", f"attachment; filename= {attachmentName}",)
|
||||
message.attach(attachment)
|
||||
else:
|
||||
self.smtp.close()
|
||||
return False, "Attachment does not exist"
|
||||
self.smtp.sendmail(self.Username(), receiver, message.as_string())
|
||||
self.smtp.close()
|
||||
return True, None
|
||||
except Exception as e:
|
||||
return False, f"Send failed | Reason: {e}"
|
@ -14,7 +14,7 @@ from Utilities import (
|
||||
ValidateIPAddressesWithRange, ValidateIPAddresses, ValidateDNSAddress,
|
||||
GenerateWireguardPublicKey, GenerateWireguardPrivateKey
|
||||
)
|
||||
|
||||
from Email import EmailSender
|
||||
|
||||
DASHBOARD_VERSION = 'v4.2.0'
|
||||
|
||||
@ -92,6 +92,7 @@ Dashboard Logger Class
|
||||
class DashboardLogger:
|
||||
def __init__(self):
|
||||
self.loggerdb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard_log.db'),
|
||||
isolation_level=None,
|
||||
check_same_thread=False)
|
||||
self.loggerdb.row_factory = sqlite3.Row
|
||||
self.__createLogDatabase()
|
||||
@ -107,20 +108,20 @@ class DashboardLogger:
|
||||
if self.loggerdb.in_transaction:
|
||||
self.loggerdb.commit()
|
||||
|
||||
def log(self, URL: str = "", IP: str = "", Status: str = "true", Message: str = "") -> bool:
|
||||
pass
|
||||
def log(self, URL: str = "", IP: str = "", Status: str = "true", Message: str = "") -> bool:
|
||||
try:
|
||||
with self.loggerdb:
|
||||
loggerdbCursor = self.loggerdb.cursor()
|
||||
loggerdbCursor.execute(
|
||||
"INSERT INTO DashboardLog (LogID, URL, IP, Status, Message) VALUES (?, ?, ?, ?, ?)", (str(uuid.uuid4()), URL, IP, Status, Message,))
|
||||
if self.loggerdb.in_transaction:
|
||||
self.loggerdb.commit()
|
||||
return True
|
||||
loggerdbCursor = self.loggerdb.cursor()
|
||||
loggerdbCursor.execute(
|
||||
"INSERT INTO DashboardLog (LogID, URL, IP, Status, Message) VALUES (?, ?, ?, ?, ?);", (str(uuid.uuid4()), URL, IP, Status, Message,))
|
||||
loggerdbCursor.close()
|
||||
self.loggerdb.commit()
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[WGDashboard] Access Log Error: {str(e)}")
|
||||
return False
|
||||
|
||||
|
||||
|
||||
"""
|
||||
Peer Job Logger
|
||||
"""
|
||||
@ -1878,7 +1879,8 @@ class DashboardConfig:
|
||||
"port": "",
|
||||
"encryption": "",
|
||||
"username": "",
|
||||
"email_password": ""
|
||||
"email_password": "",
|
||||
"send_from": ""
|
||||
},
|
||||
"WireGuardConfiguration": {
|
||||
"autostart": ""
|
||||
@ -1919,22 +1921,22 @@ class DashboardConfig:
|
||||
sqlUpdate("UPDATE DashboardAPIKeys SET ExpiredAt = datetime('now', 'localtime') WHERE Key = ?", (key, ))
|
||||
self.DashboardAPIKeys = self.__getAPIKeys()
|
||||
|
||||
def __configValidation(self, key, value: Any) -> [bool, str]:
|
||||
if type(value) is str and len(value) == 0:
|
||||
def __configValidation(self, section : str, key: str, value: Any) -> [bool, str]:
|
||||
if type(value) is str and len(value) == 0 and section not in ['Email', 'WireGuardConfiguration']:
|
||||
return False, "Field cannot be empty!"
|
||||
if key == "peer_global_dns":
|
||||
if section == "Peers" and key == "peer_global_dns":
|
||||
return ValidateDNSAddress(value)
|
||||
if key == "peer_endpoint_allowed_ip":
|
||||
if section == "Peers" and key == "peer_endpoint_allowed_ip":
|
||||
value = value.split(",")
|
||||
for i in value:
|
||||
try:
|
||||
ipaddress.ip_network(i, strict=False)
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
if key == "wg_conf_path":
|
||||
if section == "Server" and key == "wg_conf_path":
|
||||
if not os.path.exists(value):
|
||||
return False, f"{value} is not a valid path"
|
||||
if key == "password":
|
||||
if section == "Account" and key == "password":
|
||||
if self.GetConfig("Account", "password")[0]:
|
||||
if not self.__checkPassword(
|
||||
value["currentPassword"], self.GetConfig("Account", "password")[1].encode("utf-8")):
|
||||
@ -1954,7 +1956,7 @@ class DashboardConfig:
|
||||
return False, None
|
||||
|
||||
if not init:
|
||||
valid, msg = self.__configValidation(key, value)
|
||||
valid, msg = self.__configValidation(section, key, value)
|
||||
if not valid:
|
||||
return False, msg
|
||||
|
||||
@ -2021,32 +2023,7 @@ class DashboardConfig:
|
||||
if key not in self.hiddenAttribute:
|
||||
the_dict[section][key] = self.GetConfig(section, key)[1]
|
||||
return the_dict
|
||||
|
||||
"""
|
||||
Email Sender
|
||||
"""
|
||||
|
||||
class EmailSender:
|
||||
import smtplib
|
||||
|
||||
def __init__(self):
|
||||
self.Server = DashboardConfig.GetConfig("Email", "server")[1]
|
||||
self.Port = DashboardConfig.GetConfig("Email", "port")[1]
|
||||
self.Encryption = DashboardConfig.GetConfig("Email", "encryption")[1]
|
||||
self.Username = DashboardConfig.GetConfig("Email", "username")[1]
|
||||
self.Password = DashboardConfig.GetConfig("Email", "email_password")[1]
|
||||
|
||||
self.login()
|
||||
|
||||
def ready(self):
|
||||
return self.Server and self.Port and self.Encryption and self.Username and self.Password
|
||||
|
||||
def login(self):
|
||||
if self.ready():
|
||||
self.smtp = smtplib.SMTP(self.Server, port=int(self.Port))
|
||||
if self.Encryption == "STARTTLS":
|
||||
self.smtp.starttls()
|
||||
self.smtp.login(self.Username, self.Password)
|
||||
|
||||
|
||||
"""
|
||||
Database Connection Functions
|
||||
@ -2054,16 +2031,9 @@ Database Connection Functions
|
||||
|
||||
sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
|
||||
sqldb.row_factory = sqlite3.Row
|
||||
# cursor = sqldb.cursor()
|
||||
|
||||
def sqlSelect(statement: str, paramters: tuple = ()) -> sqlite3.Cursor:
|
||||
|
||||
|
||||
# sqldb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'))
|
||||
# sqldb.row_factory = sqlite3.Row
|
||||
# cursor = sqldb.cursor()
|
||||
result = []
|
||||
# with sqldb:
|
||||
try:
|
||||
cursor = sqldb.cursor()
|
||||
result = cursor.execute(statement, paramters)
|
||||
@ -2086,9 +2056,8 @@ def sqlUpdate(statement: str, paramters: tuple = ()) -> sqlite3.Cursor:
|
||||
print("[WGDashboard] SQLite Error:" + str(error) + " | Statement: " + statement)
|
||||
sqldb.close()
|
||||
|
||||
|
||||
DashboardConfig = DashboardConfig()
|
||||
# EmailSender = EmailSender()
|
||||
EmailSender = EmailSender(DashboardConfig)
|
||||
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
|
||||
cors = CORS(app, resources={rf"{APP_PREFIX}/api/*": {
|
||||
"origins": "*",
|
||||
@ -3008,7 +2977,6 @@ def API_Welcome_VerifyTotpLink():
|
||||
DashboardConfig.SetConfig("Account", "enable_totp", "true")
|
||||
return ResponseObject(totp == data['totp'])
|
||||
|
||||
|
||||
@app.post(f'{APP_PREFIX}/api/Welcome_Finish')
|
||||
def API_Welcome_Finish():
|
||||
data = request.get_json()
|
||||
@ -3039,7 +3007,6 @@ class Locale:
|
||||
with open(os.path.join(f"{self.localePath}active_languages.json"), "r") as f:
|
||||
self.activeLanguages = json.loads(''.join(f.readlines()))
|
||||
|
||||
|
||||
def getLanguage(self) -> dict | None:
|
||||
currentLanguage = DashboardConfig.GetConfig("Server", "dashboard_language")[1]
|
||||
if currentLanguage == "en":
|
||||
@ -3056,7 +3023,6 @@ class Locale:
|
||||
else:
|
||||
DashboardConfig.SetConfig("Server", "dashboard_language", lang_id)
|
||||
|
||||
|
||||
Locale = Locale()
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/locale')
|
||||
@ -3075,6 +3041,19 @@ def API_Locale_Update():
|
||||
Locale.updateLanguage(data['lang_id'])
|
||||
return ResponseObject(data=Locale.getLanguage())
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/email/ready')
|
||||
def API_Email_Ready():
|
||||
return ResponseObject(EmailSender.ready())
|
||||
|
||||
@app.post(f'{APP_PREFIX}/api/email/send')
|
||||
def API_Email_Send():
|
||||
data = request.get_json()
|
||||
if "receiver" not in data.keys():
|
||||
return ResponseObject(False, "Please at least specify receiver")
|
||||
|
||||
s, m = EmailSender.send(data.get('receiver'), data.get('subject', ''), data.get('body', ''))
|
||||
return ResponseObject(s, m)
|
||||
|
||||
@app.get(f'{APP_PREFIX}/api/systemStatus')
|
||||
def API_SystemStatus():
|
||||
cpu_percpu = psutil.cpu_percent(interval=0.5, percpu=True)
|
||||
|
@ -1,14 +1,14 @@
|
||||
<script setup>
|
||||
import LocaleText from "@/components/text/localeText.vue";
|
||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||
import {preventDefault} from "ol/events/Event.js";
|
||||
import {onMounted} from "vue";
|
||||
import {fetchPost} from "@/utilities/fetch.js";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
|
||||
const store = DashboardConfigurationStore()
|
||||
|
||||
onMounted(() => {
|
||||
document.querySelectorAll("#emailAccount input").forEach(x => {
|
||||
x.addEventListener("blur", async () => {
|
||||
checkEmailReady()
|
||||
document.querySelectorAll("#emailAccount input, #emailAccount select").forEach(x => {
|
||||
x.addEventListener("change", async () => {
|
||||
let id = x.attributes.getNamedItem('id').value;
|
||||
await fetchPost("/api/updateDashboardConfigurationItem", {
|
||||
section: "Email",
|
||||
@ -22,22 +22,53 @@ onMounted(() => {
|
||||
x.classList.remove('is-valid')
|
||||
x.classList.add('is-invalid')
|
||||
}
|
||||
checkEmailReady()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const emailIsReady = ref(false)
|
||||
const testEmailReceiver = ref("")
|
||||
const testing = ref(false)
|
||||
|
||||
const checkEmailReady = async () => {
|
||||
await fetchGet("/api/email/ready", {}, (res) => {
|
||||
emailIsReady.value = res.status
|
||||
})
|
||||
}
|
||||
|
||||
const sendTestEmail = async () => {
|
||||
testing.value = true
|
||||
await fetchPost("/api/email/send", {
|
||||
receiver: testEmailReceiver.value,
|
||||
subject: "WGDashboard Testing Email",
|
||||
body: "Test 1, 2, 3! Hello World :)"
|
||||
}, (res) => {
|
||||
if (res.status){
|
||||
store.newMessage("Server", "Test email sent successfully!", "success")
|
||||
}else{
|
||||
store.newMessage("Server", `Test email sent failed! Reason: ${res.message}`, "danger")
|
||||
}
|
||||
testing.value = false
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="card" id="emailAccount">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="my-2">
|
||||
<h6 class="my-2 d-flex">
|
||||
<LocaleText t="Email Account"></LocaleText>
|
||||
<span class="text-success ms-auto" v-if="emailIsReady">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>
|
||||
<LocaleText t="Ready"></LocaleText>
|
||||
</span>
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form @submit="(e) => preventDefault(e)">
|
||||
<form @submit="(e) => e.preventDefault(e)" id="emailAccount">
|
||||
<div class="row gx-2 gy-2">
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="form-group">
|
||||
@ -70,12 +101,19 @@ onMounted(() => {
|
||||
<LocaleText t="Encryption"></LocaleText>
|
||||
</small></strong>
|
||||
</label>
|
||||
<input id="encryption"
|
||||
v-model="store.Configuration.Email.encryption"
|
||||
type="text" class="form-control">
|
||||
<select class="form-select"
|
||||
v-model="store.Configuration.Email.encryption"
|
||||
id="encryption">
|
||||
<option value="STARTTLS">
|
||||
STARTTLS
|
||||
</option>
|
||||
<option value="NOTLS">
|
||||
<LocaleText t="No Encryption"></LocaleText>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="form-group">
|
||||
<label for="username" class="text-muted mb-1">
|
||||
<strong><small>
|
||||
@ -87,7 +125,7 @@ onMounted(() => {
|
||||
type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6">
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="form-group">
|
||||
<label for="email_password" class="text-muted mb-1">
|
||||
<strong><small>
|
||||
@ -99,8 +137,39 @@ onMounted(() => {
|
||||
type="password" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-12 col-lg-4">
|
||||
<div class="form-group">
|
||||
<label for="send_from" class="text-muted mb-1">
|
||||
<strong><small>
|
||||
<LocaleText t="Send From"></LocaleText>
|
||||
</small></strong>
|
||||
</label>
|
||||
<input id="send_from"
|
||||
v-model="store.Configuration.Email.send_from"
|
||||
type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<form
|
||||
@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>
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</span>
|
||||
<LocaleText :t="!testing ? 'Send Test Email':'Sending...'"></LocaleText>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -60,7 +60,6 @@ export const fetchPost = async (url, body, callback) => {
|
||||
if (!x.ok){
|
||||
if (x.status !== 200){
|
||||
if (x.status === 401){
|
||||
|
||||
store.newMessage("WGDashboard", "Sign in session ended, please sign in again", "warning")
|
||||
}
|
||||
throw new Error(x.statusText)
|
||||
|
Loading…
Reference in New Issue
Block a user