devpi-admin 1.4.6__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.6 → devpi_admin-1.4.7}/PKG-INFO +1 -1
  2. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/_version.py +3 -3
  3. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/css/style.css +11 -5
  4. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/js/app.js +60 -32
  5. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/PKG-INFO +1 -1
  6. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/.github/workflows/publish.yml +0 -0
  7. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/.github/workflows/tests.yml +0 -0
  8. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/.gitignore +0 -0
  9. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/LICENSE +0 -0
  10. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/README.md +0 -0
  11. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/__init__.py +0 -0
  12. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/customizer.py +0 -0
  13. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/main.py +0 -0
  14. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/favicon.svg +0 -0
  15. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/index.html +0 -0
  16. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/js/api.js +0 -0
  17. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/js/marked.min.js +0 -0
  18. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/js/theme.js +0 -0
  19. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/tokens.py +0 -0
  20. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/SOURCES.txt +0 -0
  21. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/dependency_links.txt +0 -0
  22. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/entry_points.txt +0 -0
  23. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/requires.txt +0 -0
  24. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/top_level.txt +0 -0
  25. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/pyproject.toml +0 -0
  26. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/setup.cfg +0 -0
  27. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/__init__.py +0 -0
  28. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_acl_read.py +0 -0
  29. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_devpi_tokens_ui.py +0 -0
  30. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_filter.py +0 -0
  31. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_hooks.py +0 -0
  32. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_json_safe.py +0 -0
  33. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_package.py +0 -0
  34. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_pipconf.py +0 -0
  35. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_tokens.py +0 -0
  36. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_tween.py +0 -0
  37. {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_view_helpers.py +0 -0
  38. {devpi_admin-1.4.6 → 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.6
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.6'
22
- __version_tuple__ = version_tuple = (1, 4, 6)
21
+ __version__ = version = '1.4.7'
22
+ __version_tuple__ = version_tuple = (1, 4, 7)
23
23
 
24
- __commit_id__ = commit_id = 'gcd18d639e'
24
+ __commit_id__ = commit_id = 'g609f18017'
@@ -1313,11 +1313,17 @@ body {
1313
1313
  font-family: inherit;
1314
1314
  }
1315
1315
 
1316
- /* Visually mask admin-set passwords for other users. The input stays
1317
- type="text" on purpose type="password" would make the browser
1318
- offer to save someone else's credentials as the admin's own. */
1319
- .masked-input {
1320
- -webkit-text-security: disc;
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;
1321
1327
  }
1322
1328
 
1323
1329
  .modal-body select:not(.tag-picker-add) {
@@ -2637,46 +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
2665
- // text fields. Visual masking via CSS text-security
2666
- // keeps the password manager out of the loop.
2667
- pwInput = el('input', {
2668
- type: 'text',
2669
- id: 'form-password',
2670
- className: 'masked-input',
2679
+ } else if (userInput) {
2680
+ // Create flow: mirror the typed username
2681
+ userInput.addEventListener('input', function () {
2682
+ hiddenUser.value = userInput.value;
2671
2683
  });
2672
- pwInput.setAttribute('autocomplete', 'off');
2673
- pwInput.setAttribute('spellcheck', 'false');
2674
- pwInput.setAttribute('autocapitalize', 'off');
2675
2684
  }
2676
- 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(
2677
2698
  isEdit ? 'New Password (leave empty to keep)' : 'Password',
2678
2699
  pwInput
2679
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);
2680
2705
  },
2681
2706
  [
2682
2707
  el('button', {
@@ -2720,12 +2745,15 @@
2720
2745
  var method = isEdit ? Api.patch : Api.put;
2721
2746
  method(url, data)
2722
2747
  .then(function () {
2723
- if (password && isEdit && editName === Api.getUser()) {
2724
- // 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.
2725
2753
  closeModal();
2726
- _triggerPasswordSave(editName, password, 'users');
2754
+ _triggerPasswordSave(
2755
+ isEdit ? editName : username, password, 'users');
2727
2756
  } else {
2728
- // Other user — wipe field value before close so browser has nothing to save
2729
2757
  if (passwordEl) passwordEl.value = '';
2730
2758
  closeModal();
2731
2759
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpi-admin
3
- Version: 1.4.6
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