Commit 445d8738 by Mac Stephens

Improve shared device management, add mobile cards, and bring BFD live-update…

Improve shared device management, add mobile cards, and bring BFD live-update handling in line with WC
parent 28a1f426
...@@ -37,6 +37,8 @@ type ...@@ -37,6 +37,8 @@ type
FSelectProc: TSelectProc; FSelectProc: TSelectProc;
FLoading: Boolean; FLoading: Boolean;
FFirstLoad: Boolean; FFirstLoad: Boolean;
FRefreshPending: Boolean;
FPendingWsData: TJSObject;
[async] procedure GetComplaints; [async] procedure GetComplaints;
procedure HandleListClick(e: TJSMouseEvent); procedure HandleListClick(e: TJSMouseEvent);
procedure ShowHideBusinessRows; procedure ShowHideBusinessRows;
...@@ -57,6 +59,8 @@ procedure TFViewComplaints.WebFormCreate(Sender: TObject); ...@@ -57,6 +59,8 @@ procedure TFViewComplaints.WebFormCreate(Sender: TObject);
begin begin
Document.addEventListener('click', @HandleListClick); Document.addEventListener('click', @HandleListClick);
FFirstLoad := True; FFirstLoad := True;
FRefreshPending := False;
FPendingWsData := nil;
GetComplaints; GetComplaints;
asm asm
if (!window.showComplaintDetails) { if (!window.showComplaintDetails) {
...@@ -176,12 +180,31 @@ begin ...@@ -176,12 +180,31 @@ begin
HideSpinner('spinner'); HideSpinner('spinner');
FFirstLoad := False; FFirstLoad := False;
end; end;
if Assigned(FPendingWsData) then
begin
respObj := FPendingWsData;
FPendingWsData := nil;
ApplyWsData(respObj);
end;
if FRefreshPending then
begin
FRefreshPending := False;
GetComplaints;
end;
end; end;
end; end;
procedure TFViewComplaints.RefreshData; procedure TFViewComplaints.RefreshData;
begin begin
Console.Log('Complaints.RefreshData'); Console.Log('Complaints.RefreshData');
if FLoading then
begin
FRefreshPending := True;
Exit;
end;
GetComplaints; GetComplaints;
end; end;
...@@ -190,7 +213,10 @@ var ...@@ -190,7 +213,10 @@ var
complaintsCount: Integer; complaintsCount: Integer;
begin begin
if FLoading then if FLoading then
begin
FPendingWsData := aRespObj;
Exit; Exit;
end;
xdwdsComplaints.Close; xdwdsComplaints.Close;
xdwdsComplaints.SetJsonData(aRespObj['data']); xdwdsComplaints.SetJsonData(aRespObj['data']);
......
...@@ -31,6 +31,9 @@ type ...@@ -31,6 +31,9 @@ type
FUnitsLoaded: Boolean; FUnitsLoaded: Boolean;
FComplaintsLoaded: Boolean; FComplaintsLoaded: Boolean;
FLoadingPoints: Boolean; FLoadingPoints: Boolean;
FRefreshPending: Boolean;
FPendingWsUnitMapData: TJSArray;
FPendingWsComplaintMapData: TJSArray;
mapFilters: TMapFilters; mapFilters: TMapFilters;
FPendingUnitId: string; FPendingUnitId: string;
FPendingComplaintId: string; FPendingComplaintId: string;
...@@ -243,33 +246,54 @@ begin ...@@ -243,33 +246,54 @@ begin
unitsData := nil; unitsData := nil;
complaintsData := nil; complaintsData := nil;
// --- Fetch Units --------------------------------------------------------- // Section: Fetch units.
try try
resp := await(xdwcMap.RawInvokeAsync('IApiService.GetUnitMap', [])); resp := await(xdwcMap.RawInvokeAsync('IApiService.GetUnitMap', []));
root := TJSObject(resp.Result); root := TJSObject(resp.Result);
unitsData := TJSArray(root['data']); unitsData := TJSArray(root['data']);
FUnitsLoaded := True; FUnitsLoaded := Assigned(unitsData);
except except
on E: EXDataClientRequestException do on E: EXDataClientRequestException do
Console.Log('Units XData error: ' + E.ErrorResult.ErrorMessage); Console.Log('Units XData error: ' + E.ErrorResult.ErrorMessage);
on E: Exception do
Console.Log('Units error: ' + E.Message);
end; end;
// --- Fetch Complaints ---------------------------------------------------- // Section: Fetch incidents.
try try
resp := await(xdwcMap.RawInvokeAsync('IApiService.GetComplaintMap', [])); resp := await(xdwcMap.RawInvokeAsync('IApiService.GetComplaintMap', []));
root := TJSObject(resp.Result); root := TJSObject(resp.Result);
complaintsData := TJSArray(root['data']); complaintsData := TJSArray(root['data']);
FComplaintsLoaded := True; FComplaintsLoaded := Assigned(complaintsData);
except except
on E: EXDataClientRequestException do on E: EXDataClientRequestException do
Console.Log('Complaints XData error: ' + E.ErrorResult.ErrorMessage); Console.Log('Complaints XData error: ' + E.ErrorResult.ErrorMessage);
on E: Exception do
Console.Log('Complaints error: ' + E.Message);
end;
// Note: Apply the latest buffered WebSocket snapshot after the HTTP load.
if Assigned(FPendingWsUnitMapData) then
begin
unitsData := FPendingWsUnitMapData;
FPendingWsUnitMapData := nil;
FUnitsLoaded := True;
end; end;
// --- Place markers (BeginUpdate wraps both so the map redraws once) ------ if Assigned(FPendingWsComplaintMapData) then
begin
complaintsData := FPendingWsComplaintMapData;
FPendingWsComplaintMapData := nil;
FComplaintsLoaded := True;
end;
// Section: Render both marker sets in one update.
lfMap.BeginUpdate; lfMap.BeginUpdate;
try try
PlaceUnitMarkers(unitsData); if FUnitsLoaded then
PlaceComplaintMarkers(complaintsData); PlaceUnitMarkers(unitsData);
if FComplaintsLoaded then
PlaceComplaintMarkers(complaintsData);
finally finally
lfMap.EndUpdate; lfMap.EndUpdate;
end; end;
...@@ -283,6 +307,12 @@ begin ...@@ -283,6 +307,12 @@ begin
if showBusy then if showBusy then
HideSpinner('spinner'); HideSpinner('spinner');
FLoadingPoints := False; FLoadingPoints := False;
if FRefreshPending then
begin
FRefreshPending := False;
LoadPointsAsync(False);
end;
end; end;
end; end;
...@@ -477,8 +507,13 @@ end; ...@@ -477,8 +507,13 @@ end;
procedure TFViewMap.ApplyWsUnitMapData(aData: TJSArray); procedure TFViewMap.ApplyWsUnitMapData(aData: TJSArray);
begin begin
// Skip if the map is still initialising or an HTTP load is in flight. if FLoadingPoints then
if (not Assigned(mapFilters)) or FLoadingPoints then begin
FPendingWsUnitMapData := aData;
Exit;
end;
if not Assigned(mapFilters) then
Exit; Exit;
lfMap.BeginUpdate; lfMap.BeginUpdate;
...@@ -494,7 +529,13 @@ end; ...@@ -494,7 +529,13 @@ end;
procedure TFViewMap.ApplyWsComplaintMapData(aData: TJSArray); procedure TFViewMap.ApplyWsComplaintMapData(aData: TJSArray);
begin begin
if (not Assigned(mapFilters)) or FLoadingPoints then if FLoadingPoints then
begin
FPendingWsComplaintMapData := aData;
Exit;
end;
if not Assigned(mapFilters) then
Exit; Exit;
lfMap.BeginUpdate; lfMap.BeginUpdate;
...@@ -692,6 +733,13 @@ end; ...@@ -692,6 +733,13 @@ end;
procedure TFViewMap.RefreshData; procedure TFViewMap.RefreshData;
begin begin
Console.Log('Map.RefreshData'); Console.Log('Map.RefreshData');
if FLoadingPoints then
begin
FRefreshPending := True;
Exit;
end;
LoadPointsAsync(False); LoadPointsAsync(False);
end; end;
......
...@@ -45,6 +45,8 @@ type ...@@ -45,6 +45,8 @@ type
private private
FLoading: Boolean; FLoading: Boolean;
FFirstLoad: Boolean; FFirstLoad: Boolean;
FRefreshPending: Boolean;
FPendingWsData: TJSObject;
[async] procedure GetUnits; [async] procedure GetUnits;
procedure HandleListClick(e: TJSMouseEvent); procedure HandleListClick(e: TJSMouseEvent);
procedure PrepareUnitData(aRespObj: TJSObject); procedure PrepareUnitData(aRespObj: TJSObject);
...@@ -66,6 +68,8 @@ begin ...@@ -66,6 +68,8 @@ begin
DMConnection.ApiConnection.Connected := True; DMConnection.ApiConnection.Connected := True;
Document.addEventListener('click', @HandleListClick); Document.addEventListener('click', @HandleListClick);
FFirstLoad := True; FFirstLoad := True;
FRefreshPending := False;
FPendingWsData := nil;
GetUnits; GetUnits;
asm asm
...@@ -232,12 +236,31 @@ begin ...@@ -232,12 +236,31 @@ begin
FFirstLoad := False; FFirstLoad := False;
FLoading := False; FLoading := False;
if Assigned(FPendingWsData) then
begin
respObj := FPendingWsData;
FPendingWsData := nil;
ApplyWsData(respObj);
end;
if FRefreshPending then
begin
FRefreshPending := False;
GetUnits;
end;
end; end;
end; end;
procedure TFViewUnits.RefreshData; procedure TFViewUnits.RefreshData;
begin begin
Console.Log('Units.RefreshData'); Console.Log('Units.RefreshData');
if FLoading then
begin
FRefreshPending := True;
Exit;
end;
GetUnits; GetUnits;
end; end;
...@@ -246,7 +269,10 @@ var ...@@ -246,7 +269,10 @@ var
unitCount: Integer; unitCount: Integer;
begin begin
if FLoading then if FLoading then
begin
FPendingWsData := aRespObj;
Exit; Exit;
end;
PrepareUnitData(aRespObj); PrepareUnitData(aRespObj);
......
unit Auth.Service; unit Auth.Service;
interface interface
...@@ -7,10 +7,10 @@ uses ...@@ -7,10 +7,10 @@ uses
XData.Web.Client; XData.Web.Client;
const const
TOKEN_NAME = 'WEBEMIMOBILE_TOKEN'; TOKEN_NAME = 'WCEMIMOBILE_TOKEN';
CREDENTIAL_NAME = 'WEBEMIMOBILE_CREDENTIAL_ID'; CREDENTIAL_NAME = 'WCEMIMOBILE_CREDENTIAL_ID';
KEY_TYPE_NAME = 'WEBEMIMOBILE_KEY_TYPE'; KEY_TYPE_NAME = 'WCEMIMOBILE_KEY_TYPE';
DEVICE_MANAGER_TOKEN_NAME = 'WEBEMIMOBILE_DEVICE_MANAGER_TOKEN'; DEVICE_MANAGER_TOKEN_NAME = 'WCEMIMOBILE_DEVICE_MANAGER_TOKEN';
type type
TOnLoginSuccess = reference to procedure; TOnLoginSuccess = reference to procedure;
...@@ -112,7 +112,35 @@ end; ...@@ -112,7 +112,35 @@ end;
{ TAuthService } { TAuthService }
constructor TAuthService.Create; constructor TAuthService.Create;
const
StorageKeys: array[0..2] of string = (TOKEN_NAME, CREDENTIAL_NAME, KEY_TYPE_NAME);
var
i: Integer;
keyName, oldKeyName, storedValue: string;
begin begin
// Note: Preserve existing WC registrations when renaming the browser storage keys.
for i := Low(StorageKeys) to High(StorageKeys) do
begin
keyName := StorageKeys[i];
oldKeyName := StringReplace(keyName, 'WCEMIMOBILE_', 'WEBEMIMOBILE_', []);
storedValue := window.localStorage.getItem(oldKeyName);
if not isNull(storedValue) then
begin
if isNull(window.localStorage.getItem(keyName)) then
window.localStorage.setItem(keyName, storedValue);
window.localStorage.removeItem(oldKeyName);
end;
end;
oldKeyName := 'WEBEMIMOBILE_DEVICE_MANAGER_TOKEN';
storedValue := window.sessionStorage.getItem(oldKeyName);
if not isNull(storedValue) then
begin
if isNull(window.sessionStorage.getItem(DEVICE_MANAGER_TOKEN_NAME)) then
window.sessionStorage.setItem(DEVICE_MANAGER_TOKEN_NAME, storedValue);
window.sessionStorage.removeItem(oldKeyName);
end;
FClient := TXDataWebClient.Create(nil); FClient := TXDataWebClient.Create(nil);
FClient.Connection := DMConnection.AuthConnection; FClient.Connection := DMConnection.AuthConnection;
end; end;
......
...@@ -31,10 +31,10 @@ type ...@@ -31,10 +31,10 @@ type
function DeviceList: TDataSet; virtual; abstract; function DeviceList: TDataSet; virtual; abstract;
function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; virtual; abstract; function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; virtual; abstract;
function UnrevokeDevice(CredentialId: string): Int64; virtual; abstract; function UnrevokeDevice(CredentialId: string): Int64; virtual; abstract;
function DeviceNameExists(DeviceName: string): Boolean; virtual; abstract;
function PendingPhone(PhoneNumber: string): TDataSet; virtual; abstract; function PendingPhone(PhoneNumber: string): TDataSet; virtual; abstract;
function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; virtual; abstract; function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; virtual; abstract;
function DeletePendingDevice(PhoneNumber: string): Int64; virtual; abstract; function DeleteDevice(DeviceId: Integer): Int64; virtual; abstract;
function DeleteDevice(CredentialId: string): Int64; virtual; abstract;
function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; virtual; abstract; function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; virtual; abstract;
function UpdateDeviceUsername(Username: string; CredentialId: string): Int64; virtual; abstract; function UpdateDeviceUsername(Username: string; CredentialId: string): Int64; virtual; abstract;
function ActiveDevice(CredentialId: string): TDataSet; virtual; abstract; function ActiveDevice(CredentialId: string): TDataSet; virtual; abstract;
......
...@@ -377,6 +377,21 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -377,6 +377,21 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
ParamType = ptInput ParamType = ptInput
end> end>
end end
object uqDeviceName: TUniQuery
Connection = ucDevices
SQL.Strings = (
'SELECT ID FROM DEVICE_REGISTRATIONS'
'WHERE UPPER(TRIM(DEVICE_NAME)) = UPPER(:NAME)')
ReadOnly = True
Left = 760
Top = 440
ParamData = <
item
DataType = ftString
Name = 'NAME'
ParamType = ptInput
end>
end
object uqPendingPhone: TUniQuery object uqPendingPhone: TUniQuery
Connection = ucDevices Connection = ucDevices
SQL.Strings = ( SQL.Strings = (
...@@ -420,36 +435,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase ...@@ -420,36 +435,19 @@ object OracleDeviceDatabase: TOracleDeviceDatabase
ParamType = ptInput ParamType = ptInput
end> end>
end end
object uqDeletePendingDevice: TUniQuery
Connection = ucDevices
SQL.Strings = (
'DELETE'
'FROM DEVICE_REGISTRATIONS'
'WHERE PHONE_NUMBER = :PHONE'
' AND STATUS = ''pending''')
Active = False
Left = 60
Top = 550
ParamData = <
item
DataType = ftString
Name = 'PHONE'
ParamType = ptInput
end>
end
object uqDeleteDevice: TUniQuery object uqDeleteDevice: TUniQuery
Connection = ucDevices Connection = ucDevices
SQL.Strings = ( SQL.Strings = (
'DELETE' 'DELETE'
'FROM DEVICE_REGISTRATIONS' 'FROM DEVICE_REGISTRATIONS'
'WHERE CREDENTIAL_ID = :CID') 'WHERE ID = :ID')
Active = False Active = False
Left = 250 Left = 250
Top = 550 Top = 550
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftInteger
Name = 'CID' Name = 'ID'
ParamType = ptInput ParamType = ptInput
end> end>
end end
......
...@@ -28,8 +28,8 @@ type ...@@ -28,8 +28,8 @@ type
uqRevokeDevice: TUniQuery; uqRevokeDevice: TUniQuery;
uqUnrevokeDevice: TUniQuery; uqUnrevokeDevice: TUniQuery;
uqPendingPhone: TUniQuery; uqPendingPhone: TUniQuery;
uqDeviceName: TUniQuery;
uqAddPendingDevice: TUniQuery; uqAddPendingDevice: TUniQuery;
uqDeletePendingDevice: TUniQuery;
uqDeleteDevice: TUniQuery; uqDeleteDevice: TUniQuery;
uqReserveRedeemCode: TUniQuery; uqReserveRedeemCode: TUniQuery;
uqUpdateDeviceUsername: TUniQuery; uqUpdateDeviceUsername: TUniQuery;
...@@ -57,10 +57,10 @@ type ...@@ -57,10 +57,10 @@ type
function DeviceList: TDataSet; override; function DeviceList: TDataSet; override;
function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; override; function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; override;
function UnrevokeDevice(CredentialId: string): Int64; override; function UnrevokeDevice(CredentialId: string): Int64; override;
function DeviceNameExists(DeviceName: string): Boolean; override;
function PendingPhone(PhoneNumber: string): TDataSet; override; function PendingPhone(PhoneNumber: string): TDataSet; override;
function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; override; function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; override;
function DeletePendingDevice(PhoneNumber: string): Int64; override; function DeleteDevice(DeviceId: Integer): Int64; override;
function DeleteDevice(CredentialId: string): Int64; override;
function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; override; function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; override;
function UpdateDeviceUsername(Username: string; CredentialId: string): Int64; override; function UpdateDeviceUsername(Username: string; CredentialId: string): Int64; override;
function ActiveDevice(CredentialId: string): TDataSet; override; function ActiveDevice(CredentialId: string): TDataSet; override;
...@@ -242,6 +242,18 @@ begin ...@@ -242,6 +242,18 @@ begin
Result := uqUnrevokeDevice.RowsAffected; Result := uqUnrevokeDevice.RowsAffected;
end; end;
function TOracleDeviceDatabase.DeviceNameExists(DeviceName: string): Boolean;
begin
uqDeviceName.Close;
uqDeviceName.ParamByName('NAME').AsString := Trim(DeviceName);
uqDeviceName.Open;
try
Result := not uqDeviceName.IsEmpty;
finally
uqDeviceName.Close;
end;
end;
function TOracleDeviceDatabase.PendingPhone(PhoneNumber: string): TDataSet; function TOracleDeviceDatabase.PendingPhone(PhoneNumber: string): TDataSet;
begin begin
uqPendingPhone.Close; uqPendingPhone.Close;
...@@ -260,18 +272,10 @@ begin ...@@ -260,18 +272,10 @@ begin
Result := uqAddPendingDevice.RowsAffected; Result := uqAddPendingDevice.RowsAffected;
end; end;
function TOracleDeviceDatabase.DeletePendingDevice(PhoneNumber: string): Int64; function TOracleDeviceDatabase.DeleteDevice(DeviceId: Integer): Int64;
begin
uqDeletePendingDevice.Close;
uqDeletePendingDevice.ParamByName('PHONE').AsString := PhoneNumber;
uqDeletePendingDevice.ExecSQL;
Result := uqDeletePendingDevice.RowsAffected;
end;
function TOracleDeviceDatabase.DeleteDevice(CredentialId: string): Int64;
begin begin
uqDeleteDevice.Close; uqDeleteDevice.Close;
uqDeleteDevice.ParamByName('CID').AsString := CredentialId; uqDeleteDevice.ParamByName('ID').AsInteger := DeviceId;
uqDeleteDevice.ExecSQL; uqDeleteDevice.ExecSQL;
Result := uqDeleteDevice.RowsAffected; Result := uqDeleteDevice.RowsAffected;
end; end;
......
...@@ -379,6 +379,21 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase ...@@ -379,6 +379,21 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase
Value = nil Value = nil
end> end>
end end
object uqDeviceName: TUniQuery
Connection = ucDevices
SQL.Strings = (
'SELECT ID FROM lems.device_registrations'
'WHERE UPPER(TRIM(DEVICE_NAME)) = UPPER(:NAME)')
ReadOnly = True
Left = 760
Top = 440
ParamData = <
item
DataType = ftString
Name = 'NAME'
ParamType = ptInput
end>
end
object uqPendingPhone: TUniQuery object uqPendingPhone: TUniQuery
Connection = ucDevices Connection = ucDevices
SQL.Strings = ( SQL.Strings = (
...@@ -423,32 +438,16 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase ...@@ -423,32 +438,16 @@ object PostgreSQLDeviceDatabase: TPostgreSQLDeviceDatabase
Value = nil Value = nil
end> end>
end end
object uqDeletePendingDevice: TUniQuery
Connection = ucDevices
SQL.Strings = (
'DELETE FROM lems.device_registrations WHERE phone_number = :PHON' +
'E AND status = '#39'pending'#39)
Left = 60
Top = 566
ParamData = <
item
DataType = ftString
Name = 'PHONE'
ParamType = ptInput
Value = nil
end>
end
object uqDeleteDevice: TUniQuery object uqDeleteDevice: TUniQuery
Connection = ucDevices Connection = ucDevices
SQL.Strings = ( SQL.Strings = (
'DELETE FROM lems.device_registrations WHERE credential_id = :CID') 'DELETE FROM lems.device_registrations WHERE id = :ID')
Left = 188 Left = 188
Top = 530 Top = 530
ParamData = < ParamData = <
item item
DataType = ftString DataType = ftInteger
Name = 'CID' Name = 'ID'
ParamType = ptInput ParamType = ptInput
Value = nil Value = nil
end> end>
......
...@@ -28,8 +28,8 @@ type ...@@ -28,8 +28,8 @@ type
uqRevokeDevice: TUniQuery; uqRevokeDevice: TUniQuery;
uqUnrevokeDevice: TUniQuery; uqUnrevokeDevice: TUniQuery;
uqPendingPhone: TUniQuery; uqPendingPhone: TUniQuery;
uqDeviceName: TUniQuery;
uqAddPendingDevice: TUniQuery; uqAddPendingDevice: TUniQuery;
uqDeletePendingDevice: TUniQuery;
uqDeleteDevice: TUniQuery; uqDeleteDevice: TUniQuery;
uqReserveRedeemCode: TUniQuery; uqReserveRedeemCode: TUniQuery;
uqMarkRedeemCodeUsed: TUniQuery; uqMarkRedeemCodeUsed: TUniQuery;
...@@ -58,10 +58,10 @@ type ...@@ -58,10 +58,10 @@ type
function DeviceList: TDataSet; override; function DeviceList: TDataSet; override;
function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; override; function RevokeDevice(RevokedBy: string; CredentialId: string): Int64; override;
function UnrevokeDevice(CredentialId: string): Int64; override; function UnrevokeDevice(CredentialId: string): Int64; override;
function DeviceNameExists(DeviceName: string): Boolean; override;
function PendingPhone(PhoneNumber: string): TDataSet; override; function PendingPhone(PhoneNumber: string): TDataSet; override;
function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; override; function AddPendingDevice(DeviceName: string; DeviceType: string; PhoneNumber: string): Int64; override;
function DeletePendingDevice(PhoneNumber: string): Int64; override; function DeleteDevice(DeviceId: Integer): Int64; override;
function DeleteDevice(CredentialId: string): Int64; override;
function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; override; function ReserveRedeemCode(PhoneNumber: string; out Code: string): Boolean; override;
function UpdateDeviceUsername(Username: string; CredentialId: string): Int64; override; function UpdateDeviceUsername(Username: string; CredentialId: string): Int64; override;
function ActiveDevice(CredentialId: string): TDataSet; override; function ActiveDevice(CredentialId: string): TDataSet; override;
...@@ -243,6 +243,18 @@ begin ...@@ -243,6 +243,18 @@ begin
Result := uqUnrevokeDevice.RowsAffected; Result := uqUnrevokeDevice.RowsAffected;
end; end;
function TPostgreSQLDeviceDatabase.DeviceNameExists(DeviceName: string): Boolean;
begin
uqDeviceName.Close;
uqDeviceName.ParamByName('NAME').AsString := Trim(DeviceName);
uqDeviceName.Open;
try
Result := not uqDeviceName.IsEmpty;
finally
uqDeviceName.Close;
end;
end;
function TPostgreSQLDeviceDatabase.PendingPhone(PhoneNumber: string): TDataSet; function TPostgreSQLDeviceDatabase.PendingPhone(PhoneNumber: string): TDataSet;
begin begin
uqPendingPhone.Close; uqPendingPhone.Close;
...@@ -261,18 +273,10 @@ begin ...@@ -261,18 +273,10 @@ begin
Result := uqAddPendingDevice.RowsAffected; Result := uqAddPendingDevice.RowsAffected;
end; end;
function TPostgreSQLDeviceDatabase.DeletePendingDevice(PhoneNumber: string): Int64; function TPostgreSQLDeviceDatabase.DeleteDevice(DeviceId: Integer): Int64;
begin
uqDeletePendingDevice.Close;
uqDeletePendingDevice.ParamByName('PHONE').AsString := PhoneNumber;
uqDeletePendingDevice.ExecSQL;
Result := uqDeletePendingDevice.RowsAffected;
end;
function TPostgreSQLDeviceDatabase.DeleteDevice(CredentialId: string): Int64;
begin begin
uqDeleteDevice.Close; uqDeleteDevice.Close;
uqDeleteDevice.ParamByName('CID').AsString := CredentialId; uqDeleteDevice.ParamByName('ID').AsInteger := DeviceId;
uqDeleteDevice.ExecSQL; uqDeleteDevice.ExecSQL;
Result := uqDeleteDevice.RowsAffected; Result := uqDeleteDevice.RowsAffected;
end; end;
......
...@@ -39,8 +39,7 @@ type ...@@ -39,8 +39,7 @@ type
function UnrevokeDevice(const CredentialId: string): TJSONObject; function UnrevokeDevice(const CredentialId: string): TJSONObject;
function AddPendingDevice(const DeviceName, PhoneNumber, function AddPendingDevice(const DeviceName, PhoneNumber,
DeviceType: string): TJSONObject; DeviceType: string): TJSONObject;
function DeletePendingDevice(const PhoneNumber: string): TJSONObject; function DeleteDevice(DeviceId: Integer): TJSONObject;
function DeleteDevice(CredentialId: string): TJSONObject;
function SendAppLink(const PhoneNumber: string): TJSONObject; function SendAppLink(const PhoneNumber: string): TJSONObject;
function UpdateDeviceUsername(const CredentialId, Username: string): TJSONObject; function UpdateDeviceUsername(const CredentialId, Username: string): TJSONObject;
end; end;
......
...@@ -43,8 +43,7 @@ type ...@@ -43,8 +43,7 @@ type
function UnrevokeDevice(const CredentialId: string): TJSONObject; function UnrevokeDevice(const CredentialId: string): TJSONObject;
function AddPendingDevice(const DeviceName, PhoneNumber, function AddPendingDevice(const DeviceName, PhoneNumber,
DeviceType: string): TJSONObject; DeviceType: string): TJSONObject;
function DeletePendingDevice(const PhoneNumber: string): TJSONObject; function DeleteDevice(DeviceId: Integer): TJSONObject;
function DeleteDevice(CredentialId: string): TJSONObject;
function SendAppLink(const PhoneNumber: string): TJSONObject; function SendAppLink(const PhoneNumber: string): TJSONObject;
function UpdateDeviceUsername(const CredentialId, Username: string): TJSONObject; function UpdateDeviceUsername(const CredentialId, Username: string): TJSONObject;
end; end;
...@@ -527,6 +526,13 @@ begin ...@@ -527,6 +526,13 @@ begin
store := CreateDeviceStore; store := CreateDeviceStore;
try try
if store.DeviceNameExists(DeviceName) then
begin
Result.AddPair('status', 'error');
Result.AddPair('message', 'A device with that name already exists. Enter a different device name.');
Exit;
end;
// Reject if a pending entry with this phone already exists // Reject if a pending entry with this phone already exists
q := store.PendingPhone(normalizedPhone); q := store.PendingPhone(normalizedPhone);
if not q.IsEmpty then if not q.IsEmpty then
...@@ -549,50 +555,7 @@ begin ...@@ -549,50 +555,7 @@ begin
end; end;
end; end;
function TApiService.DeletePendingDevice(const PhoneNumber: string): TJSONObject; function TApiService.DeleteDevice(DeviceId: Integer): TJSONObject;
var
affectedRows: Int64;
store: TDeviceStore;
normalizedPhone: string;
begin
RequireAdmin;
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
try
normalizedPhone := NormalizePhoneE164Api(PhoneNumber);
except
on E: Exception do
begin
Result.AddPair('status', 'error');
Result.AddPair('message', E.Message);
Exit;
end;
end;
store := CreateDeviceStore;
try
affectedRows := store.DeletePendingDevice(normalizedPhone);
if affectedRows > 0 then
begin
Logger.Log(2, 'TApiService.DeletePendingDevice - deleted phone: ' + normalizedPhone);
Result.AddPair('status', 'ok');
Result.AddPair('message', 'Pending device removed.');
end
else
begin
Result.AddPair('status', 'error');
Result.AddPair('message', 'Pending device not found.');
end;
finally
store.Free;
end;
end;
function TApiService.DeleteDevice(CredentialId: string): TJSONObject;
var var
affectedRows: Int64; affectedRows: Int64;
store: TDeviceStore; store: TDeviceStore;
...@@ -602,21 +565,20 @@ begin ...@@ -602,21 +565,20 @@ begin
Result := TJSONObject.Create; Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result); TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
if Trim(CredentialId) = '' then if DeviceId <= 0 then
begin begin
Result.AddPair('status', 'error'); Result.AddPair('status', 'error');
Result.AddPair('message', 'CredentialId is required.'); Result.AddPair('message', 'DeviceId is required.');
Exit; Exit;
end; end;
store := CreateDeviceStore; store := CreateDeviceStore;
try try
affectedRows := store.DeleteDevice(Trim(CredentialId)); affectedRows := store.DeleteDevice(DeviceId);
if affectedRows > 0 then if affectedRows > 0 then
begin begin
Logger.Log(2, 'TApiService.DeleteDevice - deleted credential: ' + Logger.Log(2, 'TApiService.DeleteDevice - deleted id: ' + IntToStr(DeviceId));
Copy(CredentialId, 1, 20));
Result.AddPair('status', 'ok'); Result.AddPair('status', 'ok');
Result.AddPair('message', 'Device deleted.'); Result.AddPair('message', 'Device deleted.');
end end
......
...@@ -81,6 +81,15 @@ object FViewDeviceManager: TFViewDeviceManager ...@@ -81,6 +81,15 @@ object FViewDeviceManager: TFViewDeviceManager
WidthPercent = 100.000000000000000000 WidthPercent = 100.000000000000000000
OnClick = btnLogoutClick OnClick = btnLogoutClick
end end
object btnRefresh: TWebButton
Left = 620
Top = 50
Width = 75
Height = 25
Caption = 'Refresh'
ElementID = 'view.devmgr.btnrefresh'
OnClick = btnRefreshClick
end
object XDataWebClient: TXDataWebClient object XDataWebClient: TXDataWebClient
Connection = DMConnection.ApiConnection Connection = DMConnection.ApiConnection
Left = 800 Left = 800
......
<div class="container-fluid p-3 h-100 d-flex flex-column"> <div class="container-fluid p-3 h-100 d-flex flex-column">
<div class="d-flex align-items-center mb-3"> <div class="d-flex align-items-center flex-wrap gap-2 mb-3">
<h5 class="mb-0 me-auto">Device Management</h5> <h5 class="mb-0 me-auto">Device Management</h5>
<button id="view.devmgr.btnlogout" class="btn btn-outline-secondary btn-sm me-2" type="button"> <button id="view.devmgr.btnlogout" class="btn btn-outline-secondary btn-sm me-2" type="button">
Log Out Log Out
</button> </button>
<button id="view.devmgr.btnrefresh" <button id="view.devmgr.btnrefresh"
class="btn btn-outline-secondary btn-sm" class="btn btn-outline-secondary btn-sm"
onclick="document.dispatchEvent(new CustomEvent('devmgr-refresh'))"> type="button">
Refresh Refresh
</button> </button>
</div> </div>
...@@ -26,9 +26,9 @@ ...@@ -26,9 +26,9 @@
<!-- Add pending device --> <!-- Add pending device -->
<div class="card mb-3"> <div class="card mb-3">
<div class="card-body py-2"> <div class="card-body py-2">
<div class="d-flex align-items-start gap-2 flex-wrap"> <div class="row g-2 align-items-start">
<span class="fw-semibold text-nowrap small mt-2">Add Device:</span> <div class="col-12 col-md-auto"><span class="fw-semibold text-nowrap small">Add Device:</span></div>
<div style="width:180px;"> <div class="col-6 col-md-auto">
<input type="text" <input type="text"
id="view.devmgr.newname" id="view.devmgr.newname"
class="form-control form-control-sm" class="form-control form-control-sm"
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
required> required>
<div class="invalid-feedback small">Device name is required.</div> <div class="invalid-feedback small">Device name is required.</div>
</div> </div>
<div style="width:160px;"> <div class="col-6 col-md-auto">
<input type="tel" <input type="tel"
id="view.devmgr.newphone" id="view.devmgr.newphone"
class="form-control form-control-sm" class="form-control form-control-sm"
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
required> required>
<div class="invalid-feedback small">Phone number is required.</div> <div class="invalid-feedback small">Phone number is required.</div>
</div> </div>
<div style="width:140px;"> <div class="col-6 col-md-auto">
<select id="view.devmgr.newtype" <select id="view.devmgr.newtype"
class="form-select form-select-sm" class="form-select form-select-sm"
required> required>
...@@ -56,20 +56,24 @@ ...@@ -56,20 +56,24 @@
</select> </select>
<div class="invalid-feedback small">Device type is required.</div> <div class="invalid-feedback small">Device type is required.</div>
</div> </div>
<button id="view.devmgr.btnadd" <div class="col-6 col-md-auto d-grid">
class="btn btn-primary btn-sm">Add</button> <button id="view.devmgr.btnadd" class="btn btn-primary btn-sm">Add</button>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Device table --> <!-- Device table -->
<div class="table-responsive flex-grow-1"> <div class="flex-grow-1 overflow-auto">
<div id="view.devmgr.cards" class="d-md-none"></div>
<div class="table-responsive d-none d-md-block">
<table class="table table-sm table-hover align-middle" id="view.devmgr.table"> <table class="table table-sm table-hover align-middle" id="view.devmgr.table">
<thead class="table-light sticky-top"> <thead class="table-light sticky-top">
<tr> <tr>
<th style="min-width:130px;">Device Name</th> <th style="min-width:130px;">Device Name</th>
<th style="min-width:100px;">Device Type</th>
<th style="min-width:120px;">Phone</th> <th style="min-width:120px;">Phone</th>
<th style="min-width:145px;">Verification Code</th> <th style="min-width:110px;">Verification Code</th>
<th style="min-width:140px;">Username</th> <th style="min-width:140px;">Username</th>
<th>Browser / User Agent</th> <th>Browser / User Agent</th>
<th style="min-width:135px;">Registered</th> <th style="min-width:135px;">Registered</th>
...@@ -82,6 +86,7 @@ ...@@ -82,6 +86,7 @@
<tbody id="view.devmgr.tbody"> <tbody id="view.devmgr.tbody">
</tbody> </tbody>
</table> </table>
</div>
<p id="view.devmgr.empty" class="text-muted d-none text-center py-4"> <p id="view.devmgr.empty" class="text-muted d-none text-center py-4">
No devices found. No devices found.
</p> </p>
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Client=WC Client=WC
[Settings] [Settings]
LogFileNum=180 LogFileNum=184
webClientVersion=0.9.4.9 webClientVersion=0.9.4.9
MemoLogLevel=3 MemoLogLevel=3
FileLogLevel=4 FileLogLevel=4
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment