Optimize table view and export for software lists added. excelexport fixed - first line
This commit is contained in:
+51
-10
@@ -1950,9 +1950,8 @@ def _xlsx_response(filename: str, headers: list[str], rows: list[list]) -> Respo
|
||||
workbook = Workbook()
|
||||
sheet = workbook.active
|
||||
sheet.title = "Export"
|
||||
sheet.freeze_panes = "A2"
|
||||
sheet.auto_filter.ref = f"A1:{sheet.cell(row=1, column=max(1, len(headers))).column_letter}1"
|
||||
sheet.append(headers)
|
||||
sheet.freeze_panes = "A2"
|
||||
header_fill = PatternFill("solid", fgColor="D9EAF7")
|
||||
for cell in sheet[1]:
|
||||
cell.font = Font(bold=True)
|
||||
@@ -1960,6 +1959,8 @@ def _xlsx_response(filename: str, headers: list[str], rows: list[list]) -> Respo
|
||||
cell.alignment = Alignment(vertical="top")
|
||||
for row in rows:
|
||||
sheet.append([_display_value(value) for value in row])
|
||||
last_column = sheet.cell(row=1, column=max(1, len(headers))).column_letter
|
||||
sheet.auto_filter.ref = f"A1:{last_column}{max(1, sheet.max_row)}"
|
||||
for column in sheet.columns:
|
||||
max_length = min(max((len(str(cell.value or "")) for cell in column), default=10) + 2, 60)
|
||||
sheet.column_dimensions[column[0].column_letter].width = max_length
|
||||
@@ -4582,8 +4583,17 @@ async def status_options_save(request: Request, db: Session = Depends(get_db)):
|
||||
name = (form.get(f"name_{option.id}") or "").strip()
|
||||
if not name:
|
||||
continue
|
||||
old_name = option.name
|
||||
if name != old_name:
|
||||
# Asset.status stores the status name as text. Keep existing assets
|
||||
# linked when an administrator renames a status option.
|
||||
db.query(Asset).filter(Asset.status == old_name).update(
|
||||
{Asset.status: name},
|
||||
synchronize_session=False,
|
||||
)
|
||||
option.name = name
|
||||
option.active = form.get(f"active_{option.id}") == "on"
|
||||
option.show_in_software_list = form.get(f"software_list_{option.id}") == "on"
|
||||
try:
|
||||
option.sort_order = int(form.get(f"sort_{option.id}") or option.sort_order)
|
||||
except ValueError:
|
||||
@@ -4596,7 +4606,12 @@ async def status_options_save(request: Request, db: Session = Depends(get_db)):
|
||||
new_name = (form.get("new_name") or "").strip()
|
||||
if new_name:
|
||||
max_order = max((item.sort_order for item in existing), default=-1)
|
||||
db.add(StatusOption(name=new_name, sort_order=max_order + 1, active=True))
|
||||
db.add(StatusOption(
|
||||
name=new_name,
|
||||
sort_order=max_order + 1,
|
||||
active=True,
|
||||
show_in_software_list=form.get("new_show_in_software_list") == "on",
|
||||
))
|
||||
try:
|
||||
db.commit()
|
||||
except IntegrityError as exc:
|
||||
@@ -5080,6 +5095,27 @@ def software_callback_health():
|
||||
}
|
||||
|
||||
|
||||
def _software_list_query(db: Session, *entities):
|
||||
"""Base query for global software lists and statistics.
|
||||
|
||||
Status options explicitly disabled for software lists are excluded.
|
||||
Assets without a status, or with a legacy/unmatched status value, remain
|
||||
visible so enabling this feature cannot silently hide existing inventory.
|
||||
The software inventory on an individual asset remains unchanged.
|
||||
"""
|
||||
return (
|
||||
db.query(*entities)
|
||||
.join(Asset, InstalledSoftware.asset_id == Asset.id)
|
||||
.outerjoin(StatusOption, StatusOption.name == Asset.status)
|
||||
.filter(
|
||||
or_(
|
||||
StatusOption.id.is_(None),
|
||||
StatusOption.show_in_software_list.is_(True),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _software_overview_statistics(db: Session) -> dict[str, int]:
|
||||
"""Return only the counters required by the software overview.
|
||||
|
||||
@@ -5087,7 +5123,8 @@ def _software_overview_statistics(db: Session) -> dict[str, int]:
|
||||
PostgreSQL performs the counting and grouping and returns only scalar values.
|
||||
"""
|
||||
total_entries, initialized_assets = (
|
||||
db.query(
|
||||
_software_list_query(
|
||||
db,
|
||||
func.count(InstalledSoftware.id),
|
||||
func.count(func.distinct(InstalledSoftware.asset_id)),
|
||||
)
|
||||
@@ -5098,7 +5135,8 @@ def _software_overview_statistics(db: Session) -> dict[str, int]:
|
||||
# intentionally preserves the exact grouping semantics used by the existing
|
||||
# detailed summary (Unicode casefold is stronger than SQL LOWER).
|
||||
summary_rows = (
|
||||
db.query(
|
||||
_software_list_query(
|
||||
db,
|
||||
InstalledSoftware.name,
|
||||
InstalledSoftware.publisher,
|
||||
InstalledSoftware.platform,
|
||||
@@ -5158,7 +5196,8 @@ def _software_summary_rows(db: Session, grouping: str) -> list[dict[str, Any]]:
|
||||
distinct(func.nullif(func.trim(func.coalesce(InstalledSoftware.version, "")), ""))
|
||||
)
|
||||
query_rows = (
|
||||
db.query(
|
||||
_software_list_query(
|
||||
db,
|
||||
name_key.label("name_key"),
|
||||
platform_key.label("platform_key"),
|
||||
func.min(func.coalesce(InstalledSoftware.name, "")).label("name"),
|
||||
@@ -5198,7 +5237,8 @@ def _software_summary_rows(db: Session, grouping: str) -> list[dict[str, Any]]:
|
||||
|
||||
if grouping == "version":
|
||||
query_rows = (
|
||||
db.query(
|
||||
_software_list_query(
|
||||
db,
|
||||
*common_columns,
|
||||
version_key.label("version_key"),
|
||||
func.min(func.coalesce(InstalledSoftware.version, "")).label("version"),
|
||||
@@ -5232,7 +5272,8 @@ def _software_summary_rows(db: Session, grouping: str) -> list[dict[str, Any]]:
|
||||
distinct(func.nullif(func.trim(func.coalesce(InstalledSoftware.version, "")), ""))
|
||||
)
|
||||
query_rows = (
|
||||
db.query(
|
||||
_software_list_query(
|
||||
db,
|
||||
*common_columns,
|
||||
versions_array.label("versions"),
|
||||
func.count(func.distinct(InstalledSoftware.asset_id)).label("asset_count"),
|
||||
@@ -5266,7 +5307,8 @@ def _software_summary_rows(db: Session, grouping: str) -> list[dict[str, Any]]:
|
||||
def _software_installation_rows(db: Session):
|
||||
"""Return only columns rendered by the detailed installation table."""
|
||||
return (
|
||||
db.query(
|
||||
_software_list_query(
|
||||
db,
|
||||
InstalledSoftware.id.label("id"),
|
||||
InstalledSoftware.asset_id.label("asset_id"),
|
||||
Asset.name.label("asset_name"),
|
||||
@@ -5278,7 +5320,6 @@ def _software_installation_rows(db: Session):
|
||||
InstalledSoftware.install_date.label("install_date"),
|
||||
InstalledSoftware.last_seen_at.label("last_seen_at"),
|
||||
)
|
||||
.join(Asset, InstalledSoftware.asset_id == Asset.id)
|
||||
.order_by(
|
||||
InstalledSoftware.name.asc(),
|
||||
Asset.name.asc(),
|
||||
|
||||
Reference in New Issue
Block a user