devpi-admin 1.1.2__tar.gz → 1.2.0__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 (33) hide show
  1. devpi_admin-1.2.0/.github/workflows/publish.yml +39 -0
  2. devpi_admin-1.2.0/.github/workflows/tests.yml +32 -0
  3. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/.gitignore +1 -1
  4. devpi_admin-1.2.0/INSTALL.textile +190 -0
  5. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/PKG-INFO +8 -5
  6. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/README.md +3 -1
  7. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/_version.py +3 -3
  8. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/static/css/style.css +25 -0
  9. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/static/js/app.js +61 -7
  10. devpi_admin-1.2.0/devpi_admin.egg-info/PKG-INFO +231 -0
  11. devpi_admin-1.2.0/devpi_admin.egg-info/SOURCES.txt +31 -0
  12. devpi_admin-1.2.0/devpi_admin.egg-info/dependency_links.txt +1 -0
  13. devpi_admin-1.2.0/devpi_admin.egg-info/entry_points.txt +2 -0
  14. devpi_admin-1.2.0/devpi_admin.egg-info/requires.txt +1 -0
  15. devpi_admin-1.2.0/devpi_admin.egg-info/top_level.txt +1 -0
  16. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/pyproject.toml +7 -19
  17. devpi_admin-1.2.0/setup.cfg +4 -0
  18. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/LICENSE +0 -0
  19. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/__init__.py +0 -0
  20. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/main.py +0 -0
  21. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/static/favicon.svg +0 -0
  22. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/static/index.html +0 -0
  23. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/static/js/api.js +0 -0
  24. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/static/js/marked.min.js +0 -0
  25. {devpi_admin-1.1.2/src → devpi_admin-1.2.0}/devpi_admin/static/js/theme.js +0 -0
  26. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/tests/__init__.py +0 -0
  27. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/tests/test_cached_versions.py +0 -0
  28. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/tests/test_helpers.py +0 -0
  29. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/tests/test_hooks.py +0 -0
  30. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/tests/test_json_safe.py +0 -0
  31. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/tests/test_package.py +0 -0
  32. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/tests/test_tween.py +0 -0
  33. {devpi_admin-1.1.2 → devpi_admin-1.2.0}/tests/test_wants_html.py +0 -0
@@ -0,0 +1,39 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ environment: pypi
11
+ permissions:
12
+ id-token: write
13
+
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ with:
17
+ fetch-depth: 0 # needed for hatch-vcs to derive version from tag
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.14"
23
+
24
+ - name: Install build dependencies
25
+ run: pip install build
26
+
27
+ - name: Install package
28
+ run: pip install .
29
+
30
+ - name: Run unit tests
31
+ run: python -m unittest discover -v tests/
32
+ env:
33
+ PYTHONWARNINGS: "ignore::UserWarning"
34
+
35
+ - name: Build package
36
+ run: python -m build
37
+
38
+ - name: Publish to PyPI
39
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,32 @@
1
+ name: Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test-ubuntu:
11
+ runs-on: ubuntu-latest
12
+ strategy:
13
+ matrix:
14
+ python-version: ["3.10", "3.14"]
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ fetch-depth: 0 # needed for hatch-vcs to read git tags
20
+
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+
26
+ - name: Install package
27
+ run: pip install .
28
+
29
+ - name: Run unit tests
30
+ run: python -m unittest discover -v tests/
31
+ env:
32
+ PYTHONWARNINGS: "ignore::UserWarning"
@@ -10,6 +10,6 @@ build
10
10
  !.gitignore
11
11
  !.github
12
12
  !.gitea
13
- src/devpi_admin/_version.py
13
+ devpi_admin/_version.py
14
14
  *.md
15
15
  !README*.md
@@ -0,0 +1,190 @@
1
+ h1. devpi-admin — inštalačný návod
2
+
3
+ h2. Predpoklady
4
+
5
+ * Python 3.9+
6
+ * Systémový používateľ vyhradený pre službu (ďalej @pypi@)
7
+ * Adresárová štruktúra podľa konvencie — príklady nižšie používajú @/opt/pypi@ a @/var/lib/pypi@, cesty je možné upraviť podľa potreby
8
+
9
+ h2. 1. Adresárová štruktúra
10
+
11
+ <pre>
12
+ /opt/pypi/venv/ — Python virtualenv
13
+ /var/lib/pypi/data/ — dáta devpi servera (databáza, stiahnuté balíky)
14
+ /opt/pypi/etc/ — konfiguračné súbory
15
+ </pre>
16
+
17
+ h2. 2. Vytvorenie virtualenv a inštalácia
18
+
19
+ <pre>
20
+ python3 -m venv /opt/pypi/venv
21
+ /opt/pypi/venv/bin/pip install --upgrade pip
22
+ /opt/pypi/venv/bin/pip install devpi-server devpi-admin
23
+ </pre>
24
+
25
+ h2. 3. Inicializácia serverových dát
26
+
27
+ Inicializácia sa vykoná *raz* pred prvým spustením:
28
+
29
+ <pre>
30
+ sudo -u pypi /opt/pypi/venv/bin/devpi-server \
31
+ --serverdir /var/lib/pypi/data \
32
+ --init
33
+ </pre>
34
+
35
+ h2. 4. Generovanie secret súboru
36
+
37
+ Devpi podpisuje autentifikačné tokeny tajným kľúčom. Bez tohto súboru sa pri každom reštarte vygeneruje nový náhodný kľúč a *všetky existujúce tokeny sa zneplatnia* — používatelia sa musia znova prihlásiť.
38
+
39
+ Secret súbor sa vytvorí *raz*:
40
+
41
+ <pre>
42
+ sudo -u pypi /opt/pypi/venv/bin/devpi-gen-secret --secretfile /opt/pypi/etc/devpi-secret
43
+ chmod 600 /opt/pypi/etc/devpi-secret
44
+ </pre>
45
+
46
+ *Pozor:* adresár @/opt/pypi/etc/@ nesmie byť group-writable, inak devpi odmietne štart.
47
+ Skontrolovať: @ls -ld /opt/pypi/etc/@ — permissions musia byť @drwxr-x---@ alebo prísnejšie.
48
+
49
+ h2. 5. Konfigurácia logovania
50
+
51
+ Devpi štandardne pridáva do logov vlastné timestampy, čo je pri journald zbytočné.
52
+ Vytvorí sa konfiguračný súbor @/opt/pypi/etc/devpi-logging.json@:
53
+
54
+ <pre>
55
+ {
56
+ "version": 1,
57
+ "disable_existing_loggers": false,
58
+ "formatters": {
59
+ "simple": {
60
+ "format": "%(levelname)-5s %(name)s %(message)s"
61
+ }
62
+ },
63
+ "handlers": {
64
+ "console": {
65
+ "class": "logging.StreamHandler",
66
+ "stream": "ext://sys.stdout",
67
+ "formatter": "simple"
68
+ }
69
+ },
70
+ "root": {
71
+ "level": "INFO",
72
+ "handlers": ["console"]
73
+ }
74
+ }
75
+ </pre>
76
+
77
+ h2. 6. systemd service unit
78
+
79
+ Uložiť ako @/etc/systemd/system/devpi.service@:
80
+
81
+ <pre>
82
+ [Unit]
83
+ Description=devpi package server
84
+ After=network.target
85
+
86
+ [Service]
87
+ User=pypi
88
+ Group=pypi
89
+ ExecStart=/opt/pypi/venv/bin/devpi-server \
90
+ --serverdir /var/lib/pypi/data \
91
+ --secretfile /opt/pypi/etc/devpi-secret \
92
+ --logger-cfg /opt/pypi/etc/devpi-logging.json \
93
+ --host 0.0.0.0 \
94
+ --port 3141 \
95
+ --outside-url https://pypi.villapro.eu \
96
+ --restrict-modify root
97
+ Restart=on-failure
98
+ RestartSec=5
99
+ StandardOutput=journal
100
+ StandardError=journal
101
+
102
+ [Install]
103
+ WantedBy=multi-user.target
104
+ </pre>
105
+
106
+ *@--outside-url@* — verejná URL servera (ak beží za reverse proxy). Ak nie je reverse proxy, je možné túto voľbu vynechať alebo nastaviť na @http://server-ip:3141@.
107
+
108
+ *@--restrict-modify root@* — zakazuje bežným používateľom vytvárať nových používateľov a indexy. Odporúčané pre produkciu.
109
+
110
+ Aktivácia a spustenie:
111
+
112
+ <pre>
113
+ systemctl daemon-reload
114
+ systemctl enable devpi
115
+ systemctl start devpi
116
+ systemctl status devpi
117
+ </pre>
118
+
119
+ h2. 7. Overenie inštalácie
120
+
121
+ Po spustení je server dostupný na @http://localhost:3141@.
122
+ Webové rozhranie: @http://localhost:3141/+admin/@
123
+
124
+ Na overenie, že plugin devpi-admin je správne načítaný:
125
+
126
+ <pre>
127
+ curl -s http://localhost:3141/+api | python3 -m json.tool | grep devpi-admin
128
+ </pre>
129
+
130
+ Výstup by mal obsahovať @"devpi-admin"@ v zozname features.
131
+
132
+ h2. 8. Prvé prihlásenie a nastavenie
133
+
134
+ h3. 8.1 Prihlásenie
135
+
136
+ Webové rozhranie sa otvorí v prehliadači. Prihlásenie cez tlačidlo *Login* (vpravo hore).
137
+
138
+ Predvolené prihlasovacie údaje:
139
+ * *Používateľ:* @root@
140
+ * *Heslo:* _(prázdne — pri prvom prihlásení stačí stlačiť Login bez hesla)_
141
+
142
+ *Ihneď po prihlásení je potrebné nastaviť heslo pre root:* kliknutím na meno @root@ v pravom hornom rohu sa otvorí dialóg na zmenu hesla.
143
+
144
+ h3. 8.2 Mirror index (PyPI)
145
+
146
+ devpi pri inicializácii automaticky vytvorí používateľa @root@ a index @root/pypi@ (mirror PyPI). Tento index je pripravený na použitie bez ďalšej konfigurácie.
147
+
148
+ V záložke *Indexes* by mal byť viditeľný @root/pypi@ typu @mirror@.
149
+
150
+ h3. 8.3 Vytvorenie privátneho indexu
151
+
152
+ Pre nahrávanie vlastných balíkov:
153
+
154
+ # Vytvorenie používateľa pre tím: *Users → + New User*
155
+ # Vytvorenie indexu: *Indexes → + New Index*
156
+ ** *Owner:* daný používateľ
157
+ ** *Type:* @stage@
158
+ ** *Bases:* @root/pypi@ (balíky ktoré nie sú v indexe sa stiahnu z PyPI)
159
+ ** *Volatile:* podľa potreby (volatile = možnosť prepísať rovnakú verziu)
160
+
161
+ h3. 8.4 Konfigurácia pip na klientoch
162
+
163
+ <pre>
164
+ # /etc/pip.conf alebo ~/.pip/pip.conf
165
+ [global]
166
+ index-url = https://pypi.villapro.eu/user/index/+simple/
167
+ </pre>
168
+
169
+ Pre autentifikovaný prístup:
170
+
171
+ <pre>
172
+ [global]
173
+ index-url = https://user:heslo@pypi.villapro.eu/user/index/+simple/
174
+ </pre>
175
+
176
+ h2. 9. Aktualizácia devpi-admin
177
+
178
+ <pre>
179
+ systemctl stop devpi
180
+ /opt/pypi/venv/bin/pip install --upgrade devpi-admin
181
+ systemctl start devpi
182
+ </pre>
183
+
184
+ Dáta servera (@/var/lib/pypi/data@) sa aktualizáciou pluginu nedotknú.
185
+
186
+ h2. 10. Logy
187
+
188
+ <pre>
189
+ journalctl -u devpi -f
190
+ </pre>
@@ -1,11 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devpi-admin
3
- Version: 1.1.2
3
+ Version: 1.2.0
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
7
- License-File: LICENSE
8
- Keywords: admin,devpi,pypi,ui,web
7
+ Keywords: devpi,pypi,admin,web,ui
9
8
  Classifier: Development Status :: 4 - Beta
10
9
  Classifier: Framework :: Pyramid
11
10
  Classifier: Intended Audience :: Developers
@@ -16,8 +15,10 @@ Classifier: Programming Language :: Python :: 3
16
15
  Classifier: Topic :: Software Development :: Libraries
17
16
  Classifier: Topic :: System :: Software Distribution
18
17
  Requires-Python: >=3.9
19
- Requires-Dist: devpi-server>=6.0
20
18
  Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: devpi-server>=6.0
21
+ Dynamic: license-file
21
22
 
22
23
  # devpi-admin
23
24
 
@@ -35,6 +36,8 @@ talks to the standard devpi JSON API directly.
35
36
  - Server info with version of devpi-server and all installed plugins (auto-detected)
36
37
  - Cache metrics with hit-rate bars (storage, changelog, relpath caches)
37
38
  - Whoosh search index queue status
39
+ - **Replica status** (master only) — per-replica cards with online/offline badge, serial lag,
40
+ and last-seen timestamp; visible only when replicas are connected
38
41
 
39
42
  ### Indexes
40
43
  - Visual cards color-coded by type: green (stage), amber (volatile stage), blue (mirror)
@@ -51,7 +54,7 @@ talks to the standard devpi JSON API directly.
51
54
 
52
55
  ### Packages
53
56
  - Client-side search with PEP 503 name normalization
54
- - Mirror indexes: shows only cached packages (fast filesystem scan, no 17 MB index download);
57
+ - Mirror indexes: shows only cached packages (filesystem scan, no 17 MB index download);
55
58
  "Download full index" button available for complete browse
56
59
  - Package cards with latest version and `pip install` command
57
60
 
@@ -14,6 +14,8 @@ talks to the standard devpi JSON API directly.
14
14
  - Server info with version of devpi-server and all installed plugins (auto-detected)
15
15
  - Cache metrics with hit-rate bars (storage, changelog, relpath caches)
16
16
  - Whoosh search index queue status
17
+ - **Replica status** (master only) — per-replica cards with online/offline badge, serial lag,
18
+ and last-seen timestamp; visible only when replicas are connected
17
19
 
18
20
  ### Indexes
19
21
  - Visual cards color-coded by type: green (stage), amber (volatile stage), blue (mirror)
@@ -30,7 +32,7 @@ talks to the standard devpi JSON API directly.
30
32
 
31
33
  ### Packages
32
34
  - Client-side search with PEP 503 name normalization
33
- - Mirror indexes: shows only cached packages (fast filesystem scan, no 17 MB index download);
35
+ - Mirror indexes: shows only cached packages (filesystem scan, no 17 MB index download);
34
36
  "Download full index" button available for complete browse
35
37
  - Package cards with latest version and `pip install` command
36
38
 
@@ -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.2'
22
- __version_tuple__ = version_tuple = (1, 1, 2)
21
+ __version__ = version = '1.2.0'
22
+ __version_tuple__ = version_tuple = (1, 2, 0)
23
23
 
24
- __commit_id__ = commit_id = None
24
+ __commit_id__ = commit_id = 'g34be8ff92'
@@ -1536,6 +1536,31 @@ body {
1536
1536
  margin-bottom: 4px;
1537
1537
  }
1538
1538
 
1539
+ .replica-title {
1540
+ display: flex;
1541
+ justify-content: space-between;
1542
+ align-items: center;
1543
+ }
1544
+
1545
+ .replica-badge {
1546
+ font-size: 11px;
1547
+ font-weight: 600;
1548
+ padding: 2px 7px;
1549
+ border-radius: 10px;
1550
+ text-transform: uppercase;
1551
+ letter-spacing: 0.04em;
1552
+ }
1553
+
1554
+ .replica-online {
1555
+ background: color-mix(in srgb, var(--success) 15%, transparent);
1556
+ color: var(--success);
1557
+ }
1558
+
1559
+ .replica-offline {
1560
+ background: color-mix(in srgb, var(--danger) 15%, transparent);
1561
+ color: var(--danger);
1562
+ }
1563
+
1539
1564
  .status-row {
1540
1565
  display: flex;
1541
1566
  justify-content: space-between;
@@ -1185,6 +1185,12 @@
1185
1185
 
1186
1186
  var PKG_LIMIT = 100;
1187
1187
 
1188
+ function canDeleteFromIndex(indexPath) {
1189
+ var user = Api.getUser();
1190
+ if (!user) return false;
1191
+ return user === 'root' || user === indexPath.split('/')[0];
1192
+ }
1193
+
1188
1194
  function buildPackageCard(indexPath, pkg, fetchVersion) {
1189
1195
  var card = el('div', {className: 'pkg-card'});
1190
1196
  var cardHead = el('div', {className: 'pkg-card-head'});
@@ -1194,9 +1200,11 @@
1194
1200
  textContent: pkg,
1195
1201
  }));
1196
1202
 
1197
- cardHead.appendChild(buildKebabMenu([
1198
- {label: 'Delete all versions', danger: true, onclick: function () { closeAllKebabs(); deletePackage(indexPath, pkg); }},
1199
- ]));
1203
+ if (canDeleteFromIndex(indexPath)) {
1204
+ cardHead.appendChild(buildKebabMenu([
1205
+ {label: 'Delete all versions', danger: true, onclick: function () { closeAllKebabs(); deletePackage(indexPath, pkg); }},
1206
+ ]));
1207
+ }
1200
1208
  card.appendChild(cardHead);
1201
1209
 
1202
1210
  card.appendChild(buildPipBlock(indexPath, pkg));
@@ -1240,7 +1248,8 @@
1240
1248
  function fetchStage() {
1241
1249
  showHeadingAndLoading(false);
1242
1250
  Api.get('/' + indexPath).then(function (data) {
1243
- renderPackages(indexPath, data.result, false);
1251
+ var resultIsMirror = !!(data.result && data.result.type === 'mirror');
1252
+ renderPackages(indexPath, data.result, resultIsMirror);
1244
1253
  }).catch(handleApiError);
1245
1254
  }
1246
1255
 
@@ -1443,13 +1452,13 @@
1443
1452
  ' ',
1444
1453
  el('span', {className: 'page-heading-version', textContent: 'v' + currentVer}),
1445
1454
  ]),
1446
- el('div', {className: 'view-header-actions'}, [
1455
+ el('div', {className: 'view-header-actions'}, canDeleteFromIndex(indexPath) ? [
1447
1456
  el('button', {
1448
- className: 'btn btn-danger auth-only',
1457
+ className: 'btn btn-danger',
1449
1458
  textContent: 'Delete package',
1450
1459
  onclick: function () { deletePackage(indexPath, pkg); },
1451
1460
  }),
1452
- ]),
1461
+ ] : []),
1453
1462
  ]));
1454
1463
 
1455
1464
  if (cachedVersions.length === 0 && !allVersions) {
@@ -1972,10 +1981,55 @@
1972
1981
  grid.appendChild(whooshCard);
1973
1982
  }
1974
1983
 
1984
+ // Replicas — only shown on master with connected replicas
1985
+ var pollingReplicas = status.polling_replicas || {};
1986
+ var replicaUuids = Object.keys(pollingReplicas);
1987
+ if (status.role === 'MASTER' && replicaUuids.length > 0) {
1988
+ var masterSerial = status.serial || 0;
1989
+ var now = Date.now() / 1000;
1990
+ // Replica is considered offline if it hasn't polled in >90s
1991
+ // (normal polling interval is ~37.5s)
1992
+ var OFFLINE_THRESHOLD = 90;
1993
+
1994
+ for (var ri = 0; ri < replicaUuids.length; ri++) {
1995
+ var uuid = replicaUuids[ri];
1996
+ var rep = pollingReplicas[uuid];
1997
+ var lastRequest = rep['last-request'] || 0;
1998
+ var age = now - lastRequest;
1999
+ var isOnline = rep['in-request'] || age < OFFLINE_THRESHOLD;
2000
+ var lag = masterSerial - (rep['serial'] || 0);
2001
+ var label = rep['remote-ip'] || uuid.substring(0, 8);
2002
+
2003
+ var repCard = el('div', {className: 'status-card'});
2004
+ var titleRow = el('div', {className: 'status-card-title replica-title'}, [
2005
+ el('span', {textContent: 'Replica: ' + label}),
2006
+ el('span', {
2007
+ className: 'replica-badge ' + (isOnline ? 'replica-online' : 'replica-offline'),
2008
+ textContent: isOnline ? 'online' : 'offline',
2009
+ }),
2010
+ ]);
2011
+ repCard.appendChild(titleRow);
2012
+ repCard.appendChild(statusRow('Serial lag', lag === 0 ? 'in sync' : '+' + lag));
2013
+ repCard.appendChild(statusRow('Last seen', _formatAge(age)));
2014
+ repCard.appendChild(statusRow('Polling', rep['in-request'] ? 'active' : 'idle'));
2015
+ if (rep['outside-url']) {
2016
+ repCard.appendChild(statusRow('URL', rep['outside-url']));
2017
+ }
2018
+ grid.appendChild(repCard);
2019
+ }
2020
+ }
2021
+
1975
2022
  content.appendChild(grid);
1976
2023
  }).catch(handleApiError);
1977
2024
  }
1978
2025
 
2026
+ function _formatAge(seconds) {
2027
+ if (seconds < 5) return 'just now';
2028
+ if (seconds < 60) return Math.floor(seconds) + 's ago';
2029
+ if (seconds < 3600) return Math.floor(seconds / 60) + 'm ago';
2030
+ return Math.floor(seconds / 3600) + 'h ago';
2031
+ }
2032
+
1979
2033
  function formatNum(n) {
1980
2034
  if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
1981
2035
  if (n >= 1000) return (n / 1000).toFixed(1) + 'k';
@@ -0,0 +1,231 @@
1
+ Metadata-Version: 2.4
2
+ Name: devpi-admin
3
+ Version: 1.2.0
4
+ Summary: Modern web UI plugin for devpi-server — drop-in replacement for devpi-web
5
+ Author-email: Pavel Revak <pavelrevak@gmail.com>
6
+ License: MIT
7
+ Keywords: devpi,pypi,admin,web,ui
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Framework :: Pyramid
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Intended Audience :: System Administrators
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Classifier: Topic :: System :: Software Distribution
17
+ Requires-Python: >=3.9
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: devpi-server>=6.0
21
+ Dynamic: license-file
22
+
23
+ # devpi-admin
24
+
25
+ A modern web UI plugin for [devpi-server](https://devpi.net/) — a drop-in replacement for
26
+ `devpi-web`. Ships as a Python package that registers itself as a devpi-server plugin via the
27
+ standard entry point mechanism, so a single `pip install devpi-admin` is enough.
28
+
29
+ The UI itself is a bundled single-page application (pure HTML + CSS + vanilla JavaScript, no
30
+ build step) served under `/+admin/`. All devpi REST API endpoints remain untouched — the SPA
31
+ talks to the standard devpi JSON API directly.
32
+
33
+ ## Features
34
+
35
+ ### Dashboard
36
+ - Server info with version of devpi-server and all installed plugins (auto-detected)
37
+ - Cache metrics with hit-rate bars (storage, changelog, relpath caches)
38
+ - Whoosh search index queue status
39
+ - **Replica status** (master only) — per-replica cards with online/offline badge, serial lag,
40
+ and last-seen timestamp; visible only when replicas are connected
41
+
42
+ ### Indexes
43
+ - Visual cards color-coded by type: green (stage), amber (volatile stage), blue (mirror)
44
+ - `pip install` command with copy-to-clipboard (click to copy, green flash feedback)
45
+ - `pip.conf` toggle — switch between short form and full `--index-url` / `--trusted-host`
46
+ - `pip.conf` generator — download a ready-to-use config per index
47
+ - Create / edit / delete indexes via modal dialogs
48
+ - `bases` editor with drag & drop priority ordering and transitive inheritance display
49
+ - `acl_upload` tag picker with user selection dropdown
50
+ - `volatile`, `mirror_url`, `title` configuration
51
+
52
+ ### Users
53
+ - Create, edit (email, password), delete users (admin only)
54
+
55
+ ### Packages
56
+ - Client-side search with PEP 503 name normalization
57
+ - Mirror indexes: shows only cached packages (filesystem scan, no 17 MB index download);
58
+ "Download full index" button available for complete browse
59
+ - Package cards with latest version and `pip install` command
60
+
61
+ ### Package detail (PyPI-like layout)
62
+ - **Sidebar**: metadata (author, license, Python version, keywords, platform, maintainer,
63
+ extras, project URLs, dependencies), `pip install` command, file downloads with upload dates
64
+ - **Version list**: cached versions shown normally, uncached versions link to pypi.org (↗);
65
+ "Load all versions" button for mirrors
66
+ - **README**: rendered markdown (via `marked.js`); fetched from PyPI.org for mirror packages
67
+ where devpi doesn't cache the description
68
+
69
+ ### General
70
+ - **Anonymous browsing** — visitors can explore public indexes without logging in; admin
71
+ actions (create/edit/delete) appear only after authentication
72
+ - **Dark / light / auto theme** with half-circle icon for auto mode
73
+ - **Responsive mobile menu** with hamburger toggle
74
+ - **ESC + outside-click** dismissal for modals, dropdown menus, mobile menu
75
+ - **Login via modal** — no separate login page
76
+
77
+ ## Plugin API endpoints
78
+
79
+ In addition to serving the SPA, `devpi-admin` registers custom API endpoints under
80
+ `/+admin-api/` for features that the standard devpi REST API doesn't provide efficiently:
81
+
82
+ | Endpoint | Method | Description |
83
+ |----------|--------|-------------|
84
+ | `/+admin-api/cached/{user}/{index}` | GET | List cached package names for a mirror index (filesystem scan) |
85
+ | `/+admin-api/versions/{user}/{index}/{project}` | GET | Version list with cached/uncached distinction |
86
+ | `/+admin-api/versions/{user}/{index}/{project}?all=1` | GET | Include all upstream versions (mirrors) |
87
+ | `/+admin-api/versiondata/{user}/{index}/{project}/{version}` | GET | Metadata + file links for a single version |
88
+
89
+ ## Installation
90
+
91
+ ```bash
92
+ pip install devpi-admin
93
+ ```
94
+
95
+ This pulls in `devpi-server` as a dependency. If you are using devpi in a dedicated venv
96
+ (recommended), install the plugin into the same venv:
97
+
98
+ ```bash
99
+ /var/lib/pypi/venv/bin/pip install devpi-admin
100
+ systemctl --user restart devpi # or however you run devpi-server
101
+ ```
102
+
103
+ You should uninstall `devpi-web` — `devpi-admin` replaces it entirely:
104
+
105
+ ```bash
106
+ pip uninstall devpi-web
107
+ ```
108
+
109
+ Both plugins can technically coexist but it is not recommended. `devpi-admin` intercepts `/`
110
+ for HTML requests while `devpi-web` would still serve its own HTML on other routes like
111
+ `/<user>/<index>/<package>`, leading to a confusing mixed experience.
112
+
113
+ ## Usage
114
+
115
+ After restart, open:
116
+
117
+ ```
118
+ http://<your-devpi-host>:3141/
119
+ ```
120
+
121
+ Browser visits to `/` are redirected to `/+admin/`, which serves the SPA. Direct links like
122
+ `http://<host>:3141/+admin/#packages/ci/testing` work and can be bookmarked.
123
+
124
+ devpi CLI tools and other JSON clients are unaffected — they send `Accept: application/json`
125
+ and bypass the redirect.
126
+
127
+ ## How it works
128
+
129
+ `devpi-admin` registers a `devpi_server` entry point that hooks into
130
+ `devpiserver_pyramid_configure` (with `@hookimpl` from pluggy) to:
131
+
132
+ 1. Serve the bundled static assets under `/+admin/` via a Pyramid static view.
133
+ 2. Add an explicit view at `/+admin/` that returns `index.html`.
134
+ 3. Register custom API views under `/+admin-api/` for cached-package and per-version queries.
135
+ 4. Install a tween that redirects HTML browser requests on `/` to `/+admin/` while leaving
136
+ JSON requests intact.
137
+
138
+ The plugin uses devpi-server internals (`xom.model.getstage`, `stage.list_versions`,
139
+ `stage.get_versiondata`, `stage.get_releaselinks`) and direct filesystem access
140
+ (`serverdir/+files/`) for the cached-packages API.
141
+
142
+ ## Requirements
143
+
144
+ - Python 3.9+
145
+ - devpi-server 6.0+
146
+ - A browser with ES6 support (`Promise`, `fetch`, `sessionStorage`)
147
+
148
+ ## Routes (UI)
149
+
150
+ Routing is hash-based, so any of these URLs can be bookmarked or shared:
151
+
152
+ | Hash | View |
153
+ |------|------|
154
+ | `#` | Status dashboard (default) |
155
+ | `#indexes` | All indexes |
156
+ | `#indexes/<user>` | Indexes filtered by user |
157
+ | `#packages/<user>/<index>` | Packages in an index |
158
+ | `#package/<user>/<index>/<name>` | Package detail (latest cached version) |
159
+ | `#package/<user>/<index>/<name>?version=<ver>` | Specific version |
160
+ | `#users` | User management (requires login) |
161
+
162
+ ## Project layout
163
+
164
+ ```
165
+ devpi-admin/
166
+ ├── pyproject.toml
167
+ ├── README.md
168
+ ├── LICENSE
169
+ ├── .github/workflows/
170
+ │ ├── tests.yml — CI on push/PR (Python 3.10 + 3.14)
171
+ │ └── publish.yml — publish to PyPI on release
172
+ ├── src/
173
+ │ └── devpi_admin/
174
+ │ ├── __init__.py — version (from git tag via hatch-vcs)
175
+ │ ├── main.py — Pyramid hooks, tween, API views
176
+ │ └── static/
177
+ │ ├── index.html — SPA entry point
178
+ │ ├── css/style.css
179
+ │ └── js/
180
+ │ ├── api.js — devpi REST wrapper + auth
181
+ │ ├── theme.js — theme toggle (light/dark/auto)
182
+ │ ├── marked.min.js — vendored markdown renderer
183
+ │ └── app.js — routing, views, rendering
184
+ └── tests/
185
+ ├── test_cached_versions.py — filesystem scan (tmpdir)
186
+ ├── test_helpers.py — filename parsing, normalization
187
+ ├── test_hooks.py — pluggy hook registration
188
+ ├── test_json_safe.py — readonly view conversion
189
+ ├── test_package.py — entry point, static files
190
+ ├── test_tween.py — redirect behavior
191
+ └── test_wants_html.py — Accept header heuristic
192
+ ```
193
+
194
+ ## Development
195
+
196
+ ```bash
197
+ git clone <repo>
198
+ cd devpi-admin
199
+ python -m venv .venv
200
+ .venv/bin/pip install -e .
201
+ ```
202
+
203
+ The static files live at `src/devpi_admin/static/` and can be edited in place — changes
204
+ show up on the next browser reload, no restart of devpi-server required (static views
205
+ read from disk on each request). Python changes (`main.py`) require a devpi-server restart.
206
+
207
+ Run the unit tests:
208
+
209
+ ```bash
210
+ PYTHONWARNINGS="ignore::UserWarning" python -m unittest discover -v tests/
211
+ ```
212
+
213
+ (The `PYTHONWARNINGS` shim hides an unrelated deprecation warning emitted by Pyramid 2.1
214
+ when it imports `pkg_resources`.)
215
+
216
+ ## Releasing
217
+
218
+ Version is derived from the git tag via `hatch-vcs`. To release:
219
+
220
+ 1. `git tag v0.1.0 && git push --tags`
221
+ 2. On GitHub: Releases → Draft new release → select tag → Publish
222
+ 3. The `publish.yml` workflow runs tests, builds wheel+sdist, and uploads to PyPI via trusted
223
+ publishing (no API tokens needed — configure the GitHub environment `pypi` in PyPI settings).
224
+
225
+ ## Author
226
+
227
+ Pavel Revak <pavelrevak@gmail.com>
228
+
229
+ ## License
230
+
231
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,31 @@
1
+ .gitignore
2
+ INSTALL.textile
3
+ LICENSE
4
+ README.md
5
+ pyproject.toml
6
+ .github/workflows/publish.yml
7
+ .github/workflows/tests.yml
8
+ devpi_admin/__init__.py
9
+ devpi_admin/_version.py
10
+ devpi_admin/main.py
11
+ devpi_admin.egg-info/PKG-INFO
12
+ devpi_admin.egg-info/SOURCES.txt
13
+ devpi_admin.egg-info/dependency_links.txt
14
+ devpi_admin.egg-info/entry_points.txt
15
+ devpi_admin.egg-info/requires.txt
16
+ devpi_admin.egg-info/top_level.txt
17
+ devpi_admin/static/favicon.svg
18
+ devpi_admin/static/index.html
19
+ devpi_admin/static/css/style.css
20
+ devpi_admin/static/js/api.js
21
+ devpi_admin/static/js/app.js
22
+ devpi_admin/static/js/marked.min.js
23
+ devpi_admin/static/js/theme.js
24
+ tests/__init__.py
25
+ tests/test_cached_versions.py
26
+ tests/test_helpers.py
27
+ tests/test_hooks.py
28
+ tests/test_json_safe.py
29
+ tests/test_package.py
30
+ tests/test_tween.py
31
+ tests/test_wants_html.py
@@ -0,0 +1,2 @@
1
+ [devpi_server]
2
+ devpi-admin = devpi_admin.main
@@ -0,0 +1 @@
1
+ devpi-server>=6.0
@@ -0,0 +1 @@
1
+ devpi_admin
@@ -1,6 +1,6 @@
1
1
  [build-system]
2
- requires = ["hatchling", "hatch-vcs"]
3
- build-backend = "hatchling.build"
2
+ requires = ["setuptools", "setuptools-scm"]
3
+ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "devpi-admin"
@@ -31,21 +31,9 @@ dependencies = [
31
31
  [project.entry-points.devpi_server]
32
32
  devpi-admin = "devpi_admin.main"
33
33
 
34
- [tool.hatch.version]
35
- source = "vcs"
36
- fallback-version = "0.0.0"
34
+ [tool.setuptools.packages.find]
35
+ include = ["devpi_admin*"]
37
36
 
38
- [tool.hatch.build.hooks.vcs]
39
- version-file = "src/devpi_admin/_version.py"
40
-
41
- [tool.hatch.build.targets.wheel]
42
- packages = ["src/devpi_admin"]
43
-
44
- [tool.hatch.build.targets.sdist]
45
- include = [
46
- "src/devpi_admin",
47
- "tests",
48
- "README.md",
49
- "pyproject.toml",
50
- "LICENSE",
51
- ]
37
+ [tool.setuptools_scm]
38
+ write_to = "devpi_admin/_version.py"
39
+ fallback_version = "0.0.0"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes