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.
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/PKG-INFO +1 -1
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/_version.py +3 -3
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/css/style.css +11 -5
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/js/app.js +60 -32
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/PKG-INFO +1 -1
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/.github/workflows/publish.yml +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/.github/workflows/tests.yml +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/.gitignore +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/LICENSE +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/README.md +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/__init__.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/customizer.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/main.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/favicon.svg +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/index.html +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/js/api.js +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/js/marked.min.js +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/static/js/theme.js +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin/tokens.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/SOURCES.txt +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/dependency_links.txt +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/entry_points.txt +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/requires.txt +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/devpi_admin.egg-info/top_level.txt +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/pyproject.toml +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/setup.cfg +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/__init__.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_acl_read.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_devpi_tokens_ui.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_filter.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_hooks.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_json_safe.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_package.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_pipconf.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_tokens.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_tween.py +0 -0
- {devpi_admin-1.4.6 → devpi_admin-1.4.7}/tests/test_view_helpers.py +0 -0
- {devpi_admin-1.4.6 → 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,11 +1313,17 @@ body {
|
|
|
1313
1313
|
font-family: inherit;
|
|
1314
1314
|
}
|
|
1315
1315
|
|
|
1316
|
-
/*
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
.
|
|
1320
|
-
|
|
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
|
-
|
|
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
|
-
// 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
|
-
|
|
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
|
|
2724
|
-
//
|
|
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(
|
|
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
|
}
|
|
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
|