Files
vibetree/internal/web/router.go
T
Owen 14e2f3bb77 Bulk multi-select actions, barcode format, batch printing, site name on login
Multi-select (Ctrl/Cmd+click toggle, Shift+click range, right-click the
active selection for bulk actions - same conventions as a native file
manager):
- Print Labels (N) / Export CSV (N) for any mix of individually-picked
  assets and whole folders. Labels/export now take asset_ids/folder_ids
  instead of a single asset_id/folder_id.
- Edit Status..., Check Out..., Duplicate, Move to..., and Delete, bulk
  versions of the existing single-asset actions. Checkout/Move modals
  were generalized to accept an array of ids instead of just one; status
  editing got its own lightweight POST /app/assets/{id}/status endpoint
  (assets.SetStatus) so a bulk status change doesn't need to resend every
  other field the way a full PATCH would.

Labels:
- Choose QR (a /scan/<tag> URL, for a phone camera) or Code128 barcode
  (the bare Asset ID, for a USB scanner typing into the search box) via a
  format selector on the print page. New barcodeDataURI using
  github.com/boombuler/barcode alongside the existing QR generator.
- "Start at #" lets printing resume partway into an already-used sheet
  instead of wasting it on a fresh one.
- Added the Avery 5260 label template (same grid as 5160, rounded
  corners, so it isn't flagged approximate).

Also: the configured Site Name now shows on the login page too, not just
the authenticated navbar.
2026-08-17 17:12:17 -05:00

122 lines
6.5 KiB
Go

package web
import (
"net/http"
content "inventree/web"
)
// Router builds the complete HTTP handler for inventree: static assets,
// authentication, the AJAX-driven inventory tree, and the traditional
// full-page admin sections (item types, users, settings).
func (a *App) Router() http.Handler {
mux := http.NewServeMux()
// Static assets (Bootstrap, icons, vanilla JS) and health check are
// always public.
mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServerFS(mustSub(content.StaticFS, "static"))))
mux.HandleFunc("GET /healthz", a.handleHealthz)
// Auth
mux.HandleFunc("GET /login", a.handleLoginForm)
mux.HandleFunc("POST /login", a.handleLoginSubmit)
mux.HandleFunc("POST /logout", a.handleLogout)
mux.HandleFunc("GET /{$}", a.requireLogin(a.handleRoot))
// Inventory tree (AJAX fragments)
mux.HandleFunc("GET /inventory", a.requireLogin(a.handleInventoryPage))
mux.HandleFunc("GET /app/tree/children", a.requireLogin(a.handleTreeChildren))
mux.HandleFunc("POST /app/tree/collapse", a.requireLogin(a.handleTreeCollapse))
mux.HandleFunc("POST /app/tree/collapse-all", a.requireLogin(a.handleTreeCollapseAll))
mux.HandleFunc("GET /app/panel/root", a.requireLogin(a.handlePanelRoot))
mux.HandleFunc("GET /app/panel/folders/{id}", a.requireLogin(a.handlePanelFolder))
mux.HandleFunc("GET /app/panel/assets/{id}", a.requireLogin(a.handlePanelAsset))
mux.HandleFunc("GET /app/panel/assets/new", a.requireAdmin(a.handlePanelAssetNew))
mux.HandleFunc("GET /app/panel/assets/{id}/edit", a.requireAdmin(a.handlePanelAssetEdit))
mux.HandleFunc("GET /app/forms/asset-fields", a.requireLogin(a.handleAssetFieldsFragment))
mux.HandleFunc("GET /app/search", a.requireLogin(a.handleSearch))
// Folder mutations (JSON in/out)
mux.HandleFunc("GET /app/folders/options", a.requireAdmin(a.handleFolderOptions))
mux.HandleFunc("GET /app/folders/{id}", a.requireAdmin(a.handleFolderGet))
mux.HandleFunc("POST /app/folders", a.requireAdmin(a.handleFolderCreate))
mux.HandleFunc("PATCH /app/folders/{id}", a.requireAdmin(a.handleFolderUpdate))
mux.HandleFunc("POST /app/folders/{id}/move", a.requireAdmin(a.handleFolderMove))
mux.HandleFunc("GET /app/folders/{id}/delete-info", a.requireAdmin(a.handleFolderDeleteInfo))
mux.HandleFunc("DELETE /app/folders/{id}", a.requireAdmin(a.handleFolderDelete))
// Asset mutations (JSON in/out)
mux.HandleFunc("GET /app/assets/{id}", a.requireAdmin(a.handleAssetGet))
mux.HandleFunc("POST /app/assets", a.requireAdmin(a.handleAssetCreate))
mux.HandleFunc("PATCH /app/assets/{id}", a.requireAdmin(a.handleAssetUpdate))
mux.HandleFunc("DELETE /app/assets/{id}", a.requireAdmin(a.handleAssetDelete))
mux.HandleFunc("POST /app/assets/{id}/duplicate", a.requireAdmin(a.handleAssetDuplicate))
mux.HandleFunc("POST /app/assets/{id}/move", a.requireAdmin(a.handleAssetMove))
mux.HandleFunc("POST /app/assets/{id}/checkout", a.requireAdmin(a.handleAssetCheckout))
mux.HandleFunc("POST /app/assets/{id}/status", a.requireAdmin(a.handleAssetSetStatus))
mux.HandleFunc("POST /app/assets/{id}/quantity", a.requireAdmin(a.handleAssetQuantity))
mux.HandleFunc("GET /app/users/options", a.requireAdmin(a.handleUserOptions))
// CSV import/export
mux.HandleFunc("GET /export/assets.csv", a.requireLogin(a.handleExportCSV))
mux.HandleFunc("POST /import/assets.csv", a.requireAdmin(a.handleImportCSV))
// QR asset tags: scan a printed label to jump to that asset, or print
// a sheet of labels for a folder/asset.
mux.HandleFunc("GET /scan/{tag}", a.requireLogin(a.handleScan))
mux.HandleFunc("GET /labels", a.requireLogin(a.handleLabelsPage))
// Settings (admin, traditional pages) - Item Types, Users, and general
// settings all live under one unified /settings section with shared
// sub-navigation, rather than as separate top-level nav items.
mux.HandleFunc("GET /settings", a.requireAdmin(a.handleSettingsPage))
mux.HandleFunc("POST /settings/general", a.requireAdmin(a.handleSettingsGeneral))
mux.HandleFunc("GET /settings/statuses", a.requireAdmin(a.handleSettingsStatusesPage))
mux.HandleFunc("POST /settings/statuses", a.requireAdmin(a.handleStatusCreate))
mux.HandleFunc("POST /settings/statuses/{id}", a.requireAdmin(a.handleStatusUpdate))
mux.HandleFunc("POST /settings/statuses/{id}/delete", a.requireAdmin(a.handleStatusDelete))
mux.HandleFunc("POST /settings/statuses/{id}/reorder", a.requireAdmin(a.handleStatusReorder))
mux.HandleFunc("GET /settings/item-types", a.requireAdmin(a.handleItemTypesList))
mux.HandleFunc("GET /settings/item-types/new", a.requireAdmin(a.handleItemTypeNewForm))
mux.HandleFunc("POST /settings/item-types", a.requireAdmin(a.handleItemTypeCreate))
mux.HandleFunc("GET /settings/item-types/{id}/edit", a.requireAdmin(a.handleItemTypeEditForm))
mux.HandleFunc("POST /settings/item-types/{id}", a.requireAdmin(a.handleItemTypeUpdate))
mux.HandleFunc("POST /settings/item-types/{id}/delete", a.requireAdmin(a.handleItemTypeDelete))
mux.HandleFunc("POST /settings/item-types/{id}/fields", a.requireAdmin(a.handleFieldCreate))
mux.HandleFunc("POST /settings/item-types/{id}/fields/{fieldID}", a.requireAdmin(a.handleFieldUpdate))
mux.HandleFunc("POST /settings/item-types/{id}/fields/{fieldID}/delete", a.requireAdmin(a.handleFieldDelete))
mux.HandleFunc("GET /settings/import", a.requireAdmin(a.handleSettingsImportPage))
mux.HandleFunc("POST /settings/import/assets", a.requireAdmin(a.handleSettingsImportAssets))
mux.HandleFunc("POST /settings/import/statuses", a.requireAdmin(a.handleSettingsImportStatuses))
mux.HandleFunc("GET /settings/users", a.requireAdmin(a.handleUsersList))
mux.HandleFunc("GET /settings/users/new", a.requireAdmin(a.handleUserNewForm))
mux.HandleFunc("POST /settings/users", a.requireAdmin(a.handleUserCreate))
mux.HandleFunc("GET /settings/users/{id}/edit", a.requireAdmin(a.handleUserEditForm))
mux.HandleFunc("POST /settings/users/{id}", a.requireAdmin(a.handleUserUpdate))
mux.HandleFunc("POST /settings/users/{id}/password", a.requireAdmin(a.handleUserPassword))
mux.HandleFunc("POST /settings/users/{id}/delete", a.requireAdmin(a.handleUserDelete))
return a.loadSession(mux)
}
func (a *App) handleHealthz(w http.ResponseWriter, r *http.Request) {
if err := a.DB.PingContext(r.Context()); err != nil {
writeErr(w, http.StatusServiceUnavailable, "database unavailable")
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte("ok"))
}
func (a *App) handleRoot(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/inventory", http.StatusSeeOther)
}