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.
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/PKG-INFO +1 -1
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/_version.py +3 -3
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/css/style.css +13 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/js/app.js +61 -26
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/PKG-INFO +1 -1
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/.github/workflows/publish.yml +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/.github/workflows/tests.yml +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/.gitignore +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/LICENSE +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/README.md +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/__init__.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/customizer.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/main.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/favicon.svg +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/index.html +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/js/api.js +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/js/marked.min.js +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/static/js/theme.js +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin/tokens.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/SOURCES.txt +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/dependency_links.txt +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/entry_points.txt +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/requires.txt +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/devpi_admin.egg-info/top_level.txt +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/pyproject.toml +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/setup.cfg +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/__init__.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_acl_read.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_devpi_tokens_ui.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_filter.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_hooks.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_json_safe.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_package.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_pipconf.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_tokens.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_tween.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_view_helpers.py +0 -0
- {devpi_admin-1.4.5 → devpi_admin-1.4.7}/tests/test_wants_html.py +0 -0
|
@@ -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.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 4,
|
|
21
|
+
__version__ = version = '1.4.7'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 4, 7)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2649
|
-
//
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
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
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
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
|
-
|
|
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
|
|
2717
|
-
//
|
|
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(
|
|
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
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|