Initial import: shopdb-flask multi-site asset platform
Flask + Vue rewrite of the classic-ASP shopdb, built as a plugin framework so sister GE Aerospace sites can adopt it. Product version 0.7.0-pre-0.8.0, plugin contract 0.11.0, 966 backend tests. Key entry points: - docs/INSTALL-WINDOWS-IIS.md - tested Windows + IIS install runbook (own-site and subpath deployment methods) - docs/PILOT-DEPLOY.md - production pilot: stand up + import + verify - docs/IMPORT-API.md - schema-agnostic legacy data import contract - scripts/site_imports/wjf/ - West Jefferson reference import loader - docs/adr/ - twelve accepted architecture decision records
This commit is contained in:
216
frontend/src/views/settings/EmailSettings.vue
Normal file
216
frontend/src/views/settings/EmailSettings.vue
Normal file
@@ -0,0 +1,216 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="page-header">
|
||||
<h2>Email / SMTP</h2>
|
||||
</div>
|
||||
|
||||
<div class="section-card">
|
||||
<div class="setting-group">
|
||||
<h3>Email Notifications</h3>
|
||||
<p class="setting-description">
|
||||
Configure SMTP settings to enable email notifications for alerts, toner reports,
|
||||
and other system notifications.
|
||||
</p>
|
||||
|
||||
<div class="setting-row">
|
||||
<label class="toggle-label">
|
||||
<span>Enable Email Notifications</span>
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ active: settings.smtp_enabled }"
|
||||
@click="toggleSetting('smtp_enabled')"
|
||||
:disabled="saving"
|
||||
>
|
||||
<span class="toggle-slider"></span>
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<template v-if="settings.smtp_enabled">
|
||||
<div class="settings-grid">
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>SMTP Host</span>
|
||||
<input
|
||||
type="text"
|
||||
v-model="settings.smtp_host"
|
||||
placeholder="smtp.example.com"
|
||||
@blur="saveSetting('smtp_host', settings.smtp_host)"
|
||||
:disabled="saving"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>SMTP Port</span>
|
||||
<input
|
||||
type="number"
|
||||
v-model="settings.smtp_port"
|
||||
placeholder="587"
|
||||
@blur="saveSetting('smtp_port', settings.smtp_port)"
|
||||
:disabled="saving"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>Username</span>
|
||||
<input
|
||||
type="text"
|
||||
v-model="settings.smtp_username"
|
||||
placeholder="username"
|
||||
@blur="saveSetting('smtp_username', settings.smtp_username)"
|
||||
:disabled="saving"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>Password</span>
|
||||
<input
|
||||
type="password"
|
||||
v-model="settings.smtp_password"
|
||||
placeholder="password"
|
||||
@blur="saveSetting('smtp_password', settings.smtp_password)"
|
||||
:disabled="saving"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<label class="toggle-label">
|
||||
<span>Use TLS Encryption</span>
|
||||
<button
|
||||
class="toggle-btn"
|
||||
:class="{ active: settings.smtp_use_tls }"
|
||||
@click="toggleSetting('smtp_use_tls')"
|
||||
:disabled="saving"
|
||||
>
|
||||
<span class="toggle-slider"></span>
|
||||
</button>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="settings-grid">
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>From Address</span>
|
||||
<input
|
||||
type="email"
|
||||
v-model="settings.smtp_from_address"
|
||||
placeholder="noreply@example.com"
|
||||
@blur="saveSetting('smtp_from_address', settings.smtp_from_address)"
|
||||
:disabled="saving"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="setting-row">
|
||||
<label>
|
||||
<span>From Name</span>
|
||||
<input
|
||||
type="text"
|
||||
v-model="settings.smtp_from_name"
|
||||
placeholder="ShopDB"
|
||||
@blur="saveSetting('smtp_from_name', settings.smtp_from_name)"
|
||||
:disabled="saving"
|
||||
>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-row full-width">
|
||||
<label>
|
||||
<span>Default Alert Recipients</span>
|
||||
<input
|
||||
type="text"
|
||||
v-model="settings.alert_recipients"
|
||||
placeholder="user1@example.com, user2@example.com"
|
||||
@blur="saveSetting('alert_recipients', settings.alert_recipients)"
|
||||
:disabled="saving"
|
||||
>
|
||||
<small class="input-hint">Comma-separated list of email addresses</small>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="status-indicator">
|
||||
<span class="status-dot" :class="smtpStatus"></span>
|
||||
<span>{{ smtpMessage }}</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="test-btn"
|
||||
@click="testEmail"
|
||||
:disabled="saving || testingEmail || !canTestEmail"
|
||||
>
|
||||
{{ testingEmail ? 'Sending...' : 'Send Test Email' }}
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="error" class="error-message">{{ error }}</div>
|
||||
<div v-if="success" class="settings-success">{{ success }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import { useSystemSettings } from '../../composables/systemSettings'
|
||||
import { apiError } from '../../utils/apiError'
|
||||
import { settingsApi } from '../../api'
|
||||
|
||||
const {
|
||||
settings, saving, error, success,
|
||||
loadSettings, saveSetting, toggleSetting,
|
||||
} = useSystemSettings()
|
||||
|
||||
const testingEmail = ref(false)
|
||||
|
||||
// Per-page connection status (stays with the page, not the composable).
|
||||
const smtpStatus = computed(() => {
|
||||
if (!settings.smtp_enabled) return 'inactive'
|
||||
if (!settings.smtp_host || !settings.smtp_from_address) return 'warning'
|
||||
return 'pending'
|
||||
})
|
||||
|
||||
const smtpMessage = computed(() => {
|
||||
if (!settings.smtp_enabled) return 'Disabled'
|
||||
if (!settings.smtp_host) return 'SMTP host not configured'
|
||||
if (!settings.smtp_from_address) return 'From address not configured'
|
||||
return 'Configured - use test button to verify'
|
||||
})
|
||||
|
||||
const canTestEmail = computed(() => {
|
||||
return settings.smtp_host && settings.smtp_from_address && settings.alert_recipients
|
||||
})
|
||||
|
||||
async function testEmail() {
|
||||
testingEmail.value = true
|
||||
error.value = ''
|
||||
success.value = ''
|
||||
try {
|
||||
// Send to the configured Alert Recipients (the endpoint splits the list).
|
||||
const response = await settingsApi.testEmail(settings.alert_recipients)
|
||||
const result = response.data?.data || {}
|
||||
if (result.sent) {
|
||||
success.value = `Test email sent to ${settings.alert_recipients}`
|
||||
} else {
|
||||
error.value = result.error
|
||||
? `Test email failed: ${result.error}`
|
||||
: (response.data?.message || 'Email is not configured')
|
||||
}
|
||||
} catch (e) {
|
||||
error.value = apiError(e, 'Failed to send test email')
|
||||
} finally {
|
||||
testingEmail.value = false
|
||||
setTimeout(() => { success.value = '' }, 4000)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadSettings)
|
||||
</script>
|
||||
Reference in New Issue
Block a user