devpi-admin 1.4.1__tar.gz → 1.4.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/.github/workflows/publish.yml +2 -2
  2. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/.github/workflows/tests.yml +2 -2
  3. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/PKG-INFO +1 -1
  4. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/_version.py +3 -3
  5. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/static/css/style.css +5 -1
  6. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/static/js/app.js +73 -9
  7. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin.egg-info/PKG-INFO +1 -1
  8. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_devpi_tokens_ui.py +17 -0
  9. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/.gitignore +0 -0
  10. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/LICENSE +0 -0
  11. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/README.md +0 -0
  12. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/__init__.py +0 -0
  13. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/customizer.py +0 -0
  14. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/main.py +0 -0
  15. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/static/favicon.svg +0 -0
  16. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/static/index.html +0 -0
  17. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/static/js/api.js +0 -0
  18. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/static/js/marked.min.js +0 -0
  19. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/static/js/theme.js +0 -0
  20. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin/tokens.py +0 -0
  21. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin.egg-info/SOURCES.txt +0 -0
  22. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin.egg-info/dependency_links.txt +0 -0
  23. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin.egg-info/entry_points.txt +0 -0
  24. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin.egg-info/requires.txt +0 -0
  25. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/devpi_admin.egg-info/top_level.txt +0 -0
  26. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/pyproject.toml +0 -0
  27. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/setup.cfg +0 -0
  28. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/__init__.py +0 -0
  29. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_acl_read.py +0 -0
  30. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_filter.py +0 -0
  31. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_hooks.py +0 -0
  32. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_json_safe.py +0 -0
  33. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_package.py +0 -0
  34. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_pipconf.py +0 -0
  35. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_tokens.py +0 -0
  36. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_tween.py +0 -0
  37. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_view_helpers.py +0 -0
  38. {devpi_admin-1.4.1 → devpi_admin-1.4.2}/tests/test_wants_html.py +0 -0
@@ -12,12 +12,12 @@ jobs:
12
12
  id-token: write
13
13
 
14
14
  steps:
15
- - uses: actions/checkout@v4
15
+ - uses: actions/checkout@v5
16
16
  with:
17
17
  fetch-depth: 0 # needed for setuptools-scm to derive version from tag
18
18
 
19
19
  - name: Set up Python
20
- uses: actions/setup-python@v5
20
+ uses: actions/setup-python@v6
21
21
  with:
22
22
  python-version: "3.14"
23
23
 
@@ -14,12 +14,12 @@ jobs:
14
14
  python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
15
15
 
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v5
18
18
  with:
19
19
  fetch-depth: 0 # needed for setuptools-scm to read git tags
20
20
 
21
21
  - name: Set up Python ${{ matrix.python-version }}
22
- uses: actions/setup-python@v5
22
+ uses: actions/setup-python@v6
23
23
  with:
24
24
  python-version: ${{ matrix.python-version }}
25
25
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpi-admin
3
- Version: 1.4.1
3
+ Version: 1.4.2
4
4
  Summary: Modern web UI plugin for devpi-server — drop-in replacement for devpi-web
5
5
  Author-email: Pavel Revak <pavelrevak@gmail.com>
6
6
  License: MIT
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '1.4.1'
22
- __version_tuple__ = version_tuple = (1, 4, 1)
21
+ __version__ = version = '1.4.2'
22
+ __version_tuple__ = version_tuple = (1, 4, 2)
23
23
 
24
- __commit_id__ = commit_id = 'gf69f25fba'
24
+ __commit_id__ = commit_id = 'g1bf2e00e1'
@@ -1588,15 +1588,18 @@ body {
1588
1588
  align-items: center;
1589
1589
  justify-content: space-between;
1590
1590
  padding: 2px 0;
1591
+ gap: 4px;
1591
1592
  }
1592
1593
 
1593
1594
  .pkg-version-link {
1594
- flex: 1;
1595
+ flex: 1 1 auto;
1596
+ min-width: 0;
1595
1597
  padding: 5px 8px;
1596
1598
  border-radius: 4px;
1597
1599
  font-size: 13px;
1598
1600
  color: var(--text-muted);
1599
1601
  text-decoration: none;
1602
+ overflow-wrap: anywhere;
1600
1603
  }
1601
1604
 
1602
1605
  .pkg-version-link:hover {
@@ -1619,6 +1622,7 @@ body {
1619
1622
  font-size: 16px;
1620
1623
  line-height: 1;
1621
1624
  border-radius: 4px;
1625
+ flex-shrink: 0;
1622
1626
  }
1623
1627
 
1624
1628
  .pkg-version-del:hover {
@@ -1242,10 +1242,65 @@
1242
1242
  // Token types supported by the unified Issue modal. The selector at the
1243
1243
  // top of the modal switches the form between Devpi tokens (multi-index,
1244
1244
  // permission checkboxes) and Admin tokens (single index, scope select).
1245
- // Devpi is the default unless the plugin isn't installed.
1245
+ // When the caller doesn't pin a type, the modal defaults to whichever
1246
+ // backend the user *most recently* issued against — saves picking the
1247
+ // same radio every time. Falls back to Devpi (or Admin if the plugin
1248
+ // isn't installed) for users with no tokens yet.
1246
1249
  var TOKEN_TYPE_DEVPI = 'devpi';
1247
1250
  var TOKEN_TYPE_ADMIN = 'admin';
1248
1251
 
1252
+ function _detectRecentTokenType(username) {
1253
+ // Returns Promise<TOKEN_TYPE_*|null>. ``null`` means the user has
1254
+ // no tokens (or detection failed) — caller picks a static default.
1255
+ //
1256
+ // Admin tokens carry ``issued_at`` (epoch). Macaroon tokens have
1257
+ // no first-class issuance timestamp; we use ``not_before`` when
1258
+ // present (it tracks the issue moment for the typical "starts
1259
+ // now" flow) and fall back to ``expires`` so a freshly-issued
1260
+ // long-TTL token still wins over an older short-TTL one.
1261
+ var pAdmin = Api.get(
1262
+ '/+admin-api/users/' + encodeURIComponent(username) + '/tokens')
1263
+ .then(function (data) {
1264
+ var tokens = (data && data.result) || [];
1265
+ var max = 0;
1266
+ for (var i = 0; i < tokens.length; i++) {
1267
+ var t = tokens[i].issued_at || 0;
1268
+ if (t > max) max = t;
1269
+ }
1270
+ return max || null;
1271
+ })
1272
+ .catch(function () { return null; });
1273
+
1274
+ var pDevpi = hasDevpiTokens()
1275
+ ? Api.get('/' + encodeURIComponent(username) + '/+tokens')
1276
+ .then(function (data) {
1277
+ var tokens = (data && data.result
1278
+ && data.result.tokens) || {};
1279
+ var max = 0;
1280
+ for (var id in tokens) {
1281
+ if (!Object.prototype.hasOwnProperty.call(
1282
+ tokens, id)) continue;
1283
+ var parsed = parseMacaroonRestrictions(
1284
+ tokens[id].restrictions);
1285
+ var t = parsed.not_before
1286
+ || parsed.expires || 0;
1287
+ if (t > max) max = t;
1288
+ }
1289
+ return max || null;
1290
+ })
1291
+ .catch(function () { return null; })
1292
+ : Promise.resolve(null);
1293
+
1294
+ return Promise.all([pAdmin, pDevpi]).then(function (results) {
1295
+ var adminTs = results[0];
1296
+ var devpiTs = results[1];
1297
+ if (!adminTs && !devpiTs) return null;
1298
+ if (!adminTs) return TOKEN_TYPE_DEVPI;
1299
+ if (!devpiTs) return TOKEN_TYPE_ADMIN;
1300
+ return adminTs >= devpiTs ? TOKEN_TYPE_ADMIN : TOKEN_TYPE_DEVPI;
1301
+ });
1302
+ }
1303
+
1249
1304
  // Public entry point. `options` may include:
1250
1305
  // • `preselectType` — 'devpi' or 'admin'
1251
1306
  // • `preselectIndexes` — list of 'user/index' strings
@@ -1259,18 +1314,25 @@
1259
1314
  function showIssueTokenModal(username, options) {
1260
1315
  options = options || {};
1261
1316
  var presel = (options.preselectIndexes || []).slice();
1262
- var preselType = options.preselectType || TOKEN_TYPE_DEVPI;
1317
+ var explicitType = options.preselectType || null;
1263
1318
  _issueReturnTo = options.returnTo || function () {
1264
1319
  showTokensModal(username);
1265
1320
  };
1266
1321
  _issueLockedIndex = (options.lockIndex && presel.length)
1267
1322
  ? presel[0] : null;
1268
- // If the user asked for Devpi but the plugin isn't installed, silently
1269
- // fall back to Admin saves them a trip back through the kebab.
1270
- if (preselType === TOKEN_TYPE_DEVPI && !hasDevpiTokens()) {
1271
- preselType = TOKEN_TYPE_ADMIN;
1272
- }
1273
- fetchRoot().then(function (rootResult) {
1323
+ // Caller pinned the type honour it; otherwise detect from the
1324
+ // user's existing tokens (most-recently-issued backend wins).
1325
+ // Detection failure falls back to Devpi (or Admin if the plugin
1326
+ // isn't installed).
1327
+ var pType = explicitType
1328
+ ? Promise.resolve(explicitType)
1329
+ : _detectRecentTokenType(username);
1330
+ Promise.all([fetchRoot(), pType]).then(function (results) {
1331
+ var rootResult = results[0];
1332
+ var preselType = results[1] || TOKEN_TYPE_DEVPI;
1333
+ if (preselType === TOKEN_TYPE_DEVPI && !hasDevpiTokens()) {
1334
+ preselType = TOKEN_TYPE_ADMIN;
1335
+ }
1274
1336
  var indexInfos = getAllIndexes(rootResult);
1275
1337
  var aclByIndex = {};
1276
1338
  // Indexes accessible to the bound user: ones they own, ones
@@ -1304,11 +1366,13 @@
1304
1366
  };
1305
1367
  _renderIssueTokenModal(username, presel, preselType);
1306
1368
  }).catch(function () {
1369
+ var fallbackType = explicitType
1370
+ || (hasDevpiTokens() ? TOKEN_TYPE_DEVPI : TOKEN_TYPE_ADMIN);
1307
1371
  _issueContext = {
1308
1372
  accessibleIndexes: presel.slice(),
1309
1373
  aclByIndex: {},
1310
1374
  };
1311
- _renderIssueTokenModal(username, presel, preselType);
1375
+ _renderIssueTokenModal(username, presel, fallbackType);
1312
1376
  });
1313
1377
  }
1314
1378
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpi-admin
3
- Version: 1.4.1
3
+ Version: 1.4.2
4
4
  Summary: Modern web UI plugin for devpi-server — drop-in replacement for devpi-web
5
5
  Author-email: Pavel Revak <pavelrevak@gmail.com>
6
6
  License: MIT
@@ -277,6 +277,23 @@ class UnifiedIssueModalTests(unittest.TestCase):
277
277
  def test_unified_entry_point_exists(self):
278
278
  self.assertIn("function showIssueTokenModal(username, options)", self.js)
279
279
 
280
+ def test_recent_token_type_detector_present(self):
281
+ # Auto-preselect: when caller doesn't pin a type, pick the
282
+ # backend the user most recently issued against.
283
+ self.assertIn("function _detectRecentTokenType(username)", self.js)
284
+ # Admin tokens compared by issued_at, macaroons by
285
+ # not_before / expires.
286
+ self.assertIn("tokens[i].issued_at", self.js)
287
+ self.assertIn("parsed.not_before", self.js)
288
+
289
+ def test_issue_modal_uses_detector_when_caller_is_silent(self):
290
+ # `options.preselectType` short-circuits detection; absence
291
+ # triggers _detectRecentTokenType.
292
+ self.assertIn("var explicitType = options.preselectType || null;",
293
+ self.js)
294
+ self.assertIn("explicitType\n ? Promise.resolve(explicitType)\n"
295
+ " : _detectRecentTokenType(username);", self.js)
296
+
280
297
  def test_unified_listing_has_issue_button(self):
281
298
  # Single Issue entry point on the unified per-user Tokens modal —
282
299
  # no preselectType so the user picks devpi/admin in the form.
File without changes
File without changes
File without changes
File without changes
File without changes