91 Commits

Author SHA1 Message Date
gballan 39f95575da Version bump to v0.2.1
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
gballan 6e1ab89209 Updating version number in appinfo
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-13 14:57:19 -04:00
gballan 9cd6c78741 Adding missing step from Docker dev
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 14:54:02 -04:00
gballan 5b730df803 Adding build badge to README
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 14:52:34 -04:00
gballan 0ce4a3ecee Updated README
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 14:46:38 -04:00
gballan 12ece12394 Fixed some lints
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 14:13:26 -04:00
gballan 05c20b5811 Adding a model association test
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 11:06:55 -04:00
gballan bd822664b0 Added some model tests
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-13 09:57:04 -04:00
gballan 3f0efce0d8 Fixed a few lints; changed rake task name to test:lint
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 23:56:46 -04:00
gballan 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
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 23:47:38 -04:00
gballan 164eea1bde Updated Woodpecker config for 3.x
ci/woodpecker/manual/woodpecker Pipeline was successful
2025-08-12 21:59:59 -04:00
gballan 85fe3b0b38 Removing broken ruby test versions for now
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 21:55:03 -04:00
gballan 519955e57a Fixing unit tests
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-12 21:30:17 -04:00
gballan 96b746822c Fixing unit tests
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-12 21:27:17 -04:00
gballan eac833fd6d Adding db:migrate step to tests; adding two new versions of Ruby to test against
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-12 20:29:57 -04:00
gballan ad391c84a4 Fixing ruby 3.4 test
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 20:23:50 -04:00
gballan 641c9315bc Adding Ruby 3.4 testing to Woodpecker
ci/woodpecker/push/woodpecker Pipeline failed
2025-08-12 20:03:20 -04:00
gballan 3a136865b0 Added some more tests; changed URLs for model list pages and added redirects
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
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
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
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
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
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
ci/woodpecker/push/woodpecker Pipeline was successful
2025-08-12 12:19:25 -04:00
gballan 0a1037e79a Modified the front-end to display averaged results up to two decimals
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
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-31 14:07:58 -04:00
Gregory Ballantine ec2bf45a6e Fixed the test edit page
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-31 14:06:08 -04:00
gballan 57163b10e4 The report test selection resets when you change the benchmark
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-30 23:29:53 -04:00
gballan 25d394627d Version bump to v0.1.1
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/tag/woodpecker Pipeline was successful
2025-07-28 11:01:35 -04:00
gballan 85dfdb163a Fixed rubocop warnings
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-28 10:57:40 -04:00
gballan b593ef7593 Updating to ruby:3.4 in CI/CD
ci/woodpecker/push/woodpecker Pipeline was successful
2025-07-28 10:54:53 -04:00
gballan 26698082f4 Removed backup copy of the reports.coffee script
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-28 10:49:09 -04:00
gballan 42a0b95015 Fixed the chart generation page with the new ChartJS version and DB schema
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-28 10:48:49 -04:00
gballan 5cc3b8f824 Added a better way of setting/reading the app version; fixed some of the flexbox sizing
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-28 10:23:51 -04:00
gballan d59c75281e Updating puma.rb to allow changing the rack environment dynamically
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-28 10:17:45 -04:00
gballan 55e4f397f8 Fixed the test edit view
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-26 11:37:43 -04:00
gballan 49d1276031 Fixed puma config for new Puma versions 2025-07-26 11:32:32 -04:00
gballan bc70fb8dd0 Added config/puma.rb for production deployment
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-26 11:16:07 -04:00
gballan 0231ebad2d Made some more style changes
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-26 01:08:35 -04:00
gballan 541b5236f0 Finished transition to Bootstrap 5
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-26 00:55:52 -04:00
Gregory Ballantine 6215cecb53 Cleaned up app version output
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
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
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-25 13:41:51 -04:00
gballan bc4cb181c3 Added some styles
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-23 23:54:12 -04:00
gballan 822f49bcc2 fixed display of tests on the dashboard 2025-07-23 23:26:22 -04:00
gballan aec77628f7 Small updates
ci/woodpecker/push/woodpecker Pipeline failed
2025-07-23 23:25:00 -04:00
gballan ecb696372d Added dockerfile to build assets via Gulp; updated docker scripts 2025-07-23 23:24:49 -04:00
gballan 31b8404be1 Swapping out Grunt for Gulp 2025-06-22 19:30:17 -04:00
gballan 8cd47d7e70 Added API routes to provide some information for JS; added CoffeeScript to grab benchmark results for a test 2025-06-19 00:38:36 -04:00
gballan d17b66920a Fixed typo in watch task for CoffeeScript files 2025-06-18 16:44:10 -04:00
gballan 47cb580393 Started adding test model/routes/views; removed a bit of the old paradigm with tying results directly to hardware 2025-06-10 17:35:52 -04:00
gballan 883019b181 Fixing server:dev Rake command to actuualy reload the app 2025-06-09 23:27:48 -04:00
gballan 2cc80e239b Added TTY flag to docker-run.bat 2025-05-30 09:38:20 -04:00
gballan ed085e4b40 Added abilities to view and edit benchmark info 2025-05-30 01:45:49 -04:00
gballan cf9235cb18 Added ability to edit hardware 2025-05-30 01:30:36 -04:00
gballan 28b6cc66bb Updating dependencies to newer versions; Updating to Ruby 3.4 2025-05-30 01:20:43 -04:00
gballan e3a334546f Transitioned to using SASS and CoffeeScript for assets; using Grunt.js for compiling
ci/woodpecker/push/woodpecker Pipeline was successful
2024-06-12 22:19:46 -04:00
gballan d899ffe58a Adding bundle config to project
ci/woodpecker/push/woodpecker Pipeline was successful
2024-06-12 19:58:08 -04:00
gballan e6853a52d7 Fixed some alignment issues on the benchmark add page
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-26 15:53:13 -05:00
gballan 6d26eea419 Added shell scripts for managing the docker container
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-26 00:10:42 -05:00
gballan 778bd25ef2 Fixed awful FirstHashElementIndentation setting
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 15:45:16 -05:00
gballan 6aca95d832 Added rudimentary chart generation for benchmarks
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 15:39:49 -05:00
gballan 357bb69257 Fixed linter errors
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 13:59:44 -05:00
gballan 1e0cd49cf8 Added ability to add benchmark results from hardware page
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 13:57:01 -05:00
gballan 5628c69d66 Fix style linting
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 10:05:10 -05:00
gballan e6333b855f Added form to hardware view page for adding test results
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 10:03:27 -05:00
gballan f7979cd005 Removed more references to test
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 08:42:49 -05:00
gballan ef2e0a12a5 Removed Test object to simplify database schema; updated docker scripts to run database migrations before starting the server if the database doesn't exist
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 08:40:34 -05:00
gballan c83d517437 Updating Gem versions
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 07:19:30 -05:00
gballan 8dee8b1f40 Added docker support for development environment
ci/woodpecker/push/woodpecker Pipeline was successful
2024-02-09 07:17:14 -05:00
gballan 175ce24b27 Started work to redo the UI for database changes
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-21 01:29:59 -05:00
gballan 516f125ea7 Fixed a lot of linter warnings
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-11 13:08:46 -05:00
gballan 34741691ed Fixed Rubocop all
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-08 15:21:11 -05:00
Gregory Ballantine fa9245d7ec Added rubocop for style checking; added woodpecker CI config
ci/woodpecker/manual/woodpecker Pipeline was successful
2023-12-08 12:17:13 -05:00
gballan 1a363a5862 Improved the db:migrate function in the Rakefile to utilize the Sequel API for running migrations, and to allow downgrading to a specific migration easily 2023-12-07 13:04:02 -05:00
gballan b819f4f1cd Reworked database schema from scratch 2023-12-07 12:59:32 -05:00
gballan 394a8ef91f Modified the Rakefile to make the rerun command a lot better 2023-11-29 22:01:44 -05:00
gballan c940a248d7 [Issue #5] - Reworked app to better organize results with their corresponding tests 2023-09-21 20:41:25 -06:00
gballan 269587e6c9 Updated README 2023-09-20 23:04:05 -06:00
gballan 3c9e6eab6b Removed unnecessary score formatting from result add 2023-09-08 07:15:54 -06:00
gballan a1a00d9fe5 Added functionality for better tracking of the minimum and maximum fps 2023-09-08 00:36:40 -06:00
gballan 7e38154bc1 Fixed the table on the dashboard 2023-09-03 09:26:09 -04:00
gballan f0115847b8 Fixed a couple of things missing from result displays 2023-09-03 09:02:31 -04:00
gballan b8bec03357 Removed the setup rake task to run bundler and replaced it with a shell script 2023-07-06 11:49:48 -04:00
gballan 7655b75410 Added functionality to add a benchmark result 2023-07-05 23:39:08 -04:00
gballan 691e2e9b1e Added module for helper functions; added helper for formatting date consistently 2023-07-05 22:27:07 -04:00
gballan 291db231d5 Added functionality to add new hardware and benchmarks to tie to results (this may be moved to an admin panel later) 2023-07-05 22:08:37 -04:00
gballan 6c1c8bca0a Added Sequel ORM for database handling; added navbar 2023-07-05 18:04:31 -04:00
gballan 6f680e72e5 Added some more scaffolding to the project to get going; added Foundation css to start with styles 2023-07-03 23:02:25 -04:00
gballan b376bf3a79 Added started adding some Sinatra app stuff 2023-07-02 23:42:24 -04:00
77 changed files with 5504 additions and 3 deletions
+2
View File
@@ -0,0 +1,2 @@
---
BUNDLE_PATH: "vendor/bundle"
+12
View File
@@ -56,3 +56,15 @@ build-iPhoneSimulator/
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
# .rubocop-https?--*
# Ignore local data
data/
# Node modules
node_modules/
# Compiled assets
public/css/
public/js/
# Ignore production configuration files to protect against credential leaks
config/production.yaml
+25
View File
@@ -0,0 +1,25 @@
plugins:
- rubocop-rspec
- rubocop-sequel
AllCops:
NewCops: enable
Layout/EmptyLinesAroundClassBody:
EnforcedStyle: 'empty_lines_except_namespace'
Layout/EmptyLinesAroundModuleBody:
EnforcedStyle: 'empty_lines_except_namespace'
Layout/FirstHashElementIndentation:
EnforcedStyle: 'consistent'
Metrics/ClassLength:
Max: 150
Style/ClassVars:
Enabled: false
Style/GlobalVars:
Enabled: false
Style/MethodCallWithoutArgsParentheses:
Enabled: false
Style/MethodCallWithArgsParentheses:
Enabled: true
Style/RedundantReturn:
Enabled: false
+40
View File
@@ -0,0 +1,40 @@
steps:
setup:
image: ruby:3.4
env:
RACK_ENV: testing
commands:
- 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
settings:
api_key:
from_secret: gitea_api_key
base_url: https://git.metaunix.net
title: "${CI_COMMIT_TAG}"
when:
event:
- tag
+16
View File
@@ -0,0 +1,16 @@
FROM ruby:3.4
RUN gem install bundler
WORKDIR /usr/src/game-data
COPY Gemfile Gemfile.l*ck ./
RUN bundle check || bundle install
RUN gem install rake
COPY . ./
ENTRYPOINT ["bash", "entrypoints/dev.sh"]
+13
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" ]
+26
View File
@@ -0,0 +1,26 @@
source 'https://rubygems.org'
gem 'sinatra', '~> 4.1'
gem 'sinatra-contrib', '~> 4.1'
gem 'puma', '~> 6.6'
gem 'sequel', '~> 5.92'
gem 'sqlite3', '~> 2.6'
gem 'logger'
group :development, :test do
gem 'rerun'
gem 'wdm', '>= 0.1.0' if Gem.win_platform?
# 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
+151
View File
@@ -0,0 +1,151 @@
GEM
remote: https://rubygems.org/
specs:
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)
ffi (1.17.2-arm-linux-musl)
ffi (1.17.2-arm64-darwin)
ffi (1.17.2-x86-linux-gnu)
ffi (1.17.2-x86-linux-musl)
ffi (1.17.2-x86_64-darwin)
ffi (1.17.2-x86_64-linux-gnu)
ffi (1.17.2-x86_64-linux-musl)
json (2.12.2)
language_server-protocol (3.17.0.5)
lint_roller (1.1.0)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.7.0)
multi_json (1.15.0)
mustermann (3.0.3)
ruby2_keywords (~> 0.0.1)
nio4r (2.7.4)
parallel (1.27.0)
parser (3.3.8.0)
ast (~> 2.4.1)
racc
prism (1.4.0)
puma (6.6.0)
nio4r (~> 2.0)
racc (1.8.1)
rack (3.2.0)
rack-protection (4.1.1)
base64 (>= 0.1.0)
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)
ffi (~> 1.0)
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)
lint_roller (~> 1.1.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.45.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 4.0)
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)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
sequel (5.93.0)
bigdecimal
sinatra (4.1.1)
logger (>= 1.6.0)
mustermann (~> 3.0)
rack (>= 3.0.0, < 4)
rack-protection (= 4.1.1)
rack-session (>= 2.0.0, < 3)
tilt (~> 2.0)
sinatra-contrib (4.1.1)
multi_json (>= 0.0.2)
mustermann (~> 3.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)
sqlite3 (2.7.0-arm-linux-gnu)
sqlite3 (2.7.0-arm-linux-musl)
sqlite3 (2.7.0-arm64-darwin)
sqlite3 (2.7.0-x86-linux-gnu)
sqlite3 (2.7.0-x86-linux-musl)
sqlite3 (2.7.0-x86_64-darwin)
sqlite3 (2.7.0-x86_64-linux-gnu)
sqlite3 (2.7.0-x86_64-linux-musl)
tilt (2.6.0)
unicode-display_width (3.1.4)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
PLATFORMS
aarch64-linux-gnu
aarch64-linux-musl
arm-linux-gnu
arm-linux-musl
arm64-darwin
x86-linux-gnu
x86-linux-musl
x86_64-darwin
x86_64-linux-gnu
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 (~> 4.1)
sinatra-contrib (~> 4.1)
sqlite3 (~> 2.6)
BUNDLED WITH
2.6.9
+1 -1
View File
@@ -1,4 +1,4 @@
Copyright (c) <year> <owner>
Copyright (c) 2023 Bit Goblin
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+65 -2
View File
@@ -1,3 +1,66 @@
# game-data
# Game Data
Web-based tool to store and organize PC hardware gaming benchmarks
![Build badge](https://builds.metaunix.net/api/badges/84/status.svg)
Web-based tool to store and organize PC hardware gaming 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
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
### 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`
2. Perform database migrations:
`rake db:migrate`
3. Run the server in development with auto-reloading:
`rake server:dev`
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.
+39
View File
@@ -0,0 +1,39 @@
require 'bundler/setup'
namespace :db do
desc 'Run migrations'
task :migrate, [:version] do |t, args|
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(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['RACK_ENV'] = 'production'
system("puma")
end
task :dev do
system('rerun --no-notify --dir="src/" puma')
end
end
namespace :test do
task :unit do
ENV['RACK_ENV'] = 'testing'
system("rspec")
end
task :lint do
system("rubocop src/ spec/")
end
end
+21
View File
@@ -0,0 +1,21 @@
$ ->
# run foundation scripts
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,
}
+109
View File
@@ -0,0 +1,109 @@
$ ->
chartInstance = null
$('#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')
+44
View File
@@ -0,0 +1,44 @@
testId = $('#results-table').data('test-id')
fetchTestBenchmarkResults = (testId, benchmarkId) ->
try
benchmarkSearchParams = new URLSearchParams
benchmark_id: benchmarkId
benchmarkRes = await fetch("/api/v1/benchmark/details?#{benchmarkSearchParams}")
benchmarkData = await benchmarkRes.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)
tableRow = $("#results-table tr[data-benchmark-id=#{benchmarkId}]")
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>' + 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>')
tableRow.append('<td>N/a</td>')
tableRow.append('<td>N/a</td>')
catch error
console.error 'An error occurred while fetching benchmark results.', error
$('#results-table tbody tr').each((index, tr) ->
benchmarkId = $(tr).data('benchmark-id')
console.log("Fetching results for benchmark id: " + benchmarkId)
fetchTestBenchmarkResults(testId, benchmarkId)
)
+21
View File
@@ -0,0 +1,21 @@
@use "sass:color"
//$primary-color: #3399ff
$primary-color: cornflowerblue
$primary-color-highlight: color.adjust($primary-color, $lightness: -10%)
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
+2
View File
@@ -0,0 +1,2 @@
docker build -t game-data -f Dockerfile.dev .
docker build -t game-data-gulp -f Dockerfile.gulp .
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
docker build -t game-data -f Dockerfile.dev .
docker build -t game-data-gulp -f Dockerfile.gulp .
+1
View File
@@ -0,0 +1 @@
docker exec game-data rake %*
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
docker exec game-data rake "$@"
+2
View File
@@ -0,0 +1,2 @@
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
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
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
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Configure bundler to use local vendor path
bundle config set --local path 'vendor/bundle'
# Install gems
bundle install
+5
View File
@@ -0,0 +1,5 @@
# Load application config
root = ::File.dirname(__FILE__)
require ::File.join( root, 'src', 'server' )
run GameData.new
+6
View File
@@ -0,0 +1,6 @@
server:
host: '0.0.0.0'
port: '9292'
testing:
minimum_results_required: 3
+3
View File
@@ -0,0 +1,3 @@
database:
adapter: 'sqlite'
database: 'data/gamedata.db'
+11
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
View File
@@ -0,0 +1,3 @@
database:
adapter: 'sqlite'
database: 'data/gamedata_testing.db'
View File
+28
View File
@@ -0,0 +1,28 @@
Sequel.migration do
up do
create_table(:hardware) do
primary_key :id
String :name, null: false
String :type, null: false
String :description, text: true
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
create_table(:benchmarks) do
primary_key :id
String :name, null: false
String :description, text: true
String :scoring, null: false
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
end
down do
drop_table(:hardware)
drop_table(:benchmarks)
end
end
+21
View File
@@ -0,0 +1,21 @@
Sequel.migration do
up do
# create tests table
create_table(:results) do
primary_key :id
foreign_key :hardware_id, :hardware
foreign_key :benchmark_id, :benchmarks
Float :avg_score, null: false
Float :min_score
Float :max_score
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
end
down do
drop_table(:results)
end
end
+39
View File
@@ -0,0 +1,39 @@
Sequel.migration do
up do
# create tests table
create_table(:tests) do
primary_key :id
foreign_key :hardware_id, :hardware
String :name, null: false
String :description
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
# create many-to-many table between tests and benchmarks
create_table(:benchmarks_tests) do
primary_key :id
foreign_key :benchmark_id, :benchmarks
foreign_key :test_id, :tests
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
# modify results table to integrate with the new tests table
alter_table(:results) do
drop_foreign_key :hardware_id
add_foreign_key :test_id, :tests
end
end
down do
alter_table(:results) do
drop_foreign_key :test_id
add_foreign_key :hardware_id, :hardware
end
drop_table(:benchmarks_tests)
drop_table(:tests)
end
end
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
if [ ! -f ./data/gamedata.db ]; then
rake db:migrate
fi
rake server:dev
+50
View File
@@ -0,0 +1,50 @@
const gulp = require('gulp');
const sass = require('gulp-sass')(require('sass'));
const coffee = require('gulp-coffee');
const sourcemaps = require('gulp-sourcemaps');
const plumber = require('gulp-plumber');
// Compile .sass files to compressed CSS
function compileSass() {
return gulp.src('assets/styles/**/*.sass')
.pipe(plumber())
.pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError))
.pipe(gulp.dest('public/css'));
}
// Compile .coffee files to JavaScript with source maps
function compileCoffee() {
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(cb) {
gulp.watch('assets/styles/**/*.sass', compileSass);
gulp.watch('assets/scripts/**/*.coffee', compileCoffee);
cb();
}
// 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 = buildAssets();
// start builds with file watching
exports.watch = buildAndWatch();
// Define default task (defaults to watch)
exports.default = exports.watch;
+3159
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -0,0 +1,28 @@
{
"name": "game-data",
"description": "PC hardware benchmarking data logger",
"version": "0.1.0",
"main": "src/server.rb",
"scripts": {
"gulp": "gulp"
},
"repository": {
"type": "git",
"url": "https://git.metaunix.net/BitGoblin/game-data"
},
"keywords": [
"pc",
"hardware",
"benchmarking"
],
"author": "Gregory Ballantine <gballantine@bitgoblin.tech>",
"license": "BSD-2-Clause",
"devDependencies": {
"gulp": "^5.0.1",
"gulp-coffee": "^3.0.3",
"gulp-plumber": "^1.2.1",
"gulp-sass": "^6.0.1",
"gulp-sourcemaps": "^3.0.0",
"sass": "^1.89.2"
}
}
@@ -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
@@ -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
+21
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
@@ -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
+44
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
+27
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
+25
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
+36
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
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
View File
@@ -0,0 +1,7 @@
# frozen_string_literal: true
module AppInfo
VERSION = '0.2.1'
end
+29
View File
@@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'yaml'
# Config - loads and manages the app's configuration
class Config
DEFAULT_CONFIG = 'config/defaults.yaml'
ENVIRONMENT_CONFIG = ENV.fetch('RACK_ENV', 'development')
def initialize(config_path = "config/#{ENVIRONMENT_CONFIG}.yaml")
@data = YAML.load_file(DEFAULT_CONFIG)
# merge in user-defined configuration if it exists
@data.merge!(YAML.load_file(config_path)) if File.exist?(config_path)
end
def get(key)
bits = key.split('.')
value = @data
bits.each do |bit|
value = value[bit]
end
return value
end
end
+38
View File
@@ -0,0 +1,38 @@
# 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 APIv1Controller < BaseController
get '/api/v1/benchmark/details' do
benchmark_id = params[:benchmark_id]
benchmark = Benchmark.where(id: benchmark_id).first()
json benchmark.values()
end
get '/api/v1/result/list' do
test_id = params[:test_id]
benchmark_id = params[:benchmark_id]
results = Result.where(test_id: test_id, benchmark_id: benchmark_id).all()
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
+15
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
+65
View File
@@ -0,0 +1,65 @@
# frozen_string_literal: true
require_relative 'base_controller'
require_relative '../models/benchmark'
# /benchmark routes
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: {
title: 'List of Benchmarks',
benchmarks: benchmarks
}
end
get '/benchmark/add' do
erb :'benchmark/add', locals: {
title: 'Add Benchmark'
}
end
post '/benchmark/add' do
benchmark = Benchmark.create(
name: params[:benchmark_name],
scoring: params[:benchmark_scoring],
description: params[:benchmark_description]
)
redirect "/benchmark/#{benchmark.id}"
end
get '/benchmark/:benchmark_id' do
benchmark = Benchmark.where(id: params[:benchmark_id]).first()
erb :'benchmark/view', locals: {
title: benchmark.name,
benchmark: benchmark
}
end
get '/benchmark/:benchmark_id/edit' do
benchmark = Benchmark.where(id: params[:benchmark_id]).first()
erb :'benchmark/edit', locals: {
title: "Editing: #{benchmark.name}",
benchmark: benchmark
}
end
post '/benchmark/:benchmark_id/edit' do
benchmark = Benchmark.where(id: params[:benchmark_id]).first()
benchmark.update(
name: params[:benchmark_name],
scoring: params[:benchmark_scoring],
description: params[:benchmark_description]
)
redirect "/benchmark/#{benchmark.id}"
end
end
+66
View File
@@ -0,0 +1,66 @@
# frozen_string_literal: true
require_relative 'base_controller'
require_relative '../models/hardware'
require_relative '../models/benchmark'
# /hardware routes
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: {
title: 'List of Hardware',
hardware: hardware
}
end
get '/hardware/add' do
erb :'hardware/add', locals: {
title: 'Add Hardware'
}
end
post '/hardware/add' do
hardware = Hardware.create(
name: params[:hardware_name],
type: params[:hardware_type]
)
redirect "/hardware/#{hardware.id}"
end
get '/hardware/:hardware_id' do
benchmarks = Benchmark.order(:name).all()
hardware = Hardware.where(id: params[:hardware_id]).first()
erb :'hardware/view', locals: {
title: hardware.name,
hardware: hardware,
benchmarks: benchmarks
}
end
get '/hardware/:hardware_id/edit' do
hardware = Hardware.where(id: params[:hardware_id]).first()
erb :'hardware/edit', locals: {
title: "Editing: #{hardware.name}",
hardware: hardware
}
end
post '/hardware/: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
+18
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
+53
View File
@@ -0,0 +1,53 @@
# frozen_string_literal: true
require 'sinatra/json'
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()
tests = Test.order(:name).all()
erb :'reports/index', locals: {
title: 'Generate Reports',
tests: tests,
benchmarks: benchmarks
}
end
post '/report' do
report_type = params[:type]
report_choice = params[:choice]
report_compare = params[:compare]
if report_type == 'benchmark'
choice = Benchmark.where(id: report_choice).first().name
names = []
avg_results = []
min_results = []
report_compare.each do |c|
tst = Test.where(id: c).first()
names.push(tst.name)
res = Result.where(benchmark_id: report_choice, test_id: c).first()
avg_results.push(res.avg_score)
min_results.push(res.min_score)
end
json({
choice: choice,
names: names,
avg_results: avg_results,
min_results: min_results
})
end
end
end
+32
View File
@@ -0,0 +1,32 @@
# frozen_string_literal: true
require_relative 'base_controller'
require_relative '../models/result'
# /result routes
class ResultController < BaseController
post '/result/add' do
result_minimum = params[:result_minimum] if params.key?(:result_minimum)
result_maximum = params[:result_maximum] if params.key?(:result_maximum)
Result.create(
test_id: params[:result_test],
benchmark_id: params[:result_benchmark],
avg_score: params[:result_average],
min_score: result_minimum,
max_score: result_maximum
)
if params.key?(:result_referrer)
if params[:result_referrer] == 'test'
redirect "/test/#{params[:result_test]}"
elsif params[:result_referrer] == 'benchmark'
redirect "/benchmark/#{params[:result_benchmark]}"
end
else
redirect '/result'
end
end
end
+96
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
+31
View File
@@ -0,0 +1,31 @@
# 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')
end
def average_array(arr, decimals = 1)
sum = 0
arr.each do |i|
sum += i
end
return (sum / arr.length).round(decimals)
end
end
+9
View File
@@ -0,0 +1,9 @@
# frozen_string_literal: true
# Benchmark - database model for PC benchmarks
class Benchmark < Sequel::Model
many_to_many :tests
one_to_many :results
end
+20
View File
@@ -0,0 +1,20 @@
# frozen_string_literal: true
# Hardware - database model for PC hardware
class Hardware < Sequel::Model(:hardware)
one_to_many :tests
def bench_results
br = {}
results.each do |r|
br[r.benchmark.name.to_s] = [] unless br.key?(r.benchmark.name)
br[r.benchmark.name.to_s].push(r.avg_score)
end
return br
end
end
+13
View File
@@ -0,0 +1,13 @@
# frozen_string_literal: true
# Result - database model for benchmark results
class Result < Sequel::Model
many_to_one :test
many_to_one :benchmark
def formatted_score
return @avg_score
end
end
+14
View File
@@ -0,0 +1,14 @@
# frozen_string_literal: true
# Test - database model for hardware tests
class Test < Sequel::Model
one_to_many :result
many_to_one :hardware
many_to_many :benchmarks
def benchmark?(benchmark_id)
return benchmarks_dataset.where(Sequel[:benchmarks][:id] => benchmark_id).any?
end
end
+46
View File
@@ -0,0 +1,46 @@
# frozen_string_literal: true
require 'sinatra/base'
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'))
# 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
# Set up static file serving
enable :static
set :public_folder, File.join(__dir__, '/../public')
use IndexController
use HardwareController
use BenchmarkController
use TestController
use ResultController
use ReportsController
use APIv1Controller
end
+40
View File
@@ -0,0 +1,40 @@
<div class="row mb-4">
<div class="col-12">
<h1>Add new benchmark</h1>
</div>
</div>
<div class="row">
<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="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="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="row">
<div class="col-12">
<input class="btn btn-primary w-100" type="submit" value="Create Benchmark">
</div>
</div>
</form>
</div>
+40
View File
@@ -0,0 +1,40 @@
<div class="row mb-4">
<div class="col-12">
<h1>Editing: <%= benchmark.name %></h1>
</div>
</div>
<div class="row">
<form class="cell small-12" action="/benchmark/<%= benchmark.id %>/edit" 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" value="<%= benchmark.name %>">
</div>
<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="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="row">
<div class="col-12">
<input class="btn btn-primary w-100" type="submit" value="Submit Changes">
</div>
</div>
</form>
</div>
+40
View File
@@ -0,0 +1,40 @@
<div class="row mb-3">
<div class="col-12">
<h1>List of benchmarks</h1>
</div>
<div class="col-12">
<p>
<a href="/benchmark/add">Add new benchmark</a>
</p>
</div>
</div>
<div class="row">
<% if benchmarks.length > 0 %>
<div class="col-12">
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Benchmark name</th>
<th>Scoring type</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<% benchmarks.each do |b| %>
<tr>
<td><a href="/benchmark/<%= b.id %>"><%= b.name %></a></td>
<td><%= b.scoring %></td>
<td><%= b.description %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="col-12">
<p>I'm sorry, there doesn't appear to be any benchmarks added yet. Check again later!</p>
</div>
<% end %>
</div>
+49
View File
@@ -0,0 +1,49 @@
<div class="row">
<div class="col-12">
<h1><%= benchmark.name %></h1>
</div>
<div class="col-12">
<p><a href="/benchmark/<%= benchmark.id %>/edit">Edit</a></p>
</div>
<div class="col-12">
Benchmark scoring type: <%= benchmark.scoring %>
</div>
<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>
+32
View File
@@ -0,0 +1,32 @@
<div class="row mb-3">
<div class="col-12">
<h1>Add new hardware</h1>
</div>
</div>
<div class="row mb-3">
<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="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>
<div class="row">
<div class="col-12">
<input class="btn btn-primary w-100" type="submit" value="Create Hardware">
</div>
</div>
</form>
</div>
+32
View File
@@ -0,0 +1,32 @@
<div class="row mb-3">
<div class="col-12">
<h1>Add new hardware</h1>
</div>
</div>
<div class="row mb-3">
<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="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>
</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>
+42
View File
@@ -0,0 +1,42 @@
<div class="row mb-3">
<div class="col-12">
<h1>List of hardware</h1>
</div>
<div class="col-12">
<p>
<a href="/hardware/add">Add new hardware</a>
</p>
</div>
</div>
<div class="row mb-3">
<% if hardware.length > 0 %>
<div class="col-12">
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Hardware name</th>
<th>Type</th>
<th>Date added</th>
<th>Date modified</th>
</tr>
</thead>
<tbody>
<% hardware.each do |h| %>
<tr>
<td><a href="/hardware/<%= h.id %>"><%= h.name %></a></td>
<td><%= h.type %></td>
<td><%= date_format(h.created_at) %></td>
<td><%= date_format(h.updated_at) %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="col-12">
<p>I'm sorry, there doesn't appear to be any hardware added yet. Check again later!</p>
</div>
<% end %>
</div>
+44
View File
@@ -0,0 +1,44 @@
<div class="row">
<div class="col-12">
<h1><%= hardware.name %></h1>
</div>
<div class="col-12">
<p><a href="/hardware/<%= hardware.id %>/edit">Edit</a></p>
</div>
<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>
+32
View File
@@ -0,0 +1,32 @@
<div class="row">
<% if tests.length > 0 %>
<div class="12-col mb-3">
<h2>Latest benchmark results:</h2>
</div>
<div class="12-col">
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Test name</th>
<th># Benchmarks</th>
<th>Last Updated</th>
</tr>
</thead>
<tbody>
<% 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>
</div>
<% else %>
<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 %>
</div>
+28
View File
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<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/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.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>
</head>
<body class="d-flex flex-column min-vh-100">
<!-- main navigation -->
<%= erb :'partials/navbar', :locals => locals %>
<!-- main content -->
<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
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>
+29
View File
@@ -0,0 +1,29 @@
<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>
+49
View File
@@ -0,0 +1,49 @@
<div class="row">
<div class="col-12">
<h1>Generate report</h1>
</div>
</div>
<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="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>
<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>
+49
View File
@@ -0,0 +1,49 @@
<div class="row mb-3">
<div class="col-12">
<h1>Add new test</h1>
</div>
</div>
<div class="row">
<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="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>
<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>
+49
View File
@@ -0,0 +1,49 @@
<div class="row mb-3">
<div class="col-12">
<h1>Editing: <%= test.name %></h1>
</div>
</div>
<div class="row">
<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="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>
<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>
+42
View File
@@ -0,0 +1,42 @@
<div class="row mb-3">
<div class="col-12">
<h1>List of tests</h1>
</div>
<div class="col-12">
<p>
<a href="/test/add">Add new test</a>
</p>
</div>
</div>
<div class="row mb-3">
<% if tests.length > 0 %>
<div class="col-12">
<table class="table table-hover table-responsive">
<thead class="table-light">
<tr>
<th>Test name</th>
<th># of benchmarks</th>
<th>Date added</th>
<th>Date modified</th>
</tr>
</thead>
<tbody>
<% tests.each do |t| %>
<tr>
<td><a href="/test/<%= t.id %>"><%= t.name %></a></td>
<td><%= t.benchmarks.length %></td>
<td><%= date_format(t.created_at) %></td>
<td><%= date_format(t.updated_at) %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<div class="col-12">
<p>I'm sorry, there doesn't appear to be any tests added yet. Check again later!</p>
</div>
<% end %>
</div>
+83
View File
@@ -0,0 +1,83 @@
<div class="row">
<div class="col-12">
<h1><%= test.name %></h1>
</div>
<div class="col-12">
<p><a href="/test/<%= test.id %>/edit">Edit</a></p>
</div>
<div class="col-12">
<p>Hardware tested: <a href="/hardware/<%= test.hardware.id %>"><%= test.hardware.name %></a></p>
</div>
<div class="col-12">
<p><%= test.description %></p>
</div>
</div>
<hr>
<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="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="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="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="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="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>
<hr>
<h4>Benchmark results for this test:</h4>
<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>
<th># results</th>
<th>Average</th>
<th>Minimum</th>
<th>Maximum</th>
</tr>
</thead>
<tbody>
<% test.benchmarks.each do |benchmark| %>
<tr data-benchmark-id="<%= benchmark.id %>"></tr>
<% end %>
</tbody>
</table>
</div>
</div>
<!-- load test data fetching functionality -->
<script src="/js/test.js" charset="utf-8"></script>