Add timezone localization support to endpoints plugin
Endpoints plugin now respects the site_timezone setting from /settings. All timestamps in endpoint details, script runs, and log collections are now converted from UTC to the configured site timezone for display. Changes: - Added _get_site_timezone() helper to read site_timezone setting - Added _localize_timestamp() to convert UTC datetimes to site timezone - Applied localization to GET /api/endpoints/<id> (last_seen, created_at) - Applied localization to GET /api/endpoints/<id>/scriptruns (execution times) - Applied localization to GET /api/endpoints/<id>/logcollections (collection times) This aligns endpoints plugin with notifications and backups plugins which already use site_timezone for their time-sensitive operations.
This commit is contained in:
@@ -43,6 +43,34 @@ def _naive_utcnow():
|
|||||||
return datetime.now(timezone.utc).replace(tzinfo=None)
|
return datetime.now(timezone.utc).replace(tzinfo=None)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_site_timezone():
|
||||||
|
"""Get configured site timezone from settings, fallback to UTC."""
|
||||||
|
try:
|
||||||
|
import pytz
|
||||||
|
setting = Setting.query.filter_by(key='site_timezone').first()
|
||||||
|
if setting and setting.value:
|
||||||
|
return pytz.timezone(setting.value)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
return timezone.utc
|
||||||
|
|
||||||
|
|
||||||
|
def _localize_timestamp(utc_datetime):
|
||||||
|
"""Convert naive UTC datetime to site timezone ISO string for display."""
|
||||||
|
if not utc_datetime:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
import pytz
|
||||||
|
site_tz = _get_site_timezone()
|
||||||
|
# Assume stored datetime is UTC (naive)
|
||||||
|
utc_aware = pytz.UTC.localize(utc_datetime)
|
||||||
|
localized = utc_aware.astimezone(site_tz)
|
||||||
|
return localized.isoformat()
|
||||||
|
except Exception:
|
||||||
|
# Fallback: return as UTC if anything fails
|
||||||
|
return utc_datetime.isoformat() + 'Z' if utc_datetime else None
|
||||||
|
|
||||||
|
|
||||||
# What counts as RUNNING is defined once, by the reconciler, and imported here.
|
# What counts as RUNNING is defined once, by the reconciler, and imported here.
|
||||||
# The panel and the reconciler read the same agent snapshot, so a disagreement
|
# The panel and the reconciler read the same agent snapshot, so a disagreement
|
||||||
# would paint a service green and flag it as drift on the same page. Importing
|
# would paint a service green and flag it as drift on the same page. Importing
|
||||||
@@ -440,6 +468,12 @@ def get_endpoint(asset_id: int):
|
|||||||
# the enqueue guard refuses against these same numbers, so a copy in the
|
# the enqueue guard refuses against these same numbers, so a copy in the
|
||||||
# frontend would silently disagree the moment a floor was raised here.
|
# frontend would silently disagree the moment a floor was raised here.
|
||||||
data['actionminimums'] = action_minimum_versions()
|
data['actionminimums'] = action_minimum_versions()
|
||||||
|
|
||||||
|
# Localize timestamps to site timezone
|
||||||
|
for ts_field in ['lastreportedat', 'lasteventat', 'created_at', 'updated_at']:
|
||||||
|
if ts_field in data and data[ts_field]:
|
||||||
|
data[ts_field] = _localize_timestamp(state.__dict__.get(ts_field))
|
||||||
|
|
||||||
return success_response(data)
|
return success_response(data)
|
||||||
|
|
||||||
|
|
||||||
@@ -2380,6 +2414,12 @@ def list_endpoint_script_runs(asset_id: int):
|
|||||||
for row in rows:
|
for row in rows:
|
||||||
data = row.to_dict(includedetail=True)
|
data = row.to_dict(includedetail=True)
|
||||||
data['scriptname'] = scriptnames.get(row.endpointcommandid)
|
data['scriptname'] = scriptnames.get(row.endpointcommandid)
|
||||||
|
|
||||||
|
# Localize timestamps to site timezone
|
||||||
|
for ts_field in ['createdat', 'claimedat', 'completedat']:
|
||||||
|
if ts_field in data and data[ts_field]:
|
||||||
|
data[ts_field] = _localize_timestamp(row.__dict__.get(ts_field))
|
||||||
|
|
||||||
payload.append(data)
|
payload.append(data)
|
||||||
return success_response(payload)
|
return success_response(payload)
|
||||||
|
|
||||||
@@ -2404,7 +2444,16 @@ def list_endpoint_log_collections(asset_id: int):
|
|||||||
EndpointLogCollection.endpointlogcollectionid.desc())
|
EndpointLogCollection.endpointlogcollectionid.desc())
|
||||||
.limit(EndpointLogCollection.RETENTION_PER_ASSET)
|
.limit(EndpointLogCollection.RETENTION_PER_ASSET)
|
||||||
.all())
|
.all())
|
||||||
return success_response([row.to_dict() for row in rows])
|
|
||||||
|
# Localize timestamps to site timezone
|
||||||
|
payload = []
|
||||||
|
for row in rows:
|
||||||
|
data = row.to_dict()
|
||||||
|
if 'collectedat' in data and data['collectedat']:
|
||||||
|
data['collectedat'] = _localize_timestamp(row.collectedat)
|
||||||
|
payload.append(data)
|
||||||
|
|
||||||
|
return success_response(payload)
|
||||||
|
|
||||||
|
|
||||||
@endpoints_bp.route('/logcollections/<int:collection_id>', methods=['GET'])
|
@endpoints_bp.route('/logcollections/<int:collection_id>', methods=['GET'])
|
||||||
|
|||||||
Reference in New Issue
Block a user