feat(fpagent): show scanner availability and gate capture
Consume fpagent's fprint-device-status {available, device, captureAgentId}
in the /fingerprint wizard. Availability is tracked tri-state so an agent
that does not report it leaves capture working as before; the header tag
shows offline / no-scanner / ready, the capture panel warns, and capture is
disabled until a supported reader is attached.
Adds a vm-sandbox test for the wizard logic and the OpenSpec change
fpagent-device-availability, archived with the fingerprint-wizard spec.
This commit is contained in:
@@ -20,7 +20,9 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
session: null,
|
||||
tick: Date.now(),
|
||||
socket: null,
|
||||
agent: { connected: false, version: '', captureAgentId: '' },
|
||||
// available is tri-state: null = not yet reported (old agent), true/false
|
||||
// = the last fprint-device-status. Unknown is not blocked.
|
||||
agent: { connected: false, version: '', captureAgentId: '', available: null, device: '' },
|
||||
domain: '',
|
||||
reference: '',
|
||||
otpLookup: '',
|
||||
@@ -68,6 +70,27 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
if (!this.session || !this.session.domainsCheckedAt) return '';
|
||||
return new Date(this.session.domainsCheckedAt).toLocaleTimeString();
|
||||
},
|
||||
// Warning shown in the capture panel when the agent cannot capture.
|
||||
scannerNotice: function () {
|
||||
if (!this.agent.connected) {
|
||||
return {
|
||||
title: 'Capture agent offline',
|
||||
subtitle: 'The fpagent service is not reachable on this machine. Captures are disabled until it reconnects.',
|
||||
};
|
||||
}
|
||||
if (this.agent.available === false) {
|
||||
return {
|
||||
title: 'Scanner not connected',
|
||||
subtitle: 'Attach the fingerprint reader. Captures are disabled until the agent detects it.',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
},
|
||||
// Capture is allowed when the agent is connected and no status has said
|
||||
// the scanner is missing. Unknown availability (old agent) stays allowed.
|
||||
captureReady: function () {
|
||||
return this.agent.connected && this.agent.available !== false;
|
||||
},
|
||||
},
|
||||
|
||||
created: function () {
|
||||
@@ -139,18 +162,38 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
renderAgentTag: function () {
|
||||
var el = document.getElementById('agent-status');
|
||||
if (!el) return;
|
||||
el.textContent = this.agent.connected ? 'Agent connected' : 'Agent offline';
|
||||
el.setAttribute('type', this.agent.connected ? 'green' : 'red');
|
||||
el.setAttribute('title', this.agent.version ? 'fpagent ' + this.agent.version : '');
|
||||
var label, type, title = this.agent.version ? 'fpagent ' + this.agent.version : '';
|
||||
if (!this.agent.connected) {
|
||||
label = 'Agent offline'; type = 'red';
|
||||
} else if (this.agent.available === false) {
|
||||
label = 'Scanner not connected'; type = 'red';
|
||||
} else if (this.agent.available === true) {
|
||||
label = 'Agent ready'; type = 'green';
|
||||
} else {
|
||||
// Connected but no status yet (or an agent that does not report it).
|
||||
label = 'Agent connected'; type = 'green';
|
||||
}
|
||||
if (this.agent.available === true && this.agent.device) {
|
||||
title += ' · ' + this.agent.device;
|
||||
}
|
||||
el.textContent = label;
|
||||
el.setAttribute('type', type);
|
||||
el.setAttribute('title', title);
|
||||
},
|
||||
|
||||
connectAgent: function () {
|
||||
var self = this;
|
||||
this.socket = io(this.session.agentUrl, { reconnectionDelayMax: 5000 });
|
||||
|
||||
var offline = function () {
|
||||
self.agent.connected = false;
|
||||
self.agent.available = null;
|
||||
self.agent.device = '';
|
||||
self.renderAgentTag();
|
||||
};
|
||||
this.socket.on('connect', function () { self.agent.connected = true; self.renderAgentTag(); });
|
||||
this.socket.on('disconnect', function () { self.agent.connected = false; self.renderAgentTag(); });
|
||||
this.socket.on('connect_error', function () { self.agent.connected = false; self.renderAgentTag(); });
|
||||
this.socket.on('disconnect', offline);
|
||||
this.socket.on('connect_error', offline);
|
||||
|
||||
this.socket.on('agent-hello', function (data) {
|
||||
self.agent.version = (data && data.version) || '';
|
||||
@@ -158,6 +201,12 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
self.renderAgentTag();
|
||||
});
|
||||
|
||||
this.socket.on('fprint-device-status', function (data) {
|
||||
self.agent.available = !!(data && data.available);
|
||||
self.agent.device = (data && data.device) || '';
|
||||
self.renderAgentTag();
|
||||
});
|
||||
|
||||
this.socket.on('fprint-enroll-status', function (data) {
|
||||
if (!self.capturing) return;
|
||||
self.fingers[self.capturing].stage = (data && data.stage) || 0;
|
||||
@@ -213,6 +262,11 @@ Vue.config.ignoredElements = [/^cds-/];
|
||||
|
||||
capture: function (finger) {
|
||||
if (this.capturing || !this.socket || !this.domain || !this.reference) return;
|
||||
if (!this.captureReady) {
|
||||
var notice = this.scannerNotice;
|
||||
if (notice) this.showNotif('warning', notice.title, notice.subtitle);
|
||||
return;
|
||||
}
|
||||
// Preserve a previous good capture so a cancelled/failed recapture
|
||||
// can restore it instead of destroying it.
|
||||
var old = this.fingers[finger];
|
||||
|
||||
Reference in New Issue
Block a user