devpi-admin 1.1.1__tar.gz → 1.1.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 (23) hide show
  1. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/PKG-INFO +1 -1
  2. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/src/devpi_admin/_version.py +2 -2
  3. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/src/devpi_admin/main.py +16 -0
  4. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/src/devpi_admin/static/css/style.css +19 -3
  5. devpi_admin-1.1.2/src/devpi_admin/static/favicon.svg +10 -0
  6. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/src/devpi_admin/static/index.html +1 -0
  7. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/src/devpi_admin/static/js/app.js +32 -1
  8. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/.gitignore +0 -0
  9. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/LICENSE +0 -0
  10. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/README.md +0 -0
  11. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/pyproject.toml +0 -0
  12. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/src/devpi_admin/__init__.py +0 -0
  13. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/src/devpi_admin/static/js/api.js +0 -0
  14. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/src/devpi_admin/static/js/marked.min.js +0 -0
  15. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/src/devpi_admin/static/js/theme.js +0 -0
  16. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/tests/__init__.py +0 -0
  17. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/tests/test_cached_versions.py +0 -0
  18. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/tests/test_helpers.py +0 -0
  19. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/tests/test_hooks.py +0 -0
  20. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/tests/test_json_safe.py +0 -0
  21. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/tests/test_package.py +0 -0
  22. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/tests/test_tween.py +0 -0
  23. {devpi_admin-1.1.1 → devpi_admin-1.1.2}/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.1.1
3
+ Version: 1.1.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.1.1'
22
- __version_tuple__ = version_tuple = (1, 1, 1)
21
+ __version__ = version = '1.1.2'
22
+ __version_tuple__ = version_tuple = (1, 1, 2)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -44,6 +44,14 @@ def devpiserver_pyramid_configure(config, pyramid_config):
44
44
  lambda request: HTTPFound("/+admin/"),
45
45
  route_name="devpi_admin_spa_noslash")
46
46
 
47
+ # Session validity check.
48
+ pyramid_config.add_route(
49
+ "devpi_admin_session",
50
+ "/+admin-api/session")
51
+ pyramid_config.add_view(
52
+ _session_view, route_name="devpi_admin_session",
53
+ request_method="GET")
54
+
47
55
  # Cached packages API for mirror indexes.
48
56
  pyramid_config.add_route(
49
57
  "devpi_admin_cached",
@@ -82,6 +90,14 @@ def _serve_index(request):
82
90
  content_type="text/html")
83
91
 
84
92
 
93
+ def _session_view(request):
94
+ """Return whether the current request carries a valid authenticated session."""
95
+ user = request.authenticated_userid
96
+ if user:
97
+ return _json_response({"valid": True, "user": user})
98
+ raise HTTPForbidden(json_body={"valid": False, "error": "not authenticated"})
99
+
100
+
85
101
  def _get_stage_or_404(xom, user, index):
86
102
  """Return stage object or raise HTTPNotFound."""
87
103
  stage = xom.model.getstage(user, index)
@@ -196,14 +196,18 @@ body {
196
196
  .user-btn-name {
197
197
  padding: 4px 10px;
198
198
  font-weight: 600;
199
- color: var(--text);
199
+ color: #22c55e;
200
200
  background: var(--bg);
201
201
  cursor: pointer;
202
202
  }
203
203
 
204
204
  .user-btn-name:hover {
205
205
  background: var(--bg-alt);
206
- color: var(--accent);
206
+ opacity: 0.8;
207
+ }
208
+
209
+ .user-btn.is-root .user-btn-name {
210
+ color: #f59e0b;
207
211
  }
208
212
 
209
213
  .user-btn-sep {
@@ -366,7 +370,19 @@ body {
366
370
  /* --- User cards --- */
367
371
 
368
372
  .user-card {
369
- border-left-color: var(--text-faint);
373
+ border-left-color: #22c55e;
374
+ }
375
+
376
+ .user-card.user-root {
377
+ border-left-color: #f59e0b;
378
+ }
379
+
380
+ .user-card .index-card-name {
381
+ color: #22c55e;
382
+ }
383
+
384
+ .user-card.user-root .index-card-name {
385
+ color: #f59e0b;
370
386
  }
371
387
 
372
388
  .user-card .kebab-menu {
@@ -0,0 +1,10 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <!-- Box bottom face -->
3
+ <polygon points="16,18 2,11 16,4 30,11" fill="#4a9eff" opacity="0.85"/>
4
+ <!-- Box left face -->
5
+ <polygon points="2,11 2,23 16,30 16,18" fill="#2563eb"/>
6
+ <!-- Box right face -->
7
+ <polygon points="30,11 30,23 16,30 16,18" fill="#1d4ed8"/>
8
+ <!-- Highlight stripe on top face -->
9
+ <polygon points="16,6 26,10.5 16,15 6,10.5" fill="#93c5fd" opacity="0.4"/>
10
+ </svg>
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>devpi admin</title>
7
+ <link rel="icon" type="image/svg+xml" href="favicon.svg">
7
8
  <link rel="stylesheet" href="css/style.css">
8
9
  </head>
9
10
  <body>
@@ -463,6 +463,7 @@
463
463
  logoutBtn.appendChild(el('span', {className: 'user-btn-action', textContent: 'Logout'}));
464
464
  loginBtn.hidden = true;
465
465
  logoutBtn.hidden = false;
466
+ logoutBtn.classList.toggle('is-root', user === 'root');
466
467
  navUsers.hidden = user !== 'root';
467
468
  document.body.classList.add('authenticated');
468
469
  } else {
@@ -543,6 +544,18 @@
543
544
  });
544
545
  }
545
546
 
547
+ // Reload current view when clicking an already-active nav link
548
+ document.getElementById('main-nav').addEventListener('click', function (e) {
549
+ if (e.target.tagName === 'A') {
550
+ var href = e.target.getAttribute('href') || '#';
551
+ var current = window.location.hash || '#';
552
+ if (href === current) {
553
+ e.preventDefault();
554
+ navigate();
555
+ }
556
+ }
557
+ });
558
+
546
559
  logoutBtn.addEventListener('click', function (e) {
547
560
  // Clicking the username part opens change-password modal
548
561
  if (e.target.classList.contains('user-btn-name')) {
@@ -634,7 +647,7 @@
634
647
  var currentUser = Api.getUser();
635
648
  var canEdit = currentUser === name || currentUser === 'root';
636
649
 
637
- var card = el('div', {className: 'index-card user-card'});
650
+ var card = el('div', {className: 'index-card user-card' + (name === 'root' ? ' user-root' : '')});
638
651
 
639
652
  // Card head: username + kebab menu
640
653
  var cardHead = el('div', {className: 'index-card-head'});
@@ -1981,4 +1994,22 @@
1981
1994
  updateAuthUI();
1982
1995
  updateNav();
1983
1996
  navigate();
1997
+
1998
+ var _sessionCheckReady = false;
1999
+ setTimeout(function () { _sessionCheckReady = true; }, 2000);
2000
+
2001
+ function checkSession() {
2002
+ if (!_sessionCheckReady || !Api.getUser()) return;
2003
+ Api.get('/+admin-api/session').catch(function (err) {
2004
+ if (err.status === 403 || err.status === 401) {
2005
+ Api.logout();
2006
+ updateAuthUI();
2007
+ showError(new Error('Session expired. Please log in again.'));
2008
+ }
2009
+ });
2010
+ }
2011
+ document.addEventListener('visibilitychange', function () {
2012
+ if (document.visibilityState === 'visible') checkSession();
2013
+ });
2014
+ window.addEventListener('focus', checkSession);
1984
2015
  })();
File without changes
File without changes
File without changes
File without changes