* Implement ClockIN service for IDID Biometrics
* Make Biometrics service configurable to domain * Use native threading with flask (remove 'eventlet') * Other minor UI adjustment.
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
step: 0,
|
||||
phone: '',
|
||||
domain: { code: 'uc', name: 'UNICAL' },
|
||||
errorMessage: '',
|
||||
otp: '',
|
||||
busy: {
|
||||
login: false
|
||||
},
|
||||
user: {
|
||||
phone: '',
|
||||
username: '',
|
||||
roles: []
|
||||
},
|
||||
staff: {
|
||||
reference: ''
|
||||
},
|
||||
fingerDetails: {
|
||||
'right-thumb': { },
|
||||
'right-index-finger': { }
|
||||
},
|
||||
socket: null
|
||||
},
|
||||
created: function () {
|
||||
var userStr = localStorage.getItem("user");
|
||||
if (userStr) {
|
||||
this.user = JSON.parse(userStr);
|
||||
this.step = 1;
|
||||
}
|
||||
|
||||
this.socket = io();
|
||||
|
||||
this.socket.on("connect", () => { })
|
||||
|
||||
this.socket.on("config", (data) => {
|
||||
this.domain = data.domain;
|
||||
});
|
||||
|
||||
this.socket.on("fprint-enroll-error", (data) => {
|
||||
console.log({ event: 'enroll-error', data });
|
||||
swal("Oops!", `Operation failed. ${data}.`, "error");
|
||||
this.busy.login = false;
|
||||
});
|
||||
|
||||
this.socket.on("fprint-enroll-done", (data) => {
|
||||
console.log({ event: 'enroll-done', data});
|
||||
if (data.ok) {
|
||||
let { id, fingerprint, data: { finger }} = data;
|
||||
this.step = 1.2;
|
||||
this.fingerDetails[finger] = { id, fingerprint, finger }
|
||||
}
|
||||
this.busy.login = false;
|
||||
})
|
||||
|
||||
this.socket.on("fprint-verify-done", (data) => {
|
||||
console.log({ event: 'verify-done', data});
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
authenticateUrl: function (api, authenticate = true) {
|
||||
return `https://apis.a2loft.net/identity${api}` + (authenticate ? `?phone=${this.user.phone}&otp=${this.otp}` : '');
|
||||
},
|
||||
login: async function () {
|
||||
this.busy.login = true;
|
||||
var { data } = await axios.get(
|
||||
this.authenticateUrl(`/validate/uc/${this.phone}/${this.otp}`, false)
|
||||
);
|
||||
this.errorMessage = '';
|
||||
if (!data.ok)
|
||||
this.errorMessage = "Invalid OTP specified.";
|
||||
else {
|
||||
localStorage.setItem("user", JSON.stringify(data.data));
|
||||
this.user = data.data;
|
||||
this.step = 1.0;
|
||||
}
|
||||
this.busy.login = false;
|
||||
},
|
||||
logout: function () {
|
||||
localStorage.removeItem("user");
|
||||
this.step = 0;
|
||||
},
|
||||
loadUserByReference: async function () {
|
||||
this.busy.login = true;
|
||||
try {
|
||||
var { data } = await axios.get(
|
||||
this.authenticateUrl(`/${this.domain.code}/${this.staff.reference}`)
|
||||
);
|
||||
if (!data.ok) {
|
||||
swal("Oops!", `Operation failed. ${data.message}.` + this.staff.reference, "error");
|
||||
} else {
|
||||
this.staff = data.data;
|
||||
// this.staff.verified = false;
|
||||
this.step = 1.1;
|
||||
}
|
||||
} finally {
|
||||
this.busy.login = false;
|
||||
}
|
||||
},
|
||||
captureFingerPrint: async function (finger) {
|
||||
this.busy.login = true;
|
||||
this.step = 1.1;
|
||||
// var { data: { success, message: { id, data, fingerprint } } } = await axios.post("http://localhost:9000/api/user", {
|
||||
// name: this.staff.reference
|
||||
// });
|
||||
// this.fingerDetails = { id, fingerprint };
|
||||
// var { data } = await axios.delete(`http://localhost:9000/api/user/${id}`);
|
||||
// console.log(`${id} purged.`);
|
||||
this.socket.emit("fprint-enroll", { finger });
|
||||
},
|
||||
saveFingerPrint: async function () {
|
||||
this.busy.login = true;
|
||||
try {
|
||||
var fingerprints = Object.keys(this.fingerDetails).map(finger => {
|
||||
return {
|
||||
finger,
|
||||
fingerprint: this.fingerDetails[finger].fingerprint
|
||||
}
|
||||
});
|
||||
|
||||
if (fingerprints.length < 2) {
|
||||
swal("Oops!", 'Please capture the two fingers for this profile.', "error");
|
||||
return;
|
||||
}
|
||||
|
||||
var { data } = await axios.post(
|
||||
this.authenticateUrl(`/${this.domain.code}/fingerprints/${this.staff.reference}`),
|
||||
{ fingerprints }
|
||||
);
|
||||
|
||||
if (data.ok) {
|
||||
swal("Saved", `Fingerprint saved for ${this.staff.name} with reference ${this.staff.reference}.`, "success");
|
||||
this.newEnrolment();
|
||||
} else {
|
||||
swal("Oops!", `Operation failed. ${data.message}.` + this.staff.reference, "error");
|
||||
}
|
||||
} finally {
|
||||
this.busy.login = false;
|
||||
}
|
||||
},
|
||||
newEnrolment: function () {
|
||||
this.step = 1.0;
|
||||
this.busy.login = false;
|
||||
this.staff.reference = "";
|
||||
this.fingerDetails = {
|
||||
'right-thumb': { },
|
||||
'right-index-finger': { }
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,175 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Fingerprint Service</title>
|
||||
<script src="https://cdn.socket.io/4.4.0/socket.io.min.js" integrity="sha384-1fOn6VtTq3PWwfsOrk45LnYcGosJwzMHv+Xh/Jx5303FVOXzEnw0EpLv30mtjmlj" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
<style>
|
||||
.disabledbutton {
|
||||
pointer-events: none;
|
||||
opacity: 0.4;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body style="font-size: 14px; font-family: 'Segoe UI';">
|
||||
<div class="container" id="app">
|
||||
<div class="col-md-12">
|
||||
<div style="margin-top: 100px;">
|
||||
|
||||
</div>
|
||||
<h1>
|
||||
{{domain.name}} Biometrics Enrolment Wizard
|
||||
</h1>
|
||||
<hr />
|
||||
<div v-if="step > 0" style="text-align: right">
|
||||
<span style="cursor: pointer;" v-on:click="newEnrolment">+ New
|
||||
Enrolment</span> •
|
||||
<span>~{{user.username.substring(7)}}</span> • <span v-on:click='logout'
|
||||
style="cursor: pointer; font-weight: bold;">Logout</span>
|
||||
<hr />
|
||||
</div>
|
||||
<div v-if="step == 0">
|
||||
<div style="background-color: whitesmoke; margin-left: auto; margin-right: auto; padding: 10px;">
|
||||
<form class="form" style="padding: 20px;">
|
||||
<h3>Agent Login</h3>
|
||||
<br />
|
||||
<div class="form-group">
|
||||
<label>Phone Number</label>
|
||||
<input type="text" v-model="phone" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>One Time Password</label>
|
||||
<input type="password" v-model="otp" class="form-control" />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="btn btn-success btn-block" v-bind:class="{ disabledbutton: busy.login || !phone || !otp }"
|
||||
v-on:click="login">
|
||||
<span v-if="!busy.login">Login</span>
|
||||
<span v-else>Please wait...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group" v-if="errorMessage">
|
||||
<div class="alert alert-danger">{{errorMessage}}</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="step >= 1">
|
||||
<div v-if="step == 1.0">
|
||||
<form class="form" style="padding: 20px;">
|
||||
<h3>New Profile Enrollment</h3>
|
||||
<br />
|
||||
<div class="form-group">
|
||||
<label>Reference Number</label>
|
||||
<input type="text" v-model="staff.reference" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>OTP</label>
|
||||
<input type="text" v-model="otp" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="btn btn-success" v-bind:class="{ disabledbutton: busy.login || !staff.reference || !otp }"
|
||||
v-on:click="loadUserByReference">
|
||||
<span v-if="!busy.login">Get Details</span>
|
||||
<span v-else>Please wait...</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div v-if="step >= 1.1">
|
||||
<form class="form form-horizontal" style="padding: 20px;">
|
||||
<h3>Enroll Profile</h3>
|
||||
<br />
|
||||
<div class="form-group">
|
||||
<label>Reference Number</label>
|
||||
<input type="text" readonly v-model="staff.reference" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Name</label>
|
||||
<input type="text" readonly v-model="staff.name" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Reference</label>
|
||||
<input type="text" readonly v-model="staff.domainReference" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Rank</label>
|
||||
<input type="text" readonly v-model="staff.domainRankDescription" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Total Fingerprint Enrollment(s)</label>
|
||||
<input type="text" readonly v-model="staff.fingerPrintCaptureCount" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group" v-if="step >= 1.1">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Finger</label>
|
||||
<input type="text" readonly value="Right Thumb" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group" v-if="fingerDetails['right-thumb'].id">
|
||||
<label>Enrolment ID</label>
|
||||
<input type="text" readonly v-model="fingerDetails['right-thumb'].id" class="form-control" />
|
||||
</div>
|
||||
<div class="btn btn-success" style="display: inline" v-if="!fingerDetails['right-thumb'].id" v-bind:class="{ disabledbutton: busy.login }"
|
||||
v-on:click="() => { captureFingerPrint('right-thumb') }">
|
||||
<span v-if="!busy.login">Capture Fingerprint</span>
|
||||
<span v-else>Please wait...</span>
|
||||
</div>
|
||||
<div class="btn btn-warning" style="display: inline" v-if="fingerDetails['right-thumb'].id" v-bind:class="{ disabledbutton: busy.login }"
|
||||
v-on:click="() => { captureFingerPrint('right-thumb') }">
|
||||
<span v-if="!busy.login">Recapture Fingerprint</span>
|
||||
<span v-else>Please wait...</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<label>Finger</label>
|
||||
<input type="text" readonly value="Right Index Finger" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group" v-if="fingerDetails['right-index-finger'].id">
|
||||
<label>Enrolment ID</label>
|
||||
<input type="text" readonly v-model="fingerDetails['right-index-finger'].id" class="form-control" />
|
||||
</div>
|
||||
<div class="btn btn-success" style="display: inline" v-if="!fingerDetails['right-index-finger'].id" v-bind:class="{ disabledbutton: busy.login }"
|
||||
v-on:click="() => { captureFingerPrint('right-index-finger') }">
|
||||
<span v-if="!busy.login">Capture Fingerprint</span>
|
||||
<span v-else>Please wait...</span>
|
||||
</div>
|
||||
<div class="btn btn-warning" style="display: inline" v-if="fingerDetails['right-index-finger'].id" v-bind:class="{ disabledbutton: busy.login }"
|
||||
v-on:click="() => { captureFingerPrint('right-index-finger') }">
|
||||
<span v-if="!busy.login">Recapture Fingerprint</span>
|
||||
<span v-else>Please wait...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Your profile has <span style="color: red; font-weight: bold;" v-if="!staff.verified">NOT </span>been verified. -->
|
||||
</div>
|
||||
<div v-if="fingerDetails['right-thumb'].id && fingerDetails['right-index-finger'].id">
|
||||
<div class="form-group">
|
||||
<label>OTP</label>
|
||||
<input type="text" v-model="otp" class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="btn btn-success" v-bind:class="{ disabledbutton: busy.login || !otp }"
|
||||
v-on:click="saveFingerPrint">
|
||||
<span v-if="!busy.login">Save Finger Print</span>
|
||||
<span v-else>Please wait...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- production version, optimized for size and speed -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
|
||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
||||
<script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
|
||||
<script src="/assets/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user