multi value filtering added and font color fix for duplicates
This commit is contained in:
Regular → Executable
+344
-116
@@ -1840,21 +1840,41 @@ def save_upload(upload: UploadFile | None) -> str | None:
|
||||
return f"/static/uploads/{filename}"
|
||||
|
||||
|
||||
def _standard_image_url(filename: str) -> str:
|
||||
return "/static/uploads/library/" + urllib.parse.quote(filename)
|
||||
def _standard_image_url(relative_path: str | Path) -> str:
|
||||
"""Build a public URL for a file inside the persistent image library."""
|
||||
relative = Path(relative_path)
|
||||
encoded = "/".join(urllib.parse.quote(part, safe="") for part in relative.parts)
|
||||
return "/static/uploads/library/" + encoded
|
||||
|
||||
|
||||
def list_standard_images() -> list[dict[str, str]]:
|
||||
"""Return reusable images stored in the persistent standard image library."""
|
||||
"""Read reusable images directly from the persistent filesystem library.
|
||||
|
||||
The directory is scanned on every form request so files copied to the Docker
|
||||
host become available after a browser reload. Subdirectories are supported
|
||||
as well; symlinks are ignored so the library cannot escape its configured
|
||||
root directory.
|
||||
"""
|
||||
images: list[dict[str, str]] = []
|
||||
try:
|
||||
candidates = sorted(STANDARD_IMAGE_DIR.iterdir(), key=lambda item: item.name.casefold())
|
||||
candidates = sorted(
|
||||
STANDARD_IMAGE_DIR.rglob("*"),
|
||||
key=lambda item: item.relative_to(STANDARD_IMAGE_DIR).as_posix().casefold(),
|
||||
)
|
||||
except OSError:
|
||||
return images
|
||||
|
||||
for path in candidates:
|
||||
if not path.is_file() or path.suffix.lower() not in STANDARD_IMAGE_EXTENSIONS:
|
||||
try:
|
||||
if path.is_symlink() or not path.is_file() or path.suffix.lower() not in STANDARD_IMAGE_EXTENSIONS:
|
||||
continue
|
||||
relative = path.relative_to(STANDARD_IMAGE_DIR)
|
||||
except (OSError, ValueError):
|
||||
continue
|
||||
images.append({"name": path.name, "path": _standard_image_url(path.name)})
|
||||
images.append({
|
||||
"name": relative.as_posix(),
|
||||
"path": _standard_image_url(relative),
|
||||
})
|
||||
return images
|
||||
|
||||
|
||||
@@ -2313,7 +2333,11 @@ def _seed_default_charts(db: Session) -> None:
|
||||
|
||||
@app.get("/")
|
||||
def dashboard(request: Request, db: Session = Depends(get_db)):
|
||||
categories = db.query(Category).options(joinedload(Category.visible_fields)).all()
|
||||
categories = _sort_localized(
|
||||
db.query(Category).options(joinedload(Category.visible_fields)).all(),
|
||||
"name",
|
||||
request,
|
||||
)
|
||||
category_asset_counts = dict(
|
||||
_apply_asset_access(
|
||||
db.query(Asset.category_id, func.count(Asset.id)),
|
||||
@@ -2639,13 +2663,161 @@ def _filter_assets_by_job_states(
|
||||
return filtered_assets, views
|
||||
|
||||
|
||||
def _software_compare_field_options(db: Session, request: Request) -> list[dict[str, str]]:
|
||||
"""Return scalar, filterable asset fields that can identify a comparison asset."""
|
||||
definitions = (
|
||||
db.query(FieldDefinition)
|
||||
.filter(
|
||||
FieldDefinition.is_active.is_(True),
|
||||
FieldDefinition.filterable.is_(True),
|
||||
)
|
||||
.order_by(FieldDefinition.sort_order, func.lower(FieldDefinition.label), FieldDefinition.id)
|
||||
.all()
|
||||
)
|
||||
language_code = _request_language(request)
|
||||
own = translation_dictionary(db, language_code)
|
||||
english = translation_dictionary(db, "en") if language_code != "en" else own
|
||||
options: list[dict[str, str]] = []
|
||||
for definition in definitions:
|
||||
if definition.data_type in {"long_text", "json"}:
|
||||
continue
|
||||
if definition.field_name == "parent_asset_id":
|
||||
continue
|
||||
key = definition.translation_key or f"field.{definition.field_name}"
|
||||
label = own.get(key) or english.get(key) or definition.label or definition.field_name
|
||||
options.append({"field_name": definition.field_name, "label": label})
|
||||
|
||||
options.sort(
|
||||
key=lambda item: (
|
||||
0 if item["field_name"] == "name" else 1,
|
||||
_localized_text_key(item["label"], language_code),
|
||||
item["field_name"],
|
||||
)
|
||||
)
|
||||
return options
|
||||
|
||||
|
||||
def _software_compare_custom_value(row: AssetFieldValue, definition: FieldDefinition) -> Any:
|
||||
if definition.data_type == "boolean":
|
||||
return row.value_boolean
|
||||
if definition.data_type == "datetime":
|
||||
return row.value_datetime
|
||||
if definition.data_type == "date":
|
||||
return row.value_date
|
||||
if definition.data_type in {"integer", "decimal"}:
|
||||
return row.value_number
|
||||
if definition.data_type == "json":
|
||||
return row.value_json
|
||||
return row.value_text
|
||||
|
||||
|
||||
def _software_compare_display_value(request: Request, value: Any, definition: FieldDefinition) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
if definition.data_type == "boolean":
|
||||
return _translate_request(request, "common.yes", "Yes") if bool(value) else _translate_request(request, "common.no", "No")
|
||||
if isinstance(value, datetime):
|
||||
return value.isoformat(timespec="seconds")
|
||||
if isinstance(value, date):
|
||||
return value.isoformat()
|
||||
if isinstance(value, (dict, list)):
|
||||
return json.dumps(value, ensure_ascii=False, sort_keys=True)
|
||||
return str(value).strip()
|
||||
|
||||
|
||||
@app.get("/api/assets/software-compare/options")
|
||||
def assets_software_compare_options(
|
||||
request: Request,
|
||||
field_name: str = "name",
|
||||
exclude_asset_id: int | None = None,
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""Return accessible second-asset choices for the requested selection field."""
|
||||
_require_admin(request)
|
||||
definition = (
|
||||
db.query(FieldDefinition)
|
||||
.filter(
|
||||
FieldDefinition.field_name == field_name,
|
||||
FieldDefinition.is_active.is_(True),
|
||||
FieldDefinition.filterable.is_(True),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
if not definition or definition.data_type in {"long_text", "json"} or definition.field_name == "parent_asset_id":
|
||||
raise HTTPException(400, "Unsupported comparison selection field")
|
||||
|
||||
query = _apply_asset_access(db.query(Asset), request)
|
||||
if exclude_asset_id is not None:
|
||||
query = query.filter(Asset.id != exclude_asset_id)
|
||||
assets = query.order_by(func.lower(Asset.name), Asset.id).all()
|
||||
if not assets:
|
||||
return {"field_name": field_name, "options": []}
|
||||
|
||||
values_by_asset: dict[int, Any] = {}
|
||||
if definition.is_system and hasattr(Asset, definition.field_name):
|
||||
for asset in assets:
|
||||
values_by_asset[asset.id] = getattr(asset, definition.field_name, None)
|
||||
else:
|
||||
rows = (
|
||||
db.query(AssetFieldValue)
|
||||
.filter(
|
||||
AssetFieldValue.field_definition_id == definition.id,
|
||||
AssetFieldValue.asset_id.in_([asset.id for asset in assets]),
|
||||
)
|
||||
.all()
|
||||
)
|
||||
for row in rows:
|
||||
values_by_asset[row.asset_id] = _software_compare_custom_value(row, definition)
|
||||
|
||||
candidates: list[dict[str, Any]] = []
|
||||
counts: dict[str, int] = {}
|
||||
for asset in assets:
|
||||
display_value = _software_compare_display_value(
|
||||
request,
|
||||
values_by_asset.get(asset.id),
|
||||
definition,
|
||||
)
|
||||
if not display_value:
|
||||
continue
|
||||
normalized = _normalize_filter_text(display_value)
|
||||
counts[normalized] = counts.get(normalized, 0) + 1
|
||||
candidates.append({
|
||||
"asset_id": asset.id,
|
||||
"asset_name": asset.name,
|
||||
"value": display_value,
|
||||
"normalized": normalized,
|
||||
})
|
||||
|
||||
language_code = _request_language(request)
|
||||
candidates.sort(
|
||||
key=lambda item: (
|
||||
_localized_text_key(item["value"], language_code),
|
||||
_localized_text_key(item["asset_name"], language_code),
|
||||
item["asset_id"],
|
||||
)
|
||||
)
|
||||
options = []
|
||||
for item in candidates:
|
||||
label = item["value"]
|
||||
if counts.get(item["normalized"], 0) > 1:
|
||||
label = f"{label} — {item['asset_name']}"
|
||||
options.append({
|
||||
"asset_id": item["asset_id"],
|
||||
"value": item["value"],
|
||||
"label": label,
|
||||
"asset_name": item["asset_name"],
|
||||
})
|
||||
return {"field_name": field_name, "options": options}
|
||||
|
||||
|
||||
@app.get("/assets")
|
||||
def assets_list(request: Request, category_id: int | None = None, db: Session = Depends(get_db)):
|
||||
categories = (
|
||||
categories = _sort_localized(
|
||||
db.query(Category)
|
||||
.options(selectinload(Category.visible_fields).joinedload(CategoryField.definition))
|
||||
.order_by(Category.name)
|
||||
.all()
|
||||
.all(),
|
||||
"name",
|
||||
request,
|
||||
)
|
||||
|
||||
requested_exclusions = request.query_params.getlist("job_filter_exclude")
|
||||
@@ -2715,6 +2887,7 @@ def assets_list(request: Request, category_id: int | None = None, db: Session =
|
||||
"job_filter_statuses": ASSET_JOB_FILTER_STATUSES,
|
||||
"job_state_by_asset": job_state_by_asset,
|
||||
"asset_platforms": {asset.id: detect_platform(asset) for asset in assets},
|
||||
"compare_field_options": _software_compare_field_options(db, request) if _is_admin(request) else [],
|
||||
},
|
||||
)
|
||||
|
||||
@@ -2949,7 +3122,11 @@ def chart_delete(chart_id: int, request: Request, db: Session = Depends(get_db))
|
||||
@app.get("/assets/new")
|
||||
def asset_new(request: Request, category_id: int | None = None, db: Session = Depends(get_db)):
|
||||
_require_admin(request)
|
||||
categories = db.query(Category).options(joinedload(Category.visible_fields)).all()
|
||||
categories = _sort_localized(
|
||||
db.query(Category).options(joinedload(Category.visible_fields)).all(),
|
||||
"name",
|
||||
request,
|
||||
)
|
||||
selected = next((c for c in categories if c.id == category_id), categories[0] if categories else None)
|
||||
return templates.TemplateResponse("asset_form.html", {"request": request, "asset": None, "categories": categories, "selected": selected, "fields": ASSET_FIELDS, "status_options": _active_status_options(db, request), "duplicate_mode": False, "parent_candidates": db.query(Asset).order_by(Asset.name).all(), "custom_values": {}, "standard_images": list_standard_images()})
|
||||
|
||||
@@ -2960,7 +3137,11 @@ def asset_duplicate(asset_id: int, request: Request, db: Session = Depends(get_d
|
||||
original = db.query(Asset).options(joinedload(Asset.category).joinedload(Category.visible_fields)).filter(Asset.id == asset_id).first()
|
||||
if not original:
|
||||
raise HTTPException(404, "Asset nicht gefunden")
|
||||
categories = db.query(Category).options(joinedload(Category.visible_fields)).all()
|
||||
categories = _sort_localized(
|
||||
db.query(Category).options(joinedload(Category.visible_fields)).all(),
|
||||
"name",
|
||||
request,
|
||||
)
|
||||
class DuplicateValues:
|
||||
pass
|
||||
duplicate = DuplicateValues()
|
||||
@@ -3431,7 +3612,7 @@ def asset_tree(
|
||||
or bool(by_parent.get(asset.id))
|
||||
]
|
||||
|
||||
categories = db.query(Category).order_by(Category.name).all()
|
||||
categories = _sort_localized(db.query(Category).all(), "name", request)
|
||||
actual_roots = [
|
||||
asset for asset in all_assets
|
||||
if asset.parent_asset_id is None or asset.parent_asset_id not in all_by_id
|
||||
@@ -3821,7 +4002,11 @@ def asset_mesh_inventory_json(asset_id: int, request: Request, db: Session = Dep
|
||||
def asset_edit(asset_id: int, request: Request, category_id: int | None = None, db: Session = Depends(get_db)):
|
||||
_require_admin(request)
|
||||
asset = db.query(Asset).options(joinedload(Asset.category).joinedload(Category.visible_fields)).filter(Asset.id == asset_id).first()
|
||||
categories = db.query(Category).options(joinedload(Category.visible_fields)).all()
|
||||
categories = _sort_localized(
|
||||
db.query(Category).options(joinedload(Category.visible_fields)).all(),
|
||||
"name",
|
||||
request,
|
||||
)
|
||||
if not asset:
|
||||
raise HTTPException(404, "Asset nicht gefunden")
|
||||
selected = next((category for category in categories if category.id == category_id), None) if category_id else asset.category
|
||||
@@ -4429,11 +4614,12 @@ def status_option_delete(option_id: int, request: Request, db: Session = Depends
|
||||
@app.get("/categories")
|
||||
def categories_list(request: Request, db: Session = Depends(get_db)):
|
||||
_require_admin(request)
|
||||
categories = (
|
||||
categories = _sort_localized(
|
||||
db.query(Category)
|
||||
.options(selectinload(Category.visible_fields))
|
||||
.order_by(Category.name)
|
||||
.all()
|
||||
.all(),
|
||||
"name",
|
||||
request,
|
||||
)
|
||||
asset_counts = dict(
|
||||
db.query(Asset.category_id, func.count(Asset.id))
|
||||
@@ -5115,7 +5301,7 @@ _SOFTWARE_JOB_FILTER_KEYS = (
|
||||
def _software_job_filter_values(request: Request) -> dict[str, str]:
|
||||
"""Read and normalize the server-side filters of the recent-job table."""
|
||||
return {
|
||||
key: str(request.query_params.get(f"job_filter_{key}") or "").strip()[:200]
|
||||
key: str(request.query_params.get(f"job_filter_{key}") or "").strip()[:12000]
|
||||
for key in _SOFTWARE_JOB_FILTER_KEYS
|
||||
}
|
||||
|
||||
@@ -5240,115 +5426,157 @@ def _software_job_date_filter(column: Any, search_value: str):
|
||||
return or_(*conditions)
|
||||
|
||||
|
||||
def _multi_filter_terms(value: Any, *, max_terms: int = 200) -> list[str]:
|
||||
"""Split a table filter into unique non-empty line-based OR terms."""
|
||||
terms: list[str] = []
|
||||
seen: set[str] = set()
|
||||
for raw in str(value or "").replace("\r\n", "\n").replace("\r", "\n").split("\n"):
|
||||
term = raw.strip()
|
||||
if not term:
|
||||
continue
|
||||
key = term.casefold()
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
terms.append(term[:300])
|
||||
if len(terms) >= max_terms:
|
||||
break
|
||||
return terms
|
||||
|
||||
|
||||
def _apply_software_job_filters(query: Any, request: Request, filters: dict[str, str]):
|
||||
"""Apply all recent-job filters before LIMIT is evaluated.
|
||||
|
||||
This keeps an old job searchable even when the visible result size is only
|
||||
25 rows. The large script, result and log columns are still excluded from
|
||||
the final SELECT by ``load_only`` below.
|
||||
Multiple lines within one column are OR-linked. Different columns remain
|
||||
AND-linked. This allows users to paste a column copied from Excel directly
|
||||
into a table filter without changing the normal single-value behavior.
|
||||
"""
|
||||
parameters_text = cast(SoftwareJob.parameters, String)
|
||||
|
||||
if filters["job_id"]:
|
||||
value = filters["job_id"]
|
||||
if value.isdigit():
|
||||
query = query.filter(SoftwareJob.id == int(value))
|
||||
else:
|
||||
query = query.filter(_sql_text_contains(SoftwareJob.id, value))
|
||||
|
||||
if filters["created_at"]:
|
||||
query = query.filter(
|
||||
_software_job_date_filter(SoftwareJob.created_at, filters["created_at"])
|
||||
)
|
||||
|
||||
if filters["asset"]:
|
||||
query = query.filter(_sql_text_contains(Asset.name, filters["asset"]))
|
||||
|
||||
if filters["job_type"]:
|
||||
value = filters["job_type"]
|
||||
conditions = [
|
||||
_sql_text_contains(SoftwareJob.job_type, value),
|
||||
_sql_text_contains(parameters_text, value),
|
||||
]
|
||||
job_type_codes = _translated_filter_codes(
|
||||
request,
|
||||
"jobs.type",
|
||||
("software_inventory", "job_definition"),
|
||||
value,
|
||||
)
|
||||
if job_type_codes:
|
||||
conditions.append(SoftwareJob.job_type.in_(job_type_codes))
|
||||
job_kind_codes = _translated_filter_codes(
|
||||
request,
|
||||
"jobdefs.kind",
|
||||
("script", "registry"),
|
||||
value,
|
||||
)
|
||||
for kind in job_kind_codes:
|
||||
conditions.append(
|
||||
and_(
|
||||
SoftwareJob.job_type == "job_definition",
|
||||
_sql_text_contains(parameters_text, kind),
|
||||
)
|
||||
)
|
||||
terms = _multi_filter_terms(filters["job_id"])
|
||||
if terms:
|
||||
conditions = []
|
||||
for value in terms:
|
||||
if value.isdigit():
|
||||
conditions.append(SoftwareJob.id == int(value))
|
||||
else:
|
||||
conditions.append(_sql_text_contains(SoftwareJob.id, value))
|
||||
query = query.filter(or_(*conditions))
|
||||
|
||||
if filters["package"]:
|
||||
value = filters["package"]
|
||||
query = query.filter(
|
||||
terms = _multi_filter_terms(filters["created_at"])
|
||||
if terms:
|
||||
query = query.filter(or_(*[
|
||||
_software_job_date_filter(SoftwareJob.created_at, value)
|
||||
for value in terms
|
||||
]))
|
||||
|
||||
terms = _multi_filter_terms(filters["asset"])
|
||||
if terms:
|
||||
query = query.filter(or_(*[
|
||||
_sql_text_contains(Asset.name, value)
|
||||
for value in terms
|
||||
]))
|
||||
|
||||
terms = _multi_filter_terms(filters["job_type"])
|
||||
if terms:
|
||||
term_conditions = []
|
||||
for value in terms:
|
||||
conditions = [
|
||||
_sql_text_contains(SoftwareJob.job_type, value),
|
||||
_sql_text_contains(parameters_text, value),
|
||||
]
|
||||
job_type_codes = _translated_filter_codes(
|
||||
request,
|
||||
"jobs.type",
|
||||
("software_inventory", "job_definition"),
|
||||
value,
|
||||
)
|
||||
if job_type_codes:
|
||||
conditions.append(SoftwareJob.job_type.in_(job_type_codes))
|
||||
job_kind_codes = _translated_filter_codes(
|
||||
request,
|
||||
"jobdefs.kind",
|
||||
("script", "registry"),
|
||||
value,
|
||||
)
|
||||
for kind in job_kind_codes:
|
||||
conditions.append(
|
||||
and_(
|
||||
SoftwareJob.job_type == "job_definition",
|
||||
_sql_text_contains(parameters_text, kind),
|
||||
)
|
||||
)
|
||||
term_conditions.append(or_(*conditions))
|
||||
query = query.filter(or_(*term_conditions))
|
||||
|
||||
terms = _multi_filter_terms(filters["package"])
|
||||
if terms:
|
||||
query = query.filter(or_(*[
|
||||
or_(
|
||||
_sql_text_contains(SoftwarePackage.name, value),
|
||||
_sql_text_contains(parameters_text, value),
|
||||
)
|
||||
)
|
||||
for value in terms
|
||||
]))
|
||||
|
||||
if filters["action"]:
|
||||
value = filters["action"]
|
||||
conditions = [_sql_text_contains(SoftwareJob.action, value)]
|
||||
action_codes = _translated_filter_codes(
|
||||
request,
|
||||
"jobs.action",
|
||||
("run", "install", "uninstall", "update"),
|
||||
value,
|
||||
)
|
||||
if action_codes:
|
||||
conditions.append(SoftwareJob.action.in_(action_codes))
|
||||
terms = _multi_filter_terms(filters["action"])
|
||||
if terms:
|
||||
term_conditions = []
|
||||
for value in terms:
|
||||
conditions = [_sql_text_contains(SoftwareJob.action, value)]
|
||||
action_codes = _translated_filter_codes(
|
||||
request,
|
||||
"jobs.action",
|
||||
("run", "install", "uninstall", "update"),
|
||||
value,
|
||||
)
|
||||
if action_codes:
|
||||
conditions.append(SoftwareJob.action.in_(action_codes))
|
||||
term_conditions.append(or_(*conditions))
|
||||
query = query.filter(or_(*term_conditions))
|
||||
|
||||
terms = _multi_filter_terms(filters["priority"])
|
||||
if terms:
|
||||
conditions = []
|
||||
for value in terms:
|
||||
if re.fullmatch(r"[-+]?\d+", value):
|
||||
conditions.append(SoftwareJob.priority == int(value))
|
||||
else:
|
||||
conditions.append(_sql_text_contains(SoftwareJob.priority, value))
|
||||
query = query.filter(or_(*conditions))
|
||||
|
||||
if filters["priority"]:
|
||||
value = filters["priority"]
|
||||
if re.fullmatch(r"[-+]?\d+", value):
|
||||
query = query.filter(SoftwareJob.priority == int(value))
|
||||
else:
|
||||
query = query.filter(_sql_text_contains(SoftwareJob.priority, value))
|
||||
terms = _multi_filter_terms(filters["platform"])
|
||||
if terms:
|
||||
query = query.filter(or_(*[
|
||||
_sql_text_contains(SoftwareJob.platform, value)
|
||||
for value in terms
|
||||
]))
|
||||
|
||||
if filters["platform"]:
|
||||
query = query.filter(
|
||||
_sql_text_contains(SoftwareJob.platform, filters["platform"])
|
||||
)
|
||||
|
||||
if filters["status"]:
|
||||
value = filters["status"]
|
||||
conditions = [_sql_text_contains(SoftwareJob.status, value)]
|
||||
status_codes = _translated_filter_codes(
|
||||
request,
|
||||
"software.status",
|
||||
(
|
||||
"created",
|
||||
"sending",
|
||||
"sent",
|
||||
"running",
|
||||
"success",
|
||||
"partial",
|
||||
"failed",
|
||||
"timeout",
|
||||
"cancelled",
|
||||
),
|
||||
value,
|
||||
)
|
||||
if status_codes:
|
||||
conditions.append(SoftwareJob.status.in_(status_codes))
|
||||
query = query.filter(or_(*conditions))
|
||||
terms = _multi_filter_terms(filters["status"])
|
||||
if terms:
|
||||
term_conditions = []
|
||||
for value in terms:
|
||||
conditions = [_sql_text_contains(SoftwareJob.status, value)]
|
||||
status_codes = _translated_filter_codes(
|
||||
request,
|
||||
"software.status",
|
||||
(
|
||||
"created",
|
||||
"sending",
|
||||
"sent",
|
||||
"running",
|
||||
"success",
|
||||
"partial",
|
||||
"failed",
|
||||
"timeout",
|
||||
"cancelled",
|
||||
),
|
||||
value,
|
||||
)
|
||||
if status_codes:
|
||||
conditions.append(SoftwareJob.status.in_(status_codes))
|
||||
term_conditions.append(or_(*conditions))
|
||||
query = query.filter(or_(*term_conditions))
|
||||
|
||||
return query
|
||||
|
||||
@@ -7269,7 +7497,7 @@ async def translations_import(request: Request, file: UploadFile = File(...), ig
|
||||
@app.get("/settings/appearance")
|
||||
def settings_appearance_page(request: Request, db: Session = Depends(get_db)):
|
||||
_require_admin(request)
|
||||
categories = db.query(Category).order_by(Category.name.asc()).all()
|
||||
categories = _sort_localized(db.query(Category).all(), "name", request)
|
||||
return templates.TemplateResponse(
|
||||
"settings.html",
|
||||
{
|
||||
@@ -7286,7 +7514,7 @@ def settings_appearance_page(request: Request, db: Session = Depends(get_db)):
|
||||
@app.get("/settings/authentication")
|
||||
def settings_authentication_page(request: Request, db: Session = Depends(get_db)):
|
||||
_require_admin(request)
|
||||
categories = db.query(Category).order_by(Category.name.asc()).all()
|
||||
categories = _sort_localized(db.query(Category).all(), "name", request)
|
||||
return templates.TemplateResponse(
|
||||
"settings.html",
|
||||
{
|
||||
@@ -7303,7 +7531,7 @@ def settings_authentication_page(request: Request, db: Session = Depends(get_db)
|
||||
@app.get("/settings/system")
|
||||
def settings_system_page(request: Request, db: Session = Depends(get_db)):
|
||||
_require_admin(request)
|
||||
categories = db.query(Category).order_by(Category.name.asc()).all()
|
||||
categories = _sort_localized(db.query(Category).all(), "name", request)
|
||||
return templates.TemplateResponse(
|
||||
"settings.html",
|
||||
{
|
||||
@@ -8050,7 +8278,7 @@ def settings_backup_restore(request: Request, backup_name: str, confirmation: st
|
||||
@app.get("/settings")
|
||||
def settings_page(request: Request, db: Session = Depends(get_db)):
|
||||
_require_admin(request)
|
||||
categories = db.query(Category).order_by(Category.name.asc()).all()
|
||||
categories = _sort_localized(db.query(Category).all(), "name", request)
|
||||
return templates.TemplateResponse(
|
||||
"settings.html",
|
||||
{
|
||||
@@ -8217,7 +8445,7 @@ def meshcentral_sync_page(request: Request, db: Session = Depends(get_db)):
|
||||
missing = db.query(Asset).filter(Asset.mesh_sync_status == "missing").count()
|
||||
definitions = db.query(FieldDefinition).filter(FieldDefinition.is_active.is_(True)).order_by(FieldDefinition.sort_order, FieldDefinition.label).all()
|
||||
mappings = db.query(MeshFieldMapping).order_by(MeshFieldMapping.priority, MeshFieldMapping.id).all()
|
||||
categories = db.query(Category).order_by(Category.name.asc()).all()
|
||||
categories = _sort_localized(db.query(Category).all(), "name", request)
|
||||
return templates.TemplateResponse("meshcentral_sync.html", {
|
||||
"request": request, "runs": runs, "config": public_config(),
|
||||
"linked": linked, "conflicts": conflicts, "missing": missing,
|
||||
|
||||
Reference in New Issue
Block a user