withcache 0.7.0__tar.gz → 0.7.1__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.
- {withcache-0.7.0 → withcache-0.7.1}/PKG-INFO +1 -1
- {withcache-0.7.0 → withcache-0.7.1}/shim/build.zig.zon +1 -1
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/__init__.py +1 -1
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/server.py +329 -85
- {withcache-0.7.0 → withcache-0.7.1}/.gitignore +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/LICENSE +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/README.md +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/deploy/Containerfile +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/deploy/compose.yml +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/hatch_build.py +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/pyproject.toml +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/shim/build.zig +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/shim/shim.zig +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/_shim.py +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/client.py +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/curlwithcache.py +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/oras.py +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/static/bootstrap-icons.min.css +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/static/bootstrap.min.css +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/static/fonts/bootstrap-icons.woff +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/static/fonts/bootstrap-icons.woff2 +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/static/htmx.min.js +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/src/withcache/wgetwithcache.py +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/tests/test_differential.py +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/tests/test_oras.py +0 -0
- {withcache-0.7.0 → withcache-0.7.1}/tests/test_withcache.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: withcache
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: Operator-curated, URL-keyed artifact cache for a small lab (CUDA/ROCm/DOCA/firmware)
|
|
5
5
|
Project-URL: Homepage, https://github.com/safl/withcache
|
|
6
6
|
Author-email: "Simon A. F. Lund" <safl@safl.dk>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
.name = .withcache_shim,
|
|
3
3
|
// Zig requires a literal here; keep it in lockstep with the project's
|
|
4
4
|
// single source (src/withcache/__init__.py) via `make bump` / `make version-check`.
|
|
5
|
-
.version = "0.7.
|
|
5
|
+
.version = "0.7.1",
|
|
6
6
|
.fingerprint = 0xd7d96c5ed212ccaa,
|
|
7
7
|
.minimum_zig_version = "0.16.0",
|
|
8
8
|
.paths = .{
|
|
@@ -17,6 +17,6 @@ All modules are stdlib-only and self-contained.
|
|
|
17
17
|
from . import oras
|
|
18
18
|
from .client import blob_url, cache_base, is_cached, serve_url
|
|
19
19
|
|
|
20
|
-
__version__ = "0.7.
|
|
20
|
+
__version__ = "0.7.1"
|
|
21
21
|
|
|
22
22
|
__all__ = ["__version__", "blob_url", "cache_base", "is_cached", "oras", "serve_url"]
|
|
@@ -1136,6 +1136,13 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1136
1136
|
_PRIMARY_RGB = "143, 27, 113"
|
|
1137
1137
|
|
|
1138
1138
|
def _head(self, title: str) -> str:
|
|
1139
|
+
"""Emit the shared page prelude: Bootstrap + icons + htmx +
|
|
1140
|
+
the full bty-family chrome CSS. Every service (bty, withcache,
|
|
1141
|
+
nbdmux) uses this same block; the only per-service knob is the
|
|
1142
|
+
primary hue (``--bs-primary`` and the derived button /
|
|
1143
|
+
rgba() variants). See bty's ``_layout.html`` for the origin
|
|
1144
|
+
of these class names -- kept identical here so operators
|
|
1145
|
+
moving between consoles see one visual grammar."""
|
|
1139
1146
|
primary = self._PRIMARY_HEX
|
|
1140
1147
|
hover = self._PRIMARY_HOVER
|
|
1141
1148
|
rgb = self._PRIMARY_RGB
|
|
@@ -1147,9 +1154,11 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1147
1154
|
<link rel="stylesheet" href="/static/bootstrap-icons.min.css">
|
|
1148
1155
|
<script src="/static/htmx.min.js"></script>
|
|
1149
1156
|
<style>
|
|
1150
|
-
/*
|
|
1151
|
-
|
|
1152
|
-
|
|
1157
|
+
/* Palette anchor for the three-service trio (bty navy,
|
|
1158
|
+
withcache dark-magenta, nbdmux magenta). Bootstrap 5
|
|
1159
|
+
exposes --bs-primary + a matching -rgb triplet used for
|
|
1160
|
+
translucent variants (alerts, focus rings, .bg-*-subtle).
|
|
1161
|
+
Overriding both here re-tints every stock component
|
|
1153
1162
|
without patching bootstrap.min.css. */
|
|
1154
1163
|
:root {{
|
|
1155
1164
|
--bs-primary: {primary};
|
|
@@ -1157,51 +1166,254 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1157
1166
|
--bs-link-color: {primary};
|
|
1158
1167
|
--bs-link-hover-color: {hover};
|
|
1159
1168
|
}}
|
|
1160
|
-
.btn-primary {{
|
|
1161
|
-
|
|
1162
|
-
|
|
1169
|
+
.btn-primary {{
|
|
1170
|
+
--bs-btn-bg: {primary};
|
|
1171
|
+
--bs-btn-border-color: {primary};
|
|
1172
|
+
--bs-btn-hover-bg: {hover};
|
|
1173
|
+
--bs-btn-hover-border-color: {hover};
|
|
1174
|
+
--bs-btn-active-bg: {hover};
|
|
1175
|
+
--bs-btn-active-border-color: {hover};
|
|
1176
|
+
}}
|
|
1163
1177
|
.bg-primary {{ --bs-bg-opacity: 1; background-color: {primary} !important; }}
|
|
1164
1178
|
.text-primary {{ --bs-text-opacity: 1; color: {primary} !important; }}
|
|
1165
1179
|
.border-primary {{ --bs-border-opacity: 1; border-color: {primary} !important; }}
|
|
1166
|
-
/* Brand strip:
|
|
1167
|
-
across
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1180
|
+
/* Brand strip: a thin gradient accent above the navbar.
|
|
1181
|
+
Stable across themes; defined inline rather than in
|
|
1182
|
+
bootstrap.min.css so it renders even when the CSS load
|
|
1183
|
+
races the initial paint. Shared navy -> dark-magenta ->
|
|
1184
|
+
magenta gradient across bty (navy), withcache
|
|
1185
|
+
(dark-magenta) and nbdmux (magenta) so the trio reads as
|
|
1186
|
+
one product family from any of the three consoles. */
|
|
1187
|
+
.brand-accent {{
|
|
1188
|
+
height: 3px;
|
|
1189
|
+
background: linear-gradient(90deg, #0d3585 0%, #8f1b71 50%, #d63384 100%);
|
|
1190
|
+
}}
|
|
1191
|
+
/* The accent + navbar + sub-nav pin to the top as one unit so
|
|
1192
|
+
the sub-nav jump links stay visible while scrolling. */
|
|
1193
|
+
.sticky-header {{
|
|
1194
|
+
position: sticky;
|
|
1195
|
+
top: 0;
|
|
1196
|
+
z-index: 1030;
|
|
1197
|
+
}}
|
|
1198
|
+
/* In-page ``#anchor`` jumps (the sub-nav tab pills) land below
|
|
1199
|
+
the sticky header instead of under it. */
|
|
1200
|
+
html {{
|
|
1201
|
+
scroll-padding-top: 6.5rem;
|
|
1202
|
+
scroll-behavior: smooth;
|
|
1203
|
+
}}
|
|
1204
|
+
/* Brand pill keeps the same padding + radius everywhere; the
|
|
1205
|
+
``brand-active`` variant lights up on the home page so the
|
|
1206
|
+
brand doubles as a Home indicator. */
|
|
1207
|
+
.navbar-brand {{
|
|
1208
|
+
border-radius: 0.5rem;
|
|
1209
|
+
padding-left: 0.6rem;
|
|
1210
|
+
padding-right: 0.6rem;
|
|
1211
|
+
margin-right: 0.25rem;
|
|
1212
|
+
transition: background-color 0.15s;
|
|
1213
|
+
}}
|
|
1214
|
+
.navbar-brand.brand-active {{
|
|
1215
|
+
background-color: rgba({rgb}, 0.85);
|
|
1216
|
+
}}
|
|
1217
|
+
.navbar-brand:hover {{
|
|
1218
|
+
background-color: rgba(255, 255, 255, 0.06);
|
|
1219
|
+
}}
|
|
1220
|
+
.navbar-brand.brand-active:hover {{
|
|
1221
|
+
background-color: rgba({rgb}, 0.95);
|
|
1222
|
+
}}
|
|
1223
|
+
/* Version sits in the navbar alongside the brand pill but
|
|
1224
|
+
OUTSIDE it -- so the brand button stays a clean click target
|
|
1225
|
+
and the version reads as adjacent metadata. */
|
|
1226
|
+
.navbar-version {{
|
|
1227
|
+
color: rgba(255, 255, 255, 0.55);
|
|
1228
|
+
font-weight: 400;
|
|
1229
|
+
font-size: 0.85rem;
|
|
1230
|
+
align-self: center;
|
|
1231
|
+
white-space: nowrap;
|
|
1232
|
+
}}
|
|
1233
|
+
.navbar-brand .brand-icon {{
|
|
1234
|
+
/* Sized to match bty's mascot PNG (1.05rem tall). Using a
|
|
1235
|
+
Bootstrap Icon rather than an image keeps the shell
|
|
1236
|
+
stdlib-only. */
|
|
1237
|
+
font-size: 1.05rem;
|
|
1238
|
+
vertical-align: -0.05rem;
|
|
1239
|
+
}}
|
|
1240
|
+
.navbar .nav-btn {{
|
|
1241
|
+
display: inline-flex;
|
|
1242
|
+
align-items: center;
|
|
1243
|
+
gap: 0.4rem;
|
|
1244
|
+
padding: 0.4rem 0.8rem;
|
|
1245
|
+
margin-right: 0.25rem;
|
|
1246
|
+
border-radius: 0.5rem;
|
|
1247
|
+
color: rgba(255, 255, 255, 0.85);
|
|
1248
|
+
text-decoration: none;
|
|
1249
|
+
transition: background-color 0.15s;
|
|
1250
|
+
}}
|
|
1251
|
+
.navbar .nav-btn:hover {{
|
|
1252
|
+
background-color: rgba(255, 255, 255, 0.10);
|
|
1253
|
+
color: #fff;
|
|
1254
|
+
}}
|
|
1255
|
+
.navbar .nav-btn.active {{
|
|
1256
|
+
background-color: {primary};
|
|
1257
|
+
color: #fff;
|
|
1258
|
+
box-shadow: 0 0 0 1px rgba({rgb}, 0.6);
|
|
1259
|
+
}}
|
|
1260
|
+
.navbar .nav-btn i {{
|
|
1261
|
+
font-size: 1.05rem;
|
|
1262
|
+
}}
|
|
1263
|
+
/* User-bar: a single pill containing username + logout, with a
|
|
1264
|
+
thin vertical divider between them. Visually one widget,
|
|
1265
|
+
but two click targets and zero JavaScript. */
|
|
1266
|
+
.user-bar {{
|
|
1267
|
+
display: inline-flex;
|
|
1268
|
+
align-items: stretch;
|
|
1269
|
+
border-radius: 999px;
|
|
1270
|
+
background-color: rgba(255, 255, 255, 0.08);
|
|
1271
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
1272
|
+
overflow: hidden;
|
|
1273
|
+
font-size: 0.85rem;
|
|
1274
|
+
}}
|
|
1275
|
+
.user-bar-name {{
|
|
1276
|
+
display: inline-flex;
|
|
1277
|
+
align-items: center;
|
|
1278
|
+
gap: 0.4rem;
|
|
1279
|
+
padding: 0.35rem 0.8rem;
|
|
1280
|
+
color: rgba(255, 255, 255, 0.92);
|
|
1281
|
+
}}
|
|
1282
|
+
.user-bar-name code {{
|
|
1283
|
+
color: #fff;
|
|
1284
|
+
background: transparent;
|
|
1285
|
+
padding: 0;
|
|
1286
|
+
}}
|
|
1287
|
+
.user-bar-divider {{
|
|
1288
|
+
width: 1px;
|
|
1289
|
+
background-color: rgba(255, 255, 255, 0.18);
|
|
1290
|
+
}}
|
|
1291
|
+
.user-bar-action {{
|
|
1292
|
+
display: inline-flex;
|
|
1293
|
+
align-items: center;
|
|
1294
|
+
padding: 0.35rem 0.7rem;
|
|
1295
|
+
background: transparent;
|
|
1296
|
+
border: none;
|
|
1297
|
+
color: rgba(255, 255, 255, 0.85);
|
|
1298
|
+
text-decoration: none;
|
|
1299
|
+
transition: background-color 0.15s, color 0.15s;
|
|
1300
|
+
}}
|
|
1301
|
+
.user-bar-action:hover,
|
|
1302
|
+
.user-bar-action:focus {{
|
|
1303
|
+
background-color: rgba(255, 255, 255, 0.10);
|
|
1304
|
+
color: #fff;
|
|
1305
|
+
outline: none;
|
|
1306
|
+
}}
|
|
1307
|
+
.user-bar-action.active {{
|
|
1308
|
+
background-color: rgba(255, 255, 255, 0.16);
|
|
1309
|
+
color: #fff;
|
|
1310
|
+
}}
|
|
1311
|
+
/* Logout hover keeps the danger-red signal so it doesn't
|
|
1312
|
+
look indistinguishable from the account action. */
|
|
1313
|
+
.user-bar-logout:hover,
|
|
1314
|
+
.user-bar-logout:focus {{
|
|
1315
|
+
background-color: rgba(220, 53, 69, 0.65);
|
|
1316
|
+
color: #fff;
|
|
1317
|
+
}}
|
|
1318
|
+
/* Sub-nav strip. Sits immediately below the main ``bg-dark``
|
|
1319
|
+
navbar; coloured CLEARLY lighter so it reads as "second-tier
|
|
1320
|
+
nav, still chrome". Same ``#495057`` (--bs-gray-700) bty
|
|
1321
|
+
uses so the boundary is unambiguous regardless of theme. */
|
|
1322
|
+
.subnav-strip {{
|
|
1323
|
+
background-color: #495057;
|
|
1324
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.10);
|
|
1325
|
+
padding-top: 0.25rem;
|
|
1326
|
+
padding-bottom: 0.25rem;
|
|
1327
|
+
font-size: 0.85rem;
|
|
1328
|
+
line-height: 1.4;
|
|
1329
|
+
}}
|
|
1330
|
+
.subnav-strip > .container {{
|
|
1331
|
+
min-height: 2rem;
|
|
1332
|
+
display: flex;
|
|
1333
|
+
align-items: center;
|
|
1334
|
+
gap: 0.6rem;
|
|
1335
|
+
}}
|
|
1336
|
+
.subnav-strip .form-control-sm,
|
|
1337
|
+
.subnav-strip .btn-sm,
|
|
1338
|
+
.subnav-strip a,
|
|
1339
|
+
.subnav-strip label,
|
|
1340
|
+
.subnav-strip .small,
|
|
1341
|
+
.subnav-strip small {{
|
|
1342
|
+
font-size: inherit;
|
|
1343
|
+
line-height: inherit;
|
|
1344
|
+
}}
|
|
1345
|
+
.subnav-strip .form-control-sm,
|
|
1346
|
+
.subnav-strip .btn-sm {{
|
|
1347
|
+
padding-top: 0.15rem;
|
|
1348
|
+
padding-bottom: 0.15rem;
|
|
1349
|
+
}}
|
|
1350
|
+
.subnav-strip .nav-pills {{
|
|
1351
|
+
gap: 0.25rem;
|
|
1352
|
+
}}
|
|
1353
|
+
.subnav-strip .nav-pills .nav-link {{
|
|
1354
|
+
color: rgba(255, 255, 255, 0.78);
|
|
1355
|
+
padding: 0.15rem 0.55rem;
|
|
1356
|
+
}}
|
|
1357
|
+
.subnav-strip .nav-pills .nav-link:hover {{
|
|
1358
|
+
color: #fff;
|
|
1359
|
+
background-color: rgba(255, 255, 255, 0.10);
|
|
1360
|
+
}}
|
|
1361
|
+
.subnav-strip .nav-pills .nav-link.active-tab {{
|
|
1362
|
+
color: #fff;
|
|
1363
|
+
background-color: {primary};
|
|
1364
|
+
}}
|
|
1365
|
+
.subnav-strip .text-muted {{
|
|
1366
|
+
color: rgba(255, 255, 255, 0.55) !important;
|
|
1367
|
+
}}
|
|
1368
|
+
.subnav-strip code {{
|
|
1369
|
+
color: rgba(255, 255, 255, 0.85);
|
|
1370
|
+
background-color: transparent;
|
|
1371
|
+
}}
|
|
1372
|
+
.subnav-strip a {{
|
|
1373
|
+
color: rgba(255, 255, 255, 0.78);
|
|
1374
|
+
}}
|
|
1375
|
+
.subnav-strip a:hover {{
|
|
1376
|
+
color: #fff;
|
|
1377
|
+
}}
|
|
1378
|
+
/* Withcache-specific bits kept minimal. Everything else
|
|
1379
|
+
inherits from Bootstrap + the chrome above. */
|
|
1172
1380
|
code {{ color: inherit; font-size: .85em; }}
|
|
1173
1381
|
.url {{ word-break: break-all; }}
|
|
1174
1382
|
.num {{ text-align: right; }}
|
|
1175
1383
|
.mono {{ font-family: var(--bs-font-monospace); font-size: .85em; }}
|
|
1176
1384
|
#spin {{ width: 7rem; height: .5rem; margin: 0; }}
|
|
1177
|
-
/*
|
|
1178
|
-
|
|
1179
|
-
so the
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
border-bottom: 2px solid transparent; padding: .45rem .9rem;
|
|
1183
|
-
font-size: .9rem; margin-bottom: -1px; }}
|
|
1184
|
-
nav.tabs .nav-link:hover {{ color: var(--bs-body-color); }}
|
|
1185
|
-
nav.tabs .nav-link.active-tab {{ color: var(--bs-body-color);
|
|
1186
|
-
border-bottom-color: var(--bs-primary); font-weight: 600; }}
|
|
1187
|
-
section.tab {{ display: none; padding-top: .75rem; }}
|
|
1385
|
+
/* Tab content panels: shown only when the section carries
|
|
1386
|
+
``.active-tab``. Same hook the previous nav-tabs code used;
|
|
1387
|
+
unchanged so the existing htmx post-swap script keeps
|
|
1388
|
+
working. */
|
|
1389
|
+
section.tab {{ display: none; padding-top: .25rem; }}
|
|
1188
1390
|
section.tab.active-tab {{ display: block; }}
|
|
1189
1391
|
</style>
|
|
1190
1392
|
</head>"""
|
|
1191
1393
|
|
|
1192
1394
|
def render_login(self, error: str = "") -> str:
|
|
1395
|
+
"""The unauthenticated shell: accent strip + dark navbar with the
|
|
1396
|
+
brand pill (no user-bar since there's no session), then a
|
|
1397
|
+
centered login card. Same chrome the authenticated page uses,
|
|
1398
|
+
so the operator sees continuity across the auth boundary."""
|
|
1193
1399
|
err = f'<div class="alert alert-danger">{html.escape(error)}</div>' if error else ""
|
|
1194
1400
|
return f"""{self._head("withcache - login")}
|
|
1195
|
-
<body>
|
|
1401
|
+
<body class="bg-light">
|
|
1402
|
+
<div class="sticky-header">
|
|
1196
1403
|
<div class="brand-accent"></div>
|
|
1197
|
-
<
|
|
1198
|
-
|
|
1404
|
+
<nav class="navbar navbar-expand-md bg-dark navbar-dark py-2">
|
|
1405
|
+
<div class="container">
|
|
1406
|
+
<a class="navbar-brand fw-semibold brand-active" href="/">
|
|
1407
|
+
<i class="bi bi-database brand-icon me-1"></i>WITHCACHE
|
|
1408
|
+
</a>
|
|
1409
|
+
<span class="navbar-version">v{html.escape(__version__)}</span>
|
|
1410
|
+
</div>
|
|
1411
|
+
</nav>
|
|
1412
|
+
</div>
|
|
1413
|
+
<main class="container">
|
|
1414
|
+
<div class="card mx-auto mt-5" style="max-width: 24rem;">
|
|
1199
1415
|
<div class="card-body">
|
|
1200
|
-
<
|
|
1201
|
-
<i class="bi bi-hdd-stack"></i> withcache
|
|
1202
|
-
<small class="text-muted fs-6">v{html.escape(__version__)}</small>
|
|
1203
|
-
</h3>
|
|
1204
|
-
<p class="text-muted small mb-3">Operator login</p>
|
|
1416
|
+
<h5 class="card-title mb-3">Operator login</h5>
|
|
1205
1417
|
{err}
|
|
1206
1418
|
<form method="post" action="/ui/login">
|
|
1207
1419
|
<div class="mb-3">
|
|
@@ -1215,29 +1427,68 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1215
1427
|
</main></body></html>"""
|
|
1216
1428
|
|
|
1217
1429
|
def render_page(self) -> str:
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1430
|
+
"""The authenticated shell: sticky-header wrapping the accent
|
|
1431
|
+
strip, the dark navbar (brand pill + version + user-bar), and
|
|
1432
|
+
the subnav strip carrying the tab pills. Then main.container
|
|
1433
|
+
with the "Add from URI" card and the htmx-swapped #dash
|
|
1434
|
+
region. Same chrome bty uses; only ``--bs-primary`` differs."""
|
|
1435
|
+
user_bar = (
|
|
1436
|
+
'<div class="user-bar mt-2 mt-md-0" title="Operator session">'
|
|
1437
|
+
'<span class="user-bar-name">'
|
|
1438
|
+
'<i class="bi bi-person-circle"></i><code>operator</code>'
|
|
1439
|
+
"</span>"
|
|
1440
|
+
'<span class="user-bar-divider"></span>'
|
|
1441
|
+
'<form action="/ui/logout" method="post" class="m-0 d-inline-flex">'
|
|
1442
|
+
'<button type="submit" class="user-bar-action user-bar-logout" title="Sign out">'
|
|
1443
|
+
'<i class="bi bi-box-arrow-right"></i>'
|
|
1444
|
+
"</button></form>"
|
|
1445
|
+
"</div>"
|
|
1222
1446
|
if self.auth.enabled
|
|
1223
1447
|
else ""
|
|
1224
1448
|
)
|
|
1225
1449
|
return f"""{self._head("withcache cache-host")}
|
|
1226
|
-
<body>
|
|
1450
|
+
<body class="bg-light">
|
|
1451
|
+
<!-- Sticky header: the accent strip, the main navbar, and the
|
|
1452
|
+
sub-nav strip travel together and pin to the top so the tab
|
|
1453
|
+
pills stay reachable while scrolling a long list. -->
|
|
1454
|
+
<div class="sticky-header">
|
|
1227
1455
|
<div class="brand-accent"></div>
|
|
1228
|
-
<nav class="navbar navbar-expand
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
>
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1456
|
+
<nav class="navbar navbar-expand-md bg-dark navbar-dark py-2">
|
|
1457
|
+
<div class="container">
|
|
1458
|
+
<a class="navbar-brand fw-semibold brand-active" href="/">
|
|
1459
|
+
<i class="bi bi-database brand-icon me-1"></i>WITHCACHE
|
|
1460
|
+
</a>
|
|
1461
|
+
<div class="d-flex flex-grow-1 align-items-center flex-wrap">
|
|
1462
|
+
<div class="me-auto d-flex flex-wrap">
|
|
1463
|
+
<!-- withcache is a single-page dashboard: no middle
|
|
1464
|
+
nav-btns. me-auto pushes version + user-bar right. -->
|
|
1465
|
+
</div>
|
|
1466
|
+
<span class="navbar-version me-2">v{html.escape(__version__)}</span>
|
|
1467
|
+
{user_bar}
|
|
1468
|
+
</div>
|
|
1238
1469
|
</div>
|
|
1239
|
-
</div>
|
|
1240
1470
|
</nav>
|
|
1471
|
+
|
|
1472
|
+
<!-- Sub-nav strip. Carries the five tab pills below the main
|
|
1473
|
+
navbar; visually attached (same sticky container, #495057
|
|
1474
|
+
background) but rendered STATIC here so tab labels don't
|
|
1475
|
+
flicker on the 1 Hz htmx dashboard swap. Per-tab counts live
|
|
1476
|
+
in the summary line inside the dash content and update on
|
|
1477
|
+
every swap. -->
|
|
1478
|
+
<div class="subnav-strip">
|
|
1479
|
+
<div class="container">
|
|
1480
|
+
<ul class="nav nav-pills flex-nowrap flex-md-wrap">
|
|
1481
|
+
<li class="nav-item"><a class="nav-link" href="#tab-cached">Cached</a></li>
|
|
1482
|
+
<li class="nav-item"><a class="nav-link" href="#tab-streams">Streams</a></li>
|
|
1483
|
+
<li class="nav-item"><a class="nav-link" href="#tab-downloads">Downloads</a></li>
|
|
1484
|
+
<li class="nav-item"><a class="nav-link" href="#tab-misses">Misses</a></li>
|
|
1485
|
+
<li class="nav-item"><a class="nav-link" href="#tab-catalog">Catalog</a></li>
|
|
1486
|
+
<span class="ms-auto"><progress id="spin" class="htmx-indicator"></progress></span>
|
|
1487
|
+
</ul>
|
|
1488
|
+
</div>
|
|
1489
|
+
</div>
|
|
1490
|
+
</div><!-- /.sticky-header -->
|
|
1491
|
+
|
|
1241
1492
|
<main class="container py-4">
|
|
1242
1493
|
<div class="card mb-4">
|
|
1243
1494
|
<div class="card-header"><i class="bi bi-cloud-download text-primary"></i> Add from URI</div>
|
|
@@ -1256,15 +1507,11 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1256
1507
|
|
|
1257
1508
|
<!-- The hx-trigger gates polling on the user NOT having an active
|
|
1258
1509
|
text selection, so highlight-and-copy a URL out of a table cell
|
|
1259
|
-
isn't wiped by the 1 Hz refresh.
|
|
1260
|
-
|
|
1261
|
-
the
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
as ``X-Active-Tab`` on every refresh so the server can bake
|
|
1265
|
-
``.active-tab`` into the rendered HTML -- eliminating the
|
|
1266
|
-
visible flicker the post-swap JS-applied class would otherwise
|
|
1267
|
-
cause when the new innerHTML lands without the class. -->
|
|
1510
|
+
isn't wiped by the 1 Hz refresh. hx-headers sends the current
|
|
1511
|
+
URL hash (the active tab id) as X-Active-Tab on every refresh
|
|
1512
|
+
so the server can bake .active-tab into the rendered HTML,
|
|
1513
|
+
eliminating the visible flicker the post-swap JS-applied class
|
|
1514
|
+
would otherwise cause when the new innerHTML lands. -->
|
|
1268
1515
|
<div id="dash" hx-get="/admin/dash"
|
|
1269
1516
|
hx-trigger="load, every 1s [document.getSelection().isCollapsed]"
|
|
1270
1517
|
hx-swap="innerHTML"
|
|
@@ -1272,16 +1519,15 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1272
1519
|
{self.render_dash(active_tab=(self.headers.get("X-Active-Tab") or "").strip())}
|
|
1273
1520
|
</div>
|
|
1274
1521
|
|
|
1275
|
-
<!-- Tab activation
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
to the first tab within a second of any click. -->
|
|
1522
|
+
<!-- Tab activation: apply .active-tab to the section (inside
|
|
1523
|
+
#dash) AND to the matching subnav pill (outside #dash) whose
|
|
1524
|
+
id/hash matches the current URL hash. Runs on hashchange, on
|
|
1525
|
+
click into a tab link, and on every htmx:afterSettle so the
|
|
1526
|
+
class survives the 1 Hz innerHTML replacement of #dash.
|
|
1527
|
+
Subnav pills live outside #dash so the JS-applied class on
|
|
1528
|
+
them is not touched by the swap; sections inside #dash also
|
|
1529
|
+
get the class baked server-side (via X-Active-Tab) so there
|
|
1530
|
+
is no visible flicker between swap and JS re-apply. -->
|
|
1285
1531
|
<script>
|
|
1286
1532
|
(function () {{
|
|
1287
1533
|
function applyActiveTab() {{
|
|
@@ -1293,7 +1539,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1293
1539
|
sections.forEach(function (s) {{
|
|
1294
1540
|
s.classList.toggle('active-tab', s.id === hash);
|
|
1295
1541
|
}});
|
|
1296
|
-
document.querySelectorAll('
|
|
1542
|
+
document.querySelectorAll('.subnav-strip .nav-pills .nav-link').forEach(function (a) {{
|
|
1297
1543
|
var target = (a.getAttribute('href') || '').replace(/^#/, '');
|
|
1298
1544
|
a.classList.toggle('active-tab', target === hash);
|
|
1299
1545
|
}});
|
|
@@ -1301,7 +1547,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1301
1547
|
window.addEventListener('hashchange', applyActiveTab);
|
|
1302
1548
|
document.body.addEventListener('htmx:afterSettle', applyActiveTab);
|
|
1303
1549
|
document.addEventListener('click', function (ev) {{
|
|
1304
|
-
var a = ev.target.closest && ev.target.closest('
|
|
1550
|
+
var a = ev.target.closest && ev.target.closest('.subnav-strip .nav-pills .nav-link');
|
|
1305
1551
|
if (a) setTimeout(applyActiveTab, 0);
|
|
1306
1552
|
}});
|
|
1307
1553
|
applyActiveTab();
|
|
@@ -1317,7 +1563,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1317
1563
|
# 1 Hz refresh (and on every operator click via the same
|
|
1318
1564
|
# script that watches hashchange). Unknown / empty value
|
|
1319
1565
|
# defaults to the first tab.
|
|
1320
|
-
_TAB_IDS = ("tab-cached", "tab-streams", "tab-downloads", "tab-misses")
|
|
1566
|
+
_TAB_IDS = ("tab-cached", "tab-streams", "tab-downloads", "tab-misses", "tab-catalog")
|
|
1321
1567
|
if active_tab not in _TAB_IDS:
|
|
1322
1568
|
active_tab = _TAB_IDS[0]
|
|
1323
1569
|
|
|
@@ -1442,24 +1688,22 @@ class Handler(http.server.BaseHTTPRequestHandler):
|
|
|
1442
1688
|
njobs = len(jobs)
|
|
1443
1689
|
|
|
1444
1690
|
catalog_rows = self._catalog_rows()
|
|
1691
|
+
ncatalog = len(self.catalog.entries)
|
|
1445
1692
|
|
|
1693
|
+
# The per-tab count summary lives here (inside the htmx-swapped
|
|
1694
|
+
# region) so it updates every second. Tab labels themselves
|
|
1695
|
+
# live in the static sub-nav strip in ``render_page`` per bty
|
|
1696
|
+
# convention -- labels are chrome, counts are content.
|
|
1446
1697
|
return f"""
|
|
1447
|
-
<p class="text-muted small mb-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
href="#tab-downloads">Downloads ({njobs})</a></li>
|
|
1457
|
-
<li class="nav-item"><a class="nav-link {_active("tab-misses").lstrip()}"
|
|
1458
|
-
href="#tab-misses">Misses ({nmisses})</a></li>
|
|
1459
|
-
<li class="nav-item"><a class="nav-link {_active("tab-catalog").lstrip()}"
|
|
1460
|
-
href="#tab-catalog">Catalog ({len(self.catalog.entries)})</a></li>
|
|
1461
|
-
</ul>
|
|
1462
|
-
</nav>
|
|
1698
|
+
<p class="text-muted small mb-3">
|
|
1699
|
+
<span class="badge bg-primary bg-opacity-10 text-primary me-1">{nblobs} cached ({used}){
|
|
1700
|
+
full
|
|
1701
|
+
}</span>
|
|
1702
|
+
<span class="badge bg-secondary bg-opacity-10 text-secondary me-1">{nstreams} streams</span>
|
|
1703
|
+
<span class="badge bg-secondary bg-opacity-10 text-secondary me-1">{njobs} downloads</span>
|
|
1704
|
+
<span class="badge bg-secondary bg-opacity-10 text-secondary me-1">{nmisses} misses</span>
|
|
1705
|
+
<span class="badge bg-secondary bg-opacity-10 text-secondary">{ncatalog} catalog</span>
|
|
1706
|
+
</p>
|
|
1463
1707
|
|
|
1464
1708
|
<section id="tab-cached" class="tab{_active("tab-cached")}">
|
|
1465
1709
|
<div class="table-responsive"><table class="table table-sm table-striped table-hover mb-0">
|
|
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
|