Compare commits
2 Commits
2213e25385
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| c9ae494f62 | |||
| 0370fa58ad |
+1
-1
@@ -55,7 +55,7 @@ Returns server version for compatibility checking.
|
|||||||
|
|
||||||
**Example response:**
|
**Example response:**
|
||||||
```json
|
```json
|
||||||
{"version": "2.1.1"}
|
{"version": "2.1.2"}
|
||||||
```
|
```
|
||||||
|
|
||||||
### `GET /api/status`
|
### `GET /api/status`
|
||||||
|
|||||||
+8
-5
@@ -18,7 +18,7 @@
|
|||||||
<div class="max-w-7xl mx-auto px-6 py-2">
|
<div class="max-w-7xl mx-auto px-6 py-2">
|
||||||
|
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.1.1</span></h1>
|
<h1 class="text-2xl font-bold text-heading">OTAMan <span id="slogan" class="text-sm font-normal text-gray-500 dark:text-slate-400 ml-2" data-l10n="SIM OTA with a Human Face">SIM OTA with a Human Face</span> <span class="text-xs text-gray-400 dark:text-slate-500 ml-1">v2.1.2</span></h1>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<span id="state-indicator" class="flex items-center select-none" style="cursor:default" title="Connecting...">
|
<span id="state-indicator" class="flex items-center select-none" style="cursor:default" title="Connecting...">
|
||||||
<span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting...">●</span>
|
<span id="state-indicator-dot" class="text-xs text-gray-400" title="Connecting...">●</span>
|
||||||
@@ -7820,10 +7820,12 @@ async function profilerScanCard(ignoreFids, ignoreNames, fciMode, onProgress, ma
|
|||||||
const files = [];
|
const files = [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
|
|
||||||
// dir: { name, fid, parentSel (to select this dir), pathPrefix (rule-path segs for its children) }
|
// dir: { name, fid, parentSel (legacy), parentPath (full path of this dir),
|
||||||
|
// pathPrefix (rule-path segs for its children) }
|
||||||
async function walkDir(dir) {
|
async function walkDir(dir) {
|
||||||
let body = { name: dir.name, fid: dir.fid };
|
let body = { name: dir.name, fid: dir.fid };
|
||||||
if (dir.parentPath && dir.parentPath.length) body.parent_path = dir.parentPath;
|
const parentPath = (dir.parentPath || []).slice(0, -1);
|
||||||
|
if (parentPath.length) body.parent_path = parentPath;
|
||||||
if (dir.parentSel) body.parent_sel = dir.parentSel;
|
if (dir.parentSel) body.parent_sel = dir.parentSel;
|
||||||
let data;
|
let data;
|
||||||
try {
|
try {
|
||||||
@@ -7835,12 +7837,13 @@ async function profilerScanCard(ignoreFids, ignoreNames, fciMode, onProgress, ma
|
|||||||
const parentSel = (dir.name === 'MF') ? 'MF'
|
const parentSel = (dir.name === 'MF') ? 'MF'
|
||||||
: (dir.name && dir.name.startsWith('ADF.')) ? dir.name
|
: (dir.name && dir.name.startsWith('ADF.')) ? dir.name
|
||||||
: (dir.fid ? dir.fid : dir.name);
|
: (dir.fid ? dir.fid : dir.name);
|
||||||
|
const childSeg = c.aid ? c.aid.toUpperCase() : (c.fid ? c.fid : c.name);
|
||||||
if (c.isDir) {
|
if (c.isDir) {
|
||||||
// ADF roots use the AID, not the generic ADF fid
|
// ADF roots use the AID, not the generic ADF fid
|
||||||
const childPrefix = c.aid ? [c.aid.toUpperCase()]
|
const childPrefix = c.aid ? [c.aid.toUpperCase()]
|
||||||
: dir.pathPrefix.concat(c.fid ? c.fid.toUpperCase() : c.name);
|
: dir.pathPrefix.concat(c.fid ? c.fid.toUpperCase() : c.name);
|
||||||
const childParentPath = (dir.parentPath || []).concat(c.aid ? c.aid.toUpperCase() : (c.fid ? c.fid : c.name));
|
const childDirPath = dir.parentPath.concat(childSeg);
|
||||||
await walkDir({ name: c.name, fid: c.fid, parentSel: parentSel, parentPath: childParentPath, pathPrefix: childPrefix });
|
await walkDir({ name: c.name, fid: c.fid, parentSel: parentSel, parentPath: childDirPath, pathPrefix: childPrefix });
|
||||||
} else {
|
} else {
|
||||||
const childPath = dir.pathPrefix.concat(c.fid ? c.fid.toUpperCase() : c.name).join('/');
|
const childPath = dir.pathPrefix.concat(c.fid ? c.fid.toUpperCase() : c.name).join('/');
|
||||||
if (seen.has(childPath)) continue;
|
if (seen.has(childPath)) continue;
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
const CACHE = 'otaman-v131';
|
const CACHE = 'otaman-v133';
|
||||||
const URLS = [
|
const URLS = [
|
||||||
'index.html',
|
'index.html',
|
||||||
'help.html',
|
'help.html',
|
||||||
|
|||||||
@@ -947,6 +947,32 @@ test('profilerScanCard snapshot mode builds snapshot entries with ICCID', async
|
|||||||
delete global.pysimCustomFiles;
|
delete global.pysimCustomFiles;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('profilerScanCard walks nested dirs with their parent path, not their own segment', async () => {
|
||||||
|
global.pysimCustomFiles = [];
|
||||||
|
const treeCalls = [];
|
||||||
|
global.pysimFetch = async (path, body) => {
|
||||||
|
if (path === '/api/tree') {
|
||||||
|
treeCalls.push(body);
|
||||||
|
if (body.name === 'MF') return { exists: true, name: 'MF', children: [
|
||||||
|
{ name: 'EF.DIR', fid: '2f00', isDir: false },
|
||||||
|
{ name: 'DF.GSM', fid: '7f20', isDir: true },
|
||||||
|
] };
|
||||||
|
if (body.name === 'DF.GSM') return { exists: true, name: 'DF.GSM', children: [
|
||||||
|
{ name: 'EF.ADN', fid: '6f3a', isDir: false },
|
||||||
|
] };
|
||||||
|
throw new Error('unexpected tree ' + body.name);
|
||||||
|
}
|
||||||
|
if (path === '/api/select') return { name: 'X', fid: '0000', file_type: 'transparent', file_size: 1, record_len: null, num_of_rec: null, exists: true };
|
||||||
|
if (path === '/api/read') return { success: true, data: 'AA' };
|
||||||
|
throw new Error('unexpected ' + path);
|
||||||
|
};
|
||||||
|
const files = await profilerScanCard(new Set(), new Set(), 'type', undefined, new Set(), 'snapshot');
|
||||||
|
// MF root has no parent; DF.GSM must be looked up under MF, not under itself
|
||||||
|
assert.deepStrictEqual(treeCalls.map(b => b.parent_path), [undefined, ['MF']]);
|
||||||
|
assert.deepStrictEqual(files.map(f => f.path).sort(), ['MF/2F00', 'MF/7F20/6F3A']);
|
||||||
|
delete global.pysimCustomFiles;
|
||||||
|
});
|
||||||
|
|
||||||
test('profilerListSwitch toggles the profiles/snapshots tabs', () => {
|
test('profilerListSwitch toggles the profiles/snapshots tabs', () => {
|
||||||
const mkBtn = tab => ({
|
const mkBtn = tab => ({
|
||||||
dataset: { listTab: tab },
|
dataset: { listTab: tab },
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pysim-otaman-server"
|
name = "pysim-otaman-server"
|
||||||
version = "2.1.1"
|
version = "2.1.2"
|
||||||
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
|
description = "HTTP REST server wrapping pysim for the OTAMan PWA"
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
# pysim is a git-only dependency installed explicitly by setup.bat/setup.sh.
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from osmocom.construct import GsmOrUcs2Adapter
|
|||||||
from osmocom.tlv import BER_TLV_IE
|
from osmocom.tlv import BER_TLV_IE
|
||||||
|
|
||||||
|
|
||||||
VERSION = '2.1.1'
|
VERSION = '2.1.2'
|
||||||
|
|
||||||
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
MAX_ENVELOPE_SEGMENTS = 5 # max SMS segments for outgoing C-APDU in ENVELOPE
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user