Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions application/single_app/background_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
calculate_next_control_center_auto_refresh_run,
execute_control_center_refresh,
get_control_center_auto_refresh_schedule,
is_control_center_auto_refresh_due,
parse_control_center_auto_refresh_datetime,
)
from functions_cosmos_throughput import (
Expand Down Expand Up @@ -308,13 +309,14 @@
'control_center_auto_refresh_time': schedule['time'],
'control_center_auto_refresh_hour': schedule['hour'],
'control_center_auto_refresh_minute': schedule['minute'],
'control_center_auto_refresh_timezone': schedule['timezone'],
'control_center_auto_refresh_next_run': next_run.isoformat(),
})
return next_run


def check_control_center_auto_refresh_once():
"""Run the scheduled Control Center refresh when its daily UTC schedule is due."""
"""Run the scheduled Control Center refresh when its UTC timestamp is due."""
settings = get_settings()
if not settings.get('control_center_auto_refresh_enabled', True):
return None
Expand All @@ -325,12 +327,15 @@
_seed_control_center_auto_refresh_next_run(settings, current_time)
return None

if current_time < next_run:
if not is_control_center_auto_refresh_due(settings, current_time=current_time):
return None

lock_document = acquire_distributed_task_lock('control_center_auto_refresh', lease_seconds=7200)
if not lock_document:
debug_print('Skipping Control Center auto-refresh because another worker holds the lease.')
log_event(

Check warning on line 335 in application/single_app/background_tasks.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
'[ControlCenterAutoRefresh] Skipped scheduled refresh because another worker holds the lease.',

Check warning on line 336 in application/single_app/background_tasks.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
debug_only=True,
)
return None

try:
Expand All @@ -343,11 +348,32 @@
if not next_run:
_seed_control_center_auto_refresh_next_run(settings, current_time)
return None
if current_time < next_run:
if not is_control_center_auto_refresh_due(settings, current_time=current_time):
return None

print(f"Executing scheduled Control Center auto-refresh at {current_time.isoformat()}")
return execute_control_center_refresh(manual_execution=False)
schedule = get_control_center_auto_refresh_schedule(settings)
log_event(

Check warning on line 355 in application/single_app/background_tasks.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
'[ControlCenterAutoRefresh] Starting scheduled Control Center metrics refresh.',
extra={
'scheduled_run_utc': next_run.isoformat(),
'schedule_time': schedule['time'],
'schedule_timezone': schedule['timezone'],
},
level=logging.INFO,
)
result = execute_control_center_refresh(manual_execution=False)
log_event(

Check warning on line 365 in application/single_app/background_tasks.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
'[ControlCenterAutoRefresh] Scheduled Control Center metrics refresh completed.',
extra={
'success': bool(result and result.get('success')),
'refreshed_users': result.get('refreshed_users', 0) if result else 0,
'failed_users': result.get('failed_users', 0) if result else 0,
'refreshed_groups': result.get('refreshed_groups', 0) if result else 0,
'failed_groups': result.get('failed_groups', 0) if result else 0,
},
level=logging.INFO,
)
return result
finally:
release_distributed_task_lock(lock_document)

Expand Down Expand Up @@ -394,8 +420,12 @@
try:
check_control_center_auto_refresh_once()
except Exception as exc:
print(f"Error in Control Center auto-refresh check: {exc}")
log_event(f"Error in Control Center auto-refresh check: {exc}", level=logging.ERROR)
log_event(

Check warning on line 423 in application/single_app/background_tasks.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
'[ControlCenterAutoRefresh] Error checking the scheduled Control Center refresh.',
extra={'error': str(exc)},
level=logging.ERROR,
exceptionTraceback=True,

Check warning on line 427 in application/single_app/background_tasks.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
)

time.sleep(300)

Expand Down
66 changes: 55 additions & 11 deletions application/single_app/functions_control_center.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
# functions_control_center.py
"""
Functions for Control Center operations including scheduled auto-refresh.
Version: 0.241.029
Version: 0.250.102
"""

from datetime import datetime, timezone, timedelta
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

from config import cosmos_user_settings_container, cosmos_groups_container
from functions_debug import debug_print
from functions_settings import get_settings, update_settings
from functions_appinsights import log_event


CONTROL_CENTER_DEFAULT_AUTO_REFRESH_HOUR = 6
CONTROL_CENTER_DEFAULT_AUTO_REFRESH_HOUR = 2
CONTROL_CENTER_DEFAULT_AUTO_REFRESH_MINUTE = 0
CONTROL_CENTER_DEFAULT_AUTO_REFRESH_TIME = '06:00'
CONTROL_CENTER_DEFAULT_AUTO_REFRESH_TIME = '02:00'
CONTROL_CENTER_DEFAULT_AUTO_REFRESH_TIMEZONE = 'America/New_York'


def normalize_control_center_auto_refresh_time(schedule_time=None, schedule_hour=None, schedule_minute=None):
"""Return a normalized UTC daily refresh schedule."""
def normalize_control_center_auto_refresh_time(
schedule_time=None,
schedule_hour=None,
schedule_minute=None,
schedule_timezone=None,
):
"""Return a normalized daily refresh rule with an IANA timezone."""
normalized_hour = CONTROL_CENTER_DEFAULT_AUTO_REFRESH_HOUR
normalized_minute = CONTROL_CENTER_DEFAULT_AUTO_REFRESH_MINUTE

Expand Down Expand Up @@ -47,10 +55,21 @@
except (TypeError, ValueError):
pass

normalized_timezone = (
schedule_timezone.strip()
if isinstance(schedule_timezone, str) and schedule_timezone.strip()
else CONTROL_CENTER_DEFAULT_AUTO_REFRESH_TIMEZONE
)
try:

Check warning on line 63 in application/single_app/functions_control_center.py

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Changed line contains security control, sanitization, or audit marker. Recommendation%3A Confirm the change does not weaken auth, CSRF, CSP, XSS defenses, settings sanitization, redaction, audit logging, or tests.
ZoneInfo(normalized_timezone)
except (ZoneInfoNotFoundError, ValueError):
normalized_timezone = CONTROL_CENTER_DEFAULT_AUTO_REFRESH_TIMEZONE

return {
'hour': normalized_hour,
'minute': normalized_minute,
'time': f"{normalized_hour:02d}:{normalized_minute:02d}",
'timezone': normalized_timezone,
}


Expand All @@ -61,28 +80,31 @@
settings.get('control_center_auto_refresh_time'),
settings.get('control_center_auto_refresh_hour'),
settings.get('control_center_auto_refresh_minute'),
settings.get('control_center_auto_refresh_timezone'),
)


def calculate_next_control_center_auto_refresh_run(settings=None, current_time=None):
"""Calculate the next UTC daily Control Center auto-refresh run time."""
"""Calculate the next daily Control Center refresh as a UTC datetime."""
current_time = current_time or datetime.now(timezone.utc)
if current_time.tzinfo is None:
current_time = current_time.replace(tzinfo=timezone.utc)
else:
current_time = current_time.astimezone(timezone.utc)

schedule = get_control_center_auto_refresh_schedule(settings)
next_run = current_time.replace(
schedule_timezone = ZoneInfo(schedule['timezone'])
local_current_time = current_time.astimezone(schedule_timezone)
next_run_local = local_current_time.replace(
hour=schedule['hour'],
minute=schedule['minute'],
second=0,
microsecond=0,
)
if next_run <= current_time:
next_run += timedelta(days=1)
if next_run_local <= local_current_time:
next_run_local += timedelta(days=1)

return next_run
return next_run_local.astimezone(timezone.utc)


def parse_control_center_auto_refresh_datetime(timestamp_value):
Expand All @@ -103,6 +125,27 @@
return None


def is_control_center_auto_refresh_due(settings=None, current_time=None):
"""Return whether an enabled schedule has reached its saved UTC next run."""
settings = settings or {}
if not settings.get('control_center_auto_refresh_enabled', True):
return False

next_run = parse_control_center_auto_refresh_datetime(
settings.get('control_center_auto_refresh_next_run')
)
if not next_run:
return False

current_time = current_time or datetime.now(timezone.utc)
if current_time.tzinfo is None:
current_time = current_time.replace(tzinfo=timezone.utc)
else:
current_time = current_time.astimezone(timezone.utc)

return current_time >= next_run


def execute_control_center_refresh(manual_execution=False):
"""
Execute Control Center data refresh operation.
Expand Down Expand Up @@ -196,6 +239,7 @@
settings['control_center_auto_refresh_time'] = schedule['time']
settings['control_center_auto_refresh_hour'] = schedule['hour']
settings['control_center_auto_refresh_minute'] = schedule['minute']
settings['control_center_auto_refresh_timezone'] = schedule['timezone']

# Calculate next scheduled auto-refresh time if enabled
if settings.get('control_center_auto_refresh_enabled', True):
Expand Down
30 changes: 28 additions & 2 deletions application/single_app/functions_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -837,9 +837,10 @@ def get_settings(use_cosmos=False, include_source=False):
# Control Center settings
'control_center_last_refresh': None, # Timestamp of last data refresh
'control_center_auto_refresh_enabled': True,
'control_center_auto_refresh_time': '06:00',
'control_center_auto_refresh_hour': 6,
'control_center_auto_refresh_time': '02:00',
'control_center_auto_refresh_hour': 2,
'control_center_auto_refresh_minute': 0,
'control_center_auto_refresh_timezone': 'America/New_York',
'control_center_auto_refresh_next_run': None,
# -- Your entire default dictionary here --
'app_title': 'Simple Chat',
Expand Down Expand Up @@ -1376,9 +1377,33 @@ def _format_result(settings_payload, source):
level=logging.WARNING
)

legacy_control_center_schedule = (
'control_center_auto_refresh_timezone' not in settings_item
)
legacy_control_center_time = settings_item.get('control_center_auto_refresh_time')
if not isinstance(legacy_control_center_time, str):
legacy_hour = settings_item.get('control_center_auto_refresh_hour', 6)
legacy_minute = settings_item.get('control_center_auto_refresh_minute', 0)
if not isinstance(legacy_hour, int):
legacy_hour = 6
if not isinstance(legacy_minute, int):
legacy_minute = 0
legacy_control_center_time = f"{legacy_hour:02d}:{legacy_minute:02d}"

# Merge default_settings in, to fill in any missing or nested keys
merge_changed = deep_merge_dicts(default_settings, settings_item)
merged = settings_item
control_center_schedule_migration_updated = False
if legacy_control_center_schedule:
if legacy_control_center_time == '06:00':
merged['control_center_auto_refresh_time'] = '02:00'
merged['control_center_auto_refresh_hour'] = 2
merged['control_center_auto_refresh_minute'] = 0
merged['control_center_auto_refresh_timezone'] = 'America/New_York'
else:
merged['control_center_auto_refresh_timezone'] = 'UTC'
merged['control_center_auto_refresh_next_run'] = None
control_center_schedule_migration_updated = True
migration_updated = apply_custom_endpoint_setting_migration(merged)
assignment_settings_updated = normalize_group_workflow_assignment_settings(merged)
promoted_popular_settings_updated = normalize_agents_page_promoted_popular_settings(merged)
Expand All @@ -1390,6 +1415,7 @@ def _format_result(settings_payload, source):
# If merging added anything new, upsert back to Cosmos so future reads remain up to date
if (
merge_changed
or control_center_schedule_migration_updated
or migration_updated
or assignment_settings_updated
or promoted_popular_settings_updated
Expand Down
3 changes: 2 additions & 1 deletion application/single_app/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
python-dotenv==1.2.2
azure-ai-formrecognizer==3.3.3
azure-ai-projects==1.0.0
azure-ai-agents==1.2.0b6

Check warning on line 28 in application/single_app/requirements.txt

View workflow job for this annotation

GitHub Actions / malicious-pr-security-review

Important - Python requirement appears to use a pre-release version. Recommendation%3A Confirm the pre-release package was intentionally selected and reviewed.
pyjwt==2.13.0
markdown2==2.5.5
azure-mgmt-cognitiveservices==13.6.0
Expand Down Expand Up @@ -63,4 +63,5 @@
html2text==2025.4.15
matplotlib==3.10.7
azure-cognitiveservices-speech==1.48.2
playwright==1.58.0
playwright==1.58.0
tzdata==2026.3
31 changes: 27 additions & 4 deletions application/single_app/route_backend_control_center.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@
from config import *
from functions_authentication import *
from functions_chat_bootstrap_cache import bump_chat_bootstrap_global_cache_version
from functions_control_center import (
calculate_next_control_center_auto_refresh_run,
get_control_center_auto_refresh_schedule,
parse_control_center_auto_refresh_datetime,
)
Comment thread
paullizer marked this conversation as resolved.
Dismissed
from functions_settings import *
from functions_logging import *
from functions_activity_logging import *
Expand Down Expand Up @@ -5873,14 +5878,32 @@
Get the last refresh timestamp for Control Center data.
"""
try:
from functions_settings import get_settings

settings = get_settings()
last_refresh = settings.get('control_center_last_refresh')

last_refresh_datetime = parse_control_center_auto_refresh_datetime(last_refresh)
auto_refresh_enabled = settings.get('control_center_auto_refresh_enabled', True)
auto_refresh_schedule = get_control_center_auto_refresh_schedule(settings)
auto_refresh_next_run = parse_control_center_auto_refresh_datetime(
settings.get('control_center_auto_refresh_next_run')
)
if auto_refresh_enabled and not auto_refresh_next_run:
auto_refresh_next_run = calculate_next_control_center_auto_refresh_run(settings)

return jsonify({
'last_refresh': last_refresh,
'last_refresh_formatted': None if not last_refresh else datetime.fromisoformat(last_refresh.replace('Z', '+00:00') if 'Z' in last_refresh else last_refresh).strftime('%m/%d/%Y %I:%M %p UTC')
'last_refresh_formatted': (
last_refresh_datetime.strftime('%m/%d/%Y %I:%M %p UTC')
if last_refresh_datetime
else None
),
'auto_refresh_enabled': auto_refresh_enabled,
'auto_refresh_time': auto_refresh_schedule['time'],
'auto_refresh_timezone': auto_refresh_schedule['timezone'],
'auto_refresh_next_run_utc': (
auto_refresh_next_run.isoformat()
if auto_refresh_next_run
else None
),
}), 200

except Exception as e:
Expand Down
22 changes: 19 additions & 3 deletions application/single_app/route_frontend_admin_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,14 @@ def admin_settings():
settings['control_center_auto_refresh_time'] = control_center_auto_refresh_schedule['time']
settings['control_center_auto_refresh_hour'] = control_center_auto_refresh_schedule['hour']
settings['control_center_auto_refresh_minute'] = control_center_auto_refresh_schedule['minute']
settings['control_center_auto_refresh_timezone'] = control_center_auto_refresh_schedule['timezone']
if (
settings['control_center_auto_refresh_enabled']
and not settings.get('control_center_auto_refresh_next_run')
):
settings['control_center_auto_refresh_next_run'] = (
calculate_next_control_center_auto_refresh_run(settings).isoformat()
)
settings.update(normalize_cosmos_throughput_settings(settings))
cosmos_resource_config = get_cosmos_resource_config(settings)
settings['cosmos_throughput_resolved_subscription_id'] = cosmos_resource_config.get('subscription_id', '')
Expand Down Expand Up @@ -1146,19 +1154,25 @@ def parse_admin_int(raw_value, fallback_value, field_name="unknown", hard_defaul
control_center_auto_refresh_enabled = form_data.get('control_center_auto_refresh_enabled') == 'on'
incoming_control_center_auto_refresh_time = form_data.get(
'control_center_auto_refresh_time',
settings.get('control_center_auto_refresh_time', '06:00')
settings.get('control_center_auto_refresh_time', '02:00')
)
incoming_control_center_auto_refresh_timezone = form_data.get(
'control_center_auto_refresh_timezone',
settings.get('control_center_auto_refresh_timezone', 'America/New_York'),
)
control_center_auto_refresh_schedule = get_control_center_auto_refresh_schedule({
'control_center_auto_refresh_time': incoming_control_center_auto_refresh_time,
'control_center_auto_refresh_hour': settings.get('control_center_auto_refresh_hour', 6),
'control_center_auto_refresh_hour': settings.get('control_center_auto_refresh_hour', 2),
'control_center_auto_refresh_minute': settings.get('control_center_auto_refresh_minute', 0),
'control_center_auto_refresh_timezone': incoming_control_center_auto_refresh_timezone,
})
existing_control_center_auto_refresh_schedule = get_control_center_auto_refresh_schedule(settings)
existing_control_center_auto_refresh_enabled = settings.get('control_center_auto_refresh_enabled', True)
existing_control_center_auto_refresh_next_run = settings.get('control_center_auto_refresh_next_run')
control_center_auto_refresh_schedule_changed = (
control_center_auto_refresh_enabled != existing_control_center_auto_refresh_enabled or
control_center_auto_refresh_schedule['time'] != existing_control_center_auto_refresh_schedule['time']
control_center_auto_refresh_schedule['time'] != existing_control_center_auto_refresh_schedule['time'] or
control_center_auto_refresh_schedule['timezone'] != existing_control_center_auto_refresh_schedule['timezone']
)
if control_center_auto_refresh_enabled:
if control_center_auto_refresh_schedule_changed or not existing_control_center_auto_refresh_next_run:
Expand All @@ -1167,6 +1181,7 @@ def parse_admin_int(raw_value, fallback_value, field_name="unknown", hard_defaul
'control_center_auto_refresh_time': control_center_auto_refresh_schedule['time'],
'control_center_auto_refresh_hour': control_center_auto_refresh_schedule['hour'],
'control_center_auto_refresh_minute': control_center_auto_refresh_schedule['minute'],
'control_center_auto_refresh_timezone': control_center_auto_refresh_schedule['timezone'],
},
current_time=datetime.now(timezone.utc),
).isoformat()
Expand Down Expand Up @@ -2706,6 +2721,7 @@ def is_valid_url(url):
'control_center_auto_refresh_time': control_center_auto_refresh_schedule['time'],
'control_center_auto_refresh_hour': control_center_auto_refresh_schedule['hour'],
'control_center_auto_refresh_minute': control_center_auto_refresh_schedule['minute'],
'control_center_auto_refresh_timezone': control_center_auto_refresh_schedule['timezone'],
'control_center_auto_refresh_next_run': control_center_auto_refresh_next_run,
}

Expand Down
Loading
Loading