Skip to content

Add Equipment Module: DHEquipment service, DHEquipmentPortal, Discord bot, and Admin integration#271

Open
tmnyhbs wants to merge 15 commits into
pumpingstationone:daves-equipment-branchfrom
tmnyhbs:equipment-module
Open

Add Equipment Module: DHEquipment service, DHEquipmentPortal, Discord bot, and Admin integration#271
tmnyhbs wants to merge 15 commits into
pumpingstationone:daves-equipment-branchfrom
tmnyhbs:equipment-module

Conversation

@tmnyhbs
Copy link
Copy Markdown

@tmnyhbs tmnyhbs commented May 12, 2026

Summary

This PR adds a full equipment management module to Deep Harbor, built and tested against dev (rebased as of 2026-05-12). It introduces three new components and extends the existing Admin Portal:

  • DHEquipment (code/services/DHEquipment/) — FastAPI service with its own PostgreSQL schema, OAuth2 client-credentials auth, and a notifications/webhook dispatch engine
  • DHEquipmentPortal (code/DHEquipmentPortal/) — Flask SPA portal (similar in structure to DHAdminPortal/DHMemberPortal) for day-to-day equipment management
  • DHDiscordBot (code/external/DHDiscordBot/) — Discord bot that surfaces equipment events and ticket updates into channels
  • Admin Portal — Equipment Module panel — new section in DHAdminPortal for managing equipment configuration (notifications, module toggles) without leaving the admin UI; Settings in DHEquipmentPortal links out to this panel

What the Equipment Module covers

  • Equipment inventory — items, make/model, serial, status, location photos, electrical specs, attachments
  • Areas — physical zones equipment belongs to, with photos
  • Repair Tickets — open/in-progress/on-hold/closed lifecycle, priority, work log, attachments
  • Groups — logical collections of equipment with member enrollments
  • Maintenance — recurring schedules and event log
  • Scheduling — calendar-based equipment reservations
  • Authorization Sessions — tracked member authorization sign-offs per equipment item
  • Notifications — email and webhook (including Discord rich embeds) per-event delivery, configurable from Admin Portal
  • Module toggles — Maintenance, Scheduling, and Authorizations modules can be individually disabled from Admin Portal; DHEquipmentPortal hides disabled sections on load

Networking

Follows the existing docker-compose.dev.yml pattern: production services use network_mode: host; the dev overlay resets all new services to bridge networking and routes inter-container calls through http://gateway/dh/equipment. No changes to existing service networking.

Database

  • pg/sql/migrate_equipment.sql — migration script for adding the equipment schema to an existing database
  • pg/db-init/03-seed_equipment.sql — template seed file (placeholder rows, not pre-filled) for dev bootstrapping
  • New tables: areas, equipment, equipment_groups, equipment_group_members, repair_tickets, ticket_work_log, maintenance_schedules, maintenance_events, equipment_schedules, equip_auth_sessions, equip_auth_enrollments, equipment_config, equipment_attachments
  • New oauth2_users entries for dev-equipment-portal and dev-discord-bot

Test plan

  • docker compose -f docker-compose.yaml -f docker-compose.dev.yml up --build -d starts cleanly with no errors
  • DHEquipmentPortal loads at http://localhost:5003, dev login works
  • Dashboard shows area breakdown and open tickets table
  • Equipment, Tickets, Groups, Areas, Maintenance, Scheduling, Authorizations sections all load
  • Photo and attachment upload/download works (requires RustFS running)
  • DHAdminPortal → Equipment nav item opens Equipment Module panel
  • Notifications tab: email config saves; webhook add/test/save works; Discord webhook sends a test embed
  • Module Toggles tab: toggling Maintenance off hides it in DHEquipmentPortal sidebar on reload
  • DHEquipmentPortal Settings page shows redirect card linking to Admin Portal
  • Existing admin portal tabs (Identity, Status, Roles, Onboard, etc.) unaffected
  • Existing member portal unaffected

tmnyhbs and others added 15 commits May 12, 2026 11:01
- Port equipment list, detail, add/edit modal, and CRUD JS from PA1
  into DHEquipmentPortal/templates/index.html (loadEquipment,
  renderEquipGrid, openEquipDetail, openEquipModal, renderAttrEditor,
  saveEquipment, deleteEquipment, location image helpers)
- Add equipment section HTML: toolbar, area filter, status filters,
  equip grid, detail offcanvas, add/edit modal with attr editor
- Fix bridge networking for all three portals in docker-compose.yaml
  (remove network_mode: host, add dh_network + DH_API_BASE_URL)
- Add dhequipmentportal entry to docker-compose.dev.yml with dev
  auth env vars and DH_EQUIP_API_BASE_URL
- Add CLAUDE.md and .gitignore for DHEquipmentPortal
- Remove config.ini.example files replaced by env-var configuration
- Add uv.lock files for DHEquipmentPortal, DHDiscordBot, DHEquipment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Areas section fully ported: list table, detail offcanvas, add/edit modal
  with all metadata fields (website, host_name, host_contact, email, discord)
- Add .detail-section-label and .gitignore *.ini to complete the section
- Wire AUTH_MODE env var into dhequipmentportal in docker-compose.yaml
  so dev mode can be enabled via .env without touching the compose file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Repair Tickets: list table with multi-select status/priority filters,
  create/edit modal with work log timeline and add-entry form, optimistic
  locking via version field, all CRUD wired to /api/tickets[/{id}[/worklog]]
- Equipment Groups: collapsible group cards, create/edit modal with
  multi-select equipment picker, CRUD wired to /api/equipment-groups
- Lazy-load allEquipment in ticket and group modals so pickers work
  even if the Equipment section hasn't been visited first
- Fix CSP font-src to include cdn.jsdelivr.net so Bootstrap Icons
  woff2 fonts load (fixes squares rendering in place of icons)
- Add ticket/badge/worklog/group-card CSS using theme custom properties
- Fix groups section using canChange('equipment.groups') instead of
  PA1's can('groups.manage')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ortal

- Shared calendar engine: calRenderDayWeek, calRenderMonth, nav label helpers
- Scheduling: day/week/month/list view toggle, equipment filter, Book Time modal,
  15-min minimum / 24-hour maximum duration validation, /api/schedules integration
- Authorization sessions: tiles/list/calendar views, filter bar (upcoming/past/mine),
  enroll/unenroll via /api/auth-sessions/{id}/enroll, create/edit/delete modal,
  enrollment list with member names in detail modal
- Calendar CSS: grid, event chips, month cells, now-line, session-card badges
- All currentUser refs replaced with MEMBER_ID; API paths prefixed with /api/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Section UI: day/week/month/list/schedules view toggle with cal nav bar,
  status legend badges, summary stats area, and permission-gated New Schedule button
- Schedules tab: lists all maintenance schedules with recurrence info, priority badge,
  edit/delete per-row; lazy-loads equipment and groups if not yet fetched
- Events list/calendar: maps due_date to calendar slot, colors events by status
  (pending=yellow, in_progress=cyan, overdue=red, completed=green, skipped=grey),
  patches inline styles after calRenderDayWeek since shared engine uses classes
- Event detail modal: shows status, due date, equipment, priority, estimated time,
  recurrence description, linked ticket (clickable to open ticket detail), completion
  notes textarea; Start/Complete/Skip action buttons gated by canChange
- Checklist support: renders checklist_items as checkboxes, toggles checklist_state
  via PATCH /api/maintenance/events/{id} without closing the modal
- Schedule modal: title, description, target type toggle (equipment/group), recurrence
  interval + type, priority, estimated minutes; populates selects lazily
- API paths: /api/maintenance/events, /api/maintenance/schedules (no summary endpoint)
- can('maintenance.manage') added to bridge → canChange('equipment.maintenance')
- IDs are integers; parseInt used throughout for ID comparisons and payloads

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replaces the stub settingsContent div with a two-tab interface gated on
canChange('equipment.config'). Notifications tab has three sub-tabs:
Channels (email/push SMTP/VAPID config), Events (per-event channel matrix
for 8 equipment-specific events), and Webhooks (generic + Discord, with
event filter, enable toggle, and HMAC secret). All three sub-tabs read/write
via GET/PUT /api/config/notifications. Export tab renders a card grid for
six entities (equipment, areas, tickets, groups, schedules,
maintenance_schedules) and downloads client-side JSON→CSV with a timestamped
filename. PA1's import, JSON snapshot, role routing, and all non-equipment
panels are intentionally excluded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add POST /v1/equipment/upload endpoint to DHEquipment service; stores
  images in RustFS (S3-compatible) with auto bucket creation and
  public-read bucket policy on first use
- Add GET /v1/equipment/media/{key} endpoint to proxy stored images
  back through the service layer
- Add POST /api/upload and GET /api/media/<key> proxy routes to the
  equipment portal; /api/media serves images same-origin so they render
  correctly when the portal is accessed through a reverse proxy/tunnel
- Fix CSRF error handler to return JSON 400 for requests carrying
  X-CSRFToken header (multipart uploads were getting 302 redirects)
- Gitignore CLAUDE.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Upload endpoint now accepts images, documents (PDF/Word/Excel/PPT/CSV),
  and video up to 250 MB (was image-only at 10 MB)
- Added DELETE /v1/equipment/media/{key} endpoint to remove files from RustFS
- Added GET /api/areas/<id> proxy route (was missing)
- Added DELETE /api/media/<key> proxy route in Flask portal
- Upload proxy timeout raised to 300s; media proxy to 60s
- Flask MAX_CONTENT_LENGTH set to 260 MB; nginx client_max_body_size raised to 260m
- Attachment sections added to equipment, ticket, area, and group modals
- Shared JS attachment module: upload, render, remove (with S3 deletion)
- Schema: attachments JSONB DEFAULT '[]' added to areas and equipment_groups
- Migration: pg/sql/migrate_attachments.sql for existing databases
- db.py: update_area and update_equipment_group now handle attachments column
- Administrator and SuperAdmin roles updated with full equipment.* permissions
- Fixed invalid bcrypt placeholder hashes for dev-equipment-portal and dev-discord-bot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- notifications.py: New webhook dispatcher that fires on equipment events
  (ticket opened/closed/status changed, equipment status changed, maintenance
  started/completed/overdue). Reads config from equipment_config table, respects
  per-event webhook toggle, supports Discord embeds and generic signed JSON.
  Fixes Cloudflare 1010 block with DiscordBot User-Agent.

- Webhook settings UI: Discord message style editor (content, title prefix,
  embed color picker with per-event defaults, footer, description template
  with {field} variable substitution). Real server-side test send via
  /api/webhook-test with style preview.

- Electrical fields: Voltage, amperage, phase, plug type, notes, and circuit
  breaker (panel, breaker #, location, notes) added to equipment add/edit modal.
  Detail panel shows Electrical section when data is present. Equipment cards
  show a compact ⚡ badge with voltage/amperage/panel summary.

- nginx.conf: Fix persistent 500s after container restarts. Switched all
  proxy_pass directives to use variables with explicit rewrites, forcing
  per-request DNS re-resolution via Docker's resolver (127.0.0.11, TTL 5s).
  Removed upstream blocks that cached IPs at startup.

- dhservices.py: Token fetch retries once on 502/503/504 and connection errors
  to absorb transient gateway unavailability during container startup races.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move notifications config from DHEquipmentPortal settings into a new
  Equipment Module section in DHAdminPortal (Notifications + Module
  Toggles tabs); DHAdmin proxies to DHEquipment API with its own token
- Add module toggle switches in Equipment Module panel to
  enable/disable Maintenance, Scheduling, and Authorizations modules;
  DHEquipmentPortal hides disabled module nav items on load
- Add open tickets table to DHEquipmentPortal dashboard (status:
  open/in_progress/on_hold) with priority badges and click-through
- Remove export tab, routes, and JS from DHEquipmentPortal
- Add 03-seed_equipment.sql template for areas, equipment, and groups
- Wire DHEquipment credentials into DHAdminPortal via config.ini,
  config.py, and docker-compose env vars (dev uses dev-equipment-portal)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Settings in DHEquipmentPortal now shows a redirect card pointing to
DHAdminPortal's Equipment Module panel instead of rendering the
notifications config locally. DH_ADMIN_BASE_URL env var controls
the link target (defaults to http://localhost:5001).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove push notification channel (VAPID config) from both portals
- Merge Webhooks tab content into the Channels tab; webhooks render
  below email config separated by a divider, no separate tab needed
- Remove push column from Events matrix; email and webhook remain
- _renderNotifWebhooks prefers notif-webhooks-container so add/remove
  webhook actions re-render in place within the Channels tab

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant