Commit 63620b75 by cam

Initial Commit

parent 8e04993b
kgOrdersClient/__history/
kgOrdersClient/__recovery/
kgOrdersClient/TMSWeb
kgOrdersClient/Win32
kgOrdersClient/template/css/__history/
kgOrdersServer/__history
kgOrdersServer/__recovery
kgOrdersServer/doc/
kgOrdersServer/Win32/
kgOrdersServer/*.log
kgOrdersServer/*.txt
kgOrdersServer/Source/__history
kgOrdersServer/Source/__recovery/
*.local
*.exe
*.identcache
*.res
*.tvsconfig
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);
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 = 'WEBENVOYCALLS_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: 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: 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 = '') then
begin
AError('Please enter a username and a password');
Exit;
end;
FClient.RawInvoke(
'IAuthService.Login', [AUser, APassword],
@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 = 264
Width = 395
object ApiConnection: TXDataWebConnection
URL = 'http://localhost:2004/emsys/envoy/api/'
OnError = ApiConnectionError
OnRequest = ApiConnectionRequest
OnResponse = ApiConnectionResponse
Left = 48
Top = 80
end
object AuthConnection: TXDataWebConnection
URL = 'http://localhost:2004/emsys/envoycalls/auth/'
OnError = AuthConnectionError
Left = 48
Top = 16
end
end
unit ConnectionModule;
interface
uses
System.SysUtils, System.Classes, WEBLib.Modules, XData.Web.Connection,
App.Types, App.Config;
type
TDMConnection = class(TWebDataModule)
ApiConnection: TXDataWebConnection;
AuthConnection: TXDataWebConnection;
procedure ApiConnectionError(Error: TXDataWebConnectionError);
procedure ApiConnectionRequest(Args: TXDataWebConnectionRequest);
procedure ApiConnectionResponse(Args: TXDataWebConnectionResponse);
procedure AuthConnectionError(Error: TXDataWebConnectionError);
private
FUnauthorizedAccessProc: TUnauthorizedAccessProc;
public
procedure InitApp(SuccessProc: TSuccessProc;
UnauthorizedAccessProc: TUnauthorizedAccessProc);
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;
end.
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.
object FViewCalls: TFViewCalls
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
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
WidthPercent = 100.000000000000000000
end
object wcbLocation: TWebLookupComboBox
Left = 154
Top = 0
Width = 145
Height = 22
ElementID = 'wcblocation'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
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 wcbPageSize: TWebComboBox
Left = 0
Top = 0
Width = 145
Height = 21
ElementClassName = 'custom-select'
ElementID = 'wcbpagesize'
ElementFont = efCSS
HeightStyle = ssAuto
HeightPercent = 100.000000000000000000
Text = '10'
WidthPercent = 100.000000000000000000
ItemIndex = -1
Items.Strings = (
'10'
'25'
'50')
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'
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
WidthPercent = 100.000000000000000000
OnClick = btnApplyClick
end
object edtSearch: TWebEdit
Left = 48
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'
WidthPercent = 100.000000000000000000
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
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
WidthPercent = 100.000000000000000000
end
object XDataWebClient1: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 426
Top = 240
end
object XDataWebDataSet1: TXDataWebDataSet
Left = 440
Top = 300
end
end
<div class="row">
<div class="col-12">
<h1 class="page-header pt-3" id="view.calls.title" style="font-size: 24px;">Calls</h1>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 col-md-8">
<form class="form-inline">
<div class="col-sm py-2">
<label class='pe-2'style="font-weight: 700;">Location:</label>
<select class="custom-select" id="wcblocation" style="font-size: 1.00rem;"></select>
</div>
<div class="row">
<div class="col-sm-6">
<label class='pe-2'style="font-weight: 700;">Phone Number:</label>
<input class="form-control input-sm" id="edtsearch">
</div>
</div>
</form>
<form class="form-inline">
<div class="row">
<div class="col-sm-6">
<label style="font-weight: 700;">Start Date:</label>
<input class="form-control input-sm" id="dtpstartdate" type="date">
</div>
<div class="col-sm-6">
<label class= 'pe-2'style="font-weight: 700;">End Date:</label>
<input class="form-control input-sm" id="dtpenddate" type="date">
</div>
</div>
</form>
<form class="form-inline">
<div class= "row">
<div class="col-sm-5">
<label class="py-2" style="font-weight: 700;">Show <select class="custom-select" id="wcbpagesize" style="font-size: 1.00rem;"></select> entries</label>
</div>
<div class="col-sm-5">
<label class="py-2" style="font-weight: 700;">Sorted by:</label>
<select class="custom-select" id="wcbsortby" style="font-size: 1.00rem;"></select>
</div>
<div class="col-sm-2">
<button class= "ps-3" id="btnapply"></button>
</div>
</div>
</form>
<table class="table table-responsive table-striped table-bordered" id="tblPhoneGrid">
<thead class="thead-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 will be added dynamically via Delphi code -->
</tbody>
</table>
<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>
</div>
</div>
<!-- Modal -->
<div class="modal fade" id="audioModal" tabindex="-1" aria-labelledby="audioModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="audioModalLabel">Audio Player</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" onclick="stopAudio()"></button>
</div>
<div class="modal-body">
<audio controls id="audioPlayer">
<source src="" type="audio/mp3" id="audioSource">
Your browser does not support the audio element.
</audio>
</div>
</div>
</div>
</div>
<script>
// JavaScript function to stop audio
function stopAudio() {
var audioPlayer = document.getElementById('audioPlayer');
audioPlayer.pause(); // Pause the audio
audioPlayer.currentTime = 0; // Reset audio to beginning
}
</script>
object FViewEditUser: TFViewEditUser
Width = 640
Height = 480
OnShow = WebFormCreate
object WebLabel1: TWebLabel
Left = 8
Top = 125
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 = 291
Top = 125
Width = 38
Height = 15
Caption = 'Active?'
Color = clBtnFace
ElementID = 'lblactive'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
end
object lblLocation: TWebLabel
Left = 3
Top = 96
Width = 87
Height = 15
Caption = 'Default Location'
ElementID = 'lbllocation'
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
WidthPercent = 100.000000000000000000
end
object edtConfirmPassword: TWebEdit
Left = 348
Top = 34
Width = 121
Height = 22
ChildOrder = 7
ElementID = 'edtconfirmpassword'
HeightPercent = 100.000000000000000000
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
WidthPercent = 100.000000000000000000
end
object cbAdmin: TWebCheckBox
Left = 96
Top = 124
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 = 170
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 = 237
Top = 170
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 = 124
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 wcbLocation: TWebLookupComboBox
Left = 96
Top = 96
Width = 145
Height = 22
ElementID = 'wcblocation'
ElementFont = efCSS
HeightPercent = 100.000000000000000000
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 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">
<h1 class="page-header pt-3" id="view.calls.title" style="font-size: 24px;">Users</h1>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 col-md-8">
<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">
<label class= 'pe-2' style="font-weight: 700;font-size: 15px"id="lbllocation">Location:</label>
<select class="custom-select-large" id="wcblocation" style="font-size: 1.00rem;"></select>
</div>
</div>
<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 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_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;
lblLocation: TWebLabel;
wcbLocation: TWebLookupComboBox;
procedure WebFormCreate(Sender: TObject);
procedure btnConfirmClick(Sender: TObject);
procedure btnCancelClick(Sender: TObject);
procedure WebTimer1Timer(Sender: TObject);
procedure btnConfirmChangesClick(Sender: TObject);
procedure btnCloseNotificationClick(Sender: TObject);
private
{ Private declarations }
FMessage: string;
Mode: string;
Username: string;
FullName: string;
Phone: string;
Email: string;
Location: 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, Location: string; Admin, Active: boolean): TWebForm;
end;
var
FViewEditUser: TFViewEditUser;
implementation
uses
Windows,
View.Main,
View.Users,
VCL.Dialogs,
ConnectionModule;
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;
procedure TFViewEditUSer.btnConfirmChangesClick(Sender: TObject);
begin
if Mode = 'Edit' then
EditUser()
else
AddUser();
WebTimer1.Enabled := true;
asm
startSpinner();
end;
end;
function TFViewEditUser.AddUser(): string;
// Sends UserInfo over to the server so it can be added to the database
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) +
'&location=' + wcbLocation.Value;
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.AddUser',
[userInfo]));
responseString := TJSObject(xdcResponse.Result);
Info := string(responseString['value']);
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;
procedure TFViewEditUser.EditUser();
// Sends EditOptions over to the server so the given user can be editted
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) +
'&location=' + wcbLocation.DisplayText;
console.log(editOptions);
xdcResponse := await(XDataWebClient1.RawInvokeAsync('ILookupService.EditUser',
[editOptions]));
responseString := TJSObject(xdcResponse.Result);
Info := string(responseString['value']);
end;
class function TFViewEditUser.CreateForm(AElementID, Mode, Username, FullName, Phone, Email, Location: 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).Location:= Location;
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 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
cbAdmin.Enabled := False;
wcbLocation.DisplayText := Location;
edtPhoneNumber.Text := Phone;
edtEmail.Text := Email;
cbAdmin.checked := Admin;
cbActive.Checked := Active;
end;
procedure TFViewEditUser.WebTimer1Timer(Sender: TObject);
begin
WebTimer1.Enabled := False;
asm
endSpinner();
end;
if (not Info.Contains('Failure')) then
begin
FViewMain.ShowUserForm(Info);
end
else
showNotification(Info);
console.log('Info at Timer:' + Info);
end;
procedure TFViewEditUser.btnConfirmClick(Sender: TObject);
// Confirms the edit or addition
var
checkString: string;
charIndex: integer;
phoneNum: string;
begin
checkString := edtFullName.Text + edtUsername.Text + edtPassword.Text
+ edtConfirmPassword.Text + edtPhoneNumber.Text + edtEmail.Text;
if string(edtFullName.Text).IsEmpty then
begin
ShowNotification('Full Name field is blank!');
exit;
end;
if string(edtUsername.Text).IsEmpty then
begin
ShowNotification('Username field is blank!');
exit;
end;
if string(edtPassword.Text).IsEmpty then
begin
ShowNotification('Password field is blank!');
exit;
end;
if string(edtConfirmPassword.Text).IsEmpty then
begin
ShowNotification('Please confirm your password!');
exit;
end;
if string(edtPhoneNumber.Text).IsEmpty then
begin
ShowNotification('Phone Number field is blank!');
exit;
end;
if string(edtEmail.Text).IsEmpty then
begin
ShowNotification('Email field is blank!');
exit;
end;
if checkString.Contains('&') then
begin
ShowNotification('No fields may contain "&&"!');
exit;
end;
if string(edtEmail.Text).Contains('@') = false then
begin
ShowNotification('Please enter a valid email address');
exit;
end;
if (length(string(edtEmail.Text).Split(['@'])) <> 2) or (string(edtEmail.text).CountChar('@') > 1) then
begin
ShowNotification('Please enter a valid email address');
exit;
end;
phoneNum := edtPhoneNumber.Text;
if (not phoneNum.Contains('(')) or (not phoneNum.Contains(')')) or (not phoneNum.Contains('-')) then
begin
ShowNotification('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('Please enter a valid phone number');
exit;
end;
phoneNum := phoneNum.Replace('(', '');
phoneNum := phoneNum.Replace(')', '');
phoneNum := phoneNum.Replace('-', '');
phoneNum := phoneNum.Replace(' ', '');
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
console.log('here');
ShowNotification('Please enter a valid phone number');
exit;
end;
end;
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 6-20 characters!');
exit;
end;
asm
var confirmationModal = new bootstrap.Modal(document.getElementById('confirmation_modal'), {
keyboard: false });
confirmationModal.show();
end;
end;
end.
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 FViewHome: TFViewHome
Width = 640
Height = 480
object WebLabel1: TWebLabel
Left = 24
Top = 43
Width = 33
Height = 15
Caption = 'Home'
ElementID = 'view.home.title'
HeightPercent = 100.000000000000000000
Transparent = False
WidthPercent = 100.000000000000000000
end
object WebMemo1: TWebMemo
Left = 24
Top = 62
Width = 471
Height = 329
ElementID = 'view.home.notesmemo'
HeightPercent = 100.000000000000000000
Lines.Strings = (
'KG Orders Alpha Version')
ReadOnly = True
SelLength = 0
SelStart = 25
WidthPercent = 100.000000000000000000
end
end
<div class="row">
<div class="col-12">
<h1 class="page-header pt-3" id="view.home.title" style="font-size: 24px;">Home</h1>
<div class="row card-body">
<div class="form-outline mb-4">
<textarea class="form-control" id="view.home.notesmemo" rows="20"></textarea>
</div>
</div>
</div>
</div>
unit View.Home;
interface
uses
System.SysUtils, System.Classes, WEBLib.Graphics, WEBLib.Forms, WEBLib.Dialogs,
Vcl.Controls, Vcl.StdCtrls, WEBLib.StdCtrls, WEBLib.Controls, WEBLib.Grids,
XData.Web.Client, WEBLib.ExtCtrls, DB;
type
TFViewHome = class(TWebForm)
WebLabel1: TWebLabel;
WebMemo1: TWebMemo;
procedure WebFormCreate(Sender: TObject);
end;
var
FViewHome: TFViewHome;
implementation
uses
JS, XData.Model.Classes,
ConnectionModule;
{$R *.dfm}
procedure TFViewHome.WebFormCreate(Sender: TObject);
begin
WebLabel1.Caption := 'Please select a menu option to continue!';
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 = 190
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 XDataWebClient: TXDataWebClient
Connection = DMConnection.AuthConnection
Left = 492
Top = 102
end
end
<nav class="navbar navbar-light bg-light login-navbar">
<div class="container-fluid">
<a class="navbar-brand" href="#">Envoy Calls</a>
</div>
</nav>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6 col-lg-4">
<div class="card login-card">
<div class="card-header">
<h3 id="view.login.title" class="fs-6 card-title">Please Sign In</h3>
</div>
<div class="card-body">
<div role="form">
<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>
<fieldset>
<div class="mb-3">
<input id="view.login.edtusername" class="form-control" type="text" autofocus placeholder="Username">
</div>
<div class="mb-3">
<input id="view.login.edtpassword" class="form-control" type="password" placeholder="Password">
</div>
<div class="mb-3">
<button id="view.login.btnlogin" class="btn btn-primary w-100">Login</button>
</div>
</fieldset>
</div>
</div>
</div>
</div>
</div>
</div>
unit View.Login;
interface
uses
System.SysUtils, System.Classes, Web, 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;
procedure btnLoginClick(Sender: TObject);
procedure btnCloseNotificationClick(Sender: TObject);
procedure WebFormCreate(Sender: TObject);
private
FLoginProc: TSuccessProc;
FMessage: string;
procedure ShowNotification(Notification: string);
procedure HideNotification;
[async] procedure VerifyVersion();
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}
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,
@LoginSuccess,
@LoginError
);
end;
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.HideNotification;
begin
pnlMessage.ElementHandle.hidden := True;
end;
procedure TFViewLogin.ShowNotification(Notification: string);
begin
if Notification <> '' then
begin
lblMessage.Caption := Notification;
pnlMessage.ElementHandle.hidden := False;
end;
end;
procedure TFViewLogin.btnCloseNotificationClick(Sender: TObject);
begin
HideNotification;
end;
procedure TFViewLogin.VerifyVersion();
var
xdcResponse: TXDataClientResponse;
begin
xdcResponse := await(XDataWebClient.RawInvokeAsync('IAuthService.VerifyVersion',
['1.0.0']));
ShowNotification(string(TJSObject(xdcResponse.Result)['value']));
end;
procedure TFViewLogin.WebFormCreate(Sender: TObject);
begin
// lblAppTitle.Caption := 'EM Systems - webCharms App ver 0.9.2.22';
VerifyVersion();
if FMessage <> '' then
ShowNotification(FMessage)
else
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
WidthPercent = 100.000000000000000000
end
object wllblUserProfile: TWebLinkLabel
Left = 529
Top = 21
Width = 63
Height = 15
ElementID = 'dropdown.menu.userprofile'
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'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = wllblLogoutClick
Caption = ' Logout'
end
object lblHome: TWebLinkLabel
Left = 556
Top = 38
Width = 33
Height = 15
ElementID = 'dropdown.menu.home'
HeightPercent = 100.000000000000000000
WidthPercent = 100.000000000000000000
OnClick = lblHomeClick
Caption = 'Home'
end
object lblAppTitle: TWebLabel
Left = 57
Top = 31
Width = 60
Height = 15
Caption = 'Envoy Calls'
ElementID = 'view.main.apptitle'
HeightPercent = 100.000000000000000000
Transparent = False
WidthPercent = 100.000000000000000000
end
object lblCallsList: TWebLinkLabel
Left = 564
Top = 56
Width = 25
Height = 15
ElementID = 'dropdown.menu.callslist'
ElementFont = efCSS
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
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 XDataWebClient: TXDataWebClient
Connection = DMConnection.ApiConnection
Left = 44
Top = 280
end
end
<div id="wrapper">
<nav class="navbar navbar-expand navbar-light bg-light" style="margin-bottom: 0px;">
<div class="container-fluid">
<a id="view.main.apptitle" class="navbar-brand" href="index.html">Envoy Calls</a>
<div class="collapse navbar-collapse show" id="navbarNavDropdown">
<ul class="navbar-nav ms-auto">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="navbarDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
<i class="fa fa-user fa-fw"></i><span class="panel-title" id="view.main.username"> Username </span>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownMenuLink">
<li>
<a class="dropdown-item" id="dropdown.menu.home" href="#"><i class="fa fa-home fa-fw"></i><span> Home</span></a>
</li>
<li>
<a class="dropdown-item" id="dropdown.menu.userprofile" href="#"><i class="fa fa-user fa-fw"></i><span> User Profile</span></a>
</li>
<li>
<a class="dropdown-item" id="dropdown.menu.callslist" href="#"><i class="fa fa-phone fa-fw"></i><span> Calls</span></a>
</li>
<li>
<a class="dropdown-item" id="dropdown.menu.users" href="#"><i class="fas fa-address-book fa-fw"></i><span> Users</span></abbr></a>
</li>
<li>
<hr class="dropdown-divider">
</li>
<li>
<a class="dropdown-item" id="dropdown.menu.logout" href="#"><i class="fa fa-sign-out fa-fw"></i><span> Logout</span></a>
</li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
<div id="page-wrapper" class="container-fluid">
<div class="row">
<div id="main.webpanel" class="col-12"></div>
</div>
<div class="row">
<div class="col-12">
<div class="form-outline mb-4">
<textarea class="form-control" id="main.debugmemo" rows="4"></textarea>
</div>
</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;
lblHome: TWebLinkLabel;
WebMessageDlg1: TWebMessageDlg;
lblAppTitle: TWebLabel;
WebMemo1: TWebMemo;
XDataWebClient: TXDataWebClient;
lblCallsList: TWebLinkLabel;
lblUsers: TWebLinkLabel;
procedure WebFormCreate(Sender: TObject);
procedure mnuLogoutClick(Sender: TObject);
procedure wllblUserProfileClick(Sender: TObject);
procedure wllblLogoutClick(Sender: TObject);
procedure lblHomeClick(Sender: TObject);
procedure lblCallsListClick(Sender: TObject);
procedure lblUsersClick(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;
public
{ Public declarations }
class procedure Display(LogoutProc: TLogoutProc);
procedure ShowForm( AFormClass: TWebFormClass );
procedure EditUser( Mode, FullName, Username, Phone, Email, Location: string; admin, active: boolean);
procedure ShowUserForm(Info: string);
end;
var
FViewMain: TFViewMain;
implementation
uses
Auth.Service,
View.Login,
View.UserProfile,
View.Home,
View.Calls,
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;
//Change this later
lblUsers.Visible := true;
ShowForm(TFViewHome);
end;
procedure TFViewMain.lblHomeClick(Sender: TObject);
begin
ShowForm(TFViewHome);
end;
procedure TFViewMain.lblUsersClick(Sender: TObject);
begin
ShowForm(TFViewUsers);
end;
procedure TFViewMain.lblCallsListClick(Sender: TObject);
begin
ShowForm(TFViewCalls);
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;
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, Location: string; Admin, Active: boolean);
begin
if Assigned(FChildForm) then
FChildForm.Free;
FChildForm := TFViewEditUser.CreateForm(WebPanel1.ElementID, Mode, FullName, Username, Phone, Email, Location, Admin, Active);
end;
procedure TFViewMain.ShowUserForm(Info: string);
begin
if Assigned(FChildForm) then
FChildForm.Free;
FChildForm := TFViewUsers.CreateForm(WebPanel1.ElementID, Info);
end;
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="row">
<div class="col-lg-12">
<h1 class="page-header" id="view.userprofile.title">User Profile</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 role="form">
<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.lblUserName">Username:</label>
<input id="view.userprofile.form.edtUsername" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblFullName">Full Name:</label>
<input id="view.userprofile.form.edtFullName" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblPhone">Phone Number:</label>
<input id="view.userprofile.form.edtPhone" class="form-control">
</div>
<div class="form-group">
<label id="view.userprofile.form.lblEmail">Email Address:</label>
<input id="view.userprofile.form.edtEmail" 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-group">
<button class="btn btn-primary" id="view.userprofile.form.btnconfirm">Confirm Changes</button>
<button class="btn btn-primary" id="view.userprofile.form.btncancel">Xancel Changes</button>
</div>
<div class="form-group">
<label id="view.userprofile.form.lblresult"></label>
</div>
</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'
end
object XDataWebDataSet1username: TStringField
FieldName = 'username'
end
object XDataWebDataSet1password: TStringField
FieldName = 'password'
end
object XDataWebDataSet1full_name: TStringField
FieldName = 'full_name'
end
object XDataWebDataSet1status: TStringField
FieldName = 'status'
end
object XDataWebDataSet1email_address: TStringField
FieldName = 'email_address'
end
object XDataWebDataSet1Atype: TStringField
FieldName = 'Atype'
end
object XDataWebDataSet1rights: TIntegerField
FieldName = 'rights'
end
object XDataWebDataSet1perspectiveID: TStringField
FieldName = 'perspectiveID'
end
object XDataWebDataSet1QBID: TStringField
FieldName = 'QBID'
end
end
object WebDataSource1: TWebDataSource
DataSet = XDataWebDataSet1
Left = 436
Top = 376
end
end
<div class="row">
<div class="col-12">
<h1 class="page-header pt-3" id="view.calls.title" style="font-size: 24px;">Users</h1>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-12 col-md-8">
<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>
<div class="row justify-content-center py-2">
<div class="col-sm">
<button id="btnadduser" class="btn btn-primary">Add User></button>
</div>
</div class="table-user-responsive">
<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">Status</th>
<th scope="col">Email Address</th>
<th scope="col">Access Type</th>
<th scope="col">System Rights</th>
<th scope="col">Perspective ID</th>
<th scope="col">QB ID</th>
</tr>
</thead>
<tbody>
<!-- Rows will be added dynamically via Delphi code -->
</tbody>
</table>
<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>
</div>
</div>
<!--</div> -->
<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>
{
"AuthUrl": "hhttp://localhost:2004/emsys/envoy/auth/",
"ApiUrl": "http://localhost:2004/emsys/envoy/api/",
"AppUrl": "http://localhost:2004/emsys/envoy/app/"
}
\ No newline at end of file
{
"AuthUrl" : "http://144.71.200.57:2004/emsys/envoy/auth/",
"ApiUrl" : "http://144.71.200.57:2004/emsys/envoy/api/",
"AppUrl" : "http://144.71.200.57:2004/emsys/envoy/app/"
}
\ No newline at end of file
<html><head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<noscript>Your browser does not support JavaScript!</noscript>
<link rel="icon" href="data:;base64,=">
<title>TMS Web Project</title>
<link href="template/css/app.css" rel="stylesheet" type="text/css">
<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 crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" type="text/javascript"></script>
<script src="https://code.jquery.com/jquery-3.7.1.js" integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4=" crossorigin="anonymous"></script>
<script type="text/javascript" src="$(ProjectName).js"></script>
<script>
function startSpinner() {
//window.scrollTo(0, 0);
document.body.style.setProperty('--spinner-top', window.scrollY+'px');
$(".hide-scene").addClass("scene");
}
function endSpinner() {
$(".hide-scene").removeClass("scene");
}
</script>
<title>EM Systems webCharms App</title>
<script type="text/javascript" src="$(ProjectName).js"></script>
<style>
/*-----svg loader styles-------*/
:root {
--spinner-top: 0px; }
.hide-scene {
display:none;
}
.scene {
position: absolute;
z-index: 9999999;
top: var(--spinner-top);
width: 100%;
height: 100%;
perspective: 600;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
display: flex;
align-items: center;
justify-content: center;
}
.scene svg {
width: 165px;
height: 165px;
/* background-color: lavender; */
}
@keyframes arrow-spin {
50% {
transform: rotateY(360deg);
}
}
/*-------------------------------------------*/
</style>
</head>
<body>
</body>
<script type="text/javascript">
rtl.run();
</script>
<div class="hide-scene">
<svg
version="1.1"
id="dc-spinner"
xmlns="http://www.w3.org/2000/svg"
x="0px" y="0px"
width:"38"
height:"38"
viewBox="0 0 38 38"
preserveAspectRatio="xMinYMin meet">
<circle cx="20" cy="20" r="16" fill="#D3D3D3"></circle>
<text x="55%" y="54%" font-family="Monaco" font-size="2px" text-anchor="middle" dy=".3em" style="letter-spacing:0.6">PROCESSING
<animate
attributeName="opacity"
values="0;1;0" dur="1.8s"
repeatCount="indefinite"/>
</text>
<path fill="#373a42" d="M20,35c-8.271,0-15-6.729-15-15S11.729,5,20,5s15,6.729,15,15S28.271,35,20,35z M20,5.203
C11.841,5.203,5.203,11.841,5.203,20c0,8.159,6.638,14.797,14.797,14.797S34.797,28.159,34.797,20
C34.797,11.841,28.159,5.203,20,5.203z">
</path>
<path fill="#373a42" d="M20,33.125c-7.237,0-13.125-5.888-13.125-13.125S12.763,6.875,20,6.875S33.125,12.763,33.125,20
S27.237,33.125,20,33.125z M20,7.078C12.875,7.078,7.078,12.875,7.078,20c0,7.125,5.797,12.922,12.922,12.922
S32.922,27.125,32.922,20C32.922,12.875,27.125,7.078,20,7.078z">
</path>
<path fill="#2AA198" stroke="#2AA198" stroke-width="0.6027" stroke-miterlimit="10" d="M5.203,20
c0-8.159,6.638-14.797,14.797-14.797V5C11.729,5,5,11.729,5,20s6.729,15,15,15v-0.203C11.841,34.797,5.203,28.159,5.203,20z">
<animateTransform
attributeName="transform"
type="rotate"
from="0 20 20"
to="360 20 20"
calcMode="spline"
keySplines="0.4, 0, 0.2, 1"
keyTimes="0;1"
dur="2s"
repeatCount="indefinite" />
</path>
<path fill="#859900" stroke="#859900" stroke-width="0.2027" stroke-miterlimit="10" d="M7.078,20
c0-7.125,5.797-12.922,12.922-12.922V6.875C12.763,6.875,6.875,12.763,6.875,20S12.763,33.125,20,33.125v-0.203
C12.875,32.922,7.078,27.125,7.078,20z">
<animateTransform
attributeName="transform"
type="rotate"
from="0 20 20"
to="360 20 20"
dur="1.8s"
repeatCount="indefinite" />
</path>
</svg>
</div>
</html>
.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;
}
.custom-select-large {
font-size: 1.25rem;
padding: 0.5rem;
height: 2.5rem;
}
.player-container {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #fff;
border: 1px solid #ccc;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
.player-container audio {
width: 100%;
}
.close-btn {
position: absolute;
top: 10px;
right: 10px;
cursor: pointer;
}
.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;
}
.navbar-nav {
margin-left: auto; /* Align the dropdown to the right */
}
.container {
margin-top: 50px; /* Adjust the top margin as needed */
}
@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 */
}
}
.btn-primary {
background-color: #286090 !important;
border-color: #286090 !important;
color: #fff !important;
}
.btn-primary:hover {
background-color: #204d74 !important;
border-color: #204d74 !important;
}
.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;
}
program webKGOrders;
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.ErrorPage in 'View.ErrorPage.pas' {FViewErrorPage: TWebForm} {*.html},
App.Config in 'App.Config.pas',
Paginator.Plugins in 'Paginator.Plugins.pas',
View.Calls in 'View.Calls.pas' {FViewCalls: TWebForm} {*.html},
View.Main in 'View.Main.pas' {FViewMain: TWebForm} {*.html},
View.Home in 'View.Home.pas' {FViewHome: 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};
{$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
if (not AuthService.Authenticated) or AuthService.TokenExpired then
DisplayLoginView
else
DisplayMainView;
end;
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TDMConnection, DMConnection);
Application.Run;
DMConnection.InitApp(@StartApplication, @UnauthorizedAccessProc);
end.
object ApiServerModule: TApiServerModule
Height = 273
Width = 230
object SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher
Active = True
Left = 84
Top = 30
end
object XDataServer: TXDataServer
BaseUrl = 'http://localhost:2004/emsys/envoy/api/'
Dispatcher = SparkleHttpSysDispatcher
EntitySetPermissions = <>
SwaggerOptions.Enabled = True
SwaggerOptions.AuthMode = Jwt
SwaggerUIOptions.Enabled = True
SwaggerUIOptions.ShowFilter = True
SwaggerUIOptions.TryItOutEnabled = True
Left = 85
Top = 110
object XDataServerLogging: TSparkleGenericMiddleware
OnMiddlewareCreate = XDataServerLoggingMiddlewareCreate
end
object XDataServerCORS: TSparkleCorsMiddleware
end
object XDataServerCompress: TSparkleCompressMiddleware
end
object XDataServerJWT: TSparkleJwtMiddleware
OnGetSecret = XDataServerJWTGetSecret
end
end
end
// Server Module for the API part of the project.
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;
XDataServer: TXDataServer;
XDataServerLogging: TSparkleGenericMiddleware;
XDataServerCORS: TSparkleCorsMiddleware;
XDataServerCompress: TSparkleCompressMiddleware;
XDataServerJWT: TSparkleJwtMiddleware;
procedure XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
procedure XDataServerJWTGetSecret(Sender: TObject; var Secret: string);
private
{ Private declarations }
public
{ Public declarations }
procedure StartApiServer(ABaseUrl: 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);
var
Url: string;
begin
RegisterOpenApiService;
Url := ABaseUrl;
if not Url.EndsWith('/') then
Url := Url + '/';
Url := Url + SERVER_PATH_SEGMENT;
XDataServer.BaseUrl := Url;
SparkleHttpSysDispatcher.Start;
Logger.Log(1, Format('Api server module listening at "%s"', [Url]));
end;
procedure TApiServerModule.XDataServerLoggingMiddlewareCreate(Sender: TObject;
var Middleware: IHttpServerMiddleware);
begin
Middleware := TLoggingMiddleware.Create(Logger);
end;
procedure TApiServerModule.XDataServerJWTGetSecret(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
BaseUrl = 'http://localhost:2004/emsys/envoy/app/'
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 = ucKG
SQL.Strings = (
'select * from users')
FetchRows = 100
Left = 162
Top = 45
end
object uqMisc: TUniQuery
FetchRows = 100
Left = 249
Top = 45
end
object ucKG: TUniConnection
ProviderName = 'MySQL'
Database = 'kg_order_entry'
SpecificOptions.Strings = (
'PostgreSQL.Schema=envoy')
Username = 'root'
Server = '192.168.159.132'
Connected = True
LoginPrompt = False
Left = 67
Top = 131
EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF'
end
object MySQLUniProvider1: TMySQLUniProvider
Left = 230
Top = 140
end
end
// Auth Database to verify logins
unit Auth.Database;
interface
uses
System.SysUtils, System.Classes, IniFiles, Vcl.Forms, MemDS,
Data.DB, DBAccess, Uni, UniProvider, PostgreSQLUniProvider, MySQLUniProvider;
type
TAuthDatabase = class(TDataModule)
uq: TUniQuery;
uqMisc: TUniQuery;
ucKG: TUniConnection;
MySQLUniProvider1: TMySQLUniProvider;
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;
iniStr: string;
begin
IniFile := TIniFile.Create( ChangeFileExt(Application.ExeName, '.ini') );
try
iniStr := IniFile.ReadString( 'Database', 'Server', '' );
if iniStr.IsEmpty then
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Database->Server: Entry not found' )
else
begin
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Database->Server: ' + iniStr );
ucKG.Server := iniStr;
end;
iniStr := IniFile.ReadString( 'Twilio', 'AccountSID', '' );
if iniStr.IsEmpty then
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Twilio->AccountSID: Entry not found' )
else
begin
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Twilio->AccountSID: ' + iniStr );
//accountSID := iniStr;
end;
iniStr := IniFile.ReadString( 'Twilio', 'AuthHeader', '' );
if iniStr.IsEmpty then
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Twilio->AuthHeader: Entry not found' )
else
begin
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Twilio->AuthHeader: ' + iniStr );
//authHeader := iniStr;
end;
Logger.Log(1, '');
finally
IniFile.Free;
end;
end;
procedure TAuthDatabase.DataModuleDestroy(Sender: TObject);
begin
ucKG.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'];
userid := userInfo.Values['userId'];
personnelid := userInfo.Values['personnelid'];
finally
userInfo.Free;
end;
end;
end.
object AuthServerModule: TAuthServerModule
Height = 273
Width = 230
object SparkleHttpSysDispatcher: TSparkleHttpSysDispatcher
Left = 88
Top = 16
end
object XDataServer: TXDataServer
BaseUrl = 'http://localhost:2004/emsys/envoy/auth/'
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
// Auth Server Module for the project
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.
// Auth Interface service declaration
unit Auth.Service;
interface
uses
XData.Service.Common,
Aurelius.Mapping.Attributes,
System.Generics.Collections;
const
AUTH_MODEL = 'Auth';
type
[ServiceContract, Model(AUTH_MODEL)]
IAuthService = interface(IInvokable)
['{9CFD59B2-A832-4F82-82BB-9A25FC93F305}']
function Login(const user, password: string): string;
function VerifyVersion(version: string): string;
end;
implementation
end.
// Implementation of Auth Serice that will eventually retrieve login information
// from the auth database.
unit Auth.ServiceImpl;
interface
uses
XData.Service.Common,
XData.Server.Module,
Auth.Service,
Auth.Database,
Uni, Data.DB, System.Hash;
type
[ServiceImplementation]
TAuthService = class(TInterfacedObject, IAuthService)
strict private
authDB: TAuthDatabase;
function GetQuery: TUniQuery;
private
userName: string;
userFullName: string;
userId: string;
userAdmin: boolean;
userPhone: string;
userEmail: string;
procedure AfterConstruction; override;
procedure BeforeDestruction; override;
property Query: TUniQuery read GetQuery;
function CheckUser(const user, password: string): Integer;
public
function Login(const user, password: string): string;
function VerifyVersion(version: string): string;
end;
implementation
uses
System.SysUtils,
System.DateUtils,
System.Generics.Collections,
Bcl.JOSE.Core.Builder,
Bcl.JOSE.Core.JWT,
Aurelius.Global.Utils,
XData.Sys.Exceptions,
Common.Logging,
Common.Config,
uLibrary;
{ TLoginService }
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.VerifyVersion(version: string): string;
begin
if( version <> '1.0.0' ) then
begin
Logger.Log( 2, 'TLoginService.GetAgenciesConfigList - Error: wrong ver!' );
result := 'Error - You have the wrong version! Please clear your cache and refresh!';
Exit;
end;
result := '';
end;
function TAuthService.Login(const user, password: string): string;
// Login verification: currently checks if logins are the same or if the user
// is admin, checks for admin password. Eventually will do a database lookup
// instead. If the user has a JWT token they do not need to login.
// Potential error occuring when logging in after you have already logged in
// webcharms.
var
userState: Integer;
JWT: TJWT;
begin
Logger.Log(1, Format( 'AuthService.Login - User: "%s"', [User]));
userState := CheckUser( user, password );
if userState = 0 then
raise EXDataHttpUnauthorized.Create('Invalid username or password');
if userState = 1 then
raise EXDataHttpUnauthorized.Create('User does not exist!');
if userState = 2 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_id', userId);
JWT.Claims.SetClaimOfType<boolean>('user_admin', userAdmin);
JWT.Claims.SetClaimOfType<string>('user_phone', userPhone);
JWT.Claims.SetClaimOfType<string>('user_email', userEmail);
Result := TJOSE.SHA256CompactToken(serverConfig.jwtTokenSecret, JWT);
finally
JWT.Free;
end;
end;
function TAuthService.CheckUser(const user, password: string): Integer;
var
userStr: string;
SQL: string;
date_created: string;
checkString: string;
begin
//authDB := TAuthDatabase.Create(nil);
Result := 0;
//Logger.Log( 3, Format('AuthService.CheckUser - User: "%s"', [user]) );
SQL := 'select * from users where USER_NAME = ' + QuotedStr(user);
DoQuery(authDB.uq, SQL);
if authDB.uq.IsEmpty then
begin
Result := 1; //user does not exist, replace this with 0 for more security
end
else if ( authDB.uq.FieldByName('STATUS').AsString <> 'ACTIVE' ) then
Result := 2 // user is not active
else
begin
//date_created := authDB.uq.FieldByName('date_created').AsString;
//checkString := THashSHA2.GetHashString(date_created + password, THashSHA2.TSHA2Version.SHA512).ToUpper;
if password = authDB.uq.FieldByName('PASSWORD').AsString then
begin
{userName := user;
userFullName:= authDB.uq.FieldByName('full_name').AsString;;
userId := authDB.uq.FieldByName('user_id').AsString;
userAdmin := authDB.uq.FieldByName('admin').AsBoolean;
userPhone := authDB.uq.FieldByName('phone_number').AsString;
userEmail := authDB.uq.FieldByName('email').AsString;
userStr := '?username=' + userName;
userStr := userStr + '&fullname=' + userFullName;
userStr := userStr + '&userid=' + userId;
userStr := userStr + '&useradmin=' + BoolToStr(userAdmin);}
//Logger.Log( 3, Format('AuthDB.SetLoginAuditEntry: "%s"', [user]) );
//AuthDB.SetLoginAuditEntry( userStr );
Result := 3; // Succcess
end
else
Result := 0; // invalid password
end;
end;
initialization
RegisterServiceType(TAuthService);
end.
// The configuartion file for the program. Contains important info like the admin
// password and the secret token. Should likely move this to the ini file..
unit Common.Config;
interface
const
defaultServerUrl = 'http://localhost:2004/emsys/envoycalls/';
type
TServerConfig = class
private
Furl: string;
FJWTTokenSecret: string;
FAdminPassword: string;
FWebAppFolder: string;
FMemoLogLevel: Integer;
FFileLogLevel: Integer;
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 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,
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' );
end
else
begin
Logger.Log( 1, '-- Config file not found.' );
end;
Logger.Log( 1, '-------------------------------------------------------------' );
Logger.Log( 1, '-- serverConfig.Server url: ' + serverConfig.url );
Logger.Log( 1, '-- serverConfig.adminPassword: ' + serverConfig.adminPassword );
Logger.Log( 1, '-- serverConfig.jwtTokenSecret: ' + serverConfig.jwtTokenSecret );
Logger.Log( 1, '-- serverConfig.webAppFolder: ' + serverConfig.webAppFolder );
Logger.Log( 1, '-- serverConfig.memoLogLevel: ' + IntToStr(serverConfig.memoLogLevel) );
Logger.Log( 1, '-- serverConfig.fileLogLevel: ' + IntToStr(serverConfig.fileLogLevel) );
Logger.Log( 1, '--LoadServerConfig - end' );
end;
{ TServerConfig }
constructor TServerConfig.Create;
//var
// ConfigFile: string;
// ServerConfigStr: string;
begin
Logger.Log( 1, '--TServerConfig.Create - start' );
url := defaultServerUrl;
adminPassword := 'whatisthisusedfor';
jwtTokenSecret := 'super_secret0123super_secret4567';
webAppFolder := 'static';
memoLogLevel := 3;
fileLogLevel := 4;
// ServerConfigStr := Bcl.Json.TJson.Serialize( ServerConfig );
// ConfigFile := 'serverconfig.json';
// TFile.WriteAllText( ConfigFile, ServerConfigStr );
// Logger.Log( 1, 'ServerConfig saved to file: ' + ConfigFile );
Logger.Log( 1, '--TServerConfig.Create - 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.
// 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)
dsUsers: TDataSource;
btnFind: TButton;
Memo1: TMemo;
DBAdvGrid1: TDBAdvGrid;
DBAdvGrid2: TDBAdvGrid;
dsRecordings: TDataSource;
edtUsername: TEdit;
edtPassword: TEdit;
lblHash: TLabel;
btnAddUser: TButton;
lblHash2: TLabel;
uqUsers: TUniQuery;
cbAdmin: TCheckBox;
edtFullName: TEdit;
edtPhoneNumber: TEdit;
edtEmailAddress: TEdit;
procedure FormCreate(Sender: TObject);
procedure btnFindClick(Sender: TObject);
private
{ Private declarations }
accountSID: string;
authHeader: string;
public
{ Public declarations }
end;
var
FData: TFData;
implementation
{$R *.dfm}
uses Database, uLibrary;
procedure TFData.FormCreate(Sender: TObject);
begin
FDatabaseModule := TFDatabaseModule.Create(Self);
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
Memo1.Lines.Add(uqUsers.Connection.Server);
SQL := 'select * from users';
uqUsers.Close;
uqUsers.SQL.Text := sql;
uqUsers.Open;
DBAdvGrid1.AutoSizeColumns(true);
end;
end.
object FDatabaseModule: TFDatabaseModule
OnCreate = DataModuleCreate
Height = 480
Width = 640
object ucKG: TUniConnection
ProviderName = 'MySQL'
Database = 'kg_order_entry'
SpecificOptions.Strings = (
'PostgreSQL.Schema=envoy')
Username = 'root'
Server = '192.168.159.132'
Connected = True
LoginPrompt = False
Left = 75
Top = 139
EncryptedPassword = '9AFF92FF8CFF86FF8CFFCFFFCEFF'
end
object UniQuery1: TUniQuery
Connection = ucKG
SQL.Strings = (
'')
Left = 363
Top = 138
end
object MySQLUniProvider1: TMySQLUniProvider
Left = 220
Top = 134
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 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, MySQLUniProvider;
type
TFDatabaseModule = class(TDataModule)
ucKG: TUniConnection;
UniQuery1: TUniQuery;
MySQLUniProvider1: TMySQLUniProvider;
procedure DataModuleCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
class procedure ExecSQL(const SQL: string);
end;
var
FDatabaseModule: TFDatabaseModule;
implementation
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TFDatabaseModule.DataModuleCreate(Sender: TObject);
// Sets the database connection to the ini file IP
// TODO: clean up unnecessary reads from the ini file
var
IniFile: TIniFile;
iniStr: string;
begin
IniFile := TIniFile.Create( ChangeFileExt(Application.ExeName, '.ini') );
try
iniStr := IniFile.ReadString( 'Database', 'Server', '' );
if iniStr.IsEmpty then
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Database->Server: Entry not found' )
else
begin
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Database->Server: ' + iniStr );
ucKG.Server := iniStr;
end;
iniStr := IniFile.ReadString( 'Twilio', 'AccountSID', '' );
if iniStr.IsEmpty then
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Twilio->AccountSID: Entry not found' )
else
begin
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Twilio->AccountSID: ' + iniStr );
//accountSID := iniStr;
end;
iniStr := IniFile.ReadString( 'Twilio', 'AuthHeader', '' );
if iniStr.IsEmpty then
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Twilio->AuthHeader: Entry not found' )
else
begin
Logger.Log( 1, 'iniFile: ' + ChangeFileExt(Application.ExeName, '.ini') +
' Twilio->AuthHeader: ' + iniStr );
//authHeader := iniStr;
end;
Logger.Log(1, '');
finally
IniFile.Free;
end;
end;
class procedure TFDatabaseModule.ExecSQL(const SQL: string);
var
DB: TFDatabaseModule;
begin
DB := TFDatabaseModule.Create(nil);
try
DB.UniQuery1.SQL.Text := SQL;
DB.UniQuery1.ExecSQL;
finally
DB.Free;
end;
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;
password: string;
full_name: string;
status: string;
email_address: string;
Atype: string;
rights: integer;
perspectiveID: string;
QBID: string;
end;
TUserList = class
public
count: integer;
data: TList<TUserItem>;
end;
type
[ServiceContract]
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.
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