From e703da995e42a3cfea521ce6b3922aa73f165359 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sat, 7 Jun 2025 17:32:55 +0200 Subject: [PATCH] Got most of the create menu working --- .../pcinv/controllers/WebController.java | 35 ++++++++++ ...etProperties.java => AssetDescriptor.java} | 18 ++++- .../pcinv/meta/AssetDescriptors.java | 9 +-- .../be/seeseepuff/pcinv/meta/AssetEnum.java | 15 ++++ .../be/seeseepuff/pcinv/meta/AssetOption.java | 18 +++++ .../seeseepuff/pcinv/meta/AssetProperty.java | 69 +++++++++++++++++-- .../be/seeseepuff/pcinv/meta/Capacity.java | 20 ++++++ .../be/seeseepuff/pcinv/meta/Property.java | 7 ++ .../pcinv/models/AssetCondition.java | 26 +++++-- .../seeseepuff/pcinv/models/GenericAsset.java | 2 +- .../be/seeseepuff/pcinv/models/HddAsset.java | 2 + .../be/seeseepuff/pcinv/models/RamAsset.java | 2 + .../pcinv/services/AssetService.java | 33 +++++++++ .../resources/templates/create_asset.html | 40 +++++++++++ .../resources/templates/create_select.html | 8 +++ src/main/resources/templates/fragments.html | 1 - src/main/resources/templates/index.html | 1 + 17 files changed, 286 insertions(+), 20 deletions(-) rename src/main/java/be/seeseepuff/pcinv/meta/{AssetProperties.java => AssetDescriptor.java} (82%) create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/AssetEnum.java create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/AssetOption.java create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/Capacity.java create mode 100644 src/main/resources/templates/create_asset.html create mode 100644 src/main/resources/templates/create_select.html diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java index caf9de1..80676ae 100644 --- a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java +++ b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -5,15 +5,50 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +/** + * Controller for handling web requests related to assets. + * Provides endpoints for viewing and creating assets. + */ @RequiredArgsConstructor @Controller public class WebController { + /// The name of the model attribute that holds the global asset descriptors. + 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 final AssetService assetService; + /** + * Handles the root URL and returns the index page with asset descriptors and asset count. + */ @GetMapping("/") public String index(Model model) { + model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptors()); model.addAttribute("asset_count", assetService.countAssets()); return "index"; } + + /** + * Shows a view where the user can create the type of asset to create. + */ + @GetMapping("/create") + public String create(Model model) { + model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptors()); + return "create_select"; + } + + /** + * Shows a view where the user can create a specific type of asset. + * + * @param type The type of asset to create. + */ + @GetMapping("/create/{type}") + public String createType(Model model, @PathVariable String type) { + model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(type)); + model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(type)); + return "create_asset"; + } } diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java similarity index 82% rename from src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java rename to src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java index 3efed5b..5acb1a8 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java @@ -1,6 +1,7 @@ package be.seeseepuff.pcinv.meta; import lombok.Builder; +import lombok.Getter; import java.util.Arrays; import java.util.Collection; @@ -9,8 +10,9 @@ import java.util.Objects; /** * Describes the properties of an asset */ +@Getter @Builder -public class AssetProperties { +public class AssetDescriptor { /// The type of property, e.g.: ram, asset, etc... private final String type; @@ -30,10 +32,10 @@ public class AssetProperties { * @param assetType The class of the asset to load properties for. * @return An AssetProperties instance containing the loaded properties. */ - public static AssetProperties loadFrom(Class assetType) { + public static AssetDescriptor loadFrom(Class assetType) { var assetInfo = assetType.getAnnotation(AssetInfo.class); Objects.requireNonNull(assetInfo, "Asset class must be annotated with @AssetInfo"); - return AssetProperties.builder() + return AssetDescriptor.builder() .type(assetInfo.type()) .displayName(assetInfo.displayName()) .visible(assetInfo.isVisible()) @@ -66,4 +68,14 @@ public class AssetProperties { builder.append(indent).append('}'); return builder.toString(); } + + /** + * Returns a string representation of the asset property in the format "type-propertyName". + * + * @param property The asset property to format. + * @return A string representation of the asset property. + */ + public String asString(AssetProperty property) { + return String.format("%s-%s", type, property.getName()); + } } diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java index 029e30e..fce5d91 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java @@ -2,8 +2,9 @@ package be.seeseepuff.pcinv.meta; import lombok.Getter; -import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; +import java.util.TreeSet; /** * Holds the descriptors for all possible asset types. @@ -11,7 +12,7 @@ import java.util.Collection; @Getter public class AssetDescriptors { /// A collection of all types of assets. - private final Collection assets = new ArrayList<>(); + private final Collection assets = new TreeSet<>(Comparator.comparing(AssetDescriptor::getDisplayName)); /** * Loads the asset properties from a given asset type. @@ -19,7 +20,7 @@ public class AssetDescriptors { * @param assetType The type of the asset to load properties for. */ public void loadFrom(Class assetType) { - var property = AssetProperties.loadFrom(assetType); + var property = AssetDescriptor.loadFrom(assetType); assets.add(property); } @@ -27,7 +28,7 @@ public class AssetDescriptors { public String toString() { var builder = new StringBuilder(); builder.append("AssetDescriptors [\n"); - assets.forEach(assetProperties -> builder.append(assetProperties.toString(" ")).append('\n')); + assets.forEach(assetDescriptor -> builder.append(assetDescriptor.toString(" ")).append('\n')); builder.append("]"); return builder.toString(); } diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetEnum.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetEnum.java new file mode 100644 index 0000000..b8ef941 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetEnum.java @@ -0,0 +1,15 @@ +package be.seeseepuff.pcinv.meta; + +/** + * An interface that should be implemented by all asset enums. + */ +public interface AssetEnum { + /// Get the internal value of the enum. + String getValue(); + + /// Get the display name of the enum. + String getDisplayName(); + + /// Get the default value of the enum that should be selected when the user creates a device. + boolean isDefaultValue(); +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetOption.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetOption.java new file mode 100644 index 0000000..1322fa1 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetOption.java @@ -0,0 +1,18 @@ +package be.seeseepuff.pcinv.meta; + +import lombok.Builder; +import lombok.Getter; + +/** + * Represents an option for an asset property enum. + */ +@Getter +@Builder +public class AssetOption { + /// The internal value of the option. + private final String value; + /// The display name of the option. + private final String displayName; + /// Whether this option is the default value for the property. + private final boolean isDefaultValue; +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java index 7ff640b..7578104 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java @@ -2,14 +2,19 @@ package be.seeseepuff.pcinv.meta; import be.seeseepuff.pcinv.models.AssetCondition; import jakarta.annotation.Nullable; +import lombok.AllArgsConstructor; import lombok.Builder; +import lombok.Getter; +import lombok.Singular; import java.lang.reflect.Field; +import java.util.List; /** * Represents a property of an asset, such as its name or type. * This class is used to define the metadata for assets in the system. */ +@Getter @Builder public class AssetProperty { /// The name of the property, e.g., "brand", "model", etc. @@ -18,12 +23,37 @@ public class AssetProperty { private final String displayName; /// The type of the property, which can be a string or an integer. private final Type type; + /// Whether the property is required for the asset. + private final boolean required; + /// A set of options for the property, used for enum types. + @Singular + private final List options; + /// Whether the capacity should be displayed in SI units (e.g., GB, MB). + private final boolean capacityAsSI; + /// Whether the capacity should be displayed in IEC units (e.g., GiB, MiB). + private final boolean capacityAsIEC; /** * Enum representing the possible types of asset properties. */ + @AllArgsConstructor public enum Type { - STRING, INTEGER, CONDITION + STRING(false), + INTEGER(false), + CAPACITY(false), + CONDITION(true), + ; + /// Set to `true` if the type is an enum, `false` otherwise. + public final boolean isEnum; + + /** + * Returns the name of the type, or "enum" if it is an enum type. + * + * @return The name of the type or "enum" if it is an enum. + */ + public String nameOrEnum() { + return isEnum ? "enum" : name(); + } } /** @@ -38,11 +68,34 @@ public class AssetProperty { if (annotation == null) { return null; } - return AssetProperty.builder() + + var type = determineType(property); + var builder = AssetProperty.builder() .name(property.getName()) .displayName(annotation.value()) - .type(determineType(property)) - .build(); + .type(type) + .required(annotation.required()); + + if (type.isEnum) { + var enumConstants = property.getType().getEnumConstants(); + for (var enumConstant : enumConstants) { + if (!(enumConstant instanceof AssetEnum assetEnum)) { + throw new IllegalArgumentException("Property " + enumConstant.getClass().getName() + " does not implement AssetEnum"); + } + var option = AssetOption.builder() + .value(assetEnum.getValue()) + .displayName(assetEnum.getDisplayName()) + .isDefaultValue(assetEnum.isDefaultValue()) + .build(); + builder.option(option); + } + } + if (type == Type.CAPACITY) { + var capacityAnnotation = property.getAnnotation(Capacity.class); + builder.capacityAsSI(capacityAnnotation.si()); + builder.capacityAsIEC(capacityAnnotation.iec()); + } + return builder.build(); } /** @@ -55,6 +108,8 @@ public class AssetProperty { private static Type determineType(Field property) { if (property.getType() == String.class) { return Type.STRING; + } else if (property.isAnnotationPresent(Capacity.class)) { + return Type.CAPACITY; } else if (property.getType() == Integer.class || property.getType() == int.class || property.getType() == Long.class || property.getType() == long.class) { return Type.INTEGER; } else if (property.getType() == AssetCondition.class) { @@ -66,6 +121,10 @@ public class AssetProperty { @Override public String toString() { - return String.format("%s:%s (%s)", name, type.name(), displayName); + var enumOptions = ""; + if (type.isEnum) { + enumOptions = " [" + String.join(", ", options.stream().map(AssetOption::getValue).toList()) + "]"; + } + return String.format("%s:%s (%s)%s", name, type.name(), displayName, enumOptions); } } diff --git a/src/main/java/be/seeseepuff/pcinv/meta/Capacity.java b/src/main/java/be/seeseepuff/pcinv/meta/Capacity.java new file mode 100644 index 0000000..d5db0bb --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/Capacity.java @@ -0,0 +1,20 @@ +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; + +/** + * An annotation to mark a property descriptor as representing some sort of capacity. + * It is used to render the capacity in the UI as bytes, kilobytes, megabytes, etc. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Capacity { + /// Set to `true` if the capacity should include SI units (e.g., 1 KB = 1000 bytes). + boolean si() default false; + + /// Set to `true` if the capacity should be displayed in IEC units (e.g., 1 KiB = 1024 bytes). + boolean iec() default true; +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/Property.java b/src/main/java/be/seeseepuff/pcinv/meta/Property.java index eb71c13..0db3629 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/Property.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/Property.java @@ -17,4 +17,11 @@ public @interface Property { * @return the display name of the property */ String value(); + + /** + * Whether the property is required for the asset. + * + * @return true if the property is required, false otherwise + */ + boolean required() default false; } diff --git a/src/main/java/be/seeseepuff/pcinv/models/AssetCondition.java b/src/main/java/be/seeseepuff/pcinv/models/AssetCondition.java index c928d6e..4392a36 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/AssetCondition.java +++ b/src/main/java/be/seeseepuff/pcinv/models/AssetCondition.java @@ -1,18 +1,32 @@ package be.seeseepuff.pcinv.models; +import be.seeseepuff.pcinv.meta.AssetEnum; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + /** * Represents the condition of an asset in the inventory system. */ -public enum AssetCondition +@Getter +@RequiredArgsConstructor +public enum AssetCondition implements AssetEnum { /// The asset is in perfect working order. - HEALTHY, + HEALTHY("healthy", "Healthy"), /// The condition of the asset is unknown. E.g.: it is untested. - UNKNOWN, + UNKNOWN("unknown", "Not known"), /// The asset generally works, but has some known issues. - PARTIAL, + PARTIAL("partial", "Partially working"), /// The asset is in need of repair, but is not completely broken. - REPAIR, + REPAIR("repair", "Requires repairs"), /// The asset is completely broken and cannot be used. - BORKED, + BORKED("borked", "Borked"), + ; + private final String value; + private final String displayName; + + @Override + public boolean isDefaultValue() { + return this == UNKNOWN; + } } diff --git a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java index 08def8c..bf66381 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java @@ -27,7 +27,7 @@ public class GenericAsset private long id; /// The QR code attached to the asset, used for identification. - @Property("QR") + @Property(value = "QR", required = true) private long qr; /// The brand of the asset. diff --git a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java index ae2cb61..b81da1a 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java @@ -1,6 +1,7 @@ package be.seeseepuff.pcinv.models; import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.Capacity; import be.seeseepuff.pcinv.meta.Property; import jakarta.persistence.*; import lombok.Getter; @@ -29,6 +30,7 @@ public class HddAsset implements Asset /// The capacity of the drive in bytes. @Property("Capacity") + @Capacity(si = true, iec = true) private long capacity; /// The drive's interface type, such as SATA, IDE, ISA-16, ... diff --git a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java index 407a613..0150ca8 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java @@ -1,6 +1,7 @@ package be.seeseepuff.pcinv.models; import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.Capacity; import be.seeseepuff.pcinv.meta.Property; import jakarta.persistence.*; import lombok.Getter; @@ -29,6 +30,7 @@ public class RamAsset implements Asset /// The capacity of the RAM in bytes. @Property("Capacity") + @Capacity private long capacity; /// The type of memory. E.g.: DDR2, SDRAM, ISA-8, etc... diff --git a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java index c56fd8a..5596de7 100644 --- a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java +++ b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java @@ -1,5 +1,6 @@ package be.seeseepuff.pcinv.services; +import be.seeseepuff.pcinv.meta.AssetDescriptor; import be.seeseepuff.pcinv.meta.AssetDescriptors; import be.seeseepuff.pcinv.models.GenericAsset; import be.seeseepuff.pcinv.repositories.AssetRepository; @@ -8,6 +9,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.Collection; +import java.util.List; /** * Service for managing assets in the repository. @@ -28,6 +30,11 @@ public class AssetService { return assetRepository.count(); } + /** + * Retrieves the global asset descriptors for all asset types. + * + * @return the AssetProperties for the specified type + */ public AssetDescriptors getAssetDescriptors() { var descriptors = new AssetDescriptors(); descriptors.loadFrom(GenericAsset.class); @@ -36,4 +43,30 @@ public class AssetService { } return descriptors; } + + /** + * Retrieves the asset properties for a specific type. + * + * @param type the type of asset to retrieve properties for + * @return the AssetProperties for the specified type + */ + public AssetDescriptor getAssetDescriptor(String type) { + return getAssetDescriptors().getAssets().stream() + .filter(asset -> asset.getType().equals(type)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unknown asset type: " + type)); + } + + /** + * Retrieves a tree of asset descriptors for the specified type. + * + * @param type the type of asset to retrieve descriptors for + * @return a list of AssetDescriptors for the specified type + */ + public List getAssetDescriptorTree(String type) { + if (type.equals("asset")) { + return List.of(getAssetDescriptor("asset")); + } + return List.of(getAssetDescriptor("asset"), getAssetDescriptor(type)); + } } diff --git a/src/main/resources/templates/create_asset.html b/src/main/resources/templates/create_asset.html new file mode 100644 index 0000000..58948e2 --- /dev/null +++ b/src/main/resources/templates/create_asset.html @@ -0,0 +1,40 @@ + +
+ Create a +
+
+

+ + + + + +
+ + + + + + + + Bad input type for +
+
+

+ +

+
+
+ diff --git a/src/main/resources/templates/create_select.html b/src/main/resources/templates/create_select.html new file mode 100644 index 0000000..80c5f8f --- /dev/null +++ b/src/main/resources/templates/create_select.html @@ -0,0 +1,8 @@ + +
+ Create a new device +
    +
  • +
+
+ diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html index 4ca2c16..9200871 100644 --- a/src/main/resources/templates/fragments.html +++ b/src/main/resources/templates/fragments.html @@ -1,4 +1,3 @@ - diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index f528dd7..5a58521 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -1,5 +1,6 @@
+ Create a new device

This system holds 5 assets.