update to 0.16.0 #72

Merged
serversdown merged 32 commits from dev into main 2026-06-23 00:59:46 -04:00
2 changed files with 80 additions and 32 deletions
Showing only changes of commit 80464c6f11 - Show all commits
+40 -3
View File
@@ -470,8 +470,10 @@ async def get_projects_list(
for project in projects:
# Get project type
project_type = db.query(ProjectType).filter_by(id=project.project_type_id).first()
mods = _get_project_modules(project.id, db)
# Count locations
# Count locations (project-wide, includes removed — kept for back-compat
# with the compact list view).
location_count = db.query(func.count(MonitoringLocation.id)).filter_by(
project_id=project.id
).scalar()
@@ -484,7 +486,7 @@ async def get_projects_list(
)
).scalar()
# Count active sessions
# Count active (recording) sessions — a sound-monitoring concept
active_session_count = db.query(func.count(MonitoringSession.id)).filter(
and_(
MonitoringSession.project_id == project.id,
@@ -492,11 +494,46 @@ async def get_projects_list(
)
).scalar()
# Per-module stats — each module shows only its own, relevant counts.
# Locations: active only (removed_at IS NULL). Units: active assignments
# counted by the TYPE OF LOCATION they sit on (join), so a module's unit
# count always lines up with its own location count — independent of the
# assignment's denormalized device_type.
def _module_loc_count(loc_type):
return db.query(func.count(MonitoringLocation.id)).filter(
MonitoringLocation.project_id == project.id,
MonitoringLocation.location_type == loc_type,
MonitoringLocation.removed_at.is_(None),
).scalar()
def _module_unit_count(loc_type):
return db.query(func.count(UnitAssignment.id)).join(
MonitoringLocation, MonitoringLocation.id == UnitAssignment.location_id
).filter(
UnitAssignment.project_id == project.id,
UnitAssignment.assigned_until.is_(None),
MonitoringLocation.location_type == loc_type,
).scalar()
module_stats = {}
if "vibration_monitoring" in mods:
module_stats["vibration"] = {
"locations": _module_loc_count("vibration"),
"units": _module_unit_count("vibration"),
}
if "sound_monitoring" in mods:
module_stats["sound"] = {
"locations": _module_loc_count("sound"),
"units": _module_unit_count("sound"),
"recording": active_session_count,
}
projects_data.append({
"project": project,
"project_type": project_type,
"modules": _get_project_modules(project.id, db),
"modules": mods,
"module_status": _get_module_statuses(project.id, db),
"module_stats": module_stats,
"location_count": location_count,
"unit_count": unit_count,
"active_session_count": active_session_count,
+40 -29
View File
@@ -39,36 +39,50 @@
{% endif %}
</div>
<!-- Module chips (with per-module status) -->
{% if mods %}
<div class="flex flex-wrap items-center gap-1.5 mb-3">
{% for m in mods %}
{% set st = mstatus.get(m, 'active') %}
<span class="inline-flex items-center gap-1 pl-2 pr-2 py-0.5 rounded-full text-[11px] font-medium
{% if m == 'sound_monitoring' %}bg-orange-100 text-orange-800 dark:bg-orange-900/40 dark:text-orange-300
{% elif m == 'vibration_monitoring' %}bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300
{% else %}bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300{% endif %}">
{% if m == 'sound_monitoring' %}
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072M12 6v12M9 8.464a5 5 0 000 7.072"/></svg>
Sound
{% elif m == 'vibration_monitoring' %}
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
Vibration
{% else %}{{ m }}{% endif %}
{% if st == 'completed' %}<span class="text-blue-600 dark:text-blue-300" title="Completed"></span>
{% elif st == 'on_hold' %}<span class="opacity-70" title="On hold"></span>{% endif %}
</span>
{% endfor %}
</div>
{% endif %}
<!-- Description -->
{% if p.description %}
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4 line-clamp-2">{{ p.description }}</p>
{% endif %}
<!-- Stats -->
<div class="grid grid-cols-3 gap-3 pt-4 border-t border-gray-100 dark:border-gray-700/60">
<!-- Per-module stats — each module shows only its own, relevant counts.
These lines double as the module legend (identity + status), so a
separate chip row would be redundant. -->
{% set ms = item.module_stats %}
{% if ms %}
<div class="space-y-2.5 pt-4 border-t border-gray-100 dark:border-gray-700/60">
{% if 'vibration' in ms %}
{% set vst = mstatus.get('vibration_monitoring', 'active') %}
<div class="flex items-baseline gap-2.5 text-sm flex-wrap">
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-medium bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
Vibration
{% if vst == 'completed' %}<span title="Completed"></span>{% elif vst == 'on_hold' %}<span class="opacity-80" title="On hold"></span>{% endif %}
</span>
<span class="text-gray-500 dark:text-gray-400">
<b class="font-semibold text-gray-900 dark:text-white tabular-nums">{{ ms.vibration.locations }}</b> location{{ '' if ms.vibration.locations == 1 else 's' }}
· <b class="font-semibold text-gray-900 dark:text-white tabular-nums">{{ ms.vibration.units }}</b> unit{{ '' if ms.vibration.units == 1 else 's' }}
</span>
</div>
{% endif %}
{% if 'sound' in ms %}
{% set sst = mstatus.get('sound_monitoring', 'active') %}
<div class="flex items-baseline gap-2.5 text-sm flex-wrap">
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-medium bg-orange-100 text-orange-800 dark:bg-orange-900/40 dark:text-orange-300">
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072M12 6v12M9 8.464a5 5 0 000 7.072"/></svg>
Sound
{% if sst == 'completed' %}<span title="Completed"></span>{% elif sst == 'on_hold' %}<span class="opacity-80" title="On hold"></span>{% endif %}
</span>
<span class="text-gray-500 dark:text-gray-400">
<b class="font-semibold text-gray-900 dark:text-white tabular-nums">{{ ms.sound.locations }}</b> NRL{{ '' if ms.sound.locations == 1 else 's' }}
· <b class="font-semibold text-gray-900 dark:text-white tabular-nums">{{ ms.sound.units }}</b> unit{{ '' if ms.sound.units == 1 else 's' }}
· {% if ms.sound.recording > 0 %}<b class="font-semibold text-green-600 dark:text-green-400 tabular-nums">{{ ms.sound.recording }}</b> recording{% else %}<span class="text-gray-400 dark:text-gray-500">0 recording</span>{% endif %}
</span>
</div>
{% endif %}
</div>
{% else %}
<!-- Fallback: project with no modules enabled yet -->
<div class="grid grid-cols-2 gap-3 pt-4 border-t border-gray-100 dark:border-gray-700/60">
<div>
<p class="text-[11px] uppercase tracking-wide text-gray-400 dark:text-gray-500">Locations</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ item.location_count }}</p>
@@ -77,11 +91,8 @@
<p class="text-[11px] uppercase tracking-wide text-gray-400 dark:text-gray-500">Units</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white">{{ item.unit_count }}</p>
</div>
<div>
<p class="text-[11px] uppercase tracking-wide text-gray-400 dark:text-gray-500">Active</p>
<p class="text-lg font-semibold {% if item.active_session_count > 0 %}text-green-600 dark:text-green-400{% else %}text-gray-900 dark:text-white{% endif %}">{{ item.active_session_count }}</p>
</div>
</div>
{% endif %}
</a>
<!-- Per-module quick-open footer — jumps straight into that module's tab -->