Add support for hiding properties in asset overview and enhance input list handling

This commit is contained in:
2025-06-08 14:39:25 +02:00
parent afd36bcb0c
commit a490b2a306
8 changed files with 89 additions and 5 deletions

View File

@@ -35,6 +35,8 @@ public class WebController {
private static final String ACTION = "action";
/// The name of the model attribute that holds the current time in milliseconds.
private static final String TIME = "time";
/// The name of the model attribute that holds the input lists for creating or editing assets.
private static final String INPUT_LIST = "inputLists";
private final AssetService assetService;
@@ -157,6 +159,7 @@ public class WebController {
model.addAttribute(ACTION, "create");
model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(type));
model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(type));
model.addAttribute(INPUT_LIST, assetService.getInputList(type));
return "create_asset";
}
@@ -177,6 +180,7 @@ public class WebController {
model.addAttribute(ASSET, asset);
model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(assetType));
model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(assetType));
model.addAttribute(INPUT_LIST, assetService.getInputList(assetType));
return "create_asset";
}

View File

@@ -43,6 +43,8 @@ public class AssetProperty {
private final Function<Object, Object> getter;
/// Whether the property is an input list.
private final boolean inputList;
/// Whether the property should be hidden in the overview.
private final boolean hideInOverview;
/**
* Enum representing the possible types of asset properties.
@@ -87,6 +89,7 @@ public class AssetProperty {
.type(type)
.required(annotation.required())
.inputList(property.isAnnotationPresent(InputList.class))
.hideInOverview(property.isAnnotationPresent(HideInOverview.class))
.setter((obj, value) -> {
try {
property.setAccessible(true);

View File

@@ -0,0 +1,14 @@
package be.seeseepuff.pcinv.meta;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Indicates that the field should not be included in the overview of an asset.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface HideInOverview {
}

View File

@@ -2,6 +2,7 @@ package be.seeseepuff.pcinv.models;
import be.seeseepuff.pcinv.meta.AssetInfo;
import be.seeseepuff.pcinv.meta.InputList;
import be.seeseepuff.pcinv.meta.HideInOverview;
import be.seeseepuff.pcinv.meta.Property;
import jakarta.persistence.*;
import lombok.Getter;
@@ -52,6 +53,7 @@ public class GenericAsset
/// A description of the asset, providing additional details.
@Property("Description")
@HideInOverview
private String description;
/// The state of the asset, indicating its condition.

View File

@@ -2,6 +2,7 @@ package be.seeseepuff.pcinv.models;
import be.seeseepuff.pcinv.meta.AssetInfo;
import be.seeseepuff.pcinv.meta.Capacity;
import be.seeseepuff.pcinv.meta.InputList;
import be.seeseepuff.pcinv.meta.Property;
import jakarta.persistence.*;
import lombok.Getter;
@@ -36,9 +37,11 @@ public class HddAsset implements Asset
/// The drive's interface type, such as SATA, IDE, ISA-16, ...
@Property("Interface Type")
@InputList
private String interfaceType;
/// The drive's form factor, such as 2.5", 3.5", etc.
@Property("Form Factor")
@InputList
private String formFactor;
}

View File

@@ -12,9 +12,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* Service for managing assets in the repository.
@@ -173,6 +171,12 @@ public class AssetService {
private void fillIn(Object asset, AssetDescriptor assetDescriptor, Map<String, String> formData) {
for (var property : assetDescriptor.getProperties()) {
var value = parseValue(assetDescriptor, property, formData);
if (property.isInputList()) {
var selectedItem = formData.get(assetDescriptor.asString(property) + "-list");
if (selectedItem != null && !selectedItem.isBlank() && !selectedItem.equals("__new__")) {
value = selectedItem;
}
}
if (value == null && property.isRequired()) {
throw new IllegalArgumentException("Property '" + property.getName() + "' is required but not provided.");
}
@@ -237,4 +241,50 @@ public class AssetService {
genericRepository.delete(genericAsset);
genericRepository.flush();
}
/**
* Retrieves the input list mapping for a specific asset type.
*
* @param type the type of asset to retrieve the input list for
* @return a map of input names to their corresponding list
*/
public Map<String, Set<String>> getInputList(String type) {
var map = new HashMap<String, Set<String>>();
var tree = getAssetDescriptorTree(type);
for (var descriptor : tree) {
for (var property : descriptor.getProperties()) {
if (property.isInputList()) {
var inputList = getInputList(descriptor, property);
map.put(descriptor.asString(property), inputList);
}
}
}
return map;
}
/**
* Retrieves the input list for a specific asset descriptor and property.
*
* @param descriptor the asset descriptor containing the property
* @param property the asset property to retrieve the input list for
* @return a set of input values for the specified property
*/
private Set<String> getInputList(AssetDescriptor descriptor, AssetProperty property) {
List<?> entries;
if (descriptor.getType().equals(GenericAsset.TYPE)) {
entries = genericRepository.findAll();
} else {
var repository = getRepositoryFor(descriptor.getType());
entries = repository.findAll();
}
Set<String> inputList = new TreeSet<>();
for (var entry : entries) {
var value = property.getValue(entry);
if (value != null) {
inputList.add(value.toString());
}
}
return inputList;
}
}

View File

@@ -3,11 +3,11 @@
There are <span th:text="${assets.size()}"></span> <span th:text="${descriptor.pluralName}"></span> in the database.
<table border="1" cellpadding="4">
<tr bgcolor="#d3d3d3">
<th th:each="p : ${properties}" th:text="${p.displayName}"></th>
<th th:each="p : ${properties}" th:if="${!p.hideInOverview}" th:text="${p.displayName}"></th>
<th>Actions</th>
</tr>
<tr th:each="a : ${assets}">
<td th:each="p : ${properties}">
<td th:each="p : ${properties}" th:if="${!p.hideInOverview}">
<a th:if="${p.name == 'qr'}" th:href="'/view/'+${a.getQr()}" th:text="${p.renderValue(a)}"></a>
<span th:if="${p.name != 'qr'}" th:text="${p.renderValue(a)}"></span>
</td>

View File

@@ -8,6 +8,14 @@
<tr th:each="p : ${d.getProperties()}">
<td bgcolor="#d3d3d3"><b><label th:text="${p.displayName}" th:for="${d.asString(p)}"></label></b></td>
<td th:switch="${p.type.nameOrEnum()}">
<span th:case="STRING">
<select th:if="${p.inputList}" th:id="${d.asString(p)+'-list'}" th:name="${d.asString(p)+'-list'}">
<option value="__new__">New...</option>
<option th:each="e : ${inputLists.get(d.asString(p))}" th:text="${e}" th:value="${e}"></option>
</select>
<span th:if="${p.inputList}"> or </span>
<input type="text" th:id="${d.asString(p)}" th:name="${d.asString(p)}" th:value="${p.getValue(asset)}" th:placeholder="${p.displayName}" th:required="${p.required}"/>
</span>
<input th:case="STRING" type="text" th:id="${d.asString(p)}" th:name="${d.asString(p)}" th:value="${p.getValue(asset)}" th:placeholder="${p.displayName}" th:required="${p.required}"/>
<input th:case="INTEGER" type="number" th:id="${d.asString(p)}" th:name="${d.asString(p)}" th:value="${p.getValue(asset)}" th:required="${p.required}"/>
<select th:case="enum" th:id="${d.asString(p)}" th:name="${d.asString(p)}">