Commit 13ffaf77 by Mac Stephens

Update to version 0.9.4.9 - Add device type validation and platform-specific…

Update to version 0.9.4.9 - Add device type validation and platform-specific link handling; standardize archive details styling
parent f1cc5bac
......@@ -37,7 +37,8 @@ type
[HttpGet] function GetDeviceList: TDeviceList;
function RevokeDevice(const CredentialId: string): TJSONObject;
function UnrevokeDevice(const CredentialId: string): TJSONObject;
function AddPendingDevice(const DeviceName, PhoneNumber: string): TJSONObject;
function AddPendingDevice(const DeviceName, PhoneNumber,
DeviceType: string): TJSONObject;
function DeletePendingDevice(const PhoneNumber: string): TJSONObject;
function DeleteDevice(CredentialId: string): TJSONObject;
function SendAppLink(const PhoneNumber: string): TJSONObject;
......
......@@ -43,7 +43,8 @@ type
function GetDeviceList: TDeviceList;
function RevokeDevice(const CredentialId: string): TJSONObject;
function UnrevokeDevice(const CredentialId: string): TJSONObject;
function AddPendingDevice(const DeviceName, PhoneNumber: string): TJSONObject;
function AddPendingDevice(const DeviceName, PhoneNumber,
DeviceType: string): TJSONObject;
function DeletePendingDevice(const PhoneNumber: string): TJSONObject;
function DeleteDevice(CredentialId: string): TJSONObject;
function SendAppLink(const PhoneNumber: string): TJSONObject;
......@@ -1374,7 +1375,7 @@ begin
try
q.Connection := conn;
q.SQL.Text :=
'SELECT id, credential_id, device_name, phone_number, username, ' +
'SELECT id, credential_id, device_name, device_type, phone_number, username, ' +
' user_agent, registered_at, revoked_at, revoked_by, status, ' +
' verification_code, ' +
' COALESCE(status = ''pending'' AND verification_expires_at > NOW(), FALSE) ' +
......@@ -1391,6 +1392,7 @@ begin
item.id := q.FieldByName('id').AsInteger;
item.credential_id := q.FieldByName('credential_id').AsString;
item.device_name := q.FieldByName('device_name').AsString;
item.device_type := q.FieldByName('device_type').AsString;
item.phone_number := q.FieldByName('phone_number').AsString;
item.username := q.FieldByName('username').AsString;
item.user_agent := q.FieldByName('user_agent').AsString;
......@@ -1577,11 +1579,12 @@ begin
Result := '+1' + digits;
end;
function TApiService.AddPendingDevice(const DeviceName, PhoneNumber: string): TJSONObject;
function TApiService.AddPendingDevice(const DeviceName, PhoneNumber,
DeviceType: string): TJSONObject;
var
conn: TUniConnection;
q: TUniQuery;
normalizedPhone: string;
normalizedPhone, normalizedDeviceType: string;
begin
RequireAdmin;
......@@ -1595,6 +1598,17 @@ begin
Exit;
end;
normalizedDeviceType := LowerCase(Trim(DeviceType));
if (normalizedDeviceType <> 'iphone') and
(normalizedDeviceType <> 'android') and
(normalizedDeviceType <> 'browser') and
(normalizedDeviceType <> 'other') then
begin
Result.AddPair('status', 'error');
Result.AddPair('message', 'Please select a valid device type.');
Exit;
end;
try
normalizedPhone := NormalizePhoneE164Api(PhoneNumber);
except
......@@ -1628,9 +1642,11 @@ begin
q.Close;
q.SQL.Text :=
'INSERT INTO lems.device_registrations (device_name, phone_number, status) ' +
'VALUES (:NAME, :PHONE, ''pending'')';
'INSERT INTO lems.device_registrations ' +
'(device_name, device_type, phone_number, status) ' +
'VALUES (:NAME, :TYPE, :PHONE, ''pending'')';
q.ParamByName('NAME').AsString := Trim(DeviceName);
q.ParamByName('TYPE').AsString := normalizedDeviceType;
q.ParamByName('PHONE').AsString := normalizedPhone;
q.ExecSQL;
......
......@@ -45,6 +45,7 @@ type
id: Integer;
credential_id: string;
device_name: string;
device_type: string;
phone_number: string;
username: string;
agency: string;
......
[Settings]
LogFileNum=148
webClientVersion=0.9.4.8
LogFileNum=154
webClientVersion=0.9.4.9
MemoLogLevel=3
FileLogLevel=4
......
......@@ -108,9 +108,9 @@
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>4</VerInfo_Release>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.7;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.2.0;Comments=</VerInfo_Keys>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.9;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.2.0;Comments=</VerInfo_Keys>
<DCC_UnitSearchPath>C:\RADTOOLS\FastMM4;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
<VerInfo_Build>7</VerInfo_Build>
<VerInfo_Build>9</VerInfo_Build>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
......
......@@ -21,7 +21,7 @@ type
procedure VerifyVersion(SuccessProc: TSuccessProc);
public
property WsUrl: string read FWsUrl;
const clientVersion = '0.9.4.8';
const clientVersion = '0.9.4.9';
procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
end;
......
......@@ -13,7 +13,7 @@ object FViewComplaintArchive: TFViewComplaintArchive
ElementHeaderClassName = 'table-light'
ElementTableClassName =
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'gn-middle'
'gn-middle small'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.DropDownElementClassName = 'form-control'
......
......@@ -29,7 +29,7 @@
<div id="cdetails_summary_arc" class="collapse show">
<div class="card border-0 shadow-sm mb-2">
<div class="card-body py-2">
<table class="table table-sm mb-0 w-100">
<table class="table table-sm mb-0 w-100 small">
<tbody>
<tr>
<th scope="row" class="fw-semibold text-nowrap pe-2 w-auto">Priority:</th>
......
......@@ -26,18 +26,36 @@
<!-- Add pending device -->
<div class="card mb-3">
<div class="card-body py-2">
<div class="d-flex align-items-center gap-2 flex-wrap">
<span class="fw-semibold text-nowrap small">Add Device:</span>
<input type="text"
id="view.devmgr.newname"
class="form-control form-control-sm"
placeholder="Device name"
style="max-width:180px;">
<input type="tel"
id="view.devmgr.newphone"
class="form-control form-control-sm"
placeholder="(303) 555-1234"
style="max-width:160px;">
<div class="d-flex align-items-start gap-2 flex-wrap">
<span class="fw-semibold text-nowrap small mt-2">Add Device:</span>
<div style="width:180px;">
<input type="text"
id="view.devmgr.newname"
class="form-control form-control-sm"
placeholder="Device name"
required>
<div class="invalid-feedback small">Device name is required.</div>
</div>
<div style="width:160px;">
<input type="tel"
id="view.devmgr.newphone"
class="form-control form-control-sm"
placeholder="(303) 555-1234"
required>
<div class="invalid-feedback small">Phone number is required.</div>
</div>
<div style="width:140px;">
<select id="view.devmgr.newtype"
class="form-select form-select-sm"
required>
<option value="" selected>Device type</option>
<option value="iphone">iPhone</option>
<option value="android">Android</option>
<option value="browser">Browser</option>
<option value="other">Other</option>
</select>
<div class="invalid-feedback small">Device type is required.</div>
</div>
<button id="view.devmgr.btnadd"
class="btn btn-primary btn-sm">Add</button>
</div>
......@@ -69,6 +87,23 @@
</p>
</div>
<!-- Information modal used by Utils.ShowInformationModal -->
<div class="modal fade" id="main_information_modal" tabindex="-1"
aria-labelledby="main_information_modal_title" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-sm mx-auto px-3">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="main_information_modal_title">Information</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body fs-6 fw-bold" id="main_information_modal_body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-primary w-100" data-bs-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>
<!-- Section: Confirmation modal used by Utils.ShowConfirmationModal -->
<div class="modal fade" id="main_confirmation_modal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
......
......@@ -25,8 +25,9 @@ type
procedure ShowNotification(const AMsg: string; AIsError: Boolean = True);
procedure HideNotification;
procedure ClearTable;
procedure AddDeviceRow(const ACredentialId, AName, APhoneNumber, AUsername,
AUserAgent, ARegisteredAt, AStatus, AVerificationCode: string;
procedure AddDeviceRow(const ACredentialId, AName, ADeviceType,
APhoneNumber, AUsername, AUserAgent, ARegisteredAt, AStatus,
AVerificationCode: string;
AVerificationCodeActive: Boolean);
[async] procedure LoadDevices;
[async] procedure RevokeDevice(const ACredentialId, AName: string);
......@@ -73,10 +74,18 @@ begin
LoadDevices;
asm
var nameInput = document.getElementById('view.devmgr.newname');
if (nameInput) {
nameInput.addEventListener('input', function() {
nameInput.classList.remove('is-invalid');
});
}
// Format phone input on-the-fly
var inp = document.getElementById('view.devmgr.newphone');
if (inp) {
inp.addEventListener('input', function() {
inp.classList.remove('is-invalid');
var digits = inp.value.replace(/\D/g, '');
if (digits.length > 10) digits = digits.slice(0, 10);
var fmt = '';
......@@ -89,6 +98,13 @@ begin
inp.value = fmt;
});
}
var typeSelect = document.getElementById('view.devmgr.newtype');
if (typeSelect) {
typeSelect.addEventListener('change', function() {
typeSelect.classList.remove('is-invalid');
});
}
end;
end;
......@@ -135,14 +151,14 @@ begin
end;
procedure TFViewDeviceManager.AddDeviceRow(const ACredentialId, AName,
APhoneNumber, AUsername, AUserAgent, ARegisteredAt, AStatus,
ADeviceType, APhoneNumber, AUsername, AUserAgent, ARegisteredAt, AStatus,
AVerificationCode: string; AVerificationCodeActive: Boolean);
var
tbody, tr, tdName, tdPhone, tdCode, tdUser, tdAgent, tdReg, tdStatus,
tdAccess, tdDelete, tdSend: TJSHTMLElement;
btn, btnSend, inp: TJSHTMLElement;
displayDate, displayPhone: string;
isPending, isRevoked, isActive: Boolean;
isPending, isRevoked, isActive, isIPhone, isAndroid: Boolean;
begin
tbody := TJSHTMLElement(document.getElementById('view.devmgr.tbody'));
if not Assigned(tbody) then Exit;
......@@ -150,6 +166,8 @@ begin
isPending := AStatus = 'pending';
isRevoked := AStatus = 'revoked';
isActive := AStatus = 'active';
isIPhone := SameText(ADeviceType, 'iphone');
isAndroid := SameText(ADeviceType, 'android');
displayPhone := FormatPhoneDisplay(APhoneNumber);
tr := TJSHTMLElement(document.createElement('tr'));
......@@ -288,14 +306,18 @@ begin
// Section: Send link action
tdSend := TJSHTMLElement(document.createElement('td'));
if (not isRevoked) and (APhoneNumber <> '') then
if (not isRevoked) and (APhoneNumber <> '') and (isIPhone or isAndroid) then
begin
btnSend := TJSHTMLElement(document.createElement('button'));
btnSend.className := 'btn btn-outline-primary btn-sm';
btnSend.innerText := 'Send Link';
btnSend.addEventListener('click', procedure(Event: TJSMouseEvent)
begin
SendAppLink(APhoneNumber, AName);
if isAndroid then
ShowInformationModal('Android App',
'Android app installation is not available yet.')
else
SendAppLink(APhoneNumber, AName);
end);
tdSend.appendChild(btnSend);
end
......@@ -337,6 +359,7 @@ begin
AddDeviceRow(
string(item['credential_id']),
string(item['device_name']),
string(item['device_type']),
string(item['phone_number']),
string(item['username']),
string(item['user_agent']),
......@@ -517,27 +540,56 @@ end;
procedure TFViewDeviceManager.AddPendingDevice;
var
deviceName, phoneNumber: string;
deviceName, phoneNumber, deviceType: string;
nameElement, phoneElement, typeElement: TJSHTMLElement;
resp: TXDataClientResponse;
res: TJSObject;
status: string;
valid: Boolean;
begin
deviceName := Trim(edtNewDeviceName.Text);
phoneNumber := Trim(edtNewPhoneNumber.Text);
nameElement := TJSHTMLElement(document.getElementById('view.devmgr.newname'));
phoneElement := TJSHTMLElement(document.getElementById('view.devmgr.newphone'));
typeElement := TJSHTMLElement(document.getElementById('view.devmgr.newtype'));
deviceType := '';
if Assigned(typeElement) then
deviceType := Trim(string(TJSHTMLSelectElement(typeElement).value));
valid := True;
if Assigned(nameElement) then
nameElement.classList.remove('is-invalid');
if Assigned(phoneElement) then
phoneElement.classList.remove('is-invalid');
if Assigned(typeElement) then
typeElement.classList.remove('is-invalid');
if deviceName = '' then
begin
ShowNotification('Please enter a device name.');
Exit;
if Assigned(nameElement) then
nameElement.classList.add('is-invalid');
valid := False;
end;
if phoneNumber = '' then
begin
ShowNotification('Please enter a phone number.');
Exit;
if Assigned(phoneElement) then
phoneElement.classList.add('is-invalid');
valid := False;
end;
if deviceType = '' then
begin
if Assigned(typeElement) then
typeElement.classList.add('is-invalid');
valid := False;
end;
if not valid then
Exit;
try
resp := await(XDataWebClient.RawInvokeAsync('IApiService.AddPendingDevice', [deviceName, phoneNumber]));
resp := await(XDataWebClient.RawInvokeAsync('IApiService.AddPendingDevice',
[deviceName, phoneNumber, deviceType]));
res := TJSObject(resp.Result);
status := string(res['status']);
......@@ -545,6 +597,7 @@ begin
begin
edtNewDeviceName.Text := '';
edtNewPhoneNumber.Text := '';
TJSHTMLSelectElement(typeElement).value := '';
ShowNotification('Device "' + deviceName + '" added — waiting for user to register.', False);
LoadDevices;
end
......
......@@ -91,12 +91,12 @@
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.7;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.3.0;Comments=;LastCompiledTime=2018/08/27 15:18:29</VerInfo_Keys>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.4.9;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.3.0;Comments=;LastCompiledTime=2018/08/27 15:18:29</VerInfo_Keys>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_Release>4</VerInfo_Release>
<VerInfo_Build>7</VerInfo_Build>
<VerInfo_Build>9</VerInfo_Build>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSWebBrowser>1</TMSWebBrowser>
<TMSWebOutputPath>..\emiMobileServer\bin\static</TMSWebOutputPath>
......
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