Files
gandalf/templates/rack.html
jaredandClaude Opus 5.5 c50e1f8322
Lint / JS (eslint) (push) Successful in 7s
Lint / Python (flake8) (push) Successful in 49s
Lint / Notify on failure (push) Skipped
Lint / Deploy (push) Skipped
Security / Python Security (bandit) (push) Successful in 51s
Lint / Python (flake8) (pull_request) Successful in 55s
Test / Python Tests (pytest) (push) Successful in 1m4s
Lint / JS (eslint) (pull_request) Successful in 7s
Lint / Notify on failure (pull_request) Skipped
Lint / Deploy (pull_request) Skipped
Security / Python Security (bandit) (pull_request) Successful in 52s
Test / Python Tests (pytest) (pull_request) Successful in 55s
Rack: front and rear mounting, rack settings in the page
Items are mounted from the front, the rear, or full depth. On a 4-post
rack a front and a rear item can share a unit; a full-depth one takes
both sides, and the rear view shows its back. Overlaps are checked per
side. Shelf contents take the shelf's side. A Front / Rear switch flips
the drawing.

Racks are saved in Gandalf (new cutsheet_racks table) and edited on the
rack page by admins: name, height, notes, add and delete. Renaming moves
the rack's items; saving the first rack carries over the items of the
config default it replaces. Config racks remain the fallback.

Faceplates are stretched to the full width only when the drawing is
close to the slot's proportions, so the PDU is not squashed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-10 22:35:37 -05:00

89 lines
4.3 KiB
HTML

{% extends "base.html" %}
{% block title %}Rack{% endblock %}
{% block content %}
<div class="lt-page-header">
<div>
<h1 class="lt-page-title">Rack</h1>
<p class="g-page-sub">The rack front, drawn from the rack units on the cutsheet with live port state. Hover a port to light up every port on the same cable run; click it to open the port or the cable.</p>
</div>
<div class="cs-actions no-print">
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-export="svg">Download SVG</button>
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-export="png">Download PNG</button>
{% if can_edit %}
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-action="rack-settings">Rack settings</button>
<button type="button" class="lt-btn lt-btn-secondary lt-btn-sm" data-action="add-rack">+ Rack</button>
<a class="lt-btn lt-btn-secondary lt-btn-sm" href="/cutsheet#cs-items-section">Edit positions</a>
{% endif %}
</div>
</div>
<div class="rack-bar">
<div class="rack-tabs" id="rack-tabs" role="tablist" aria-label="Racks" hidden></div>
<div class="rack-face" role="group" aria-label="Rack side">
<button type="button" class="rack-face-btn active" data-face="front" aria-pressed="true">Front</button>
<button type="button" class="rack-face-btn" data-face="rear" aria-pressed="false">Rear</button>
</div>
</div>
<div class="rack-warnings" id="rack-warnings" role="alert" hidden></div>
<section class="g-section">
<div class="g-section-header">
<h2 class="g-section-title" id="rack-title">Front</h2>
<span class="g-section-badge" id="rack-count">0</span>
</div>
<p class="rack-readout" id="rack-readout" aria-live="polite"></p>
<div class="rack-legend" aria-hidden="true">
<span><i class="rack-key rack-key-ok"></i>matches cutsheet</span>
<span><i class="rack-key rack-key-bad"></i>mismatch</span>
<span><i class="rack-key rack-key-doc"></i>documented, unconfirmed</span>
<label class="rack-toggle"><input type="checkbox" id="rack-show-empty"> Show empty units</label>
</div>
<div class="rack" id="rack"><div class="link-loading">Loading rack</div></div>
<p class="cs-hint rack-unplaced" id="rack-unplaced"></p>
</section>
{% if can_edit %}
<div id="rack-modal" class="lt-modal-overlay" role="dialog" aria-modal="true" aria-labelledby="rack-modal-title" aria-hidden="true">
<div class="lt-modal">
<div class="lt-modal-header">
<h3 class="lt-modal-title" id="rack-modal-title">Rack settings</h3>
<button type="button" class="lt-modal-close" data-modal-close aria-label="Close">&#x2715;</button>
</div>
<form id="rack-form" autocomplete="off">
<div class="lt-modal-body">
<input type="hidden" name="id">
<input type="hidden" name="previous">
<div class="form-row">
<div class="lt-form-group">
<label class="lt-label" for="rack-name">Name <span class="required">*</span></label>
<input class="lt-input" id="rack-name" name="name" required maxlength="100" placeholder="e.g. StarTech 25U">
</div>
<div class="lt-form-group">
<label class="lt-label" for="rack-units">Height (U) <span class="required">*</span></label>
<input class="lt-input" id="rack-units" name="units" type="number" min="1" max="60" required>
</div>
</div>
<div class="lt-form-group">
<label class="lt-label" for="rack-notes">Notes</label>
<textarea class="lt-input" id="rack-notes" name="notes" rows="2" maxlength="2000" placeholder="model, location, depth"></textarea>
</div>
<p class="cs-hint">Renaming a rack moves its items with it. A rack can be deleted once it is empty.</p>
</div>
<div class="lt-modal-footer">
<button type="button" class="lt-btn lt-btn-danger" data-action="delete-rack">Delete</button>
<button type="button" class="lt-btn lt-btn-secondary" data-modal-close>Cancel</button>
<button type="submit" class="lt-btn lt-btn-primary">Save</button>
</div>
</form>
</div>
</div>
{% endif %}
{% endblock %}
{% block scripts %}
<script>const RACK_CAN_EDIT = {{ 'true' if can_edit else 'false' }};</script>
<script src="{{ url_for('static', filename='faceplates.js') }}"></script>
<script src="{{ url_for('static', filename='rack.js') }}"></script>
{% endblock %}