mirror of
https://github.com/joaovitoriasilva/endurain.git
synced 2026-01-07 23:13:57 -05:00
Squashed commit of the following:
commitd32e3d8fdcMerge:d722e793db959207Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 12:52:35 2025 +0100 Merge remote-tracking branch 'origin/l10n_pre-release' into pre-release commitdb959207c1Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:51:52 2025 +0100 New translations activitymandabovepillscomponent.json (Portuguese) commit595256934eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:50:51 2025 +0100 New translations editactivitymodalcomponent.json (Portuguese) commitc659dcd84fAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:50:46 2025 +0100 New translations activitysummarycomponent.json (Portuguese) commit1f8e699753Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:41 2025 +0100 New translations editactivitymodalcomponent.json (Portuguese) commitc93884c2c7Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:40 2025 +0100 New translations editactivitymodalcomponent.json (German) commit9a59c21a75Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:39 2025 +0100 New translations editactivitymodalcomponent.json (Catalan) commit72ea5b5467Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:38 2025 +0100 New translations editactivitymodalcomponent.json (Spanish) commit7f309e9e32Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:37 2025 +0100 New translations editactivitymodalcomponent.json (French) commit0ed4e9134dAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:36 2025 +0100 New translations activitysummarycomponent.json (Portuguese) commitb22b45f185Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:35 2025 +0100 New translations activitysummarycomponent.json (Dutch) commit78a49d35afAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:34 2025 +0100 New translations activitysummarycomponent.json (German) commitd03f260e75Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:33 2025 +0100 New translations activitysummarycomponent.json (Catalan) commit1203787f8aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:32 2025 +0100 New translations activitysummarycomponent.json (Spanish) commit9a948aea26Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:31 2025 +0100 New translations activitysummarycomponent.json (French) commitcf2a0e8bddAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Wed Jun 25 12:38:30 2025 +0100 New translations editactivitymodalcomponent.json (Dutch) commitd722e79394Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 12:28:40 2025 +0100 Fix Docker image tag & bump frontend version [docker] fix image tag on docker-compose.yml.example [frontend] bump version on frontend commite15f4e89c7Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 12:18:17 2025 +0100 Bump dependencies commit319b83ae0fAuthor: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 12:10:53 2025 +0100 Add indoor cycling support & fix config/logging issues [backend] fixed invalid default value for JAEGER_ENABLED [backend] added support for activity type indoor_cycling [backend] fixed logging spacing and added docs to logger [docker] fixed logging spacing [docs] added support for activity type indoor_cycling [frontend] added support for activity type indoor_cycling [frontend] re added activityMaxHR to activitySummaryComponent.json commitf3e298300fMerge:c9e006a710668d3eAuthor: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 11:20:16 2025 +0100 Merge branch 'docker_immutable_feature' into pre-release commit10668d3ed6Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Wed Jun 25 09:26:34 2025 +0100 Update env.js commitb138f81f1eAuthor: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Tue Jun 24 23:14:58 2025 +0100 Update database.py commit5c7c81f123Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Tue Jun 24 23:03:14 2025 +0100 Refactor env var usage and runtime config for frontend/backend Backend now uses core.config for environment variables with sensible defaults, reducing direct os.environ access and improving robustness. Dockerfile and start.sh were updated to remove hardcoded env vars and generate a runtime env.js for frontend configuration. Frontend code now reads ENDURAIN_HOST from window.env instead of Vite env, enabling runtime configuration. Obsolete .env file was removed, and documentation was updated to fix a typo in JAEGER_PORT. commitbb2ae4f548Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:27 2025 +0100 New translations activitymandabovepillscomponent.json (Portuguese) commit64f438000fAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:26 2025 +0100 New translations activitymandabovepillscomponent.json (German) commit4129e66768Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:25 2025 +0100 New translations activitymandabovepillscomponent.json (Catalan) commit7c37b2c375Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:23 2025 +0100 New translations activitymandabovepillscomponent.json (Spanish) commited236a0ee7Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:22 2025 +0100 New translations activitymandabovepillscomponent.json (French) commit825ef122d7Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:21 2025 +0100 New translations activitysummarycomponent.json (Portuguese) commitd04742caeeAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:20 2025 +0100 New translations activitysummarycomponent.json (Dutch) commit187b56221bAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:19 2025 +0100 New translations activitysummarycomponent.json (German) commit83847a2d0cAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:18 2025 +0100 New translations activitysummarycomponent.json (Catalan) commit8a2417360aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:17 2025 +0100 New translations activitysummarycomponent.json (Spanish) commit7bf9dfdc66Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:16 2025 +0100 New translations activitysummarycomponent.json (French) commit683b1fd260Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 16:34:15 2025 +0100 New translations activitymandabovepillscomponent.json (Dutch) commitc9e006a757Merge:1a5f5cca6cfe4999Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 16:28:37 2025 +0100 Merge branch 'pr/199' into pre-release commit6cfe4999e1Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 16:27:44 2025 +0100 Add HR Zones chart to mobile, move logic to chartUtils [frontend] moved chart functions to chartUtils file [frontend] added HR Zones Bar Chart to mobile view commite475138d11Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 16:15:42 2025 +0100 Refactor HR Zones chart, clean up ActivitySummary [frontend] moved HR Zones graph away from ActivitySummaryComponent [frontend] removed duplicated translation [frontend] reverted changes on ActivitySummaryComponent since chart was removed [frontend] moved chart to ActivityMandAbovePillsComponent [frontend] small adjustments to BarChart commita7d8418d51Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 15:04:05 2025 +0100 Improve formatting, docstrings & fix activityActivityStreams prop [backend] fixed formatting [backend] changed existing docstrings to be more complete [backend] changed imports to be consistent with rest of the code [frontend] fixed formatting [frontend] set new prop on ActivitySummaryComponent to be not required because it is not always necessary (HomeView) [frontend] fixed issue onMounted on ActivitySummaryComponent when new prop is null commit1a5f5cca86Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 13:52:31 2025 +0100 Fix for docs commit1960c3153aMerge:c895ae0d935a5d43Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 13:37:08 2025 +0100 Merge branch 'pr/203' into pre-release commitc895ae0d83Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 12:33:21 2025 +0100 New translations activitybellowmpillscomponent.json (Dutch) commit5338c95879Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 12:33:20 2025 +0100 New translations activitylapscomponent.json (Dutch) commit7d3930bfbaAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 12:33:19 2025 +0100 New translations generalitems.json (Dutch) commit0838b6cf6eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:11:34 2025 +0100 New translations activitybellowmpillscomponent.json (Dutch) commit9776b6461aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:11:31 2025 +0100 New translations activitylapscomponent.json (Dutch) commit8b2b6c6fc8Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:23 2025 +0100 New translations stravacallbackview.json (Dutch) commit6acf86fb47Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:09 2025 +0100 New translations summaryview.json (Dutch) commit79708f1aa9Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:07 2025 +0100 New translations settingsuserprofilezonecomponent.json (Dutch) commit5e93661fa0Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:06 2025 +0100 New translations settingsintegrationszonecomponent.json (Dutch) commitbfa7093fc4Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:04 2025 +0100 New translations editactivitymodalcomponent.json (Dutch) commit27036db437Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:10:03 2025 +0100 New translations navbarcomponent.json (Dutch) commitc4143435d1Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:09:48 2025 +0100 New translations activityview.json (Dutch) commit8b1cc16bc6Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:09:47 2025 +0100 New translations loginview.json (Dutch) commit3cd447368eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:09:46 2025 +0100 New translations generalitems.json (Dutch) commitbac2b90b88Merge:d0f850dd13afbe86Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 11:06:59 2025 +0100 Merge remote-tracking branch 'origin/l10n_pre-release' into pre-release commitd0f850dd0eMerge:ed4c87d4426500ffAuthor: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 11:04:10 2025 +0100 Merge branch 'pr/208' into pre-release commit13afbe86aeAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 11:00:40 2025 +0100 New translations gearslistcomponent.json (German) commit59ad9170b0Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Mon Jun 23 10:59:50 2025 +0100 New translations gearview.json (Catalan) commit426500ff2eAuthor: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 11:59:36 2025 +0200 fixing some typos commited4c87d407Author: João Vitória Silva <8648976+joaovitoriasilva@users.noreply.github.com> Date: Mon Jun 23 10:55:01 2025 +0100 Silence StravaLib token warnings + bump dependencies commit6f24ac158fAuthor: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 01:10:40 2025 +0200 fully translated commit386e5ae853Author: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 00:57:42 2025 +0200 done upto strava commit56cb31288dAuthor: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 00:54:16 2025 +0200 next to translate - gears commit8d3158ef6cAuthor: C2gl <97646342+C2gl@users.noreply.github.com> Date: Mon Jun 23 00:25:31 2025 +0200 initial commit for fork commit935a5d43b5Author: Fredrik Fyksen <fredrik@fyksen.me> Date: Wed Jun 18 15:06:48 2025 +0200 Updated hosting guide commit56a7ff881fAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 21:02:22 2025 +0100 New translations summaryview.json (Dutch) commit9c0acab25aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 19:35:49 2025 +0100 New translations activityview.json (Dutch) commitbfe8ae1d14Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 19:35:48 2025 +0100 New translations summaryview.json (Dutch) commit0c0a375d1aAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 19:35:47 2025 +0100 New translations activitymandabovepillscomponent.json (Dutch) commit8c4619fc8dAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 19:35:46 2025 +0100 New translations editactivitymodalcomponent.json (Dutch) commit9748f7776fAuthor: Zuhdi <rccheattest2@gmail.com> Date: Tue Jun 17 22:57:52 2025 +0700 feat: change hr zone to bar chart - return transform_activity_streams to get_public_activity_stream_by_type - commitfcad6ea3e5Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:27 2025 +0100 New translations summaryview.json (Dutch) commit515427d89bAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:26 2025 +0100 New translations activitiesview.json (Dutch) commit50a76062f0Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:24 2025 +0100 New translations activitymandabovepillscomponent.json (Dutch) commit95c6b9ef0eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:23 2025 +0100 New translations searchview.json (Dutch) commit503b943a2eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:22 2025 +0100 New translations settingsuserprofilezonecomponent.json (Dutch) commitb1884f4fe9Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:21 2025 +0100 New translations settingsintegrationszonecomponent.json (Dutch) commit04c899a3a4Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:20 2025 +0100 New translations gearslistcomponent.json (Dutch) commit5163263b88Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:19 2025 +0100 New translations gearsaddeditgearmodalcomponent.json (Dutch) commitae3943d352Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:18 2025 +0100 New translations editactivitymodalcomponent.json (Dutch) commit01a900b166Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:16 2025 +0100 New translations gearview.json (Dutch) commite1c006fd7eAuthor: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:15 2025 +0100 New translations loginview.json (Dutch) commit1f406e5022Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 14:39:14 2025 +0100 New translations generalitems.json (Dutch) commit9b43447128Author: João Vitória Silva <joao.vitoria.silva@pm.me> Date: Tue Jun 17 12:53:54 2025 +0100 New translations navbarcomponent.json (Dutch) commitc503c35607Author: Zuhdi <rccheattest2@gmail.com> Date: Tue Jun 17 08:01:37 2025 +0700 chore: fix review commit85c8f9410cAuthor: Zuhdi <rccheattest2@gmail.com> Date: Tue Jun 17 07:15:01 2025 +0700 fix: handle when average_heartrate and max_heartrate is null when import from strava commit7e9b3227c4Author: Zuhdi <rccheattest2@gmail.com> Date: Sun Jun 15 11:44:39 2025 +0700 feat: change default value for hrZones commitbd1ec74968Author: Zuhdi <rccheattest2@gmail.com> Date: Sun Jun 15 11:32:40 2025 +0700 feat: show HR based on zone that calculated from user birthdate (if any)
This commit is contained in:
14
.env.example
Normal file
14
.env.example
Normal file
@@ -0,0 +1,14 @@
|
||||
# This is an environment variable file for Endurain's docker-compose.yml.example
|
||||
# These are just the variable you have to set to be up and running.
|
||||
# There is many more variable you could set. Check them out here: https://docs.endurain.com/getting-started/advanced-started/#supported-environment-variables
|
||||
|
||||
DB_PASSWORD=changeme
|
||||
POSTGRES_PASSWORD=changeme
|
||||
SECRET_KEY=changeme
|
||||
FERNET_KEY=changeme
|
||||
GEOCODES_MAPS_API=changeme
|
||||
TZ=Europe/Lisbon
|
||||
ENDURAIN_HOST=https://endurain.example.com
|
||||
BEHIND_PROXY=true
|
||||
POSTGRES_DB=endurain
|
||||
POSTGRES_USER=endurain
|
||||
@@ -164,6 +164,7 @@ ACTIVITY_NAME_TO_ID.update(
|
||||
"racquetball": 25,
|
||||
"pickleball": 26,
|
||||
"commuting_ride": 27,
|
||||
"indoor_ride": 28,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -641,7 +642,7 @@ def calculate_activity_distances(activities: list[activities_schema.Activity]):
|
||||
for activity in activities:
|
||||
if activity.activity_type in [1, 2, 3]:
|
||||
run += activity.distance
|
||||
elif activity.activity_type in [4, 5, 6, 7, 27]:
|
||||
elif activity.activity_type in [4, 5, 6, 7, 27, 28]:
|
||||
bike += activity.distance
|
||||
elif activity.activity_type in [8, 9]:
|
||||
swim += activity.distance
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import os
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
from datetime import datetime
|
||||
@@ -7,6 +6,8 @@ import activities.activity_laps.schema as activity_laps_schema
|
||||
|
||||
import activities.activity.schema as activities_schema
|
||||
|
||||
import core.config as core_config
|
||||
|
||||
def serialize_activity_lap(activity: activities_schema.Activity, activity_lap: activity_laps_schema.ActivityLaps):
|
||||
def make_aware_and_format(dt, timezone):
|
||||
if isinstance(dt, str):
|
||||
@@ -18,7 +19,7 @@ def serialize_activity_lap(activity: activities_schema.Activity, activity_lap: a
|
||||
timezone = (
|
||||
ZoneInfo(activity.timezone)
|
||||
if activity.timezone
|
||||
else ZoneInfo(os.environ.get("TZ"))
|
||||
else ZoneInfo(core_config.TZ)
|
||||
)
|
||||
|
||||
activity_lap.start_time = make_aware_and_format(activity_lap.start_time, timezone)
|
||||
|
||||
@@ -7,6 +7,8 @@ import activities.activity_sets.schema as activity_sets_schema
|
||||
|
||||
import activities.activity.schema as activities_schema
|
||||
|
||||
import core.config as core_config
|
||||
|
||||
def serialize_activity_set(activity: activities_schema.Activity, activity_set: activity_sets_schema.ActivitySets):
|
||||
def make_aware_and_format(dt, timezone):
|
||||
if isinstance(dt, str):
|
||||
@@ -18,7 +20,7 @@ def serialize_activity_set(activity: activities_schema.Activity, activity_set: a
|
||||
timezone = (
|
||||
ZoneInfo(activity.timezone)
|
||||
if activity.timezone
|
||||
else ZoneInfo(os.environ.get("TZ"))
|
||||
else ZoneInfo(core_config.TZ)
|
||||
)
|
||||
|
||||
activity_set.start_time = make_aware_and_format(activity_set.start_time, timezone)
|
||||
|
||||
8
backend/app/activities/activity_streams/constants.py
Normal file
8
backend/app/activities/activity_streams/constants.py
Normal file
@@ -0,0 +1,8 @@
|
||||
# Stream type constants for activity streams
|
||||
STREAM_TYPE_HR = 1
|
||||
STREAM_TYPE_POWER = 2
|
||||
STREAM_TYPE_CADENCE = 3
|
||||
STREAM_TYPE_ELEVATION = 4
|
||||
STREAM_TYPE_SPEED = 5
|
||||
STREAM_TYPE_PACE = 6
|
||||
STREAM_TYPE_MAP = 7
|
||||
@@ -1,6 +1,9 @@
|
||||
from fastapi import HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
import numpy as np
|
||||
import datetime
|
||||
|
||||
import activities.activity_streams.constants as activity_streams_constants
|
||||
import activities.activity_streams.schema as activity_streams_schema
|
||||
import activities.activity_streams.models as activity_streams_models
|
||||
|
||||
@@ -10,14 +13,16 @@ import activities.activity.models as activities_models
|
||||
|
||||
import server_settings.crud as server_settings_crud
|
||||
|
||||
import users.user.crud as users_crud
|
||||
|
||||
import core.logger as core_logger
|
||||
|
||||
|
||||
def get_activity_streams(activity_id: int, token_user_id: int, db: Session):
|
||||
def get_activity_streams(
|
||||
activity_id: int, token_user_id: int, db: Session
|
||||
) -> list[activity_streams_schema.ActivityStreams] | None:
|
||||
try:
|
||||
activity = activity_crud.get_activity_by_id(
|
||||
activity_id, db
|
||||
)
|
||||
activity = activity_crud.get_activity_by_id(activity_id, db)
|
||||
|
||||
if not activity:
|
||||
# If the activity does not exist, return None
|
||||
@@ -42,20 +47,52 @@ def get_activity_streams(activity_id: int, token_user_id: int, db: Session):
|
||||
|
||||
if not user_is_owner:
|
||||
activity_streams = [
|
||||
stream for stream in activity_streams
|
||||
stream
|
||||
for stream in activity_streams
|
||||
if not (
|
||||
(activity.hide_hr and stream.stream_type == 1) or
|
||||
(activity.hide_power and stream.stream_type == 2) or
|
||||
(activity.hide_cadence and stream.stream_type == 3) or
|
||||
(activity.hide_elevation and stream.stream_type == 4) or
|
||||
(activity.hide_speed and stream.stream_type == 5) or
|
||||
(activity.hide_pace and stream.stream_type == 6) or
|
||||
(activity.hide_map and stream.stream_type == 7)
|
||||
(
|
||||
activity.hide_hr
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_HR
|
||||
)
|
||||
or (
|
||||
activity.hide_power
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_POWER
|
||||
)
|
||||
or (
|
||||
activity.hide_cadence
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_CADENCE
|
||||
)
|
||||
or (
|
||||
activity.hide_elevation
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_ELEVATION
|
||||
)
|
||||
or (
|
||||
activity.hide_speed
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_SPEED
|
||||
)
|
||||
or (
|
||||
activity.hide_pace
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_PACE
|
||||
)
|
||||
or (
|
||||
activity.hide_map
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_MAP
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
# Return the activity streams
|
||||
return activity_streams
|
||||
return [
|
||||
transform_activity_streams(stream, activity, db)
|
||||
for stream in activity_streams
|
||||
]
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
@@ -76,10 +113,8 @@ def get_public_activity_streams(activity_id: int, db: Session):
|
||||
# Return None if public sharable links are disabled
|
||||
if not server_settings or not server_settings.public_shareable_links:
|
||||
return None
|
||||
|
||||
activity = activity_crud.get_activity_by_id_if_is_public(
|
||||
activity_id, db
|
||||
)
|
||||
|
||||
activity = activity_crud.get_activity_by_id_if_is_public(activity_id, db)
|
||||
|
||||
if not activity:
|
||||
# If the activity does not exist, return None
|
||||
@@ -104,22 +139,52 @@ def get_public_activity_streams(activity_id: int, db: Session):
|
||||
# Check if there are activity streams, if not return None
|
||||
if not activity_streams:
|
||||
return None
|
||||
|
||||
|
||||
activity_streams = [
|
||||
stream for stream in activity_streams
|
||||
stream
|
||||
for stream in activity_streams
|
||||
if not (
|
||||
(activity.hide_hr and stream.stream_type == 1) or
|
||||
(activity.hide_power and stream.stream_type == 2) or
|
||||
(activity.hide_cadence and stream.stream_type == 3) or
|
||||
(activity.hide_elevation and stream.stream_type == 4) or
|
||||
(activity.hide_speed and stream.stream_type == 5) or
|
||||
(activity.hide_pace and stream.stream_type == 6) or
|
||||
(activity.hide_map and stream.stream_type == 7)
|
||||
(
|
||||
activity.hide_hr
|
||||
and stream.stream_type == activity_streams_constants.STREAM_TYPE_HR
|
||||
)
|
||||
or (
|
||||
activity.hide_power
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_POWER
|
||||
)
|
||||
or (
|
||||
activity.hide_cadence
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_CADENCE
|
||||
)
|
||||
or (
|
||||
activity.hide_elevation
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_ELEVATION
|
||||
)
|
||||
or (
|
||||
activity.hide_speed
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_SPEED
|
||||
)
|
||||
or (
|
||||
activity.hide_pace
|
||||
and stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_PACE
|
||||
)
|
||||
or (
|
||||
activity.hide_map
|
||||
and stream.stream_type == activity_streams_constants.STREAM_TYPE_MAP
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
# Return the activity streams
|
||||
return activity_streams
|
||||
return [
|
||||
transform_activity_streams(stream, activity, db)
|
||||
for stream in activity_streams
|
||||
]
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
@@ -132,16 +197,16 @@ def get_public_activity_streams(activity_id: int, db: Session):
|
||||
) from err
|
||||
|
||||
|
||||
def get_activity_stream_by_type(activity_id: int, stream_type: int, token_user_id: int, db: Session):
|
||||
def get_activity_stream_by_type(
|
||||
activity_id: int, stream_type: int, token_user_id: int, db: Session
|
||||
):
|
||||
try:
|
||||
activity = activity_crud.get_activity_by_id(
|
||||
activity_id, db
|
||||
)
|
||||
activity = activity_crud.get_activity_by_id(activity_id, db)
|
||||
|
||||
if not activity:
|
||||
# If the activity does not exist, return None
|
||||
return None
|
||||
|
||||
|
||||
# Get the activity stream from the database
|
||||
activity_stream = (
|
||||
db.query(activity_streams_models.ActivityStreams)
|
||||
@@ -155,29 +220,57 @@ def get_activity_stream_by_type(activity_id: int, stream_type: int, token_user_i
|
||||
# Check if there are activity stream if not return None
|
||||
if not activity_stream:
|
||||
return None
|
||||
|
||||
|
||||
user_is_owner = True
|
||||
if token_user_id != activity.user_id:
|
||||
user_is_owner = False
|
||||
|
||||
if not user_is_owner:
|
||||
if activity.hide_hr and activity_stream.stream_type == 1:
|
||||
if (
|
||||
activity.hide_hr
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_HR
|
||||
):
|
||||
return None
|
||||
if activity.hide_power and activity_stream.stream_type == 2:
|
||||
if (
|
||||
activity.hide_power
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_POWER
|
||||
):
|
||||
return None
|
||||
if activity.hide_cadence and activity_stream.stream_type == 3:
|
||||
if (
|
||||
activity.hide_cadence
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_CADENCE
|
||||
):
|
||||
return None
|
||||
if activity.hide_elevation and activity_stream.stream_type == 4:
|
||||
if (
|
||||
activity.hide_elevation
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_ELEVATION
|
||||
):
|
||||
return None
|
||||
if activity.hide_speed and activity_stream.stream_type == 5:
|
||||
if (
|
||||
activity.hide_speed
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_SPEED
|
||||
):
|
||||
return None
|
||||
if activity.hide_pace and activity_stream.stream_type == 6:
|
||||
if (
|
||||
activity.hide_pace
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_PACE
|
||||
):
|
||||
return None
|
||||
if activity.hide_map and activity_stream.stream_type == 7:
|
||||
if (
|
||||
activity.hide_map
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_MAP
|
||||
):
|
||||
return None
|
||||
|
||||
# Return the activity stream
|
||||
return activity_stream
|
||||
return transform_activity_streams(activity_stream, activity, db)
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
@@ -190,6 +283,99 @@ def get_activity_stream_by_type(activity_id: int, stream_type: int, token_user_i
|
||||
) from err
|
||||
|
||||
|
||||
def transform_activity_streams(activity_stream, activity, db):
|
||||
"""
|
||||
Transforms an activity stream based on its stream type.
|
||||
If the stream type of the given activity_stream is heart rate (HR), this function delegates
|
||||
the transformation to the `transform_activity_streams_hr` function. Otherwise, it returns
|
||||
the activity_stream unchanged.
|
||||
Args:
|
||||
activity_stream: The activity stream object to be transformed.
|
||||
activity: The activity object associated with the stream.
|
||||
db: The database session or connection object.
|
||||
Returns:
|
||||
The transformed activity stream if the stream type is HR, otherwise the original activity_stream.
|
||||
"""
|
||||
if activity_stream.stream_type == activity_streams_constants.STREAM_TYPE_HR:
|
||||
return transform_activity_streams_hr(activity_stream, activity, db)
|
||||
|
||||
return activity_stream
|
||||
|
||||
|
||||
def transform_activity_streams_hr(activity_stream, activity, db):
|
||||
"""
|
||||
Transforms an activity stream by calculating the percentage of time spent in each heart rate zone based on user details.
|
||||
Args:
|
||||
activity_stream: The activity stream object containing waypoints with heart rate data.
|
||||
activity: The activity object associated with the stream, used to retrieve the user ID.
|
||||
db: The database session or connection used to fetch user details.
|
||||
Returns:
|
||||
The activity stream object with an added 'hr_zone_percentages' attribute, which contains the percentage of time spent in each heart rate zone and their respective HR boundaries. If waypoints or user details are missing, returns the original activity stream unchanged.
|
||||
Notes:
|
||||
- Heart rate zones are calculated using the formula: max_heart_rate = 220 - age.
|
||||
- The function expects waypoints to be a list of dicts with an "hr" key.
|
||||
- If no valid heart rate data is present, the activity stream is returned as is.
|
||||
"""
|
||||
# Check if the activity stream has waypoints
|
||||
waypoints = activity_stream.stream_waypoints
|
||||
if not waypoints or not isinstance(waypoints, list):
|
||||
# If there are no waypoints, return the activity stream as is
|
||||
return activity_stream
|
||||
|
||||
# Get the user details to calculate heart rate zones
|
||||
detail_user = users_crud.get_user_by_id(activity.user_id, db)
|
||||
if not detail_user or not detail_user.birthdate:
|
||||
# If user details are not available or birthdate is missing, return the activity stream as is
|
||||
return activity_stream
|
||||
|
||||
# Calculate the maximum heart rate based on the user's birthdate
|
||||
year = int(detail_user.birthdate.split("-")[0])
|
||||
current_year = datetime.datetime.now().year
|
||||
max_heart_rate = 220 - (current_year - year)
|
||||
|
||||
# Calculate heart rate zones based on the maximum heart rate
|
||||
zone_1 = max_heart_rate * 0.5
|
||||
zone_2 = max_heart_rate * 0.6
|
||||
zone_3 = max_heart_rate * 0.7
|
||||
zone_4 = max_heart_rate * 0.8
|
||||
|
||||
# Extract heart rate values from waypoints
|
||||
hr_values = np.array([wp.get("hr") for wp in waypoints if wp.get("hr") is not None])
|
||||
|
||||
# If there are no valid heart rate values, return the activity stream as is
|
||||
total = len(hr_values)
|
||||
if total == 0:
|
||||
return activity_stream
|
||||
|
||||
# Calculate the percentage of time spent in each heart rate zone
|
||||
zone_counts = [
|
||||
np.sum(hr_values < zone_1),
|
||||
np.sum((hr_values >= zone_1) & (hr_values < zone_2)),
|
||||
np.sum((hr_values >= zone_2) & (hr_values < zone_3)),
|
||||
np.sum((hr_values >= zone_3) & (hr_values < zone_4)),
|
||||
np.sum(hr_values >= zone_4),
|
||||
]
|
||||
zone_percentages = [round((count / total) * 100, 2) for count in zone_counts]
|
||||
|
||||
# Calculate zone HR boundaries for display
|
||||
zone_hr = {
|
||||
"zone_1": f"< {int(zone_1)}",
|
||||
"zone_2": f"{int(zone_1)} - {int(zone_2) - 1}",
|
||||
"zone_3": f"{int(zone_2)} - {int(zone_3) - 1}",
|
||||
"zone_4": f"{int(zone_3)} - {int(zone_4) - 1}",
|
||||
"zone_5": f">= {int(zone_4)}",
|
||||
}
|
||||
activity_stream.hr_zone_percentages = {
|
||||
"zone_1": {"percent": zone_percentages[0], "hr": zone_hr["zone_1"]},
|
||||
"zone_2": {"percent": zone_percentages[1], "hr": zone_hr["zone_2"]},
|
||||
"zone_3": {"percent": zone_percentages[2], "hr": zone_hr["zone_3"]},
|
||||
"zone_4": {"percent": zone_percentages[3], "hr": zone_hr["zone_4"]},
|
||||
"zone_5": {"percent": zone_percentages[4], "hr": zone_hr["zone_5"]},
|
||||
}
|
||||
|
||||
return activity_stream
|
||||
|
||||
|
||||
def get_public_activity_stream_by_type(activity_id: int, stream_type: int, db: Session):
|
||||
try:
|
||||
# Check if public sharable links are enabled in server settings
|
||||
@@ -198,10 +384,8 @@ def get_public_activity_stream_by_type(activity_id: int, stream_type: int, db: S
|
||||
# Return None if public sharable links are disabled
|
||||
if not server_settings or not server_settings.public_shareable_links:
|
||||
return None
|
||||
|
||||
activity = activity_crud.get_activity_by_id_if_is_public(
|
||||
activity_id, db
|
||||
)
|
||||
|
||||
activity = activity_crud.get_activity_by_id_if_is_public(activity_id, db)
|
||||
|
||||
if not activity:
|
||||
# If the activity does not exist, return None
|
||||
@@ -227,24 +411,51 @@ def get_public_activity_stream_by_type(activity_id: int, stream_type: int, db: S
|
||||
# Check if there is an activity stream; if not, return None
|
||||
if not activity_stream:
|
||||
return None
|
||||
|
||||
if activity.hide_hr and activity_stream.stream_type == 1:
|
||||
|
||||
if (
|
||||
activity.hide_hr
|
||||
and activity_stream.stream_type == activity_streams_constants.STREAM_TYPE_HR
|
||||
):
|
||||
return None
|
||||
if activity.hide_power and activity_stream.stream_type == 2:
|
||||
if (
|
||||
activity.hide_power
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_POWER
|
||||
):
|
||||
return None
|
||||
if activity.hide_cadence and activity_stream.stream_type == 3:
|
||||
if (
|
||||
activity.hide_cadence
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_CADENCE
|
||||
):
|
||||
return None
|
||||
if activity.hide_elevation and activity_stream.stream_type == 4:
|
||||
if (
|
||||
activity.hide_elevation
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_ELEVATION
|
||||
):
|
||||
return None
|
||||
if activity.hide_speed and activity_stream.stream_type == 5:
|
||||
if (
|
||||
activity.hide_speed
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_SPEED
|
||||
):
|
||||
return None
|
||||
if activity.hide_pace and activity_stream.stream_type == 6:
|
||||
if (
|
||||
activity.hide_pace
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_PACE
|
||||
):
|
||||
return None
|
||||
if activity.hide_map and activity_stream.stream_type == 7:
|
||||
if (
|
||||
activity.hide_map
|
||||
and activity_stream.stream_type
|
||||
== activity_streams_constants.STREAM_TYPE_MAP
|
||||
):
|
||||
return None
|
||||
|
||||
# Return the activity stream
|
||||
return activity_stream
|
||||
return transform_activity_streams(activity_stream, activity, db)
|
||||
except Exception as err:
|
||||
# Log the exception
|
||||
core_logger.print_to_log(
|
||||
|
||||
@@ -3,11 +3,29 @@ from typing import List
|
||||
|
||||
|
||||
class ActivityStreams(BaseModel):
|
||||
"""
|
||||
Represents a stream of activity data associated with an activity.
|
||||
|
||||
Attributes:
|
||||
id (int | None): Unique identifier for the activity stream (optional).
|
||||
activity_id (int): Identifier of the related activity.
|
||||
stream_type (int): Type of the stream (e.g., GPS, heart rate, etc.).
|
||||
stream_waypoints (List[dict]): List of waypoints or data points in the stream.
|
||||
strava_activity_stream_id (int | None): Identifier for the corresponding Strava activity stream (optional).
|
||||
hr_zone_percentages (dict | None): Heart rate zone percentages for the activity (optional).
|
||||
"""
|
||||
id: int | None = None
|
||||
activity_id: int
|
||||
stream_type: int
|
||||
stream_waypoints: List[dict]
|
||||
strava_activity_stream_id: int | None = None
|
||||
hr_zone_percentages: dict | None = None
|
||||
|
||||
class Config:
|
||||
"""
|
||||
Pydantic configuration class enabling ORM mode for model serialization.
|
||||
|
||||
Attributes:
|
||||
orm_mode (bool): When set to True, allows Pydantic models to read data from ORM objects.
|
||||
"""
|
||||
orm_mode = True
|
||||
@@ -9,30 +9,17 @@ LICENSE_IDENTIFIER = "AGPL-3.0-or-later"
|
||||
LICENSE_URL = "https://spdx.org/licenses/AGPL-3.0-or-later.html"
|
||||
ROOT_PATH = "/api/v1"
|
||||
FRONTEND_DIR = "/app/frontend/dist"
|
||||
ENVIRONMENT = os.getenv("ENVIRONMENT")
|
||||
ENVIRONMENT = os.getenv("ENVIRONMENT", "production")
|
||||
TZ = os.getenv("TZ", "UTC")
|
||||
|
||||
|
||||
def check_required_env_vars():
|
||||
required_env_vars = [
|
||||
"TZ",
|
||||
"DB_TYPE",
|
||||
"DB_HOST",
|
||||
"DB_PORT",
|
||||
"DB_USER",
|
||||
"DB_PASSWORD",
|
||||
"DB_DATABASE",
|
||||
"SECRET_KEY",
|
||||
"FERNET_KEY",
|
||||
"ALGORITHM",
|
||||
"ACCESS_TOKEN_EXPIRE_MINUTES",
|
||||
"REFRESH_TOKEN_EXPIRE_DAYS",
|
||||
"JAEGER_ENABLED",
|
||||
"JAEGER_PROTOCOL",
|
||||
"JAEGER_HOST",
|
||||
"JAGGER_PORT",
|
||||
"ENDURAIN_HOST",
|
||||
"GEOCODES_MAPS_API",
|
||||
"ENVIRONMENT",
|
||||
]
|
||||
|
||||
for var in required_env_vars:
|
||||
|
||||
@@ -5,34 +5,25 @@ from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy.engine.url import URL
|
||||
|
||||
|
||||
# Helper to fetch environment variables
|
||||
def get_env_variable(var_name: str) -> str:
|
||||
value = os.environ.get(var_name)
|
||||
if value is None:
|
||||
raise EnvironmentError(f"Environment variable {var_name} is not set")
|
||||
return value
|
||||
|
||||
|
||||
# Fetch the database type (e.g., mariadb or postgresql)
|
||||
db_type = get_env_variable("DB_TYPE").lower()
|
||||
db_type = os.environ.get("DB_TYPE", "postgres").lower()
|
||||
|
||||
# Define supported database drivers
|
||||
supported_drivers = {
|
||||
"mariadb": "mysql+mysqldb",
|
||||
"postgres": "postgresql+psycopg"
|
||||
}
|
||||
supported_drivers = {"mariadb": "mysql+mysqldb", "postgres": "postgresql+psycopg"}
|
||||
|
||||
if db_type not in supported_drivers:
|
||||
raise ValueError(f"Unsupported DB_TYPE: {db_type}. Supported types are {list(supported_drivers.keys())}")
|
||||
raise ValueError(
|
||||
f"Unsupported DB_TYPE: {db_type}. Supported types are {list(supported_drivers.keys())}"
|
||||
)
|
||||
|
||||
# Define the database connection URL using environment variables
|
||||
db_url = URL.create(
|
||||
drivername=supported_drivers[db_type],
|
||||
username=get_env_variable("DB_USER"),
|
||||
password=get_env_variable("DB_PASSWORD"),
|
||||
host=get_env_variable("DB_HOST"),
|
||||
port=get_env_variable("DB_PORT"),
|
||||
database=get_env_variable("DB_DATABASE"),
|
||||
username=os.environ.get("DB_USER", "endurain"),
|
||||
password=os.environ.get("DB_PASSWORD"),
|
||||
host=os.environ.get("DB_HOST", "postgres"),
|
||||
port=os.environ.get("DB_PORT", "5432"),
|
||||
database=os.environ.get("DB_DATABASE", "endurain"),
|
||||
)
|
||||
|
||||
# Create the SQLAlchemy engine
|
||||
|
||||
@@ -2,6 +2,17 @@ import logging
|
||||
|
||||
|
||||
def setup_main_logger():
|
||||
"""
|
||||
Sets up the main application logger and attaches a file handler to it, as well as to the Alembic and APScheduler loggers.
|
||||
|
||||
The logger writes log messages to 'logs/app.log' with a specific format and log level.
|
||||
- The main logger ('main_logger') is set to DEBUG level.
|
||||
- The Alembic logger ('alembic') and APScheduler logger ('apscheduler') are set to INFO level.
|
||||
- All three loggers share the same file handler and formatter.
|
||||
|
||||
Returns:
|
||||
logging.Logger: The configured main logger instance.
|
||||
"""
|
||||
main_logger = logging.getLogger("main_logger")
|
||||
main_logger.setLevel(logging.DEBUG)
|
||||
|
||||
@@ -29,21 +40,65 @@ def setup_main_logger():
|
||||
|
||||
|
||||
def get_main_logger():
|
||||
"""
|
||||
Returns the main logger instance for the application.
|
||||
|
||||
This function retrieves a logger named "main_logger" using Python's standard logging module.
|
||||
It can be used throughout the application to log messages under a consistent logger name.
|
||||
|
||||
Returns:
|
||||
logging.Logger: The logger instance named "main_logger".
|
||||
"""
|
||||
return logging.getLogger("main_logger")
|
||||
|
||||
|
||||
def print_to_log(message: str, type: str = "info", exc: Exception = None):
|
||||
def print_to_log(message: str, log_level: str = "info", exc: Exception = None):
|
||||
"""
|
||||
Logs a message at the specified log level using the main logger.
|
||||
|
||||
Args:
|
||||
message (str): The message to log.
|
||||
log_level (str, optional): The log level to use ('info', 'error', 'warning', 'debug'). Defaults to "info".
|
||||
exc (Exception, optional): An exception instance to include in the log if log_level is "error". Defaults to None.
|
||||
|
||||
Notes:
|
||||
- If log_level is "error" and exc is provided, exception information will be included in the log.
|
||||
"""
|
||||
main_logger = get_main_logger()
|
||||
if type == "info":
|
||||
if log_level == "info":
|
||||
main_logger.info(message)
|
||||
elif type == "error":
|
||||
elif log_level == "error":
|
||||
main_logger.error(message, exc_info=exc is not None)
|
||||
elif type == "warning":
|
||||
elif log_level == "warning":
|
||||
main_logger.warning(message)
|
||||
elif type == "debug":
|
||||
elif log_level == "debug":
|
||||
main_logger.debug(message)
|
||||
|
||||
|
||||
def print_to_log_and_console(message: str, type: str = "info", exc: Exception = None):
|
||||
print_to_log(message, type, exc)
|
||||
print(message)
|
||||
def print_to_log_and_console(
|
||||
message: str, log_level: str = "info", exc: Exception = None
|
||||
):
|
||||
"""
|
||||
Logs a message to both the main logger and the console.
|
||||
|
||||
This function temporarily adds a console handler to the main logger, logs the provided message at the specified log level (optionally including exception information), and then removes the console handler to ensure subsequent logs are not printed to the console.
|
||||
|
||||
Args:
|
||||
message (str): The message to log.
|
||||
log_level (str, optional): The logging level to use (e.g., "info", "warning", "error"). Defaults to "info".
|
||||
exc (Exception, optional): An exception to include in the log entry. Defaults to None.
|
||||
"""
|
||||
main_logger = get_main_logger()
|
||||
|
||||
# Create a temporary console handler
|
||||
console_handler = logging.StreamHandler()
|
||||
console_formatter = logging.Formatter("%(levelname)s: %(message)s")
|
||||
console_handler.setFormatter(console_formatter)
|
||||
|
||||
# Add console handler temporarily
|
||||
main_logger.addHandler(console_handler)
|
||||
|
||||
print_to_log(message, log_level, exc)
|
||||
|
||||
# Remove console handler so future logs only go to file
|
||||
main_logger.removeHandler(console_handler)
|
||||
|
||||
@@ -9,7 +9,7 @@ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
||||
|
||||
def setup_tracing(app):
|
||||
# Check if Jaeger tracing is enabled using the 'JAEGER_ENABLED' environment variable
|
||||
if os.environ.get("JAEGER_ENABLED") == "true":
|
||||
if os.environ.get("JAEGER_ENABLED", "false") == "true":
|
||||
# Configure OpenTelemetry with a specified service name
|
||||
trace.set_tracer_provider(
|
||||
TracerProvider(resource=Resource.create({"service.name": "backend_api"}))
|
||||
@@ -17,11 +17,11 @@ def setup_tracing(app):
|
||||
trace.get_tracer_provider().add_span_processor(
|
||||
BatchSpanProcessor(
|
||||
OTLPSpanExporter(
|
||||
endpoint=os.environ.get("JAEGER_PROTOCOL")
|
||||
endpoint=os.environ.get("JAEGER_PROTOCOL", "http")
|
||||
+ "://"
|
||||
+ os.environ.get("JAEGER_HOST")
|
||||
+ os.environ.get("JAEGER_HOST", "jaeger")
|
||||
+ ":"
|
||||
+ os.environ.get("JAGGER_PORT")
|
||||
+ os.environ.get("JAEGER_PORT", "4317")
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -26,6 +26,8 @@ import users.user_privacy_settings.schema as users_privacy_settings_schema
|
||||
|
||||
import core.logger as core_logger
|
||||
|
||||
import core.config as core_config
|
||||
|
||||
|
||||
def create_activity_objects(
|
||||
sessions_records: dict,
|
||||
@@ -38,7 +40,7 @@ def create_activity_objects(
|
||||
try:
|
||||
# Create an instance of TimezoneFinder
|
||||
tf = TimezoneFinder()
|
||||
timezone = os.environ.get("TZ")
|
||||
timezone = core_config.TZ
|
||||
|
||||
# Define variables
|
||||
gear_id = None
|
||||
@@ -767,6 +769,8 @@ def parse_frame_session(frame):
|
||||
activity_type = "virtual_ride"
|
||||
elif activity_type == "cycling" and sub_sport == "commuting":
|
||||
activity_type = "commuting_ride"
|
||||
elif activity_type == "cycling" and sub_sport == "indoor_cycling":
|
||||
activity_type = "indoor_ride"
|
||||
else:
|
||||
activity_type = sub_sport
|
||||
|
||||
@@ -1080,15 +1084,13 @@ def calculate_pace(distance, total_timer_time, activity_type, split_summary):
|
||||
if distance:
|
||||
if activity_type != "lap_swimming":
|
||||
return total_timer_time, total_timer_time / distance
|
||||
else:
|
||||
time_active = 0
|
||||
for split in split_summary:
|
||||
if split["split_type"] != 4:
|
||||
time_active += split["total_timer_time"]
|
||||
time_active = 0
|
||||
for split in split_summary:
|
||||
if split["split_type"] != 4:
|
||||
time_active += split["total_timer_time"]
|
||||
|
||||
return time_active, time_active / distance
|
||||
else:
|
||||
return total_timer_time, 0
|
||||
return time_active, time_active / distance
|
||||
return total_timer_time, 0
|
||||
|
||||
|
||||
def find_timezone_name(offset_seconds, reference_date):
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import gpxpy
|
||||
import gpxpy.gpx
|
||||
import os
|
||||
from geopy.distance import geodesic
|
||||
from timezonefinder import TimezoneFinder
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -16,6 +14,7 @@ import users.user_default_gear.utils as user_default_gear_utils
|
||||
import users.user_privacy_settings.schema as users_privacy_settings_schema
|
||||
|
||||
import core.logger as core_logger
|
||||
import core.config as core_config
|
||||
|
||||
|
||||
def parse_gpx_file(
|
||||
@@ -27,7 +26,7 @@ def parse_gpx_file(
|
||||
try:
|
||||
# Create an instance of TimezoneFinder
|
||||
tf = TimezoneFinder()
|
||||
timezone = os.environ.get("TZ")
|
||||
timezone = core_config.TZ
|
||||
|
||||
# Initialize default values for various variables
|
||||
activity_type = "Workout"
|
||||
|
||||
@@ -119,6 +119,8 @@ def create_app() -> FastAPI:
|
||||
|
||||
return app
|
||||
|
||||
# Silence stravalib token warnings
|
||||
os.environ["SILENCE_TOKEN_WARNINGS"] = "TRUE"
|
||||
|
||||
# Create the FastAPI application
|
||||
app = create_app()
|
||||
|
||||
@@ -11,6 +11,7 @@ import migrations.crud as migrations_crud
|
||||
import health_data.crud as health_data_crud
|
||||
|
||||
import core.logger as core_logger
|
||||
import core.config as core_config
|
||||
|
||||
|
||||
def process_migration_2(db: Session):
|
||||
@@ -47,7 +48,7 @@ def process_migration_2(db: Session):
|
||||
)
|
||||
continue
|
||||
|
||||
timezone = os.environ.get("TZ")
|
||||
timezone = core_config.TZ
|
||||
|
||||
# Get activity stream
|
||||
try:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import os
|
||||
|
||||
# JWT Token constants
|
||||
JWT_ALGORITHM = os.environ.get("ALGORITHM")
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES"))
|
||||
JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(os.environ.get("REFRESH_TOKEN_EXPIRE_DAYS"))
|
||||
JWT_ALGORITHM = os.environ.get("ALGORITHM", "HS256")
|
||||
JWT_ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("ACCESS_TOKEN_EXPIRE_MINUTES", "15"))
|
||||
JWT_REFRESH_TOKEN_EXPIRE_DAYS = int(os.environ.get("REFRESH_TOKEN_EXPIRE_DAYS", "7"))
|
||||
JWT_SECRET_KEY = os.environ.get("SECRET_KEY")
|
||||
|
||||
# Scopes definition
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import os
|
||||
|
||||
from fastapi import HTTPException, status
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -8,6 +6,7 @@ from stravalib.exc import AccessUnauthorized
|
||||
from timezonefinder import TimezoneFinder
|
||||
|
||||
import core.logger as core_logger
|
||||
import core.config as core_config
|
||||
|
||||
import activities.activity.schema as activities_schema
|
||||
import activities.activity.crud as activities_crud
|
||||
@@ -131,7 +130,7 @@ def parse_activity(
|
||||
) -> dict:
|
||||
# Create an instance of TimezoneFinder
|
||||
tf = TimezoneFinder()
|
||||
timezone = os.environ.get("TZ")
|
||||
timezone = core_config.TZ
|
||||
|
||||
# Get the detailed activity
|
||||
try:
|
||||
@@ -646,8 +645,12 @@ def fetch_and_process_activity_laps(
|
||||
"total_elapsed_time": lap.elapsed_time,
|
||||
"total_timer_time": lap.moving_time,
|
||||
"total_distance": lap.distance,
|
||||
"avg_heart_rate": round(lap.average_heartrate),
|
||||
"max_heart_rate": round(lap.max_heartrate),
|
||||
"avg_heart_rate": (
|
||||
round(lap.average_heartrate) if lap.average_heartrate else None
|
||||
),
|
||||
"max_heart_rate": (
|
||||
round(lap.max_heartrate) if lap.max_heartrate else None
|
||||
),
|
||||
"avg_cadence": round(cad_avg) if cad_stream else None,
|
||||
"max_cadence": round(cad_max) if cad_stream else None,
|
||||
"avg_power": round(power_avg) if power_stream else None,
|
||||
|
||||
234
backend/poetry.lock
generated
234
backend/poetry.lock
generated
@@ -2,14 +2,14 @@
|
||||
|
||||
[[package]]
|
||||
name = "alembic"
|
||||
version = "1.16.1"
|
||||
version = "1.16.2"
|
||||
description = "A database migration tool for SQLAlchemy."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "alembic-1.16.1-py3-none-any.whl", hash = "sha256:0cdd48acada30d93aa1035767d67dff25702f8de74d7c3919f2e8492c8db2e67"},
|
||||
{file = "alembic-1.16.1.tar.gz", hash = "sha256:43d37ba24b3d17bc1eb1024fe0f51cd1dc95aeb5464594a02c6bb9ca9864bfa4"},
|
||||
{file = "alembic-1.16.2-py3-none-any.whl", hash = "sha256:5f42e9bd0afdbd1d5e3ad856c01754530367debdebf21ed6894e34af52b3bb03"},
|
||||
{file = "alembic-1.16.2.tar.gz", hash = "sha256:e53c38ff88dadb92eb22f8b150708367db731d58ad7e9d417c9168ab516cbed8"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -673,14 +673,14 @@ idna = ">=2.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.12"
|
||||
version = "0.115.13"
|
||||
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d"},
|
||||
{file = "fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681"},
|
||||
{file = "fastapi-0.115.13-py3-none-any.whl", hash = "sha256:0a0cab59afa7bab22f5eb347f8c9864b681558c278395e94035a741fc10cd865"},
|
||||
{file = "fastapi-0.115.13.tar.gz", hash = "sha256:55d1d25c2e1e0a0a50aceb1c8705cd932def273c102bff0b1c1da88b3c6eb307"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -813,14 +813,14 @@ testing = ["coverage", "pytest", "pytest-vcr"]
|
||||
|
||||
[[package]]
|
||||
name = "garth"
|
||||
version = "0.5.16"
|
||||
version = "0.5.17"
|
||||
description = "Garmin SSO auth + Connect client"
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "garth-0.5.16-py3-none-any.whl", hash = "sha256:b10ec2b5c7e6816207b2e66a3481aa2b8a0e9d8a47462ee4c21c2a76d28372cf"},
|
||||
{file = "garth-0.5.16.tar.gz", hash = "sha256:e599b12f92581d9079b05ae968217cbeb0a8e392c029561f3ffc9f8a5987a6b2"},
|
||||
{file = "garth-0.5.17-py3-none-any.whl", hash = "sha256:4b109140dfacea6d96060bad8dead8da36699a0729cb0c4ca3b31fa49f860e88"},
|
||||
{file = "garth-0.5.17.tar.gz", hash = "sha256:48ede938c38b2fd70777e55c7025538775d93c1047f43cc7c4481f2a04b109cb"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -1041,49 +1041,49 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "h3"
|
||||
version = "4.2.2"
|
||||
version = "4.3.0"
|
||||
description = "Uber's hierarchical hexagonal geospatial indexing system"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "h3-4.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:75bae45428b133c3006a3c72646e42856c8800a74e47a818e1bcd242a86e9dae"},
|
||||
{file = "h3-4.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e9992eb522636cbcf7c5b5fd32436395bee9423805ee06fd8ad6eca83f4af654"},
|
||||
{file = "h3-4.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7687de29510ece132e59739bd87dcb3fe25783150502685d7d5be266ea4162b5"},
|
||||
{file = "h3-4.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51d715d0471bb581bd94f72529c3194e9b91ce30202672d2050161292f774742"},
|
||||
{file = "h3-4.2.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f4eab1fdfc15b83edac415377790114cae5f117ae7d3293fc9b27354fb2987e6"},
|
||||
{file = "h3-4.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:6c2677a20c46148a31838e5b4eed11554fd83a9f07c63a2056f4187cb1ba605f"},
|
||||
{file = "h3-4.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b4039d3bc8236ea371402c755867c6b0d36d072dcd1117e22a9040dd63ba522c"},
|
||||
{file = "h3-4.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d637189edf2f1523d625e03415ce4b7fdfe224e6254b73c987b8774214a09edf"},
|
||||
{file = "h3-4.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b88187fcfc88a4315633b0cf27fa55b736948b78833045d03a08a33db4feb3"},
|
||||
{file = "h3-4.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2459e482147a37292e9638f08bf3b4f32df972457064721b888883c1883848c9"},
|
||||
{file = "h3-4.2.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4db576b1d96d3d828f1b06eef943e1ad00eb9ff2caae5fb894ec3f4badcfde34"},
|
||||
{file = "h3-4.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:db1dc702760a8b94abbd1ddb6efd1e88c63032f0bbd7288159af54ce6a611f3a"},
|
||||
{file = "h3-4.2.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c09b7df785cf8a191e33e3b15ba34aef864b78061b2aeaee87a434aa703bb29c"},
|
||||
{file = "h3-4.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:35afe80a011abf9ffa8bc885b847a01a0909fca6556f6f997d6adf7beec87e38"},
|
||||
{file = "h3-4.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a56aa550e56de5fc9726fd20f484b23a336d6b0844e40604500ce9c3bc8f86c"},
|
||||
{file = "h3-4.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e83d7484441f42a6bcd0ffc9626387e4ff8a7563ae659667f5b59b814fe198b7"},
|
||||
{file = "h3-4.2.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737d7cf3aeebc60b0e091220a1bdf94608f16457589cf67acc288444ee5f5dc8"},
|
||||
{file = "h3-4.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:bc61382bceff776f0d6278e6ba54d94c9e15137d527adbd50914c7591f9c4460"},
|
||||
{file = "h3-4.2.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d70059de58956d58a92213a0b7adbc4fcf643a102911bc1ad637c99554765add"},
|
||||
{file = "h3-4.2.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a9b32da65d9ed6f97ca9ff404721c97f0faec5656e205a7601628ae402f2b282"},
|
||||
{file = "h3-4.2.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bea5b734343c5074c60a575cb609157f51aa1c8da5c4064491bb5c58a4dac55"},
|
||||
{file = "h3-4.2.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7836e8e32c30f34fba1c26cdce8e4e7b4a9f48d18f3a2ab9edfeee995deb92a"},
|
||||
{file = "h3-4.2.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:652299d3bef76af65f5166bc4af0598afa6c7d93cfc13c7a645a95d8c8e5b10e"},
|
||||
{file = "h3-4.2.2-cp313-cp313-win_amd64.whl", hash = "sha256:dfda0f2864462f074cc22df75a50b9db010988e341a1871993675bb21954891b"},
|
||||
{file = "h3-4.2.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4c7320ea6551b21275e14cde684ff060b2a7ee1cfd7c4232cccc8922ead5820c"},
|
||||
{file = "h3-4.2.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0e8e6ba11dba11b130fced35e553b2813f4ab0ff5d34d570443e7e9d454845d6"},
|
||||
{file = "h3-4.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4988081ae5544bf316bff6dcf627328a244841580f73238eadef5a7374b63384"},
|
||||
{file = "h3-4.2.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecd448725c7c36c59edf831e4666ea950060e99afdfc38e384da3cbab21453da"},
|
||||
{file = "h3-4.2.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8ffb174db4b7fff195ac72bd1c4eef9b4b720ede5c991257b0136860a9c2d3f5"},
|
||||
{file = "h3-4.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:7fc37373148b2c5626da1a580db4d686dac3243e1cb1c29b20aab281c640a129"},
|
||||
{file = "h3-4.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8794ee6f88c6af206bc74d18cac0c3b57aeea8eb697a884e6aa7ad9c8c132d26"},
|
||||
{file = "h3-4.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6006c28b9d1fbf993c916983ed497add8a0be73a2bf9fb684324624cc69097c1"},
|
||||
{file = "h3-4.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:529ae90ac230561cc9150038d629d396772f272bef95af870d281cb14145d6f3"},
|
||||
{file = "h3-4.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7bcdc503332b7087f083a699c1434084cad38dd15d60e4390da54019628a813"},
|
||||
{file = "h3-4.2.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0b48d8b1efe07e13aecb279c8154962d05b13cb7056d5ed133f66ddd71a490a5"},
|
||||
{file = "h3-4.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:b91698ec93973dfbc8926d49a2aca00f7ec23f34e354744ec2db24185954b188"},
|
||||
{file = "h3-4.2.2.tar.gz", hash = "sha256:5cc78d546b5c732f480a842e3e436c393fe37fe59b063fe9cb5589206b7c4c7e"},
|
||||
{file = "h3-4.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:15bf255188d5fdd4b33cd24ed3952fbf8161575a42dcba2730a4530ef0f057fa"},
|
||||
{file = "h3-4.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d45005bb56a9d70ab420bda6096c579e4246038f8b0553394b2b82aa474f2d1c"},
|
||||
{file = "h3-4.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c3a8d883b7b233b30a372f85434f48cba3b42b926598a835a73512a0a3d958"},
|
||||
{file = "h3-4.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e5d2180c1391db507473a6877c2df036de396d88212e90d7d0f6c73b33901d3"},
|
||||
{file = "h3-4.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7de13e47ea3e3a3fc06c50def371050227ecfb1ccf0c8923c2b8bd8acba27355"},
|
||||
{file = "h3-4.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:67c82019afb5b0b214f50502a141bdb4e9b72dfffe5ed63fdd7630119edee123"},
|
||||
{file = "h3-4.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:689cb238becb5f5af3631934e54cb8c7782c60af8fa9daf7e2cd972235b88c6a"},
|
||||
{file = "h3-4.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:69dc186712c6adeb500b1aaec875493c742fbddb7b9b40d02016769a991732d1"},
|
||||
{file = "h3-4.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f870c0c90fd6657b51d8b1501142e9bee0787437be9486251a0e86e2d1513c1"},
|
||||
{file = "h3-4.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56862fa893903b966840a4aff27e19be2ee62b80e7e67d93dbd805ab719195bc"},
|
||||
{file = "h3-4.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0f6b8432a104dacf21a2a06bb5dd60e2b63375b544d4964d7e4980543d8285da"},
|
||||
{file = "h3-4.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:a9c18bec33c300485452f3ed46a5165d27b186cfb15ee36fb7252228483c84b9"},
|
||||
{file = "h3-4.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a647d9635633ca243d8c24c990a9e37e3b294781fd6a47a0f5dfe9a1dec0dd13"},
|
||||
{file = "h3-4.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6adba5b249792d8f01abd9e410439a2912a4e900073cfcc2d32297f2ec803db0"},
|
||||
{file = "h3-4.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69ceab01438558ab2b5a90a2a30cf9e1627a4a8b6923ad8ec51a9e2dfa2ad0e8"},
|
||||
{file = "h3-4.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8a7d63ee5213f43dea80b79821fa7e07d9377260bc352201cac5c954017ea99"},
|
||||
{file = "h3-4.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6409d7d9c7c3a888359c5012aa7eedfe909da230e0f109d6a80ab973c0a2bf73"},
|
||||
{file = "h3-4.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:24ad4e247388bb7f5afece4fb5f901b1168c4b2f0417b3459bae5c4939939327"},
|
||||
{file = "h3-4.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ee2d2ab17c10172b265876b4655094c530251cae24f03cf0d2f339aca5ee603f"},
|
||||
{file = "h3-4.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:902e92c06bb2c08a6a551d5ca6a69a5deae2a9802263ae54455a803c3fb1766c"},
|
||||
{file = "h3-4.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c61761f524d7b42872f085dfcafc6466b7ff2ef0fc4c4626b336448e8fffca3a"},
|
||||
{file = "h3-4.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe4c11a1b512ef64cc155c84977fa41bdc8f059d0db3c411da854805b08ad59"},
|
||||
{file = "h3-4.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e46ac17a7dc520ba7e5b5698bf39f3123e68e2c834398711a93a42ab3ec46d60"},
|
||||
{file = "h3-4.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:02df69a61c59d06b8516c85d9cf4f6bb2efc8e8b526000ac46b16328540d0952"},
|
||||
{file = "h3-4.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b75f40700466cf5ac82509cccb3c66404b586ee9ddf97168f4550e02504edfa1"},
|
||||
{file = "h3-4.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2edcae634d23535827e16bf16282646eacacd911f1735aacee381ff717d8a5e9"},
|
||||
{file = "h3-4.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:309bed3271bb7dc5ed32b073dd0a3fda56f4985cf7c4ec2b33d0621ce4357e16"},
|
||||
{file = "h3-4.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81ce3b1caeb510618332853a75d6aba5a259f4cf03f9e681c726063915b90d14"},
|
||||
{file = "h3-4.3.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:dbaa311260ed1497f6ff97ec8c582fcfb8fbfd90a4156d3d4831d964f0431702"},
|
||||
{file = "h3-4.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:0963d760b9b067cffbebab3c8514214bdbc18eb66c6292092ca6439fbed25b2b"},
|
||||
{file = "h3-4.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f22ce0f63b1bae098e7258321d51ea268b4cb9f8b4db0be68042b5158e1433b"},
|
||||
{file = "h3-4.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c961be621a7a14a6fcc2a71c5c4e6b4cf0865781d29e000048a4a5ceb8734ff2"},
|
||||
{file = "h3-4.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e42d6efec68363943dd062e228f2d77b288041870679ffd08eb37813e9694c96"},
|
||||
{file = "h3-4.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5d19bdb1d592fa01aaeb6a469050c621eea6d82ffb99f095b5502f483151b70"},
|
||||
{file = "h3-4.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:019f47d5d3bd174b1714ba4c2560783c2a81be98b6a37b130a52e6f00c2aea37"},
|
||||
{file = "h3-4.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:23ee690ab45f95997fa20674937eb2d49018395c272ad63373d0f2cc31925094"},
|
||||
{file = "h3-4.3.0.tar.gz", hash = "sha256:b9d35dfa97e7f4b4c9c9f1564f22a44565694ac1ca96469a08170869e7d0ce0a"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -1226,18 +1226,18 @@ test = ["portend", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-c
|
||||
|
||||
[[package]]
|
||||
name = "jaraco-functools"
|
||||
version = "4.1.0"
|
||||
version = "4.2.1"
|
||||
description = "Functools like those found in stdlib"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649"},
|
||||
{file = "jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d"},
|
||||
{file = "jaraco_functools-4.2.1-py3-none-any.whl", hash = "sha256:590486285803805f4b1f99c60ca9e94ed348d4added84b74c7a12885561e524e"},
|
||||
{file = "jaraco_functools-4.2.1.tar.gz", hash = "sha256:be634abfccabce56fa3053f8c7ebe37b682683a4ee7793670ced17bab0087353"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
more-itertools = "*"
|
||||
more_itertools = "*"
|
||||
|
||||
[package.extras]
|
||||
check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
|
||||
@@ -1503,75 +1503,75 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.3.0"
|
||||
version = "2.3.1"
|
||||
description = "Fundamental package for array computing in Python"
|
||||
optional = false
|
||||
python-versions = ">=3.11"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "numpy-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c3c9fdde0fa18afa1099d6257eb82890ea4f3102847e692193b54e00312a9ae9"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:46d16f72c2192da7b83984aa5455baee640e33a9f1e61e656f29adf55e406c2b"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a0be278be9307c4ab06b788f2a077f05e180aea817b3e41cebbd5aaf7bd85ed3"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:99224862d1412d2562248d4710126355d3a8db7672170a39d6909ac47687a8a4"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2393a914db64b0ead0ab80c962e42d09d5f385802006a6c87835acb1f58adb96"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7729c8008d55e80784bd113787ce876ca117185c579c0d626f59b87d433ea779"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:06d4fb37a8d383b769281714897420c5cc3545c79dc427df57fc9b852ee0bf58"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c39ec392b5db5088259c68250e342612db82dc80ce044cf16496cf14cf6bc6f8"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-win32.whl", hash = "sha256:ee9d3ee70d62827bc91f3ea5eee33153212c41f639918550ac0475e3588da59f"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:43c55b6a860b0eb44d42341438b03513cf3879cb3617afb749ad49307e164edd"},
|
||||
{file = "numpy-2.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:2e6a1409eee0cb0316cb64640a49a49ca44deb1a537e6b1121dc7c458a1299a8"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:389b85335838155a9076e9ad7f8fdba0827496ec2d2dc32ce69ce7898bde03ba"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9498f60cd6bb8238d8eaf468a3d5bb031d34cd12556af53510f05fcf581c1b7e"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:622a65d40d8eb427d8e722fd410ac3ad4958002f109230bc714fa551044ebae2"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:b9446d9d8505aadadb686d51d838f2b6688c9e85636a0c3abaeb55ed54756459"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:50080245365d75137a2bf46151e975de63146ae6d79f7e6bd5c0e85c9931d06a"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c24bb4113c66936eeaa0dc1e47c74770453d34f46ee07ae4efd853a2ed1ad10a"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4d8d294287fdf685281e671886c6dcdf0291a7c19db3e5cb4178d07ccf6ecc67"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6295f81f093b7f5769d1728a6bd8bf7466de2adfa771ede944ce6711382b89dc"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-win32.whl", hash = "sha256:e6648078bdd974ef5d15cecc31b0c410e2e24178a6e10bf511e0557eed0f2570"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:0898c67a58cdaaf29994bc0e2c65230fd4de0ac40afaf1584ed0b02cd74c6fdd"},
|
||||
{file = "numpy-2.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:bd8df082b6c4695753ad6193018c05aac465d634834dca47a3ae06d4bb22d9ea"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5754ab5595bfa2c2387d241296e0381c21f44a4b90a776c3c1d39eede13a746a"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d11fa02f77752d8099573d64e5fe33de3229b6632036ec08f7080f46b6649959"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:aba48d17e87688a765ab1cd557882052f238e2f36545dfa8e29e6a91aef77afe"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4dc58865623023b63b10d52f18abaac3729346a7a46a778381e0e3af4b7f3beb"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:df470d376f54e052c76517393fa443758fefcdd634645bc9c1f84eafc67087f0"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:87717eb24d4a8a64683b7a4e91ace04e2f5c7c77872f823f02a94feee186168f"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8fa264d56882b59dcb5ea4d6ab6f31d0c58a57b41aec605848b6eb2ef4a43e8"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e651756066a0eaf900916497e20e02fe1ae544187cb0fe88de981671ee7f6270"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-win32.whl", hash = "sha256:e43c3cce3b6ae5f94696669ff2a6eafd9a6b9332008bafa4117af70f4b88be6f"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:81ae0bf2564cf475f94be4a27ef7bcf8af0c3e28da46770fc904da9abd5279b5"},
|
||||
{file = "numpy-2.3.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8738baa52505fa6e82778580b23f945e3578412554d937093eac9205e845e6e"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39b27d8b38942a647f048b675f134dd5a567f95bfff481f9109ec308515c51d8"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0eba4a1ea88f9a6f30f56fdafdeb8da3774349eacddab9581a21234b8535d3d3"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0f1f11d0a1da54927436505a5a7670b154eac27f5672afc389661013dfe3d4f"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:690d0a5b60a47e1f9dcec7b77750a4854c0d690e9058b7bef3106e3ae9117808"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:8b51ead2b258284458e570942137155978583e407babc22e3d0ed7af33ce06f8"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:aaf81c7b82c73bd9b45e79cfb9476cb9c29e937494bfe9092c26aece812818ad"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f420033a20b4f6a2a11f585f93c843ac40686a7c3fa514060a97d9de93e5e72b"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d344ca32ab482bcf8735d8f95091ad081f97120546f3d250240868430ce52555"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-win32.whl", hash = "sha256:48a2e8eaf76364c32a1feaa60d6925eaf32ed7a040183b807e02674305beef61"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ba17f93a94e503551f154de210e4d50c5e3ee20f7e7a1b5f6ce3f22d419b93bb"},
|
||||
{file = "numpy-2.3.0-cp313-cp313t-win_arm64.whl", hash = "sha256:f14e016d9409680959691c109be98c436c6249eaf7f118b424679793607b5944"},
|
||||
{file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:80b46117c7359de8167cc00a2c7d823bdd505e8c7727ae0871025a86d668283b"},
|
||||
{file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:5814a0f43e70c061f47abd5857d120179609ddc32a613138cbb6c4e9e2dbdda5"},
|
||||
{file = "numpy-2.3.0-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ef6c1e88fd6b81ac6d215ed71dc8cd027e54d4bf1d2682d362449097156267a2"},
|
||||
{file = "numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33a5a12a45bb82d9997e2c0b12adae97507ad7c347546190a18ff14c28bbca12"},
|
||||
{file = "numpy-2.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:54dfc8681c1906d239e95ab1508d0a533c4a9505e52ee2d71a5472b04437ef97"},
|
||||
{file = "numpy-2.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e017a8a251ff4d18d71f139e28bdc7c31edba7a507f72b1414ed902cbe48c74d"},
|
||||
{file = "numpy-2.3.0.tar.gz", hash = "sha256:581f87f9e9e9db2cba2141400e160e9dd644ee248788d6f90636eeb8fd9260a6"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff"},
|
||||
{file = "numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76"},
|
||||
{file = "numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42"},
|
||||
{file = "numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc"},
|
||||
{file = "numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244"},
|
||||
{file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3"},
|
||||
{file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b"},
|
||||
{file = "numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7"},
|
||||
{file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df"},
|
||||
{file = "numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68"},
|
||||
{file = "numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb"},
|
||||
{file = "numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oauthlib"
|
||||
version = "3.2.2"
|
||||
version = "3.3.1"
|
||||
description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"},
|
||||
{file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"},
|
||||
{file = "oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1"},
|
||||
{file = "oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -2245,14 +2245,14 @@ six = ">=1.5"
|
||||
|
||||
[[package]]
|
||||
name = "python-dotenv"
|
||||
version = "1.1.0"
|
||||
version = "1.1.1"
|
||||
description = "Read key-value pairs from a .env file and set them as environment variables"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"},
|
||||
{file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"},
|
||||
{file = "python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc"},
|
||||
{file = "python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
@@ -2627,14 +2627,14 @@ full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart
|
||||
|
||||
[[package]]
|
||||
name = "stravalib"
|
||||
version = "2.3"
|
||||
version = "2.4"
|
||||
description = "A Python package that makes it easy to access and download data from the Strava V3 REST API."
|
||||
optional = false
|
||||
python-versions = ">=3.10"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "stravalib-2.3-py3-none-any.whl", hash = "sha256:93921f51d87d5cee70415dd374a8a4abb28610d95c0ac1c7c5334af60d509b8d"},
|
||||
{file = "stravalib-2.3.tar.gz", hash = "sha256:13e83ac7bab7bdcd898599867c61346e5bd329497cf3f7d6cbb0a8d0211a9f89"},
|
||||
{file = "stravalib-2.4-py3-none-any.whl", hash = "sha256:04ea866eb06d0de144e30fa11effc225295e04f2dc9503fb9a361bbd274b5241"},
|
||||
{file = "stravalib-2.4.tar.gz", hash = "sha256:eb52a05f3a650a5afc57fc61ebd1936788b9b5b6f56e7e2b68fcdf198a57656f"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
@@ -2809,14 +2809,14 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "urllib3"
|
||||
version = "2.4.0"
|
||||
version = "2.5.0"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"},
|
||||
{file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"},
|
||||
{file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"},
|
||||
{file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
|
||||
109
docker-compose.yml-multiple-backends.example
Normal file
109
docker-compose.yml-multiple-backends.example
Normal file
@@ -0,0 +1,109 @@
|
||||
services:
|
||||
endurain:
|
||||
container_name: endurain
|
||||
image: ghcr.io/joaovitoriasilva/endurain:latest
|
||||
environment:
|
||||
# Read the Getting Started section on https://docs.endurain.com for all available env variables
|
||||
- TZ=Europe/Lisbon
|
||||
- DB_PASSWORD=changeme
|
||||
- SECRET_KEY=chnageme # openssl rand -hex 32
|
||||
- FERNET_KEY=changeme # https://fernetkeygen.com or python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
||||
- GEOCODES_MAPS_API=changeme
|
||||
- ENDURAIN_HOST=http://localhost:8080 # host or local ip (example: http://192.168.1.10:8080 or https://endurain.com), default is http://localhost:8080
|
||||
volumes:
|
||||
# - <local_path>/endurain/backend/app:/app/backend # Configure volume if you want to edit the code locally by cloning the repo
|
||||
- <local_path>/endurain/backend/user_images:/app/backend/user_images # necessary for user image persistence on container image updates
|
||||
- <local_path>/endurain/backend/files/bulk_import:/app/backend/files/bulk_import # necessary to enable bulk import of activities. Place here your activities files
|
||||
- <local_path>/endurain/backend/files/processed:/app/backend/files/processed # necessary for processed original files persistence on container image updates
|
||||
- <local_path>/endurain/backend/logs:/app/backend/logs # log files for the backend
|
||||
ports:
|
||||
- "8080:8080" # Endurain port, change per your needs
|
||||
depends_on:
|
||||
postgres: # mariadb or postgres
|
||||
condition: service_healthy # remove this line when using mariadb
|
||||
jaeger: {} # optional
|
||||
restart: unless-stopped
|
||||
|
||||
# mysql mariadb logic
|
||||
mariadb:
|
||||
image: mariadb:latest
|
||||
container_name: mariadb
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=changeme
|
||||
- MYSQL_DATABASE=endurain
|
||||
- MYSQL_USER=endurain
|
||||
- MYSQL_PASSWORD=changeme
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- <local_path>/mariadb:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
|
||||
# postgres logic
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
container_name: postgres
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=changeme
|
||||
- POSTGRES_DB=endurain
|
||||
- POSTGRES_USER=endurain
|
||||
- PGDATA=/var/lib/postgresql/data/pgdata
|
||||
ports:
|
||||
- "5432:5432"
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U endurain"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
- <local_path>/postgres:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
|
||||
# Jaeger for opentelemetry - optional
|
||||
# Jaeger is not enabled by default. If you do not need it or want it, you can remove this container
|
||||
jaeger:
|
||||
container_name: jaeger
|
||||
image: jaegertracing/all-in-one:latest
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Lisbon
|
||||
- COLLECTOR_ZIPKIN_HOST_PORT=:9411
|
||||
ports:
|
||||
- 6831:6831/udp
|
||||
- 6832:6832/udp
|
||||
- 5778:5778
|
||||
- 16686:16686
|
||||
- 4317:4317
|
||||
- 4318:4318
|
||||
- 14250:14250
|
||||
- 14268:14268
|
||||
- 14269:14269
|
||||
- 9411:9411
|
||||
restart: unless-stopped
|
||||
|
||||
# adminer for DB manipulation - optional
|
||||
adminer:
|
||||
container_name: adminer
|
||||
image: adminer
|
||||
ports:
|
||||
- 8081:8080
|
||||
restart: unless-stopped
|
||||
|
||||
# phpmyadmin for DB manipulation - optional
|
||||
phpmyadmin:
|
||||
container_name: phpmyadmin
|
||||
image: phpmyadmin
|
||||
ports:
|
||||
- 81:80
|
||||
environment:
|
||||
- PMA_HOST=mariadb
|
||||
- PMA_ARBITRARY=1
|
||||
depends_on:
|
||||
- mariadb
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
default:
|
||||
external: true
|
||||
name: endurain_network
|
||||
@@ -1,107 +1,31 @@
|
||||
services:
|
||||
endurain:
|
||||
container_name: endurain
|
||||
container_name: endurain-app
|
||||
image: ghcr.io/joaovitoriasilva/endurain:latest
|
||||
environment:
|
||||
# Read the Getting Started section on https://docs.endurain.com for all available env variables
|
||||
- TZ=Europe/Lisbon
|
||||
- DB_PASSWORD=changeme
|
||||
- SECRET_KEY=chnageme # openssl rand -hex 32
|
||||
- FERNET_KEY=changeme # https://fernetkeygen.com or python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
|
||||
- GEOCODES_MAPS_API=changeme
|
||||
- ENDURAIN_HOST=http://localhost:8080 # host or local ip (example: http://192.168.1.10:8080 or https://endurain.com), default is http://localhost:8080
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
# - <local_path>/endurain/backend/app:/app/backend # Configure volume if you want to edit the code locally by cloning the repo
|
||||
- <local_path>/endurain/backend/logs:/app/backend/logs # log files for the backend
|
||||
- <local_path>/endurain/backend/config:/app/backend/config # necessary for image and activity files persistence on docker image update
|
||||
ports:
|
||||
- "8080:8080" # Endurain port, change per your needs
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
postgres: # mariadb or postgres
|
||||
condition: service_healthy # remove this line when using mariadb
|
||||
jaeger: {} # optional
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
# mysql mariadb logic
|
||||
mariadb:
|
||||
image: mariadb:latest
|
||||
container_name: mariadb
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=changeme
|
||||
- MYSQL_DATABASE=endurain
|
||||
- MYSQL_USER=endurain
|
||||
- MYSQL_PASSWORD=changeme
|
||||
ports:
|
||||
- "3306:3306"
|
||||
volumes:
|
||||
- <local_path>/mariadb:/var/lib/mysql
|
||||
restart: unless-stopped
|
||||
|
||||
# postgres logic
|
||||
# postgres logic
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
container_name: postgres
|
||||
environment:
|
||||
- POSTGRES_PASSWORD=changeme
|
||||
- POSTGRES_DB=endurain
|
||||
- POSTGRES_USER=endurain
|
||||
- PGDATA=/var/lib/postgresql/data/pgdata
|
||||
ports:
|
||||
- "5432:5432"
|
||||
image: docker.io/postgres:17.5
|
||||
container_name: endurain-postgres
|
||||
env_file:
|
||||
- .env
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U endurain"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
volumes:
|
||||
- <local_path>/postgres:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
|
||||
# Jaeger for opentelemetry - optional
|
||||
# Jaeger is not enabled by default. If you do not need it or want it, you can remove this container
|
||||
jaeger:
|
||||
container_name: jaeger
|
||||
image: jaegertracing/all-in-one:latest
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/Lisbon
|
||||
- COLLECTOR_ZIPKIN_HOST_PORT=:9411
|
||||
ports:
|
||||
- 6831:6831/udp
|
||||
- 6832:6832/udp
|
||||
- 5778:5778
|
||||
- 16686:16686
|
||||
- 4317:4317
|
||||
- 4318:4318
|
||||
- 14250:14250
|
||||
- 14268:14268
|
||||
- 14269:14269
|
||||
- 9411:9411
|
||||
restart: unless-stopped
|
||||
|
||||
# adminer for DB manipulation - optional
|
||||
adminer:
|
||||
container_name: adminer
|
||||
image: adminer
|
||||
ports:
|
||||
- 8081:8080
|
||||
restart: unless-stopped
|
||||
|
||||
# phpmyadmin for DB manipulation - optional
|
||||
phpmyadmin:
|
||||
container_name: phpmyadmin
|
||||
image: phpmyadmin
|
||||
ports:
|
||||
- 81:80
|
||||
environment:
|
||||
- PMA_HOST=mariadb
|
||||
- PMA_ARBITRARY=1
|
||||
depends_on:
|
||||
- mariadb
|
||||
restart: unless-stopped
|
||||
|
||||
networks:
|
||||
default:
|
||||
external: true
|
||||
name: endurain_network
|
||||
- /opt/endurain/postgres:/var/lib/postgresql/data
|
||||
restart: unless-stopped
|
||||
@@ -62,21 +62,7 @@ RUN set -eux; \
|
||||
# Define environment variables
|
||||
ENV UID=1000 \
|
||||
GID=1000 \
|
||||
TZ="UTC" \
|
||||
DB_TYPE="postgres" \
|
||||
DB_HOST="postgres" \
|
||||
DB_PORT=5432 \
|
||||
DB_USER="endurain" \
|
||||
DB_DATABASE="endurain" \
|
||||
ALGORITHM="HS256" \
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES=15 \
|
||||
REFRESH_TOKEN_EXPIRE_DAYS=7 \
|
||||
JAEGER_ENABLED="false" \
|
||||
JAEGER_HOST="jaeger" \
|
||||
JAEGER_PROTOCOL="http" \
|
||||
JAGGER_PORT=4317 \
|
||||
BEHIND_PROXY=false \
|
||||
ENVIRONMENT="production"
|
||||
BEHIND_PROXY=false
|
||||
|
||||
# Set the working directory to /app/frontend
|
||||
WORKDIR /app/frontend
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
set -e
|
||||
|
||||
echo_info_log() {
|
||||
echo "INFO: $1"
|
||||
echo "INFO: $1"
|
||||
}
|
||||
|
||||
echo_error_log() {
|
||||
echo "ERROR: $1" >&2
|
||||
echo "ERROR: $1" >&2
|
||||
}
|
||||
|
||||
validate_id() {
|
||||
@@ -19,30 +19,14 @@ validate_id() {
|
||||
esac
|
||||
}
|
||||
|
||||
adjust_folder_ownership() {
|
||||
if [ -d "$1" ]; then
|
||||
chown -R "$UID:$GID" "$1"
|
||||
echo_info_log "Ownership adjusted for $1"
|
||||
else
|
||||
echo_info_log "Directory $1 does not exist, skipping."
|
||||
fi
|
||||
}
|
||||
|
||||
validate_id "$UID"
|
||||
validate_id "$GID"
|
||||
|
||||
echo_info_log "UID=$UID, GID=$GID"
|
||||
|
||||
# List of directories (space-separated for POSIX shell)
|
||||
directories="/app/backend/logs /app/backend/config"
|
||||
|
||||
for dir in $directories; do
|
||||
adjust_folder_ownership "$dir"
|
||||
done
|
||||
|
||||
if [ -n "$ENDURAIN_HOST" ]; then
|
||||
echo_info_log "Substituting MY_APP_ENDURAIN_HOST with $ENDURAIN_HOST"
|
||||
find /app/frontend/dist -type f \( -name '*.js' -o -name '*.css' \) -exec sed -i "s|MY_APP_ENDURAIN_HOST|$ENDURAIN_HOST|g" {} +
|
||||
echo "window.env = { ENDURAIN_HOST: \"$ENDURAIN_HOST\" };" > /app/frontend/dist/env.js
|
||||
echo_info_log "Runtime env.js written with ENDURAIN_HOST=$ENDURAIN_HOST"
|
||||
fi
|
||||
|
||||
echo_info_log "Starting FastAPI with BEHIND_PROXY=$BEHIND_PROXY"
|
||||
|
||||
@@ -164,6 +164,7 @@ The table bellow details the activity types supported by Endurain:
|
||||
| MTB cycling | 6 |
|
||||
| Commuting cycling | 27 |
|
||||
| Virtual cycling | 7 |
|
||||
| Indoor cycling | 28 |
|
||||
| Indoor swimming | 8 |
|
||||
| Open water swimming | 9 |
|
||||
| General workout | 10 |
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
# Getting started
|
||||
|
||||
---
|
||||
|
||||
## Default Credentials
|
||||
|
||||
- **Username:** admin
|
||||
@@ -39,7 +35,7 @@ Environment variable | Default value | Optional | Notes |
|
||||
| JAEGER_ENABLED | false | Yes | N/A |
|
||||
| JAEGER_PROTOCOL | http | Yes | N/A |
|
||||
| JAEGER_HOST | jaeger | Yes | N/A |
|
||||
| JAGGER_PORT | 4317 | Yes | N/A |
|
||||
| JAEGER_PORT | 4317 | Yes | N/A |
|
||||
| BEHIND_PROXY | false | Yes | Change to true if behind reverse proxy |
|
||||
| ENVIRONMENT | production | Yes | "production" and "development" allowed. "development" allows connections from localhost:8080 and localhost:5173 at the CORS level |
|
||||
|
||||
182
docs/getting-started/getting-started.md
Normal file
182
docs/getting-started/getting-started.md
Normal file
@@ -0,0 +1,182 @@
|
||||
# Getting started
|
||||
|
||||
---
|
||||
|
||||
Welcome to the guide for getting started on hosting your own production instance of Endurain. Like many other services, Endurain is easiest to get up and running trough Docker compose. It is possible to get Endurain up and running without a domain and reverse proxy, but this guide assumes you want to use a reverse proxy and your domain. Endurain can run on any computer that support OCI containers, but in this guide we are using Debian 13 (should also work with 12).
|
||||
|
||||
## Prerequisites
|
||||
* Domain name pointed to your external IP address.
|
||||
* Open FW rules to your server on port 443 and 80. (trough NAT if you are running ipv4)
|
||||
* A computer/server with enough disk space for your activity files.
|
||||
* API key from [geocode.maps.co](https://geocode.maps.co/) (free, but need to register).
|
||||
|
||||
## Installing docker and Caddy reverse proxy
|
||||
|
||||
We use apt to do this
|
||||
|
||||
```
|
||||
sudo apt update -y
|
||||
sudo apt install docker.io docker-compose caddy -y
|
||||
```
|
||||
|
||||
Confirm your user has the id 1000:
|
||||
|
||||
```
|
||||
id
|
||||
```
|
||||
|
||||
If you are not the user 1000, you need to set the `UID` and `GID` to your id in the .env file. But to keep this guide as easy to follow as possible, we will assume that you are user 1000.
|
||||
|
||||
## Create directory structure
|
||||
|
||||
Lets use `/opt/endurain/` as the root directory for our project.
|
||||
|
||||
```bash
|
||||
sudo mkdir /opt/endurain
|
||||
sudo chown 1000:1000 /opt/endurain
|
||||
mkdir -p \
|
||||
/opt/endurain/app/{user_images,files/{bulk_import,processed},logs} \
|
||||
/opt/endurain/postgres
|
||||
```
|
||||
|
||||
## Docker compose Deployment
|
||||
|
||||
In this example of setting up Endurain, we will need two files. One `docker-compose.yml` and `.env`.
|
||||
|
||||
* docker-compose.yml tells your system how to set up the container, network and storage.
|
||||
* .env holds our secrets and environment variables.
|
||||
|
||||
Splitting up the setup like this make it easy to handle updates to the containers, without touching the secrets and other variables.
|
||||
|
||||
### Creating the docker-compose and .env file
|
||||
|
||||
To make it as easy as possible for selfhoster to get up and running examples of docker-compose.yml and .env is on the git repo. Here are links to the files on the repo:
|
||||
|
||||
* [docker-compose.yml.example](https://raw.githubusercontent.com/joaovitoriasilva/endurain/refs/heads/master/docker-compose.yml.example)
|
||||
* [.env.example](https://raw.githubusercontent.com/joaovitoriasilva/endurain/refs/heads/master/env.example)
|
||||
|
||||
```bash
|
||||
cd /opt/endurain
|
||||
wget https://raw.githubusercontent.com/joaovitoriasilva/endurain/refs/heads/master/docker-compose.yml.example
|
||||
wget https://raw.githubusercontent.com/joaovitoriasilva/endurain/refs/heads/master/env.example
|
||||
|
||||
mv docker-compose.yml.example docker-compose.yml
|
||||
mv .env.example .env
|
||||
```
|
||||
|
||||
Now we need to make changes to the files to reflect *your* environment. Inside docker-compose.yml there is not much we need to do. If you want to store the files another place then `/opt/endurain` this is the file you need to change.
|
||||
|
||||
Here is an explaination on what you can set in the `.env`:
|
||||
|
||||
Environment variable | How to set it |
|
||||
| --- | --- |
|
||||
| DB_PASSWORD | Run `openssl rand -hex 32` on a terminal to get a secret |
|
||||
| POSTGRES_PASSWORD | Set the same value as DB_PASSWORD.|
|
||||
| SECRET_KEY | Run `openssl rand -hex 32` on a terminal to get a secret |
|
||||
| FERNET_KEY |Run `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` on a terminal to get a secret or go to [https://fernetkeygen.com](https://fernetkeygen.com). Example output is `7NfMMRSCWcoNDSjqBX8WoYH9nTFk1VdQOdZY13po53Y=` |
|
||||
| GEOCODES_MAPS_API | <a href="https://geocode.maps.co/">Geocode maps</a> offers a free plan consisting of 1 Request/Second. Registration necessary. |
|
||||
| TZ | Timezone definition. Insert your timezone. List of available time zones [here](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones). Format `Europe/Lisbon` expected |
|
||||
| ENDURAIN_HOST | https://endurain.yourdomain.com |
|
||||
| BEHIND_PROXY | Change to true if behind reverse proxy |
|
||||
| POSTGRES_DB | Postgres name for the database. |
|
||||
| POSTGRES_USER | Postgres user for the database. |
|
||||
|
||||
**Please note:**
|
||||
|
||||
POSTGRES_DB and POSTGRES_USER is values for the database. If you change it from endurain, you also need to set the environment variables for the app image. Please leave them as `endurain` if you are unsure.
|
||||
|
||||
### Start the stack
|
||||
|
||||
It is finally time to start the stack!
|
||||
|
||||
```bash
|
||||
cd /opt/endurain
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
Check the log output:
|
||||
|
||||
```
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
If you do not get any errors, continue to next step.
|
||||
|
||||
### Visit the site
|
||||
|
||||
* Visit the site insecurly on http://`IP-OF-YOUR-SERVER`:8080
|
||||
* We still can not login to the site, because the `ENDURAIN_HOST` doesn't match our local URL.
|
||||
|
||||
## Configure Caddy as reverse proxy and get SSL cert from letsencrypt
|
||||
|
||||
* Before we configure caddy you need to set your DNS provider to point your domain to your external IP.
|
||||
* You also need to open your firewall on port 443 and 80 to the server.
|
||||
|
||||
We use caddy outside docker. This way Debian handles the updates (you just need to run `sudo apt get update -y` and `sudo apt get upgrade -y`)
|
||||
|
||||
Caddy is configured in the file `/etc/caddy/Caddyfile`
|
||||
|
||||
Open the file in your favourite editor, delete the default text, and paste in this:
|
||||
|
||||
```
|
||||
endurain.yourdomain.com {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
```
|
||||
|
||||
Restart caddy
|
||||
|
||||
```
|
||||
sudo systemctl restart caddy
|
||||
```
|
||||
|
||||
Check the ouput of caddy with:
|
||||
|
||||
```
|
||||
sudo journalctl -u caddy
|
||||
```
|
||||
|
||||
You should now be able to access your site on endurain.yourdomain.com
|
||||
|
||||
**Log in with username: admin password: admin, and remember to change the password**
|
||||
|
||||
|
||||
|
||||
🎉 **Weee** 🎉 You now have your own instance of Endurain up and running!
|
||||
|
||||
## How to update
|
||||
|
||||
* Take a backup of your files and db.
|
||||
* Check for new releases of the container image [here](https://github.com/joaovitoriasilva/endurain). Read release notes carefully for breaking changes.
|
||||
* Log on your server and run:
|
||||
* Inside `/opt/endurain/docker-compose.yml`, change out the version tag (the version after `:`)
|
||||
|
||||
|
||||
```bash
|
||||
cd /opt/endurain
|
||||
sudo docker compose pull
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
The same is the case for Postgres. Check for breaking changes in release notes on [Postgres Website](https://www.postgresql.org/docs/release/).
|
||||
|
||||
** It is generally pretty safe to upgrade postgres minor version f.eks 17.4 to 17.5, but major is often breaking change (example 17.2 to 18.1 )
|
||||
|
||||
|
||||
## Things to think about
|
||||
|
||||
You should implement backup strategy for the following directories:
|
||||
|
||||
```
|
||||
/opt/endurain/app/user_images
|
||||
/opt/endurain/app/files/bulk_import
|
||||
/opt/endurain/app/files/processed
|
||||
/opt/endurain/app/logs
|
||||
```
|
||||
|
||||
You also need to backup your postgres database. It is not good practice to just backup the volume `/opt/endurain/postgres` this might be corrupted if the database is in the middle of a wright when the database goes down.
|
||||
|
||||
## Default Credentials
|
||||
|
||||
- **Username:** admin
|
||||
- **Password:** admin
|
||||
@@ -1 +0,0 @@
|
||||
VITE_ENDURAIN_HOST=MY_APP_ENDURAIN_HOST
|
||||
@@ -15,6 +15,7 @@
|
||||
<link rel="icon" href="/logo/favicon-16x16.png" type="image/png" sizes="16x16">
|
||||
<link rel="apple-touch-icon" href="/logo/apple-touch-icon.png">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<script src="/env.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
503
frontend/app/package-lock.json
generated
503
frontend/app/package-lock.json
generated
@@ -15,6 +15,7 @@
|
||||
"@fortawesome/vue-fontawesome": "^3.0.8",
|
||||
"bootstrap": "^5.3.3",
|
||||
"chart.js": "^4.4.6",
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
"flag-icons": "^7.2.3",
|
||||
"leaflet": "^1.9.4",
|
||||
"luxon": "^3.5.0",
|
||||
@@ -2472,13 +2473,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/core-base": {
|
||||
"version": "11.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.1.5.tgz",
|
||||
"integrity": "sha512-xGRkISwV/2Trqb8yVQevlHm5roaQqy+75qwUzEQrviaQF0o4c5VDhjBW7WEGEoKFx09HSgq7NkvK/DAyuerTDg==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.1.7.tgz",
|
||||
"integrity": "sha512-gYiGnQeJVp3kNBeXQ73m1uFOak0ry4av8pn+IkEWigyyPWEMGzB+xFeQdmGMFn49V+oox6294oGVff8bYOhtOw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/message-compiler": "11.1.5",
|
||||
"@intlify/shared": "11.1.5"
|
||||
"@intlify/message-compiler": "11.1.7",
|
||||
"@intlify/shared": "11.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
@@ -2488,12 +2489,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/message-compiler": {
|
||||
"version": "11.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.1.5.tgz",
|
||||
"integrity": "sha512-YLSBbjD7qUdShe3ZAat9Hnf9E8FRpN6qmNFD/x5Xg5JVXjsks0kJ90Zj6aAuyoppJQA/YJdWZ8/bB7k3dg2TjQ==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-11.1.7.tgz",
|
||||
"integrity": "sha512-0ezkep1AT30NyuKj8QbRlmvMORCCRlOIIu9v8RNU8SwDjjTiFCZzczCORMns2mCH4HZ1nXgrfkKzYUbfjNRmng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/shared": "11.1.5",
|
||||
"@intlify/shared": "11.1.7",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2504,9 +2505,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@intlify/shared": {
|
||||
"version": "11.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.1.5.tgz",
|
||||
"integrity": "sha512-+I4vRzHm38VjLr/CAciEPJhGYFzWWW4HMTm+6H3WqknXLh0ozNX9oC8ogMUwTSXYR/wGUb1/lTpNziiCH5MybQ==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-11.1.7.tgz",
|
||||
"integrity": "sha512-4yZeMt2Aa/7n5Ehy4KalUlvt3iRLcg1tq9IBVfOgkyWFArN4oygn6WxgGIFibP3svpaH8DarbNaottq+p0gUZQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 16"
|
||||
@@ -2692,9 +2693,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/pluginutils": {
|
||||
"version": "5.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
|
||||
"integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.2.0.tgz",
|
||||
"integrity": "sha512-qWJ2ZTbmumwiLFomfzTyt5Kng4hwPi9rwCYN4SHb6eaRU1KNO4ccxINHr/VhH4GgPlt1XfSTLX2LBTme8ne4Zw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -2722,9 +2723,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.43.0.tgz",
|
||||
"integrity": "sha512-Krjy9awJl6rKbruhQDgivNbD1WuLb8xAclM4IR4cN5pHGAs2oIMMQJEiC3IC/9TZJ+QZkmZhlMO/6MBGxPidpw==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.0.tgz",
|
||||
"integrity": "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2736,9 +2737,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.43.0.tgz",
|
||||
"integrity": "sha512-ss4YJwRt5I63454Rpj+mXCXicakdFmKnUNxr1dLK+5rv5FJgAxnN7s31a5VchRYxCFWdmnDWKd0wbAdTr0J5EA==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.0.tgz",
|
||||
"integrity": "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2750,9 +2751,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.43.0.tgz",
|
||||
"integrity": "sha512-eKoL8ykZ7zz8MjgBenEF2OoTNFAPFz1/lyJ5UmmFSz5jW+7XbH1+MAgCVHy72aG59rbuQLcJeiMrP8qP5d/N0A==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.0.tgz",
|
||||
"integrity": "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2764,9 +2765,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.43.0.tgz",
|
||||
"integrity": "sha512-SYwXJgaBYW33Wi/q4ubN+ldWC4DzQY62S4Ll2dgfr/dbPoF50dlQwEaEHSKrQdSjC6oIe1WgzosoaNoHCdNuMg==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.0.tgz",
|
||||
"integrity": "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2778,9 +2779,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-arm64": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.43.0.tgz",
|
||||
"integrity": "sha512-SV+U5sSo0yujrjzBF7/YidieK2iF6E7MdF6EbYxNz94lA+R0wKl3SiixGyG/9Klab6uNBIqsN7j4Y/Fya7wAjQ==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.0.tgz",
|
||||
"integrity": "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2792,9 +2793,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-freebsd-x64": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.43.0.tgz",
|
||||
"integrity": "sha512-J7uCsiV13L/VOeHJBo5SjasKiGxJ0g+nQTrBkAsmQBIdil3KhPnSE9GnRon4ejX1XDdsmK/l30IYLiAaQEO0Cg==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.0.tgz",
|
||||
"integrity": "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2806,9 +2807,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.43.0.tgz",
|
||||
"integrity": "sha512-gTJ/JnnjCMc15uwB10TTATBEhK9meBIY+gXP4s0sHD1zHOaIh4Dmy1X9wup18IiY9tTNk5gJc4yx9ctj/fjrIw==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.0.tgz",
|
||||
"integrity": "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2820,9 +2821,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.43.0.tgz",
|
||||
"integrity": "sha512-ZJ3gZynL1LDSIvRfz0qXtTNs56n5DI2Mq+WACWZ7yGHFUEirHBRt7fyIk0NsCKhmRhn7WAcjgSkSVVxKlPNFFw==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.0.tgz",
|
||||
"integrity": "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@@ -2834,9 +2835,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.43.0.tgz",
|
||||
"integrity": "sha512-8FnkipasmOOSSlfucGYEu58U8cxEdhziKjPD2FIa0ONVMxvl/hmONtX/7y4vGjdUhjcTHlKlDhw3H9t98fPvyA==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.0.tgz",
|
||||
"integrity": "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2848,9 +2849,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.43.0.tgz",
|
||||
"integrity": "sha512-KPPyAdlcIZ6S9C3S2cndXDkV0Bb1OSMsX0Eelr2Bay4EsF9yi9u9uzc9RniK3mcUGCLhWY9oLr6er80P5DE6XA==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.0.tgz",
|
||||
"integrity": "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2862,9 +2863,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-loongarch64-gnu": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.43.0.tgz",
|
||||
"integrity": "sha512-HPGDIH0/ZzAZjvtlXj6g+KDQ9ZMHfSP553za7o2Odegb/BEfwJcR0Sw0RLNpQ9nC6Gy8s+3mSS9xjZ0n3rhcYg==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.0.tgz",
|
||||
"integrity": "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@@ -2876,9 +2877,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.43.0.tgz",
|
||||
"integrity": "sha512-gEmwbOws4U4GLAJDhhtSPWPXUzDfMRedT3hFMyRAvM9Mrnj+dJIFIeL7otsv2WF3D7GrV0GIewW0y28dOYWkmw==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.0.tgz",
|
||||
"integrity": "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@@ -2890,9 +2891,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.43.0.tgz",
|
||||
"integrity": "sha512-XXKvo2e+wFtXZF/9xoWohHg+MuRnvO29TI5Hqe9xwN5uN8NKUYy7tXUG3EZAlfchufNCTHNGjEx7uN78KsBo0g==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.0.tgz",
|
||||
"integrity": "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2904,9 +2905,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-musl": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.43.0.tgz",
|
||||
"integrity": "sha512-ruf3hPWhjw6uDFsOAzmbNIvlXFXlBQ4nk57Sec8E8rUxs/AI4HD6xmiiasOOx/3QxS2f5eQMKTAwk7KHwpzr/Q==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.0.tgz",
|
||||
"integrity": "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@@ -2918,9 +2919,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-s390x-gnu": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.43.0.tgz",
|
||||
"integrity": "sha512-QmNIAqDiEMEvFV15rsSnjoSmO0+eJLoKRD9EAa9rrYNwO/XRCtOGM3A5A0X+wmG+XRrw9Fxdsw+LnyYiZWWcVw==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.0.tgz",
|
||||
"integrity": "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@@ -2932,9 +2933,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.43.0.tgz",
|
||||
"integrity": "sha512-jAHr/S0iiBtFyzjhOkAics/2SrXE092qyqEg96e90L3t9Op8OTzS6+IX0Fy5wCt2+KqeHAkti+eitV0wvblEoQ==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.0.tgz",
|
||||
"integrity": "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2946,9 +2947,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.43.0.tgz",
|
||||
"integrity": "sha512-3yATWgdeXyuHtBhrLt98w+5fKurdqvs8B53LaoKD7P7H7FKOONLsBVMNl9ghPQZQuYcceV5CDyPfyfGpMWD9mQ==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.0.tgz",
|
||||
"integrity": "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2960,9 +2961,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.43.0.tgz",
|
||||
"integrity": "sha512-wVzXp2qDSCOpcBCT5WRWLmpJRIzv23valvcTwMHEobkjippNf+C3ys/+wf07poPkeNix0paTNemB2XrHr2TnGw==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.0.tgz",
|
||||
"integrity": "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2974,9 +2975,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.43.0.tgz",
|
||||
"integrity": "sha512-fYCTEyzf8d+7diCw8b+asvWDCLMjsCEA8alvtAutqJOJp/wL5hs1rWSqJ1vkjgW0L2NB4bsYJrpKkiIPRR9dvw==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.0.tgz",
|
||||
"integrity": "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@@ -2988,9 +2989,9 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.43.0.tgz",
|
||||
"integrity": "sha512-SnGhLiE5rlK0ofq8kzuDkM0g7FN1s5VYY+YSMTibP7CqShxCQvqtNxTARS4xX4PFJfHjG0ZQYX9iGzI3FQh5Aw==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.0.tgz",
|
||||
"integrity": "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -3080,9 +3081,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/leaflet": {
|
||||
"version": "1.9.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.18.tgz",
|
||||
"integrity": "sha512-ht2vsoPjezor5Pmzi5hdsA7F++v5UGq9OlUduWHmMZiuQGIpJ2WS5+Gg9HaAA79gNh1AIPtCqhzejcIZ3lPzXQ==",
|
||||
"version": "1.9.19",
|
||||
"resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.19.tgz",
|
||||
"integrity": "sha512-pB+n2daHcZPF2FDaWa+6B0a0mSDf4dPU35y5iTXsx7x/PzzshiX5atYiS1jlBn43X7XvM8AP+AB26lnSk0J4GA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3118,15 +3119,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.3.tgz",
|
||||
"integrity": "sha512-W2RH2TPWVHA1o7UmaFKISPvdicFJH+mjykctJFoAkUw+SPTJTGjUNdKscFBrqM7IPnCVu6zihtKYa7TkZS1dkQ==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz",
|
||||
"integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/spy": "3.2.3",
|
||||
"@vitest/utils": "3.2.3",
|
||||
"@vitest/spy": "3.2.4",
|
||||
"@vitest/utils": "3.2.4",
|
||||
"chai": "^5.2.0",
|
||||
"tinyrainbow": "^2.0.0"
|
||||
},
|
||||
@@ -3135,13 +3136,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/mocker": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.3.tgz",
|
||||
"integrity": "sha512-cP6fIun+Zx8he4rbWvi+Oya6goKQDZK+Yq4hhlggwQBbrlOQ4qtZ+G4nxB6ZnzI9lyIb+JnvyiJnPC2AGbKSPA==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz",
|
||||
"integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/spy": "3.2.3",
|
||||
"@vitest/spy": "3.2.4",
|
||||
"estree-walker": "^3.0.3",
|
||||
"magic-string": "^0.30.17"
|
||||
},
|
||||
@@ -3162,9 +3163,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/pretty-format": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.3.tgz",
|
||||
"integrity": "sha512-yFglXGkr9hW/yEXngO+IKMhP0jxyFw2/qys/CK4fFUZnSltD+MU7dVYGrH8rvPcK/O6feXQA+EU33gjaBBbAng==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
|
||||
"integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3175,13 +3176,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/runner": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.3.tgz",
|
||||
"integrity": "sha512-83HWYisT3IpMaU9LN+VN+/nLHVBCSIUKJzGxC5RWUOsK1h3USg7ojL+UXQR3b4o4UBIWCYdD2fxuzM7PQQ1u8w==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz",
|
||||
"integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/utils": "3.2.3",
|
||||
"@vitest/utils": "3.2.4",
|
||||
"pathe": "^2.0.3",
|
||||
"strip-literal": "^3.0.0"
|
||||
},
|
||||
@@ -3190,13 +3191,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/snapshot": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.3.tgz",
|
||||
"integrity": "sha512-9gIVWx2+tysDqUmmM1L0hwadyumqssOL1r8KJipwLx5JVYyxvVRfxvMq7DaWbZZsCqZnu/dZedaZQh4iYTtneA==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz",
|
||||
"integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "3.2.3",
|
||||
"@vitest/pretty-format": "3.2.4",
|
||||
"magic-string": "^0.30.17",
|
||||
"pathe": "^2.0.3"
|
||||
},
|
||||
@@ -3205,9 +3206,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/spy": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.3.tgz",
|
||||
"integrity": "sha512-JHu9Wl+7bf6FEejTCREy+DmgWe+rQKbK+y32C/k5f4TBIAlijhJbRBIRIOCEpVevgRsCQR2iHRUH2/qKVM/plw==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz",
|
||||
"integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -3218,14 +3219,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vitest/utils": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.3.tgz",
|
||||
"integrity": "sha512-4zFBCU5Pf+4Z6v+rwnZ1HU1yzOKKvDkMXZrymE2PBlbjKJRlrOxbvpfPSvJTGRIwGoahaOGvp+kbCoxifhzJ1Q==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz",
|
||||
"integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vitest/pretty-format": "3.2.3",
|
||||
"loupe": "^3.1.3",
|
||||
"@vitest/pretty-format": "3.2.4",
|
||||
"loupe": "^3.1.4",
|
||||
"tinyrainbow": "^2.0.0"
|
||||
},
|
||||
"funding": {
|
||||
@@ -3233,13 +3234,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-core": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.16.tgz",
|
||||
"integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.17.tgz",
|
||||
"integrity": "sha512-Xe+AittLbAyV0pabcN7cP7/BenRBNcteM4aSDCtRvGw0d9OL+HG1u/XHLY/kt1q4fyMeZYXyIYrsHuPSiDPosA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.27.2",
|
||||
"@vue/shared": "3.5.16",
|
||||
"@babel/parser": "^7.27.5",
|
||||
"@vue/shared": "3.5.17",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.1"
|
||||
@@ -3264,29 +3265,29 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/compiler-dom": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz",
|
||||
"integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.17.tgz",
|
||||
"integrity": "sha512-+2UgfLKoaNLhgfhV5Ihnk6wB4ljyW1/7wUIog2puUqajiC29Lp5R/IKDdkebh9jTbTogTbsgB+OY9cEWzG95JQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-core": "3.5.16",
|
||||
"@vue/shared": "3.5.16"
|
||||
"@vue/compiler-core": "3.5.17",
|
||||
"@vue/shared": "3.5.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/compiler-sfc": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz",
|
||||
"integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.17.tgz",
|
||||
"integrity": "sha512-rQQxbRJMgTqwRugtjw0cnyQv9cP4/4BxWfTdRBkqsTfLOHWykLzbOc3C4GGzAmdMDxhzU/1Ija5bTjMVrddqww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.27.2",
|
||||
"@vue/compiler-core": "3.5.16",
|
||||
"@vue/compiler-dom": "3.5.16",
|
||||
"@vue/compiler-ssr": "3.5.16",
|
||||
"@vue/shared": "3.5.16",
|
||||
"@babel/parser": "^7.27.5",
|
||||
"@vue/compiler-core": "3.5.17",
|
||||
"@vue/compiler-dom": "3.5.17",
|
||||
"@vue/compiler-ssr": "3.5.17",
|
||||
"@vue/shared": "3.5.17",
|
||||
"estree-walker": "^2.0.2",
|
||||
"magic-string": "^0.30.17",
|
||||
"postcss": "^8.5.3",
|
||||
"postcss": "^8.5.6",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
},
|
||||
@@ -3297,13 +3298,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/compiler-ssr": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz",
|
||||
"integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.17.tgz",
|
||||
"integrity": "sha512-hkDbA0Q20ZzGgpj5uZjb9rBzQtIHLS78mMilwrlpWk2Ep37DYntUz0PonQ6kr113vfOEdM+zTBuJDaceNIW0tQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.16",
|
||||
"@vue/shared": "3.5.16"
|
||||
"@vue/compiler-dom": "3.5.17",
|
||||
"@vue/shared": "3.5.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/devtools-api": {
|
||||
@@ -3355,53 +3356,53 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/reactivity": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.16.tgz",
|
||||
"integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.17.tgz",
|
||||
"integrity": "sha512-l/rmw2STIscWi7SNJp708FK4Kofs97zc/5aEPQh4bOsReD/8ICuBcEmS7KGwDj5ODQLYWVN2lNibKJL1z5b+Lw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/shared": "3.5.16"
|
||||
"@vue/shared": "3.5.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-core": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.16.tgz",
|
||||
"integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.17.tgz",
|
||||
"integrity": "sha512-QQLXa20dHg1R0ri4bjKeGFKEkJA7MMBxrKo2G+gJikmumRS7PTD4BOU9FKrDQWMKowz7frJJGqBffYMgQYS96Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.16",
|
||||
"@vue/shared": "3.5.16"
|
||||
"@vue/reactivity": "3.5.17",
|
||||
"@vue/shared": "3.5.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/runtime-dom": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz",
|
||||
"integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.17.tgz",
|
||||
"integrity": "sha512-8El0M60TcwZ1QMz4/os2MdlQECgGoVHPuLnQBU3m9h3gdNRW9xRmI8iLS4t/22OQlOE6aJvNNlBiCzPHur4H9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/reactivity": "3.5.16",
|
||||
"@vue/runtime-core": "3.5.16",
|
||||
"@vue/shared": "3.5.16",
|
||||
"@vue/reactivity": "3.5.17",
|
||||
"@vue/runtime-core": "3.5.17",
|
||||
"@vue/shared": "3.5.17",
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/server-renderer": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.16.tgz",
|
||||
"integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.17.tgz",
|
||||
"integrity": "sha512-BOHhm8HalujY6lmC3DbqF6uXN/K00uWiEeF22LfEsm9Q93XeJ/plHTepGwf6tqFcF7GA5oGSSAAUock3VvzaCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-ssr": "3.5.16",
|
||||
"@vue/shared": "3.5.16"
|
||||
"@vue/compiler-ssr": "3.5.17",
|
||||
"@vue/shared": "3.5.17"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": "3.5.16"
|
||||
"vue": "3.5.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@vue/shared": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.16.tgz",
|
||||
"integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.17.tgz",
|
||||
"integrity": "sha512-CabR+UN630VnsJO/jHWYBC1YVXyMq94KKp6iF5MQgZJs5I8cmjw6oVMO1oDbtBkENSHSSn/UadWlW/OAgdmKrg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vue/test-utils": {
|
||||
@@ -3679,9 +3680,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/bootstrap": {
|
||||
"version": "5.3.6",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz",
|
||||
"integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==",
|
||||
"version": "5.3.7",
|
||||
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
|
||||
"integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -3709,9 +3710,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.25.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz",
|
||||
"integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
|
||||
"version": "4.25.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz",
|
||||
"integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3729,8 +3730,8 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001718",
|
||||
"electron-to-chromium": "^1.5.160",
|
||||
"caniuse-lite": "^1.0.30001726",
|
||||
"electron-to-chromium": "^1.5.173",
|
||||
"node-releases": "^2.0.19",
|
||||
"update-browserslist-db": "^1.1.3"
|
||||
},
|
||||
@@ -3819,9 +3820,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001723",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001723.tgz",
|
||||
"integrity": "sha512-1R/elMjtehrFejxwmexeXAtae5UO9iSyFn6G/I806CYC/BLyyBk1EPhrKBkWhy6wM6Xnm47dSJQec+tLJ39WHw==",
|
||||
"version": "1.0.30001726",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz",
|
||||
"integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3885,6 +3886,15 @@
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-plugin-datalabels": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-2.2.0.tgz",
|
||||
"integrity": "sha512-14ZU30lH7n89oq+A4bWaJPnAG8a7ZTk7dKf48YAzMvJjQtjrgg5Dpk9f+LbjCF6bpx3RAGTeL13IXpKQYyRvlw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"chart.js": ">=3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/check-error": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
|
||||
@@ -4028,9 +4038,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cssstyle": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.4.0.tgz",
|
||||
"integrity": "sha512-W0Y2HOXlPkb2yaKrCVRjinYKciu/qSLEmK0K9mcfDei3zwlnHFEHAs/Du3cIRwPqY+J4JsiBzUjoHyc8RsJ03A==",
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
|
||||
"integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -4287,9 +4297,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.167",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.167.tgz",
|
||||
"integrity": "sha512-LxcRvnYO5ez2bMOFpbuuVuAI5QNeY1ncVytE/KXaL6ZNfzX1yPlAO0nSOyIHx2fVAuUprMqPs/TdVhUFZy7SIQ==",
|
||||
"version": "1.5.173",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.173.tgz",
|
||||
"integrity": "sha512-2bFhXP2zqSfQHugjqJIDFVwa+qIxyNApenmXTp9EjaKtdPrES5Qcn9/aSFy/NaP2E+fWG/zxKu/LBvY36p5VNQ==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -4598,9 +4608,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.4.1.tgz",
|
||||
"integrity": "sha512-9dF+KuU/Ilkq27A8idRP7N2DH8iUR6qXcjF3FR2wETY21PZdBrIjwCau8oboyGj9b7etWmTGEeM8e7oOed6ZWg==",
|
||||
"version": "5.5.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.5.1.tgz",
|
||||
"integrity": "sha512-dobTkHT6XaEVOo8IO90Q4DOSxnm3Y151QxPJlM/vKC0bVy+d6cVWQZLlFiuZPP0wS6vZwSKeJgKkcS+KfMBlRw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -6626,9 +6636,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pathval": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
|
||||
"integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
|
||||
"integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -6692,9 +6702,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.5",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.5.tgz",
|
||||
"integrity": "sha512-d/jtm+rdNT8tpXuHY5MMtcbJFBkhXE6593XVR9UoGCH8jSFGci7jGvMGH5RYd5PBJW+00NZQt6gf7CbagJCrhg==",
|
||||
"version": "8.5.6",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
@@ -6744,9 +6754,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"version": "3.6.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.1.tgz",
|
||||
"integrity": "sha512-5xGWRa90Sp2+x1dQtNpIpeOQpTDBs9cZDmA/qs2vDNN2i18PdapqY7CmBeyLlMuGqXJRIOPaCaVZTLNQRWUH/A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
@@ -6976,13 +6986,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.43.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.43.0.tgz",
|
||||
"integrity": "sha512-wdN2Kd3Twh8MAEOEJZsuxuLKCsBEo4PVNLK6tQWAn10VhsVewQLzcucMgLolRlhFybGxfclbPeEYBaP6RvUFGg==",
|
||||
"version": "4.44.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz",
|
||||
"integrity": "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "1.0.7"
|
||||
"@types/estree": "1.0.8"
|
||||
},
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
@@ -6992,36 +7002,29 @@
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.43.0",
|
||||
"@rollup/rollup-android-arm64": "4.43.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.43.0",
|
||||
"@rollup/rollup-darwin-x64": "4.43.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.43.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.43.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.43.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.43.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.43.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.43.0",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.43.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.43.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.43.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.43.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.43.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.43.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.43.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.43.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.43.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.43.0",
|
||||
"@rollup/rollup-android-arm-eabi": "4.44.0",
|
||||
"@rollup/rollup-android-arm64": "4.44.0",
|
||||
"@rollup/rollup-darwin-arm64": "4.44.0",
|
||||
"@rollup/rollup-darwin-x64": "4.44.0",
|
||||
"@rollup/rollup-freebsd-arm64": "4.44.0",
|
||||
"@rollup/rollup-freebsd-x64": "4.44.0",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.44.0",
|
||||
"@rollup/rollup-linux-arm-musleabihf": "4.44.0",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.44.0",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.44.0",
|
||||
"@rollup/rollup-linux-loongarch64-gnu": "4.44.0",
|
||||
"@rollup/rollup-linux-powerpc64le-gnu": "4.44.0",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.44.0",
|
||||
"@rollup/rollup-linux-riscv64-musl": "4.44.0",
|
||||
"@rollup/rollup-linux-s390x-gnu": "4.44.0",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.44.0",
|
||||
"@rollup/rollup-linux-x64-musl": "4.44.0",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.44.0",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.44.0",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.44.0",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup/node_modules/@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
"integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/rrweb-cssom": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
|
||||
@@ -7773,9 +7776,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.42.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.42.0.tgz",
|
||||
"integrity": "sha512-UYCvU9YQW2f/Vwl+P0GfhxJxbUGLwd+5QrrGgLajzWAtC/23AX0vcise32kkP7Eu0Wu9VlzzHAXkLObgjQfFlQ==",
|
||||
"version": "5.43.1",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.43.1.tgz",
|
||||
"integrity": "sha512-+6erLbBm0+LROX2sPXlUYx/ux5PyE9K/a92Wrt6oA+WDAoFTdpHE5tCYCI5PNzq2y8df4rA+QgHLJuR4jNymsg==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
@@ -7830,9 +7833,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tinypool": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.0.tgz",
|
||||
"integrity": "sha512-7CotroY9a8DKsKprEy/a14aCCm8jYVmR7aFy4fpkZM8sdpNJbKkixuNjgM50yCmip2ezc8z4N7k3oe2+rfRJCQ==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
|
||||
"integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -8230,9 +8233,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite-node": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.3.tgz",
|
||||
"integrity": "sha512-gc8aAifGuDIpZHrPjuHyP4dpQmYXqWw7D1GmDnWeNWP654UEXzVfQ5IHPSK5HaHkwB/+p1atpYpSdw/2kOv8iQ==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz",
|
||||
"integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -8284,20 +8287,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vitest": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.3.tgz",
|
||||
"integrity": "sha512-E6U2ZFXe3N/t4f5BwUaVCKRLHqUpk1CBWeMh78UT4VaTPH/2dyvH6ALl29JTovEPu9dVKr/K/J4PkXgrMbw4Ww==",
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz",
|
||||
"integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/chai": "^5.2.2",
|
||||
"@vitest/expect": "3.2.3",
|
||||
"@vitest/mocker": "3.2.3",
|
||||
"@vitest/pretty-format": "^3.2.3",
|
||||
"@vitest/runner": "3.2.3",
|
||||
"@vitest/snapshot": "3.2.3",
|
||||
"@vitest/spy": "3.2.3",
|
||||
"@vitest/utils": "3.2.3",
|
||||
"@vitest/expect": "3.2.4",
|
||||
"@vitest/mocker": "3.2.4",
|
||||
"@vitest/pretty-format": "^3.2.4",
|
||||
"@vitest/runner": "3.2.4",
|
||||
"@vitest/snapshot": "3.2.4",
|
||||
"@vitest/spy": "3.2.4",
|
||||
"@vitest/utils": "3.2.4",
|
||||
"chai": "^5.2.0",
|
||||
"debug": "^4.4.1",
|
||||
"expect-type": "^1.2.1",
|
||||
@@ -8308,10 +8311,10 @@
|
||||
"tinybench": "^2.9.0",
|
||||
"tinyexec": "^0.3.2",
|
||||
"tinyglobby": "^0.2.14",
|
||||
"tinypool": "^1.1.0",
|
||||
"tinypool": "^1.1.1",
|
||||
"tinyrainbow": "^2.0.0",
|
||||
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0",
|
||||
"vite-node": "3.2.3",
|
||||
"vite-node": "3.2.4",
|
||||
"why-is-node-running": "^2.3.0"
|
||||
},
|
||||
"bin": {
|
||||
@@ -8327,8 +8330,8 @@
|
||||
"@edge-runtime/vm": "*",
|
||||
"@types/debug": "^4.1.12",
|
||||
"@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
|
||||
"@vitest/browser": "3.2.3",
|
||||
"@vitest/ui": "3.2.3",
|
||||
"@vitest/browser": "3.2.4",
|
||||
"@vitest/ui": "3.2.4",
|
||||
"happy-dom": "*",
|
||||
"jsdom": "*"
|
||||
},
|
||||
@@ -8357,16 +8360,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue": {
|
||||
"version": "3.5.16",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.16.tgz",
|
||||
"integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==",
|
||||
"version": "3.5.17",
|
||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.17.tgz",
|
||||
"integrity": "sha512-LbHV3xPN9BeljML+Xctq4lbz2lVHCR6DtbpTf5XIO6gugpXUN49j2QQPcMj086r9+AkJ0FfUT8xjulKKBkkr9g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@vue/compiler-dom": "3.5.16",
|
||||
"@vue/compiler-sfc": "3.5.16",
|
||||
"@vue/runtime-dom": "3.5.16",
|
||||
"@vue/server-renderer": "3.5.16",
|
||||
"@vue/shared": "3.5.16"
|
||||
"@vue/compiler-dom": "3.5.17",
|
||||
"@vue/compiler-sfc": "3.5.17",
|
||||
"@vue/runtime-dom": "3.5.17",
|
||||
"@vue/server-renderer": "3.5.17",
|
||||
"@vue/shared": "3.5.17"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "*"
|
||||
@@ -8458,13 +8461,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vue-i18n": {
|
||||
"version": "11.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.1.5.tgz",
|
||||
"integrity": "sha512-XCwuaEA5AF97g1frvH/EI1zI9uo1XKTf2/OCFgts7NvUWRsjlgeHPrkJV+a3gpzai2pC4quZ4AnOHFO8QK9hsg==",
|
||||
"version": "11.1.7",
|
||||
"resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-11.1.7.tgz",
|
||||
"integrity": "sha512-CDrU7Cmyh1AxJjerQmipV9nVa//exVBdhTcWGlbfcDCN8bKp/uAe7Le6IoN4//5emIikbsSKe9Uofmf/xXkhOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@intlify/core-base": "11.1.5",
|
||||
"@intlify/shared": "11.1.5",
|
||||
"@intlify/core-base": "11.1.7",
|
||||
"@intlify/shared": "11.1.7",
|
||||
"@vue/devtools-api": "^6.5.0"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"@fortawesome/vue-fontawesome": "^3.0.8",
|
||||
"bootstrap": "^5.3.3",
|
||||
"chart.js": "^4.4.6",
|
||||
"chartjs-plugin-datalabels": "^2.2.0",
|
||||
"flag-icons": "^7.2.3",
|
||||
"leaflet": "^1.9.4",
|
||||
"luxon": "^3.5.0",
|
||||
|
||||
3
frontend/app/public/env.js
Normal file
3
frontend/app/public/env.js
Normal file
@@ -0,0 +1,3 @@
|
||||
window.env = {
|
||||
ENDURAIN_HOST: "http://localhost:8080"
|
||||
};
|
||||
@@ -15,7 +15,7 @@
|
||||
<span v-if="activity.activity_type === 1 || activity.activity_type === 2 || activity.activity_type === 3 || activity.activity_type === 8 || activity.activity_type === 9 || activity.activity_type === 11 || activity.activity_type === 12 || activity.activity_type === 13">
|
||||
{{ formatPace(activity, authStore.user.units) }}
|
||||
</span>
|
||||
<span v-else-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27">
|
||||
<span v-else-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27 || activity.activity_type === 28">
|
||||
{{ formatAverageSpeed(activity, authStore.user.units) }}
|
||||
</span>
|
||||
<span v-else>
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
<template>
|
||||
<div if="activity" class="fw-lighter">
|
||||
<!-- laps -->
|
||||
<ActivityLapsComponent :activity="activity" :activityActivityLaps="activityActivityLaps" :units="units" v-if="activityActivityLaps && activityActivityLaps.length > 0"/>
|
||||
<ActivityLapsComponent :activity="activity" :activityActivityLaps="activityActivityLaps" :units="units"
|
||||
v-if="activityActivityLaps && activityActivityLaps.length > 0" />
|
||||
|
||||
<!-- Pace values -->
|
||||
<div v-if="pacePresent">
|
||||
<span class="fw-normal">
|
||||
{{ $t("activityBellowMPillsComponent.subTitlePace") }}
|
||||
</span>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'pace'" :activityStreams="activityActivityStreams" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'pace'"
|
||||
:activityStreams="activityActivityStreams" />
|
||||
<div class="d-flex justify-content-between mt-3" v-if="formattedPace">
|
||||
<span>
|
||||
{{ $t("activityBellowMPillsComponent.labelAvgPace") }}
|
||||
@@ -40,14 +42,19 @@
|
||||
<span class="fw-normal">
|
||||
{{ $t("activityBellowMPillsComponent.subTitleSpeed") }}
|
||||
</span>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'vel'" :activityStreams="activityActivityStreams" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'vel'"
|
||||
:activityStreams="activityActivityStreams" />
|
||||
<div class="d-flex justify-content-between mt-3" v-if="activity.average_speed">
|
||||
<span>
|
||||
{{ $t("activityBellowMPillsComponent.labelAvgSpeed") }}
|
||||
</span>
|
||||
<span>
|
||||
<span v-if="activity.average_speed && Number(units) === 1"><b>{{ formatAverageSpeedMetric(activity.average_speed) }}{{ ' ' + $t("generalItems.unitsKmH") }}</b></span>
|
||||
<span v-else-if="activity.average_speed && Number(units) === 2"><b>{{ formatAverageSpeedImperial(activity.average_speed) }}{{ ' ' + $t("generalItems.unitsMph") }}</b></span>
|
||||
<span v-if="activity.average_speed && Number(units) === 1"><b>{{
|
||||
formatAverageSpeedMetric(activity.average_speed) }}{{ ' ' + $t("generalItems.unitsKmH")
|
||||
}}</b></span>
|
||||
<span v-else-if="activity.average_speed && Number(units) === 2"><b>{{
|
||||
formatAverageSpeedImperial(activity.average_speed) }}{{ ' ' + $t("generalItems.unitsMph")
|
||||
}}</b></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-3" v-if="activity.max_speed">
|
||||
@@ -55,8 +62,12 @@
|
||||
{{ $t("activityBellowMPillsComponent.labelMaxSpeed") }}
|
||||
</span>
|
||||
<span>
|
||||
<span v-if="activity.max_speed && Number(units) === 1"><b>{{ formatAverageSpeedMetric(activity.max_speed) }}{{ ' ' + $t("generalItems.unitsKmH") }}</b></span>
|
||||
<span v-else-if="activity.max_speed && Number(units) === 2"><b>{{ formatAverageSpeedImperial(activity.max_speed) }}{{ ' ' + $t("generalItems.unitsMph") }}</b></span>
|
||||
<span v-if="activity.max_speed && Number(units) === 1"><b>{{
|
||||
formatAverageSpeedMetric(activity.max_speed) }}{{ ' ' + $t("generalItems.unitsKmH")
|
||||
}}</b></span>
|
||||
<span v-else-if="activity.max_speed && Number(units) === 2"><b>{{
|
||||
formatAverageSpeedImperial(activity.max_speed) }}{{ ' ' + $t("generalItems.unitsMph")
|
||||
}}</b></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between mt-3" v-if="activity.total_elapsed_time">
|
||||
@@ -82,7 +93,8 @@
|
||||
<span class="fw-normal">
|
||||
{{ $t("activityBellowMPillsComponent.subTitleHeartRate") }}
|
||||
</span>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'hr'" :activityStreams="activityActivityStreams" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'hr'"
|
||||
:activityStreams="activityActivityStreams" />
|
||||
<div class="d-flex justify-content-between mt-3" v-if="activity.average_hr">
|
||||
<span>
|
||||
{{ $t("activityBellowMPillsComponent.labelAvgHeartRate") }}
|
||||
@@ -99,6 +111,11 @@
|
||||
<b>{{ activity.max_hr }}{{ ' ' + $t("generalItems.unitsBpm") }}</b>
|
||||
</span>
|
||||
</div>
|
||||
<BarChartComponent v-if="Object.values(hrZones).length > 0 && hrPresent"
|
||||
:labels="getHrBarChartData(hrZones, t).labels" :values="getHrBarChartData(hrZones, t).values"
|
||||
:barColors="getHrBarChartData(hrZones, t).barColors"
|
||||
:datalabelsFormatter="(value) => `${Math.round(value)}%`"
|
||||
:title="$t('activityMandAbovePillsComponent.labelHRZones')" />
|
||||
<hr>
|
||||
</div>
|
||||
<!-- Power values -->
|
||||
@@ -106,7 +123,8 @@
|
||||
<span class="fw-normal">
|
||||
{{ $t("activityBellowMPillsComponent.subTitlePower") }}
|
||||
</span>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'power'" :activityStreams="activityActivityStreams" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'power'"
|
||||
:activityStreams="activityActivityStreams" />
|
||||
<div class="d-flex justify-content-between mt-3" v-if="activity.average_power">
|
||||
<span>
|
||||
{{ $t("activityBellowMPillsComponent.labelAvgPower") }}
|
||||
@@ -141,7 +159,8 @@
|
||||
<span class="fw-normal" v-else>
|
||||
{{ $t("activityBellowMPillsComponent.subTitleStrokeRate") }}
|
||||
</span>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'cad'" :activityStreams="activityActivityStreams" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'cad'"
|
||||
:activityStreams="activityActivityStreams" />
|
||||
<div class="d-flex justify-content-between mt-3" v-if="activity.average_cad">
|
||||
<span v-if="!activityTypeIsSwimming(activity)">
|
||||
{{ $t("activityBellowMPillsComponent.labelAvgCadence") }}
|
||||
@@ -171,7 +190,8 @@
|
||||
<span class="fw-normal">
|
||||
{{ $t("activityBellowMPillsComponent.subTitleElevation") }}
|
||||
</span>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'ele'" :activityStreams="activityActivityStreams" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="'ele'"
|
||||
:activityStreams="activityActivityStreams" />
|
||||
<div class="d-flex justify-content-between mt-3" v-if="activity.elevation_gain">
|
||||
<span>
|
||||
{{ $t("activityBellowMPillsComponent.labelElevationGain") }}
|
||||
@@ -197,8 +217,11 @@
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<!-- sets -->
|
||||
<ActivityWorkoutStepsComponent :activity="activity" :activityActivityWorkoutSteps="activityActivityWorkoutSteps" :units="units" :activityActivityExerciseTitles="activityActivityExerciseTitles" :activityActivitySets="activityActivitySets" v-if="activityActivityWorkoutSteps && activityActivityWorkoutSteps.length > 0"/>
|
||||
<!-- sets -->
|
||||
<ActivityWorkoutStepsComponent :activity="activity" :activityActivityWorkoutSteps="activityActivityWorkoutSteps"
|
||||
:units="units" :activityActivityExerciseTitles="activityActivityExerciseTitles"
|
||||
:activityActivitySets="activityActivitySets"
|
||||
v-if="activityActivityWorkoutSteps && activityActivityWorkoutSteps.length > 0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -209,50 +232,51 @@ import { useI18n } from "vue-i18n";
|
||||
import ActivityLapsComponent from "@/components/Activities/ActivityLapsComponent.vue";
|
||||
import ActivityStreamsLineChartComponent from "@/components/Activities/ActivityStreamsLineChartComponent.vue";
|
||||
import ActivityWorkoutStepsComponent from "@/components/Activities/ActivityWorkoutStepsComponent.vue";
|
||||
import BarChartComponent from '@/components/GeneralComponents/BarChartComponent.vue';
|
||||
// Import Notivue push
|
||||
import { push } from "notivue";
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
// Import the utils
|
||||
import { getHrBarChartData } from "@/utils/chartUtils";
|
||||
import { formatPaceMetric, formatPaceImperial, formatPaceSwimMetric, formatPaceSwimImperial, formatAverageSpeedMetric, formatAverageSpeedImperial, activityTypeIsSwimming } from "@/utils/activityUtils";
|
||||
import { formatSecondsToMinutes } from "@/utils/dateTimeUtils";
|
||||
import {
|
||||
metersToFeet,
|
||||
metersToFeet,
|
||||
} from "@/utils/unitsUtils";
|
||||
|
||||
// Define props
|
||||
const props = defineProps({
|
||||
activity: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
activityActivityLaps: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
activityActivityWorkoutSteps: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
activityActivityStreams: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
units: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
activityActivityExerciseTitles: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
activityActivitySets: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
activity: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
activityActivityLaps: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
activityActivityWorkoutSteps: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
activityActivityStreams: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
units: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
activityActivityExerciseTitles: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
activityActivitySets: {
|
||||
type: [Object, null],
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Setup composables and reactive data
|
||||
const { t } = useI18n();
|
||||
const authStore = useAuthStore();
|
||||
const hrPresent = ref(false);
|
||||
const powerPresent = ref(false);
|
||||
const elePresent = ref(false);
|
||||
@@ -260,74 +284,87 @@ const cadPresent = ref(false);
|
||||
const velPresent = ref(false);
|
||||
const pacePresent = ref(false);
|
||||
const formattedPace = ref(null);
|
||||
const hrZones = ref({
|
||||
zone_1: {},
|
||||
zone_2: {},
|
||||
zone_3: {},
|
||||
zone_4: {},
|
||||
zone_5: {},
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
if (props.activityActivityStreams && props.activityActivityStreams.length > 0) {
|
||||
// Check if the activity has the streams
|
||||
for (let i = 0; i < props.activityActivityStreams.length; i++) {
|
||||
if (props.activityActivityStreams[i].stream_type === 1) {
|
||||
hrPresent.value = true;
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 2) {
|
||||
powerPresent.value = true;
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 3) {
|
||||
cadPresent.value = true;
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 4) {
|
||||
elePresent.value = true;
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 5) {
|
||||
if (
|
||||
props.activity.activity_type === 4 ||
|
||||
props.activity.activity_type === 5 ||
|
||||
props.activity.activity_type === 6 ||
|
||||
props.activity.activity_type === 7 ||
|
||||
props.activity.activity_type === 27
|
||||
) {
|
||||
velPresent.value = true;
|
||||
}
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 6) {
|
||||
if (
|
||||
props.activity.activity_type !== 4 &&
|
||||
props.activity.activity_type !== 5 &&
|
||||
props.activity.activity_type !== 6 &&
|
||||
props.activity.activity_type !== 7 &&
|
||||
props.activity.activity_type !== 27
|
||||
) {
|
||||
pacePresent.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// If there is an error, set the error message and show the error alert.
|
||||
push.error(
|
||||
`${t("activityMandAbovePillsComponent.errorMessageProcessingActivityStreams")} - ${error}`,
|
||||
);
|
||||
}
|
||||
try {
|
||||
if (props.activityActivityStreams && props.activityActivityStreams.length > 0) {
|
||||
// Check if the activity has the streams
|
||||
for (let i = 0; i < props.activityActivityStreams.length; i++) {
|
||||
if (props.activityActivityStreams[i].stream_type === 1) {
|
||||
hrPresent.value = true;
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 2) {
|
||||
powerPresent.value = true;
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 3) {
|
||||
cadPresent.value = true;
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 4) {
|
||||
elePresent.value = true;
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 5) {
|
||||
if (
|
||||
props.activity.activity_type === 4 ||
|
||||
props.activity.activity_type === 5 ||
|
||||
props.activity.activity_type === 6 ||
|
||||
props.activity.activity_type === 7 ||
|
||||
props.activity.activity_type === 27 ||
|
||||
props.activity.activity_type === 28
|
||||
) {
|
||||
velPresent.value = true;
|
||||
}
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 6) {
|
||||
if (
|
||||
props.activity.activity_type !== 4 &&
|
||||
props.activity.activity_type !== 5 &&
|
||||
props.activity.activity_type !== 6 &&
|
||||
props.activity.activity_type !== 7 &&
|
||||
props.activity.activity_type !== 27 &&
|
||||
props.activity.activity_type !== 28
|
||||
) {
|
||||
pacePresent.value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
hrZones.value = props.activityActivityStreams.find(stream => stream.hr_zone_percentages).hr_zone_percentages || {};
|
||||
if (Object.keys(hrZones.value).length > 0) {
|
||||
hrPresent.value = true;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// If there is an error, set the error message and show the error alert.
|
||||
push.error(
|
||||
`${t("activityMandAbovePillsComponent.errorMessageProcessingActivityStreams")} - ${error}`,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (
|
||||
activityTypeIsSwimming(props.activity) ||
|
||||
props.activity.activity_type === 13
|
||||
) {
|
||||
if (Number(props.units) === 1) {
|
||||
formattedPace.value = computed(() => formatPaceSwimMetric(props.activity.pace));
|
||||
} else {
|
||||
formattedPace.value = computed(() => formatPaceSwimImperial(props.activity.pace));
|
||||
}
|
||||
} else {
|
||||
if (Number(props.units) === 1) {
|
||||
formattedPace.value = computed(() => formatPaceMetric(props.activity.pace));
|
||||
} else {
|
||||
formattedPace.value = computed(() => formatPaceImperial(props.activity.pace));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
push.error(`${t("activitySummaryComponent.errorFetchingUserById")} - ${error}`);
|
||||
}
|
||||
try {
|
||||
if (
|
||||
activityTypeIsSwimming(props.activity) ||
|
||||
props.activity.activity_type === 13
|
||||
) {
|
||||
if (Number(props.units) === 1) {
|
||||
formattedPace.value = computed(() => formatPaceSwimMetric(props.activity.pace));
|
||||
} else {
|
||||
formattedPace.value = computed(() => formatPaceSwimImperial(props.activity.pace));
|
||||
}
|
||||
} else {
|
||||
if (Number(props.units) === 1) {
|
||||
formattedPace.value = computed(() => formatPaceMetric(props.activity.pace));
|
||||
} else {
|
||||
formattedPace.value = computed(() => formatPaceImperial(props.activity.pace));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
push.error(`${t("activitySummaryComponent.errorFetchingUserById")} - ${error}`);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@@ -7,7 +7,7 @@
|
||||
<th v-if="hasIntensity">{{ $t("activityLapsComponent.labelLapIntensity") }}</th>
|
||||
<th>{{ $t("activityLapsComponent.labelLapDistance") }}</th>
|
||||
<th>{{ $t("activityLapsComponent.labelLapTime") }}</th>
|
||||
<th v-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27">{{ $t("activityLapsComponent.labelLapSpeed") }}</th>
|
||||
<th v-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27 || activity.activity_type === 28">{{ $t("activityLapsComponent.labelLapSpeed") }}</th>
|
||||
<th v-else>{{ $t("activityLapsComponent.labelLapPace") }}</th>
|
||||
<!-- Do not show elevation for swimming activities -->
|
||||
<th v-if="!activityTypeIsSwimming(activity)">{{ $t("activityLapsComponent.labelLapElevation") }}</th>
|
||||
@@ -22,7 +22,7 @@
|
||||
<td v-if="hasIntensity">{{ lap.intensity ?? $t("generalItems.labelNoData") }}</td>
|
||||
<td>{{ lap.formattedDistance }}</td>
|
||||
<td>{{ lap.lapSecondsToMinutes }}</td>
|
||||
<td v-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27">{{ lap.formattedSpeedFull }}</td>
|
||||
<td v-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27 || activity.activity_type === 28">{{ lap.formattedSpeedFull }}</td>
|
||||
<td v-else>{{ lap.formattedPaceFull }}</td>
|
||||
<td v-if="!activityTypeIsSwimming(activity)">{{ lap.formattedElevationFull }}</td>
|
||||
<td v-if="activityTypeIsSwimming(activity)">{{ lap.avg_cadence }}</td>
|
||||
@@ -44,7 +44,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" style="width: 5%;">#</th>
|
||||
<th scope="col" style="width: 15%;" v-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27">{{ $t("activityLapsComponent.labelLapSpeed") }}</th>
|
||||
<th scope="col" style="width: 15%;" v-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27 || activity.activity_type === 28">{{ $t("activityLapsComponent.labelLapSpeed") }}</th>
|
||||
<th scope="col" style="width: 15%;" v-else>{{ $t("activityLapsComponent.labelLapPace") }}</th>
|
||||
<th scope="col" style="width: auto;"> </th>
|
||||
<th scope="col" style="width: 10%;" v-if="!activityTypeIsSwimming(activity) && activity.activity_type !== 13">{{ $t("activityLapsComponent.labelLapElev") }}</th>
|
||||
@@ -55,7 +55,7 @@
|
||||
<tbody>
|
||||
<tr v-for="(lap, index) in normalizedLaps" :key="index">
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td v-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27">{{ lap.formattedSpeed }}</td>
|
||||
<td v-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27 || activity.activity_type === 28">{{ lap.formattedSpeed }}</td>
|
||||
<td v-else>{{ lap.formattedPace }}</td>
|
||||
<td>
|
||||
<div class="progress" role="progressbar" aria-label="Basic example" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
||||
|
||||
@@ -1,59 +1,88 @@
|
||||
<template>
|
||||
<ul class="nav nav-pills mb-3 mt-3 justify-content-center" id="pills-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation" v-if="graphItems && graphItems.length > 0">
|
||||
<button class="nav-link link-body-emphasis" :class="{ active: graphItems || graphItems.length > 0 }" id="pills-graphs-tab" data-bs-toggle="pill" data-bs-target="#pills-graphs" type="button" role="tab" aria-controls="pills-graphs" :aria-selected="graphItems && graphItems.length > 0 ? true : false">
|
||||
{{ $t("activityMandAbovePillsComponent.labelPillGraphs") }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation" v-if="activityActivityLaps && activityActivityLaps.length > 0">
|
||||
<button class="nav-link link-body-emphasis" :class="{ active: !graphItems || graphItems.length === 0 }" id="pills-laps-tab" data-bs-toggle="pill" data-bs-target="#pills-laps" type="button" role="tab" aria-controls="pills-laps" :aria-selected="!graphItems || graphItems.length === 0 ? 'true' : 'false'">
|
||||
{{ $t("activityMandAbovePillsComponent.labelPillLaps") }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation" v-if="activityActivityWorkoutSteps && activityActivityWorkoutSteps.length > 0">
|
||||
<button class="nav-link link-body-emphasis" id="pills-workout-steps-tab" data-bs-toggle="pill" data-bs-target="#pills-workout-steps" type="button" role="tab" aria-controls="pills-workout-steps" aria-selected="false">
|
||||
{{ $t("activityMandAbovePillsComponent.labelPillWorkoutSets") }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="nav nav-pills mb-3 mt-3 justify-content-center" id="pills-tab" role="tablist">
|
||||
<li class="nav-item" role="presentation" v-if="graphItems && graphItems.length > 0">
|
||||
<button class="nav-link link-body-emphasis" :class="{ active: graphItems || graphItems.length > 0 }"
|
||||
id="pills-graphs-tab" data-bs-toggle="pill" data-bs-target="#pills-graphs" type="button" role="tab"
|
||||
aria-controls="pills-graphs" :aria-selected="graphItems && graphItems.length > 0 ? true : false">
|
||||
{{ $t("activityMandAbovePillsComponent.labelPillGraphs") }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation" v-if="activityActivityLaps && activityActivityLaps.length > 0">
|
||||
<button class="nav-link link-body-emphasis" :class="{ active: !graphItems || graphItems.length === 0 }"
|
||||
id="pills-laps-tab" data-bs-toggle="pill" data-bs-target="#pills-laps" type="button" role="tab"
|
||||
aria-controls="pills-laps" :aria-selected="!graphItems || graphItems.length === 0 ? 'true' : 'false'">
|
||||
{{ $t("activityMandAbovePillsComponent.labelPillLaps") }}
|
||||
</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation"
|
||||
v-if="activityActivityWorkoutSteps && activityActivityWorkoutSteps.length > 0">
|
||||
<button class="nav-link link-body-emphasis" id="pills-workout-steps-tab" data-bs-toggle="pill"
|
||||
data-bs-target="#pills-workout-steps" type="button" role="tab" aria-controls="pills-workout-steps"
|
||||
aria-selected="false">
|
||||
{{ $t("activityMandAbovePillsComponent.labelPillWorkoutSets") }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="pills-tabContent">
|
||||
<div class="tab-pane fade show" :class="{ active: graphItems || graphItems.length > 0 }" id="pills-graphs" role="tabpanel" aria-labelledby="pills-graphs-tab" tabindex="0" v-if="graphItems && graphItems.length > 0">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<p>{{ $t("activityMandAbovePillsComponent.labelGraph") }}</p>
|
||||
<ul class="nav nav-pills flex-column mb-auto" id="sidebarLineGraph">
|
||||
<li class="nav-item" v-for="item in graphItems" :key="item.type">
|
||||
<a href="javascript:void(0);" class="nav-link text-secondary"
|
||||
:class="{ 'active text-white': graphSelection === item.type }"
|
||||
@click="selectGraph(item.type)">
|
||||
{{ item.label }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-2">{{ $t("activityMandAbovePillsComponent.labelDownsampling") }}</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div if="activity">
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'hr' && hrPresent"/>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'power' && powerPresent"/>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'cad' && cadPresent"/>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'ele' && elePresent"/>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'vel' && velPresent"/>
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection" :activityStreams="activityActivityStreams" v-if="graphSelection === 'pace' && pacePresent"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" :class="{ 'show active': !graphItems || graphItems.length === 0 }" id="pills-laps" role="tabpanel" aria-labelledby="pills-laps-tab" tabindex="1" v-if="activityActivityLaps && activityActivityLaps.length > 0">
|
||||
<ActivityLapsComponent :activity="activity" :activityActivityLaps="activityActivityLaps" :units="units" />
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="pills-workout-steps" role="tabpanel" aria-labelledby="pills-workout-steps-tab" tabindex="2" v-if="activityActivityWorkoutSteps && activityActivityWorkoutSteps.length > 0">
|
||||
<ActivityWorkoutStepsComponent :activity="activity" :activityActivityWorkoutSteps="activityActivityWorkoutSteps" :units="units" :activityActivityExerciseTitles="activityActivityExerciseTitles" :activityActivitySets="activityActivitySets" />
|
||||
<div class="tab-content" id="pills-tabContent">
|
||||
<div class="tab-pane fade show" :class="{ active: graphItems || graphItems.length > 0 }" id="pills-graphs"
|
||||
role="tabpanel" aria-labelledby="pills-graphs-tab" tabindex="0" v-if="graphItems && graphItems.length > 0">
|
||||
<div class="row">
|
||||
<div class="col-md-2">
|
||||
<p>{{ $t("activityMandAbovePillsComponent.labelGraph") }}</p>
|
||||
<ul class="nav nav-pills flex-column mb-auto" id="sidebarLineGraph">
|
||||
<li class="nav-item" v-for="item in graphItems" :key="item.type">
|
||||
<a href="javascript:void(0);" class="nav-link text-secondary"
|
||||
:class="{ 'active text-white': graphSelection === item.type }"
|
||||
@click="selectGraph(item.type)">
|
||||
{{ item.label }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="mt-2">{{ $t("activityMandAbovePillsComponent.labelDownsampling") }}</p>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div if="activity">
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection"
|
||||
:activityStreams="activityActivityStreams" v-if="graphSelection === 'hr' && hrPresent" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection"
|
||||
:activityStreams="activityActivityStreams"
|
||||
v-if="graphSelection === 'power' && powerPresent" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection"
|
||||
:activityStreams="activityActivityStreams" v-if="graphSelection === 'cad' && cadPresent" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection"
|
||||
:activityStreams="activityActivityStreams" v-if="graphSelection === 'ele' && elePresent" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection"
|
||||
:activityStreams="activityActivityStreams" v-if="graphSelection === 'vel' && velPresent" />
|
||||
<ActivityStreamsLineChartComponent :activity="activity" :graphSelection="graphSelection"
|
||||
:activityStreams="activityActivityStreams"
|
||||
v-if="graphSelection === 'pace' && pacePresent" />
|
||||
<BarChartComponent
|
||||
v-if="Object.values(hrZones).length > 0 && graphSelection === 'hrZones' && hrPresent"
|
||||
:labels="getHrBarChartData(hrZones, t).labels"
|
||||
:values="getHrBarChartData(hrZones, t).values"
|
||||
:barColors="getHrBarChartData(hrZones, t).barColors"
|
||||
:datalabelsFormatter="(value) => `${Math.round(value)}%`"
|
||||
:title="$t('activityMandAbovePillsComponent.labelHRZones')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" :class="{ 'show active': !graphItems || graphItems.length === 0 }" id="pills-laps"
|
||||
role="tabpanel" aria-labelledby="pills-laps-tab" tabindex="1"
|
||||
v-if="activityActivityLaps && activityActivityLaps.length > 0">
|
||||
<ActivityLapsComponent :activity="activity" :activityActivityLaps="activityActivityLaps" :units="units" />
|
||||
</div>
|
||||
|
||||
<div class="tab-pane fade" id="pills-workout-steps" role="tabpanel" aria-labelledby="pills-workout-steps-tab"
|
||||
tabindex="2" v-if="activityActivityWorkoutSteps && activityActivityWorkoutSteps.length > 0">
|
||||
<ActivityWorkoutStepsComponent :activity="activity"
|
||||
:activityActivityWorkoutSteps="activityActivityWorkoutSteps" :units="units"
|
||||
:activityActivityExerciseTitles="activityActivityExerciseTitles"
|
||||
:activityActivitySets="activityActivitySets" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
@@ -63,10 +92,12 @@ import { useI18n } from "vue-i18n";
|
||||
import ActivityLapsComponent from "@/components/Activities/ActivityLapsComponent.vue";
|
||||
import ActivityStreamsLineChartComponent from "@/components/Activities/ActivityStreamsLineChartComponent.vue";
|
||||
import ActivityWorkoutStepsComponent from "@/components/Activities/ActivityWorkoutStepsComponent.vue";
|
||||
import BarChartComponent from '@/components/GeneralComponents/BarChartComponent.vue';
|
||||
import { activityTypeIsSwimming } from "@/utils/activityUtils";
|
||||
import { useAuthStore } from "@/stores/authStore";
|
||||
// Import Notivue push
|
||||
import { push } from "notivue";
|
||||
// Import the utils
|
||||
import { getHrBarChartData } from "@/utils/chartUtils";
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
@@ -102,7 +133,6 @@ const props = defineProps({
|
||||
|
||||
// Composables
|
||||
const { t } = useI18n();
|
||||
const authStore = useAuthStore();
|
||||
|
||||
// Reactive state
|
||||
const graphSelection = ref("hr");
|
||||
@@ -113,6 +143,13 @@ const elePresent = ref(false);
|
||||
const cadPresent = ref(false);
|
||||
const velPresent = ref(false);
|
||||
const pacePresent = ref(false);
|
||||
const hrZones = ref({
|
||||
zone_1: {},
|
||||
zone_2: {},
|
||||
zone_3: {},
|
||||
zone_4: {},
|
||||
zone_5: {},
|
||||
});
|
||||
|
||||
// Methods
|
||||
function selectGraph(type) {
|
||||
@@ -124,16 +161,16 @@ onMounted(async () => {
|
||||
try {
|
||||
if (props.activityActivityStreams && props.activityActivityStreams.length > 0) {
|
||||
// Check if the activity has the streams
|
||||
for (let i = 0; i < props.activityActivityStreams.length; i++) {
|
||||
if (props.activityActivityStreams[i].stream_type === 1) {
|
||||
for (const element of props.activityActivityStreams) {
|
||||
if (element.stream_type === 1) {
|
||||
hrPresent.value = true;
|
||||
graphItems.value.push({ type: "hr", label: `${t("activityMandAbovePillsComponent.labelGraphHR")}` });
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 2) {
|
||||
if (element.stream_type === 2) {
|
||||
powerPresent.value = true;
|
||||
graphItems.value.push({ type: "power", label: `${t("activityMandAbovePillsComponent.labelGraphPower")}` });
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 3) {
|
||||
if (element.stream_type === 3) {
|
||||
cadPresent.value = true;
|
||||
// Label as "Stroke Rate" over "Cadence" for swimming activities
|
||||
if (activityTypeIsSwimming(props.activity)) {
|
||||
@@ -142,38 +179,45 @@ onMounted(async () => {
|
||||
graphItems.value.push({ type: "cad", label: `${t("activityMandAbovePillsComponent.labelGraphCadence")}` });
|
||||
}
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 4) {
|
||||
if (element.stream_type === 4) {
|
||||
// Do not show elevation for swimming activities
|
||||
if (!activityTypeIsSwimming(props.activity)) {
|
||||
elePresent.value = true;
|
||||
graphItems.value.push({ type: "ele", label: `${t("activityMandAbovePillsComponent.labelGraphElevation")}` });
|
||||
}
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 5) {
|
||||
if (element.stream_type === 5) {
|
||||
velPresent.value = true;
|
||||
if (
|
||||
props.activity.activity_type === 4 ||
|
||||
props.activity.activity_type === 5 ||
|
||||
props.activity.activity_type === 6 ||
|
||||
props.activity.activity_type === 7 ||
|
||||
props.activity.activity_type === 27
|
||||
props.activity.activity_type === 27 ||
|
||||
props.activity.activity_type === 28
|
||||
) {
|
||||
graphItems.value.push({ type: "vel", label: `${t("activityMandAbovePillsComponent.labelGraphVelocity")}` });
|
||||
}
|
||||
}
|
||||
if (props.activityActivityStreams[i].stream_type === 6) {
|
||||
if (element.stream_type === 6) {
|
||||
pacePresent.value = true;
|
||||
if (
|
||||
props.activity.activity_type !== 4 &&
|
||||
props.activity.activity_type !== 5 &&
|
||||
props.activity.activity_type !== 6 &&
|
||||
props.activity.activity_type !== 7 &&
|
||||
props.activity.activity_type !== 27
|
||||
props.activity.activity_type !== 27 &&
|
||||
props.activity.activity_type !== 28
|
||||
) {
|
||||
graphItems.value.push({ type: "pace", label: `${t("activityMandAbovePillsComponent.labelGraphPace")}` });
|
||||
}
|
||||
}
|
||||
}
|
||||
hrZones.value = props.activityActivityStreams.find(stream => stream.hr_zone_percentages).hr_zone_percentages || {};
|
||||
if (Object.keys(hrZones.value).length > 0) {
|
||||
hrPresent.value = true;
|
||||
graphItems.value.push({ type: "hrZones", label: `${t("activityMandAbovePillsComponent.labelHRZones")}` });
|
||||
}
|
||||
}
|
||||
if (graphItems.value.length > 0) {
|
||||
graphSelection.value = graphItems.value[0].type;
|
||||
|
||||
@@ -6,15 +6,19 @@
|
||||
<div class="d-flex justify-content-between">
|
||||
<!-- user name and photo zone -->
|
||||
<div class="d-flex align-items-center">
|
||||
<UserAvatarComponent :user="userActivity" :width=55 :height=55 />
|
||||
<UserAvatarComponent :user="userActivity" :width=55 :height=55 />
|
||||
<div class="ms-3 me-3">
|
||||
<div class="fw-bold">
|
||||
<router-link :to="{ name: 'activity', params: { id: activity.id }}" class="link-body-emphasis link-underline-opacity-0 link-underline-opacity-100-hover" v-if="source === 'home'">
|
||||
{{ activity.name}}
|
||||
<router-link :to="{ name: 'activity', params: { id: activity.id } }"
|
||||
class="link-body-emphasis link-underline-opacity-0 link-underline-opacity-100-hover"
|
||||
v-if="source === 'home'">
|
||||
{{ activity.name }}
|
||||
</router-link>
|
||||
<span v-if="userActivity">
|
||||
<router-link :to="{ name: 'user', params: { id: userActivity.id }}" class="link-body-emphasis link-underline-opacity-0 link-underline-opacity-100-hover" v-if="source === 'activity'">
|
||||
{{ userActivity.name}}
|
||||
<router-link :to="{ name: 'user', params: { id: userActivity.id } }"
|
||||
class="link-body-emphasis link-underline-opacity-0 link-underline-opacity-100-hover"
|
||||
v-if="source === 'activity'">
|
||||
{{ userActivity.name }}
|
||||
</router-link>
|
||||
</span>
|
||||
<span v-else>
|
||||
@@ -24,52 +28,68 @@
|
||||
<h6>
|
||||
<!-- Display the visibility of the activity -->
|
||||
<span v-if="activity.visibility == 0">
|
||||
<font-awesome-icon :icon="['fas', 'globe']"/> {{ $t("activitySummaryComponent.visibilityPublic") }}
|
||||
<font-awesome-icon :icon="['fas', 'globe']" /> {{
|
||||
$t("activitySummaryComponent.visibilityPublic") }}
|
||||
</span>
|
||||
<span v-if="activity.visibility == 1">
|
||||
<font-awesome-icon :icon="['fas', 'users']" v-if="activity.visibility == 1" /> {{ $t("activitySummaryComponent.visibilityFollowers") }}
|
||||
<font-awesome-icon :icon="['fas', 'users']" v-if="activity.visibility == 1" /> {{
|
||||
$t("activitySummaryComponent.visibilityFollowers") }}
|
||||
</span>
|
||||
<span v-if="activity.visibility == 2">
|
||||
<font-awesome-icon :icon="['fas', 'lock']" v-if="activity.visibility == 2" /> {{ $t("activitySummaryComponent.visibilityPrivate") }}
|
||||
<font-awesome-icon :icon="['fas', 'lock']" v-if="activity.visibility == 2" /> {{
|
||||
$t("activitySummaryComponent.visibilityPrivate") }}
|
||||
</span>
|
||||
<span> - </span>
|
||||
|
||||
<!-- Display the activity type -->
|
||||
<span>
|
||||
<font-awesome-icon class="me-1" :icon="getIcon(activity.activity_type)" />
|
||||
<span v-if="activity.activity_type === 3 || activity.activity_type === 7">{{ $t("activitySummaryComponent.labelVirtual") }}</span>
|
||||
<span v-if="activity.activity_type === 3 || activity.activity_type === 7">{{
|
||||
$t("activitySummaryComponent.labelVirtual") }}</span>
|
||||
</span>
|
||||
|
||||
<!-- Display the date and time -->
|
||||
<span v-if="activity.start_time">{{ formatDateMed(activity.start_time) }} @ {{ formatTime(activity.start_time) }}</span>
|
||||
<span v-if="activity.start_time">
|
||||
{{ formatDateMed(activity.start_time) }} @ {{ formatTime(activity.start_time) }}
|
||||
</span>
|
||||
<!-- Conditionally display city and country -->
|
||||
<span v-if="activity.town || activity.city || activity.country">
|
||||
-
|
||||
-
|
||||
<span>{{ formatLocation(activity) }}</span>
|
||||
</span>
|
||||
</h6>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown d-flex" v-if="activity.user_id == authStore.user.id">
|
||||
<a class="btn btn-link btn-lg link-body-emphasis" :href="`https://www.strava.com/activities/${activity.strava_activity_id}`" role="button" v-if="activity.strava_activity_id">
|
||||
<a class="btn btn-link btn-lg link-body-emphasis"
|
||||
:href="`https://www.strava.com/activities/${activity.strava_activity_id}`" role="button"
|
||||
v-if="activity.strava_activity_id">
|
||||
<font-awesome-icon :icon="['fab', 'fa-strava']" />
|
||||
</a>
|
||||
<a class="btn btn-link btn-lg link-body-emphasis" :href="`https://connect.garmin.com/modern/activity/${activity.garminconnect_activity_id}`" role="button" v-if="activity.garminconnect_activity_id">
|
||||
<img src="/src/assets/garminconnect/Garmin_Connect_app_1024x1024-02.png" alt="Garmin Connect logo" height="22" />
|
||||
<a class="btn btn-link btn-lg link-body-emphasis"
|
||||
:href="`https://connect.garmin.com/modern/activity/${activity.garminconnect_activity_id}`"
|
||||
role="button" v-if="activity.garminconnect_activity_id">
|
||||
<img src="/src/assets/garminconnect/Garmin_Connect_app_1024x1024-02.png" alt="Garmin Connect logo"
|
||||
height="22" />
|
||||
</a>
|
||||
<div>
|
||||
<button class="btn btn-link btn-lg link-body-emphasis" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||
<button class="btn btn-link btn-lg link-body-emphasis" type="button" data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
<font-awesome-icon :icon="['fas', 'fa-ellipsis-vertical']" />
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li v-if="source === 'activity'">
|
||||
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#editActivityModal">
|
||||
<a class="dropdown-item" href="#" data-bs-toggle="modal"
|
||||
data-bs-target="#editActivityModal">
|
||||
{{ $t("activitySummaryComponent.buttonEditActivity") }}
|
||||
</a>
|
||||
</li>
|
||||
<li v-if="source === 'activity'"><hr class="dropdown-divider"></li>
|
||||
<li v-if="source === 'activity'">
|
||||
<hr class="dropdown-divider">
|
||||
</li>
|
||||
<li>
|
||||
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#deleteActivityModal">
|
||||
<a class="dropdown-item" href="#" data-bs-toggle="modal"
|
||||
data-bs-target="#deleteActivityModal">
|
||||
{{ $t("activitySummaryComponent.buttonDeleteActivity") }}
|
||||
</a>
|
||||
</li>
|
||||
@@ -79,10 +99,13 @@
|
||||
</div>
|
||||
|
||||
<!-- Modal edit activity -->
|
||||
<EditActivityModalComponent :activity="activity" @activityEditedFields="updateActivityFieldsOnEdit"/>
|
||||
<EditActivityModalComponent :activity="activity" @activityEditedFields="updateActivityFieldsOnEdit" />
|
||||
|
||||
<!-- Modal delete activity -->
|
||||
<ModalComponent modalId="deleteActivityModal" :title="t('activitySummaryComponent.buttonDeleteActivity')" :body="`${t('activitySummaryComponent.modalDeleteBody1')}<b>${activity.name}</b>?<br>${t('activitySummaryComponent.modalDeleteBody2')}`" :actionButtonType="`danger`" :actionButtonText="t('activitySummaryComponent.buttonDeleteActivity')" @submitAction="submitDeleteActivity"/>
|
||||
<ModalComponent modalId="deleteActivityModal" :title="t('activitySummaryComponent.buttonDeleteActivity')"
|
||||
:body="`${t('activitySummaryComponent.modalDeleteBody1')}<b>${activity.name}</b>?<br>${t('activitySummaryComponent.modalDeleteBody2')}`"
|
||||
:actionButtonType="`danger`" :actionButtonText="t('activitySummaryComponent.buttonDeleteActivity')"
|
||||
@submitAction="submitDeleteActivity" />
|
||||
|
||||
<!-- Activity title -->
|
||||
<h1 class="mt-3" v-if="source === 'activity'">
|
||||
@@ -95,7 +118,8 @@
|
||||
<!-- Activity summary -->
|
||||
<div class="row mt-3 align-items-center text-start">
|
||||
<!-- distance -->
|
||||
<div class="col" v-if="activity.activity_type != 10 && activity.activity_type != 14 && activity.activity_type != 18 && activity.activity_type != 19 && activity.activity_type != 20 && activity.activity_type != 21 && activity.activity_type != 22 && activity.activity_type != 23 && activity.activity_type != 24 && activity.activity_type != 25 && activity.activity_type != 26">
|
||||
<div class="col"
|
||||
v-if="activity.activity_type != 10 && activity.activity_type != 14 && activity.activity_type != 18 && activity.activity_type != 19 && activity.activity_type != 20 && activity.activity_type != 21 && activity.activity_type != 22 && activity.activity_type != 23 && activity.activity_type != 24 && activity.activity_type != 25 && activity.activity_type != 26">
|
||||
<span class="fw-lighter">
|
||||
{{ $t("activitySummaryComponent.activityDistance") }}
|
||||
</span>
|
||||
@@ -120,15 +144,17 @@
|
||||
</div>
|
||||
<div class="col border-start border-opacity-50">
|
||||
<!-- elevation -->
|
||||
<div v-if="activity.activity_type != 1 && activity.activity_type != 2 && activity.activity_type != 3 && activity.activity_type != 8 && activity.activity_type != 9 && activity.activity_type != 10 && activity.activity_type != 13 && activity.activity_type != 14 && activity.activity_type != 18 && activity.activity_type != 19 && activity.activity_type != 20 && activity.activity_type != 21 && activity.activity_type != 22 && activity.activity_type != 23 && activity.activity_type != 24 && activity.activity_type != 25 && activity.activity_type != 26">
|
||||
<div
|
||||
v-if="activity.activity_type != 1 && activity.activity_type != 2 && activity.activity_type != 3 && activity.activity_type != 8 && activity.activity_type != 9 && activity.activity_type != 10 && activity.activity_type != 13 && activity.activity_type != 14 && activity.activity_type != 18 && activity.activity_type != 19 && activity.activity_type != 20 && activity.activity_type != 21 && activity.activity_type != 22 && activity.activity_type != 23 && activity.activity_type != 24 && activity.activity_type != 25 && activity.activity_type != 26">
|
||||
<span class="fw-lighter">
|
||||
{{ $t("activitySummaryComponent.activityElevationGain") }}
|
||||
{{ $t("activitySummaryComponent.activityEleGain") }}
|
||||
</span>
|
||||
<br>
|
||||
<span>{{ formatElevation(activity.elevation_gain, authStore.user.units) }}</span>
|
||||
</div>
|
||||
<!-- pace -->
|
||||
<div v-else-if="activity.activity_type != 10 && activity.activity_type != 14 && activity.activity_type != 18 && activity.activity_type != 19 && activity.activity_type != 20 && activity.activity_type != 21 && activity.activity_type != 22 && activity.activity_type != 23 && activity.activity_type != 24 && activity.activity_type != 25 && activity.activity_type != 26">
|
||||
<div
|
||||
v-else-if="activity.activity_type != 10 && activity.activity_type != 14 && activity.activity_type != 18 && activity.activity_type != 19 && activity.activity_type != 20 && activity.activity_type != 21 && activity.activity_type != 22 && activity.activity_type != 23 && activity.activity_type != 24 && activity.activity_type != 25 && activity.activity_type != 26">
|
||||
<span class="fw-lighter">
|
||||
{{ $t("activitySummaryComponent.activityPace") }}
|
||||
</span>
|
||||
@@ -144,10 +170,12 @@
|
||||
<span>{{ formatHr(activity.average_hr) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row d-flex mt-3" v-if="source === 'activity' && activity.activity_type != 10 && activity.activity_type != 14 && activity.activity_type != 18 && activity.activity_type != 19 && activity.activity_type != 20 && activity.activity_type != 21 && activity.activity_type != 22 && activity.activity_type != 23 && activity.activity_type != 24 && activity.activity_type != 25 && activity.activity_type != 26">
|
||||
</div>
|
||||
<div class="row d-flex mt-3"
|
||||
v-if="source === 'activity' && activity.activity_type != 10 && activity.activity_type != 14 && activity.activity_type != 18 && activity.activity_type != 19 && activity.activity_type != 20 && activity.activity_type != 21 && activity.activity_type != 22 && activity.activity_type != 23 && activity.activity_type != 24 && activity.activity_type != 25 && activity.activity_type != 26">
|
||||
<!-- avg_power running and cycling activities-->
|
||||
<div class="col" v-if="activity.activity_type == 1 || activity.activity_type == 2 || activity.activity_type == 3 || activity.activity_type == 4 || activity.activity_type == 5 || activity.activity_type == 6 || activity.activity_type == 7 || activity.activity_type == 27">
|
||||
<div class="col"
|
||||
v-if="activity.activity_type == 1 || activity.activity_type == 2 || activity.activity_type == 3 || activity.activity_type == 4 || activity.activity_type == 5 || activity.activity_type == 6 || activity.activity_type == 7 || activity.activity_type == 27 || activity.activity_type == 28">
|
||||
<span class="fw-lighter">
|
||||
{{ $t("activitySummaryComponent.activityAvgPower") }}
|
||||
</span>
|
||||
@@ -155,7 +183,8 @@
|
||||
<span>{{ formatPower(activity.average_power) }}</span>
|
||||
</div>
|
||||
<!-- avg_hr not running and cycling activities-->
|
||||
<div class="col" v-if="activity.activity_type != 1 && activity.activity_type != 2 && activity.activity_type != 3 && activity.activity_type != 4 && activity.activity_type != 5 && activity.activity_type != 6 && activity.activity_type != 7 && activity.activity_type != 27">
|
||||
<div class="col"
|
||||
v-if="activity.activity_type != 1 && activity.activity_type != 2 && activity.activity_type != 3 && activity.activity_type != 4 && activity.activity_type != 5 && activity.activity_type != 6 && activity.activity_type != 7 && activity.activity_type != 27 && activity.activity_type != 28">
|
||||
<span class="fw-lighter">
|
||||
{{ $t("activitySummaryComponent.activityAvgHR") }}
|
||||
</span>
|
||||
@@ -163,7 +192,8 @@
|
||||
<span>{{ formatHr(activity.average_hr) }}</span>
|
||||
</div>
|
||||
<!-- max_hr not running and cycling activities-->
|
||||
<div class="col border-start border-opacity-50" v-if="activity.activity_type != 1 && activity.activity_type != 2 && activity.activity_type != 3 && activity.activity_type != 4 && activity.activity_type != 5 && activity.activity_type != 6 && activity.activity_type != 7 && activity.activity_type != 27">
|
||||
<div class="col border-start border-opacity-50"
|
||||
v-if="activity.activity_type != 1 && activity.activity_type != 2 && activity.activity_type != 3 && activity.activity_type != 4 && activity.activity_type != 5 && activity.activity_type != 6 && activity.activity_type != 7 && activity.activity_type != 27 && activity.activity_type != 28">
|
||||
<span class="fw-lighter">
|
||||
{{ $t("activitySummaryComponent.activityMaxHR") }}
|
||||
</span>
|
||||
@@ -171,13 +201,15 @@
|
||||
<span>{{ formatHr(activity.max_hr) }}</span>
|
||||
</div>
|
||||
<!-- ele gain running activities -->
|
||||
<div class="col border-start border-opacity-50" v-if="activity.activity_type == 1 || activity.activity_type == 2 || activity.activity_type == 3">
|
||||
<div class="col border-start border-opacity-50"
|
||||
v-if="activity.activity_type == 1 || activity.activity_type == 2 || activity.activity_type == 3">
|
||||
<span class="fw-lighter">{{ $t("activitySummaryComponent.activityEleGain") }}</span>
|
||||
<br>
|
||||
<span>{{ formatElevation(activity.elevation_gain, authStore.user.units) }}</span>
|
||||
</div>
|
||||
<!-- avg_speed cycling activities -->
|
||||
<div class="col border-start border-opacity-50" v-if="activity.activity_type == 4 || activity.activity_type == 5 || activity.activity_type == 6 || activity.activity_type == 7 || activity.activity_type == 27">
|
||||
<div class="col border-start border-opacity-50"
|
||||
v-if="activity.activity_type == 4 || activity.activity_type == 5 || activity.activity_type == 6 || activity.activity_type == 7 || activity.activity_type == 27 || activity.activity_type == 28">
|
||||
<span class="fw-lighter">
|
||||
{{ $t("activitySummaryComponent.activityAvgSpeed") }}
|
||||
</span>
|
||||
@@ -210,37 +242,38 @@ import LoadingComponent from "@/components/GeneralComponents/LoadingComponent.vu
|
||||
import UserAvatarComponent from "@/components/Users/UserAvatarComponent.vue";
|
||||
import EditActivityModalComponent from "@/components/Activities/Modals/EditActivityModalComponent.vue";
|
||||
import ModalComponent from "@/components/Modals/ModalComponent.vue";
|
||||
import BarChartComponent from '@/components/GeneralComponents/BarChartComponent.vue';
|
||||
// Importing the services
|
||||
import { users } from "@/services/usersService";
|
||||
import { activities } from "@/services/activitiesService";
|
||||
// Importing the utils
|
||||
import {
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatPace,
|
||||
formatHr,
|
||||
formatCalories,
|
||||
getIcon,
|
||||
formatLocation,
|
||||
formatDistance,
|
||||
formatElevation,
|
||||
formatPace,
|
||||
formatHr,
|
||||
formatCalories,
|
||||
getIcon,
|
||||
formatLocation,
|
||||
formatAverageSpeed,
|
||||
formatPower,
|
||||
} from "@/utils/activityUtils";
|
||||
import {
|
||||
formatDateMed,
|
||||
formatTime,
|
||||
formatDateMed,
|
||||
formatTime,
|
||||
formatSecondsToMinutes,
|
||||
} from "@/utils/dateTimeUtils";
|
||||
|
||||
// Props
|
||||
const props = defineProps({
|
||||
activity: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
source: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
activity: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
source: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
units: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
@@ -262,7 +295,7 @@ const userActivity = ref(null);
|
||||
|
||||
// Lifecycle
|
||||
onMounted(async () => {
|
||||
try {
|
||||
try {
|
||||
if (authStore.isAuthenticated) {
|
||||
userActivity.value = await users.getUserById(props.activity.user_id);
|
||||
} else {
|
||||
@@ -270,17 +303,17 @@ onMounted(async () => {
|
||||
userActivity.value = await users.getPublicUserById(props.activity.user_id);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
push.error(`${t("activitySummaryComponent.errorFetchingUserById")} - ${error}`);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
} catch (error) {
|
||||
push.error(`${t("activitySummaryComponent.errorFetchingUserById")} - ${error}`);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Methods
|
||||
async function submitDeleteActivity() {
|
||||
try {
|
||||
userActivity.value = await activities.deleteActivity(props.activity.id);
|
||||
try {
|
||||
userActivity.value = await activities.deleteActivity(props.activity.id);
|
||||
if (props.source === 'activity') {
|
||||
return router.push({
|
||||
path: "/",
|
||||
@@ -288,13 +321,13 @@ async function submitDeleteActivity() {
|
||||
});
|
||||
}
|
||||
emit("activityDeleted", props.activity.id);
|
||||
} catch (error) {
|
||||
push.error(`${t("activitySummaryComponent.errorDeletingActivity")} - ${error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
push.error(`${t("activitySummaryComponent.errorDeletingActivity")} - ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
function updateActivityFieldsOnEdit(data) {
|
||||
// Emit the activityEditedFields event to the parent component
|
||||
emit("activityEditedFields", data);
|
||||
// Emit the activityEditedFields event to the parent component
|
||||
emit("activityEditedFields", data);
|
||||
}
|
||||
</script>
|
||||
@@ -40,9 +40,11 @@
|
||||
</option>
|
||||
<option value="6">{{ $t("editActivityModalComponent.modalEditActivityTypeOption6") }}
|
||||
</option>
|
||||
<option value="27">{{ $t("editActivityModalComponent.modalEditActivityTypeOption27") }}
|
||||
</option>
|
||||
<option value="7">{{ $t("editActivityModalComponent.modalEditActivityTypeOption7") }}
|
||||
</option>
|
||||
<option value="27">{{ $t("editActivityModalComponent.modalEditActivityTypeOption27") }}
|
||||
<option value="28">{{ $t("editActivityModalComponent.modalEditActivityTypeOption28") }}
|
||||
</option>
|
||||
<hr>
|
||||
<option value="8">{{ $t("editActivityModalComponent.modalEditActivityTypeOption8") }}
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<div>
|
||||
<canvas ref="barChartCanvas"></canvas>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { Chart, registerables } from 'chart.js';
|
||||
import ChartDataLabels from 'chartjs-plugin-datalabels';
|
||||
|
||||
Chart.register(...registerables);
|
||||
|
||||
const props = defineProps({
|
||||
labels: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
values: {
|
||||
type: Array,
|
||||
required: true
|
||||
},
|
||||
barColors: {
|
||||
type: Array,
|
||||
default: () => ['#1e90ff', '#28a745', '#ffc107', '#fd7e14', '#dc3545']
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
datalabelsFormatter: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
});
|
||||
|
||||
const barChartCanvas = ref(null);
|
||||
let chartInstance = null;
|
||||
|
||||
function renderChart() {
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
}
|
||||
chartInstance = new Chart(barChartCanvas.value, {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: props.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: props.title,
|
||||
data: props.values,
|
||||
backgroundColor: props.barColors,
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
indexAxis: 'y',
|
||||
responsive: true,
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
title: {
|
||||
display: props.title,
|
||||
text: props.title
|
||||
},
|
||||
tooltip: { enabled: false },
|
||||
datalabels: {
|
||||
backgroundColor: function(context) {
|
||||
return "black";
|
||||
},
|
||||
borderRadius: 4,
|
||||
color: 'white',
|
||||
align: 'end', // Align datalabels to the end of the bar
|
||||
anchor: 'end', // Anchor datalabels to the end of the bar
|
||||
formatter: props.datalabelsFormatter || undefined,
|
||||
padding: 6
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
ticks: { stepSize: 10 }
|
||||
}
|
||||
}
|
||||
},
|
||||
plugins: [ChartDataLabels]
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
renderChart();
|
||||
});
|
||||
|
||||
watch(() => [props.labels, props.values, props.barColors, props.title], renderChart, { deep: true });
|
||||
</script>
|
||||
@@ -31,7 +31,7 @@ export default {
|
||||
emits: ['userDeleted'],
|
||||
setup(props) {
|
||||
const altText = ref('User Avatar');
|
||||
const userPhotoUrl = ref(props.user?.photo_path ? `${import.meta.env.VITE_ENDURAIN_HOST}/${props.user.photo_path}` : null);
|
||||
const userPhotoUrl = ref(props.user?.photo_path ? `${window.env.ENDURAIN_HOST}/${props.user.photo_path}` : null);
|
||||
const alignTopValue = ref(props.alignTop);
|
||||
|
||||
return {
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
"labelPillWorkoutSets": "Sets",
|
||||
"labelGraph": "Gràfics d'Activitat",
|
||||
"labelGraphHR": "Freqüència cardíaca",
|
||||
"labelHRZones": "Heart rate zones",
|
||||
"labelGraphPower": "Potència",
|
||||
"labelGraphCadence": "Cadència",
|
||||
"labelGraphElevation": "Elevació",
|
||||
"labelGraphVelocity": "Velocitat",
|
||||
"labelGraphPace": "Ritme",
|
||||
"labelGraphHRZone": "Zone",
|
||||
"labelDownsampling": "Dades reduïdes a ~200 punts",
|
||||
"errorMessageProcessingActivityStreams": "Error processant activitats de carrera",
|
||||
"labelGraphStrokeRate": "Freqüència batecs"
|
||||
|
||||
@@ -10,14 +10,13 @@
|
||||
"labelVirtual": "(Virtual) ",
|
||||
"activityDistance": "Distància",
|
||||
"activityTime": "Temps",
|
||||
"activityElevationGain": "Desnivell",
|
||||
"activityPace": "Ritme",
|
||||
"activityAvgPower": "Força Mitja",
|
||||
"activityAvgSpeed": "Velocitat Mitja",
|
||||
"activityAvgHR": "FC Mitja",
|
||||
"activityMaxHR": "FC Mitja",
|
||||
"activityEleGain": "Desnivell",
|
||||
"activityEleLoss": "Pèrdua d'elevació",
|
||||
"activityMaxHR": "Max HR",
|
||||
"activityAvgPower": "Avg power",
|
||||
"activityAvgSpeed": "Avg speed",
|
||||
"activityEleGain": "Ele gain",
|
||||
"activityEleLoss": "Ele loss",
|
||||
"activityCalories": "Calories",
|
||||
"activityNoData": "Sense dades",
|
||||
"errorFetchingUserById": "Error obtenint usuari per id",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"modalEditActivityTypeOption25": "Raquetbol",
|
||||
"modalEditActivityTypeOption26": "Pickleball",
|
||||
"modalEditActivityTypeOption27": "Desplaçament",
|
||||
"modalEditActivityTypeOption28": "Indoor ride",
|
||||
"modalEditActivityVisibilityLabel": "Visibilitat",
|
||||
"modalEditActivityVisibilityOption0": "Públic",
|
||||
"modalEditActivityVisibilityOption1": "Seguidors",
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"gearTypeOption2": "Sabatilles",
|
||||
"gearTypeOption3": "Neoprè",
|
||||
"gearTypeOption4": "Raqueta",
|
||||
"gearTypeOption5": "Skis",
|
||||
"gearTypeOption6": "Snowboard",
|
||||
"gearTypeOption5": "Esquís",
|
||||
"gearTypeOption6": "Taula de snow",
|
||||
"gearFromStrava": "Strava",
|
||||
"gearFromGarminConnect": "Garmin Connect",
|
||||
"labelBrand": "Marca",
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
"labelPillWorkoutSets": "Sets",
|
||||
"labelGraph": "Activity data graphs",
|
||||
"labelGraphHR": "Heart rate",
|
||||
"labelHRZones": "Heart rate zones",
|
||||
"labelGraphPower": "Power",
|
||||
"labelGraphCadence": "Cadence",
|
||||
"labelGraphElevation": "Elevation",
|
||||
"labelGraphVelocity": "Speed",
|
||||
"labelGraphPace": "Pace",
|
||||
"labelGraphHRZone": "Zone",
|
||||
"labelDownsampling": "Data downsampled to ~200 points",
|
||||
"errorMessageProcessingActivityStreams": "Error processing activity streams",
|
||||
"labelGraphStrokeRate": "Schlagzahl"
|
||||
|
||||
@@ -10,14 +10,13 @@
|
||||
"labelVirtual": "(Virtuell) ",
|
||||
"activityDistance": "Distanz",
|
||||
"activityTime": "Zeit",
|
||||
"activityElevationGain": "Höhenunterschied",
|
||||
"activityPace": "Tempo",
|
||||
"activityAvgPower": "Durchschnittliche Leistung",
|
||||
"activityAvgSpeed": "Durchschnittsgeschwindigkeit",
|
||||
"activityAvgHR": "Durchschn. Herzfrequenz",
|
||||
"activityMaxHR": "Max. Herzfrequenz",
|
||||
"activityEleGain": "Höhenunterschied",
|
||||
"activityEleLoss": "Höhenverlust",
|
||||
"activityMaxHR": "Max HR",
|
||||
"activityAvgPower": "Avg power",
|
||||
"activityAvgSpeed": "Avg speed",
|
||||
"activityEleGain": "Ele gain",
|
||||
"activityEleLoss": "Ele loss",
|
||||
"activityCalories": "Kalorien",
|
||||
"activityNoData": "Keine Daten",
|
||||
"errorFetchingUserById": "Fehler beim Abrufen des Benutzers über id",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"modalEditActivityTypeOption25": "Racquetball",
|
||||
"modalEditActivityTypeOption26": "Pickleball",
|
||||
"modalEditActivityTypeOption27": "Pendeln",
|
||||
"modalEditActivityTypeOption28": "Indoor ride",
|
||||
"modalEditActivityVisibilityLabel": "Sichtbarkeit",
|
||||
"modalEditActivityVisibilityOption0": "Öffentlich",
|
||||
"modalEditActivityVisibilityOption1": "Follower",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"gearListTypeOption1": "Fahrrad",
|
||||
"gearListTypeOption2": "Schuhe",
|
||||
"gearListTypeOption3": "Neoprenanzug",
|
||||
"gearListTypeOption4": "Tennisschläger",
|
||||
"gearListTypeOption4": "Rack",
|
||||
"gearListTypeOption5": "Skis",
|
||||
"gearListTypeOption6": "Snowboard",
|
||||
"gearListGearIsInactiveBadge": "Inaktiv",
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
"labelPillWorkoutSets": "Sets",
|
||||
"labelGraph": "Gráficos de datos de actividad",
|
||||
"labelGraphHR": "Frecuencia cardíaca",
|
||||
"labelHRZones": "Heart rate zones",
|
||||
"labelGraphPower": "Potencia",
|
||||
"labelGraphCadence": "Cadencia",
|
||||
"labelGraphElevation": "Altura",
|
||||
"labelGraphVelocity": "Speed",
|
||||
"labelGraphPace": "Ritmo",
|
||||
"labelGraphHRZone": "Zone",
|
||||
"labelDownsampling": "Datos reducidos a ~200 puntos",
|
||||
"errorMessageProcessingActivityStreams": "Error al procesar los flujos de actividad",
|
||||
"labelGraphStrokeRate": "Stroke rate"
|
||||
|
||||
@@ -10,14 +10,13 @@
|
||||
"labelVirtual": "(Virtual) ",
|
||||
"activityDistance": "Distancia",
|
||||
"activityTime": "Tiempo",
|
||||
"activityElevationGain": "Ganancia de elevación",
|
||||
"activityPace": "Ritmo",
|
||||
"activityAvgPower": "Potencia media",
|
||||
"activityAvgSpeed": "Velocidad media",
|
||||
"activityAvgHR": "RC medio",
|
||||
"activityMaxHR": "RC máximo",
|
||||
"activityEleGain": "Ganancia de elevación",
|
||||
"activityEleLoss": "Pérdida de elevación",
|
||||
"activityMaxHR": "Max HR",
|
||||
"activityAvgPower": "Avg power",
|
||||
"activityAvgSpeed": "Avg speed",
|
||||
"activityEleGain": "Ele gain",
|
||||
"activityEleLoss": "Ele loss",
|
||||
"activityCalories": "Calorías",
|
||||
"activityNoData": "No hay datos",
|
||||
"errorFetchingUserById": "Error obteniendo usuario por id",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"modalEditActivityTypeOption25": "Racquetball",
|
||||
"modalEditActivityTypeOption26": "Pickleball",
|
||||
"modalEditActivityTypeOption27": "Viaje Conmutando",
|
||||
"modalEditActivityTypeOption28": "Indoor ride",
|
||||
"modalEditActivityVisibilityLabel": "Visibilidad",
|
||||
"modalEditActivityVisibilityOption0": "Público",
|
||||
"modalEditActivityVisibilityOption1": "Seguidores",
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
"labelPillWorkoutSets": "Ensembles",
|
||||
"labelGraph": "Graphiques des données d'activité",
|
||||
"labelGraphHR": "Fréquence cardiaque",
|
||||
"labelHRZones": "Heart rate zones",
|
||||
"labelGraphPower": "Puissance",
|
||||
"labelGraphCadence": "Cadence",
|
||||
"labelGraphElevation": "Élévation",
|
||||
"labelGraphVelocity": "Speed",
|
||||
"labelGraphPace": "Allure",
|
||||
"labelGraphHRZone": "Zone",
|
||||
"labelDownsampling": "Données abaissées à ~200 points",
|
||||
"errorMessageProcessingActivityStreams": "Erreur lors du traitement des flux d'activité",
|
||||
"labelGraphStrokeRate": "Stroke rate"
|
||||
|
||||
@@ -10,14 +10,13 @@
|
||||
"labelVirtual": "(Virtuel) ",
|
||||
"activityDistance": "Distance",
|
||||
"activityTime": "Temps",
|
||||
"activityElevationGain": "Dénivelé positif",
|
||||
"activityPace": "Allure",
|
||||
"activityAvgPower": "Puissance moyenne",
|
||||
"activityAvgSpeed": "Vitesse moyenne",
|
||||
"activityAvgHR": "FC moyenne",
|
||||
"activityMaxHR": "FC Max",
|
||||
"activityEleGain": "Dénivelé positif",
|
||||
"activityEleLoss": "Dénivelé négatif",
|
||||
"activityMaxHR": "Max HR",
|
||||
"activityAvgPower": "Avg power",
|
||||
"activityAvgSpeed": "Avg speed",
|
||||
"activityEleGain": "Ele gain",
|
||||
"activityEleLoss": "Ele loss",
|
||||
"activityCalories": "Calories",
|
||||
"activityNoData": "Aucune donnée",
|
||||
"errorFetchingUserById": "Erreur lors de la récupération de l'utilisateur par l'id",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"modalEditActivityTypeOption25": "Racquetball",
|
||||
"modalEditActivityTypeOption26": "Tennis léger",
|
||||
"modalEditActivityTypeOption27": "Vélotaf",
|
||||
"modalEditActivityTypeOption28": "Indoor ride",
|
||||
"modalEditActivityVisibilityLabel": "Visibilité",
|
||||
"modalEditActivityVisibilityOption0": "Publique",
|
||||
"modalEditActivityVisibilityOption1": "Abonnés",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"title": "Activiteiten",
|
||||
"filterLabelType": "Type",
|
||||
"filterOptionAllTypes": "All types",
|
||||
"filterLabelFromDate": "From date",
|
||||
"filterLabelToDate": "To date",
|
||||
"filterLabelNameLocation": "Name or location",
|
||||
"filterPlaceholderNameLocation": "e.g., Morning run",
|
||||
"filterOptionAllTypes": "Alle typen",
|
||||
"filterLabelFromDate": "Vanaf datum",
|
||||
"filterLabelToDate": "Tot datum",
|
||||
"filterLabelNameLocation": "Naam of locatie",
|
||||
"filterPlaceholderNameLocation": "bijv.: Ochtendloop",
|
||||
"buttonClear": "Wissen",
|
||||
"buttonApply": "Toepassen",
|
||||
"errorFailedFetchActivityTypes": "Kon activiteitstypes niet ophalen",
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"successMessageGearDeleted": "Uitrusting verwijderd uit activiteit",
|
||||
"errorMessageDeleteGear": "Fout bij verwijderen van uitrusting uit activiteit",
|
||||
"errorMessageActivityNotFound": "Activiteit niet gevonden",
|
||||
"alertPrivacyMessage": "You have hidden information in this activity. You can see it, but others cannot."
|
||||
"alertPrivacyMessage": "Je hebt informatie in deze activiteit verborgen. Je kunt het zelf zien, maar anderen niet."
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
"subTitleElevation": "Hoogte",
|
||||
"labelElevationGain": "Stijging",
|
||||
"labelElevationLoss": "Daling",
|
||||
"subTitleStrokeRate": "Stroke rate",
|
||||
"labelAvgStrokeRate": "Avg stroke rate",
|
||||
"labelMaxStrokeRate": "Max stroke rate"
|
||||
"subTitleStrokeRate": "Slagsnelheid",
|
||||
"labelAvgStrokeRate": "Gem slagsnelheid",
|
||||
"labelMaxStrokeRate": "Max slagsnelheid"
|
||||
}
|
||||
@@ -9,6 +9,6 @@
|
||||
"labelLapElev": "Hoogte",
|
||||
"labelLapAvgHr": "Gem hartslag",
|
||||
"labelLapHR": "HS",
|
||||
"labelLapStrokeRate": "Stroke rate",
|
||||
"labelLapStrokeRate": "Slagsnelheid",
|
||||
"labelLapSR": "SR"
|
||||
}
|
||||
@@ -4,12 +4,14 @@
|
||||
"labelPillWorkoutSets": "Sets",
|
||||
"labelGraph": "Activiteit data grafieken",
|
||||
"labelGraphHR": "Hartslag",
|
||||
"labelHRZones": "Heart rate zones",
|
||||
"labelGraphPower": "Vermogen",
|
||||
"labelGraphCadence": "Cadans",
|
||||
"labelGraphElevation": "Hoogte",
|
||||
"labelGraphVelocity": "Speed",
|
||||
"labelGraphVelocity": "Snelheid",
|
||||
"labelGraphPace": "Tempo",
|
||||
"labelGraphHRZone": "Zone",
|
||||
"labelDownsampling": "Gegevens teruggeschaald naar ~200 punten",
|
||||
"errorMessageProcessingActivityStreams": "Fout bij verwerken activiteit streams",
|
||||
"labelGraphStrokeRate": "Stroke rate"
|
||||
"labelGraphStrokeRate": "Slagsnelheid"
|
||||
}
|
||||
@@ -10,14 +10,13 @@
|
||||
"labelVirtual": "(Virtueel) ",
|
||||
"activityDistance": "Afstand",
|
||||
"activityTime": "Tijd",
|
||||
"activityElevationGain": "Hoogtewinst",
|
||||
"activityPace": "Tempo",
|
||||
"activityAvgPower": "Gem. vermogen",
|
||||
"activityAvgSpeed": "Gem. snelheid",
|
||||
"activityAvgHR": "Gem. HR",
|
||||
"activityMaxHR": "Max. HR",
|
||||
"activityEleGain": "Hoogtewinst",
|
||||
"activityEleLoss": "Hoogteverlies",
|
||||
"activityMaxHR": "Max HR",
|
||||
"activityAvgPower": "Avg power",
|
||||
"activityAvgSpeed": "Avg speed",
|
||||
"activityEleGain": "Ele gain",
|
||||
"activityEleLoss": "Ele loss",
|
||||
"activityCalories": "Calorieën",
|
||||
"activityNoData": "Geen gegevens",
|
||||
"errorFetchingUserById": "Fout bij ophalen gebruiker per id",
|
||||
|
||||
@@ -32,22 +32,23 @@
|
||||
"modalEditActivityTypeOption25": "Racquetbal",
|
||||
"modalEditActivityTypeOption26": "Picklebal",
|
||||
"modalEditActivityTypeOption27": "Overschrijven overstappen",
|
||||
"modalEditActivityTypeOption28": "Indoor ride",
|
||||
"modalEditActivityVisibilityLabel": "Zichtbaarheid",
|
||||
"modalEditActivityVisibilityOption0": "Openbaar",
|
||||
"modalEditActivityVisibilityOption1": "Volgers",
|
||||
"modalEditActivityVisibilityOption2": "Privé",
|
||||
"modalEditActivityHideStartTimeLabel": "Hide start time",
|
||||
"modalEditActivityHideLocationLabel": "Hide location",
|
||||
"modalEditActivityHideMapLabel": "Hide map",
|
||||
"modalEditActivityHideHrLabel": "Hide heart rate",
|
||||
"modalEditActivityHidePowerLabel": "Hide power",
|
||||
"modalEditActivityHideCadenceLabel": "Hide cadence",
|
||||
"modalEditActivityHideElevationLabel": "Hide elevation",
|
||||
"modalEditActivityHideSpeedLabel": "Hide speed",
|
||||
"modalEditActivityHidePaceLabel": "Hide pace",
|
||||
"modalEditActivityHideLapsLabel": "Hide laps",
|
||||
"modalEditActivityHideWorkoutSetsStepsLabel": "Hide workout sets/steps",
|
||||
"modalEditActivityHideGearLabel": "Hide gear",
|
||||
"modalEditActivityHideStartTimeLabel": "Verberg begintijd",
|
||||
"modalEditActivityHideLocationLabel": "Verberg locatie",
|
||||
"modalEditActivityHideMapLabel": "Verberg kaart",
|
||||
"modalEditActivityHideHrLabel": "Verberg hartslag",
|
||||
"modalEditActivityHidePowerLabel": "Verberg vermogen",
|
||||
"modalEditActivityHideCadenceLabel": "Verberg cadans",
|
||||
"modalEditActivityHideElevationLabel": "Verberg hoogteverschil",
|
||||
"modalEditActivityHideSpeedLabel": "Verberg snelheid",
|
||||
"modalEditActivityHidePaceLabel": "Verberg tempo",
|
||||
"modalEditActivityHideLapsLabel": "Verberg rondes",
|
||||
"modalEditActivityHideWorkoutSetsStepsLabel": "Verberg sets / stappen",
|
||||
"modalEditActivityHideGearLabel": "Verberg uitrusting",
|
||||
"successActivityEdit": "Activiteit succesvol bewerkt",
|
||||
"errorActivityEdit": "Fout bij bewerken activiteit"
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
"addEditGearModalAddTypeOption2": "Schoenen",
|
||||
"addEditGearModalAddTypeOption3": "Wetsuit",
|
||||
"addEditGearModalAddTypeOption4": "Raket",
|
||||
"addEditGearModalAddTypeOption5": "Skis",
|
||||
"addEditGearModalAddTypeOption5": "Ski's",
|
||||
"addEditGearModalAddTypeOption6": "Snowboard",
|
||||
"addEditGearModalAddDateLabel": "Aanmaakdatum",
|
||||
"addEditGearModalAddIsActiveLabel": "Is actief",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"gearListTypeOption2": "Schoenen",
|
||||
"gearListTypeOption3": "Wetsuit",
|
||||
"gearListTypeOption4": "Raket",
|
||||
"gearListTypeOption5": "Skis",
|
||||
"gearListTypeOption5": "Ski's",
|
||||
"gearListTypeOption6": "Snowboard",
|
||||
"gearListGearIsInactiveBadge": "Inactief",
|
||||
"gearListGearFromStrava": "Strava",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"search": "Zoeken",
|
||||
"activities": "Activiteiten",
|
||||
"summary": "Summary",
|
||||
"summary": "Overzicht",
|
||||
"gear": "Uitrusting",
|
||||
"health": "Gezondheid",
|
||||
"profile": "Profiel",
|
||||
|
||||
@@ -47,6 +47,6 @@
|
||||
"modalRetrieveHealthDataByDaysTitle": "Gezondheidsgegevens ophalen per dag",
|
||||
"modalRetrieveHealthDataByDateRangeTitle": "Retrieve health data by date range",
|
||||
"errorMessageUnableToGetGarminConnectHealthDataDays": "Unable to get Garmin Connect health data by days",
|
||||
"errorMessageUnableToGetGarminConnectHealthDataDateRange": "Unable to get Garmin Connect health data using date range",
|
||||
"errorMessageUnableToGetGarminConnectHealthDataDateRange": "Garmin Connect gezondheidsgegevens ophalen voor datumbereik is niet gelukt",
|
||||
"loadingMessageRetrievingGarminConnectHealthData": "Ophalen van Garmin Connect gezondheidsgegevens"
|
||||
}
|
||||
@@ -47,14 +47,14 @@
|
||||
"privacyOption1": "Openbaar",
|
||||
"privacyOption2": "Volgers",
|
||||
"privacyOption3": "Privé",
|
||||
"defaultActivityStartTime": "Hide activity start time",
|
||||
"defaultActivityLocation": "Hide activity location",
|
||||
"defaultActivityMap": "Hide activity map",
|
||||
"defaultActivityHeartRate": "Hide activity heart rate",
|
||||
"defaultActivityStartTime": "Verberg begintijd van activiteit",
|
||||
"defaultActivityLocation": "Verberg locatie van activiteit",
|
||||
"defaultActivityMap": "Verberg kaart van activiteit",
|
||||
"defaultActivityHeartRate": "Verberg hartslag van activiteit",
|
||||
"defaultActivityPower": "Hide activity power",
|
||||
"defaultActivityCadence": "Hide activity cadence",
|
||||
"defaultActivityElevation": "Hide activity elevation",
|
||||
"defaultActivitySpeed": "Hide activity speed",
|
||||
"defaultActivitySpeed": "Verberg snelheid van activiteit",
|
||||
"defaultActivityPace": "Hide activity pace",
|
||||
"defaultActivityLaps": "Hide activity laps",
|
||||
"defaultActivitySetsSteps": "Hide activity sets/steps",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"gearTypeOption2": "Schoenen",
|
||||
"gearTypeOption3": "Wetsuit",
|
||||
"gearTypeOption4": "Raket",
|
||||
"gearTypeOption5": "Skis",
|
||||
"gearTypeOption5": "Ski's",
|
||||
"gearTypeOption6": "Snowboard",
|
||||
"gearFromStrava": "Strava",
|
||||
"gearFromGarminConnect": "Garmin Connect",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"buttonBack": "Terug",
|
||||
"buttonClose": "Sluiten",
|
||||
"true": "True",
|
||||
"false": "False",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"true": "Waar",
|
||||
"false": "Fout",
|
||||
"yes": "Ja",
|
||||
"no": "Nee",
|
||||
"languageOption1": "Engels (VS)",
|
||||
"languageOption2": "Catalaans (CA)",
|
||||
"languageOption3": "Portugees (PT)",
|
||||
@@ -41,15 +41,15 @@
|
||||
"unitsSpm": "spm",
|
||||
"labelElevationInMeters": "Hoogte in meters",
|
||||
"labelElevationInFeet": "Hoogte in voet",
|
||||
"labelVelocityInKmH": "Speed in km/h",
|
||||
"labelVelocityInMph": "Speed in mph",
|
||||
"labelVelocityInKmH": "Snelheid in km/u",
|
||||
"labelVelocityInMph": "Snelheid in mijl per uur",
|
||||
"labelPaceInMinKm": "Ritme in min/km",
|
||||
"labelPaceInMin100m": "Ritme in min/100m",
|
||||
"labelPaceInMinMile": "Ritme in min/mijl",
|
||||
"labelPaceInMin100yd": "Ritme in min/100yd",
|
||||
"labelLaps": "Laps",
|
||||
"labelRest": "Rest",
|
||||
"labelStrokeRateInSpm": "Stroke rate in spm",
|
||||
"startDateLabel": "Start date",
|
||||
"endDateLabel": "End date"
|
||||
"labelLaps": "Rondes",
|
||||
"labelRest": "Rust",
|
||||
"labelStrokeRateInSpm": "Slagsnelheid in spm",
|
||||
"startDateLabel": "Startdatum",
|
||||
"endDateLabel": "Einddatum"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sessionExpired": "Gebruikerssessie verlopen",
|
||||
"logoutSuccess": "You have been successfully logged out",
|
||||
"logoutSuccess": "Je bent succesvol uitgelogd",
|
||||
"error401": "Gebruikersnaam of wachtwoord ongeldig",
|
||||
"error403": "U bent niet gemachtigd voor toegang tot deze bron",
|
||||
"error500": "Het was niet mogelijk om verbinding te maken met de server. Probeer het later opnieuw",
|
||||
|
||||
@@ -12,15 +12,15 @@
|
||||
"searchSelectActivityType6": "Hiken",
|
||||
"searchSelectActivityType7": "Roeien",
|
||||
"searchSelectActivityType8": "Yoga",
|
||||
"searchSelectActivityType9": "Skis",
|
||||
"searchSelectActivityType9": "Ski's",
|
||||
"searchSelectActivityType10": "Snowboarden",
|
||||
"searchSelectActivityType11": "Tennis",
|
||||
"searchSelectGearType0": "Alle",
|
||||
"searchSelectGearType1": "Fiets",
|
||||
"searchSelectGearType2": "Schoenen",
|
||||
"searchSelectGearType3": "Wetsuit",
|
||||
"searchSelectGearType4": "Racquet",
|
||||
"searchSelectGearType5": "Skis",
|
||||
"searchSelectGearType4": "Racket",
|
||||
"searchSelectGearType5": "Ski's",
|
||||
"searchSelectGearType6": "Snowboard",
|
||||
"resultIsInactiveBadge": "Inactief",
|
||||
"searchInputPlaceholder": "Zoeken",
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
{
|
||||
"title": "Activity summary",
|
||||
"title": "Overzicht activiteit",
|
||||
"filterLabelActivityType": "Type",
|
||||
"filterOptionAllTypes": "All types",
|
||||
"labelViewType": "View by",
|
||||
"optionWeekly": "Weekly",
|
||||
"optionMonthly": "Monthly",
|
||||
"optionYearly": "Yearly",
|
||||
"filterOptionAllTypes": "Alle typen",
|
||||
"labelViewType": "Bekijk per",
|
||||
"optionWeekly": "week",
|
||||
"optionMonthly": "maand",
|
||||
"optionYearly": "jaar",
|
||||
"optionLifetime": "Lifetime",
|
||||
"labelSelectWeek": "Week",
|
||||
"labelSelectMonth": "Month",
|
||||
"labelSelectYear": "Year",
|
||||
"labelSelectPeriod": "Period",
|
||||
"buttonPreviousPeriod": "Previous",
|
||||
"buttonNextPeriod": "Next",
|
||||
"labelSelectMonth": "Maand",
|
||||
"labelSelectYear": "Jaar",
|
||||
"labelSelectPeriod": "Periode",
|
||||
"buttonPreviousPeriod": "Vorige",
|
||||
"buttonNextPeriod": "Volgende",
|
||||
"headerSummaryFor": "Summary for {period}",
|
||||
"headerBreakdown": "Breakdown",
|
||||
"headerActivitiesInPeriod": "Activities in period",
|
||||
@@ -20,21 +20,21 @@
|
||||
"errorLoadingSummary": "Error loading summary",
|
||||
"errorLoadingSummaryLoad": "Error loading summary on page load",
|
||||
"errorFetchingActivities": "Error fetching activities",
|
||||
"noDataForPeriod": "No data for this period.",
|
||||
"colDay": "Day",
|
||||
"colWeekNum": "Week #",
|
||||
"colMonth": "Month",
|
||||
"colDistance": "Distance",
|
||||
"colDuration": "Duration",
|
||||
"noDataForPeriod": "Geen gegevens voor deze periode.",
|
||||
"colDay": "Dag",
|
||||
"colWeekNum": "Weeknummer",
|
||||
"colMonth": "Maand",
|
||||
"colDistance": "Afstand",
|
||||
"colDuration": "Duur",
|
||||
"colElevation": "Elevation",
|
||||
"colCalories": "Calories",
|
||||
"colActivities": "Activities",
|
||||
"metricTotalDistance": "Total distance",
|
||||
"metricTotalDuration": "Total duration",
|
||||
"metricTotalElevation": "Total elevation",
|
||||
"metricTotalCalories": "Total calories",
|
||||
"colCalories": "Calorieën",
|
||||
"colActivities": "Activiteiten",
|
||||
"metricTotalDistance": "Totale afstand",
|
||||
"metricTotalDuration": "Totale duur",
|
||||
"metricTotalElevation": "Totaal hoogteverschil",
|
||||
"metricTotalCalories": "Totaal aantal calorieën",
|
||||
"metricTotalActivities": "Total activities",
|
||||
"invalidYearSelected": "Invalid year selected",
|
||||
"invalidYearSelected": "Ongeldig jaar geselecteerd",
|
||||
"headerTypeBreakdown": "Breakdown by type",
|
||||
"colActivityType": "Type",
|
||||
"headerYear": "Year {year}",
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
"labelPillWorkoutSets": "Jogos",
|
||||
"labelGraph": "Gráficos de dados da atividade",
|
||||
"labelGraphHR": "Frequência cardíaca",
|
||||
"labelHRZones": "Zonas de FC",
|
||||
"labelGraphPower": "Potência",
|
||||
"labelGraphCadence": "Cadência",
|
||||
"labelGraphElevation": "Elevação",
|
||||
"labelGraphVelocity": "Velocidade",
|
||||
"labelGraphPace": "Ritmo",
|
||||
"labelGraphHRZone": "Zona",
|
||||
"labelDownsampling": "Dados downsampled para ~200 pontos",
|
||||
"errorMessageProcessingActivityStreams": "Erro ao processar fluxos de atividade",
|
||||
"labelGraphStrokeRate": "Velocidade de braçada"
|
||||
|
||||
@@ -10,14 +10,13 @@
|
||||
"labelVirtual": "(Virtual) ",
|
||||
"activityDistance": "Distância",
|
||||
"activityTime": "Tempo",
|
||||
"activityElevationGain": "Ganho de elevação",
|
||||
"activityPace": "Ritmo",
|
||||
"activityAvgPower": "Potência média",
|
||||
"activityAvgSpeed": "Velocidade média",
|
||||
"activityAvgHR": "Média de FC",
|
||||
"activityMaxHR": "FC máxima",
|
||||
"activityEleGain": "Ganho de elevação",
|
||||
"activityEleLoss": "Perda de elevação",
|
||||
"activityAvgPower": "Potência média",
|
||||
"activityAvgSpeed": "Vel. média",
|
||||
"activityEleGain": "Ganho ele",
|
||||
"activityEleLoss": "Perda ele",
|
||||
"activityCalories": "Calorias",
|
||||
"activityNoData": "Sem dados",
|
||||
"errorFetchingUserById": "Erro ao procurar utilizador por ID",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"modalEditActivityTypeOption25": "Raquetebol",
|
||||
"modalEditActivityTypeOption26": "Pickleball",
|
||||
"modalEditActivityTypeOption27": "Deslocação em bicicleta",
|
||||
"modalEditActivityTypeOption28": "Ciclismo interior",
|
||||
"modalEditActivityVisibilityLabel": "Visibilidade",
|
||||
"modalEditActivityVisibilityOption0": "Público",
|
||||
"modalEditActivityVisibilityOption1": "Seguidores",
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
"labelPillWorkoutSets": "Sets",
|
||||
"labelGraph": "Activity data graphs",
|
||||
"labelGraphHR": "Heart rate",
|
||||
"labelHRZones": "Heart rate zones",
|
||||
"labelGraphPower": "Power",
|
||||
"labelGraphCadence": "Cadence",
|
||||
"labelGraphElevation": "Elevation",
|
||||
"labelGraphVelocity": "Speed",
|
||||
"labelGraphPace": "Pace",
|
||||
"labelGraphHRZone": "Zone",
|
||||
"labelDownsampling": "Data downsampled to ~200 points",
|
||||
"errorMessageProcessingActivityStreams": "Error processing activity streams",
|
||||
"labelGraphStrokeRate": "Stroke rate"
|
||||
|
||||
@@ -10,14 +10,13 @@
|
||||
"labelVirtual": "(Virtual) ",
|
||||
"activityDistance": "Distance",
|
||||
"activityTime": "Time",
|
||||
"activityElevationGain": "Elevation Gain",
|
||||
"activityPace": "Pace",
|
||||
"activityAvgPower": "Avg Power",
|
||||
"activityAvgSpeed": "Avg Speed",
|
||||
"activityAvgHR": "Avg HR",
|
||||
"activityMaxHR": "Max HR",
|
||||
"activityEleGain": "Elevation gain",
|
||||
"activityEleLoss": "Elevation loss",
|
||||
"activityAvgPower": "Avg power",
|
||||
"activityAvgSpeed": "Avg speed",
|
||||
"activityEleGain": "Ele gain",
|
||||
"activityEleLoss": "Ele loss",
|
||||
"activityCalories": "Calories",
|
||||
"activityNoData": "No data",
|
||||
"errorFetchingUserById": "Error fetching user by id",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"modalEditActivityTypeOption25": "Racquetball",
|
||||
"modalEditActivityTypeOption26": "Pickleball",
|
||||
"modalEditActivityTypeOption27": "Commuting ride",
|
||||
"modalEditActivityTypeOption28": "Indoor ride",
|
||||
"modalEditActivityVisibilityLabel": "Visibility",
|
||||
"modalEditActivityVisibilityOption0": "Public",
|
||||
"modalEditActivityVisibilityOption1": "Followers",
|
||||
|
||||
@@ -12,7 +12,7 @@ export const strava = {
|
||||
return fetchPutRequest('strava/client', data);
|
||||
},
|
||||
linkStrava(state, stravaClientId) {
|
||||
let redirectUri = `${import.meta.env.VITE_ENDURAIN_HOST}`;
|
||||
let redirectUri = `${window.env.ENDURAIN_HOST}`;
|
||||
redirectUri = encodeURIComponent(redirectUri);
|
||||
const scope = 'read,read_all,profile:read_all,activity:read,activity:read_all';
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@ export function activityTypeIsRunning(activity) {
|
||||
* @returns {boolean} True if the type of the activity is cycling, false otherwise.
|
||||
*/
|
||||
export function activityTypeIsCycling(activity) {
|
||||
return activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27;
|
||||
return activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27 || activity.activity_type === 28;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +232,8 @@ export function formatAverageSpeed(
|
||||
activity.activity_type === 5 ||
|
||||
activity.activity_type === 6 ||
|
||||
activity.activity_type === 7 ||
|
||||
activity.activity_type === 27
|
||||
activity.activity_type === 27 ||
|
||||
activity.activity_type === 28
|
||||
) {
|
||||
if (units) {
|
||||
return `${formatAverageSpeedImperial(speed)} ${i18n.global.t("generalItems.unitsKmH")}`;
|
||||
@@ -246,7 +247,8 @@ export function formatAverageSpeed(
|
||||
activity.activity_type === 5 ||
|
||||
activity.activity_type === 6 ||
|
||||
activity.activity_type === 7 ||
|
||||
activity.activity_type === 27
|
||||
activity.activity_type === 27 ||
|
||||
activity.activity_type === 28
|
||||
) {
|
||||
if (units) {
|
||||
return `${formatAverageSpeedMetric(speed)} ${i18n.global.t("generalItems.unitsMph")}`;
|
||||
@@ -425,6 +427,7 @@ export function getIcon(typeId) {
|
||||
25: ["fas", "table-tennis-paddle-ball"],
|
||||
26: ["fas", "table-tennis-paddle-ball"],
|
||||
27: ["fas", "person-biking"],
|
||||
28: ["fas", "person-biking"],
|
||||
};
|
||||
|
||||
return iconMap[typeId] || ["fas", "dumbbell"];
|
||||
|
||||
21
frontend/app/src/utils/chartUtils.js
Normal file
21
frontend/app/src/utils/chartUtils.js
Normal file
@@ -0,0 +1,21 @@
|
||||
export function getZoneColor(index) {
|
||||
// Example colors for 5 HR zones
|
||||
const colors = [
|
||||
'#1e90ff', // Zone 1: blue
|
||||
'#28a745', // Zone 2: green
|
||||
'#ffc107', // Zone 3: yellow
|
||||
'#fd7e14', // Zone 4: orange
|
||||
'#dc3545', // Zone 5: red
|
||||
];
|
||||
return colors[index] || '#000';
|
||||
}
|
||||
|
||||
export function getHrBarChartData(hrZones, t) {
|
||||
const zones = Object.values(hrZones);
|
||||
return {
|
||||
labels: zones.map((z, i) => `${t('activityMandAbovePillsComponent.labelGraphHRZone')} ${i + 1} (${z.hr || ''})`),
|
||||
// values: zones.map(z => `${z.percent ?? 0}%`),
|
||||
values: zones.map(z => z.percent ?? 0),
|
||||
barColors: zones.map((_, i) => getZoneColor(i)),
|
||||
};
|
||||
}
|
||||
@@ -3,8 +3,8 @@ import { useAuthStore } from "@/stores/authStore";
|
||||
|
||||
let refreshTokenPromise = null;
|
||||
|
||||
export const API_URL = `${import.meta.env.VITE_ENDURAIN_HOST}/api/v1/`;
|
||||
export const FRONTEND_URL = `${import.meta.env.VITE_ENDURAIN_HOST}/`;
|
||||
export const API_URL = `${window.env.ENDURAIN_HOST}/api/v1/`
|
||||
export const FRONTEND_URL = `${window.env.ENDURAIN_HOST}/`
|
||||
|
||||
async function fetchWithRetry(url, options) {
|
||||
// Add CSRF token to headers for state-changing requests
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<span v-if="activity.activity_type === 1 || activity.activity_type === 2 || activity.activity_type === 3">
|
||||
<font-awesome-icon :icon="['fas', 'person-running']" />
|
||||
</span>
|
||||
<span v-else-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27">
|
||||
<span v-else-if="activity.activity_type === 4 || activity.activity_type === 5 || activity.activity_type === 6 || activity.activity_type === 7 || activity.activity_type === 27 || activity.activity_type === 28">
|
||||
<font-awesome-icon :icon="['fas', 'fa-person-biking']" />
|
||||
</span>
|
||||
<span v-else-if="activity.activity_type === 8 || activity.activity_type === 9">
|
||||
|
||||
@@ -65,7 +65,7 @@ export default {
|
||||
const serverSettingsStore = useServerSettingsStore();
|
||||
const showPassword = ref(false);
|
||||
const loginPhotoUrl = serverSettingsStore.serverSettings.login_photo_set
|
||||
? `${import.meta.env.VITE_ENDURAIN_HOST}/server_images/login.png`
|
||||
? `${window.env.ENDURAIN_HOST}/server_images/login.png`
|
||||
: null;
|
||||
|
||||
// Toggle password visibility
|
||||
|
||||
@@ -200,7 +200,7 @@ function updateSearchResultsBasedOnActivityType() {
|
||||
);
|
||||
} else if (searchSelectActivityType.value === "2") {
|
||||
searchResults.value = searchResultsOriginal.value.filter((user) =>
|
||||
[4, 5, 6, 7, 27].includes(user.activity_type),
|
||||
[4, 5, 6, 7, 27, 28].includes(user.activity_type),
|
||||
);
|
||||
} else if (searchSelectActivityType.value === "3") {
|
||||
searchResults.value = searchResultsOriginal.value.filter((user) =>
|
||||
|
||||
@@ -6,7 +6,9 @@ theme:
|
||||
user_color_mode_toggle: true
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Getting started: getting-started.md
|
||||
- Hosting Guide:
|
||||
- Getting started easy: getting-started/getting-started.md
|
||||
- Getting started advanced: getting-started/advanced-started.md
|
||||
- Integrations:
|
||||
- 3rd party apps: integrations/3rd-party-apps.md
|
||||
- 3rd party services: integrations/3rd-party-services.md
|
||||
|
||||
Reference in New Issue
Block a user