Implement async manual CSV ingest, unified UI pagination/filters, and serial placeholder strategy

This commit is contained in:
2026-02-21 22:14:04 +03:00
parent ca762a658b
commit c84102d2f1
44 changed files with 3314 additions and 342 deletions

View File

@@ -8,11 +8,30 @@
<main class="container">
<section class="card">
<h2>Component Card</h2>
<div class="button-row" style="justify-content: space-between; margin-bottom: 16px;">
<h2 style="margin: 0;">Component Card</h2>
<div class="button-row">
<button class="button" id="component-edit-toggle" type="button">Edit</button>
<button class="button button-secondary" id="component-edit-cancel" type="button" hidden>Cancel</button>
</div>
</div>
<div class="meta" id="component-edit-message" style="margin-bottom: 12px;"></div>
<div class="meta-grid">
<div><span>Vendor Serial</span>{{.Component.VendorSerial}}</div>
<div><span>Vendor</span>{{if .Component.Vendor}}{{.Component.Vendor}}{{else}}—{{end}}</div>
<div><span>Model</span>{{if .Component.Model}}{{.Component.Model}}{{else}}—{{end}}</div>
<div>
<span>Vendor Serial</span>
<div class="field-value">{{.Component.VendorSerial}}</div>
<input class="input field-input" id="component-vendor-serial-input" value="{{.Component.VendorSerial}}" hidden />
</div>
<div>
<span>Vendor</span>
<div class="field-value">{{if .Component.Vendor}}{{.Component.Vendor}}{{else}}—{{end}}</div>
<input class="input field-input" id="component-vendor-input" value="{{if .Component.Vendor}}{{.Component.Vendor}}{{end}}" hidden />
</div>
<div>
<span>Model</span>
<div class="field-value">{{if .Component.Model}}{{.Component.Model}}{{else}}—{{end}}</div>
<input class="input field-input" id="component-model-input" value="{{if .Component.Model}}{{.Component.Model}}{{end}}" hidden />
</div>
<div><span>Status</span><span class="badge {{componentStatusClass .ComponentStatus}}">{{componentStatusText .ComponentStatus}}</span></div>
<div><span>Asset</span>{{if .CurrentAssetID}}<a href="/ui/assets/{{.CurrentAssetID}}">{{.CurrentAssetLabel}}</a>{{else}}—{{end}}</div>
<div><span>Firmware</span>{{if .ComponentFirmware}}{{.ComponentFirmware}}{{else}}—{{end}}</div>
@@ -74,6 +93,95 @@
{{end}}
</section>
</main>
<script>
const componentEditToggle = document.getElementById('component-edit-toggle');
const componentEditCancel = document.getElementById('component-edit-cancel');
const componentEditMessage = document.getElementById('component-edit-message');
const componentFieldValues = [...document.querySelectorAll('.field-value')];
const componentFieldInputs = [...document.querySelectorAll('.field-input')];
const componentInputs = {
vendorSerial: document.getElementById('component-vendor-serial-input'),
vendor: document.getElementById('component-vendor-input'),
model: document.getElementById('component-model-input')
};
const initialComponentForm = {
vendorSerial: componentInputs.vendorSerial ? componentInputs.vendorSerial.value : '',
vendor: componentInputs.vendor ? componentInputs.vendor.value : '',
model: componentInputs.model ? componentInputs.model.value : ''
};
let componentEditMode = false;
function setComponentMessage(message) {
if (componentEditMessage) {
componentEditMessage.textContent = message;
}
}
function resetComponentForm() {
if (componentInputs.vendorSerial) componentInputs.vendorSerial.value = initialComponentForm.vendorSerial;
if (componentInputs.vendor) componentInputs.vendor.value = initialComponentForm.vendor;
if (componentInputs.model) componentInputs.model.value = initialComponentForm.model;
}
function setComponentEditMode(enabled) {
componentEditMode = enabled;
componentFieldValues.forEach((element) => {
element.hidden = enabled;
});
componentFieldInputs.forEach((element) => {
element.hidden = !enabled;
});
if (componentEditToggle) {
componentEditToggle.textContent = enabled ? 'Save' : 'Edit';
}
if (componentEditCancel) {
componentEditCancel.hidden = !enabled;
}
if (!enabled) {
setComponentMessage('');
}
}
async function saveComponent() {
const payload = {
vendor_serial: (componentInputs.vendorSerial ? componentInputs.vendorSerial.value : '').trim(),
vendor: (componentInputs.vendor ? componentInputs.vendor.value : '').trim(),
model: (componentInputs.model ? componentInputs.model.value : '').trim()
};
if (!payload.vendor_serial) {
setComponentMessage('Vendor serial is required.');
return;
}
const response = await fetch('/registry/components/{{.Component.ID}}', {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(payload)
});
if (!response.ok) {
const body = await response.json().catch(() => ({error: 'Request failed'}));
setComponentMessage(body.error || 'Update component failed');
return;
}
window.location.reload();
}
if (componentEditToggle) {
componentEditToggle.addEventListener('click', async () => {
if (!componentEditMode) {
setComponentEditMode(true);
return;
}
await saveComponent();
});
}
if (componentEditCancel) {
componentEditCancel.addEventListener('click', () => {
resetComponentForm();
setComponentEditMode(false);
});
}
</script>
</body>
</html>
{{end}}