Fix favicon not showing issue
- Use url_for() for all favicon and icon references in layout.html - Add specific routes for favicon files to ensure proper serving - Create dynamic webmanifest route with correct icon paths - Import send_from_directory for serving static files - This ensures favicons work correctly in all deployment scenarios
This commit is contained in:
43
app.py
43
app.py
@@ -1,4 +1,4 @@
|
||||
from flask import Flask, render_template, request, redirect, url_for, jsonify, flash, session, g, Response, send_file, abort
|
||||
from flask import Flask, render_template, request, redirect, url_for, jsonify, flash, session, g, Response, send_file, abort, send_from_directory
|
||||
from flask_migrate import Migrate
|
||||
from models import db, TimeEntry, WorkConfig, User, SystemSettings, Team, Role, Project, Company, CompanyWorkConfig, CompanySettings, UserPreferences, WorkRegion, AccountType, ProjectCategory, Task, SubTask, TaskStatus, TaskPriority, TaskDependency, Sprint, SprintStatus, Announcement, SystemEvent, WidgetType, UserDashboard, DashboardWidget, WidgetTemplate, Comment, CommentVisibility, BrandingSettings, CompanyInvitation, Note, NoteFolder, NoteShare
|
||||
from data_formatting import (
|
||||
@@ -397,6 +397,47 @@ def sitemap_xml():
|
||||
|
||||
return Response(sitemap_xml, mimetype='application/xml')
|
||||
|
||||
@app.route('/site.webmanifest')
|
||||
def serve_webmanifest():
|
||||
"""Serve web manifest with correct icon paths"""
|
||||
manifest = {
|
||||
"name": "TimeTrack",
|
||||
"short_name": "TimeTrack",
|
||||
"icons": [
|
||||
{
|
||||
"src": url_for('static', filename='android-chrome-192x192.png'),
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": url_for('static', filename='android-chrome-512x512.png'),
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#667eea",
|
||||
"background_color": "#ffffff",
|
||||
"display": "standalone"
|
||||
}
|
||||
return jsonify(manifest)
|
||||
|
||||
# Favicon routes for compatibility
|
||||
@app.route('/favicon.ico')
|
||||
def favicon():
|
||||
return send_from_directory(app.static_folder, 'favicon.ico', mimetype='image/x-icon')
|
||||
|
||||
@app.route('/favicon-32x32.png')
|
||||
def favicon_32():
|
||||
return send_from_directory(app.static_folder, 'favicon-32x32.png', mimetype='image/png')
|
||||
|
||||
@app.route('/favicon-16x16.png')
|
||||
def favicon_16():
|
||||
return send_from_directory(app.static_folder, 'favicon-16x16.png', mimetype='image/png')
|
||||
|
||||
@app.route('/apple-touch-icon.png')
|
||||
def apple_touch_icon():
|
||||
return send_from_directory(app.static_folder, 'apple-touch-icon.png', mimetype='image/png')
|
||||
|
||||
@app.route('/')
|
||||
def home():
|
||||
if g.user:
|
||||
|
||||
BIN
static/android-chrome-192x192.png
Normal file
BIN
static/android-chrome-192x192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
static/android-chrome-512x512.png
Normal file
BIN
static/android-chrome-512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
static/apple-touch-icon.png
Normal file
BIN
static/apple-touch-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
BIN
static/favicon-16x16.png
Normal file
BIN
static/favicon-16x16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 560 B |
BIN
static/favicon-32x32.png
Normal file
BIN
static/favicon-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 900 B |
BIN
static/favicon.ico
Normal file
BIN
static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
1
static/site.webmanifest
Normal file
1
static/site.webmanifest
Normal file
@@ -0,0 +1 @@
|
||||
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
||||
@@ -4,14 +4,14 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% if title == 'Home' %}{{ g.branding.app_name if g.branding else 'TimeTrack' }} - Enterprise Time Tracking & Project Management Software{% else %}{{ title }} - {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% endif %}{% if g.company %} - {{ g.company.name }}{% endif %}</title>
|
||||
|
||||
|
||||
<!-- SEO Meta Tags -->
|
||||
<meta name="description" content="{% block meta_description %}{{ g.branding.app_name if g.branding else 'TimeTrack' }} is a comprehensive time tracking solution with project management, team collaboration, billing & invoicing. Free, open-source, and enterprise-ready.{% endblock %}">
|
||||
<meta name="keywords" content="{% block meta_keywords %}time tracking, project management, team collaboration, billing software, invoice management, enterprise time tracker, open source time tracking{% endblock %}">
|
||||
<meta name="author" content="{{ g.branding.app_name if g.branding else 'TimeTrack' }}">
|
||||
<meta name="robots" content="index, follow">
|
||||
<link rel="canonical" href="{{ request.url }}">
|
||||
|
||||
|
||||
<!-- Open Graph Meta Tags -->
|
||||
<meta property="og:title" content="{% block og_title %}{{ title }} - {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% endblock %}">
|
||||
<meta property="og:description" content="{% block og_description %}Transform your productivity with intelligent time tracking, project management, and team collaboration tools. Enterprise-grade, open-source solution.{% endblock %}">
|
||||
@@ -21,7 +21,7 @@
|
||||
{% if g.branding and g.branding.logo_filename %}
|
||||
<meta property="og:image" content="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename, _external=True) }}">
|
||||
{% endif %}
|
||||
|
||||
|
||||
<!-- Twitter Card Meta Tags -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="{% block twitter_title %}{{ title }} - {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% endblock %}">
|
||||
@@ -38,7 +38,7 @@
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/mobile-bottom-nav.css') }}">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/tablet-optimized.css') }}">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons@latest/iconfont/tabler-icons.min.css">
|
||||
|
||||
|
||||
<!-- PWA Support -->
|
||||
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||
<meta name="theme-color" content="#667eea">
|
||||
@@ -54,6 +54,12 @@
|
||||
{% endif %}
|
||||
{% if g.branding and g.branding.favicon_filename %}
|
||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='uploads/branding/' + g.branding.favicon_filename) }}">
|
||||
{% else %}
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ url_for('static', filename='apple-touch-icon.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='favicon-32x32.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ url_for('static', filename='favicon-16x16.png') }}">
|
||||
<link rel="icon" type="image/x-icon" href="{{ url_for('static', filename='favicon.ico') }}">
|
||||
<link rel="manifest" href="{{ url_for('static', filename='site.webmanifest') }}">
|
||||
{% endif %}
|
||||
<style>
|
||||
:root {
|
||||
@@ -98,7 +104,7 @@
|
||||
<body{% if g.user %} class="has-user has-bottom-nav"{% endif %}>
|
||||
{% if g.user and g.user.preferences %}
|
||||
<!-- User preferences for JavaScript -->
|
||||
<div id="user-preferences" style="display: none;"
|
||||
<div id="user-preferences" style="display: none;"
|
||||
data-date-format="{{ g.user.preferences.date_format }}"
|
||||
data-time-format-24h="{{ g.user.preferences.time_format_24h|lower }}">
|
||||
</div>
|
||||
@@ -109,8 +115,8 @@
|
||||
<div class="mobile-nav-brand">
|
||||
<a href="{{ url_for('home') }}">
|
||||
{% if g.branding and g.branding.logo_filename %}
|
||||
<img src="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename) }}"
|
||||
alt="{{ g.branding.logo_alt_text }}"
|
||||
<img src="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename) }}"
|
||||
alt="{{ g.branding.logo_alt_text }}"
|
||||
class="mobile-logo">
|
||||
{% else %}
|
||||
{{ g.branding.app_name if g.branding else 'TimeTrack' }}
|
||||
@@ -133,8 +139,8 @@
|
||||
<!-- <h2>
|
||||
<a href="{{ url_for('home') }}">
|
||||
{% if g.branding and g.branding.logo_filename %}
|
||||
<img src="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename) }}"
|
||||
alt="{{ g.branding.logo_alt_text }}"
|
||||
<img src="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename) }}"
|
||||
alt="{{ g.branding.logo_alt_text }}"
|
||||
class="sidebar-logo">
|
||||
{% else %}
|
||||
{{ g.branding.app_name if g.branding else 'TimeTrack' }}
|
||||
@@ -153,7 +159,7 @@
|
||||
<img src="{{ g.user.get_avatar_url(32) }}" alt="{{ g.user.username }}" class="user-avatar">
|
||||
<span class="nav-text">{{ g.user.username }}<span class="dropdown-arrow">▼</span></span>
|
||||
</a>
|
||||
|
||||
|
||||
<!-- User Dropdown Context Menu -->
|
||||
<div class="user-dropdown-modal" id="user-dropdown-modal">
|
||||
<div class="user-dropdown-header">
|
||||
@@ -187,7 +193,7 @@
|
||||
<li><a href="{{ url_for('sprints.sprint_management') }}" data-tooltip="Sprint Management"><i class="nav-icon ti ti-run"></i><span class="nav-text">Sprints</span></a></li>
|
||||
<li><a href="{{ url_for('notes.notes_list') }}" data-tooltip="Notes"><i class="nav-icon ti ti-notes"></i><span class="nav-text">Notes</span></a></li>
|
||||
<li><a href="{{ url_for('analytics') }}" data-tooltip="Time Analytics"><i class="nav-icon ti ti-chart-bar"></i><span class="nav-text">Analytics</span></a></li>
|
||||
|
||||
|
||||
<!-- Role-based menu items -->
|
||||
{% if g.user.role == Role.ADMIN or g.user.role == Role.SYSTEM_ADMIN %}
|
||||
<li class="nav-divider">Admin</li>
|
||||
@@ -257,7 +263,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
|
||||
<!-- Email Nag Screens -->
|
||||
{% if g.show_email_nag %}
|
||||
<div class="email-nag-banner">
|
||||
@@ -282,7 +288,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
@@ -300,7 +306,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
<!-- Mobile Bottom Navigation -->
|
||||
{% if g.user %}
|
||||
<nav class="mobile-bottom-nav">
|
||||
@@ -353,7 +359,7 @@
|
||||
// Store in session storage to not show again this session
|
||||
sessionStorage.setItem('emailNagDismissed', 'true');
|
||||
}
|
||||
|
||||
|
||||
// Check if already dismissed this session
|
||||
if (sessionStorage.getItem('emailNagDismissed') === 'true') {
|
||||
const banner = document.querySelector('.email-nag-banner');
|
||||
@@ -365,7 +371,7 @@
|
||||
{% else %}
|
||||
<script src="{{ url_for('static', filename='js/splash.js') }}"></script>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<!-- Custom Tracking Script -->
|
||||
{% if tracking_script_enabled and tracking_script_code %}
|
||||
{{ tracking_script_code|safe }}
|
||||
|
||||
Reference in New Issue
Block a user