feat: Armarium v1.1.0 — dashboard, auth, 2FA, SMTP, settings, deploy
Dashboard: - ApexCharts bar chart (income vs fixed costs vs expenses) and donut chart - KPI cards: income, fixed costs, savings rate with configurable goal - Greeting with time-of-day and locale-aware date/time display Authentication & security: - Email-based login (no username), case-insensitive lookup - JWT access/refresh tokens with rotation and blacklist - TOTP 2FA with QR code, backup codes (copy + PDF export) - 2FA recovery via email code - Cloudflare Turnstile CAPTCHA on login and register Email flows: - Email verification on registration (24h token) - Password reset flow (15min token, anti-enumeration) - Brevo SMTP integration with HTML + plaintext email templates - Notification emails: 2FA recovery, password changed, email changed Settings page: - 2FA management (enable/disable, QR, backup codes) - Active sessions list with per-device revoke - Data export: ZIP with 6 PDFs via fpdf2 - Notification preferences (3 toggles) - Danger zone: account deletion with mandatory export + confirmation phrase UI & layout: - Sidebar with collapsible/flyout mode, Angular signal-based dropdowns - Dark mode (class-based), language switcher (DE/FR/IT/EN) - Mobile-responsive layout with touch-friendly targets - Roboto font via @fontsource (GDPR-compliant, no Google CDN) - Pure Tailwind CSS v3 Infrastructure: - Forgejo Actions CI/CD pipeline (auto-deploy on push to main) - Gunicorn + Nginx + PostgreSQL production setup - Rate limiting, HSTS, secure cookies, CSRF protection
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { ApplicationConfig, provideBrowserGlobalErrorListeners, importProvidersFrom, APP_INITIALIZER } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
import { TranslateModule, TranslateLoader, TranslateService } from '@ngx-translate/core';
|
||||
import { TranslateHttpLoader, TRANSLATE_HTTP_LOADER_CONFIG } from '@ngx-translate/http-loader';
|
||||
import { routes } from './app.routes';
|
||||
import { authInterceptor } from './interceptors/auth.interceptor';
|
||||
|
||||
const SUPPORTED_LANGS = ['de', 'fr', 'it', 'en'];
|
||||
|
||||
function preloadTranslations(translate: TranslateService): () => Promise<any> {
|
||||
return () => {
|
||||
const stored = localStorage.getItem('app_language');
|
||||
const browser = navigator.language?.split('-')[0].toLowerCase();
|
||||
const lang = SUPPORTED_LANGS.includes(stored ?? '') ? stored!
|
||||
: SUPPORTED_LANGS.includes(browser) ? browser
|
||||
: 'de';
|
||||
translate.setDefaultLang('de');
|
||||
return translate.use(lang).toPromise();
|
||||
};
|
||||
}
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideBrowserGlobalErrorListeners(),
|
||||
provideRouter(routes),
|
||||
provideHttpClient(withInterceptors([authInterceptor])),
|
||||
importProvidersFrom(
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateHttpLoader,
|
||||
},
|
||||
})
|
||||
),
|
||||
{ provide: TRANSLATE_HTTP_LOADER_CONFIG, useValue: { prefix: '/assets/i18n/', suffix: '.json' } },
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
useFactory: preloadTranslations,
|
||||
deps: [TranslateService],
|
||||
multi: true,
|
||||
},
|
||||
]
|
||||
};
|
||||
Reference in New Issue
Block a user