chore!: retire the python fingerprint service
app.py, requirement.txt, public/ (capture + clockio UIs), .config.json handling and the identify test image are removed — capture now runs via the fpagent binary and the web UI lives in tools/ (see fpagent-tools-ui).
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"mode": "capture",
|
||||
"domain": {
|
||||
"name": "UCTH",
|
||||
"code": "ucth"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -1,315 +0,0 @@
|
||||
import datetime
|
||||
import json
|
||||
import socketio
|
||||
import uuid
|
||||
import base64
|
||||
import os
|
||||
import requests
|
||||
from flask import Flask
|
||||
import subprocess
|
||||
|
||||
import gi
|
||||
gi.require_version("FPrint", "2.0")
|
||||
gi.require_version("Gio", "2.0")
|
||||
from gi.repository import FPrint, GLib, Gio
|
||||
|
||||
config = {
|
||||
'domain': {
|
||||
'name': 'UNICAL',
|
||||
'code': 'uc'
|
||||
},
|
||||
'mode': 'capture'
|
||||
}
|
||||
config_default = config;
|
||||
|
||||
os.chdir(os.path.dirname(__file__));
|
||||
|
||||
configFileExists = os.path.exists('.config.json');
|
||||
if configFileExists:
|
||||
with open('.config.json') as reader:
|
||||
try:
|
||||
|
||||
config = json.loads(reader.read());
|
||||
except:
|
||||
config = config_default
|
||||
|
||||
sio = socketio.Server(async_mode="threading")
|
||||
app = Flask(__name__)
|
||||
static_files = {
|
||||
'/assets': './public/{}'.format(config['mode']),
|
||||
'/': { 'filename': './public/{}/index.html'.format(config['mode']) }
|
||||
}
|
||||
app.wsgi_app = socketio.WSGIApp(sio, static_files=static_files)
|
||||
|
||||
stopEnrollment = False
|
||||
def progress(device, completed_stages, prnt, sessionId, error):
|
||||
print({'completedStages': completed_stages, 'sessionId': sessionId})
|
||||
global stopEnrollment
|
||||
if (error):
|
||||
stopEnrollment = True
|
||||
print({'error': error})
|
||||
else:
|
||||
if (completed_stages == 5):
|
||||
if (not stopEnrollment):
|
||||
sio.emit("fprint-enroll-status", { 'stage': completed_stages }, room=sessionId)
|
||||
else:
|
||||
print('Completed with an error.')
|
||||
|
||||
enroll_canceller = None;
|
||||
def enroll_start(sessionId, data):
|
||||
global enroll_canceller;
|
||||
|
||||
ctx = FPrint.Context()
|
||||
devices = ctx.get_devices()
|
||||
if (len(devices) == 0):
|
||||
print("There is no attached device.")
|
||||
sio.emit('fprint-enroll-error', "There is no attached device.");
|
||||
return;
|
||||
else:
|
||||
device = devices[0]
|
||||
if (not device):
|
||||
print("There is no attached device")
|
||||
|
||||
if (not device.is_open()):
|
||||
device.open_sync()
|
||||
while (not device.is_open()):
|
||||
print('Opening...')
|
||||
try:
|
||||
prt = FPrint.Print.new(device)
|
||||
enroll_canceller = Gio.Cancellable.new();
|
||||
prt = device.enroll_sync(prt, enroll_canceller, progress_cb=progress, progress_data=sessionId)
|
||||
img = prt.serialize()
|
||||
sio.emit('fprint-enroll-done', { 'ok': True, "fingerprint": base64.b64encode(img).decode('utf-8'), 'id': str(uuid.uuid4()), 'data': data })
|
||||
except Exception as e:
|
||||
print({'error': e});
|
||||
sio.emit('fprint-enroll-done', { 'ok': False, 'message': e.message})
|
||||
finally:
|
||||
try:
|
||||
device.close_sync()
|
||||
except:
|
||||
print('An error occured while closing device.');
|
||||
finally:
|
||||
print('It appears device has already been removed.')
|
||||
|
||||
is_verify_active = False
|
||||
device = None;
|
||||
|
||||
def verify_start(sessionId, data):
|
||||
global device;
|
||||
global is_verify_active;
|
||||
|
||||
ctx = FPrint.Context()
|
||||
devices = ctx.get_devices()
|
||||
if (len(devices) == 0):
|
||||
print("There is no attached device.")
|
||||
sio.emit('fprint-verify-start-error', "There is no attached device.");
|
||||
return;
|
||||
else:
|
||||
device = devices[0]
|
||||
if (not device):
|
||||
print("There is no attached device")
|
||||
|
||||
if (not device.is_open()):
|
||||
device.open_sync()
|
||||
while (not device.is_open()):
|
||||
print('Opening...')
|
||||
|
||||
while True:
|
||||
with open('adebisi-fa.jpg', 'r+b') as f:
|
||||
img = f.read()
|
||||
prtRecord = FPrint.Print.deserialize(img)
|
||||
(match, prtFound) = device.verify_sync(prtRecord, Gio.Cancellable.new(), None)
|
||||
if (match):
|
||||
sio.emit('fprint-verify-start-done', { 'ok': True, 'data': data }, room=sessionId)
|
||||
else:
|
||||
sio.emit('fprint-verify-start-done', { 'ok': False }, room=sessionId)
|
||||
|
||||
if not is_verify_active:
|
||||
break;
|
||||
|
||||
if device.is_open():
|
||||
device.close_sync()
|
||||
pass
|
||||
|
||||
is_identify_active = False
|
||||
identify_canceller = None
|
||||
raw_prints = None
|
||||
def identify_start(sessionId, data):
|
||||
global device;
|
||||
global is_identify_active;
|
||||
global identify_canceller;
|
||||
global raw_prints;
|
||||
|
||||
ctx = FPrint.Context()
|
||||
devices = ctx.get_devices()
|
||||
if (len(devices) == 0):
|
||||
print("There is no attached device.")
|
||||
sio.emit('fprint-identify-start-error', "There is no attached device.");
|
||||
return;
|
||||
else:
|
||||
device = devices[0]
|
||||
if (not device):
|
||||
print("There is no attached device")
|
||||
|
||||
if (not device.is_open()):
|
||||
device.open_sync()
|
||||
while (not device.is_open()):
|
||||
print('Opening...')
|
||||
|
||||
sio.emit('fprint-identify-start-done', room=sessionId)
|
||||
is_identify_active = True
|
||||
while True:
|
||||
prtRecords = [];
|
||||
with open('adebisi-fa.jpg', 'r+b') as f:
|
||||
img = f.read()
|
||||
prtRecords = [FPrint.Print.deserialize(img)]
|
||||
|
||||
try:
|
||||
identify_canceller = Gio.Cancellable.new();
|
||||
(match, prtFound) = device.identify_sync(prtRecords, identify_canceller, None)
|
||||
if (match):
|
||||
sio.emit('fprint-identify-done', { 'ok': True, 'name': 'Adebisi Foluso A.', 'phone': "+2348068287222" }, room=sessionId)
|
||||
print('Found.')
|
||||
else:
|
||||
sio.emit('fprint-identify-done', { 'ok': False }, room=sessionId)
|
||||
print('Not found.')
|
||||
except:
|
||||
print("Identification stopped due to error.")
|
||||
sio.emit("fprint-identify-stop-done", { 'ok': True, 'message': 'Identification stopped due to error.' })
|
||||
is_identify_active = False;
|
||||
finally:
|
||||
if not is_identify_active:
|
||||
print('Identification is active. Quiting...')
|
||||
break;
|
||||
pass
|
||||
|
||||
if device.is_open():
|
||||
try:
|
||||
device.close_sync()
|
||||
except:
|
||||
print('An error occured while closing device.');
|
||||
finally:
|
||||
print('It appears device has already been removed.')
|
||||
|
||||
# events
|
||||
|
||||
@sio.on("fprint-verify-stop")
|
||||
def fprint_verify_stop(sid, data):
|
||||
global is_verify_active;
|
||||
|
||||
if (device.is_open()):
|
||||
try:
|
||||
device.close_sync();
|
||||
sio.emit("fprint-verify-stop-done", { 'ok': True, 'message': 'Verifycation stopped.' })
|
||||
return;
|
||||
except:
|
||||
print('An error occured while closing device.');
|
||||
finally:
|
||||
print("It appears device has already been removed.")
|
||||
else:
|
||||
sio.emit("fprint-verify-stop-done", { 'ok': False, 'message': 'Verifycation was inactive.' })
|
||||
|
||||
is_verify_active = False;
|
||||
pass
|
||||
|
||||
|
||||
@sio.on("fprint-verify-start")
|
||||
def fprint_verify_start(sid, data):
|
||||
verify_start(sid, data)
|
||||
|
||||
def stop_fprint_identify():
|
||||
global is_identify_active;
|
||||
global identify_canceller;
|
||||
|
||||
if identify_canceller:
|
||||
identify_canceller.cancel()
|
||||
|
||||
sio.emit("fprint-identify-stop-done", { 'ok': True, 'message': 'Identification stopped.' })
|
||||
is_identify_active = False;
|
||||
|
||||
def stop_fprint_enroll():
|
||||
global enroll_canceller;
|
||||
|
||||
if enroll_canceller:
|
||||
enroll_canceller.cancel()
|
||||
|
||||
sio.emit("fprint-enroll-done", { 'ok': False, 'message': 'Enrollment stopped.' })
|
||||
|
||||
@sio.on("fprint-identify-start")
|
||||
def fprint_identify_start(sid, data):
|
||||
identify_start(sid, data)
|
||||
|
||||
@sio.on("fprint-identify-stop")
|
||||
def fprint_identify_stop(sid, data):
|
||||
stop_fprint_identify()
|
||||
|
||||
@sio.on("fprint-enroll")
|
||||
def fprint_enroll_start(sid, data):
|
||||
print('Enrolling {}'.format(data['finger']))
|
||||
enroll_start(sid, data)
|
||||
pass
|
||||
|
||||
@sio.on("fprint-enroll-stop")
|
||||
def fprint_enroll_stop(sid, data):
|
||||
stop_fprint_enroll()
|
||||
|
||||
@sio.event
|
||||
def capture(sid, data):
|
||||
print(data)
|
||||
pass
|
||||
|
||||
@sio.event
|
||||
def connect(sid, data):
|
||||
global config;
|
||||
config['version'] = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).decode('ascii').strip()
|
||||
if config['mode'] == 'clockio':
|
||||
global raw_prints;
|
||||
stop_fprint_identify();
|
||||
config['started'] = is_identify_active
|
||||
domain_code = config['domain']['code'];
|
||||
response = requests.get("https://apis.a2loft.net/identity/{}/units".format(domain_code))
|
||||
config['departments'] = [];
|
||||
if response.ok:
|
||||
responseJson = json.loads(response.text)
|
||||
if responseJson['ok']:
|
||||
config['departments'] = responseJson['data'];
|
||||
domain_fprint_file = '{}/fprint-sync/.{}-fprint-data.json'.format(os.getenv("HOME"), domain_code);
|
||||
config['records'] = {
|
||||
'length': 0,
|
||||
'timestamp': 0
|
||||
};
|
||||
if os.path.exists(domain_fprint_file):
|
||||
with open(domain_fprint_file) as f:
|
||||
records = json.loads(f.read());
|
||||
raw_prints = records['profiles'];
|
||||
prints_dict = {};
|
||||
for profile in raw_prints:
|
||||
for index, fp in enumerate(profile['fingerprints']):
|
||||
reference = profile['reference'];
|
||||
finger = fp['finger']
|
||||
prints_dict['{}-{}-{}'.format(reference, finger, index+1)] = profile
|
||||
config['records'] = {
|
||||
'length': len(records['profiles']),
|
||||
'timestamp': records['timestamp']
|
||||
};
|
||||
else:
|
||||
stop_fprint_enroll();
|
||||
|
||||
sio.emit('config', config);
|
||||
pass
|
||||
|
||||
@sio.event
|
||||
def disconnect(sid):
|
||||
print({ 'sid': sid, 'event': 'disconnect'});
|
||||
pass
|
||||
|
||||
@sio.on('capture-other')
|
||||
def capture_other(sid, data):
|
||||
print({ "event": "capture_other", "data": data })
|
||||
pass
|
||||
|
||||
with open('/tmp/libfprint-server', 'w') as f:
|
||||
f.write(str(os.getpid()));
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(port=9000)
|
||||
@@ -1,155 +0,0 @@
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
step: 0,
|
||||
phone: '',
|
||||
domain: { code: 'uc', name: 'UNICAL' },
|
||||
errorMessage: '',
|
||||
version: '',
|
||||
otp: '',
|
||||
busy: {
|
||||
login: false,
|
||||
capture: 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.version = data.version;
|
||||
this.device = data.device;
|
||||
});
|
||||
|
||||
this.socket.on("fprint-enroll-error", (data) => {
|
||||
console.log({ event: 'enroll-error', data });
|
||||
swal("Oops!", `Operation failed. ${data}.`, "error");
|
||||
this.busy.capture = 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.capture = 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.capture = true;
|
||||
this.step = 1.1;
|
||||
this.socket.emit("fprint-enroll", { finger });
|
||||
},
|
||||
cancelCapture: async function () {
|
||||
this.socket.emit("fprint-enroll-stop", { });
|
||||
},
|
||||
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.busy.capture = false;
|
||||
this.staff.reference = "";
|
||||
this.fingerDetails = {
|
||||
'right-thumb': { },
|
||||
'right-index-finger': { }
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,177 +0,0 @@
|
||||
<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 <span style="color: gainsboro">#{{version}}</span>
|
||||
</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-if="device">{{device}} • </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.capture }"
|
||||
v-on:click="() => { captureFingerPrint('right-thumb') }">
|
||||
<span v-if="!busy.capture">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.capture }"
|
||||
v-on:click="() => { captureFingerPrint('right-thumb') }">
|
||||
<span v-if="!busy.capture">Recapture Fingerprint</span>
|
||||
<span v-else>Please wait...</span>
|
||||
</div>
|
||||
<span style="cursor:pointer;" @click="cancelCapture" v-if="busy.capture"> Cancel</span>
|
||||
</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.capture }"
|
||||
v-on:click="() => { captureFingerPrint('right-index-finger') }">
|
||||
<span v-if="!busy.capture">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.capture }"
|
||||
v-on:click="() => { captureFingerPrint('right-index-finger') }">
|
||||
<span v-if="!busy.capture">Recapture Fingerprint</span>
|
||||
<span v-else>Please wait...</span>
|
||||
</div>
|
||||
<span style="cursor:pointer;" @click="cancelCapture" v-if="busy.capture"> Cancel</span>
|
||||
</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@2"></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>
|
||||
@@ -1,122 +0,0 @@
|
||||
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
data: {
|
||||
domain: { name: '', code: '' },
|
||||
errorMessage: '',
|
||||
staff: { reference: '' },
|
||||
department: '-',
|
||||
departments: {},
|
||||
started: false,
|
||||
ready: false,
|
||||
socket: null,
|
||||
savedDepartments: [],
|
||||
latestReportDate: new Date().toLocaleTimeString(),
|
||||
lastProfile: 'None',
|
||||
configuring: false
|
||||
},
|
||||
created: function () {
|
||||
this.socket = io();
|
||||
|
||||
this.socket.on("connect", () => { })
|
||||
|
||||
this.socket.on("config", (data) => {
|
||||
let deptStr = localStorage.getItem("idid-departments");
|
||||
if (deptStr) this.savedDepartments = JSON.parse(deptStr);
|
||||
if (data.departments)
|
||||
data.departments.forEach(d => {
|
||||
d.name = d.name.replace('*', '');
|
||||
d.checked = this.savedDepartments.includes(d.id);
|
||||
return d;
|
||||
});
|
||||
else
|
||||
data.departments = [];
|
||||
|
||||
this.domain = data.domain;
|
||||
this.started = data.started;
|
||||
this.today = new Date().toDateString();
|
||||
this.records = data.records;
|
||||
this.ready = true;
|
||||
});
|
||||
|
||||
//#region Verify handlers
|
||||
|
||||
this.socket.on("fprint-verify-start-done", (data) => {
|
||||
console.log({ event: 'fprint-verify-start-done', data});
|
||||
this.started = true;
|
||||
});
|
||||
|
||||
this.socket.on("fprint-verify-start-error", (data) => {
|
||||
console.log({ event: 'fprint-verify-start-error', data });
|
||||
swal("Oops!", `Operation failed. ${data}.`, "error");
|
||||
});
|
||||
|
||||
this.socket.on("fprint-verify-stop-done", (data) => {
|
||||
console.log({ event: 'fprint-verify-stop-done', data});
|
||||
this.started = false;
|
||||
});
|
||||
|
||||
this.socket.on("fprint-verify-stop-error", (data) => {
|
||||
console.log({ event: 'fprint-verify-stop-error', data});
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Identify handlers
|
||||
|
||||
this.socket.on("fprint-identify-start-done", (data) => {
|
||||
console.log({ event: 'fprint-identify-start-done', data});
|
||||
this.started = true;
|
||||
});
|
||||
|
||||
this.socket.on("fprint-identify-done", (data) => {
|
||||
console.log({ event: 'fprint-identify-done', data});
|
||||
if (data.ok)
|
||||
this.lastProfile = data.name;
|
||||
else
|
||||
this.lastProfile = "No Match";
|
||||
this.latestReportDate = new Date().toLocaleTimeString();
|
||||
});
|
||||
|
||||
this.socket.on("fprint-identify-start-error", (data) => {
|
||||
console.log({ event: 'fprint-identify-start-error', data});
|
||||
console.log({ event: 'identify-error', data });
|
||||
swal("Oops!", `Operation failed. ${data}.`, "error");
|
||||
});
|
||||
|
||||
this.socket.on("fprint-identify-stop-done", (data) => {
|
||||
console.log({ event: 'fprint-identify-stop-done', data});
|
||||
this.started = false;
|
||||
});
|
||||
|
||||
this.socket.on("fprint-identify-stop-error", (data) => {
|
||||
console.log({ event: 'fprint-identify-stop-error', data});
|
||||
});
|
||||
|
||||
//#endregion
|
||||
},
|
||||
methods: {
|
||||
toggleIdentifyMode: async function () {
|
||||
console.log("toggling clockin status ...");
|
||||
this.socket.emit(`fprint-identify-${this.started ?'stop' : 'start'}`, {
|
||||
departmentId: this.department
|
||||
});
|
||||
},
|
||||
|
||||
toggleCheck: async function (d) {
|
||||
if (!d.checked) {
|
||||
let dpts = this.domain.departments.filter(q => q.checked);
|
||||
if (dpts.length == 10)
|
||||
return alert("Maximum number of departments, 10, selected.");
|
||||
}
|
||||
d.checked = !d.checked;
|
||||
},
|
||||
|
||||
saveDepartments() {
|
||||
let tmp = this.domain.departments.filter(q => q.checked).map(d => d.id);
|
||||
this.savedDepartments = tmp;
|
||||
localStorage.setItem("idid-departments", JSON.stringify(tmp));
|
||||
this.configuring = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 204 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
@@ -1,93 +0,0 @@
|
||||
<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;
|
||||
}
|
||||
|
||||
.bolden {
|
||||
font-weight: bold;
|
||||
}
|
||||
</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>
|
||||
<div v-if="!ready">
|
||||
Loading...
|
||||
</div>
|
||||
<div v-if="ready">
|
||||
<h1>
|
||||
{{domain.name}} Biometric Clock-IN Service
|
||||
</h1>
|
||||
<hr />
|
||||
<div>
|
||||
<div style="margin-left: auto; margin-right: auto; padding: 10px;">
|
||||
<form class="form">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
<img src="/assets/fingerprint.png" style="width: 100%" v-if="!started"/>
|
||||
<img src="/assets/fingerprint.gif" style="width: 100%" v-if="started" />
|
||||
<br/><br/>
|
||||
<div class="btn btn-block" v-bind:class="{ 'btn-success': !started, 'btn-danger': started }"
|
||||
v-on:click="toggleIdentifyMode">
|
||||
<span v-if="!started">Start Service</span>
|
||||
<span v-else>Stop Service</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" v-if="errorMessage">
|
||||
<div class="alert alert-danger">{{errorMessage}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div style="padding: 20px">
|
||||
<div v-if="configuring">
|
||||
<h4>Configure Departments</h4>
|
||||
<hr/>
|
||||
<div v-on:click="toggleCheck(d)" v-bind:class="{ 'bolden': d.checked }" style="cursor: pointer; font-size: 16px;" v-for="d in domain.departments">{{d.name}}</div>
|
||||
<hr/>
|
||||
<div class="btn btn-success btn-block" @click="saveDepartments">Save</div>
|
||||
</div>
|
||||
<div v-if="!configuring">
|
||||
<h3>{{savedDepartments.length}}</h3>
|
||||
<div style="margin-bottom: 25px">Configured Departments • <span style="color: red; font-size: 11px; cursor: pointer" @click="configuring=true">edit</span></div>
|
||||
|
||||
<h3>{{records.length}}</h3>
|
||||
<div style="margin-bottom: 25px">Total Records • <b>version {{records.timestamp}}</b></div>
|
||||
|
||||
<h3>{{today}}</h3>
|
||||
<div style="margin-bottom: 25px">Today's Date</div>
|
||||
|
||||
<h3>{{started ? 'Started' : 'Stopped'}}</h3>
|
||||
<div style="margin-bottom: 25px">Service status</div>
|
||||
|
||||
<div style="border-right: 2px solid green; border-left: 2px solid green; background-color: whitesmoke; padding: 8px">
|
||||
<h3>{{lastProfile}}</h3>
|
||||
<div>Last Clocked-In Profile <span style="font-size: 11px"> • {{latestReportDate}}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- production version, optimized for size and speed -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2"></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>
|
||||
@@ -1,4 +0,0 @@
|
||||
python-socketio
|
||||
simple-websocket
|
||||
flask
|
||||
requests
|
||||
Reference in New Issue
Block a user