8 Commits

Author SHA1 Message Date
gballan 7c6977f4a6 Adding event: tag to the asset packaging step
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-14 13:53:50 -04:00
gballan fcee38d33b Updating Grunt commands 2025-07-14 13:53:50 -04:00
gballan 0cc7113a8b Started working on test edit page; removed nodemon because it wasn't working 2025-07-14 13:53:50 -04:00
gballan 107a77d6f8 Added nodemon to dev dependencies to automatially reload the app on changes 2025-07-14 13:53:50 -04:00
gballan 1a3048b5f2 Fixed typo in Gruntfile; added Make tasks to compile frontend assets; Modernized the gathering of benchmark result data for the test view 2025-07-14 13:53:50 -04:00
gballan 4b9065ca4b Improved some styles 2025-07-14 13:53:50 -04:00
gballan acdac071ed [Issue #8] - Licensing project under the BSD 2-Clause license (#10)
Adding license to project

Co-authored-by: Gregory Ballantine <gballantine@bitgoblin.tech>
Reviewed-on: #10
2025-07-14 13:53:50 -04:00
gballan 90472e443c 3-add-gruntjs (#9)
Adding Grunt.js to compile SASS and CoffeeScript assets

Co-authored-by: Gregory Ballantine <gregory.w.ballantine@nasa.gov>
Co-authored-by: Gregory Ballantine <gballantine555@gmail.com>
Reviewed-on: #9
2025-07-14 13:53:50 -04:00
37 changed files with 112 additions and 727 deletions
-21
View File
@@ -1,21 +0,0 @@
[build]
# Tell Air to use your Makefile build target directly!
cmd = "make build BUILD_DIR=/tmp"
# Tell Air where your compiled binary ends up
bin = "/tmp/blt"
# Watch these extensions and ignore these paths
include_ext = ["go", "tpl", "tmpl", "html"]
exclude_dir = ["assets", "tmp", "bin", "node_modules", "public"]
# Delay before rebuilding after a file save (ms)
delay = 500
[log]
time = false
[color]
main = "magenta"
build = "yellow"
runner = "green"
-1
View File
@@ -3,7 +3,6 @@ blt
# Local data files
data/
tmp/
# Compiled assets
public/css/
+1 -4
View File
@@ -1,7 +1,4 @@
when:
event: push
steps:
pipeline:
build:
image: golang:1.22
commands:
-21
View File
@@ -1,21 +0,0 @@
FROM golang:1.26
WORKDIR /app
RUN apt install -y make gcc
# Make sure /app is safe regardless of who owns it
RUN git config --global --add safe.directory /app
ENV CGO_ENABLED=1
# Install Air for auto-reloading
RUN go install github.com/air-verse/air@latest
# Copy dependency definitions first (caching optimization)
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN make build BUILD_DIR=/tmp
-11
View File
@@ -1,11 +0,0 @@
# Node.js runtime
FROM node:24
WORKDIR /var/www/html/
COPY package.* /var/www/html/
RUN npm install
# Run the app
CMD [ "npm", "run", "grunt" ]
+36 -19
View File
@@ -1,28 +1,45 @@
NAME=blt
VERSION=`git describe --tags`
# used for changing the output directory within docker containers
BUILD_DIR ?= .
dev-up:
docker compose -f docker-dev.yml up -d
dev-down:
docker compose -f docker-dev.yml down
dev-build:
docker compose -f docker-dev.yml build
.PHONY: build
## build: Compile the packages.
build:
@echo "Building Go binary..."
@go build -o $(BUILD_DIR)/$(NAME) -ldflags "-X git.metaunix.net/bitgoblin/blt/app.AppVersion=$(VERSION)"
@go build -o $(NAME) -ldflags "-X git.metaunix.net/bitgoblin/blt/app.AppVersion=$(VERSION)"
run:
$(BUILD_DIR)/$(NAME)
.PHONY: grunt
## grunt: Compile frontend assets.
grunt:
@npm run grunt
watch:
@echo "Starting hot-reload watcher..."
@air
.PHONY: grunt-watch
## grunt-watch: Compile frontend assets while watching for changes.
grunt-watch:
@npm run grunt watch
.PHONY: run
## run: Build and Run in development mode.
run: build
@./$(NAME) -e development
.PHONY: run-prod
## run-prod: Build and Run in production mode.
run-prod: build
@./$(NAME) -e production
.PHONY: clean
## clean: Clean project and previous builds.
clean:
@rm -f $(NAME)
.PHONY: deps
## deps: Download modules
deps:
@go mod download
.PHONY: test
## test: Run tests with verbose mode
test:
@go test -v ./tests/*
.PHONY: help
all: help
-54
View File
@@ -1,54 +0,0 @@
# Benchmark Logging Tool (BLT)
![Build badge](https://builds.metaunix.net/api/badges/87/status.svg)
Web-based tool to store and organize PC hardware benchmarks.
## Project Goals
The goals of this project are to:
* Record benchmarking results from multiple devices - e.g. log from a laptop or a phone.
* Group results into tests to keep track of different testing configurations.
* Encourage running tests multiple times - it's good practice to run a benchmark multiple times for accuracy.
* Create comparisons of hardware tests to compare performance.
* Generate graphs of hardware comparisons for usage in videos and articles.
## Requirements
BLT runs on Go. It uses the built-in `go mod` tool to manage dependencies, thus there is no external tooling to install to build/run BLT.
Debian/Ubuntu: `apt install -y golang`
RedHat and clones: `dnf install -y golang`
## Production Deployment
**TODO**
## Development
### Via Docker
**TODO**
### Local/Native Development
BLT uses [fresh](https://github.com/gravityblast/fresh) to auto-reload the app. While this is not strictly necessary, it used to make development more convenient. If you wish to forego installing it, you may simply build and run the app with the standard `go run main.go`.
1. Install dependencies:
`go mod download`
2. Install fresh to auto-reload the app:
`go install github.com/gravityblast/fresh@latest`
3. Run the app via air:
`fresh`
4. If everything is running successfully you can open your browser and go to http://localhost:2830.
## License
This project is available under the BSD 2-Clause license.
+27 -16
View File
@@ -1,7 +1,3 @@
$primary-color: #ff3300
$primary-color-dark: darken($primary-color, 10%)
@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100..900;1,100..900&display=swap')
html,
body
width: 100%
@@ -16,22 +12,18 @@ body
padding-top: 80px
padding-bottom: 100px
background: #eee
font-family: "Roboto", sans-serif
font-optical-sizing: auto
a
color: $primary-color
color: teal
transition: color 180ms ease-in-out
&:hover
color: $primary-color-dark
color: darkcyan
button.button-primary,
input[type=button].button-primary,
input[type=submit].button-primary
background: $primary-color
button.button-primary
background: teal
transition: background 180ms ease-in-out
&:hover
background: $primary-color-dark
background: darkcyan
textarea
max-width: 100%
@@ -67,7 +59,26 @@ table tr:nth-child(even)
table tbody tr
transition: background 180ms ease-in-out
&:hover
background: #d8d8d8
background: lightgrey
/* Material card styles */
.card-1
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24)
transition: all 0.3s cubic-bezier(.25,.8,.25,1)
&:hover
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22)
.card-2
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23)
.card-3
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23)
.card-4
box-shadow: 0 14px 28px rgba(0,0,0,0.25), 0 10px 10px rgba(0,0,0,0.22)
.card-5
box-shadow: 0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22)
.container
max-width: 1200px
@@ -78,7 +89,7 @@ table tbody tr
left: 0
width: 100%
height: 64px
background: $primary-color
background: teal
color: white
z-index: 20
@@ -97,12 +108,12 @@ table tbody tr
a
color: white
text-decoration: none
font-size: 2.25rem
line-height: 64px
transition: all 200ms ease-in-out
&:hover
color: #eee
font-size: 2.5rem
#main-content
padding: 15px 25px
-32
View File
@@ -1,32 +0,0 @@
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
command: make watch
ports:
- "2830:2830"
volumes:
- .:/app # Mounts local directory for hot-reloading/live edits
- go-modules:/go/pkg/mod # Caches Go modules on host
# Mount the compiled static assets generated by Grunt
- static-assets:/app/public
environment:
- APP_ENV=development
assets:
build:
context: .
dockerfile: Dockerfile.grunt
# Override CMD to run watch mode during local dev
command: npm run grunt watch
volumes:
# Mount host code so Grunt detects file changes in real time
- .:/var/www/html
# Preserve container node_modules from being overwritten by host
- /var/www/html/node_modules
# Output compiled assets into the shared volume
- static-assets:/var/www/html/public
volumes:
go-modules:
static-assets:
+1 -1
View File
@@ -44,6 +44,6 @@ func main() {
web.RegisterRoutes(f)
// alert the user and start the server
log.Println("Server is listening on 0.0.0.0:2830.")
log.Println("Server is running...")
log.Println(http.ListenAndServe("0.0.0.0:2830", f))
}
-27
View File
@@ -1,27 +0,0 @@
package models
import (
"strconv"
"gorm.io/gorm"
)
type BenchmarkProfile struct {
gorm.Model
Label string
Settings string
// belongs to Benchmark
BenchmarkID int
Benchmark Benchmark
// many-to-many with tests
Tests []Test `gorm:"many2many:tests_benchmark_profiles;"`
// has many results
Results []Result
}
func (b *BenchmarkProfile) StringID() string {
return strconv.Itoa(int(b.ID))
}
+4 -7
View File
@@ -1,8 +1,6 @@
package models
import (
"strconv"
"gorm.io/gorm"
)
@@ -12,10 +10,9 @@ type Benchmark struct {
ScoringType string
Description string
// one-to-many BenchmarkProfiles
BenchmarkProfiles []BenchmarkProfile
}
// many-to-many test
Tests []Test `gorm:"many2many:tests_benchmarks;"`
func (b *Benchmark) StringID() string {
return strconv.Itoa(int(b.ID))
// has many results
Results []Result
}
+2 -2
View File
@@ -11,8 +11,8 @@ type Result struct {
MaximumScore float32
// belongs to Benchmark
BenchmarkProfileID int
BenchmarkProfile BenchmarkProfile
BenchmarkID int
Benchmark Benchmark
// belongs to Test
TestID int
+2 -25
View File
@@ -1,8 +1,6 @@
package models
import (
"strconv"
"gorm.io/gorm"
)
@@ -15,30 +13,9 @@ type Test struct {
HardwareID int
Hardware Hardware
// many-to-many benchmark profiles
BenchmarkProfiles []Benchmark `gorm:"many2many:tests_benchmark_profiles;"`
// many-to-many benchmarks
Benchmarks []Benchmark `gorm:"many2many:tests_benchmarks;"`
// has many results
Results []Result
}
func (t *Test) SelectedBenchmarks() []string {
benchmarks := t.BenchmarkProfiles
ids := make([]string, len(benchmarks))
for i, b := range benchmarks {
ids[i] = strconv.Itoa(int(b.ID))
}
return ids
}
func (t *Test) IsBenchmarkSelected(benchmarkID uint) bool {
benchmarkUint := uint(benchmarkID)
for _, b := range t.BenchmarkProfiles {
if b.ID == benchmarkUint {
return true
}
}
return false
}
-38
View File
@@ -1,38 +0,0 @@
{{ template "header" . }}
<div class="row">
<h2>Editing Benchmark: {{ .benchmark.Name }}</h2>
<form class="twelve columns" action="/benchmark/{{ .benchmark.ID }}/edit" method="POST">
<div class="row">
<div class="nine columns">
<label for="benchmark_name">
Benchmark name:
<input id="benchmark_name" class="u-full-width" type="text" name="benchmark_name" placeholder="Unigine Heaven" value="{{ .benchmark.Name }}">
</label>
</div>
<div class="three columns">
<label for="benchmark_scoring">
Benchmark type:
<select id="benchmark_scoring" class="u-full-width" name="benchmark_scoring">
<option value="fps" {{ if eq .benchmark.ScoringType "fps" }}selected{{ end }}>Frames per second</option>
<option value="ms" {{ if eq .benchmark.ScoringType "ms" }}selected{{ end }}>Frame time</option>
<option value="pts" {{ if eq .benchmark.ScoringType "pts" }}selected{{ end }}>Total points</option>
</select>
</label>
</div>
</div>
<div class="row">
<label for="benchmark_description">
Benchmark description:
<textarea id="benchmark_description" class="twelve columns" cols="30" rows="10" name="benchmark_description">{{ .benchmark.Description }}</textarea>
</label>
</div>
<input class="button-primary u-full-width" type="submit" value="Submit">
</form>
</div>
{{ template "footer" . }}
+4 -4
View File
@@ -1,10 +1,10 @@
{{ template "header" . }}
<div class="row">
<h2>List of benchmarks</h2>
<h2>Benchmark</h2>
<a href="/benchmark/create">Add new benchmark</a>
<table class="twelve columns">
<table class="twelve columns card-2">
<thead>
<tr>
<td>Name</td>
@@ -16,8 +16,8 @@
{{ range $b := .benchmarks }}
<tr>
<td><a href="/benchmark/{{ $b.ID }}">{{ $b.Name }}</a></td>
<td>{{ $b.CreatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $b.UpdatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $b.CreatedAt.Format "01/02/2006 15:04am" }}</td>
<td>{{ $b.UpdatedAt.Format "01/02/2006 15:04am" }}</td>
</tr>
{{ end }}
</tbody>
-47
View File
@@ -1,47 +0,0 @@
{{ template "header" . }}
<div class="row">
<h2>Add benchmark settings profile</h2>
<form class="twelve columns" action="/benchmark/profile/add" method="POST">
<div class="row">
<div class="six columns">
<label for="benchmark_profile_benchmark">
Benchmark:
{{ if .benchmark }}
<select id="benchmark_profile_benchmark" class="u-full-width" disabled>
<option value="{{ .benchmark.ID }}">{{ .benchmark.Name }}</option>
</select>
<input type="hidden" name="benchmark_profile_benchmark" value="{{ .benchmark.ID }}">
{{ end }}
{{ if .benchmarks }}
<select id="benchmark_profile_benchmark" class="u-full-width" name="benchmark_profile_benchmark">
{{ range $benchmark := .benchmarks }}
<option value="{{ $benchmark.ID }}">{{ $benchmark.Name }}</option>
{{ end }}
</select>
{{ end }}
</label>
</div>
<div class="six columns">
<label for="benchmark_profile_label">
Profile label:
<input id="benchmark_profile_label" class="u-full-width" type="text" name="benchmark_profile_label" placeholder="My benchmark settings profile">
</label>
</div>
</div>
<div class="row">
<label for="benchmark_description">
Benchmark description:
<textarea id="benchmark_description" class="twelve columns" cols="30" rows="10" name="benchmark_description"></textarea>
</label>
</div>
<input class="button-primary u-full-width" type="submit" value="Submit">
</form>
</div>
{{ template "footer" . }}
-22
View File
@@ -3,30 +3,8 @@
<div class="row">
<h2>{{ .benchmark.Name }}</h2>
<span><a href="/benchmark/{{ .benchmark.ID }}/edit">Edit</a></span>
<p>{{ .benchmark.ScoringType }}</p>
<p>{{ .benchmark.Description }}</p>
<hr>
<h4>Profiles for this Benchmark</h4>
<p><a href="/benchmark/profile/add?benchmark={{ .benchmark.ID }}">Add settings profile</a></p>
<ul class="benchmark-profiles">
{{ range $profile := .benchmark.BenchmarkProfiles }}
<li>
{{ $profile.Label }}
<span>
<a href="/benchmark/profile/{{ $profile.ID }}/edit">Edit</a>
<a href="/benchmark/profile/{{ $profile.ID }}/delete">Delete</a>
</span>
</li>
{{ end }}
</ul>
<hr>
<h4>Latest Benchmark Results:</h4>
-34
View File
@@ -1,34 +0,0 @@
{{ template "header" . }}
<div class="row">
<h2>Add new hardware</h2>
<form class="twelve columns" action="/hardware/{{ .hardware.ID }}/edit" method="POST">
<div class="row">
<div class="nine columns">
<label for="hardware_name">
Hardware name:
<input id="hardware_name" class="u-full-width" type="text" name="hardware_name" placeholder="EVGA RTX 3080 Ti" value="{{ .hardware.Name }}">
</label>
</div>
<div class="three columns">
<label for="hardware_type">
Hardware type:
<select id="hardware_type" class="u-full-width" name="hardware_type">
<option value="cpu" {{ if eq .hardware.Type "cpu" }}selected{{ end }}>Processor</option>
<option value="mem" {{ if eq .hardware.Type "mem" }}selected{{ end }}>Memory</option>
<option value="gpu" {{ if eq .hardware.Type "gpu" }}selected{{ end }}>Graphics Card</option>
<option value="ssd" {{ if eq .hardware.Type "ssd" }}selected{{ end }}>Solid State Drive</option>
<option value="hdd" {{ if eq .hardware.Type "hdd" }}selected{{ end }}>Hard Drive</option>
<option value="nic" {{ if eq .hardware.Type "nic" }}selected{{ end }}>Network Card</option>
</select>
</label>
</div>
</div>
<input class="button-primary u-full-width" type="submit" value="Submit">
</form>
</div>
{{ template "footer" . }}
+4 -4
View File
@@ -1,10 +1,10 @@
{{ template "header" . }}
<div class="row">
<h2>List of hardware</h2>
<h2>Hardware</h2>
<a href="/hardware/create">Add new hardware</a>
<table class="twelve columns">
<table class="twelve columns card-2">
<thead>
<tr>
<td>Name</td>
@@ -16,8 +16,8 @@
{{ range $h := .hardware }}
<tr>
<td><a href="/hardware/{{ $h.ID }}">{{ $h.Name }}</a></td>
<td>{{ $h.CreatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $h.UpdatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $h.CreatedAt.Format "01/02/2006 15:04am" }}</td>
<td>{{ $h.UpdatedAt.Format "01/02/2006 15:04am" }}</td>
</tr>
{{ end }}
</tbody>
+2 -4
View File
@@ -3,8 +3,6 @@
<div class="row">
<h2>{{ .hardware.Name }}</h2>
<span><a href="/hardware/{{ .hardware.ID }}/edit">Edit</a></span>
<p>{{ .hardware.Type }}</p>
<hr>
@@ -14,7 +12,7 @@
{{ $length := len .hardware.Tests }} {{ if eq $length 0 }}
<p>There are currently no tests using this hardware component.</p>
{{ else }}
<table class="u-full-width">
<table class="u-full-width card-2">
<thead>
<tr>
<th>Test</th>
@@ -27,7 +25,7 @@
<tr>
<td><a href="/test/{{ $test.ID }}">{{ $test.Name }}</a></td>
<td>{{ len $test.Benchmarks }}</td>
<td>{{ $test.CreatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $test.CreatedAt.Format "01/02/2006 15:04am" }}</td>
</tr>
{{ end }}
</tbody>
-75
View File
@@ -2,79 +2,4 @@
<p>This is the BLT dashboard.</p>
<section class="row">
<h3>Recently updated tests:</h3>
<table class="twelve columns">
<thead>
<tr>
<td>Title</td>
<td>Created at</td>
<td>Last updated</td>
</tr>
</thead>
<tbody>
{{ range $p := .tests }}
<tr>
<td><a href="/test/{{ $p.ID }}">{{ $p.Name }}</a></td>
<td>{{ $p.CreatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $p.UpdatedAt.Format "01/02/2006 3:04pm" }}</td>
</tr>
{{ end }}
</tbody>
</table>
<p>Visit the <a href="/test/list">tests</a> page to for more information and actions.</p>
</section>
<hr>
<section class="row">
<div class="six columns">
<h3>Recently updated hardware:</h3>
<table class="u-full-width">
<thead>
<tr>
<td>Name</td>
<td>Created at</td>
<td>Last updated</td>
</tr>
</thead>
<tbody>
{{ range $h := .hardware }}
<tr>
<td><a href="/hardware/{{ $h.ID }}">{{ $h.Name }}</a></td>
<td>{{ $h.CreatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $h.UpdatedAt.Format "01/02/2006 3:04pm" }}</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
<div class="six columns">
<h3>Recently updated benchmarks:</h3>
<table class="u-full-width">
<thead>
<tr>
<td>Name</td>
<td>Created at</td>
<td>Last updated</td>
</tr>
</thead>
<tbody>
{{ range $b := .benchmarks }}
<tr>
<td><a href="/benchmark/{{ $b.ID }}">{{ $b.Name }}</a></td>
<td>{{ $b.CreatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $b.UpdatedAt.Format "01/02/2006 3:04pm" }}</td>
</tr>
{{ end }}
</tbody>
</table>
</div>
</section>
{{ template "footer" . }}
+1 -1
View File
@@ -17,5 +17,5 @@
{{ template "navbar" . }}
<!-- main content -->
<div id="main-content" class="container">
<div id="main-content" class="container card-5">
{{ end }}
+1 -1
View File
@@ -1,5 +1,5 @@
{{ define "navbar" }}
<nav id="main-nav">
<nav id="main-nav" class="card-2">
<div class="nav-left">
<h4>BLT</h4>
<ul>
+2 -2
View File
@@ -30,9 +30,9 @@
<label for="test_benchmarks">
Benchmarks to Test:
<select id="test_benchmarks" class="u-full-width" name="test_benchmarks" multiple>
{{ $selectedBenchmarks := .selectedBenchmarks }}
{{ $testBenchmarks := .test.Benchmarks }}
{{ range $bm := .benchmarks }}
<option value="{{ $bm.ID }}" {{ if contains $selectedBenchmarks $bm.StringID }}selected{{ end }}>{{ $bm.Name }}</option>
<option value="{{ $bm.ID }}" {{ if contains $testBenchmarks $bm.ID }}selected{{ end }}>{{ $bm.Name }}</option>
{{ end }}
</select>
</label>
+4 -4
View File
@@ -1,10 +1,10 @@
{{ template "header" . }}
<div class="row">
<h2>List of tests</h2>
<h2>Tests</h2>
<a href="/test/create">Create a new test</a>
<table class="twelve columns">
<table class="twelve columns card-2">
<thead>
<tr>
<td>Title</td>
@@ -16,8 +16,8 @@
{{ range $p := .tests }}
<tr>
<td><a href="/test/{{ $p.ID }}">{{ $p.Name }}</a></td>
<td>{{ $p.CreatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $p.UpdatedAt.Format "01/02/2006 3:04pm" }}</td>
<td>{{ $p.CreatedAt.Format "01/02/2006 15:04am" }}</td>
<td>{{ $p.UpdatedAt.Format "01/02/2006 15:04am" }}</td>
</tr>
{{ end }}
</tbody>
+2 -6
View File
@@ -3,15 +3,11 @@
<div class="row">
<h2>{{ .test.Name }}</h2>
<span><a href="/test/{{ .test.ID }}/edit">Edit</a></span>
<p>Hardware tested: <a href="/hardware/{{ .test.Hardware.ID }}">{{ .test.Hardware.Name }}</a></p>
<p>{{ .test.Description }}</p>
<h4>Test Info:</h4>
<p>Hardware tested: <a href="/hardware/{{ .test.Hardware.ID }}">{{ .test.Hardware.Name }}</a></p>
<p>Benchmarks used:</p>
<h4>Benchmarks used:</h4>
<ul>
{{ range $bm := .test.Benchmarks }}
-7
View File
@@ -1,7 +0,0 @@
package forms
type BenchmarkProfileForm struct {
BenchmarkID int `form:"benchmark_profile_benchmark" validate:"required"`
Label string `form:"benchmark_profile_label" validate:"required"`
Settings string `form:"benchmark_profile_settings" validate:"required"`
}
+5 -5
View File
@@ -1,9 +1,9 @@
package forms
type ResultForm struct {
Test int `form:"result_test" validate:"required"`
BenchmarkProfile int `form:"result_benchmark" validate:"required"`
AverageScore float32 `form:"result_avg" validate:"required"`
MinimumScore float32 `form:"result_min"`
MaximumScore float32 `form:"result_max"`
Test int `form:"result_test" validate:"required"`
Benchmark int `form:"result_benchmark" validate:"required"`
AverageScore float32 `form:"result_avg" validate:"required"`
MinimumScore float32 `form:"result_min"`
MaximumScore float32 `form:"result_max"`
}
+4 -14
View File
@@ -1,18 +1,8 @@
package forms
type TestForm struct {
Name string `form:"test_name" validate:"required"`
Description string `form:"test_description"`
Hardware int `form:"test_hardware" validate:"required"`
BenchmarkProfiles []uint `form:"test_benchmarks" validate:"required"`
}
func (t *TestForm) IsBenchmarkSelected(checkID uint) bool {
for _, selectedID := range t.BenchmarkProfiles {
if checkID == selectedID {
return true
}
}
return false
Name string `form:"test_name" validate:"required"`
Description string `form:"test_description"`
Hardware int `form:"test_hardware" validate:"required"`
Benchmarks []string `form:"test_benchmarks" validate:"required"`
}
-16
View File
@@ -24,9 +24,6 @@ func RegisterRoutes(f *flamego.Flame) {
f.Post("/create", binding.Form(forms.HardwareForm{}), routes.HardwarePostCreate)
f.Get("/{hardware_id}", routes.HardwareGetView)
f.Get("/{hardware_id}/edit", routes.HardwareGetEdit)
f.Post("/{hardware_id}/edit", binding.Form(forms.HardwareForm{}), routes.HardwarePostEdit)
})
// benchmark routes
@@ -41,14 +38,6 @@ func RegisterRoutes(f *flamego.Flame) {
f.Post("/create", binding.Form(forms.BenchmarkForm{}), routes.BenchmarkPostCreate)
f.Get("/{benchmark_id}", routes.BenchmarkGetView)
f.Get("/{benchmark_id}/edit", routes.BenchmarkGetEdit)
f.Post("/{benchmark_id}/edit", binding.Form(forms.BenchmarkForm{}), routes.BenchmarkPostEdit)
f.Group("/profile", func() {
f.Get("/add", routes.BenchmarkGetProfileAdd)
f.Post("/add", binding.Form(forms.BenchmarkProfileForm{}), routes.BenchmarkPostProfileAdd)
})
})
// test routes
@@ -65,7 +54,6 @@ func RegisterRoutes(f *flamego.Flame) {
f.Group("/{test_id}", func() {
f.Get("", routes.TestGetView)
f.Get("/edit", routes.TestGetEdit)
f.Post("/edit", binding.Form(forms.TestForm{}), routes.TestPostEdit)
})
})
@@ -81,10 +69,6 @@ func RegisterRoutes(f *flamego.Flame) {
f.Get("/details", routes.ApiV1BenchmarkDetails)
})
f.Group("/benchmark_profile", func() {
f.Get("/details", routes.ApiV1BenchmarkProfileDetails)
})
f.Group("/result", func() {
f.Post("/add", binding.Form(forms.ResultForm{}), routes.ApiV1ResultAdd)
f.Get("/list", routes.ApiV1ResultList)
+5 -17
View File
@@ -18,23 +18,11 @@ func ApiV1BenchmarkDetails(c flamego.Context, r flamego.Render) {
// find benchmark from DB
var benchmark models.Benchmark
models.DB.First(&benchmark, benchmarkID)
// return JSON response
r.JSON(200, benchmark)
}
func ApiV1BenchmarkProfileDetails(c flamego.Context, r flamego.Render) {
// find benchmark ID from request
benchmarkProfileID := c.Query("benchmark_profile_id")
// find benchmark from DB
var benchmarkProfile models.BenchmarkProfile
models.DB.Preload("BenchmarkProfiles").First(&benchmarkProfile, benchmarkProfileID)
// return JSON response
r.JSON(200, benchmarkProfile)
}
func ApiV1ResultAdd(c flamego.Context, form forms.ResultForm, errs binding.Errors, r flamego.Render) {
if len(errs) > 0 {
var err error
@@ -48,8 +36,8 @@ func ApiV1ResultAdd(c flamego.Context, form forms.ResultForm, errs binding.Error
}
result := models.Result{
TestID: form.Test,
BenchmarkProfileID: form.BenchmarkProfile,
TestID: form.Test,
BenchmarkID: form.Benchmark,
AverageScore: form.AverageScore,
MinimumScore: form.MinimumScore,
MaximumScore: form.MaximumScore,
@@ -62,12 +50,12 @@ func ApiV1ResultAdd(c flamego.Context, form forms.ResultForm, errs binding.Error
func ApiV1ResultList(c flamego.Context, r flamego.Render) {
// find benchmark and test IDs from request
benchmarkProfileID := c.Query("benchmark_id")
benchmarkID := c.Query("benchmark_id")
testID := c.Query("test_id")
// find results from the DB that match the benchmark and test
var results []models.Result
models.DB.Where("test_id = ? AND benchmark_profile_id = ?", testID, benchmarkProfileID).Find(&results)
models.DB.Where("test_id = ? AND benchmark_id = ?", testID, benchmarkID).Find(&results)
// return JSON response
r.JSON(200, results)
+1 -75
View File
@@ -30,7 +30,7 @@ func BenchmarkGetView(c flamego.Context, t template.Template, data template.Data
// find benchmark from DB
var benchmark models.Benchmark
models.DB.Preload("BenchmarkProfiles").First(&benchmark, benchmarkID)
models.DB.First(&benchmark, benchmarkID)
data["benchmark"] = benchmark
data["title"] = benchmark.Name
@@ -64,77 +64,3 @@ func BenchmarkPostCreate(c flamego.Context, form forms.BenchmarkForm, errs bindi
c.Redirect(fmt.Sprintf("/benchmark/%d", benchmark.ID))
}
func BenchmarkGetEdit(c flamego.Context, t template.Template, data template.Data) {
// find benchmark ID from request
benchmarkID := c.Param("benchmark_id")
// find benchmark from DB
var benchmark models.Benchmark
models.DB.First(&benchmark, benchmarkID)
data["benchmark"] = benchmark
data["title"] = "Editing Benchmark"
t.HTML(http.StatusOK, "benchmark/edit")
}
func BenchmarkPostEdit(c flamego.Context, form forms.BenchmarkForm, errs binding.Errors) {
if len(errs) > 0 {
var err error
switch errs[0].Category {
case binding.ErrorCategoryValidation:
err = errs[0].Err.(validator.ValidationErrors)[0]
default:
err = errs[0].Err
}
log.Fatal(err)
}
// find benchmark ID from request
benchmarkID := c.Param("benchmark_id")
// find benchmark from DB
var benchmark models.Benchmark
models.DB.First(&benchmark, benchmarkID)
benchmark.Name = form.Name
benchmark.ScoringType = form.ScoringType
benchmark.Description = form.Description
models.DB.Save(&benchmark)
c.Redirect(fmt.Sprintf("/benchmark/%d", benchmark.ID))
}
func BenchmarkGetProfileAdd(c flamego.Context, t template.Template, data template.Data) {
// find benchmark ID from GET parameters
benchmarkID := c.Query("benchmark")
// check if the query parameter was used
if benchmarkID != "" {
// find benchmark from DB
var benchmark models.Benchmark
models.DB.First(&benchmark, benchmarkID)
data["benchmark"] = benchmark
} else {
// find all benchmarks from DB
var benchmarks []models.Benchmark
models.DB.Find(&benchmarks)
data["benchmarks"] = benchmarks
}
data["title"] = "Add new benchmark settings profile"
t.HTML(http.StatusOK, "benchmark/profile/add")
}
func BenchmarkPostProfileAdd(c flamego.Context, form forms.BenchmarkProfileForm, errs binding.Errors) {
benchmarkProfile := models.BenchmarkProfile{
BenchmarkID: form.BenchmarkID,
Label: form.Label,
Settings: form.Settings,
}
_ = models.DB.Create(&benchmarkProfile)
c.Redirect(fmt.Sprintf("/benchmark/%d", form.BenchmarkID))
}
-40
View File
@@ -63,43 +63,3 @@ func HardwarePostCreate(c flamego.Context, form forms.HardwareForm, errs binding
c.Redirect(fmt.Sprintf("/hardware/%d", hardware.ID))
}
func HardwareGetEdit(c flamego.Context, t template.Template, data template.Data) {
// find hardware ID from request
hardwareID := c.Param("hardware_id")
// find hardware from DB
var hardware models.Hardware
models.DB.Preload("Tests.Benchmarks").First(&hardware, hardwareID)
data["hardware"] = hardware
data["title"] = "Edit Hardware"
t.HTML(http.StatusOK, "hardware/edit")
}
func HardwarePostEdit(c flamego.Context, form forms.HardwareForm, errs binding.Errors) {
if len(errs) > 0 {
var err error
switch errs[0].Category {
case binding.ErrorCategoryValidation:
err = errs[0].Err.(validator.ValidationErrors)[0]
default:
err = errs[0].Err
}
log.Fatal(err)
}
// find hardware ID from request
hardwareID := c.Param("hardware_id")
// find hardware from DB
var hardware models.Hardware
models.DB.Preload("Tests.Benchmarks").First(&hardware, hardwareID)
hardware.Name = form.Name
hardware.Type = form.Type
models.DB.Save(&hardware)
c.Redirect(fmt.Sprintf("/hardware/%d", hardware.ID))
}
-16
View File
@@ -4,25 +4,9 @@ import (
"net/http"
"github.com/flamego/template"
"git.metaunix.net/bitgoblin/blt/models"
)
func GetDashboard(t template.Template, data template.Data) {
// add tests to template; limited to 10 most recent
var tests []models.Test
models.DB.Order("updated_at desc").Limit(10).Find(&tests)
data["tests"] = tests
// add hardwares to template; limited to 5 most recent
var hardware []models.Hardware
models.DB.Order("updated_at desc").Limit(5).Find(&hardware)
data["hardware"] = hardware
// add benchmarks to template; limited to 5 most recent
var benchmarks []models.Benchmark
models.DB.Order("updated_at desc").Limit(5).Find(&benchmarks)
data["benchmarks"] = benchmarks
data["title"] = "Dashboard"
t.HTML(http.StatusOK, "index/dashboard")
}
+1 -1
View File
@@ -26,7 +26,7 @@ func ResultPostCreate(c flamego.Context, form forms.ResultForm, errs binding.Err
result := models.Result{
TestID: form.Test,
BenchmarkProfileID: form.BenchmarkProfile,
BenchmarkID: form.Benchmark,
AverageScore: form.AverageScore,
MinimumScore: form.MinimumScore,
MaximumScore: form.MaximumScore,
+3 -53
View File
@@ -73,10 +73,10 @@ func TestPostCreate(c flamego.Context, form forms.TestForm, errs binding.Errors)
_ = models.DB.Create(&test)
// bind benchmarks to test
for _, v := range form.BenchmarkProfiles {
for _, v := range form.Benchmarks {
var benchmark models.Benchmark
models.DB.First(&benchmark, v) // find benchmark
models.DB.Model(&test).Association("BenchmarkProfiles").Append(&benchmark)
models.DB.Model(&test).Association("Benchmarks").Append(&benchmark)
}
c.Redirect(fmt.Sprintf("/test/%d", test.ID))
@@ -86,7 +86,7 @@ func TestGetEdit(c flamego.Context, t template.Template, data template.Data) {
// find test in DB
testID := c.Param("test_id")
var test models.Test
models.DB.Preload("Hardware").Preload("BenchmarkProfiles").First(&test, testID)
models.DB.Preload("Hardware").Preload("Benchmarks").First(&test, testID)
data["test"] = test
// add hardware components to template
@@ -99,56 +99,6 @@ func TestGetEdit(c flamego.Context, t template.Template, data template.Data) {
models.DB.Find(&benchmarks)
data["benchmarks"] = benchmarks
// determine which benchmarks are selected in a test
selectedBenchmarks := test.SelectedBenchmarks()
data["selectedBenchmarks"] = selectedBenchmarks
data["title"] = fmt.Sprintf("Editing Test: %s", test.Name)
t.HTML(http.StatusOK, "test/edit")
}
func TestPostEdit(c flamego.Context, form forms.TestForm, errs binding.Errors) {
if len(errs) > 0 {
var err error
switch errs[0].Category {
case binding.ErrorCategoryValidation:
err = errs[0].Err.(validator.ValidationErrors)[0]
default:
err = errs[0].Err
}
log.Fatal(err)
}
// find test ID from request
testID := c.Param("test_id")
// find hardware from DB
var test models.Test
models.DB.Preload("Hardware").Preload("BenchmarkProfiles").First(&test, testID)
test.Name = form.Name
test.Description = form.Description
test.HardwareID = form.Hardware
// bind benchmarks to test that aren't already associated
for _, b := range form.BenchmarkProfiles {
if ! test.IsBenchmarkSelected(b) {
var benchmark models.BenchmarkProfile
models.DB.First(&benchmark, b) // find benchmark
models.DB.Model(&test).Association("BenchmarkProfiles").Append(&benchmark)
}
}
// removed associated benchmarks that weren't in the form
for _, b := range test.BenchmarkProfiles {
if ! form.IsBenchmarkSelected(b.ID) {
var benchmark models.BenchmarkProfile
models.DB.First(&benchmark, b) // find benchmark
models.DB.Model(&test).Association("BenchmarkProfiles").Delete(&benchmark)
}
}
models.DB.Save(&test)
c.Redirect(fmt.Sprintf("/test/%d", test.ID))
}