From 920ce2646beefddf94dcecc7f1cfa46defcbae30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D1=82=D0=BE=D0=BD=20=D0=A2=D1=80=D0=BE=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 15 Oct 2026 00:54:02 +0300 Subject: [PATCH] v3.30.0: the batch status modal A running batch now shows in a modal window (the same chrome as the single-operation modal): the live status on top - the current step from ram_progress (Step n/N - , with the bar) or the waiting prompt ("Insert next card. Waiting for a card...") - plus the counts and the last finished card's verdict, and the processed cards below (the same attempts table with the per-step verdicts). It opens on Start and on a page load with a run in flight (the first poll sees ram_batch.active); Close is disabled until the run ends (the run is server-side and survives a reload - that is what reopens the window), and Stop/Clear live inside it (Clear only once the run ended). The panel's new Show status button reopens a finished report. A single operation takes the modal back; ramBatchRender feeds both the panel and the modal (the poll's fetch decision now runs for either). Docs: help EN/RU, the repo AGENTS. Tests: the open on Start and on the poll, the live status/bar/counts/attempts, the close guard (active vs ended), the reopen button. 929 frontend + 945 Python; sw cache simple-v450. --- frontend/help-ru.html | 1 + frontend/help.html | 1 + frontend/index.html | 136 ++++++++++++++++++++++++++++--- frontend/sw.js | 2 +- frontend/tests/ram_batch.test.js | 127 ++++++++++++++++++++++++++++- pyproject.toml | 2 +- pysim_simple_server/server.py | 2 +- 7 files changed, 255 insertions(+), 16 deletions(-) diff --git a/frontend/help-ru.html b/frontend/help-ru.html index 4018589..c080c91 100644 --- a/frontend/help-ru.html +++ b/frontend/help-ru.html @@ -331,6 +331,7 @@ INSTALL [for make selectable]Один APDU INSTALL [for make selectable] (AID экземпляра) для установленного, но невыбираемого экземпляра апплета. +

Пакетная операция показывает свой статус в модальном окне: сверху — текущий прогресс (или «Вставьте следующую карту. Ожидание карты…»), ниже — обработанные карты, а внутри — Остановить и Очистить. Окно открывается при запуске и при загрузке страницы, пока запуск активен; Закрыть остаётся недоступным до окончания запуска — сам запуск выполняется на сервере и переживает перезагрузку. После завершения отчёт можно открыть заново кнопкой Показать статус в панели.

Обзор карты (Explorer View)

После выполнения «Обзор карты» отображается:

diff --git a/frontend/help.html b/frontend/help.html index 6c7a32b..1145868 100644 --- a/frontend/help.html +++ b/frontend/help.html @@ -330,6 +330,7 @@ INSTALL [for make selectable]One INSTALL [for make selectable] APDU (the instance AID) for an INSTALLED, non-selectable applet instance. +

A batch operation shows its status in a modal window: the live progress (or “Insert next card. Waiting for a card...”) on top and the processed cards below, with Stop and Clear inside. It opens on Start and on a page load while a run is active; Close stays disabled until the run ends — the run itself is server-side and survives a reload. After a finished run the panel’s Show status button reopens the report.

Explorer View

After “Explore Card” runs, the explorer displays:

diff --git a/frontend/index.html b/frontend/index.html index 4060cd0..81ba33e 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -936,6 +936,7 @@ + @@ -1910,6 +1911,8 @@
+ +
@@ -1919,6 +1922,7 @@
+
Do not close or reload this window while the operation is running.
@@ -1928,7 +1932,7 @@ // ===== Version ===== // Single source of truth for the PWA version: shown in the header and used // by the server version check in pysimConnect(). -const SIMPLE_VERSION = '3.29.2'; +const SIMPLE_VERSION = '3.30.0'; document.getElementById('app-version').textContent = 'v' + SIMPLE_VERSION; // ===== view history (the browser Back/Forward buttons) ===== @@ -10186,6 +10190,7 @@ let _ramBatchFetching = false; let _ramBatchFetchAt = 0; // when the in-flight fetch started (the watchdog) let _ramBatchSummary = null; // /api/status.ram_batch let _ramBatchProgress = null; // /api/status.ram_progress +let _ramBatchModalShown = false; // the batch status modal is open (v3.30.0) // A fetch that never settled must not block the report forever (v3.28.7). const RAM_BATCH_FETCH_STALE_MS = 10000; @@ -10195,6 +10200,94 @@ function ramBatchPanelVisible() { && !!op && ramIsBatchOp(op.value); } +// The merged batch state: the summary (the 2 s poll) wins over the last +// detailed fetch. +function ramBatchMergedState() { + return Object.assign({ active: false, phase: 'idle', attempts: [], counts: {} }, + _ramBatchLast || {}, _ramBatchSummary || {}); +} + +function ramBatchTitle(kind) { + return kind === 'delete' ? t('Batch delete (per card)') + : (kind === 'reinstall-app' ? t('Batch reinstall app (per card)') + : t('Batch install (.cap file, per card)')); +} + +// ===== the batch status modal (v3.30.0) ===== +// The same chrome as the single-operation modal, owned by the batch: the live +// status on top (the current step or the waiting prompt) and the processed +// cards below. It opens on Start and on a page load with a run in flight; +// Close stays disabled while the run is active (the run itself survives a +// reload - that is what reopens this window) and Stop/Clear live inside it. +function ramBatchModalOpen() { + const modal = document.getElementById('ram-op-modal'); + if (!modal) return; // no modal in the document (tests) + _ramBatchModalShown = true; + document.getElementById('ram-op-title').textContent = + ramBatchTitle(ramBatchMergedState().kind || 'install-cap'); + document.getElementById('ram-op-status').textContent = t('Working...'); + document.getElementById('ram-op-status').className = 'text-sm text-gray-600 dark:text-slate-400'; + document.getElementById('ram-op-bar').style.width = '0%'; + const stepsEl = document.getElementById('ram-op-steps'); + stepsEl.className = 'mt-2 text-xs overflow-auto text-gray-700 dark:text-slate-300'; + stepsEl.innerHTML = ''; + document.getElementById('ram-op-spin').classList.add('hidden'); + document.getElementById('ram-op-warn').classList.add('hidden'); + document.getElementById('ram-op-batch-info').classList.remove('hidden'); + modal.classList.remove('hidden'); + ramBatchRender(); // paint from the current state now +} + +// The modal's live render: the status (the current step or the waiting +// prompt), the counts and the last verdict, the processed cards, the +// Stop/Clear/Close states. +function ramBatchModalRender(st, okLabel, actLabel) { + const statusEl = document.getElementById('ram-op-status'); + if (!statusEl) return; + document.getElementById('ram-op-title').textContent = + ramBatchTitle(st.kind || 'install-cap'); + const active = !!st.active; + const phase = st.phase || (active ? 'waiting_card' : 'idle'); + let status; + if (phase === 'installing' || phase === 'stopping') { + status = phase === 'stopping' ? t('Stopping after the current card...') + : (ramOpProgressText(_ramBatchProgress) + || (actLabel + ((st.current && st.current.iccid) ? ' ' + st.current.iccid : ''))); + } else if (active) { + status = t('Insert next card. Waiting for a card...'); + } else { + status = phase === 'stopped' ? t('Batch stopped') + : (phase === 'finished' ? t('Batch finished') : ''); + } + statusEl.textContent = status; + // the bar follows the current operation's step; a finished report fills it + const p = _ramBatchProgress; + const pct = !active ? ((st.attempts_total || 0) > 0 ? 100 : 0) + : ((p && p.active && p.total) ? Math.min(100, Math.round(p.step * 100 / p.total)) : 0); + document.getElementById('ram-op-bar').style.width = pct + '%'; + // the counts and the last finished card's verdict + const c = st.counts || {}; + let info = okLabel + ': ' + (c.installed || 0) + ' \u00b7 ' + t('Failed') + ': ' + (c.failed || 0) + + ' \u00b7 ' + t('Skipped') + ': ' + (c.skipped || 0); + if (st.attempts_total) info += ' \u00b7 ' + st.attempts_total + ' ' + t('attempts'); + if (st.notice) info += '\n' + st.notice; + document.getElementById('ram-op-batch-info').textContent = info; + // the processed cards (the reader's scroll position is kept across renders) + const box = document.getElementById('ram-op-steps'); + const prev = box.scrollTop; + box.innerHTML = ramBatchAttemptsHtml(st.attempts || []); + if (prev) box.scrollTop = prev; + // Stop/Clear live here; Close only once the run ended + const stopBtn = document.getElementById('ram-op-batch-stop'); + stopBtn.classList.toggle('hidden', !active); + stopBtn.textContent = phase === 'stopping' ? t('Stopping...') : t('Stop'); + stopBtn.disabled = phase === 'stopping'; + const clearBtn = document.getElementById('ram-op-batch-clear'); + clearBtn.classList.remove('hidden'); + clearBtn.disabled = active; + document.getElementById('ram-op-close').disabled = active; +} + async function ramBatchFetch() { if (_ramBatchFetching) return; _ramBatchFetching = true; @@ -10212,7 +10305,12 @@ function ramBatchApplyStatus(status) { if (!status) return; _ramBatchSummary = status.ram_batch || null; _ramBatchProgress = status.ram_progress || null; - if (!ramBatchPanelVisible()) return; + // The status modal opens itself for a run in flight: Start opens it, and a + // page load with an active batch reopens it on the first poll (v3.30.0). + if (!_ramBatchModalShown && status.ram_batch && status.ram_batch.active) { + ramBatchModalOpen(); + } + if (!ramBatchPanelVisible() && !_ramBatchModalShown) return; // The summary carries the headline (the phase, the current card, the // counts, the notice): render it on every poll, even when a detailed fetch // is due or stuck - a hung fetch used to freeze the whole panel at the @@ -10233,17 +10331,12 @@ function ramBatchApplyStatus(status) { function ramBatchRender() { const panel = document.getElementById('ram-batch-panel'); - if (!panel) return; - if (!ramBatchPanelVisible()) { - panel.classList.add('hidden'); - return; - } - panel.classList.remove('hidden'); + const visible = ramBatchPanelVisible(); + if (panel) panel.classList.toggle('hidden', !visible); // The summary (the 2 s status poll) is fresher than the last detailed // fetch: it carries the phase/current/notice/counts, the attempts list // comes from the detailed status. - const st = Object.assign({ active: false, phase: 'idle', attempts: [], counts: {} }, - _ramBatchLast || {}, _ramBatchSummary || {}); + const st = ramBatchMergedState(); const active = !!st.active; const phase = st.phase || (active ? 'waiting_card' : 'idle'); // The success labels follow the operation (v3.29.0): the counts bucket is @@ -10253,6 +10346,10 @@ function ramBatchRender() { : (kind === 'reinstall-app' ? t('Reinstalled') : t('Installed')); const actLabel = kind === 'delete' ? t('Deleting...') : (kind === 'reinstall-app' ? t('Reinstalling...') : t('Installing...')); + // The status modal (when open) follows the same state - it must update + // even when the panel is not on screen (v3.30.0). + if (_ramBatchModalShown) ramBatchModalRender(st, okLabel, actLabel); + if (!visible) return; const startBtn = document.getElementById('ram-batch-start'); if (startBtn) startBtn.classList.toggle('hidden', active); const stopBtn = document.getElementById('ram-batch-stop'); @@ -10264,6 +10361,9 @@ function ramBatchRender() { // the server refuses a clear while the run is active const clearBtn = document.getElementById('ram-batch-clear'); if (clearBtn) clearBtn.disabled = active; + // the status modal can be reopened for a running or finished report + const showBtn = document.getElementById('ram-batch-show'); + if (showBtn) showBtn.classList.toggle('hidden', !(active || (st.attempts_total || 0) > 0)); // A throbber while a card is actually being installed (`current` is set for // the whole install, the stopping phase included). const spinEl = document.getElementById('ram-batch-spin'); @@ -10430,6 +10530,8 @@ async function ramBatchStart() { try { _ramBatchLast = await pysimFetchOk('/api/ram-batch/start', fields); _ramBatchSummary = null; + // the run's status window: the progress on top, the cards below + ramBatchModalOpen(); } catch (e) { alert(t('Error') + ': ' + e.message); } finally { @@ -10442,6 +10544,9 @@ async function ramBatchStart() { async function ramBatchStop() { const stopBtn = document.getElementById('ram-batch-stop'); if (stopBtn) stopBtn.disabled = true; + // the modal's Stop too (the render restores the states after the request) + const modalStop = document.getElementById('ram-op-batch-stop'); + if (modalStop) modalStop.disabled = true; try { _ramBatchLast = await pysimFetchOk('/api/ram-batch/stop', {}); _ramBatchSummary = null; // the poll refreshes it; the fresh detail wins @@ -10477,14 +10582,20 @@ function ramOpBeforeUnload(e) { function ramOpModalBegin(title) { _ramOpActive = true; + _ramBatchModalShown = false; // a single operation owns the modal document.getElementById('ram-op-title').textContent = title || ''; document.getElementById('ram-op-status').textContent = t('Working...'); document.getElementById('ram-op-status').className = 'text-sm text-gray-600 dark:text-slate-400'; document.getElementById('ram-op-bar').style.width = '0%'; document.getElementById('ram-op-steps').textContent = ''; + document.getElementById('ram-op-steps').className = + 'mt-2 text-xs font-mono whitespace-pre-wrap overflow-auto text-gray-700 dark:text-slate-300'; document.getElementById('ram-op-close').disabled = true; document.getElementById('ram-op-spin').classList.remove('hidden'); document.getElementById('ram-op-warn').classList.remove('hidden'); + document.getElementById('ram-op-batch-info').classList.add('hidden'); + document.getElementById('ram-op-batch-stop').classList.add('hidden'); + document.getElementById('ram-op-batch-clear').classList.add('hidden'); document.getElementById('ram-op-modal').classList.remove('hidden'); window.addEventListener('beforeunload', ramOpBeforeUnload); } @@ -10532,6 +10643,10 @@ function ramOpModalFinish(summary, ok, content, asHtml) { function ramOpModalClose() { if (_ramOpActive) return; + // the batch window stays while its run is active: the button is disabled + // by the render and this guard covers the window before the next render + if (_ramBatchModalShown && ramBatchMergedState().active) return; + _ramBatchModalShown = false; document.getElementById('ram-op-modal').classList.add('hidden'); } @@ -24545,6 +24660,7 @@ const LANG_RU = { 'Batch (per card)': 'Пакетные операции (по картам)', 'Batch reinstall app (per card)': 'Пакетная переустановка приложения (по картам)', 'Batch delete (per card)': 'Пакетное удаление (по картам)', + 'Show status': 'Показать статус', 'AID (hex)': 'AID (hex)', 'Target': 'Цель', 'Application (instance only)': 'Приложение (только экземпляр)', diff --git a/frontend/sw.js b/frontend/sw.js index 12b24d4..8207045 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -1,4 +1,4 @@ -const CACHE = 'simple-v449'; +const CACHE = 'simple-v450'; const URLS = [ 'index.html', 'help.html', diff --git a/frontend/tests/ram_batch.test.js b/frontend/tests/ram_batch.test.js index d0548fa..c24fb13 100644 --- a/frontend/tests/ram_batch.test.js +++ b/frontend/tests/ram_batch.test.js @@ -23,9 +23,11 @@ function extractFunc(src, name, asyncFn) { const ASYNC_FNS = ['ramBatchFetch', 'ramBatchStart', 'ramBatchStop', 'ramBatchClear']; let code = ''; -for (const fn of ['ramIsBatchOp', 'ramBatchPanelVisible', 'ramBatchStepsText', +for (const fn of ['ramIsBatchOp', 'ramBatchPanelVisible', 'ramBatchMergedState', + 'ramBatchTitle', 'ramBatchModalOpen', 'ramBatchModalRender', 'ramBatchStepsText', 'ramBatchAttemptsHtml', 'ramBatchRender', 'ramBatchApplyStatus', 'ramBatchFetch', - 'ramBatchStart', 'ramBatchStop', 'ramBatchClear', 'esc']) { + 'ramBatchStart', 'ramBatchStop', 'ramBatchClear', 'ramOpProgressText', + 'ramOpModalClose', 'esc']) { code += extractFunc(html, fn, ASYNC_FNS.indexOf(fn) >= 0) + '\n'; } @@ -36,7 +38,7 @@ const els = {}; function setEl(id, value) { els[id] = { id: id, value: value === undefined ? '' : value, textContent: '', innerHTML: '', - disabled: false, + disabled: false, style: {}, classes: {}, classList: { add(c) { els[id].classes[c] = true; }, @@ -57,6 +59,18 @@ function setEl(id, value) { function clearEls() { for (const k of Object.keys(els)) delete els[k]; } +// The modal chrome and the batch panel: the element sets the modal tests need. +function setModalEls() { + for (const id of ['ram-op-modal', 'ram-op-title', 'ram-op-status', 'ram-op-bar', + 'ram-op-steps', 'ram-op-spin', 'ram-op-warn', 'ram-op-close', + 'ram-op-batch-info', 'ram-op-batch-stop', 'ram-op-batch-clear']) setEl(id, ''); +} +function setPanelEls() { + for (const id of ['ram-op', 'ram-batch-panel', 'ram-batch-start', 'ram-batch-stop', + 'ram-batch-clear', 'ram-batch-show', 'ram-batch-spin', 'ram-batch-state', + 'ram-batch-notice', 'ram-batch-counts', 'ram-batch-attempts']) setEl(id, ''); + els['ram-op'].value = 'batch'; +} const app = new Function('els', ` var sandbox = { fetched: [], replies: {}, fetchError: null, alerts: [], visibility: true }; @@ -83,6 +97,8 @@ const app = new Function('els', ` var updateRcPriv = () => {}; var updateStkParamsHex = () => {}; var pysimApplyAvailability = () => {}; + var _ramOpActive = false; + var _ramBatchModalShown = false; var _ramBatchLast = null; var _ramBatchFetching = false; var _ramBatchFetchAt = 0; @@ -93,6 +109,7 @@ const app = new Function('els', ` sandbox.getLast = () => _ramBatchLast; sandbox.setLast = v => { _ramBatchLast = v; }; sandbox.getSummary = () => _ramBatchSummary; + sandbox.setSummary = v => { _ramBatchSummary = v; }; sandbox.isFetching = () => _ramBatchFetching; sandbox.setFetchingStale = ms => { _ramBatchFetching = true; _ramBatchFetchAt = Date.now() - ms; }; sandbox.resetFetching = () => { _ramBatchFetching = false; }; @@ -100,6 +117,11 @@ const app = new Function('els', ` sandbox.ramBatchPanelVisible = ramBatchPanelVisible; sandbox.ramBatchAttemptsHtml = ramBatchAttemptsHtml; sandbox.ramBatchStepsText = ramBatchStepsText; + sandbox.ramBatchModalOpen = ramBatchModalOpen; + sandbox.ramOpModalClose = ramOpModalClose; + sandbox.isBatchModalShown = () => _ramBatchModalShown; + sandbox.resetBatchModal = () => { _ramBatchModalShown = false; }; + sandbox.setProgress = v => { _ramBatchProgress = v; }; sandbox.ramBatchRender = ramBatchRender; sandbox.ramBatchApplyStatus = ramBatchApplyStatus; sandbox.ramBatchFetch = ramBatchFetch; @@ -475,6 +497,105 @@ test('the attempts render the per-step tail for short chains (v3.29.0)', () => { assert.strictEqual(app.ramBatchStepsText({ steps: [{}, {}, {}, {}], reason: '' }), ''); }); +test('the batch status modal opens on Start and on a poll with a run in flight (v3.30.0)', async () => { + // the markup: the window's Stop/Clear/info and the panel's Show status + assert.match(html, /id="ram-op-batch-stop"/); + assert.match(html, /id="ram-op-batch-clear"/); + assert.match(html, /id="ram-op-batch-info"/); + assert.match(html, /id="ram-batch-show" onclick="ramBatchModalOpen\(\)"/); + assert.match(html, /'Show status': /); + // Start opens it + clearEls(); + setModalEls(); + setPanelEls(); + app.setSummary(null); + els['ram-op'].value = 'batch-delete'; + setEl('ram-del-aid', 'F0414C4641610101'); + setEl('ram-del-target', 'app'); + app.fetched = []; + await app.ramBatchStart(); + assert.ok(app.isBatchModalShown()); + assert.ok(els['ram-op-modal'].classes['hidden'] === undefined); + // a page load with an active run reopens it on the first poll + clearEls(); + setModalEls(); + setPanelEls(); + app.resetBatchModal(); + app.ramBatchApplyStatus({ ram_batch: { active: true, phase: 'waiting_card', + attempts_total: 0, counts: { installed: 0, failed: 0, skipped: 0 } }, + ram_progress: null }); + assert.ok(app.isBatchModalShown()); + assert.ok(els['ram-op-modal'].classes['hidden'] === undefined); +}); + +test('the batch modal follows the run: the step, the bar, the cards (v3.30.0)', () => { + clearEls(); + setModalEls(); + setPanelEls(); + app.setSummary(null); + app.ramBatchModalOpen(); + // waiting for a card + app.setLast({ kind: 'reinstall-app', active: true, phase: 'waiting_card', + attempts: [], counts: {}, attempts_total: 0 }); + app.ramBatchRender(); + assert.strictEqual(els['ram-op-title'].textContent, 'Batch reinstall app (per card)'); + assert.strictEqual(els['ram-op-status'].textContent, 'Insert next card. Waiting for a card...'); + assert.strictEqual(els['ram-op-bar'].style.width, '0%'); + assert.ok(els['ram-op-close'].disabled); + assert.ok(els['ram-op-batch-stop'].classes['hidden'] === undefined); + assert.ok(els['ram-op-batch-clear'].disabled); + // installing: the live step and the bar + app.setLast({ kind: 'reinstall-app', active: true, phase: 'installing', + attempts: [], counts: { installed: 1, failed: 0, skipped: 0 }, + attempts_total: 1, current: { iccid: '8970119000004600098' } }); + app.setProgress({ active: true, step: 2, total: 4, name: 'INSTALL [for install]' }); + app.ramBatchRender(); + assert.match(els['ram-op-status'].textContent, /Step 2\/4 — INSTALL \[for install\]/); + assert.strictEqual(els['ram-op-bar'].style.width, '50%'); + assert.match(els['ram-op-batch-info'].textContent, /Reinstalled: 1/); + // the finished report: the cards list, the notice, Close enabled + app.setLast({ kind: 'reinstall-app', active: false, phase: 'finished', attempts: [ + { iccid: '8970119000004600098', result: 'installed', steps: [ + { name: 'DELETE (application)', sw: '6101', ok: true }, + { name: 'INSTALL [for install]', sw: '6101', ok: true }] }], + counts: { installed: 1, failed: 0, skipped: 0 }, attempts_total: 1, + notice: 'Card 8970119000004600098 reinstalled.' }); + app.setProgress(null); + app.ramBatchRender(); + assert.match(els['ram-op-steps'].innerHTML, /DELETE \(application\) 6101 · INSTALL \[for install\] 6101/); + assert.match(els['ram-op-batch-info'].textContent, /Card 8970119000004600098 reinstalled\./); + assert.strictEqual(els['ram-op-bar'].style.width, '100%'); + assert.ok(!els['ram-op-close'].disabled); + assert.ok(els['ram-op-batch-stop'].classes['hidden']); // the run ended + assert.ok(!els['ram-op-batch-clear'].disabled); +}); + +test('the batch modal stays open while the run is active (v3.30.0)', () => { + clearEls(); + setModalEls(); + setPanelEls(); + app.setSummary(null); + app.ramBatchModalOpen(); + app.setLast({ active: true, phase: 'waiting_card', attempts: [], counts: {}, attempts_total: 0 }); + app.ramBatchRender(); + app.ramOpModalClose(); + assert.ok(app.isBatchModalShown(), 'the run keeps the window open'); + assert.ok(els['ram-op-modal'].classes['hidden'] === undefined); + // the run ended: Close works, and the panel offers the reopen button + app.setLast({ active: false, phase: 'stopped', attempts: [], counts: { installed: 1 }, + attempts_total: 1 }); + app.ramBatchRender(); + app.ramOpModalClose(); + assert.ok(!app.isBatchModalShown()); + assert.ok(els['ram-op-modal'].classes['hidden']); + app.ramBatchRender(); + assert.ok(els['ram-batch-show'].classes['hidden'] === undefined); + // and it reopens for the finished report + app.ramBatchModalOpen(); + assert.ok(app.isBatchModalShown()); + assert.ok(els['ram-op-modal'].classes['hidden'] === undefined); +}); + test('the stop and clear calls hit their endpoints', async () => { clearEls(); app.fetched = []; diff --git a/pyproject.toml b/pyproject.toml index baea7d4..c291d8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "pysim-simple-server" -version = "3.29.2" +version = "3.30.0" description = "HTTP REST server wrapping pysim for the SIMple PWA" requires-python = ">=3.8" # pysim is a git-only dependency installed explicitly by setup.bat/setup.sh. diff --git a/pysim_simple_server/server.py b/pysim_simple_server/server.py index 4befae5..d135ed1 100644 --- a/pysim_simple_server/server.py +++ b/pysim_simple_server/server.py @@ -37,7 +37,7 @@ from osmocom.tlv import BER_TLV_IE from osmocom.utils import rpad -VERSION = '3.29.2' +VERSION = '3.30.0' MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE