mirror of
https://github.com/donaldzou/WGDashboard.git
synced 2025-07-18 02:56:59 +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,
|
ValidateIPAddressesWithRange, ValidateIPAddresses, ValidateDNSAddress,
|
||||||
GenerateWireguardPublicKey, GenerateWireguardPrivateKey
|
GenerateWireguardPublicKey, GenerateWireguardPrivateKey
|
||||||
)
|
)
|
||||||
|
from Email import EmailSender
|
||||||
|
|
||||||
DASHBOARD_VERSION = 'v4.2.0'
|
DASHBOARD_VERSION = 'v4.2.0'
|
||||||
|
|
||||||
@ -92,6 +92,7 @@ Dashboard Logger Class
|
|||||||
class DashboardLogger:
|
class DashboardLogger:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.loggerdb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard_log.db'),
|
self.loggerdb = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard_log.db'),
|
||||||
|
isolation_level=None,
|
||||||
check_same_thread=False)
|
check_same_thread=False)
|
||||||
self.loggerdb.row_factory = sqlite3.Row
|
self.loggerdb.row_factory = sqlite3.Row
|
||||||
self.__createLogDatabase()
|
self.__createLogDatabase()
|
||||||
@ -107,20 +108,20 @@ class DashboardLogger:
|
|||||||
if self.loggerdb.in_transaction:
|
if self.loggerdb.in_transaction:
|
||||||
self.loggerdb.commit()
|
self.loggerdb.commit()
|
||||||
|
|
||||||
def log(self, URL: str = "", IP: str = "", Status: str = "true", Message: str = "") -> bool:
|
def log(self, URL: str = "", IP: str = "", Status: str = "true", Message: str = "") -> bool:
|
||||||
pass
|
|
||||||
try:
|
try:
|
||||||
with self.loggerdb:
|
loggerdbCursor = self.loggerdb.cursor()
|
||||||
loggerdbCursor = self.loggerdb.cursor()
|
loggerdbCursor.execute(
|
||||||
loggerdbCursor.execute(
|
"INSERT INTO DashboardLog (LogID, URL, IP, Status, Message) VALUES (?, ?, ?, ?, ?);", (str(uuid.uuid4()), URL, IP, Status, Message,))
|
||||||
"INSERT INTO DashboardLog (LogID, URL, IP, Status, Message) VALUES (?, ?, ?, ?, ?)", (str(uuid.uuid4()), URL, IP, Status, Message,))
|
loggerdbCursor.close()
|
||||||
if self.loggerdb.in_transaction:
|
self.loggerdb.commit()
|
||||||
self.loggerdb.commit()
|
return True
|
||||||
return True
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[WGDashboard] Access Log Error: {str(e)}")
|
print(f"[WGDashboard] Access Log Error: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Peer Job Logger
|
Peer Job Logger
|
||||||
"""
|
"""
|
||||||
@ -1878,7 +1879,8 @@ class DashboardConfig:
|
|||||||
"port": "",
|
"port": "",
|
||||||
"encryption": "",
|
"encryption": "",
|
||||||
"username": "",
|
"username": "",
|
||||||
"email_password": ""
|
"email_password": "",
|
||||||
|
"send_from": ""
|
||||||
},
|
},
|
||||||
"WireGuardConfiguration": {
|
"WireGuardConfiguration": {
|
||||||
"autostart": ""
|
"autostart": ""
|
||||||
@ -1919,22 +1921,22 @@ class DashboardConfig:
|
|||||||
sqlUpdate("UPDATE DashboardAPIKeys SET ExpiredAt = datetime('now', 'localtime') WHERE Key = ?", (key, ))
|
sqlUpdate("UPDATE DashboardAPIKeys SET ExpiredAt = datetime('now', 'localtime') WHERE Key = ?", (key, ))
|
||||||
self.DashboardAPIKeys = self.__getAPIKeys()
|
self.DashboardAPIKeys = self.__getAPIKeys()
|
||||||
|
|
||||||
def __configValidation(self, key, value: Any) -> [bool, str]:
|
def __configValidation(self, section : str, key: str, value: Any) -> [bool, str]:
|
||||||
if type(value) is str and len(value) == 0:
|
if type(value) is str and len(value) == 0 and section not in ['Email', 'WireGuardConfiguration']:
|
||||||
return False, "Field cannot be empty!"
|
return False, "Field cannot be empty!"
|
||||||
if key == "peer_global_dns":
|
if section == "Peers" and key == "peer_global_dns":
|
||||||
return ValidateDNSAddress(value)
|
return ValidateDNSAddress(value)
|
||||||
if key == "peer_endpoint_allowed_ip":
|
if section == "Peers" and key == "peer_endpoint_allowed_ip":
|
||||||
value = value.split(",")
|
value = value.split(",")
|
||||||
for i in value:
|
for i in value:
|
||||||
try:
|
try:
|
||||||
ipaddress.ip_network(i, strict=False)
|
ipaddress.ip_network(i, strict=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
if key == "wg_conf_path":
|
if section == "Server" and key == "wg_conf_path":
|
||||||
if not os.path.exists(value):
|
if not os.path.exists(value):
|
||||||
return False, f"{value} is not a valid path"
|
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 self.GetConfig("Account", "password")[0]:
|
||||||
if not self.__checkPassword(
|
if not self.__checkPassword(
|
||||||
value["currentPassword"], self.GetConfig("Account", "password")[1].encode("utf-8")):
|
value["currentPassword"], self.GetConfig("Account", "password")[1].encode("utf-8")):
|
||||||
@ -1954,7 +1956,7 @@ class DashboardConfig:
|
|||||||
return False, None
|
return False, None
|
||||||
|
|
||||||
if not init:
|
if not init:
|
||||||
valid, msg = self.__configValidation(key, value)
|
valid, msg = self.__configValidation(section, key, value)
|
||||||
if not valid:
|
if not valid:
|
||||||
return False, msg
|
return False, msg
|
||||||
|
|
||||||
@ -2021,32 +2023,7 @@ class DashboardConfig:
|
|||||||
if key not in self.hiddenAttribute:
|
if key not in self.hiddenAttribute:
|
||||||
the_dict[section][key] = self.GetConfig(section, key)[1]
|
the_dict[section][key] = self.GetConfig(section, key)[1]
|
||||||
return the_dict
|
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
|
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 = sqlite3.connect(os.path.join(CONFIGURATION_PATH, 'db', 'wgdashboard.db'), check_same_thread=False)
|
||||||
sqldb.row_factory = sqlite3.Row
|
sqldb.row_factory = sqlite3.Row
|
||||||
# cursor = sqldb.cursor()
|
|
||||||
|
|
||||||
def sqlSelect(statement: str, paramters: tuple = ()) -> sqlite3.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 = []
|
result = []
|
||||||
# with sqldb:
|
|
||||||
try:
|
try:
|
||||||
cursor = sqldb.cursor()
|
cursor = sqldb.cursor()
|
||||||
result = cursor.execute(statement, paramters)
|
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)
|
print("[WGDashboard] SQLite Error:" + str(error) + " | Statement: " + statement)
|
||||||
sqldb.close()
|
sqldb.close()
|
||||||
|
|
||||||
|
|
||||||
DashboardConfig = DashboardConfig()
|
DashboardConfig = DashboardConfig()
|
||||||
# EmailSender = EmailSender()
|
EmailSender = EmailSender(DashboardConfig)
|
||||||
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
|
_, APP_PREFIX = DashboardConfig.GetConfig("Server", "app_prefix")
|
||||||
cors = CORS(app, resources={rf"{APP_PREFIX}/api/*": {
|
cors = CORS(app, resources={rf"{APP_PREFIX}/api/*": {
|
||||||
"origins": "*",
|
"origins": "*",
|
||||||
@ -3008,7 +2977,6 @@ def API_Welcome_VerifyTotpLink():
|
|||||||
DashboardConfig.SetConfig("Account", "enable_totp", "true")
|
DashboardConfig.SetConfig("Account", "enable_totp", "true")
|
||||||
return ResponseObject(totp == data['totp'])
|
return ResponseObject(totp == data['totp'])
|
||||||
|
|
||||||
|
|
||||||
@app.post(f'{APP_PREFIX}/api/Welcome_Finish')
|
@app.post(f'{APP_PREFIX}/api/Welcome_Finish')
|
||||||
def API_Welcome_Finish():
|
def API_Welcome_Finish():
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
@ -3039,7 +3007,6 @@ class Locale:
|
|||||||
with open(os.path.join(f"{self.localePath}active_languages.json"), "r") as f:
|
with open(os.path.join(f"{self.localePath}active_languages.json"), "r") as f:
|
||||||
self.activeLanguages = json.loads(''.join(f.readlines()))
|
self.activeLanguages = json.loads(''.join(f.readlines()))
|
||||||
|
|
||||||
|
|
||||||
def getLanguage(self) -> dict | None:
|
def getLanguage(self) -> dict | None:
|
||||||
currentLanguage = DashboardConfig.GetConfig("Server", "dashboard_language")[1]
|
currentLanguage = DashboardConfig.GetConfig("Server", "dashboard_language")[1]
|
||||||
if currentLanguage == "en":
|
if currentLanguage == "en":
|
||||||
@ -3056,7 +3023,6 @@ class Locale:
|
|||||||
else:
|
else:
|
||||||
DashboardConfig.SetConfig("Server", "dashboard_language", lang_id)
|
DashboardConfig.SetConfig("Server", "dashboard_language", lang_id)
|
||||||
|
|
||||||
|
|
||||||
Locale = Locale()
|
Locale = Locale()
|
||||||
|
|
||||||
@app.get(f'{APP_PREFIX}/api/locale')
|
@app.get(f'{APP_PREFIX}/api/locale')
|
||||||
@ -3075,6 +3041,19 @@ def API_Locale_Update():
|
|||||||
Locale.updateLanguage(data['lang_id'])
|
Locale.updateLanguage(data['lang_id'])
|
||||||
return ResponseObject(data=Locale.getLanguage())
|
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')
|
@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,14 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import LocaleText from "@/components/text/localeText.vue";
|
import LocaleText from "@/components/text/localeText.vue";
|
||||||
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
import {DashboardConfigurationStore} from "@/stores/DashboardConfigurationStore.js";
|
||||||
import {preventDefault} from "ol/events/Event.js";
|
import {onMounted, ref} from "vue";
|
||||||
import {onMounted} from "vue";
|
import {fetchGet, fetchPost} from "@/utilities/fetch.js";
|
||||||
import {fetchPost} from "@/utilities/fetch.js";
|
|
||||||
const store = DashboardConfigurationStore()
|
const store = DashboardConfigurationStore()
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.querySelectorAll("#emailAccount input").forEach(x => {
|
checkEmailReady()
|
||||||
x.addEventListener("blur", async () => {
|
document.querySelectorAll("#emailAccount input, #emailAccount select").forEach(x => {
|
||||||
|
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", {
|
||||||
section: "Email",
|
section: "Email",
|
||||||
@ -22,22 +22,53 @@ onMounted(() => {
|
|||||||
x.classList.remove('is-valid')
|
x.classList.remove('is-valid')
|
||||||
x.classList.add('is-invalid')
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="card" id="emailAccount">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h6 class="my-2">
|
<h6 class="my-2 d-flex">
|
||||||
<LocaleText t="Email Account"></LocaleText>
|
<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>
|
</h6>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<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="row gx-2 gy-2">
|
||||||
<div class="col-12 col-lg-4">
|
<div class="col-12 col-lg-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
@ -70,12 +101,19 @@ onMounted(() => {
|
|||||||
<LocaleText t="Encryption"></LocaleText>
|
<LocaleText t="Encryption"></LocaleText>
|
||||||
</small></strong>
|
</small></strong>
|
||||||
</label>
|
</label>
|
||||||
<input id="encryption"
|
<select class="form-select"
|
||||||
v-model="store.Configuration.Email.encryption"
|
v-model="store.Configuration.Email.encryption"
|
||||||
type="text" class="form-control">
|
id="encryption">
|
||||||
|
<option value="STARTTLS">
|
||||||
|
STARTTLS
|
||||||
|
</option>
|
||||||
|
<option value="NOTLS">
|
||||||
|
<LocaleText t="No Encryption"></LocaleText>
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username" class="text-muted mb-1">
|
<label for="username" class="text-muted mb-1">
|
||||||
<strong><small>
|
<strong><small>
|
||||||
@ -87,7 +125,7 @@ onMounted(() => {
|
|||||||
type="text" class="form-control">
|
type="text" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-12 col-lg-6">
|
<div class="col-12 col-lg-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="email_password" class="text-muted mb-1">
|
<label for="email_password" class="text-muted mb-1">
|
||||||
<strong><small>
|
<strong><small>
|
||||||
@ -99,8 +137,39 @@ onMounted(() => {
|
|||||||
type="password" class="form-control">
|
type="password" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</form>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -60,7 +60,6 @@ export const fetchPost = async (url, body, callback) => {
|
|||||||
if (!x.ok){
|
if (!x.ok){
|
||||||
if (x.status !== 200){
|
if (x.status !== 200){
|
||||||
if (x.status === 401){
|
if (x.status === 401){
|
||||||
|
|
||||||
store.newMessage("WGDashboard", "Sign in session ended, please sign in again", "warning")
|
store.newMessage("WGDashboard", "Sign in session ended, please sign in again", "warning")
|
||||||
}
|
}
|
||||||
throw new Error(x.statusText)
|
throw new Error(x.statusText)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user