Can show an asset

This commit is contained in:
2025-06-08 06:47:34 +02:00
parent 257abddc15
commit d4718d15c3
10 changed files with 119 additions and 8 deletions

View File

@@ -24,6 +24,7 @@ public class WebController {
private static final String DESCRIPTORS = "descriptors";
/// The name of the model attribute that holds the asset descriptor for the current view.
private static final String DESCRIPTOR = "descriptor";
private static final String GENERIC_DESCRIPTOR = "generic";
private final AssetService assetService;
@@ -37,6 +38,23 @@ public class WebController {
return "index";
}
/**
* Handles the view of an asset by its QR code.
* If the asset does not exist, it redirects to the index page.
*
* @param qr The QR code of the asset to view.
*/
@GetMapping("/view/{qr}")
public String view(Model model, @PathVariable long qr) {
var asset = assetService.getAssetByQr(qr);
if (asset == null) {
return "redirect:/";
}
model.addAttribute("asset", asset);
model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(asset.getAsset().getType()));
return "view";
}
/**
* Shows a view where the user can create the type of asset to create.
*/

View File

@@ -46,7 +46,7 @@ public class AssetDescriptor {
.displayName(assetInfo.displayName())
.visible(assetInfo.isVisible())
.properties(Arrays.stream(assetType.getDeclaredFields())
.map(AssetProperty::loadFrom)
.map(field -> AssetProperty.loadFrom(field, assetInfo.type()))
.filter(Objects::nonNull)
.toList());
if (Asset.class.isAssignableFrom(assetType)) {

View File

@@ -1,6 +1,9 @@
package be.seeseepuff.pcinv.meta;
import be.seeseepuff.pcinv.models.Asset;
import be.seeseepuff.pcinv.models.AssetCondition;
import be.seeseepuff.pcinv.models.GenericAsset;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import lombok.AllArgsConstructor;
import lombok.Builder;
@@ -10,6 +13,7 @@ import lombok.Singular;
import java.lang.reflect.Field;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* Represents a property of an asset, such as its name or type.
@@ -35,6 +39,8 @@ public class AssetProperty {
private final boolean capacityAsIEC;
/// A setter function that can be used to set the value of the property on an asset.
private final BiConsumer<Object, Object> setter;
/// A getter function that can be used to get the value of the property from an asset.
private final Function<Object, Object> getter;
/**
* Enum representing the possible types of asset properties.
@@ -66,7 +72,7 @@ public class AssetProperty {
* @return An AssetProperty instance with the name, display name, and type determined from the field.
*/
@Nullable
public static AssetProperty loadFrom(Field property) {
public static AssetProperty loadFrom(Field property, @Nonnull String assetType) {
var annotation = property.getAnnotation(Property.class);
if (annotation == null) {
return null;
@@ -85,6 +91,17 @@ public class AssetProperty {
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
})
.getter(obj -> {
try {
if (assetType.equals(GenericAsset.TYPE) && obj instanceof Asset asset) {
obj = asset.getAsset();
}
property.setAccessible(true);
return property.get(obj);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
if (type.isEnum) {
@@ -144,6 +161,39 @@ public class AssetProperty {
setter.accept(asset, value);
}
/**
* Gets the value of the property from the given asset.
*
* @param asset The asset to get the property value from.
* @return The value of the property.
*/
public Object getValue(Object asset) {
return getter.apply(asset);
}
/**
* Renders the value of the property as a string.
*
* @return The rendered value as a string.
*/
public String renderValue(Object asset) {
var value = getValue(asset);
if (value == null) {
return "Unknown";
} else if (type == Type.INTEGER || type == Type.STRING) {
return value.toString();
} else if (type == Type.CAPACITY) {
return String.format("%s bytes", value);
} else if (type.isEnum) {
if (value instanceof AssetEnum assetEnum) {
return assetEnum.getDisplayName();
}
throw new IllegalArgumentException("Expected value to be an instance of AssetEnum, but got: " + value.getClass().getName());
} else {
return value.toString();
}
}
@Override
public String toString() {
var enumOptions = "";

View File

@@ -23,6 +23,8 @@ import lombok.Setter;
@Table(name = "assets")
public class GenericAsset
{
public static final String TYPE = "asset";
@Id @GeneratedValue
private long id;
@@ -30,6 +32,9 @@ public class GenericAsset
@Property(value = "QR", required = true)
private long qr;
/// The type of asset
private String type;
/// The brand of the asset.
@Property("Brand")
private String brand;

View File

@@ -1,6 +1,7 @@
package be.seeseepuff.pcinv.repositories;
import be.seeseepuff.pcinv.models.Asset;
import be.seeseepuff.pcinv.models.GenericAsset;
import org.springframework.stereotype.Repository;
@Repository
@@ -16,6 +17,8 @@ public interface AssetRepository<T extends Asset> {
}
}
T findByAsset(GenericAsset asset);
Class<T> getAssetType();
long count();

View File

@@ -5,4 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository;
@SuppressWarnings("unused")
public interface GenericAssetRepository extends JpaRepository<GenericAsset, Long> {
GenericAsset findByQr(long qr);
}

View File

@@ -35,6 +35,21 @@ public class AssetService {
return genericRepository.count();
}
/**
* Retrieves an asset by its QR code.
*
* @param qr the QR code of the asset to retrieve
* @return the Asset associated with the given QR code
* @throws IllegalArgumentException if no asset is found with the given QR code
*/
public Asset getAssetByQr(long qr) {
var genericAsset = genericRepository.findByQr(qr);
if (genericAsset == null) {
throw new IllegalArgumentException("No asset found with QR code: " + qr);
}
return getRepositoryFor(genericAsset.getType()).findByAsset(genericAsset);
}
/**
* Retrieves the global asset descriptors for all asset types.
*
@@ -69,10 +84,10 @@ public class AssetService {
* @return a list of AssetDescriptors for the specified type
*/
public List<AssetDescriptor> getAssetDescriptorTree(String type) {
if (type.equals("asset")) {
return List.of(getAssetDescriptor("asset"));
if (type.equals(GenericAsset.TYPE)) {
return List.of(getAssetDescriptor(GenericAsset.TYPE));
}
return List.of(getAssetDescriptor("asset"), getAssetDescriptor(type));
return List.of(getAssetDescriptor(GenericAsset.TYPE), getAssetDescriptor(type));
}
/**
@@ -84,10 +99,11 @@ public class AssetService {
*/
@Transactional
public Asset createAsset(String type, Map<String, String> formData) {
var genericDescriptor = getAssetDescriptor("asset");
var genericDescriptor = getAssetDescriptor(GenericAsset.TYPE);
var assetDescriptor = getAssetDescriptor(type);
var genericAsset = new GenericAsset();
genericAsset.setType(type);
fillIn(genericAsset, genericDescriptor, formData);
var asset = assetDescriptor.newInstance();

View File

@@ -4,7 +4,12 @@
<title>PC Inventory</title>
</head>
<body>
<h1>PC Inventory - <span th:text="${title}"></span></h1>
<h1>PC Inventory - <span th:text="${title}"></span>
</h1>
<hr>
<a href="/">Home</a>
<a href="/browse">Browse</a>
<a href="/create">Create</a>
<hr>
<div th:replace="${content}">
</div>

View File

@@ -1,6 +1,5 @@
<div th:replace="fragments :: base(title='Home', content=~{::content})">
<div th:fragment="content">
<a href="/create">Create a new device</a>
<p>This system holds <span th:text="${asset_count}">5</span> assets.</p>
</div>
</div>

View File

@@ -0,0 +1,14 @@
<body th:replace="~{fragments :: base(title='Select type to create', content=~{::content})}">
<div th:fragment="content">
View device details
<div th:each="d : ${descriptors}">
<h2 th:text="${d.displayName}"></h2>
<table border="1">
<tr th:each="p : ${d.properties}">
<td><b th:text="${p.displayName}"></b></td>
<td th:text="${p.renderValue(asset)}"></td>
</tr>
</table>
</div>
</div>
</body>