From 6e72582d77685626a9e75144276093e208c960a4 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Wed, 4 Jun 2025 05:33:51 +0200 Subject: [PATCH 01/22] Working on using new data model --- data.go | 52 ++--- descriptors.go | 130 ++++++++++++ go.mod | 39 ++-- go.sum | 44 +++++ static/scripts.js | 7 + template_funcs.go | 12 +- templates/create_device_step1.gohtml | 26 +-- templates/create_device_step2.gohtml | 286 ++++++++++++++++----------- templates/header.gohtml | 4 +- view_createdevice.go | 135 +++++++++++++ view_device.go | 73 +++++++ view_index.go | 50 +++++ views.go | 266 ++++++------------------- 13 files changed, 734 insertions(+), 390 deletions(-) create mode 100644 descriptors.go create mode 100644 view_createdevice.go create mode 100644 view_device.go create mode 100644 view_index.go diff --git a/data.go b/data.go index 1ff9338..df94ecd 100644 --- a/data.go +++ b/data.go @@ -45,32 +45,32 @@ func (a *App) GetAllHddSpeeds() ([]string, error) { func (a *App) GetAllGroups(vm *CreateDeviceVM) error { var err error - vm.AssetBrands, err = a.GetAllBrands() - if err != nil { - return err - } - - vm.RamTypes, err = a.GetAllRamTypes() - if err != nil { - return err - } - - vm.HddTypes, err = a.GetAllHddTypes() - if err != nil { - return err - } - - vm.HddFormFactors, err = a.GetAllHddFormFactors() - if err != nil { - return err - } - - vm.HddFormFactors, err = a.GetAllHddConnections() - if err != nil { - return err - } - - vm.HddRpms, err = a.GetAllHddSpeeds() + //vm.AssetBrands, err = a.GetAllBrands() + //if err != nil { + // return err + //} + // + //vm.RamTypes, err = a.GetAllRamTypes() + //if err != nil { + // return err + //} + // + //vm.HddTypes, err = a.GetAllHddTypes() + //if err != nil { + // return err + //} + // + //vm.HddFormFactors, err = a.GetAllHddFormFactors() + //if err != nil { + // return err + //} + // + //vm.HddFormFactors, err = a.GetAllHddConnections() + //if err != nil { + // return err + //} + // + //vm.HddRpms, err = a.GetAllHddSpeeds() if err != nil { return err } diff --git a/descriptors.go b/descriptors.go new file mode 100644 index 0000000..3ab2e1d --- /dev/null +++ b/descriptors.go @@ -0,0 +1,130 @@ +package main + +var DescriptorTree = createDescriptorTree(DescriptorRoot{ + AssetTypes: []AssetType{ + { + Id: "asset", + Table: "assets", + Name: "General Information", + Abstract: true, + Fields: []AssetField{ + { + Id: "qr", + Name: "QR Code", + Type: Number, + }, + { + Id: "type", + Name: "Type", + Type: Type, + }, + { + Id: "brand", + Name: "Brand", + Type: Selection, + }, + { + Id: "name", + Name: "Name", + Type: String, + }, + { + Id: "description", + Name: "Description", + Type: String, + }, + }, + }, + { + Id: "ram", + Name: "Random Access Memory", + Table: "info_ram", + Fields: []AssetField{ + { + Id: "type", + Name: "Type", + Type: String, + }, + { + Id: "capacity", + Name: "Capacity", + Type: Capacity, + }, + }, + }, + { + Id: "hdd", + Name: "Hard Disk Drive", + Table: "info_hdd", + Fields: []AssetField{ + { + Id: "type", + Name: "Type", + Type: Selection, + }, + { + Id: "capacity", + Name: "Capacity", + Type: Capacity, + }, + { + Id: "form_factor", + Name: "Form Factor", + Type: Selection, + }, + { + Id: "interface", + Name: "Interface", + Type: Selection, + }, + { + Id: "rpm", + Name: "RPM", + Type: Selection, + }, + }, + }, + }, +}) + +func createDescriptorTree(tree DescriptorRoot) *DescriptorRoot { + return &tree +} + +func getAssetTypeById(id string) *AssetType { + for _, assetType := range DescriptorTree.AssetTypes { + if assetType.Id == id { + return &assetType + } + } + return nil +} + +type DescriptorRoot struct { + AssetTypes []AssetType +} + +type AssetType struct { + Id string + Name string + Table string + Fields []AssetField + Abstract bool +} + +type AssetField struct { + Id string + Name string + Type FieldType +} + +type FieldType string + +const ( + Number FieldType = "number" + String = "string" + Type = "type" + Selection = "selection" + Bool = "bool" + Capacity = "capacity" +) diff --git a/go.mod b/go.mod index 54d933d..ae21222 100644 --- a/go.mod +++ b/go.mod @@ -5,20 +5,20 @@ go 1.24 toolchain go1.24.1 require ( - gitea.seeseepuff.be/seeseemelk/mysqlite v0.9.0 - github.com/gin-gonic/gin v1.10.0 + gitea.seeseepuff.be/seeseemelk/mysqlite v0.15.0 + github.com/gin-gonic/gin v1.10.1 ) require ( - github.com/bytedance/sonic v1.13.1 // indirect + github.com/bytedance/sonic v1.13.2 // indirect github.com/bytedance/sonic/loader v0.2.4 // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect - github.com/gin-contrib/sse v1.0.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.25.0 // indirect + github.com/go-playground/validator/v10 v10.26.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -28,20 +28,21 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.15.0 // indirect - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/text v0.23.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + github.com/ugorji/go/codec v1.2.14 // indirect + golang.org/x/arch v0.17.0 // indirect + golang.org/x/crypto v0.38.0 // indirect + golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect + golang.org/x/net v0.40.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/text v0.25.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.55.3 // indirect - modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.8.0 // indirect - modernc.org/sqlite v1.33.1 // indirect - zombiezen.com/go/sqlite v1.4.0 // indirect + modernc.org/libc v1.65.8 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.37.1 // indirect + zombiezen.com/go/sqlite v1.4.2 // indirect ) diff --git a/go.sum b/go.sum index 7c65f36..98e21e1 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,13 @@ gitea.seeseepuff.be/seeseemelk/mysqlite v0.9.0 h1:GaU2DSrgDfZEqST3HdnNgfKSI4sNXvMm8SSfeMvBxA4= gitea.seeseepuff.be/seeseemelk/mysqlite v0.9.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= +gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0 h1:aRItVfUj48fBmuec7rm/jY9KCfvHW2VzJfItVk4t8sw= +gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= +gitea.seeseepuff.be/seeseemelk/mysqlite v0.15.0 h1:+k0iBYM/aZJxz7++EKi/G9e66E9u4bPS3DFLrBeDb9Y= +gitea.seeseepuff.be/seeseemelk/mysqlite v0.15.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= +github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= @@ -15,10 +21,16 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -27,6 +39,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= @@ -53,6 +67,8 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= @@ -73,25 +89,43 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.2.14 h1:yOQvXCBc3Ij46LRkRoh4Yd5qK6LVOgi0bYOXfb7ifjw= +github.com/ugorji/go/codec v1.2.14/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= +golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= +golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= +golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA= +golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= +golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= +golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -107,16 +141,24 @@ modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/libc v1.65.8 h1:7PXRJai0TXZ8uNA3srsmYzmTyrLoHImV5QxHeni108Q= +modernc.org/libc v1.65.8/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU= modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= +modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs= +modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= @@ -124,3 +166,5 @@ modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU= zombiezen.com/go/sqlite v1.4.0/go.mod h1:0w9F1DN9IZj9AcLS9YDKMboubCACkwYCGkzoy3eG5ik= +zombiezen.com/go/sqlite v1.4.2 h1:KZXLrBuJ7tKNEm+VJcApLMeQbhmAUOKA5VWS93DfFRo= +zombiezen.com/go/sqlite v1.4.2/go.mod h1:5Kd4taTAD4MkBzT25mQ9uaAlLjyR0rFhsR6iINO70jc= diff --git a/static/scripts.js b/static/scripts.js index 34e9308..82f2c03 100644 --- a/static/scripts.js +++ b/static/scripts.js @@ -14,3 +14,10 @@ function newOption(elementId, name) { el.prepend(child) el.value = newValue } + +window.onload = function() { + var elements = document.getElementsByClassName("to-delete") + while (elements.length > 0) { + elements[0].remove() + } +} diff --git a/template_funcs.go b/template_funcs.go index c7d4481..ac65f39 100644 --- a/template_funcs.go +++ b/template_funcs.go @@ -88,14 +88,12 @@ func isRamType(size int, unit string) bool { } func formatType(t string) string { - switch t { - case "ram": - return "Random Access Memory" - case "hdd": - return "Hard Disk Drive" - default: - return t + for _, assetType := range DescriptorTree.AssetTypes { + if assetType.Id == t { + return assetType.Name + } } + panic("unknown type") } type SelectMenu struct { diff --git a/templates/create_device_step1.gohtml b/templates/create_device_step1.gohtml index e4369e8..c4a20ba 100644 --- a/templates/create_device_step1.gohtml +++ b/templates/create_device_step1.gohtml @@ -1,18 +1,20 @@ -{{- /*gotype: main.CreateDeviceVM */}} +{{- /*gotype: pcinv.CreateDeviceVM*/ -}} {{define "create_device_step1"}} {{template "header" "Create Device - Choose Device Type"}} {{template "footer"}} {{end}} - -{{define "create_device_link"}} - {{if .Qr}} -
  • {{"ram" | formatType}}
  • -
  • {{"hdd" | formatType}}
  • - {{- else}} -
  • {{"ram" | formatType}}
  • -
  • {{"hdd" | formatType}}
  • - {{- end}} -{{end}} diff --git a/templates/create_device_step2.gohtml b/templates/create_device_step2.gohtml index 006210d..518a2f5 100644 --- a/templates/create_device_step2.gohtml +++ b/templates/create_device_step2.gohtml @@ -2,129 +2,187 @@ {{define "create_device_step2"}} {{template "header" "Create Device - Enter Device Data"}}
    -

    General Information

    - - - - {{if .Qr}} - - {{else}} - - {{end}} - - - - - - - - - - - - - - - - - -
    - -
    - {{if eq .Type "ram"}} -

    Memory Information

    - - - - + + {{end}} +
    - + {{range .Fields}} + + + - - - - - -
    + {{if eq .Type "type"}} + {{$.NiceType}} + + {{else if eq .Type "number"}} + + {{else if eq .Type "string"}} + + {{else if eq .Type "selection"}} + + + {{else if eq .Type "capacity"}} + + {{end}} - {{end}} - - -
    - - -
    +
    + {{end}} {{end}} - {{if eq .Type "hdd"}} -

    Hard Drive Information

    - - - - - - - - - - - - - - - - - - - - - -
    - - -
    {{template "create_device_select" createSelectMenu "hdd_type" "HDD Type" .HddType .HddTypes}}
    {{template "create_device_select" createSelectMenu "hdd_form_factor" "HDD Form Factor" .HddFormFactor .HddFormFactors}}
    {{template "create_device_select" createSelectMenu "hdd_connection" "HDD Connection" .HddConnection .HddConnections}}
    {{template "create_device_select" createSelectMenuDefault "hdd_rpm" "HDD RPM" .HddRpm .HddRpms "Not Applicable"}}
    - {{end}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* {{if .Qr}}*/}} +{{/* */}} +{{/* {{else}}*/}} +{{/* */}} +{{/* {{end}}*/}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/*
    */}} +{{/* */}} +{{/*
    */}} + +{{/* {{if eq .Type "ram"}}*/}} +{{/*

    Memory Information

    */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/*
    */}} +{{/* */}} +{{/*
    */}} +{{/* */}} +{{/* */}} +{{/*
    */}} +{{/* {{end}}*/}} + +{{/* {{if eq .Type "hdd"}}*/}} +{{/*

    Hard Drive Information

    */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/* */}} +{{/*
    */}} +{{/* */}} +{{/* */}} +{{/*
    {{template "create_device_select" createSelectMenu "hdd_type" "HDD Type" .HddType .HddTypes}}
    {{template "create_device_select" createSelectMenu "hdd_form_factor" "HDD Form Factor" .HddFormFactor .HddFormFactors}}
    {{template "create_device_select" createSelectMenu "hdd_connection" "HDD Connection" .HddConnection .HddConnections}}
    {{template "create_device_select" createSelectMenuDefault "hdd_rpm" "HDD RPM" .HddRpm .HddRpms "Not Applicable"}}
    */}} +{{/* {{end}}*/}} {{if .IsEdit}} - + {{else}} - + {{end}}
    {{template "footer"}} {{end}} -{{define "create_device_select"}} - -{{end}} +{{/*{{define "create_device_select"}}*/}} +{{/* */}} +{{/*{{end}}*/}} diff --git a/templates/header.gohtml b/templates/header.gohtml index ba9c862..be6b2af 100644 --- a/templates/header.gohtml +++ b/templates/header.gohtml @@ -4,12 +4,12 @@ PC Inventory{{if .}} - {{.}}{{end}} - +

    PC Inventory{{if .}} - {{.}}{{end}}

    - + Create Device

    diff --git a/view_createdevice.go b/view_createdevice.go new file mode 100644 index 0000000..14f7dfc --- /dev/null +++ b/view_createdevice.go @@ -0,0 +1,135 @@ +package main + +import ( + "fmt" + "github.com/gin-gonic/gin" + "net/http" + "strconv" + "strings" +) + +type CreateDeviceVM struct { + IsEdit bool + DescriptorTree *DescriptorRoot + + // Assets + Qr *int + Type string + + Values map[string]string +} + +func (vm *CreateDeviceVM) SetValue(assetType string, field string, value string) { + if vm.Values == nil { + vm.Values = make(map[string]string) + } + vm.Values[assetType+"-"+field] = value +} + +func (vm *CreateDeviceVM) GetValue(assetType string, field string) string { + if vm.Values == nil { + return "" + } + return vm.Values[assetType+"-"+field] +} + +func (vm *CreateDeviceVM) NiceType() string { + return getAssetTypeById(vm.Type).Name +} + +func (vm *CreateDeviceVM) IsPartOf(assertType string) bool { + if assertType == "asset" { + return true + } + return assertType == vm.Type +} + +func (a *App) getCreateDevice(c *gin.Context) { + var err error + vm := &CreateDeviceVM{} + vm.DescriptorTree = DescriptorTree + vm.Type = c.Query("type") + + qr := c.Query("id") + if qr != "" { + qrInt, err := strconv.Atoi(qr) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("invalid qr: %v", err)) + return + } + vm.Qr = &qrInt + } + + err = a.GetAllGroups(vm) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + if c.Query("edit") != "" && vm.Qr != nil { + vm.IsEdit = true + err := a.pullDataFromDb(*vm.Qr, "asset", vm) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to load asset data: %v", err)) + return + } + //// Load the asset data from the database + //err = a.db.Query("SELECT * FROM assets WHERE qr = ?"). + // Bind(*vm.Qr). + // ScanColumns(&columns). + // ScanSingle(&vm.Qr, &vm.Type, &vm.Values) + //if err != nil { + // c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to load asset: %v", err)) + // return + // + //} + + //err = a.db.Query("SELECT name, type, brand, description FROM assets WHERE qr = ?"). + // Bind(*vm.Qr). + // ScanSingle(&vm.AssetName, &vm.Type, &vm.AssetBrand, &vm.AssetDescription) + //if err != nil { + // c.AbortWithError(http.StatusInternalServerError, err) + // return + //} + //if vm.Type == "ram" { + // err = a.db.Query("SELECT type, capacity FROM info_ram WHERE asset = ?"). + // Bind(*vm.Qr). + // ScanSingle(&vm.RamType, &vm.RamCapacity) + // if err != nil { + // c.AbortWithError(http.StatusInternalServerError, err) + // return + // } + //} else if vm.Type == "hdd" { + // err = a.db.Query("SELECT capacity, type, form_factor, connection, rpm FROM info_hdd WHERE asset = ?"). + // Bind(*vm.Qr). + // ScanSingle(&vm.HddCapacity, &vm.HddType, &vm.HddFormFactor, &vm.HddConnection, &vm.HddRpm) + // if err != nil { + // c.AbortWithError(http.StatusInternalServerError, err) + // return + // } + //} + } + + c.HTML(http.StatusOK, "create_device", vm) +} + +func (a *App) pullDataFromDb(qr int, assetType string, vm *CreateDeviceVM) error { + var fields []string + assetDescriptor := getAssetTypeById(assetType) + for _, field := range assetDescriptor.Fields { + fields = append(fields, field.Id) + } + values := make([]any, len(fields)) + + query := fmt.Sprintf("SELECT %s FROM %s WHERE qr = ?", strings.Join(fields, ", "), assetDescriptor.Table) + err := a.db.Query(query).Bind(qr).ScanSingle(values...) + if err != nil { + return fmt.Errorf("failed to load asset data: %v", err) + } + + for i, field := range fields { + vm.SetValue(assetType, field, fmt.Sprintf("%v", values[i])) + } + + return err +} diff --git a/view_device.go b/view_device.go new file mode 100644 index 0000000..9ac3d6c --- /dev/null +++ b/view_device.go @@ -0,0 +1,73 @@ +package main + +import ( + "github.com/gin-gonic/gin" + "net/http" + "strconv" +) + +type DeviceVM struct { + Qr int + Name string + Brand string + Type string + Description string + RamType string + RamCapacity int + HddCapacity int + HddType string + HddFormFactor string + HddConnection string + HddRpm int +} + +func (a *App) getDevice(c *gin.Context) { + qr, err := strconv.Atoi(c.Query("id")) + if err != nil { + c.AbortWithError(http.StatusBadRequest, err) + return + } + + var count int + err = a.db.Query("SELECT COUNT(*) FROM assets WHERE qr = ?"). + Bind(qr). + ScanSingle(&count) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + if count == 0 { + c.Redirect(http.StatusTemporaryRedirect, "/create?id="+strconv.Itoa(qr)) + return + } + + vm := &DeviceVM{Qr: qr} + err = a.db.Query("SELECT name, brand, type, description FROM assets WHERE qr = ?"). + Bind(qr). + ScanSingle(&vm.Name, &vm.Brand, &vm.Type, &vm.Description) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + if vm.Type == "ram" { + err = a.db.Query("SELECT type, capacity FROM info_ram WHERE asset = ?"). + Bind(qr). + ScanSingle(&vm.RamType, &vm.RamCapacity) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + } else if vm.Type == "hdd" { + err = a.db.Query("SELECT capacity, type, form_factor, connection, rpm FROM info_hdd WHERE asset = ?"). + Bind(qr). + ScanSingle(&vm.HddCapacity, &vm.HddType, &vm.HddFormFactor, &vm.HddConnection, &vm.HddRpm) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + } + + c.HTML(http.StatusOK, "device", vm) +} diff --git a/view_index.go b/view_index.go new file mode 100644 index 0000000..da0fd32 --- /dev/null +++ b/view_index.go @@ -0,0 +1,50 @@ +package main + +import ( + "github.com/gin-gonic/gin" + "net/http" +) + +type IndexVM struct { + AssetCount int + BrandCount int + TotalRamCapacity int + Brands []string + Types []string +} + +func (a *App) getIndex(c *gin.Context) { + vm := &IndexVM{} + var err error + vm.AssetCount, err = a.GetAssetCount() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + vm.BrandCount, err = a.GetBrandCount() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + vm.TotalRamCapacity, err = a.GetTotalRamCapacity() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + vm.Brands, err = a.GetAllBrands() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + vm.Types, err = a.GetAllTypes() + if err != nil { + c.AbortWithError(http.StatusInternalServerError, err) + return + } + + c.HTML(http.StatusOK, "index", vm) +} diff --git a/views.go b/views.go index 2f14ff3..978dde3 100644 --- a/views.go +++ b/views.go @@ -3,217 +3,22 @@ package main import ( "errors" "fmt" - "net/http" - "strconv" - "gitea.seeseepuff.be/seeseemelk/mysqlite" "github.com/gin-gonic/gin" + "net/http" + "strconv" + "strings" ) type App struct { db *mysqlite.Db } -type IndexVM struct { - AssetCount int - BrandCount int - TotalRamCapacity int - Brands []string - Types []string -} - -func (a *App) getIndex(c *gin.Context) { - vm := &IndexVM{} - var err error - vm.AssetCount, err = a.GetAssetCount() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - vm.BrandCount, err = a.GetBrandCount() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - vm.TotalRamCapacity, err = a.GetTotalRamCapacity() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - vm.Brands, err = a.GetAllBrands() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - vm.Types, err = a.GetAllTypes() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - c.HTML(http.StatusOK, "index", vm) -} - -type DeviceVM struct { - Qr int - Name string - Brand string - Type string - Description string - RamType string - RamCapacity int - HddCapacity int - HddType string - HddFormFactor string - HddConnection string - HddRpm int -} - -func (a *App) getDevice(c *gin.Context) { - qr, err := strconv.Atoi(c.Query("id")) - if err != nil { - c.AbortWithError(http.StatusBadRequest, err) - return - } - - var count int - err = a.db.Query("SELECT COUNT(*) FROM assets WHERE qr = ?"). - Bind(qr). - ScanSingle(&count) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - if count == 0 { - c.Redirect(http.StatusTemporaryRedirect, "/create?id="+strconv.Itoa(qr)) - return - } - - vm := &DeviceVM{Qr: qr} - err = a.db.Query("SELECT name, brand, type, description FROM assets WHERE qr = ?"). - Bind(qr). - ScanSingle(&vm.Name, &vm.Brand, &vm.Type, &vm.Description) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - if vm.Type == "ram" { - err = a.db.Query("SELECT type, capacity FROM info_ram WHERE asset = ?"). - Bind(qr). - ScanSingle(&vm.RamType, &vm.RamCapacity) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - } else if vm.Type == "hdd" { - err = a.db.Query("SELECT capacity, type, form_factor, connection, rpm FROM info_hdd WHERE asset = ?"). - Bind(qr). - ScanSingle(&vm.HddCapacity, &vm.HddType, &vm.HddFormFactor, &vm.HddConnection, &vm.HddRpm) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - } - - c.HTML(http.StatusOK, "device", vm) -} - -type CreateDeviceVM struct { - IsEdit bool - - // Assets - Qr *int - Type string - - AssetBrand string - AssetBrands []string - - AssetName string - AssetDescription string - - // RAM - RamType string - RamTypes []string - - RamCapacity int - - // HDD - HddCapacity int - - HddType string - HddTypes []string - HddFormFactor string - HddFormFactors []string - HddConnection string - HddConnections []string - HddRpm string - HddRpms []string -} - -func (a *App) getCreateDevice(c *gin.Context) { - var err error - vm := &CreateDeviceVM{} - vm.Type = c.Query("type") - - qr := c.Query("id") - if qr != "" { - qrInt, err := strconv.Atoi(qr) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("invalid qr: %v", err)) - return - } - vm.Qr = &qrInt - } - - err = a.GetAllGroups(vm) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - if c.Query("edit") != "" && vm.Qr != nil { - vm.IsEdit = true - err = a.db.Query("SELECT name, type, brand, description FROM assets WHERE qr = ?"). - Bind(*vm.Qr). - ScanSingle(&vm.AssetName, &vm.Type, &vm.AssetBrand, &vm.AssetDescription) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - if vm.Type == "ram" { - err = a.db.Query("SELECT type, capacity FROM info_ram WHERE asset = ?"). - Bind(*vm.Qr). - ScanSingle(&vm.RamType, &vm.RamCapacity) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - } else if vm.Type == "hdd" { - err = a.db.Query("SELECT capacity, type, form_factor, connection, rpm FROM info_hdd WHERE asset = ?"). - Bind(*vm.Qr). - ScanSingle(&vm.HddCapacity, &vm.HddType, &vm.HddFormFactor, &vm.HddConnection, &vm.HddRpm) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - } - } - - c.HTML(http.StatusOK, "create_device", vm) -} - func (a *App) postCreateDevice(c *gin.Context) { - qr, err := strconv.Atoi(c.PostForm("qr")) + qr, err := strconv.Atoi(c.PostForm("asset-qr")) if err != nil { c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("invalid qr: %v", err)) } - assetType := c.PostForm("asset_type") tx, err := a.db.Begin() defer tx.MustRollback() @@ -227,20 +32,18 @@ func (a *App) postCreateDevice(c *gin.Context) { c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error removing assets: %v", err)) return } - err = tx.Query("INSERT INTO assets (qr, type, brand, name, description) VALUES (?, ?, ?, ?, ?)"). - Bind(qr, c.PostForm("asset_type"), c.PostForm("asset_brand"), c.PostForm("asset_name"), c.PostForm("asset_description")). - Exec() + + // Insert the asset into the database + var id int + err = a.createAsset("asset", tx, c, &id) if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error inserting assets: %v", err)) + c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error inserting asset: %v", err)) return } - - if assetType == "ram" { - err = a.postCreateDeviceRam(c, qr, tx) - } else if assetType == "hdd" { - err = a.postCreateDeviceHdd(c, qr, tx) - } else { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("invalid type: %s", assetType)) + assetType := c.PostForm("asset-type") + err = a.createAsset(assetType, tx, c, &id) + if err != nil { + c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error inserting asset type: %v", err)) return } @@ -257,6 +60,49 @@ func (a *App) postCreateDevice(c *gin.Context) { c.Redirect(http.StatusSeeOther, "/") } +func (a *App) createAsset(assetType string, tx *mysqlite.Tx, c *gin.Context, assetId *int) error { + for _, assetDescriptor := range DescriptorTree.AssetTypes { + if assetDescriptor.Id != assetType { + continue + } + + var fields []string + var questions []string + var values []string + + for _, field := range assetDescriptor.Fields { + fields = append(fields, field.Id) + questions = append(questions, "?") + values = append(values, c.PostForm(assetType+"-"+field.Id)) + } + + if *assetId != 0 { + fields = append(fields, "asset") + questions = append(questions, "?") + values = append(values, strconv.Itoa(*assetId)) + } + + fieldsStr := strings.Join(fields, ", ") + questionsStr := strings.Join(questions, ", ") + err := tx.Query(fmt.Sprintf("INSERT INTO %s (%s) values (%s)", assetDescriptor.Table, fieldsStr, questionsStr)). + Bind(values). + Exec() + if err != nil { + return err + } + + if *assetId == 0 { + err := tx.Query("SELECT LAST_INSERT_ROWID()").ScanSingle(assetId) + if err != nil { + return fmt.Errorf("error getting last insert id: %v", err) + } + } + + return nil + } + return errors.New("Create Asset not implemented for type: " + assetType) +} + func (a *App) postCreateDeviceRam(c *gin.Context, qr int, tx *mysqlite.Tx) error { var err error capacity := 0 From 1dec655fb9ea8cf22f0aac3b9502f5bc5c9a14d3 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Wed, 4 Jun 2025 05:55:14 +0200 Subject: [PATCH 02/22] pcinvj --- pcinvj/pcinv/.gitattributes | 3 + pcinvj/pcinv/.gitignore | 37 +++ .../.gradle/8.14/checksums/checksums.lock | Bin 0 -> 17 bytes .../.gradle/8.14/checksums/md5-checksums.bin | Bin 0 -> 33747 bytes .../.gradle/8.14/checksums/sha1-checksums.bin | Bin 0 -> 80591 bytes .../executionHistory/executionHistory.bin | Bin 0 -> 44115 bytes .../executionHistory/executionHistory.lock | Bin 0 -> 17 bytes .../.gradle/8.14/fileChanges/last-build.bin | Bin 0 -> 1 bytes .../.gradle/8.14/fileHashes/fileHashes.bin | Bin 0 -> 19147 bytes .../.gradle/8.14/fileHashes/fileHashes.lock | Bin 0 -> 17 bytes .../8.14/fileHashes/resourceHashesCache.bin | Bin 0 -> 18565 bytes pcinvj/pcinv/.gradle/8.14/gc.properties | 0 .../buildOutputCleanup.lock | Bin 0 -> 17 bytes .../buildOutputCleanup/cache.properties | 2 + .../buildOutputCleanup/outputFiles.bin | Bin 0 -> 18929 bytes pcinvj/pcinv/.gradle/file-system.probe | Bin 0 -> 8 bytes pcinvj/pcinv/.gradle/vcs-1/gc.properties | 0 pcinvj/pcinv/.idea/.gitignore | 10 + pcinvj/pcinv/.idea/gradle.xml | 10 + pcinvj/pcinv/.idea/misc.xml | 7 + pcinvj/pcinv/.idea/modules.xml | 8 + pcinvj/pcinv/.idea/vcs.xml | 6 + pcinvj/pcinv/.idea/workspace.xml | 78 ++++++ pcinvj/pcinv/HELP.md | 30 +++ pcinvj/pcinv/build.gradle.kts | 41 +++ .../seeseepuff/pcinv/PcinvApplication.class | Bin 0 -> 742 bytes .../pcinv/controllers/WebController.class | Bin 0 -> 594 bytes .../resources/main/application.properties | 1 + .../build/resources/main/templates/index.html | 9 + .../compileJava/previous-compilation-data.bin | Bin 0 -> 32361 bytes pcinvj/pcinv/docker-compose.yml | 9 + .../pcinv/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43764 bytes .../gradle/wrapper/gradle-wrapper.properties | 7 + pcinvj/pcinv/gradlew | 251 ++++++++++++++++++ pcinvj/pcinv/gradlew.bat | 94 +++++++ pcinvj/pcinv/pcinv.iml | 8 + pcinvj/pcinv/settings.gradle.kts | 1 + .../be/seeseepuff/pcinv/PcinvApplication.java | 13 + .../pcinv/controllers/WebController.java | 12 + .../src/main/resources/application.properties | 1 + .../src/main/resources/templates/index.html | 9 + .../pcinv/PcinvApplicationTests.java | 13 + 42 files changed, 660 insertions(+) create mode 100644 pcinvj/pcinv/.gitattributes create mode 100644 pcinvj/pcinv/.gitignore create mode 100644 pcinvj/pcinv/.gradle/8.14/checksums/checksums.lock create mode 100644 pcinvj/pcinv/.gradle/8.14/checksums/md5-checksums.bin create mode 100644 pcinvj/pcinv/.gradle/8.14/checksums/sha1-checksums.bin create mode 100644 pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.bin create mode 100644 pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.lock create mode 100644 pcinvj/pcinv/.gradle/8.14/fileChanges/last-build.bin create mode 100644 pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.bin create mode 100644 pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.lock create mode 100644 pcinvj/pcinv/.gradle/8.14/fileHashes/resourceHashesCache.bin create mode 100644 pcinvj/pcinv/.gradle/8.14/gc.properties create mode 100644 pcinvj/pcinv/.gradle/buildOutputCleanup/buildOutputCleanup.lock create mode 100644 pcinvj/pcinv/.gradle/buildOutputCleanup/cache.properties create mode 100644 pcinvj/pcinv/.gradle/buildOutputCleanup/outputFiles.bin create mode 100644 pcinvj/pcinv/.gradle/file-system.probe create mode 100644 pcinvj/pcinv/.gradle/vcs-1/gc.properties create mode 100644 pcinvj/pcinv/.idea/.gitignore create mode 100644 pcinvj/pcinv/.idea/gradle.xml create mode 100644 pcinvj/pcinv/.idea/misc.xml create mode 100644 pcinvj/pcinv/.idea/modules.xml create mode 100644 pcinvj/pcinv/.idea/vcs.xml create mode 100644 pcinvj/pcinv/.idea/workspace.xml create mode 100644 pcinvj/pcinv/HELP.md create mode 100644 pcinvj/pcinv/build.gradle.kts create mode 100644 pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/PcinvApplication.class create mode 100644 pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/controllers/WebController.class create mode 100644 pcinvj/pcinv/build/resources/main/application.properties create mode 100644 pcinvj/pcinv/build/resources/main/templates/index.html create mode 100644 pcinvj/pcinv/build/tmp/compileJava/previous-compilation-data.bin create mode 100644 pcinvj/pcinv/docker-compose.yml create mode 100644 pcinvj/pcinv/gradle/wrapper/gradle-wrapper.jar create mode 100644 pcinvj/pcinv/gradle/wrapper/gradle-wrapper.properties create mode 100755 pcinvj/pcinv/gradlew create mode 100644 pcinvj/pcinv/gradlew.bat create mode 100644 pcinvj/pcinv/pcinv.iml create mode 100644 pcinvj/pcinv/settings.gradle.kts create mode 100644 pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/PcinvApplication.java create mode 100644 pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java create mode 100644 pcinvj/pcinv/src/main/resources/application.properties create mode 100644 pcinvj/pcinv/src/main/resources/templates/index.html create mode 100644 pcinvj/pcinv/src/test/java/be/seeseepuff/pcinv/PcinvApplicationTests.java diff --git a/pcinvj/pcinv/.gitattributes b/pcinvj/pcinv/.gitattributes new file mode 100644 index 0000000..8af972c --- /dev/null +++ b/pcinvj/pcinv/.gitattributes @@ -0,0 +1,3 @@ +/gradlew text eol=lf +*.bat text eol=crlf +*.jar binary diff --git a/pcinvj/pcinv/.gitignore b/pcinvj/pcinv/.gitignore new file mode 100644 index 0000000..c2065bc --- /dev/null +++ b/pcinvj/pcinv/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/pcinvj/pcinv/.gradle/8.14/checksums/checksums.lock b/pcinvj/pcinv/.gradle/8.14/checksums/checksums.lock new file mode 100644 index 0000000000000000000000000000000000000000..61a5ef652c6e236430f940d8a9fa9ea9b1d88945 GIT binary patch literal 17 VcmZS1pUHgUt?<({1~6cL4*)ag1rz`P literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/.gradle/8.14/checksums/md5-checksums.bin b/pcinvj/pcinv/.gradle/8.14/checksums/md5-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..e5894350b5ebe9953efed2a64363a607f528d648 GIT binary patch literal 33747 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 an@RUwI7~x4;pgpKbRcF}VR8nQ%>M@fS6T4@ literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/.gradle/8.14/checksums/sha1-checksums.bin b/pcinvj/pcinv/.gradle/8.14/checksums/sha1-checksums.bin new file mode 100644 index 0000000000000000000000000000000000000000..6198bf27316a255e5645fec4f88152659adfc12b GIT binary patch literal 80591 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 MARFw1$O+8<0eGNIuK)l5 literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.bin b/pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.bin new file mode 100644 index 0000000000000000000000000000000000000000..c74e9673e3bf93ca2a41977b1d477d8daa5076e0 GIT binary patch literal 44115 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 literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.lock b/pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.lock new file mode 100644 index 0000000000000000000000000000000000000000..1d30d9a9a3fd94538b1192327af4a0fffc84be53 GIT binary patch literal 17 UcmZQ>-t#(P{)XQ_86bcK07l6L)c^nh literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/.gradle/8.14/fileChanges/last-build.bin b/pcinvj/pcinv/.gradle/8.14/fileChanges/last-build.bin new file mode 100644 index 0000000000000000000000000000000000000000..f76dd238ade08917e6712764a16a22005a50573d GIT binary patch literal 1 IcmZPo000310RR91 literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.bin b/pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.bin new file mode 100644 index 0000000000000000000000000000000000000000..edab974799db092f87a2d3bcc42f7144bae0eea8 GIT binary patch literal 19147 zcmeI(T}V?=00;0hZPd!GiH63OROo|dADYX_YM>N3^C`uPm|9`8_Oe z%~#*FY8(x8zAwuyfrkR>%?HhN{xz06NORS|?4d}SOIZH#{E^8pi9MI*`&s_IIyu!G z64*}PU&V5fDJDOvYC|8NCz@EUh^bxLdGXsRn#Z#|r9AYODyg)E<^wFxEf-hxU2&E0 zdD1187xe41w;z2SN9X6WoLvbK>G*qig#ZK~009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SNpXfz{kH;oP~zeadwsgS$o> z=Zzn5J0aI4KC8y_S;V1tx$6q=hURiZN4fr4-lL2Pb7q>f*Q-H#yM&Fz zAH!`VrTNan&&tBslW7574e^{xVPmvR?KD}%Ih|*f5p6zxBVLW5qDEokYF2&C+S;!} zwTo88irOzsdRa4*yLVNkbbD#Ii@T+59QgJkcWo`yU+%Tpkt&BDf( zo`O#kWs6&!3Ekla|NGbZhWZuX@OdL_SRKh3?`-Lo8(mLQihcd#X~RJqGpx}>%IXH! zG(?@NdwXwe--j{2p;hvY6YeJ_rtKr>@J7ooy*>L&aZjgBwkOrokZKfsgM|G87dJte_e$)nS0qgdFOaSgfF{l#z}f4zoQ#TSTYjX%R8P_h63 literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.lock b/pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.lock new file mode 100644 index 0000000000000000000000000000000000000000..e213f32f650cb2e55fae7f0390e8b9935754e423 GIT binary patch literal 17 TcmZRswrxp$vc}zs0Rlt;FChcd literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/.gradle/8.14/fileHashes/resourceHashesCache.bin b/pcinvj/pcinv/.gradle/8.14/fileHashes/resourceHashesCache.bin new file mode 100644 index 0000000000000000000000000000000000000000..cd45c02f9afe3fd8e2f98c1d7ea1312539e965af GIT binary patch literal 18565 zcmeI%u}Xqb6ae6hV+fj1*dpyZ1gHcCYxN{at^CbEVpwDSblw;<58LzUANYNe^oOn$=T0>4;3)y)s+-@?2R# zZ)~_#_WQJN9K9D^JF$LB_P^G?^|odq^yAZa>3g($ye0XeSnH!C1g%md#P)=cc`W_UG|Uv>dldhhv)nkq<_?2)z#1w zNt@X(a|X57CMt5S1g6ePZ`NM_rt$I1cwvsrxugC1kzC({@I+kpPit>q_bvvL8#?Ey z_Kv0T>Cwb9r|eH^4?NF&SWrK3Q05=kuHVL_`t56cB7gt_2q1s}0tg_000IagfB*sr zAb + + + + + \ No newline at end of file diff --git a/pcinvj/pcinv/.idea/misc.xml b/pcinvj/pcinv/.idea/misc.xml new file mode 100644 index 0000000..5cd9a10 --- /dev/null +++ b/pcinvj/pcinv/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/pcinvj/pcinv/.idea/modules.xml b/pcinvj/pcinv/.idea/modules.xml new file mode 100644 index 0000000..ae57db8 --- /dev/null +++ b/pcinvj/pcinv/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/pcinvj/pcinv/.idea/vcs.xml b/pcinvj/pcinv/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/pcinvj/pcinv/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/pcinvj/pcinv/.idea/workspace.xml b/pcinvj/pcinv/.idea/workspace.xml new file mode 100644 index 0000000..6c86464 --- /dev/null +++ b/pcinvj/pcinv/.idea/workspace.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1739869040002 + + + + + + + false + true + + \ No newline at end of file diff --git a/pcinvj/pcinv/HELP.md b/pcinvj/pcinv/HELP.md new file mode 100644 index 0000000..1fdb4b5 --- /dev/null +++ b/pcinvj/pcinv/HELP.md @@ -0,0 +1,30 @@ +# Getting Started + +### Reference Documentation + +For further reference, please consider the following sections: + +* [Official Gradle documentation](https://docs.gradle.org) +* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/3.5.0/gradle-plugin) +* [Create an OCI image](https://docs.spring.io/spring-boot/3.5.0/gradle-plugin/packaging-oci-image.html) +* [Spring Configuration Processor](https://docs.spring.io/spring-boot/3.5.0/specification/configuration-metadata/annotation-processor.html) +* [Spring Data JPA](https://docs.spring.io/spring-boot/3.5.0/reference/data/sql.html#data.sql.jpa-and-spring-data) +* [Spring Boot DevTools](https://docs.spring.io/spring-boot/3.5.0/reference/using/devtools.html) +* [Thymeleaf](https://docs.spring.io/spring-boot/3.5.0/reference/web/servlet.html#web.servlet.spring-mvc.template-engines) +* [Spring Web](https://docs.spring.io/spring-boot/3.5.0/reference/web/servlet.html) + +### Guides + +The following guides illustrate how to use some features concretely: + +* [Accessing Data with JPA](https://spring.io/guides/gs/accessing-data-jpa/) +* [Handling Form Submission](https://spring.io/guides/gs/handling-form-submission/) +* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) +* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) +* [Building REST services with Spring](https://spring.io/guides/tutorials/rest/) + +### Additional Links + +These additional references should also help you: + +* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) diff --git a/pcinvj/pcinv/build.gradle.kts b/pcinvj/pcinv/build.gradle.kts new file mode 100644 index 0000000..9e56f52 --- /dev/null +++ b/pcinvj/pcinv/build.gradle.kts @@ -0,0 +1,41 @@ +plugins { + java + id("org.springframework.boot") version "3.5.0" + id("io.spring.dependency-management") version "1.1.7" +} + +group = "be.seeseepuff" +version = "0.0.1-SNAPSHOT" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +configurations { + compileOnly { + extendsFrom(configurations.annotationProcessor.get()) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-data-jpa") + implementation("org.springframework.boot:spring-boot-starter-thymeleaf") + implementation("org.springframework.boot:spring-boot-starter-web") + compileOnly("org.projectlombok:lombok") + developmentOnly("org.springframework.boot:spring-boot-devtools") + runtimeOnly("org.postgresql:postgresql") + annotationProcessor("org.springframework.boot:spring-boot-configuration-processor") + annotationProcessor("org.projectlombok:lombok") + testImplementation("org.springframework.boot:spring-boot-starter-test") + testRuntimeOnly("org.junit.platform:junit-platform-launcher") +} + +tasks.withType { + useJUnitPlatform() +} diff --git a/pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/PcinvApplication.class b/pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/PcinvApplication.class new file mode 100644 index 0000000000000000000000000000000000000000..8581ee05a4e13b79acfa245bb7fcfdfc2734271f GIT binary patch literal 742 zcma)4OHbQC5dJnKIE1u7c$8NUl@JO!us1HLkZ7cy3`GQm1E;mwI1A3MwO&L2R!^WF z_yPPV)L9n^tIDC)dPX~sZ)U%lm)GCV08a3^g&O7p)Z1uao?-b`+zXxxo$w#gt&APR z{8y!wJ7$>c9*hDs8TKN{b1CU$#dyrKSm}Fy?&V3Ar79Lq8Qnq)ivikge85MBp0Nqf zGplqmwqhzDjJ@NLF^&&QVjt&J;3LIB4z*(&EC3TKG6k}ml+m2Whl3T=ISK|l-RzZ#fd8IntMD{1|6_V}00q9_rZVOn$I)V7)qY?~gc7om$7Ovj&{dR&DDZpX{qlpcCBKs!& T4Puv64O^w$#tvy;b{F^q^}@i) literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/controllers/WebController.class b/pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/controllers/WebController.class new file mode 100644 index 0000000000000000000000000000000000000000..f8699304af36df5ef57493e9832a2110f7afdc71 GIT binary patch literal 594 zcmb7BO-~y!5PikaiB?&xL~bk^k&}k^LYP0-2VZvhfgtTs7GjY&_qjMd88&v=E@A^Njjp;3$(s# zqy4TxeWTZJqb<B#0pZJ)tLHE8viG)KTAJwcaa5M-YY*LO02SG NZK_;H=my)5z#|F!jfnsN literal 0 HcmV?d00001 diff --git a/pcinvj/pcinv/build/resources/main/application.properties b/pcinvj/pcinv/build/resources/main/application.properties new file mode 100644 index 0000000..c127059 --- /dev/null +++ b/pcinvj/pcinv/build/resources/main/application.properties @@ -0,0 +1 @@ +spring.application.name=pcinv diff --git a/pcinvj/pcinv/build/resources/main/templates/index.html b/pcinvj/pcinv/build/resources/main/templates/index.html new file mode 100644 index 0000000..a4ee839 --- /dev/null +++ b/pcinvj/pcinv/build/resources/main/templates/index.html @@ -0,0 +1,9 @@ + + + + PC Inventory + + +

    Hello, world!

    + + diff --git a/pcinvj/pcinv/build/tmp/compileJava/previous-compilation-data.bin b/pcinvj/pcinv/build/tmp/compileJava/previous-compilation-data.bin new file mode 100644 index 0000000000000000000000000000000000000000..b1e8211c354558f4b48e784d529474edc735342b GIT binary patch literal 32361 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/pcinvj/pcinv/gradlew.bat b/pcinvj/pcinv/gradlew.bat new file mode 100644 index 0000000..db3a6ac --- /dev/null +++ b/pcinvj/pcinv/gradlew.bat @@ -0,0 +1,94 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/pcinvj/pcinv/pcinv.iml b/pcinvj/pcinv/pcinv.iml new file mode 100644 index 0000000..482334b --- /dev/null +++ b/pcinvj/pcinv/pcinv.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/pcinvj/pcinv/settings.gradle.kts b/pcinvj/pcinv/settings.gradle.kts new file mode 100644 index 0000000..3134321 --- /dev/null +++ b/pcinvj/pcinv/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "pcinv" diff --git a/pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/PcinvApplication.java b/pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/PcinvApplication.java new file mode 100644 index 0000000..1aabf6f --- /dev/null +++ b/pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/PcinvApplication.java @@ -0,0 +1,13 @@ +package be.seeseepuff.pcinv; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class PcinvApplication { + + public static void main(String[] args) { + SpringApplication.run(PcinvApplication.class, args); + } + +} diff --git a/pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java new file mode 100644 index 0000000..7c6d0df --- /dev/null +++ b/pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -0,0 +1,12 @@ +package be.seeseepuff.pcinv.controllers; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class WebController { + @GetMapping("/") + public String index() { + return "index"; // This will resolve to src/main/resources/templates/index.html + } +} diff --git a/pcinvj/pcinv/src/main/resources/application.properties b/pcinvj/pcinv/src/main/resources/application.properties new file mode 100644 index 0000000..c127059 --- /dev/null +++ b/pcinvj/pcinv/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.application.name=pcinv diff --git a/pcinvj/pcinv/src/main/resources/templates/index.html b/pcinvj/pcinv/src/main/resources/templates/index.html new file mode 100644 index 0000000..a4ee839 --- /dev/null +++ b/pcinvj/pcinv/src/main/resources/templates/index.html @@ -0,0 +1,9 @@ + + + + PC Inventory + + +

    Hello, world!

    + + diff --git a/pcinvj/pcinv/src/test/java/be/seeseepuff/pcinv/PcinvApplicationTests.java b/pcinvj/pcinv/src/test/java/be/seeseepuff/pcinv/PcinvApplicationTests.java new file mode 100644 index 0000000..396ca1c --- /dev/null +++ b/pcinvj/pcinv/src/test/java/be/seeseepuff/pcinv/PcinvApplicationTests.java @@ -0,0 +1,13 @@ +package be.seeseepuff.pcinv; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class PcinvApplicationTests { + + @Test + void contextLoads() { + } + +} From ed68ead1fba525567840157e2855bb4cf2543a96 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Wed, 4 Jun 2025 08:59:01 +0200 Subject: [PATCH 03/22] Move files around --- pcinvj/pcinv/.gitattributes => .gitattributes | 0 .gitignore | 41 ++- Dockerfile | 13 - .../build.gradle.kts => build.gradle.kts | 0 data.go | 124 -------- descriptors.go | 130 --------- .../docker-compose.yml => docker-compose.yml | 0 go.mod | 48 --- go.sum | 170 ----------- .../wrapper/gradle-wrapper.jar | Bin .../wrapper/gradle-wrapper.properties | 0 pcinvj/pcinv/gradlew => gradlew | 0 pcinvj/pcinv/gradlew.bat => gradlew.bat | 0 main.go | 94 ------ migrations/1_initial.sql | 19 -- migrations/2_add_harddrives.sql | 8 - pcinvj/pcinv/.gitignore | 37 --- .../.gradle/8.14/checksums/checksums.lock | Bin 17 -> 0 bytes .../.gradle/8.14/checksums/md5-checksums.bin | Bin 33747 -> 0 bytes .../.gradle/8.14/checksums/sha1-checksums.bin | Bin 80591 -> 0 bytes .../executionHistory/executionHistory.bin | Bin 44115 -> 0 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 0 bytes .../.gradle/8.14/fileChanges/last-build.bin | Bin 1 -> 0 bytes .../.gradle/8.14/fileHashes/fileHashes.bin | Bin 19147 -> 0 bytes .../.gradle/8.14/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes .../8.14/fileHashes/resourceHashesCache.bin | Bin 18565 -> 0 bytes pcinvj/pcinv/.gradle/8.14/gc.properties | 0 .../buildOutputCleanup.lock | Bin 17 -> 0 bytes .../buildOutputCleanup/cache.properties | 2 - .../buildOutputCleanup/outputFiles.bin | Bin 18929 -> 0 bytes pcinvj/pcinv/.gradle/file-system.probe | Bin 8 -> 0 bytes pcinvj/pcinv/.gradle/vcs-1/gc.properties | 0 pcinvj/pcinv/HELP.md | 30 -- .../seeseepuff/pcinv/PcinvApplication.class | Bin 742 -> 0 bytes .../pcinv/controllers/WebController.class | Bin 594 -> 0 bytes .../compileJava/previous-compilation-data.bin | Bin 32361 -> 0 bytes pcinvj/pcinv/pcinv.iml | 8 - .../src/main/resources/application.properties | 1 - .../src/main/resources/templates/index.html | 9 - ...settings.gradle.kts => settings.gradle.kts | 0 .../be/seeseepuff/pcinv/PcinvApplication.java | 0 .../pcinv/controllers/WebController.java | 0 .../main/resources}/application.properties | 0 .../main/resources}/templates/index.html | 0 .../pcinv/PcinvApplicationTests.java | 0 static/scripts.js | 23 -- template_debug.go | 8 - template_funcs.go | 119 -------- templates/browse.gohtml | 31 -- templates/create_device.gohtml | 8 - templates/create_device_step1.gohtml | 20 -- templates/create_device_step2.gohtml | 188 ------------ templates/delete.gohtml | 9 - templates/device.gohtml | 57 ---- templates/errors.gohtml | 12 - templates/footer.gohtml | 4 - templates/header.gohtml | 16 - templates/index.gohtml | 34 --- view_createdevice.go | 135 --------- view_device.go | 73 ----- view_index.go | 50 ---- views.go | 276 ------------------ 62 files changed, 36 insertions(+), 1761 deletions(-) rename pcinvj/pcinv/.gitattributes => .gitattributes (100%) delete mode 100644 Dockerfile rename pcinvj/pcinv/build.gradle.kts => build.gradle.kts (100%) delete mode 100644 data.go delete mode 100644 descriptors.go rename pcinvj/pcinv/docker-compose.yml => docker-compose.yml (100%) delete mode 100644 go.mod delete mode 100644 go.sum rename {pcinvj/pcinv/gradle => gradle}/wrapper/gradle-wrapper.jar (100%) rename {pcinvj/pcinv/gradle => gradle}/wrapper/gradle-wrapper.properties (100%) rename pcinvj/pcinv/gradlew => gradlew (100%) rename pcinvj/pcinv/gradlew.bat => gradlew.bat (100%) delete mode 100644 main.go delete mode 100644 migrations/1_initial.sql delete mode 100644 migrations/2_add_harddrives.sql delete mode 100644 pcinvj/pcinv/.gitignore delete mode 100644 pcinvj/pcinv/.gradle/8.14/checksums/checksums.lock delete mode 100644 pcinvj/pcinv/.gradle/8.14/checksums/md5-checksums.bin delete mode 100644 pcinvj/pcinv/.gradle/8.14/checksums/sha1-checksums.bin delete mode 100644 pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.bin delete mode 100644 pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.lock delete mode 100644 pcinvj/pcinv/.gradle/8.14/fileChanges/last-build.bin delete mode 100644 pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.bin delete mode 100644 pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.lock delete mode 100644 pcinvj/pcinv/.gradle/8.14/fileHashes/resourceHashesCache.bin delete mode 100644 pcinvj/pcinv/.gradle/8.14/gc.properties delete mode 100644 pcinvj/pcinv/.gradle/buildOutputCleanup/buildOutputCleanup.lock delete mode 100644 pcinvj/pcinv/.gradle/buildOutputCleanup/cache.properties delete mode 100644 pcinvj/pcinv/.gradle/buildOutputCleanup/outputFiles.bin delete mode 100644 pcinvj/pcinv/.gradle/file-system.probe delete mode 100644 pcinvj/pcinv/.gradle/vcs-1/gc.properties delete mode 100644 pcinvj/pcinv/HELP.md delete mode 100644 pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/PcinvApplication.class delete mode 100644 pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/controllers/WebController.class delete mode 100644 pcinvj/pcinv/build/tmp/compileJava/previous-compilation-data.bin delete mode 100644 pcinvj/pcinv/pcinv.iml delete mode 100644 pcinvj/pcinv/src/main/resources/application.properties delete mode 100644 pcinvj/pcinv/src/main/resources/templates/index.html rename pcinvj/pcinv/settings.gradle.kts => settings.gradle.kts (100%) rename {pcinvj/pcinv/src => src}/main/java/be/seeseepuff/pcinv/PcinvApplication.java (100%) rename {pcinvj/pcinv/src => src}/main/java/be/seeseepuff/pcinv/controllers/WebController.java (100%) rename {pcinvj/pcinv/build/resources/main => src/main/resources}/application.properties (100%) rename {pcinvj/pcinv/build/resources/main => src/main/resources}/templates/index.html (100%) rename {pcinvj/pcinv/src => src}/test/java/be/seeseepuff/pcinv/PcinvApplicationTests.java (100%) delete mode 100644 static/scripts.js delete mode 100644 template_debug.go delete mode 100644 template_funcs.go delete mode 100644 templates/browse.gohtml delete mode 100644 templates/create_device.gohtml delete mode 100644 templates/create_device_step1.gohtml delete mode 100644 templates/create_device_step2.gohtml delete mode 100644 templates/delete.gohtml delete mode 100644 templates/device.gohtml delete mode 100644 templates/errors.gohtml delete mode 100644 templates/footer.gohtml delete mode 100644 templates/header.gohtml delete mode 100644 templates/index.gohtml delete mode 100644 view_createdevice.go delete mode 100644 view_device.go delete mode 100644 view_index.go delete mode 100644 views.go diff --git a/pcinvj/pcinv/.gitattributes b/.gitattributes similarity index 100% rename from pcinvj/pcinv/.gitattributes rename to .gitattributes diff --git a/.gitignore b/.gitignore index a47f9f7..c2065bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,37 @@ -*.db3 -*.db3-* -*.db3.* -*.iml +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### .idea -pcinv +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 752719c..0000000 --- a/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM golang:1.24.1-alpine3.21 - -WORKDIR /app -COPY go.mod go.sum ./ -RUN go mod download - -COPY migrations ./migrations -COPY static ./static -COPY templates ./templates -COPY *.go ./ -RUN go build -o /pcinv - -CMD ["/pcinv"] diff --git a/pcinvj/pcinv/build.gradle.kts b/build.gradle.kts similarity index 100% rename from pcinvj/pcinv/build.gradle.kts rename to build.gradle.kts diff --git a/data.go b/data.go deleted file mode 100644 index df94ecd..0000000 --- a/data.go +++ /dev/null @@ -1,124 +0,0 @@ -package main - -import ( - "fmt" - "gitea.seeseepuff.be/seeseemelk/mysqlite" -) - -func (a *App) getAllTypes(column, table string) ([]string, error) { - var types []string - var err error - for row := range a.db.Query(fmt.Sprintf("SELECT %s FROM %s GROUP BY %s ORDER BY %s ASC", column, table, column, column)).Range(&err) { - var name string - err := row.Scan(&name) - if err != nil { - return nil, err - } - types = append(types, name) - } - return types, err -} - -func (a *App) GetAllBrands() ([]string, error) { - return a.getAllTypes("brand", "assets") -} - -func (a *App) GetAllRamTypes() ([]string, error) { - return a.getAllTypes("type", "info_ram") -} - -func (a *App) GetAllHddTypes() ([]string, error) { - return a.getAllTypes("type", "info_hdd") -} - -func (a *App) GetAllHddFormFactors() ([]string, error) { - return a.getAllTypes("form_factor", "info_hdd") -} - -func (a *App) GetAllHddConnections() ([]string, error) { - return a.getAllTypes("connection", "info_hdd") -} - -func (a *App) GetAllHddSpeeds() ([]string, error) { - return a.getAllTypes("rpm", "info_hdd") -} - -func (a *App) GetAllGroups(vm *CreateDeviceVM) error { - var err error - //vm.AssetBrands, err = a.GetAllBrands() - //if err != nil { - // return err - //} - // - //vm.RamTypes, err = a.GetAllRamTypes() - //if err != nil { - // return err - //} - // - //vm.HddTypes, err = a.GetAllHddTypes() - //if err != nil { - // return err - //} - // - //vm.HddFormFactors, err = a.GetAllHddFormFactors() - //if err != nil { - // return err - //} - // - //vm.HddFormFactors, err = a.GetAllHddConnections() - //if err != nil { - // return err - //} - // - //vm.HddRpms, err = a.GetAllHddSpeeds() - if err != nil { - return err - } - return nil -} - -func (a *App) GetAllTypes() ([]string, error) { - var types []string - var err error - for row := range a.db.Query("SELECT type FROM assets GROUP BY type ORDER BY type ASC").Range(&err) { - var name string - err := row.Scan(&name) - if err != nil { - return nil, err - } - types = append(types, name) - } - return types, err -} - -func (a *App) GetAssetCount() (int, error) { - var count int - err := a.db.Query("SELECT COUNT(*) FROM assets").ScanSingle(&count) - return count, err -} - -func (a *App) GetBrandCount() (int, error) { - var count int - err := a.db.Query("SELECT COUNT(DISTINCT brand) FROM assets").ScanSingle(&count) - return count, err -} - -func (a *App) GetTotalRamCapacity() (int, error) { - var capacity int - err := a.db.Query("SELECT SUM(capacity) FROM info_ram").ScanSingle(&capacity) - return capacity, err -} - -func (a *App) DeleteAsset(tx *mysqlite.Tx, qr int) error { - err := tx.Query("DELETE FROM assets WHERE qr=?").Bind(qr).Exec() - if err != nil { - return err - } - - err = tx.Query("DELETE FROM info_ram WHERE asset=?").Bind(qr).Exec() - if err != nil { - return err - } - - return nil -} diff --git a/descriptors.go b/descriptors.go deleted file mode 100644 index 3ab2e1d..0000000 --- a/descriptors.go +++ /dev/null @@ -1,130 +0,0 @@ -package main - -var DescriptorTree = createDescriptorTree(DescriptorRoot{ - AssetTypes: []AssetType{ - { - Id: "asset", - Table: "assets", - Name: "General Information", - Abstract: true, - Fields: []AssetField{ - { - Id: "qr", - Name: "QR Code", - Type: Number, - }, - { - Id: "type", - Name: "Type", - Type: Type, - }, - { - Id: "brand", - Name: "Brand", - Type: Selection, - }, - { - Id: "name", - Name: "Name", - Type: String, - }, - { - Id: "description", - Name: "Description", - Type: String, - }, - }, - }, - { - Id: "ram", - Name: "Random Access Memory", - Table: "info_ram", - Fields: []AssetField{ - { - Id: "type", - Name: "Type", - Type: String, - }, - { - Id: "capacity", - Name: "Capacity", - Type: Capacity, - }, - }, - }, - { - Id: "hdd", - Name: "Hard Disk Drive", - Table: "info_hdd", - Fields: []AssetField{ - { - Id: "type", - Name: "Type", - Type: Selection, - }, - { - Id: "capacity", - Name: "Capacity", - Type: Capacity, - }, - { - Id: "form_factor", - Name: "Form Factor", - Type: Selection, - }, - { - Id: "interface", - Name: "Interface", - Type: Selection, - }, - { - Id: "rpm", - Name: "RPM", - Type: Selection, - }, - }, - }, - }, -}) - -func createDescriptorTree(tree DescriptorRoot) *DescriptorRoot { - return &tree -} - -func getAssetTypeById(id string) *AssetType { - for _, assetType := range DescriptorTree.AssetTypes { - if assetType.Id == id { - return &assetType - } - } - return nil -} - -type DescriptorRoot struct { - AssetTypes []AssetType -} - -type AssetType struct { - Id string - Name string - Table string - Fields []AssetField - Abstract bool -} - -type AssetField struct { - Id string - Name string - Type FieldType -} - -type FieldType string - -const ( - Number FieldType = "number" - String = "string" - Type = "type" - Selection = "selection" - Bool = "bool" - Capacity = "capacity" -) diff --git a/pcinvj/pcinv/docker-compose.yml b/docker-compose.yml similarity index 100% rename from pcinvj/pcinv/docker-compose.yml rename to docker-compose.yml diff --git a/go.mod b/go.mod deleted file mode 100644 index ae21222..0000000 --- a/go.mod +++ /dev/null @@ -1,48 +0,0 @@ -module pcinv - -go 1.24 - -toolchain go1.24.1 - -require ( - gitea.seeseepuff.be/seeseemelk/mysqlite v0.15.0 - github.com/gin-gonic/gin v1.10.1 -) - -require ( - github.com/bytedance/sonic v1.13.2 // indirect - github.com/bytedance/sonic/loader v0.2.4 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/gabriel-vasile/mimetype v1.4.9 // indirect - github.com/gin-contrib/sse v1.1.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.26.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/ncruces/go-strftime v0.1.9 // indirect - github.com/pelletier/go-toml/v2 v2.2.4 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.14 // indirect - golang.org/x/arch v0.17.0 // indirect - golang.org/x/crypto v0.38.0 // indirect - golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.25.0 // indirect - google.golang.org/protobuf v1.36.6 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - modernc.org/libc v1.65.8 // indirect - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect - modernc.org/sqlite v1.37.1 // indirect - zombiezen.com/go/sqlite v1.4.2 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 98e21e1..0000000 --- a/go.sum +++ /dev/null @@ -1,170 +0,0 @@ -gitea.seeseepuff.be/seeseemelk/mysqlite v0.9.0 h1:GaU2DSrgDfZEqST3HdnNgfKSI4sNXvMm8SSfeMvBxA4= -gitea.seeseepuff.be/seeseemelk/mysqlite v0.9.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= -gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0 h1:aRItVfUj48fBmuec7rm/jY9KCfvHW2VzJfItVk4t8sw= -gitea.seeseepuff.be/seeseemelk/mysqlite v0.14.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= -gitea.seeseepuff.be/seeseemelk/mysqlite v0.15.0 h1:+k0iBYM/aZJxz7++EKi/G9e66E9u4bPS3DFLrBeDb9Y= -gitea.seeseepuff.be/seeseemelk/mysqlite v0.15.0/go.mod h1:cgswydOxJjMlNwfcBIXnKjr47LwXnMT9BInkiHb0tXE= -github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= -github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= -github.com/bytedance/sonic v1.13.2/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= -github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= -github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= -github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= -github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= -github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= -github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= -github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= -github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= -github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= -github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= -github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= -github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= -github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= -github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/ugorji/go/codec v1.2.14 h1:yOQvXCBc3Ij46LRkRoh4Yd5qK6LVOgi0bYOXfb7ifjw= -github.com/ugorji/go/codec v1.2.14/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= -golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= -golang.org/x/arch v0.17.0 h1:4O3dfLzd+lQewptAHqjewQZQDyEdejz3VwgeYwkZneU= -golang.org/x/arch v0.17.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= -golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= -golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA= -golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= -modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= -modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= -modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= -modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= -modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= -modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= -modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= -modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= -modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= -modernc.org/libc v1.65.8 h1:7PXRJai0TXZ8uNA3srsmYzmTyrLoHImV5QxHeni108Q= -modernc.org/libc v1.65.8/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU= -modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= -modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= -modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= -modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= -modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= -modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= -modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= -modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= -modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= -modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= -modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= -modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs= -modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g= -modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= -modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -zombiezen.com/go/sqlite v1.4.0 h1:N1s3RIljwtp4541Y8rM880qgGIgq3fTD2yks1xftnKU= -zombiezen.com/go/sqlite v1.4.0/go.mod h1:0w9F1DN9IZj9AcLS9YDKMboubCACkwYCGkzoy3eG5ik= -zombiezen.com/go/sqlite v1.4.2 h1:KZXLrBuJ7tKNEm+VJcApLMeQbhmAUOKA5VWS93DfFRo= -zombiezen.com/go/sqlite v1.4.2/go.mod h1:5Kd4taTAD4MkBzT25mQ9uaAlLjyR0rFhsR6iINO70jc= diff --git a/pcinvj/pcinv/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar similarity index 100% rename from pcinvj/pcinv/gradle/wrapper/gradle-wrapper.jar rename to gradle/wrapper/gradle-wrapper.jar diff --git a/pcinvj/pcinv/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from pcinvj/pcinv/gradle/wrapper/gradle-wrapper.properties rename to gradle/wrapper/gradle-wrapper.properties diff --git a/pcinvj/pcinv/gradlew b/gradlew similarity index 100% rename from pcinvj/pcinv/gradlew rename to gradlew diff --git a/pcinvj/pcinv/gradlew.bat b/gradlew.bat similarity index 100% rename from pcinvj/pcinv/gradlew.bat rename to gradlew.bat diff --git a/main.go b/main.go deleted file mode 100644 index 14526ea..0000000 --- a/main.go +++ /dev/null @@ -1,94 +0,0 @@ -package main - -import ( - "embed" - "html/template" - "log" - "net/http" - "os" - - "gitea.seeseepuff.be/seeseemelk/mysqlite" - "github.com/gin-gonic/gin" -) - -//go:embed migrations/*.sql -var migrations embed.FS - -//go:embed templates/*.gohtml -var templateFS embed.FS - -func main() { - log.Println("Starting...") - - // Get database path from environment variable or use default - dbPath := os.Getenv("PCINV_DB") - if dbPath == "" { - dbPath = "pcinv.db3" - } - - db, err := mysqlite.OpenDb(dbPath) - if err != nil { - log.Fatalf("error opening db: %v", err) - } - defer db.MustClose() - - err = db.MigrateDb(migrations, "migrations") - if err != nil { - log.Fatalf("error migrating db: %v", err) - } - - app := &App{ - db: db, - } - - templates, err := template.New("undefined.gohtml"). - Funcs(template.FuncMap{ - "statusText": http.StatusText, - "createDeviceLink": createDeviceLink, - "formatMemorySize": formatMemorySize, - "formatMemoryPlainSize": formatMemoryPlainSize, - "formatType": formatType, - "isRamType": isRamType, - "createSelectMenu": createSelectMenu, - "createSelectMenuDefault": createSelectMenuDefault, - }). - ParseFS(templateFS, "templates/*.gohtml") - - if err != nil { - log.Fatalf("error parsing templates: %v", err) - } - - gin.DebugPrintFunc = log.Printf - gin.SetMode(gin.ReleaseMode) - r := gin.Default() - r.SetHTMLTemplate(templates) - r.StaticFS("/static", staticFiles) - r.Use(errorHandler) - r.GET("/", app.getIndex) - r.GET("/device", app.getDevice) - r.GET("/create", app.getCreateDevice) - r.POST("/create", app.postCreateDevice) - r.GET("/browse", app.getBrowse) - r.GET("/delete", app.getDelete) - r.POST("/delete", app.postDelete) - err = r.Run() - if err != nil { - log.Fatalf("error serving website: %v", err) - } -} - -type ErrorVM struct { - Errors []*gin.Error - StatusCode int -} - -func errorHandler(c *gin.Context) { - c.Next() - if len(c.Errors) != 0 { - vm := ErrorVM{ - Errors: c.Errors, - StatusCode: c.Writer.Status(), - } - c.HTML(vm.StatusCode, "errors", vm) - } -} diff --git a/migrations/1_initial.sql b/migrations/1_initial.sql deleted file mode 100644 index d6a9938..0000000 --- a/migrations/1_initial.sql +++ /dev/null @@ -1,19 +0,0 @@ -create table assets ( - qr integer unique, - type text not null, - brand text, - name text, - description text -) strict; - -create table worklog ( - asset integer not null, - timestamp integer not null, - action text not null -) strict; - -create table info_ram ( - asset integer not null unique, - capacity integer, - type text -) strict; diff --git a/migrations/2_add_harddrives.sql b/migrations/2_add_harddrives.sql deleted file mode 100644 index d2b0225..0000000 --- a/migrations/2_add_harddrives.sql +++ /dev/null @@ -1,8 +0,0 @@ -create table info_hdd ( - asset integer not null unique, - capacity integer, - type text, - form_factor text, - connection text, - rpm integer -); diff --git a/pcinvj/pcinv/.gitignore b/pcinvj/pcinv/.gitignore deleted file mode 100644 index c2065bc..0000000 --- a/pcinvj/pcinv/.gitignore +++ /dev/null @@ -1,37 +0,0 @@ -HELP.md -.gradle -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ - -### STS ### -.apt_generated -.classpath -.factorypath -.project -.settings -.springBeans -.sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ - -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ - -### VS Code ### -.vscode/ diff --git a/pcinvj/pcinv/.gradle/8.14/checksums/checksums.lock b/pcinvj/pcinv/.gradle/8.14/checksums/checksums.lock deleted file mode 100644 index 61a5ef652c6e236430f940d8a9fa9ea9b1d88945..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZS1pUHgUt?<({1~6cL4*)ag1rz`P diff --git a/pcinvj/pcinv/.gradle/8.14/checksums/md5-checksums.bin b/pcinvj/pcinv/.gradle/8.14/checksums/md5-checksums.bin deleted file mode 100644 index e5894350b5ebe9953efed2a64363a607f528d648..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33747 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 an@RUwI7~x4;pgpKbRcF}VR8nQ%>M@fS6T4@ diff --git a/pcinvj/pcinv/.gradle/8.14/checksums/sha1-checksums.bin b/pcinvj/pcinv/.gradle/8.14/checksums/sha1-checksums.bin deleted file mode 100644 index 6198bf27316a255e5645fec4f88152659adfc12b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 80591 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

    Hello, world!

    - - diff --git a/pcinvj/pcinv/settings.gradle.kts b/settings.gradle.kts similarity index 100% rename from pcinvj/pcinv/settings.gradle.kts rename to settings.gradle.kts diff --git a/pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/PcinvApplication.java b/src/main/java/be/seeseepuff/pcinv/PcinvApplication.java similarity index 100% rename from pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/PcinvApplication.java rename to src/main/java/be/seeseepuff/pcinv/PcinvApplication.java diff --git a/pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java similarity index 100% rename from pcinvj/pcinv/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java rename to src/main/java/be/seeseepuff/pcinv/controllers/WebController.java diff --git a/pcinvj/pcinv/build/resources/main/application.properties b/src/main/resources/application.properties similarity index 100% rename from pcinvj/pcinv/build/resources/main/application.properties rename to src/main/resources/application.properties diff --git a/pcinvj/pcinv/build/resources/main/templates/index.html b/src/main/resources/templates/index.html similarity index 100% rename from pcinvj/pcinv/build/resources/main/templates/index.html rename to src/main/resources/templates/index.html diff --git a/pcinvj/pcinv/src/test/java/be/seeseepuff/pcinv/PcinvApplicationTests.java b/src/test/java/be/seeseepuff/pcinv/PcinvApplicationTests.java similarity index 100% rename from pcinvj/pcinv/src/test/java/be/seeseepuff/pcinv/PcinvApplicationTests.java rename to src/test/java/be/seeseepuff/pcinv/PcinvApplicationTests.java diff --git a/static/scripts.js b/static/scripts.js deleted file mode 100644 index 82f2c03..0000000 --- a/static/scripts.js +++ /dev/null @@ -1,23 +0,0 @@ -function newOption(elementId, name) { - var el = document.getElementById(elementId) - if (el.value !== "New...") { - return - } - - var newValue = window.prompt("Enter " + name + " Name") - if (newValue === null) { - return; - } - var child = document.createElement("option") - child.value = newValue - child.innerText = newValue - el.prepend(child) - el.value = newValue -} - -window.onload = function() { - var elements = document.getElementsByClassName("to-delete") - while (elements.length > 0) { - elements[0].remove() - } -} diff --git a/template_debug.go b/template_debug.go deleted file mode 100644 index f73461b..0000000 --- a/template_debug.go +++ /dev/null @@ -1,8 +0,0 @@ -// / +build !release -package main - -import ( - "net/http" -) - -const staticFiles = http.Dir("./static") diff --git a/template_funcs.go b/template_funcs.go deleted file mode 100644 index ac65f39..0000000 --- a/template_funcs.go +++ /dev/null @@ -1,119 +0,0 @@ -package main - -import ( - "fmt" -) - -func createDeviceLink(deviceType, name string, qr *int) CreateDeviceLink { - return CreateDeviceLink{ - Type: deviceType, - Name: name, - Qr: qr, - } -} - -type CreateDeviceLink struct { - Type string - Name string - Qr *int -} - -func formatMemoryUnit(size int) string { - const ( - KB = 1024 - MB = KB * 1024 - GB = MB * 1024 - ) - - switch { - case size >= GB: - return "GB" - case size >= MB: - return "MB" - case size >= KB: - return "KB" - default: - return "B" - } -} - -func formatMemorySize(size int) string { - const ( - KB = 1024 - MB = KB * 1024 - GB = MB * 1024 - ) - - switch formatMemoryUnit(size) { - case "GB": - return fmt.Sprintf("%.2f GB", float64(size)/GB) - case "MB": - return fmt.Sprintf("%.2f MB", float64(size)/MB) - case "KB": - return fmt.Sprintf("%.2f KB", float64(size)/KB) - case "B": - return fmt.Sprintf("%d B", size) - default: - panic("invalid memory size") - } -} - -func formatMemoryPlainSize(size int) int { - const ( - KB = 1024 - MB = KB * 1024 - GB = MB * 1024 - ) - - switch formatMemoryUnit(size) { - case "GB": - return size / GB - case "MB": - return size / MB - case "KB": - return size / KB - case "B": - return size - default: - panic("invalid memory size") - } -} - -func isRamType(size int, unit string) bool { - if size == 0 && unit == "MB" { - return true - } - actualUnit := formatMemoryUnit(size) - return unit == actualUnit -} - -func formatType(t string) string { - for _, assetType := range DescriptorTree.AssetTypes { - if assetType.Id == t { - return assetType.Name - } - } - panic("unknown type") -} - -type SelectMenu struct { - Name string - Label string - Selected string - Options []string - DefaultValue string -} - -func createSelectMenu(name, label, selected string, options []string) SelectMenu { - return createSelectMenuDefault(name, label, selected, options, "Unknown") -} - -func createSelectMenuDefault(name, label, selected string, options []string, defaultValue string) SelectMenu { - return SelectMenu{ - Name: name, - Label: label, - Selected: selected, - Options: options, - DefaultValue: defaultValue, - } -} diff --git a/templates/browse.gohtml b/templates/browse.gohtml deleted file mode 100644 index c24bd45..0000000 --- a/templates/browse.gohtml +++ /dev/null @@ -1,31 +0,0 @@ -{{- /*gotype: main.BrowseVM */}} -{{define "browse"}} -{{template "header" "Search Results"}} - - - - - - - - {{if .HasRam}} - - - {{end}} - - {{range .Assets}} - - - - - - - {{if $.HasRam}} - - - {{end}} - - {{end}} -
    QRTypeNameBrandDescriptionRAM TypeRAM Capacity
    {{.Qr}}{{.Type | formatType}}{{.Name}}{{.Brand}}{{.Description}}{{.RamType}}{{.RamCapacity | formatMemorySize}}
    -{{template "footer"}} -{{end}} diff --git a/templates/create_device.gohtml b/templates/create_device.gohtml deleted file mode 100644 index 3825755..0000000 --- a/templates/create_device.gohtml +++ /dev/null @@ -1,8 +0,0 @@ -{{- /*gotype: main.CreateDeviceVM */}} -{{define "create_device"}} - {{if .Type}} - {{template "create_device_step2" .}} - {{else}} - {{template "create_device_step1" .}} - {{end}} -{{end}} diff --git a/templates/create_device_step1.gohtml b/templates/create_device_step1.gohtml deleted file mode 100644 index c4a20ba..0000000 --- a/templates/create_device_step1.gohtml +++ /dev/null @@ -1,20 +0,0 @@ -{{- /*gotype: pcinv.CreateDeviceVM*/ -}} -{{define "create_device_step1"}} -{{template "header" "Create Device - Choose Device Type"}} -
    -{{template "footer"}} -{{end}} diff --git a/templates/create_device_step2.gohtml b/templates/create_device_step2.gohtml deleted file mode 100644 index 518a2f5..0000000 --- a/templates/create_device_step2.gohtml +++ /dev/null @@ -1,188 +0,0 @@ -{{- /*gotype: pcinv.CreateDeviceVM*/}} -{{define "create_device_step2"}} -{{template "header" "Create Device - Enter Device Data"}} -
    - - {{range .DescriptorTree.AssetTypes}} - {{if $.IsPartOf .Id}} -

    {{.Name}}

    - {{$asset := .}} - - {{range .Fields}} - - - - - {{end}} -
    - {{if eq .Type "type"}} - {{$.NiceType}} - - {{else if eq .Type "number"}} - - {{else if eq .Type "string"}} - - {{else if eq .Type "selection"}} - - - {{else if eq .Type "capacity"}} - - - {{end}} -
    - {{end}} - {{end}} - -{{/* */}} -{{/* */}} -{{/* */}} -{{/* {{if .Qr}}*/}} -{{/* */}} -{{/* {{else}}*/}} -{{/* */}} -{{/* {{end}}*/}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/*
    */}} -{{/* */}} -{{/*
    */}} - -{{/* {{if eq .Type "ram"}}*/}} -{{/*

    Memory Information

    */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/*
    */}} -{{/* */}} -{{/*
    */}} -{{/* */}} -{{/* */}} -{{/*
    */}} -{{/* {{end}}*/}} - -{{/* {{if eq .Type "hdd"}}*/}} -{{/*

    Hard Drive Information

    */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/* */}} -{{/*
    */}} -{{/* */}} -{{/* */}} -{{/*
    {{template "create_device_select" createSelectMenu "hdd_type" "HDD Type" .HddType .HddTypes}}
    {{template "create_device_select" createSelectMenu "hdd_form_factor" "HDD Form Factor" .HddFormFactor .HddFormFactors}}
    {{template "create_device_select" createSelectMenu "hdd_connection" "HDD Connection" .HddConnection .HddConnections}}
    {{template "create_device_select" createSelectMenuDefault "hdd_rpm" "HDD RPM" .HddRpm .HddRpms "Not Applicable"}}
    */}} -{{/* {{end}}*/}} - - {{if .IsEdit}} - - {{else}} - - {{end}} -
    -{{template "footer"}} -{{end}} - -{{/*{{define "create_device_select"}}*/}} -{{/* */}} -{{/*{{end}}*/}} diff --git a/templates/delete.gohtml b/templates/delete.gohtml deleted file mode 100644 index 2f955c5..0000000 --- a/templates/delete.gohtml +++ /dev/null @@ -1,9 +0,0 @@ -{{- /*gotype: pcinv.DeleteVM*/ -}} -{{define "delete"}} -{{template "header" "Delete Device"}} -

    Are you sure you want to delete this device?

    -
    - - -{{template "footer"}} -{{end}} diff --git a/templates/device.gohtml b/templates/device.gohtml deleted file mode 100644 index f47de45..0000000 --- a/templates/device.gohtml +++ /dev/null @@ -1,57 +0,0 @@ -{{- /*gotype: main.DeviceVM */}} -{{define "device"}} -{{template "header" "Device Details"}} - - - - - - - - - - - - - - - - - - {{if eq .Type "ram"}} - - - - - - - - - {{end}} - {{if eq .Type "hdd"}} - - - - - - - - - - - - - - - - - - - - - {{end}} -
    Name:{{.Name}}
    Brand:{{.Brand}}
    Type:{{.Type}}
    Description:{{.Description}}
    RAM Type:{{.RamType}}
    RAM Capacity:{{.RamCapacity | formatMemorySize}}
    HDD Capacity:{{.HddCapacity}}
    HDD Type:{{.HddType}}
    Form Factor:{{.HddFormFactor}}
    Connection:{{.HddConnection}}
    RPM:{{.HddRpm}}
    - - -{{template "footer"}} -{{end}} diff --git a/templates/errors.gohtml b/templates/errors.gohtml deleted file mode 100644 index 45e7b65..0000000 --- a/templates/errors.gohtml +++ /dev/null @@ -1,12 +0,0 @@ -{{- /*gotype: main.ErrorVM*/}} -{{define "errors"}} -{{template "header" .StatusCode | statusText}} - -
      - {{range .Errors}} -
    • {{.}}
    • - {{end}} -
    - -{{template "footer"}} -{{end}} diff --git a/templates/footer.gohtml b/templates/footer.gohtml deleted file mode 100644 index 634a10f..0000000 --- a/templates/footer.gohtml +++ /dev/null @@ -1,4 +0,0 @@ -{{define "footer"}} - - -{{end}} diff --git a/templates/header.gohtml b/templates/header.gohtml deleted file mode 100644 index be6b2af..0000000 --- a/templates/header.gohtml +++ /dev/null @@ -1,16 +0,0 @@ -{{define "header"}} - - - PC Inventory{{if .}} - {{.}}{{end}} - - - -

    PC Inventory{{if .}} - {{.}}{{end}}

    -
    - - - - Create Device -
    -
    -{{end}} diff --git a/templates/index.gohtml b/templates/index.gohtml deleted file mode 100644 index c6782a3..0000000 --- a/templates/index.gohtml +++ /dev/null @@ -1,34 +0,0 @@ -{{- /*gotype: main.IndexVM*/}} -{{define "index"}} -{{template "header"}} -

    Statistics

    - The inventory contains: -
      -
    • {{.AssetCount}} assets in total.
    • -
    • {{.BrandCount}} unique brands.
    • -
    • a combined {{.TotalRamCapacity | formatMemorySize}} of RAM.
    • -
    - -

    Filter Devices

    -
    -

    Select Brands:

    - {{range .Brands}} -
    - {{end}} - -

    Select Types:

    - {{range .Types}} -
    - {{end}} - - -
    - -{{template "footer"}} -{{end}} diff --git a/view_createdevice.go b/view_createdevice.go deleted file mode 100644 index 14f7dfc..0000000 --- a/view_createdevice.go +++ /dev/null @@ -1,135 +0,0 @@ -package main - -import ( - "fmt" - "github.com/gin-gonic/gin" - "net/http" - "strconv" - "strings" -) - -type CreateDeviceVM struct { - IsEdit bool - DescriptorTree *DescriptorRoot - - // Assets - Qr *int - Type string - - Values map[string]string -} - -func (vm *CreateDeviceVM) SetValue(assetType string, field string, value string) { - if vm.Values == nil { - vm.Values = make(map[string]string) - } - vm.Values[assetType+"-"+field] = value -} - -func (vm *CreateDeviceVM) GetValue(assetType string, field string) string { - if vm.Values == nil { - return "" - } - return vm.Values[assetType+"-"+field] -} - -func (vm *CreateDeviceVM) NiceType() string { - return getAssetTypeById(vm.Type).Name -} - -func (vm *CreateDeviceVM) IsPartOf(assertType string) bool { - if assertType == "asset" { - return true - } - return assertType == vm.Type -} - -func (a *App) getCreateDevice(c *gin.Context) { - var err error - vm := &CreateDeviceVM{} - vm.DescriptorTree = DescriptorTree - vm.Type = c.Query("type") - - qr := c.Query("id") - if qr != "" { - qrInt, err := strconv.Atoi(qr) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("invalid qr: %v", err)) - return - } - vm.Qr = &qrInt - } - - err = a.GetAllGroups(vm) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - if c.Query("edit") != "" && vm.Qr != nil { - vm.IsEdit = true - err := a.pullDataFromDb(*vm.Qr, "asset", vm) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to load asset data: %v", err)) - return - } - //// Load the asset data from the database - //err = a.db.Query("SELECT * FROM assets WHERE qr = ?"). - // Bind(*vm.Qr). - // ScanColumns(&columns). - // ScanSingle(&vm.Qr, &vm.Type, &vm.Values) - //if err != nil { - // c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("failed to load asset: %v", err)) - // return - // - //} - - //err = a.db.Query("SELECT name, type, brand, description FROM assets WHERE qr = ?"). - // Bind(*vm.Qr). - // ScanSingle(&vm.AssetName, &vm.Type, &vm.AssetBrand, &vm.AssetDescription) - //if err != nil { - // c.AbortWithError(http.StatusInternalServerError, err) - // return - //} - //if vm.Type == "ram" { - // err = a.db.Query("SELECT type, capacity FROM info_ram WHERE asset = ?"). - // Bind(*vm.Qr). - // ScanSingle(&vm.RamType, &vm.RamCapacity) - // if err != nil { - // c.AbortWithError(http.StatusInternalServerError, err) - // return - // } - //} else if vm.Type == "hdd" { - // err = a.db.Query("SELECT capacity, type, form_factor, connection, rpm FROM info_hdd WHERE asset = ?"). - // Bind(*vm.Qr). - // ScanSingle(&vm.HddCapacity, &vm.HddType, &vm.HddFormFactor, &vm.HddConnection, &vm.HddRpm) - // if err != nil { - // c.AbortWithError(http.StatusInternalServerError, err) - // return - // } - //} - } - - c.HTML(http.StatusOK, "create_device", vm) -} - -func (a *App) pullDataFromDb(qr int, assetType string, vm *CreateDeviceVM) error { - var fields []string - assetDescriptor := getAssetTypeById(assetType) - for _, field := range assetDescriptor.Fields { - fields = append(fields, field.Id) - } - values := make([]any, len(fields)) - - query := fmt.Sprintf("SELECT %s FROM %s WHERE qr = ?", strings.Join(fields, ", "), assetDescriptor.Table) - err := a.db.Query(query).Bind(qr).ScanSingle(values...) - if err != nil { - return fmt.Errorf("failed to load asset data: %v", err) - } - - for i, field := range fields { - vm.SetValue(assetType, field, fmt.Sprintf("%v", values[i])) - } - - return err -} diff --git a/view_device.go b/view_device.go deleted file mode 100644 index 9ac3d6c..0000000 --- a/view_device.go +++ /dev/null @@ -1,73 +0,0 @@ -package main - -import ( - "github.com/gin-gonic/gin" - "net/http" - "strconv" -) - -type DeviceVM struct { - Qr int - Name string - Brand string - Type string - Description string - RamType string - RamCapacity int - HddCapacity int - HddType string - HddFormFactor string - HddConnection string - HddRpm int -} - -func (a *App) getDevice(c *gin.Context) { - qr, err := strconv.Atoi(c.Query("id")) - if err != nil { - c.AbortWithError(http.StatusBadRequest, err) - return - } - - var count int - err = a.db.Query("SELECT COUNT(*) FROM assets WHERE qr = ?"). - Bind(qr). - ScanSingle(&count) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - if count == 0 { - c.Redirect(http.StatusTemporaryRedirect, "/create?id="+strconv.Itoa(qr)) - return - } - - vm := &DeviceVM{Qr: qr} - err = a.db.Query("SELECT name, brand, type, description FROM assets WHERE qr = ?"). - Bind(qr). - ScanSingle(&vm.Name, &vm.Brand, &vm.Type, &vm.Description) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - if vm.Type == "ram" { - err = a.db.Query("SELECT type, capacity FROM info_ram WHERE asset = ?"). - Bind(qr). - ScanSingle(&vm.RamType, &vm.RamCapacity) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - } else if vm.Type == "hdd" { - err = a.db.Query("SELECT capacity, type, form_factor, connection, rpm FROM info_hdd WHERE asset = ?"). - Bind(qr). - ScanSingle(&vm.HddCapacity, &vm.HddType, &vm.HddFormFactor, &vm.HddConnection, &vm.HddRpm) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - } - - c.HTML(http.StatusOK, "device", vm) -} diff --git a/view_index.go b/view_index.go deleted file mode 100644 index da0fd32..0000000 --- a/view_index.go +++ /dev/null @@ -1,50 +0,0 @@ -package main - -import ( - "github.com/gin-gonic/gin" - "net/http" -) - -type IndexVM struct { - AssetCount int - BrandCount int - TotalRamCapacity int - Brands []string - Types []string -} - -func (a *App) getIndex(c *gin.Context) { - vm := &IndexVM{} - var err error - vm.AssetCount, err = a.GetAssetCount() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - vm.BrandCount, err = a.GetBrandCount() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - vm.TotalRamCapacity, err = a.GetTotalRamCapacity() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - vm.Brands, err = a.GetAllBrands() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - vm.Types, err = a.GetAllTypes() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - c.HTML(http.StatusOK, "index", vm) -} diff --git a/views.go b/views.go deleted file mode 100644 index 978dde3..0000000 --- a/views.go +++ /dev/null @@ -1,276 +0,0 @@ -package main - -import ( - "errors" - "fmt" - "gitea.seeseepuff.be/seeseemelk/mysqlite" - "github.com/gin-gonic/gin" - "net/http" - "strconv" - "strings" -) - -type App struct { - db *mysqlite.Db -} - -func (a *App) postCreateDevice(c *gin.Context) { - qr, err := strconv.Atoi(c.PostForm("asset-qr")) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("invalid qr: %v", err)) - } - - tx, err := a.db.Begin() - defer tx.MustRollback() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error beginning tx: %v", err)) - return - } - - err = a.DeleteAsset(tx, qr) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error removing assets: %v", err)) - return - } - - // Insert the asset into the database - var id int - err = a.createAsset("asset", tx, c, &id) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error inserting asset: %v", err)) - return - } - assetType := c.PostForm("asset-type") - err = a.createAsset(assetType, tx, c, &id) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("error inserting asset type: %v", err)) - return - } - - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - err = tx.Commit() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - c.Redirect(http.StatusSeeOther, "/") -} - -func (a *App) createAsset(assetType string, tx *mysqlite.Tx, c *gin.Context, assetId *int) error { - for _, assetDescriptor := range DescriptorTree.AssetTypes { - if assetDescriptor.Id != assetType { - continue - } - - var fields []string - var questions []string - var values []string - - for _, field := range assetDescriptor.Fields { - fields = append(fields, field.Id) - questions = append(questions, "?") - values = append(values, c.PostForm(assetType+"-"+field.Id)) - } - - if *assetId != 0 { - fields = append(fields, "asset") - questions = append(questions, "?") - values = append(values, strconv.Itoa(*assetId)) - } - - fieldsStr := strings.Join(fields, ", ") - questionsStr := strings.Join(questions, ", ") - err := tx.Query(fmt.Sprintf("INSERT INTO %s (%s) values (%s)", assetDescriptor.Table, fieldsStr, questionsStr)). - Bind(values). - Exec() - if err != nil { - return err - } - - if *assetId == 0 { - err := tx.Query("SELECT LAST_INSERT_ROWID()").ScanSingle(assetId) - if err != nil { - return fmt.Errorf("error getting last insert id: %v", err) - } - } - - return nil - } - return errors.New("Create Asset not implemented for type: " + assetType) -} - -func (a *App) postCreateDeviceRam(c *gin.Context, qr int, tx *mysqlite.Tx) error { - var err error - capacity := 0 - capacityString := c.PostForm("ram_capacity") - if capacityString != "" { - capacity, err = strconv.Atoi(c.PostForm("ram_capacity")) - if err != nil { - return err - } - } - switch c.PostForm("ram_capacity_unit") { - case "B": - case "KB": - capacity *= 1024 - case "MB": - capacity *= 1024 * 1024 - case "GB": - capacity *= 1024 * 1024 * 1024 - default: - return errors.New("invalid ram_capacity_unit") - } - - err = tx.Query("INSERT INTO info_ram (asset, type, capacity) VALUES (?, ?, ?)"). - Bind(qr, c.PostForm("ram_type"), capacity).Exec() - return err -} - -func (a *App) postCreateDeviceHdd(c *gin.Context, qr int, tx *mysqlite.Tx) error { - var err error - capacity := 0 - capacityString := c.PostForm("hdd_capacity") - if capacityString != "" { - capacity, err = strconv.Atoi(c.PostForm("hdd_capacity")) - if err != nil { - return err - } - } - switch c.PostForm("hdd_capacity_unit") { - case "B": - case "KB": - capacity *= 1024 - case "MB": - capacity *= 1024 * 1024 - case "GB": - capacity *= 1024 * 1024 * 1024 - default: - return errors.New("invalid hdd_capacity_unit") - } - - err = tx.Query("INSERT INTO info_hdd (asset, capacity, type, form_factor, connection, rpm) VALUES (?, ?, ?, ?, ?, ?)"). - Bind(qr, capacity, c.PostForm("hdd_type"), c.PostForm("hdd_form_factor"), c.PostForm("hdd_connection"), c.PostForm("hdd_rpm")). - Exec() - return err -} - -type BrowseVM struct { - Assets []Asset - HasRam bool -} - -type Asset struct { - Qr int - Name string - Brand string - Type string - Description string - RamType string - RamCapacity int - HddCapacity int - HddType string - HddFormFactor string - HddConnection string - HddRpm int -} - -func (a *App) getBrowse(c *gin.Context) { - brands := c.QueryArray("brand") - types := c.QueryArray("type") - - query := `SELECT assets.qr, assets.name, assets.brand, assets.type, assets.description, - info_ram.type, info_ram.capacity, - info_hdd.capacity, info_hdd.type, info_hdd.form_factor, info_hdd.connection, info_hdd.rpm - FROM assets - LEFT JOIN info_ram ON info_ram.asset = assets.qr - LEFT JOIN info_hdd ON info_hdd.asset = assets.qr - WHERE 1=1` - if len(brands) > 0 { - query += " AND assets.brand IN (" + placeholders(len(brands)) + ")" - } - if len(types) > 0 { - query += " AND assets.type IN (" + placeholders(len(types)) + ")" - } - query += "ORDER BY assets.type, assets.brand, assets.name, assets.qr" - - vm := &BrowseVM{} - - var err error - q := a.db.Query(query).Bind(brands, types) - for row := range q.Range(&err) { - var asset Asset - err := row.Scan(&asset.Qr, &asset.Name, &asset.Brand, &asset.Type, &asset.Description, - &asset.RamType, &asset.RamCapacity, - &asset.HddCapacity, &asset.HddType, &asset.HddFormFactor, &asset.HddConnection, &asset.HddRpm) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - vm.Assets = append(vm.Assets, asset) - if asset.Type == "ram" { - vm.HasRam = true - } - } - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - c.HTML(http.StatusOK, "browse", vm) -} - -type DeleteVM struct { - Qr int -} - -func (a *App) getDelete(c *gin.Context) { - qr, err := strconv.Atoi(c.Query("id")) - if err != nil { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid qr: %v", err)) - return - } - vm := &DeleteVM{Qr: qr} - c.HTML(http.StatusOK, "delete", vm) -} - -func (a *App) postDelete(c *gin.Context) { - qr, err := strconv.Atoi(c.Query("id")) - if err != nil { - c.AbortWithError(http.StatusBadRequest, fmt.Errorf("invalid qr: %v", err)) - return - } - tx, err := a.db.Begin() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - err = a.DeleteAsset(tx, qr) - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - - err = tx.Commit() - if err != nil { - c.AbortWithError(http.StatusInternalServerError, err) - return - } - c.Redirect(http.StatusSeeOther, "/") -} - -func placeholders(count int) string { - if count == 0 { - return "" - } - placeholder := "?" - for count > 1 { - placeholder += ", ?" - count-- - } - return placeholder -} From 20bd00f67b0d633bdd7cb47c697e6408631b1a16 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Wed, 4 Jun 2025 09:43:53 +0200 Subject: [PATCH 04/22] Got initial framework running --- build.gradle.kts | 1 + docker-compose.yml | 6 +- pcinvj/pcinv/.idea/.gitignore | 10 --- pcinvj/pcinv/.idea/gradle.xml | 10 --- pcinvj/pcinv/.idea/misc.xml | 7 -- pcinvj/pcinv/.idea/modules.xml | 8 -- pcinvj/pcinv/.idea/vcs.xml | 6 -- pcinvj/pcinv/.idea/workspace.xml | 78 ------------------- .../be/seeseepuff/pcinv/models/Asset.java | 37 +++++++++ .../pcinv/models/AssetCondition.java | 18 +++++ .../be/seeseepuff/pcinv/models/HddAsset.java | 32 ++++++++ .../be/seeseepuff/pcinv/models/RamAsset.java | 29 +++++++ .../pcinv/repositories/AssetRepository.java | 8 ++ src/main/resources/application.properties | 5 ++ 14 files changed, 133 insertions(+), 122 deletions(-) delete mode 100644 pcinvj/pcinv/.idea/.gitignore delete mode 100644 pcinvj/pcinv/.idea/gradle.xml delete mode 100644 pcinvj/pcinv/.idea/misc.xml delete mode 100644 pcinvj/pcinv/.idea/modules.xml delete mode 100644 pcinvj/pcinv/.idea/vcs.xml delete mode 100644 pcinvj/pcinv/.idea/workspace.xml create mode 100644 src/main/java/be/seeseepuff/pcinv/models/Asset.java create mode 100644 src/main/java/be/seeseepuff/pcinv/models/AssetCondition.java create mode 100644 src/main/java/be/seeseepuff/pcinv/models/HddAsset.java create mode 100644 src/main/java/be/seeseepuff/pcinv/models/RamAsset.java create mode 100644 src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java diff --git a/build.gradle.kts b/build.gradle.kts index 9e56f52..5497c36 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -27,6 +27,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-thymeleaf") implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-actuator") compileOnly("org.projectlombok:lombok") developmentOnly("org.springframework.boot:spring-boot-devtools") runtimeOnly("org.postgresql:postgresql") diff --git a/docker-compose.yml b/docker-compose.yml index 3cdc7c1..ed02886 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,8 +2,8 @@ services: database: image: postgres:latest environment: - POSTGRES_USER: pcinvj - POSTGRES_PASSWORD: pcinvj - POSTGRES_DB: pcinvj + POSTGRES_USER: pcinv + POSTGRES_PASSWORD: pcinv + POSTGRES_DB: pcinv ports: - "5432:5432" diff --git a/pcinvj/pcinv/.idea/.gitignore b/pcinvj/pcinv/.idea/.gitignore deleted file mode 100644 index 7bc07ec..0000000 --- a/pcinvj/pcinv/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Environment-dependent path to Maven home directory -/mavenHomeManager.xml -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/pcinvj/pcinv/.idea/gradle.xml b/pcinvj/pcinv/.idea/gradle.xml deleted file mode 100644 index 49ef579..0000000 --- a/pcinvj/pcinv/.idea/gradle.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pcinvj/pcinv/.idea/misc.xml b/pcinvj/pcinv/.idea/misc.xml deleted file mode 100644 index 5cd9a10..0000000 --- a/pcinvj/pcinv/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/pcinvj/pcinv/.idea/modules.xml b/pcinvj/pcinv/.idea/modules.xml deleted file mode 100644 index ae57db8..0000000 --- a/pcinvj/pcinv/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/pcinvj/pcinv/.idea/vcs.xml b/pcinvj/pcinv/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/pcinvj/pcinv/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/pcinvj/pcinv/.idea/workspace.xml b/pcinvj/pcinv/.idea/workspace.xml deleted file mode 100644 index 6c86464..0000000 --- a/pcinvj/pcinv/.idea/workspace.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1739869040002 - - - - - - - false - true - - \ No newline at end of file diff --git a/src/main/java/be/seeseepuff/pcinv/models/Asset.java b/src/main/java/be/seeseepuff/pcinv/models/Asset.java new file mode 100644 index 0000000..a3c6b23 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/models/Asset.java @@ -0,0 +1,37 @@ +package be.seeseepuff.pcinv.models; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.Setter; + +/** + * Represents a generic asset in the inventory system. + */ +@Getter +@Setter +@Entity +public class Asset +{ + @Id @GeneratedValue + private Long id; + + /// The QR code attached to the asset, used for identification. + private Long qr; + + /// The brand of the asset. + private String brand; + + /// The model of the asset + private String model; + + /// The asset's serial number. + private String serialNumber; + + /// A description of the asset, providing additional details. + private String description; + + /// The state of the asset, indicating its condition. + private AssetCondition condition; +} diff --git a/src/main/java/be/seeseepuff/pcinv/models/AssetCondition.java b/src/main/java/be/seeseepuff/pcinv/models/AssetCondition.java new file mode 100644 index 0000000..c928d6e --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/models/AssetCondition.java @@ -0,0 +1,18 @@ +package be.seeseepuff.pcinv.models; + +/** + * Represents the condition of an asset in the inventory system. + */ +public enum AssetCondition +{ + /// The asset is in perfect working order. + HEALTHY, + /// The condition of the asset is unknown. E.g.: it is untested. + UNKNOWN, + /// The asset generally works, but has some known issues. + PARTIAL, + /// The asset is in need of repair, but is not completely broken. + REPAIR, + /// The asset is completely broken and cannot be used. + BORKED, +} diff --git a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java new file mode 100644 index 0000000..06fc866 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java @@ -0,0 +1,32 @@ +package be.seeseepuff.pcinv.models; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.Setter; + +/** + * Represents a hard drive or similar device. + */ +@Getter +@Setter +@Entity +public class HddAsset +{ + @Id + @GeneratedValue + private Long id; + + /// The ID of the associated asset, linking it to the generic Asset model. + private Long assetId; + + /// The capacity of the drive in bytes. + private Long capacity; + + /// The drive's interface type, such as SATA, IDE, ISA-16, ... + private String interfaceType; + + /// The drive's form factor, such as 2.5", 3.5", etc. + private String formFactor; +} diff --git a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java new file mode 100644 index 0000000..9340450 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java @@ -0,0 +1,29 @@ +package be.seeseepuff.pcinv.models; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.Setter; + +/** + * Represents a RAM DIMM or similar memory asset in the inventory system. + */ +@Getter +@Setter +@Entity +public class RamAsset +{ + @Id + @GeneratedValue + private Long id; + + /// The ID of the associated asset, linking it to the generic Asset model. + private Long assetId; + + /// The capacity of the RAM in bytes. + private Long capacity; + + /// The type of memory. E.g.: DDR2, SDRAM, ISA-8, etc... + private String type; +} diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java new file mode 100644 index 0000000..6f4c96d --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java @@ -0,0 +1,8 @@ +package be.seeseepuff.pcinv.repositories; + +import be.seeseepuff.pcinv.models.Asset; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface AssetRepository extends CrudRepository {} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c127059..91c83da 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,6 @@ spring.application.name=pcinv +server.port=8088 +spring.datasource.url=jdbc:postgresql://localhost:5432/pcinv +spring.datasource.username=pcinv +spring.datasource.password=pcinv +a From ca47f8f8ab63e2ba0c17b4d2e31f5067f6792469 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Thu, 5 Jun 2025 21:17:17 +0200 Subject: [PATCH 05/22] Add ability to reflectively determine asset information --- .../pcinv/controllers/StartupController.java | 23 ++++++ .../pcinv/meta/AssetDescriptors.java | 34 +++++++++ .../be/seeseepuff/pcinv/meta/AssetInfo.java | 34 +++++++++ .../pcinv/meta/AssetProperties.java | 69 ++++++++++++++++++ .../seeseepuff/pcinv/meta/AssetProperty.java | 71 +++++++++++++++++++ .../be/seeseepuff/pcinv/meta/Property.java | 20 ++++++ .../be/seeseepuff/pcinv/models/Asset.java | 36 +--------- .../seeseepuff/pcinv/models/GenericAsset.java | 52 ++++++++++++++ .../be/seeseepuff/pcinv/models/HddAsset.java | 25 ++++--- .../be/seeseepuff/pcinv/models/RamAsset.java | 24 ++++--- .../pcinv/repositories/AssetRepository.java | 7 +- .../repositories/GenericAssetRepository.java | 8 +++ .../repositories/HddAssetRepository.java | 12 ++++ .../repositories/RamAssetRepository.java | 12 ++++ .../pcinv/services/AssetService.java | 39 ++++++++++ src/main/resources/application.properties | 2 +- 16 files changed, 416 insertions(+), 52 deletions(-) create mode 100644 src/main/java/be/seeseepuff/pcinv/controllers/StartupController.java create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/Property.java create mode 100644 src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java create mode 100644 src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java create mode 100644 src/main/java/be/seeseepuff/pcinv/repositories/HddAssetRepository.java create mode 100644 src/main/java/be/seeseepuff/pcinv/repositories/RamAssetRepository.java create mode 100644 src/main/java/be/seeseepuff/pcinv/services/AssetService.java diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/StartupController.java b/src/main/java/be/seeseepuff/pcinv/controllers/StartupController.java new file mode 100644 index 0000000..603c8df --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/controllers/StartupController.java @@ -0,0 +1,23 @@ +package be.seeseepuff.pcinv.controllers; + +import be.seeseepuff.pcinv.services.AssetService; +import jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +@Slf4j +@RequiredArgsConstructor +@Service +public class StartupController { + private final AssetService assetService; + + @PostConstruct + public void init() { + var descriptors = assetService.getAssetDescriptors(); + log.info("Asset descriptors loaded:\n{}", descriptors); + + var assets = assetService.countAssets(); + log.info("The repository contains {} assets.", assets); + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java new file mode 100644 index 0000000..029e30e --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptors.java @@ -0,0 +1,34 @@ +package be.seeseepuff.pcinv.meta; + +import lombok.Getter; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Holds the descriptors for all possible asset types. + */ +@Getter +public class AssetDescriptors { + /// A collection of all types of assets. + private final Collection assets = new ArrayList<>(); + + /** + * Loads the asset properties from a given asset type. + * + * @param assetType The type of the asset to load properties for. + */ + public void loadFrom(Class assetType) { + var property = AssetProperties.loadFrom(assetType); + assets.add(property); + } + + @Override + public String toString() { + var builder = new StringBuilder(); + builder.append("AssetDescriptors [\n"); + assets.forEach(assetProperties -> builder.append(assetProperties.toString(" ")).append('\n')); + builder.append("]"); + return builder.toString(); + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java new file mode 100644 index 0000000..40dcb49 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java @@ -0,0 +1,34 @@ +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; + +/** + * Provides metadata about an asset type itself. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface AssetInfo { + /** + * The displayable name of the asset type. + * + * @return the display name of the asset type + */ + String displayName() default ""; + + /** + * The type of the asset, which can be a string or an integer. + * + * @return the type of the asset + */ + String type() default ""; + + /** + * Indicates whether the asset type should be visible in the UI. + * + * @return true if the asset type is visible, false otherwise + */ + boolean isVisible() default true; +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java new file mode 100644 index 0000000..3efed5b --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperties.java @@ -0,0 +1,69 @@ +package be.seeseepuff.pcinv.meta; + +import lombok.Builder; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Objects; + +/** + * Describes the properties of an asset + */ +@Builder +public class AssetProperties { + /// The type of property, e.g.: ram, asset, etc... + private final String type; + + /// The displayable name of the property, e.g.: "Random Access memory" + private final String displayName; + + /// Whether the asset is visible in the user interface. + private final boolean visible; + + /// The properties of the asset, such as "brand", "model", etc. + @lombok.Singular + private Collection properties; + + /** + * Loads the asset properties from a given asset class. + * + * @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) { + var assetInfo = assetType.getAnnotation(AssetInfo.class); + Objects.requireNonNull(assetInfo, "Asset class must be annotated with @AssetInfo"); + return AssetProperties.builder() + .type(assetInfo.type()) + .displayName(assetInfo.displayName()) + .visible(assetInfo.isVisible()) + .properties(Arrays.stream(assetType.getDeclaredFields()) + .map(AssetProperty::loadFrom) + .filter(Objects::nonNull) + .toList()) + .build(); + } + + /** + * Returns a string representation of the asset properties with the specified indentation. + * + * @param indent The indentation to use for formatting. + * @return A formatted string representation of the asset properties. + */ + public String toString(String indent) { + var builder = new StringBuilder(); + builder.append(indent).append("AssetProperties {\n"); + builder.append(indent).append(" type='").append(type).append("',\n"); + builder.append(indent).append(" displayName='").append(displayName).append("',\n"); + if (!visible) { + builder.append(indent).append(" hidden").append(",\n"); + } + builder.append(indent).append(" properties=[\n"); + for (var property : properties) { + builder.append(indent).append(" ").append(property.toString()).append("\n"); + } + builder.append(indent).append(" ]\n"); + builder.append(indent).append('}'); + return builder.toString(); + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java new file mode 100644 index 0000000..7ff640b --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java @@ -0,0 +1,71 @@ +package be.seeseepuff.pcinv.meta; + +import be.seeseepuff.pcinv.models.AssetCondition; +import jakarta.annotation.Nullable; +import lombok.Builder; + +import java.lang.reflect.Field; + +/** + * 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. + */ +@Builder +public class AssetProperty { + /// The name of the property, e.g., "brand", "model", etc. + private final String name; + /// The name of the property as it should be displayed, e.g., "Brand", "Model", etc. + private final String displayName; + /// The type of the property, which can be a string or an integer. + private final Type type; + + /** + * Enum representing the possible types of asset properties. + */ + public enum Type { + STRING, INTEGER, CONDITION + } + + /** + * Loads an AssetProperty from a given field. + * + * @param property The field representing the property. + * @return An AssetProperty instance with the name, display name, and type determined from the field. + */ + @Nullable + public static AssetProperty loadFrom(Field property) { + var annotation = property.getAnnotation(Property.class); + if (annotation == null) { + return null; + } + return AssetProperty.builder() + .name(property.getName()) + .displayName(annotation.value()) + .type(determineType(property)) + .build(); + } + + /** + * Determines the type of the property based on its field type. + * + * @param property The field representing the property. + * @return The type of the property. + * @throws IllegalArgumentException if the property type is unsupported. + */ + private static Type determineType(Field property) { + if (property.getType() == String.class) { + return Type.STRING; + } 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) { + return Type.CONDITION; + } else { + throw new IllegalArgumentException("Unsupported property type: " + property.getType()); + } + } + + @Override + public String toString() { + return String.format("%s:%s (%s)", name, type.name(), displayName); + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/meta/Property.java b/src/main/java/be/seeseepuff/pcinv/meta/Property.java new file mode 100644 index 0000000..eb71c13 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/Property.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 of an asset. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface Property { + /** + * The displayable name of the property. + * + * @return the display name of the property + */ + String value(); +} diff --git a/src/main/java/be/seeseepuff/pcinv/models/Asset.java b/src/main/java/be/seeseepuff/pcinv/models/Asset.java index a3c6b23..7e295d6 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/Asset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/Asset.java @@ -1,37 +1,7 @@ package be.seeseepuff.pcinv.models; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import lombok.Getter; -import lombok.Setter; +public interface Asset { + long getId(); -/** - * Represents a generic asset in the inventory system. - */ -@Getter -@Setter -@Entity -public class Asset -{ - @Id @GeneratedValue - private Long id; - - /// The QR code attached to the asset, used for identification. - private Long qr; - - /// The brand of the asset. - private String brand; - - /// The model of the asset - private String model; - - /// The asset's serial number. - private String serialNumber; - - /// A description of the asset, providing additional details. - private String description; - - /// The state of the asset, indicating its condition. - private AssetCondition condition; + GenericAsset getAsset(); } diff --git a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java new file mode 100644 index 0000000..08def8c --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java @@ -0,0 +1,52 @@ +package be.seeseepuff.pcinv.models; + +import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.Property; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.Getter; +import lombok.Setter; + +/** + * Represents a generic asset in the inventory system. + */ +@Getter +@Setter +@Entity +@AssetInfo( + displayName = "Asset", + type = "asset", + isVisible = false +) +@Table(name = "assets") +public class GenericAsset +{ + @Id @GeneratedValue + private long id; + + /// The QR code attached to the asset, used for identification. + @Property("QR") + private long qr; + + /// The brand of the asset. + @Property("Brand") + private String brand; + + /// The model of the asset + @Property("Model") + private String model; + + /// The asset's serial number. + @Property("Serial Number") + private String serialNumber; + + /// A description of the asset, providing additional details. + @Property("Description") + private String description; + + /// The state of the asset, indicating its condition. + @Property("Condition") + private AssetCondition condition; +} diff --git a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java index 06fc866..ae2cb61 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java @@ -1,8 +1,8 @@ package be.seeseepuff.pcinv.models; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; +import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.Property; +import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -12,21 +12,30 @@ import lombok.Setter; @Getter @Setter @Entity -public class HddAsset +@AssetInfo( + displayName = "Hard Drive", + type = "HDD" +) +@Table(name = "hdd_assets") +public class HddAsset implements Asset { @Id @GeneratedValue - private Long id; + private long id; - /// The ID of the associated asset, linking it to the generic Asset model. - private Long assetId; + /// The generic asset associated with this HDD. + @OneToOne(orphanRemoval = true) + private GenericAsset asset; /// The capacity of the drive in bytes. - private Long capacity; + @Property("Capacity") + private long capacity; /// The drive's interface type, such as SATA, IDE, ISA-16, ... + @Property("Interface Type") private String interfaceType; /// The drive's form factor, such as 2.5", 3.5", etc. + @Property("Form Factor") private String formFactor; } diff --git a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java index 9340450..407a613 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java @@ -1,8 +1,8 @@ package be.seeseepuff.pcinv.models; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; +import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.Property; +import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -12,18 +12,26 @@ import lombok.Setter; @Getter @Setter @Entity -public class RamAsset +@AssetInfo( + displayName = "Random Access Memory", + type = "RAM" +) +@Table(name = "ram_assets") +public class RamAsset implements Asset { @Id @GeneratedValue - private Long id; + private long id; - /// The ID of the associated asset, linking it to the generic Asset model. - private Long assetId; + /// The generic asset associated with this RAM. + @OneToOne(orphanRemoval = true) + private GenericAsset asset; /// The capacity of the RAM in bytes. - private Long capacity; + @Property("Capacity") + private long capacity; /// The type of memory. E.g.: DDR2, SDRAM, ISA-8, etc... + @Property("Type") private String type; } diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java index 6f4c96d..09aa689 100644 --- a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java +++ b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java @@ -1,8 +1,11 @@ package be.seeseepuff.pcinv.repositories; import be.seeseepuff.pcinv.models.Asset; -import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; @Repository -public interface AssetRepository extends CrudRepository {} +public interface AssetRepository { + Class getAssetType(); + + long count(); +} diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java new file mode 100644 index 0000000..a989809 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java @@ -0,0 +1,8 @@ +package be.seeseepuff.pcinv.repositories; + +import be.seeseepuff.pcinv.models.GenericAsset; +import org.springframework.data.jpa.repository.JpaRepository; + +@SuppressWarnings("unused") +public interface GenericAssetRepository extends JpaRepository { +} diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/HddAssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/HddAssetRepository.java new file mode 100644 index 0000000..625de83 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/repositories/HddAssetRepository.java @@ -0,0 +1,12 @@ +package be.seeseepuff.pcinv.repositories; + +import be.seeseepuff.pcinv.models.HddAsset; +import org.springframework.data.jpa.repository.JpaRepository; + +@SuppressWarnings("unused") +public interface HddAssetRepository extends JpaRepository, AssetRepository { + @Override + default Class getAssetType() { + return HddAsset.class; + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/RamAssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/RamAssetRepository.java new file mode 100644 index 0000000..2b8bf98 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/repositories/RamAssetRepository.java @@ -0,0 +1,12 @@ +package be.seeseepuff.pcinv.repositories; + +import be.seeseepuff.pcinv.models.RamAsset; +import org.springframework.data.jpa.repository.JpaRepository; + +@SuppressWarnings("unused") +public interface RamAssetRepository extends JpaRepository, AssetRepository { + @Override + default Class getAssetType() { + return RamAsset.class; + } +} diff --git a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java new file mode 100644 index 0000000..c56fd8a --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java @@ -0,0 +1,39 @@ +package be.seeseepuff.pcinv.services; + +import be.seeseepuff.pcinv.meta.AssetDescriptors; +import be.seeseepuff.pcinv.models.GenericAsset; +import be.seeseepuff.pcinv.repositories.AssetRepository; +import be.seeseepuff.pcinv.repositories.GenericAssetRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Collection; + +/** + * Service for managing assets in the repository. + * Provides methods to interact with the asset repositories. + */ +@Service +@RequiredArgsConstructor +public class AssetService { + private final GenericAssetRepository assetRepository; + private final Collection> repositories; + + /** + * Returns the count of all assets in the repository. + * + * @return the total number of assets + */ + public long countAssets() { + return assetRepository.count(); + } + + public AssetDescriptors getAssetDescriptors() { + var descriptors = new AssetDescriptors(); + descriptors.loadFrom(GenericAsset.class); + for (var repository : repositories) { + descriptors.loadFrom(repository.getAssetType()); + } + return descriptors; + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 91c83da..1b82a3d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -3,4 +3,4 @@ server.port=8088 spring.datasource.url=jdbc:postgresql://localhost:5432/pcinv spring.datasource.username=pcinv spring.datasource.password=pcinv -a +spring.jpa.hibernate.ddl-auto=update From 8d86b56a34a51cb6b618572b7c1ced7166b0cc7e Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Fri, 6 Jun 2025 10:05:03 +0200 Subject: [PATCH 06/22] Ignore sqlite things --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index c2065bc..7777057 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,7 @@ out/ ### VS Code ### .vscode/ + +### Old SQLite DB files ### +*.db +*.db3* From 07765b83674542fd10ba85c236e9b31382ee679c Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sat, 7 Jun 2025 08:47:53 +0200 Subject: [PATCH 07/22] Working on fragments --- .../pcinv/controllers/WebController.java | 11 +++++++++-- src/main/resources/templates/fragments.html | 13 +++++++++++++ src/main/resources/templates/index.html | 14 +++++--------- 3 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 src/main/resources/templates/fragments.html diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java index 7c6d0df..caf9de1 100644 --- a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java +++ b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -1,12 +1,19 @@ package be.seeseepuff.pcinv.controllers; +import be.seeseepuff.pcinv.services.AssetService; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +@RequiredArgsConstructor @Controller public class WebController { + private final AssetService assetService; + @GetMapping("/") - public String index() { - return "index"; // This will resolve to src/main/resources/templates/index.html + public String index(Model model) { + model.addAttribute("asset_count", assetService.countAssets()); + return "index"; } } diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html new file mode 100644 index 0000000..4ca2c16 --- /dev/null +++ b/src/main/resources/templates/fragments.html @@ -0,0 +1,13 @@ + + + + + PC Inventory + + +

    PC Inventory -

    +
    +
    +
    + + diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index a4ee839..f528dd7 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -1,9 +1,5 @@ - - - - PC Inventory - - -

    Hello, world!

    - - +
    +
    +

    This system holds 5 assets.

    +
    +
    From e703da995e42a3cfea521ce6b3922aa73f165359 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sat, 7 Jun 2025 17:32:55 +0200 Subject: [PATCH 08/22] 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.

    From a9889511d1b76d69b350467d7c5be617accf450f Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sat, 7 Jun 2025 19:22:14 +0200 Subject: [PATCH 09/22] Fix Thymeleaf syntax for fragment replacement in asset creation pages --- src/main/resources/templates/create_asset.html | 2 +- src/main/resources/templates/create_select.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/create_asset.html b/src/main/resources/templates/create_asset.html index 58948e2..53e4451 100644 --- a/src/main/resources/templates/create_asset.html +++ b/src/main/resources/templates/create_asset.html @@ -1,4 +1,4 @@ - +
    Create a
    diff --git a/src/main/resources/templates/create_select.html b/src/main/resources/templates/create_select.html index 80c5f8f..d10491d 100644 --- a/src/main/resources/templates/create_select.html +++ b/src/main/resources/templates/create_select.html @@ -1,4 +1,4 @@ - +
    Create a new device
      From 1c65630565fb4c0413de089fdde8783c54eaa9f2 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sat, 7 Jun 2025 19:30:42 +0200 Subject: [PATCH 10/22] Add asset creation form handling and update asset interface --- .../pcinv/controllers/WebController.java | 21 +++++++++++++++++++ .../be/seeseepuff/pcinv/models/Asset.java | 2 ++ .../resources/templates/create_asset.html | 2 +- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java index 80676ae..5b303c2 100644 --- a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java +++ b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -2,10 +2,14 @@ package be.seeseepuff.pcinv.controllers; import be.seeseepuff.pcinv.services.AssetService; import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; /** * Controller for handling web requests related to assets. @@ -51,4 +55,21 @@ public class WebController { model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(type)); return "create_asset"; } + + /** + * Handles the form submission for creating an asset. + * + * @param model The model to add attributes to. + * @param type The type of asset to create. + * @return The view name for creating the asset. + */ + @PostMapping( + value = "/create/{type}", + consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE + ) + public String createTypePost(Model model, @PathVariable String type, @RequestBody MultiValueMap formData) { + model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(type)); + model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(type)); + return "create_asset"; + } } diff --git a/src/main/java/be/seeseepuff/pcinv/models/Asset.java b/src/main/java/be/seeseepuff/pcinv/models/Asset.java index 7e295d6..aec1485 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/Asset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/Asset.java @@ -4,4 +4,6 @@ public interface Asset { long getId(); GenericAsset getAsset(); + + void setAsset(GenericAsset asset); } diff --git a/src/main/resources/templates/create_asset.html b/src/main/resources/templates/create_asset.html index 53e4451..9688384 100644 --- a/src/main/resources/templates/create_asset.html +++ b/src/main/resources/templates/create_asset.html @@ -1,7 +1,7 @@
      Create a - +

      From 257abddc1591d8fe38d4f3b31c259be6b2a2bb01 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sun, 8 Jun 2025 06:10:45 +0200 Subject: [PATCH 11/22] Actually support creating assets --- .../pcinv/controllers/WebController.java | 13 ++- .../pcinv/meta/AssetDescriptor.java | 34 +++++- .../be/seeseepuff/pcinv/meta/AssetOption.java | 2 + .../seeseepuff/pcinv/meta/AssetProperty.java | 27 ++++- .../be/seeseepuff/pcinv/models/Asset.java | 4 + .../be/seeseepuff/pcinv/models/HddAsset.java | 2 +- .../be/seeseepuff/pcinv/models/RamAsset.java | 2 +- .../pcinv/repositories/AssetRepository.java | 11 ++ .../pcinv/services/AssetService.java | 108 +++++++++++++++++- 9 files changed, 192 insertions(+), 11 deletions(-) diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java index 5b303c2..14842ea 100644 --- a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java +++ b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -11,6 +11,8 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import java.util.HashMap; + /** * Controller for handling web requests related to assets. * Provides endpoints for viewing and creating assets. @@ -68,8 +70,13 @@ public class WebController { consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE ) public String createTypePost(Model model, @PathVariable String type, @RequestBody MultiValueMap formData) { - model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(type)); - model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(type)); - return "create_asset"; + var formMap = new HashMap(); + formData.forEach((key, values) -> { + if (!values.isEmpty()) { + formMap.put(key, values.getFirst()); + } + }); + var asset = assetService.createAsset(type, formMap); + return "redirect:/view/" + asset.getQr(); } } diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java index 5acb1a8..517be6a 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java @@ -1,11 +1,14 @@ package be.seeseepuff.pcinv.meta; +import be.seeseepuff.pcinv.models.Asset; import lombok.Builder; import lombok.Getter; +import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collection; import java.util.Objects; +import java.util.function.Supplier; /** * Describes the properties of an asset @@ -26,6 +29,9 @@ public class AssetDescriptor { @lombok.Singular private Collection properties; + /// A supplier that can be used to create a new instance of the asset type described by this descriptor. + private Supplier instanceProducer; + /** * Loads the asset properties from a given asset class. * @@ -35,15 +41,25 @@ public class AssetDescriptor { public static AssetDescriptor loadFrom(Class assetType) { var assetInfo = assetType.getAnnotation(AssetInfo.class); Objects.requireNonNull(assetInfo, "Asset class must be annotated with @AssetInfo"); - return AssetDescriptor.builder() + var builder = AssetDescriptor.builder() .type(assetInfo.type()) .displayName(assetInfo.displayName()) .visible(assetInfo.isVisible()) .properties(Arrays.stream(assetType.getDeclaredFields()) .map(AssetProperty::loadFrom) .filter(Objects::nonNull) - .toList()) - .build(); + .toList()); + if (Asset.class.isAssignableFrom(assetType)) { + builder.instanceProducer(() -> { + try { + return (Asset) assetType.getConstructor().newInstance(); + } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | + InvocationTargetException e) { + throw new RuntimeException(e); + } + }); + } + return builder.build(); } /** @@ -78,4 +94,16 @@ public class AssetDescriptor { public String asString(AssetProperty property) { return String.format("%s-%s", type, property.getName()); } + + /** + * Creates a new instance of the asset type described by this descriptor. + * + * @return A new instance of the asset type. + */ + public Asset newInstance() { + if (instanceProducer == null) { + throw new IllegalStateException("Instance producer is not set for asset descriptor: " + type); + } + return instanceProducer.get(); + } } diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetOption.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetOption.java index 1322fa1..aa22bf2 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetOption.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetOption.java @@ -13,6 +13,8 @@ public class AssetOption { private final String value; /// The display name of the option. private final String displayName; + /// The actual enum value associated with this option. + private final Object enumConstant; /// 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 7578104..78fe0e6 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java @@ -9,6 +9,7 @@ import lombok.Singular; import java.lang.reflect.Field; import java.util.List; +import java.util.function.BiConsumer; /** * Represents a property of an asset, such as its name or type. @@ -32,6 +33,8 @@ public class AssetProperty { private final boolean capacityAsSI; /// Whether the capacity should be displayed in IEC units (e.g., GiB, MiB). private final boolean capacityAsIEC; + /// A setter function that can be used to set the value of the property on an asset. + private final BiConsumer setter; /** * Enum representing the possible types of asset properties. @@ -74,7 +77,15 @@ public class AssetProperty { .name(property.getName()) .displayName(annotation.value()) .type(type) - .required(annotation.required()); + .required(annotation.required()) + .setter((obj, value) -> { + try { + property.setAccessible(true); + property.set(obj, value); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); if (type.isEnum) { var enumConstants = property.getType().getEnumConstants(); @@ -86,6 +97,7 @@ public class AssetProperty { .value(assetEnum.getValue()) .displayName(assetEnum.getDisplayName()) .isDefaultValue(assetEnum.isDefaultValue()) + .enumConstant(enumConstant) .build(); builder.option(option); } @@ -119,6 +131,19 @@ public class AssetProperty { } } + /** + * Sets the value of the property on the given asset. + * + * @param asset The asset to set the property on. + * @param value The value to set for the property. + */ + public void setValue(Object asset, Object value) { + if (value == null && required) { + throw new IllegalArgumentException("Property '" + name + "' is required but received null value."); + } + setter.accept(asset, value); + } + @Override public String toString() { var enumOptions = ""; diff --git a/src/main/java/be/seeseepuff/pcinv/models/Asset.java b/src/main/java/be/seeseepuff/pcinv/models/Asset.java index aec1485..0d97e25 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/Asset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/Asset.java @@ -5,5 +5,9 @@ public interface Asset { GenericAsset getAsset(); + default long getQr() { + return getAsset().getQr(); + } + void setAsset(GenericAsset asset); } diff --git a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java index b81da1a..305a07f 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java @@ -31,7 +31,7 @@ public class HddAsset implements Asset /// The capacity of the drive in bytes. @Property("Capacity") @Capacity(si = true, iec = true) - private long capacity; + private Long capacity; /// The drive's interface type, such as SATA, IDE, ISA-16, ... @Property("Interface Type") diff --git a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java index 0150ca8..787306f 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java @@ -31,7 +31,7 @@ public class RamAsset implements Asset /// The capacity of the RAM in bytes. @Property("Capacity") @Capacity - private long capacity; + private Long capacity; /// The type of memory. E.g.: DDR2, SDRAM, ISA-8, etc... @Property("Type") diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java index 09aa689..36b837c 100644 --- a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java +++ b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java @@ -5,6 +5,17 @@ import org.springframework.stereotype.Repository; @Repository public interface AssetRepository { + T saveAndFlush(T entity); + + default Asset saveAndFlushAsset(Asset entity) { + if (getAssetType().isInstance(entity)) { + //noinspection unchecked + return saveAndFlush((T) entity); + } else { + throw new ClassCastException("Entity is not of type " + getAssetType().getName()); + } + } + Class getAssetType(); long count(); diff --git a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java index 5596de7..8d494db 100644 --- a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java +++ b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java @@ -2,14 +2,19 @@ package be.seeseepuff.pcinv.services; import be.seeseepuff.pcinv.meta.AssetDescriptor; import be.seeseepuff.pcinv.meta.AssetDescriptors; +import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.AssetProperty; +import be.seeseepuff.pcinv.models.Asset; import be.seeseepuff.pcinv.models.GenericAsset; import be.seeseepuff.pcinv.repositories.AssetRepository; import be.seeseepuff.pcinv.repositories.GenericAssetRepository; 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; /** * Service for managing assets in the repository. @@ -18,7 +23,7 @@ import java.util.List; @Service @RequiredArgsConstructor public class AssetService { - private final GenericAssetRepository assetRepository; + private final GenericAssetRepository genericRepository; private final Collection> repositories; /** @@ -27,7 +32,7 @@ public class AssetService { * @return the total number of assets */ public long countAssets() { - return assetRepository.count(); + return genericRepository.count(); } /** @@ -69,4 +74,103 @@ public class AssetService { } return List.of(getAssetDescriptor("asset"), getAssetDescriptor(type)); } + + /** + * Creates a new asset of the specified type with the provided form data. + * + * @param type The type of asset to create. + * @param formData The form data containing the asset properties. + * @return The created asset. + */ + @Transactional + public Asset createAsset(String type, Map formData) { + var genericDescriptor = getAssetDescriptor("asset"); + var assetDescriptor = getAssetDescriptor(type); + + var genericAsset = new GenericAsset(); + fillIn(genericAsset, genericDescriptor, formData); + + var asset = assetDescriptor.newInstance(); + fillIn(asset, assetDescriptor, formData); + + genericAsset = genericRepository.saveAndFlush(genericAsset); + asset.setAsset(genericAsset); + asset = getRepositoryFor(type).saveAndFlushAsset(asset); + return asset; + } + + /** + * Gets the asset repository for the specified type. + * + * @param type the type of asset to get the repository for + * @return the AssetRepository for the specified type + */ + private AssetRepository getRepositoryFor(String type) { + return repositories.stream() + .filter(repo -> repo.getAssetType().getAnnotation(AssetInfo.class).type().equals(type)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No repository found for type: " + type)); + } + + /** + * Fills in the properties of a generic asset from the form data. + * + * @param asset The generic asset to fill in. + * @param assetDescriptor The descriptor containing the properties to fill in. + * @param formData The form data containing the property values. + */ + private void fillIn(Object asset, AssetDescriptor assetDescriptor, Map formData) { + for (var property : assetDescriptor.getProperties()) { + var value = parseValue(assetDescriptor, property, formData); + if (value != null) { + property.setValue(asset, value); + } else if (property.isRequired()) { + throw new IllegalArgumentException("Property '" + property.getName() + "' is required but not provided."); + } + } + } + + /** + * Parses the string value into the appropriate type based on the asset property. + * + * @param descriptor The asset descriptor containing the property. + * @param property The asset property to determine the type. + * @param values The map of values from the form data. + * @return The parsed value as an Object. + */ + private Object parseValue(AssetDescriptor descriptor, AssetProperty property, Map values) { + if (property.getType() == AssetProperty.Type.CAPACITY) { + var value = values.get(descriptor.asString(property) + "-value"); + var unit = values.get(descriptor.asString(property) + "-unit"); + if (value == null || value.isBlank() || unit == null || unit.isBlank()) { + return null; + } + + try { + return Long.parseLong(value) * Long.parseLong(unit); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Invalid numeric value for property '" + property.getName() + "': " + value, e); + } + } + + var stringValue = values.get(descriptor.asString(property)); + if (stringValue == null || stringValue.isBlank()) { + return null; + } + + if (property.getType() == AssetProperty.Type.INTEGER) { + return Integer.parseInt(stringValue); + } else if (property.getType() == AssetProperty.Type.STRING) { + return stringValue; + } else if (property.getType().isEnum) { + for (var option : property.getOptions()) { + if (option.getValue().equals(stringValue)) { + return option.getEnumConstant(); + } + } + throw new IllegalArgumentException("Invalid value for enum property '" + property.getName() + "': " + stringValue); + } else { + throw new IllegalArgumentException("Unsupported property type: " + property.getType()); + } + } } From d4718d15c3f717bd20e4211199e2dedcb263b2c9 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sun, 8 Jun 2025 06:47:34 +0200 Subject: [PATCH 12/22] Can show an asset --- .../pcinv/controllers/WebController.java | 18 +++++++ .../pcinv/meta/AssetDescriptor.java | 2 +- .../seeseepuff/pcinv/meta/AssetProperty.java | 52 ++++++++++++++++++- .../seeseepuff/pcinv/models/GenericAsset.java | 5 ++ .../pcinv/repositories/AssetRepository.java | 3 ++ .../repositories/GenericAssetRepository.java | 1 + .../pcinv/services/AssetService.java | 24 +++++++-- src/main/resources/templates/fragments.html | 7 ++- src/main/resources/templates/index.html | 1 - src/main/resources/templates/view.html | 14 +++++ 10 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 src/main/resources/templates/view.html diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java index 14842ea..616cfd6 100644 --- a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java +++ b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -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. */ diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java index 517be6a..e352970 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java @@ -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)) { diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java index 78fe0e6..7defda0 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java @@ -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 setter; + /// A getter function that can be used to get the value of the property from an asset. + private final Function 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 = ""; diff --git a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java index bf66381..fe3e1cc 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java @@ -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; diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java index 36b837c..ac3ce64 100644 --- a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java +++ b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java @@ -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 findByAsset(GenericAsset asset); + Class getAssetType(); long count(); diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java index a989809..e672dcc 100644 --- a/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java +++ b/src/main/java/be/seeseepuff/pcinv/repositories/GenericAssetRepository.java @@ -5,4 +5,5 @@ import org.springframework.data.jpa.repository.JpaRepository; @SuppressWarnings("unused") public interface GenericAssetRepository extends JpaRepository { + GenericAsset findByQr(long qr); } diff --git a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java index 8d494db..62e7108 100644 --- a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java +++ b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java @@ -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 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 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(); diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html index 9200871..86928ef 100644 --- a/src/main/resources/templates/fragments.html +++ b/src/main/resources/templates/fragments.html @@ -4,7 +4,12 @@ PC Inventory -

      PC Inventory -

      +

      PC Inventory - +

      +
      + Home + Browse + Create
      diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 5a58521..f528dd7 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -1,6 +1,5 @@
      - Create a new device

      This system holds 5 assets.

      diff --git a/src/main/resources/templates/view.html b/src/main/resources/templates/view.html new file mode 100644 index 0000000..cba2a27 --- /dev/null +++ b/src/main/resources/templates/view.html @@ -0,0 +1,14 @@ + +
      + View device details +
      +

      +
      + + + + +
      +
      +
      + From 3a706130237620c55df011194a9fbdf73b46b436 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sun, 8 Jun 2025 07:02:13 +0200 Subject: [PATCH 13/22] Close to the same feature set as the Go interface --- .../pcinv/controllers/WebController.java | 32 ++++++++++++++++++- .../pcinv/meta/AssetDescriptor.java | 6 +++- .../be/seeseepuff/pcinv/meta/AssetInfo.java | 11 +++++-- .../seeseepuff/pcinv/models/GenericAsset.java | 1 + .../be/seeseepuff/pcinv/models/HddAsset.java | 1 + .../be/seeseepuff/pcinv/models/RamAsset.java | 1 + .../pcinv/repositories/AssetRepository.java | 4 +++ .../pcinv/services/AssetService.java | 10 ++++++ src/main/resources/templates/browse.html | 10 ++++++ src/main/resources/templates/browse_type.html | 13 ++++++++ .../resources/templates/create_asset.html | 2 +- src/main/resources/templates/view.html | 2 +- 12 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 src/main/resources/templates/browse.html create mode 100644 src/main/resources/templates/browse_type.html diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java index 616cfd6..2c9aa7b 100644 --- a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java +++ b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -24,7 +24,10 @@ 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"; + /// The name of the model attribute that holds the list of assets. + private static final String ASSETS = "assets"; + /// The name of the model attribute that holds a list of all properties of all descriptors. + private static final String PROPERTIES = "properties"; private final AssetService assetService; @@ -38,6 +41,33 @@ public class WebController { return "index"; } + /** + * Shows a view where a user can select the type of asset to browse. + */ + @GetMapping("/browse") + public String browse(Model model) { + model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptors()); + return "browse"; + } + + /** + * Handles the browsing of assets by type. + * Displays the asset descriptor tree and the specific descriptor for the given type. + * + * @param model The model to add attributes to. + * @param type The type of asset to browse. + * @return The view name for browsing assets by type. + */ + @GetMapping("/browse/{type}") + public String browseType(Model model, @PathVariable String type) { + var tree = assetService.getAssetDescriptorTree(type); + model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(type)); + model.addAttribute(DESCRIPTORS, tree); + model.addAttribute(PROPERTIES, tree.stream().flatMap(d -> d.getProperties().stream()).toList()); + model.addAttribute(ASSETS, assetService.getAssetsByType(type)); + return "browse_type"; + } + /** * Handles the view of an asset by its QR code. * If the asset does not exist, it redirects to the index page. diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java index e352970..ce59f3f 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetDescriptor.java @@ -19,9 +19,12 @@ public class AssetDescriptor { /// The type of property, e.g.: ram, asset, etc... private final String type; - /// The displayable name of the property, e.g.: "Random Access memory" + /// The displayable name of the property, e.g.: "Random Access Memory" private final String displayName; + /// The plural name of the property, e.g.: "Random Access Memories" + private final String pluralName; + /// Whether the asset is visible in the user interface. private final boolean visible; @@ -44,6 +47,7 @@ public class AssetDescriptor { var builder = AssetDescriptor.builder() .type(assetInfo.type()) .displayName(assetInfo.displayName()) + .pluralName(assetInfo.pluralName()) .visible(assetInfo.isVisible()) .properties(Arrays.stream(assetType.getDeclaredFields()) .map(field -> AssetProperty.loadFrom(field, assetInfo.type())) diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java index 40dcb49..28ad4a9 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetInfo.java @@ -16,14 +16,21 @@ public @interface AssetInfo { * * @return the display name of the asset type */ - String displayName() default ""; + String displayName(); + + /** + * The plural name of the asset type, used for display purposes. + * + * @return the plural name of the asset type + */ + String pluralName(); /** * The type of the asset, which can be a string or an integer. * * @return the type of the asset */ - String type() default ""; + String type(); /** * Indicates whether the asset type should be visible in the UI. diff --git a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java index fe3e1cc..7255c49 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java @@ -17,6 +17,7 @@ import lombok.Setter; @Entity @AssetInfo( displayName = "Asset", + pluralName = "Assets", type = "asset", isVisible = false ) diff --git a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java index 305a07f..d77cff3 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java @@ -15,6 +15,7 @@ import lombok.Setter; @Entity @AssetInfo( displayName = "Hard Drive", + pluralName = "Hard Drives", type = "HDD" ) @Table(name = "hdd_assets") diff --git a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java index 787306f..527649e 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/RamAsset.java @@ -15,6 +15,7 @@ import lombok.Setter; @Entity @AssetInfo( displayName = "Random Access Memory", + pluralName = "Random Access Memories", type = "RAM" ) @Table(name = "ram_assets") diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java index ac3ce64..36fb356 100644 --- a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java +++ b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java @@ -4,6 +4,8 @@ import be.seeseepuff.pcinv.models.Asset; import be.seeseepuff.pcinv.models.GenericAsset; import org.springframework.stereotype.Repository; +import java.util.List; + @Repository public interface AssetRepository { T saveAndFlush(T entity); @@ -19,6 +21,8 @@ public interface AssetRepository { T findByAsset(GenericAsset asset); + List findAll(); + Class getAssetType(); long count(); diff --git a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java index 62e7108..d1c56a0 100644 --- a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java +++ b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java @@ -50,6 +50,16 @@ public class AssetService { return getRepositoryFor(genericAsset.getType()).findByAsset(genericAsset); } + /** + * Retrieves all assets of a specific type. + * + * @param type the type of asset to retrieve + * @return a list of assets of the specified type + */ + public List getAssetsByType(String type) { + return getRepositoryFor(type).findAll(); + } + /** * Retrieves the global asset descriptors for all asset types. * diff --git a/src/main/resources/templates/browse.html b/src/main/resources/templates/browse.html new file mode 100644 index 0000000..5e6f3a5 --- /dev/null +++ b/src/main/resources/templates/browse.html @@ -0,0 +1,10 @@ + +
      + View device details +
        +
      • + +
      • +
      +
      + diff --git a/src/main/resources/templates/browse_type.html b/src/main/resources/templates/browse_type.html new file mode 100644 index 0000000..ceee163 --- /dev/null +++ b/src/main/resources/templates/browse_type.html @@ -0,0 +1,13 @@ + +
      + There are in the database. + + + + + + + +
      +
      + diff --git a/src/main/resources/templates/create_asset.html b/src/main/resources/templates/create_asset.html index 9688384..713773c 100644 --- a/src/main/resources/templates/create_asset.html +++ b/src/main/resources/templates/create_asset.html @@ -1,4 +1,4 @@ - +
      Create a diff --git a/src/main/resources/templates/view.html b/src/main/resources/templates/view.html index cba2a27..832ab0a 100644 --- a/src/main/resources/templates/view.html +++ b/src/main/resources/templates/view.html @@ -1,4 +1,4 @@ - +
      View device details
      From 72f4a1cd2841bd303eb635d58237387f69ca17d1 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sun, 8 Jun 2025 08:57:55 +0200 Subject: [PATCH 14/22] Working on edit --- src/main/resources/templates/browse_type.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/resources/templates/browse_type.html b/src/main/resources/templates/browse_type.html index ceee163..4193c60 100644 --- a/src/main/resources/templates/browse_type.html +++ b/src/main/resources/templates/browse_type.html @@ -2,11 +2,15 @@
      There are in the database. - - + + + +
      Actions
      + Edit +
      From afd36bcb0c29ad010bdfb2ab78e365634227e996 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sun, 8 Jun 2025 14:20:29 +0200 Subject: [PATCH 15/22] Can create, edit, view, and delete assets --- .../pcinv/controllers/WebController.java | 95 ++++++++++++++++++- .../seeseepuff/pcinv/meta/AssetProperty.java | 15 ++- .../be/seeseepuff/pcinv/meta/InputList.java | 15 +++ .../seeseepuff/pcinv/models/GenericAsset.java | 12 ++- .../pcinv/repositories/AssetRepository.java | 6 +- .../pcinv/services/AssetService.java | 44 ++++++++- src/main/resources/templates/browse_type.html | 10 +- .../resources/templates/create_asset.html | 31 +++--- src/main/resources/templates/fragments.html | 4 + src/main/resources/templates/index.html | 7 ++ src/main/resources/templates/view.html | 18 +++- 11 files changed, 223 insertions(+), 34 deletions(-) create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/InputList.java diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java index 2c9aa7b..ba863b2 100644 --- a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java +++ b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -2,6 +2,7 @@ package be.seeseepuff.pcinv.controllers; import be.seeseepuff.pcinv.services.AssetService; import lombok.RequiredArgsConstructor; +import org.springframework.data.repository.query.Param; import org.springframework.http.MediaType; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -26,8 +27,14 @@ public class WebController { private static final String DESCRIPTOR = "descriptor"; /// The name of the model attribute that holds the list of assets. private static final String ASSETS = "assets"; + /// The name of the model attribute that holds the asset being viewed or edited. + private static final String ASSET = "asset"; /// The name of the model attribute that holds a list of all properties of all descriptors. private static final String PROPERTIES = "properties"; + /// The name of the model attribute that holds the action to be performed. + 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"; private final AssetService assetService; @@ -36,6 +43,7 @@ public class WebController { */ @GetMapping("/") public String index(Model model) { + model.addAttribute(TIME, System.currentTimeMillis()); model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptors()); model.addAttribute("asset_count", assetService.countAssets()); return "index"; @@ -46,6 +54,7 @@ public class WebController { */ @GetMapping("/browse") public String browse(Model model) { + model.addAttribute(TIME, System.currentTimeMillis()); model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptors()); return "browse"; } @@ -60,6 +69,7 @@ public class WebController { */ @GetMapping("/browse/{type}") public String browseType(Model model, @PathVariable String type) { + model.addAttribute(TIME, System.currentTimeMillis()); var tree = assetService.getAssetDescriptorTree(type); model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(type)); model.addAttribute(DESCRIPTORS, tree); @@ -76,20 +86,62 @@ public class WebController { */ @GetMapping("/view/{qr}") public String view(Model model, @PathVariable long qr) { + model.addAttribute(TIME, System.currentTimeMillis()); + model.addAttribute(ACTION, "view"); + return renderView(model, qr); + } + + /** + * Show a page asking if the user wants to delete an asset. + * + * @param qr The QR code of the asset to delete. + */ + @GetMapping("/delete/{qr}") + public String delete(Model model, @PathVariable long qr, @Param("confirm") boolean confirm) { + model.addAttribute(TIME, System.currentTimeMillis()); + model.addAttribute(ACTION, "delete"); + if (confirm) { + var asset = assetService.getAssetByQr(qr); + if (asset == null) { + return "redirect:/"; + } + var type = asset.getAsset().getType(); + assetService.deleteAsset(qr); + return "redirect:/browse/" + type; + } + return renderView(model, qr); + } + + /** + * Renders the view for an asset based on its QR code. + * If the asset does not exist, it redirects to the index page. + * + * @param model The model to add attributes to. + * @param qr The QR code of the asset to view. + * @return The view name for viewing the asset. + */ + private String renderView(Model model, long qr) { var asset = assetService.getAssetByQr(qr); if (asset == null) { return "redirect:/"; } - model.addAttribute("asset", asset); + model.addAttribute(ASSET, asset); model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(asset.getAsset().getType())); + model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(asset.getAsset().getType())); return "view"; } + @GetMapping("/search") + public String search(@Param("qr") long qr) { + return "redirect:/view/" + qr; + } + /** * Shows a view where the user can create the type of asset to create. */ @GetMapping("/create") public String create(Model model) { + model.addAttribute(TIME, System.currentTimeMillis()); model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptors()); return "create_select"; } @@ -101,11 +153,51 @@ public class WebController { */ @GetMapping("/create/{type}") public String createType(Model model, @PathVariable String type) { + model.addAttribute(TIME, System.currentTimeMillis()); + model.addAttribute(ACTION, "create"); model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(type)); model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(type)); return "create_asset"; } + /** + * Shows a view where the user can edit an existing asset. + * + * @param qr The QR code of the asset to edit. + */ + @GetMapping("/edit/{qr}") + public String edit(Model model, @PathVariable long qr) { + model.addAttribute(TIME, System.currentTimeMillis()); + var asset = assetService.getAssetByQr(qr); + if (asset == null) { + throw new RuntimeException("Asset not found"); + } + String assetType = asset.getAsset().getType(); + model.addAttribute(ACTION, "edit"); + model.addAttribute(ASSET, asset); + model.addAttribute(DESCRIPTORS, assetService.getAssetDescriptorTree(assetType)); + model.addAttribute(DESCRIPTOR, assetService.getAssetDescriptor(assetType)); + return "create_asset"; + } + + /** + * Actually edits an asset based on the form data submitted. + * + * @param qr The QR code of the asset to edit. + */ + @PostMapping("/edit/{qr}") + public String editPost(Model model, @PathVariable long qr, @RequestBody MultiValueMap formData) { + model.addAttribute(TIME, System.currentTimeMillis()); + var formMap = new HashMap(); + formData.forEach((key, values) -> { + if (!values.isEmpty()) { + formMap.put(key, values.getFirst()); + } + }); + var asset = assetService.editAsset(qr, formMap); + return "redirect:/view/" + asset.getQr(); + } + /** * Handles the form submission for creating an asset. * @@ -118,6 +210,7 @@ public class WebController { consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE ) public String createTypePost(Model model, @PathVariable String type, @RequestBody MultiValueMap formData) { + model.addAttribute(TIME, System.currentTimeMillis()); var formMap = new HashMap(); formData.forEach((key, values) -> { if (!values.isEmpty()) { diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java index 7defda0..8e40f90 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java @@ -41,6 +41,8 @@ public class AssetProperty { private final BiConsumer setter; /// A getter function that can be used to get the value of the property from an asset. private final Function getter; + /// Whether the property is an input list. + private final boolean inputList; /** * Enum representing the possible types of asset properties. @@ -84,6 +86,7 @@ public class AssetProperty { .displayName(annotation.value()) .type(type) .required(annotation.required()) + .inputList(property.isAnnotationPresent(InputList.class)) .setter((obj, value) -> { try { property.setAccessible(true); @@ -167,7 +170,11 @@ public class AssetProperty { * @param asset The asset to get the property value from. * @return The value of the property. */ - public Object getValue(Object asset) { + @Nullable + public Object getValue(@Nullable Object asset) { + if (asset == null) { + return null; + } return getter.apply(asset); } @@ -176,7 +183,11 @@ public class AssetProperty { * * @return The rendered value as a string. */ - public String renderValue(Object asset) { + @Nullable + public String renderValue(@Nullable Object asset) { + if (asset == null) { + return null; + } var value = getValue(asset); if (value == null) { return "Unknown"; diff --git a/src/main/java/be/seeseepuff/pcinv/meta/InputList.java b/src/main/java/be/seeseepuff/pcinv/meta/InputList.java new file mode 100644 index 0000000..333fa7c --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/InputList.java @@ -0,0 +1,15 @@ +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 any form for the given field should be rendered as a dropdown + * list with optional manual text input. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface InputList { +} diff --git a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java index 7255c49..ef170de 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java @@ -1,11 +1,9 @@ package be.seeseepuff.pcinv.models; import be.seeseepuff.pcinv.meta.AssetInfo; +import be.seeseepuff.pcinv.meta.InputList; import be.seeseepuff.pcinv.meta.Property; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; import lombok.Getter; import lombok.Setter; @@ -21,7 +19,10 @@ import lombok.Setter; type = "asset", isVisible = false ) -@Table(name = "assets") +@Table( + name = "assets", + uniqueConstraints = @UniqueConstraint(columnNames = "qr") +) public class GenericAsset { public static final String TYPE = "asset"; @@ -38,6 +39,7 @@ public class GenericAsset /// The brand of the asset. @Property("Brand") + @InputList private String brand; /// The model of the asset diff --git a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java index 36fb356..294ac9d 100644 --- a/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java +++ b/src/main/java/be/seeseepuff/pcinv/repositories/AssetRepository.java @@ -21,9 +21,11 @@ public interface AssetRepository { T findByAsset(GenericAsset asset); + void deleteByAsset(GenericAsset asset); + + void flush(); + List findAll(); Class getAssetType(); - - long count(); } diff --git a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java index d1c56a0..88bd8e2 100644 --- a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java +++ b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java @@ -125,6 +125,31 @@ public class AssetService { return asset; } + /** + * Edits an existing asset with the provided form data. + * + * @param qr The QR code of the asset to edit. + * @param formData The form data containing the updated asset properties. + * @return The updated asset. + */ + @Transactional + public Asset editAsset(long qr, Map formData) { + var genericAsset = genericRepository.findByQr(qr); + if (genericAsset == null) { + throw new IllegalArgumentException("No asset found with QR code: " + qr); + } + + var assetType = genericAsset.getType(); + var assetDescriptor = getAssetDescriptor(assetType); + var asset = getRepositoryFor(assetType).findByAsset(genericAsset); + + fillIn(genericAsset, getAssetDescriptor(GenericAsset.TYPE), formData); + fillIn(asset, assetDescriptor, formData); + + genericRepository.saveAndFlush(genericAsset); + return getRepositoryFor(assetType).saveAndFlushAsset(asset); + } + /** * Gets the asset repository for the specified type. * @@ -148,11 +173,10 @@ public class AssetService { private void fillIn(Object asset, AssetDescriptor assetDescriptor, Map formData) { for (var property : assetDescriptor.getProperties()) { var value = parseValue(assetDescriptor, property, formData); - if (value != null) { - property.setValue(asset, value); - } else if (property.isRequired()) { + if (value == null && property.isRequired()) { throw new IllegalArgumentException("Property '" + property.getName() + "' is required but not provided."); } + property.setValue(asset, value); } } @@ -199,4 +223,18 @@ public class AssetService { throw new IllegalArgumentException("Unsupported property type: " + property.getType()); } } + + @Transactional + public void deleteAsset(long qr) { + var genericAsset = genericRepository.findByQr(qr); + if (genericAsset == null) { + throw new IllegalArgumentException("No asset found with QR code: " + qr); + } + var assetType = genericAsset.getType(); + var assetRepository = getRepositoryFor(assetType); + assetRepository.deleteByAsset(genericAsset); + assetRepository.flush(); + genericRepository.delete(genericAsset); + genericRepository.flush(); + } } diff --git a/src/main/resources/templates/browse_type.html b/src/main/resources/templates/browse_type.html index 4193c60..ad0420f 100644 --- a/src/main/resources/templates/browse_type.html +++ b/src/main/resources/templates/browse_type.html @@ -1,15 +1,19 @@
      There are in the database. - +
      - +
      Actions
      + + + - Edit + View + Edit
      diff --git a/src/main/resources/templates/create_asset.html b/src/main/resources/templates/create_asset.html index 713773c..90cec0f 100644 --- a/src/main/resources/templates/create_asset.html +++ b/src/main/resources/templates/create_asset.html @@ -1,30 +1,30 @@
      Create a - +

      - +
      - +
      - - + + Bad input type for @@ -33,7 +33,8 @@

      - + +

      diff --git a/src/main/resources/templates/fragments.html b/src/main/resources/templates/fragments.html index 86928ef..edde0fb 100644 --- a/src/main/resources/templates/fragments.html +++ b/src/main/resources/templates/fragments.html @@ -13,5 +13,9 @@
      +
      +

      + Rendered in 25ms on . +

      diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index f528dd7..e170565 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -1,5 +1,12 @@

      This system holds 5 assets.

      +
      + + +
      diff --git a/src/main/resources/templates/view.html b/src/main/resources/templates/view.html index 832ab0a..a6075eb 100644 --- a/src/main/resources/templates/view.html +++ b/src/main/resources/templates/view.html @@ -1,14 +1,26 @@
      - View device details +

      Details of Hard Disk Drive 21

      +

      Are you sure you want to delete Hard Disk Drive 21

      - +
      - +
      +

      +

      + +

      From a490b2a3067aab8565040823614f630214daaf0b Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Sun, 8 Jun 2025 14:39:25 +0200 Subject: [PATCH 16/22] Add support for hiding properties in asset overview and enhance input list handling --- .../pcinv/controllers/WebController.java | 4 ++ .../seeseepuff/pcinv/meta/AssetProperty.java | 3 + .../seeseepuff/pcinv/meta/HideInOverview.java | 14 +++++ .../seeseepuff/pcinv/models/GenericAsset.java | 2 + .../be/seeseepuff/pcinv/models/HddAsset.java | 3 + .../pcinv/services/AssetService.java | 56 ++++++++++++++++++- src/main/resources/templates/browse_type.html | 4 +- .../resources/templates/create_asset.html | 8 +++ 8 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 src/main/java/be/seeseepuff/pcinv/meta/HideInOverview.java diff --git a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java index ba863b2..7ad845b 100644 --- a/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java +++ b/src/main/java/be/seeseepuff/pcinv/controllers/WebController.java @@ -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"; } diff --git a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java index 8e40f90..68e606a 100644 --- a/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java +++ b/src/main/java/be/seeseepuff/pcinv/meta/AssetProperty.java @@ -43,6 +43,8 @@ public class AssetProperty { private final Function 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); diff --git a/src/main/java/be/seeseepuff/pcinv/meta/HideInOverview.java b/src/main/java/be/seeseepuff/pcinv/meta/HideInOverview.java new file mode 100644 index 0000000..a25f732 --- /dev/null +++ b/src/main/java/be/seeseepuff/pcinv/meta/HideInOverview.java @@ -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 { +} diff --git a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java index ef170de..cbbfe47 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/GenericAsset.java @@ -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. diff --git a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java index d77cff3..44ea113 100644 --- a/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java +++ b/src/main/java/be/seeseepuff/pcinv/models/HddAsset.java @@ -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; } diff --git a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java index 88bd8e2..37db028 100644 --- a/src/main/java/be/seeseepuff/pcinv/services/AssetService.java +++ b/src/main/java/be/seeseepuff/pcinv/services/AssetService.java @@ -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 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> getInputList(String type) { + var map = new HashMap>(); + 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 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 inputList = new TreeSet<>(); + for (var entry : entries) { + var value = property.getValue(entry); + if (value != null) { + inputList.add(value.toString()); + } + } + return inputList; + } } diff --git a/src/main/resources/templates/browse_type.html b/src/main/resources/templates/browse_type.html index ad0420f..64373d9 100644 --- a/src/main/resources/templates/browse_type.html +++ b/src/main/resources/templates/browse_type.html @@ -3,11 +3,11 @@ There are in the database. - + - diff --git a/src/main/resources/templates/create_asset.html b/src/main/resources/templates/create_asset.html index 90cec0f..09ab10d 100644 --- a/src/main/resources/templates/create_asset.html +++ b/src/main/resources/templates/create_asset.html @@ -8,6 +8,14 @@
      Actions
      +
      + + + or + + - +

    2@hDA1eC8ex0Yq=)`KpccS9PlNnQ6PfMqs0%lQGD$t{t6(;f2g*DWdIj8FNm=ZL7Mp7K+1#GK55MONe MARFw1$O+8<0eGNIuK)l5 diff --git a/pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.bin b/pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.bin deleted file mode 100644 index c74e9673e3bf93ca2a41977b1d477d8daa5076e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44115 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 diff --git a/pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.lock b/pcinvj/pcinv/.gradle/8.14/executionHistory/executionHistory.lock deleted file mode 100644 index 1d30d9a9a3fd94538b1192327af4a0fffc84be53..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZQ>-t#(P{)XQ_86bcK07l6L)c^nh diff --git a/pcinvj/pcinv/.gradle/8.14/fileChanges/last-build.bin b/pcinvj/pcinv/.gradle/8.14/fileChanges/last-build.bin deleted file mode 100644 index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1 IcmZPo000310RR91 diff --git a/pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.bin b/pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.bin deleted file mode 100644 index edab974799db092f87a2d3bcc42f7144bae0eea8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19147 zcmeI(T}V?=00;0hZPd!GiH63OROo|dADYX_YM>N3^C`uPm|9`8_Oe z%~#*FY8(x8zAwuyfrkR>%?HhN{xz06NORS|?4d}SOIZH#{E^8pi9MI*`&s_IIyu!G z64*}PU&V5fDJDOvYC|8NCz@EUh^bxLdGXsRn#Z#|r9AYODyg)E<^wFxEf-hxU2&E0 zdD1187xe41w;z2SN9X6WoLvbK>G*qig#ZK~009U<00Izz00bZa0SG_<0uX=z1Rwwb z2tWV=5P$##AOHafKmY;|fB*y_009U<00Izz00bZa0SNpXfz{kH;oP~zeadwsgS$o> z=Zzn5J0aI4KC8y_S;V1tx$6q=hURiZN4fr4-lL2Pb7q>f*Q-H#yM&Fz zAH!`VrTNan&&tBslW7574e^{xVPmvR?KD}%Ih|*f5p6zxBVLW5qDEokYF2&C+S;!} zwTo88irOzsdRa4*yLVNkbbD#Ii@T+59QgJkcWo`yU+%Tpkt&BDf( zo`O#kWs6&!3Ekla|NGbZhWZuX@OdL_SRKh3?`-Lo8(mLQihcd#X~RJqGpx}>%IXH! zG(?@NdwXwe--j{2p;hvY6YeJ_rtKr>@J7ooy*>L&aZjgBwkOrokZKfsgM|G87dJte_e$)nS0qgdFOaSgfF{l#z}f4zoQ#TSTYjX%R8P_h63 diff --git a/pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.lock b/pcinvj/pcinv/.gradle/8.14/fileHashes/fileHashes.lock deleted file mode 100644 index e213f32f650cb2e55fae7f0390e8b9935754e423..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 TcmZRswrxp$vc}zs0Rlt;FChcd diff --git a/pcinvj/pcinv/.gradle/8.14/fileHashes/resourceHashesCache.bin b/pcinvj/pcinv/.gradle/8.14/fileHashes/resourceHashesCache.bin deleted file mode 100644 index cd45c02f9afe3fd8e2f98c1d7ea1312539e965af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18565 zcmeI%u}Xqb6ae6hV+fj1*dpyZ1gHcCYxN{at^CbEVpwDSblw;<58LzUANYNe^oOn$=T0>4;3)y)s+-@?2R# zZ)~_#_WQJN9K9D^JF$LB_P^G?^|odq^yAZa>3g($ye0XeSnH!C1g%md#P)=cc`W_UG|Uv>dldhhv)nkq<_?2)z#1w zNt@X(a|X57CMt5S1g6ePZ`NM_rt$I1cwvsrxugC1kzC({@I+kpPit>q_bvvL8#?Ey z_Kv0T>Cwb9r|eH^4?NF&SWrK3Q05=kuHVL_`t56cB7gt_2q1s}0tg_000IagfB*sr zAbc9*hDs8TKN{b1CU$#dyrKSm}Fy?&V3Ar79Lq8Qnq)ivikge85MBp0Nqf zGplqmwqhzDjJ@NLF^&&QVjt&J;3LIB4z*(&EC3TKG6k}ml+m2Whl3T=ISK|l-RzZ#fd8IntMD{1|6_V}00q9_rZVOn$I)V7)qY?~gc7om$7Ovj&{dR&DDZpX{qlpcCBKs!& T4Puv64O^w$#tvy;b{F^q^}@i) diff --git a/pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/controllers/WebController.class b/pcinvj/pcinv/build/classes/java/main/be/seeseepuff/pcinv/controllers/WebController.class deleted file mode 100644 index f8699304af36df5ef57493e9832a2110f7afdc71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 594 zcmb7BO-~y!5PikaiB?&xL~bk^k&}k^LYP0-2VZvhfgtTs7GjY&_qjMd88&v=E@A^Njjp;3$(s# zqy4TxeWTZJqb<B#0pZJ)tLHE8viG)KTAJwcaa5M-YY*LO02SG NZK_;H=my)5z#|F!jfnsN diff --git a/pcinvj/pcinv/build/tmp/compileJava/previous-compilation-data.bin b/pcinvj/pcinv/build/tmp/compileJava/previous-compilation-data.bin deleted file mode 100644 index b1e8211c354558f4b48e784d529474edc735342b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32361 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 - - - - - - - \ No newline at end of file diff --git a/pcinvj/pcinv/src/main/resources/application.properties b/pcinvj/pcinv/src/main/resources/application.properties deleted file mode 100644 index c127059..0000000 --- a/pcinvj/pcinv/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=pcinv diff --git a/pcinvj/pcinv/src/main/resources/templates/index.html b/pcinvj/pcinv/src/main/resources/templates/index.html deleted file mode 100644 index a4ee839..0000000 --- a/pcinvj/pcinv/src/main/resources/templates/index.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - PC Inventory - - -