Switch GST operator agent to custom protocol launch

This commit is contained in:
A R R R Associates
2026-09-11 12:40:00 +05:30
parent a32132348d
commit b361a66e44
7 changed files with 181 additions and 287 deletions
@@ -55,10 +55,11 @@
<div class="flex flex-wrap items-center gap-2 rounded-lg border bg-slate-50 p-3 text-sm">
<span>GST Operator Agent:</span>
<span id="gst-agent-status" class="font-semibold text-slate-600">Checking…</span>
<span class="font-semibold text-slate-700">Windows protocol launcher</span>
<span class="text-slate-400">v{{ operator_agent_version }}</span>
<a href="/tools/accounting/gst-reconciliation/operator-agent/download" class="ml-auto rounded border bg-white px-3 py-1.5 text-xs font-medium">Download / Install Agent</a>
<a href="/tools/accounting/gst-reconciliation/operator-agent/download" class="ml-auto rounded border bg-white px-3 py-1.5 text-xs font-medium">Download / Clean Install Agent</a>
</div>
<p class="text-xs text-slate-500">No browser-to-localhost connection is required. When you start a GST download, Windows opens the installed agent through <b>arrrgst://</b>. Chrome/Edge may ask once for permission to open the ARRR GST Operator Agent.</p>
<label class="text-sm">Credential Vault Entry
<select name="credential_id" required class="mt-1 w-full rounded border p-2" {% if not credentials %}disabled{% endif %}>
@@ -83,7 +84,7 @@
<button {% if not node_online or not credentials %}disabled{% endif %} class="rounded bg-indigo-600 px-4 py-2 text-white disabled:opacity-50">Start GST Download</button>
<div class="text-xs">Configured Storage Agent: <b>{{ 'Online' if node_online else 'Offline' }}</b></div>
<p class="text-xs text-slate-500">For Full Financial Year, the job processes April through March and reports progress month by month. The password is redeemed directly by the localhost agent and is never placed in this page's HTML or URL.</p>
<p class="text-xs text-slate-500">For Full Financial Year, the job processes April through March and reports progress month by month. The custom protocol contains only a short-lived signed job token. GST username/password are redeemed directly by the agent and are never placed in this page's HTML or protocol URL.</p>
</form>
<form method="post" action="/tools/accounting/gst-reconciliation/import" enctype="multipart/form-data" class="rounded-xl border bg-white p-4 space-y-3">
@@ -106,10 +107,11 @@
</div>
<div id="gst-job-panel" class="hidden rounded-xl border bg-white p-4 space-y-2">
<div class="flex items-center justify-between"><h2 class="font-semibold">Interactive GST Download</h2><span id="gst-job-percent" class="text-sm font-medium">0%</span></div>
<div class="h-2 overflow-hidden rounded bg-slate-200"><div id="gst-job-bar" class="h-full bg-indigo-600" style="width:0%"></div></div>
<h2 class="font-semibold">Interactive GST Download</h2>
<div id="gst-job-stage" class="text-sm font-medium"></div>
<div id="gst-job-message" class="text-sm text-slate-600"></div>
<a id="gst-agent-open" href="#" class="hidden inline-block rounded bg-indigo-600 px-4 py-2 text-sm text-white">Open GST Operator Agent</a>
<p class="text-xs text-slate-500">If Chrome/Edge asks whether to open ARRR GST Operator Agent, choose Open/Allow. The GST browser then opens separately and the operator completes CAPTCHA/OTP there.</p>
</div>
<form method="post" action="/tools/accounting/gst-reconciliation/analyze" class="rounded-xl border bg-white p-4 space-y-3">
@@ -135,78 +137,23 @@
<script>
(() => {
const port = {{ operator_agent_port|int }};
const base = `https://localhost:${port}`;
const statusEl = document.getElementById('gst-agent-status');
const panel = document.getElementById('gst-job-panel');
const bar = document.getElementById('gst-job-bar');
const pct = document.getElementById('gst-job-percent');
const stage = document.getElementById('gst-job-stage');
const msg = document.getElementById('gst-job-message');
async function agentStatus() {
if (!statusEl) return false;
try {
const r = await fetch(base + '/api/status', {cache:'no-store'});
const b = await r.json();
if (!r.ok || !b.ok) throw new Error(b.error || 'Agent unavailable');
statusEl.textContent = `Online · v${b.version}`;
statusEl.className = 'font-semibold text-emerald-700';
return true;
} catch (e) {
statusEl.textContent = 'Browser cannot reach local agent';
statusEl.className = 'font-semibold text-amber-700';
statusEl.title = e && e.message ? e.message : 'Local HTTPS connection failed';
return false;
}
}
function renderJob(job) {
panel?.classList.remove('hidden');
const total = Number(job.period_total || (job.periods || []).length || 1);
const index = Number(job.period_index || 0);
let percent = Number(job.percent || 0);
if (!percent && total) percent = Math.min(95, Math.round((index / total) * 100));
if (job.status === 'completed') percent = 100;
if (bar) bar.style.width = `${percent}%`;
if (pct) pct.textContent = `${percent}%`;
if (stage) stage.textContent = job.stage || job.status || '';
if (msg) msg.textContent = job.message || job.error || '';
}
async function poll(jobId) {
try {
const r = await fetch(base + '/api/gst/status?job_id=' + encodeURIComponent(jobId), {cache:'no-store'});
const b = await r.json();
if (b.job) renderJob(b.job);
if (b.job && !['completed','failed'].includes(b.job.status)) setTimeout(() => poll(jobId), 1800);
} catch (e) {
if (msg) msg.textContent = 'Could not read GST Operator Agent progress: ' + e.message;
}
}
agentStatus();
const openLink = document.getElementById('gst-agent-open');
{% if operator_job %}
const prepared = {{ operator_job|tojson }};
(async () => {
panel?.classList.remove('hidden');
if (stage) stage.textContent = 'Connecting to GST Operator Agent';
if (msg) msg.textContent = 'Starting visible GST login on this workstation…';
try {
const online = await agentStatus();
if (!online) throw new Error('The GST Operator Agent is not reachable over local HTTPS. Install/update the agent using the button above, then refresh this page.');
const r = await fetch(base + '/api/gst/start', {
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({token:prepared.token})
});
const b = await r.json();
if (!r.ok || !b.ok) throw new Error(b.error || 'Agent could not start GST browser.');
renderJob(b.job || {status:'queued',stage:'Queued',message:'GST browser job queued.'});
poll(prepared.job_id);
} catch (e) {
renderJob({status:'failed',percent:100,stage:'Could not start GST browser',message:e.message});
}
})();
if (panel) panel.classList.remove('hidden');
if (stage) stage.textContent = 'Launching GST Operator Agent';
if (msg) msg.textContent = 'Windows is being asked to open the lightweight GST agent. If the browser shows an external-app confirmation, choose Open/Allow.';
if (openLink) {
openLink.href = prepared.launch_url;
openLink.classList.remove('hidden');
}
// The protocol launch replaces the previous browser-to-localhost HTTPS fetch.
// A short delay lets the ERP page finish rendering before Chrome/Edge opens the registered Windows handler.
window.setTimeout(() => { window.location.href = prepared.launch_url; }, 250);
{% endif %}
})();
</script>