library: same default ports as post-install (8097/8098/8099); borrower home shows loans with due info
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013wi3ZSot4XbUViwRFgLpRf
This commit is contained in:
+11
-4
@@ -12,9 +12,9 @@ it never overwrites your `.env` (passwords) or your data.
|
||||
|
||||
| What | Address | Login |
|
||||
|---|---|---|
|
||||
| **Your library**: add books, find a book, people, lending, labels, settings | `http://<this machine>:8081` | `admin` and the generated password |
|
||||
| **Add books** (scan an ISBN, or use the phone camera) | `http://<this machine>:8081/add/` | same login (asked once more) |
|
||||
| **Public catalogue** (search only, for a kiosk or visitors) | `http://<this machine>:8080` | none |
|
||||
| **Your library**: add books, find a book, people, lending, labels, settings | `http://<this machine>:8098` | `admin` and the generated password |
|
||||
| **Add books** (scan an ISBN, or use the phone camera) | `http://<this machine>:8098/add/` | same login (asked once more) |
|
||||
| **Public catalogue** (search only, for a kiosk or visitors) | `http://<this machine>:8097` | none |
|
||||
|
||||
For home use you only need the first address: sign in to **Your library**, and use the **Add books**
|
||||
button on its home page. The Add books page has a **Find a book** tab (title, author or ISBN, with the
|
||||
@@ -24,7 +24,7 @@ staff sites the same URL shapes, so they cannot share one address; Add books can
|
||||
|
||||
Ports move up to the next free number if one is taken; the final addresses are printed
|
||||
at the end and by `sudo ./install.sh --status`. Add books is also reachable directly on its own port
|
||||
(`8082`), which is handy behind a proxy but not needed otherwise.
|
||||
(`8099`), which is handy behind a proxy but not needed otherwise.
|
||||
|
||||
## What it is made of
|
||||
|
||||
@@ -290,3 +290,10 @@ points, quiz number and interest level fill in by themselves; check them and pre
|
||||
The book page's AR box has **Open beside this page** (a pop-up window next to the book) and **Show it here** (AR BookFind inside the
|
||||
page; some browsers block its cookies inside another page, then use the pop-up). Either copies the title first so you can paste
|
||||
it into AR's search; AR's own page cannot be pre-filled from here.
|
||||
|
||||
### Same ports on every install
|
||||
|
||||
New installs of this stack use the same three ports whether you install it from the kiosk add-on or from `ubuntu-post-install`:
|
||||
**8098** is the one to use (the Room for Books home page, with Koha behind the menu), 8099 is Add books direct, and 8097 is the
|
||||
public catalogue, which stays closed. If one is taken, the next free port is used and the installer prints it. Existing installs
|
||||
keep the ports they already have.
|
||||
|
||||
@@ -46,8 +46,8 @@ services:
|
||||
KOHA_ITEM_TYPES: ${ITEM_TYPES:-}
|
||||
ports:
|
||||
# The public catalogue is off by default (loopback only); set OPAC_BIND=0.0.0.0 in .env to expose it.
|
||||
- "${OPAC_BIND:-127.0.0.1}:${OPAC_PORT:-8080}:80"
|
||||
- "${BIND_ADDR:-0.0.0.0}:${STAFF_PORT:-8081}:8081"
|
||||
- "${OPAC_BIND:-127.0.0.1}:${OPAC_PORT:-8097}:80"
|
||||
- "${BIND_ADDR:-0.0.0.0}:${STAFF_PORT:-8098}:8081"
|
||||
volumes:
|
||||
- koha-data:/var/lib/koha
|
||||
|
||||
@@ -67,13 +67,13 @@ services:
|
||||
VIEWER_USER: ${VIEWER_USER:-guest}
|
||||
VIEWER_PASSWORD: ${VIEWER_PASSWORD:-}
|
||||
KOHA_LIBRARY: ${LIBRARY_CODE:-HOME}
|
||||
OPAC_PORT: ${OPAC_PORT:-8080}
|
||||
STAFF_PORT: ${STAFF_PORT:-8081}
|
||||
OPAC_PORT: ${OPAC_PORT:-8097}
|
||||
STAFF_PORT: ${STAFF_PORT:-8098}
|
||||
OPAC_URL: ${OPAC_URL:-}
|
||||
STAFF_URL: ${STAFF_URL:-}
|
||||
QUICKADD_SOURCES: ${QUICKADD_SOURCES:-z3950:lx2.loc.gov:210/LCDB,openlibrary}
|
||||
ports:
|
||||
- "${BIND_ADDR:-0.0.0.0}:${QUICKADD_PORT:-8082}:8082"
|
||||
- "${BIND_ADDR:-0.0.0.0}:${QUICKADD_PORT:-8099}:8082"
|
||||
volumes:
|
||||
- quickadd-data:/data
|
||||
|
||||
|
||||
+6
-6
@@ -11,7 +11,7 @@
|
||||
# LIBRARY_CODE=HOME short code for the library (letters/digits)
|
||||
# SHELVES="LR=Living room;BR=Bedroom" shelves/rooms to offer (first install only)
|
||||
# ITEM_TYPES="CD=Compact disc" extra item types (first install only)
|
||||
# OPAC_PORT=8080 STAFF_PORT=8081 preferred host ports (next free is used)
|
||||
# OPAC_PORT=8097 STAFF_PORT=8098 preferred host ports (next free is used)
|
||||
# EXTRA_CA=/path/to/proxy-ca.crt trust an extra CA while building
|
||||
set -euo pipefail
|
||||
|
||||
@@ -117,9 +117,9 @@ LIBRARY_CODE=${LIBRARY_CODE:-HOME}
|
||||
SHELVES="${SHELVES:-}"
|
||||
ITEM_TYPES="${ITEM_TYPES:-}"
|
||||
TZ=${TZ_NOW}
|
||||
OPAC_PORT=$(free_port "${OPAC_PORT:-8080}")
|
||||
STAFF_PORT=$(free_port "${STAFF_PORT:-8081}")
|
||||
QUICKADD_PORT=$(free_port "${QUICKADD_PORT:-8082}")
|
||||
OPAC_PORT=$(free_port "${OPAC_PORT:-8097}")
|
||||
STAFF_PORT=$(free_port "${STAFF_PORT:-8098}")
|
||||
QUICKADD_PORT=$(free_port "${QUICKADD_PORT:-8099}")
|
||||
BIND_ADDR=${BIND_ADDR:-0.0.0.0}
|
||||
# The public catalogue is off by default: 127.0.0.1 = not reachable from the network.
|
||||
OPAC_BIND=${OPAC_BIND:-127.0.0.1}
|
||||
@@ -139,7 +139,7 @@ else
|
||||
c_info "Re-using existing .env"
|
||||
# Settings added in later versions: append them to an older .env.
|
||||
if ! grep -q '^QUICKADD_PORT=' .env; then
|
||||
echo "QUICKADD_PORT=$(free_port 8082)" >> .env
|
||||
echo "QUICKADD_PORT=$(free_port 8099)" >> .env
|
||||
fi
|
||||
if ! grep -q '^VIEWER_PASSWORD=' .env; then
|
||||
{ echo "VIEWER_USER=guest"; echo "VIEWER_PASSWORD=$(rand 14)"; } >> .env
|
||||
@@ -170,7 +170,7 @@ with_retry dc up -d
|
||||
c_info "First start installs the database - waiting until Koha reports healthy..."
|
||||
for i in $(seq 1 180); do
|
||||
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}' library-koha 2>/dev/null || echo missing)"
|
||||
if [ "$health" = healthy ] && curl -fsS -o /dev/null -m 5 "http://127.0.0.1:${QUICKADD_PORT:-8082}/healthz" 2>/dev/null; then READY=1; break; fi
|
||||
if [ "$health" = healthy ] && curl -fsS -o /dev/null -m 5 "http://127.0.0.1:${QUICKADD_PORT:-8099}/healthz" 2>/dev/null; then READY=1; break; fi
|
||||
# One restart is tolerated (Docker's restart policy); a crash loop is not.
|
||||
if [ "$(docker inspect -f '{{.RestartCount}}' library-koha 2>/dev/null || echo 99)" -ge 3 ]; then
|
||||
c_err "library-koha keeps crashing - see: docker logs library-koha"; exit 1
|
||||
|
||||
Binary file not shown.
@@ -985,7 +985,8 @@ def _loan_rows(cos):
|
||||
due = (c.get("due_date") or "")[:10]
|
||||
rows.append({"checkout_id": c["checkout_id"], "patron_id": c["patron_id"], "barcode": it.get("external_id") or "",
|
||||
"biblio_id": it.get("biblio_id"), "title": t[0], "author": t[1], "isbn": t[2], "due": due,
|
||||
"overdue": bool(due) and due < today, "renewals": c.get("renewals_count") or 0})
|
||||
"overdue": bool(due) and due < today, "renewals": c.get("renewals_count") or 0,
|
||||
"checkout_date": (c.get("checkout_date") or "")[:10]})
|
||||
return rows
|
||||
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@
|
||||
<button class="primary" type="submit">Look up</button>
|
||||
</form>
|
||||
<div id="homeresults" aria-live="polite"></div>
|
||||
<div id="homemine" hidden></div>
|
||||
<div class="bigacts" id="homeacts">
|
||||
<a class="bigact" href="#lend"><strong>Check out</strong><span>Scan a card, then the books</span></a>
|
||||
<a class="bigact" href="#return"><strong>Return</strong><span>Scan the books coming back</span></a>
|
||||
@@ -1243,21 +1244,37 @@
|
||||
};
|
||||
$('printcard').onclick = () => window.print();
|
||||
|
||||
async function loadMine() {
|
||||
let r; try { r = await api('/api/me'); } catch (e) { r = {}; }
|
||||
if (!r.loans) { $('meloans').textContent = r.message || 'Could not load your books.'; return; }
|
||||
$('mename').textContent = r.name; $('mecard').textContent = r.card;
|
||||
// A borrower's loans: title, who wrote it, when it was borrowed, when it is due (days left / overdue), renewals, Renew.
|
||||
function renderLoans(target, r, reload) {
|
||||
const today = new Date(); today.setHours(12, 0, 0, 0);
|
||||
const rows = r.loans.map(l => {
|
||||
const days = Math.round((new Date(l.due + 'T12:00:00') - today) / 86400000);
|
||||
const when = l.overdue ? 'Overdue by ' + (-days) + (days === -1 ? ' day' : ' days') : days === 0 ? 'Due today' : 'Due ' + fmtDue(l.due) + ' (in ' + days + (days === 1 ? ' day)' : ' days)');
|
||||
const ren = el('button', { type: 'button' }, 'Renew');
|
||||
ren.onclick = async () => { ren.disabled = true; const x = await api('/api/me/renew', { checkout_id: l.checkout_id });
|
||||
if (x.status === 'renewed') loadMine(); else { ren.disabled = false; alert(x.message || 'That one cannot be renewed.'); } };
|
||||
return el('div', { className: 'loan' }, el('span', { className: 't' }, el('a', { href: '#book/' + l.biblio_id }, l.title), el('div', { className: 'small' }, l.author)),
|
||||
el('span', { className: 'chip ' + (l.overdue ? 'late' : 'in') }, when), ren);
|
||||
if (x.status === 'renewed') reload(); else { ren.disabled = false; alert(x.message || 'That one cannot be renewed (the limit is reached, or someone is waiting for it).'); } };
|
||||
const facts = ['Borrowed ' + fmtDue(l.checkout_date), l.renewals ? 'renewed ' + (l.renewals === 1 ? 'once' : l.renewals + ' times') : ''].filter(Boolean).join(' · ');
|
||||
return el('div', { className: 'loan' }, el('span', { className: 't' }, el('a', { href: '#book/' + l.biblio_id }, l.title),
|
||||
el('div', { className: 'small' }, [l.author, facts].filter(Boolean).join(' · '))),
|
||||
el('span', { className: 'chip ' + (l.overdue ? 'late' : days <= 3 ? 'out' : 'in') }, when), ren);
|
||||
});
|
||||
$('meloans').replaceChildren(...(rows.length ? rows : [el('div', { className: 'card ok' }, el('div', { className: 'body' }, 'You have nothing checked out right now.'))]));
|
||||
const late = r.loans.filter(l => l.overdue).length;
|
||||
const head = r.loans.length ? 'You have ' + r.loans.length + (r.loans.length === 1 ? ' book' : ' books') + ' out' + (late ? ', ' + late + ' overdue' : '') + '.' : 'You have nothing checked out right now.';
|
||||
target.replaceChildren(el('div', { className: 'card ' + (late ? 'warn' : 'ok') }, el('div', { className: 'body' }, el('strong', {}, head))), ...rows);
|
||||
}
|
||||
async function loadMine() {
|
||||
let r; try { r = await api('/api/me'); } catch (e) { r = {}; }
|
||||
if (!r.loans) { $('meloans').textContent = r.message || 'Could not load your books.'; return; }
|
||||
$('mename').textContent = r.name; $('mecard').textContent = r.card;
|
||||
renderLoans($('meloans'), r, loadMine);
|
||||
}
|
||||
async function loadHomeMine() {
|
||||
let r; try { r = await api('/api/me'); } catch (e) { return; }
|
||||
if (!r.loans) return;
|
||||
const box = $('homemine'); box.hidden = false;
|
||||
const inner = el('div', {});
|
||||
box.replaceChildren(el('h2', {}, 'Hello, ' + r.name), inner);
|
||||
renderLoans(inner, r, loadHomeMine);
|
||||
}
|
||||
$('mepwform').onsubmit = async ev => {
|
||||
ev.preventDefault(); const f = ev.target; const r = await api('/api/me/password', Object.fromEntries(new FormData(f)));
|
||||
@@ -1324,7 +1341,7 @@
|
||||
const names = { home: 'Home', library: 'Browse', book: 'Book', add: 'Add books', lend: 'Lend / return', people: 'People', card: 'Library card', cards: 'Library cards', mine: 'My books' };
|
||||
document.title = (view === 'home' ? '' : names[view] + ' - ') + 'Room for Books';
|
||||
window.scrollTo(0, 0);
|
||||
if (view === 'home') { $('homeacts').hidden = viewer; $('patronacts').hidden = !isPatron; loadRecent(); if (h === 'find') $('hq').focus(); }
|
||||
if (view === 'home') { $('homeacts').hidden = viewer; $('patronacts').hidden = !isPatron; $('homemine').hidden = !isPatron; if (isPatron) loadHomeMine(); loadRecent(); if (h === 'find') $('hq').focus(); }
|
||||
else if (view === 'book') loadBook(m[1]);
|
||||
else if (view === 'mine') loadMine();
|
||||
else if (view === 'add') { if (h === 'settings') { $('settings').open = true; $('settings').scrollIntoView(); } else $('code').focus(); }
|
||||
|
||||
Reference in New Issue
Block a user