48 Commits

Author SHA1 Message Date
39f95575da Version bump to v0.2.1
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
ci/woodpecker/release/woodpecker Pipeline was successful
2025-08-13 14:57:45 -04:00
6e1ab89209 Updating version number in appinfo
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-13 14:57:19 -04:00
9cd6c78741 Adding missing step from Docker dev
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 14:54:02 -04:00
5b730df803 Adding build badge to README
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 14:52:34 -04:00
0ce4a3ecee Updated README
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 14:46:38 -04:00
12ece12394 Fixed some lints
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 14:13:26 -04:00
05c20b5811 Adding a model association test
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 11:06:55 -04:00
bd822664b0 Added some model tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 09:57:04 -04:00
3f0efce0d8 Fixed a few lints; changed rake task name to test:lint
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 23:56:46 -04:00
eeedc57cd3 Overhauled configuration so that it's a bit more useful in more spots; configuration now properly loads an environment config as well as defaults; updated some woodpecker config
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 23:47:38 -04:00
164eea1bde Updated Woodpecker config for 3.x
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2025-08-12 21:59:59 -04:00
85fe3b0b38 Removing broken ruby test versions for now
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 21:55:03 -04:00
519955e57a Fixing unit tests
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-12 21:30:17 -04:00
96b746822c Fixing unit tests
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-12 21:27:17 -04:00
eac833fd6d Adding db:migrate step to tests; adding two new versions of Ruby to test against
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-12 20:29:57 -04:00
ad391c84a4 Fixing ruby 3.4 test
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 20:23:50 -04:00
641c9315bc Adding Ruby 3.4 testing to Woodpecker
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-12 20:03:20 -04:00
3a136865b0 Added some more tests; changed URLs for model list pages and added redirects
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2025-08-12 18:22:22 -04:00
Gregory Ballantine
f40d69a98d Using before hook for index route
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 16:26:48 -04:00
Gregory Ballantine
40cfdcc2a3 Changed naming from Routes to Controllers; fixed some Sinatra modular layout stuff; added RSpec for testing and some basic tests
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 16:15:43 -04:00
Gregory Ballantine
260d0d1268 Added rspec testing. It should work, but doesn't for unknown reasons 2025-08-12 15:35:46 -04:00
Gregory Ballantine
e1f5bd3950 Updating to Sinatra 4.1
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 14:27:22 -04:00
Gregory Ballantine
1f0c481105 Refactored app to more explicitly require gems/modules that are used per-file
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 14:12:32 -04:00
Gregory Ballantine
dd8e419e52 Switched over to a modular Sinatra app layout
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 13:54:25 -04:00
Gregory Ballantine
c74ca114d8 Fixed a logic error with removing benchmarks from a test; cleaned up some linter errors
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 12:19:25 -04:00
0a1037e79a Modified the front-end to display averaged results up to two decimals
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-11 23:39:01 -04:00
Gregory Ballantine
bc5ae4962f Fixed the placeholder for benchmark add/edit pages
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-31 14:07:58 -04:00
Gregory Ballantine
ec2bf45a6e Fixed the test edit page
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-31 14:06:08 -04:00
57163b10e4 The report test selection resets when you change the benchmark
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-30 23:29:53 -04:00
25d394627d Version bump to v0.1.1
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2025-07-28 11:01:35 -04:00
85dfdb163a Fixed rubocop warnings
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-28 10:57:40 -04:00
b593ef7593 Updating to ruby:3.4 in CI/CD
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-28 10:54:53 -04:00
26698082f4 Removed backup copy of the reports.coffee script
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-28 10:49:09 -04:00
42a0b95015 Fixed the chart generation page with the new ChartJS version and DB schema
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-28 10:48:49 -04:00
5cc3b8f824 Added a better way of setting/reading the app version; fixed some of the flexbox sizing
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-28 10:23:51 -04:00
d59c75281e Updating puma.rb to allow changing the rack environment dynamically
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-28 10:17:45 -04:00
55e4f397f8 Fixed the test edit view
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-26 11:37:43 -04:00
49d1276031 Fixed puma config for new Puma versions 2025-07-26 11:32:32 -04:00
bc70fb8dd0 Added config/puma.rb for production deployment
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-26 11:16:07 -04:00
0231ebad2d Made some more style changes
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-26 01:08:35 -04:00
541b5236f0 Finished transition to Bootstrap 5
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-26 00:55:52 -04:00
Gregory Ballantine
6215cecb53 Cleaned up app version output
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-25 17:48:21 -04:00
Gregory Ballantine
886f566ae2 Started work to move the CSS framework to Bootstrap 5
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
ci/woodpecker/tag/woodpecker Pipeline failed
2025-07-25 17:44:25 -04:00
Gregory Ballantine
fdd350e16f Updated gulpfile to be a little bit less repetitive
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-25 13:41:51 -04:00
bc4cb181c3 Added some styles
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-23 23:54:12 -04:00
822f49bcc2 fixed display of tests on the dashboard 2025-07-23 23:26:22 -04:00
aec77628f7 Small updates
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-23 23:25:00 -04:00
ecb696372d Added dockerfile to build assets via Gulp; updated docker scripts 2025-07-23 23:24:49 -04:00
69 changed files with 1269 additions and 652 deletions

3
.gitignore vendored
View File

@@ -65,3 +65,6 @@ node_modules/
# Compiled assets
public/css/
public/js/
# Ignore production configuration files to protect against credential leaks
config/production.yaml

View File

@@ -1,4 +1,6 @@
require: rubocop-sequel
plugins:
- rubocop-rspec
- rubocop-sequel
AllCops:
NewCops: enable

View File

@@ -1,11 +1,32 @@
pipeline:
style:
image: ruby:3.0
steps:
setup:
image: ruby:3.4
env:
RACK_ENV: testing
commands:
- 'gem install rake'
- 'bundle config set --local path "vendor/bundle"'
- 'bundle install'
- 'rake test:rubocop'
- gem install rake
- bundle config set --local path "vendor/bundle"
- bundle install
- rake db:migrate
test_ruby34:
image: ruby:3.4
env:
RACK_ENV: testing
commands:
- gem install rake
- bundle config set --local path "vendor/bundle"
- rake test:unit
group: tests
style:
image: ruby:3.4
env:
RACK_ENV: testing
commands:
- gem install rake
- bundle config set --local path "vendor/bundle"
- rake test:lint
gitea_release:
image: plugins/gitea-release
@@ -15,5 +36,5 @@ pipeline:
base_url: https://git.metaunix.net
title: "${CI_COMMIT_TAG}"
when:
event: tag
event:
- tag

View File

@@ -2,7 +2,7 @@ FROM ruby:3.4
RUN gem install bundler
WORKDIR /src
WORKDIR /usr/src/game-data
COPY Gemfile Gemfile.l*ck ./
@@ -13,3 +13,4 @@ RUN gem install rake
COPY . ./
ENTRYPOINT ["bash", "entrypoints/dev.sh"]

13
Dockerfile.gulp Normal file
View File

@@ -0,0 +1,13 @@
# Node.js runtime
FROM node:24
WORKDIR /usr/src/game-data/
COPY package.* /usr/src/game-data/
RUN npm install
VOLUME /usr/src/game-data/node_modules/
# Run the app
CMD [ "npm", "run", "gulp" ]

11
Gemfile
View File

@@ -1,7 +1,7 @@
source 'https://rubygems.org'
gem 'sinatra', '~> 3.2'
gem 'sinatra-contrib', '~> 3.2'
gem 'sinatra', '~> 4.1'
gem 'sinatra-contrib', '~> 4.1'
gem 'puma', '~> 6.6'
gem 'sequel', '~> 5.92'
@@ -15,5 +15,12 @@ group :development, :test do
# rubocop and extensions for code style
gem 'rubocop'
gem 'rubocop-rspec'
gem 'rubocop-sequel'
end
group :test do
gem 'rspec'
gem 'rack-test'
gem 'database_cleaner-sequel'
end

View File

@@ -4,6 +4,11 @@ GEM
ast (2.4.3)
base64 (0.3.0)
bigdecimal (3.2.2)
database_cleaner-core (2.0.1)
database_cleaner-sequel (2.0.2)
database_cleaner-core (~> 2.0.0)
sequel
diff-lcs (1.6.2)
ffi (1.17.2-aarch64-linux-gnu)
ffi (1.17.2-aarch64-linux-musl)
ffi (1.17.2-arm-linux-gnu)
@@ -33,10 +38,16 @@ GEM
puma (6.6.0)
nio4r (~> 2.0)
racc (1.8.1)
rack (2.2.17)
rack-protection (3.2.0)
rack (3.2.0)
rack-protection (4.1.1)
base64 (>= 0.1.0)
rack (~> 2.2, >= 2.2.4)
logger (>= 1.6.0)
rack (>= 3.0.0, < 4)
rack-session (2.1.1)
base64 (>= 0.1.0)
rack (>= 3.0.0)
rack-test (2.2.0)
rack (>= 1.3)
rainbow (3.1.1)
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
@@ -44,6 +55,19 @@ GEM
regexp_parser (2.10.0)
rerun (0.14.0)
listen (~> 3.0)
rspec (3.13.1)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0)
rspec-core (3.13.5)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.4)
rubocop (1.76.1)
json (~> 2.3)
language_server-protocol (~> 3.17.0.2)
@@ -58,6 +82,9 @@ GEM
rubocop-ast (1.45.1)
parser (>= 3.3.7.2)
prism (~> 1.4)
rubocop-rspec (3.6.0)
lint_roller (~> 1.1)
rubocop (~> 1.72, >= 1.72.1)
rubocop-sequel (0.4.1)
lint_roller (~> 1.1)
rubocop (>= 1.72.1, < 2)
@@ -65,16 +92,18 @@ GEM
ruby2_keywords (0.0.5)
sequel (5.93.0)
bigdecimal
sinatra (3.2.0)
sinatra (4.1.1)
logger (>= 1.6.0)
mustermann (~> 3.0)
rack (~> 2.2, >= 2.2.4)
rack-protection (= 3.2.0)
rack (>= 3.0.0, < 4)
rack-protection (= 4.1.1)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
sinatra-contrib (3.2.0)
sinatra-contrib (4.1.1)
multi_json (>= 0.0.2)
mustermann (~> 3.0)
rack-protection (= 3.2.0)
sinatra (= 3.2.0)
rack-protection (= 4.1.1)
sinatra (= 4.1.1)
tilt (~> 2.0)
sqlite3 (2.7.0-aarch64-linux-gnu)
sqlite3 (2.7.0-aarch64-linux-musl)
@@ -104,14 +133,18 @@ PLATFORMS
x86_64-linux-musl
DEPENDENCIES
database_cleaner-sequel
logger
puma (~> 6.6)
rack-test
rerun
rspec
rubocop
rubocop-rspec
rubocop-sequel
sequel (~> 5.92)
sinatra (~> 3.2)
sinatra-contrib (~> 3.2)
sinatra (~> 4.1)
sinatra-contrib (~> 4.1)
sqlite3 (~> 2.6)
BUNDLED WITH

View File

@@ -1,5 +1,7 @@
# Game Data
![Build badge](https://builds.metaunix.net/api/badges/84/status.svg)
Web-based tool to store and organize PC hardware gaming benchmarks.
## Project Goals
@@ -7,28 +9,58 @@ Web-based tool to store and organize PC hardware gaming benchmarks.
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 - it's good practice to run a benchmark multiple times for accuracy.
* 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
Game Data runs on Ruby, and takes advantage of Bundler to manage code dependencies and Rake to run various tasks for maintaining the app. You can install them globally like so:
Game Data runs on Ruby, and takes advantage of [Bundler](https://bundler.io/) to manage code dependencies and [Rake](https://ruby.github.io/rake/) to run various tasks for maintaining the app. You can install them globally like so:
Debian/Ubuntu: `apt install -y ruby ruby-bundler rake`
RedHat and clones: `dnf install -y ruby rubygem-bundler rubygem-rake`
## Production Deployment
**TBD**
## Development
Install dependencies via bundler:
### Via Docker
If you'd prefer not to install dependencies and such to your local OS, you can do the development via Docker. The scripts provided in `bin/` will build Docker images for running the Ruby app and building the front-end assets via Gulp. *Both containers will automatically watch for changes.*
**Note:** Using the scripts below, the Docker images will remove themselves when stopped. This is to make clean up a bit more streamlined.
1. [Install Docker](https://docs.docker.com/engine/install/) for your OS.
2. Build the docker images for Ruby and Gulp:
`bin/docker-build.sh`
3. Run the docker images:
`bin/docker-run.sh`
4. If everything is running successfully you can open your browser and go to https://localhost:9292.
### Local/Native Development
1. Install dependencies via bundler:
`bundle install`
Perform database migrations:
2. Perform database migrations:
`rake db:migrate`
Run the server in development with auto-reloading:
3. Run the server in development with auto-reloading:
`rake server:dev`
If everything is running successfully you can open your browser and go to https://localhost:9292.
4. If everything is running successfully you can open your browser and go to https://localhost:9292.
## License
This project is available under the BSD 2-Clause license.

View File

@@ -3,18 +3,22 @@ require 'bundler/setup'
namespace :db do
desc 'Run migrations'
task :migrate, [:version] do |t, args|
require "sequel/core"
require 'sequel/core'
# load configuration
require_relative 'src/config'
conf = Config.new()
Sequel.extension :migration
version = args[:version].to_i if args[:version]
Sequel.connect('sqlite://data/gamedata.db') do |db|
Sequel::Migrator.run(db, "db/migrations", target: version)
Sequel.connect(adapter: conf.get('database.adapter'), database: conf.get('database.database')) do |db|
Sequel::Migrator.run(db, 'db/migrations', target: version)
end
end
end
namespace :server do
task :start do
ENV['APP_ENV'] = 'production'
ENV['RACK_ENV'] = 'production'
system("puma")
end
@@ -24,8 +28,12 @@ namespace :server do
end
namespace :test do
task :rubocop do
system("rubocop src/")
task :unit do
ENV['RACK_ENV'] = 'testing'
system("rspec")
end
task :lint do
system("rubocop src/ spec/")
end
end

View File

@@ -1,3 +1,21 @@
$ ->
# run foundation scripts
$(document).foundation()
console.log('Ready.')
averageResults = (results, decimals = 2) ->
avgScore = 0
minScore = Infinity
maxScore = -Infinity
factor = (10 ^ decimals)
for result in results
avgScore += result.avg_score
minScore = Math.min(minScore, result.min_score)
maxScore = Math.max(maxScore, result.max_score)
return {
avgScore: Math.round((avgScore / results.length) * factor) / factor,
minScore: minScore,
maxScore: maxScore,
}

View File

@@ -1,54 +1,109 @@
$(document).ready ->
$('#generate_button').on 'click', (e) ->
e.preventDefault()
$.ajax(
method: 'POST'
url: '/reports'
data:
type: $('#report_type').val()
choice: $('#report_choice').val()
compare: $('#report_compare').val()).done (data) ->
benchChart.options.title.text = data.choice
benchChart.data.labels = data.names
benchChart.data.datasets[0].data = data.avg_results
benchChart.data.datasets[1].data = data.min_results
benchChart.update()
return
return
return
$ ->
chartInstance = null
benchChart = new Chart(document.getElementById('chart_canvas').getContext('2d'),
type: 'horizontalBar'
data:
labels: []
datasets: [
{
label: 'Average FPS'
data: []
backgroundColor: 'hotpink'
borderColor: '#212121'
borderWidth: 1
}
{
label: 'Minimum FPS'
data: []
backgroundColor: 'cornflowerblue'
borderColor: '#212121'
borderWidth: 1
}
]
options:
title:
display: true
text: 'N/a'
scales: xAxes: [ {
display: true
ticks: beginAtZero: true
} ]
animation: onComplete: ->
dwnbtn = $('#download_button')
dwnbtn.attr 'href', benchChart.toBase64Image()
dwnbtn.attr 'download', 'benchmark_chart.png'
dwnbtn.attr 'disabled', false
return
)
$('#report-benchmarks').on 'change', (e) ->
$('#report-tests option').prop('selected', false)
$('#reports-download').on 'click', (e) ->
e.preventDefault()
canvas = $('#benchmark-chart')[0]
a = document.createElement 'a'
a.href = canvas.toDataURL 'image/png'
a.download = 'chart.png'
a.click()
$('#reports-button').on 'click', (e) ->
e.preventDefault()
$('#reports-download').attr('disabled', true)
chartInstance.destroy() if chartInstance
benchmarkId = $('#report-benchmarks').val()
testIds = $('#report-tests').val()
benchmarkSearchParams = new URLSearchParams
benchmark_id: benchmarkId
benchmarkRes = await fetch("/api/v1/benchmark/details?#{benchmarkSearchParams}")
benchmarkData = await benchmarkRes.json()
data =
labels: []
datasets: []
switch benchmarkData.scoring
when 'pts'
data.datasets.push({
label: 'Average Score'
data: []
})
when 'fps'
data.datasets.push({
label: 'Average FPS'
data: []
})
data.datasets.push({
label: 'Minimum FPS'
data: []
})
when 'ms'
data.datasets.push({
label: 'Average Frame Time'
data: []
})
data.datasets.push({
label: 'Minimum Frame Time'
data: []
})
for testId in testIds
try
testSearchParams = new URLSearchParams
test_id: testId
testRes = await fetch("/api/v1/test/details?#{testSearchParams}")
testData = await testRes.json()
resultSearchParams = new URLSearchParams
test_id: testId
benchmark_id: benchmarkId
resultRes = await fetch("/api/v1/result/list?#{resultSearchParams}")
resultData = await resultRes.json()
resultAverage = averageResults(resultData)
data.labels.push(testData.name)
data.datasets[0].data.push(resultAverage.avgScore)
console.log(data.datasets[0].data)
switch benchmarkData.scoring
when 'fps', 'ms'
data.datasets[1].data.push(resultAverage.minScore)
catch error
console.error 'An error occurred while fetching benchmark results.', error
ctx = $('#benchmark-chart')[0].getContext('2d')
options =
indexAxis: 'y'
plugins:
title:
display: true
text: benchmarkData.name
font:
size: '24'
datalabels:
anchor: 'end'
align: 'left'
color: 'black'
font:
weight: 'bold'
formatter: (value) -> value
scales:
y:
beginAtZero: true
chartInstance = new Chart ctx,
type: 'bar'
data: data
options: options
plugins: [ChartDataLabels]
$('#reports-download').attr('disabled', false)
$('#benchmark-chart').removeClass('disabled')

View File

@@ -10,33 +10,28 @@ fetchTestBenchmarkResults = (testId, benchmarkId) ->
resultSearchParams = new URLSearchParams
test_id: testId
benchmark_id: benchmarkId
resultRes = await fetch("/api/v1/results?#{resultSearchParams}")
resultRes = await fetch("/api/v1/result/list?#{resultSearchParams}")
resultData = await resultRes.json()
avg_total = 0
min_total = 0
max_total = 0
for result in resultData
avg_total += result.avg_score
min_total += result.min_score
max_total += result.max_score
resultAverage = averageResults(resultData)
tableRow = $("#results-table tr[data-benchmark-id=#{benchmarkId}]")
tableRow.append('<td>' + benchmarkData.name + '</td>')
tableRow.append('<td><a href="/benchmark/' + benchmarkData.id + '">' + benchmarkData.name + '</a></td>')
tableRow.append('<td>' + benchmarkData.scoring + '</td>')
tableRow.append('<td>' + resultData.length + '</td>')
if resultData.length != 0
tableRow.append('<td>' + (avg_total / resultData.length) + '</td>')
tableRow.append('<td>' + resultAverage.avgScore + '</td>')
if benchmarkData.scoring == 'fps'
tableRow.append('<td>' + resultAverage.minScore + '</td>')
tableRow.append('<td>' + resultAverage.maxScore + '</td>')
else
tableRow.append('<td>N/a</td>')
tableRow.append('<td>N/a</td>')
else
tableRow.append('<td>N/a</td>')
if min_total != 0
tableRow.append('<td>' + (min_total / resultData.length) + '</td>')
tableRow.append('<td>' + (max_total / resultData.length) + '</td>')
else
tableRow.append('<td>N/a</td>')
tableRow.append('<td>N/a</td>')
catch error

View File

@@ -1,10 +1,21 @@
#main-nav
margin-bottom: 15px
@use "sass:color"
h1.invalid
color: red
//$primary-color: #3399ff
$primary-color: cornflowerblue
$primary-color-highlight: color.adjust($primary-color, $lightness: -10%)
.button
position: relative
top: -25%
margin-top: 50%
html
width: 100%
height: 100%
body
background: rgb(240, 235, 248)
table
border: 1px solid #666
#wrapper
background: white
padding: 1.5rem 2rem
border: 1px solid #bbb
border-radius: 8px

View File

@@ -1 +1,2 @@
docker build -t game-data -f Dockerfile.dev .
docker build -t game-data-gulp -f Dockerfile.gulp .

View File

@@ -1,3 +1,5 @@
#!/bin/sh
docker build -t game-data -f Dockerfile.dev .
docker build -t game-data-gulp -f Dockerfile.gulp .

View File

@@ -1 +1,2 @@
docker run --rm -d -t -v "%cd%:/src" -p 9292:9292 --name game-data game-data
docker run --rm -d -t -v "%cd%:/usr/src/game-data" -p 9292:9292 --name game-data game-data
docker run --rm -d -t -v "%cd%:/usr/src/game-data" --name game-data-gulp game-data-gulp npm run gulp watch

View File

@@ -1,3 +1,5 @@
#!/bin/sh
docker run --rm -d -t -v "$(pwd):/src" -p 9292:9292 --name game-data game-data
docker run --rm -d -t -v "$(pwd):/usr/src/game-data" -p 9292:9292 --name game-data game-data
docker run --rm -d -t -v "$(pwd):/usr/src/game-data" --name game-data-gulp game-data-gulp npm run gulp watch

View File

@@ -1,6 +1,4 @@
# Load application config
require_relative 'src/config.rb'
$conf = Config.new(File.join(__dir__, 'config/defaults.yaml'))
root = ::File.dirname(__FILE__)
require ::File.join( root, 'src', 'server' )

View File

@@ -1,6 +1,6 @@
database:
adapter: 'sqlite'
database: 'data/gamedata.db'
server:
host: '0.0.0.0'
port: '9292'
testing:
minimum_results_required: 3

3
config/development.yaml Normal file
View File

@@ -0,0 +1,3 @@
database:
adapter: 'sqlite'
database: 'data/gamedata.db'

11
config/puma.rb Normal file
View File

@@ -0,0 +1,11 @@
app_dir = File.expand_path('..', __dir__)
directory app_dir
environment ENV.fetch('RACK_ENV', 'development')
require_relative '../src/config'
conf = Config.new()
bind "tcp://#{conf.get('server.host')}:#{conf.get('server.port')}"
workers 2
threads 1, 5

3
config/testing.yaml Normal file
View File

@@ -0,0 +1,3 @@
database:
adapter: 'sqlite'
database: 'data/gamedata_testing.db'

View File

@@ -14,26 +14,37 @@ function compileSass() {
// Compile .coffee files to JavaScript with source maps
function compileCoffee() {
return gulp.src('assets/scripts/*.coffee', { sourcemaps: true })
return gulp.src('assets/scripts/**/*.coffee', { sourcemaps: true })
.pipe(plumber())
.pipe(coffee({ bare: true }))
.pipe(gulp.dest('public/js', { sourcemaps: '.' }));
}
// Watch files for changes
function watchFiles() {
function watchFiles(cb) {
gulp.watch('assets/styles/**/*.sass', compileSass);
gulp.watch('assets/scripts/**/*.coffee', compileCoffee);
cb();
}
// Define default task
exports.default = gulp.series(
gulp.parallel(compileSass, compileCoffee),
watchFiles
);
// Chain all asset builds together
function buildAssets() {
return gulp.parallel(compileSass, compileCoffee);
}
// Perform initial build then watch
function buildAndWatch() {
return gulp.series(
buildAssets(),
watchFiles
);
}
// one-time build
exports.build = gulp.parallel(compileSass, compileCoffee);
exports.build = buildAssets();
// start builds with file watching
exports.watch = watchFiles;
exports.watch = buildAndWatch();
// Define default task (defaults to watch)
exports.default = exports.watch;

View File

@@ -0,0 +1,44 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(BenchmarkController) do
# /benchmark - redirects to /benchmark/list
describe 'GET /benchmark' do
before { get '/benchmark' }
it 'Benchmark base route is a redirect' do
expect(last_response).to(be_redirect)
end
it 'Benchmark base route Location header points to /benchmark/list' do
expect(last_response['Location']).to(eq("#{BASE_URL}/benchmark/list"))
end
end
# /benchmark/list - displays a table of benchmarks
describe 'GET /benchmark/list' do
before { get '/benchmark/list' }
it 'Benchmark list page returns 200.' do
expect(last_response).to(be_ok)
end
it "Benchmark list page contains 'List of benchmarks' on page." do
expect(last_response.body).to(include('List of benchmarks'))
end
end
# /benchmark/add - form for adding benchmark
describe 'GET /benchmark/add' do
before { get '/benchmark/add' }
it 'Benchmark add page returns 200.' do
expect(last_response).to(be_ok)
end
it "Benchmark add page contains 'Add new benchmark' on page." do
expect(last_response.body).to(include('Add new benchmark'))
end
end
end

View File

@@ -0,0 +1,44 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(HardwareController) do
# /hardware - redirects to /hardware/list
describe 'GET /hardware' do
before { get '/hardware' }
it 'Hardware base route is a redirect' do
expect(last_response).to(be_redirect)
end
it 'Hardware base route Location header points to /hardware/list' do
expect(last_response['Location']).to(eq("#{BASE_URL}/hardware/list"))
end
end
# /hardware/list - displays a table of hardwares
describe 'GET /hardware/list' do
before { get '/hardware/list' }
it 'Hardware list page returns 200.' do
expect(last_response).to(be_ok)
end
it "Hardware list page contains 'List of hardware' on page." do
expect(last_response.body).to(include('List of hardware'))
end
end
# /hardware/add - form for adding hardware
describe 'GET /hardware/add' do
before { get '/hardware/add' }
it 'Hardware add page returns 200.' do
expect(last_response).to(be_ok)
end
it "Hardware add page contains 'Add new hardware' on page." do
expect(last_response.body).to(include('Add new hardware'))
end
end
end

View File

@@ -0,0 +1,21 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(IndexController) do
describe 'GET /' do
before { get '/' }
it 'Dashboard returns 200.' do
expect(last_response).to(be_ok)
end
it "Dashboard contains 'Game Data' on page (nav bar should be loaded)." do
expect(last_response.body).to(include('Game Data'))
end
it "Dashboard contains 'Ruby version' on page (footer should be loaded)." do
expect(last_response.body).to(include('Ruby version'))
end
end
end

View File

@@ -0,0 +1,17 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(ReportsController) do
describe 'GET /report' do
before { get '/report' }
it 'Reports page returns 200.' do
expect(last_response).to(be_ok)
end
it "Reports page contains 'Generate report' on page." do
expect(last_response.body).to(include('Generate report'))
end
end
end

View File

@@ -0,0 +1,44 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(TestController) do
# /test - redirects to /test/list
describe 'GET /test' do
before { get '/test' }
it 'Test base route is a redirect' do
expect(last_response).to(be_redirect)
end
it 'Test base route Location header points to /test/list' do
expect(last_response['Location']).to(eq("#{BASE_URL}/test/list"))
end
end
# /test/list - displays a table of tests
describe 'GET /test/list' do
before { get '/test/list' }
it 'Test list page returns 200.' do
expect(last_response).to(be_ok)
end
it "Test list page contains 'List of tests' on page." do
expect(last_response.body).to(include('List of tests'))
end
end
# /test/add - form for adding test
describe 'GET /test/add' do
before { get '/test/add' }
it 'Test add page returns 200.' do
expect(last_response).to(be_ok)
end
it "Test add page contains 'Add new test' on page." do
expect(last_response.body).to(include('Add new test'))
end
end
end

View File

@@ -0,0 +1,27 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(Benchmark) do
describe 'Benchmark Creation' do
it 'Benchmark creation updates model count.' do
expect do
described_class.create(name: 'Test Benchmark', scoring: 'fps')
end.to(change(described_class, :count).by(1))
end
end
describe 'Benchmark Read' do
before { described_class.create(name: 'Test Benchmark', scoring: 'fps') }
it 'Benchmark model has name.' do
bench = described_class.first()
expect(bench.name).to(eq('Test Benchmark'))
end
it 'Benchmark model has scoring.' do
bench = described_class.first()
expect(bench.scoring).to(eq('fps'))
end
end
end

View File

@@ -0,0 +1,25 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(Hardware) do
describe 'Hardware Creation' do
it 'Hardware creation updates model count.' do
expect { described_class.create(name: 'Test Hardware', type: 'gpu') }.to(change(described_class, :count).by(1))
end
end
describe 'Hardware Read' do
before { described_class.create(name: 'Test Hardware', type: 'gpu') }
it 'Hardware model has name.' do
hardware = described_class.first()
expect(hardware.name).to(eq('Test Hardware'))
end
it 'Hardware model has scoring.' do
hardware = described_class.first()
expect(hardware.type).to(eq('gpu'))
end
end
end

View File

@@ -0,0 +1,36 @@
# frozen_string_literal: true
require_relative '../spec_helper'
RSpec.describe(Test) do
describe 'Test Creation' do
it 'Test creation updates model count.' do
expect { described_class.create(name: 'Test Test') }.to(change(described_class, :count).by(1))
end
end
describe 'Test Read' do
before do
described_class.create(name: 'Test Test')
end
it 'Test model has name.' do
tst = described_class.first()
expect(tst.name).to(eq('Test Test'))
end
end
describe 'Test one-to-many association with Hardware' do
it 'Test model has Hardware associated with it.' do
hardware = Hardware.create(name: 'Test Hardware', type: 'gpu')
tst = described_class.create(name: 'Test Test', hardware_id: hardware.id)
expect(tst.hardware).to(eq(hardware))
end
it 'Test model\'s hardware has name set.' do
hardware = Hardware.create(name: 'Test Hardware', type: 'gpu')
tst = described_class.create(name: 'Test Test', hardware_id: hardware.id)
expect(tst.hardware.name).to(eq('Test Hardware'))
end
end
end

35
spec/spec_helper.rb Normal file
View File

@@ -0,0 +1,35 @@
# frozen_string_literal: true
ENV['APP_ENV'] = 'test'
require_relative '../src/server'
require 'rspec'
require 'rack/test'
require 'database_cleaner/sequel'
# setting this here so all redirect tests can reference the same base URL
BASE_URL = 'http://example.org'
module RSpecMixin
include Rack::Test::Methods
def app
GameData
end
end
RSpec.configure do |config|
config.include(RSpecMixin)
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
end
config.around do |suite|
DatabaseCleaner.cleaning do
suite.run
end
end
end

7
src/appinfo.rb Normal file
View File

@@ -0,0 +1,7 @@
# frozen_string_literal: true
module AppInfo
VERSION = '0.2.1'
end

View File

@@ -6,8 +6,9 @@ require 'yaml'
class Config
DEFAULT_CONFIG = 'config/defaults.yaml'
ENVIRONMENT_CONFIG = ENV.fetch('RACK_ENV', 'development')
def initialize(config_path)
def initialize(config_path = "config/#{ENVIRONMENT_CONFIG}.yaml")
@data = YAML.load_file(DEFAULT_CONFIG)
# merge in user-defined configuration if it exists

View File

@@ -1,7 +1,14 @@
# frozen_string_literal: true
require 'sinatra/json'
require_relative 'base_controller'
require_relative '../models/benchmark'
require_relative '../models/test'
require_relative '../models/result'
# /api/v1 routes
class GameData < Sinatra::Base
class APIv1Controller < BaseController
get '/api/v1/benchmark/details' do
benchmark_id = params[:benchmark_id]
@@ -11,7 +18,7 @@ class GameData < Sinatra::Base
json benchmark.values()
end
get '/api/v1/results' do
get '/api/v1/result/list' do
test_id = params[:test_id]
benchmark_id = params[:benchmark_id]
@@ -20,4 +27,12 @@ class GameData < Sinatra::Base
json results.map(&:values)
end
get '/api/v1/test/details' do
test_id = params[:test_id]
tst = Test.where(id: test_id).first()
json tst.values()
end
end

View File

@@ -0,0 +1,15 @@
# frozen_string_literal: true
require 'sinatra/base'
# BaseController - base modular Sinatra app class
class BaseController < Sinatra::Base
# Register view helpers
require_relative '../helpers'
helpers Helpers
# Set up our view engine
set :views, File.join(settings.root, '/../../views')
end

View File

@@ -1,9 +1,16 @@
# frozen_string_literal: true
require_relative 'base_controller'
require_relative '../models/benchmark'
# /benchmark routes
class GameData < Sinatra::Base
class BenchmarkController < BaseController
get '/benchmark' do
redirect('/benchmark/list')
end
get '/benchmark/list' do
benchmarks = Benchmark.reverse(:updated_at).limit(10).all()
erb :'benchmark/index', locals: {

View File

@@ -1,9 +1,17 @@
# frozen_string_literal: true
require_relative 'base_controller'
require_relative '../models/hardware'
require_relative '../models/benchmark'
# /hardware routes
class GameData < Sinatra::Base
class HardwareController < BaseController
get '/hardware' do
redirect('/hardware/list')
end
get '/hardware/list' do
hardware = Hardware.reverse(:updated_at).limit(10).all()
erb :'hardware/index', locals: {

18
src/controllers/index.rb Normal file
View File

@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative 'base_controller'
require_relative '../models/test'
# / (top-level) routes
class IndexController < BaseController
get '/' do
tests = Test.reverse(:updated_at).limit(10).all()
erb :'index/index', locals: {
title: 'Dashboard',
tests: tests
}
end
end

View File

@@ -1,20 +1,27 @@
# frozen_string_literal: true
# /reports routes
class GameData < Sinatra::Base
require 'sinatra/json'
get '/reports' do
require_relative 'base_controller'
require_relative '../models/benchmark'
require_relative '../models/result'
require_relative '../models/test'
# /reports routes
class ReportsController < BaseController
get '/report' do
benchmarks = Benchmark.order(:name).all()
hardware = Hardware.order(:name).all()
tests = Test.order(:name).all()
erb :'reports/index', locals: {
title: 'Generate Reports',
hardware: hardware,
tests: tests,
benchmarks: benchmarks
}
end
post '/reports' do
post '/report' do
report_type = params[:type]
report_choice = params[:choice]
report_compare = params[:compare]
@@ -26,10 +33,10 @@ class GameData < Sinatra::Base
min_results = []
report_compare.each do |c|
hrd = Hardware.where(id: c).first()
names.push(hrd.name)
tst = Test.where(id: c).first()
names.push(tst.name)
res = Result.where(benchmark_id: report_choice, hardware_id: c).first()
res = Result.where(benchmark_id: report_choice, test_id: c).first()
avg_results.push(res.avg_score)
min_results.push(res.min_score)
end

View File

@@ -1,27 +1,11 @@
# frozen_string_literal: true
require_relative 'base_controller'
require_relative '../models/result'
# /result routes
class GameData < Sinatra::Base
class ResultController < BaseController
get '/result' do
results = Result.reverse(:updated_at).limit(10).all()
erb :'result/index', locals: {
title: 'List of Results',
results: results
}
end
get '/result/add' do
hardware = Hardware.all()
benchmarks = Benchmark.all()
erb :'result/add', locals: {
title: 'Add Result',
hardware: hardware,
benchmarks: benchmarks
}
end
post '/result/add' do
result_minimum = params[:result_minimum] if params.key?(:result_minimum)
result_maximum = params[:result_maximum] if params.key?(:result_maximum)

96
src/controllers/test.rb Normal file
View File

@@ -0,0 +1,96 @@
# frozen_string_literal: true
require_relative 'base_controller'
require_relative '../models/benchmark'
require_relative '../models/hardware'
require_relative '../models/test'
# /test routes
class TestController < BaseController
get '/test' do
redirect('/test/list')
end
get '/test/list' do
tests = Test.reverse(:updated_at).limit(10).all()
erb :'test/index', locals: {
title: 'List of Tests',
tests: tests
}
end
get '/test/add' do
hardware = Hardware.order(:name).all()
benchmarks = Benchmark.order(:name).all()
erb :'test/add', locals: {
title: 'Add Test',
hardware: hardware,
benchmarks: benchmarks
}
end
post '/test/add' do
tst = Test.create(
name: params[:test_name],
hardware_id: params[:test_hardware],
description: params[:test_description]
)
# create an array of the selected benchmarks
benchmarks = Array(params[:test_benchmarks])
# associate the benchmarks to the test
benchmarks.each do |b|
tst.add_benchmark(b)
end
redirect "/test/#{tst.id}"
end
get '/test/:test_id' do
tst = Test.where(id: params[:test_id]).first()
erb :'test/view', locals: {
title: tst.name,
test: tst
}
end
get '/test/:test_id/edit' do
tst = Test.where(id: params[:test_id]).first()
hardware = Hardware.order(:name).all()
benchmarks = Benchmark.order(:name).all()
erb :'test/edit', locals: {
title: "Editing: #{tst.name}",
test: tst,
hardware: hardware,
benchmarks: benchmarks
}
end
post '/test/:test_id/edit' do
tst = Test.where(id: params[:test_id]).first()
tst.update(
name: params[:test_name],
hardware_id: params[:test_hardware],
description: params[:test_description]
)
selected_benchmarks = Array(params[:test_benchmarks])
# remove benchmarks no longer associated with the test
tst.benchmarks.dup.each do |b|
tst.remove_benchmark(b.id) unless selected_benchmarks.include?(b.id)
end
# associate the benchmarks to the test
selected_benchmarks.each do |b|
tst.add_benchmark(b) unless tst.benchmark?(b)
end
redirect "/test/#{tst.id}"
end
end

View File

@@ -1,8 +1,18 @@
# frozen_string_literal: true
require_relative 'appinfo'
# Helpers - view helper functions
module Helpers
def ruby_version
return RUBY_VERSION
end
def app_version
return AppInfo::VERSION
end
def date_format(date)
dt = date.to_datetime
return dt.strftime('%B %d, %Y @ %I:%M:%S %p %Z')

View File

@@ -3,6 +3,7 @@
# Benchmark - database model for PC benchmarks
class Benchmark < Sequel::Model
many_to_many :tests
one_to_many :results
end

View File

@@ -1,6 +0,0 @@
# frozen_string_literal: true
require_relative 'hardware'
require_relative 'benchmark'
require_relative 'result'
require_relative 'test'

View File

@@ -5,6 +5,10 @@ class Test < Sequel::Model
one_to_many :result
many_to_one :hardware
many_to_many :benchmark
many_to_many :benchmarks
def benchmark?(benchmark_id)
return benchmarks_dataset.where(Sequel[:benchmarks][:id] => benchmark_id).any?
end
end

View File

@@ -1,15 +0,0 @@
# frozen_string_literal: true
# / (top-level) routes
class GameData < Sinatra::Base
get '/' do
results = Result.reverse(:updated_at).limit(10).all()
erb :'index/index', locals: {
title: 'Dashboard',
results: results
}
end
end

View File

@@ -1,10 +0,0 @@
# frozen_string_literal: true
require_relative 'index'
require_relative 'hardware'
require_relative 'benchmark'
require_relative 'result'
require_relative 'reports'
require_relative 'test'
require_relative 'api1'

View File

@@ -1,68 +0,0 @@
# frozen_string_literal: true
# /test routes
class GameData < Sinatra::Base
get '/test' do
tests = Test.reverse(:updated_at).limit(10).all()
erb :'test/index', locals: {
title: 'List of Tests',
tests: tests
}
end
get '/test/add' do
hardware = Hardware.order(:name).all()
benchmarks = Benchmark.order(:name).all()
erb :'test/add', locals: {
title: 'Add Test',
hardware: hardware,
benchmarks: benchmarks
}
end
post '/test/add' do
tst = Test.create(
name: params[:test_name],
hardware_id: params[:test_hardware],
description: params[:test_description]
)
# create an array of the selected benchmarks
benchmarks = Array(params[:test_benchmarks])
# associate the benchmarks to the test
benchmarks.each do |b|
tst.add_benchmark(b)
end
redirect "/test/#{tst.id}"
end
get '/test/:test_id' do
tst = Test.where(id: params[:test_id]).first()
erb :'test/view', locals: {
title: tst.name,
test: tst
}
end
get '/test/:hardware_id/edit' do
hardware = Hardware.where(id: params[:hardware_id]).first()
erb :'test/edit', locals: {
title: "Editing: #{hardware.name}",
hardware: hardware
}
end
post '/test/:hardware_id/edit' do
hardware = Hardware.where(id: params[:hardware_id]).first()
hardware.update(
name: params[:hardware_name],
type: params[:hardware_type]
)
redirect "/hardware/#{hardware.id}"
end
end

38
src/server.rb Executable file → Normal file
View File

@@ -1,16 +1,32 @@
# frozen_string_literal: true
require 'sinatra/base'
require 'sinatra/json'
require 'sequel'
require 'sqlite3'
require_relative 'config'
# Load configuration from environment config file
$conf = Config.new()
# Load the Sequel timestamps plugin
Sequel::Model.plugin(:timestamps)
# Initialize Sequel gem for database actions
DB = Sequel.connect(adapter: $conf.get('database.adapter'), database: $conf.get('database.database'))
# Base app
# Load in routes (must happen after Sequel is loaded!)
require_relative 'controllers/api1'
require_relative 'controllers/benchmark'
require_relative 'controllers/hardware'
require_relative 'controllers/index'
require_relative 'controllers/reports'
require_relative 'controllers/result'
require_relative 'controllers/test'
# GameData - main app that gets launched
# - inherits from Sinatra::Base to instantiate the server
# - sets up some base app configuration
# - registers route classes with the base app
class GameData < Sinatra::Base
enable :sessions
@@ -19,16 +35,12 @@ class GameData < Sinatra::Base
enable :static
set :public_folder, File.join(__dir__, '/../public')
# Register view helpers
require_relative 'helpers'
helpers Helpers
# Set up our view engine
set :views, File.join(settings.root, '/../views')
use IndexController
use HardwareController
use BenchmarkController
use TestController
use ResultController
use ReportsController
use APIv1Controller
end
# Load routes
require_relative 'routes/init'
# Load models
require_relative 'models/init'

View File

@@ -1,41 +1,38 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row mb-4">
<div class="col-12">
<h1>Add new benchmark</h1>
</div>
</div>
<div class="grid-x grid-margin-x">
<div class="row">
<form class="cell small-12" action="/benchmark/add" method="post">
<div class="grid-x grid-padding-x">
<div class="cell medium-9">
<label>
Benchmark name
<input type="text" name="benchmark_name" placeholder="Example benchmark">
</label>
<form class="col-12" action="/benchmark/add" method="post">
<div class="row mb-3">
<div class="col-9">
<label for="benchmark_name">Benchmark name</label>
<input id="benchmark_name" class="form-control" type="text" name="benchmark_name" placeholder="Example benchmark">
</div>
<div class="cell medium-3">
<label>
Scoring type
<select name="benchmark_scoring">
<option value="fps">Frames per Second (fps)</option>
<option value="ms">Frame Time (ms)</option>
<option value="pts">Total Points</option>
</select>
</label>
<div class="col-3">
<label for="benchmark_scoring">Scoring type</label>
<select id="benchmark_scoring" class="form-select" name="benchmark_scoring">
<option value="fps">Frames per Second (fps)</option>
<option value="ms">Frame Time (ms)</option>
<option value="pts">Total Points</option>
</select>
</div>
</div>
<div class="grid-x grid-padding-x">
<div class="cell small-12">
<textarea name="benchmark_description" class="cell small-12">Enter a description/notes here.</textarea>
<div class="row mb-3">
<div class="col-12">
<label for="benchmark_description">Benchmark description</label>
<textarea id="benchmark_description" class="form-control" name="benchmark_description" placeholder="Enter a description/notes here."></textarea>
</div>
</div>
<div class="grid-x grid-padding-x">
<div class="cell small-12">
<input type="submit" class="button" value="Submit">
<div class="row">
<div class="col-12">
<input class="btn btn-primary w-100" type="submit" value="Create Benchmark">
</div>
</div>
</form>

View File

@@ -1,41 +1,38 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row mb-4">
<div class="col-12">
<h1>Editing: <%= benchmark.name %></h1>
</div>
</div>
<div class="grid-x grid-margin-x">
<div class="row">
<form class="cell small-12" action="/benchmark/<%= benchmark.id %>/edit" method="post">
<div class="grid-x grid-padding-x">
<div class="cell medium-9">
<label>
Benchmark name
<input type="text" name="benchmark_name" placeholder="Example benchmark" value="<%= benchmark.name %>">
</label>
<div class="row mb-3">
<div class="col-9">
<label for="benchmark_name">Benchmark name</label>
<input id="benchmark_name" class="form-control" type="text" name="benchmark_name" placeholder="Example benchmark" value="<%= benchmark.name %>">
</div>
<div class="cell medium-3">
<label>
Scoring type
<select name="benchmark_scoring">
<option value="fps" <% if benchmark.scoring == 'fps' %>selected<% end %>>Frames per Second (fps)</option>
<option value="ms" <% if benchmark.scoring == 'ms' %>selected<% end %>>Frame Time (ms)</option>
<option value="pts" <% if benchmark.scoring == 'pts' %>selected<% end %>>Total Points</option>
</select>
</label>
<div class="col-3">
<label for="benchmark_scoring">Scoring type</label>
<select id="benchmark_scoring" class="form-select" name="benchmark_scoring">
<option value="fps" <% if benchmark.scoring == 'fps' %>selected<% end %>>Frames per Second (fps)</option>
<option value="ms" <% if benchmark.scoring == 'ms' %>selected<% end %>>Frame Time (ms)</option>
<option value="pts" <% if benchmark.scoring == 'pts' %>selected<% end %>>Total Points</option>
</select>
</div>
</div>
<div class="grid-x grid-padding-x">
<div class="cell small-12">
<textarea name="benchmark_description" class="cell small-12"><%= benchmark.description %></textarea>
<div class="row mb-3">
<div class="col-12">
<label for="benchmark_description">Benchmark description</label>
<textarea id="benchmark_description" class="form-control" name="benchmark_description" placeholder="Enter a description/notes here."><%= benchmark.description %></textarea>
</div>
</div>
<div class="grid-x grid-padding-x">
<div class="cell small-12">
<input type="submit" class="button" value="Submit">
<div class="row">
<div class="col-12">
<input class="btn btn-primary w-100" type="submit" value="Submit Changes">
</div>
</div>
</form>

View File

@@ -1,20 +1,20 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row mb-3">
<div class="col-12">
<h1>List of benchmarks</h1>
</div>
<div class="cell small-12">
<div class="col-12">
<p>
<a href="/benchmark/add">Add new benchmark</a>
</p>
</div>
</div>
<div class="grid-x grid-margin-x">
<div class="row">
<% if benchmarks.length > 0 %>
<div class="cell small-12">
<table>
<thead>
<div class="col-12">
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Benchmark name</th>
<th>Scoring type</th>
@@ -24,7 +24,7 @@
<tbody>
<% benchmarks.each do |b| %>
<tr>
<td><a href="/benchmark/<%= b.id %>"><%= b.name %></a</td>
<td><a href="/benchmark/<%= b.id %>"><%= b.name %></a></td>
<td><%= b.scoring %></td>
<td><%= b.description %></td>
</tr>
@@ -33,7 +33,7 @@
</table>
</div>
<% else %>
<div class="cell small-12">
<div class="col-12">
<p>I'm sorry, there doesn't appear to be any benchmarks added yet. Check again later!</p>
</div>
<% end %>

View File

@@ -1,18 +1,49 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row">
<div class="col-12">
<h1><%= benchmark.name %></h1>
</div>
<div class="cell small-12">
<div class="col-12">
<p><a href="/benchmark/<%= benchmark.id %>/edit">Edit</a></p>
</div>
<div class="cell small-12">
<div class="col-12">
Benchmark scoring type: <%= benchmark.scoring %>
</div>
<div class="cell small-12">
<div class="col-12">
Description:
<p><%= benchmark.description %></p>
</div>
</div>
<hr>
<div class="row">
<div class="col-12">
<h3 class="mb-3">Tests using this benchmark:</h3>
<% if benchmark.tests.length > 0 %>
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Test title</th>
<th>Benchmarks</th>
<th>Last updated</th>
</tr>
</thead>
<tbody>
<% benchmark.tests.each do |t| %>
<tr>
<td><a href="/test/<%= t.id %>"><%= t.name %></a></td>
<td><%= t.benchmarks.length %></td>
<td><%= t.updated_at %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>There are no tests associated with this benchmark.</p>
<% end %>
</div>
</div>

View File

@@ -1,32 +1,32 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row mb-3">
<div class="col-12">
<h1>Add new hardware</h1>
</div>
</div>
<div class="grid-x grid-margin-x">
<div class="row mb-3">
<form class="cell small-12" action="/hardware/add" method="post">
<div class="grid-x grid-padding-x">
<div class="cell medium-9">
<label>
Hardware name
<input type="text" name="hardware_name" placeholder="Example hardware">
</label>
<form class="col-12" action="/hardware/add" method="post">
<div class="row mb-3">
<div class="col-12 col-md-9">
<label for="hardware_name">Hardware name</label>
<input id="hardware_name" class="form-control" type="text" name="hardware_name" placeholder="Example hardware">
</div>
<div class="cell medium-3">
<label>
Type
<select name="hardware_type">
<option value="gpu">Graphics card</option>
<option value="cpu">Processor</option>
</select>
</label>
<div class="col-12 col-md-3">
<label for="hardware_type">Type</label>
<select id="hardware_type" class="form-select" name="hardware_type">
<option value="gpu">Graphics card</option>
<option value="cpu">Processor</option>
</select>
</div>
</div>
<input type="submit" class="button" value="Submit">
<div class="row">
<div class="col-12">
<input class="btn btn-primary w-100" type="submit" value="Create Hardware">
</div>
</div>
</form>
</div>

View File

@@ -1,32 +1,32 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<h1>Editing: <%= hardware.name %></h1>
<div class="row mb-3">
<div class="col-12">
<h1>Add new hardware</h1>
</div>
</div>
<div class="grid-x grid-margin-x">
<div class="row mb-3">
<form class="cell small-12" action="/hardware/<%= hardware.id %>/edit" method="post">
<div class="grid-x grid-padding-x">
<div class="cell medium-9">
<label>
Hardware name
<input type="text" name="hardware_name" placeholder="Example hardware" value="<%= hardware.name %>">
</label>
<form class="col-12" action="/hardware/<%= hardware.id %>/edit" method="post">
<div class="row mb-3">
<div class="col-12 col-md-9">
<label for="hardware_name">Hardware name</label>
<input id="hardware_name" class="form-control" type="text" name="hardware_name" placeholder="Example hardware" value="<%= hardware.name %>">
</div>
<div class="cell medium-3">
<label>
Type
<select name="hardware_type">
<option value="gpu" <% if hardware.type == 'gpu' %>selected<% end %>>Graphics card</option>
<div class="col-12 col-md-3">
<label for="hardware_type">Type</label>
<select id="hardware_type" class="form-select" name="hardware_type">
<option value="gpu" <% if hardware.type == 'gpu' %>selected<% end %>>Graphics card</option>
<option value="cpu" <% if hardware.type == 'cpu' %>selected<% end %>>Processor</option>
</select>
</label>
</select>
</div>
</div>
<input type="submit" class="button" value="Submit">
<div class="row">
<div class="col-12">
<input class="btn btn-primary w-100" type="submit" value="Submit Changes">
</div>
</div>
</form>
</div>

View File

@@ -1,20 +1,20 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row mb-3">
<div class="col-12">
<h1>List of hardware</h1>
</div>
<div class="cell small-12">
<div class="col-12">
<p>
<a href="/hardware/add">Add new hardware</a>
</p>
</div>
</div>
<div class="grid-x grid-margin-x">
<div class="row mb-3">
<% if hardware.length > 0 %>
<div class="cell small-12">
<table>
<thead>
<div class="col-12">
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Hardware name</th>
<th>Type</th>
@@ -35,7 +35,7 @@
</table>
</div>
<% else %>
<div class="cell small-12">
<div class="col-12">
<p>I'm sorry, there doesn't appear to be any hardware added yet. Check again later!</p>
</div>
<% end %>

View File

@@ -1,13 +1,44 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row">
<div class="col-12">
<h1><%= hardware.name %></h1>
</div>
<div class="cell small-12">
<div class="col-12">
<p><a href="/hardware/<%= hardware.id %>/edit">Edit</a></p>
</div>
<div class="cell small-12">
<div class="col-12">
Hardware type: <%= hardware.type %>
</div>
</div>
<hr>
<div class="row">
<div class="col-12">
<h3 class="mb-3">Tests using this benchmark:</h3>
<% if hardware.tests.length > 0 %>
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Test title</th>
<th>Benchmarks</th>
<th>Last updated</th>
</tr>
</thead>
<tbody>
<% hardware.tests.each do |t| %>
<tr>
<td><a href="/test/<%= t.id %>"><%= t.name %></a></td>
<td><%= t.benchmarks.length %></td>
<td><%= t.updated_at %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>There are no tests associated with this benchmark.</p>
<% end %>
</div>
</div>

View File

@@ -1,30 +1,31 @@
<div class="grid-x grid-margin-x">
<% if results.length > 0 %>
<div class="cell small-12">
<div class="row">
<% if tests.length > 0 %>
<div class="12-col mb-3">
<h2>Latest benchmark results:</h2>
</div>
<div class="cell small-12">
<table>
<thead>
<div class="12-col">
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Hardware tested</th>
<th>Benchmark used</th>
<th>Score</th>
<th>Test name</th>
<th># Benchmarks</th>
<th>Last Updated</th>
</tr>
</thead>
<tbody>
<% results.each do |r| %>
<% tests.each do |t| %>
<tr>
<td><%= r.hardware.name %></td>
<td><%= r.benchmark.name %></td>
<td><%= r.formatted_score() %></td>
<td><a href="/test/<%= t.id %>"><%= t.name %></a></td>
<td><%= t.benchmarks.length %></td>
<td><%= t.updated_at %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="cell small-12">
<div class="12-col">
<p>I'm sorry, there don't appear to be any benchmark results logged yet. Check again later!</p>
</div>
<% end %>

View File

@@ -5,20 +5,24 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= title %> | Game Data</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.7.5/css/foundation.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="/css/rimmington.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.0/jquery.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/foundation/6.7.5/js/foundation.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/js/bootstrap.min.js" charset="utf-8"></script>
<script src="/js/edgeville.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js" charset="utf-8"></script>
</head>
<body>
<body class="d-flex flex-column min-vh-100">
<!-- main navigation -->
<%= erb :'partials/navbar', :locals => locals %>
<!-- main content -->
<div class="grid-container">
<%= yield %>
</div>
<main class="flex-grow-1 py-4">
<div id="wrapper" class="container mb-4">
<%= yield %>
</div>
</main>
<!-- site footer -->
<%= erb :'partials/footer', :locals => locals %>
</body>
</html>

10
views/partials/footer.erb Normal file
View File

@@ -0,0 +1,10 @@
<footer id="main-footer" class="bg-light border-top py-3 text-center">
<div class="container">
<div class="row">
<div class="col-12">
<p>Game Data version v<%= app_version() %></p>
<p class="mb-0">Running Ruby version v<%= ruby_version() %></p>
</div>
</div>
</div>
</footer>

View File

@@ -1,11 +1,29 @@
<div id="main-nav" class="top-bar">
<div class="top-bar-left">
<ul class="menu">
<li><a href="/">Dashboard</a></li>
<li><a href="/hardware">Hardware</a></li>
<li><a href="/benchmark">Benchmarks</a></li>
<li><a href="/test">Tests</a></li>
<li><a href="/reports">Reports</a></li>
</ul>
<div id="main-nav" class="navbar navbar-expand-md bg-dark border-bottom border-body mb-3" data-bs-theme="dark">
<div class="container-fluid">
<a class="navbar-brand mb-0 h1" href="#">Game Data</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-md-0">
<li class="nav-item">
<a class="nav-link" href="/">Dashboard</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/benchmark">Benchmarks</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/hardware">Hardware</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/test">Test</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/report">Reports</a>
</li>
</ul>
</div>
</div>
</div>

View File

@@ -1,35 +1,49 @@
<div class="grid-x grid-margin-x">
<form class="cell small-12" action="/reports" method="post">
<div class="grid-x grid-margin-x">
<select class="cell medium-6" id="report_type" name="report_type" disabled>
<option value="benchmark">Benchmark</option>
<option value="hardware">Hardware</option>
</select>
<div class="row">
<div class="col-12">
<h1>Generate report</h1>
</div>
</div>
<select class="cell medium-6" id="report_choice" name="report_choice">
<% benchmarks.each do |b| %>
<option value="<%= b.id %>"><%= b.name %></option>
<div class="row">
<form class="col-12" action="/reports" method="post">
<div class="row mb-3">
<div class="col-12 col-md-6">
<select id="report-type" class="form-select" name="report_type" disabled>
<option value="benchmark">Benchmark</option>
<option value="test">Test</option>
</select>
</div>
<div class="col-12 col-md-6">
<select id="report-benchmarks" class="form-select" name="report_choice">
<% benchmarks.each do |b| %>
<option value="<%= b.id %>"><%= b.name %></option>
<% end %>
</select>
</div>
</div>
<div class="col-12 mb-3">
<select id="report-tests" class="col-12 form-select" name="report_compare[]" multiple>
<% tests.each do |t| %>
<option value="<%= t.id %>"><%= t.name %></option>
<% end %>
</select>
</div>
<div class="grid-x grid-margin-x">
<select class="cell small-12" id="report_compare" name="report_compare[]" multiple>
<% hardware.each do |h| %>
<option value="<%= h.id %>"><%= h.name %></option>
<% end %>
</select>
<div class="col-12 mb-3">
<button id="reports-button" class="btn btn-primary">Generate</button>
<button id="reports-download" class="btn btn-primary" disabled>Download</button>
</div>
<input type="submit" class="button" id="generate_button" value="Generate">
<a href="#" class="button" id="download_button" disabled>Download</a>
<div class="grid-x grid-margin-x">
<canvas id="chart_canvas" width="100%" height="25"></canvas>
<div class="col-12">
<canvas id="benchmark-chart" width="100%" height="25"></canvas>
</div>
</form>
</div>
<!-- load the chart.js library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-datalabels/2.2.0/chartjs-plugin-datalabels.min.js" charset="utf-8"></script>
<!-- load chart functionality -->
<script src="/js/reports.js" charset="utf-8"></script>

View File

@@ -1,60 +0,0 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<h1>Add new result</h1>
</div>
</div>
<div class="grid-x grid-margin-x">
<form class="cell small-12" action="/result/add" method="post">
<div class="grid-x grid-padding-x">
<div class="cell medium-6">
<label>
Hardware:
<select name="result_hardware">
<% hardware.each do |h| %>
<option value="<%= h.id %>"><%= h.name %></option>
<% end %>
</select>
</label>
</div>
<div class="cell medium-6">
<label>
Benchmark:
<select name="result_benchmark">
<% benchmarks.each do |b| %>
<option value="<%= b.id %>"><%= b.name %></option>
<% end %>
</select>
</label>
</div>
</div>
<div class="grid-x grid-padding-x">
<div class="cell medium-4">
<label>
Average score:
<input type="number" name="result_average" value="0.0" step="0.01" required>
</label>
</div>
<div class="cell medium-4">
<label>
Minimum score:
<input type="number" name="result_minimum" value="0.0" step="0.01">
</label>
</div>
<div class="cell medium-4">
<label>
Maximum score:
<input type="number" name="result_maximum" value="0.0" step="0.01">
</label>
</div>
</div>
<input type="submit" class="button" value="Submit">
</form>
</div>

View File

@@ -1,44 +0,0 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<h1>List of results</h1>
</div>
<div class="cell small-12">
<p>
<a href="/result/add">Add new result</a>
</p>
</div>
</div>
<div class="grid-x grid-margin-x">
<% if results.length > 0 %>
<div class="cell small-12">
<table>
<thead>
<tr>
<th>Hardware</th>
<th>Benchmark</th>
<th>Score (type)</th>
<th>Date added</th>
<th>Date modified</th>
</tr>
</thead>
<tbody>
<% results.each do |r| %>
<tr>
<td><%= r.hardware.name %></td>
<td><%= r.benchmark.name %></td>
<td><%= r.score %> (<%= r.benchmark.scoring %>)</td>
<td><%= date_format(r.created_at) %></td>
<td><%= date_format(r.updated_at) %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="cell small-12">
<p>I'm sorry, there don't appear to be any results added yet. Check again later!</p>
</div>
<% end %>
</div>

View File

@@ -1,50 +1,49 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row mb-3">
<div class="col-12">
<h1>Add new test</h1>
</div>
</div>
<div class="grid-x grid-margin-x">
<div class="row">
<form class="cell small-12" action="/test/add" method="post">
<div class="grid-x grid-padding-x">
<div class="cell medium-6">
<label>
Test name
<input type="text" name="test_name" placeholder="My hardware test (01/99)">
</label>
<form class="col-12" action="/test/add" method="post">
<div class="row mb-0 mb-md-3">
<div class="col-12 col-md-6 mb-3 mb-md-0">
<label for="test_name">Test name</label>
<input id="test_name" class="form-control" type="text" name="test_name" placeholder="My hardware test (01/99)">
</div>
<div class="cell medium-6">
<label>
Type
<select name="test_hardware">
<% for h in hardware %>
<option value="<%= h.id %>"><%= h.name %></option>
<% end %>
</select>
</label>
<div class="col-12 col-md-6 mb-3 mb-md-0">
<label for="test_hardware">Hardware to test</label>
<select id="test_hardware" class="form-select" name="test_hardware">
<% for h in hardware %>
<option value="<%= h.id %>"><%= h.name %></option>
<% end %>
</select>
</div>
<div class="cell medium-4">
<label>
Benchmarks
<select name="test_benchmarks[]" multiple>
<% for b in benchmarks %>
<option value="<%= b.id %>"><%= b.name %></option>
<% end %>
</select>
</label>
</div>
<div class="cell medium-8">
<label>
Test description
<textarea name="test_description" placeholder="This is my test for a hardware..."></textarea>
</label>
</div>
<input type="submit" class="button" value="Submit">
<div class="row mb-0 mb-md-3">
<div class="col-12 col-md-4 mb-3 mb-md-0">
<label for="test_benchmarks">Benchmarks</label>
<select id="test_benchmarks" class="form-select" name="test_benchmarks[]" multiple>
<% for b in benchmarks %>
<option value="<%= b.id %>"><%= b.name %></option>
<% end %>
</select>
</div>
<div class="col-12 col-md-8 mb-3 mb-md-0">
<label for="test_description">Test description</label>
<textarea id="test_description" class="form-control" name="test_description" placeholder="This is my test for a hardware..."></textarea>
</div>
</div>
<div class="row">
<div class="col-12">
<input class="btn btn-primary w-100" type="submit" value="Create Test">
</div>
</div>
</form>
</div>

View File

@@ -1,32 +1,49 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<h1>Editing: <%= hardware.name %></h1>
<div class="row mb-3">
<div class="col-12">
<h1>Editing: <%= test.name %></h1>
</div>
</div>
<div class="grid-x grid-margin-x">
<div class="row">
<form class="cell small-12" action="/hardware/<%= hardware.id %>/edit" method="post">
<div class="grid-x grid-padding-x">
<div class="cell medium-9">
<label>
Hardware name
<input type="text" name="hardware_name" placeholder="Example hardware" value="<%= hardware.name %>">
</label>
<form class="col-12" action="/test/<%= test.id %>/edit" method="post">
<div class="row mb-0 mb-md-3">
<div class="col-12 col-md-6 mb-3 mb-md-0">
<label for="test_name">Test name</label>
<input id="test_name" class="form-control" type="text" name="test_name" placeholder="My hardware test (01/99)" value="<%= test.name %>">
</div>
<div class="cell medium-3">
<label>
Type
<select name="hardware_type">
<option value="gpu" <% if hardware.type == 'gpu' %>selected<% end %>>Graphics card</option>
<option value="cpu" <% if hardware.type == 'cpu' %>selected<% end %>>Processor</option>
</select>
</label>
<div class="col-12 col-md-6 mb-3 mb-md-0">
<label for="test_hardware">Hardware to test</label>
<select id="test_hardware" class="form-select" name="test_hardware">
<% for h in hardware %>
<option value="<%= h.id %>" <% if h.id == test.hardware.id %>selected<% end %>><%= h.name %></option>
<% end %>
</select>
</div>
</div>
<input type="submit" class="button" value="Submit">
<div class="row mb-0 mb-md-3">
<div class="col-12 col-md-4 mb-3 mb-md-0">
<label for="test_benchmarks">Benchmarks</label>
<select id="test_benchmarks" class="form-select" name="test_benchmarks[]" multiple>
<% for b in benchmarks %>
<option value="<%= b.id %>" <% if test.benchmark?(b.id) %>selected<% end %>><%= b.name %></option>
<% end %>
</select>
</div>
<div class="col-12 col-md-8 mb-3 mb-md-0">
<label for="test_description">Test description</label>
<textarea id="test_description" class="form-control" name="test_description" placeholder="This is my test for a hardware..."><%= test.description %></textarea>
</div>
</div>
<div class="row">
<div class="col-12">
<input class="btn btn-primary w-100" type="submit" value="Submit Changes">
</div>
</div>
</form>
</div>

View File

@@ -1,20 +1,20 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row mb-3">
<div class="col-12">
<h1>List of tests</h1>
</div>
<div class="cell small-12">
<div class="col-12">
<p>
<a href="/test/add">Add new test</a>
</p>
</div>
</div>
<div class="grid-x grid-margin-x">
<div class="row mb-3">
<% if tests.length > 0 %>
<div class="cell small-12">
<table>
<thead>
<div class="col-12">
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Test name</th>
<th># of benchmarks</th>
@@ -26,7 +26,7 @@
<% tests.each do |t| %>
<tr>
<td><a href="/test/<%= t.id %>"><%= t.name %></a></td>
<td><%= t.benchmark.length %></td>
<td><%= t.benchmarks.length %></td>
<td><%= date_format(t.created_at) %></td>
<td><%= date_format(t.updated_at) %></td>
</tr>
@@ -35,7 +35,7 @@
</table>
</div>
<% else %>
<div class="cell small-12">
<div class="col-12">
<p>I'm sorry, there doesn't appear to be any tests added yet. Check again later!</p>
</div>
<% end %>

View File

@@ -1,64 +1,56 @@
<div class="grid-x grid-margin-x">
<div class="cell small-12">
<div class="row">
<div class="col-12">
<h1><%= test.name %></h1>
</div>
<div class="cell small-12">
<div class="col-12">
<p><a href="/test/<%= test.id %>/edit">Edit</a></p>
</div>
<div class="cell small-12">
Hardware tested: <%= test.hardware.name %>
<div class="col-12">
<p>Hardware tested: <a href="/hardware/<%= test.hardware.id %>"><%= test.hardware.name %></a></p>
</div>
<div class="cell small-12">
<div class="col-12">
<p><%= test.description %></p>
</div>
</div>
<hr>
<div class="grix-x grix-margin-x">
<div class="cell small-12">
<form class="u-full-width" action="/result/add" method="post">
<div class="row">
<div class="col-12">
<form action="/result/add" method="post">
<input type="hidden" name="result_test" value="<%= test.id %>">
<input type="hidden" name="result_referrer" value="test">
<div class="grid-x grid-margin-x">
<div class="cell medium-5">
<label for="result_benchmark">
Add benchmark result:
<select class="u-full-width" id="result_benchmark" name="result_benchmark">
<% test.benchmark.each do |b| %>
<option value="<%= b.id %>"><%= b.name %></option>
<% end %>
</select>
</label>
<div class="row">
<div class="col-12 col-md-5 mb-3 mb-md-0">
<label for="result_benchmark">Add benchmark result:</label>
<select class="form-select" id="result_benchmark" name="result_benchmark">
<% test.benchmarks.each do |b| %>
<option value="<%= b.id %>"><%= b.name %></option>
<% end %>
</select>
</div>
<div class="cell medium-2">
<label for="result_average">
Average score:
<input type="text" id="result_average" name="result_average" value="">
</label>
<div class="col-4 col-md-2">
<label for="result_average">Average score:</label>
<input id="result_average" class="form-control" type="text" name="result_average" value="">
</div>
<div class="cell medium-2">
<label for="result_minimum">
Minimum score:
<input type="text" id="result_minimum" name="result_minimum" value="">
</label>
<div class="col-4 col-md-2">
<label for="result_minimum">Minimum score:</label>
<input id="result_minimum" class="form-control" type="text" name="result_minimum" value="">
</div>
<div class="cell medium-2">
<label for="result_maximum">
Maximum score:
<input type="text" id="result_maximum" name="result_maximum" value="">
</label>
<div class="col-4 col-md-2 mb-3 mb-md-0">
<label for="result_maximum">Maximum score:</label>
<input id="result_maximum" class="form-control" type="text" name="result_maximum" value="">
</div>
<div class="cell medium-1">
<input type="submit" class="u-full-width button" value="Submit">
<div class="col-12 col-md-1 d-flex align-items-stretch mb-3 mb-md-0">
<input class="btn btn-primary w-100" type="submit" value="Submit">
</div>
</div>
</form>
@@ -67,8 +59,8 @@
<h4>Benchmark results for this test:</h4>
<table id="results-table" data-test-id="<%= test.id %>">
<thead>
<table id="results-table" class="table table-hover table-responsive" data-test-id="<%= test.id %>">
<thead class="table-light">
<tr>
<th>Benchmark name</th>
<th>Scoring type</th>
@@ -79,7 +71,7 @@
</tr>
</thead>
<tbody>
<% test.benchmark.each do |benchmark| %>
<% test.benchmarks.each do |benchmark| %>
<tr data-benchmark-id="<%= benchmark.id %>"></tr>
<% end %>
</tbody>