botserver/web/desktop/account.html
Rodrigo Rodriguez (Pragmatismo) d0563391b6 ``` Add comprehensive email account management and user settings
interface

Implements multi-user authentication system with email account
management, profile settings, drive configuration, and security
controls. Includes database migrations for user accounts, email
credentials, preferences, and session management. Frontend provides
intuitive UI for adding IMAP/SMTP accounts with provider presets and
connection testing. Backend supports per-user vector databases for email
and file indexing with Zitadel SSO integration and automatic workspace
initialization. ```
2025-11-21 09:28:35 -03:00

1073 lines
23 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<div class="account-layout" x-data="accountApp()" x-cloak>
<!-- Header -->
<div class="account-header">
<h1>Account Settings</h1>
<p class="subtitle">Manage your email accounts, preferences, and profile</h1></p>
</div>
<!-- Navigation Tabs -->
<div class="tabs">
<button
class="tab"
:class="{ active: currentTab === 'profile' }"
@click="currentTab = 'profile'">
👤 Profile
</button>
<button
class="tab"
:class="{ active: currentTab === 'email' }"
@click="currentTab = 'email'">
📧 Email Accounts
</button>
<button
class="tab"
:class="{ active: currentTab === 'drive' }"
@click="currentTab = 'drive'">
💾 Drive Settings
</button>
<button
class="tab"
:class="{ active: currentTab === 'security' }"
@click="currentTab = 'security'">
🔒 Security
</button>
</div>
<!-- Content Panels -->
<div class="tab-content">
<!-- Profile Tab -->
<template x-if="currentTab === 'profile'">
<div class="panel-content">
<h2>Profile Information</h2>
<form @submit.prevent="saveProfile" class="form">
<div class="form-group">
<label for="username">Username</label>
<input
type="text"
id="username"
x-model="profile.username"
placeholder="Enter username"
disabled>
</div>
<div class="form-group">
<label for="email">Email Address</label>
<input
type="email"
id="email"
x-model="profile.email"
placeholder="Enter email"
disabled>
</div>
<div class="form-group">
<label for="displayName">Display Name</label>
<input
type="text"
id="displayName"
x-model="profile.displayName"
placeholder="Enter display name">
</div>
<div class="form-group">
<label for="phone">Phone Number</label>
<input
type="tel"
id="phone"
x-model="profile.phone"
placeholder="Enter phone number">
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary" :disabled="saving">
<span x-show="!saving">💾 Save Changes</span>
<span x-show="saving">⏳ Saving...</span>
</button>
</div>
</form>
</div>
</template>
<!-- Email Accounts Tab -->
<template x-if="currentTab === 'email'">
<div class="panel-content">
<div class="section-header">
<h2>Email Accounts</h2>
<button class="btn btn-primary" @click="showAddAccount = true">
Add Account
</button>
</div>
<!-- Email Accounts List -->
<div class="accounts-list">
<template x-if="emailAccounts.length === 0">
<div class="empty-state">
<div class="empty-icon">📧</div>
<h3>No email accounts</h3>
<p>Add your first email account to start using the mail client</p>
<button class="btn btn-primary" @click="showAddAccount = true">
Add Email Account
</button>
</div>
</template>
<template x-for="account in emailAccounts" :key="account.id">
<div class="account-card">
<div class="account-info">
<div class="account-icon">📧</div>
<div class="account-details">
<h3 x-text="account.display_name || account.email"></h3>
<p class="account-email" x-text="account.email"></p>
<div class="account-meta">
<span x-text="`${account.imap_server}:${account.imap_port}`"></span>
<span x-show="account.is_primary" class="badge badge-primary">Primary</span>
<span x-show="account.is_active" class="badge badge-success">Active</span>
</div>
</div>
</div>
<div class="account-actions">
<button
class="btn btn-sm btn-secondary"
@click="testAccount(account)"
:disabled="testingAccount === account.id">
<span x-show="testingAccount !== account.id">🔌 Test</span>
<span x-show="testingAccount === account.id"></span>
</button>
<button
class="btn btn-sm btn-secondary"
@click="editAccount(account)">
✏️ Edit
</button>
<button
class="btn btn-sm btn-danger"
@click="deleteAccount(account.id)">
🗑️ Delete
</button>
</div>
</div>
</template>
</div>
</div>
</template>
<!-- Drive Settings Tab -->
<template x-if="currentTab === 'drive'">
<div class="panel-content">
<h2>Drive Storage Settings</h2>
<div class="storage-info">
<div class="storage-usage">
<h3>Storage Usage</h3>
<div class="progress-bar">
<div
class="progress-fill"
:style="`width: ${storageUsagePercent}%`">
</div>
</div>
<p x-text="`${storageUsed} of ${storageTotal} used`"></p>
</div>
</div>
<form @submit.prevent="saveDriveSettings" class="form">
<div class="form-group">
<label for="driveServer">Drive Server</label>
<input
type="text"
id="driveServer"
x-model="driveSettings.server"
placeholder="e.g., drive.example.com"
readonly>
</div>
<div class="form-group">
<label>
<input type="checkbox" x-model="driveSettings.autoSync">
Enable automatic file synchronization
</label>
</div>
<div class="form-group">
<label>
<input type="checkbox" x-model="driveSettings.offlineMode">
Enable offline mode
</label>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
💾 Save Settings
</button>
</div>
</form>
</div>
</template>
<!-- Security Tab -->
<template x-if="currentTab === 'security'">
<div class="panel-content">
<h2>Security Settings</h2>
<div class="security-section">
<h3>Change Password</h3>
<form @submit.prevent="changePassword" class="form">
<div class="form-group">
<label for="currentPassword">Current Password</label>
<input
type="password"
id="currentPassword"
x-model="security.currentPassword"
placeholder="Enter current password">
</div>
<div class="form-group">
<label for="newPassword">New Password</label>
<input
type="password"
id="newPassword"
x-model="security.newPassword"
placeholder="Enter new password">
</div>
<div class="form-group">
<label for="confirmPassword">Confirm New Password</label>
<input
type="password"
id="confirmPassword"
x-model="security.confirmPassword"
placeholder="Confirm new password">
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
🔒 Change Password
</button>
</div>
</form>
</div>
<div class="security-section">
<h3>Active Sessions</h3>
<div class="sessions-list">
<template x-for="session in activeSessions" :key="session.id">
<div class="session-card">
<div class="session-info">
<h4 x-text="session.device"></h4>
<p x-text="`Last active: ${session.lastActive}`"></p>
<p x-text="`IP: ${session.ip}`"></p>
</div>
<button
class="btn btn-sm btn-danger"
@click="revokeSession(session.id)">
❌ Revoke
</button>
</div>
</template>
</div>
</div>
</div>
</template>
</div>
<!-- Add Email Account Modal -->
<template x-if="showAddAccount">
<div class="modal-overlay" @click.self="showAddAccount = false">
<div class="modal">
<div class="modal-header">
<h2>Add Email Account</h2>
<button class="close-btn" @click="showAddAccount = false"></button>
</div>
<div class="modal-body">
<form @submit.prevent="addEmailAccount" class="form">
<div class="form-group">
<label for="newEmail">Email Address *</label>
<input
type="email"
id="newEmail"
x-model="newAccount.email"
placeholder="user@example.com"
required>
</div>
<div class="form-group">
<label for="newDisplayName">Display Name</label>
<input
type="text"
id="newDisplayName"
x-model="newAccount.displayName"
placeholder="Your Name">
</div>
<div class="form-row">
<div class="form-group">
<label for="newImapServer">IMAP Server *</label>
<input
type="text"
id="newImapServer"
x-model="newAccount.imapServer"
placeholder="imap.gmail.com"
required>
</div>
<div class="form-group">
<label for="newImapPort">IMAP Port *</label>
<input
type="number"
id="newImapPort"
x-model="newAccount.imapPort"
placeholder="993"
required>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="newSmtpServer">SMTP Server *</label>
<input
type="text"
id="newSmtpServer"
x-model="newAccount.smtpServer"
placeholder="smtp.gmail.com"
required>
</div>
<div class="form-group">
<label for="newSmtpPort">SMTP Port *</label>
<input
type="number"
id="newSmtpPort"
x-model="newAccount.smtpPort"
placeholder="587"
required>
</div>
</div>
<div class="form-group">
<label for="newUsername">Username *</label>
<input
type="text"
id="newUsername"
x-model="newAccount.username"
placeholder="username or email"
required>
</div>
<div class="form-group">
<label for="newPassword">Password *</label>
<input
type="password"
id="newPassword"
x-model="newAccount.password"
placeholder="Enter password"
required>
</div>
<div class="form-group">
<label>
<input type="checkbox" x-model="newAccount.isPrimary">
Set as primary email account
</label>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" @click="showAddAccount = false">
Cancel
</button>
<button type="submit" class="btn btn-primary" :disabled="addingAccount">
<span x-show="!addingAccount"> Add Account</span>
<span x-show="addingAccount">⏳ Adding...</span>
</button>
</div>
</form>
<div class="help-text">
<h4>Common IMAP/SMTP Settings:</h4>
<ul>
<li><strong>Gmail:</strong> imap.gmail.com:993, smtp.gmail.com:587</li>
<li><strong>Outlook:</strong> outlook.office365.com:993, smtp.office365.com:587</li>
<li><strong>Yahoo:</strong> imap.mail.yahoo.com:993, smtp.mail.yahoo.com:587</li>
</ul>
<p><strong>Note:</strong> You may need to enable "Less secure app access" or use app-specific passwords for some providers.</p>
</div>
</div>
</div>
</div>
</template>
</div>
<style>
.account-layout {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
.account-header {
margin-bottom: 2rem;
}
.account-header h1 {
font-size: 2rem;
font-weight: 600;
margin: 0 0 0.5rem 0;
color: #202124;
}
[data-theme="dark"] .account-header h1 {
color: #e8eaed;
}
.subtitle {
color: #5f6368;
margin: 0;
}
[data-theme="dark"] .subtitle {
color: #9aa0a6;
}
/* Tabs */
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 2rem;
border-bottom: 2px solid #e0e0e0;
}
[data-theme="dark"] .tabs {
border-bottom-color: #3c4043;
}
.tab {
padding: 1rem 1.5rem;
background: none;
border: none;
border-bottom: 3px solid transparent;
color: #5f6368;
font-size: 0.95rem;
cursor: pointer;
transition: all 0.2s;
margin-bottom: -2px;
}
.tab:hover {
color: #1a73e8;
background: rgba(26, 115, 232, 0.04);
}
.tab.active {
color: #1a73e8;
border-bottom-color: #1a73e8;
font-weight: 500;
}
[data-theme="dark"] .tab {
color: #9aa0a6;
}
[data-theme="dark"] .tab:hover {
color: #8ab4f8;
background: rgba(138, 180, 248, 0.08);
}
[data-theme="dark"] .tab.active {
color: #8ab4f8;
border-bottom-color: #8ab4f8;
}
/* Panel Content */
.panel-content {
background: #ffffff;
border: 1px solid #e0e0e0;
border-radius: 12px;
padding: 2rem;
}
[data-theme="dark"] .panel-content {
background: #202124;
border-color: #3c4043;
}
.panel-content h2 {
font-size: 1.5rem;
font-weight: 500;
margin: 0 0 1.5rem 0;
color: #202124;
}
[data-theme="dark"] .panel-content h2 {
color: #e8eaed;
}
/* Forms */
.form {
max-width: 600px;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
color: #202124;
}
[data-theme="dark"] .form-group label {
color: #e8eaed;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="tel"],
.form-group input[type="password"],
.form-group input[type="number"] {
width: 100%;
padding: 0.75rem;
border: 1px solid #dadce0;
border-radius: 8px;
font-size: 0.95rem;
background: #ffffff;
color: #202124;
transition: all 0.2s;
}
[data-theme="dark"] .form-group input[type="text"],
[data-theme="dark"] .form-group input[type="email"],
[data-theme="dark"] .form-group input[type="tel"],
[data-theme="dark"] .form-group input[type="password"],
[data-theme="dark"] .form-group input[type="number"] {
background: #292a2d;
border-color: #5f6368;
color: #e8eaed;
}
.form-group input:focus {
outline: none;
border-color: #1a73e8;
box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1);
}
[data-theme="dark"] .form-group input:focus {
border-color: #8ab4f8;
box-shadow: 0 0 0 3px rgba(138, 180, 248, 0.1);
}
.form-group input:disabled,
.form-group input[readonly] {
background: #f8f9fa;
color: #5f6368;
cursor: not-allowed;
}
[data-theme="dark"] .form-group input:disabled,
[data-theme="dark"] .form-group input[readonly] {
background: #1a1a1a;
color: #9aa0a6;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
}
.form-actions {
margin-top: 2rem;
display: flex;
gap: 1rem;
}
/* Buttons */
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
font-size: 0.95rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.btn-primary {
background: #1a73e8;
color: white;
}
.btn-primary:hover:not(:disabled) {
background: #1557b0;
}
.btn-secondary {
background: #f1f3f4;
color: #202124;
}
[data-theme="dark"] .btn-secondary {
background: #3c4043;
color: #e8eaed;
}
.btn-secondary:hover:not(:disabled) {
background: #e8eaed;
}
[data-theme="dark"] .btn-secondary:hover:not(:disabled) {
background: #5f6368;
}
.btn-danger {
background: #d93025;
color: white;
}
.btn-danger:hover:not(:disabled) {
background: #b31412;
}
.btn-sm {
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Section Header */
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.section-header h2 {
margin: 0;
}
/* Accounts List */
.accounts-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.account-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
background: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 12px;
transition: all 0.2s;
}
[data-theme="dark"] .account-card {
background: #292a2d;
border-color: #3c4043;
}
.account-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.account-info {
display: flex;
gap: 1rem;
flex: 1;
}
.account-icon {
font-size: 2rem;
width: 3rem;
height: 3rem;
display: flex;
align-items: center;
justify-content: center;
background: #e8f0fe;
border-radius: 50%;
}
[data-theme="dark"] .account-icon {
background: #1e3a5f;
}
.account-details h3 {
margin: 0 0 0.25rem 0;
font-size: 1.1rem;
font-weight: 500;
color: #202124;
}
[data-theme="dark"] .account-details h3 {
color: #e8eaed;
}
.account-email {
margin: 0 0 0.5rem 0;
color: #5f6368;
font-size: 0.9rem;
}
[data-theme="dark"] .account-email {
color: #9aa0a6;
}
.account-meta {
display: flex;
gap: 0.5rem;
font-size: 0.85rem;
color: #5f6368;
}
[data-theme="dark"] .account-meta {
color: #9aa0a6;
}
.account-actions {
display: flex;
gap: 0.5rem;
}
/* Badges */
.badge {
padding: 0.25rem 0.75rem;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 500;
}
.badge-primary {
background: #e8f0fe;
color: #1a73e8;
}
[data-theme="dark"] .badge-primary {
background: #1e3a5f;
color: #8ab4f8;
}
.badge-success {
background: #e6f4ea;
color: #1e8e3e;
}
[data-theme="dark"] .badge-success {
background: #1e3a2e;
color: #81c995;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 3rem;
color: #5f6368;
}
[data-theme="dark"] .empty-state {
color: #9aa0a6;
}
.empty-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
.empty-state h3 {
font-size: 1.25rem;
margin: 0 0 0.5rem 0;
color: #202124;
}
[data-theme="dark"] .empty-state h3 {
color: #e8eaed;
}
.empty-state p {
margin: 0 0 1.5rem 0;
}
/* Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 2rem;
}
.modal {
background: #ffffff;
border-radius: 12px;
max-width: 600px;
width: 100%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
}
[data-theme="dark"] .modal {
background: #202124;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem;
border-bottom: 1px solid #e0e0e0;
}
[data-theme="dark"] .modal-header {
border-bottom-color: #3c4043;
}
.modal-header h2 {
margin: 0;
font-size: 1.5rem;
color: #202124;
}
[data-theme="dark"] .modal-header h2 {
color: #e8eaed;
}
.close-btn {
background: none;
border: none;
font-size: 1.5rem;
color: #5f6368;
cursor: pointer;
padding: 0.5rem;
line-height: 1;
transition: color 0.2s;
}
.close-btn:hover {
color: #202124;
}
[data-theme="dark"] .close-btn {
color: #9aa0a6;
}
[data-theme="dark"] .close-btn:hover {
color: #e8eaed;
}
.modal-body {
padding: 1.5rem;
}
.help-text {
margin-top: 2rem;
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
font-size: 0.875rem;
}
[data-theme="dark"] .help-text {
background: #292a2d;
}
.help-text h4 {
margin: 0 0 0.5rem 0;
font-size: 0.95rem;
color: #202124;
}
[data-theme="dark"] .help-text h4 {
color: #e8eaed;
}
.help-text ul {
margin: 0.5rem 0;
padding-left: 1.5rem;
color: #5f6368;
}
[data-theme="dark"] .help-text ul {
color: #9aa0a6;
}
.help-text p {
margin: 0.5rem 0 0 0;
color: #5f6368;
}
[data-theme="dark"] .help-text p {
color: #9aa0a6;
}
/* Storage Info */
.storage-info {
margin-bottom: 2rem;
}
.storage-usage {
padding: 1.5rem;
background: #f8f9fa;
border-radius: 12px;
}
[data-theme="dark"] .storage-usage {
background: #292a2d;
}
.storage-usage h3 {
margin: 0 0 1rem 0;
font-size: 1.1rem;
color: #202124;
}
[data-theme="dark"] .storage-usage h3 {
color: #e8eaed;
}
.progress-bar {
height: 12px;
background: #e0e0e0;
border-radius: 6px;
overflow: hidden;
margin-bottom: 0.5rem;
}
[data-theme="dark"] .progress-bar {
background: #3c4043;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #1a73e8, #34a853);
transition: width 0.3s;
}
.storage-usage p {
margin: 0;
color: #5f6368;
font-size: 0.9rem;
}
[data-theme="dark"] .storage-usage p {
color: #9aa0a6;
}
/* Security Section */
.security-section {
margin-bottom: 3rem;
}
.security-section h3 {
font-size: 1.25rem;
margin: 0 0 1.5rem 0;
color: #202124;
}
[data-theme="dark"] .security-section h3 {
color: #e8eaed;
}
.sessions-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.session-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem;
background: #f8f9fa;
border: 1px solid #e0e0e0;
border-radius: 8px;
}
[data-theme="dark"] .session-card {
background: #292a2d;
border-color: #3c4043;
}
.session-info h4 {
margin: 0 0 0.25rem 0;
font-size: 1rem;
color: #202124;
}
[data-theme="dark"] .session-info h4 {
color: #e8eaed;
}
.session-info p {
margin: 0;
font-size: 0.875rem;
color: #5f6368;
}
[data-theme="dark"] .session-info p {
color: #9aa0a6;
}
/* Alpine cloak */
[x-cloak] {
display: none !important;
}
/* Responsive */
@media (max-width: 768px) {
.account-layout {
padding: 1rem;
}
.tabs {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.tab {
padding: 0.75rem 1rem;
white-space: nowrap;
}
.panel-content {
padding: 1.5rem;
}
.form-row {
grid-template-columns: 1fr;
}
.account-card {
flex-direction: column;
gap: 1rem;
}
.account-actions {
width: 100%;
justify-content: flex-end;
}
.section-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.modal-overlay {
padding: 1rem;
}
}
</style>