344 lines
9.6 KiB
Python
344 lines
9.6 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
from flask import Blueprint, jsonify, render_template, request
|
|
|
|
from .get_period_times import (
|
|
SUPPORTED_TIMEZONES,
|
|
convert_seconds,
|
|
default_timezone,
|
|
get_current_task_info,
|
|
get_period_totals,
|
|
get_raw_switches,
|
|
get_task_blocks_calendar,
|
|
get_task_time_seconds,
|
|
get_timezone,
|
|
get_work_period_totals,
|
|
task_or_none,
|
|
)
|
|
|
|
dmbp = Blueprint("deskmeter", __name__, url_prefix="/", template_folder="templates")
|
|
|
|
|
|
@dmbp.route("/favicon.ico")
|
|
def favicon():
|
|
return "", 204 # No Content
|
|
|
|
|
|
@dmbp.route("/calendar")
|
|
@dmbp.route("/calendar/<string:scope>")
|
|
@dmbp.route("/calendar/<string:scope>/<int:year>/<int:month>/<int:day>")
|
|
def calendar_view(scope="daily", year=None, month=None, day=None):
|
|
"""
|
|
Google Calendar-style view showing task blocks at their actual times.
|
|
"""
|
|
task = None
|
|
grid = int(request.args.get("grid", 1)) # Grid hours: 1, 3, or 6
|
|
if grid not in [1, 3, 6]:
|
|
grid = 1
|
|
|
|
tz_name = request.args.get("tz")
|
|
tz = get_timezone(tz_name)
|
|
|
|
if not year:
|
|
year = datetime.today().year
|
|
if not month:
|
|
month = datetime.today().month
|
|
if not day:
|
|
day = datetime.today().day
|
|
|
|
base_date = datetime(year, month, day).replace(
|
|
hour=0, minute=0, second=0, tzinfo=tz
|
|
)
|
|
|
|
if scope == "daily":
|
|
start = base_date
|
|
end = base_date.replace(hour=23, minute=59, second=59)
|
|
blocks = get_task_blocks_calendar(
|
|
start, end, task, min_block_seconds=60, grid_hours=grid, tz=tz
|
|
)
|
|
prev_date = base_date - timedelta(days=1)
|
|
next_date = base_date + timedelta(days=1)
|
|
days = [base_date]
|
|
|
|
elif scope == "weekly":
|
|
start = base_date - timedelta(days=base_date.weekday())
|
|
end = start + timedelta(days=6, hours=23, minutes=59, seconds=59)
|
|
blocks = get_task_blocks_calendar(
|
|
start, end, task, min_block_seconds=300, grid_hours=grid, tz=tz
|
|
)
|
|
prev_date = start - timedelta(days=7)
|
|
next_date = start + timedelta(days=7)
|
|
days = [start + timedelta(days=i) for i in range(7)]
|
|
|
|
elif scope == "monthly":
|
|
start = base_date.replace(day=1)
|
|
if month == 12:
|
|
end = datetime(year + 1, 1, 1, tzinfo=tz) - timedelta(seconds=1)
|
|
else:
|
|
end = datetime(year, month + 1, 1, tzinfo=tz) - timedelta(seconds=1)
|
|
blocks = get_task_blocks_calendar(
|
|
start, end, task, min_block_seconds=600, grid_hours=grid, tz=tz
|
|
)
|
|
if month == 1:
|
|
prev_date = datetime(year - 1, 12, 1, tzinfo=tz)
|
|
else:
|
|
prev_date = datetime(year, month - 1, 1, tzinfo=tz)
|
|
if month == 12:
|
|
next_date = datetime(year + 1, 1, 1, tzinfo=tz)
|
|
else:
|
|
next_date = datetime(year, month + 1, 1, tzinfo=tz)
|
|
days = []
|
|
current = start
|
|
while current <= end:
|
|
days.append(current)
|
|
current += timedelta(days=1)
|
|
else:
|
|
scope = "daily"
|
|
start = base_date
|
|
end = base_date.replace(hour=23, minute=59, second=59)
|
|
blocks = get_task_blocks_calendar(
|
|
start, end, task, min_block_seconds=60, grid_hours=grid, tz=tz
|
|
)
|
|
prev_date = base_date - timedelta(days=1)
|
|
next_date = base_date + timedelta(days=1)
|
|
days = [base_date]
|
|
|
|
return render_template(
|
|
"calendar_view.html",
|
|
scope=scope,
|
|
blocks=blocks,
|
|
start=start,
|
|
end=end,
|
|
base_date=base_date,
|
|
prev_date=prev_date,
|
|
next_date=next_date,
|
|
days=days,
|
|
grid=grid,
|
|
tz_name=tz_name,
|
|
timezones=SUPPORTED_TIMEZONES,
|
|
auto_refresh=False,
|
|
)
|
|
|
|
|
|
@dmbp.route("/switches")
|
|
@dmbp.route("/switches/<string:scope>")
|
|
@dmbp.route("/switches/<string:scope>/<int:year>/<int:month>/<int:day>")
|
|
def switches_view(scope="daily", year=None, month=None, day=None):
|
|
"""
|
|
Raw switches view showing all switch documents.
|
|
"""
|
|
task = None
|
|
|
|
tz_name = request.args.get("tz")
|
|
tz = get_timezone(tz_name)
|
|
|
|
if not year:
|
|
year = datetime.today().year
|
|
if not month:
|
|
month = datetime.today().month
|
|
if not day:
|
|
day = datetime.today().day
|
|
|
|
base_date = datetime(year, month, day).replace(
|
|
hour=0, minute=0, second=0, tzinfo=tz
|
|
)
|
|
|
|
if scope == "daily":
|
|
start = base_date
|
|
end = base_date.replace(hour=23, minute=59, second=59)
|
|
prev_date = base_date - timedelta(days=1)
|
|
next_date = base_date + timedelta(days=1)
|
|
|
|
elif scope == "weekly":
|
|
start = base_date - timedelta(days=base_date.weekday())
|
|
end = start + timedelta(days=6, hours=23, minutes=59, seconds=59)
|
|
prev_date = start - timedelta(days=7)
|
|
next_date = start + timedelta(days=7)
|
|
|
|
elif scope == "monthly":
|
|
start = base_date.replace(day=1)
|
|
if month == 12:
|
|
end = datetime(year + 1, 1, 1, tzinfo=tz) - timedelta(seconds=1)
|
|
else:
|
|
end = datetime(year, month + 1, 1, tzinfo=tz) - timedelta(seconds=1)
|
|
if month == 1:
|
|
prev_date = datetime(year - 1, 12, 1, tzinfo=tz)
|
|
else:
|
|
prev_date = datetime(year, month - 1, 1, tzinfo=tz)
|
|
if month == 12:
|
|
next_date = datetime(year + 1, 1, 1, tzinfo=tz)
|
|
else:
|
|
next_date = datetime(year, month + 1, 1, tzinfo=tz)
|
|
else:
|
|
scope = "daily"
|
|
start = base_date
|
|
end = base_date.replace(hour=23, minute=59, second=59)
|
|
prev_date = base_date - timedelta(days=1)
|
|
next_date = base_date + timedelta(days=1)
|
|
|
|
raw_switches = get_raw_switches(start, end, task, tz=tz)
|
|
|
|
return render_template(
|
|
"switches_view.html",
|
|
scope=scope,
|
|
switches=raw_switches,
|
|
start=start,
|
|
end=end,
|
|
base_date=base_date,
|
|
prev_date=prev_date,
|
|
next_date=next_date,
|
|
tz_name=tz_name,
|
|
timezones=SUPPORTED_TIMEZONES,
|
|
auto_refresh=False,
|
|
)
|
|
|
|
|
|
@dmbp.route("/")
|
|
@dmbp.route("/<string:task>")
|
|
def index(task=None):
|
|
"""
|
|
Show total time used in each desktop for today
|
|
"""
|
|
task = task_or_none(task)
|
|
|
|
start = datetime.today().replace(
|
|
hour=0, minute=0, second=0, tzinfo=default_timezone
|
|
)
|
|
end = datetime.today().replace(
|
|
hour=23, minute=59, second=59, tzinfo=default_timezone
|
|
)
|
|
|
|
rows = get_period_totals(start, end, task)
|
|
|
|
# Get current task info
|
|
current_task_id, current_task_path = get_current_task_info()
|
|
|
|
# Get all tasks worked on today
|
|
task_rows = get_work_period_totals(start, end)
|
|
|
|
return render_template(
|
|
"main.html",
|
|
rows=rows,
|
|
current_task_path=current_task_path,
|
|
task_rows=task_rows,
|
|
auto_refresh=True,
|
|
)
|
|
|
|
|
|
@dmbp.route("/api/current_task")
|
|
def api_current_task():
|
|
"""
|
|
JSON API endpoint returning current task information
|
|
"""
|
|
current_task_id, current_task_path = get_current_task_info()
|
|
return jsonify(
|
|
{"task_id": current_task_id, "task_path": current_task_path or "no task"}
|
|
)
|
|
|
|
|
|
@dmbp.route("/api/today")
|
|
@dmbp.route("/api/today/<string:task>")
|
|
def api_today(task=None):
|
|
"""
|
|
HTML fragment API endpoint for today's data (for AJAX updates)
|
|
"""
|
|
task = task_or_none(task)
|
|
|
|
start = datetime.today().replace(
|
|
hour=0, minute=0, second=0, tzinfo=default_timezone
|
|
)
|
|
end = datetime.today().replace(
|
|
hour=23, minute=59, second=59, tzinfo=default_timezone
|
|
)
|
|
|
|
rows = get_period_totals(start, end, task)
|
|
|
|
# Get current task info
|
|
current_task_id, current_task_path = get_current_task_info()
|
|
|
|
# Get all tasks worked on today
|
|
task_rows = get_work_period_totals(start, end)
|
|
|
|
return render_template(
|
|
"main_content.html",
|
|
rows=rows,
|
|
current_task_path=current_task_path,
|
|
task_rows=task_rows,
|
|
)
|
|
|
|
|
|
@dmbp.route("/day/<int:month>/<int:day>")
|
|
@dmbp.route("/day/<string:task>/<int:month>/<int:day>")
|
|
def oneday(
|
|
month,
|
|
day,
|
|
task=None,
|
|
):
|
|
task = task_or_none(task)
|
|
|
|
start = datetime(2025, month, day).replace(
|
|
hour=0, minute=0, second=0, tzinfo=default_timezone
|
|
)
|
|
|
|
end = datetime(2025, month, day).replace(
|
|
hour=23, minute=59, second=59, tzinfo=default_timezone
|
|
)
|
|
|
|
rows = get_period_totals(start, end)
|
|
|
|
return render_template("pages.html", rows=rows, auto_refresh=True)
|
|
|
|
|
|
@dmbp.route("/period/<start>/<end>")
|
|
def period(start, end):
|
|
start = datetime(*map(int, start.split("-"))).replace(
|
|
hour=0, minute=0, second=0, tzinfo=default_timezone
|
|
)
|
|
|
|
end = datetime(*map(int, end.split("-"))).replace(
|
|
hour=23, minute=59, second=59, tzinfo=default_timezone
|
|
)
|
|
|
|
rows = get_period_totals(start, end)
|
|
|
|
return render_template("pages.html", rows=rows, auto_refresh=True)
|
|
|
|
|
|
@dmbp.route("/work")
|
|
def work():
|
|
"""
|
|
Show total time used per work project for today
|
|
"""
|
|
start = datetime.today().replace(
|
|
hour=0, minute=0, second=0, tzinfo=default_timezone
|
|
)
|
|
end = datetime.today().replace(
|
|
hour=23, minute=59, second=59, tzinfo=default_timezone
|
|
)
|
|
|
|
rows = get_work_period_totals(start, end)
|
|
|
|
return render_template("main.html", rows=rows, auto_refresh=False)
|
|
|
|
|
|
@dmbp.route("/totals")
|
|
@dmbp.route("/totals/<string:task>")
|
|
def totals(task=None):
|
|
"""
|
|
Show total time used in each desktop for all time
|
|
"""
|
|
|
|
task = task_or_none(task)
|
|
|
|
start = datetime(2020, 1, 1).replace(
|
|
hour=0, minute=0, second=0, tzinfo=default_timezone
|
|
)
|
|
|
|
end = datetime(2030, 1, 1).replace(
|
|
hour=23, minute=59, second=59, tzinfo=default_timezone
|
|
)
|
|
|
|
rows = get_period_totals(start, end)
|
|
|
|
return render_template("pages.html", rows=rows, auto_refresh=True)
|