Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
emiMobile
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Mac Stephens
emiMobile
Commits
f1cc5bac
Commit
f1cc5bac
authored
Oct 09, 2026
by
Mac Stephens
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update to version 0.9.4.8 - Improve device management, verification, and live updates
parent
6a8f477c
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
27 changed files
with
360 additions
and
149 deletions
+360
-149
Api.Database.dfm
emiMobileServer/Source/Api.Database.dfm
+13
-0
Api.Database.pas
emiMobileServer/Source/Api.Database.pas
+22
-0
Api.Service.pas
emiMobileServer/Source/Api.Service.pas
+1
-0
Api.ServiceImpl.pas
emiMobileServer/Source/Api.ServiceImpl.pas
+64
-3
Auth.Service.pas
emiMobileServer/Source/Auth.Service.pas
+4
-2
Auth.ServiceImpl.pas
emiMobileServer/Source/Auth.ServiceImpl.pas
+0
-0
Common.Config.pas
emiMobileServer/Source/Common.Config.pas
+2
-2
Main.dfm
emiMobileServer/Source/Main.dfm
+1
-1
Main.pas
emiMobileServer/Source/Main.pas
+2
-0
Ws.DataModel.pas
emiMobileServer/Source/Ws.DataModel.pas
+69
-19
emiMobileServer.ini
emiMobileServer/bin/emiMobileServer.ini
+7
-5
twilioinfo.txt
twilioinfo.txt
+0
-4
Auth.Service.pas
webEMIMobile/Auth.Service.pas
+8
-9
ConnectionModule.pas
webEMIMobile/ConnectionModule.pas
+1
-1
Utils.pas
webEMIMobile/Utils.pas
+2
-2
View.ComplaintDetails.dfm
webEMIMobile/View.ComplaintDetails.dfm
+8
-7
View.ComplaintDetails.html
webEMIMobile/View.ComplaintDetails.html
+1
-1
View.Complaints.dfm
webEMIMobile/View.Complaints.dfm
+8
-1
View.Complaints.pas
webEMIMobile/View.Complaints.pas
+1
-0
View.DeviceManager.html
webEMIMobile/View.DeviceManager.html
+21
-1
View.DeviceManager.pas
webEMIMobile/View.DeviceManager.pas
+109
-40
View.DeviceRegistration.pas
webEMIMobile/View.DeviceRegistration.pas
+2
-5
View.Login.pas
webEMIMobile/View.Login.pas
+4
-43
View.Main.html
webEMIMobile/View.Main.html
+3
-1
View.Main.pas
webEMIMobile/View.Main.pas
+5
-0
View.UnitDetails.dfm
webEMIMobile/View.UnitDetails.dfm
+1
-1
View.UnitDetails.html
webEMIMobile/View.UnitDetails.html
+1
-1
No files found.
emiMobileServer/Source/Api.Database.dfm
View file @
f1cc5bac
...
@@ -420,6 +420,7 @@ object ApiDatabaseModule: TApiDatabaseModule
...
@@ -420,6 +420,7 @@ object ApiDatabaseModule: TApiDatabaseModule
' ca.COMPLAINT,'
' ca.COMPLAINT,'
' ca.AGENCY,'
' ca.AGENCY,'
' cdc.CODE_DESC AS DISPATCH_CODE_DESC,'
' cdc.CODE_DESC AS DISPATCH_CODE_DESC,'
' cdc.CATEGORY AS DISPATCHCODECATEGORY,'
' ca.SOURCE,'
' ca.SOURCE,'
' ccs.CODE_DESC AS SOURCE_DESC,'
' ccs.CODE_DESC AS SOURCE_DESC,'
' ca.PRIORITY,'
' ca.PRIORITY,'
...
@@ -485,6 +486,11 @@ object ApiDatabaseModule: TApiDatabaseModule
...
@@ -485,6 +486,11 @@ object ApiDatabaseModule: TApiDatabaseModule
ReadOnly = True
ReadOnly = True
Size = 60
Size = 60
end
end
object uqComplaintListdispatchcodecategory: TStringField
FieldName = 'dispatchcodecategory'
ReadOnly = True
Size = 30
end
object uqComplaintListsource: TStringField
object uqComplaintListsource: TStringField
FieldName = 'source'
FieldName = 'source'
Size = 6
Size = 6
...
@@ -591,6 +597,12 @@ object ApiDatabaseModule: TApiDatabaseModule
...
@@ -591,6 +597,12 @@ object ApiDatabaseModule: TApiDatabaseModule
FieldName = 'complaintNumber'
FieldName = 'complaintNumber'
Calculated = True
Calculated = True
end
end
object uqComplaintListpngName: TStringField
FieldKind = fkCalculated
FieldName = 'pngName'
Size = 80
Calculated = True
end
end
end
object uqComplaintDetails: TUniQuery
object uqComplaintDetails: TUniQuery
Connection = ucENTCAD
Connection = ucENTCAD
...
@@ -1333,6 +1345,7 @@ object ApiDatabaseModule: TApiDatabaseModule
...
@@ -1333,6 +1345,7 @@ object ApiDatabaseModule: TApiDatabaseModule
' dul.log as LOG_TEXT'
' dul.log as LOG_TEXT'
'from DIS_UNIT_LOG dul'
'from DIS_UNIT_LOG dul'
'where dul.unitid = :UNITID'
'where dul.unitid = :UNITID'
' and dul.timestamp > current_timestamp - interval ''24 hours'''
'order by dul.timestamp desc')
'order by dul.timestamp desc')
ReadOnly = True
ReadOnly = True
Left = 198
Left = 198
...
...
emiMobileServer/Source/Api.Database.pas
View file @
f1cc5bac
...
@@ -32,6 +32,7 @@ type
...
@@ -32,6 +32,7 @@ type
uqComplaintListcomplaint
:
TStringField
;
uqComplaintListcomplaint
:
TStringField
;
uqComplaintListagency
:
TStringField
;
uqComplaintListagency
:
TStringField
;
uqComplaintListdispatch_code_desc
:
TStringField
;
uqComplaintListdispatch_code_desc
:
TStringField
;
uqComplaintListdispatchcodecategory
:
TStringField
;
uqComplaintListsource
:
TStringField
;
uqComplaintListsource
:
TStringField
;
uqComplaintListsource_desc
:
TStringField
;
uqComplaintListsource_desc
:
TStringField
;
uqComplaintListpriority
:
TStringField
;
uqComplaintListpriority
:
TStringField
;
...
@@ -59,6 +60,7 @@ type
...
@@ -59,6 +60,7 @@ type
uqComplaintListdistrict_desc
:
TStringField
;
uqComplaintListdistrict_desc
:
TStringField
;
uqComplaintListsector_desc
:
TStringField
;
uqComplaintListsector_desc
:
TStringField
;
uqComplaintListcomplaintNumber
:
TStringField
;
uqComplaintListcomplaintNumber
:
TStringField
;
uqComplaintListpngName
:
TStringField
;
uqComplaintDetailscomplaintid
:
TFloatField
;
uqComplaintDetailscomplaintid
:
TFloatField
;
uqComplaintDetailscfsid
:
TFloatField
;
uqComplaintDetailscfsid
:
TFloatField
;
uqComplaintDetailscomplaint
:
TStringField
;
uqComplaintDetailscomplaint
:
TStringField
;
...
@@ -322,6 +324,8 @@ end;
...
@@ -322,6 +324,8 @@ end;
procedure
TApiDatabaseModule
.
uqComplaintListCalcFields
(
DataSet
:
TDataSet
);
procedure
TApiDatabaseModule
.
uqComplaintListCalcFields
(
DataSet
:
TDataSet
);
var
var
raw
:
string
;
raw
:
string
;
rawCategory
:
string
;
statusKey
:
string
;
begin
begin
raw
:=
uqComplaintListCOMPLAINT
.
AsString
.
Trim
;
raw
:=
uqComplaintListCOMPLAINT
.
AsString
.
Trim
;
...
@@ -331,6 +335,21 @@ begin
...
@@ -331,6 +335,21 @@ begin
uqComplaintListcomplaintNumber
.
AsString
:=
raw
+
'-'
uqComplaintListcomplaintNumber
.
AsString
:=
raw
+
'-'
else
else
uqComplaintListcomplaintNumber
.
AsString
:=
raw
;
uqComplaintListcomplaintNumber
.
AsString
:=
raw
;
rawCategory
:=
uqComplaintListdispatchcodecategory
.
AsString
;
if
not
uqComplaintListdatearrived
.
IsNull
then
statusKey
:=
'onscene'
else
if
not
uqComplaintListdatedispatched
.
IsNull
then
statusKey
:=
'attached'
else
statusKey
:=
'notattached'
;
if
Trim
(
rawCategory
)
=
''
then
uqComplaintListpngName
.
AsString
:=
Format
(
'default_%s.png'
,
[
statusKey
])
else
uqComplaintListpngName
.
AsString
:=
Format
(
'%s_%s.png'
,
[
HandleUniqueFilenames
(
rawCategory
),
statusKey
]);
end
;
end
;
...
@@ -388,6 +407,9 @@ end;
...
@@ -388,6 +407,9 @@ end;
procedure
TApiDatabaseModule
.
UniAlerter1Event
(
Sender
:
TDAAlerter
;
const
EventName
,
Message
:
string
);
procedure
TApiDatabaseModule
.
UniAlerter1Event
(
Sender
:
TDAAlerter
;
const
EventName
,
Message
:
string
);
begin
begin
// ADD THIS DELETE BEFORE DEPLOY
Logger
.
Log
(
1
,
Format
(
'PostgreSQL alerter event received: "%s"'
,
[
EventName
]));
if
SameText
(
EventName
,
'disupdate'
)
then
if
SameText
(
EventName
,
'disupdate'
)
then
MarkCADUpdate
;
MarkCADUpdate
;
end
;
end
;
...
...
emiMobileServer/Source/Api.Service.pas
View file @
f1cc5bac
...
@@ -39,6 +39,7 @@ type
...
@@ -39,6 +39,7 @@ type
function
UnrevokeDevice
(
const
CredentialId
:
string
):
TJSONObject
;
function
UnrevokeDevice
(
const
CredentialId
:
string
):
TJSONObject
;
function
AddPendingDevice
(
const
DeviceName
,
PhoneNumber
:
string
):
TJSONObject
;
function
AddPendingDevice
(
const
DeviceName
,
PhoneNumber
:
string
):
TJSONObject
;
function
DeletePendingDevice
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
DeletePendingDevice
(
const
PhoneNumber
:
string
):
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
;
...
...
emiMobileServer/Source/Api.ServiceImpl.pas
View file @
f1cc5bac
...
@@ -45,6 +45,7 @@ type
...
@@ -45,6 +45,7 @@ type
function
UnrevokeDevice
(
const
CredentialId
:
string
):
TJSONObject
;
function
UnrevokeDevice
(
const
CredentialId
:
string
):
TJSONObject
;
function
AddPendingDevice
(
const
DeviceName
,
PhoneNumber
:
string
):
TJSONObject
;
function
AddPendingDevice
(
const
DeviceName
,
PhoneNumber
:
string
):
TJSONObject
;
function
DeletePendingDevice
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
DeletePendingDevice
(
const
PhoneNumber
:
string
):
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
;
...
@@ -425,6 +426,7 @@ begin
...
@@ -425,6 +426,7 @@ begin
item
.
AddPair
(
'Agency'
,
ApiDB
.
uqComplaintListAGENCY
.
AsString
);
item
.
AddPair
(
'Agency'
,
ApiDB
.
uqComplaintListAGENCY
.
AsString
);
item
.
AddPair
(
'Priority'
,
ApiDB
.
uqComplaintListPRIORITY
.
AsString
);
item
.
AddPair
(
'Priority'
,
ApiDB
.
uqComplaintListPRIORITY
.
AsString
);
item
.
AddPair
(
'DispatchCodeDesc'
,
ApiDB
.
uqComplaintListDISPATCH_CODE_DESC
.
AsString
);
item
.
AddPair
(
'DispatchCodeDesc'
,
ApiDB
.
uqComplaintListDISPATCH_CODE_DESC
.
AsString
);
item
.
AddPair
(
'pngName'
,
ApiDB
.
uqComplaintListpngName
.
AsString
);
item
.
AddPair
(
'Address'
,
ApiDB
.
uqComplaintListADDRESS
.
AsString
);
item
.
AddPair
(
'Address'
,
ApiDB
.
uqComplaintListADDRESS
.
AsString
);
item
.
AddPair
(
'Business'
,
ApiDB
.
uqComplaintListBUSINESS
.
AsString
);
item
.
AddPair
(
'Business'
,
ApiDB
.
uqComplaintListBUSINESS
.
AsString
);
item
.
AddPair
(
'CFSId'
,
ApiDB
.
uqComplaintListCFSID
.
AsString
);
item
.
AddPair
(
'CFSId'
,
ApiDB
.
uqComplaintListCFSID
.
AsString
);
...
@@ -1325,9 +1327,15 @@ begin
...
@@ -1325,9 +1327,15 @@ begin
conn
.
Free
;
conn
.
Free
;
end
;
end
;
if
deviceStatus
=
'
revoked
'
then
if
deviceStatus
=
''
then
begin
begin
Logger
.
Log
(
2
,
'RequireActiveDevice - rejected revoked credential: '
+
Copy
(
credId
,
1
,
20
));
Logger
.
Log
(
2
,
'RequireActiveDevice - rejected missing credential: '
+
Copy
(
credId
,
1
,
20
));
raise
EXDataHttpException
.
Create
(
401
,
'Device registration was not found.'
);
end
;
if
not
SameText
(
deviceStatus
,
'active'
)
then
begin
Logger
.
Log
(
2
,
'RequireActiveDevice - rejected inactive credential: '
+
Copy
(
credId
,
1
,
20
));
raise
EXDataHttpException
.
Create
(
401
,
'Device access has been revoked.'
);
raise
EXDataHttpException
.
Create
(
401
,
'Device access has been revoked.'
);
end
;
end
;
end
;
end
;
...
@@ -1367,7 +1375,10 @@ begin
...
@@ -1367,7 +1375,10 @@ begin
q
.
Connection
:=
conn
;
q
.
Connection
:=
conn
;
q
.
SQL
.
Text
:=
q
.
SQL
.
Text
:=
'SELECT id, credential_id, device_name, phone_number, username, '
+
'SELECT id, credential_id, device_name, phone_number, username, '
+
' user_agent, registered_at, revoked_at, revoked_by, status '
+
' user_agent, registered_at, revoked_at, revoked_by, status, '
+
' verification_code, '
+
' COALESCE(status = ''pending'' AND verification_expires_at > NOW(), FALSE) '
+
' AS verification_code_active '
+
'FROM lems.device_registrations '
+
'FROM lems.device_registrations '
+
'ORDER BY registered_at DESC'
;
'ORDER BY registered_at DESC'
;
q
.
Open
;
q
.
Open
;
...
@@ -1390,6 +1401,8 @@ begin
...
@@ -1390,6 +1401,8 @@ begin
item
.
revoked_at
:=
q
.
FieldByName
(
'revoked_at'
).
AsString
;
item
.
revoked_at
:=
q
.
FieldByName
(
'revoked_at'
).
AsString
;
item
.
revoked_by
:=
q
.
FieldByName
(
'revoked_by'
).
AsString
;
item
.
revoked_by
:=
q
.
FieldByName
(
'revoked_by'
).
AsString
;
item
.
status
:=
q
.
FieldByName
(
'status'
).
AsString
;
item
.
status
:=
q
.
FieldByName
(
'status'
).
AsString
;
item
.
verification_code
:=
q
.
FieldByName
(
'verification_code'
).
AsString
;
item
.
verification_code_active
:=
q
.
FieldByName
(
'verification_code_active'
).
AsBoolean
;
Result
.
data
.
Add
(
item
);
Result
.
data
.
Add
(
item
);
q
.
Next
;
q
.
Next
;
...
@@ -1684,6 +1697,54 @@ begin
...
@@ -1684,6 +1697,54 @@ begin
end
;
end
;
end
;
end
;
function
TApiService
.
DeleteDevice
(
CredentialId
:
string
):
TJSONObject
;
var
conn
:
TUniConnection
;
q
:
TUniQuery
;
begin
RequireAdmin
;
Result
:=
TJSONObject
.
Create
;
TXDataOperationContext
.
Current
.
Handler
.
ManagedObjects
.
Add
(
Result
);
if
Trim
(
CredentialId
)
=
''
then
begin
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
'CredentialId is required.'
);
Exit
;
end
;
conn
:=
OpenLemsConnection
;
try
q
:=
TUniQuery
.
Create
(
nil
);
try
q
.
Connection
:=
conn
;
q
.
SQL
.
Text
:=
'DELETE FROM lems.device_registrations '
+
'WHERE credential_id = :CID'
;
q
.
ParamByName
(
'CID'
).
AsString
:=
Trim
(
CredentialId
);
q
.
ExecSQL
;
if
q
.
RowsAffected
>
0
then
begin
Logger
.
Log
(
2
,
'TApiService.DeleteDevice - deleted credential: '
+
Copy
(
CredentialId
,
1
,
20
));
Result
.
AddPair
(
'status'
,
'ok'
);
Result
.
AddPair
(
'message'
,
'Device deleted.'
);
end
else
begin
Result
.
AddPair
(
'status'
,
'error'
);
Result
.
AddPair
(
'message'
,
'Device not found.'
);
end
;
finally
q
.
Free
;
end
;
finally
conn
.
Free
;
end
;
end
;
function
TApiService
.
SendAppLink
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
TApiService
.
SendAppLink
(
const
PhoneNumber
:
string
):
TJSONObject
;
var
var
conn
:
TUniConnection
;
conn
:
TUniConnection
;
...
...
emiMobileServer/Source/Auth.Service.pas
View file @
f1cc5bac
...
@@ -53,6 +53,8 @@ type
...
@@ -53,6 +53,8 @@ type
revoked_at
:
string
;
revoked_at
:
string
;
revoked_by
:
string
;
revoked_by
:
string
;
status
:
string
;
status
:
string
;
verification_code
:
string
;
verification_code_active
:
Boolean
;
end
;
end
;
TDeviceList
=
class
TDeviceList
=
class
...
@@ -78,8 +80,8 @@ type
...
@@ -78,8 +80,8 @@ type
// Section: Device registration step 1 checks phone pre-auth and sends an SMS code
// Section: Device registration step 1 checks phone pre-auth and sends an SMS code
function
BeginRegistration
(
const
PhoneNumber
:
string
):
TJSONObject
;
function
BeginRegistration
(
const
PhoneNumber
:
string
):
TJSONObject
;
// Section: Device registration step 2 verifies the SMS code and returns a challenge
// Section: Device registration step 2 verifies the SMS code and returns a challenge
function
VerifyRegistrationCode
(
const
PhoneNumber
,
VerificationCode
,
function
VerifyRegistrationCode
(
const
PhoneNumber
,
Verification
Token
:
string
):
TJSONObject
;
Verification
Code
:
string
):
TJSONObject
;
// Section: WebAuthn registration verifies the credential and activates the pending row
// Section: WebAuthn registration verifies the credential and activates the pending row
function
CompleteRegistration
(
const
PhoneNumber
,
CredentialId
,
function
CompleteRegistration
(
const
PhoneNumber
,
CredentialId
,
AttestationObject
,
ClientDataJSON
,
AttestationObject
,
ClientDataJSON
,
...
...
emiMobileServer/Source/Auth.ServiceImpl.pas
View file @
f1cc5bac
This diff is collapsed.
Click to expand it.
emiMobileServer/Source/Common.Config.pas
View file @
f1cc5bac
unit
Common
.
Config
;
unit
Common
.
Config
;
interface
interface
const
const
defaultServerUrl
=
'http://
0.0.0.0
:2009/emiMobile/'
;
defaultServerUrl
=
'http://
localhost
:2009/emiMobile/'
;
type
type
TServerConfig
=
class
TServerConfig
=
class
...
...
emiMobileServer/Source/Main.dfm
View file @
f1cc5bac
...
@@ -94,7 +94,7 @@ object FMain: TFMain
...
@@ -94,7 +94,7 @@ object FMain: TFMain
Top = 18
Top = 18
Width = 141
Width = 141
Height = 25
Height = 25
Caption = '
Refresh
Client'
Caption = '
Disconnect
Client'
TabOrder = 0
TabOrder = 0
OnClick = btnDisconnectClientClick
OnClick = btnDisconnectClientClick
end
end
...
...
emiMobileServer/Source/Main.pas
View file @
f1cc5bac
...
@@ -178,6 +178,8 @@ begin
...
@@ -178,6 +178,8 @@ begin
Logger
.
Log
(
1
,
''
);
Logger
.
Log
(
1
,
''
);
Logger
.
Log
(
1
,
'--- Settings ---'
);
Logger
.
Log
(
1
,
'--- Settings ---'
);
Logger
.
Log
(
1
,
LogValue
(
'--Settings->MemoLogLevel'
,
IniEntries
.
MemoLogLevel
.
ToString
,
IniEntries
.
MemoLogLevelFromIni
));
Logger
.
Log
(
1
,
LogValue
(
'--Settings->FileLogLevel'
,
IniEntries
.
FileLogLevel
.
ToString
,
IniEntries
.
FileLogLevelFromIni
));
Logger
.
Log
(
1
,
LogValue
(
'--Settings->LogFileNum'
,
IniEntries
.
LogFileNum
.
ToString
,
IniEntries
.
LogFileNumFromIni
));
Logger
.
Log
(
1
,
LogValue
(
'--Settings->LogFileNum'
,
IniEntries
.
LogFileNum
.
ToString
,
IniEntries
.
LogFileNumFromIni
));
Logger
.
Log
(
1
,
LogValue
(
'--Settings->webClientVersion'
,
IniEntries
.
WebClientVersion
,
IniEntries
.
WebClientVersionFromIni
));
Logger
.
Log
(
1
,
LogValue
(
'--Settings->webClientVersion'
,
IniEntries
.
WebClientVersion
,
IniEntries
.
WebClientVersionFromIni
));
...
...
emiMobileServer/Source/Ws.DataModel.pas
View file @
f1cc5bac
unit
Ws
.
DataModel
;
unit
Ws
.
DataModel
;
// Server-side WebSocket data model.
// Server-side WebSocket data model.
//
Owns a worker thread that checks every FIntervalMs milliseconds, queries the
//
Note: Owns a main-thread PostgreSQL notification listener and a worker thread that
//
database for the five data sets used by the polling timers that existed in
//
queries the database for the five data sets used by the polling timers that
// e
ach connected browser client, and broadcasts the results to every
// e
xisted in each connected browser client, then broadcasts the results to
// handshaked WebSocket connection via the supplied broadcast callback.
//
every
handshaked WebSocket connection via the supplied broadcast callback.
//
//
// All five result shapes mirror what the XData API methods return so that the
// All five result shapes mirror what the XData API methods return so that the
// client-side parsing code can be shared between the initial HTTP load and the
// client-side parsing code can be shared between the initial HTTP load and the
...
@@ -16,6 +16,7 @@ uses
...
@@ -16,6 +16,7 @@ uses
System
.
SysUtils
,
System
.
Classes
,
System
.
JSON
,
System
.
SysUtils
,
System
.
Classes
,
System
.
JSON
,
System
.
Generics
.
Collections
,
System
.
SyncObjs
,
System
.
Generics
.
Collections
,
System
.
SyncObjs
,
Data
.
DB
,
Data
.
DB
,
Vcl
.
ExtCtrls
,
Api
.
Database
,
Api
.
Database
,
WsMessages
,
WsMessages
,
Common
.
Logging
;
Common
.
Logging
;
...
@@ -26,20 +27,23 @@ type
...
@@ -26,20 +27,23 @@ type
TWsDataModel
=
class
TWsDataModel
=
class
private
private
FDb
:
TApiDatabaseModule
;
FDb
:
TApiDatabaseModule
;
FListenerDb
:
TApiDatabaseModule
;
FListenerTimer
:
TTimer
;
FThread
:
TThread
;
FThread
:
TThread
;
FStopEvent
:
TEvent
;
FStopEvent
:
TEvent
;
FIntervalMs
:
Integer
;
FIntervalMs
:
Integer
;
FBroadcast
:
TWsBroadcastProc
;
FBroadcast
:
TWsBroadcastProc
;
procedure
ListenerTimerFire
(
Sender
:
TObject
);
procedure
Execute
;
procedure
Execute
;
procedure
ConfigureQueryTimeouts
;
procedure
ConfigureQueryTimeouts
;
procedure
BroadcastAll
;
procedure
BroadcastAll
;
function
BuildBadgeCountsJson
:
string
;
function
BuildBadgeCountsJson
:
string
;
function
BuildUnitMapJson
:
string
;
function
BuildUnitMapJson
:
string
;
function
BuildComplaintMapJson
:
string
;
function
BuildComplaintMapJson
(
out
AComplaintCount
:
Integer
)
:
string
;
function
BuildUnitListJson
:
string
;
function
BuildUnitListJson
:
string
;
function
BuildComplaintListJson
:
string
;
function
BuildComplaintListJson
(
out
AComplaintCount
:
Integer
)
:
string
;
public
public
constructor
Create
(
ABroadcast
:
TWsBroadcastProc
;
AIntervalMs
:
Integer
=
5000
);
constructor
Create
(
ABroadcast
:
TWsBroadcastProc
;
AIntervalMs
:
Integer
=
5000
);
destructor
Destroy
;
override
;
destructor
Destroy
;
override
;
...
@@ -61,8 +65,13 @@ begin
...
@@ -61,8 +65,13 @@ begin
FBroadcast
:=
ABroadcast
;
FBroadcast
:=
ABroadcast
;
FIntervalMs
:=
AIntervalMs
;
FIntervalMs
:=
AIntervalMs
;
FStopEvent
:=
TEvent
.
Create
(
nil
,
False
,
False
,
''
);
FStopEvent
:=
TEvent
.
Create
(
nil
,
False
,
False
,
''
);
FDb
:=
TApiDatabaseModule
.
Create
(
nil
);
FDb
:=
nil
;
ConfigureQueryTimeouts
;
FListenerDb
:=
TApiDatabaseModule
.
Create
(
nil
);
FListenerTimer
:=
TTimer
.
Create
(
nil
);
FListenerTimer
.
Interval
:=
FIntervalMs
;
FListenerTimer
.
OnTimer
:=
ListenerTimerFire
;
FListenerTimer
.
Enabled
:=
True
;
ListenerTimerFire
(
nil
);
FThread
:=
TThread
.
CreateAnonymousThread
(
Execute
);
FThread
:=
TThread
.
CreateAnonymousThread
(
Execute
);
FThread
.
FreeOnTerminate
:=
False
;
FThread
.
FreeOnTerminate
:=
False
;
FThread
.
Start
;
FThread
.
Start
;
...
@@ -70,22 +79,34 @@ end;
...
@@ -70,22 +79,34 @@ end;
destructor
TWsDataModel
.
Destroy
;
destructor
TWsDataModel
.
Destroy
;
begin
begin
FListenerTimer
.
Enabled
:=
False
;
FListenerTimer
.
Free
;
FThread
.
Terminate
;
FThread
.
Terminate
;
FStopEvent
.
SetEvent
;
FStopEvent
.
SetEvent
;
FThread
.
WaitFor
;
FThread
.
WaitFor
;
FThread
.
Free
;
FThread
.
Free
;
FDb
.
Free
;
F
Listener
Db
.
Free
;
FStopEvent
.
Free
;
FStopEvent
.
Free
;
inherited
;
inherited
;
end
;
end
;
procedure
TWsDataModel
.
ListenerTimerFire
(
Sender
:
TObject
);
begin
FListenerDb
.
StartUpdateListener
;
end
;
procedure
TWsDataModel
.
Execute
;
procedure
TWsDataModel
.
Execute
;
var
var
unitMapElapsedMs
:
Integer
;
unitMapElapsedMs
:
Integer
;
begin
begin
unitMapElapsedMs
:=
0
;
unitMapElapsedMs
:=
0
;
FDb
:=
nil
;
try
try
try
FDb
:=
TApiDatabaseModule
.
Create
(
nil
);
ConfigureQueryTimeouts
;
while
not
TThread
.
CurrentThread
.
CheckTerminated
do
while
not
TThread
.
CurrentThread
.
CheckTerminated
do
begin
begin
if
FStopEvent
.
WaitFor
(
FIntervalMs
)
=
wrSignaled
then
if
FStopEvent
.
WaitFor
(
FIntervalMs
)
=
wrSignaled
then
...
@@ -93,11 +114,13 @@ begin
...
@@ -93,11 +114,13 @@ begin
Inc
(
unitMapElapsedMs
,
FIntervalMs
);
Inc
(
unitMapElapsedMs
,
FIntervalMs
);
if
not
FDb
.
StartUpdateListener
then
if
not
FDb
.
EnsureConnected
then
Continue
;
Continue
;
if
F
Db
.
ConsumeCADUpdate
then
if
FListener
Db
.
ConsumeCADUpdate
then
begin
begin
// ADD THIS DELETE BEFORE DEPLOY
Logger
.
Log
(
1
,
'WsDataModel: disupdate consumed; starting BroadcastAll'
);
BroadcastAll
;
BroadcastAll
;
unitMapElapsedMs
:=
0
;
unitMapElapsedMs
:=
0
;
end
end
...
@@ -116,6 +139,10 @@ begin
...
@@ -116,6 +139,10 @@ begin
on
E
:
Exception
do
on
E
:
Exception
do
Logger
.
Log
(
1
,
'WsDataModel worker stopped: '
+
E
.
Message
);
Logger
.
Log
(
1
,
'WsDataModel worker stopped: '
+
E
.
Message
);
end
;
end
;
finally
FDb
.
Free
;
FDb
:=
nil
;
end
;
end
;
end
;
procedure
TWsDataModel
.
ConfigureQueryTimeouts
;
procedure
TWsDataModel
.
ConfigureQueryTimeouts
;
...
@@ -131,13 +158,31 @@ begin
...
@@ -131,13 +158,31 @@ begin
end
;
end
;
procedure
TWsDataModel
.
BroadcastAll
;
procedure
TWsDataModel
.
BroadcastAll
;
var
complaintMapJson
,
complaintListJson
:
string
;
complaintMapCount
,
complaintListCount
:
Integer
;
begin
begin
complaintMapCount
:=
-
1
;
complaintListCount
:=
-
1
;
Logger
.
Log
(
5
,
'WsDataModel: BroadcastAll initiated'
);
Logger
.
Log
(
5
,
'WsDataModel: BroadcastAll initiated'
);
try
FBroadcast
(
BuildBadgeCountsJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel BADGE_COUNTS error: '
+
E
.
Message
);
end
;
try
FBroadcast
(
BuildBadgeCountsJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel BADGE_COUNTS error: '
+
E
.
Message
);
end
;
try
FBroadcast
(
BuildUnitMapJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel UNIT_MAP error: '
+
E
.
Message
);
end
;
try
FBroadcast
(
BuildUnitMapJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel UNIT_MAP error: '
+
E
.
Message
);
end
;
try
FBroadcast
(
BuildComplaintMapJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel COMPLAINT_MAP error: '
+
E
.
Message
);
end
;
try
complaintMapJson
:=
BuildComplaintMapJson
(
complaintMapCount
);
FBroadcast
(
complaintMapJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel COMPLAINT_MAP error: '
+
E
.
Message
);
end
;
try
FBroadcast
(
BuildUnitListJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel UNIT_LIST error: '
+
E
.
Message
);
end
;
try
FBroadcast
(
BuildUnitListJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel UNIT_LIST error: '
+
E
.
Message
);
end
;
try
FBroadcast
(
BuildComplaintListJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel COMPLAINT_LIST error: '
+
E
.
Message
);
end
;
try
complaintListJson
:=
BuildComplaintListJson
(
complaintListCount
);
FBroadcast
(
complaintListJson
);
except
on
E
:
Exception
do
Logger
.
Log
(
2
,
'WsDataModel COMPLAINT_LIST error: '
+
E
.
Message
);
end
;
Logger
.
Log
(
3
,
Format
(
'WsDataModel: BroadcastAll complaint counts - map=%d, list=%d'
,
[
complaintMapCount
,
complaintListCount
]));
Logger
.
Log
(
5
,
'WsDataModel: BroadcastAll end'
);
Logger
.
Log
(
5
,
'WsDataModel: BroadcastAll end'
);
end
;
end
;
...
@@ -232,7 +277,7 @@ begin
...
@@ -232,7 +277,7 @@ begin
end
;
end
;
end
;
end
;
function
TWsDataModel
.
BuildComplaintMapJson
:
string
;
function
TWsDataModel
.
BuildComplaintMapJson
(
out
AComplaintCount
:
Integer
)
:
string
;
var
var
obj
:
TJSONObject
;
obj
:
TJSONObject
;
data
:
TJSONArray
;
data
:
TJSONArray
;
...
@@ -242,6 +287,7 @@ var
...
@@ -242,6 +287,7 @@ var
complaintId
,
unitStatus
:
string
;
complaintId
,
unitStatus
:
string
;
latestUpdate
:
TDateTime
;
latestUpdate
:
TDateTime
;
begin
begin
AComplaintCount
:=
0
;
obj
:=
TJSONObject
.
Create
;
obj
:=
TJSONObject
.
Create
;
data
:=
TJSONArray
.
Create
;
data
:=
TJSONArray
.
Create
;
UnitsByComplaintMap
:=
TDictionary
<
string
,
TJSONArray
>.
Create
;
UnitsByComplaintMap
:=
TDictionary
<
string
,
TJSONArray
>.
Create
;
...
@@ -330,8 +376,9 @@ begin
...
@@ -330,8 +376,9 @@ begin
FDb
.
uqMapComplaints
.
Close
;
FDb
.
uqMapComplaints
.
Close
;
end
;
end
;
obj
.
AddPair
(
'count'
,
TJSONNumber
.
Create
(
data
.
Count
));
AComplaintCount
:=
data
.
Count
;
obj
.
AddPair
(
'returned'
,
TJSONNumber
.
Create
(
data
.
Count
));
obj
.
AddPair
(
'count'
,
TJSONNumber
.
Create
(
AComplaintCount
));
obj
.
AddPair
(
'returned'
,
TJSONNumber
.
Create
(
AComplaintCount
));
Result
:=
obj
.
ToJSON
;
Result
:=
obj
.
ToJSON
;
finally
finally
// Free the per-complaint unit arrays (not cloned copies — those are now owned by item)
// Free the per-complaint unit arrays (not cloned copies — those are now owned by item)
...
@@ -448,7 +495,7 @@ begin
...
@@ -448,7 +495,7 @@ begin
end
;
end
;
end
;
end
;
function
TWsDataModel
.
BuildComplaintListJson
:
string
;
function
TWsDataModel
.
BuildComplaintListJson
(
out
AComplaintCount
:
Integer
)
:
string
;
var
var
obj
:
TJSONObject
;
obj
:
TJSONObject
;
data
:
TJSONArray
;
data
:
TJSONArray
;
...
@@ -458,6 +505,7 @@ var
...
@@ -458,6 +505,7 @@ var
colorVal
:
Integer
;
colorVal
:
Integer
;
complaintNumber
:
string
;
complaintNumber
:
string
;
begin
begin
AComplaintCount
:=
0
;
obj
:=
TJSONObject
.
Create
;
obj
:=
TJSONObject
.
Create
;
data
:=
TJSONArray
.
Create
;
data
:=
TJSONArray
.
Create
;
lastDistrict
:=
''
;
lastDistrict
:=
''
;
...
@@ -509,6 +557,7 @@ begin
...
@@ -509,6 +557,7 @@ begin
item
.
AddPair
(
'Agency'
,
FDb
.
uqComplaintListAGENCY
.
AsString
);
item
.
AddPair
(
'Agency'
,
FDb
.
uqComplaintListAGENCY
.
AsString
);
item
.
AddPair
(
'Priority'
,
FDb
.
uqComplaintListPRIORITY
.
AsString
);
item
.
AddPair
(
'Priority'
,
FDb
.
uqComplaintListPRIORITY
.
AsString
);
item
.
AddPair
(
'DispatchCodeDesc'
,
FDb
.
uqComplaintListDISPATCH_CODE_DESC
.
AsString
);
item
.
AddPair
(
'DispatchCodeDesc'
,
FDb
.
uqComplaintListDISPATCH_CODE_DESC
.
AsString
);
item
.
AddPair
(
'pngName'
,
FDb
.
uqComplaintListpngName
.
AsString
);
item
.
AddPair
(
'Address'
,
FDb
.
uqComplaintListADDRESS
.
AsString
);
item
.
AddPair
(
'Address'
,
FDb
.
uqComplaintListADDRESS
.
AsString
);
item
.
AddPair
(
'Business'
,
FDb
.
uqComplaintListBUSINESS
.
AsString
);
item
.
AddPair
(
'Business'
,
FDb
.
uqComplaintListBUSINESS
.
AsString
);
item
.
AddPair
(
'CFSId'
,
FDb
.
uqComplaintListCFSID
.
AsString
);
item
.
AddPair
(
'CFSId'
,
FDb
.
uqComplaintListCFSID
.
AsString
);
...
@@ -523,8 +572,9 @@ begin
...
@@ -523,8 +572,9 @@ begin
end
;
end
;
end
;
end
;
obj
.
AddPair
(
'count'
,
TJSONNumber
.
Create
(
data
.
Count
));
AComplaintCount
:=
data
.
Count
;
obj
.
AddPair
(
'returned'
,
TJSONNumber
.
Create
(
data
.
Count
));
obj
.
AddPair
(
'count'
,
TJSONNumber
.
Create
(
AComplaintCount
));
obj
.
AddPair
(
'returned'
,
TJSONNumber
.
Create
(
AComplaintCount
));
obj
.
AddPair
(
'data'
,
data
);
obj
.
AddPair
(
'data'
,
data
);
Result
:=
obj
.
ToJSON
;
Result
:=
obj
.
ToJSON
;
finally
finally
...
...
emiMobileServer/bin/emiMobileServer.ini
View file @
f1cc5bac
[Settings]
[Settings]
LogFileNum
=
130
LogFileNum
=
148
webClientVersion
=
0.9.4.7
webClientVersion
=
0.9.4.8
MemoLogLevel
=
3
FileLogLevel
=
4
[Twilio]
[Twilio]
AccountSid
=
AccountSid
=
AC717f06b3c10bbecff42487426e34fdb3
AuthToken
=
AuthToken
=
a0dec128c1b5a3231909ba81da8947b6
FromNumber
=
FromNumber
=
+18775346578
[Database]
[Database]
--Server
=
192.168.91.129
--Server
=
192.168.91.129
...
...
twilioinfo.txt
deleted
100644 → 0
View file @
6a8f477c
AccountSid=AC717f06b3c10bbecff42487426e34fdb3
AuthToken=a0dec128c1b5a3231909ba81da8947b6
FromNumber=+18775346578
\ No newline at end of file
webEMIMobile/Auth.Service.pas
View file @
f1cc5bac
unit
Auth
.
Service
;
unit
Auth
.
Service
;
interface
interface
...
@@ -16,7 +16,7 @@ type
...
@@ -16,7 +16,7 @@ type
TOnLoginSuccess
=
reference
to
procedure
;
TOnLoginSuccess
=
reference
to
procedure
;
TOnLoginError
=
reference
to
procedure
(
AMsg
:
string
);
TOnLoginError
=
reference
to
procedure
(
AMsg
:
string
);
TOnCodeSentSuccess
=
reference
to
procedure
(
AVerificationToken
:
string
)
;
TOnCodeSentSuccess
=
reference
to
procedure
;
TOnBeginSuccess
=
reference
to
procedure
(
AChallenge
,
AChallengeToken
:
string
);
TOnBeginSuccess
=
reference
to
procedure
(
AChallenge
,
AChallengeToken
:
string
);
TOnDeviceSuccess
=
reference
to
procedure
;
TOnDeviceSuccess
=
reference
to
procedure
;
TOnDeviceError
=
reference
to
procedure
(
AMsg
:
string
);
TOnDeviceError
=
reference
to
procedure
(
AMsg
:
string
);
...
@@ -53,8 +53,8 @@ type
...
@@ -53,8 +53,8 @@ type
// Section: Device registration verification
// Section: Device registration verification
procedure
BeginRegistration
(
APhoneNumber
:
string
;
procedure
BeginRegistration
(
APhoneNumber
:
string
;
ASuccess
:
TOnCodeSentSuccess
;
AError
:
TOnDeviceError
);
ASuccess
:
TOnCodeSentSuccess
;
AError
:
TOnDeviceError
);
procedure
VerifyRegistrationCode
(
APhoneNumber
,
AVerificationCode
,
procedure
VerifyRegistrationCode
(
APhoneNumber
,
AVerificationCode
:
string
;
A
VerificationToken
:
string
;
A
Success
:
TOnBeginSuccess
;
ASuccess
:
TOnBeginSuccess
;
AError
:
TOnDeviceError
);
AError
:
TOnDeviceError
);
// Section: WebAuthn registration
// Section: WebAuthn registration
procedure
CompleteRegistration
(
APhoneNumber
,
ACredentialId
,
procedure
CompleteRegistration
(
APhoneNumber
,
ACredentialId
,
...
@@ -224,7 +224,7 @@ procedure TAuthService.BeginRegistration(APhoneNumber: string;
...
@@ -224,7 +224,7 @@ procedure TAuthService.BeginRegistration(APhoneNumber: string;
procedure
OnLoad
(
Response
:
TXDataClientResponse
);
procedure
OnLoad
(
Response
:
TXDataClientResponse
);
var
var
resp
:
JS
.
TJSObject
;
resp
:
JS
.
TJSObject
;
verificationToken
,
status
:
string
;
status
:
string
;
begin
begin
resp
:=
JS
.
TJSObject
(
Response
.
Result
);
resp
:=
JS
.
TJSObject
(
Response
.
Result
);
status
:=
JS
.
toString
(
resp
.
Properties
[
'status'
]);
status
:=
JS
.
toString
(
resp
.
Properties
[
'status'
]);
...
@@ -233,8 +233,7 @@ procedure TAuthService.BeginRegistration(APhoneNumber: string;
...
@@ -233,8 +233,7 @@ procedure TAuthService.BeginRegistration(APhoneNumber: string;
AError
(
JS
.
toString
(
resp
.
Properties
[
'message'
]));
AError
(
JS
.
toString
(
resp
.
Properties
[
'message'
]));
Exit
;
Exit
;
end
;
end
;
verificationToken
:=
JS
.
toString
(
resp
.
Properties
[
'verificationToken'
]);
ASuccess
;
ASuccess
(
verificationToken
);
end
;
end
;
procedure
OnError
(
Error
:
TXDataClientError
);
procedure
OnError
(
Error
:
TXDataClientError
);
...
@@ -251,7 +250,7 @@ begin
...
@@ -251,7 +250,7 @@ begin
end
;
end
;
procedure
TAuthService
.
VerifyRegistrationCode
(
APhoneNumber
,
procedure
TAuthService
.
VerifyRegistrationCode
(
APhoneNumber
,
AVerificationCode
,
AVerificationToken
:
string
;
ASuccess
:
TOnBeginSuccess
;
AVerificationCode
:
string
;
ASuccess
:
TOnBeginSuccess
;
AError
:
TOnDeviceError
);
AError
:
TOnDeviceError
);
procedure
OnLoad
(
Response
:
TXDataClientResponse
);
procedure
OnLoad
(
Response
:
TXDataClientResponse
);
...
@@ -279,7 +278,7 @@ procedure TAuthService.VerifyRegistrationCode(APhoneNumber,
...
@@ -279,7 +278,7 @@ procedure TAuthService.VerifyRegistrationCode(APhoneNumber,
begin
begin
FClient
.
RawInvoke
(
FClient
.
RawInvoke
(
'IAuthService.VerifyRegistrationCode'
,
'IAuthService.VerifyRegistrationCode'
,
[
APhoneNumber
,
AVerificationCode
,
AVerificationToken
],
[
APhoneNumber
,
AVerificationCode
],
@
OnLoad
,
@
OnError
@
OnLoad
,
@
OnError
);
);
end
;
end
;
...
...
webEMIMobile/ConnectionModule.pas
View file @
f1cc5bac
...
@@ -21,7 +21,7 @@ type
...
@@ -21,7 +21,7 @@ type
procedure
VerifyVersion
(
SuccessProc
:
TSuccessProc
);
procedure
VerifyVersion
(
SuccessProc
:
TSuccessProc
);
public
public
property
WsUrl
:
string
read
FWsUrl
;
property
WsUrl
:
string
read
FWsUrl
;
const
clientVersion
=
'0.9.4.
7
'
;
const
clientVersion
=
'0.9.4.
8
'
;
procedure
InitApp
(
SuccessProc
:
TSuccessProc
;
procedure
InitApp
(
SuccessProc
:
TSuccessProc
;
UnauthorizedAccessProc
:
TUnauthorizedAccessProc
);
UnauthorizedAccessProc
:
TUnauthorizedAccessProc
);
end
;
end
;
...
...
webEMIMobile/Utils.pas
View file @
f1cc5bac
...
@@ -207,12 +207,12 @@ begin
...
@@ -207,12 +207,12 @@ begin
btnLeft
.
onclick
=
function
()
{
btnLeft
.
onclick
=
function
()
{
bsModal.hide();
bsModal.hide();
ConfirmProc(
true); // user confirmed
ConfirmProc(
false);
}
;
}
;
btnRight
.
onclick
=
function
()
{
btnRight
.
onclick
=
function
()
{
bsModal.hide();
bsModal.hide();
ConfirmProc(
false); // user canceled
ConfirmProc(
true);
}
;
}
;
bsModal
=
new
bootstrap
.
Modal
(
modal
,
{ keyboard: false }
);
bsModal
=
new
bootstrap
.
Modal
(
modal
,
{ keyboard: false }
);
...
...
webEMIMobile/View.ComplaintDetails.dfm
View file @
f1cc5bac
...
@@ -120,7 +120,7 @@ object FViewComplaintDetails: TFViewComplaintDetails
...
@@ -120,7 +120,7 @@ object FViewComplaintDetails: TFViewComplaintDetails
ElementHeaderClassName = 'table-light'
ElementHeaderClassName = 'table-light'
ElementTableClassName =
ElementTableClassName =
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'gn-middle'
'gn-middle
small
'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.DropDownElementClassName = 'form-control'
Footer.DropDownElementClassName = 'form-control'
...
@@ -169,7 +169,7 @@ object FViewComplaintDetails: TFViewComplaintDetails
...
@@ -169,7 +169,7 @@ object FViewComplaintDetails: TFViewComplaintDetails
ElementHeaderClassName = 'table-light'
ElementHeaderClassName = 'table-light'
ElementTableClassName =
ElementTableClassName =
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'gn-middle'
'gn-middle
small
'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.DropDownElementClassName = 'form-control'
Footer.DropDownElementClassName = 'form-control'
...
@@ -223,7 +223,7 @@ object FViewComplaintDetails: TFViewComplaintDetails
...
@@ -223,7 +223,7 @@ object FViewComplaintDetails: TFViewComplaintDetails
ElementHeaderClassName = 'table-light'
ElementHeaderClassName = 'table-light'
ElementTableClassName =
ElementTableClassName =
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'gn-middle'
'gn-middle
small
'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.DropDownElementClassName = 'form-control'
Footer.DropDownElementClassName = 'form-control'
...
@@ -282,7 +282,7 @@ object FViewComplaintDetails: TFViewComplaintDetails
...
@@ -282,7 +282,7 @@ object FViewComplaintDetails: TFViewComplaintDetails
ElementHeaderClassName = 'table-light'
ElementHeaderClassName = 'table-light'
ElementTableClassName =
ElementTableClassName =
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'gn-middle'
'gn-middle
small
'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.DropDownElementClassName = 'form-control'
Footer.DropDownElementClassName = 'form-control'
...
@@ -352,10 +352,11 @@ object FViewComplaintDetails: TFViewComplaintDetails
...
@@ -352,10 +352,11 @@ object FViewComplaintDetails: TFViewComplaintDetails
Style = lsListGroup
Style = lsListGroup
DataSource = wdsWarnings
DataSource = wdsWarnings
ItemTemplate =
ItemTemplate =
'<div class="card mb-2"> <div class="card-body py-2"> <div cl' +
'<div class="card mb-2 small"> <div class="card-body py-2"> <' +
'div cl' +
'ass="d-flex justify-content-between gap-2"> <div class="fw-' +
'ass="d-flex justify-content-between gap-2"> <div class="fw-' +
'semibold">(%CodeDesc%)</div> <div class="text-muted
small t
' +
'semibold">(%CodeDesc%)</div> <div class="text-muted
text-en
' +
'
ext-end">(%ADDRESS%)</div> </div> <div class="small
mt-1">' +
'
d">(%ADDRESS%)</div> </div> <div class="
mt-1">' +
'(%NOTES%)</div> </div></div>'
'(%NOTES%)</div> </div></div>'
ListSource = wdsWarnings
ListSource = wdsWarnings
end
end
...
...
webEMIMobile/View.ComplaintDetails.html
View file @
f1cc5bac
...
@@ -31,7 +31,7 @@
...
@@ -31,7 +31,7 @@
<div
class=
"card-body py-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>
<tbody>
<tr>
<tr>
<th
scope=
"row"
class=
"fw-semibold text-nowrap pe-2 w-auto"
>
Priority:
</th>
<th
scope=
"row"
class=
"fw-semibold text-nowrap pe-2 w-auto"
>
Priority:
</th>
...
...
webEMIMobile/View.Complaints.dfm
View file @
f1cc5bac
...
@@ -45,7 +45,10 @@ object FViewComplaints: TFViewComplaints
...
@@ -45,7 +45,10 @@ object FViewComplaints: TFViewComplaints
'<div class="list-section-header small fw-semibold bg-secondary t' +
'<div class="list-section-header small fw-semibold bg-secondary t' +
'ext-white rounded-1 px-2 mb-1">(%DistrictHeader%)</div><div clas' +
'ext-white rounded-1 px-2 mb-1">(%DistrictHeader%)</div><div clas' +
's="card border shadow-sm"><div class="card-body py-2 px-3 d-flex' +
's="card border shadow-sm"><div class="card-body py-2 px-3 d-flex' +
' gap-2"><div class="flex-grow-1"><div class="fw-bold text-upperc' +
' gap-2"><img src="assets/markers/(%pngName%)" class="flex-shrink' +
'-0 align-self-center" style="width:28px;height:36px;object-fit:c' +
'ontain" alt=""><div class="flex-grow-1"><div class="fw-bold text' +
'-upperc' +
'ase small">(%Priority%): (%DispatchCodeDesc%)</div><div class="s' +
'ase small">(%Priority%): (%DispatchCodeDesc%)</div><div class="s' +
'mall">(%Address%)</div><div class="small d-none complaint-busine' +
'mall">(%Address%)</div><div class="small d-none complaint-busine' +
'ss" data-business="(%Business%)">(%Business%)</div><div class="s' +
'ss" data-business="(%Business%)">(%Business%)</div><div class="s' +
...
@@ -82,6 +85,10 @@ object FViewComplaints: TFViewComplaints
...
@@ -82,6 +85,10 @@ object FViewComplaints: TFViewComplaints
object xdwdsComplaintsDispatchCodeDesc: TStringField
object xdwdsComplaintsDispatchCodeDesc: TStringField
FieldName = 'DispatchCodeDesc'
FieldName = 'DispatchCodeDesc'
end
end
object xdwdsComplaintspngName: TStringField
FieldName = 'pngName'
Size = 80
end
object xdwdsComplaintsAddress: TStringField
object xdwdsComplaintsAddress: TStringField
FieldName = 'Address'
FieldName = 'Address'
end
end
...
...
webEMIMobile/View.Complaints.pas
View file @
f1cc5bac
...
@@ -19,6 +19,7 @@ type
...
@@ -19,6 +19,7 @@ type
xdwdsComplaintsAgency
:
TStringField
;
xdwdsComplaintsAgency
:
TStringField
;
xdwdsComplaintsPriority
:
TStringField
;
xdwdsComplaintsPriority
:
TStringField
;
xdwdsComplaintsDispatchCodeDesc
:
TStringField
;
xdwdsComplaintsDispatchCodeDesc
:
TStringField
;
xdwdsComplaintspngName
:
TStringField
;
xdwdsComplaintsAddress
:
TStringField
;
xdwdsComplaintsAddress
:
TStringField
;
xdwdsComplaintsStatus
:
TStringField
;
xdwdsComplaintsStatus
:
TStringField
;
xdwdsComplaintsDispatchDistrict
:
TStringField
;
xdwdsComplaintsDispatchDistrict
:
TStringField
;
...
...
webEMIMobile/View.DeviceManager.html
View file @
f1cc5bac
...
@@ -51,11 +51,14 @@
...
@@ -51,11 +51,14 @@
<tr>
<tr>
<th
style=
"min-width:130px;"
>
Device Name
</th>
<th
style=
"min-width:130px;"
>
Device Name
</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: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>
<th
style=
"min-width:80px;"
>
Status
</th>
<th
style=
"min-width:80px;"
>
Status
</th>
<th
style=
"min-width:180px;"
>
Actions
</th>
<th
style=
"min-width:85px;"
>
Access
</th>
<th
style=
"min-width:75px;"
>
Delete
</th>
<th
style=
"min-width:90px;"
>
Send Link
</th>
</tr>
</tr>
</thead>
</thead>
<tbody
id=
"view.devmgr.tbody"
>
<tbody
id=
"view.devmgr.tbody"
>
...
@@ -66,4 +69,21 @@
...
@@ -66,4 +69,21 @@
</p>
</p>
</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"
>
<div
class=
"modal-content shadow-lg"
>
<div
class=
"modal-header"
>
<h5
class=
"modal-title"
>
Confirm
</h5>
<button
type=
"button"
class=
"btn-close"
data-bs-dismiss=
"modal"
aria-label=
"Close"
></button>
</div>
<div
class=
"modal-body fw-bold"
id=
"main_modal_body"
></div>
<div
class=
"modal-footer justify-content-center"
>
<button
type=
"button"
class=
"btn btn-secondary me-3"
id=
"btn_confirm_left"
>
Cancel
</button>
<button
type=
"button"
class=
"btn btn-danger"
id=
"btn_confirm_right"
>
Delete
</button>
</div>
</div>
</div>
</div>
</div>
</div>
webEMIMobile/View.DeviceManager.pas
View file @
f1cc5bac
...
@@ -26,11 +26,15 @@ type
...
@@ -26,11 +26,15 @@ type
procedure
HideNotification
;
procedure
HideNotification
;
procedure
ClearTable
;
procedure
ClearTable
;
procedure
AddDeviceRow
(
const
ACredentialId
,
AName
,
APhoneNumber
,
AUsername
,
procedure
AddDeviceRow
(
const
ACredentialId
,
AName
,
APhoneNumber
,
AUsername
,
AUserAgent
,
ARegisteredAt
,
AStatus
:
string
);
AUserAgent
,
ARegisteredAt
,
AStatus
,
AVerificationCode
:
string
;
AVerificationCodeActive
:
Boolean
);
[
async
]
procedure
LoadDevices
;
[
async
]
procedure
LoadDevices
;
[
async
]
procedure
RevokeDevice
(
const
ACredentialId
,
AName
:
string
);
[
async
]
procedure
RevokeDevice
(
const
ACredentialId
,
AName
:
string
);
[
async
]
procedure
UnrevokeDevice
(
const
ACredentialId
,
AName
:
string
);
[
async
]
procedure
UnrevokeDevice
(
const
ACredentialId
,
AName
:
string
);
procedure
ConfirmDeleteDevice
(
ACredentialId
,
APhoneNumber
,
AName
:
string
;
AIsPending
:
Boolean
);
[
async
]
procedure
DeletePendingDevice
(
const
APhoneNumber
,
AName
:
string
);
[
async
]
procedure
DeletePendingDevice
(
const
APhoneNumber
,
AName
:
string
);
[
async
]
procedure
DeleteDevice
(
ACredentialId
,
AName
:
string
);
[
async
]
procedure
SendAppLink
(
const
APhoneNumber
,
AName
:
string
);
[
async
]
procedure
SendAppLink
(
const
APhoneNumber
,
AName
:
string
);
[
async
]
procedure
AddPendingDevice
;
[
async
]
procedure
AddPendingDevice
;
[
async
]
procedure
UpdateDeviceUsername
(
const
ACredentialId
,
AUsername
:
string
);
[
async
]
procedure
UpdateDeviceUsername
(
const
ACredentialId
,
AUsername
:
string
);
...
@@ -43,7 +47,8 @@ var
...
@@ -43,7 +47,8 @@ var
implementation
implementation
uses
uses
Auth
.
Service
;
Auth
.
Service
,
Utils
;
{$R *.dfm}
{$R *.dfm}
...
@@ -130,10 +135,12 @@ begin
...
@@ -130,10 +135,12 @@ begin
end
;
end
;
procedure
TFViewDeviceManager
.
AddDeviceRow
(
const
ACredentialId
,
AName
,
procedure
TFViewDeviceManager
.
AddDeviceRow
(
const
ACredentialId
,
AName
,
APhoneNumber
,
AUsername
,
AUserAgent
,
ARegisteredAt
,
AStatus
:
string
);
APhoneNumber
,
AUsername
,
AUserAgent
,
ARegisteredAt
,
AStatus
,
AVerificationCode
:
string
;
AVerificationCodeActive
:
Boolean
);
var
var
tbody
,
tr
,
tdName
,
tdPhone
,
tdUser
,
tdAgent
,
tdReg
,
tdStatus
,
tdAction
:
TJSHTMLElement
;
tbody
,
tr
,
tdName
,
tdPhone
,
tdCode
,
tdUser
,
tdAgent
,
tdReg
,
tdStatus
,
btn
,
btnSend
,
inp
,
saveBtn
:
TJSHTMLElement
;
tdAccess
,
tdDelete
,
tdSend
:
TJSHTMLElement
;
btn
,
btnSend
,
inp
:
TJSHTMLElement
;
displayDate
,
displayPhone
:
string
;
displayDate
,
displayPhone
:
string
;
isPending
,
isRevoked
,
isActive
:
Boolean
;
isPending
,
isRevoked
,
isActive
:
Boolean
;
begin
begin
...
@@ -162,24 +169,40 @@ begin
...
@@ -162,24 +169,40 @@ begin
tdPhone
.
innerText
:=
displayPhone
;
tdPhone
.
innerText
:=
displayPhone
;
tr
.
appendChild
(
tdPhone
);
tr
.
appendChild
(
tdPhone
);
// Section: Verification code
tdCode
:=
TJSHTMLElement
(
document
.
createElement
(
'td'
));
if
isPending
and
(
AVerificationCode
<>
''
)
then
begin
if
AVerificationCodeActive
then
tdCode
.
innerHTML
:=
'<span class="font-monospace">'
+
AVerificationCode
+
'</span> <span class="badge bg-success">Active</span>'
else
tdCode
.
innerHTML
:=
'<span class="font-monospace">'
+
AVerificationCode
+
'</span> <span class="badge bg-secondary">Inactive</span>'
;
end
else
if
isPending
then
tdCode
.
innerHTML
:=
'<em class="text-muted small">Not sent</em>'
else
tdCode
.
innerHTML
:=
'<em class="text-muted small">—</em>'
;
tr
.
appendChild
(
tdCode
);
// Username — editable for active devices
// Username — editable for active devices
tdUser
:=
TJSHTMLElement
(
document
.
createElement
(
'td'
));
tdUser
:=
TJSHTMLElement
(
document
.
createElement
(
'td'
));
if
isActive
then
if
isActive
then
begin
begin
tdUser
.
innerHTML
:=
tdUser
.
innerHTML
:=
'<div
class="d-flex gap-1 align-items-center"
>'
+
'<div>'
+
'<input type="text" class="form-control form-control-sm devmgr-username-input" '
+
'<input type="text" class="form-control form-control-sm devmgr-username-input" '
+
'style="max-width:110px;" value="'
+
AUsername
+
'" '
+
'style="max-width:110px;" value="'
+
AUsername
+
'" '
+
'placeholder="username">'
+
'placeholder="username">'
+
'<button class="btn btn-outline-secondary btn-sm devmgr-username-save">Save</button>'
+
'</div>'
;
'</div>'
;
inp
:=
TJSHTMLElement
(
tdUser
.
querySelector
(
'.devmgr-username-input'
));
inp
:=
TJSHTMLElement
(
tdUser
.
querySelector
(
'.devmgr-username-input'
));
saveBtn
:=
TJSHTMLElement
(
tdUser
.
querySelector
(
'.devmgr-username-save'
));
if
Assigned
(
inp
)
then
if
Assigned
(
saveBtn
)
and
Assigned
(
inp
)
then
inp
.
addEventListener
(
'change'
,
TJSRawEventHandler
(
saveBtn
.
addEventListener
(
'click'
,
procedure
(
Event
:
TJSMouse
Event
)
procedure
(
Event
:
TJS
Event
)
begin
begin
UpdateDeviceUsername
(
ACredentialId
,
string
(
TJSHTMLInputElement
(
inp
).
value
));
UpdateDeviceUsername
(
ACredentialId
,
string
(
TJSHTMLInputElement
(
inp
).
value
));
end
);
end
)
);
end
end
else
if
AUsername
<>
''
then
else
if
AUsername
<>
''
then
tdUser
.
innerText
:=
AUsername
tdUser
.
innerText
:=
AUsername
...
@@ -223,22 +246,9 @@ begin
...
@@ -223,22 +246,9 @@ begin
tdStatus
.
innerHTML
:=
'<span class="badge bg-success">Active</span>'
;
tdStatus
.
innerHTML
:=
'<span class="badge bg-success">Active</span>'
;
tr
.
appendChild
(
tdStatus
);
tr
.
appendChild
(
tdStatus
);
// Actions
// Section: Access action
tdAction
:=
TJSHTMLElement
(
document
.
createElement
(
'td'
));
tdAccess
:=
TJSHTMLElement
(
document
.
createElement
(
'td'
));
tdAction
.
className
:=
'd-flex gap-1 flex-wrap'
;
if
isRevoked
then
if
isPending
then
begin
btn
:=
TJSHTMLElement
(
document
.
createElement
(
'button'
));
btn
.
className
:=
'btn btn-outline-danger btn-sm'
;
btn
.
innerText
:=
'Cancel'
;
btn
.
addEventListener
(
'click'
,
procedure
(
Event
:
TJSMouseEvent
)
begin
DeletePendingDevice
(
APhoneNumber
,
AName
);
end
);
tdAction
.
appendChild
(
btn
);
end
else
if
isRevoked
then
begin
begin
btn
:=
TJSHTMLElement
(
document
.
createElement
(
'button'
));
btn
:=
TJSHTMLElement
(
document
.
createElement
(
'button'
));
btn
.
className
:=
'btn btn-outline-success btn-sm'
;
btn
.
className
:=
'btn btn-outline-success btn-sm'
;
...
@@ -247,9 +257,9 @@ begin
...
@@ -247,9 +257,9 @@ begin
begin
begin
UnrevokeDevice
(
ACredentialId
,
AName
);
UnrevokeDevice
(
ACredentialId
,
AName
);
end
);
end
);
tdAc
tion
.
appendChild
(
btn
);
tdAc
cess
.
appendChild
(
btn
);
end
end
else
else
if
isActive
then
begin
begin
btn
:=
TJSHTMLElement
(
document
.
createElement
(
'button'
));
btn
:=
TJSHTMLElement
(
document
.
createElement
(
'button'
));
btn
.
className
:=
'btn btn-danger btn-sm'
;
btn
.
className
:=
'btn btn-danger btn-sm'
;
...
@@ -258,10 +268,26 @@ begin
...
@@ -258,10 +268,26 @@ begin
begin
begin
RevokeDevice
(
ACredentialId
,
AName
);
RevokeDevice
(
ACredentialId
,
AName
);
end
);
end
);
tdAction
.
appendChild
(
btn
);
tdAccess
.
appendChild
(
btn
);
end
;
end
else
tdAccess
.
innerHTML
:=
'<span class="text-muted">—</span>'
;
tr
.
appendChild
(
tdAccess
);
// Send App Link for pending and active rows with a phone number
// Section: Delete action
tdDelete
:=
TJSHTMLElement
(
document
.
createElement
(
'td'
));
btn
:=
TJSHTMLElement
(
document
.
createElement
(
'button'
));
btn
.
className
:=
'btn btn-outline-danger btn-sm'
;
btn
.
innerText
:=
'Delete'
;
btn
.
addEventListener
(
'click'
,
procedure
(
Event
:
TJSMouseEvent
)
begin
ConfirmDeleteDevice
(
ACredentialId
,
APhoneNumber
,
AName
,
isPending
);
end
);
tdDelete
.
appendChild
(
btn
);
tr
.
appendChild
(
tdDelete
);
// Section: Send link action
tdSend
:=
TJSHTMLElement
(
document
.
createElement
(
'td'
));
if
(
not
isRevoked
)
and
(
APhoneNumber
<>
''
)
then
if
(
not
isRevoked
)
and
(
APhoneNumber
<>
''
)
then
begin
begin
btnSend
:=
TJSHTMLElement
(
document
.
createElement
(
'button'
));
btnSend
:=
TJSHTMLElement
(
document
.
createElement
(
'button'
));
...
@@ -271,13 +297,11 @@ begin
...
@@ -271,13 +297,11 @@ begin
begin
begin
SendAppLink
(
APhoneNumber
,
AName
);
SendAppLink
(
APhoneNumber
,
AName
);
end
);
end
);
tdAction
.
appendChild
(
btnSend
);
tdSend
.
appendChild
(
btnSend
);
end
;
end
else
if
tdAction
.
children
.
length
=
0
then
tdSend
.
innerHTML
:=
'<span class="text-muted">—</span>'
;
tdAction
.
innerText
:=
'—'
;
tr
.
appendChild
(
tdSend
);
tr
.
appendChild
(
tdAction
);
tbody
.
appendChild
(
tr
);
tbody
.
appendChild
(
tr
);
end
;
end
;
...
@@ -317,7 +341,9 @@ begin
...
@@ -317,7 +341,9 @@ begin
string
(
item
[
'username'
]),
string
(
item
[
'username'
]),
string
(
item
[
'user_agent'
]),
string
(
item
[
'user_agent'
]),
string
(
item
[
'registered_at'
]),
string
(
item
[
'registered_at'
]),
string
(
item
[
'status'
])
string
(
item
[
'status'
]),
string
(
item
[
'verification_code'
]),
JS
.
toBoolean
(
item
[
'verification_code_active'
])
);
);
end
;
end
;
...
@@ -351,6 +377,25 @@ begin
...
@@ -351,6 +377,25 @@ begin
end
;
end
;
end
;
end
;
procedure
TFViewDeviceManager
.
ConfirmDeleteDevice
(
ACredentialId
,
APhoneNumber
,
AName
:
string
;
AIsPending
:
Boolean
);
begin
ShowConfirmationModal
(
'Are you sure you want to permanently delete device "'
+
AName
+
'"? This cannot be undone.'
,
'Cancel'
,
'Delete'
,
procedure
(
Confirmed
:
Boolean
)
begin
if
not
Confirmed
then
Exit
;
if
AIsPending
then
DeletePendingDevice
(
APhoneNumber
,
AName
)
else
DeleteDevice
(
ACredentialId
,
AName
);
end
);
end
;
procedure
TFViewDeviceManager
.
DeletePendingDevice
(
const
APhoneNumber
,
AName
:
string
);
procedure
TFViewDeviceManager
.
DeletePendingDevice
(
const
APhoneNumber
,
AName
:
string
);
var
var
resp
:
TXDataClientResponse
;
resp
:
TXDataClientResponse
;
...
@@ -375,6 +420,30 @@ begin
...
@@ -375,6 +420,30 @@ begin
end
;
end
;
end
;
end
;
procedure
TFViewDeviceManager
.
DeleteDevice
(
ACredentialId
,
AName
:
string
);
var
resp
:
TXDataClientResponse
;
res
:
TJSObject
;
status
:
string
;
begin
try
resp
:=
await
(
XDataWebClient
.
RawInvokeAsync
(
'IApiService.DeleteDevice'
,
[
ACredentialId
]));
res
:=
TJSObject
(
resp
.
Result
);
status
:=
string
(
res
[
'status'
]);
if
status
=
'ok'
then
begin
ShowNotification
(
'Device "'
+
AName
+
'" has been deleted.'
,
False
);
LoadDevices
;
end
else
ShowNotification
(
string
(
res
[
'message'
]));
except
on
E
:
Exception
do
ShowNotification
(
'Delete failed: '
+
E
.
Message
);
end
;
end
;
procedure
TFViewDeviceManager
.
SendAppLink
(
const
APhoneNumber
,
AName
:
string
);
procedure
TFViewDeviceManager
.
SendAppLink
(
const
APhoneNumber
,
AName
:
string
);
var
var
resp
:
TXDataClientResponse
;
resp
:
TXDataClientResponse
;
...
...
webEMIMobile/View.DeviceRegistration.pas
View file @
f1cc5bac
...
@@ -25,7 +25,6 @@ type
...
@@ -25,7 +25,6 @@ type
procedure
WebFormCreate
(
Sender
:
TObject
);
procedure
WebFormCreate
(
Sender
:
TObject
);
private
private
FRegistrationProc
:
TSuccessProc
;
FRegistrationProc
:
TSuccessProc
;
FVerificationToken
:
string
;
procedure
ShowNotification
(
const
AMsg
:
string
;
AIsError
:
Boolean
=
True
);
procedure
ShowNotification
(
const
AMsg
:
string
;
AIsError
:
Boolean
=
True
);
procedure
HideNotification
;
procedure
HideNotification
;
procedure
SetBusy
(
ABusy
:
Boolean
);
procedure
SetBusy
(
ABusy
:
Boolean
);
...
@@ -60,7 +59,6 @@ var
...
@@ -60,7 +59,6 @@ var
userAgent
:
string
;
userAgent
:
string
;
begin
begin
HideNotification
;
HideNotification
;
FVerificationToken
:=
''
;
pnlVerification
.
ElementHandle
.
classList
.
add
(
'd-none'
);
pnlVerification
.
ElementHandle
.
classList
.
add
(
'd-none'
);
userAgent
:=
''
;
userAgent
:=
''
;
asm
asm
...
@@ -116,10 +114,9 @@ procedure TFViewDeviceRegistration.btnRegisterClick(Sender: TObject);
...
@@ -116,10 +114,9 @@ procedure TFViewDeviceRegistration.btnRegisterClick(Sender: TObject);
var
var
phoneNumber
:
string
;
phoneNumber
:
string
;
procedure
OnCodeSent
(
AVerificationToken
:
string
)
;
procedure
OnCodeSent
;
begin
begin
SetBusy
(
False
);
SetBusy
(
False
);
FVerificationToken
:=
AVerificationToken
;
edtPhoneNumber
.
Enabled
:=
False
;
edtPhoneNumber
.
Enabled
:=
False
;
pnlVerification
.
ElementHandle
.
classList
.
remove
(
'd-none'
);
pnlVerification
.
ElementHandle
.
classList
.
remove
(
'd-none'
);
edtVerificationCode
.
SetFocus
;
edtVerificationCode
.
SetFocus
;
...
@@ -182,7 +179,7 @@ begin
...
@@ -182,7 +179,7 @@ begin
SetVerifyBusy
(
True
);
SetVerifyBusy
(
True
);
HideNotification
;
HideNotification
;
AuthService
.
VerifyRegistrationCode
(
phoneNumber
,
verificationCode
,
AuthService
.
VerifyRegistrationCode
(
phoneNumber
,
verificationCode
,
FVerificationToken
,
@
OnVerified
,
@
OnVerifyError
);
@
OnVerified
,
@
OnVerifyError
);
end
;
end
;
procedure
TFViewDeviceRegistration
.
DoWebAuthnCreate
(
procedure
TFViewDeviceRegistration
.
DoWebAuthnCreate
(
...
...
webEMIMobile/View.Login.pas
View file @
f1cc5bac
...
@@ -79,42 +79,13 @@ var
...
@@ -79,42 +79,13 @@ var
credId
:
string
;
credId
:
string
;
procedure
OnDeviceUser
(
AUsername
,
AAgency
:
string
);
procedure
OnDeviceUser
(
AUsername
,
AAgency
:
string
);
procedure
OnAutoLoginOK
;
begin
FLoginProc
;
end
;
procedure
OnAutoLoginError
(
AMsg
:
string
);
begin
asm
var
autoSection
=
document
.
getElementById
(
'view.login.autosection'
);
var
manualSection
=
document
.
getElementById
(
'view.login.manualsection'
);
if
(
autoSection
)
autoSection
.
classList
.
add
(
'd-none'
);
if
(
manualSection
)
manualSection
.
classList
.
remove
(
'd-none'
);
end
;
ShowNotification
(
AMsg
);
end
;
procedure
OnBeginForSimpleOK
(
AChallenge
,
AChallengeToken
:
string
);
begin
AuthService
.
LoginDeviceKey
(
credId
,
AChallengeToken
,
@
OnAutoLoginOK
,
@
OnAutoLoginError
);
end
;
procedure
OnBeginForSimpleError
(
AMsg
:
string
);
begin
asm
var
autoSection
=
document
.
getElementById
(
'view.login.autosection'
);
var
manualSection
=
document
.
getElementById
(
'view.login.manualsection'
);
if
(
autoSection
)
autoSection
.
classList
.
add
(
'd-none'
);
if
(
manualSection
)
manualSection
.
classList
.
remove
(
'd-none'
);
end
;
ShowNotification
(
'Login Error: '
+
AMsg
);
end
;
begin
begin
FAutoUsername
:=
AUsername
;
FAutoUsername
:=
AUsername
;
FAutoAgency
:=
AAgency
;
FAutoAgency
:=
AAgency
;
if
AuthService
.
IsSimpleKey
then
Exit
;
if
AUsername
<>
''
then
if
AUsername
<>
''
then
begin
begin
asm
asm
...
@@ -135,16 +106,6 @@ var
...
@@ -135,16 +106,6 @@ var
}
);
}
);
}
}
end
;
end
;
if
AuthService
.
IsSimpleKey
then
begin
// Hide passkey button — simple-key auto-login needs no hardware interaction
asm
var
btn
=
document
.
getElementById
(
'view.login.btnpasskeylogin'
);
if
(
btn
)
btn
.
style
.
display
=
'none'
;
end
;
AuthService
.
BeginAuthentication
(
credId
,
@
OnBeginForSimpleOK
,
@
OnBeginForSimpleError
);
end
;
end
;
end
;
end
;
end
;
...
...
webEMIMobile/View.Main.html
View file @
f1cc5bac
...
@@ -33,10 +33,12 @@
...
@@ -33,10 +33,12 @@
<i
class=
"fas fa-bars"
aria-hidden=
"true"
></i>
<i
class=
"fas fa-bars"
aria-hidden=
"true"
></i>
</button>
</button>
<ul
class=
"dropdown-menu dropdown-menu-end"
>
<ul
class=
"dropdown-menu dropdown-menu-end"
>
<li><button
id=
"btn_devices"
type=
"button"
class=
"dropdown-item d-none"
>
Devices
</button></li>
<li><button
id=
"btn_logout"
type=
"button"
class=
"dropdown-item"
>
Logout
</button></li>
<li><button
id=
"btn_logout"
type=
"button"
class=
"dropdown-item"
>
Logout
</button></li>
<li><hr
class=
"dropdown-divider"
></li>
<li><span
id=
"view.main.version"
class=
"dropdown-item-text text-muted small"
></span></li>
</ul>
</ul>
</div>
</div>
<button
id=
"btn_devices"
type=
"button"
class=
"btn btn-outline-light btn-sm d-none"
>
Devices
</button>
</div>
</div>
</div>
</div>
</nav>
</nav>
...
...
webEMIMobile/View.Main.pas
View file @
f1cc5bac
...
@@ -133,10 +133,15 @@ const
...
@@ -133,10 +133,15 @@ const
procedure
TFViewMain
.
WebFormCreate
(
Sender
:
TObject
);
procedure
TFViewMain
.
WebFormCreate
(
Sender
:
TObject
);
var
var
userName
:
string
;
userName
:
string
;
el
:
TJSElement
;
begin
begin
userName
:=
JS
.
toString
(
AuthService
.
TokenPayload
.
Properties
[
'user_name'
]);
userName
:=
JS
.
toString
(
AuthService
.
TokenPayload
.
Properties
[
'user_name'
]);
lblUsername
.
Caption
:=
' '
+
userName
.
ToLower
+
' '
;
lblUsername
.
Caption
:=
' '
+
userName
.
ToLower
+
' '
;
el
:=
Document
.
getElementById
(
'view.main.version'
);
if
Assigned
(
el
)
then
TJSHtmlElement
(
el
).
innerText
:=
'Version '
+
TDMConnection
.
clientVersion
;
FChildForm
:=
nil
;
FChildForm
:=
nil
;
FDetailsForm
:=
nil
;
FDetailsForm
:=
nil
;
FArchiveForm
:=
nil
;
FArchiveForm
:=
nil
;
...
...
webEMIMobile/View.UnitDetails.dfm
View file @
f1cc5bac
...
@@ -15,7 +15,7 @@ object FViewUnitDetails: TFViewUnitDetails
...
@@ -15,7 +15,7 @@ object FViewUnitDetails: TFViewUnitDetails
ElementHeaderClassName = 'table-light'
ElementHeaderClassName = 'table-light'
ElementTableClassName =
ElementTableClassName =
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'table table-sm table-striped table-hover table-bordered mb-0 ali' +
'gn-middle'
'gn-middle
small
'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonActiveElementClassName = 'btn btn-primary'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.ButtonElementClassName = 'btn btn-light'
Footer.DropDownElementClassName = 'form-control'
Footer.DropDownElementClassName = 'form-control'
...
...
webEMIMobile/View.UnitDetails.html
View file @
f1cc5bac
...
@@ -30,7 +30,7 @@
...
@@ -30,7 +30,7 @@
<div
class=
"card border-0 shadow-sm mb-2"
>
<div
class=
"card border-0 shadow-sm mb-2"
>
<div
class=
"card-body py-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>
<tbody>
<tr>
<tr>
<th
scope=
"row"
class=
"fw-semibold text-nowrap pe-2 w-auto"
>
Car:
</th>
<th
scope=
"row"
class=
"fw-semibold text-nowrap pe-2 w-auto"
>
Car:
</th>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment