Commit Graph

1029 Commits

Author SHA1 Message Date
Dries Peeters 1ebfbf39de refactor: comprehensive code quality, security, and performance improvements
Performance:
- Fix N+1 queries in reports.py with joinedload for TimeEntry.project,
  TimeEntry.user, TimeEntry.task, and Project.client across 6 query locations
- Replace per-task time_entries loops with batch UPDATE queries in tasks.py
- Use efficient subquery for favorite project IDs in projects.py

Architecture:
- Add get_by_id() and get_by_name() methods to ProjectService and ClientService
- Route project/client lookups through service layer in timer.py, projects.py,
  and clients.py instead of direct Model.query calls

Security:
- Add sanitize_input() with length limits to form inputs in clients.py,
  projects.py, timer.py, issues.py, and auth.py
- Add email format validation for client creation
- Warn at startup when SECRET_KEY uses the default value or is too short
  in ProductionConfig
- Replace 7 bare except: pass clauses with specific exception types
  (OSError, IOError, TypeError, ValueError) in admin.py, settings.py,
  and invoice.py

Authorization:
- Migrate all @admin_required decorators to @admin_or_permission_required()
  with granular permissions (manage_roles, manage_kanban, manage_webhooks,
  manage_api_tokens, manage_integrations, access_admin) across permissions.py,
  kanban.py, webhooks.py, and admin.py (28 routes total)

Frontend:
- Remove 40+ console.log debug statements across 18 JS files
- Replace 42 inline onclick/onchange handlers in base.html with delegated
  event listeners using data-dropdown and data-no-propagation attributes
- Migrate 6 inline handlers in time_entries_overview.html to addEventListener
- Extract shared typing detection into typing-utils.js, eliminating 5
  duplicate isTyping() implementations across keyboard shortcut files
- Add missing aria-label attributes to icon-only buttons

Dependencies:
- Migrate from pytz to stdlib zoneinfo (Python 3.9+) across all 6 files
  that used pytz; replace pytz with tzdata in requirements.txt
- Separate dev/test dependencies into requirements-dev.txt
- Configure RotatingFileHandler (10MB, 5 backups) for app and JSON logs

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 07:56:23 +01:00
Dries Peeters 87bcff804a Redesign time entries PDF export for improved readability and professionalism
Replace the bare data table with a full report layout: titled header with date range and active filters, entries grouped by date with sub-headers, optimized 8-column layout (merged start/end into time range, removed redundant date and source columns, split client into its own column), HH:MM duration format, word-wrapping notes via Paragraph objects, summary totals bar with entry count and billable hours, and page numbers. Pass filter metadata from the export route to the PDF builder.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 06:56:20 +01:00
Dries Peeters c0fd80d5e0 Bump version to 4.17.6
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 21:18:00 +01:00
Dries Peeters 7c1f7a8cc3 Time entries PDF: data-only table export with ReportLab
Replace WeasyPrint HTML-to-PDF (full page) with a dedicated ReportLab generator that outputs only a table of time entry data.

- Add app/utils/time_entries_pdf.py: A4 landscape, compact table (Date, User, Project, Task, Start, End, Duration, Notes, Tags, Billable, Source), plain-string cells and per-page tables to avoid ReportLab table-split height bug.

- Update timer export route to use build_time_entries_pdf(); remove WeasyPrint and time_entries_export_pdf.html usage for this export.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 21:17:57 +01:00
Dries Peeters 5949670c65 fix(time-entries): improve user filter and stop auto-adding client filter
- Fix user dropdown selection: use explicit type-safe comparison
  (filters.user_id|string == user.id|string) so the correct user
  stays selected after pagination or page reload
- Exclude client_id from filter URL when it is auto-selected
  (single client or locked client): add data-auto-client attribute
  to client_select hidden inputs and skip them in getFilterParams
  so filtering by user alone no longer forces client_id=1 into the URL

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 20:54:39 +01:00
Dries Peeters 0d25a6e45b fix(timer): allow combining worked time with start date on manual entry
- Keep date/time fields always editable when duration (HH:MM) is entered
- Backend: when duration and start date+time are provided, use
  end = start + duration instead of requiring full start/end or duration-only
- Update help text to explain duration can be used with a specific date

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 20:44:24 +01:00
Dries Peeters 8d8fa582ec chore: bump version to 4.17.5
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 19:12:07 +01:00
Dries Peeters 32aeb7368d fix(admin): ensure Konva decorative image name and imageUrl in PDF serialization
Match live layer children to JSON by index and explicitly inject
name and imageUrl for decorative-image nodes, since Konva toJSON()
may not include custom attributes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 19:12:04 +01:00
Dries Peeters 42a5d746e8 fix(timer): time entries overview filter order, export note, param collection
- Move start/end date filters before project/client for clearer layout
- Add note that exports use current filters; flex-wrap on filter bar
- Explicitly read filter fields (including hidden from client_select) before
  FormData so CSV/PDF export and bulk actions get correct params

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 19:12:01 +01:00
Dries Peeters a97b519128 fix(timer): use scriptRoot for tasks API URL on timer and edit pages
Build tasks URL from request.script_root so reverse-proxy and subpath
configurations work correctly when loading project tasks.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 19:11:56 +01:00
Dries Peeters f6399ea391 fix(timer): manual entry client select, task API URL, tojson, 404 handling
- Show client as normal dropdown; pre-select only from URL param
- Use scriptRoot for tasks API URL instead of url_for template
- Use tojson for all JS-translated strings to avoid syntax errors
- Handle 404 when loading tasks (project not found/inactive)
- Clear client when project is selected (mutual exclusivity)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 19:11:53 +01:00
Dries Peeters 1c343dac1a feat(timer): prefill manual entry start/end date with today (Issue #489)
Pass prefill_start_date and prefill_end_date from manual_entry and
manual_entry_for_project using the user's timezone.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 19:11:48 +01:00
Dries Peeters 0f9b374861 fix(i18n): use tojson for JS translations in base template
Avoid syntax errors from quotes and ampersands in translated strings
by serializing i18n values with Jinja's tojson filter.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-05 19:11:45 +01:00
Dries Peeters 33277d32eb chore: bump version to 4.17.4
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 22:10:36 +01:00
Dries Peeters 9e59ac788c refactor(mobile): use AppSpacing tokens and improve settings and time entry UX
Use AppSpacing in home, time entry form, empty state, and time entry card. Add setSyncInterval to AppConfig. Show notes or date range as subtitle on recent entries; style duration. Settings: add dialogs for server URL and API token, show token state. Time entry form: load tags and billable when editing, load tasks for selected project.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 22:10:35 +01:00
Dries Peeters ef19bfae7e feat(desktop): add auto-sync and sync interval settings
Persist and load auto_sync and sync_interval in settings. Disable sync interval input when auto-sync is off. Add styling for disabled inputs in settings. Preserve project filter when loading filter projects and add selectProject helper for dashboard.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 22:10:34 +01:00
Dries Peeters a53f5a6163 fix(timer): use url_for for task API URLs and improve timer page init
Build task API URLs from url_for in timer page, bulk entry, edit timer, and time entry template edit to support subpath deployment. Wrap timer page script in DOMContentLoaded, load tasks when project is pre-selected, expose selectRecentProject on window, and fix client/project select attachment logic.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 22:10:31 +01:00
Dries Peeters 25d9a8a36f refactor(dashboard): improve main dashboard layout and structure
Reorganize dashboard template for clearer structure and maintainability.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 22:10:29 +01:00
Dries Peeters 23d4482290 fix(modules): return JSON for API when module disabled or unauthenticated
When module_enabled decorator blocks access, detect JSON/AJAX requests and return 401/403 with JSON body instead of redirect or HTML abort, so API and SPA clients get proper error responses.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 22:10:27 +01:00
Dries Peeters a00b50faef fix(ui): unify keyboard shortcuts modal and remove duplicate assets
Use openKeyboardShortcutsModal when available in shortcut manager. Remove duplicate keyboard-shortcuts-enhanced.js and toast-notifications.css from base. Only open shortcuts modal on Shift+? when advanced manager is not loaded to avoid double handling.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 22:10:23 +01:00
Dries Peeters cc6315542e fix(clients): persist archive/activate and escape confirm dialog strings
Add db.session.commit() after client.archive() and client.activate() so changes are persisted. Escape translated strings in showConfirm() with |e to avoid broken JavaScript when quotes appear in translations.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 22:10:20 +01:00
Dries Peeters 50a0c848a7 fix(time-entry): improve task dropdown and filter robustness (fixes #489)
Log Time - Task dropdown:
- Detect session expiry when API returns HTML instead of JSON
- Show clearer error message suggesting page refresh
- Include HTTP status in error messages
- Add console.error with URL and project ID for debugging

Time Entries - Filters:
- Fallback to explicitly read select/hidden inputs in getFilterParams
- Add console.debug for filter params and URL (helps diagnose issues)
- Use DOMParser as fallback when response parsing fails
- Only set lastUrl on successful parse to allow retries
- Trigger filter apply on text input change (custom fields)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-04 21:23:36 +01:00
Dries Peeters 0e9f318f07 Version bump v4.17.3
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 22:21:48 +01:00
Dries Peeters 2b1e043580 fix(timer): support duration-only entries and stable filtering
- Add duration-only manual entry flow with explicit duration override support.
- Harden manual-entry task loading and base-path compatibility.
- Fix time entries filter flakiness and add filtered PDF export.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 22:21:27 +01:00
Dries Peeters 69bef9741a Version bump v4.17.2
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 20:27:52 +01:00
Dries Peeters 1e780f5bd0 fix(timer): stabilize time entry UX and filtering
Fixes manual time entry task loading, adds a worked-time helper, makes Time Entries filters reliable, and adds CSV export for the current filtered view.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-03 20:27:52 +01:00
Dries Peeters 5a33186e5f Version bump v4.17.1 2026-02-02 20:31:10 +01:00
Dries Peeters 1529a2d498 fix(oidc): resolve redirect loop with Authelia and improve callback diagnostics
- Add issuer fallback from OIDC_ISSUER when userinfo lacks iss (fixes Authelia)
- Fallback to unverified id_token decode for iss when ID token parsing failed
- Wrap authorize_access_token() in dedicated try/except; log token_exchange_failed
  and suggest session cookie/proxy checks when state or PKCE validation fails
- Log reason=... before every redirect to login in callback for easier debugging
- Add 'Redirect loop / callback returns to login' troubleshooting to OIDC_SETUP.md

Fixes #486

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 20:30:26 +01:00
Dries Peeters 791ed5b90a Update README.md 2026-02-02 19:37:07 +01:00
Dries Peeters 80e74905e8 Version bump 4.17.0 2026-02-02 19:34:28 +01:00
Dries Peeters 55ecc7d037 feat(mobile): add connection diagnostics and clear error messages for login
- Add connection diagnostics module that classifies failures (DNS lookup,
  connection refused, TLS/certificate, timeout, HTTP errors, captive portal)
- Show user-friendly summary and actionable checklist on login failure
- Add Details bottom sheet with copyable technical report; Copy button in error box
- Fix token validation: use validateTokenRaw so network/TLS errors are not
  reported as 'Invalid username or password'; only persist server URL and
  token after validation succeeds
- Cover connection methods (HTTPS/HTTP, DNS, IPv6, self-signed cert trust flow)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 19:33:46 +01:00
Dries Peeters 3c01fb34c8 feat(modules): lock client selection and make Clients admin-only
- Add settings.locked_client_id and admin UI to select locked client
- Allow disabling Clients for non-admins while keeping admin access
- Gate Clients UI routes and API endpoints when module is disabled
- Auto-select and enforce the locked client across filters and form submissions

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 19:20:48 +01:00
Dries Peeters 807e6370ee feat(roles): add per-role module visibility (hide modules by role)
Admins can hide whole app modules (Analytics, Finance & Expenses, CRM, etc.)
per role so users in that role neither see them in the nav nor access them
by URL/API.

- Add Role.hidden_module_ids (JSON denylist) and migration
- Extend ModuleRegistry.is_enabled() with role-based hide check; module is
  hidden only if ALL of the user's roles hide it (super admins bypass)
- Add Module visibility section to role create/edit form with checkboxes
  by category; persist via hidden_modules form field
- Add tests for registry hide/allow semantics and route decorator 403

Closes #484

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 19:18:28 +01:00
Dries Peeters 0779909198 feat(reports): export detailed user time entries to Excel
Add a new User Report export that outputs one row per time entry with selectable columns (date/user/project/task/duration/notes by default), while keeping the existing summary/overtime export.

Refs: #483
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 17:59:59 +01:00
Dries Peeters 3b6bcc890b fix(nav): hide sidebar folder when all modules in category are disabled
Add has_enabled_modules(category) helper and wrap Finance & Expenses
folder so it only renders when at least one FINANCE module is enabled.
Fixes empty folder visible after disabling all modules in a category.

Closes #481

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 17:58:55 +01:00
Dries Peeters e15f0e2154 fix(oidc): avoid login loop by storing id_token server-side instead of in session cookie
When the IdP issues a large id_token (e.g. with groups claim), storing it in Flask's cookie session can exceed ~4KB and cause the browser to drop the cookie, leading to redirect loops between /auth/oidc/callback and /login.

Store id_token in Redis/in-memory cache; keep only a small reference key (oidc_id_token_key) in the session cookie. On logout, resolve id_token from cache for RP-initiated logout; support legacy session for backwards compatibility. Add regression test for oversized id_token and update OIDC logout tests.

Fixes #486

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 17:24:45 +01:00
Dries Peeters 5e5ebbb263 Version Bump V4.16.1 2026-02-02 15:04:34 +01:00
Dries Peeters e63bfc3a75 Update local.properties 2026-02-02 15:03:39 +01:00
Dries Peeters 579dc6f7c0 feat(mobile): design system, timer/projects UX, and unified logo
Design system and theme:
- Add google_fonts and Inter TextTheme; extend AppTheme with
  navigationBarTheme, bottomSheetTheme, chipTheme, dividerTheme
- Add app_tokens.dart (AppSpacing, AppRadii, AppDurations)

Timer UX:
- Remove nested NavigationBar from TimerScreen; single focused timer view
- Replace Start Timer dialog with modal bottom sheet (project search,
  task picker, notes)
- Redesign timer card (chips, FilledButton stop, AnimatedSwitcher)
- TimerWidget: use Timer.periodic for tick; dispose correctly

Projects UX:
- Replace search TextField with Material 3 SearchBar; add RefreshIndicator
- Project rows: avatar initial, client chip, Start timer action
- Start timer from project opens sheet with project pre-selected

Logo and app icon:
- Use assets/icon/app_icon.png as in-app logo on splash and login
- Regenerate Android launcher icons from same asset; iOS config in
  flutter_launcher_icons_ios.yaml (remove_alpha_ios)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 14:43:19 +01:00
Dries Peeters 74c75c1b96 fix: correct invalid .gitignore glob for mobile/.android/
Replace trailing backslash (mobile\\.android\\) with forward slash
(mobile/.android/) so ripgrep and other tools can search the repo.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 14:42:47 +01:00
Dries Peeters 689700d260 fix(timer): load tasks when selecting project on Timer and Log Time
- Use /api/projects/<id>/tasks instead of /api/tasks?project_id= so task
  loading matches the working Edit Logged Time flow.
- Add credentials: 'same-origin' and response.ok checks for reliable
  session auth and error handling.
- Render JS-embedded i18n strings with |tojson to avoid breakage in
  non-English locales.

Fixes #480

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 14:11:02 +01:00
Dries Peeters 7160ce88e5 Update Podfile 2026-02-01 17:11:28 +01:00
Dries Peeters 84a4508ace Update workflow 2026-02-01 17:03:07 +01:00
Dries Peeters 930740fd08 Update Workflows 2026-02-01 16:59:47 +01:00
Dries Peeters 9b2e7cea4a Update .gitignore 2026-02-01 16:54:27 +01:00
Dries Peeters df70ac0835 Docs: update READMEs and desktop renderer
- desktop/README and docs/mobile-desktop-apps: minor updates
- desktop renderer index.html: small adjustments

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-01 16:51:21 +01:00
Dries Peeters 94fc19f6f2 Build: add icon generation to CI and scripts, bump version to 4.16.0
- Run app icon and launcher icon generation in build-mobile workflow
- Add generate-mobile-icon scripts (Python/Pillow, ImageMagick, Inkscape)
- BUILD.md: document icon requirements and troubleshooting
- setup.py: version 4.16.0

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-01 16:51:13 +01:00
Dries Peeters 9066089a34 Mobile: theme, login, sync, and launcher icons
- Align app theme with webapp (AppColors, light/dark ColorScheme); move to core/theme
- Add app config, theme mode provider, empty_state and error_view widgets
- Improve API client, sync service, and repository for time entries
- Add login screen, settings (theme toggle, logout), SSL utils for dev certs
- Android/iOS project config, Gradle wrapper, and generated launcher icons
- Add flutter_launcher_icons and icon assets (app_icon.png, README)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-01 16:51:05 +01:00
Dries Peeters 0e7656134e Backend: add API auth login and fix time entry duration calculation
- Add POST /api/v1/auth/login (rate-limited) returning API token for mobile
- Fix time entry duration when DB returns timezone-aware datetimes (_naive_dt)
- Add _parse_date_range helper; expose timezone in API info
- Extend time entries API tests

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-01 16:50:42 +01:00
Dries Peeters 0c999fd780 Version bump v4.15.1 2026-01-31 08:06:13 +01:00