-
-
-
-
-
- Save the model first, then upload a photo.
+
+
+
+
+
+ {{ pendingImageFile.name }} will be uploaded when you save.
+
+
- Manual alternative. Uploading a photo overwrites this URL.
+
+ Set automatically when you upload a photo. Accepts a full web address or a path on this server.
+
@@ -224,6 +241,8 @@ const perPage = ref(20)
const showModal = ref(false)
const editingModel = ref(null)
+// A photo chosen while CREATING a model, uploaded once the model has an id.
+const pendingImageFile = ref(null)
const saving = ref(false)
const error = ref('')
@@ -337,6 +356,9 @@ function openModal(m = null) {
function closeModal() {
showModal.value = false
editingModel.value = null
+ // Or a photo picked for a model that was never saved would be uploaded onto
+ // the NEXT model created in this session.
+ pendingImageFile.value = null
}
async function saveModel() {
@@ -351,7 +373,20 @@ async function saveModel() {
if (editingModel.value) {
await modelsApi.update(editingModel.value.modelnumberid, data)
} else {
- await modelsApi.create(data)
+ const created = await modelsApi.create(data)
+ // Send a photo chosen before the model existed. A failure here must not
+ // read as "the model was not saved" - it was.
+ if (pendingImageFile.value) {
+ const newId = created?.data?.data?.modelnumberid
+ if (newId) {
+ try {
+ await modelsApi.uploadImage(newId, pendingImageFile.value)
+ } catch (imgErr) {
+ console.error('Error uploading image:', imgErr)
+ toast.error(apiError(imgErr, 'Model saved, but the photo could not be uploaded'))
+ }
+ }
+ }
}
closeModal()
loadModels()
@@ -369,7 +404,14 @@ function triggerImageUpload() {
async function onImageSelected(event) {
const file = event.target.files?.[0]
- if (!file || !editingModel.value) return
+ if (!file) return
+ // No model id yet, so hold the file and send it once saveModel has created
+ // the record. Nothing is written to the server until the operator saves.
+ if (!editingModel.value) {
+ pendingImageFile.value = file
+ if (imageFileInput.value) imageFileInput.value.value = ''
+ return
+ }
uploadingImage.value = true
try {
const response = await modelsApi.uploadImage(editingModel.value.modelnumberid, file)
@@ -387,7 +429,12 @@ async function onImageSelected(event) {
}
async function removeImage() {
- if (!editingModel.value) return
+ // On a new model there is nothing on the server yet; just drop the choice.
+ if (!editingModel.value) {
+ pendingImageFile.value = null
+ form.value.imageurl = ''
+ return
+ }
if (!confirm('Remove this model image?')) return
try {
await modelsApi.removeImage(editingModel.value.modelnumberid)
diff --git a/plugins/network/api/routes.py b/plugins/network/api/routes.py
index 46516b3..3dfdc87 100644
--- a/plugins/network/api/routes.py
+++ b/plugins/network/api/routes.py
@@ -346,7 +346,7 @@ def create_network_device():
Optional fields:
- name, serialnumber, statusid, locationid, businessunitid
- - networkdevicetypeid, vendorid, hostname
+ - networkdevicetypeid, vendorid, modelnumberid, hostname
- firmwareversion, portcount, ispoe, ismanaged, rackunit
- mapx, mapy, notes
"""
@@ -408,6 +408,7 @@ def create_network_device():
assetid=asset.assetid,
networkdevicetypeid=data.get('networkdevicetypeid'),
vendorid=data.get('vendorid'),
+ modelnumberid=data.get('modelnumberid'),
hostname=data.get('hostname'),
firmwareversion=data.get('firmwareversion'),
portcount=data.get('portcount'),
@@ -495,7 +496,7 @@ def update_network_device(device_id: int):
setattr(asset, key, data[key])
# Update network device fields
- netdev_fields = ['networkdevicetypeid', 'vendorid', 'hostname',
+ netdev_fields = ['networkdevicetypeid', 'vendorid', 'modelnumberid', 'hostname',
'firmwareversion', 'portcount', 'ispoe', 'ismanaged', 'rackunit']
for key in netdev_fields:
if key in data:
diff --git a/plugins/network/frontend/views/NetworkDeviceForm.vue b/plugins/network/frontend/views/NetworkDeviceForm.vue
index 3488cca..605144b 100644
--- a/plugins/network/frontend/views/NetworkDeviceForm.vue
+++ b/plugins/network/frontend/views/NetworkDeviceForm.vue
@@ -133,6 +133,23 @@