Soft delete left orphaned executables on disk and let a deleted name collide with a fresh import, so delete is now a hard delete that removes the associated files. The destructive paths (delete, revert-to-record) require a typed OK because neither can be undone. Installers keep their filename at import and nest per application/version, which stops repeat imports doubling up. Version now comes from the same PE detection the endpoints agent uses; when no version can be read the caller is asked for one rather than silently storing a blank. Applications also surface Version and a monitorable toggle, with launch path and process name required only while monitorable is ticked. Fleet shows the PC alias, matching PC Details.
111 lines
4.1 KiB
Python
111 lines
4.1 KiB
Python
"""The Fleet list must carry the asset alias.
|
|
|
|
The alias is what people actually call a PC ("Bay 3 Controller"); the hostname
|
|
is what DNS calls it. The PCs list has shown both for a long time, but the
|
|
Fleet list query never selected Asset.name, so the column could not be built
|
|
even though the data existed - it was missing rather than broken.
|
|
|
|
These pin the field on the payload and its inclusion in search, so the column
|
|
cannot quietly lose its source again.
|
|
"""
|
|
|
|
LIST_URL = '/api/endpoints/'
|
|
|
|
|
|
def _endpoint(db, hostname='fleetbox', alias='Bay 3 Controller'):
|
|
"""An Asset + Computer + EndpointState triple: one row on the fleet list.
|
|
|
|
The list joins through Computer and filters on Asset.isactive, so all three
|
|
are required for a row to appear at all.
|
|
"""
|
|
from shopdb.api import Asset, AssetType
|
|
from plugins.computers.models import Computer
|
|
from plugins.endpoints.models import EndpointState
|
|
|
|
assettype = AssetType.query.filter_by(assettype='computer').first()
|
|
if assettype is None:
|
|
assettype = AssetType(assettype='computer', pluginname='computers',
|
|
tablename='computers')
|
|
db.session.add(assettype)
|
|
db.session.flush()
|
|
|
|
asset = Asset(assetnumber=hostname, name=alias,
|
|
assettypeid=assettype.assettypeid, isactive=True)
|
|
db.session.add(asset)
|
|
db.session.flush()
|
|
db.session.add(Computer(assetid=asset.assetid, hostname=hostname))
|
|
db.session.add(EndpointState(assetid=asset.assetid, hostname=hostname))
|
|
db.session.commit()
|
|
return asset.assetid
|
|
|
|
|
|
def _rows(response):
|
|
return response.get_json()['data']
|
|
|
|
|
|
def test_the_fleet_list_carries_the_alias(client, db, auth_headers):
|
|
_endpoint(db)
|
|
|
|
response = client.get(LIST_URL, headers=auth_headers)
|
|
|
|
assert response.status_code == 200, response.get_json()
|
|
rows = _rows(response)
|
|
assert len(rows) == 1
|
|
# Exposed as 'alias', not 'name': the row already has a hostname, and
|
|
# 'name' beside 'hostname' reads as a duplicate of the same thing.
|
|
assert rows[0]['alias'] == 'Bay 3 Controller'
|
|
|
|
|
|
def test_an_endpoint_without_an_alias_still_lists(client, db, auth_headers):
|
|
# The column renders '-' for these. A null must not drop the row or 500.
|
|
_endpoint(db, hostname='namelessbox', alias=None)
|
|
|
|
response = client.get(LIST_URL, headers=auth_headers)
|
|
|
|
assert response.status_code == 200, response.get_json()
|
|
rows = _rows(response)
|
|
assert len(rows) == 1
|
|
assert rows[0]['alias'] is None
|
|
assert rows[0]['hostname'] == 'namelessbox'
|
|
|
|
|
|
def test_the_alias_is_searchable(client, db, auth_headers):
|
|
# Someone looking for a PC types what it is called, not its hostname.
|
|
_endpoint(db, hostname='pc-a', alias='Bay 3 Controller')
|
|
_endpoint(db, hostname='pc-b', alias='Paint Booth')
|
|
|
|
response = client.get(LIST_URL + '?search=Bay 3', headers=auth_headers)
|
|
|
|
assert response.status_code == 200, response.get_json()
|
|
rows = _rows(response)
|
|
assert [r['hostname'] for r in rows] == ['pc-a']
|
|
|
|
|
|
def test_searching_by_hostname_still_works(client, db, auth_headers):
|
|
# The alias clause was added to an existing OR; prove it did not replace
|
|
# what was already there.
|
|
_endpoint(db, hostname='pc-a', alias='Bay 3 Controller')
|
|
_endpoint(db, hostname='pc-b', alias='Paint Booth')
|
|
|
|
response = client.get(LIST_URL + '?search=pc-b', headers=auth_headers)
|
|
|
|
assert response.status_code == 200, response.get_json()
|
|
rows = _rows(response)
|
|
assert [r['hostname'] for r in rows] == ['pc-b']
|
|
|
|
|
|
def test_the_row_keeps_its_other_columns(client, db, auth_headers):
|
|
# The alias was added mid-tuple in the query, so every following column
|
|
# shifted position. If that unpacking is wrong the values silently swap
|
|
# rather than raising.
|
|
_endpoint(db, hostname='fleetbox', alias='Bay 3 Controller')
|
|
|
|
response = client.get(LIST_URL, headers=auth_headers)
|
|
|
|
row = _rows(response)[0]
|
|
assert row['hostname'] == 'fleetbox'
|
|
assert row['assetnumber'] == 'fleetbox'
|
|
assert row['alias'] == 'Bay 3 Controller'
|
|
assert row['status'] in ('never', 'quiet', 'reporting')
|
|
assert 'computerid' in row and row['computerid'] is not None
|