devpi-admin 1.4.5__tar.gz → 1.4.7__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.5 → devpi_admin-1.4.7}/PKG-INFO +1 -1
  2. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/_version.py +3 -3
  3. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/css/style.css +13 -0
  4. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/js/app.js +61 -26
  5. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/PKG-INFO +1 -1
  6. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/.github/workflows/publish.yml +0 -0
  7. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/.github/workflows/tests.yml +0 -0
  8. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/.gitignore +0 -0
  9. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/LICENSE +0 -0
  10. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/README.md +0 -0
  11. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/__init__.py +0 -0
  12. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/customizer.py +0 -0
  13. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/main.py +0 -0
  14. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/favicon.svg +0 -0
  15. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/index.html +0 -0
  16. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/js/api.js +0 -0
  17. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/js/marked.min.js +0 -0
  18. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/js/theme.js +0 -0
  19. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/tokens.py +0 -0
  20. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/SOURCES.txt +0 -0
  21. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/dependency_links.txt +0 -0
  22. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/entry_points.txt +0 -0
  23. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/requires.txt +0 -0
  24. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/top_level.txt +0 -0
  25. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/pyproject.toml +0 -0
  26. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/setup.cfg +0 -0
  27. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/__init__.py +0 -0
  28. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_acl_read.py +0 -0
  29. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_devpi_tokens_ui.py +0 -0
  30. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_filter.py +0 -0
  31. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_hooks.py +0 -0
  32. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_json_safe.py +0 -0
  33. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_package.py +0 -0
  34. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_pipconf.py +0 -0
  35. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_tokens.py +0 -0
  36. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_tween.py +0 -0
  37. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_view_helpers.py +0 -0
  38. {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_wants_html.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpi-admin
3
- Version: 1.4.5
3
+ Version: 1.4.7
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.5'
22
- __version_tuple__ = version_tuple = (1, 4, 5)
21
+ __version__ = version = '1.4.7'
22
+ __version_tuple__ = version_tuple = (1, 4, 7)
23
23
 
24
- __commit_id__ = commit_id = 'gf67cf0f00'
24
+ __commit_id__ = commit_id = 'g609f18017'
@@ -1313,6 +1313,19 @@ body {
1313
1313
  font-family: inherit;
1314
1314
  }
1315
1315
 
1316
+ /* Offscreen but real — Safari ignores display:none username fields
1317
+ when deciding whether to offer AutoFill / password generation.
1318
+ Selector must out-rank `.modal-body input[type="text"]` above. */
1319
+ .modal-body input.offscreen-input,
1320
+ .offscreen-input {
1321
+ position: absolute;
1322
+ left: -9999px;
1323
+ width: 1px;
1324
+ height: 1px;
1325
+ opacity: 0;
1326
+ pointer-events: none;
1327
+ }
1328
+
1316
1329
  .modal-body select:not(.tag-picker-add) {
1317
1330
  width: 100%;
1318
1331
  height: 36px;
@@ -2637,39 +2637,71 @@
2637
2637
  openModal(
2638
2638
  isEdit ? 'Edit User: ' + editName : 'New User',
2639
2639
  function (body) {
2640
+ // A real <form> is required — Safari only offers
2641
+ // strong-password generation for password fields inside
2642
+ // a form (and only over HTTPS).
2643
+ var form = el('form', {id: 'user-form'});
2644
+ form.addEventListener('submit', function (e) {
2645
+ e.preventDefault();
2646
+ submitUserModal(editName);
2647
+ });
2640
2648
  if (!isEdit) {
2641
- body.appendChild(formGroup('Username', el('input', {type: 'text', id: 'form-username'})));
2649
+ var userInput = el('input', {type: 'text', id: 'form-username'});
2650
+ // Keep the browser from pairing this with the masked
2651
+ // field below as a login form (saved-creds autofill).
2652
+ userInput.setAttribute('autocomplete', 'off');
2653
+ userInput.setAttribute('spellcheck', 'false');
2654
+ userInput.setAttribute('autocapitalize', 'off');
2655
+ form.appendChild(formGroup('Username', userInput));
2642
2656
  }
2643
- body.appendChild(formGroup('Email', el('input', {
2657
+ form.appendChild(formGroup('Email', el('input', {
2644
2658
  type: 'email',
2645
2659
  id: 'form-email',
2646
2660
  value: (editInfo && editInfo.email) || '',
2647
2661
  })));
2648
- var isSelf = isEdit && editName === Api.getUser();
2649
- // Hidden username input so browser associates saved password correctly
2650
- if (isSelf) {
2651
- var hiddenUser = el('input', {type: 'text', id: 'form-hidden-username'});
2652
- hiddenUser.setAttribute('autocomplete', 'username');
2653
- hiddenUser.setAttribute('aria-hidden', 'true');
2654
- hiddenUser.style.display = 'none';
2662
+ // Hidden username input so a browser save prompt (if any)
2663
+ // is associated with the TARGET account. Without it Chrome
2664
+ // guesses the username — typically the admin's own saved
2665
+ // login and saving would overwrite the admin's entry.
2666
+ // Offscreen instead of display:none — Safari skips
2667
+ // display:none fields when wiring up its AutoFill.
2668
+ var hiddenUser = el('input', {
2669
+ type: 'text',
2670
+ id: 'form-hidden-username',
2671
+ name: 'username',
2672
+ className: 'offscreen-input',
2673
+ });
2674
+ hiddenUser.setAttribute('autocomplete', 'username');
2675
+ hiddenUser.setAttribute('aria-hidden', 'true');
2676
+ hiddenUser.setAttribute('tabindex', '-1');
2677
+ if (isEdit) {
2655
2678
  hiddenUser.value = editName;
2656
- body.appendChild(hiddenUser);
2657
- }
2658
- var pwInput;
2659
- if (isSelf) {
2660
- // Own password: type="password" + autocomplete so browser offers to save
2661
- pwInput = el('input', {type: 'password', id: 'form-password'});
2662
- pwInput.setAttribute('autocomplete', 'new-password');
2663
- } else {
2664
- // Other user: plain text — Safari won't offer to save text fields
2665
- pwInput = el('input', {type: 'text', id: 'form-password'});
2666
- pwInput.setAttribute('autocomplete', 'off');
2667
- pwInput.setAttribute('spellcheck', 'false');
2679
+ } else if (userInput) {
2680
+ // Create flow: mirror the typed username
2681
+ userInput.addEventListener('input', function () {
2682
+ hiddenUser.value = userInput.value;
2683
+ });
2668
2684
  }
2669
- body.appendChild(formGroup(
2685
+ form.appendChild(hiddenUser);
2686
+ // Real password input: masked, and Safari/Chrome offer to
2687
+ // GENERATE a strong password ("new-password" also keeps the
2688
+ // saved-credentials autofill out of the dropdown). Any save
2689
+ // prompt is anchored to the hidden username above, so the
2690
+ // admin's own keychain entry is never overwritten.
2691
+ var pwInput = el('input', {
2692
+ type: 'password',
2693
+ id: 'form-password',
2694
+ name: 'password',
2695
+ });
2696
+ pwInput.setAttribute('autocomplete', 'new-password');
2697
+ form.appendChild(formGroup(
2670
2698
  isEdit ? 'New Password (leave empty to keep)' : 'Password',
2671
2699
  pwInput
2672
2700
  ));
2701
+ // Hidden submit button enables implicit submission
2702
+ // (Enter key) for a multi-field form.
2703
+ form.appendChild(el('button', {type: 'submit', hidden: true}));
2704
+ body.appendChild(form);
2673
2705
  },
2674
2706
  [
2675
2707
  el('button', {
@@ -2713,12 +2745,15 @@
2713
2745
  var method = isEdit ? Api.patch : Api.put;
2714
2746
  method(url, data)
2715
2747
  .then(function () {
2716
- if (password && isEdit && editName === Api.getUser()) {
2717
- // Own password trigger "Save Password" before closing modal
2748
+ if (password) {
2749
+ // Browsers only offer to save credentials on a real
2750
+ // form navigation — SPA DOM changes never trigger it.
2751
+ // The fake POST carries the TARGET username, so the
2752
+ // prompt saves a separate entry for that account.
2718
2753
  closeModal();
2719
- _triggerPasswordSave(editName, password, 'users');
2754
+ _triggerPasswordSave(
2755
+ isEdit ? editName : username, password, 'users');
2720
2756
  } else {
2721
- // Other user — wipe field value before close so browser has nothing to save
2722
2757
  if (passwordEl) passwordEl.value = '';
2723
2758
  closeModal();
2724
2759
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpi-admin
3
- Version: 1.4.5
3
+ Version: 1.4.7
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
File without changes
File without changes
File without changes
File without changes
File without changes