| 
2 | 2 | 
 
  | 
3 | 3 | {% block content %}  | 
4 | 4 | <div class="container">  | 
5 |  | -    <div class="dashboard-header">  | 
6 |  | -        <h2>Email Forwarders for {{ domain }}</h2>  | 
7 |  | -        <div class="header-actions">  | 
8 |  | -            <button class="btn-refresh" onclick="loadForwarders()">Refresh</button>  | 
9 |  | -            {% if not current_user.totp_enabled %}  | 
10 |  | -            <button class="btn-secondary" onclick="window.location.href='/setup-2fa'">Enable 2FA</button>  | 
11 |  | -            {% else %}  | 
12 |  | -            <button class="btn-secondary btn-success">2FA Enabled ✓</button>  | 
13 |  | -            {% endif %}  | 
 | 5 | +    <h1>Email Forwarder Management</h1>  | 
 | 6 | + | 
 | 7 | +    <div id="messageContainer"></div>  | 
 | 8 | + | 
 | 9 | +    {% if not current_user.has_da_config() %}  | 
 | 10 | +        <div class="alert alert-warning">  | 
 | 11 | +            <p>DirectAdmin is not configured. Please <a href="{{ url_for('settings.index') }}">configure your settings</a> first.</p>  | 
14 | 12 |         </div>  | 
15 |  | -    </div>  | 
16 |  | - | 
17 |  | -    <div class="card">  | 
18 |  | -        <h3>Create New Forwarder</h3>  | 
19 |  | -        <form id="createForwarderForm">  | 
20 |  | -            <div class="form-group">  | 
21 |  | -                <label for="alias">Email Alias</label>  | 
22 |  | -                <div class="input-with-domain">  | 
23 |  | -                    <input type="text" id="alias" name="alias" required placeholder="alias">  | 
24 |  | -                    <span>@{{ domain }}</span>  | 
25 |  | -                </div>  | 
26 |  | -            </div>  | 
 | 13 | +    {% else %}  | 
 | 14 | +        <!-- Create Forwarder Form -->  | 
 | 15 | +        <div class="card">  | 
 | 16 | +            <h3>Create New Forwarder</h3>  | 
 | 17 | +            <form id="createForwarderForm">  | 
 | 18 | +                <div class="form-row">  | 
 | 19 | +                    <div class="form-group">  | 
 | 20 | +                        <label for="address">Email Address</label>  | 
 | 21 | +                        <div class="input-group">  | 
 | 22 | +                            <input type="text" id="address" name="address"   | 
 | 23 | +                                   placeholder="alias" required   | 
 | 24 | +                                   pattern="[a-zA-Z0-9._-]+"  | 
 | 25 | +                                   title="Only letters, numbers, dots, underscores and hyphens allowed">  | 
 | 26 | +                            <span class="input-addon">@{{ current_user.da_domain }}</span>  | 
 | 27 | +                        </div>  | 
 | 28 | +                    </div>  | 
27 | 29 | 
 
  | 
28 |  | -            <div class="form-group">  | 
29 |  | -                <label for="destination">Forward To</label>  | 
30 |  | -                <select id="destination" name="destination" required onchange="toggleCustomEmail()">  | 
31 |  | -                    <option value="">Loading email accounts...</option>  | 
32 |  | -                </select>  | 
 | 30 | +                    <div class="form-group">  | 
 | 31 | +                        <label for="destination">Forward To</label>  | 
 | 32 | +                        <select id="destination" name="destination" required>  | 
 | 33 | +                            <option value="">Loading email accounts...</option>  | 
 | 34 | +                        </select>  | 
 | 35 | +                        <input type="email" id="customDestination" name="customDestination"   | 
 | 36 | +                               placeholder="Enter custom email address"   | 
 | 37 | +                               style="display: none; margin-top: 0.5rem;">  | 
 | 38 | +                    </div>  | 
33 | 39 | 
 
  | 
34 |  | -                <div id="customEmailGroup" style="display: none; margin-top: 10px;">  | 
35 |  | -                    <input type="email" id="customEmail" placeholder="Enter custom email address" class="custom-email-input">  | 
 | 40 | +                    <div class="form-group form-actions">  | 
 | 41 | +                        <button type="submit" class="btn-primary">Create Forwarder</button>  | 
 | 42 | +                    </div>  | 
36 | 43 |                 </div>  | 
37 |  | -            </div>  | 
38 |  | - | 
39 |  | -            <button type="submit" class="btn-primary">Create Forwarder</button>  | 
40 |  | -        </form>  | 
41 |  | -    </div>  | 
 | 44 | +            </form>  | 
 | 45 | +        </div>  | 
42 | 46 | 
 
  | 
43 |  | -    <div class="card">  | 
44 |  | -        <h3>Existing Forwarders</h3>  | 
45 |  | -        <div id="forwardersList" class="forwarders-list">  | 
46 |  | -            <div class="loading">Loading forwarders...</div>  | 
 | 47 | +        <!-- Existing Forwarders -->  | 
 | 48 | +        <div class="card">  | 
 | 49 | +            <h3>Existing Forwarders</h3>  | 
 | 50 | +            <div class="table-container">  | 
 | 51 | +                <table id="forwardersTable">  | 
 | 52 | +                    <thead>  | 
 | 53 | +                        <tr>  | 
 | 54 | +                            <th>From</th>  | 
 | 55 | +                            <th>To</th>  | 
 | 56 | +                            <th>Actions</th>  | 
 | 57 | +                        </tr>  | 
 | 58 | +                    </thead>  | 
 | 59 | +                    <tbody>  | 
 | 60 | +                        <tr>  | 
 | 61 | +                            <td colspan="3" class="loading">Loading forwarders...</td>  | 
 | 62 | +                        </tr>  | 
 | 63 | +                    </tbody>  | 
 | 64 | +                </table>  | 
 | 65 | +            </div>  | 
 | 66 | +            <div class="refresh-info">  | 
 | 67 | +                <small>Auto-refreshes every 60 seconds</small>  | 
 | 68 | +            </div>  | 
47 | 69 |         </div>  | 
48 |  | -    </div>  | 
 | 70 | +    {% endif %}  | 
49 | 71 | </div>  | 
 | 72 | + | 
 | 73 | +<style>  | 
 | 74 | +.container {  | 
 | 75 | +    max-width: 1200px;  | 
 | 76 | +    margin: 2rem auto;  | 
 | 77 | +    padding: 0 1rem;  | 
 | 78 | +}  | 
 | 79 | + | 
 | 80 | +.card {  | 
 | 81 | +    background: white;  | 
 | 82 | +    border-radius: 8px;  | 
 | 83 | +    box-shadow: 0 2px 4px rgba(0,0,0,0.1);  | 
 | 84 | +    padding: 2rem;  | 
 | 85 | +    margin-bottom: 2rem;  | 
 | 86 | +}  | 
 | 87 | + | 
 | 88 | +.card h3 {  | 
 | 89 | +    margin-top: 0;  | 
 | 90 | +    margin-bottom: 1.5rem;  | 
 | 91 | +    color: #333;  | 
 | 92 | +}  | 
 | 93 | + | 
 | 94 | +/* Form Styling */  | 
 | 95 | +.form-row {  | 
 | 96 | +    display: flex;  | 
 | 97 | +    gap: 1rem;  | 
 | 98 | +    align-items: flex-end;  | 
 | 99 | +    flex-wrap: wrap;  | 
 | 100 | +}  | 
 | 101 | + | 
 | 102 | +.form-group {  | 
 | 103 | +    flex: 1;  | 
 | 104 | +    min-width: 200px;  | 
 | 105 | +}  | 
 | 106 | + | 
 | 107 | +.form-group label {  | 
 | 108 | +    display: block;  | 
 | 109 | +    margin-bottom: 0.5rem;  | 
 | 110 | +    font-weight: 500;  | 
 | 111 | +    color: #555;  | 
 | 112 | +}  | 
 | 113 | + | 
 | 114 | +.input-group {  | 
 | 115 | +    display: flex;  | 
 | 116 | +    align-items: center;  | 
 | 117 | +}  | 
 | 118 | + | 
 | 119 | +.input-group input {  | 
 | 120 | +    flex: 1;  | 
 | 121 | +    padding: 0.75rem;  | 
 | 122 | +    border: 1px solid #ddd;  | 
 | 123 | +    border-right: none;  | 
 | 124 | +    border-radius: 4px 0 0 4px;  | 
 | 125 | +    font-size: 1rem;  | 
 | 126 | +}  | 
 | 127 | + | 
 | 128 | +.input-addon {  | 
 | 129 | +    padding: 0.75rem;  | 
 | 130 | +    background: #f8f9fa;  | 
 | 131 | +    border: 1px solid #ddd;  | 
 | 132 | +    border-radius: 0 4px 4px 0;  | 
 | 133 | +    color: #666;  | 
 | 134 | +}  | 
 | 135 | + | 
 | 136 | +select, input[type="email"] {  | 
 | 137 | +    width: 100%;  | 
 | 138 | +    padding: 0.75rem;  | 
 | 139 | +    border: 1px solid #ddd;  | 
 | 140 | +    border-radius: 4px;  | 
 | 141 | +    font-size: 1rem;  | 
 | 142 | +    background: white;  | 
 | 143 | +}  | 
 | 144 | + | 
 | 145 | +.form-actions {  | 
 | 146 | +    display: flex;  | 
 | 147 | +    align-items: center;  | 
 | 148 | +    min-width: auto;  | 
 | 149 | +}  | 
 | 150 | + | 
 | 151 | +.btn-primary {  | 
 | 152 | +    background-color: #007bff;  | 
 | 153 | +    color: white;  | 
 | 154 | +    border: none;  | 
 | 155 | +    padding: 0.75rem 1.5rem;  | 
 | 156 | +    border-radius: 4px;  | 
 | 157 | +    cursor: pointer;  | 
 | 158 | +    font-size: 1rem;  | 
 | 159 | +    transition: background-color 0.2s;  | 
 | 160 | +}  | 
 | 161 | + | 
 | 162 | +.btn-primary:hover {  | 
 | 163 | +    background-color: #0056b3;  | 
 | 164 | +}  | 
 | 165 | + | 
 | 166 | +.btn-primary:disabled {  | 
 | 167 | +    background-color: #6c757d;  | 
 | 168 | +    cursor: not-allowed;  | 
 | 169 | +}  | 
 | 170 | + | 
 | 171 | +/* Table Styling */  | 
 | 172 | +.table-container {  | 
 | 173 | +    overflow-x: auto;  | 
 | 174 | +}  | 
 | 175 | + | 
 | 176 | +table {  | 
 | 177 | +    width: 100%;  | 
 | 178 | +    border-collapse: collapse;  | 
 | 179 | +}  | 
 | 180 | + | 
 | 181 | +th {  | 
 | 182 | +    background-color: #f8f9fa;  | 
 | 183 | +    padding: 1rem;  | 
 | 184 | +    text-align: left;  | 
 | 185 | +    font-weight: 600;  | 
 | 186 | +    color: #495057;  | 
 | 187 | +    border-bottom: 2px solid #dee2e6;  | 
 | 188 | +}  | 
 | 189 | + | 
 | 190 | +td {  | 
 | 191 | +    padding: 1rem;  | 
 | 192 | +    border-bottom: 1px solid #dee2e6;  | 
 | 193 | +}  | 
 | 194 | + | 
 | 195 | +tr:hover {  | 
 | 196 | +    background-color: #f8f9fa;  | 
 | 197 | +}  | 
 | 198 | + | 
 | 199 | +.btn-danger {  | 
 | 200 | +    background-color: #dc3545;  | 
 | 201 | +    color: white;  | 
 | 202 | +    border: none;  | 
 | 203 | +    padding: 0.5rem 1rem;  | 
 | 204 | +    border-radius: 4px;  | 
 | 205 | +    cursor: pointer;  | 
 | 206 | +    font-size: 0.875rem;  | 
 | 207 | +}  | 
 | 208 | + | 
 | 209 | +.btn-danger:hover {  | 
 | 210 | +    background-color: #c82333;  | 
 | 211 | +}  | 
 | 212 | + | 
 | 213 | +/* Messages */  | 
 | 214 | +#messageContainer {  | 
 | 215 | +    margin-bottom: 1rem;  | 
 | 216 | +}  | 
 | 217 | + | 
 | 218 | +.alert {  | 
 | 219 | +    padding: 1rem;  | 
 | 220 | +    border-radius: 4px;  | 
 | 221 | +    margin-bottom: 1rem;  | 
 | 222 | +}  | 
 | 223 | + | 
 | 224 | +.alert-success {  | 
 | 225 | +    background-color: #d4edda;  | 
 | 226 | +    color: #155724;  | 
 | 227 | +    border: 1px solid #c3e6cb;  | 
 | 228 | +}  | 
 | 229 | + | 
 | 230 | +.alert-error {  | 
 | 231 | +    background-color: #f8d7da;  | 
 | 232 | +    color: #721c24;  | 
 | 233 | +    border: 1px solid #f5c6cb;  | 
 | 234 | +}  | 
 | 235 | + | 
 | 236 | +.alert-warning {  | 
 | 237 | +    background-color: #fff3cd;  | 
 | 238 | +    color: #856404;  | 
 | 239 | +    border: 1px solid #ffeaa7;  | 
 | 240 | +}  | 
 | 241 | + | 
 | 242 | +/* Loading States */  | 
 | 243 | +.loading {  | 
 | 244 | +    text-align: center;  | 
 | 245 | +    color: #6c757d;  | 
 | 246 | +    font-style: italic;  | 
 | 247 | +}  | 
 | 248 | + | 
 | 249 | +.no-data {  | 
 | 250 | +    text-align: center;  | 
 | 251 | +    color: #6c757d;  | 
 | 252 | +    padding: 2rem;  | 
 | 253 | +}  | 
 | 254 | + | 
 | 255 | +.error-message {  | 
 | 256 | +    text-align: center;  | 
 | 257 | +    color: #dc3545;  | 
 | 258 | +    padding: 2rem;  | 
 | 259 | +}  | 
 | 260 | + | 
 | 261 | +/* Utilities */  | 
 | 262 | +.refresh-info {  | 
 | 263 | +    text-align: right;  | 
 | 264 | +    margin-top: 1rem;  | 
 | 265 | +    color: #6c757d;  | 
 | 266 | +}  | 
 | 267 | + | 
 | 268 | +/* Responsive */  | 
 | 269 | +@media (max-width: 768px) {  | 
 | 270 | +    .form-row {  | 
 | 271 | +        flex-direction: column;  | 
 | 272 | +    }  | 
 | 273 | + | 
 | 274 | +    .form-group {  | 
 | 275 | +        width: 100%;  | 
 | 276 | +    }  | 
 | 277 | + | 
 | 278 | +    .card {  | 
 | 279 | +        padding: 1rem;  | 
 | 280 | +    }  | 
 | 281 | + | 
 | 282 | +    th, td {  | 
 | 283 | +        padding: 0.5rem;  | 
 | 284 | +        font-size: 0.875rem;  | 
 | 285 | +    }  | 
 | 286 | +}  | 
 | 287 | +</style>  | 
 | 288 | +{% endblock %}  | 
 | 289 | + | 
 | 290 | +{% block scripts %}  | 
 | 291 | +<script src="{{ url_for('static', filename='dashboard.js') }}"></script>  | 
50 | 292 | {% endblock %}  | 
0 commit comments