Commit 6fa13c63 by Mac Stephens

Reworked envoy calls, added preliminary app structure, login functionality with…

Reworked envoy calls, added preliminary app structure, login functionality with embooking connection, and fncleaflet map with geojson district overlay
parent 691712ad
webEMIMobile/__history/
webEMIMobile/__recovery/
webEMIMobile/TMSWeb
webEMIMobile/Win32
webEMIMobile/css/__history/
emiMobileServer/__history
emiMobileServer/__recovery
emiMobileServer/doc/
emiMobileServer/Win32/
emiMobileServer/*.log
emiMobileServer/*.txt
emiMobileServer/Source/__history
emiMobileServer/Source/__recovery/
emiMobileServer/logs/*
*.local
*.exe
*.identcache
*.res
*.tvsconfig
object ApiDatabaseModule: TApiDatabaseModule
OnCreate = DataModuleCreate
Height = 480
Width = 640
object ucEnvoy: TUniConnection
ProviderName = 'PostgreSQL'
SpecificOptions.Strings = (
'PostgreSQL.Schema=envoy')
LoginPrompt = False
Left = 77
Top = 137
end
object PostgreSQLUniProvider1: TPostgreSQLUniProvider
Left = 228
Top = 138
end
object UniQuery1: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'')
Left = 363
Top = 138
end
object ucBooking: TUniConnection
ProviderName = 'Oracle'
Database = 'EMBOOKING'
Username = 'emBooking'
Server = 'EMBOOK-CPS'
LoginPrompt = False
Left = 77
Top = 231
EncryptedPassword = '9AFF92FF9DFF90FF90FF94FFCFFFCEFF'
end
object OracleUniProvider1: TOracleUniProvider
Left = 226
Top = 236
end
object uqBooking: TUniQuery
Connection = ucBooking
Left = 374
Top = 222
end
object uqUnitsCurrent: TUniQuery
Connection = ucBooking
SQL.Strings = (
'SELECT'
' uc.ENTRYID,'
' uc.UNITID,'
' COALESCE(uc.UNITNAME, uc.CAR_NUMBER) AS UNITNAME,'
' uc.UNIT_DISTRICT,'
' uc.GPS_LATITUDE,'
' uc.GPS_LONGITUDE'
'FROM UNITS_CURRENT@AVL_LINK uc')
Left = 462
Top = 324
end
object uqDISUnitsActive: TUniQuery
Connection = ucBooking
SQL.Strings = (
'SELECT'
' dua.UNITID,'
' dua.UNITNAME,'
' cun.CODE_DESC AS CARNUMBER_DESC,'
' cd.CODE_DESC AS DISTRICT_DESC,'
' cs.CODE_DESC AS SECTOR_DESC,'
' p1.PF_EMPNUM AS OFFICER1_EMPNUM,'
' p1.PF_LNAME AS OFFICER1_LAST_NAME,'
' p1.PF_FNAME AS OFFICER1_FIRST_NAME,'
' p1.PF_MI AS OFFICER1_MI,'
' p2.PF_EMPNUM AS OFFICER2_EMPNUM,'
' p2.PF_LNAME AS OFFICER2_LAST_NAME,'
' p2.PF_FNAME AS OFFICER2_FIRST_NAME,'
' p2.PF_MI AS OFFICER2_MI,'
' ca.LOCATION,'
' ca.COMPLAINT,'
' ca.UNITSTATUS,'
' cus.CODE_DESC AS UNIT_STATUS_DESC,'
' uc.ENTRYID,'
' uc.GPS_LATITUDE,'
' uc.GPS_LONGITUDE'
'FROM DIS_UNITS_ACTIVE dua'
'LEFT JOIN CD_UNIT_NUMBER cun ON cun.AGENCYCODE = dua.CA' +
'RNUMBER'
'LEFT JOIN CD_DISTRICT cd ON cd.AGENCYCODE = dua.DI' +
'STRICT'
'LEFT JOIN CD_SECTOR cs ON cs.AGENCYCODE = dua.SE' +
'CTOR AND cs.CODE_TYPE = cd.AGENCYCODE'
'LEFT JOIN PERSONNEL p1 ON dua.OFFICER1ID = p1.PF_' +
'NAMEID'
'LEFT JOIN PERSONNEL p2 ON dua.OFFICER2ID = p2.PF_' +
'NAMEID'
'LEFT JOIN CFS_ACTIVE ca ON dua.UNITID = ca.UNI' +
'TID'
'LEFT JOIN CD_UNITSTATUS cus ON ca.UNITSTATUS = cus.CO' +
'DE'
'LEFT JOIN UNITS_CURRENT@AVL_LINK uc ON dua.UNITID = uc.UNI' +
'TID')
Left = 462
Top = 374
end
object uqCFSActive: TUniQuery
Connection = ucBooking
SQL.Strings = (
'SELECT'
' ca.COMPLAINTID,'
' ca.UNITID,'
' ca.UNITNAME,'
' ca.DATEDISPATCHED,'
' ca.DATERESPONDED,'
' ca.DATEARRIVED,'
' ca.DATECLEARED,'
' ca.LOCATION'
'FROM CFS_ACTIVE ca'
'WHERE ca.COMPLAINTID = :COMPLAINTID'
'ORDER BY ca.DATEDISPATCHED')
Left = 278
Top = 318
ParamData = <
item
DataType = ftUnknown
Name = 'COMPLAINTID'
Value = nil
end>
end
object uqCFSMemos: TUniQuery
Connection = ucBooking
SQL.Strings = (
'SELECT'
' cm.MEMO_ID,'
' cm.CFSID,'
' cm.MEMO_TYPE,'
' cm.TIMESTAMP,'
' cm.BADGE_NUMBER,'
' cm.REMARKS'
'FROM CFS_MEMOS cm'
'WHERE cm.CFSID = :CFSID'
'ORDER BY cm.TIMESTAMP ASC')
Left = 282
Top = 376
ParamData = <
item
DataType = ftUnknown
Name = 'CFSID'
Value = nil
end>
end
object uqComplaintList: TUniQuery
Connection = ucBooking
SQL.Strings = (
'-- uqComplaintActive_List'
'SELECT'
' ca.COMPLAINTID,'
' ca.CFSID,'
' ca.COMPLAINT,'
' ca.AGENCY,'
' cdc.CODE_DESC AS DISPATCH_CODE_DESC,'
' ca.SOURCE,'
' ccs.CODE_DESC AS SOURCE_DESC,'
' ca.PRIORITY,'
' ca.ADDRESSID,'
' ca.ADDRESS,'
' ca.APARTMENT,'
' ca.CITY,'
' ca.BUSINESS,'
' ca.DISPATCHDISTRICT,'
' ca.DISPATCHSECTOR,'
' ca.ADDRESSDISTRICT,'
' ca.ADDRESSSECTOR,'
' ca.XCOORD,'
' ca.YCOORD,'
' ca.WARNINGS,'
' ca.CONTACTS,'
' ca.HISTORY,'
' ct.DATEREPORTED,'
' ct.DATERECEIVED,'
' ct.DATEDISPATCHED,'
' ct.DATERESPONDED,'
' ct.DATEARRIVED,'
' ct.DATECLEARED'
'FROM COMPLAINT_ACTIVE ca'
'JOIN COMPLAINT_TIMES ct ON ca.COMPLAINTID = ct.COMPLAINTID'
'LEFT JOIN CD_DISPATCHCODES cdc ON ca.DISPATCHCODE = cdc.CODE'
'LEFT JOIN CD_CALLSOURCES ccs ON ca.SOURCE = ccs.CODE'
'WHERE ca.COMPLAINT IS NOT NULL'
'ORDER BY ct.DATEREPORTED DESC, ca.PRIORITY DESC')
Left = 76
Top = 320
end
object uqComplaintDetails: TUniQuery
Connection = ucBooking
SQL.Strings = (
'-- uqComplaintActive_Detail'
'SELECT'
' ca.COMPLAINTID,'
' ca.CFSID,'
' ca.COMPLAINT,'
' ca.AGENCY,'
' ca.DISPATCHCODE,'
' cdc.CODE_DESC AS DISPATCH_CODE_DESC,'
' ca.SOURCE,'
' ccs.CODE_DESC AS SOURCE_DESC,'
' ca.PRIORITY,'
' ca.ADDRESSID,'
' ca.ADDRESS,'
' ca.APARTMENT,'
' ca.CITY,'
' ca.BUSINESS,'
' ca.DISPATCHDISTRICT,'
' ca.DISPATCHSECTOR,'
' ca.ADDRESSDISTRICT,'
' ca.ADDRESSSECTOR,'
' ca.XCOORD,'
' ca.YCOORD,'
' ca.WARNINGS,'
' ca.CONTACTS,'
' ca.HISTORY,'
' ct.DATEREPORTED,'
' ct.DATERECEIVED,'
' ct.DATEDISPATCHED,'
' ct.DATERESPONDED,'
' ct.DATEARRIVED,'
' ct.DATECLEARED'
'FROM COMPLAINT_ACTIVE ca'
'JOIN COMPLAINT_TIMES ct ON ca.COMPLAINTID = ct.COMPLAINTID'
'LEFT JOIN CD_DISPATCHCODES cdc ON ca.DISPATCHCODE = cdc.CODE'
'LEFT JOIN CD_CALLSOURCES ccs ON ca.SOURCE = ccs.CODE'
'WHERE ca.COMPLAINTID = :COMPLAINTID')
Left = 74
Top = 376
ParamData = <
item
DataType = ftUnknown
Name = 'COMPLAINTID'
Value = nil
end>
end
end
// Where the database is kept. Only used by Lookup.ServiceImpl to retrieve info
// from the data base and send it to the client.
// Author: ???
unit Api.Database;
interface
uses
System.SysUtils, System.Classes, Data.DB, MemDS, DBAccess, Uni, UniProvider,
PostgreSQLUniProvider, System.Variants, System.Generics.Collections, System.IniFiles,
Common.Logging, Vcl.Forms, OracleUniProvider;
type
TApiDatabaseModule = class(TDataModule)
ucEnvoy: TUniConnection;
PostgreSQLUniProvider1: TPostgreSQLUniProvider;
UniQuery1: TUniQuery;
ucBooking: TUniConnection;
OracleUniProvider1: TOracleUniProvider;
uqBooking: TUniQuery;
uqUnitsCurrent: TUniQuery;
uqDISUnitsActive: TUniQuery;
uqCFSActive: TUniQuery;
uqCFSMemos: TUniQuery;
uqComplaintList: TUniQuery;
uqComplaintDetails: TUniQuery;
procedure DataModuleCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
class procedure ExecSQL(const SQL: string);
end;
var
ApiDatabaseModule: TApiDatabaseModule;
implementation
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TApiDatabaseModule.DataModuleCreate(Sender: TObject);
var
iniFile: TIniFile;
begin
iniFile := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
try
ucEnvoy.Server := iniFile.ReadString('Database', 'Server', '');
ucEnvoy.Database := iniFile.ReadString('Database', 'Database', '');
ucEnvoy.Username := iniFile.ReadString('Database', 'Username', '');
ucEnvoy.Password := iniFile.ReadString('Database', 'Password', '');
try
Logger.Log(2, '');
Logger.Log(2, 'Connecting to envoyCalls Database (ApiDatabaseModule)...');
Logger.Log(2, Format('--ucEnvoy.Server: %s ucEnvoy.Username: %s', [ucEnvoy.Server, ucEnvoy.Username]));
if not ucEnvoy.Connected then
ucEnvoy.Connect;
Logger.Log(2, '--ucEnvoy connected!');
except
on E: Exception do
begin
Logger.Log(2, Format('Failed to connect to envoyCalls database: %s', [E.Message]));
end;
end;
Logger.Log(1, '');
Logger.Log(1, 'Loading Twilio settings...');
var twilioSID := iniFile.ReadString('Twilio', 'AccountSID', '');
if twilioSID.IsEmpty then
Logger.Log(1, 'Twilio->AccountSID: Entry not found')
else
Logger.Log(1, 'Twilio->AccountSID: ' + twilioSID);
var twilioAuth := iniFile.ReadString('Twilio', 'AuthHeader', '');
if twilioAuth.IsEmpty then
Logger.Log(1, 'Twilio->AuthHeader: Entry not found')
else
Logger.Log(1, 'Twilio->AuthHeader: ' + twilioAuth);
finally
iniFile.Free;
end;
end;
class procedure TApiDatabaseModule.ExecSQL(const SQL: string);
var
DB: TApiDatabaseModule;
begin
DB := TApiDatabaseModule.Create(nil);
try
DB.UniQuery1.SQL.Text := SQL;
DB.UniQuery1.ExecSQL;
finally
DB.Free;
end;
end;
end.
object ApiServerModule: TApiServerModule
Height = 273
Width = 230
object SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher
Left = 84
Top = 30
end
object XDataServer1: TXDataServer
Dispatcher = SparkleHttpSysDispatcher
ModelName = 'Api'
EntitySetPermissions = <>
SwaggerOptions.Enabled = True
SwaggerOptions.AuthMode = Jwt
SwaggerUIOptions.Enabled = True
SwaggerUIOptions.ShowFilter = True
SwaggerUIOptions.TryItOutEnabled = True
Left = 85
Top = 110
object XDataServer1Logging: TSparkleGenericMiddleware
OnMiddlewareCreate = XDataServer1LoggingMiddlewareCreate
end
object XDataServer1CORS: TSparkleCorsMiddleware
end
object XDataServer1Compress: TSparkleCompressMiddleware
end
object XDataServer1JWT: TSparkleJwtMiddleware
OnGetSecret = XDataServer1JWTGetSecret
end
end
end
unit Api.Server.Module;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections,
Aurelius.Drivers.SQLite,
Aurelius.Comp.Connection,
Aurelius.Drivers.Interfaces,
XData.Aurelius.ConnectionPool, XData.Server.Module, Sparkle.Comp.Server,
XData.Comp.Server, XData.Comp.ConnectionPool, Sparkle.Comp.HttpSysDispatcher,
Sparkle.Comp.JwtMiddleware, Sparkle.Middleware.Jwt, Aurelius.Criteria.Linq,
Sparkle.HttpServer.Module, Sparkle.HttpServer.Context,
Sparkle.Comp.CompressMiddleware, Sparkle.Comp.CorsMiddleware,
Sparkle.Comp.GenericMiddleware, Aurelius.Drivers.UniDac, UniProvider,
Data.DB, DBAccess, Uni;
type
TApiServerModule = class(TDataModule)
SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher;
XDataServer1: TXDataServer;
XDataServer1Logging: TSparkleGenericMiddleware;
XDataServer1CORS: TSparkleCorsMiddleware;
XDataServer1Compress: TSparkleCompressMiddleware;
XDataServer1JWT: TSparkleJwtMiddleware;
procedure XDataServer1LoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
procedure XDataServer1JWTGetSecret(Sender: TObject; var Secret: string);
private
{ Private declarations }
public
{ Public declarations }
procedure StartApiServer(ABaseUrl: string; AModelName: string);
end;
const
SERVER_PATH_SEGMENT = 'api';
var
ApiServerModule: TApiServerModule;
implementation
uses
Sparkle.HttpServer.Request,
Sparkle.Middleware.Cors,
Sparkle.Middleware.Compress,
XData.OpenApi.Service,
XData.Sys.Exceptions,
Common.Logging,
Common.Middleware.Logging,
Common.Config, Vcl.Forms, IniFiles;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
{ TApiServerModule }
function IsAdmin(Request: THttpServerRequest): Boolean;
var
User: IUserIdentity;
begin
User := Request.User;
Result := (User <> nil) and User.Claims.Exists('admin') and User.Claims['admin'].AsBoolean;
end;
procedure TApiServerModule.StartApiServer(ABaseUrl: string; AModelName: string);
var
Url: string;
begin
RegisterOpenApiService;
Url := ABaseUrl;
if not Url.EndsWith('/') then
Url := Url + '/';
Url := Url + SERVER_PATH_SEGMENT;
XDataServer1.BaseUrl := Url;
XDataServer1.ModelName := AModelName;
SparkleHttpSysDispatcher.Start;
Logger.Log(1, Format('Api server module listening at "%s"', [Url]));
end;
procedure TApiServerModule.XDataServer1LoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
begin
Middleware := TLoggingMiddleware.Create(Logger);
end;
procedure TApiServerModule.XDataServer1JWTGetSecret(Sender: TObject;
var Secret: string);
begin
Secret := serverConfig.jwtTokenSecret;
end;
end.
object AppServerModule: TAppServerModule
Height = 173
Width = 218
object SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher
Left = 88
Top = 16
end
object SparkleStaticServer: TSparkleStaticServer
Dispatcher = SparkleHttpSysDispatcher
Left = 88
Top = 88
object SparkleStaticServerCompress: TSparkleCompressMiddleware
end
object SparkleStaticServerLogging: TSparkleLoggingMiddleware
FormatString = ':method :url :statuscode - :responsetime ms'
ExceptionFormatString = '(%1:s: %4:s) %0:s - %2:s'
ErrorResponseOptions.ErrorCode = 'ServerError'
ErrorResponseOptions.ErrorMessageFormat = 'Internal server error: %4:s'
end
end
end
unit App.Server.Module;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections,
Sparkle.Comp.Server, Sparkle.Comp.StaticServer, Sparkle.Comp.HttpSysDispatcher,
Sparkle.Module.Static, Sparkle.Comp.CompressMiddleware,
Sparkle.HttpServer.Module, Sparkle.HttpServer.Context,
Sparkle.Comp.LoggingMiddleware;
type
TAppServerModule = class(TDataModule)
SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher;
SparkleStaticServer: TSparkleStaticServer;
SparkleStaticServerCompress: TSparkleCompressMiddleware;
SparkleStaticServerLogging: TSparkleLoggingMiddleware;
private
{ Private declarations }
public
{ Public declarations }
procedure StartAppServer(ABaseUrl: string);
end;
const
SERVER_PATH_SEGMENT = 'app';
var
AppServerModule: TAppServerModule;
implementation
uses
Sparkle.Middleware.Compress,
Common.Logging,
Common.Config;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
{ TAppServerModule }
procedure TAppServerModule.StartAppServer(ABaseUrl: string);
var
url: string;
begin
url := ABaseUrl;
if not url.EndsWith('/') then
url := url + '/';
url := url + SERVER_PATH_SEGMENT;
SparkleStaticServer.BaseUrl := url;
SparkleStaticServer.RootDir := serverConfig.webAppFolder;
SparkleHttpSysDispatcher.Start;
Logger.Log(1, Format('App server module listening at "%s", rootDir: %s', [url, serverConfig.webAppFolder]));
end;
end.
object AuthDatabase: TAuthDatabase
OnCreate = DataModuleCreate
OnDestroy = DataModuleDestroy
Height = 249
Width = 433
object uq: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'select * from users')
FetchRows = 100
Left = 162
Top = 45
object uquser_id: TLargeintField
FieldName = 'user_id'
end
object uqusername: TStringField
FieldName = 'username'
Required = True
Size = 64
end
object uqpassword: TMemoField
FieldName = 'password'
Required = True
BlobType = ftMemo
end
object uqdate_created: TStringField
FieldName = 'date_created'
Required = True
Size = 21
end
object uqadmin: TBooleanField
FieldName = 'admin'
end
object uqemail: TMemoField
FieldName = 'email'
BlobType = ftMemo
end
object uqphone_number: TStringField
FieldName = 'phone_number'
Size = 14
end
object uqfull_name: TStringField
FieldName = 'full_name'
Size = 30
end
object uqactive: TBooleanField
FieldName = 'active'
end
end
object uqMisc: TUniQuery
FetchRows = 100
Left = 249
Top = 45
end
object ucEnvoy: TUniConnection
ProviderName = 'PostgreSQL'
SpecificOptions.Strings = (
'PostgreSQL.Schema=envoy')
LoginPrompt = False
Left = 43
Top = 79
end
object PostgreSQLUniProvider1: TPostgreSQLUniProvider
Left = 276
Top = 156
end
object OracleUniProvider1: TOracleUniProvider
Left = 94
Top = 152
end
object ucBooking: TUniConnection
ProviderName = 'Oracle'
Database = 'EMBOOKING'
Username = 'emBooking'
Server = 'EMBOOK-CPS'
LoginPrompt = False
Left = 339
Top = 75
EncryptedPassword = '9AFF92FF9DFF90FF90FF94FFCFFFCEFF'
end
object uqUserPref: TUniQuery
Connection = ucBooking
SQL.Strings = (
'SELECT * FROM USER_PREFERENCES')
Left = 204
Top = 114
end
object uqBooking: TUniQuery
Connection = ucBooking
Left = 98
Top = 44
end
end
unit Auth.Database;
interface
uses
System.SysUtils, System.Classes, IniFiles, Vcl.Forms, MemDS,
Data.DB, DBAccess, Uni, UniProvider, PostgreSQLUniProvider, OracleUniProvider;
type
TAuthDatabase = class(TDataModule)
uq: TUniQuery;
uqMisc: TUniQuery;
ucEnvoy: TUniConnection;
PostgreSQLUniProvider1: TPostgreSQLUniProvider;
uquser_id: TLargeintField;
uqusername: TStringField;
uqpassword: TMemoField;
uqdate_created: TStringField;
uqadmin: TBooleanField;
uqemail: TMemoField;
uqphone_number: TStringField;
uqfull_name: TStringField;
uqactive: TBooleanField;
OracleUniProvider1: TOracleUniProvider;
ucBooking: TUniConnection;
uqUserPref: TUniQuery;
uqBooking: TUniQuery;
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure SetLoginAuditEntry( userStr: string );
end;
var
AuthDatabase: TAuthDatabase;
implementation
uses
System.JSON,
Common.Config,
Common.Logging,
uLibrary;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TAuthDatabase.DataModuleCreate(Sender: TObject);
var
iniFile: TIniFile;
begin
iniFile := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
try
ucEnvoy.Server := iniFile.ReadString('Database', 'Server', '');
ucEnvoy.Database := iniFile.ReadString('Database', 'Database', '');
ucEnvoy.Username := iniFile.ReadString('Database', 'Username', '');
ucEnvoy.Password := iniFile.ReadString('Database', 'Password', '');
ucBooking.Server := IniFile.ReadString('EMB Database', 'Server', 'EMBOOKING');
ucBooking.Username := IniFile.ReadString('EMB Database', 'Username', 'emBooking');
ucBooking.Password := IniFile.ReadString('EMB Database', 'Password', 'embook01');
try
Logger.Log(2, '');
Logger.Log(2, 'Connecting to envoyCalls Database...');
Logger.Log(2, Format('--ucEnvoy.Server: %s ucEnvoy.Username: %s', [ucEnvoy.Server, ucEnvoy.Username]));
if not ucEnvoy.Connected then
ucEnvoy.Connect;
Logger.Log(2, '--ucEnvoy connected!');
Logger.Log(2, '');
Logger.Log(2, 'Connecting to emBooking Database...');
if not ucBooking.Connected then
ucBooking.Connect;
except
on E: Exception do
begin
Logger.Log(2, Format('Failed to connect to database: %s', [E.Message]));
end;
end;
Logger.Log(1, '');
Logger.Log(1, 'Loading Twilio settings...');
var twilioSID := iniFile.ReadString('Twilio', 'AccountSID', '');
if twilioSID.IsEmpty then
Logger.Log(1, 'Twilio->AccountSID: Entry not found')
else
Logger.Log(1, 'Twilio->AccountSID Found');
var twilioAuth := iniFile.ReadString('Twilio', 'AuthHeader', '');
if twilioAuth.IsEmpty then
Logger.Log(1, 'Twilio->AuthHeader: Entry not found')
else
Logger.Log(1, 'Twilio->AuthHeader Found');
finally
iniFile.Free;
end;
end;
procedure TAuthDatabase.DataModuleDestroy(Sender: TObject);
begin
ucEnvoy.Connected := false;
ucBooking.Connected := false;
end;
procedure TAuthDatabase.SetLoginAuditEntry( userStr: string );
var
auditMasterId: string;
userInfo: TStringList;
entry: string;
username: string;
fullname: string;
agency: string;
userid: string;
personnelid: string;
admin: boolean;
i: Integer;
begin
Logger.Log( 3, 'TAuthDatabase.SetLoginAuditEntry - start' );
userInfo := TStringList.Create;
try
userInfo.Delimiter := '&';
userInfo.StrictDelimiter := True;
userInfo.DelimitedText := userStr;
username := userInfo.Values['username'];
fullname := userInfo.Values['fullname'];
agency := userInfo.Values['agency'];
userid := userInfo.Values['userId'];
personnelid := userInfo.Values['personnelid'];
if ServerConfig.auditEnabled then
begin
auditMasterId := GetNextSeqVal( uqMisc, 'SEQ_AUDIT_MASTERID' );
SetMasterAuditEntry( uq, auditMasterId, 'BKG', '', agency, personnelid, fullname, 'Login', '', 'webCharms' );
end
else
begin
Logger.Log( 3, 'SetSearchAuditEntry->SetMasterAuditEntry - auditEnabled = false' );
end;
finally
userInfo.Free;
end;
end;
end.
object AuthServerModule: TAuthServerModule
Height = 273
Width = 230
object SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher
Left = 88
Top = 16
end
object XDataServer: TXDataServer
Dispatcher = SparkleHttpSysDispatcher
ModelName = 'Auth'
EntitySetPermissions = <>
SwaggerOptions.Enabled = True
SwaggerUIOptions.Enabled = True
SwaggerUIOptions.ShowFilter = True
SwaggerUIOptions.TryItOutEnabled = True
Left = 91
Top = 92
object XDataServerLogging: TSparkleGenericMiddleware
OnMiddlewareCreate = XDataServerLoggingMiddlewareCreate
end
object XDataServerCORS: TSparkleCorsMiddleware
end
object XDataServerCompress: TSparkleCompressMiddleware
end
end
end
unit Auth.Server.Module;
interface
uses
System.SysUtils, System.Classes, System.Generics.Collections,
Aurelius.Comp.Connection,
Aurelius.Drivers.Interfaces,
XData.Aurelius.ConnectionPool, XData.Server.Module, XData.Comp.ConnectionPool,
Sparkle.Comp.Server, Sparkle.Comp.JwtMiddleware, XData.Comp.Server,
Sparkle.Comp.HttpSysDispatcher, Sparkle.Comp.CompressMiddleware,
Sparkle.Comp.CorsMiddleware, Sparkle.HttpServer.Module,
Sparkle.HttpServer.Context, Sparkle.Comp.GenericMiddleware;
type
TAuthServerModule = class(TDataModule)
SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher;
XDataServer: TXDataServer;
XDataServerLogging: TSparkleGenericMiddleware;
XDataServerCORS: TSparkleCorsMiddleware;
XDataServerCompress: TSparkleCompressMiddleware;
procedure XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
private
{ Private declarations }
public
{ Public declarations }
procedure StartAuthServer(ABaseUrl: string; AModelName: string);
end;
const
SERVER_PATH_SEGMENT = 'auth';
var
AuthServerModule: TAuthServerModule;
implementation
uses
Sparkle.Middleware.Cors,
Sparkle.Middleware.Compress,
XData.OpenApi.Service,
Common.Logging,
Common.Middleware.Logging;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
{ TAuthServerModule }
procedure TAuthServerModule.StartAuthServer(ABaseUrl: string;
AModelName: string);
var
Url: string;
begin
RegisterOpenApiService;
Url := ABaseUrl;
if not Url.EndsWith('/') then
Url := Url + '/';
Url := Url + SERVER_PATH_SEGMENT;
XDataServer.BaseUrl := Url;
XDataServer.ModelName := AModelName;
SparkleHttpSysDispatcher.Start;
Logger.Log(1, Format('Auth server module listening at "%s"', [Url]));
end;
procedure TAuthServerModule.XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
begin
Middleware := TLoggingMiddleware.Create(Logger);
end;
end.
unit Auth.Service;
interface
uses
XData.Service.Common,
Aurelius.Mapping.Attributes,
System.Generics.Collections,
System.JSON;
const
AUTH_MODEL = 'Auth';
type
TAgencyItem = class
public
agency: String;
constructor Create( AAgency : String );
end;
TAgenciesList = class
public
count: integer;
returned: integer;
data: TList<TAgencyItem>;
end;
TAgencyConfigItem = class
public
id: integer;
agency: String;
name: String;
end;
TAgencyConfigList = class
public
count: integer;
returned: integer;
data: TList<TAgencyConfigItem>;
end;
[ServiceContract, Model(AUTH_MODEL)]
IAuthService = interface(IInvokable)
['{D2290B28-964C-4155-A83A-DAE87C4C7FE7}']
function Login(const user, password, agency: string): string;
[HttpGet] function GetAgenciesList(): TAgenciesList;
[HttpGet] function GetAgencyConfigList: TAgencyConfigList;
function VerifyVersion(ClientVersion: string): TJSONObject;
end;
implementation
{ TAgencyItem }
constructor TAgencyItem.Create(AAgency: String);
begin
agency := AAgency;
end;
end.
constructor TAgencyItem.Create(AAgency: String);
begin
agency := AAgency;
end;
end.
unit Auth.ServiceImpl;
interface
uses
XData.Service.Common,
XData.Server.Module,
Auth.Service,
Auth.Database,
Uni, Data.DB, System.JSON, System.SysUtils, System.IOUtils, IniFiles;
type
[ServiceImplementation]
TAuthService = class(TInterfacedObject, IAuthService)
strict private
authDB: TAuthDatabase;
function GetQuery: TUniQuery;
private
userName: string;
userFullName: string;
userAgency: string;
userBadge: string;
userId: string;
userPersonnelId: string;
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
function VerifyVersion(ClientVersion: string): TJSONObject;
property Query: TUniQuery read GetQuery;
function CheckUser(const User, Password, Agency: string): Integer;
function Decrypt(inStr, keyStr: AnsiString): AnsiString;
public
function Login(const User, Password, Agency: string): string;
function GetAgencieslist(): TAgenciesList;
function GetAgencyConfiglist: TAgencyConfigList;
end;
implementation
uses
System.DateUtils,
System.Generics.Collections,
Bcl.JOSE.Core.Builder,
Bcl.JOSE.Core.JWT,
Aurelius.Global.Utils,
XData.Sys.Exceptions,
Common.Logging,
Common.Config;
{ TAuthService }
procedure TAuthService.AfterConstruction;
begin
inherited;
authDB := TAuthDatabase.Create(nil);
end;
procedure TAuthService.BeforeDestruction;
begin
authDB.Free;
inherited;
end;
function TAuthService.GetQuery: TUniQuery;
begin
Result := authDB.uq;
end;
function TAuthService.GetAgenciesList: TAgenciesList;
var
agency: TAgencyItem;
begin
Logger.Log(2, 'TAuthService.GetAgenciesList - call');
if authDB.ucBooking.Connected then
begin
authDB.uqBooking.Close;
authDB.uqBooking.SQL.Text := 'select * from agencies order by agency';
authDB.uqBooking.Open;
Result := TAgenciesList.Create;
Result.data := TList<TAgencyItem>.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result.data);
while not authDB.uqBooking.Eof do
begin
agency := TAgencyItem.Create(authDB.uqBooking.FieldByName('agency').AsString);
TXDataOperationContext.Current.Handler.ManagedObjects.Add(agency);
Result.Data.Add(agency);
authDB.uqBooking.Next;
end;
authDB.uqBooking.Close;
Result.count := Result.data.count;
Result.returned := Result.data.count;
Logger.Log( 2, 'GetAgenciesList - Count: ' + IntToStr(Result.Count) + ' Returned: ' + IntToStr(Result.Returned) );
end
else
begin
Logger.Log( 2, 'TAuthService.GetAgenciesList - Error: connecting to CPS database!' );
raise EXDataHttpException.Create('Error connecting to CPS database!');
end;
end;
function TAuthService.GetAgencyConfigList: TAgencyConfigList;
var
agencyConfig: TAgencyConfigItem;
sql: string;
begin
Logger.Log(2, 'AuthService.GetAgencyConfigList - call');
authDB.uqBooking.Close;
sql := 'select agency_id, agency, agency_name from agencyconfig ' +
'where active = ''Y'' order by agency';
authDB.uqBooking.SQL.Text := sql;
authDB.uqBooking.Open;
Result := TAgencyConfigList.Create;
Result.data := TList<TAgencyConfigItem>.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result.data);
while not authDB.uqBooking.Eof do
begin
agencyConfig := TAgencyConfigItem.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(agencyConfig);
Result.data.Add(agencyConfig);
agencyConfig.id := authDB.uqBooking.FieldByName('agency_id').AsInteger;
agencyConfig.agency := authDB.uqBooking.FieldByName('agency').AsString;
agencyConfig.name := authDB.uqBooking.FieldByName('agency_name').AsString;
authDB.uqBooking.Next;
end;
authDB.uqBooking.Close;
Result.count := Result.data.Count;
Result.returned := Result.data.Count;
Logger.Log(2, 'GetAgencyConfigList - ' + IntToStr(Result.Count));
end;
function TAuthService.VerifyVersion(ClientVersion: string): TJSONObject;
var
iniFile: TIniFile;
webClientVersion: string;
begin
Logger.Log(3, 'AuthService.VerifyVersion called');
Result := TJSONObject.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result);
iniFile := TIniFile.Create(ChangeFileExt(ParamStr(0), '.ini'));
try
webClientVersion := iniFile.ReadString('Settings', 'webClientVersion', '');
Result.AddPair('webClientVersion', webClientVersion);
if webClientVersion = '' then
begin
Logger.Log(2, 'AuthService.VerifyVersion: webClientVersion not configured');
Result.AddPair('error', 'webClientVersion is not configured.');
Exit;
end;
if clientVersion <> webClientVersion then
begin
Logger.Log(2, 'AuthService.VerifyVersion: client version mismatch');
Result.AddPair('error',
'Your browser is running an old version of the app.' + sLineBreak +
'Please click below to reload.');
end
else
Logger.Log(3, 'AuthService.VerifyVersion: version check passed');
finally
iniFile.Free;
end;
end;
function TAuthService.Login(const User, Password, Agency: string): string;
var
userState: Integer;
JWT: TJWT;
begin
Logger.Log(1, Format('AuthService.Login - User: "%s" Agency: "%s"', [User, Agency]));
userState := CheckUser(User, Password, Agency);
if userState = 0 then
raise EXDataHttpUnauthorized.Create('Invalid user or password');
if userState = 1 then
raise EXDataHttpUnauthorized.Create('User not active!');
JWT := TJWT.Create;
try
JWT.Claims.JWTId := LowerCase(Copy(TUtils.GuidToVariant(TUtils.NewGuid), 2, 36));
JWT.Claims.IssuedAt := Now;
JWT.Claims.Expiration := IncHour(Now, 24);
JWT.Claims.SetClaimOfType<string>('user_name', userName);
JWT.Claims.SetClaimOfType<string>('user_fullname', userFullName);
JWT.Claims.SetClaimOfType<string>('user_agency', userAgency);
JWT.Claims.SetClaimOfType<string>('user_badge', userBadge);
JWT.Claims.SetClaimOfType<string>('user_id', userId);
JWT.Claims.SetClaimOfType<string>('user_personnelid', userPersonnelId);
Result := TJOSE.SHA256CompactToken(ServerConfig.jwtTokenSecret, JWT);
finally
JWT.Free;
end;
end;
function TAuthService.CheckUser(const User, Password, Agency: string): Integer;
var
userStr: string;
sqlStr: string;
decryptedPassword: AnsiString;
passwordKey: AnsiString;
begin
Logger.Log(3, Format('LoginService.CheckUser - User: "%s" Agency: "%s"', [User, Agency]));
passwordKey := 'wx3cFo$kIf2jrk(gOmvi7uvPfk*iorE8@kfm+nvR6jfh=swDqalpokSjf';
sqlStr := 'select u.* from users@cps_link u ';
sqlStr := sqlStr + 'where upper(user_name) = ' + QuotedStr(UpperCase(Trim(User))) + ' ';
sqlStr := sqlStr + 'and u.dept = ' + QuotedStr(UpperCase(Trim(Agency)));
authDB.uqBooking.SQL.Text := sqlStr;
Logger.Log(4, Format('LoginService.CheckUser - Query: "%s"', [sqlStr]));
authDB.uqBooking.Open;
if authDB.uqBooking.IsEmpty then
Result := 0
else
begin
if authDB.uqBooking.FieldByName('active').AsString = 'F' then
Result := 1
else
begin
decryptedPassword := Uppercase(Trim(Decrypt(authDB.uqBooking.FieldByName('password').AsString, passwordKey)));
if decryptedPassword = Uppercase(Trim(Password)) then
begin
userName := authDB.uqBooking.FieldByName('user_name').AsString;
userFullName := authDB.uqBooking.FieldByName('firstname').AsString + ' ' + authDB.uqBooking.FieldByName('lastname').AsString;
userAgency := authDB.uqBooking.FieldByName('dept').AsString;
userBadge := authDB.uqBooking.FieldByName('badgenum').AsString;
userId := authDB.uqBooking.FieldByName('userid').AsString;
userPersonnelId := authDB.uqBooking.FieldByName('personnelid').AsString;
userStr := '?username=' + userName;
userStr := userStr + '&fullname=' + userFullName;
userStr := userStr + '&agency=' + userAgency;
userStr := userStr + '&userid=' + userId;
userStr := userStr + '&personnelid=' + userPersonnelId;
authDB.SetLoginAuditEntry(userStr);
Result := 2;
end
else
Result := 0;
end;
end;
end;
function TAuthService.Decrypt(inStr, keyStr: AnsiString): AnsiString;
var
outStr: AnsiString;
k, i: integer;
tempKeyStr: AnsiString;
begin
k := Integer(inStr[1]);
tempKeyStr := keyStr;
while Length(tempKeyStr) < 256 do
tempKeyStr := tempKeyStr + tempKeyStr;
for i := 1 to Length(inStr) - 1 do
begin
k := (k + i) mod 256;
outStr := outStr + AnsiChar(Integer(inStr[i+1]) xor Integer(tempKeyStr[k+1]));
end;
Result := Trim(outStr);
end;
initialization
RegisterServiceType(TAuthService);
end.
unit Common.Config;
interface
const
defaultServerUrl = 'http://localhost:2009/emiMobile/';
type
TServerConfig = class
private
Furl: string;
FJWTTokenSecret: string;
FAdminPassword: string;
FWebAppFolder: string;
FReportsFolder: string;
FMemoLogLevel: Integer;
FFileLogLevel: Integer;
FAuditEnabled: Boolean;
public
constructor Create;
property url: string read FUrl write FUrl;
property jwtTokenSecret: string read FJWTTokenSecret write FJWTTokenSecret;
property adminPassword: string read FAdminPassword write FAdminPassword;
property webAppFolder: string read FWebAppFolder write FWebAppFolder;
property reportsFolder: string read FReportsFolder write FReportsFolder;
property auditEnabled: Boolean read FAuditEnabled write FAuditEnabled;
property memoLogLevel: Integer read FMemoLogLevel write FMemoLogLevel;
property fileLogLevel: Integer read FFileLogLevel write FFileLogLevel;
end;
procedure LoadServerConfig;
var
serverConfig: TServerConfig;
implementation
uses
Bcl.Json, System.SysUtils, System.IOUtils, System.StrUtils,
Common.Logging;
procedure LoadServerConfig;
var
configFile: string;
localConfig: TServerConfig;
begin
Logger.Log(1, '--LoadServerConfig - start');
configFile := TPath.ChangeExtension(ParamStr(0), '.json');
Logger.Log(1, '-- Config file: ' + configFile);
if TFile.Exists(configFile) then
begin
Logger.Log(1, '-- Config file found.');
localConfig := TJson.Deserialize<TServerConfig>(TFile.ReadAllText(configFile));
Logger.Log(1, '-- localConfig loaded from config file');
serverConfig.Free;
Logger.Log(1, '-- serverConfig.Free - called');
serverConfig := localConfig;
Logger.Log(1, '-- serverConfig := localConfig - called');
Logger.Log(1, '');
Logger.Log(1, '--- Server Config Values ---');
Logger.Log(1, '-- url: ' + serverConfig.url + IfThen(serverConfig.url = defaultServerUrl, ' [default]', ' [from config]'));
Logger.Log(1, '-- adminPassword: ' + serverConfig.adminPassword + IfThen(serverConfig.adminPassword = 'whatisthisusedfor', ' [default]', ' [from config]'));
Logger.Log(1, '-- jwtTokenSecret: ' + serverConfig.jwtTokenSecret + IfThen(serverConfig.jwtTokenSecret = 'super_secret0123super_secret4567', ' [default]', ' [from config]'));
Logger.Log(1, '-- webAppFolder: ' + serverConfig.webAppFolder + IfThen(serverConfig.webAppFolder = 'static', ' [default]', ' [from config]'));
Logger.Log(1, '-- memoLogLevel: ' + IntToStr(serverConfig.memoLogLevel));
Logger.Log(1, '-- fileLogLevel: ' + IntToStr(serverConfig.fileLogLevel));
Logger.Log(1, '-- auditEnabled: ' + BoolToStr(serverConfig.auditEnabled, True));
end
else
begin
Logger.Log(1, '-- Config file not found.');
end;
Logger.Log(1, '-------------------------------------------------------------');
Logger.Log(1, '--LoadServerConfig - end');
end;
{ TServerConfig }
constructor TServerConfig.Create;
begin
Logger.Log(1, '--TServerConfig.Create - start');
url := defaultServerUrl;
adminPassword := 'whatisthisusedfor';
jwtTokenSecret := 'super_secret0123super_secret4567';
webAppFolder := 'static';
reportsFolder := 'reports';
memoLogLevel := 3;
fileLogLevel := 4;
auditEnabled := False;
Logger.Log(1, '--TServerConfig.Create - end');
end;
end.
unit Common.Ini;
interface
uses
System.SysUtils, System.IniFiles, Vcl.Forms;
type
TIniEntries = class
private
// [Settings]
FMemoLogLevel: Integer;
FMemoLogLevelFromIni: Boolean;
FFileLogLevel: Integer;
FFileLogLevelFromIni: Boolean;
FLogFileNum: Integer;
FLogFileNumFromIni: Boolean;
FEmailPolling: Boolean;
FEmailPollingFromIni: Boolean;
FEmailPollingInterval: Integer;
FEmailPollingIntervalFromIni: Boolean;
FWebClientVersion: string;
FWebClientVersionFromIni: Boolean;
FTwilioUpdateTime: Integer;
FTwilioUpdateTimeFromIni: Boolean;
// [Database]
FDatabaseServer: string;
FDatabaseServerFromIni: Boolean;
FDatabaseName: string;
FDatabaseNameFromIni: Boolean;
FDatabaseUsername: string;
FDatabaseUsernameFromIni: Boolean;
FDatabasePassword: string;
FDatabasePasswordFromIni: Boolean;
// [Twilio]
FTwilioSID: string;
FTwilioSIDFromIni: Boolean;
FTwilioAuthHeader: string;
FTwilioAuthHeaderFromIni: Boolean;
public
constructor Create;
// [Settings]
property MemoLogLevel: Integer read FMemoLogLevel;
property MemoLogLevelFromIni: Boolean read FMemoLogLevelFromIni;
property FileLogLevel: Integer read FFileLogLevel;
property FileLogLevelFromIni: Boolean read FFileLogLevelFromIni;
property LogFileNum: Integer read FLogFileNum;
property LogFileNumFromIni: Boolean read FLogFileNumFromIni;
property EmailPolling: Boolean read FEmailPolling;
property EmailPollingFromIni: Boolean read FEmailPollingFromIni;
property EmailPollingInterval: Integer read FEmailPollingInterval;
property EmailPollingIntervalFromIni: Boolean read FEmailPollingIntervalFromIni;
property WebClientVersion: string read FWebClientVersion;
property WebClientVersionFromIni: Boolean read FWebClientVersionFromIni;
property TwilioUpdateTime: Integer read FTwilioUpdateTime;
property TwilioUpdateTimeFromIni: Boolean read FTwilioUpdateTimeFromIni;
// [Database]
property DatabaseServer: string read FDatabaseServer;
property DatabaseServerFromIni: Boolean read FDatabaseServerFromIni;
property DatabaseName: string read FDatabaseName;
property DatabaseNameFromIni: Boolean read FDatabaseNameFromIni;
property DatabaseUsername: string read FDatabaseUsername;
property DatabaseUsernameFromIni: Boolean read FDatabaseUsernameFromIni;
property DatabasePassword: string read FDatabasePassword;
property DatabasePasswordFromIni: Boolean read FDatabasePasswordFromIni;
// [Twilio]
property TwilioSID: string read FTwilioSID;
property TwilioSIDFromIni: Boolean read FTwilioSIDFromIni;
property TwilioAuthHeader: string read FTwilioAuthHeader;
property TwilioAuthHeaderFromIni: Boolean read FTwilioAuthHeaderFromIni;
end;
procedure LoadIniEntries;
var
IniEntries: TIniEntries;
implementation
procedure LoadIniEntries;
begin
if Assigned(IniEntries) then
IniEntries.Free;
IniEntries := TIniEntries.Create;
end;
{ TIniEntries }
constructor TIniEntries.Create;
var
iniFile: TIniFile;
begin
iniFile := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
try
// [Settings]
FMemoLogLevel := iniFile.ReadInteger('Settings', 'MemoLogLevel', 3);
FMemoLogLevelFromIni := iniFile.ValueExists('Settings', 'MemoLogLevel');
FFileLogLevel := iniFile.ReadInteger('Settings', 'FileLogLevel', 3);
FFileLogLevelFromIni := iniFile.ValueExists('Settings', 'FileLogLevel');
FLogFileNum := iniFile.ReadInteger('Settings', 'LogFileNum', 0);
FLogFileNumFromIni := iniFile.ValueExists('Settings', 'LogFileNum');
FEmailPolling := iniFile.ReadBool('Settings', 'EmailPolling', False);
FEmailPollingFromIni := iniFile.ValueExists('Settings', 'EmailPolling');
FEmailPollingInterval := iniFile.ReadInteger('Settings', 'EmailPollingInterval', 1);
FEmailPollingIntervalFromIni := iniFile.ValueExists('Settings', 'EmailPollingInterval');
FWebClientVersion := iniFile.ReadString('Settings', 'webClientVersion', '');
FWebClientVersionFromIni := iniFile.ValueExists('Settings', 'webClientVersion');
FTwilioUpdateTime := iniFile.ReadInteger('Settings', 'TwilioUpdateTime', 0);
FTwilioUpdateTimeFromIni := iniFile.ValueExists('Settings', 'TwilioUpdateTime');
// [Database]
FDatabaseServer := iniFile.ReadString('Database', 'Server', 'localhost');
FDatabaseServerFromIni := iniFile.ValueExists('Database', 'Server');
FDatabaseName := iniFile.ReadString('Database', 'Database', 'envoy_db');
FDatabaseNameFromIni := iniFile.ValueExists('Database', 'Database');
FDatabaseUsername := iniFile.ReadString('Database', 'Username', 'postgres');
FDatabaseUsernameFromIni := iniFile.ValueExists('Database', 'Username');
FDatabasePassword := iniFile.ReadString('Database', 'Password', '');
FDatabasePasswordFromIni := iniFile.ValueExists('Database', 'Password');
// [Twilio]
FTwilioSID := iniFile.ReadString('Twilio', 'AccountSID', '');
FTwilioSIDFromIni := iniFile.ValueExists('Twilio', 'AccountSID');
FTwilioAuthHeader := iniFile.ReadString('Twilio', 'AuthHeader', '');
FTwilioAuthHeaderFromIni := iniFile.ValueExists('Twilio', 'AuthHeader');
finally
iniFile.Free;
end;
end;
end.
unit Common.Logging;
interface
uses
Generics.Collections;
type
ILog = interface;
ILogAppender = interface;
ILogger = interface
['{4D667DD2-BE11-496B-B92A-C47E03520BD6}']
procedure Log(logLevel: integer; Msg: string); overload;
procedure Log(logLevel: integer; Log: ILog); overload;
procedure AddAppender(ALogAppender: ILogAppender);
function Appenders: TArray<ILogAppender>;
end;
ILogAppender = interface
['{A3B7D6FB-C75F-4BEF-8797-907B6FDAD5D2}']
procedure Send(logLevel: integer; Log: ILog);
end;
ILog = interface
['{8E9C6580-C099-47C0-8B1B-6D7A28EC4FA3}']
function GetMessage: string;
end;
TLogger = class( TInterfacedObject, ILogger )
strict private
FAppenders: TList<ILogAppender>;
public
constructor Create; overload;
constructor Create(ALogger: ILogger); overload;
destructor Destroy; override;
procedure Log(logLevel: integer; Msg: string); overload;
procedure Log(logLevel: integer; Log: ILog); overload;
procedure AddAppender(ALogAppender: ILogAppender);
function Appenders: TArray<ILogAppender>;
end;
TLogMessage = class( TInterfacedObject, ILog )
private
FMsg: string;
public
constructor Create(AMsg: string);
function GetMessage: string;
end;
function Logger: ILogger;
implementation
var
_Logger: ILogger;
function Logger: ILogger;
begin
Result := _Logger;
end;
{ TLogMessage }
constructor TLogMessage.Create(AMsg: string);
begin
FMsg := AMsg;
end;
function TLogMessage.GetMessage: string;
begin
Result := FMsg;
end;
{ TLogger }
procedure TLogger.AddAppender(ALogAppender: ILogAppender);
begin
FAppenders.Add(ALogAppender);
end;
function TLogger.Appenders: TArray<ILogAppender>;
var
I: integer;
begin
SetLength(Result, FAppenders.Count);
for I := 0 to FAppenders.Count - 1 do
Result[I] := FAppenders[I];
end;
constructor TLogger.Create(ALogger: ILogger);
var
Appender: ILogAppender;
begin
FAppenders := TList<ILogAppender>.Create;
if ALogger <> nil then
for Appender in ALogger.Appenders do
AddAppender(Appender);
end;
constructor TLogger.Create;
begin
Create(nil);
end;
destructor TLogger.Destroy;
begin
FAppenders.Free;
inherited;
end;
procedure TLogger.Log(logLevel: integer; Log: ILog);
var
Appender: ILogAppender;
begin
for Appender in FAppenders do
Appender.Send(logLevel, Log);
end;
procedure TLogger.Log(logLevel: integer; Msg: string);
begin
Log(logLevel, TLogMessage.Create(Msg));
end;
initialization
_Logger := TLogger.Create;
end.
unit Common.Middleware.Logging;
interface
uses
System.Classes, System.SysUtils,
Sparkle.HttpServer.Module,
Sparkle.HttpServer.Context,
Sparkle.Http.Headers,
Common.Logging;
type
TLoggingMiddleware = class(THttpServerMiddleware, IHttpServerMiddleware)
private
FLogger: ILogger;
function GetNewHttpRequestLog(Request: THttpServerRequest): ILog;
protected
procedure ProcessRequest(Context: THttpServerContext; Next: THttpServerProc); override;
public
constructor Create(ALogger: ILogger);
end;
THttpRequestLog = class( TInterfacedObject, ILog )
strict private
FMethod: string;
FUriPath: string;
FUriQuery: string;
FProtocol: string;
FRemoteIp: string;
FHeaders: string;
FContent: string;
FContentLength: Int64;
public
constructor Create(AMethod: string; AUriPath: string; AUriQuery: string;
AProtocol: string; ARemoteIp: string; AHeaders: string; AContent: string;
AContentLength: Int64);
function GetMessage: string;
end;
// THttpResponseLog = class( TInterfacedObject, ILog )
// strict private
// FMethod: string;
// FUriPath: string;
// FUriQuery: string;
// FProtocol: string;
// FRemoteIp: string;
// FHeaders: string;
// FContent: string;
// FContentLength: Int64;
// public
// constructor Create(AMethod: string; AUriPath: string; AUriQuery: string;
// AProtocol: string; ARemoteIp: string; AHeaders: string; AContent: string;
// AContentLength: Int64);
// function GetMessage: string;
// end;
implementation
{ TLoggingMiddleware }
constructor TLoggingMiddleware.Create(ALogger: ILogger);
begin
FLogger := TLogger.Create(ALogger);
end;
function TLoggingMiddleware.GetNewHttpRequestLog(
Request: THttpServerRequest): ILog;
var
Msg: TStrings;
Header: THttpHeaderInfo;
StringStream: TStringStream;
Headers, Content: string;
begin
Result := nil;
Msg := TStringList.Create;
try
if Length(Request.Headers.AllHeaders.ToArray) = 0 then
Headers := ''
else
begin
for Header in Request.Headers.AllHeaders do
Msg.Add(Header.Name + ': ' + Header.Value);
Headers := Msg.Text;
end;
finally
Msg.Free;
end;
StringStream := TStringStream.Create(Request.Content);
try
Content := StringStream.DataString
finally
StringStream.Free;
end;
Result := THttpRequestLog.Create(
Request.Method,
Request.Uri.Path,
Request.Uri.Query,
Request.Protocol,
Request.RemoteIp,
Headers,
Content,
Request.ContentLength
);
end;
procedure TLoggingMiddleware.ProcessRequest(Context: THttpServerContext;
Next: THttpServerProc);
var
RequestLogMessage: string;
begin
Context.Response.OnHeaders(
procedure(Resp: THttpServerResponse)
begin
if (Resp.StatusCode >= 400) and (Resp.StatusCode <= 499) then
FLogger.Log(5, Format('%d %s on %s', [Resp.StatusCode, Resp.StatusReason, RequestLogMessage]));
end
);
RequestLogMessage := GetNewHttpRequestLog(Context.Request).GetMessage;
FLogger.Log(5, RequestLogMessage);
Next(Context);
end;
{ THttpRequestLog }
constructor THttpRequestLog.Create(AMethod, AUriPath, AUriQuery,
AProtocol, ARemoteIp, AHeaders, AContent: string; AContentLength: Int64);
begin
FMethod := AMethod;
FUriPath := AUriPath;
FUriQuery := AUriQuery;
FProtocol := AProtocol;
FRemoteIp := ARemoteIp;
FHeaders := AHeaders;
FContent := AContent;
FContentLength := AContentLength;
end;
function THttpRequestLog.GetMessage: string;
var
Msg: TStrings;
begin
Result := '';
Msg := TStringList.Create;
try
Msg.Add(Format('%s %s %s',
[
FMethod,
FUriPath + FUriQuery,
FProtocol,
FRemoteIp
]));
// if Not FHeaders.IsEmpty then
// Msg.Add(FHeaders);
// if (Not FContent.IsEmpty) then
// Msg.Add(FContent);
Result := Trim(Msg.Text);
finally
Msg.Free;
end;
end;
end.
object FData: TFData
Left = 0
Top = 0
ActiveControl = DBAdvGrid1
Caption = 'FData'
ClientHeight = 522
ClientWidth = 1002
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
OnCreate = FormCreate
OnDestroy = FormDestroy
TextHeight = 15
object lblStartDate: TLabel
Left = 0
Top = 8
Width = 51
Height = 15
Alignment = taRightJustify
Caption = 'Start Date'
end
object lblLocation: TLabel
Left = 0
Top = 40
Width = 46
Height = 15
Caption = 'Location'
end
object Label1: TLabel
Left = 190
Top = 9
Width = 47
Height = 15
Caption = 'End Date'
end
object lblHash: TLabel
Left = 644
Top = 232
Width = 3
Height = 15
end
object lblHash2: TLabel
Left = 644
Top = 278
Width = 3
Height = 15
end
object btnFind: TButton
Left = 398
Top = 9
Width = 75
Height = 25
Caption = 'Find'
TabOrder = 0
OnClick = btnFindClick
end
object Memo1: TMemo
Left = 0
Top = 433
Width = 1002
Height = 89
Align = alBottom
Lines.Strings = (
'Memo1')
TabOrder = 1
end
object btnGetCalls: TButton
Left = 398
Top = 40
Width = 75
Height = 25
Caption = 'Get Calls'
TabOrder = 2
OnClick = btnGetCallsClick
end
object txtPhoneNum: TAdvEdit
Left = 243
Top = 41
Width = 121
Height = 23
EmptyTextStyle = []
FlatLineColor = 11250603
FocusColor = clWindow
FocusFontColor = 3881787
LabelFont.Charset = DEFAULT_CHARSET
LabelFont.Color = clWindowText
LabelFont.Height = -12
LabelFont.Name = 'Segoe UI'
LabelFont.Style = []
Lookup.Font.Charset = DEFAULT_CHARSET
Lookup.Font.Color = clWindowText
Lookup.Font.Height = -11
Lookup.Font.Name = 'Segoe UI'
Lookup.Font.Style = []
Lookup.Separator = ';'
Color = clWindow
TabOrder = 3
Text = ''
Visible = True
Version = '4.0.6.1'
end
object DBAdvGrid1: TDBAdvGrid
Left = 6
Top = 84
Width = 632
Height = 154
ColCount = 25
DrawingStyle = gdsClassic
FixedColor = clWhite
RowCount = 2
FixedRows = 1
Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goColSizing, goFixedRowDefAlign]
TabOrder = 4
ActiveCellFont.Charset = DEFAULT_CHARSET
ActiveCellFont.Color = 4474440
ActiveCellFont.Height = -12
ActiveCellFont.Name = 'Segoe UI'
ActiveCellFont.Style = [fsBold]
ActiveCellColor = 11565130
ActiveCellColorTo = 11565130
BorderColor = 11250603
ControlLook.FixedGradientFrom = clWhite
ControlLook.FixedGradientTo = clWhite
ControlLook.FixedGradientHoverFrom = clGray
ControlLook.FixedGradientHoverTo = clWhite
ControlLook.FixedGradientHoverMirrorFrom = clWhite
ControlLook.FixedGradientHoverMirrorTo = clWhite
ControlLook.FixedGradientHoverBorder = 11645361
ControlLook.FixedGradientDownFrom = clWhite
ControlLook.FixedGradientDownTo = clWhite
ControlLook.FixedGradientDownMirrorFrom = clWhite
ControlLook.FixedGradientDownMirrorTo = clWhite
ControlLook.FixedGradientDownBorder = 11250603
ControlLook.DropDownHeader.Font.Charset = DEFAULT_CHARSET
ControlLook.DropDownHeader.Font.Color = clWindowText
ControlLook.DropDownHeader.Font.Height = -11
ControlLook.DropDownHeader.Font.Name = 'Segoe UI'
ControlLook.DropDownHeader.Font.Style = []
ControlLook.DropDownHeader.Visible = True
ControlLook.DropDownHeader.Buttons = <>
ControlLook.DropDownFooter.Font.Charset = DEFAULT_CHARSET
ControlLook.DropDownFooter.Font.Color = clWindowText
ControlLook.DropDownFooter.Font.Height = -11
ControlLook.DropDownFooter.Font.Name = 'Segoe UI'
ControlLook.DropDownFooter.Font.Style = []
ControlLook.DropDownFooter.Visible = True
ControlLook.DropDownFooter.Buttons = <>
ControlLook.ToggleSwitch.BackgroundBorderWidth = 1.000000000000000000
ControlLook.ToggleSwitch.ButtonBorderWidth = 1.000000000000000000
ControlLook.ToggleSwitch.CaptionFont.Charset = DEFAULT_CHARSET
ControlLook.ToggleSwitch.CaptionFont.Color = clWindowText
ControlLook.ToggleSwitch.CaptionFont.Height = -12
ControlLook.ToggleSwitch.CaptionFont.Name = 'Segoe UI'
ControlLook.ToggleSwitch.CaptionFont.Style = []
ControlLook.ToggleSwitch.Shadow = False
Filter = <>
FilterDropDown.Font.Charset = DEFAULT_CHARSET
FilterDropDown.Font.Color = clWindowText
FilterDropDown.Font.Height = -12
FilterDropDown.Font.Name = 'Segoe UI'
FilterDropDown.Font.Style = []
FilterDropDown.TextChecked = 'Checked'
FilterDropDown.TextUnChecked = 'Unchecked'
FilterDropDownClear = '(All)'
FilterEdit.TypeNames.Strings = (
'Starts with'
'Ends with'
'Contains'
'Not contains'
'Equal'
'Not equal'
'Larger than'
'Smaller than'
'Clear')
FixedColWidth = 20
FixedRowHeight = 22
FixedFont.Charset = DEFAULT_CHARSET
FixedFont.Color = clWindowText
FixedFont.Height = -11
FixedFont.Name = 'Segoe UI'
FixedFont.Style = [fsBold]
FloatFormat = '%.2f'
HoverButtons.Buttons = <>
HTMLSettings.ImageFolder = 'images'
HTMLSettings.ImageBaseName = 'img'
Look = glCustom
PrintSettings.DateFormat = 'dd/mm/yyyy'
PrintSettings.Font.Charset = DEFAULT_CHARSET
PrintSettings.Font.Color = clWindowText
PrintSettings.Font.Height = -12
PrintSettings.Font.Name = 'Segoe UI'
PrintSettings.Font.Style = []
PrintSettings.FixedFont.Charset = DEFAULT_CHARSET
PrintSettings.FixedFont.Color = clWindowText
PrintSettings.FixedFont.Height = -12
PrintSettings.FixedFont.Name = 'Segoe UI'
PrintSettings.FixedFont.Style = []
PrintSettings.HeaderFont.Charset = DEFAULT_CHARSET
PrintSettings.HeaderFont.Color = clWindowText
PrintSettings.HeaderFont.Height = -12
PrintSettings.HeaderFont.Name = 'Segoe UI'
PrintSettings.HeaderFont.Style = []
PrintSettings.FooterFont.Charset = DEFAULT_CHARSET
PrintSettings.FooterFont.Color = clWindowText
PrintSettings.FooterFont.Height = -12
PrintSettings.FooterFont.Name = 'Segoe UI'
PrintSettings.FooterFont.Style = []
PrintSettings.PageNumSep = '/'
SearchFooter.ColorTo = clNone
SearchFooter.FindNextCaption = 'Find &next'
SearchFooter.FindPrevCaption = 'Find &previous'
SearchFooter.Font.Charset = DEFAULT_CHARSET
SearchFooter.Font.Color = clWindowText
SearchFooter.Font.Height = -12
SearchFooter.Font.Name = 'Segoe UI'
SearchFooter.Font.Style = []
SearchFooter.HighLightCaption = 'Highlight'
SearchFooter.HintClose = 'Close'
SearchFooter.HintFindNext = 'Find next occurrence'
SearchFooter.HintFindPrev = 'Find previous occurrence'
SearchFooter.HintHighlight = 'Highlight occurrences'
SearchFooter.MatchCaseCaption = 'Match case'
SearchFooter.ResultFormat = '(%d of %d)'
SelectionColor = 13744549
SelectionTextColor = clWindowText
SortSettings.HeaderColor = clWhite
SortSettings.HeaderColorTo = clWhite
SortSettings.HeaderMirrorColor = clWhite
SortSettings.HeaderMirrorColorTo = clWhite
Version = '2.8.3.7'
AutoCreateColumns = True
AutoRemoveColumns = True
Columns = <
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 20
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'date_updated'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 84
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'price_unit'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'parent_call_sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 89
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'caller_name'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 75
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'duration'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 57
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'annotation'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 70
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'answered_by'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 80
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 27
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'queue_time'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 74
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'price'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 38
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'api_version'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 71
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'status'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 43
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'direction'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 59
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'start_time'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'date_created'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 79
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'from_formatted'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 96
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'group_sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'trunk_sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 60
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'uri'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 26
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'account_sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 75
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'end_time'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 61
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'to_formatted'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 81
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'phone_number_sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 113
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'forwarded_from'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 97
end>
DataSource = dsCalls
InvalidPicture.Data = {
055449636F6E0000010001002020200000000000A81000001600000028000000
2000000040000000010020000000000000100000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000006A6A6B256A6A6B606A6A6B946A6A6BC06A6A6BE1
6A6A6BF86A6A6BF86A6A6BE16A6A6BC06A6A6B946A6A6B606A6A6B2500000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000006A6A6B407575769E787879F19F9F9FF6C0C0C0FDDADADAFFEDEDEEFF
FBFBFBFFFBFBFBFFEDEDEEFFDADADAFFC0C0C0FD9F9F9FF6787879F17575769E
6A6A6B4000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000006A6A6B22
7C7C7C98888889F0BDBDBDFCE9E9EBFED9D9E9FEB5B5DDFE8B8BCDFE595AB7FF
3739A8FF2B2CA4FF4A49B1FF7171C1FFA1A2D7FFD3D3E8FFEAEAEBFEBEBEBFFC
888889F07C7C7C986A6A6B220000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000006A6A6B43838383D8
B7B7B8FAECECEFFEC0C0DFFF7977C4FF2221A0FF12129BFF1010A4FF0C0CA8FF
0A0AACFF0A0AB4FF0A0AB9FF0D0DBEFF0F0FB1FF1111A6FF5656B8FFAEADDCFF
ECECEFFEB7B7B8FA838383D86A6A6B4300000000000000000000000000000000
00000000000000000000000000000000000000006A6A6B4E878788EAD3D3D3FE
CACAE8FF4443B0FF171799FF11119CFF0C0C98FF0B0B9BFF0B0BA0FF0A0AA6FF
0909ACFF0909B2FF0808BAFF0707BFFF0B09C8FF0D0DCEFF1111CCFF1010AFFF
4A49B2FFCFCFEBFFD3D3D3FE878788EA6A6A6B4E000000000000000000000000
000000000000000000000000000000006A6A6B43878788EAE1E1E1FFA8A8DAFF
2323A0FF15159CFF0D0D92FF0C0C95FF0C0C99FF0B0B9EFF0B0BA0FF0A0AA6FF
0909ACFF0909B2FF0808B8FF0808BCFF0808C3FF0C0CC9FF0C0CD0FF0D0DD6FF
1313CFFF2222A9FFAFAFDEFFE1E1E1FF878788EA6A6A6B430000000000000000
0000000000000000000000006A6A6B22838383D8D3D3D3FEA8A8D9FF2020A4FF
13139BFF0C0C92FF0C0C95FF0C0C97FF0C0C99FF0B0B9EFF0B0BA0FF0A0AA4FF
0A0AA9FF0909B0FF0808B4FF0808BBFF0707C0FF0A0AC6FF0909CCFF0C0CD3FF
0D0DD8FF1313D3FF1A1AA8FFAEADDEFFD4D4D4FE838383D86A6A6B2200000000
0000000000000000000000007C7C7C98B7B7B8FACACAE8FF2524A3FF13139FFF
0C0C97FF0C0C95FF0C0C95FF0C0C91FF0C0C95FF0B0B9EFF0B0BA0FF0A0AA4FF
0A0AA8FF0909ADFF0909B2FF0808B8FF0808BCFF0707C0FF0808BCFF0707C5FF
0C0CD3FF0D0DD7FF1212D1FF2020A7FFCDCDEBFFB8B8B9FA7C7C7C9800000000
00000000000000006A6A6B40888889F0ECECEFFE4545B1FF1616A4FF0B0B9BFF
0C0C99FF0C0C96FF3333A2FFB9B9D0FF393A9BFF0C0C95FF0B0BA1FF0A0AA4FF
0A0AA7FF0A0AABFF0909B0FF0808B4FF0808B7FF2F2FC2FFAEAEE2FF4B4BBFFF
0707BEFF0B0BD1FF0C0CD3FF1413CCFF4848B1FFECECEFFE888889F06A6A6B40
00000000000000007575769EBFBFBFFD9B9BD5FF1C1CA6FF0C0CA1FF0B0B9FFF
0B0B9AFF3535A7FFB5B5BEFFE6E6DFFFEDEDEFFF3C3C9CFF0C0C97FF0A0AA4FF
0A0AA6FF0A0AA9FF0909ADFF0909B0FF2626B5FFCECEDEFFFFFFFBFFEEEEF1FF
4848BAFF0808BCFF0A0ACDFF0B0BCEFF1111ABFFBEC0E0FFBFC0BFFD7575769E
000000006A6A6B25787879F1E3E3E5FE4646B2FF1414A8FF0A0AA4FF0B0BA0FF
2121A9FFBDBDCAFFD0D0C8FFC5C5C5FFE3E3E1FFEDEDEFFF3E3E9EFF0C0C98FF
0A0AA6FF0A0AA8FF0A0AA9FF2B2BB0FFC0C0CDFFEAEAE2FFEBEBEBFFFEFEF8FF
EDEDEEFF2828BDFF0707C4FF0809C7FF0F0FC4FF8788CBFFEBEBECFE79797AF1
6A6A6B256A6A6B609D9E9DF6D6D7E4FF3A3AB3FF1212ADFF0A0AA8FF0A0AA4FF
1313AAFFABABCFFFD6D6CBFFCACACAFFC6C6C6FFE4E4E0FFEEEEEFFF3F3FA0FF
0C0C99FF0A0AA6FF2828ABFFB2B2BFFFD8D8CEFFD6D6D8FFE0E0E0FFF6F5EDFF
D1D1EDFF1E1CC0FF0707BEFF0707BFFF0707C0FF2120AAFFD3D5E9FE9FA0A0F6
6A6A6B606A6A6B94BDBDBDFBBABBDCFF3A39B7FF2F2FB8FF0909ADFF0A0AA9FF
0A0AA6FF1515ACFFADADCFFFD6D6CBFFCBCBCAFFC6C6C6FFE4E4E1FFEEEEEFFF
3838A1FF2222A2FFACABB8FFC8C8C0FFC7C7C8FFCDCDCDFFE1E1D9FFC8CAE1FF
2424BCFF0808B4FF0808B9FF0808BAFF0808BBFF0F0EABFFA1A2D5FEC0C0C0FC
6A6A6B946A6A6BC0D9D8D7FE9999D1FF3838BBFF3636BCFF2C2CB7FF0909ADFF
0A0AA9FF0A0AA4FF1C1CAFFFB1B1CFFFD6D6CBFFCCCCCBFFC7C7C7FFE4E4E1FF
ECECEEFFACACB7FFC2C2BCFFBEBEBFFFC0C0C0FFCFCFC6FFC1C1D5FF2727B8FF
0909ACFF0909B2FF0909B2FF0909B4FF0808B4FF0E0EB5FF6E6EBFFFD9D9D9FE
6A6A6BC06A6A6BE1EBEAEBFF7D7CC7FF3838BFFF3434BEFF3536BEFF2A2AB8FF
0909B0FF0909ACFF0A0AA8FF1C1CB1FFB2B2D0FFD7D7CCFFCBCBCBFFC7C7C8FF
C8C8C3FFC6C6C3FFBFBFC1FFBDBDBDFFC5C5BCFFB8B8CEFF2929B5FF0A0AA8FF
0909ACFF0909ADFF0909AFFF0909AFFF0909AFFF0C0CB0FF4747AFFFECECEDFF
6A6A6BE16A6A6BF8F9F9F9FF6666C1FF3838C4FF3535C2FF3434C0FF3535BEFF
3030BCFF1313B4FF0909ADFF0A0AA8FF1E1EB3FFAAAAD0FFD3D3CDFFCCCCCCFF
C8C8C8FFC3C3C3FFC2C2C1FFC4C4BFFFB2B2CBFF2B2BB4FF0A0AA4FF0A0AA8FF
0A0AA8FF0A0AA9FF0A0AA9FF0A0AA9FF0A0AA9FF0B0BA9FF3131A6FFFAFAFAFF
6A6A6BF86A6A6BF8FBFBFBFF5959BEFF3B3BCAFF3A3AC8FF3737C4FF3535C2FF
3636C0FF3636BEFF2323B8FF0909B1FF0A0AA7FF4949BEFFD6D6D4FFD3D3D1FF
CDCDCDFFC8C8C8FFC4C4C3FFEDEDEDFF5F5FB3FF0C0C98FF0A0AA7FF0A0AA6FF
0A0AA6FF0A0AA6FF0A0AA4FF0A0AA6FF0A0AA4FF0B0BA4FF2D2DA6FFFBFBFBFF
6A6A6BF86A6A6BE1EDEDEEFF7F80CBFF4041CCFF3C3CCAFF3A3AC8FF383AC8FF
3838C4FF3636C2FF3939C0FF2123B7FF4A4AC2FFCBCBDEFFE0E0DCFFD6D6D6FF
D2D2D3FFCDCDCEFFC9C9C9FFE2E2E1FFF1F1F2FF4242A3FF0C0C99FF0A0AA4FF
0A0AA4FF0A0AA4FF0B0BA3FF0B0BA3FF0B0BA1FF0E0EA1FF4443B0FFEDEDEEFF
6A6A6BE16A6A6BC0DADADAFF9C9BD5FE4949CDFF3E3DD0FF3C3DCEFF3C3CCAFF
3A3AC8FF3B39C7FF2828BDFF5C5CCCFFE5E5EDFFF4F4EDFFE5E5E6FFDEDEDEFF
DCDCD9FFD9D9D3FFCDCDCDFFC8C8C8FFE5E5E1FFF1F1F3FF3F3FA0FF0C0C99FF
0A0AA4FF0B0BA1FF0B0BA0FF0B0BA0FF0B0B9FFF1313A2FF6B6BC0FFDADADAFF
6A6A6BC06A6A6B94C0C0C0FDBDBAE1FE5655CFFF4141D4FF3F3FD2FF3F3FCEFF
3D3DCCFF2C2AC3FF5E5ED3FFEBEBF6FFFFFFFAFFF1F1F1FFEDEDEEFFF0F0E9FF
D2D2E6FFBDBDD6FFDADAD3FFCFCFCFFFC9C9CAFFE5E5E2FFF1F1F3FF3A3AA0FF
0C0C98FF0B0BA3FF0B0B9FFF0B0B9EFF0B0B9EFF1C1CA4FF9C9CD3FFC1C1C1FD
6A6A6B946A6A6B609F9F9FF6DAD9EAFF6B6BCFFF4444D7FF4143D6FF4242D3FF
3434CDFF6464DBFFEFEFFFFFFFFFFFFFFCFCFCFFF6F6F6FFFCFCF4FFE2E1F0FF
5050CCFF4040C1FFC3C3DBFFE1E1D8FFD4D4D5FFCFCFCFFFE8E8E5FFF2F2F4FF
4040A2FF0C0C99FF0F0FA2FF0F0FA0FF0F0F9DFF302FA9FFD1D1E8FEA0A0A0F6
6A6A6B606A6A6B25787879F1E9E9EBFEA7A7DAFF6060DBFF4547DBFF3C3CD6FF
5857DEFFF2F2FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8E8F8FF5B5BD4FF
2828BDFF2A2BBDFF4949C5FFC3C3DBFFE4E4DAFFD5D5D5FFCECED0FFE8E8E5FF
F4F4F4FF4949AFFF2121A6FF2A2AA6FF2C2BA9FF5557B8FFEAEAECFE787879F1
6A6A6B25000000007575769EBEBEBEFDC9CAE6FF7A79DBFF4C4CDFFF4141DBFF
5757E0FFEAEAFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8E7FFFF5B5BD7FF2E2EC6FF
3E3EC9FF3A3AC5FF2C2EC1FF4A49C8FFC2C2DDFFE3E3DAFFD5D5D4FFDADAD3FF
CACBD9FF4747BBFF2525ADFF2C2BACFF3332AEFFA5A4D8FFBFBFBFFD7575769E
00000000000000006A6A6B40888889F0ECECEFFE9696D6FF7B7BE3FF4D4BE0FF
4141DBFF5F5FE6FFE7E7FFFFFFFFFFFFE9E9FFFF5A5ADCFF3333CAFF4242CFFF
4040CBFF3D3DC9FF3D3EC8FF3030C2FF4848C9FFC0C0DDFFECEEDEFFD0D0E0FF
5554C7FF2828B3FF3232B4FF3434B1FF5453B7FFECECEFFE888889F06A6A6B40
0000000000000000000000007C7C7C98B7B7B8FAD0D0ECFF8F8FDBFF6868E3FF
4E4EE2FF3E40DBFF6565E9FFB2B2F7FF6565E4FF393BD2FF4646D7FF4343D4FF
4343D1FF4242CFFF4040CBFF3F3FCAFF3333C4FF4E4ECBFF9E9EE2FF5C5BCFFF
292ABAFF3636BCFF3938B8FF3F3EB1FFCBCBE9FFB7B7B8FA7C7C7C9800000000
0000000000000000000000006A6A6B22838383D8D3D3D3FEB5B5E2FF9E9EE4FF
6766E2FF4E50E6FF4646E0FF3D3DDAFF4444DCFF4B4BDCFF4848DBFF4847D9FF
4646D5FF4443D3FF4343D1FF4242CFFF4143CDFF3A3AC8FF312FC5FF3535C3FF
3C3CC3FF3D3DBEFF403FB5FFACACDCFFD3D3D3FE838383D86A6A6B2200000000
000000000000000000000000000000006A6A6B43878788EAE1E1E1FFB5B5E2FF
A7A6E4FF7877E5FF5151E5FF4F4FE4FF4E4EE2FF4D4DE0FF4C4CDEFF4B4BDCFF
4949DBFF4848D7FF4747D5FF4545D3FF4545D1FF4343CFFF4242CCFF3F3FCBFF
4343C2FF4645B6FFADADDCFFE1E1E1FF878788EA6A6A6B430000000000000000
00000000000000000000000000000000000000006A6A6B4E878788EAD3D3D3FE
D0D0ECFFAAA9DFFFA2A2ECFF6565E3FF5151E6FF4F4FE4FF4F4DE4FF4D4DE0FF
4D4DDFFF4D4DDCFF4C49DBFF4A4AD8FF4749D6FF4747D4FF4949CBFF4B4BC3FF
8E8ED0FFCDCCE8FFD3D3D3FE878788EA6A6A6B4E000000000000000000000000
0000000000000000000000000000000000000000000000006A6A6B43838383D8
B7B7B8FAECECEFFEC3C2E5FFADAEE1FF9E9DE8FF6F6FE0FF5C5CE1FF5452E2FF
5051E1FF4F4FDFFF4F4FDBFF5150D6FF5151CFFF5F5FC8FFA1A1D3FEC7C8E0FE
E4E4E7FEB7B7B8FA838383D86A6A6B4300000000000000000000000000000000
000000000000000000000000000000000000000000000000000000006A6A6B22
7C7C7C98888889F0BFBFBFFDEBEBECFED8D9EBFEBDBDE4FEA8A7DCFF9695D7FF
8886D4FF7F7DCEFF8C8BD2FFA1A2D9FFC0BEE1FED9D9EAFEEAEAECFEBFBFBFFD
888889F07C7C7C986A6A6B220000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000006A6A6B407575769E787879F19F9F9FF6C0C0C0FDDADADAFFEDEDEEFF
FBFBFBFFFBFBFBFFEDEDEEFFDADADAFFC0C0C0FD9F9F9FF6787879F17575769E
6A6A6B4000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000006A6A6B256A6A6B606A6A6B946A6A6BC06A6A6BE1
6A6A6BF86A6A6BF86A6A6BE16A6A6BC06A6A6B946A6A6B606A6A6B2500000000
0000000000000000000000000000000000000000000000000000000000000000
00000000FFC003FFFF0000FFFC00003FF800001FF000000FE0000007C0000003
C000000380000001800000010000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000080000001
80000001C0000003C0000003E0000007F000000FF800001FFC00003FFF0000FF
FFC003FF}
ShowUnicode = False
ColWidths = (
20
84
64
89
75
57
70
80
27
74
38
71
43
59
64
79
96
64
60
26
75
61
81
113
97)
RowHeights = (
22
22)
end
object DBAdvGrid2: TDBAdvGrid
Left = 0
Top = 238
Width = 631
Height = 199
ColCount = 20
DrawingStyle = gdsClassic
FixedColor = clWhite
RowCount = 2
FixedRows = 1
Options = [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine, goRangeSelect, goColSizing, goFixedRowDefAlign]
TabOrder = 5
ActiveCellFont.Charset = DEFAULT_CHARSET
ActiveCellFont.Color = 4474440
ActiveCellFont.Height = -12
ActiveCellFont.Name = 'Segoe UI'
ActiveCellFont.Style = [fsBold]
ActiveCellColor = 11565130
ActiveCellColorTo = 11565130
BorderColor = 11250603
ControlLook.FixedGradientFrom = clWhite
ControlLook.FixedGradientTo = clWhite
ControlLook.FixedGradientHoverFrom = clGray
ControlLook.FixedGradientHoverTo = clWhite
ControlLook.FixedGradientHoverMirrorFrom = clWhite
ControlLook.FixedGradientHoverMirrorTo = clWhite
ControlLook.FixedGradientHoverBorder = 11645361
ControlLook.FixedGradientDownFrom = clWhite
ControlLook.FixedGradientDownTo = clWhite
ControlLook.FixedGradientDownMirrorFrom = clWhite
ControlLook.FixedGradientDownMirrorTo = clWhite
ControlLook.FixedGradientDownBorder = 11250603
ControlLook.DropDownHeader.Font.Charset = DEFAULT_CHARSET
ControlLook.DropDownHeader.Font.Color = clWindowText
ControlLook.DropDownHeader.Font.Height = -11
ControlLook.DropDownHeader.Font.Name = 'Segoe UI'
ControlLook.DropDownHeader.Font.Style = []
ControlLook.DropDownHeader.Visible = True
ControlLook.DropDownHeader.Buttons = <>
ControlLook.DropDownFooter.Font.Charset = DEFAULT_CHARSET
ControlLook.DropDownFooter.Font.Color = clWindowText
ControlLook.DropDownFooter.Font.Height = -11
ControlLook.DropDownFooter.Font.Name = 'Segoe UI'
ControlLook.DropDownFooter.Font.Style = []
ControlLook.DropDownFooter.Visible = True
ControlLook.DropDownFooter.Buttons = <>
ControlLook.ToggleSwitch.BackgroundBorderWidth = 1.000000000000000000
ControlLook.ToggleSwitch.ButtonBorderWidth = 1.000000000000000000
ControlLook.ToggleSwitch.CaptionFont.Charset = DEFAULT_CHARSET
ControlLook.ToggleSwitch.CaptionFont.Color = clWindowText
ControlLook.ToggleSwitch.CaptionFont.Height = -12
ControlLook.ToggleSwitch.CaptionFont.Name = 'Segoe UI'
ControlLook.ToggleSwitch.CaptionFont.Style = []
ControlLook.ToggleSwitch.Shadow = False
Filter = <>
FilterDropDown.Font.Charset = DEFAULT_CHARSET
FilterDropDown.Font.Color = clWindowText
FilterDropDown.Font.Height = -12
FilterDropDown.Font.Name = 'Segoe UI'
FilterDropDown.Font.Style = []
FilterDropDown.TextChecked = 'Checked'
FilterDropDown.TextUnChecked = 'Unchecked'
FilterDropDownClear = '(All)'
FilterEdit.TypeNames.Strings = (
'Starts with'
'Ends with'
'Contains'
'Not contains'
'Equal'
'Not equal'
'Larger than'
'Smaller than'
'Clear')
FixedColWidth = 20
FixedRowHeight = 22
FixedFont.Charset = DEFAULT_CHARSET
FixedFont.Color = clWindowText
FixedFont.Height = -11
FixedFont.Name = 'Segoe UI'
FixedFont.Style = [fsBold]
FloatFormat = '%.2f'
HoverButtons.Buttons = <>
HTMLSettings.ImageFolder = 'images'
HTMLSettings.ImageBaseName = 'img'
Look = glCustom
PrintSettings.DateFormat = 'dd/mm/yyyy'
PrintSettings.Font.Charset = DEFAULT_CHARSET
PrintSettings.Font.Color = clWindowText
PrintSettings.Font.Height = -12
PrintSettings.Font.Name = 'Segoe UI'
PrintSettings.Font.Style = []
PrintSettings.FixedFont.Charset = DEFAULT_CHARSET
PrintSettings.FixedFont.Color = clWindowText
PrintSettings.FixedFont.Height = -12
PrintSettings.FixedFont.Name = 'Segoe UI'
PrintSettings.FixedFont.Style = []
PrintSettings.HeaderFont.Charset = DEFAULT_CHARSET
PrintSettings.HeaderFont.Color = clWindowText
PrintSettings.HeaderFont.Height = -12
PrintSettings.HeaderFont.Name = 'Segoe UI'
PrintSettings.HeaderFont.Style = []
PrintSettings.FooterFont.Charset = DEFAULT_CHARSET
PrintSettings.FooterFont.Color = clWindowText
PrintSettings.FooterFont.Height = -12
PrintSettings.FooterFont.Name = 'Segoe UI'
PrintSettings.FooterFont.Style = []
PrintSettings.PageNumSep = '/'
SearchFooter.ColorTo = clNone
SearchFooter.FindNextCaption = 'Find &next'
SearchFooter.FindPrevCaption = 'Find &previous'
SearchFooter.Font.Charset = DEFAULT_CHARSET
SearchFooter.Font.Color = clWindowText
SearchFooter.Font.Height = -12
SearchFooter.Font.Name = 'Segoe UI'
SearchFooter.Font.Style = []
SearchFooter.HighLightCaption = 'Highlight'
SearchFooter.HintClose = 'Close'
SearchFooter.HintFindNext = 'Find next occurrence'
SearchFooter.HintFindPrev = 'Find previous occurrence'
SearchFooter.HintHighlight = 'Highlight occurrences'
SearchFooter.MatchCaseCaption = 'Match case'
SearchFooter.ResultFormat = '(%d of %d)'
SelectionColor = 13744549
SelectionTextColor = clWindowText
SortSettings.HeaderColor = clWhite
SortSettings.HeaderColorTo = clWhite
SortSettings.HeaderMirrorColor = clWhite
SortSettings.HeaderMirrorColorTo = clWhite
Version = '2.8.3.7'
AutoCreateColumns = True
AutoRemoveColumns = True
Columns = <
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 20
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'account_sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'api_version'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'call_sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'conference_sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'date_created'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'date_updated'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'start_time'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'duration'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'sid'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'price'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'price_unit'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'status'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'channels'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'source'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'error_code'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'uri'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'encryption_details'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'media_url'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end
item
Borders = []
BorderPen.Color = clSilver
ButtonHeight = 18
CheckFalse = 'N'
CheckTrue = 'Y'
Color = clWindow
FieldName = 'transcription'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -12
Font.Name = 'Segoe UI'
Font.Style = []
HeaderFont.Charset = DEFAULT_CHARSET
HeaderFont.Color = 3881787
HeaderFont.Height = -12
HeaderFont.Name = 'Segoe UI'
HeaderFont.Style = []
PrintBorders = [cbTop, cbLeft, cbRight, cbBottom]
PrintFont.Charset = DEFAULT_CHARSET
PrintFont.Color = clWindowText
PrintFont.Height = -12
PrintFont.Name = 'Segoe UI'
PrintFont.Style = []
Width = 64
end>
DataSource = dsRecordings
InvalidPicture.Data = {
055449636F6E0000010001002020200000000000A81000001600000028000000
2000000040000000010020000000000000100000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000006A6A6B256A6A6B606A6A6B946A6A6BC06A6A6BE1
6A6A6BF86A6A6BF86A6A6BE16A6A6BC06A6A6B946A6A6B606A6A6B2500000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000006A6A6B407575769E787879F19F9F9FF6C0C0C0FDDADADAFFEDEDEEFF
FBFBFBFFFBFBFBFFEDEDEEFFDADADAFFC0C0C0FD9F9F9FF6787879F17575769E
6A6A6B4000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000006A6A6B22
7C7C7C98888889F0BDBDBDFCE9E9EBFED9D9E9FEB5B5DDFE8B8BCDFE595AB7FF
3739A8FF2B2CA4FF4A49B1FF7171C1FFA1A2D7FFD3D3E8FFEAEAEBFEBEBEBFFC
888889F07C7C7C986A6A6B220000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000006A6A6B43838383D8
B7B7B8FAECECEFFEC0C0DFFF7977C4FF2221A0FF12129BFF1010A4FF0C0CA8FF
0A0AACFF0A0AB4FF0A0AB9FF0D0DBEFF0F0FB1FF1111A6FF5656B8FFAEADDCFF
ECECEFFEB7B7B8FA838383D86A6A6B4300000000000000000000000000000000
00000000000000000000000000000000000000006A6A6B4E878788EAD3D3D3FE
CACAE8FF4443B0FF171799FF11119CFF0C0C98FF0B0B9BFF0B0BA0FF0A0AA6FF
0909ACFF0909B2FF0808BAFF0707BFFF0B09C8FF0D0DCEFF1111CCFF1010AFFF
4A49B2FFCFCFEBFFD3D3D3FE878788EA6A6A6B4E000000000000000000000000
000000000000000000000000000000006A6A6B43878788EAE1E1E1FFA8A8DAFF
2323A0FF15159CFF0D0D92FF0C0C95FF0C0C99FF0B0B9EFF0B0BA0FF0A0AA6FF
0909ACFF0909B2FF0808B8FF0808BCFF0808C3FF0C0CC9FF0C0CD0FF0D0DD6FF
1313CFFF2222A9FFAFAFDEFFE1E1E1FF878788EA6A6A6B430000000000000000
0000000000000000000000006A6A6B22838383D8D3D3D3FEA8A8D9FF2020A4FF
13139BFF0C0C92FF0C0C95FF0C0C97FF0C0C99FF0B0B9EFF0B0BA0FF0A0AA4FF
0A0AA9FF0909B0FF0808B4FF0808BBFF0707C0FF0A0AC6FF0909CCFF0C0CD3FF
0D0DD8FF1313D3FF1A1AA8FFAEADDEFFD4D4D4FE838383D86A6A6B2200000000
0000000000000000000000007C7C7C98B7B7B8FACACAE8FF2524A3FF13139FFF
0C0C97FF0C0C95FF0C0C95FF0C0C91FF0C0C95FF0B0B9EFF0B0BA0FF0A0AA4FF
0A0AA8FF0909ADFF0909B2FF0808B8FF0808BCFF0707C0FF0808BCFF0707C5FF
0C0CD3FF0D0DD7FF1212D1FF2020A7FFCDCDEBFFB8B8B9FA7C7C7C9800000000
00000000000000006A6A6B40888889F0ECECEFFE4545B1FF1616A4FF0B0B9BFF
0C0C99FF0C0C96FF3333A2FFB9B9D0FF393A9BFF0C0C95FF0B0BA1FF0A0AA4FF
0A0AA7FF0A0AABFF0909B0FF0808B4FF0808B7FF2F2FC2FFAEAEE2FF4B4BBFFF
0707BEFF0B0BD1FF0C0CD3FF1413CCFF4848B1FFECECEFFE888889F06A6A6B40
00000000000000007575769EBFBFBFFD9B9BD5FF1C1CA6FF0C0CA1FF0B0B9FFF
0B0B9AFF3535A7FFB5B5BEFFE6E6DFFFEDEDEFFF3C3C9CFF0C0C97FF0A0AA4FF
0A0AA6FF0A0AA9FF0909ADFF0909B0FF2626B5FFCECEDEFFFFFFFBFFEEEEF1FF
4848BAFF0808BCFF0A0ACDFF0B0BCEFF1111ABFFBEC0E0FFBFC0BFFD7575769E
000000006A6A6B25787879F1E3E3E5FE4646B2FF1414A8FF0A0AA4FF0B0BA0FF
2121A9FFBDBDCAFFD0D0C8FFC5C5C5FFE3E3E1FFEDEDEFFF3E3E9EFF0C0C98FF
0A0AA6FF0A0AA8FF0A0AA9FF2B2BB0FFC0C0CDFFEAEAE2FFEBEBEBFFFEFEF8FF
EDEDEEFF2828BDFF0707C4FF0809C7FF0F0FC4FF8788CBFFEBEBECFE79797AF1
6A6A6B256A6A6B609D9E9DF6D6D7E4FF3A3AB3FF1212ADFF0A0AA8FF0A0AA4FF
1313AAFFABABCFFFD6D6CBFFCACACAFFC6C6C6FFE4E4E0FFEEEEEFFF3F3FA0FF
0C0C99FF0A0AA6FF2828ABFFB2B2BFFFD8D8CEFFD6D6D8FFE0E0E0FFF6F5EDFF
D1D1EDFF1E1CC0FF0707BEFF0707BFFF0707C0FF2120AAFFD3D5E9FE9FA0A0F6
6A6A6B606A6A6B94BDBDBDFBBABBDCFF3A39B7FF2F2FB8FF0909ADFF0A0AA9FF
0A0AA6FF1515ACFFADADCFFFD6D6CBFFCBCBCAFFC6C6C6FFE4E4E1FFEEEEEFFF
3838A1FF2222A2FFACABB8FFC8C8C0FFC7C7C8FFCDCDCDFFE1E1D9FFC8CAE1FF
2424BCFF0808B4FF0808B9FF0808BAFF0808BBFF0F0EABFFA1A2D5FEC0C0C0FC
6A6A6B946A6A6BC0D9D8D7FE9999D1FF3838BBFF3636BCFF2C2CB7FF0909ADFF
0A0AA9FF0A0AA4FF1C1CAFFFB1B1CFFFD6D6CBFFCCCCCBFFC7C7C7FFE4E4E1FF
ECECEEFFACACB7FFC2C2BCFFBEBEBFFFC0C0C0FFCFCFC6FFC1C1D5FF2727B8FF
0909ACFF0909B2FF0909B2FF0909B4FF0808B4FF0E0EB5FF6E6EBFFFD9D9D9FE
6A6A6BC06A6A6BE1EBEAEBFF7D7CC7FF3838BFFF3434BEFF3536BEFF2A2AB8FF
0909B0FF0909ACFF0A0AA8FF1C1CB1FFB2B2D0FFD7D7CCFFCBCBCBFFC7C7C8FF
C8C8C3FFC6C6C3FFBFBFC1FFBDBDBDFFC5C5BCFFB8B8CEFF2929B5FF0A0AA8FF
0909ACFF0909ADFF0909AFFF0909AFFF0909AFFF0C0CB0FF4747AFFFECECEDFF
6A6A6BE16A6A6BF8F9F9F9FF6666C1FF3838C4FF3535C2FF3434C0FF3535BEFF
3030BCFF1313B4FF0909ADFF0A0AA8FF1E1EB3FFAAAAD0FFD3D3CDFFCCCCCCFF
C8C8C8FFC3C3C3FFC2C2C1FFC4C4BFFFB2B2CBFF2B2BB4FF0A0AA4FF0A0AA8FF
0A0AA8FF0A0AA9FF0A0AA9FF0A0AA9FF0A0AA9FF0B0BA9FF3131A6FFFAFAFAFF
6A6A6BF86A6A6BF8FBFBFBFF5959BEFF3B3BCAFF3A3AC8FF3737C4FF3535C2FF
3636C0FF3636BEFF2323B8FF0909B1FF0A0AA7FF4949BEFFD6D6D4FFD3D3D1FF
CDCDCDFFC8C8C8FFC4C4C3FFEDEDEDFF5F5FB3FF0C0C98FF0A0AA7FF0A0AA6FF
0A0AA6FF0A0AA6FF0A0AA4FF0A0AA6FF0A0AA4FF0B0BA4FF2D2DA6FFFBFBFBFF
6A6A6BF86A6A6BE1EDEDEEFF7F80CBFF4041CCFF3C3CCAFF3A3AC8FF383AC8FF
3838C4FF3636C2FF3939C0FF2123B7FF4A4AC2FFCBCBDEFFE0E0DCFFD6D6D6FF
D2D2D3FFCDCDCEFFC9C9C9FFE2E2E1FFF1F1F2FF4242A3FF0C0C99FF0A0AA4FF
0A0AA4FF0A0AA4FF0B0BA3FF0B0BA3FF0B0BA1FF0E0EA1FF4443B0FFEDEDEEFF
6A6A6BE16A6A6BC0DADADAFF9C9BD5FE4949CDFF3E3DD0FF3C3DCEFF3C3CCAFF
3A3AC8FF3B39C7FF2828BDFF5C5CCCFFE5E5EDFFF4F4EDFFE5E5E6FFDEDEDEFF
DCDCD9FFD9D9D3FFCDCDCDFFC8C8C8FFE5E5E1FFF1F1F3FF3F3FA0FF0C0C99FF
0A0AA4FF0B0BA1FF0B0BA0FF0B0BA0FF0B0B9FFF1313A2FF6B6BC0FFDADADAFF
6A6A6BC06A6A6B94C0C0C0FDBDBAE1FE5655CFFF4141D4FF3F3FD2FF3F3FCEFF
3D3DCCFF2C2AC3FF5E5ED3FFEBEBF6FFFFFFFAFFF1F1F1FFEDEDEEFFF0F0E9FF
D2D2E6FFBDBDD6FFDADAD3FFCFCFCFFFC9C9CAFFE5E5E2FFF1F1F3FF3A3AA0FF
0C0C98FF0B0BA3FF0B0B9FFF0B0B9EFF0B0B9EFF1C1CA4FF9C9CD3FFC1C1C1FD
6A6A6B946A6A6B609F9F9FF6DAD9EAFF6B6BCFFF4444D7FF4143D6FF4242D3FF
3434CDFF6464DBFFEFEFFFFFFFFFFFFFFCFCFCFFF6F6F6FFFCFCF4FFE2E1F0FF
5050CCFF4040C1FFC3C3DBFFE1E1D8FFD4D4D5FFCFCFCFFFE8E8E5FFF2F2F4FF
4040A2FF0C0C99FF0F0FA2FF0F0FA0FF0F0F9DFF302FA9FFD1D1E8FEA0A0A0F6
6A6A6B606A6A6B25787879F1E9E9EBFEA7A7DAFF6060DBFF4547DBFF3C3CD6FF
5857DEFFF2F2FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8E8F8FF5B5BD4FF
2828BDFF2A2BBDFF4949C5FFC3C3DBFFE4E4DAFFD5D5D5FFCECED0FFE8E8E5FF
F4F4F4FF4949AFFF2121A6FF2A2AA6FF2C2BA9FF5557B8FFEAEAECFE787879F1
6A6A6B25000000007575769EBEBEBEFDC9CAE6FF7A79DBFF4C4CDFFF4141DBFF
5757E0FFEAEAFFFFFFFFFFFFFFFFFFFFFFFFFFFFE8E7FFFF5B5BD7FF2E2EC6FF
3E3EC9FF3A3AC5FF2C2EC1FF4A49C8FFC2C2DDFFE3E3DAFFD5D5D4FFDADAD3FF
CACBD9FF4747BBFF2525ADFF2C2BACFF3332AEFFA5A4D8FFBFBFBFFD7575769E
00000000000000006A6A6B40888889F0ECECEFFE9696D6FF7B7BE3FF4D4BE0FF
4141DBFF5F5FE6FFE7E7FFFFFFFFFFFFE9E9FFFF5A5ADCFF3333CAFF4242CFFF
4040CBFF3D3DC9FF3D3EC8FF3030C2FF4848C9FFC0C0DDFFECEEDEFFD0D0E0FF
5554C7FF2828B3FF3232B4FF3434B1FF5453B7FFECECEFFE888889F06A6A6B40
0000000000000000000000007C7C7C98B7B7B8FAD0D0ECFF8F8FDBFF6868E3FF
4E4EE2FF3E40DBFF6565E9FFB2B2F7FF6565E4FF393BD2FF4646D7FF4343D4FF
4343D1FF4242CFFF4040CBFF3F3FCAFF3333C4FF4E4ECBFF9E9EE2FF5C5BCFFF
292ABAFF3636BCFF3938B8FF3F3EB1FFCBCBE9FFB7B7B8FA7C7C7C9800000000
0000000000000000000000006A6A6B22838383D8D3D3D3FEB5B5E2FF9E9EE4FF
6766E2FF4E50E6FF4646E0FF3D3DDAFF4444DCFF4B4BDCFF4848DBFF4847D9FF
4646D5FF4443D3FF4343D1FF4242CFFF4143CDFF3A3AC8FF312FC5FF3535C3FF
3C3CC3FF3D3DBEFF403FB5FFACACDCFFD3D3D3FE838383D86A6A6B2200000000
000000000000000000000000000000006A6A6B43878788EAE1E1E1FFB5B5E2FF
A7A6E4FF7877E5FF5151E5FF4F4FE4FF4E4EE2FF4D4DE0FF4C4CDEFF4B4BDCFF
4949DBFF4848D7FF4747D5FF4545D3FF4545D1FF4343CFFF4242CCFF3F3FCBFF
4343C2FF4645B6FFADADDCFFE1E1E1FF878788EA6A6A6B430000000000000000
00000000000000000000000000000000000000006A6A6B4E878788EAD3D3D3FE
D0D0ECFFAAA9DFFFA2A2ECFF6565E3FF5151E6FF4F4FE4FF4F4DE4FF4D4DE0FF
4D4DDFFF4D4DDCFF4C49DBFF4A4AD8FF4749D6FF4747D4FF4949CBFF4B4BC3FF
8E8ED0FFCDCCE8FFD3D3D3FE878788EA6A6A6B4E000000000000000000000000
0000000000000000000000000000000000000000000000006A6A6B43838383D8
B7B7B8FAECECEFFEC3C2E5FFADAEE1FF9E9DE8FF6F6FE0FF5C5CE1FF5452E2FF
5051E1FF4F4FDFFF4F4FDBFF5150D6FF5151CFFF5F5FC8FFA1A1D3FEC7C8E0FE
E4E4E7FEB7B7B8FA838383D86A6A6B4300000000000000000000000000000000
000000000000000000000000000000000000000000000000000000006A6A6B22
7C7C7C98888889F0BFBFBFFDEBEBECFED8D9EBFEBDBDE4FEA8A7DCFF9695D7FF
8886D4FF7F7DCEFF8C8BD2FFA1A2D9FFC0BEE1FED9D9EAFEEAEAECFEBFBFBFFD
888889F07C7C7C986A6A6B220000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
000000006A6A6B407575769E787879F19F9F9FF6C0C0C0FDDADADAFFEDEDEEFF
FBFBFBFFFBFBFBFFEDEDEEFFDADADAFFC0C0C0FD9F9F9FF6787879F17575769E
6A6A6B4000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000006A6A6B256A6A6B606A6A6B946A6A6BC06A6A6BE1
6A6A6BF86A6A6BF86A6A6BE16A6A6BC06A6A6B946A6A6B606A6A6B2500000000
0000000000000000000000000000000000000000000000000000000000000000
00000000FFC003FFFF0000FFFC00003FF800001FF000000FE0000007C0000003
C000000380000001800000010000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000080000001
80000001C0000003C0000003E0000007F000000FF800001FFC00003FFF0000FF
FFC003FF}
ShowUnicode = False
ColWidths = (
20
64
64
64
64
64
64
64
64
64
64
64
64
64
64
64
64
64
64
64)
RowHeights = (
22
22)
end
object wwlcStore: TwwDBLookupCombo
Left = 57
Top = 41
Width = 123
Height = 23
DropDownAlignment = taLeftJustify
Selected.Strings = (
'location'#9'30'#9'location'#9#9)
LookupTable = TwilioDataModule.uqLocations
LookupField = 'phone_number_formatted'
TabOrder = 6
AutoDropDown = False
ShowButton = True
PreciseEditRegion = False
AllowClearKey = True
OnCloseUp = wwlcStoreCloseUp
end
object dtpCallsDate1: TwwDBDateTimePicker
Left = 57
Top = 8
Width = 121
Height = 23
Epoch = 1950
ShowButton = True
TabOrder = 7
end
object FullUpdate: TButton
Left = 486
Top = 40
Width = 75
Height = 25
Caption = 'Full Update'
TabOrder = 8
OnClick = FullUpdateClick
end
object dtpCallsDate2: TwwDBDateTimePicker
Left = 243
Top = 9
Width = 121
Height = 23
Epoch = 1950
ShowButton = True
TabOrder = 9
end
object edtUsername: TEdit
Left = 696
Top = 10
Width = 121
Height = 23
TabOrder = 10
TextHint = 'Username'
end
object edtPassword: TEdit
Left = 696
Top = 39
Width = 121
Height = 23
TabOrder = 11
TextHint = 'Password'
end
object btnAddUser: TButton
Left = 866
Top = 29
Width = 75
Height = 25
Caption = 'Add User'
TabOrder = 12
OnClick = btnAddUserClick
end
object cbAdmin: TCheckBox
Left = 696
Top = 158
Width = 97
Height = 17
Caption = 'Make Admin?'
TabOrder = 13
end
object edtFullName: TEdit
Left = 696
Top = 68
Width = 121
Height = 23
TabOrder = 14
TextHint = 'Full Name'
end
object edtPhoneNumber: TEdit
Left = 696
Top = 97
Width = 121
Height = 23
TabOrder = 15
TextHint = 'Phone Number'
end
object edtEmailAddress: TEdit
Left = 696
Top = 129
Width = 121
Height = 23
TabOrder = 16
TextHint = 'Email Address'
end
object dsCalls: TDataSource
DataSet = TwilioDataModule.uqCalls
Left = 502
Top = 475
end
object dsRecordings: TDataSource
DataSet = TwilioDataModule.uqRecordings
Left = 348
Top = 472
end
object uqUsers: TUniQuery
Connection = TwilioDataModule.ucEnvoy
SQL.Strings = (
'select * from envoy.users')
Left = 669
Top = 456
object uqUsersuser_id: TLargeintField
FieldName = 'user_id'
end
object uqUsersusername: TStringField
FieldName = 'username'
Required = True
Size = 64
end
object uqUserspassword: TMemoField
FieldName = 'password'
Required = True
BlobType = ftMemo
end
object uqUsersdate_created: TStringField
FieldName = 'date_created'
Required = True
Size = 21
end
object uqUsersadmin: TBooleanField
FieldName = 'admin'
end
object uqUsersemail: TMemoField
FieldName = 'email'
BlobType = ftMemo
end
object uqUsersphone_number: TStringField
FieldName = 'phone_number'
Size = 14
end
object uqUsersfull_name: TStringField
FieldName = 'full_name'
Size = 30
end
object uqUsersactive: TBooleanField
FieldName = 'active'
end
end
end
// Uses Twilio.Data.Module for the rest api calls. Simply for testing querys.
// Visual aspect is for testing purposes only and has no affect on the client.
// Authors:
// Cameron Hayes
// Elias Serraf
// Mac ...
unit Data;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, AdvUtil, Data.DB, Vcl.Grids, AdvObj,
BaseGrid, AdvGrid, DBAdvGrid, MemDS, DBAccess, Uni, Vcl.StdCtrls, Vcl.Mask,
vcl.wwdbedit, vcl.wwdotdot, vcl.wwdbcomb, REST.Client, REST.Types, System.JSON,
System.Generics.Collections, AdvEdit, vcl.wwdblook, vcl.wwdbdatetimepicker,
System.Hash;
type
TFData = class(TForm)
dsCalls: TDataSource;
btnFind: TButton;
Memo1: TMemo;
btnGetCalls: TButton;
txtPhoneNum: TAdvEdit;
DBAdvGrid1: TDBAdvGrid;
DBAdvGrid2: TDBAdvGrid;
dsRecordings: TDataSource;
wwlcStore: TwwDBLookupCombo;
dtpCallsDate1: TwwDBDateTimePicker;
FullUpdate: TButton;
lblStartDate: TLabel;
lblLocation: TLabel;
dtpCallsDate2: TwwDBDateTimePicker;
Label1: TLabel;
edtUsername: TEdit;
edtPassword: TEdit;
lblHash: TLabel;
btnAddUser: TButton;
lblHash2: TLabel;
uqUsers: TUniQuery;
cbAdmin: TCheckBox;
uqUsersuser_id: TLargeintField;
uqUsersusername: TStringField;
uqUserspassword: TMemoField;
uqUsersdate_created: TStringField;
uqUsersadmin: TBooleanField;
edtFullName: TEdit;
edtPhoneNumber: TEdit;
edtEmailAddress: TEdit;
uqUsersemail: TMemoField;
uqUsersphone_number: TStringField;
uqUsersfull_name: TStringField;
uqUsersactive: TBooleanField;
procedure btnFindClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure btnGetCallsClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FullUpdateClick(Sender: TObject);
procedure wwlcStoreCloseUp(Sender: TObject; LookupTable,
FillTable: TDataSet; modified: Boolean);
procedure btnAddUserClick(Sender: TObject);
procedure addUser();
private
{ Private declarations }
accountSID: string;
authHeader: string;
public
{ Public declarations }
end;
var
FData: TFData;
implementation
{$R *.dfm}
uses Api.Database, Twilio.Data.Module, uLibrary;
procedure TFData.FormCreate(Sender: TObject);
begin
TwilioDataModule := TTwilioDataModule.Create(Self);
end;
procedure TFData.FormDestroy(Sender: TObject);
begin
TwilioDataModule.Free;
end;
procedure TFData.FullUpdateClick(Sender: TObject);
var
count: string;
begin
count := TwilioDataModule.FullUpdate();
Memo1.Lines.Add(count);
end;
procedure TFData.wwlcStoreCloseUp(Sender: TObject; LookupTable,
FillTable: TDataSet; modified: Boolean);
begin
if wwlcStore.Text = '' then
txtPhoneNum.Text := ''
else
txtPhoneNum.Text := wwlcStore.LookupValue;
end;
procedure TFData.btnGetCallsClick(Sender: TObject);
// Gets 50 calls and adds them to the database if they havent seen an earlier call
begin
TwilioDataModule.GetCalls(txtPhoneNum.Text, 50, 0 );
end;
procedure TFData.btnAddUserClick(Sender: TObject);
var
dateCreated: TDateTime;
hashString: string;
SQL: string;
begin
addUser();
end;
procedure TFData.addUser();
var
dateCreated: TDateTime;
hashString: string;
SQL: string;
username: string;
begin
dateCreated := now;
hashString := DateTimeToStr(dateCreated) + edtPassword.Text;
lblHash.Caption := THashSHA2.GetHashString(
hashString,
THashSHA2.TSHA2Version.SHA512).ToUpper;
lblHash2.Caption := IntToStr(length(DateTimeToStr(dateCreated)));
username := edtUsername.Text;
username := username.ToLower;
SQL := 'select * from envoy.users where username = ' + QuotedStr(username);
uqUsers.Close;
uqUsers.SQL.Text := sql;
uqUsers.Open;
if uqUsers.IsEmpty then
begin
uqUsers.Insert;
uqUsersusername.AsString := username;
uqUserspassword.AsString := THashSHA2.GetHashString(hashString,
THashSHA2.TSHA2Version.SHA512).ToUpper;
uqUsersdate_created.AsString := DateTimeToStr(dateCreated);
uqUsersadmin.AsBoolean := cbAdmin.Checked;
uqUsersphone_number.AsString := edtPhoneNumber.Text;
uqUsersemail.AsString := edtEmailAddress.Text;
uqUsersfull_name.AsString := edtFullName.Text;
uqUsers.Post;
lblHash2.Caption := 'Added';
end
else
lblHash2.Caption := 'Username already taken';
end;
procedure TFData.btnFindClick(Sender: TObject);
// Retrieves calls from a specific number from the database.
// SQL: SQL statement to retrieve calls from the database
// whereSQL: where section of the SQL that is built in the function
var
SQL: string;
whereSQL: string;
begin
//TwilioDataModule.ShowCalls(txtPhoneNum.Text);
whereSQL := 'where ';
if wwlcStore.Text <> '' then
whereSQL := whereSQL + 'to_formatted = ' + QuotedStr(txtPhoneNum.Text);
if dtpCallsDate1.Text <> '' then
if whereSQL = 'where ' then
whereSQL := whereSQL + 'date_created > ' + QuotedStr(dtpCallsDate1.Text)
else
whereSQL := whereSQL + 'AND date_created > ' + QuotedStr(dtpCallsDate1.Text);
if dtpCallsDate2.Text <> '' then
if whereSQL = 'where ' then
whereSQL := whereSQL + 'date_created <= ' + QuotedStr(dtpCallsDate2.Text)
else
whereSQL := whereSQL + 'AND date_created <= ' + QuotedStr(dtpCallsDate2.Text);
if whereSQL = 'where ' then
whereSQL := '';
SQL := 'select * from envoy.calls '+ whereSQL + ' order by date_created desc';
Memo1.Lines.Add(SQL);
doQuery(TwilioDataModule.uqCalls, SQL);
DBAdvGrid1.AutoSizeColumns(true);
end;
end.
// Lookup Service interface which retrieves information from the database
// which is then sent to the client.
// Authors:
// Cameron Hayes
// Mac ...
// Elias Sarraf
unit Lookup.Service;
interface
uses
XData.Service.Common,
Aurelius.Mapping.Attributes,
System.JSON,
System.Generics.Collections,
System.Classes;
const
API_MODEL = 'Api';
type
TCallItem = class
// Class of the info we want from the database from a specific call.
// callSid: SID of the call, 34 digit string.
// fromNumber: Who the phone call was from. (xxx) xxx-xxxx
// toNumber: Who the phone call was to. (xxx) xxx-xxxx
// dateCreated: Date the phone call was created. mm/dd/yyyy hh:nn:ss am/pm
// mediaURL: Link to the recording audio
// duration: Length of the entire call and recording.
// transcription: Transcription of the recording. Not always present due to
// the call being answerered or caller did not leave a message.
public
callSid: string;
fromNumber: string;
toNumber: string;
dateCreated: string;
mediaUrl: string;
duration: string;
transcription: string;
end;
// List of call items
// count: Total amount of records that fit the SQL query
// data: List of retrieved calls
TCallList = class
public
count: integer;
data: TList<TCallItem>;
end;
TUserItem = class
public
userID: string;
username: string;
full_name: string;
phone_number: string;
email_address: string;
admin: boolean;
active: boolean;
password: string;
end;
TUserList = class
public
count: integer;
data: TList<TUserItem>;
end;
type
[ServiceContract, Model(API_MODEL)]
ILookupService = interface(IInvokable)
['{F24E1468-5279-401F-A877-CD48B44F4416}']
[HttpGet] function GetCalls(searchOptions: string): TCallList;
[HttpGet] function Search(phoneNum: string): TCallList;
[HttpGet] function GetUsers(searchOptions: string): TUserList;
function AddUser(userInfo: string): string;
function DelUser(username: string): string;
function EditUser(const editOptions: string): string;
end;
implementation
initialization
RegisterServiceType(TypeInfo(ILookupService));
end.
// Implementation of the Lookup Service interface used to send call information
// to the client.
// Authors:
// Cameron Hayes
// Mac ...
// Elias Sarraf
unit Lookup.ServiceImpl;
interface
uses
XData.Server.Module, XData.Service.Common, Api.Database, Data.DB,
Lookup.Service, System.Hash, System.Classes, Common.Logging;
type
[ServiceImplementation]
TLookupService = class(TInterfacedObject, ILookupService)
strict private
callsDB: TApiDatabaseModule;
private
function GetUsers(searchOptions: string): TUserList;
function GetCalls(searchOptions: string): TCallList;
function EditUser(const editOptions: string): string;
function Search(phoneNum: string): TCallList;
function AddUser(userInfo: string): string;
function DelUser(username: string): string;
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
end;
implementation
uses
System.SysUtils,
System.Generics.Collections,
XData.Sys.Exceptions, uLibrary;
procedure TLookupService.AfterConstruction;
begin
inherited;
callsDB := TApiDatabaseModule.Create(nil);
Logger.Log(3, 'callsDB created');
end;
procedure TLookupService.BeforeDestruction;
begin
callsDB.Free;
inherited;
Logger.Log(3, 'callsDB destroyed');
end;
function TLookupService.Search(phoneNum: string): TCallList;
var
SQL: string;
call: TCallItem;
begin
Logger.Log(3, 'Search called for phone number: ' + phoneNum);
SQL := 'select * ' +
'from calls inner join recordings on calls.sid = recordings.call_sid ' +
'where from_formatted = ' + QuotedStr(phoneNum) +
' order by calls.date_created desc';
Logger.Log(3, 'Search main query: ' + SQL);
doQuery(callsDB.UniQuery1, SQL);
Result := TCallList.Create;
Result.data := TList<TCallItem>.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result.data);
while not callsDB.UniQuery1.Eof do
begin
call := TCallItem.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(call);
Result.data.Add(call);
call.callSid := callsDB.UniQuery1.FieldByName('sid').AsString;
call.fromNumber := callsDB.UniQuery1.FieldByName('from_formatted').AsString;
call.ToNumber := callsDB.UniQuery1.FieldByName('to_formatted').AsString;
call.dateCreated := callsDB.UniQuery1.FieldByName('date_created').AsString;
call.mediaUrl := callsDB.UniQuery1.FieldByName('media_url').AsString;
call.duration := callsDB.UniQuery1.FieldByName('duration').AsString;
call.transcription := callsDB.UniQuery1.FieldByName('transcription').AsString;
callsDB.UniQuery1.Next;
end;
callsDB.UniQuery1.Close;
SQL := 'select count(*) as total_count from calls inner join ' +
'recordings on calls.sid = recordings.call_sid where ' +
'from_formatted = ' + QuotedStr(phoneNum);
Logger.Log(3, 'Search count query: ' + SQL);
doQuery(callsDB.UniQuery1, SQL);
Result.count := callsDB.UniQuery1.FieldByName('total_count').AsInteger;
callsDB.UniQuery1.Close;
Logger.Log(3, 'Search completed. Total results: ' + IntToStr(Result.count));
end;
function TLookupService.GetCalls(searchOptions: string): TCallList;
var
params: TStringList;
SQL: string;
DBString: string;
call: TCallItem;
offset: string;
limit: string;
PhoneNum: string;
PageNum: integer;
PageSize: integer;
StartDate: string;
EndDate: string;
OrderBy: string;
whereSQL: string;
orderBySQL: string;
Caller: string;
begin
Logger.Log(3, 'GetCalls called with searchOptions: ' + searchOptions);
params := TStringList.Create;
params.StrictDelimiter := true;
params.Delimiter := '&';
params.DelimitedText := searchOptions;
PhoneNum := params.Values['phonenumber'];
PageNum := StrToInt(params.Values['pagenumber']);
PageSize := StrToInt(params.Values['pagesize']);
StartDate := params.Values['startdate'];
EndDate := params.Values['enddate'];
OrderBy := params.Values['orderby'];
Caller := params.Values['caller'];
offset := IntToStr((PageNum - 1) * PageSize);
limit := IntToStr(PageSize);
whereSQL := 'where ';
if PhoneNum <> '' then
whereSQL := whereSQL + 'to_formatted = ' + QuotedStr(PhoneNum);
if StartDate <> '' then
if whereSQL = 'where ' then
whereSQL := whereSQL + 'calls.date_created > ' + QuotedStr(StartDate)
else
whereSQL := whereSQL + ' AND calls.date_created > ' + QuotedStr(StartDate);
if EndDate <> '' then
if whereSQL = 'where ' then
whereSQL := whereSQL + 'calls.date_created < ' + QuotedStr(EndDate)
else
whereSQL := whereSQL + ' AND calls.date_created < ' + QuotedStr(EndDate);
if Caller <> '' then
if whereSQL = 'where ' then
whereSQL := whereSQL + 'from_formatted = ' + QuotedStr(Caller)
else
whereSQL := whereSQL + ' AND from_formatted = ' + QuotedStr(Caller);
if whereSQL = 'where ' then
whereSQL := '';
if (OrderBy = '') or (OrderBy = 'Date') then
orderBySQL := 'order by calls.date_created desc'
else
orderBySQL := 'order by calls.from_formatted desc';
SQL := 'select * ' +
'from calls inner join recordings on calls.sid = recordings.call_sid ' +
whereSQL + ' ' + orderBySQL + ' limit ' + limit + ' offset ' + offset;
Logger.Log(3, 'GetCalls query: ' + SQL);
doQuery(callsDB.UniQuery1, SQL);
Result := TCallList.Create;
Result.data := TList<TCallItem>.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result.data);
while not callsDB.UniQuery1.Eof do
begin
call := TCallItem.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(call);
Result.data.Add(call);
call.callSid := callsDB.UniQuery1.FieldByName('sid').AsString;
call.fromNumber := callsDB.UniQuery1.FieldByName('from_formatted').AsString;
call.ToNumber := callsDB.UniQuery1.FieldByName('to_formatted').AsString;
call.dateCreated := callsDB.UniQuery1.FieldByName('date_created').AsString;
call.mediaUrl := callsDB.UniQuery1.FieldByName('media_url').AsString;
call.duration := callsDB.UniQuery1.FieldByName('duration').AsString;
call.transcription := callsDB.UniQuery1.FieldByName('transcription').AsString;
callsDB.UniQuery1.Next;
end;
callsDB.UniQuery1.Close;
SQL := 'select count(*) as total_count from calls inner join recordings on calls.sid = recordings.call_sid ' + whereSQL;
Logger.Log(3, 'GetCalls count query: ' + SQL);
doQuery(callsDB.UniQuery1, SQL);
Result.count := callsDB.UniQuery1.FieldByName('total_count').AsInteger;
callsDB.UniQuery1.Close;
Logger.Log(3, 'GetCalls completed successfully. Returned count: ' + IntToStr(Result.count));
end;
function TLookupService.GetUsers(searchOptions: string): TUserList;
var
SQL: string;
user: TUserItem;
begin
if searchOptions = '' then
SQL := 'select * from users order by full_name ASC'
else
SQL := 'select * from users where username=' + QuotedStr(searchOptions);
Logger.Log(3, 'GetUsers query: ' + SQL);
doQuery(callsDB.UniQuery1, SQL);
Result := TUserList.Create;
Result.data := TList<TUserItem>.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(Result.data);
while not callsDB.UniQuery1.Eof do
begin
user := TUserItem.Create;
TXDataOperationContext.Current.Handler.ManagedObjects.Add(user);
Result.data.Add(user);
user.userID := callsDB.UniQuery1.FieldByName('user_id').AsString;
user.username := callsDB.UniQuery1.FieldByName('username').AsString;
user.full_name := callsDB.UniQuery1.FieldByName('full_name').AsString;
user.phone_number := callsDB.UniQuery1.FieldByName('phone_number').AsString;
user.email_address := callsDB.UniQuery1.FieldByName('email').AsString;
user.admin := callsDB.UniQuery1.FieldByName('admin').AsBoolean;
user.password := callsDB.UniQuery1.FieldByName('password').AsString;
user.active := callsDB.UniQuery1.FieldByName('active').AsBoolean;
callsDB.UniQuery1.Next;
end;
callsDB.UniQuery1.Close;
SQL := 'select count(*) as total_count from users';
Logger.Log(3, 'GetUsers count query: ' + SQL);
doQuery(callsDB.UniQuery1, SQL);
Result.count := callsDB.UniQuery1.FieldByName('total_count').AsInteger;
callsDB.UniQuery1.Close;
Logger.Log(3, 'GetUsers returned user list, count = ' + IntToStr(Result.count));
end;
function TLookupService.EditUser(const editOptions: string): string;
var
params: TStringList;
user, full_name, email, phone, Admin, newUser, hashString, hashPW, password, active: string;
SQL: string;
begin
Logger.Log(3, 'EditUser called with options: ' + editOptions);
params := TStringList.Create;
params.Delimiter := '&';
params.StrictDelimiter := true;
params.DelimitedText := editOptions;
user := params.Values['username'];
full_name := params.Values['fullname'];
phone := params.Values['phonenumber'];
email := params.Values['email'];
Admin := params.Values['admin'];
newUser := params.Values['newuser'];
password := params.Values['password'];
active := params.Values['active'];
SQL := 'select * from users where username = ' + QuotedStr(user);
Logger.Log(3, 'EditUser query: ' + SQL);
doQuery(callsDB.UniQuery1, SQL);
if callsDB.UniQuery1.IsEmpty then
begin
Result := 'No such user found';
Logger.Log(2, 'EditUser failed: user not found (' + user + ')');
end
else
begin
callsDB.UniQuery1.Edit;
if not newUser.IsEmpty then
callsDB.UniQuery1.FieldByName('username').AsString := newUser;
if not full_name.IsEmpty then
callsDB.UniQuery1.FieldByName('full_name').AsString := full_name;
if not phone.IsEmpty then
callsDB.UniQuery1.FieldByName('phone_number').AsString := phone;
if not email.IsEmpty then
callsDB.UniQuery1.FieldByName('email').AsString := email;
if not Admin.IsEmpty then
callsDB.UniQuery1.FieldByName('admin').AsBoolean := StrToBool(Admin);
if not Active.IsEmpty then
callsDB.UniQuery1.FieldByName('active').AsBoolean := StrToBool(Active);
if (password <> 'hidden') and (not password.IsEmpty) then
begin
hashString := callsDB.UniQuery1.FieldByName('date_created').AsString + password;
hashPW := THashSHA2.GetHashString(hashString, THashSHA2.TSHA2Version.SHA512).ToUpper;
callsDB.UniQuery1.FieldByName('password').AsString := hashPW;
end;
callsDB.UniQuery1.Post;
Result := 'Success:Edit Successful';
Logger.Log(3, 'EditUser success for: ' + user);
end;
callsDB.UniQuery1.Close;
end;
function TLookupService.AddUser(userInfo: string): string;
var
dateCreated: TDateTime;
hashString, hashPW, SQL: string;
params: TStringList;
begin
Logger.Log(3, 'AddUser called with info: ' + userInfo);
params := TStringList.Create;
try
params.StrictDelimiter := true;
params.Delimiter := '&';
params.DelimitedText := userInfo;
dateCreated := Now;
hashString := DateTimeToStr(dateCreated) + params.Values['password'];
hashPW := THashSHA2.GetHashString(hashString, THashSHA2.TSHA2Version.SHA512).ToUpper;
SQL := 'select * from users where username = ' + QuotedStr(params.Values['username'].ToLower);
Logger.Log(3, 'AddUser query: ' + SQL);
callsDB.UniQuery1.Close;
callsDB.UniQuery1.SQL.Text := SQL;
callsDB.UniQuery1.Open;
if callsDB.UniQuery1.IsEmpty then
begin
callsDB.UniQuery1.Insert;
callsDB.UniQuery1.FieldByName('username').AsString := params.Values['username'].ToLower;
callsDB.UniQuery1.FieldByName('password').AsString := hashPW;
callsDB.UniQuery1.FieldByName('date_created').AsString := DateTimeToStr(dateCreated);
callsDB.UniQuery1.FieldByName('full_name').AsString := params.Values['fullname'];
callsDB.UniQuery1.FieldByName('phone_number').AsString := params.Values['phonenumber'];
callsDB.UniQuery1.FieldByName('email').AsString := params.Values['email'];
callsDB.UniQuery1.FieldByName('admin').AsBoolean := StrToBool(params.Values['admin']);
callsDB.UniQuery1.FieldByName('active').AsBoolean := True;
callsDB.UniQuery1.Post;
Result := 'Success:User successfully added';
Logger.Log(3, 'AddUser success: ' + params.Values['username']);
end
else
begin
Result := 'Failure:Username already taken';
Logger.Log(2, 'AddUser failed: Username already taken (' + params.Values['username'] + ')');
end;
finally
params.Free;
end;
end;
function TLookupService.DelUser(username: string): string;
var
SQL: string;
begin
Logger.Log(3, 'DelUser called for: ' + username);
SQL := 'select * from users where username = ' + QuotedStr(username.ToLower);
callsDB.UniQuery1.Close;
callsDB.UniQuery1.SQL.Text := SQL;
callsDB.UniQuery1.Open;
if callsDB.UniQuery1.IsEmpty then
begin
Result := 'Failure:User does not exist';
Logger.Log(2, 'DelUser failed: user not found (' + username + ')');
end
else
begin
SQL := 'DELETE FROM users where username = ' + QuotedStr(username.ToLower);
callsDB.UniQuery1.SQL.Text := SQL;
callsDB.UniQuery1.ExecSQL;
Result := 'Success:User deleted';
Logger.Log(3, 'DelUser success: ' + username);
end;
end;
initialization
RegisterServiceType(TLookupService);
end.
object FMain: TFMain
Left = 0
Top = 0
Caption = 'emiMobileServer'
ClientHeight = 597
ClientWidth = 764
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OnClose = FormClose
DesignSize = (
764
597)
TextHeight = 13
object memoInfo: TMemo
Left = 8
Top = 40
Width = 744
Height = 549
Anchors = [akLeft, akTop, akRight, akBottom]
ReadOnly = True
TabOrder = 0
end
object btnApiSwaggerUI: TButton
Left = 297
Top = 8
Width = 100
Height = 25
Caption = 'Api SwaggerUI'
TabOrder = 1
OnClick = btnApiSwaggerUIClick
end
object btnData: TButton
Left = 525
Top = 8
Width = 75
Height = 25
Caption = 'Data'
TabOrder = 2
OnClick = btnDataClick
end
object btnExit: TButton
Left = 671
Top = 8
Width = 75
Height = 25
Caption = 'Exit'
TabOrder = 3
OnClick = btnExitClick
end
object btnAuthSwaggerUI: TButton
Left = 169
Top = 8
Width = 100
Height = 25
Caption = 'Auth SwaggerUI'
TabOrder = 4
OnClick = btnAuthSwaggerUIClick
end
object initTimer: TTimer
OnTimer = initTimerTimer
Left = 58
Top = 398
end
object ExeInfo1: TExeInfo
Version = '1.6.1.1'
Left = 256
Top = 402
end
object tmrTwilio: TTimer
Enabled = False
Interval = 30000
OnTimer = tmrTwilioTimer
Left = 146
Top = 416
end
end
unit Main;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, Winapi.ShellApi,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, Vcl.ExtCtrls, System.Generics.Collections, System.IniFiles,
Auth.Service, Auth.Server.Module, Api.Server.Module, App.Server.Module,
ExeInfo, Lookup.Service;
type
TFMain = class(TForm)
memoInfo: TMemo;
btnApiSwaggerUI: TButton;
btnData: TButton;
btnExit: TButton;
initTimer: TTimer;
btnAuthSwaggerUI: TButton;
ExeInfo1: TExeInfo;
tmrTwilio: TTimer;
procedure btnApiSwaggerUIClick(Sender: TObject);
procedure btnDataClick(Sender: TObject);
procedure btnExitClick(Sender: TObject);
procedure tmrTwilioTimer(Sender: TObject);
procedure ContactFormData(AText: String);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure initTimerTimer(Sender: TObject);
procedure btnAuthSwaggerUIClick(Sender: TObject);
strict private
phoneDict: TDictionary<string, string>;
procedure StartServers;
function LogValue(const LabelName: string; const Value: string; FromIni: Boolean): string;
end;
var
FMain: TFMain;
implementation
uses
Common.Logging,
Common.Config,
Common.Ini,
Sparkle.Utils,
Data, Twilio.Data.Module, Api.Database, System.StrUtils;
{$R *.dfm}
{ --- Event Handlers --- }
procedure TFMain.btnExitClick(Sender: TObject);
begin
Close;
end;
procedure TFMain.btnApiSwaggerUIClick(Sender: TObject);
begin
ShellExecute(Handle, 'open', PChar(TSparkleUtils.CombineUrlFast(ApiServerModule.XDataServer1.BaseUrl, 'swaggerui')), nil, nil, SW_SHOWNORMAL);
end;
procedure TFMain.btnAuthSwaggerUIClick(Sender: TObject);
begin
ShellExecute(Handle, 'open', PChar(TSparkleUtils.CombineUrlFast(AuthServerModule.XDataServer.BaseUrl, 'swaggerui')), nil, nil, SW_SHOWNORMAL);
end;
procedure TFMain.btnDataClick(Sender: TObject);
begin
FData := TFData.Create(Self);
FData.ShowModal;
FData.Free;
end;
procedure TFMain.ContactFormData(AText: String);
begin
if memoInfo.CanFocus then
TThread.Queue(nil, procedure begin memoInfo.Lines.Add(AText); end)
else
TThread.Synchronize(nil, procedure begin memoInfo.Lines.Add(AText); end);
end;
procedure TFMain.initTimerTimer(Sender: TObject);
begin
initTimer.Enabled := False;
Caption := Caption + ' ver ' + ExeInfo1.FileVersion;
ServerConfig := TServerConfig.Create;
LoadIniEntries;
LoadServerConfig;
StartServers;
end;
procedure TFMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
phoneDict.Free;
ServerConfig.Free;
IniEntries.Free;
AuthServerModule.Free;
ApiServerModule.Free;
AppServerModule.Free;
end;
{ --- Helpers --- }
function TFMain.LogValue(const LabelName: string; const Value: string; FromIni: Boolean): string;
begin
Result := LabelName + ': ' + Value + IfThen(FromIni, ' [from ini]', ' [default]');
end;
procedure TFMain.StartServers;
begin
Logger.Log(1, '');
Logger.Log(1, '*******************************************************');
Logger.Log(1, '* emiMobile Server *');
Logger.Log(1, Format('* Version: %s', [FMain.ExeInfo1.FileVersion]));
Logger.Log(1, '* Developed by EM Systems, Inc. *');
Logger.Log(1, '*******************************************************');
Logger.Log(1, '');
Logger.Log(1, '--- Settings ---');
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->TwilioUpdateTime', IniEntries.TwilioUpdateTime.ToString, IniEntries.TwilioUpdateTimeFromIni));
Logger.Log(1, '');
Logger.Log(1, '--- Database ---');
Logger.Log(1, LogValue('--Database->Server', IniEntries.DatabaseServer, IniEntries.DatabaseServerFromIni));
Logger.Log(1, LogValue('--Database->Database', IniEntries.DatabaseName, IniEntries.DatabaseNameFromIni));
Logger.Log(1, LogValue('--Database->Username', IniEntries.DatabaseUsername, IniEntries.DatabaseUsernameFromIni));
Logger.Log(1, LogValue('--Database->Password', IniEntries.DatabasePassword, IniEntries.DatabasePasswordFromIni));
Logger.Log(1, '');
Logger.Log(1, '--- Twilio ---');
Logger.Log(1, LogValue('--Twilio->AccountSID', IniEntries.TwilioSID, IniEntries.TwilioSIDFromIni));
Logger.Log(1, LogValue('--Twilio->AuthHeader', IniEntries.TwilioAuthHeader, IniEntries.TwilioAuthHeaderFromIni));
Logger.Log(1, '');
try
AuthServerModule := TAuthServerModule.Create(Self);
AuthServerModule.StartAuthServer(ServerConfig.url, AUTH_MODEL);
ApiServerModule := TApiServerModule.Create(Self);
ApiServerModule.StartApiServer(ServerConfig.url, API_MODEL);
AppServerModule := TAppServerModule.Create(Self);
AppServerModule.StartAppServer(ServerConfig.url);
if IniEntries.TwilioUpdateTime > 0 then
begin
TwilioDataModule := TTwilioDataModule.Create(Self);
tmrTwilio.Interval := IniEntries.TwilioUpdateTime * 60000;
tmrTwilio.Enabled := True;
Logger.Log(1, Format('Twilio polling enabled every %d minutes.', [IniEntries.TwilioUpdateTime]));
end
else
begin
tmrTwilio.Enabled := False;
Logger.Log(1, 'Twilio polling disabled (TwilioUpdateTime = 0)');
end;
except
on E: Exception do
Logger.Log(2, 'Failed to start server modules: ' + E.Message);
end;
end;
procedure TFMain.tmrTwilioTimer(Sender: TObject);
begin
tmrTwilio.Enabled := False;
Logger.Log(4, 'tmrTwilioTimer ---start');
TwilioDataModule := TTwilioDataModule.Create(Self);
TwilioDataModule.UpdateDB;
TwilioDataModule.Free;
Logger.Log(4, 'tmrTwilioTimer ---end (interval: ' + tmrTwilio.Interval.ToString + ' ms)');
tmrTwilio.Enabled := True;
end;
end.
object TwilioDataModule: TTwilioDataModule
OnCreate = DataModuleCreate
OnDestroy = DataModuleDestroy
Height = 338
Width = 537
object ucEnvoy: TUniConnection
ProviderName = 'PostgreSQL'
SpecificOptions.Strings = (
'PostgreSQL.Schema=envoy')
LoginPrompt = False
Left = 65
Top = 89
end
object PostgreSQLUniProvider1: TPostgreSQLUniProvider
Left = 226
Top = 88
end
object UniQuery1: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'')
Left = 361
Top = 88
end
object uqLocations: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'select * from locations')
Left = 130
Top = 172
object uqLocationslocation: TStringField
DisplayWidth = 30
FieldName = 'location'
Required = True
Size = 30
end
object uqLocationsphone_number: TStringField
DisplayWidth = 14
FieldName = 'phone_number'
Required = True
Visible = False
Size = 14
end
object uqLocationsphone_number_formatted: TStringField
FieldName = 'phone_number_formatted'
Size = 14
end
end
object uqRecordings: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'select * from recordings')
Left = 224
Top = 174
object uqRecordingsaccount_sid: TStringField
FieldName = 'account_sid'
Size = 34
end
object uqRecordingsapi_version: TStringField
FieldName = 'api_version'
Size = 10
end
object uqRecordingscall_sid: TStringField
FieldName = 'call_sid'
Size = 34
end
object uqRecordingsconference_sid: TStringField
FieldName = 'conference_sid'
Size = 34
end
object uqRecordingsdate_created: TDateTimeField
FieldName = 'date_created'
end
object uqRecordingsdate_updated: TDateTimeField
FieldName = 'date_updated'
end
object uqRecordingsstart_time: TDateTimeField
FieldName = 'start_time'
end
object uqRecordingsduration: TStringField
FieldName = 'duration'
Size = 4
end
object uqRecordingssid: TStringField
FieldName = 'sid'
Required = True
Size = 34
end
object uqRecordingsprice: TStringField
FieldName = 'price'
Size = 8
end
object uqRecordingsprice_unit: TStringField
FieldName = 'price_unit'
Size = 3
end
object uqRecordingsstatus: TStringField
FieldName = 'status'
Size = 10
end
object uqRecordingschannels: TStringField
FieldName = 'channels'
Size = 1
end
object uqRecordingssource: TStringField
FieldName = 'source'
Size = 10
end
object uqRecordingserror_code: TStringField
FieldName = 'error_code'
Size = 5
end
object uqRecordingsuri: TStringField
FieldName = 'uri'
Size = 106
end
object uqRecordingsencryption_details: TStringField
FieldName = 'encryption_details'
Size = 30
end
object uqRecordingsmedia_url: TStringField
FieldName = 'media_url'
Size = 123
end
object uqRecordingstranscription: TMemoField
FieldName = 'transcription'
BlobType = ftMemo
end
end
object uqCalls: TUniQuery
Connection = ucEnvoy
SQL.Strings = (
'select * from calls')
Left = 323
Top = 176
object uqCallsdate_updated: TDateTimeField
FieldName = 'date_updated'
end
object uqCallsprice_unit: TStringField
FieldName = 'price_unit'
Size = 3
end
object uqCallsparent_call_sid: TStringField
FieldName = 'parent_call_sid'
Size = 34
end
object uqCallscaller_name: TStringField
FieldName = 'caller_name'
Size = 30
end
object uqCallsduration: TStringField
FieldName = 'duration'
Size = 4
end
object uqCallsannotation: TMemoField
FieldName = 'annotation'
BlobType = ftMemo
end
object uqCallsanswered_by: TStringField
FieldName = 'answered_by'
Size = 30
end
object uqCallssid: TStringField
FieldName = 'sid'
Required = True
Size = 34
end
object uqCallsqueue_time: TStringField
FieldName = 'queue_time'
Size = 4
end
object uqCallsprice: TStringField
FieldName = 'price'
Size = 8
end
object uqCallsapi_version: TStringField
FieldName = 'api_version'
Size = 10
end
object uqCallsstatus: TStringField
FieldName = 'status'
Size = 10
end
object uqCallsdirection: TStringField
FieldName = 'direction'
Size = 8
end
object uqCallsstart_time: TDateTimeField
FieldName = 'start_time'
end
object uqCallsdate_created: TDateTimeField
FieldName = 'date_created'
end
object uqCallsfrom_formatted: TStringField
FieldName = 'from_formatted'
Size = 13
end
object uqCallsgroup_sid: TStringField
FieldName = 'group_sid'
Size = 34
end
object uqCallstrunk_sid: TStringField
FieldName = 'trunk_sid'
Size = 34
end
object uqCallsuri: TStringField
FieldName = 'uri'
Size = 101
end
object uqCallsaccount_sid: TStringField
FieldName = 'account_sid'
Size = 34
end
object uqCallsend_time: TDateTimeField
FieldName = 'end_time'
end
object uqCallsto_formatted: TStringField
FieldName = 'to_formatted'
Size = 13
end
object uqCallsphone_number_sid: TStringField
FieldName = 'phone_number_sid'
Size = 34
end
object uqCallsforwarded_from: TStringField
FieldName = 'forwarded_from'
Size = 12
end
end
end
//Handles all Twilio RestAPI calls, so any file that that needs to retrieve
// info from twilio uses this file
// Authors:
// Cameron Hayes
// Elias Sarraf
unit Twilio.Data.Module;
interface
uses
System.SysUtils, System.Classes, Data.DB, MemDS, DBAccess, Uni, UniProvider,
PostgreSQLUniProvider, REST.Client, REST.Types, System.JSON, Winapi.Windows,
Winapi.Messages, System.Variants, System.Generics.Collections, System.IniFiles,
Vcl.Forms;
type
TTwilioDataModule = class(TDataModule)
ucEnvoy: TUniConnection;
PostgreSQLUniProvider1: TPostgreSQLUniProvider;
UniQuery1: TUniQuery;
uqLocations: TUniQuery;
uqLocationslocation: TStringField;
uqLocationsphone_number: TStringField;
uqRecordings: TUniQuery;
uqRecordingsaccount_sid: TStringField;
uqRecordingsapi_version: TStringField;
uqRecordingscall_sid: TStringField;
uqRecordingsconference_sid: TStringField;
uqRecordingsdate_created: TDateTimeField;
uqRecordingsdate_updated: TDateTimeField;
uqRecordingsstart_time: TDateTimeField;
uqRecordingsduration: TStringField;
uqRecordingssid: TStringField;
uqRecordingsprice: TStringField;
uqRecordingsprice_unit: TStringField;
uqRecordingsstatus: TStringField;
uqRecordingschannels: TStringField;
uqRecordingssource: TStringField;
uqRecordingserror_code: TStringField;
uqRecordingsuri: TStringField;
uqRecordingsencryption_details: TStringField;
uqRecordingsmedia_url: TStringField;
uqRecordingstranscription: TMemoField;
uqCalls: TUniQuery;
uqCallsdate_updated: TDateTimeField;
uqCallsprice_unit: TStringField;
uqCallsparent_call_sid: TStringField;
uqCallscaller_name: TStringField;
uqCallsduration: TStringField;
uqCallsannotation: TMemoField;
uqCallsanswered_by: TStringField;
uqCallssid: TStringField;
uqCallsqueue_time: TStringField;
uqCallsprice: TStringField;
uqCallsapi_version: TStringField;
uqCallsstatus: TStringField;
uqCallsdirection: TStringField;
uqCallsstart_time: TDateTimeField;
uqCallsdate_created: TDateTimeField;
uqCallsfrom_formatted: TStringField;
uqCallsgroup_sid: TStringField;
uqCallstrunk_sid: TStringField;
uqCallsuri: TStringField;
uqCallsaccount_sid: TStringField;
uqCallsend_time: TDateTimeField;
uqCallsto_formatted: TStringField;
uqCallsphone_number_sid: TStringField;
uqCallsforwarded_from: TStringField;
uqLocationsphone_number_formatted: TStringField;
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
function GetRecordings(count: integer; offset: integer): integer;
function GetCalls(phoneNum: string; count: integer; offset: integer): integer;
procedure UpdateDB();
procedure ShowCalls(PhoneNum: string);
function GetTranscription(uri: string): string;
function toDateTime(date: string): TDateTime;
function FullUpdate(): string;
private
{ Private declarations }
accountSID: string;
authHeader: string;
public
{ Public declarations }
end;
var
TwilioDataModule: TTwilioDataModule;
implementation
uses
Common.Logging,
Common.Config;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
function TTwilioDataModule.GetCalls(phoneNum: string; count: integer; offset: integer): integer;
var
restClient: TRESTClient;
restRequest: TRESTRequest;
restResponse: TRESTResponse;
param: TRESTRequestParameter;
res: string;
sid: string;
jsValue: TJSONValue;
jsObj: TJSONObject;
jaCalls: TJSONArray;
joCall: TJSONObject;
i: integer;
uri: string;
sql: string;
addedCount: Integer;
begin
restClient := TRESTClient.Create(nil);
restClient.BaseURL := 'https://api.twilio.com';
restRequest := TRESTRequest.Create(nil);
restRequest.Client := restClient;
restResponse := TRESTResponse.Create(nil);
restRequest.Response := restResponse;
restRequest.Method := rmGET;
res := '/2010-04-01/Accounts/' + accountSID + '/Calls.json?Status=completed&To=+' + phoneNum + '&PageSize=' + IntToStr(count);
restRequest.Resource := res;
param := restRequest.Params.AddItem;
param.Name := 'Authorization';
param.Kind := pkHTTPHEADER;
param.Options := param.Options + [TRESTRequestParameterOption.poDoNotEncode];
param.Value := authHeader;
restRequest.Execute;
jsValue := restResponse.JSONValue;
jsObj := TJSONObject(jsValue);
jaCalls := TJSONArray(jsObj.GetValue('calls'));
addedCount := 0;
for i := offset to (jaCalls.Count - 1) do
begin
joCall := TJSONObject(jaCalls.Items[i]);
sql := 'select * from calls where sid = ' + QuotedStr(joCall.Get('sid').JsonValue.Value);
uqCalls.Close;
uqCalls.SQL.Text := sql;
uqCalls.Open;
if uqCalls.IsEmpty then
begin
uqCalls.Append;
uqCallsdate_updated.AsDateTime := toDateTime(joCall.Get('date_updated').JsonValue.Value);
uqCallsprice_unit.AsString := joCall.Get('price_unit').JsonValue.Value;
uqCallsparent_call_sid.AsString := joCall.Get('parent_call_sid').JsonValue.Value;
uqCallsduration.AsString := joCall.Get('duration').JsonValue.Value;
uqCallsannotation.AsString := joCall.Get('annotation').JsonValue.Value;
uqCallsanswered_by.AsString := joCall.Get('answered_by').JsonValue.Value;
uqCallssid.AsString := joCall.Get('sid').JsonValue.Value;
uqCallsqueue_time.AsString := joCall.Get('queue_time').JsonValue.Value;
uqCallsprice.AsString := joCall.Get('price').JsonValue.Value;
uqCallsdirection.AsString := joCall.Get('direction').JsonValue.Value;
uqCallsstart_time.AsDateTime := toDateTime(joCall.Get('start_time').JsonValue.Value);
uqCallsdate_created.AsDateTime := toDateTime(joCall.Get('date_created').JsonValue.Value);
uqCallsfrom_formatted.AsString := joCall.Get('from_formatted').JsonValue.Value;
uqCallsgroup_sid.AsString := joCall.Get('group_sid').JsonValue.Value;
uqCallstrunk_sid.AsString := joCall.Get('trunk_sid').JsonValue.Value;
uqCallsforwarded_from.AsString := joCall.Get('forwarded_from').JsonValue.Value;
uqCallsuri.AsString := joCall.Get('uri').JsonValue.Value;
uqCallsaccount_sid.AsString := joCall.Get('account_sid').JsonValue.Value;
uqCallsend_time.AsDateTime := toDateTime(joCall.Get('end_time').JsonValue.Value);
uqCallsto_formatted.AsString := joCall.Get('to_formatted').JsonValue.Value;
uqCallsphone_number_sid.AsString := joCall.Get('phone_number_sid').JsonValue.Value;
uqCallscaller_name.AsString := joCall.Get('caller_name').JsonValue.Value;
uqCallsapi_version.AsString := joCall.Get('api_version').JsonValue.Value;
uqCallsStatus.AsString := joCall.Get('status').JsonValue.Value;
uqCalls.Post;
Inc(addedCount);
end
else
continue;
end;
restClient.Free;
restRequest.Free;
restResponse.Free;
Result := addedCount;
end;
function TTwilioDataModule.GetRecordings(count: integer; offset: integer): integer;
var
restClient: TRESTClient;
restRequest: TRESTRequest;
restResponse: TRESTResponse;
param: TRESTRequestParameter;
jsValue: TJSONValue;
jsObj: TJSONObject;
jaRecordings: TJSONArray;
joRec: TJSONObject;
joTrans: TJSONObject;
i: integer;
sql: string;
turi: string;
addedCount: Integer;
begin
restClient := TRESTClient.Create(nil);
restClient.BaseURL := 'https://api.twilio.com';
restRequest := TRESTRequest.Create(nil);
restRequest.Client := restClient;
restResponse := TRESTResponse.Create(nil);
restRequest.Response := restResponse;
restRequest.Method := rmGET;
restRequest.Resource := '/2010-04-01/Accounts/' + accountSID + '/Recordings.json?PageSize=' + IntToStr(count);
param := restRequest.Params.AddItem;
param.Name := 'Authorization';
param.Kind := pkHTTPHEADER;
param.Options := param.Options + [TRESTRequestParameterOption.poDoNotEncode];
param.Value := authHeader;
restRequest.Execute;
jsValue := restResponse.JSONValue;
jsObj := TJSONObject(jsValue);
jaRecordings := TJSONArray(jsObj.GetValue('recordings'));
addedCount := 0;
for i := offset to jaRecordings.Count - 1 do
begin
joRec := TJSONObject(jaRecordings.Items[i]);
sql := 'select * from recordings where sid = ' + QuotedStr(joRec.Get('sid').JsonValue.Value);
uqRecordings.Close;
uqRecordings.SQL.Text := sql;
uqRecordings.Open;
if not uqRecordings.IsEmpty then
continue;
uqRecordings.Append;
uqRecordingsaccount_sid.AsString := joRec.Get('account_sid').JsonValue.Value;
uqRecordingscall_sid.AsString := joRec.Get('call_sid').JsonValue.Value;
uqRecordingsdate_created.AsDateTime := toDateTime(joRec.Get('date_created').JsonValue.Value);
uqRecordingsstatus.AsString := joRec.Get('status').JsonValue.Value;
uqRecordingsduration.AsString := joRec.Get('duration').JsonValue.Value;
uqRecordingsapi_version.AsString := joRec.Get('api_version').JsonValue.Value;
uqRecordingsconference_sid.AsString := joRec.Get('conference_sid').JsonValue.Value;
uqRecordingsdate_updated.AsDateTime := toDateTime(joRec.Get('date_updated').JsonValue.Value);
uqRecordingsstart_time.AsDateTime := toDateTime(joRec.Get('start_time').JsonValue.Value);
uqRecordingssid.AsString := joRec.Get('sid').JsonValue.Value;
uqRecordingsprice.AsString := joRec.Get('price').JsonValue.Value;
uqRecordingsprice_unit.AsString := joRec.Get('price_unit').JsonValue.Value;
uqRecordingschannels.AsString := joRec.Get('channels').JsonValue.Value;
uqRecordingssource.AsString := joRec.Get('source').JsonValue.Value;
uqRecordingserror_code.AsString := joRec.Get('error_code').JsonValue.Value;
uqRecordingsuri.AsString := joRec.Get('uri').JsonValue.Value;
uqRecordingsencryption_details.AsString := joRec.Get('encryption_details').JsonValue.Value;
uqRecordingsmedia_url.AsString := joRec.Get('media_url').JsonValue.Value;
if joRec.Get('duration').JsonValue.Value = '-1' then
begin
uqRecordingstranscription.AsString := 'Empty';
end
else
begin
joTrans := TJSONObject(joRec.Get('subresource_uris').JsonValue);
turi := joTrans.Get('transcriptions').JsonValue.Value;
uqRecordingstranscription.AsString := GetTranscription(turi);
end;
uqRecordings.Post;
Inc(addedCount);
end;
restClient.Free;
restRequest.Free;
restResponse.Free;
Result := addedCount;
end;
function TTwilioDataModule.GetTranscription(uri: string): string;
// Retrieves a JSONArray of 1 transcription then adds that one transcription
// into the database. The RestAPI call is directly linked to Recording sid so
// this is only called when the recording has an assoiciated transcription. Bulk
// adding transcriptions is currently not doable.
var
restClient: TRESTClient;
restRequest: TRESTRequest;
restResponse: TRESTResponse;
param: TRESTRequestParameter;
jsValue: TJSONValue;
jsObj: TJSONObject;
joTrans: TJSONObject;
jaTrans: TJSONArray;
i: integer;
trans_text: string;
begin
restClient := TRESTClient.Create(nil);
restClient.BaseURL := 'https://api.twilio.com';
restRequest := TRESTRequest.Create(nil);
restRequest.Client := restClient;
restResponse := TRESTResponse.Create(nil);
restRequest.Response := restResponse;
restRequest.Method := rmGET;
restRequest.Resource := uri;
param := restRequest.Params.AddItem;
param.Name := 'Authorization';
param.Kind := pkHTTPHEADER;
param.Options := param.Options + [TRESTRequestParameterOption.poDoNotEncode];
param.Value := authHeader;
restRequest.Execute;
jsValue := restResponse.JSONValue;
jsObj := TJSONObject(jsValue);
jaTrans := TJSONArray(jsObj.Get('transcriptions').JsonValue);
if jaTrans.IsEmpty then
begin
trans_text := 'Empty';
end
else
begin
joTrans := TJSONObject(jaTrans.Items[0]);
trans_text := joTrans.Get('transcription_text').JsonValue.Value;
end;
if trans_text = 'null' then
begin
trans_text := 'Empty';
end;
restClient.Free;
restRequest.Free;
restResponse.Free;
Result := trans_text;
end;
procedure TTwilioDataModule.UpdateDB();
// Updates the calls for all 4 locations and gets the 50 most recent recordings
// Activates on a timer in Main set for every 5 minutes.
// phoneDict: Dictionary containing all the locations which are linked to a
// phone number.
var
sql: string;
phoneNum: string;
begin
sql := 'select * from locations';
uqLocations.Close;
uqLocations.SQL.Text := sql;
uqLocations.Open;
GetRecordings(50, 0);
while not uqLocations.Eof do
begin
phoneNum := uqLocations.FieldByName('phone_number').AsString;
GetCalls(phoneNum, 50, 0);
uqLocations.Next;
end;
end;
function TTwilioDataModule.toDateTime(date: string): TDateTime;
// Converts the string we were given from twilio into a datetime
// date: string - formatted ddd, dd mmm yyyy hh:nn:ss+zzzz
// Returns a DateTime Object with formating specified below
var
fs: TFormatSettings;
dt: TDateTime;
dateString: String;
begin
// Removes the very first day(ddd, )
date := Copy(date, Pos(',', date) + 2, MaxInt);
// Removes the timezone(+zzzz) as it is always +0000
date := Copy(date, 1, Pos(' +', date)-1);
fs.Create;
fs.DateSeparator := ' ';
fs.TimeSeparator := ':';
fs.ShortDateFormat := 'mm/dd/yyyy';
fs.ShortTimeFormat := 'hh:nn:ss';
dt := VarToDateTime(date);
Result := dt;
end;
procedure TTwilioDataModule.ShowCalls(PhoneNum: string);
var
SQL: string;
begin
SQL := 'select * ' + ' from calls where to_formatted = '
+ QuotedStr(PhoneNum) + ' order by calls.date_created desc';
uqCalls.SQL.Text := SQL;
uqCalls.Open;
end;
procedure TTwilioDataModule.DataModuleCreate(Sender: TObject);
var
IniFile: TIniFile;
iniStr: string;
begin
IniFile := TIniFile.Create(ChangeFileExt(Application.ExeName, '.ini'));
Logger.Log(5, 'Twilio Data Module Created');
try
iniStr := IniFile.ReadString('Database', 'Server', '');
if iniStr <> '' then
ucEnvoy.Server := iniStr;
iniStr := IniFile.ReadString('Database', 'Database', '');
if iniStr <> '' then
ucEnvoy.Database := iniStr;
iniStr := IniFile.ReadString('Database', 'Username', '');
if iniStr <> '' then
ucEnvoy.Username := iniStr;
iniStr := IniFile.ReadString('Database', 'Password', '');
if iniStr <> '' then
ucEnvoy.Password := iniStr;
// Same logic as before for Twilio...
iniStr := IniFile.ReadString('Twilio', 'AccountSID', '');
if iniStr <> '' then
accountSID := iniStr;
iniStr := IniFile.ReadString('Twilio', 'AuthHeader', '');
if iniStr <> '' then
authHeader := iniStr;
finally
IniFile.Free;
end;
uqLocations.Open;
end;
procedure TTwilioDataModule.DataModuleDestroy(Sender: TObject);
begin
Logger.Log(5, 'Twilio Data Module Destroyed');
uqLocations.Close;
end;
function TTwilioDataModule.FullUpdate: String;
var
sql, phoneNum: string;
count, offset, added: integer;
call_count, rec_count: integer;
resultString: string;
begin
call_count := 0;
rec_count := 0;
// Update calls for each location
sql := 'select * from locations';
uqLocations.Close;
uqLocations.SQL.Text := sql;
uqLocations.Open;
while not uqLocations.Eof do
begin
phoneNum := uqLocations.FieldByName('phone_number').AsString;
offset := 0;
count := 50;
repeat
added := GetCalls(phoneNum, count, offset);
if added > 0 then
Inc(call_count, added);
Inc(offset, count);
until added = 0;
uqLocations.Next;
end;
// Update recordings
offset := 0;
count := 50;
repeat
added := GetRecordings(count, offset);
if added > 0 then
Inc(rec_count, added);
Inc(offset, count);
until added = 0;
resultString := 'Added ~' + IntToStr(call_count) + ' calls to database' + sLineBreak +
'Added ~' + IntToStr(rec_count) + ' recordings to database';
Result := resultString;
end;
end.
unit uLibrary;
interface
uses
System.Classes, Uni;
const
ADD_REC_AUDIT_ENTRY = '0';
EDIT_REC_AUDIT_ENTRY = '1';
DEL_REC_AUDIT_ENTRY = '2';
REVIEW_REC_AUDIT_ENTRY = '3';
VIEW_REC_AUDIT_ENTRY = '4';
FIND_REC_AUDIT_ENTRY = '5';
PRINT_REC_AUDIT_ENTRY = '6';
OTHER_REC_AUDIT_ENTRY = '99';
function GetServerTimeStamp( uq: TUniQuery ): TDateTime;
procedure DoQuery( uq: TUniQuery; sql: string );
function CalculateAge( const dob, dt: TDateTime ): Integer;
function GetNextSeqVal( uq: TUniQuery; sequence: string ): string;
function FormatNamePersonnel( uq: TUniQuery; format: string ): string;
function FormatBkNum( bkNum: string ): string;
function GetAssociatedNumber( uq: TUniQuery; numberType: string ): string;
function FormatBookingAddress( uq: TUniQuery; format: string ): string;
function SetMasterAuditEntry( uq: TUniQuery; const entryId, auditType, linkId, agency, personnelId, recUser, details, searchKey, execSource: string ): Boolean;
function SetDetailAuditEntry( uq: TUniQuery; const entryId, title, auditType: string; auditList: TStringList ): Boolean;
function GetOfficerName( agency, officer: string; uq: TUniQuery ): string;
function GetRiciOfficerName( agency, officer: string; uq: TUniQuery ): string;
implementation
uses
System.SysUtils,
Data.DB;
function GetServerTimeStamp( uq: TUniQuery ): TDateTime;
var
sql: string;
serverDateTime: TDateTime;
begin
sql := 'select sysdate as currentdatetime from dual';
DoQuery( uq, sql );
serverDateTime := uq.FieldByName('CURRENTDATETIME').AsDateTime;
uq.Close;
Result := serverDateTime;
end;
procedure DoQuery(uq: TUniQuery; sql: string);
begin
uq.Close;
uq.SQL.Text := sql;
uq.Open;
end;
function CalculateAge( const dob, dt: TDateTime): Integer;
var
age: Integer;
y1, m1, d1, y2, m2, d2: Word;
begin
Result := 0;
if dt < dob then
Exit;
DecodeDate( dob, y1, m1, d1);
DecodeDate( dt, y2, m2, d2);
age := y2 - y1;
// Feb 29
//if ( (m1=2) and (d1=29) ) and ( not IsLeapYear(y2) ) then
// d1 := 28;
if (m1 = 2) and (d1 = 29) and (not (IsLeapYear (y2))) then
begin
m1 := 3;
d1 := 1;
end;
if (m2 < m1) or ((m2 = m1) and (d2 < d1)) then
Dec(age);
Result := age
end;
function GetNextSeqVal(uq: TUniQuery; sequence: string ): string;
var
sql: string;
begin
sql := 'select ' + sequence + '.NEXTVAL as nextseqval from dual';
uq.Close;
uq.SQL.Text := sql;
uq.Open;
Result := uq.FieldByName('NEXTSEQVAL').AsString;
end;
function FormatNamePersonnel( uq: TUniQuery; format: string ): string;
var
leng: Integer;
i: Integer;
officerText: String;
begin
leng := Length( format );
for i := 0 to leng - 1 do
begin
case format[i+1] of
'S':
officerText := officerText + uq.FieldByName('PF_LNAME').AsString;
'F':
if not uq.FieldByName('PF_FNAME').AsString.IsEmpty then
officerText := TrimRight( officerText + uq.FieldByName('PF_FNAME').AsString ) ;
'M':
if not uq.FieldByName('PF_MI').AsString.IsEmpty then
officerText := TrimRight( officerText + uq.FieldByName('PF_MI').AsString );
',':
officerText := officerText + ',';
'.':
officerText := officerText + '.';
' ':
officerText := officerText + ' ';
end;
end;
Result := officerText;
end;
function FormatBkNum( bkNum: string ): string;
var
bkNumStr: string;
begin
bkNumStr := bkNum;
Result := bkNumStr.Insert( 4, '-' );
end;
function GetAssociatedNumber( uq: TUniQuery; numberType: string): string;
var
TLocateOptions: set of TLocateOption;
begin
if uq.Locate('OTHER_AGENCY_CODE', numberType, TLocateOptions)
then Result := uq.FieldByName('IDENTIFICATION').AsString
end;
function FormatBookingAddress( uq: TUniQuery; format: string ): string;
var
addressText: AnsiString;
leng: Integer;
i : Integer;
begin
leng := Length( format );
for i := 0 to leng - 1 do
begin
case format[i+1] of
'S':
begin
addressText := addressText + uq.FieldByName('STREET_NUM').AsString;
if uq.FieldByName('STREET_NUM_HALF').AsString = 'Y' then
addressText := addressText + ' 1/2';
if uq.FieldByName('STREET_DIRECTION').AsString <> '' then
addressText := addressText + ' ' + uq.FieldByName('STREET_DIRECTION').AsString;
if uq.FieldByName('STREET_NAME').AsString <> '' then
addressText := addressText + ' ' + TrimRight( uq.FieldByName('STREET_NAME').AsString );
if uq.FieldByName('STREET_TYPE').AsString <> '' then
addressText := addressText + ' ' + TrimRight( uq.FieldByName('STREET_TYPE').AsString );
if uq.FieldByName('APARTMENT_NUM').AsString <> '' then
addressText := addressText + ' APT: ' + TrimRight( uq.FieldByName('APARTMENT_NUM').AsString );
end;
'C':
if uq.FieldByName('CITY').AsString <> '' then
addressText := addressText + ' ' + TrimRight( uq.FieldByName('CITY').AsString );
'T':
if uq.FieldByName('STATE').AsString <> '' then
addressText := addressText + ' ' + TrimRight( uq.FieldByName('STATE').AsString );
'Z':
if uq.FieldByName('ZIP_CODE').AsString <> '' then
addressText := addressText + ' ' + TrimRight( uq.FieldByName('ZIP_CODE').AsString );
'R':
if uq.FieldByName('COUNTRY').AsString <> '' then
addressText := addressText + ' ' + TrimRight( uq.FieldByName('COUNTRY').AsString );
',':
addressText := addressText + ',';
'.':
addressText := addressText + '.';
' ':
addressText := addressText + ' ';
end;
end;
Result := addressText;
end;
function SetMasterAuditEntry(uq: TUniQuery; const entryId, auditType, linkId, agency, personnelId, recUser, details, searchKey, execSource: string) : Boolean;
var
sql: string;
begin
sql := 'insert into auditmaster ';
sql := sql + '( AUDITMASTERID, SOURCEID, AUDITTYPE, AGENCY, PERSONNELID, RECUSER, RECDATE, DETAILS, SEARCHKEY, EXECSRC) ';
sql := sql + 'values (';
sql := sql + entryID + ', ';
sql := sql + QuotedStr(linkID) + ', ';
sql := sql + QuotedStr(auditType) + ', ';
sql := sql + QuotedStr(agency) + ', ';
sql := sql + personnelid + ', ';
sql := sql + QuotedStr(recUser) + ', ';
sql := sql + 'sysdate, ';
sql := sql + QuotedStr(details) + ', ';
sql := sql + QuotedStr(searchKey) + ', ';
sql := sql + QuotedStr(execSource) + ')';
uq.Close;
uq.SQL.Text := sql;
uq.Execute;
uq.Close;
Result := True;
end;
function SetDetailAuditEntry(uq: TUniQuery; const entryId, title, auditType: string; auditList: TStringList) : Boolean;
var
i: Integer;
sql: string;
begin
for i := 0 to auditList.Count - 1 do
begin
sql := 'insert into auditdetail values (';
sql := sql + entryId + ', ';
sql := sql + QuotedStr( auditList.Names[i] ) + ', ';
sql := sql + QuotedStr( '' ) + ', ';
sql := sql + QuotedStr( auditList.ValueFromIndex[i] ) + ', ';
sql := sql + auditType + ')';
uq.Close;
uq.SQL.Text := sql;
uq.Execute;
uq.Close;
end;
Result := True;
end;
function GetOfficerName( agency, officer: string; uq: TUniQuery ): string;
var
sql: string;
begin
if agency.IsEmpty or officer.IsEmpty then
Exit;
sql := 'select a.agency_id, p.agency, p.pf_nameid, pf_lname, pf_fname, pf_mi, pf_badge ';
sql := sql + 'from personnel p ';
sql := sql + 'join agencycodes a on a.agency = p.agency ';
sql := sql + 'where a.agency_id = ' + agency + ' and p.pf_nameid = ' + officer;
uq.Close;
uq.SQL.Text := sql;
uq.Open;
if uq.IsEmpty then
Result := agency + '-' + officer + ': not found'
else
begin
Result := uq.FieldByName('pf_lname').AsString + ', ' + uq.FieldByName('pf_fname').AsString;
Result := Result + ' ' + uq.FieldByName('pf_mi').AsString + ' (' + uq.FieldByName('pf_badge').AsString + ')';
end;
end;
function GetRiciOfficerName( agency, officer: string; uq: TUniQuery ): string;
var
sql: string;
begin
if agency.IsEmpty or officer.IsEmpty then
Exit;
sql := 'select * from rici.officer@rici_link where agency = ' + agency + ' and empno = ' + QuotedStr(officer);
uq.Close;
uq.SQL.Text := sql;
uq.Open;
if uq.IsEmpty then
Result := agency + '-' + officer + ': not found'
else
Result := uq.FieldByName('surname').AsString + ', ' + uq.FieldByName('given1').AsString + ' (' + uq.FieldByName('empno').AsString + ')';
end;
end.
program emiMobileServer;
uses
FastMM4,
System.SyncObjs,
System.SysUtils,
Vcl.StdCtrls,
IniFiles,
Vcl.Forms,
Api.Server.Module in 'Source\Api.Server.Module.pas' {ApiServerModule: TDataModule},
Main in 'Source\Main.pas' {FMain},
Common.Logging in 'Source\Common.Logging.pas',
Data in 'Source\Data.pas' {FData},
Api.Database in 'Source\Api.Database.pas' {ApiDatabaseModule: TDataModule},
Common.Middleware.Logging in 'Source\Common.Middleware.Logging.pas',
Common.Config in 'Source\Common.Config.pas',
Auth.Server.Module in 'Source\Auth.Server.Module.pas' {AuthServerModule: TDataModule},
Auth.Database in 'Source\Auth.Database.pas' {AuthDatabase: TDataModule},
uLibrary in 'Source\uLibrary.pas',
Auth.Service in 'Source\Auth.Service.pas',
Lookup.Service in 'Source\Lookup.Service.pas',
Auth.ServiceImpl in 'Source\Auth.ServiceImpl.pas',
Lookup.ServiceImpl in 'Source\Lookup.ServiceImpl.pas',
Twilio.Data.Module in 'Source\Twilio.Data.Module.pas' {TwilioDataModule: TDataModule},
App.Server.Module in 'Source\App.Server.Module.pas' {AppServerModule: TDataModule},
Common.Ini in 'Source\Common.Ini.pas';
type
TMemoLogAppender = class( TInterfacedObject, ILogAppender )
private
FLogLevel: Integer;
FLogMemo: TMemo;
FCriticalSection: TCriticalSection;
public
constructor Create(ALogLevel: Integer; ALogMemo: TMemo);
destructor Destroy; override;
procedure Send(logLevel: Integer; Log: ILog);
end;
TFileLogAppender = class( TInterfacedObject, ILogAppender )
private
FLogLevel: Integer;
FFilename: string;
FLogDirectory: string;
FCriticalSection: TCriticalSection;
function GetLogFilePath: string;
public
constructor Create(ALogLevel: Integer; AFilename: string);
destructor Destroy; override;
procedure Send(logLevel: Integer; Log: ILog);
end;
{ TMemoLogAppender }
constructor TMemoLogAppender.Create(ALogLevel: Integer; ALogMemo: TMemo);
begin
FLogLevel := ALogLevel;
FLogMemo := ALogMemo;
FCriticalSection := TCriticalSection.Create;
end;
destructor TMemoLogAppender.Destroy;
begin
FCriticalSection.Free;
inherited;
end;
procedure TMemoLogAppender.Send(logLevel: Integer; Log: ILog);
var
FormattedMessage: string;
LogTime: TDateTime;
LogMsg: string;
begin
FCriticalSection.Acquire;
try
LogTime := Now;
FormattedMessage := FormatDateTime('[yyyy-mm-dd HH:nn:ss.zzz]', LogTime);
LogMsg := Log.GetMessage;
if LogMsg.IsEmpty then
FormattedMessage := ''
else
FormattedMessage := FormattedMessage + '[' + IntToStr(logLevel) +'] ' + LogMsg;
if logLevel <= FLogLevel then
FLogMemo.Lines.Add( FormattedMessage );
finally
FCriticalSection.Release;
end;
end;
{ TFileLogAppender }
constructor TFileLogAppender.Create(ALogLevel: integer; AFilename: string);
var
iniFile: TIniFile;
fileNum: integer;
logsDir: string;
begin
FLogLevel := ALogLevel;
FCriticalSection := TCriticalSection.Create;
FLogDirectory := ExtractFilePath(Application.ExeName) + 'logs\';
if not DirectoryExists(FLogDirectory) then
CreateDir(FLogDirectory);
iniFile := TIniFile.Create( ChangeFileExt(Application.ExeName, '.ini') );
try
fileNum := iniFile.ReadInteger( 'Settings', 'LogFileNum', 0 );
FFilename := AFilename + Format( '%.4d', [fileNum] );
iniFile.WriteInteger( 'Settings', 'LogFileNum', fileNum + 1 );
finally
iniFile.Free;
end;
end;
destructor TFileLogAppender.Destroy;
begin
FCriticalSection.Free;
inherited;
end;
function TFileLogAppender.GetLogFilePath: string;
begin
Result := FLogDirectory + FFilename + '.log';
end;
procedure TFileLogAppender.Send(logLevel: integer; Log: ILog);
var
FormattedMessage: string;
LogFile: string;
LogTime: TDateTime;
LogMsg: string;
FLogFile: TextFile;
begin
FCriticalSection.Acquire;
try
LogTime := Now;
LogFile := GetLogFilePath;
formattedMessage := FormatDateTime('[yyyy-mm-dd HH:nn:ss.zzz]', logTime);
logMsg := Log.GetMessage;
if logMsg.IsEmpty then
formattedMessage := ''
else
formattedMessage := formattedMessage + '[' + IntToStr(logLevel) +'] ' + logMsg;
try
AssignFile( FLogFile, LogFile );
if FileExists(LogFile) then
Append( FLogFile )
else
ReWrite( FLogFile );
if logLevel <= FLogLevel then
WriteLn( FLogFile, FormattedMessage );
finally
CloseFile(FLogFile);
end;
finally
FCriticalSection.Release;
end;
end;
{$R *.res}
var
iniFile: TIniFile;
memoLogLevel: Integer;
fileLogLevel: Integer;
begin
ReportMemoryLeaksOnShutdown := True;
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TFMain, FMain);
iniFile := TIniFile.Create( ChangeFileExt(Application.ExeName, '.ini') );
try
memoLogLevel := IniFile.ReadInteger( 'Settings', 'MemoLogLevel', 3 );
fileLogLevel := IniFile.ReadInteger( 'Settings', 'FileLogLevel', 4 );
finally
iniFile.Free;
end;
Logger.AddAppender(TMemoLogAppender.Create( memoLogLevel, FMain.memoinfo ));
Logger.AddAppender(TFileLogAppender.Create( fileLogLevel, 'webPoliceReports' ));
Application.Run;
end.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{2A3028D9-BC39-4625-9BA5-0338012E2824}</ProjectGuid>
<ProjectVersion>20.2</ProjectVersion>
<FrameworkType>VCL</FrameworkType>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<Platform Condition="'$(Platform)'==''">Win32</Platform>
<TargetedPlatforms>3</TargetedPlatforms>
<AppType>Application</AppType>
<MainSource>emiMobileServer.dpr</MainSource>
<ProjectName Condition="'$(ProjectName)'==''">emiMobileServer</ProjectName>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
<Base_Win32>true</Base_Win32>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
<Base_Win64>true</Base_Win64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_1)'!=''">
<Cfg_1>true</Cfg_1>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
<Cfg_1_Win32>true</Cfg_1_Win32>
<CfgParent>Cfg_1</CfgParent>
<Cfg_1>true</Cfg_1>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win64)'!=''">
<Cfg_1_Win64>true</Cfg_1_Win64>
<CfgParent>Cfg_1</CfgParent>
<Cfg_1>true</Cfg_1>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_2)'!=''">
<Cfg_2>true</Cfg_2>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''">
<Cfg_2_Win32>true</Cfg_2_Win32>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win64)'!=''">
<Cfg_2_Win64>true</Cfg_2_Win64>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Base)'!=''">
<DCC_DcuOutput>.\$(Platform)\$(Config)</DCC_DcuOutput>
<DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
<DCC_E>false</DCC_E>
<DCC_N>false</DCC_N>
<DCC_S>false</DCC_S>
<DCC_F>false</DCC_F>
<DCC_K>false</DCC_K>
<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)</DCC_Namespace>
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
<Icns_MainIcns>$(BDS)\bin\delphi_PROJECTICNS.icns</Icns_MainIcns>
<SanitizedProjectName>emiMobileServer</SanitizedProjectName>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<DCC_UsePackage>gtFRExpD28;vclwinx;dacvcl280;FlexCel_Report;fmx;PKIEDB28;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;aurelius;TMSCloudPkgDEDXE14;FireDACCommonDriver;sparkle;appanalytics;IndyProtocols;vclx;TatukGIS_DK11_RX11_VCL;FMXTMSFNCMapsPkgDXE14;IndyIPClient;dbxcds;vcledge;dac280;frxe28;bindcompvclwinx;gtScaleRichVwExpD28;VCLTMSFNCUIPackPkgDXE14;gtXPressExpD28;unidac280;gtPDFkitD11ProP;FlexCel_Pdf;bindcompfmx;AdvChartDEDXE14;madBasic_;VCLTMSFNCDashboardPackPkgDXE14;SKIA_FlexCel_Core;TMSVCLUIPackPkgDXE14;inetdb;TatukGIS_DK11_RX11_FMX;AcroPDF;TatukGIS_DK11_RX11;FireDACSqliteDriver;DbxClientDriver;soapmidas;vclCryptoPressStreamD28;vclactnband;gtRBExpD28;fmxFireDAC;dbexpress;DBXMySQLDriver;VclSmp;inet;unidacvcl280;dacfmx280;SigPlus;fcstudiowin;vcltouch;fmxase;VCLTMSFNCMapsPkgDXE14;ipstudiowin;TMSWEBCorePkgLibDXE14;frx28;dbrtl;QRWRunDXE11_w64;TMSWEBCorePkgDXE14;fmxdae;addict4_d28;FlexCel_XlsAdapter;gtAdvGridExpD28;FireDACMSAccDriver;VCL_FlexCel_Core;CustomIPTransport;tmsbcl;ipstudiowinwordxp;gtDocEngD28;gtRaveExpD28;FMXTMSFNCDashboardPackPkgDXE14;vcldsnap;madExcept_;DBXInterBaseDriver;frxDB28;IndySystem;ipstudiowinclient;VCLTMSFNCCorePkgDXE14;vcldb;CamRemoteD11;FMXTMSFNCUIPackPkgDXE14;TMSCloudPkgDXE14;gtQRExpD28;VirtualTreesR;WPViewPDF_RT;FlexCel_Core;vclFireDAC;vquery280;madDisAsm_;bindcomp;FireDACCommon;FlexCel_Render;unidacfmx280;FMXTMSFNCCorePkgDXE14;IndyCore;RESTBackendComponents;gtACEExpD28;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;VCL_FlexCel_Components;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;adortl;TMSVCLUIPackPkgExDXE14;WPViewPDF_DT;TMSVCLUIPackPkgWizDXE14;gtHtmVwExpD28;AdvChartDXE14;gtRichVwExpD28;vclimg;FireDACPgDriver;FireDAC;inetdbxpress;TMSVCLUIPackPkgXlsDXE14;xmlrtl;tethering;PKIECtrl28;crcontrols280;bindcompvcl;dsnap;xdata;CloudService;fmxobj;bindcompvclsmp;addict4db_d28;CEF4Delphi;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage)</DCC_UsePackage>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
<VerInfo_Locale>1033</VerInfo_Locale>
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win64)'!=''">
<DCC_UsePackage>vclwinx;FlexCel_Report;fmx;PKIEDB28;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;FireDACCommonODBC;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;FMXTMSFNCMapsPkgDXE14;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;VCLTMSFNCUIPackPkgDXE14;FlexCel_Pdf;bindcompfmx;VCLTMSFNCDashboardPackPkgDXE14;TMSVCLUIPackPkgDXE14;inetdb;FireDACSqliteDriver;DbxClientDriver;soapmidas;vclactnband;fmxFireDAC;dbexpress;DBXMySQLDriver;VclSmp;inet;fcstudiowin;vcltouch;fmxase;VCLTMSFNCMapsPkgDXE14;ipstudiowin;dbrtl;QRWRunDXE11_w64;fmxdae;FlexCel_XlsAdapter;FireDACMSAccDriver;VCL_FlexCel_Core;CustomIPTransport;vcldsnap;DBXInterBaseDriver;IndySystem;ipstudiowinclient;VCLTMSFNCCorePkgDXE14;vcldb;CamRemoteD11;FMXTMSFNCUIPackPkgDXE14;VirtualTreesR;WPViewPDF_RT;FlexCel_Core;vclFireDAC;bindcomp;FireDACCommon;FlexCel_Render;FMXTMSFNCCorePkgDXE14;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;VCL_FlexCel_Components;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;adortl;TMSVCLUIPackPkgExDXE14;AdvChartDXE14;vclimg;FireDACPgDriver;FireDAC;inetdbxpress;TMSVCLUIPackPkgXlsDXE14;xmlrtl;tethering;PKIECtrl28;bindcompvcl;dsnap;CloudService;fmxobj;bindcompvclsmp;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage)</DCC_UsePackage>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)</DCC_Namespace>
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
<VerInfo_Locale>1033</VerInfo_Locale>
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1)'!=''">
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
<DCC_DebugDCUs>true</DCC_DebugDCUs>
<DCC_Optimize>false</DCC_Optimize>
<DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
<DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
<DCC_RemoteDebug>true</DCC_RemoteDebug>
<DCC_IntegerOverflowCheck>true</DCC_IntegerOverflowCheck>
<DCC_RangeChecking>true</DCC_RangeChecking>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<DCC_ExeOutput>.</DCC_ExeOutput>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>3</VerInfo_Release>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.3.0;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>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
<DCC_DebugInformation>0</DCC_DebugInformation>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<DCC_UnitSearchPath>C:\RADTOOLS\FastMM4;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win64)'!=''">
<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="$(MainSource)">
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="Source\Api.Server.Module.pas">
<Form>ApiServerModule</Form>
<DesignClass>TDataModule</DesignClass>
</DCCReference>
<DCCReference Include="Source\Main.pas">
<Form>FMain</Form>
</DCCReference>
<DCCReference Include="Source\Common.Logging.pas"/>
<DCCReference Include="Source\Data.pas">
<Form>FData</Form>
</DCCReference>
<DCCReference Include="Source\Api.Database.pas">
<Form>ApiDatabaseModule</Form>
<DesignClass>TDataModule</DesignClass>
</DCCReference>
<DCCReference Include="Source\Common.Middleware.Logging.pas"/>
<DCCReference Include="Source\Common.Config.pas"/>
<DCCReference Include="Source\Auth.Server.Module.pas">
<Form>AuthServerModule</Form>
<DesignClass>TDataModule</DesignClass>
</DCCReference>
<DCCReference Include="Source\Auth.Database.pas">
<Form>AuthDatabase</Form>
<DesignClass>TDataModule</DesignClass>
</DCCReference>
<DCCReference Include="Source\uLibrary.pas"/>
<DCCReference Include="Source\Auth.Service.pas"/>
<DCCReference Include="Source\Lookup.Service.pas"/>
<DCCReference Include="Source\Auth.ServiceImpl.pas"/>
<DCCReference Include="Source\Lookup.ServiceImpl.pas"/>
<DCCReference Include="Source\Twilio.Data.Module.pas">
<Form>TwilioDataModule</Form>
<DesignClass>TDataModule</DesignClass>
</DCCReference>
<DCCReference Include="Source\App.Server.Module.pas">
<Form>AppServerModule</Form>
<DesignClass>TDataModule</DesignClass>
</DCCReference>
<DCCReference Include="Source\Common.Ini.pas"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
<BuildConfiguration Include="Debug">
<Key>Cfg_1</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
<BuildConfiguration Include="Release">
<Key>Cfg_2</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
</ItemGroup>
<ProjectExtensions>
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
<Borland.ProjectType>Application</Borland.ProjectType>
<BorlandProject>
<Delphi.Personality>
<Source>
<Source Name="MainSource">emiMobileServer.dpr</Source>
</Source>
<Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k290.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp290.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k290.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dclofficexp290.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
</Excluded_Packages>
</Delphi.Personality>
<Deployment Version="5">
<DeployFile LocalName="emiMobileServer.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>emiMobileServer.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="envoyCallsServer.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployClass Name="AdditionalDebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidFileProvider">
<Platform Name="Android">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiFile">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiv7aFile">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeMipsFile">
<Platform Name="Android">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidServiceOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidServiceOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDef">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDefV21">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStyles">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV21">
<Platform Name="Android">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV31">
<Platform Name="Android">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconBackground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconForeground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconMonochrome">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconV33">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Colors">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_ColorsDark">
<Platform Name="Android">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_DefaultAppIcon">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon144">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon192">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon24">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage426">
<Platform Name="Android">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage470">
<Platform Name="Android">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage640">
<Platform Name="Android">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage960">
<Platform Name="Android">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Strings">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedNotificationIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplash">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashDark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31Dark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyFramework">
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyModule">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="DependencyPackage">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Name="File">
<Platform Name="Android">
<Operation>0</Operation>
</Platform>
<Platform Name="Android64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>0</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android">
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXDebug">
<Platform Name="OSX64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXEntitlements">
<Platform Name="OSX32">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXInfoPList">
<Platform Name="OSX32">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Linux64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectUWPManifest">
<Platform Name="Win32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64x">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSDeviceDebug">
<Platform Name="iOSDevice32">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSEntitlements">
<Platform Name="iOSDevice32">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSInfoPList">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSLaunchScreen">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
<Operation>64</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
<Operation>64</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSResource">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo150">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo44">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon152">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon167">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_SpotLight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon180">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification60">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting87">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSSimARM64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="OSX64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="OSXARM64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64x" Name="$(PROJECTNAME)"/>
</Deployment>
<Platforms>
<Platform value="Win32">True</Platform>
<Platform value="Win64">True</Platform>
</Platforms>
</BorlandProject>
<ProjectFileVersion>12</ProjectFileVersion>
</ProjectExtensions>
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
<Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
<Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
</Project>
[Settings]
LogFileNum=409
webClientVersion=0.1.0
TwilioUpdateTime=1
[Database]
Server=192.168.102.130
--Server=192.168.198.129
Database=envoy_db
Username=postgres
Password=postgreSQL
--Password=emsys01
[Twilio]
AccountSID=AC37aeef9c36a2cccbaecbadafc172b2ff
AuthHeader=Basic QUMzN2FlZWY5YzM2YTJjY2NiYWVjYmFkYWZjMTcyYjJmZjo5NzM5OTAwYTgyZmRlNjVlMzI2ODFmZjVmMmI5ZGZjZgo=
[ExpressSkins]
Version=1.0.0
Enabled=1
ShowNotifications=1
Kind=2
NativeStyle=1
ScrollbarMode=0
ScrollMode=0
SkinName=WXICompact
RenderMode=0
TouchMode=0
FormCorners=0
SkinPaletteName=Default
ShowFormShadow=2
UseSkins=1
UseImageSet=0
UseSkinsInPopupMenus=1
LightStyleMode=3
UseGlobalSkin=1
dxSkinWXI=1
dxSkinTheBezier=1
dxSkinOffice2019Colorful=1
dxSkinOffice2019Black=1
dxSkinOffice2019DarkGray=1
dxSkinOffice2019White=1
dxSkinBasic=1
dxSkinBlack=0
dxSkinBlue=0
dxSkinBlueprint=0
dxSkinCaramel=0
dxSkinCoffee=0
dxSkinDarkroom=0
dxSkinDarkSide=0
dxSkinDevExpressDarkStyle=0
dxSkinDevExpressStyle=0
dxSkinFoggy=0
dxSkinGlassOceans=0
dxSkinHighContrast=0
dxSkiniMaginary=0
dxSkinLilian=0
dxSkinLiquidSky=0
dxSkinLondonLiquidSky=0
dxSkinMcSkin=0
dxSkinMetropolis=0
dxSkinMetropolisDark=0
dxSkinMoneyTwins=0
dxSkinOffice2007Black=0
dxSkinOffice2007Blue=0
dxSkinOffice2007Green=0
dxSkinOffice2007Pink=0
dxSkinOffice2007Silver=0
dxSkinOffice2010Black=0
dxSkinOffice2010Blue=0
dxSkinOffice2010Silver=0
dxSkinOffice2013DarkGray=0
dxSkinOffice2013LightGray=0
dxSkinOffice2013White=0
dxSkinOffice2016Colorful=0
dxSkinOffice2016Dark=0
dxSkinPumpkin=0
dxSkinSeven=0
dxSkinSevenClassic=0
dxSkinSharp=0
dxSkinSharpPlus=0
dxSkinSilver=0
dxSkinSpringtime=0
dxSkinStardust=0
dxSkinSummer2008=0
dxSkinTheAsphaltWorld=0
dxSkinValentine=0
dxSkinVisualStudio2013Blue=0
dxSkinVisualStudio2013Dark=0
dxSkinVisualStudio2013Light=0
dxSkinVS2010=0
dxSkinWhiteprint=0
dxSkinXmas2008Blue=0
unit App.Config;
interface
uses
JS,
XData.Web.Connection,
XData.Web.Request,
XData.Web.Response;
type
TAppConfig = class
private
FAuthUrl: string;
FApiUrl: string;
FAppUrl: string;
public
constructor Create;
property AuthUrl: string read FAuthUrl write FAuthUrl;
property ApiUrl: string read FApiUrl write FApiUrl;
property AppUrl: string read FAppUrl write FAppUrl;
end;
TConfigLoadedProc = reference to procedure(Config: TAppConfig);
procedure LoadConfig(LoadProc: TConfigLoadedProc);
implementation
procedure LoadConfig(LoadProc: TConfigLoadedProc);
procedure OnSuccess(Response: IHttpResponse);
var
Obj: TJSObject;
Config: TAppConfig;
begin
Config := TAppConfig.Create;
try
if Response.StatusCode = 200 then
begin
Obj := TJSObject(TJSJSON.parse(Response.ContentAsText));
if JS.toString(Obj['AuthUrl']) <> '' then
Config.AuthUrl := JS.toString(Obj['AuthUrl']);
if JS.toString(Obj['ApiUrl']) <> '' then
Config.ApiUrl := JS.toString(Obj['ApiUrl']);
if JS.toString(Obj['AppUrl']) <> '' then
Config.AppUrl := JS.toString(Obj['AppUrl']);
end;
finally
LoadProc(Config);
Config.Free;
end;
end;
procedure OnError;
var
Config: TAppConfig;
begin
Config := TAppConfig.Create;
try
LoadProc(Config);
finally
Config.Free;
end;
end;
var
Conn: TXDataWebConnection;
begin
Conn := TXDataWebConnection.Create(nil);
try
Conn.SendRequest(THttpRequest.Create('config/config.json'), @OnSuccess, @OnError);
finally
Conn.Free;
end;
end;
{ TAppConfig }
constructor TAppConfig.Create;
begin
FAuthUrl := '';
FApiUrl := '';
FAppUrl := '';
end;
end.
unit App.Types;
interface
uses
Bcl.Rtti.Common;
type
TProc = reference to procedure;
TSuccessProc = reference to procedure;
TLogoutProc = reference to procedure(AMessage: string = '');
TUnauthorizedAccessProc = reference to procedure(AMessage: string);
TVersionCheckCallback = reference to procedure(Success: Boolean; ErrorMessage: string);
TListProc = reference to procedure;
TSelectProc = reference to procedure(AParam: string);
TSelectProc2 = reference to procedure(AParam: string; BParam: string);
TSelectProc3 = reference to procedure(AParam: string; BParam: string; CParam: Boolean);
TSelectProc4 = reference to procedure(AParam: string; BParam: string; CParam: string; DParam: Boolean);
TSearchProc = reference to procedure(AParam: string; BParam: string; CParam: Integer; DParam: Boolean);
TReportProc = reference to procedure(AParam: string);
implementation
end.
unit Auth.Service;
interface
uses
SysUtils, Web, JS,
XData.Web.Client;
const
TOKEN_NAME = 'WEBEMIMOBILE_TOKEN';
type
TOnLoginSuccess = reference to procedure;
TOnLoginError = reference to procedure(AMsg: string);
TOnProfileSuccess = reference to procedure;
TOnProfileError = reference to procedure(AMsg: string);
TAuthService = class
private
FClient: TXDataWebClient;
procedure SetToken(AToken: string);
procedure DeleteToken;
public
constructor Create; reintroduce;
destructor Destroy; override;
procedure Login(AUser, APassword, AAgency: string; ASuccess: TOnLoginSuccess;
AError: TOnLoginError);
procedure Logout;
function GetToken: string;
function Authenticated: Boolean;
function TokenExpirationDate: TDateTime;
function TokenExpired: Boolean;
function TokenPayload: JS.TJSObject;
end;
TJwtHelper = class
private
class function HasExpirationDate(AToken: string): Boolean;
public
class function TokenExpirationDate(AToken: string): TJSDate;
class function TokenExpired(AToken: string): Boolean;
class function DecodePayload(AToken: string): string;
end;
function AuthService: TAuthService;
implementation
uses
ConnectionModule;
var
_AuthService: TAuthService;
function AuthService: TAuthService;
begin
if not Assigned(_AuthService) then
begin
_AuthService := TAuthService.Create;
end;
Result := _AuthService;
end;
{ TAuthService }
function TAuthService.Authenticated: Boolean;
begin
Result := not isNull(window.localStorage.getItem(TOKEN_NAME)) and
(window.localStorage.getItem(TOKEN_NAME) <> '');
end;
constructor TAuthService.Create;
begin
FClient := TXDataWebClient.Create(nil);
FClient.Connection := DMConnection.AuthConnection;
end;
procedure TAuthService.DeleteToken;
begin
window.localStorage.removeItem(TOKEN_NAME);
end;
destructor TAuthService.Destroy;
begin
FClient.Free;
inherited;
end;
function TAuthService.GetToken: string;
begin
Result := window.localStorage.getItem(TOKEN_NAME);
end;
procedure TAuthService.Login(AUser, APassword, AAgency: string; ASuccess: TOnLoginSuccess;
AError: TOnLoginError);
procedure OnLoad(Response: TXDataClientResponse);
var
Token: JS.TJSObject;
begin
Token := JS.TJSObject(Response.Result);
SetToken(JS.toString(Token.Properties['value']));
ASuccess;
end;
procedure OnError(Error: TXDataClientError);
begin
AError(Format('%s: %s', [Error.ErrorCode, Error.ErrorMessage]));
end;
begin
if (AUser = '') or (APassword = '') or (AAgency = '') then
begin
AError('Please enter a username, password, and agency');
Exit;
end;
FClient.RawInvoke(
'IAuthService.Login', [AUser, APassword, AAgency],
@OnLoad, @OnError
);
end;
procedure TAuthService.Logout;
begin
DeleteToken;
end;
procedure TAuthService.SetToken(AToken: string);
begin
window.localStorage.setItem(TOKEN_NAME, AToken);
end;
function TAuthService.TokenExpirationDate: TDateTime;
var
ExpirationDate: TJSDate;
begin
if not Authenticated then
Exit(Now);
ExpirationDate := TJwtHelper.TokenExpirationDate(GetToken);
Result := EncodeDate(
ExpirationDate.FullYear,
ExpirationDate.Month + 1,
ExpirationDate.Date
) +
EncodeTime(
ExpirationDate.Hours,
ExpirationDate.Minutes,
ExpirationDate.Seconds,
0
);
end;
function TAuthService.TokenExpired: Boolean;
begin
if not Authenticated then
Exit(False);
Result := TJwtHelper.TokenExpired(GetToken);
end;
function TAuthService.TokenPayload: JS.TJSObject;
begin
if not Authenticated then
Exit(nil);
Result := TJSObject(TJSJSON.parse(TJwtHelper.DecodePayload(GetToken)));
end;
{ TJwtHelper }
class function TJwtHelper.DecodePayload(AToken: string): string;
begin
if Trim(AToken) = '' then
Exit('');
Result := '';
asm
var Token = AToken.split('.');
if (Token.length = 3) {
Result = Token[1];
Result = atob(Result);
}
end;
end;
class function TJwtHelper.HasExpirationDate(AToken: string): Boolean;
var
Payload: string;
Obj: TJSObject;
begin
Payload := DecodePayload(AToken);
Obj := TJSObject(TJSJSON.parse(Payload));
Result := Obj.hasOwnProperty('exp');
end;
class function TJwtHelper.TokenExpirationDate(AToken: string): TJSDate;
var
Payload: string;
Obj: TJSObject;
Epoch: NativeInt;
begin
if not HasExpirationDate(AToken) then
raise Exception.Create('Token has no expiration date');
Payload := DecodePayload(AToken);
Obj := TJSObject(TJSJSON.parse(Payload));
Epoch := toInteger(Obj.Properties['exp']);
Result := TJSDate.New(Epoch * 1000);
end;
class function TJwtHelper.TokenExpired(AToken: string): Boolean;
begin
if not HasExpirationDate(AToken) then
Exit(False);
Result := TJSDate.now > toInteger(TokenExpirationDate(AToken).valueOf);
end;
end.
object DMConnection: TDMConnection
Height = 302
Width = 266
object ApiConnection: TXDataWebConnection
OnError = ApiConnectionError
OnRequest = ApiConnectionRequest
OnResponse = ApiConnectionResponse
Left = 112
Top = 132
end
object AuthConnection: TXDataWebConnection
OnError = AuthConnectionError
Left = 110
Top = 68
end
object XDataWebClient1: TXDataWebClient
Connection = AuthConnection
Left = 112
Top = 200
end
end
unit ConnectionModule;
interface
uses
System.SysUtils, System.Classes, WEBLib.Modules, XData.Web.Connection,
App.Types, App.Config, XData.Web.Client;
type
TDMConnection = class(TWebDataModule)
ApiConnection: TXDataWebConnection;
AuthConnection: TXDataWebConnection;
XDataWebClient1: TXDataWebClient;
procedure ApiConnectionError(Error: TXDataWebConnectionError);
procedure ApiConnectionRequest(Args: TXDataWebConnectionRequest);
procedure ApiConnectionResponse(Args: TXDataWebConnectionResponse);
procedure AuthConnectionError(Error: TXDataWebConnectionError);
private
FUnauthorizedAccessProc: TUnauthorizedAccessProc;
public
const clientVersion = '0.1.0';
procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure SetClientConfig(Callback: TVersionCheckCallback);
end;
var
DMConnection: TDMConnection;
implementation
uses
JS, Web,
XData.Web.Request,
XData.Web.Response,
Auth.Service,
View.ErrorPage;
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TDMConnection.ApiConnectionError(Error: TXDataWebConnectionError);
begin
TFViewErrorPage.DisplayConnectionError(Error);
end;
procedure TDMConnection.ApiConnectionRequest(Args: TXDataWebConnectionRequest);
begin
if AuthService.Authenticated then
Args.Request.Headers.SetValue('Authorization', 'Bearer ' + AuthService.GetToken);
end;
procedure TDMConnection.ApiConnectionResponse(
Args: TXDataWebConnectionResponse);
begin
if Args.Response.StatusCode = 401 then
FUnauthorizedAccessProc(Format('%d: %s',[Args.Response.StatusCode, Args.Response.ContentAsText]));
end;
procedure TDMConnection.AuthConnectionError(Error: TXDataWebConnectionError);
begin
TFViewErrorPage.DisplayConnectionError(Error);
end;
procedure TDMConnection.InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
procedure ConfigLoaded(Config: TAppConfig);
begin
if Config.AuthUrl <> '' then
AuthConnection.URL := Config.AuthUrl;
if Config.ApiUrl <> '' then
ApiConnection.URL := Config.ApiUrl;
AuthConnection.Open(SuccessProc);
end;
begin
FUnauthorizedAccessProc := UnauthorizedAccessProc;
LoadConfig(@ConfigLoaded);
end;
procedure TDMConnection.SetClientConfig(Callback: TVersionCheckCallback);
begin
XDataWebClient1.Connection := AuthConnection;
XDataWebClient1.RawInvoke('IAuthService.VerifyVersion', [clientVersion],
procedure(Response: TXDataClientResponse)
var
jsonResult: TJSObject;
error: string;
begin
jsonResult := TJSObject(Response.Result);
if jsonResult.HasOwnProperty('error') then
error := string(jsonResult['error'])
else
error := '';
if error <> '' then
Callback(False, error)
else
Callback(True, '');
end);
end;
end.
{
"type": "FeatureCollection",
"name": "BFDDistrict",
"crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } },
"features": [
{ "type": "Feature", "properties": { "SBE4_ID": 0, "NAME": "District A", "FROMLEFT": 0, "TOLEFT": 0, "FROMRIGHT": 0, "TORIGHT": 0, "DIRECTION": null, "STREET": null, "ST_TYPE": null, "ST_PREFIX": null, "ST_SUFFIX": null, "CITY_LEFT": null, "CITY_RIGHT": null, "LO_X_PRE": null, "LO_X_NAME": null, "LO_X_TYPE": null, "LO_X_SUF": null, "HI_X_PRE": null, "HI_X_NAME": null, "HI_X_TYPE": null, "HI_X_SUF": null, "LHS": null, "RHS": null, "BDY_LEFT": null, "BDY_RIGHT": null, "ST_CODE": null, "RECNUM_L": 0, "RECNUM_R": 0 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -78.838580621137581, 42.832640624242444 ], [ -78.838579379349227, 42.832637828223341 ], [ -78.853769883224203, 42.832256243942119 ], [ -78.85371958445721, 42.832194871720155 ], [ -78.853733424580213, 42.83219689697404 ], [ -78.860055694936989, 42.831634192917498 ], [ -78.86013734228284, 42.832269841506786 ], [ -78.859896026339129, 42.832680803438883 ], [ -78.860331112959102, 42.833777534545568 ], [ -78.86034975462735, 42.834904906086862 ], [ -78.85768545019738, 42.835848144550688 ], [ -78.858114869151507, 42.837605053389701 ], [ -78.857536532421037, 42.838545513856914 ], [ -78.858096352651074, 42.839687337785485 ], [ -78.861864583480241, 42.838698386233297 ], [ -78.86205975340269, 42.838902003038619 ], [ -78.862692380919711, 42.838804946201897 ], [ -78.862728132922896, 42.839242703902777 ], [ -78.859568800903887, 42.840181676978965 ], [ -78.859407395065432, 42.841296969378085 ], [ -78.865712578238899, 42.852676330854607 ], [ -78.87162174026551, 42.851292456357271 ], [ -78.872016282892517, 42.851882361006176 ], [ -78.872097013700895, 42.852401028660957 ], [ -78.868731884242095, 42.853274149621917 ], [ -78.869204890923257, 42.85390708456859 ], [ -78.872490649359548, 42.852817958505078 ], [ -78.876741180919694, 42.858996499122377 ], [ -78.874396356987731, 42.859770389677344 ], [ -78.873619753263739, 42.859660821705866 ], [ -78.87399285072668, 42.859861633865819 ], [ -78.874543180109029, 42.860234969333924 ], [ -78.875014936030652, 42.860593987099257 ], [ -78.8750512940248, 42.85997418590776 ], [ -78.876811140388725, 42.859277953228293 ], [ -78.882510814894758, 42.867189761467436 ], [ -78.878932430206248, 42.868654634149287 ], [ -78.879480450569972, 42.869349562574541 ], [ -78.883495141721355, 42.868167220890896 ], [ -78.883750388309764, 42.868756350747567 ], [ -78.880095121304095, 42.870078422117274 ], [ -78.880646408234384, 42.870624672382412 ], [ -78.884421489903062, 42.869274555253369 ], [ -78.890271939124617, 42.87558582502659 ], [ -78.887965695107951, 42.876802504379924 ], [ -78.890098621163943, 42.876205913975724 ], [ -78.890590430583615, 42.876593705838609 ], [ -78.890916063781987, 42.877700346284939 ], [ -78.883205768958902, 42.877189774239035 ], [ -78.882993797488041, 42.877925279047915 ], [ -78.882327302445603, 42.877883805165943 ], [ -78.880207061295906, 42.877067857368601 ], [ -78.87805895026365, 42.874522552122691 ], [ -78.877556716095029, 42.874857610217347 ], [ -78.875958954885036, 42.873857441685928 ], [ -78.875159693178475, 42.873656461201733 ], [ -78.873157675465777, 42.873156643168144 ], [ -78.87265780839121, 42.873155108530398 ], [ -78.872054850493441, 42.872990241069282 ], [ -78.871559627886398, 42.872856077792747 ], [ -78.871258478968869, 42.873855583344678 ], [ -78.870956750411864, 42.874756419191691 ], [ -78.87055656243021, 42.876156833702851 ], [ -78.870258536657332, 42.877056203810682 ], [ -78.870259928280817, 42.877356655895099 ], [ -78.869957241093033, 42.878057077538543 ], [ -78.86975904878048, 42.87875723695614 ], [ -78.869556577509613, 42.879355910320399 ], [ -78.869387780475122, 42.879951837575966 ], [ -78.869358376966289, 42.88005615122713 ], [ -78.869159247170458, 42.880555980676171 ], [ -78.86905751475463, 42.88115722766284 ], [ -78.86885745593024, 42.881456810125457 ], [ -78.864656316400058, 42.880655101515977 ], [ -78.859757939693296, 42.879956310487458 ], [ -78.857356707881706, 42.879457262118258 ], [ -78.854257382550315, 42.87885554952485 ], [ -78.851756334480143, 42.8785568410702 ], [ -78.850856794926287, 42.878457395030267 ], [ -78.848757851920865, 42.877957459459736 ], [ -78.847059027846299, 42.877656785921261 ], [ -78.84545768790359, 42.877457442727305 ], [ -78.8440576714939, 42.87725761889191 ], [ -78.842556244378372, 42.876857758658176 ], [ -78.841757388131555, 42.876755214333393 ], [ -78.841556799502669, 42.876955998075758 ], [ -78.839957561167893, 42.87815595557732 ], [ -78.839358996383396, 42.878656627987034 ], [ -78.839156194113883, 42.879255275929467 ], [ -78.83835691774442, 42.879056837484171 ], [ -78.837456569994913, 42.878756929372337 ], [ -78.836758064766627, 42.878555457288982 ], [ -78.835756447512608, 42.878157034814144 ], [ -78.834855713376044, 42.87775563809948 ], [ -78.833955401951215, 42.877455839483304 ], [ -78.833058755274735, 42.877155998336157 ], [ -78.832856931977261, 42.877057576053375 ], [ -78.829257334160488, 42.878157567817688 ], [ -78.828657889567353, 42.878457937354909 ], [ -78.8267574133816, 42.880956240808032 ], [ -78.825555243453209, 42.882657282618005 ], [ -78.824056066742116, 42.884759488306194 ], [ -78.816657238717056, 42.884857093087646 ], [ -78.816056162884067, 42.884756847493165 ], [ -78.815257702748966, 42.884755696219727 ], [ -78.814156862880751, 42.884755313966757 ], [ -78.809555878954654, 42.884657430116576 ], [ -78.808555783918976, 42.884656712606812 ], [ -78.807556279992795, 42.884757426604551 ], [ -78.806257466997707, 42.884655722425009 ], [ -78.805055921622497, 42.884655256607296 ], [ -78.803757485744924, 42.88465776349981 ], [ -78.803455133300602, 42.884655628692954 ], [ -78.802455159595638, 42.884657546665778 ], [ -78.801354855037332, 42.884755737814196 ], [ -78.800855880420585, 42.885055770186042 ], [ -78.800455932255147, 42.884856198273617 ], [ -78.799555682779072, 42.884556101935864 ], [ -78.799654572990747, 42.877657757946196 ], [ -78.799856701247919, 42.874655534437835 ], [ -78.799857449623246, 42.871656406417138 ], [ -78.799635042492156, 42.871657704791218 ], [ -78.799608714716484, 42.871083266097898 ], [ -78.799283115590782, 42.871049664274494 ], [ -78.799454480449356, 42.868457790234544 ], [ -78.799407204649867, 42.867320014669311 ], [ -78.800073311118354, 42.866957357716529 ], [ -78.800044716534117, 42.864412395692526 ], [ -78.800371824261092, 42.864428623694991 ], [ -78.800013598039598, 42.864409573490128 ], [ -78.799945757015792, 42.86264032885358 ], [ -78.799955478268046, 42.861355618675994 ], [ -78.800087783614686, 42.854413335610161 ], [ -78.800402549685344, 42.847371835462368 ], [ -78.800465911964508, 42.845722712118793 ], [ -78.799110043197146, 42.84636759509597 ], [ -78.79856968562261, 42.845972008220215 ], [ -78.796901482216654, 42.845326143395887 ], [ -78.798679696151751, 42.842891687094514 ], [ -78.79742912862821, 42.842044107335376 ], [ -78.796593525881221, 42.841252458318557 ], [ -78.796593277967546, 42.841180348947155 ], [ -78.79561273987504, 42.841001923524573 ], [ -78.796653480378495, 42.838356812365419 ], [ -78.7973547812921, 42.83845708334983 ], [ -78.798455419987192, 42.838658042838404 ], [ -78.799854997104532, 42.83905596076432 ], [ -78.800114269943663, 42.839298827327589 ], [ -78.800121452152496, 42.841353971681812 ], [ -78.800954430165106, 42.841352391840893 ], [ -78.80105438597424, 42.839756154286384 ], [ -78.800954249264223, 42.838856343380748 ], [ -78.800955176875547, 42.838057919673417 ], [ -78.801056798578884, 42.837256505681758 ], [ -78.800956591162702, 42.836356612199886 ], [ -78.800957162531574, 42.835456609345087 ], [ -78.800856332175471, 42.834356493668217 ], [ -78.800856905099536, 42.833456490495223 ], [ -78.800854452246867, 42.832756825752362 ], [ -78.800854678804555, 42.831757987354088 ], [ -78.801656174913674, 42.831756462252542 ], [ -78.805656489050051, 42.831757053110763 ], [ -78.807255799586315, 42.831756626304951 ], [ -78.808654312733736, 42.831858151928195 ], [ -78.809556107062647, 42.831757511090629 ], [ -78.812457260200006, 42.83195756431401 ], [ -78.813556600253534, 42.831856606340295 ], [ -78.814854467616954, 42.831958231069144 ], [ -78.815254119976032, 42.832157755265499 ], [ -78.815357794453462, 42.831957212445182 ], [ -78.817654269166127, 42.831958024622892 ], [ -78.818556087729135, 42.831857422619095 ], [ -78.819555557802161, 42.831956835203073 ], [ -78.820756081390527, 42.831957151448407 ], [ -78.82385750949274, 42.831857222247173 ], [ -78.823955379359887, 42.831157347180735 ], [ -78.823956394179447, 42.830457675485583 ], [ -78.823857834211026, 42.829055689472924 ], [ -78.8239569963159, 42.82775761128412 ], [ -78.823956878334343, 42.826756138706585 ], [ -78.824270151467147, 42.826743955649128 ], [ -78.825254871013783, 42.826740809659682 ], [ -78.832859733663142, 42.826702349425297 ], [ -78.832970726887567, 42.825928159785164 ], [ -78.833854758436914, 42.825955589258797 ], [ -78.837158375362606, 42.825915168567825 ], [ -78.837726797591174, 42.829447708027431 ], [ -78.838478504947247, 42.832640028620709 ], [ -78.838580621137581, 42.832640624242444 ] ] ] ] } },
{ "type": "Feature", "properties": { "SBE4_ID": 1, "NAME": "District E", "FROMLEFT": 0, "TOLEFT": 0, "FROMRIGHT": 0, "TORIGHT": 0, "DIRECTION": null, "STREET": null, "ST_TYPE": null, "ST_PREFIX": null, "ST_SUFFIX": null, "CITY_LEFT": null, "CITY_RIGHT": null, "LO_X_PRE": null, "LO_X_NAME": null, "LO_X_TYPE": null, "LO_X_SUF": null, "HI_X_PRE": null, "HI_X_NAME": null, "HI_X_TYPE": null, "HI_X_SUF": null, "LHS": null, "RHS": null, "BDY_LEFT": null, "BDY_RIGHT": null, "ST_CODE": null, "RECNUM_L": 0, "RECNUM_R": 0 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -78.83135838731252, 42.946055876655677 ], [ -78.830457761013733, 42.946856231049658 ], [ -78.829957986127056, 42.947054948618003 ], [ -78.836858858528231, 42.958456982912189 ], [ -78.83595805615208, 42.9583548178936 ], [ -78.831759186240831, 42.95835580296653 ], [ -78.83115725851971, 42.958255557825574 ], [ -78.829958131354147, 42.958255431111702 ], [ -78.828856385827848, 42.958356577970008 ], [ -78.82765685495157, 42.958254878182295 ], [ -78.826158798016664, 42.958255266970681 ], [ -78.825355700985639, 42.958256967700528 ], [ -78.824656694550697, 42.958156975888137 ], [ -78.822157505617739, 42.958156839199646 ], [ -78.820756503617915, 42.958156954762991 ], [ -78.819557377904218, 42.958156719090624 ], [ -78.818656729926502, 42.958057024236993 ], [ -78.817356630933958, 42.958057000472493 ], [ -78.815756379360749, 42.958655648454581 ], [ -78.814655722805838, 42.959055735912756 ], [ -78.814256710692703, 42.959256842446351 ], [ -78.813656408143274, 42.959557158732345 ], [ -78.812055603843234, 42.960056977439621 ], [ -78.810858317851512, 42.960556082171166 ], [ -78.80855649993903, 42.961455128370673 ], [ -78.808556641406597, 42.95905538611283 ], [ -78.807555691674963, 42.958955334769527 ], [ -78.8075556846577, 42.956555317351551 ], [ -78.803555667103964, 42.956555344613861 ], [ -78.803555710222753, 42.954555352043627 ], [ -78.808655616610892, 42.954655346219724 ], [ -78.808755637596704, 42.952255322156944 ], [ -78.810356727315778, 42.952155355087633 ], [ -78.810955498629298, 42.949456959125619 ], [ -78.803256895252787, 42.949356848211345 ], [ -78.798260549765104, 42.94926379980847 ], [ -78.798454639809037, 42.948655260106889 ], [ -78.798550139049141, 42.945208809996167 ], [ -78.798557412588266, 42.943856009896109 ], [ -78.798554783338417, 42.94095575828883 ], [ -78.798757866985625, 42.938156613636551 ], [ -78.798756824676403, 42.937857507547143 ], [ -78.798855410796321, 42.937256444303237 ], [ -78.798854712408485, 42.937056116455338 ], [ -78.798856706061073, 42.936556785168193 ], [ -78.798756895109364, 42.935755765988517 ], [ -78.798856604707169, 42.93545649758471 ], [ -78.798855208018779, 42.935055841755414 ], [ -78.798856159105142, 42.934257431455606 ], [ -78.798954580601659, 42.933557506750603 ], [ -78.798956563688861, 42.933055376437686 ], [ -78.798955520699749, 42.932756297523177 ], [ -78.798954477630886, 42.932457191154541 ], [ -78.798956609322019, 42.929856033293724 ], [ -78.799155762886727, 42.922757308165004 ], [ -78.799055197727725, 42.92175595902475 ], [ -78.798956070218281, 42.919056125478022 ], [ -78.799055128618349, 42.918556609769276 ], [ -78.799454283045222, 42.918457105177296 ], [ -78.800054256740182, 42.918156889411847 ], [ -78.800456783714296, 42.917955796927018 ], [ -78.801057105067628, 42.917757125798815 ], [ -78.80155672747378, 42.917555844750758 ], [ -78.802056207188116, 42.917357360535163 ], [ -78.802454989226121, 42.917156268155487 ], [ -78.80345780545899, 42.916756401201226 ], [ -78.803856903703789, 42.916656853904762 ], [ -78.804755137489181, 42.91625725926454 ], [ -78.805657092775974, 42.915857650327247 ], [ -78.806954527440098, 42.915355711896147 ], [ -78.807755983281623, 42.91505503448947 ], [ -78.808057645039966, 42.914856911604382 ], [ -78.808456723670673, 42.914757375724776 ], [ -78.809456090238214, 42.914456297072078 ], [ -78.810056088429604, 42.914156028667882 ], [ -78.810555550332339, 42.913957424994436 ], [ -78.811357082867673, 42.913656831921934 ], [ -78.811856489182247, 42.913455506238051 ], [ -78.812456391220152, 42.913155197963881 ], [ -78.81355576688965, 42.912656379465048 ], [ -78.813556198439798, 42.914640829675548 ], [ -78.81330446664127, 42.914579715671721 ], [ -78.81355622336676, 42.914755411822256 ], [ -78.814556580901453, 42.914756112336519 ], [ -78.815557162713134, 42.914756885970576 ], [ -78.816557594465252, 42.914757459094126 ], [ -78.817558165651363, 42.914755416513607 ], [ -78.818256145722728, 42.914756757777305 ], [ -78.819155785294967, 42.914757624719542 ], [ -78.820156244305437, 42.914755559662552 ], [ -78.821156446253028, 42.914657505838498 ], [ -78.821455090218222, 42.914656885101166 ], [ -78.822257239703006, 42.914556434655353 ], [ -78.822455128322289, 42.91455602152439 ], [ -78.823156988267698, 42.914557269928117 ], [ -78.823556423863408, 42.914556432537715 ], [ -78.824056614264222, 42.91455538195617 ], [ -78.825057080580791, 42.914556072803187 ], [ -78.826255435013962, 42.914556225453076 ], [ -78.827457447884839, 42.91455638519686 ], [ -78.828857461438815, 42.914556162759148 ], [ -78.829156067087453, 42.914555439698631 ], [ -78.832157862254618, 42.914655993557794 ], [ -78.832557335549936, 42.914655124679904 ], [ -78.837555965261672, 42.914655027677036 ], [ -78.838858697395693, 42.914654926337406 ], [ -78.842957736990499, 42.914656597685237 ], [ -78.84325638045668, 42.9146559200761 ], [ -78.843558756955673, 42.914655233203135 ], [ -78.847758513806397, 42.914656613445167 ], [ -78.848758980528913, 42.914656987399198 ], [ -78.851958257798302, 42.914655013461626 ], [ -78.852656778433314, 42.914754922998405 ], [ -78.85365812200979, 42.914955609374736 ], [ -78.853657047324589, 42.913855292993048 ], [ -78.853657967521158, 42.912357137990234 ], [ -78.85365967619083, 42.911056511983595 ], [ -78.854656859806852, 42.911155703833316 ], [ -78.855556445588121, 42.91115617512046 ], [ -78.856459765022805, 42.91115682250279 ], [ -78.859259447418182, 42.911155597178706 ], [ -78.862059586429183, 42.911255770601677 ], [ -78.865556836779717, 42.911156710343533 ], [ -78.865159677833972, 42.912455539180336 ], [ -78.864658777656672, 42.913954923753735 ], [ -78.864157479774619, 42.915355554909951 ], [ -78.861957308400946, 42.917556052161366 ], [ -78.860856832722632, 42.918557465223657 ], [ -78.859760042753081, 42.91955616966203 ], [ -78.859157947244356, 42.920155784666115 ], [ -78.858856876572062, 42.920455618453111 ], [ -78.858656530175679, 42.920755181814776 ], [ -78.856956990718302, 42.922257434331961 ], [ -78.856057477592657, 42.923156769606393 ], [ -78.853759739771817, 42.925357311533098 ], [ -78.852759287171324, 42.926256941150747 ], [ -78.851959523004112, 42.926955789183971 ], [ -78.851458130539228, 42.9275551540956 ], [ -78.851056247518528, 42.927855175636331 ], [ -78.850959569531824, 42.9279568697158 ], [ -78.85075872187268, 42.928154952031669 ], [ -78.849357066205357, 42.929554815991274 ], [ -78.848059054160757, 42.93075689229353 ], [ -78.847158434247248, 42.931456021774324 ], [ -78.846359083627647, 42.932256379439686 ], [ -78.845956921275459, 42.932556383500874 ], [ -78.845458740353124, 42.933056852948276 ], [ -78.844457613641666, 42.933854860213494 ], [ -78.843558140727509, 42.934855728084706 ], [ -78.842557083794873, 42.935656434765868 ], [ -78.842356232851728, 42.935857191087443 ], [ -78.841656567483184, 42.936456960649835 ], [ -78.841158271807103, 42.936954722350357 ], [ -78.839856210702692, 42.938156713358005 ], [ -78.838758734174164, 42.939155187600356 ], [ -78.837456087930008, 42.940255602049938 ], [ -78.835856446299658, 42.941757314897934 ], [ -78.835056290608691, 42.942456016559234 ], [ -78.834357116302002, 42.943157289902665 ], [ -78.833657690718923, 42.94385576071415 ], [ -78.833356381137804, 42.944155526831231 ], [ -78.832258592102392, 42.945156654906228 ], [ -78.83135838731252, 42.946055876655677 ] ] ], [ [ [ -78.81388190979429, 42.914719903009498 ], [ -78.81355622336676, 42.914755411822256 ], [ -78.813556198439798, 42.914640829675548 ], [ -78.81388190979429, 42.914719903009498 ] ] ] ] } },
{ "type": "Feature", "properties": { "SBE4_ID": 0, "NAME": "District B", "FROMLEFT": 0, "TOLEFT": 0, "FROMRIGHT": 0, "TORIGHT": 0, "DIRECTION": null, "STREET": null, "ST_TYPE": null, "ST_PREFIX": null, "ST_SUFFIX": null, "CITY_LEFT": null, "CITY_RIGHT": null, "LO_X_PRE": null, "LO_X_NAME": null, "LO_X_TYPE": null, "LO_X_SUF": null, "HI_X_PRE": null, "HI_X_NAME": null, "HI_X_TYPE": null, "HI_X_SUF": null, "LHS": null, "RHS": null, "BDY_LEFT": null, "BDY_RIGHT": null, "ST_CODE": null, "RECNUM_L": 0, "RECNUM_R": 0 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -78.877556716095029, 42.874857610217347 ], [ -78.87805895026365, 42.874522552122691 ], [ -78.879767789463699, 42.87705099553893 ], [ -78.882677989722453, 42.878305611941379 ], [ -78.882994922991031, 42.877911145090273 ], [ -78.886812483749637, 42.878206759469876 ], [ -78.888732385034302, 42.878825339982058 ], [ -78.890247479896416, 42.881469631441725 ], [ -78.889835470396079, 42.882279055795166 ], [ -78.88897192071019, 42.881844428097786 ], [ -78.889386931765287, 42.881187695916303 ], [ -78.888452123743221, 42.879939802637892 ], [ -78.887206299012334, 42.879344207919118 ], [ -78.886363932196801, 42.879208313175972 ], [ -78.884891941620182, 42.879517868154529 ], [ -78.883809384665241, 42.88006768014219 ], [ -78.883881226506304, 42.880141055142744 ], [ -78.88543589722687, 42.879590628920212 ], [ -78.885349153009415, 42.880294746526879 ], [ -78.884856472862353, 42.880493851630128 ], [ -78.883953613363957, 42.880826369804623 ], [ -78.88391652991092, 42.880965390027278 ], [ -78.885261140223761, 42.880731366039186 ], [ -78.885412749814421, 42.880704405359175 ], [ -78.885432935061019, 42.881989614876254 ], [ -78.884934189912883, 42.882187199689177 ], [ -78.885149591608595, 42.882417749431362 ], [ -78.885990282873877, 42.881942759172496 ], [ -78.886902106250716, 42.881435964598211 ], [ -78.887623976098538, 42.881872685765536 ], [ -78.887295848770606, 42.88218147594376 ], [ -78.886665517494535, 42.882707970148083 ], [ -78.886325573274505, 42.883318314140141 ], [ -78.887935188133397, 42.882305309596234 ], [ -78.888194750681521, 42.882788008753764 ], [ -78.888461862582389, 42.883204230311826 ], [ -78.888197905618881, 42.883428944037398 ], [ -78.887713659596528, 42.883745449631128 ], [ -78.888459338075492, 42.884289799437973 ], [ -78.88896187969182, 42.884981344336815 ], [ -78.890170993132699, 42.886575763198067 ], [ -78.890483014674359, 42.886922466630416 ], [ -78.891329524845204, 42.887212943140796 ], [ -78.891358175386998, 42.887199415290638 ], [ -78.892133110897007, 42.887504971882784 ], [ -78.892385934056861, 42.887695510810936 ], [ -78.893348314393322, 42.888403903499388 ], [ -78.894057755479366, 42.888933744445872 ], [ -78.894612242514853, 42.889338469797835 ], [ -78.895036384546501, 42.889659996207968 ], [ -78.895501392294435, 42.890011208150497 ], [ -78.896121211532275, 42.890469582881344 ], [ -78.89683887897786, 42.890993347367832 ], [ -78.898918617390336, 42.892541080916487 ], [ -78.899432482892905, 42.892922022847635 ], [ -78.899905427513588, 42.893261231452982 ], [ -78.900631448154058, 42.893814911921822 ], [ -78.901000451860682, 42.894477001624736 ], [ -78.901287413342885, 42.894960225054838 ], [ -78.90149248475656, 42.895324097147316 ], [ -78.901762135284699, 42.895734270535478 ], [ -78.901820719343334, 42.895950462273227 ], [ -78.90191675573098, 42.898106772035554 ], [ -78.901983845860258, 42.899191788067803 ], [ -78.902018825485499, 42.900005496587902 ], [ -78.902111235070834, 42.901882460953765 ], [ -78.901354550045156, 42.901657838954542 ], [ -78.901009778825738, 42.901115975466546 ], [ -78.900610944128658, 42.900787295131039 ], [ -78.900920393124537, 42.901298197182662 ], [ -78.9009544677413, 42.901756192436423 ], [ -78.901617220150982, 42.902200478390171 ], [ -78.901603239445336, 42.902900259891958 ], [ -78.901588792822579, 42.903248195747921 ], [ -78.901565452389505, 42.904327206356093 ], [ -78.901560896419539, 42.906155900137307 ], [ -78.90160098289445, 42.914954584158387 ], [ -78.90117482292824, 42.914955618835442 ], [ -78.900833802550821, 42.914856453154684 ], [ -78.899063170890415, 42.914861349800091 ], [ -78.898064267951455, 42.914847414843535 ], [ -78.897133551252566, 42.914849969619191 ], [ -78.895662005310712, 42.914861026418535 ], [ -78.894377258499588, 42.914955942341805 ], [ -78.892967674896013, 42.914959763240532 ], [ -78.890079862876149, 42.915058955206973 ], [ -78.888175984523798, 42.915154442931687 ], [ -78.886661367371204, 42.91515846580203 ], [ -78.885854268732928, 42.91515247522581 ], [ -78.885368298366402, 42.91525127115856 ], [ -78.884273792034705, 42.915254153367037 ], [ -78.883068729321764, 42.915257314605014 ], [ -78.882272685221139, 42.915251269836823 ], [ -78.881860056726552, 42.915357218260048 ], [ -78.881359298348983, 42.915256972327974 ], [ -78.880060763516965, 42.915356407999973 ], [ -78.878357872938679, 42.915256651587946 ], [ -78.876760500452249, 42.915356819507785 ], [ -78.873359629522184, 42.915357198709401 ], [ -78.872758628860012, 42.915356003171667 ], [ -78.868857549841991, 42.915354823057719 ], [ -78.86655787398773, 42.915355047378718 ], [ -78.865057126814094, 42.915356061113151 ], [ -78.864157479774619, 42.915355554909951 ], [ -78.864658777656672, 42.913954923753735 ], [ -78.865159677833972, 42.912455539180336 ], [ -78.865556836779717, 42.911156710343533 ], [ -78.862059586429183, 42.911255770601677 ], [ -78.859259447418182, 42.911155597178706 ], [ -78.856459765022805, 42.91115682250279 ], [ -78.855556445588121, 42.91115617512046 ], [ -78.854656859806852, 42.911155703833316 ], [ -78.85365967619083, 42.911056511983595 ], [ -78.853657494698368, 42.910557187534138 ], [ -78.853659033437623, 42.910054973192565 ], [ -78.853756797638667, 42.909355082581932 ], [ -78.853658635474773, 42.908254994396067 ], [ -78.853759022902551, 42.908156005376249 ], [ -78.853759265986639, 42.907357483109244 ], [ -78.853758953862595, 42.906457521063999 ], [ -78.85375875366897, 42.905557531173962 ], [ -78.853758278971, 42.903757578583011 ], [ -78.853657613396891, 42.902056531765901 ], [ -78.853656698573374, 42.900155000825954 ], [ -78.853656069099998, 42.899156258847228 ], [ -78.853656302324623, 42.898355046346644 ], [ -78.85365785349488, 42.897855629658302 ], [ -78.853756805541735, 42.897457533816237 ], [ -78.853758344049041, 42.896955400639861 ], [ -78.853757469063879, 42.896755099652516 ], [ -78.853857405997729, 42.896557304389624 ], [ -78.853858943823525, 42.896055198573613 ], [ -78.853757551303204, 42.895056693911634 ], [ -78.853756908447806, 42.894055152318728 ], [ -78.853759577852927, 42.892957539513091 ], [ -78.853758060564701, 42.89175575126616 ], [ -78.853758304791938, 42.890957254144233 ], [ -78.853756111813098, 42.890455129209947 ], [ -78.853756712784602, 42.889755383336748 ], [ -78.853759059883188, 42.887755088297254 ], [ -78.853758944339788, 42.885156604458061 ], [ -78.853714464176306, 42.884361065673055 ], [ -78.853661332722666, 42.883318515317931 ], [ -78.85365725150217, 42.88325541562201 ], [ -78.853557511100931, 42.881757490201359 ], [ -78.853656661060612, 42.88055546197964 ], [ -78.853958681519828, 42.879657470565625 ], [ -78.854257382550315, 42.87885554952485 ], [ -78.857356707881706, 42.879457262118258 ], [ -78.859757939693296, 42.879956310487458 ], [ -78.864656316400058, 42.880655101515977 ], [ -78.86885745593024, 42.881456810125457 ], [ -78.86905751475463, 42.88115722766284 ], [ -78.869159247170458, 42.880555980676171 ], [ -78.869358376966289, 42.88005615122713 ], [ -78.869387780475122, 42.879951837575966 ], [ -78.869556577509613, 42.879355910320399 ], [ -78.86975904878048, 42.87875723695614 ], [ -78.869957241093033, 42.878057077538543 ], [ -78.870259928280817, 42.877356655895099 ], [ -78.870258536657332, 42.877056203810682 ], [ -78.87055656243021, 42.876156833702851 ], [ -78.870956750411864, 42.874756419191691 ], [ -78.871258478968869, 42.873855583344678 ], [ -78.871559627886398, 42.872856077792747 ], [ -78.87265780839121, 42.873155108530398 ], [ -78.873157675465777, 42.873156643168144 ], [ -78.87405764897936, 42.873357381992662 ], [ -78.875159693178475, 42.873656461201733 ], [ -78.875958954885036, 42.873857441685928 ], [ -78.87665858052506, 42.874256231783569 ], [ -78.877556716095029, 42.874857610217347 ] ] ] ] } },
{ "type": "Feature", "properties": { "SBE4_ID": 0, "NAME": "District C", "FROMLEFT": 0, "TOLEFT": 0, "FROMRIGHT": 0, "TORIGHT": 0, "DIRECTION": null, "STREET": null, "ST_TYPE": null, "ST_PREFIX": null, "ST_SUFFIX": null, "CITY_LEFT": null, "CITY_RIGHT": null, "LO_X_PRE": null, "LO_X_NAME": null, "LO_X_TYPE": null, "LO_X_SUF": null, "HI_X_PRE": null, "HI_X_NAME": null, "HI_X_TYPE": null, "HI_X_SUF": null, "LHS": null, "RHS": null, "BDY_LEFT": null, "BDY_RIGHT": null, "ST_CODE": null, "RECNUM_L": 0, "RECNUM_R": 0 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -78.837456569994913, 42.878756929372337 ], [ -78.83835691774442, 42.879056837484171 ], [ -78.839156194113883, 42.879255275929467 ], [ -78.839358996383396, 42.878656627987034 ], [ -78.839957561167893, 42.87815595557732 ], [ -78.841556799502669, 42.876955998075758 ], [ -78.841757388131555, 42.876755214333393 ], [ -78.842556244378372, 42.876857758658176 ], [ -78.8440576714939, 42.87725761889191 ], [ -78.84545768790359, 42.877457442727305 ], [ -78.847059027846299, 42.877656785921261 ], [ -78.848757851920865, 42.877957459459736 ], [ -78.850856794926287, 42.878457395030267 ], [ -78.851756334480143, 42.8785568410702 ], [ -78.854257382550315, 42.87885554952485 ], [ -78.853958681519828, 42.879657470565625 ], [ -78.853656661060612, 42.88055546197964 ], [ -78.853557511100931, 42.881757490201359 ], [ -78.85365725150217, 42.88325541562201 ], [ -78.853661332722666, 42.883318515317931 ], [ -78.853714464176306, 42.884361065673055 ], [ -78.853758944339788, 42.885156604458061 ], [ -78.853759059883188, 42.887755088297254 ], [ -78.853756712784602, 42.889755383336748 ], [ -78.853756111813098, 42.890455129209947 ], [ -78.853758304791938, 42.890957254144233 ], [ -78.853758060564701, 42.89175575126616 ], [ -78.853759577852927, 42.892957539513091 ], [ -78.853756908447806, 42.894055152318728 ], [ -78.853757551303204, 42.895056693911634 ], [ -78.853858943823525, 42.896055198573613 ], [ -78.853857405997729, 42.896557304389624 ], [ -78.853757469063879, 42.896755099652516 ], [ -78.853758344049041, 42.896955400639861 ], [ -78.853756805541735, 42.897457533816237 ], [ -78.85365785349488, 42.897855629658302 ], [ -78.853656302324623, 42.898355046346644 ], [ -78.853656069099998, 42.899156258847228 ], [ -78.853656698573374, 42.900155000825954 ], [ -78.853657613396891, 42.902056531765901 ], [ -78.853758278971, 42.903757578583011 ], [ -78.85375875366897, 42.905557531173962 ], [ -78.853758953862595, 42.906457521063999 ], [ -78.853759265986639, 42.907357483109244 ], [ -78.853759022902551, 42.908156005376249 ], [ -78.853658635474773, 42.908254994396067 ], [ -78.853756797638667, 42.909355082581932 ], [ -78.853659033437623, 42.910054973192565 ], [ -78.853657494698368, 42.910557187534138 ], [ -78.85365967619083, 42.911056511983595 ], [ -78.853657967521158, 42.912357137990234 ], [ -78.853657047324589, 42.913855292993048 ], [ -78.85365812200979, 42.914955609374736 ], [ -78.852656778433314, 42.914754922998405 ], [ -78.851958257798302, 42.914655013461626 ], [ -78.848758980528913, 42.914656987399198 ], [ -78.847758513806397, 42.914656613445167 ], [ -78.843558756955673, 42.914655233203135 ], [ -78.84325638045668, 42.9146559200761 ], [ -78.842957736990499, 42.914656597685237 ], [ -78.838858697395693, 42.914654926337406 ], [ -78.837555965261672, 42.914655027677036 ], [ -78.832557335549936, 42.914655124679904 ], [ -78.832157862254618, 42.914655993557794 ], [ -78.829156067087453, 42.914555439698631 ], [ -78.828857461438815, 42.914556162759148 ], [ -78.827457447884839, 42.91455638519686 ], [ -78.826255435013962, 42.914556225453076 ], [ -78.825057080580791, 42.914556072803187 ], [ -78.824056614264222, 42.91455538195617 ], [ -78.823556423863408, 42.914556432537715 ], [ -78.823156988267698, 42.914557269928117 ], [ -78.822455128322289, 42.91455602152439 ], [ -78.822257239703006, 42.914556434655353 ], [ -78.821455090218222, 42.914656885101166 ], [ -78.821156446253028, 42.914657505838498 ], [ -78.820156244305437, 42.914755559662552 ], [ -78.819155785294967, 42.914757624719542 ], [ -78.818256145722728, 42.914756757777305 ], [ -78.817558165651363, 42.914755416513607 ], [ -78.816557594465252, 42.914757459094126 ], [ -78.815557162713134, 42.914756885970576 ], [ -78.814556580901453, 42.914756112336519 ], [ -78.81355622336676, 42.914755411822256 ], [ -78.81355576688965, 42.912656379465048 ], [ -78.812456391220152, 42.913155197963881 ], [ -78.811856489182247, 42.913455506238051 ], [ -78.811357082867673, 42.913656831921934 ], [ -78.810555550332339, 42.913957424994436 ], [ -78.810056088429604, 42.914156028667882 ], [ -78.809456090238214, 42.914456297072078 ], [ -78.808456723670673, 42.914757375724776 ], [ -78.808057645039966, 42.914856911604382 ], [ -78.807755983281623, 42.91505503448947 ], [ -78.806954527440098, 42.915355711896147 ], [ -78.805657092775974, 42.915857650327247 ], [ -78.804755137489181, 42.91625725926454 ], [ -78.803856903703789, 42.916656853904762 ], [ -78.80345780545899, 42.916756401201226 ], [ -78.802454989226121, 42.917156268155487 ], [ -78.802056207188116, 42.917357360535163 ], [ -78.80155672747378, 42.917555844750758 ], [ -78.801057105067628, 42.917757125798815 ], [ -78.800456783714296, 42.917955796927018 ], [ -78.800054256740182, 42.918156889411847 ], [ -78.799454283045222, 42.918457105177296 ], [ -78.799055128618349, 42.918556609769276 ], [ -78.799056743783282, 42.913655973762616 ], [ -78.799054506892517, 42.909855743480577 ], [ -78.799155722941677, 42.906757665138031 ], [ -78.799254604981854, 42.898756254692366 ], [ -78.79935567367923, 42.895655454002004 ], [ -78.799357519475777, 42.88975613471235 ], [ -78.799555682779072, 42.884556101935864 ], [ -78.800455932255147, 42.884856198273617 ], [ -78.800855880420585, 42.885055770186042 ], [ -78.801354855037332, 42.884755737814196 ], [ -78.802455159595638, 42.884657546665778 ], [ -78.803455133300602, 42.884655628692954 ], [ -78.803757485744924, 42.88465776349981 ], [ -78.805055921622497, 42.884655256607296 ], [ -78.806257466997707, 42.884655722425009 ], [ -78.807556279992795, 42.884757426604551 ], [ -78.808555783918976, 42.884656712606812 ], [ -78.809555878954654, 42.884657430116576 ], [ -78.814156862880751, 42.884755313966757 ], [ -78.815257702748966, 42.884755696219727 ], [ -78.816056162884067, 42.884756847493165 ], [ -78.816657238717056, 42.884857093087646 ], [ -78.824051345503236, 42.884759550740974 ], [ -78.824056055970587, 42.884756717006105 ], [ -78.825555243453209, 42.882657282618005 ], [ -78.8267574133816, 42.880956240808032 ], [ -78.828657889567353, 42.878457937354909 ], [ -78.829257334160488, 42.878157567817688 ], [ -78.832856931977261, 42.877057576053375 ], [ -78.833058755274735, 42.877155998336157 ], [ -78.833955401951215, 42.877455839483304 ], [ -78.834855713376044, 42.87775563809948 ], [ -78.835756447512608, 42.878157034814144 ], [ -78.836758064766627, 42.878555457288982 ], [ -78.837456569994913, 42.878756929372337 ] ] ] ] } },
{ "type": "Feature", "properties": { "SBE4_ID": 0, "NAME": "District D", "FROMLEFT": 0, "TOLEFT": 0, "FROMRIGHT": 0, "TORIGHT": 0, "DIRECTION": null, "STREET": null, "ST_TYPE": null, "ST_PREFIX": null, "ST_SUFFIX": null, "CITY_LEFT": null, "CITY_RIGHT": null, "LO_X_PRE": null, "LO_X_NAME": null, "LO_X_TYPE": null, "LO_X_SUF": null, "HI_X_PRE": null, "HI_X_NAME": null, "HI_X_TYPE": null, "HI_X_SUF": null, "LHS": null, "RHS": null, "BDY_LEFT": null, "BDY_RIGHT": null, "ST_CODE": null, "RECNUM_L": 0, "RECNUM_R": 0 }, "geometry": { "type": "MultiPolygon", "coordinates": [ [ [ [ -78.848357925732358, 42.958557155360594 ], [ -78.847558550346733, 42.958556314278027 ], [ -78.847159103369592, 42.958655986661867 ], [ -78.846658049234421, 42.95855559052405 ], [ -78.845757772716311, 42.958554968924986 ], [ -78.844659429161581, 42.958554682819461 ], [ -78.843456598624755, 42.958554733406494 ], [ -78.842257051866881, 42.958455875641029 ], [ -78.841158635672457, 42.958455665861948 ], [ -78.840157571072865, 42.958455117420101 ], [ -78.838958216436993, 42.958455112938751 ], [ -78.838158434253145, 42.95835531882836 ], [ -78.836858858528231, 42.958456982912189 ], [ -78.829957986127056, 42.947054948618003 ], [ -78.830457761013733, 42.946856231049658 ], [ -78.83135838731252, 42.946055876655677 ], [ -78.832258592102392, 42.945156654906228 ], [ -78.833356381137804, 42.944155526831231 ], [ -78.833657690718923, 42.94385576071415 ], [ -78.834357116302002, 42.943157289902665 ], [ -78.835056290608691, 42.942456016559234 ], [ -78.835856446299658, 42.941757314897934 ], [ -78.837456087930008, 42.940255602049938 ], [ -78.838758734174164, 42.939155187600356 ], [ -78.839856210702692, 42.938156713358005 ], [ -78.841158271807103, 42.936954722350357 ], [ -78.841656567483184, 42.936456960649835 ], [ -78.842356232851728, 42.935857191087443 ], [ -78.842557083794873, 42.935656434765868 ], [ -78.843558140727509, 42.934855728084706 ], [ -78.844457613641666, 42.933854860213494 ], [ -78.845458740353124, 42.933056852948276 ], [ -78.845956921275459, 42.932556383500874 ], [ -78.846359083627647, 42.932256379439686 ], [ -78.847158434247248, 42.931456021774324 ], [ -78.848059054160757, 42.93075689229353 ], [ -78.849357066205357, 42.929554815991274 ], [ -78.85075872187268, 42.928154952031669 ], [ -78.850959569531824, 42.9279568697158 ], [ -78.851056247518528, 42.927855175636331 ], [ -78.851458130539228, 42.9275551540956 ], [ -78.851959523004112, 42.926955789183971 ], [ -78.852759287171324, 42.926256941150747 ], [ -78.853759739771817, 42.925357311533098 ], [ -78.856057477592657, 42.923156769606393 ], [ -78.856956990718302, 42.922257434331961 ], [ -78.858656530175679, 42.920755181814776 ], [ -78.858856876572062, 42.920455618453111 ], [ -78.859157947244356, 42.920155784666115 ], [ -78.859760042753081, 42.91955616966203 ], [ -78.860856832722632, 42.918557465223657 ], [ -78.861957308400946, 42.917556052161366 ], [ -78.864157479774619, 42.915355554909951 ], [ -78.865057126814094, 42.915356061113151 ], [ -78.86655787398773, 42.915355047378718 ], [ -78.868857549841991, 42.915354823057719 ], [ -78.872758628860012, 42.915356003171667 ], [ -78.873359629522184, 42.915357198709401 ], [ -78.876760500452249, 42.915356819507785 ], [ -78.878357872938679, 42.915256651587946 ], [ -78.880060763516965, 42.915356407999973 ], [ -78.881359298348983, 42.915256972327974 ], [ -78.881860056726552, 42.915357218260048 ], [ -78.882272685221139, 42.915251269836823 ], [ -78.883068729321764, 42.915257314605014 ], [ -78.884273792034705, 42.915254153367037 ], [ -78.885368298366402, 42.91525127115856 ], [ -78.885854268732928, 42.91515247522581 ], [ -78.886672422971984, 42.915158436510609 ], [ -78.888164928925264, 42.915154472368307 ], [ -78.890066028293106, 42.915055944996219 ], [ -78.892973197591445, 42.914958732545394 ], [ -78.894377258499588, 42.914955942341805 ], [ -78.895675824733544, 42.914860988717805 ], [ -78.897168322242848, 42.914856907221662 ], [ -78.898109668762928, 42.914847290026792 ], [ -78.899051243879498, 42.914856809077392 ], [ -78.899673117636056, 42.914855092483272 ], [ -78.900855119132444, 42.914855753392153 ], [ -78.901159417838116, 42.914956980417145 ], [ -78.901511530128445, 42.914956002391477 ], [ -78.901259500262299, 42.915790463407561 ], [ -78.901039376175646, 42.9165211626134 ], [ -78.900803422995168, 42.917340120508733 ], [ -78.900642632086601, 42.917922293370644 ], [ -78.900352803555677, 42.918873736097275 ], [ -78.899966689539511, 42.920198582505577 ], [ -78.899497029021163, 42.92189358586883 ], [ -78.89950199452521, 42.921893572156002 ], [ -78.898955778359166, 42.924051207509997 ], [ -78.898959314199004, 42.924744023208717 ], [ -78.898966106163485, 42.926074777878405 ], [ -78.898997580075644, 42.927369108017722 ], [ -78.899015346081342, 42.927982092585005 ], [ -78.899797131705569, 42.930131477834024 ], [ -78.899824713903755, 42.930304182471211 ], [ -78.89998968080755, 42.930434992570987 ], [ -78.900817098187616, 42.93108823657095 ], [ -78.900824857978293, 42.930585100877963 ], [ -78.90132628418371, 42.930980937805437 ], [ -78.901834110429547, 42.930639013085603 ], [ -78.902728823511509, 42.930443681928907 ], [ -78.903109151324642, 42.930357845634006 ], [ -78.901834110429547, 42.930639013085603 ], [ -78.900659484006212, 42.929123959777144 ], [ -78.900580076597706, 42.926266420627407 ], [ -78.900429083107412, 42.922319764339214 ], [ -78.900760203399443, 42.921241687908463 ], [ -78.900915847459643, 42.920702745364295 ], [ -78.901546249652824, 42.919608991462646 ], [ -78.901818736656523, 42.919099082084145 ], [ -78.902109568540993, 42.917890121065405 ], [ -78.902235137734323, 42.917263840573362 ], [ -78.902332190342975, 42.916885163554234 ], [ -78.902181178734878, 42.915320070874564 ], [ -78.902124132571586, 42.914957051428722 ], [ -78.901515270083607, 42.914955690463479 ], [ -78.901515776779945, 42.914868491601403 ], [ -78.901518012111453, 42.914820720145464 ], [ -78.902133312499032, 42.914818734938109 ], [ -78.902013473047873, 42.912693915230498 ], [ -78.902533701297116, 42.911804550020491 ], [ -78.902754669155058, 42.91241524030513 ], [ -78.902915198716912, 42.912829692706374 ], [ -78.903067379972413, 42.913542508969392 ], [ -78.903271021262256, 42.914633587956949 ], [ -78.903586313334159, 42.916226591537594 ], [ -78.903778949397676, 42.917099452743621 ], [ -78.904186211959313, 42.919245250066467 ], [ -78.904440341493142, 42.920518157479009 ], [ -78.90462435719698, 42.921631183782786 ], [ -78.904737308753241, 42.922409657703007 ], [ -78.904809627095929, 42.922969833964459 ], [ -78.904909494683295, 42.923729878069061 ], [ -78.904963312104968, 42.924134968048868 ], [ -78.905138306699939, 42.92523964937368 ], [ -78.905314300750192, 42.926570915527613 ], [ -78.905514378516472, 42.927872068041225 ], [ -78.906210330417593, 42.929508631350856 ], [ -78.906212496791639, 42.929923496936539 ], [ -78.904706019158724, 42.93010111178603 ], [ -78.905197192220299, 42.930046811396345 ], [ -78.906216361094181, 42.92992699817907 ], [ -78.906115574986444, 42.932064584547803 ], [ -78.906825072161936, 42.93409383314517 ], [ -78.904965379070646, 42.932881019280011 ], [ -78.904645083186239, 42.932662492287299 ], [ -78.904309426718299, 42.932455367033597 ], [ -78.904007372774672, 42.932239695581686 ], [ -78.903451768029086, 42.931871460603965 ], [ -78.902201542601702, 42.931008740628926 ], [ -78.90202594441206, 42.930895716102775 ], [ -78.901875034453923, 42.930711282252666 ], [ -78.901398118730953, 42.931022774185323 ], [ -78.901651811431563, 42.931225910422974 ], [ -78.902350261746577, 42.931882739510314 ], [ -78.902915944600963, 42.932388693438718 ], [ -78.902629510589421, 42.932600797271924 ], [ -78.90386120948564, 42.933656869024439 ], [ -78.904459278893853, 42.933756743178414 ], [ -78.907262208987191, 42.93695979109124 ], [ -78.908521859033641, 42.938597978997493 ], [ -78.908726708704449, 42.939380438521361 ], [ -78.908730080667112, 42.939389593415029 ], [ -78.909516776232223, 42.94249073687056 ], [ -78.908916416369607, 42.943086737261048 ], [ -78.909717332335276, 42.943989928321862 ], [ -78.908911507135457, 42.944586541589423 ], [ -78.909517706327776, 42.945094129940117 ], [ -78.908916874063678, 42.945605236619571 ], [ -78.910401234703158, 42.953596504184752 ], [ -78.91267796524653, 42.95575904357041 ], [ -78.911761223912649, 42.95645513001049 ], [ -78.908360118215342, 42.958956305281539 ], [ -78.907559933526329, 42.959556760400766 ], [ -78.906759942770009, 42.96015441060144 ], [ -78.906559296799884, 42.960355305505615 ], [ -78.905758658446118, 42.960854199466368 ], [ -78.905059515844783, 42.961454324852951 ], [ -78.90406141411782, 42.962055311372481 ], [ -78.903362054777176, 42.962655454327887 ], [ -78.902561472684425, 42.963154298160894 ], [ -78.901761289295081, 42.963754712362615 ], [ -78.9009612024266, 42.964355120648463 ], [ -78.899159550375217, 42.965655144949189 ], [ -78.898359376221833, 42.966255535118037 ], [ -78.896461726581393, 42.96485586650212 ], [ -78.894559418319659, 42.963256015240091 ], [ -78.893858268674535, 42.962755823238531 ], [ -78.893261164733602, 42.962156457059415 ], [ -78.892959509184109, 42.960856745493309 ], [ -78.890458983145905, 42.958355523664345 ], [ -78.888960817281159, 42.958356844401585 ], [ -78.885061244442412, 42.958455047304646 ], [ -78.883058932473872, 42.958454819792294 ], [ -78.882158658648564, 42.958454484475844 ], [ -78.881758903828015, 42.958455527314996 ], [ -78.881157861160332, 42.958555843687328 ], [ -78.880059519299948, 42.958555897250605 ], [ -78.879260144717819, 42.958555277552378 ], [ -78.878557723983036, 42.95855709124055 ], [ -78.878060939280772, 42.958555572638929 ], [ -78.875158678177471, 42.958656381264539 ], [ -78.87425840060051, 42.958655874070374 ], [ -78.873260883445468, 42.958655715870663 ], [ -78.872457771560946, 42.958655030696043 ], [ -78.871460329053207, 42.958654856616953 ], [ -78.870358249228573, 42.958654826503142 ], [ -78.869259942906652, 42.958654886107276 ], [ -78.865557719794268, 42.958655787081888 ], [ -78.861257477462374, 42.958556554292315 ], [ -78.858359034813049, 42.958657010029732 ], [ -78.855657578720312, 42.958556430976316 ], [ -78.854656835161208, 42.958654869409195 ], [ -78.853957814556878, 42.958554946837822 ], [ -78.853659403684276, 42.958657229161389 ], [ -78.853259671690395, 42.958655373694626 ], [ -78.852859914568072, 42.958656315615869 ], [ -78.85175743026079, 42.958554639873995 ], [ -78.850958457227449, 42.958655289085193 ], [ -78.850558737387288, 42.958656222882844 ], [ -78.85005767815349, 42.958555923956126 ], [ -78.849459880382412, 42.958554516542854 ], [ -78.848357925732358, 42.958557155360594 ] ] ], [ [ [ -78.901518012111453, 42.914820720145464 ], [ -78.901516053815271, 42.914820815724703 ], [ -78.901516056764024, 42.91482072644893 ], [ -78.901518012111453, 42.914820720145464 ] ] ], [ [ [ -78.901516053815271, 42.914820815724703 ], [ -78.901515776779945, 42.914868491601403 ], [ -78.901511709782966, 42.91495540755421 ], [ -78.901511626800385, 42.914955682310051 ], [ -78.901511599202905, 42.914955682248291 ], [ -78.901516053815271, 42.914820815724703 ] ] ], [ [ [ -78.901515270083607, 42.914955690463479 ], [ -78.901515268331408, 42.914955992002454 ], [ -78.901511530128445, 42.914956002391477 ], [ -78.901511626800385, 42.914955682310051 ], [ -78.901515270083607, 42.914955690463479 ] ] ] ] } }
]
}
{
"AuthUrl" : "http://localhost:2009/emimobile/auth/",
"ApiUrl" : "http://localhost:2009/emimobile/api/",
"AppUrl" : "http://localhost:2009/emimobile/app/"
}
\ No newline at end of file
.login-card {
display: inline-block;
width: 300px; /* Adjust width as needed */
padding: 0;
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
background-color: #fff;
}
.card-header {
width: 100%;
text-align: left; /* Align text to the left */
background-color: #f8f9fa; /* Match the card background */
padding: 0.75rem 1.25rem;
border-bottom: 1px solid rgba(0, 0, 0, 0.125);
border-top-left-radius: 10px;
border-top-right-radius: 10px;
margin: 0; /* Remove any margin */
box-sizing: border-box; /* Ensure padding is included in the element's total width and height */
}
.mr-2 {
margin-right: 0.5rem;
}
.card-title {
margin: 0;
font-size: 1.25rem; /* Adjust font size as needed */
}
.card-body {
padding: 2rem;
}
.table tbody tr:hover {
background-color: #d1e7fd; /* Light blue color for hover effect */
cursor: pointer;
}
.form-input{
display: table;
}
.form-cells{
display: table-cell
}
.table tbody tr {
transition: background-color 0.3s ease;
}
.table {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
border-radius: 5px;
}
@media (max-width: 1200px) {
.table-responsive {
display: block;
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.table thead {
display: none;
}
.table tbody, .table tr, .table td {
display: block;
width: 100%;
}
.table tr {
margin-bottom: 1rem;
}
.table td {
text-align: right;
padding-left: 50%; /* Adjust padding to accommodate the data-label */
position: relative;
}
.table td::before {
content: attr(data-label);
position: absolute;
left: 0;
width: 50%;
padding-left: 15px; /* Adjust as necessary */
font-weight: bold;
text-align: left;
}
.table td .transcript {
margin-top: 20px; /* Set top margin to 20px */
text-align: left; /* Ensure text alignment is left */
margin-left: 8px;
white-space: normal; /* Prevent text from being cut off */
}
}
.login-navbar {
max-width: 1200px; /* Set the max-width to match a medium screen */
margin: auto;
border-bottom-left-radius: 10px; /* Round the bottom left corner */
border-bottom-right-radius: 10px; /* Round the bottom right corner */
border: 1px solid #d3d3d3;
}
.navbar-toggler {
display: none;
}
.dropdown-menu a {
display: flex; /* Use flexbox for alignment */
align-items: center; /* Vertically center the content */
width: 100%; /* Ensure they take up the full width */
padding: 0.5rem 1rem; /* Add padding to make them clickable */
color: #000; /* Adjust the text color if necessary */
text-decoration: none; /* Remove underlines */
}
.dropdown-menu a:hover {
background-color: #204d74;
color: #fff;
}
.dropdown-menu a span {
flex-grow: 1; /* Make the span take up the remaining space */
}
/* Style for the selected number */
.selected-number .page-link {
background-color: #204d74;
color: #fff !important;
}
/* Style for the unselected numbers and text (previous/next) */
.pagination .page-item a,
.pagination .page-item span {
color: #204d74;
}
.pagination .page-item.active .page-link,
.pagination .page-item.active .page-link:hover,
.pagination .page-item.active .page-link:focus {
background-color: #204d74;
border-color: #204d74;
color: #fff !important;
}
/* This is needed to get rid of the line that was appearing. */
span.card {
border: none;
}
.modal-backdrop {
z-index: 1040 !important;
}
.modal {
z-index: 1055 !important;
}
unit Paginator.Plugins;
interface
uses
SysUtils, WEBLib.Lists;
type
TPaginatorPlugin = class;
TOnItemClick = reference to procedure(APaginatorPlugin: TPaginatorPlugin);
TPaginatorPlugin = class
const
VISIBLE_PAGES = 7;
ITEM_CLASS_NAME = 'pagination_button';
strict private
FPaginator: TWebListControl;
FActivePage: integer;
function CreateItem: TListItem;
procedure InitPaginator(AActivePage: Integer; APageCount: Integer;
AVisiblePages: integer);
function GetActivePage: integer;
private
FOnItemClick: TOnItemClick;
FOriginalOnItemClick: TListItemEvent;
procedure InternalItemClick(Sender: TObject; AListItem: TListItem);
public
constructor Create(APaginator: TWebListControl;
AItemClickCallback: TOnItemClick);
procedure Init(AActivePage: Integer; APageCount: Integer);
property ActivePage: Integer read GetActivePage;
end;
implementation
{ TPaginatorPlugin }
constructor TPaginatorPlugin.Create(APaginator: TWebListControl;
AItemClickCallback: TOnItemClick);
begin
FPaginator := APaginator;
FOnItemClick := AItemClickCallback;
FOriginalOnItemClick := APaginator.OnItemClick;
APaginator.OnItemClick := InternalItemClick;
end;
function TPaginatorPlugin.CreateItem: TListItem;
begin
Result := FPaginator.Items.Add;
Result.ItemClassName := ITEM_CLASS_NAME;
end;
function TPaginatorPlugin.GetActivePage: integer;
begin
Result := FActivePage;
end;
procedure TPaginatorPlugin.Init(AActivePage, APageCount: Integer);
begin
FActivePage := AActivePage;
InitPaginator(FActivePage, APageCount, VISIBLE_PAGES);
end;
procedure TPaginatorPlugin.InitPaginator(AActivePage, APageCount,
AVisiblePages: integer);
var
Item: TListItem;
I, ButtonNumber, Idx: integer;
HasLeftSeparator: Boolean;
HasRightSeparator: Boolean;
begin
FPaginator.Items.Clear;
HasLeftSeparator := (AVisiblePages < APageCount) and (AActivePage > AVisiblePages - 2);
HasRightSeparator := (AVisiblePages < APageCount) and (AActivePage < APageCount - AVisiblePages + 3);
// first page
ButtonNumber := 1;
Item := CreateItem;
Item.Active := AActivePage = 1;
Item.Text := IntToStr(ButtonNumber);
if HasLeftSeparator then
begin
Item := CreateItem;
Item.Active := False;
Item.Enabled := False;
Item.Text := '...';
end;
if HasRightSeparator and HasLeftSeparator then
begin
Idx := (AVisiblePages - 2) div 2;
for I := AActivePage - Idx to AActivePage + Idx do
begin
ButtonNumber := I;
Item := CreateItem;
Item.Active := ButtonNumber = AActivePage;
Item.Text := IntToStr(ButtonNumber);
end;
end
else
for I := 2 to AVisiblePages - 1 do
begin
if I > APageCount - 1 then
Break;
ButtonNumber := I;
if (not HasRightSeparator) and HasLeftSeparator then
ButtonNumber := APageCount - AVisiblePages + I;
Item := CreateItem;
Item.Active := ButtonNumber = AActivePage;
Item.Text := IntToStr(ButtonNumber);
end;
if APageCount > 1 then
begin
// last page
if HasRightSeparator then
begin
Item := CreateItem;
Item.Active := False;
Item.Enabled := False;
Item.Text := '...';
end;
ButtonNumber := APageCount;
Item := CreateItem;
Item.Active := AActivePage = APageCount;
Item.Text := IntToStr(ButtonNumber);
end;
end;
procedure TPaginatorPlugin.InternalItemClick(Sender: TObject;
AListItem: TListItem);
var
ActivePage: integer;
begin
if TryStrToInt(AListItem.Text, ActivePage) then
begin
FActivePage := ActivePage;
if Assigned(FOnItemClick) then
FOnItemClick(Self);
if Assigned(FOriginalOnItemClick) then
FOriginalOnItemClick(Sender, AListItem);
end;
end;
end.
unit Utils;
interface
uses
System.Classes, SysUtils, JS, Web, WEBLib.Forms, WEBLib.Toast, DateUtils, WebLib.Dialogs;
procedure ShowStatusMessage(const AMessage, AClass: string; const AElementId: string);
procedure HideStatusMessage(const AElementId: string);
procedure ShowSpinner(SpinnerID: string);
procedure HideSpinner(SpinnerID: string);
procedure ShowErrorModal(msg: string);
function CalculateAge(DateOfBirth: TDateTime): Integer;
function FormatPhoneNumber(PhoneNumber: string): string;
procedure ApplyReportTitle(CurrentReportType: string);
procedure ShowToast(const MessageText: string; const ToastType: string = 'success');
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
// function FormatDollarValue(ValueStr: string): string;
implementation
procedure ShowStatusMessage(const AMessage, AClass: string; const AElementId: string);
var
StatusMessage: TJSHTMLElement;
begin
StatusMessage := TJSHTMLElement(document.getElementById(AElementId));
if Assigned(StatusMessage) then
begin
if AMessage = '' then
begin
StatusMessage.style.setProperty('display', 'none');
StatusMessage.className := '';
StatusMessage.innerHTML := '';
end
else
begin
StatusMessage.innerHTML := AMessage;
StatusMessage.className := 'alert ' + AClass;
StatusMessage.style.setProperty('display', 'block');
end
end
else
console.log('Error: Status message element not found');
end;
procedure HideStatusMessage(const AElementId: string);
var
StatusMessage: TJSHTMLElement;
begin
StatusMessage := TJSHTMLElement(document.getElementById(AElementId));
if Assigned(StatusMessage) then
begin
StatusMessage.style.setProperty('display', 'none');
StatusMessage.className := '';
StatusMessage.innerHTML := '';
end
else
console.log('Error: Status message element not found');
end;
procedure ShowSpinner(SpinnerID: string);
var
SpinnerElement: TJSHTMLElement;
begin
SpinnerElement := TJSHTMLElement(document.getElementById(SpinnerID));
if Assigned(SpinnerElement) then
begin
// Move spinner to the <body> if it's not already there
asm
if (SpinnerElement.parentNode !== document.body) {
document.body.appendChild(SpinnerElement);
}
end;
SpinnerElement.classList.remove('d-none');
SpinnerElement.classList.add('d-block');
end;
end;
procedure HideSpinner(SpinnerID: string);
var
SpinnerElement: TJSHTMLElement;
begin
SpinnerElement := TJSHTMLElement(document.getElementById(SpinnerID));
if Assigned(SpinnerElement) then
begin
SpinnerElement.classList.remove('d-block');
SpinnerElement.classList.add('d-none');
end;
end;
procedure ShowErrorModal(msg: string);
begin
asm
var modal = document.getElementById('main_errormodal');
var label = document.getElementById('main_lblmodal_body');
var reloadBtn = document.getElementById('btn_modal_restart');
if (label) label.innerText = msg;
// Ensure modal is a direct child of <body>
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
// Bind hard reload to button
if (reloadBtn) {
reloadBtn.onclick = function () {
window.location.reload(true); // hard reload, bypass cache
};
}
// Show the Bootstrap modal
var bsModal = new bootstrap.Modal(modal, { keyboard: false });
bsModal.show();
end;
end;
// ShowConfirmationModal displays a two-button modal with custom labels.
// Params:
// - messageText: text shown in the modal body
// - leftButtonText: label for the left button (e.g., "Cancel")
// - rightButtonText: label for the right button (e.g., "Delete")
// - callback: procedure(confirmed: Boolean); confirmed = True if right button clicked
//
// Example:
// ShowConfirmationModal('Delete this?', 'Cancel', 'Delete',
// procedure(confirmed: Boolean)
// begin
// if confirmed then DeleteOrder();
// end);
// function ShowConfirmationModal(msg, leftLabel, rightLabel: string;): Boolean;
// if ShowConfirmationModal then
// doThing()
// else
// doOtherThing();
procedure ShowConfirmationModal(msg, leftLabel, rightLabel: string; ConfirmProc: TProc<Boolean>);
begin
asm
var modal = document.getElementById('main_confirmation_modal');
var body = document.getElementById('main_modal_body');
var btnLeft = document.getElementById('btn_confirm_left');
var btnRight = document.getElementById('btn_confirm_right');
var bsModal;
if (body) body.innerText = msg;
if (btnLeft) btnLeft.innerText = leftLabel;
if (btnRight) btnRight.innerText = rightLabel;
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
btnLeft.onclick = null;
btnRight.onclick = null;
btnLeft.onclick = function () {
bsModal.hide();
ConfirmProc(true); // user confirmed
};
btnRight.onclick = function () {
bsModal.hide();
ConfirmProc(false); // user canceled
};
bsModal = new bootstrap.Modal(modal, { keyboard: false });
bsModal.show();
end;
end;
function CalculateAge(DateOfBirth: TDateTime): Integer;
var
Today, BirthDate: TJSDate;
Year, Month, Day, BirthYear, BirthMonth, BirthDay: NativeInt;
DOBString: string;
begin
Today := TJSDate.New;
Year := Today.FullYear;
Month := Today.Month + 1;
Day := Today.Date;
// Formats the DateOfBirth as an ISO 8601 date string
DOBString := FormatDateTime('yyyy-mm-dd', DateOfBirth);
BirthDate := TJSDate.New(DOBString);
if BirthDate = nil then
begin
Exit(0); // Exit the function with an age of 0 if the date creation fails
end;
BirthYear := BirthDate.FullYear;
BirthMonth := BirthDate.Month + 1;
BirthDay := BirthDate.Date;
Result := Year - BirthYear;
if (Month < BirthMonth) or ((Month = BirthMonth) and (Day < BirthDay)) then
Dec(Result);
end;
function FormatPhoneNumber(PhoneNumber: string): string;
var
Digits: string;
begin
Digits := PhoneNumber.Replace('(', '').Replace(')', '').Replace('-', '').Replace(' ', '');
case Length(Digits) of
7: Result := Format('%s-%s', [Copy(Digits, 1, 3), Copy(Digits, 4, 4)]);
10: Result := Format('(%s) %s-%s', [Copy(Digits, 1, 3), Copy(Digits, 4, 3), Copy(Digits, 7, 4)]);
else
// If the number does not have 7 or 10 digits, whatever they typed is returned
Result := PhoneNumber;
end;
end;
procedure ShowToast(const MessageText: string; const ToastType: string = 'success');
var
ParsedText, ToastKind, MsgPrefix: string;
Parts: TArray<string>;
begin
ParsedText := MessageText.Trim;
ToastKind := ToastType.ToLower;
// Check for "Success:" or "Failure:" at the start of message
if ParsedText.Contains(':') then
begin
Parts := ParsedText.Split([':'], 2);
MsgPrefix := Parts[0].Trim.ToLower;
if (MsgPrefix = 'success') or (MsgPrefix = 'failure') then
begin
ParsedText := Parts[1].Trim;
if MsgPrefix = 'success' then
ToastKind := 'success'
else
ToastKind := 'danger';
end;
end;
asm
var toastEl = document.getElementById('bootstrapToast');
var toastBody = document.getElementById('bootstrapToastBody');
if (!toastEl || !toastBody) return;
toastBody.innerText = ParsedText;
toastEl.classList.remove('bg-success', 'bg-danger', 'bg-warning', 'bg-info');
toastEl.classList.remove('slide-in');
switch (ToastKind) {
case 'danger':
toastEl.classList.add('bg-danger');
break;
case 'warning':
toastEl.classList.add('bg-warning');
break;
case 'info':
toastEl.classList.add('bg-info');
break;
default:
toastEl.classList.add('bg-success');
}
// Add slide-in animation
toastEl.classList.add('slide-in');
var toast = new bootstrap.Toast(toastEl, { delay: 2500 });
toast.show();
// Remove animation class after it's done (so it can be reapplied)
setTimeout(function() {
toastEl.classList.remove('slide-in');
}, 500);
end;
end;
procedure ApplyReportTitle(CurrentReportType: string);
var
CrimeTitleElement: TJSHTMLElement;
begin
CrimeTitleElement := TJSHTMLElement(document.getElementById('crime_title'));
if Assigned(CrimeTitleElement) then
CrimeTitleElement.innerText := CurrentReportType
else
Console.Log('Element with ID "crime_title" not found.');
end;
// Used html number input type to restrict the input instead of this function
// function FormatDollarValue(ValueStr: string): string;
// var
// i: Integer;
// begin
// Result := ''; // Initialize the result
// // Filter out any characters that are not digits or decimal point
// for i := 1 to Length(ValueStr) do
// begin
// if (Pos(ValueStr[i], '0123456789.') > 0) then
// begin
// Result := Result + ValueStr[i];
// end;
// end;
// end;
end.
object FViewAdmin: TFViewAdmin
Width = 640
Height = 480
OnShow = WebFormShow
object lblInfo: TWebLabel
Left = 29
Top = 255
Width = 78
Height = 15
Caption = 'Add New User:'
ElementID = 'lblinfo'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object lblResult: TWebLabel
Left = 32
Top = 374
Width = 3
Height = 15
ElementID = 'lblresult'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel1: TWebLabel
Left = 24
Top = 24
Width = 60
Height = 15
Caption = 'User Profile'
ElementID = 'view.userprofile.title'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel3: TWebLabel
Left = 24
Top = 59
Width = 61
Height = 15
Caption = 'User Name:'
ElementID = 'view.userprofile.form.lblUserName'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel2: TWebLabel
Left = 40
Top = 155
Width = 39
Height = 15
Caption = 'User Id:'
ElementID = 'view.userprofile.form.lblUserId'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel4: TWebLabel
Left = 29
Top = 83
Width = 57
Height = 15
Caption = 'Full Name:'
ElementID = 'view.userprofile.form.lblFullName'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel5: TWebLabel
Left = 39
Top = 107
Width = 43
Height = 15
Caption = 'Agency:'
ElementID = 'view.userprofile.form.lblAgency'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel6: TWebLabel
Left = 22
Top = 131
Width = 66
Height = 15
Caption = 'Badge Num:'
ElementID = 'view.userprofile.form.lblBadgeNum'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel7: TWebLabel
Left = 15
Top = 179
Width = 68
Height = 15
Caption = 'Personnel Id:'
ElementID = 'view.userprofile.form.lblPersonnelId'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object edtAddUsername: TWebEdit
Left = 29
Top = 276
Width = 121
Height = 22
ChildOrder = 1
ElementID = 'edtusername'
HeightPercent = 100.000000000000000000
TextHint = 'Username'
WidthPercent = 100.000000000000000000
end
object edtPassword: TWebEdit
Left = 29
Top = 310
Width = 121
Height = 22
ChildOrder = 2
ElementID = 'edtpassword'
HeightPercent = 100.000000000000000000
TextHint = 'Password'
WidthPercent = 100.000000000000000000
end
object btnAddUser: TWebButton
Left = 29
Top = 338
Width = 96
Height = 25
Caption = 'Add User'
ChildOrder = 3
ElementID = 'btnadduser'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnAddUserClick
end
object edtUsername: TWebEdit
Left = 83
Top = 56
Width = 121
Height = 21
ChildOrder = 12
ElementID = 'view.userprofile.form.edtUserName'
HeightPercent = 100.000000000000000000
ReadOnly = True
WidthPercent = 100.000000000000000000
end
object edtUserId: TWebEdit
Left = 83
Top = 152
Width = 121
Height = 21
ChildOrder = 13
ElementID = 'view.userprofile.form.edtUserId'
HeightPercent = 100.000000000000000000
ReadOnly = True
TabOrder = 1
WidthPercent = 100.000000000000000000
end
object edtFullName: TWebEdit
Left = 83
Top = 80
Width = 121
Height = 21
ChildOrder = 5
ElementID = 'view.userprofile.form.edtFullName'
HeightPercent = 100.000000000000000000
ReadOnly = True
WidthPercent = 100.000000000000000000
end
object edtAgency: TWebEdit
Left = 83
Top = 104
Width = 121
Height = 21
ChildOrder = 7
ElementID = 'view.userprofile.form.edtAgency'
HeightPercent = 100.000000000000000000
ReadOnly = True
WidthPercent = 100.000000000000000000
end
object chkAdminUser: TWebCheckBox
Left = 32
Top = 203
Width = 113
Height = 22
Caption = 'chkAdminUser'
ChildOrder = 9
ElementID = 'view.userprofile.form.chkAdminUser'
Enabled = False
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object edtBadgeNum: TWebEdit
Left = 83
Top = 128
Width = 121
Height = 21
ChildOrder = 7
ElementID = 'view.userprofile.form.edtBadgeNum'
HeightPercent = 100.000000000000000000
ReadOnly = True
WidthPercent = 100.000000000000000000
end
object edtPersonnelId: TWebEdit
Left = 83
Top = 176
Width = 121
Height = 21
ChildOrder = 12
ElementID = 'view.userprofile.form.edtPersonnelId'
HeightPercent = 100.000000000000000000
ReadOnly = True
TabOrder = 1
WidthPercent = 100.000000000000000000
end
object XDataWebClient1: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 339
Top = 90
end
end
<div class="row">
<div class="col-lg-12">
<h1 class="page-header" id="view.userprofile.title">Admin User Profile</h1>
<div role="form">
<div class="form-group">
<label id="view.userprofile.form.lblUserName">User Name:</label>
<input id="view.userprofile.form.edtUserName" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblFullName">User Fullname:</label>
<input id="view.userprofile.form.edtFullName" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblAgency">User Agency:</label>
<input id="view.userprofile.form.edtAgency" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblBadgeNum">User Bage #:</label>
<input id="view.userprofile.form.edtBadgeNum" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblUserId">User Id:</label>
<input id="view.userprofile.form.edtUserId" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblPersonnelId">Personnel Id:</label>
<input id="view.userprofile.form.edtPersonnelId" class="form-control">
</div>
<div class="custom-control custom-checkbox">
<input type="checkbox" class="custom-control-input" id="view.userprofile.form.chkAdminUser">
<label class="custom-control-label" for="view.userprofile.form.chkAdminUser">Admin User</label>
</div>
<div class="form-input">
<div><label id="lblinfo" class="py-2" style="font-size: 1.00rem;"></label></div>
<div><input class="form-control input-sm" id="edtusername" width='50%'/></div>
<div class="py-2"><input class="form-control input-sm" id="edtpassword" width='50%'/></div>
<button id="btnadduser"></button>
<div><label id="lblresult" class="py-2" style="font-size: 1.00rem;"></label></div>
</div>
</div>
</div>
</div>
// Delete this
unit View.Admin;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls,
XData.Web.Client, WEBLib.ExtCtrls, DB, XData.Web.JsonDataset,
XData.Web.Dataset, XData.Web.Connection, Vcl.Forms;
type
TFViewAdmin = class(TWebForm)
lblInfo: TWebLabel;
edtAddUsername: TWebEdit;
edtPassword: TWebEdit;
btnAddUser: TWebButton;
XDataWebClient1: TXDataWebClient;
lblResult: TWebLabel;
WebLabel1: TWebLabel;
WebLabel3: TWebLabel;
WebLabel2: TWebLabel;
WebLabel4: TWebLabel;
WebLabel5: TWebLabel;
WebLabel6: TWebLabel;
WebLabel7: TWebLabel;
edtUsername: TWebEdit;
edtUserId: TWebEdit;
edtFullName: TWebEdit;
edtAgency: TWebEdit;
chkAdminUser: TWebCheckBox;
edtBadgeNum: TWebEdit;
edtPersonnelId: TWebEdit;
procedure btnAddUserClick(Sender: TObject);
procedure WebFormShow(Sender: TObject);
[async] procedure AddUser();
private
{ Private declarations }
public
{ Public declarations }
end;
var
FViewAdmin: TFViewAdmin;
implementation
uses
XData.Model.Classes,
ConnectionModule,
Auth.Service;
{$R *.dfm}
procedure TFViewAdmin.WebFormShow(Sender: TObject);
begin
console.log('');
edtUsername.Text := JS.toString(AuthService.TokenPayload.Properties['user_name']);
edtFullName.Text := JS.toString(AuthService.TokenPayload.Properties['user_fullname']);
edtAgency.Text := JS.toString(AuthService.TokenPayload.Properties['user_agency']);
edtBadgeNum.Text := JS.toString(AuthService.TokenPayload.Properties['user_badge']);
edtUserId.Text := JS.toString(AuthService.TokenPayload.Properties['user_id']);
edtPersonnelId.Text := JS.toString(AuthService.TokenPayload.Properties['user_personnelid']);
//edtJwt.Text := TJSJSON.stringify(AuthService.TokenPayload);
chkAdminUser.Checked := JS.toBoolean(AuthService.TokenPayload.Properties['user_admin']);
end;
procedure TFViewAdmin.btnAddUserClick(Sender: TObject);
begin
AddUser();
end;
procedure TFViewAdmin.AddUser();
var
xdcResponse: TXDataClientResponse;
responseString: TJSObject;
begin
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.AddUser',
[edtAddUsername.Text, edtPassword.Text]));
responseString := TJSObject(xdcResponse.Result);
lblResult.Caption := string(responseString['value']);
end;
end.
\ No newline at end of file
object FViewComplaints: TFViewComplaints
Width = 676
Height = 480
CSSLibrary = cssBootstrap
ElementFont = efCSS
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
ParentFont = False
Visible = True
OnCreate = WebFormCreate
object lblEntries: TWebLabel
Left = 0
Top = 336
Width = 77
Height = 13
Caption = 'Showing 0 of ...'
ElementID = 'lblentries'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
Visible = False
WidthPercent = 100.000000000000000000
end
object wcbPageSize: TWebComboBox
Left = 0
Top = 0
Width = 145
Height = 21
ElementClassName = 'custom-select'
ElementID = 'wcbpagesize'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
Text = '10'
Visible = False
WidthPercent = 100.000000000000000000
ItemIndex = -1
Items.Strings = (
'10'
'25'
'50')
end
object wcbLocation: TWebLookupComboBox
Left = 154
Top = 0
Width = 145
Height = 22
ElementClassName = 'custom-select'
ElementID = 'wcblocation'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
Visible = False
WidthPercent = 100.000000000000000000
ItemIndex = -1
LookupValues = <
item
DisplayText = 'All'
end
item
Value = '(716) 681-8820'
DisplayText = 'Galleria'
end
item
Value = '(716) 297-4654'
DisplayText = 'NF Outlet'
end
item
Value = '(585) 445-8911'
DisplayText = 'Rochester'
end
item
Value = '(315) 565-4138'
DisplayText = 'Syracuse'
end>
end
object dtpStartDate: TWebEdit
Left = 342
Top = 0
Width = 121
Height = 22
ChildOrder = 10
ElementClassName = 'form-control'
ElementID = 'dtpstartdate'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
Visible = False
WidthPercent = 100.000000000000000000
end
object dtpEndDate: TWebEdit
Left = 478
Top = 0
Width = 121
Height = 22
ChildOrder = 10
ElementClassName = 'form-control'
ElementID = 'dtpenddate'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
Visible = False
WidthPercent = 100.000000000000000000
end
object wcbSortBy: TWebComboBox
Left = 442
Top = 52
Width = 145
Height = 21
ElementClassName = 'custom-select'
ElementID = 'wcbsortby'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
Text = 'Date'
Visible = False
WidthPercent = 100.000000000000000000
ItemIndex = -1
Items.Strings = (
'Date'
'Phone Number')
end
object btnApply: TWebButton
Left = 478
Top = 128
Width = 96
Height = 25
Caption = 'Apply'
ChildOrder = 7
ElementClassName = 'btn btn-light'
ElementID = 'btnapply'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
Visible = False
WidthPercent = 100.000000000000000000
end
object edtSearch: TWebEdit
Left = 50
Top = 382
Width = 121
Height = 22
HelpType = htKeyword
ChildOrder = 8
ElementClassName = 'form-control'
ElementID = 'edtsearch'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
HideSelection = False
TextHint = 'Format: (XXX) XXX-XXXX'
Visible = False
WidthPercent = 100.000000000000000000
end
object XDataWebClient1: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 426
Top = 240
end
object XDataWebDataSet1: TXDataWebDataSet
Connection = DMConnection.ApiConnection
Left = 440
Top = 300
end
end
<div class="sticky-top">
<!-- Local navbar (Complaints) -->
<nav class="navbar navbar-dark bg-primary py-2"><!-- removed sticky-top -->
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<span id="complaints.title" class="navbar-brand mb-0 h5 text-white">Complaints</span>
</div>
<div class="col">
<button id="complaints.btnrefresh" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span>
</button>
</div>
<div class="col">
<button id="complaints.btngroup" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span>
</button>
</div>
<div class="col">
<button id="complaints.btnfilter" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sliders-h me-1"></i><span class="d-none d-sm-inline">Filter</span>
</button>
</div>
</div>
</div>
</nav>
<!-- Search bar under local navbar -->
<div class="bg-light border-bottom py-2"><!-- removed sticky-top -->
<div class="container-fluid">
<div class="input-group">
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
<input id="complaints.search" class="form-control" placeholder="Search...">
</div>
</div>
</div>
</div> <!-- /sticky-top wrapper -->
<!-- Existing content (unchanged) -->
<div class="row">
<div class="col-12">
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<h1 class="page-header pt-3 pb-2 mb-3 border-bottom fs-4 fw-bold" id="view.calls.title">Complaints</h1>
<!-- Data Table -->
<div class="table-responsive mt-4">
<table class="table table-sm table-striped table-bordered align-middle" id="tblPhoneGrid">
<thead class="table-dark">
<tr>
<th scope="col">Phone Number</th>
<th scope="col">Caller</th>
<th scope="col">Time</th>
<th scope="col">Duration</th>
<th scope="col">Transcript</th>
<th scope="col">Listen</th>
</tr>
</thead>
<tbody>
<!-- Rows added dynamically in Delphi -->
</tbody>
</table>
</div>
<!-- Entry Count Label -->
<label id="lblentries" class="mt-2 d-block"></label>
<!-- Pagination -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center" id="pagination">
<!-- Pagination items added in Delphi -->
</ul>
</nav>
</div>
</div>
</div>
</div>
</div>
unit View.Complaints;
interface
uses
System.SysUtils, System.Classes, Web, WEBLib.Graphics, WEBLib.Forms, WEBLib.Dialogs,
Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.Controls, WEBLib.Grids, WebLib.Lists,
XData.Web.Client, WEBLib.ExtCtrls, DB, XData.Web.JsonDataset,
XData.Web.Dataset, XData.Web.Connection, Vcl.Forms, DateUtils, WebAudio;
type
TFViewComplaints = class(TWebForm)
XDataWebClient1: TXDataWebClient;
XDataWebDataSet1: TXDataWebDataSet;
lblEntries: TWebLabel;
wcbPageSize: TWebComboBox;
wcbLocation: TWebLookupComboBox;
dtpStartDate: TWebEdit;
dtpEndDate: TWebEdit;
wcbSortBy: TWebComboBox;
btnApply: TWebButton;
edtSearch: TWebEdit;
procedure WebFormCreate(Sender: TObject);
procedure btnApplyClick(Sender: TObject);
procedure btnSearchClick(Sender: TObject);
private
FChildForm: TWebForm;
procedure AddRowToTable(PhoneNumber, Caller, Time, Duration, Transcript, MediaUrl: string);
procedure ClearTable();
procedure GeneratePagination(TotalPages: Integer);
function GenerateSearchOptions(): string;
[async] procedure Search(searchOptions: string);
[async] procedure GetCalls(searchOptions: string);
var
PageNumber: integer;
PageSize: integer;
TotalPages: integer;
StartDate: string;
EndDate: string;
OrderBy: string;
Caller: string;
public
end;
var
FViewComplaints: TFViewComplaints;
implementation
uses
JS, XData.Model.Classes,
ConnectionModule, Utils;
{$R *.dfm}
procedure TFViewComplaints.WebFormCreate(Sender: TObject);
// Initializes important values:
// PageNumber: What page number the user is on IE 1: 1-10, 2: 11-20 etc
// TotalPages: Total number of pages returned from the search.
// PageSize: Number of entries per page.
begin
DMConnection.ApiConnection.Connected := True;
PageNumber := 1;
TotalPages := 1; // Initial total pages
wcbPageSize.Text := '10';
wcbLocation.DisplayText := 'All';
wcbSortBy.Text := 'Date';
// hardcoded default toNumber/location and load grid
wcbLocation.Value := 'Galleria';
PageSize := StrToInt(wcbPageSize.Text);
StartDate := '';
EndDate := '';
OrderBy := wcbSortBy.Text;
Caller := '';
GetCalls(GenerateSearchOptions);
end;
procedure TFViewComplaints.AddRowToTable(PhoneNumber, Caller, Time, Duration, Transcript, MediaUrl: string);
// Adds rows to the table
// PhoneNumber: phone number of the location
// Caller: phone number of the caller
// Duration: duration of the call
// Transcript: transcription of the recording
// MediaUrl: Link to the recording
var
NewRow, Cell, P, Button, Audio: TJSHTMLElement;
begin
NewRow := TJSHTMLElement(document.createElement('tr'));
// Phone Number Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Phone Number');
Cell.innerText := PhoneNumber;
NewRow.appendChild(Cell);
// Caller Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Caller');
Cell.innerText := Caller;
NewRow.appendChild(Cell);
// Time Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Time');
Cell.innerText := DateTimeToStr(IncHour(StrToDateTime(Time), -4));;
NewRow.appendChild(Cell);
// Duration Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Duration');
Cell.innerText := Duration;
NewRow.appendChild(Cell);
// Transcript Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Transcript');
P := TJSHTMLElement(document.createElement('p'));
P.className := 'transcript';
P.innerText := Transcript;
Cell.appendChild(P);
NewRow.appendChild(Cell);
// Listen Button Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Listen');
Button := TJSHTMLElement(document.createElement('button'));
Button.className := 'btn btn-primary';
Button.innerHTML := '<i class="fa fa-play"></i>';
// Open voicemail audio in a new tab on click
Button.addEventListener('click', procedure(Event: TJSMouseEvent)
begin
{$IFNDEF WIN32}
asm
var audioElem = document.getElementById('playerAudio');
audioElem.src = MediaUrl;
audioElem.load();
audioElem.play();
var modal = document.getElementById('playerModal');
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
// Create the Bootstrap modal object
var bsModal = new bootstrap.Modal(modal, { keyboard: false });
// Listen for when the modal *finishes* hiding:
modal.addEventListener('hidden.bs.modal', function (evt) {
// Pause and/or reset the audio
audioElem.pause();
audioElem.currentTime = 0;
}, { once: true });
// Show the modal
bsModal.show();
end;
{$ENDIF}
end);
// Add the button to the cell, and the cell to the row
Cell.appendChild(Button);
NewRow.appendChild(Cell);
// Appends new rows to the table body
TJSHTMLElement(document.getElementById('tblPhoneGrid').getElementsByTagName('tbody')[0]).appendChild(NewRow);
end;
procedure TFViewComplaints.GeneratePagination(TotalPages: Integer);
// Generates pagination for the table.
// TotalPages: Total amount of pages generated by the search
var
PaginationElement, PageItem, PageLink: TJSHTMLElement;
I, Start, Finish: Integer;
begin
PaginationElement := TJSHTMLElement(document.getElementById('pagination'));
PaginationElement.innerHTML := ''; // Clear existing pagination
// Previous Button
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if PageNumber = 1 then
PageItem.classList.add('disabled');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := 'Previous';
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
begin
if PageNumber > 1 then
begin
Dec(PageNumber);
GetCalls(GenerateSearchOptions());
end;
end);
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
// Page Numbers
if PageNumber <= 4 then
// If page number is low enough no early elipsis needed
Begin
Start := 2;
Finish := 5;
End
else if (PageNumber >= (TotalPages - 3)) then
// If page number is high enough no late elipsis needed
begin
Start := TotalPages - 3;
Finish := TotalPages - 1;
end
else
begin
Start := PageNumber - 1;
Finish := PageNumber + 1;
end;
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if 1 = PageNumber then
PageItem.classList.add('selected-number'); // Add the selected-number class
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := '1';
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
var
PageNum: Integer;
begin
PageNum := StrToInt((Event.currentTarget as TJSHTMLElement).innerText);
PageNumber := PageNum;
GetCalls(GenerateSearchOptions());
end);
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
// Adds Elipse to pagination if page number is too big
if PageNumber > 4 then
begin
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
PageItem.classList.add('disabled');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := '...';
PageLink.setAttribute('href', 'javascript:void(0)');
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
end;
// Adds Page, page - 1, and page + 1 to pagination
for I := Start to Finish do
begin
if ( I > 1) and (I < TotalPages) then
begin
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if I = PageNumber then
PageItem.classList.add('selected-number'); // Add the selected-number class
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := IntToStr(I);
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
var
PageNum: Integer;
begin
PageNum := StrToInt((Event.currentTarget as TJSHTMLElement).innerText);
PageNumber := PageNum;
GetCalls(GenerateSearchOptions());
end);
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
end;
end;
// adds ellipse if number is too small
if PageNumber < TotalPages - 4 then
begin
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
PageItem.classList.add('disabled');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := '...';
PageLink.setAttribute('href', 'javascript:void(0)');
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
end;
if TotalPages <> 1 then
begin
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if TotalPages = PageNumber then
PageItem.classList.add('selected-number');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := IntToStr(TotalPages);
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
var
PageNum: Integer;
begin
PageNum := StrToInt((Event.currentTarget as TJSHTMLElement).innerText);
PageNumber := PageNum;
GetCalls(generateSearchOptions());
end);
end;
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
// Next Button
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if PageNumber = TotalPages then
PageItem.classList.add('disabled');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := 'Next';
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
begin
if PageNumber < TotalPages then
begin
Inc(PageNumber);
GetCalls(GenerateSearchOptions());
end;
end);
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
end;
procedure TFViewComplaints.GetCalls(searchOptions: string);
// Retrieves list of calls from the server that meet the criteria searchOptions
// searchOptions: information about how to generate the SQL statement based on
// user input.
var
xdcResponse: TXDataClientResponse;
callList : TJSObject;
i: integer;
data: TJSArray;
call: TJSObject;
callListLength: integer;
begin
if PageNumber > 0 then
begin
Utils.ShowSpinner('spinner');
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.GetCalls',
[searchOptions]));
callList := TJSObject(xdcResponse.Result);
data := TJSArray(callList['data']);
callListLength := integer(callList['count']);
ClearTable();
Utils.HideSpinner('spinner');
for i := 0 to data.Length - 1 do
begin
call := TJSObject(data[i]);
AddRowToTable(string(call['toNumber']), string(call['fromNumber']), string(call['dateCreated']),
string(call['duration']), string(call['transcription']), string(call['mediaUrl']));
end;
TotalPages := (callListLength + PageSize - 1) div PageSize;
if (PageNumber * PageSize) < callListLength then
begin
lblEntries.Caption := 'Showing entries ' + IntToStr((PageNumber - 1) * PageSize + 1) +
' - ' + IntToStr(PageNumber * PageSize) +
' of ' + IntToStr(callListLength);
end
else
begin
lblEntries.Caption := 'Showing entries ' + IntToStr((PageNumber - 1) * PageSize + 1) +
' - ' + IntToStr(callListLength) +
' of ' + IntToStr(callListLength);
end;
GeneratePagination(TotalPages);
end;
end;
procedure TFViewComplaints.btnApplyClick(Sender: TObject);
// Button that effectively functions as a GetCalls() button
var
searchOptions: string;
begin
PageNumber := 1;
PageSize := StrToInt(wcbPageSize.Text);
StartDate := dtpStartDate.Text;
EndDate := dtpEndDate.Text;
OrderBy := wcbSortBy.Text;
Caller := edtSearch.Text;
searchOptions := '&phonenumber=' + wcbLocation.Value +
'&pagenumber=' + IntToStr(PageNumber) +
'&pagesize=' + IntToStr(PageSize) +
'&startdate=' + StartDate +
'&enddate=' + EndDate +
'&orderby=' + OrderBy +
'&caller=' + Caller;
GetCalls(searchOptions);
end;
procedure TFViewComplaints.Search(searchOptions: string);
// Search method that searches the database for a specific phone number
var
xdcResponse: TXDataClientResponse;
callList : TJSObject;
i: integer;
data: TJSArray;
call: TJSObject;
callListLength: integer;
begin
if PageNumber > 0 then
begin
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.Search',
[searchOptions]));
callList := TJSObject(xdcResponse.Result);
data := TJSArray(callList['data']);
callListLength := integer(callList['count']);
ClearTable();
for i := 0 to data.Length - 1 do
begin
call := TJSObject(data[i]);
AddRowToTable(string(call['toNumber']), string(call['fromNumber']), string(call['dateCreated']),
string(call['duration']), string(call['transcription']), string(call['mediaUrl']));
end;
TotalPages := (callListLength + PageSize - 1) div PageSize;
lblEntries.Caption := 'Showing entries for phone number: ' + searchOptions;
end;
end;
procedure TFViewComplaints.btnSearchClick(Sender: TObject);
// calls Search method
begin
end;
procedure TFViewComplaints.ClearTable();
// clears the table
var
tbody: TJSHTMLElement;
begin
tbody := TJSHTMLElement(document.getElementById('tblPhoneGrid').getElementsByTagName('tbody')[0]);
tbody.innerHTML := '';
end;
function TFViewComplaints.GenerateSearchOptions(): string;
// Generates searchOptions for get calls.
var
searchOptions: string;
begin
PageSize := StrToInt(wcbPageSize.Text);
StartDate := dtpStartDate.Text;
EndDate := dtpEndDate.Text;
OrderBy := wcbSortBy.Text;
searchOptions := '&phonenumber=' + wcbLocation.Value +
'&pagenumber=' + IntToStr(PageNumber) +
'&pagesize=' + IntToStr(PageSize) +
'&startdate=' + StartDate +
'&enddate=' + EndDate +
'&orderby=' + OrderBy +
'&caller=' + Caller;
Result := searchOptions;
end;
end.
object FViewEditUser: TFViewEditUser
Width = 640
Height = 480
OnShow = WebFormCreate
object WebLabel1: TWebLabel
Left = 8
Top = 99
Width = 73
Height = 15
Caption = 'Make Admin?'
Color = clBtnFace
ElementID = 'lblAdmin'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel2: TWebLabel
Left = 16
Top = 8
Width = 57
Height = 15
Caption = 'Full Name:'
Color = clBtnFace
ElementID = 'lblfullname'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel3: TWebLabel
Left = 14
Top = 37
Width = 53
Height = 15
Caption = 'Password:'
Color = clBtnFace
ElementID = 'lblpassword'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel4: TWebLabel
Left = 6
Top = 62
Width = 84
Height = 15
Caption = 'Phone Number:'
Color = clBtnFace
ElementID = 'lblphone'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel5: TWebLabel
Left = 256
Top = 8
Width = 56
Height = 15
Caption = 'Username:'
Color = clBtnFace
ElementID = 'lblusername'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel6: TWebLabel
Left = 240
Top = 41
Width = 100
Height = 15
Caption = 'Confirm Password:'
Color = clBtnFace
ElementID = 'lblconfirm'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel7: TWebLabel
Left = 252
Top = 69
Width = 32
Height = 15
Caption = 'Email:'
Color = clBtnFace
ElementID = 'lblemail'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object lblactive: TWebLabel
Left = 237
Top = 95
Width = 38
Height = 15
Caption = 'Active?'
Color = clBtnFace
ElementID = 'lblactive'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object edtPhoneNumber: TWebEdit
Left = 96
Top = 62
Width = 121
Height = 22
ChildOrder = 7
ElementID = 'edtphonenumber'
HeightPercent = 100.000000000000000000
ShowHint = True
TextHint = '(555) 555-5555'
WidthPercent = 100.000000000000000000
end
object edtConfirmPassword: TWebEdit
Left = 348
Top = 34
Width = 121
Height = 22
ChildOrder = 7
ElementID = 'edtconfirmpassword'
HeightPercent = 100.000000000000000000
PasswordChar = '*'
WidthPercent = 100.000000000000000000
end
object edtEmail: TWebEdit
Left = 348
Top = 62
Width = 121
Height = 22
ChildOrder = 7
ElementID = 'edtemail'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object edtPassword: TWebEdit
Left = 96
Top = 34
Width = 121
Height = 22
ChildOrder = 13
ElementID = 'edtpassword'
HeightPercent = 100.000000000000000000
PasswordChar = '*'
WidthPercent = 100.000000000000000000
end
object cbAdmin: TWebCheckBox
Left = 96
Top = 96
Width = 107
Height = 20
Caption = 'Make Admin?'
ChildOrder = 12
ElementID = 'cbadminuser'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = [fsBold]
HeightPercent = 100.000000000000000000
ParentFont = False
WidthPercent = 100.000000000000000000
end
object btnConfirm: TWebButton
Left = 96
Top = 138
Width = 96
Height = 25
Caption = 'Confirm'
ChildOrder = 9
ElementClassName = 'btn btn-light'
ElementID = 'btnconfirm'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -20
Font.Name = 'Segoe UI'
Font.Style = []
HeightPercent = 100.000000000000000000
ParentFont = False
WidthPercent = 100.000000000000000000
OnClick = btnConfirmClick
end
object edtFullname: TWebEdit
Left = 96
Top = 4
Width = 121
Height = 22
ChildOrder = 14
ElementID = 'edtfullname'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object edtUsername: TWebEdit
Left = 346
Top = 4
Width = 121
Height = 22
ChildOrder = 14
ElementID = 'edtusername'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnCancel: TWebButton
Left = 238
Top = 138
Width = 96
Height = 25
Caption = 'Cancel'
ChildOrder = 9
ElementClassName = 'btn btn-light'
ElementID = 'btncancel'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -20
Font.Name = 'Segoe UI'
Font.Style = []
HeightPercent = 100.000000000000000000
ParentFont = False
WidthPercent = 100.000000000000000000
OnClick = btnCancelClick
end
object btnConfirmChanges: TWebButton
Left = 100
Top = 330
Width = 96
Height = 25
Caption = 'Confirm'
ChildOrder = 16
ElementID = 'btn_confirm_changes'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnConfirmChangesClick
end
object pnlMessage: TWebPanel
Left = 482
Top = 4
Width = 121
Height = 33
ElementID = 'view.login.message'
ChildOrder = 17
TabOrder = 10
object lblMessage: TWebLabel
Left = 16
Top = 11
Width = 46
Height = 15
Caption = 'Message'
ElementID = 'view.login.message.label'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnCloseNotification: TWebButton
Left = 96
Top = 3
Width = 22
Height = 25
ChildOrder = 1
ElementID = 'view.login.message.button'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnCloseNotificationClick
end
end
object cbActive: TWebCheckBox
Left = 346
Top = 94
Width = 107
Height = 20
Caption = 'Active?'
ChildOrder = 12
ElementID = 'cbactive'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = [fsBold]
HeightPercent = 100.000000000000000000
ParentFont = False
WidthPercent = 100.000000000000000000
end
object XDataWebClient1: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 462
Top = 164
end
object WebTimer1: TWebTimer
Enabled = False
Interval = 500
OnTimer = WebTimer1Timer
Left = 236
Top = 194
end
end
<div class="row">
<div class="col-12">
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 col-md-8 col-lg-8">
<h1 class="page-header pt-3 pb-2 mb-3 border-bottom fs-4 fw-bold" id="view.editusers.title">Update/Add User</h1>
<div class="row">
<div class=col-sm>
<div id="view.login.message" class="alert alert-danger">
<button id="view.login.message.button" type="button" class="btn-close" aria-label="Close"></button>
<span id="view.login.message.label"></span>
</div>
</div>
</div>
<form class="form-inline">
<div class="row">
<div class="col-sm">
<label class= 'pe-2' style="font-weight: 700;font-size: 15px;"id="lblfullname">Full Name:</label>
<input id="edtfullname" class= "form-control input-sm" width='50%'/>
</div>
<div class="col-sm">
<label class= 'pe-2' style="font-weight: 700;font-size: 15px"id="lblusername">Username:</label>
<input id="edtusername" class="form-control input-sm" width='50%'>
</div>
</div>
</form>
<form class="form-inline">
<div class="row">
<div class="col-sm">
<label class='pe-2' style="font-weight: 700;font-size: 15px;"id="lblpassword">Password:</label>
<input id="edtpassword" class= "form-control input-sm" width='50%'/>
</div>
<div class="col-sm">
<label class= 'pe-2' style="font-weight: 700;font-size: 15px"id="lblconfirm">Confirm Password:</label>
<input class="form-control input-sm" id="edtconfirmpassword">
</div>
</div>
</form>
<form class="form-inline">
<div class="row">
<div class="col-sm">
<label class='pe-2' style="font-weight: 700;font-size: 15px;" id="lblphone">Phone Number:</label>
<input id="edtphonenumber" class= "form-control input-sm" width='50%'/>
</div>
<div class="col-sm">
<label class= 'pe-2' style="font-weight: 700;font-size: 15px"id="lblemail">Email Address:</label>
<input class="form-control input-sm" id="edtemail">
</div>
</div>
</form>
<div class="row">
<div class="col-sm">
<form class='form-inline'>
<div class="col-sm">
<div class="form-cells"><input type="checkbox" id="cbadminuser"></div>
<div class="form-cells ps-1 py-2"><label style="font-weight: 700;font-size: 15px" id="lblAdmin">Make Admin?</label></div>
</div>
<div class="col-sm">
<div class="form-cells"><input type="checkbox" id="cbactive"></div>
<div class="form-cells ps-1 py-2"><label style="font-weight: 700;font-size: 15px" id="lblactive">Active></label></div>
</div>
</form>
</div>
<div class="col-sm-12 py-2">
<button class="py-2" id="btnconfirm" style="font-weight: 700;font-size: 15px";>Confirm</button>
<button class="py-2" id="btncancel" style="font-weight: 700;font-size: 15px";>Cancel</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" id="confirmation_modal" tabindex="-1" aria-labelledby="confirmation_modal_label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmation_modal_label">Confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to make these changes?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" id="btn_confirm_changes">Confirm</button>
</div>
</div>
</div>
</div>
// Form that functions as both a way to edit or add users to the database
// Author: Cameron Hayes
unit View.EditUser;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls,
WEBLib.DBCtrls, XData.Web.Client, WEBLib.ExtCtrls, Vcl.Menus, WEBLib.Menus;
type
TFViewEditUser = class(TWebForm)
WebLabel1: TWebLabel;
WebLabel2: TWebLabel;
WebLabel3: TWebLabel;
WebLabel4: TWebLabel;
WebLabel5: TWebLabel;
WebLabel6: TWebLabel;
WebLabel7: TWebLabel;
edtPhoneNumber: TWebEdit;
edtConfirmPassword: TWebEdit;
edtEmail: TWebEdit;
edtPassword: TWebEdit;
cbAdmin: TWebCheckBox;
btnConfirm: TWebButton;
edtFullname: TWebEdit;
edtUsername: TWebEdit;
XDataWebClient1: TXDataWebClient;
btnCancel: TWebButton;
WebTimer1: TWebTimer;
btnConfirmChanges: TWebButton;
pnlMessage: TWebPanel;
lblMessage: TWebLabel;
btnCloseNotification: TWebButton;
lblactive: TWebLabel;
cbActive: TWebCheckBox;
procedure WebFormCreate(Sender: TObject);
procedure btnConfirmClick(Sender: TObject);
procedure btnCancelClick(Sender: TObject);
procedure WebTimer1Timer(Sender: TObject);
[async] procedure btnConfirmChangesClick(Sender: TObject);
procedure btnCloseNotificationClick(Sender: TObject);
private
{ Private declarations }
FMessage: string;
Mode: string;
Username: string;
FullName: string;
Phone: string;
Email: string;
Admin: boolean;
Active: boolean;
[async] procedure EditUser();
[async] function AddUser(): string;
procedure HideNotification();
procedure ShowNotification(notification: string);
public
{ Public declarations }
Info: string;
class function CreateForm(AElementID, Mode, Username, FullName, Phone, Email: string; Admin, Active: boolean): TWebForm;
end;
var
FViewEditUser: TFViewEditUser;
implementation
uses
Windows,
View.Main,
View.Users,
VCL.Dialogs,
ConnectionModule,
Utils;
procedure TFViewEditUser.btnCancelClick(Sender: TObject);
// Cancels the edit or addition
begin
Info := 'Failure:Changes discarded!';
FViewMain.ShowUserForm(Info);
end;
procedure TFViewEditUser.btnCloseNotificationClick(Sender: TObject);
begin
HideNotification;
end;
[async]
procedure TFViewEditUser.btnConfirmChangesClick(Sender: TObject);
begin
console.log('btnConfirmChangesClick triggered');
Utils.ShowSpinner('spinner');
if Mode = 'Edit' then
begin
console.log('Calling EditUser()');
await(EditUser());
end
else
begin
console.log('Calling AddUser()');
await(AddUser());
end;
console.log('Info after operation: ', Info);
WebTimer1.Enabled := true;
end;
[async] function TFViewEditUser.AddUser(): string;
var
userInfo: string;
xdcResponse: TXDataClientResponse;
responseString: TJSObject;
begin
userInfo := '&username=' + string(edtUsername.Text).ToLower +
'&password=' + edtPassword.Text +
'&fullname=' + edtFullName.Text +
'&phonenumber=' + edtPhoneNumber.Text +
'&email=' + edtEmail.Text +
'&admin=' + BoolToStr(cbAdmin.Checked);
console.log('AddUser -> userInfo: ', userInfo);
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.AddUser', [userInfo]));
responseString := TJSObject(xdcResponse.Result);
Info := string(responseString['value']);
console.log('AddUser -> response Info: ', Info);
end;
procedure TFViewEditUser.HideNotification;
begin
pnlMessage.ElementHandle.hidden := True;
end;
procedure TFViewEditUser.ShowNotification(Notification: string);
begin
if Notification <> '' then
begin
lblMessage.Caption := Notification;
pnlMessage.ElementHandle.hidden := False;
end;
end;
[async] procedure TFViewEditUser.EditUser();
var
editOptions: string;
xdcResponse: TXDataClientResponse;
responseString: TJSObject;
begin
editOptions := 'username=' + Username +
'&fullname=' + edtFullName.Text +
'&phonenumber=' + edtPhoneNumber.Text +
'&email=' + edtEmail.Text +
'&admin=' + BoolToStr(cbAdmin.Checked) +
'&newuser=' + edtUsername.Text +
'&password=' + edtPassword.Text +
'&active=' + BoolToStr(cbActive.Checked);
console.log('EditUser -> editOptions: ', editOptions);
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.EditUser', [editOptions]));
responseString := TJSObject(xdcResponse.Result);
Info := string(responseString['value']);
console.log('EditUser -> response Info: ', Info);
end;
class function TFViewEditUser.CreateForm(AElementID, Mode, Username, FullName, Phone, Email: string; Admin, Active: boolean): TWebForm;
// Autofills known information about a user on create
procedure AfterCreate(AForm: TObject);
begin
TFViewEditUser(AForm).Mode := Mode;
TFViewEditUser(AForm).Username := Username;
TFViewEditUser(AForm).FullName := FullName;
TFViewEditUser(AForm).Phone := Phone;
TFViewEditUser(AForm).Email:= Email;
TFViewEditUser(AForm).Admin := Admin;
TFViewEditUser(AForm).Active := Active;
end;
{$R *.dfm}
begin
Application.CreateForm(TFViewEditUser, AElementID, Result, @AfterCreate);
end;
procedure TFViewEditUser.WebFormCreate(Sender: TObject);
// Autofills known information about a user on create
begin
if not Assigned(DMConnection) then
Application.CreateForm(TDMConnection, DMConnection);
if FMessage <> '' then
ShowNotification(FMessage)
else
HideNotification;
edtUsername.Text := Username;
edtFullName.Text := FullName;
if Mode = 'Edit' then
begin
edtPassword.Text := 'Hidden';
edtConfirmPassword.Text := 'Hidden';
end
else
edtPhoneNumber.Text := Phone;
edtEmail.Text := Email;
cbAdmin.checked := Admin;
cbActive.Checked := Active;
end;
procedure TFViewEditUser.WebTimer1Timer(Sender: TObject);
begin
WebTimer1.Enabled := False;
Utils.HideSpinner('spinner');
console.log('WebTimer1Timer triggered. Info = ', Info);
if (not Info.Contains('Failure')) then
begin
console.log('Navigating back to user list...');
FViewMain.ShowUserForm(Info);
end
else
begin
console.log('Showing notification instead of redirect');
showNotification(Info);
end;
end;
procedure TFViewEditUser.btnConfirmClick(Sender: TObject);
var
checkString: string;
charIndex: Integer;
phoneNum: string;
begin
// Combine all inputs to quickly check for illegal characters like &
checkString := edtFullName.Text + edtUsername.Text + edtPassword.Text +
edtConfirmPassword.Text + edtPhoneNumber.Text + edtEmail.Text;
// Basic field presence checks
if edtFullName.Text.Trim = '' then
begin
ShowNotification('Full Name field is blank!');
Exit;
end;
if edtUsername.Text.Trim = '' then
begin
ShowNotification('Username field is blank!');
Exit;
end;
if edtPassword.Text.Trim = '' then
begin
ShowNotification('Password field is blank!');
Exit;
end;
if edtConfirmPassword.Text.Trim = '' then
begin
ShowNotification('Please confirm your password!');
Exit;
end;
if edtPhoneNumber.Text.Trim = '' then
begin
ShowNotification('Phone Number field is blank!');
Exit;
end;
if edtEmail.Text.Trim = '' then
begin
ShowNotification('Email field is blank!');
Exit;
end;
// Special character check
if checkString.Contains('&') then
begin
ShowNotification('No fields may contain "&"!');
Exit;
end;
// Email format validation
if not edtEmail.Text.Contains('@') or
(TJSArray(edtEmail.Text.Split(['@'])).length <> 2) or
(edtEmail.Text.CountChar('@') > 1) then
begin
ShowNotification('Please enter a valid email address');
Exit;
end;
// Phone number validation (allow spaces, dashes, parentheses, etc.)
phoneNum := edtPhoneNumber.Text;
phoneNum := phoneNum.Replace('(', '')
.Replace(')', '')
.Replace('-', '')
.Replace(' ', '')
.Trim;
if Length(phoneNum) <> 10 then
begin
ShowNotification('Please enter a valid phone number');
Exit;
end;
for charIndex := 1 to Length(phoneNum) do
begin
if not (phoneNum[charIndex] in ['0' .. '9']) then
begin
ShowNotification('Please enter a valid phone number');
Exit;
end;
end;
// Password match and length validation
if edtPassword.Text <> edtConfirmPassword.Text then
begin
ShowNotification('Passwords must match!');
Exit;
end;
if (Length(edtPassword.Text) > 20) or (Length(edtPassword.Text) < 6) then
begin
ShowNotification('Passwords must be between 620 characters!');
Exit;
end;
// All validations passed, show modal
asm
var modal = document.getElementById('confirmation_modal');
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal);
}
var bsModal = new bootstrap.Modal(modal, { keyboard: false });
bsModal.show();
end;
end;
end.
object FViewEditUser2: TFViewEditUser2
Width = 640
Height = 480
Color = clBtnFace
end
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>TMS Web Project</title>
<style>
</style>
</head>
<body>
</body>
</html>
\ No newline at end of file
unit View.EditUser2;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls,
WEBLib.DBCtrls;
type
TFViewEditUser2 = class(TWebForm)
private
{ Private declarations }
UserID: string;
Username: string;
FullName: string;
Phone: string;
Email: string;
Admin: boolean;
public
{ Public declarations }
class function CreateForm(AElementID, AParam, BParam, CParam, DParam, EParam: string; FParam: boolean): TWebForm;
end;
var
FViewEditUser2: TFViewEditUser2;
implementation
class function TFViewEditUser2.CreateForm(AElementID, AParam, BParam, CParam, DParam, EParam: string; FParam: boolean): TWebForm;
procedure AfterCreate(AForm: TObject);
begin
{TFViewEditUser(AForm).UserID := AParam;
TFViewEditUser(AForm).Username := BParam;
TFViewEditUser(AForm).FullName := CParam;
TFViewEditUser(AForm).Phone := DParam;
TFViewEditUser(AForm).Email:= EParam;
TFViewEditUser(AForm).Admin := FParam;}
end;
{$R *.dfm}
begin
//Application.CreateForm(TFViewEditUser, AElementID, Result, @AfterCreate);
end;
end.
\ No newline at end of file
object FViewErrorPage: TFViewErrorPage
Left = 0
Top = 0
Width = 534
Height = 426
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
TabOrder = 1
object lbTitle: TWebLabel
Left = 24
Top = 24
Width = 128
Height = 13
Caption = 'Oops... an error occurred!'
ElementID = 'view.errorpage.title'
Transparent = False
end
object lbMessage: TWebLabel
Left = 24
Top = 56
Width = 42
Height = 13
Caption = 'Message'
ElementID = 'view.errorpage.message'
Transparent = False
end
end
<div class="container">
<br />
<div class="panel panel-red">
<div id="view.errorpage.title" class="panel-heading">
Error Page
</div>
<div id="view.errorpage.message" class="panel-body">
Message
</div>
<div class="panel-footer">
<a href=".">Reload web application</a>
</div>
</div>
</div>
unit View.ErrorPage;
interface
uses
System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
Vcl.Controls, Vcl.StdCtrls,
XData.Web.Connection, WEBLib.StdCtrls;
type
TFViewErrorPage = class(TWebForm)
lbTitle: TWebLabel;
lbMessage: TWebLabel;
public
class procedure Display(AErrorMessage: string);
class procedure DisplayConnectionError(AError: TXDataWebConnectionError);
end;
var
FViewErrorPage: TFViewErrorPage;
implementation
{$R *.dfm}
{ TFViewErrorPage }
class procedure TFViewErrorPage.Display(AErrorMessage: string);
procedure AfterCreateProc(AForm: TObject);
begin
TFViewErrorPage(AForm).lbMessage.Caption := AErrorMessage;
end;
begin
if Assigned(FViewErrorPage) then
FViewErrorPage.Free;
FViewErrorPage := TFViewErrorPage.CreateNew(@AfterCreateProc);
end;
class procedure TFViewErrorPage.DisplayConnectionError(
AError: TXDataWebConnectionError);
begin
Display(AError.ErrorMessage + ': ' + AError.RequestUrl);
end;
end.
object FViewLogin: TFViewLogin
Width = 640
Height = 480
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
ParentFont = False
OnCreate = WebFormCreate
object WebLabel1: TWebLabel
Left = 240
Top = 112
Width = 67
Height = 13
Caption = 'Please Sign In'
ElementID = 'view.login.title'
HeightPercent = 100.000000000000000000
Transparent = False
WidthPercent = 100.000000000000000000
end
object edtUsername: TWebEdit
Left = 240
Top = 136
Width = 121
Height = 21
ElementID = 'view.login.edtusername'
HeightPercent = 100.000000000000000000
TextHint = 'Username'
WidthPercent = 100.000000000000000000
end
object edtPassword: TWebEdit
Left = 240
Top = 163
Width = 121
Height = 21
ElementID = 'view.login.edtpassword'
HeightPercent = 100.000000000000000000
PasswordChar = '*'
TabOrder = 1
TextHint = 'Password'
WidthPercent = 100.000000000000000000
end
object btnLogin: TWebButton
Left = 240
Top = 217
Width = 121
Height = 25
Caption = 'Login'
ElementID = 'view.login.btnlogin'
HeightPercent = 100.000000000000000000
TabOrder = 2
WidthPercent = 100.000000000000000000
OnClick = btnLoginClick
end
object pnlMessage: TWebPanel
Left = 240
Top = 65
Width = 121
Height = 33
ElementID = 'view.login.message'
TabOrder = 3
object lblMessage: TWebLabel
Left = 16
Top = 11
Width = 42
Height = 13
Caption = 'Message'
ElementID = 'view.login.message.label'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnCloseNotification: TWebButton
Left = 96
Top = 3
Width = 22
Height = 25
ElementID = 'view.login.message.button'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnCloseNotificationClick
end
end
object lucbAgency: TWebLookupComboBox
Left = 240
Top = 190
Width = 121
Height = 21
ElementID = 'view.login.edtagency'
HeightPercent = 100.000000000000000000
TextHint = 'Agency'
WidthPercent = 100.000000000000000000
ItemIndex = -1
LookupValues = <
item
Value = 'BUF'
DisplayText = 'BUF - Buffalo Police Department'
end>
end
object XDataWebClient: TXDataWebClient
Connection = DMConnection.AuthConnection
Left = 492
Top = 102
end
end
<div class="container">
<nav class="navbar navbar-light bg-light border rounded-bottom"
style="--bs-border-color: #d6d6d6; --bs-border-radius: 0.25rem;">
<div class="container-fluid">
<a id="view.login.apptitle"
class="navbar-brand text-secondary small"
href="index.html">
emiMobile
</a>
</div>
</nav>
<div class="row justify-content-center mt-4">
<div class="col-12 col-sm-10 col-md-8 col-lg-5 col-xl-4">
<div class="card shadow-sm">
<div class="card-header">
<h5 id="view.login.title" class="card-title mb-0 text-center">
Please Sign In
</h5>
</div>
<div class="card-body">
<div id="view.login.message"
class="alert alert-danger d-flex align-items-start d-none"
role="alert">
<span id="view.login.message.label" class="me-auto"></span>
<button id="view.login.message.button"
type="button"
class="btn-close ms-2"
aria-label="Close"></button>
</div>
<div class="mb-3">
<input id="view.login.edtusername"
class="form-control"
type="text"
placeholder="Username"
autofocus>
</div>
<div class="mb-3">
<input id="view.login.edtpassword"
class="form-control"
type="password"
placeholder="Password">
</div>
<div class="mb-3">
<select id="view.login.edtagency" class="form-select">
<!-- populated dynamically -->
</select>
</div>
<button id="view.login.btnlogin"
class="btn btn-primary w-100">
Login
</button>
</div>
<div class="card-footer text-muted small">
Please use your CHARMS username &amp; password to login.<br><br>
For non-CHARMS users, please call the CPS Help Desk at
(716) 858-7773 during business hours<br>
or email: <a href="mailto:CPSHelpdesk@erie.gov">CPSHelpdesk@erie.gov</a>
</div>
</div>
</div>
</div>
</div>
unit View.Login;
interface
uses
System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.JSON,
JS, XData.Web.Connection, WEBLib.ExtCtrls,
App.Types, ConnectionModule, XData.Web.Client;
type
TFViewLogin = class(TWebForm)
WebLabel1: TWebLabel;
edtUsername: TWebEdit;
edtPassword: TWebEdit;
btnLogin: TWebButton;
pnlMessage: TWebPanel;
lblMessage: TWebLabel;
btnCloseNotification: TWebButton;
XDataWebClient: TXDataWebClient;
lucbAgency: TWebLookupComboBox;
procedure btnLoginClick(Sender: TObject);
procedure btnCloseNotificationClick(Sender: TObject);
procedure WebFormCreate(Sender: TObject);
private
FLoginProc: TSuccessProc;
FMessage: string;
procedure ShowNotification(Notification: string);
procedure HideNotification;
procedure GetAgencyConfigList;
public
class procedure Display(LoginProc: TSuccessProc); overload;
class procedure Display(LoginProc: TSuccessProc; AMsg: string); overload;
end;
var
FViewLogin: TFViewLogin;
implementation
uses
Auth.Service,
View.ErrorPage;
{$R *.dfm}
class procedure TFViewLogin.Display(LoginProc: TSuccessProc);
begin
TFViewLogin.Display(LoginProc, '');
end;
class procedure TFViewLogin.Display(LoginProc: TSuccessProc; AMsg: string);
procedure FormCreate(AForm: TObject);
begin
TFViewLogin(AForm).FMessage := AMsg;
end;
begin
if Assigned(FViewLogin) then
FViewLogin.Free;
FViewLogin := TFViewLogin.CreateNew(@FormCreate);
FViewLogin.FLoginProc := LoginProc;
end;
procedure TFViewLogin.WebFormCreate(Sender: TObject);
begin
// lblAppTitle.Caption := 'EM Systems - webCharms App ver 0.9.2.22';
GetAgencyConfigList();
if FMessage <> '' then
ShowNotification(FMessage)
else
HideNotification;
end;
procedure TFViewLogin.btnLoginClick(Sender: TObject);
procedure LoginSuccess;
begin
FLoginProc;
end;
procedure LoginError(AMsg: string);
begin
ShowNotification('Login Error: ' + AMsg);
end;
begin
AuthService.Login(
edtUsername.Text, edtPassword.Text, lucbAgency.Value,
@LoginSuccess,
@LoginError
);
end;
procedure TFViewLogin.GetAgencyConfigList;
procedure OnLoad(Response: TXDataClientResponse);
var
jsResponse: TJSObject;
count: Integer;
returned: Integer;
jsArray: TJSArray;
jsObject: TJSObject;
agency: string;
name: string;
i: Integer;
begin
jsResponse := TJSObject(Response.Result);
count := Integer(jsResponse['count']);
returned := Integer(jsResponse['returned']);
jsArray := TJSArray(TJSObject(Response.Result)['data']);
lucbAgency.LookupValues.Clear;
for i := 0 to jsArray.Length - 1 do
begin
jsObject := TJSObject( jsArray[i] );
agency := string( jsObject['agency'] );
name := string( jsObject['name'] );
lucbAgency.LookupValues.AddPair( agency, agency + ' - ' + name );
end;
end;
procedure OnError(Error: TXDataClientError);
begin
ShowNotification('GetAgencyConfigList: ' + Format('%s: %s', [Error.ErrorCode, Error.ErrorMessage]));
end;
begin
XDataWebClient.RawInvoke(
'IAuthService.GetAgencyConfigList', [],
@OnLoad, @OnError
);
end;
procedure TFViewLogin.ShowNotification(Notification: string);
begin
if Notification <> '' then
begin
lblMessage.Caption := Notification;
pnlMessage.ElementHandle.hidden := False;
end;
end;
procedure TFViewLogin.HideNotification;
begin
pnlMessage.ElementHandle.hidden := True;
end;
procedure TFViewLogin.btnCloseNotificationClick(Sender: TObject);
begin
HideNotification;
end;
end.
object FViewMain: TFViewMain
Width = 640
Height = 586
CSSLibrary = cssBootstrap
ElementFont = efCSS
OnCreate = WebFormCreate
object lblUsername: TWebLabel
Left = 529
Top = 4
Width = 66
Height = 15
Caption = 'lblUsername'
ElementID = 'view.main.username'
HeightPercent = 100.000000000000000000
Transparent = False
Visible = False
WidthPercent = 100.000000000000000000
end
object wllblUserProfile: TWebLinkLabel
Left = 529
Top = 21
Width = 63
Height = 15
ElementID = 'dropdown.menu.userprofile'
Visible = False
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = wllblUserProfileClick
Caption = ' User Profile'
end
object wllblLogout: TWebLinkLabel
Left = 551
Top = 85
Width = 41
Height = 15
ElementID = 'dropdown.menu.logout'
Visible = False
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = wllblLogoutClick
Caption = ' Logout'
end
object lblAppTitle: TWebLabel
Left = 57
Top = 31
Width = 57
Height = 15
Caption = 'emiMobile'
ElementID = 'view.main.apptitle'
HeightPercent = 100.000000000000000000
Transparent = False
WidthPercent = 100.000000000000000000
end
object lblCallsList: TWebLinkLabel
Left = 564
Top = 58
Width = 25
Height = 15
ElementID = 'dropdown.menu.callslist'
ElementFont = efCSS
Visible = False
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = lblCallsListClick
Caption = 'Calls'
end
object lblUsers: TWebLinkLabel
Left = 561
Top = 70
Width = 28
Height = 15
ElementID = 'dropdown.menu.users'
ElementFont = efCSS
Visible = False
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = lblUsersClick
Caption = 'Users'
end
object WebPanel1: TWebPanel
Left = 136
Top = 110
Width = 471
Height = 369
ElementID = 'main.webpanel'
ChildOrder = 3
TabOrder = 0
end
object WebMessageDlg1: TWebMessageDlg
Left = 47
Top = 232
Width = 24
Height = 24
Buttons = []
CustomButtons = <>
Opacity = 0.200000000000000000
end
object WebMemo1: TWebMemo
Left = 136
Top = 467
Width = 471
Height = 83
ElementID = 'main.debugmemo'
HeightPercent = 100.000000000000000000
Lines.Strings = (
'WebMemo1')
SelLength = 0
SelStart = 0
Visible = False
WidthPercent = 100.000000000000000000
end
object btnMap: TWebButton
Left = 148
Top = 58
Width = 59
Height = 25
Caption = 'Map'
ChildOrder = 9
ElementID = 'view.main.btnmap'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnMapClick
end
object btnComplaints: TWebButton
Left = 213
Top = 58
Width = 68
Height = 25
Caption = 'Complaints'
ChildOrder = 9
ElementID = 'view.main.btncomplaints'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnComplaintsClick
end
object btnUnits: TWebButton
Left = 294
Top = 58
Width = 59
Height = 25
Caption = 'Units'
ChildOrder = 9
ElementID = 'view.main.btnunits'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnUnitsClick
end
object XDataWebClient: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 44
Top = 280
end
end
<div class="d-flex flex-column vh-100">
<!-- Top Nav -->
<nav class="navbar navbar-light bg-primary border-light text-light fixed-top">
<div class="container-fluid">
<!-- Left: Font button -->
<button id="view.main.btnfont" type="button" class="btn btn-outline-primary text-light border-light btn-sm">
font
</button>
<!-- Center: App title -->
<a id="view.main.apptitle" class="navbar-brand text-light ms-3" href="index.html">emiMobile</a>
<!-- Right: Connection label -->
<span id="view.main.lblconnection" class="navbar-text text-light ms-auto">Connected</span>
</div>
</nav>
<!-- Main content: fills space between navbars -->
<main id="main.webpanel" class="flex-grow-1 overflow-auto mt-5 mb-5">
<!-- TWebPanel content gets injected here -->
</main>
<!-- Bottom Nav -->
<nav class="navbar navbar-dark bg-primary fixed-bottom py-2">
<div class="container-fluid">
<div class="d-flex justify-content-center gap-3 w-100">
<button id="view.main.btnmap" type="button" class="btn btn-primary">
<i class="far fa-map me-1"></i>Map
</button>
<button id="view.main.btncomplaints" type="button" class="btn btn-primary position-relative">
<i class="fa fa-exclamation-circle me-1"></i>Complaints
<span id="view.main.badgecomplaints"
class="position-absolute top-0 start-100 translate-middle mt-1 badge rounded-pill bg-danger">0</span>
</button>
<button id="view.main.btnunits" type="button" class="btn btn-primary position-relative">
<i class="fa fa-users me-1"></i>Units
<span id="view.main.badgeunits"
class="position-absolute top-0 start-100 translate-middle mt-1 badge rounded-pill bg-danger">0</span>
</button>
</div>
</div>
</nav>
</div>
<!-- Spinner -->
<div id="spinner" class="position-absolute top-50 start-50 translate-middle d-none">
<div class="lds-roller">
<div></div><div></div><div></div><div></div>
<div></div><div></div><div></div><div></div>
</div>
</div>
<!-- Error modal -->
<div class="modal fade" id="main_errormodal" tabindex="-1" aria-labelledby="main_lblmodal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content shadow-lg">
<div class="modal-header">
<h5 class="modal-title" id="main_lblmodal">Error</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_lblmodal_body">
Please contact EMSystems to solve the issue.
</div>
<div class="modal-footer justify-content-center">
<button type="button" id="btn_modal_restart" class="btn btn-primary">Back to Orders</button>
</div>
</div>
</div>
</div>
<!-- Confirmation modal -->
<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">Placeholder text</div>
<div class="modal-footer justify-content-center">
<button type="button" class="btn btn-primary me-3" id="btn_confirm_left">Cancel</button>
<button type="button" class="btn btn-secondary" id="btn_confirm_right">Confirm</button>
</div>
</div>
</div>
</div>
unit View.Main;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs, WEBLib.ExtCtrls, Vcl.Controls, Vcl.StdCtrls,
WEBLib.StdCtrls, Data.DB, XData.Web.JsonDataset, XData.Web.Dataset,
App.Types, ConnectionModule, XData.Web.Client;
type
TFViewMain = class(TWebForm)
lblUsername: TWebLabel;
wllblUserProfile: TWebLinkLabel;
wllblLogout: TWebLinkLabel;
WebPanel1: TWebPanel;
WebMessageDlg1: TWebMessageDlg;
lblAppTitle: TWebLabel;
WebMemo1: TWebMemo;
XDataWebClient: TXDataWebClient;
lblCallsList: TWebLinkLabel;
lblUsers: TWebLinkLabel;
btnMap: TWebButton;
btnComplaints: TWebButton;
btnUnits: TWebButton;
procedure WebFormCreate(Sender: TObject);
procedure mnuLogoutClick(Sender: TObject);
procedure wllblUserProfileClick(Sender: TObject);
procedure wllblLogoutClick(Sender: TObject);
procedure lblCallsListClick(Sender: TObject);
procedure lblUsersClick(Sender: TObject);
procedure btnUnitsClick(Sender: TObject);
procedure btnComplaintsClick(Sender: TObject);
procedure btnMapClick(Sender: TObject);
private
{ Private declarations }
FUserInfo: string;
FSearchSettings: string;
FChildForm: TWebForm;
FLogoutProc: TLogoutProc;
FSearchProc: TSearchProc;
procedure ShowCrudForm( AFormClass: TWebFormClass );
//procedure EditUser( AParam, BParam, CParam, DParam, EParam: string);
function GetUserInfo: string;
procedure SetActiveNavButton(const BtnId: string);
public
{ Public declarations }
class procedure Display(LogoutProc: TLogoutProc);
procedure ShowForm( AFormClass: TWebFormClass );
procedure EditUser( Mode, FullName, Username, Phone, Email: string; admin, active: boolean);
procedure ShowUserForm(Info: string);
end;
var
FViewMain: TFViewMain;
implementation
uses
Auth.Service,
View.Login,
View.UserProfile,
View.Map,
View.Complaints,
View.Units,
View.Admin,
View.Users,
View.EditUser;
{$R *.dfm}
procedure TFViewMain.WebFormCreate(Sender: TObject);
var
userName: string;
begin
FUserInfo := GetUserInfo;
userName := JS.toString(AuthService.TokenPayload.Properties['user_name']);
lblUsername.Caption := ' ' + userName.ToLower + ' ';
FChildForm := nil;
if (not (JS.toBoolean(AuthService.TokenPayload.Properties['user_admin']))) then
lblUsers.Visible := false;
ShowForm(TFViewMap);
end;
procedure TFViewMain.lblUsersClick(Sender: TObject);
begin
ShowForm(TFViewUsers);
end;
procedure TFViewMain.lblCallsListClick(Sender: TObject);
begin
ShowForm(TFViewComplaints);
end;
procedure TFViewMain.mnuLogoutClick(Sender: TObject);
begin
FLogoutProc;
end;
procedure TFViewMain.wllblLogoutClick(Sender: TObject);
begin
FLogoutProc;
end;
procedure TFViewMain.wllblUserProfileClick(Sender: TObject);
begin
ShowCrudForm(TFViewUserProfile);
end;
function TFViewMain.GetUserInfo: string;
var
userStr: string;
begin
userStr := '?username=' + JS.toString(AuthService.TokenPayload.Properties['user_name']);
userStr := userStr + '&fullname=' + JS.toString(AuthService.TokenPayload.Properties['user_fullname']);
userStr := userStr + '&agency=' + JS.toString(AuthService.TokenPayload.Properties['user_agency']);
userStr := userStr + '&badge=' + JS.toString(AuthService.TokenPayload.Properties['user_badge']);
userStr := userStr + '&userid=' + JS.toString(AuthService.TokenPayload.Properties['user_id']);
userStr := userStr + '&personnelid=' + JS.toString(AuthService.TokenPayload.Properties['user_personnelid']);
Result := userStr;
end;
procedure TFViewMain.btnComplaintsClick(Sender: TObject);
begin
SetActiveNavButton('view.main.btncomplaints');
ShowForm(TFViewComplaints);
end;
procedure TFViewMain.btnMapClick(Sender: TObject);
begin
SetActiveNavButton('view.main.btnmap');
ShowForm(TFViewMap);
end;
procedure TFViewMain.btnUnitsClick(Sender: TObject);
begin
SetActiveNavButton('view.main.btnunits');
ShowForm(TFViewUnits);
end;
class procedure TFViewMain.Display(LogoutProc: TLogoutProc);
begin
if Assigned(FViewMain) then
FViewMain.Free;
FViewMain := TFViewMain.CreateNew;
FViewMain.FLogoutProc := LogoutProc;
end;
procedure TFViewMain.ShowCrudForm(AFormClass: TWebFormClass);
begin
ShowForm(AFormClass);
end;
procedure TFViewMain.ShowForm(AFormClass: TWebFormClass);
begin
if Assigned(FChildForm) then
FChildForm.Free;
Application.CreateForm(AFormClass, WebPanel1.ElementID, FChildForm);
end;
procedure TFViewMain.EditUser( Mode, FullName, Username, Phone, Email: string; Admin, Active: boolean);
begin
if Assigned(FChildForm) then
FChildForm.Free;
FChildForm := TFViewEditUser.CreateForm(WebPanel1.ElementID, Mode, FullName, Username, Phone, Email, Admin, Active);
end;
procedure TFViewMain.ShowUserForm(Info: string);
begin
if Assigned(FChildForm) then
FChildForm.Free;
FChildForm := TFViewUsers.CreateForm(WebPanel1.ElementID, Info);
end;
procedure TFViewMain.SetActiveNavButton(const btnId: string);
var
m, c, u: TJSHTMLElement;
begin
m := TJSHTMLElement(btnMap.ElementHandle);
c := TJSHTMLElement(btnComplaints.ElementHandle);
u := TJSHTMLElement(btnUnits.ElementHandle);
// clear
m.classList.remove('active');
c.classList.remove('active');
u.classList.remove('active');
// set
if btnId = 'view.main.btnmap' then
m.classList.add('active')
else if btnId = 'view.main.btncomplaints' then
c.classList.add('active')
else if btnId = 'view.main.btnunits' then
u.classList.add('active');
end;
end.
object FViewMap: TFViewMap
Width = 475
Height = 802
ElementFont = efCSS
object btnMenu: TWebButton
Left = 62
Top = 66
Width = 41
Height = 25
Caption = 'Menu'
ChildOrder = 1
ElementID = 'map.btnmenu'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnAlerts: TWebButton
Left = 148
Top = 66
Width = 35
Height = 25
Caption = 'Alerts'
ChildOrder = 1
ElementID = 'map.btnalerts'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnGroups: TWebButton
Left = 194
Top = 66
Width = 41
Height = 25
Caption = 'Groups'
ChildOrder = 1
ElementID = 'map.btngroups'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnLocate: TWebButton
Left = 246
Top = 66
Width = 39
Height = 25
Caption = 'Locate'
ChildOrder = 1
ElementID = 'map.btnlocate'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnFilters: TWebButton
Left = 297
Top = 66
Width = 35
Height = 25
Caption = 'Filters'
ChildOrder = 1
ElementID = 'map.btnfilters'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnDisplay: TWebButton
Left = 351
Top = 66
Width = 40
Height = 25
Caption = 'Display'
ChildOrder = 1
ElementID = 'map.btndisplay'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object pnlMap: TWebPanel
Left = 62
Top = 120
Width = 335
Height = 555
ElementID = 'map_pnlmap'
Caption = 'pnlMap'
ChildOrder = 7
TabOrder = 6
object lfMap: TTMSFNCLeaflet
Left = 0
Top = 0
Width = 335
Height = 555
Align = alClient
ParentDoubleBuffered = False
DoubleBuffered = True
TabStop = False
TabOrder = 0
OnMapInitialized = lfMapMapInitialized
Polylines = <>
Polygons = <>
Circles = <>
Rectangles = <>
Markers = <>
Options.DefaultLatitude = 42.880230000000000000
Options.DefaultLongitude = -78.878738000000000000
Options.DefaultZoomLevel = 12.000000000000000000
Options.AttributionPrefix = '<a href='#39'https://www.leafletjs.com'#39' target='#39'_blank'#39'>Leaflet</a>'
Options.AttributionText =
'&copy; <a href='#39'https://www.openstreetmap.org/copyright'#39' target=' +
#39'_blank'#39'>OpenStreetMap</a>'
HeatMaps = <>
LocalFileAccess = True
TileLayers = <>
ElementContainers = <>
HeadLinks = <>
end
end
object httpReqGeoJson: TWebHttpRequest
ResponseType = rtText
URL = 'assets/bpddistricts.geojson'
OnResponse = httpReqGeoJsonResponse
Left = 114
Top = 696
end
end
<!-- Root wrapper inside main.webpanel -->
<div id="map.root" class="d-flex flex-column" style="height:100%;">
<!-- Local navbar -->
<nav class="navbar navbar-dark bg-primary py-2">
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<button id="map.btnmenu" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-bars me-1"></i><span class="d-none d-sm-inline">Menu</span>
</button>
</div>
<div class="col">
<button id="map.btnalerts" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-exclamation-circle me-1"></i><span class="d-none d-sm-inline">Alerts</span>
</button>
</div>
<div class="col">
<button id="map.btngroups" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-users me-1"></i><span class="d-none d-sm-inline">Groups</span>
</button>
</div>
<div class="col">
<button id="map.btnlocate" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-location-arrow me-1"></i><span class="d-none d-sm-inline">Locate</span>
</button>
</div>
<div class="col">
<button id="map.btnfilters" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sliders-h me-1"></i><span class="d-none d-sm-inline">Filter</span>
</button>
</div>
<div class="col">
<button id="map.btndisplay" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sun me-1"></i><span class="d-none d-sm-inline">Display</span>
</button>
</div>
</div>
</div>
</nav>
<!-- Map fills remaining space -->
<div class="flex-grow-1" style="min-height:400px;">
<!-- TWebPanel (pnlMap) will render itself here -->
<div id="map_pnlmap" class="w-100 h-100"></div>
</div>
</div>
unit View.Map;
interface
uses
System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Forms, WEBLib.Dialogs,
Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.Controls, WEBLib.Grids,
WEBLib.ExtCtrls, DB, WEBLib.WebCtrls, WEBLib.REST,
VCL.TMSFNCTypes, VCL.TMSFNCUtils, VCL.TMSFNCGraphics, VCL.TMSFNCGraphicsTypes,
VCL.TMSFNCCustomControl, VCL.TMSFNCWebBrowser, VCL.TMSFNCMaps, VCL.TMSFNCLeaflet,
VCL.TMSFNCMapsCommonTypes;
type
TFViewMap = class(TWebForm)
btnMenu: TWebButton;
btnAlerts: TWebButton;
btnGroups: TWebButton;
btnLocate: TWebButton;
btnFilters: TWebButton;
btnDisplay: TWebButton;
pnlMap: TWebPanel;
lfMap: TTMSFNCLeaflet;
httpReqGeoJson: TWebHttpRequest;
procedure lfMapMapInitialized(Sender: TObject);
procedure httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
procedure httpReqGeoJsonError(Sender: TObject; AError: string);
procedure lfMapPolyElementMouseEnter(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
procedure lfMapPolyElementMouseLeave(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
private
procedure StyleDistrictsAndFit;
procedure AssignDistrictNamesFromGeoJSON(const AJson: string);
public
end;
var
FViewMap: TFViewMap;
implementation
uses
JS, Web;
{$R *.dfm}
procedure TFViewMap.lfMapMapInitialized(Sender: TObject);
begin
httpReqGeoJson.Execute; // GET assets/bpddistricts.geojson as text
end;
procedure TFViewMap.httpReqGeoJsonResponse(Sender: TObject; AResponse: string);
begin
lfMap.LoadGeoJSONFromText(AResponse, True, False);
AssignDistrictNamesFromGeoJSON(AResponse);
StyleDistrictsAndFit;
end;
procedure TFViewMap.httpReqGeoJsonError(Sender: TObject; AError: string);
begin
Console.Log('Failed to load bpddistricts.geojson: ' + AError);
end;
procedure TFViewMap.AssignDistrictNamesFromGeoJSON(const AJson: string);
var
Root, Feature, Props, Geom: JS.TJSObject;
Features, Coords: JS.TJSArray;
f, count, i, polyIndex: Integer;
name, gtype: string;
begin
Root := JS.TJSObject(JS.TJSJSON.parse(AJson));
Features := JS.TJSArray(Root['features']);
if Features = nil then Exit;
polyIndex := 0;
for f := 0 to Features.Length - 1 do
begin
Feature := JS.TJSObject(Features[f]);
Props := JS.TJSObject(Feature['properties']);
Geom := JS.TJSObject(Feature['geometry']);
if (Props <> nil) and Props.hasOwnProperty('NAME') then
name := string(Props['NAME'])
else
name := '';
if Geom <> nil then
gtype := string(Geom['type'])
else
gtype := 'Polygon';
if SameText(gtype, 'Polygon') then
count := 1
else if SameText(gtype, 'MultiPolygon') then
begin
Coords := JS.TJSArray(Geom['coordinates']); // array of polygons
if Coords <> nil then
count := Coords.Length
else
count := 1;
end
else
count := 1;
// tag each created polygon with the district name
for i := 0 to count - 1 do
begin
if polyIndex < lfMap.Polygons.Count then
begin
lfMap.Polygons[polyIndex].DisplayName := name;
Inc(polyIndex);
end;
end;
end;
end;
procedure TFViewMap.StyleDistrictsAndFit;
function DistrictLetter(const S: string): Char;
var U: string; k: Integer;
begin
U := UpperCase(Trim(S));
// find last A..Z; works even if theres extra text/spacing
Result := #0;
for k := Length(U) downto 1 do
if (U[k] >= 'A') and (U[k] <= 'Z') then
begin
Result := U[k];
Break;
end;
end;
procedure ApplyHardCodedColors(const P: TTMSFNCMapsPolygon);
var L: Char;
begin
L := DistrictLetter(P.DisplayName);
case L of
'A': begin P.FillColor := $FF60A5FA; P.StrokeColor := $FF1D4ED8; end; // blue
'B': begin P.FillColor := $FF34D399; P.StrokeColor := $FF047857; end; // emerald
'C': begin P.FillColor := $FFF59E0B; P.StrokeColor := $FFB45309; end; // amber
'D': begin P.FillColor := $FFF87171; P.StrokeColor := $FFB91C1C; end; // red
'E': begin P.FillColor := $FFA78BFA; P.StrokeColor := $FF6D28D9; end; // purple (north)
else
// fallback cycle if a name didnt come through
case (P.Index mod 5) of
0: begin P.FillColor := $FF60A5FA; P.StrokeColor := $FF1D4ED8; end;
1: begin P.FillColor := $FF34D399; P.StrokeColor := $FF047857; end;
2: begin P.FillColor := $FFF59E0B; P.StrokeColor := $FFB45309; end;
3: begin P.FillColor := $FFF87171; P.StrokeColor := $FFB91C1C; end;
else begin P.FillColor := $FFA78BFA; P.StrokeColor := $FF6D28D9; end;
end;
end;
P.FillOpacity := 0.42; // bolder than before
P.StrokeOpacity := 1.0;
P.StrokeWidth := 2;
end;
var
I, J: Integer;
P: TTMSFNCMapsPolygon;
C: TTMSFNCMapsCoordinates;
minLat, minLng, maxLat, maxLng, lat, lng: Double;
hasAny: Boolean;
B: TTMSFNCMapsBoundsRec;
begin
if lfMap.Polygons.Count = 0 then Exit;
// Apply hard-coded district colors
for I := 0 to lfMap.Polygons.Count - 1 do
begin
P := lfMap.Polygons[I];
ApplyHardCodedColors(P);
end;
// Build bounds and zoom to fit
hasAny := False;
minLat := 90; minLng := 180;
maxLat := -90; maxLng := -180;
for I := 0 to lfMap.Polygons.Count - 1 do
begin
P := lfMap.Polygons[I];
C := P.Coordinates;
if (C <> nil) and (C.Count > 0) then
for J := 0 to C.Count - 1 do
begin
lat := C.Items[J].Latitude;
lng := C.Items[J].Longitude;
if not hasAny then
begin
minLat := lat; maxLat := lat;
minLng := lng; maxLng := lng;
hasAny := True;
end
else
begin
if lat < minLat then minLat := lat;
if lat > maxLat then maxLat := lat;
if lng < minLng then minLng := lng;
if lng > maxLng then maxLng := lng;
end;
end;
end;
if hasAny then
begin
B.SouthWest.Latitude := minLat;
B.SouthWest.Longitude := minLng;
B.NorthEast.Latitude := maxLat;
B.NorthEast.Longitude := maxLng;
lfMap.ZoomToBounds(B);
end;
end;
procedure TFViewMap.lfMapPolyElementMouseEnter(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
begin
if AElement is TTMSFNCMapsPolygon then
begin
TTMSFNCMapsPolygon(AElement).StrokeWidth := 3;
TTMSFNCMapsPolygon(AElement).FillOpacity := 0.55;
end;
end;
procedure TFViewMap.lfMapPolyElementMouseLeave(Sender: TObject; AElement: TTMSFNCMapsPolyElement);
begin
if AElement is TTMSFNCMapsPolygon then
begin
TTMSFNCMapsPolygon(AElement).StrokeWidth := 2;
TTMSFNCMapsPolygon(AElement).FillOpacity := 0.42;
end;
end;
end.
object FViewUnits: TFViewUnits
Width = 534
Height = 426
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
ParentFont = False
end
<div class="sticky-top">
<!-- Local navbar (Complaints) -->
<nav class="navbar navbar-dark bg-primary py-2"><!-- removed sticky-top -->
<div class="container-fluid">
<div class="row w-100 g-2 align-items-stretch">
<div class="col">
<span id="units.title" class="navbar-brand mb-0 h5 text-white">Units</span>
</div>
<div class="col">
<button id="units.btnrefresh" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sync-alt me-1"></i><span class="d-none d-sm-inline">Refresh</span>
</button>
</div>
<div class="col">
<button id="units.btngroup" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-layer-group me-1"></i><span class="d-none d-sm-inline">Group</span>
</button>
</div>
<div class="col">
<button id="units.btnfilter" type="button" class="btn btn-primary w-100 h-100">
<i class="fa fa-sliders-h me-1"></i><span class="d-none d-sm-inline">Filter</span>
</button>
</div>
</div>
</div>
</nav>
<!-- Search bar under local navbar -->
<div class="bg-light border-bottom py-2"><!-- removed sticky-top -->
<div class="container-fluid">
<div class="input-group">
<span class="input-group-text bg-white"><i class="fa fa-search"></i></span>
<input id="units.search" class="form-control" placeholder="Search...">
</div>
</div>
</div>
</div> <!-- /sticky-top wrapper -->
<!-- Existing content (unchanged) -->
<div class="row">
<div class="col-12">
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<h1 class="page-header pt-3 pb-2 mb-3 border-bottom fs-4 fw-bold" id="view.calls.title">Units</h1>
<!-- Data Table -->
<div class="table-responsive mt-4">
<table class="table table-sm table-striped table-bordered align-middle" id="tblPhoneGrid">
<thead class="table-dark">
<tr>
<th scope="col">Phone Number</th>
<th scope="col">Caller</th>
<th scope="col">Time</th>
<th scope="col">Duration</th>
<th scope="col">Transcript</th>
<th scope="col">Listen</th>
</tr>
</thead>
<tbody>
<!-- Rows added dynamically in Delphi -->
</tbody>
</table>
</div>
<!-- Entry Count Label -->
<label id="lblentries" class="mt-2 d-block"></label>
<!-- Pagination -->
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center" id="pagination">
<!-- Pagination items added in Delphi -->
</ul>
</nav>
</div>
</div>
</div>
</div>
</div>
unit View.Units;
interface
uses
System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Controls, WEBLib.Forms, WEBLib.Dialogs,
Vcl.Controls, Vcl.StdCtrls,
XData.Web.Connection, WEBLib.StdCtrls;
type
TFViewUnits = class(TWebForm)
public
end;
var
FViewUnits: TFViewUnits;
implementation
{$R *.dfm}
{ TFViewErrorPage }
end.
object FViewUserProfile: TFViewUserProfile
Width = 604
Height = 434
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
ParentFont = False
OnShow = WebFormShow
object WebLabel1: TWebLabel
Left = 24
Top = 24
Width = 55
Height = 13
Caption = 'User Profile'
ElementID = 'view.userprofile.title'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel3: TWebLabel
Left = 39
Top = 59
Width = 40
Height = 13
Caption = 'User ID:'
ElementID = 'view.userprofile.form.lblUserID'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel2: TWebLabel
Left = 13
Top = 157
Width = 70
Height = 13
Caption = 'Email Address:'
ElementID = 'view.userprofile.form.lblEmail'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel4: TWebLabel
Left = 29
Top = 83
Width = 52
Height = 13
Caption = 'Username:'
ElementID = 'view.userprofile.form.lblUserName'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel5: TWebLabel
Left = 29
Top = 107
Width = 50
Height = 13
Caption = 'Full Name:'
ElementID = 'view.userprofile.form.lblFullName'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object WebLabel6: TWebLabel
Left = 5
Top = 133
Width = 74
Height = 13
Caption = 'Phone Number:'
ElementID = 'view.userprofile.form.lblPhone'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object lblResult: TWebLabel
Left = 85
Top = 246
Width = 3
Height = 13
ElementID = 'view.userprofile.form.lblresult'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object edtUsername: TWebEdit
Left = 85
Top = 80
Width = 121
Height = 21
ElementID = 'view.userprofile.form.edtUsername'
Enabled = False
HeightPercent = 100.000000000000000000
ReadOnly = True
WidthPercent = 100.000000000000000000
end
object edtUserId: TWebEdit
Left = 85
Top = 56
Width = 121
Height = 21
ElementID = 'view.userprofile.form.edtUserID'
Enabled = False
HeightPercent = 100.000000000000000000
ReadOnly = True
TabOrder = 1
WidthPercent = 100.000000000000000000
end
object edtFullName: TWebEdit
Left = 85
Top = 104
Width = 121
Height = 21
ChildOrder = 5
ElementID = 'view.userprofile.form.edtFullName'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object edtPhone: TWebEdit
Left = 85
Top = 128
Width = 121
Height = 21
ChildOrder = 7
ElementID = 'view.userprofile.form.edtPhone'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object chkAdminUser: TWebCheckBox
Left = 85
Top = 179
Width = 113
Height = 22
Caption = 'chkAdminUser'
ChildOrder = 9
ElementID = 'view.userprofile.form.chkAdminUser'
Enabled = False
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object edtEmail: TWebEdit
Left = 85
Top = 152
Width = 121
Height = 21
ChildOrder = 7
ElementID = 'view.userprofile.form.edtEmail'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnConfirm: TWebButton
Left = 85
Top = 210
Width = 96
Height = 25
Caption = 'Confirm Changes'
ChildOrder = 12
ElementID = 'view.userprofile.form.btnconfirm'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnConfirmClick
end
object WebButton1: TWebButton
Left = 208
Top = 210
Width = 96
Height = 25
Caption = 'Cancel Changes'
ChildOrder = 14
ElementID = 'view.userprofile.form.btncancel'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = WebButton1Click
end
object pnlMessage: TWebPanel
Left = 236
Top = 4
Width = 121
Height = 33
ElementID = 'view.login.message'
ChildOrder = 17
TabOrder = 8
object lblMessage: TWebLabel
Left = 16
Top = 11
Width = 42
Height = 13
Caption = 'Message'
ElementID = 'view.login.message.label'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnCloseNotification: TWebButton
Left = 96
Top = 3
Width = 22
Height = 25
ChildOrder = 1
ElementID = 'view.login.message.button'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnCloseNotificationClick
end
end
object XDataWebClient1: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 359
Top = 52
end
end
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<h1 class="page-header pt-3 pb-2 mb-3 border-bottom fs-4 fw-bold" id="view.userprofile.title">User Profile</h1>
<!-- Alert Notification -->
<div id="view.login.message" class="alert alert-danger d-flex justify-content-between align-items-center d-none" role="alert" hidden>
<span id="view.login.message.label" class="me-2"></span>
<button id="view.login.message.button" type="button" class="btn-close" aria-label="Close"></button>
</div>
<!-- User Profile Form -->
<form class="row g-3">
<!-- User ID -->
<div class="col-12">
<label id="view.userprofile.form.lblUserID" for="view.userprofile.form.edtUserID" class="form-label fw-bold">User ID:</label>
<input id="view.userprofile.form.edtUserID" class="form-control form-control-sm" readonly>
</div>
<!-- Username -->
<div class="col-12 col-md-6">
<label id="view.userprofile.form.lblUserName" for="view.userprofile.form.edtUsername" class="form-label fw-bold">Username:</label>
<input id="view.userprofile.form.edtUsername" class="form-control form-control-sm">
</div>
<!-- Full Name -->
<div class="col-12 col-md-6">
<label id="view.userprofile.form.lblFullName" for="view.userprofile.form.edtFullName" class="form-label fw-bold">Full Name:</label>
<input id="view.userprofile.form.edtFullName" class="form-control form-control-sm">
</div>
<!-- Phone Number -->
<div class="col-12 col-md-6">
<label id="view.userprofile.form.lblPhone" for="view.userprofile.form.edtPhone" class="form-label fw-bold">Phone Number:</label>
<input id="view.userprofile.form.edtPhone" class="form-control form-control-sm">
</div>
<!-- Email -->
<div class="col-12 col-md-6">
<label id="view.userprofile.form.lblEmail" for="view.userprofile.form.edtEmail" class="form-label fw-bold">Email Address:</label>
<input id="view.userprofile.form.edtEmail" class="form-control form-control-sm">
</div>
<!-- Admin Checkbox -->
<div class="col-12">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="view.userprofile.form.chkAdminUser">
<label id="view.userprofile.form.lblAdminUser" class="form-check-label fw-bold" for="view.userprofile.form.chkAdminUser">Admin User</label>
</div>
</div>
<!-- Buttons -->
<div class="col-12 d-flex flex-column flex-md-row justify-content-md-end gap-2">
<button class="btn btn-primary" id="view.userprofile.form.btnconfirm" type="button">
<i class="fa fa-check me-1"></i> Confirm Changes
</button>
<button class="btn btn-secondary" id="view.userprofile.form.btncancel" type="button">
<i class="fa fa-times me-1"></i> Cancel Changes
</button>
</div>
<!-- Result Label -->
<div class="col-12">
<label id="view.userprofile.form.lblresult" class="form-label"></label>
</div>
</form>
</div>
</div>
</div>
unit View.UserProfile;
interface
uses
System.SysUtils, System.Classes, JS, Web, WEBLib.Graphics, WEBLib.Controls,
WEBLib.Forms, WEBLib.Dialogs, Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls,
XData.Web.Client, WEBLib.ExtCtrls, DB, XData.Web.JsonDataset,
XData.Web.Dataset, XData.Web.Connection, Vcl.Forms;
type
TFViewUserProfile = class(TWebForm)
WebLabel1: TWebLabel;
WebLabel3: TWebLabel;
edtUsername: TWebEdit;
WebLabel2: TWebLabel;
edtUserId: TWebEdit;
edtFullName: TWebEdit;
WebLabel4: TWebLabel;
edtPhone: TWebEdit;
WebLabel5: TWebLabel;
chkAdminUser: TWebCheckBox;
edtEmail: TWebEdit;
WebLabel6: TWebLabel;
btnConfirm: TWebButton;
lblResult: TWebLabel;
XDataWebClient1: TXDataWebClient;
WebButton1: TWebButton;
pnlMessage: TWebPanel;
lblMessage: TWebLabel;
btnCloseNotification: TWebButton;
procedure WebFormShow(Sender: TObject);
procedure btnConfirmClick(Sender: TObject);
[async] procedure EditUser();
[async] procedure GetUser();
procedure WebButton1Click(Sender: TObject);
procedure HideNotification();
procedure ShowNotification(Notification: string);
procedure btnCloseNotificationClick(Sender: TObject);
function CheckInputs(): boolean;
end;
var
FViewUserProfile: TFViewUserProfile;
implementation
uses
Auth.Service,
XData.Model.Classes,
ConnectionModule;
{$R *.dfm}
procedure TFViewUserProfile.btnCloseNotificationClick(Sender: TObject);
begin
HideNotification;
end;
procedure TFViewUserProfile.btnConfirmClick(Sender: TObject);
var
resultString: string;
begin
asm
var messageDiv = document.getElementById('view.login.message');
messageDiv.classList.remove('alert-success');
messageDiv.classList.add('alert-danger');
end;
if CheckInputs() then
begin
EditUser();
end
end;
procedure TFViewUserProfile.EditUser();
var
xdcResponse: TXDataClientResponse;
responseString: TJSObject;
editOptions: string;
begin
if(checkInputs()) then
begin
console.log(edtFullName.Text);
editOptions := '&username=' + edtUsername.Text +
'&fullname=' + edtFullName.Text +
'&phonenumber=' + edtPhone.Text +
'&email=' + edtEmail.Text;
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.EditUser',
[editOptions]));
responseString := TJSObject(xdcResponse.Result);
asm
var messageDiv = document.getElementById('view.login.message');
messageDiv.classList.remove('alert-danger');
messageDiv.classList.add('alert-success');
end;
ShowNotification(string(responseString['value']));
end;
end;
procedure TFViewUserProfile.WebButton1Click(Sender: TObject);
var
xdcResponse: TXDataClientResponse;
userList: TJSObject;
data: TJSArray;
user: TJSObject;
begin
GetUser();
showNotification('Failure:Changes discarded');
end;
procedure TFViewUserProfile.WebFormShow(Sender: TObject);
var
xdcResponse: TXDataClientResponse;
userList: TJSObject;
data: TJSArray;
user: TJSObject;
begin
HideNotification;
GetUser();
//edtJwt.Text := TJSJSON.stringify(AuthService.TokenPayload);
chkAdminUser.Checked := JS.toBoolean(AuthService.TokenPayload.Properties['user_admin']);
end;
procedure TFViewUserProfile.GetUser;
var
xdcResponse: TXDataClientResponse;
userList: TJSObject;
data: TJSArray;
user: TJSObject;
begin
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.GetUsers',
[JS.toString(AuthService.TokenPayload.Properties['user_name'])]));
userList := TJSObject(xdcResponse.Result);
data := TJSArray(userList['data']);
user := TJSObject(data[0]);
edtUsername.Text := string(user['username']);
edtFullName.Text := string(user['full_name']);
edtPhone.Text := string(user['phone_number']);
edtEmail.Text := string(user['email_address']);
edtUserId.Text := string(user['userID']);
chkAdminUser.Checked := boolean(user['admin']);
end;
procedure TFViewUserProfile.HideNotification;
begin
pnlMessage.ElementHandle.hidden := True;
end;
procedure TFViewUserProfile.ShowNotification(Notification: string);
var
splitNotification: TArray<string>;
begin
if Notification <> '' then
begin
splitNotification := Notification.Split([':']);
if(splitNotification[0] = 'Success') then
begin
asm
var messageDiv = document.getElementById('view.login.message');
messageDiv.classList.remove('alert-danger');
messageDiv.classList.add('alert-success');
end;
end
else
begin
asm
var messageDiv = document.getElementById('view.login.message');
messageDiv.classList.remove('alert-success');
messageDiv.classList.add('alert-danger');
end;
end;
lblMessage.Caption := splitNotification[1];
pnlMessage.ElementHandle.hidden := False;
end;
end;
function TFViewUserProfile.CheckInputs(): boolean;
var
checkString: string;
charIndex: integer;
phoneNum: string;
begin
Result := false;
checkString := edtFullName.Text + edtUsername.Text + edtPhone.Text + edtEmail.Text;
if string(edtFullName.Text).IsEmpty then
begin
ShowNotification('Failure:Full Name field is blank!');
exit;
end;
if string(edtUsername.Text).IsEmpty then
begin
ShowNotification('Failure:Username field is blank!');
exit;
end;
if string(edtPhone.Text).IsEmpty then
begin
ShowNotification('Failure:Phone Number field is blank!');
exit;
end;
if string(edtEmail.Text).IsEmpty then
begin
ShowNotification('Failure:Email field is blank!');
exit;
end;
if checkString.Contains('&') then
begin
ShowNotification('Failure:No fields may contain "&&"!');
exit;
end;
if string(edtEmail.Text).Contains('@') = false then
begin
ShowNotification('Failure:Please enter a valid email address');
exit;
end;
if (length(string(edtEmail.Text).Split(['@'])) <> 2) or (string(edtEmail.text).CountChar('@') > 1) then
begin
ShowNotification('Failure:Please enter a valid email address');
exit;
end;
phoneNum := edtPhone.Text;
if (not phoneNum.Contains('(')) or (not phoneNum.Contains(')')) or (not phoneNum.Contains('-')) then
begin
ShowNotification('Failure:Please enter a valid phone number');
exit;
end;
if (phoneNum.CountChar('(') <> 1) or (phoneNum.CountChar(')') <> 1) or (phoneNum.CountChar('-') <> 1) or (phoneNum.CountChar(' ') > 1) then
begin
ShowNotification('Failure:Please enter a valid phone number');
exit;
end;
phoneNum := phoneNum.Replace('(', '');
phoneNum := phoneNum.Replace(')', '');
phoneNum := phoneNum.Replace('-', '');
phoneNum := phoneNum.Replace(' ', '');
console.log(phoneNum);
console.log(length(phoneNum));
if(length(phoneNum) <> 10) then
begin
ShowNotification('Failure:Please enter a valid phone number');
exit;
end;
for CharIndex := 1 to Length(phoneNum) do
begin
if not (phoneNum[CharIndex] in ['0' .. '9']) then
begin
console.log('here');
ShowNotification('Failure:Please enter a valid phone number');
exit;
end;
end;
result := true;
end;
end.
object FViewUsers: TFViewUsers
Width = 640
Height = 480
OnShow = WebFormCreate
object lblEntries: TWebLabel
Left = 8
Top = 433
Width = 81
Height = 15
Caption = 'Showing 0 of ...'
ElementID = 'lblentries'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnAddUser: TWebButton
Left = 346
Top = 90
Width = 96
Height = 25
Caption = 'Add User'
ChildOrder = 9
ElementClassName = 'btn btn-light'
ElementID = 'btnadduser'
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -20
Font.Name = 'Segoe UI'
Font.Style = []
HeightPercent = 100.000000000000000000
ParentFont = False
WidthPercent = 100.000000000000000000
OnClick = btnAddUserClick
end
object btnConfirmDelete: TWebButton
Left = 506
Top = 174
Width = 96
Height = 25
Caption = 'Confirm'
ChildOrder = 16
ElementID = 'btn_confirm_delete'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnConfirmDeleteClick
end
object pnlMessage: TWebPanel
Left = 12
Top = 16
Width = 121
Height = 33
ElementID = 'view.login.message'
ChildOrder = 17
TabOrder = 2
object lblMessage: TWebLabel
Left = 16
Top = 11
Width = 46
Height = 15
Caption = 'Message'
ElementID = 'view.login.message.label'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object btnCloseNotification: TWebButton
Left = 96
Top = 3
Width = 22
Height = 25
ChildOrder = 1
ElementID = 'view.login.message.button'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = btnCloseNotificationClick
end
end
object XDataWebClient1: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 462
Top = 242
end
object XDataWebDataSet1: TXDataWebDataSet
Connection = DMConnection.ApiConnection
Left = 462
Top = 300
object XDataWebDataSet1userID: TStringField
FieldName = 'userID'
Size = 30
end
object XDataWebDataSet1username: TStringField
FieldName = 'username'
Size = 30
end
object XDataWebDataSet1password: TStringField
FieldName = 'password'
Size = 30
end
object XDataWebDataSet1full_name: TStringField
FieldName = 'full_name'
end
object XDataWebDataSet1phone_number: TStringField
FieldName = 'phone_number'
Size = 30
end
object XDataWebDataSet1email_address: TStringField
FieldName = 'email_address'
Size = 30
end
object XDataWebDataSet1admin: TBooleanField
FieldName = 'admin'
end
object XDataWebDataSet1active: TBooleanField
FieldName = 'active'
end
end
object WebDataSource1: TWebDataSource
DataSet = XDataWebDataSet1
Left = 436
Top = 376
end
end
<!-- Main Users Page Content -->
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 col-md-8 col-lg-8">
<h1 class="page-header pt-3 pb-2 mb-3 border-bottom fs-4 fw-bold" id="view.users.title">Users</h1>
<!-- Alert Message -->
<div class="row g-3">
<div class="col-sm">
<div id="view.login.message" class="alert alert-danger">
<button id="view.login.message.button" type="button" class="btn-close" aria-label="Close"></button>
<span id="view.login.message.label"></span>
</div>
</div>
</div>
<!-- Users Table -->
<table class="table table-striped table-bordered" id="tblPhoneGrid">
<thead class="thead-dark">
<tr>
<th scope="col">UserID</th>
<th scope="col">Username</th>
<th scope="col">Password</th>
<th scope="col">Full Name</th>
<th scope="col">Phone Number</th>
<th scope="col">Email Address</th>
<th scope="col">Admin</th>
<th scope="col">Active</th>
<th scope="col">Edit</th>
<th scope="col">Delete</th>
</tr>
</thead>
<tbody>
<!-- Rows will be added dynamically via Delphi code -->
</tbody>
</table>
<!-- Add User Button -->
<div class="row justify-content-center py-2">
<div class="col-sm text-center">
<button id="btnadduser" class="btn btn-primary">Add User</button>
</div>
</div>
<!-- Entry Label & Pagination -->
<label id="lblentries"></label>
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center" id="pagination">
<!-- Pagination items will be added dynamically via Delphi code -->
</ul>
</nav>
</div>
</div>
</div>
<!-- Modal (Place near the end of the body) -->
<div class="modal fade" id="confirmation_modal" tabindex="-1" aria-labelledby="confirmation_modal_label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmation_modal_label">Confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Are you sure you want to make these changes?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" id="btn_confirm_delete">Confirm</button>
</div>
</div>
</div>
</div>
unit View.Users;
interface
uses
System.SysUtils, System.Classes, Web, WEBLib.Graphics, WEBLib.Forms, WEBLib.Dialogs,
Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.Controls, WEBLib.Grids, WebLib.Lists,
XData.Web.Client, WEBLib.ExtCtrls, DB, XData.Web.JsonDataset,
XData.Web.Dataset, XData.Web.Connection, Vcl.Forms, WEBLib.DBCtrls, JS;
type
TFViewUsers = class(TWebForm)
XDataWebClient1: TXDataWebClient;
XDataWebDataSet1: TXDataWebDataSet;
lblEntries: TWebLabel;
btnAddUser: TWebButton;
WebDataSource1: TWebDataSource;
XDataWebDataSet1userID: TStringField;
XDataWebDataSet1username: TStringField;
XDataWebDataSet1password: TStringField;
XDataWebDataSet1full_name: TStringField;
XDataWebDataSet1phone_number: TStringField;
XDataWebDataSet1email_address: TStringField;
XDataWebDataSet1admin: TBooleanField;
btnConfirmDelete: TWebButton;
pnlMessage: TWebPanel;
lblMessage: TWebLabel;
btnCloseNotification: TWebButton;
XDataWebDataSet1active: TBooleanField;
procedure WebFormCreate(Sender: TObject);
//procedure tblPhoneGridGetCellChildren(Sender: TObject; ACol, ARow: Integer;
//AField: TField; AValue: string; AElement: TJSHTMLElementRecord);
procedure btnConfirmDeleteClick(Sender: TObject);
procedure btnAddUserClick(Sender: TObject);
procedure btnCloseNotificationClick(Sender: TObject);
//procedure btnApplyClick(Sender: TObject);
//procedure btnSearchClick(Sender: TObject);
private
FChildForm: TWebForm;
procedure ClearTable();
procedure GeneratePagination(TotalPages: Integer);
//function GenerateSearchOptions(): string;
//[async] procedure Search(searchOptions: string);
[async] procedure DelUser(username: string);
[async] procedure EditUser(row: TJSHTMLElement);
[async] procedure GetUsers(searchOptions: string);
procedure HideNotification;
procedure ShowNotification(Notification: string);
procedure AddRowToTable(UserID, Username, Full_Name, Phone, Email: string; Admin, Active: boolean);
//[async] procedure addUser();
var
PageNumber: integer;
PageSize: integer;
TotalPages: integer;
StartDate: string;
EndDate: string;
OrderBy: string;
cur_user: string;
Info: string;
public
class function CreateForm(AElementID, Info: string): TWebForm;
end;
var
FViewUsers: TFViewUsers;
implementation
{$R *.dfm}
uses
XData.Model.Classes,
ConnectionModule,
View.Main;
{$R *.dfm}
class function TFViewUsers.CreateForm(AElementID, Info: string): TWebForm;
// Autofills known information about a user on create
procedure AfterCreate(AForm: TObject);
begin
TFViewUsers(AForm).Info := Info;
end;
{$R *.dfm}
begin
Application.CreateForm(TFViewUsers, AElementID, Result, @AfterCreate);
end;
procedure TFViewUsers.WebFormCreate(Sender: TObject);
begin
DMConnection.ApiConnection.Connected := True;
PageNumber := 1;
TotalPages := 1; // Initial total pages
PageSize := 10;
GetUsers('');
if Info <> '' then
ShowNotification(Info)
else
HideNotification();
end;
procedure TFViewUsers.DelUser(username: string);
var
xdcResponse: TXDataClientResponse;
responseString: TJSObject;
begin
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.DelUser',
[username]));
responseString := TJSObject(xdcResponse.Result);
ShowNotification(string(responseString['value']));
getUsers('');
end;
procedure TFViewUsers.EditUser(row: TJSHTMLElement);
var
cells: TJSHTMLCollection;
UserID: TJSNode;
Username: TJSNode;
FullName: TJSNode;
PhoneNum: TJSNode;
Email: TJSNode;
Admin: TJSNode;
Password: TJSNode;
isAdmin: boolean;
isActive: boolean;
ButtonCancel: TJSHTMLElement;
splitNames: TArray<string>;
firstName: string;
lastName: string;
xdcResponse: TXDataClientResponse;
responseString: TJSObject;
editOptions: string;
begin
cells := row.getElementsByTagName('td');
UserID := cells[0];
Username := cells[1];
Password := cells[2];
FullName := cells[3];
PhoneNum := cells[4];
Email := cells[5];
Admin := cells[6];
if TJSHTMLElement(cells[6].childNodes.item(0).childNodes.item(0)).attributes.Attrs['checked'] = nil then
isAdmin := false
else
isAdmin := true;
if TJSHTMLElement(cells[7].childNodes.item(0).childNodes.item(0)).attributes.Attrs['checked'] = nil then
isActive := false
else
isActive := true;
FViewMain.EditUser('Edit', Username.innerText, FullName.innerText, PhoneNum.innerText, Email.innerText, isAdmin, isActive);
end;
procedure TFViewUsers.GeneratePagination(TotalPages: Integer);
var
PaginationElement, PageItem, PageLink: TJSHTMLElement;
I, Start, Finish: Integer;
begin
PaginationElement := TJSHTMLElement(document.getElementById('pagination'));
PaginationElement.innerHTML := ''; // Clear existing pagination
// Previous Button
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if PageNumber = 1 then
PageItem.classList.add('disabled');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := 'Previous';
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
begin
if PageNumber > 1 then
begin
Dec(PageNumber);
GetUsers('');
end;
end);
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
// Page Numbers
if PageNumber <= 4 then
Begin
Start := 2;
Finish := 5;
End
else if (PageNumber >= (TotalPages - 3)) then
begin
Start := TotalPages - 3;
Finish := TotalPages - 1;
end
else
begin
Start := PageNumber - 1;
Finish := PageNumber + 1;
end;
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if 1 = PageNumber then
PageItem.classList.add('selected-number'); // Add the selected-number class
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := '1';
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
var
PageNum: Integer;
begin
PageNum := StrToInt((Event.currentTarget as TJSHTMLElement).innerText);
PageNumber := PageNum;
GetUsers('');
end);
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
// Adds Elipse to pagination if page number is too big
if PageNumber > 4 then
begin
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
PageItem.classList.add('disabled');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := '...';
PageLink.setAttribute('href', 'javascript:void(0)');
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
end;
// Adds Page, page - 1, and page + 1 to pagination
for I := Start to Finish do
begin
if ( I > 1) and (I < TotalPages) then
begin
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if I = PageNumber then
PageItem.classList.add('selected-number'); // Add the selected-number class
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := IntToStr(I);
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
var
PageNum: Integer;
begin
PageNum := StrToInt((Event.currentTarget as TJSHTMLElement).innerText);
PageNumber := PageNum;
GetUsers('');
end);
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
end;
end;
if PageNumber < TotalPages - 4 then
begin
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
PageItem.classList.add('disabled');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := '...';
PageLink.setAttribute('href', 'javascript:void(0)');
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
end;
if TotalPages <> 1 then
begin
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if TotalPages = PageNumber then
PageItem.classList.add('selected-number');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := IntToStr(TotalPages);
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
var
PageNum: Integer;
begin
PageNum := StrToInt((Event.currentTarget as TJSHTMLElement).innerText);
PageNumber := PageNum;
GetUsers('');
end);
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
end;
// Next Button
PageItem := TJSHTMLElement(document.createElement('li'));
PageItem.className := 'page-item';
if PageNumber = TotalPages then
PageItem.classList.add('disabled');
PageLink := TJSHTMLElement(document.createElement('a'));
PageLink.className := 'page-link';
PageLink.innerText := 'Next';
PageLink.setAttribute('href', 'javascript:void(0)');
PageLink.addEventListener('click', procedure(Event: TJSMouseEvent)
begin
if PageNumber < TotalPages then
begin
Inc(PageNumber);
GetUsers('');
end;
end);
PageItem.appendChild(PageLink);
PaginationElement.appendChild(PageItem);
end;
procedure TFViewUsers.GetUsers(searchOptions: string);
var
xdcResponse: TXDataClientResponse;
userList : TJSObject;
i: integer;
data: TJSArray;
user: TJSObject;
userListLength: integer;
begin
if PageNumber > 0 then
begin
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.GetUsers',
[searchOptions]));
userList := TJSObject(xdcResponse.Result);
data := TJSArray(userList['data']);
userListLength := integer(userList['count']);
ClearTable();
XDataWebDataSet1.Close;
XDataWebDataSet1.SetJsonData(userList['data']);
XDataWebDataSet1.Open;
for i := 0 to data.Length - 1 do
begin
user := TJSObject(data[i]);
AddRowToTable(XDataWebDataSet1userID.AsString, XDataWebDataSet1username.AsString,
XDataWebDataSet1full_name.AsString, XDataWebDataSet1phone_number.AsString,
XDataWebDataSet1email_address.AsString, XDataWebDataSet1Admin.AsBoolean,
XDataWebDataSet1Active.AsBoolean);
XDataWebDataSet1.Next;
end;
TotalPages := (userListLength + PageSize - 1) div PageSize;
if (PageNumber * PageSize) < userListLength then
begin
lblEntries.Caption := 'Showing entries ' + IntToStr((PageNumber - 1) * PageSize + 1) +
' - ' + IntToStr(PageNumber * PageSize) +
' of ' + IntToStr(userListLength);
end
else
begin
lblEntries.Caption := 'Showing entries ' + IntToStr((PageNumber - 1) * PageSize + 1) +
' - ' + IntToStr(userListLength) +
' of ' + IntToStr(userListLength);
end;
GeneratePagination(TotalPages);
end;
end;
procedure TFViewUsers.AddRowToTable(UserID, Username, Full_Name, Phone, Email: string; Admin, Active: boolean);
// Adds rows to the table
// PhoneNumber: phone number of the location
// Caller: phone number of the caller
// Duration: duration of the call
// Transcript: transcription of the recording
// MediaUrl: Link to the recording
var
NewRow, Cell, P, Button, cbAdmin: TJSHTMLElement;
begin
NewRow := TJSHTMLElement(document.createElement('tr'));
// UserID Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'UserID');
Cell.innerText := UserID;
NewRow.appendChild(Cell);
// Username Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Username');
Cell.innerText := Username;
NewRow.appendChild(Cell);
// Password Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Password');
Cell.innerText := 'hidden';
NewRow.appendChild(Cell);
// Full Name Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Full Name');
Cell.innerText := Full_Name;
NewRow.appendChild(Cell);
// Phone Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Phone Number');
Cell.innerText := Phone;
NewRow.appendChild(Cell);
// Email Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Email Address');
Cell.innerText := Email;
NewRow.appendChild(Cell);
// Admin Cell
Cell := TJSHTMLElement(document.createElement('td'));
cbAdmin := TJSHTMLElement(document.createElement('a'));
//Button.className := 'btn btn-primary';
//Button.setAttribute('href', MediaUrl);
if Admin then
cbAdmin.innerHTML := '<input type="checkbox" id="cbadminuser" checked>'
else
cbAdmin.innerHTML := '<input type="checkbox" id="cbadminuser">';
Cell.appendChild(cbAdmin);
Cell.setAttribute('data-label', 'Admin');
NewRow.appendChild(Cell);
// Admin Cell
Cell := TJSHTMLElement(document.createElement('td'));
cbAdmin := TJSHTMLElement(document.createElement('a'));
//Button.className := 'btn btn-primary';
//Button.setAttribute('href', MediaUrl);
if Active then
cbAdmin.innerHTML := '<input type="checkbox" id="cbactive" checked>'
else
cbAdmin.innerHTML := '<input type="checkbox" id="cbactive">';
Cell.appendChild(cbAdmin);
Cell.setAttribute('data-label', 'Active');
NewRow.appendChild(Cell);
// Edit Button Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Edit');
Button := TJSHTMLElement(document.createElement('a'));
Button.className := 'btn btn-primary';
Button.innerHTML := '<i class="far fa-edit fa-fw"></i><span>Edit</span>';
Button.addEventListener('click', procedure(Event: TJSMouseEvent)
begin
EditUser(TJSHTMLElement(NewRow));
end);
Cell.appendChild(Button);
NewRow.appendChild(Cell);
// Delete Button Cell
Cell := TJSHTMLElement(document.createElement('td'));
Cell.setAttribute('data-label', 'Delete');
Button := TJSHTMLElement(document.createElement('a'));
Button.className := 'btn btn-primary';
Button.innerHTML := '<i class="fas fa-times fa-fw"></i>';
Button.addEventListener('click', procedure(Event: TJSMouseEvent)
begin
cur_user := Username;
asm
var modal = document.getElementById('confirmation_modal');
if (modal && modal.parentNode !== document.body) {
document.body.appendChild(modal); // Pull modal out of nested z-index hell
}
var bsModal = new bootstrap.Modal(modal, { keyboard: false });
bsModal.show();
end;
end);
Cell.appendChild(Button);
NewRow.appendChild(Cell);
// Appends new rows to the table body
TJSHTMLElement(document.getElementById('tblPhoneGrid').getElementsByTagName('tbody')[0]).appendChild(NewRow);
end;
procedure TFViewUsers.btnAddUserClick(Sender: TObject);
begin
//Info := '';
FViewMain.EditUser('Add', '', '', '', '', false, true);
end;
procedure TFViewUsers.btnCloseNotificationClick(Sender: TObject);
begin
HideNotification;
end;
procedure TFViewUsers.HideNotification;
begin
pnlMessage.ElementHandle.hidden := True;
end;
procedure TFViewUsers.ShowNotification(Notification: string);
var
splitNotification: TArray<string>;
begin
if Notification <> '' then
begin
splitNotification := Notification.Split([':']);
if(splitNotification[0] = 'Success') then
begin
asm
var messageDiv = document.getElementById('view.login.message');
messageDiv.classList.remove('alert-danger');
messageDiv.classList.add('alert-success');
end;
end
else
begin
asm
var messageDiv = document.getElementById('view.login.message');
messageDiv.classList.remove('alert-success');
messageDiv.classList.add('alert-danger');
end;
end;
lblMessage.Caption := splitNotification[1];
pnlMessage.ElementHandle.hidden := False;
end;
end;
procedure TFViewUsers.btnConfirmDeleteClick(Sender: TObject);
begin
DelUser(cur_user);
end;
procedure TFViewUsers.ClearTable();
var
tbody: TJSHTMLElement;
begin
tbody := TJSHTMLElement(document.getElementById('tblPhoneGrid').getElementsByTagName('tbody')[0]);
tbody.innerHTML := '';
end;
{function TFViewUsers.GenerateSearchOptions(): string;
var
searchOptions: string;
begin
PageSize := StrToInt(wcbPageSize.Text);
StartDate := dtpStartDate.Text;
EndDate := dtpEndDate.Text;
OrderBy := wcbSortBy.Text;
searchOptions := '&phonenumber=' + wcbLocation.Value +
'&pagenumber=' + IntToStr(PageNumber) +
'&pagesize=' + IntToStr(PageSize) +
'&startdate=' + StartDate +
'&enddate=' + EndDate +
'&orderby=' + OrderBy;
Result := searchOptions;
end; }
end.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<noscript>Your browser does not support JavaScript!</noscript>
<link href="data:;base64,=" rel="icon"/>
<title>emiMobile</title>
<script crossorigin="anonymous" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" src="https://code.jquery.com/jquery-3.5.1.min.js" type="text/javascript"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/flag-icon-css/2.3.1/css/flag-icon.min.css" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.0/css/all.min.css" rel="stylesheet"/>
<link href="css/app.css" rel="stylesheet" type="text/css"/>
<link href="css/spinner.css" rel="stylesheet" type="text/css"/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/js/bootstrap.bundle.min.js" type="text/javascript"></script>
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.7/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js" type="text/javascript"></script>
<link href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" rel="stylesheet"/>
<script src="$(ProjectName).js" type="text/javascript"></script>
<style>
</style>
</head>
<body>
<script type="text/javascript">rtl.run();</script>
</body>
</html>
program webEmiMobile;
uses
Vcl.Forms,
XData.Web.Connection,
Auth.Service in 'Auth.Service.pas',
App.Types in 'App.Types.pas',
ConnectionModule in 'ConnectionModule.pas' {DMConnection: TWebDataModule},
View.Login in 'View.Login.pas' {FViewLogin: TWebForm} {*.html},
View.UserProfile in 'View.UserProfile.pas' {FViewUserProfile: TWebForm} {*.html},
View.Units in 'View.Units.pas' {FViewUnits: TWebForm} {*.html},
App.Config in 'App.Config.pas',
Paginator.Plugins in 'Paginator.Plugins.pas',
View.Complaints in 'View.Complaints.pas' {FViewComplaints: TWebForm} {*.html},
View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html},
View.Map in 'View.Map.pas' {FViewMap: TWebForm} {*.html},
View.Admin in 'View.Admin.pas' {FViewAdmin: TWebForm} {*.html},
View.Users in 'View.Users.pas' {FViewUsers: TWebForm} {*.html},
View.EditUser in 'View.EditUser.pas' {FViewEditUser: TWebForm} {*.html},
Utils in 'Utils.pas',
View.ErrorPage in 'View.ErrorPage.pas' {FViewErrorPage: TWebForm} {*.html};
{$R *.res}
procedure DisplayLoginView(AMessage: string = ''); forward;
procedure DisplayMainView;
procedure ConnectProc;
begin
if Assigned(FViewLogin) then
FViewLogin.Free;
TFViewMain.Display(@DisplayLoginView);
end;
begin
if not DMConnection.ApiConnection.Connected then
DMConnection.ApiConnection.Open(@ConnectProc)
else
ConnectProc;
end;
procedure DisplayLoginView(AMessage: string);
begin
AuthService.Logout;
DMConnection.ApiConnection.Connected := False;
if Assigned(FViewMain) then
FViewMain.Free;
TFViewLogin.Display(@DisplayMainView, AMessage);
end;
procedure UnauthorizedAccessProc(AMessage: string);
begin
DisplayLoginView(AMessage);
end;
procedure StartApplication;
begin
DMConnection.SetClientConfig(
procedure(Success: Boolean; ErrorMessage: string)
begin
if Success then
begin
if (not AuthService.Authenticated) or AuthService.TokenExpired then
DisplayLoginView
else
DisplayMainView;
end
else
begin
asm
var dlg = document.createElement("dialog");
dlg.classList.add("shadow", "rounded", "border", "p-4");
dlg.style.maxWidth = "500px";
dlg.style.width = "90%";
dlg.style.fontFamily = "system-ui, sans-serif";
dlg.innerHTML =
"<h5 class='fw-bold mb-3 text-danger'>kgOrders web app</h5>" +
"<p class='mb-3' style='white-space: pre-wrap;'>" + ErrorMessage + "</p>" +
"<div class='text-end'>" +
"<button id='refreshBtn' class='btn btn-primary'>Reload</button></div>";
document.body.appendChild(dlg);
dlg.showModal();
document.getElementById("refreshBtn").addEventListener("click", function () {
location.reload(true); // Hard refresh
});
end;
end;
end);
end;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TDMConnection, DMConnection);
Application.Run;
DMConnection.InitApp(@StartApplication, @UnauthorizedAccessProc);
end.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{DB6F5DBF-7E4B-45DA-AFFA-6C8DF15BA740}</ProjectGuid>
<ProjectVersion>20.2</ProjectVersion>
<FrameworkType>VCL</FrameworkType>
<MainSource>webEmiMobile.dpr</MainSource>
<Base>True</Base>
<Config Condition="'$(Config)'==''">Debug</Config>
<Platform Condition="'$(Platform)'==''">Win32</Platform>
<TargetedPlatforms>1</TargetedPlatforms>
<AppType>Application</AppType>
<ProjectName Condition="'$(ProjectName)'==''">webEmiMobile</ProjectName>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
<Base_Win32>true</Base_Win32>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
<Base_Win64>true</Base_Win64>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_1)'!=''">
<Cfg_1>true</Cfg_1>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
<Cfg_1_Win32>true</Cfg_1_Win32>
<CfgParent>Cfg_1</CfgParent>
<Cfg_1>true</Cfg_1>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_2)'!=''">
<Cfg_2>true</Cfg_2>
<CfgParent>Base</CfgParent>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''">
<Cfg_2_Win32>true</Cfg_2_Win32>
<CfgParent>Cfg_2</CfgParent>
<Cfg_2>true</Cfg_2>
<Base>true</Base>
</PropertyGroup>
<PropertyGroup Condition="'$(Base)'!=''">
<DCC_DcuOutput>.\$(Platform)\$(Config)</DCC_DcuOutput>
<DCC_ExeOutput>.\$(Platform)\$(Config)</DCC_ExeOutput>
<DCC_E>false</DCC_E>
<DCC_N>false</DCC_N>
<DCC_S>false</DCC_S>
<DCC_F>false</DCC_F>
<DCC_K>false</DCC_K>
<DCC_UsePackage>RESTComponents;emsclientfiredac;DataSnapFireDAC;FireDACIBDriver;xdata;emsclient;FireDACCommon;RESTBackendComponents;soapserver;CloudService;FireDACCommonDriver;inet;FireDAC;FireDACSqliteDriver;soaprtl;soapmidas;aurelius;$(DCC_UsePackage)</DCC_UsePackage>
<DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Vcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;$(DCC_Namespace)</DCC_Namespace>
<Icon_MainIcon>$(BDS)\bin\delphi_PROJECTICON.ico</Icon_MainIcon>
<Icns_MainIcns>$(BDS)\bin\delphi_PROJECTICNS.icns</Icns_MainIcns>
<SanitizedProjectName>webEmiMobile</SanitizedProjectName>
<VerInfo_Locale>1046</VerInfo_Locale>
<TMSWebProject>2</TMSWebProject>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.802;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;LastCompiledTime=2018/07/25 12:57:53</VerInfo_Keys>
<TMSWebHTMLFile>index.html</TMSWebHTMLFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win32)'!=''">
<DCC_UsePackage>DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;svnui;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;svn;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;VCLRESTComponents;vclie;TMSWEBCorePkgDXE11;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;TMSWEBCorePkgLibDXE11;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
<DCC_Namespace>Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
<BT_BuildType>Debug</BT_BuildType>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
<VerInfo_Locale>1033</VerInfo_Locale>
<Manifest_File>$(BDS)\bin\default_app.manifest</Manifest_File>
</PropertyGroup>
<PropertyGroup Condition="'$(Base_Win64)'!=''">
<DCC_UsePackage>DBXSqliteDriver;DataSnapServerMidas;DBXDb2Driver;DBXInterBaseDriver;vclactnband;vclFireDAC;tethering;FireDACADSDriver;DBXMSSQLDriver;DatasnapConnectorsFreePascal;FireDACMSSQLDriver;vcltouch;vcldb;bindcompfmx;Intraweb;DBXOracleDriver;inetdb;FmxTeeUI;emsedge;fmx;fmxdae;vclib;FireDACDBXDriver;dbexpress;IndyCore;vclx;dsnap;DataSnapCommon;DataSnapConnectors;VCLRESTComponents;vclie;bindengine;DBXMySQLDriver;FireDACOracleDriver;FireDACMySQLDriver;DBXFirebirdDriver;FireDACCommonODBC;DataSnapClient;bindcompdbx;IndyIPCommon;vcl;DBXSybaseASEDriver;IndyIPServer;IndySystem;FireDACDb2Driver;dsnapcon;FireDACMSAccDriver;fmxFireDAC;FireDACInfxDriver;vclimg;TeeDB;emshosting;FireDACPgDriver;ibmonitor;FireDACASADriver;DBXOdbcDriver;FireDACTDataDriver;FMXTee;DbxCommonDriver;ibxpress;Tee;DataSnapServer;xmlrtl;DataSnapNativeClient;fmxobj;vclwinx;ibxbindings;rtl;FireDACDSDriver;DbxClientDriver;DBXSybaseASADriver;CustomIPTransport;vcldsnap;bindcomp;appanalytics;DBXInformixDriver;IndyIPClient;bindcompvcl;TeeUI;dbxcds;VclSmp;adortl;FireDACODBCDriver;DataSnapIndy10ServerTransport;dsnapxml;DataSnapProviderClient;dbrtl;inetdbxpress;FireDACMongoDBDriver;IndyProtocols;fmxase;$(DCC_UsePackage)</DCC_UsePackage>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1)'!=''">
<DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
<DCC_DebugDCUs>true</DCC_DebugDCUs>
<DCC_Optimize>false</DCC_Optimize>
<DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
<DCC_DebugInfoInExe>true</DCC_DebugInfoInExe>
<DCC_RemoteDebug>true</DCC_RemoteDebug>
<TMSWebDebugInfo>2</TMSWebDebugInfo>
<TMSWebDefines>DEBUG</TMSWebDefines>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
<DCC_RemoteDebug>false</DCC_RemoteDebug>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.3.0;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>
<TMSWebSingleInstance>1</TMSWebSingleInstance>
<TMSUseJSDebugger>2</TMSUseJSDebugger>
<VerInfo_Release>3</VerInfo_Release>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2)'!=''">
<DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
<DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
<DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
<DCC_DebugInformation>0</DCC_DebugInformation>
<TMSWebOptimization>2</TMSWebOptimization>
<TMSWebDefines>RELEASE</TMSWebDefines>
</PropertyGroup>
<PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
<VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=0.9.1.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=0.9.1.0;Comments=;LastCompiledTime=2018/08/22 16:25:56</VerInfo_Keys>
<VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
<VerInfo_Locale>1033</VerInfo_Locale>
<AppDPIAwarenessMode>PerMonitor</AppDPIAwarenessMode>
<VerInfo_MajorVer>0</VerInfo_MajorVer>
<VerInfo_MinorVer>9</VerInfo_MinorVer>
<VerInfo_Release>1</VerInfo_Release>
</PropertyGroup>
<ItemGroup>
<DelphiCompile Include="$(MainSource)">
<MainSource>MainSource</MainSource>
</DelphiCompile>
<DCCReference Include="Auth.Service.pas"/>
<DCCReference Include="App.Types.pas"/>
<DCCReference Include="ConnectionModule.pas">
<Form>DMConnection</Form>
<DesignClass>TWebDataModule</DesignClass>
</DCCReference>
<DCCReference Include="View.Login.pas">
<Form>FViewLogin</Form>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.UserProfile.pas">
<Form>FViewUserProfile</Form>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.Units.pas">
<Form>FViewUnits</Form>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="App.Config.pas"/>
<DCCReference Include="Paginator.Plugins.pas"/>
<DCCReference Include="View.Complaints.pas">
<Form>FViewComplaints</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.Main.pas">
<Form>FViewMain</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.Map.pas">
<Form>FViewMap</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.Admin.pas">
<Form>FViewAdmin</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.Users.pas">
<Form>FViewUsers</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="View.EditUser.pas">
<Form>FViewEditUser</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<DCCReference Include="Utils.pas"/>
<DCCReference Include="View.ErrorPage.pas">
<Form>FViewErrorPage</Form>
<FormType>dfm</FormType>
<DesignClass>TWebForm</DesignClass>
</DCCReference>
<None Include="index.html"/>
<None Include="css\app.css"/>
<BuildConfiguration Include="Base">
<Key>Base</Key>
</BuildConfiguration>
<BuildConfiguration Include="Debug">
<Key>Cfg_1</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
<BuildConfiguration Include="Release">
<Key>Cfg_2</Key>
<CfgParent>Base</CfgParent>
</BuildConfiguration>
</ItemGroup>
<ProjectExtensions>
<Borland.Personality>Delphi.Personality.12</Borland.Personality>
<Borland.ProjectType>Application</Borland.ProjectType>
<BorlandProject>
<Delphi.Personality>
<Source>
<Source Name="MainSource">webEmiMobile.dpr</Source>
</Source>
<Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcboffice2k290.bpl">Embarcadero C++Builder Office 2000 Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\bcbofficexp290.bpl">Embarcadero C++Builder Office XP Servers Package</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dcloffice2k290.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
<Excluded_Packages Name="$(BDSBIN)\dclofficexp290.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
</Excluded_Packages>
</Delphi.Personality>
<Deployment Version="5">
<DeployFile LocalName="Win32\Debug\webCharms.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployFile LocalName="Win32\Debug\webEmiMobile.exe" Configuration="Debug" Class="ProjectOutput">
<Platform Name="Win32">
<RemoteName>webEmiMobile.exe</RemoteName>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="Win32\Debug\webEnvoyCalls.exe" Configuration="Debug" Class="ProjectOutput"/>
<DeployFile LocalName="css\app.css" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="index.html" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="index.html" Configuration="Debug" Class="ProjectFile">
<Platform Name="Win32">
<RemoteDir>.\</RemoteDir>
<Overwrite>true</Overwrite>
</Platform>
</DeployFile>
<DeployFile LocalName="template\bootstrap\bootstrap.min.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\bootstrap\bootstrap.min.js" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\bootstrap\dataTables.bootstrap.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\css\app.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\css\emsys.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\css\metisMenu.min.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\css\morris.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\css\sb-admin-2.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\font-awesome\font-awesome.min.css" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\font-awesome\fonts\fontawesome-webfont.ttf" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\font-awesome\fonts\fontawesome-webfont.woff2" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\font-awesome\fonts\fontawesome-webfont.woff" Configuration="Debug" Class="ProjectFile"/>
<DeployFile LocalName="template\jquery\jquery.min.js" Configuration="Debug" Class="ProjectFile"/>
<DeployClass Name="AdditionalDebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidFileProvider">
<Platform Name="Android">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\xml</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiFile">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeArmeabiv7aFile">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidLibnativeMipsFile">
<Platform Name="Android">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\mips</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidServiceOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidServiceOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDef">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashImageDefV21">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStyles">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV21">
<Platform Name="Android">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="AndroidSplashStylesV31">
<Platform Name="Android">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v26</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconBackground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconForeground">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconMonochrome">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_AdaptiveIconV33">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v33</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Colors">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_ColorsDark">
<Platform Name="Android">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values-night-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_DefaultAppIcon">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon144">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon192">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-ldpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_LauncherIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon24">
<Platform Name="Android">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-mdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon36">
<Platform Name="Android">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-hdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon48">
<Platform Name="Android">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon72">
<Platform Name="Android">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_NotificationIcon96">
<Platform Name="Android">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xxxhdpi</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage426">
<Platform Name="Android">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-small</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage470">
<Platform Name="Android">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-normal</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage640">
<Platform Name="Android">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-large</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_SplashImage960">
<Platform Name="Android">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-xlarge</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_Strings">
<Platform Name="Android">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\values</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedNotificationIcon">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v24</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplash">
<Platform Name="Android">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashDark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v21</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31">
<Platform Name="Android">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="Android_VectorizedSplashV31Dark">
<Platform Name="Android">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>res\drawable-night-anydpi-v31</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DebugSymbols">
<Platform Name="iOSSimulator">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyFramework">
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.framework</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="DependencyModule">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.dll;.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="DependencyPackage">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
<Extensions>.dylib</Extensions>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
<Extensions>.bpl</Extensions>
</Platform>
</DeployClass>
<DeployClass Name="File">
<Platform Name="Android">
<Operation>0</Operation>
</Platform>
<Platform Name="Android64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>0</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>0</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources\StartUp\</RemoteDir>
<Operation>0</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectAndroidManifest">
<Platform Name="Android">
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXDebug">
<Platform Name="OSX64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXEntitlements">
<Platform Name="OSX32">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXInfoPList">
<Platform Name="OSX32">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOSXResource">
<Platform Name="OSX32">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\Resources</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Required="true" Name="ProjectOutput">
<Platform Name="Android">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Android64">
<RemoteDir>library\lib\arm64-v8a</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
<Platform Name="Linux64">
<Operation>1</Operation>
</Platform>
<Platform Name="OSX32">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSX64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="OSXARM64">
<RemoteDir>Contents\MacOS</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win32">
<Operation>0</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectOutput_Android32">
<Platform Name="Android64">
<RemoteDir>library\lib\armeabi-v7a</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectUWPManifest">
<Platform Name="Win32">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<Operation>1</Operation>
</Platform>
<Platform Name="Win64x">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSDeviceDebug">
<Platform Name="iOSDevice32">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSEntitlements">
<Platform Name="iOSDevice32">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSInfoPList">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSLaunchScreen">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
<Operation>64</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen</RemoteDir>
<Operation>64</Operation>
</Platform>
</DeployClass>
<DeployClass Name="ProjectiOSResource">
<Platform Name="iOSDevice32">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSDevice64">
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo150">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="UWP_DelphiLogo44">
<Platform Name="Win32">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="Win64">
<RemoteDir>Assets</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iOS_AppStore1024">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon152">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_AppIcon167">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPad_SpotLight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_AppIcon180">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Launch3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark2x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_LaunchDark3x">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification40">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Notification60">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting58">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Setting87">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight120">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<DeployClass Name="iPhone_Spotlight80">
<Platform Name="iOSDevice64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
<Platform Name="iOSSimARM64">
<RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
<Operation>1</Operation>
</Platform>
</DeployClass>
<ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSSimARM64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="iOSSimulator" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="OSX32" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="OSX64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="OSXARM64" Name="$(PROJECTNAME).app"/>
<ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
<ProjectRoot Platform="Win64x" Name="$(PROJECTNAME)"/>
</Deployment>
<Platforms>
<Platform value="Win32">True</Platform>
<Platform value="Win64">False</Platform>
</Platforms>
<ModelSupport>False</ModelSupport>
</BorlandProject>
<ProjectFileVersion>12</ProjectFileVersion>
</ProjectExtensions>
<Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
<Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
<Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
</Project>
[ExpressSkins]
Version=1.0.0
Enabled=1
ShowNotifications=1
Kind=2
NativeStyle=1
ScrollbarMode=0
ScrollMode=0
SkinName=WXICompact
RenderMode=0
TouchMode=0
FormCorners=0
SkinPaletteName=Default
ShowFormShadow=2
UseSkins=1
UseImageSet=0
UseSkinsInPopupMenus=1
LightStyleMode=3
UseGlobalSkin=1
dxSkinWXI=1
dxSkinTheBezier=1
dxSkinOffice2019Colorful=1
dxSkinOffice2019Black=1
dxSkinOffice2019DarkGray=1
dxSkinOffice2019White=1
dxSkinBasic=1
dxSkinBlack=0
dxSkinBlue=0
dxSkinBlueprint=0
dxSkinCaramel=0
dxSkinCoffee=0
dxSkinDarkroom=0
dxSkinDarkSide=0
dxSkinDevExpressDarkStyle=0
dxSkinDevExpressStyle=0
dxSkinFoggy=0
dxSkinGlassOceans=0
dxSkinHighContrast=0
dxSkiniMaginary=0
dxSkinLilian=0
dxSkinLiquidSky=0
dxSkinLondonLiquidSky=0
dxSkinMcSkin=0
dxSkinMetropolis=0
dxSkinMetropolisDark=0
dxSkinMoneyTwins=0
dxSkinOffice2007Black=0
dxSkinOffice2007Blue=0
dxSkinOffice2007Green=0
dxSkinOffice2007Pink=0
dxSkinOffice2007Silver=0
dxSkinOffice2010Black=0
dxSkinOffice2010Blue=0
dxSkinOffice2010Silver=0
dxSkinOffice2013DarkGray=0
dxSkinOffice2013LightGray=0
dxSkinOffice2013White=0
dxSkinOffice2016Colorful=0
dxSkinOffice2016Dark=0
dxSkinPumpkin=0
dxSkinSeven=0
dxSkinSevenClassic=0
dxSkinSharp=0
dxSkinSharpPlus=0
dxSkinSilver=0
dxSkinSpringtime=0
dxSkinStardust=0
dxSkinSummer2008=0
dxSkinTheAsphaltWorld=0
dxSkinValentine=0
dxSkinVisualStudio2013Blue=0
dxSkinVisualStudio2013Dark=0
dxSkinVisualStudio2013Light=0
dxSkinVS2010=0
dxSkinWhiteprint=0
dxSkinXmas2008Blue=0
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