Compare commits

..

23 Commits
go ... main

Author SHA1 Message Date
385dd98dbe Migrated app to modular Sinatra application; copied in a lot of improvements for a Sinatra app that I learned from Stage Manager 2023-03-14 20:49:42 -04:00
2340dddd06 Added better mobile navigation 2023-01-17 02:15:00 -05:00
57c5fb30c6 Added ability to delete IP addresses 2022-12-22 13:08:35 -05:00
2b6a222bcf Added functions to set/read cookies; added ability to make the collapsed nav sticky across refreshes 2022-12-22 13:03:08 -05:00
10345d5238 Added basic IP tracker functionality 2022-12-22 12:50:01 -05:00
97ced7d80e Styled the navbar; allowed collapsing it to give the user more screen space 2022-12-18 23:31:43 -05:00
5139d8b492 Updated search page to consolidate results into one list 2022-12-17 23:43:26 -05:00
421539f38c Added an inventory search page 2022-12-11 22:27:45 -05:00
4c9d46f875 Revamped the home page a bit 2022-12-11 21:29:18 -05:00
f3e970ab1b Moved the navbar into a partial 2022-12-10 01:10:28 -05:00
4d76c0c072 Separated routes out into separate namespaces 2022-12-10 00:32:23 -05:00
c8764db47b Fixed some migrations that were mistyped; updated the Rakefile to include bundler setup for gems 2022-12-09 21:19:53 -05:00
7a81e2c57e Added ability to edit and delete items and licenses 2022-12-09 13:44:43 -05:00
db6bfe8e7a Added ability to comment on license records; updated item list view to link to the item display pages 2022-12-09 11:42:12 -05:00
36f7fb82a6 Added License model and views 2022-12-08 17:26:44 -05:00
f1aca6e318 Added Rakefile to project for handling common tasks 2022-12-08 11:00:19 -05:00
12045c684e Added new ItemComment model 2022-12-08 01:32:41 -05:00
11d33e394b Added item view page; added some helpers for simplifying view logic 2022-12-08 01:12:31 -05:00
dd136d19c9 Added some item views 2022-12-07 20:12:23 -05:00
1736e7e457 Added some styles; added a basic main navigation side bar 2022-12-07 18:55:12 -05:00
156759d1cd Added SCSS stylesheets 2022-12-07 18:28:24 -05:00
ee0726c271 Added rerun gem to auto-reload the app 2022-12-07 18:21:46 -05:00
6dd3e4c7d6 Initial Sinatra project strucutre 2022-12-07 17:52:27 -05:00
60 changed files with 1543 additions and 304 deletions

67
.gitignore vendored
View File

@ -1,15 +1,70 @@
# Compiled binary # ---> Ruby
raven *.gem
*.rbc
/.config
/coverage/
/InstalledFiles
/pkg/
/spec/reports/
/spec/examples.txt
/test/tmp/
/test/version_tmp/
/tmp/
# Vendored dependencies # Used by dotenv library to load environment variables.
vendor/ # .env
# Ignore Byebug command history file.
.byebug_history
## Specific to RubyMotion:
.dat*
.repl_history
build/
*.bridgesupport
build-iPhoneOS/
build-iPhoneSimulator/
## Specific to RubyMotion (use of CocoaPods):
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# vendor/Pods/
## Documentation cache and generated files:
/.yardoc/
/_yardoc/
/doc/
/rdoc/
## Environment normalization:
/.bundle/
/vendor/bundle
/lib/bundler/man/
# for a library or gem, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# Gemfile.lock
# .ruby-version
# .ruby-gemset
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
.rvmrc
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
# .rubocop-https?--*
# Local database storage # Local database storage
data/raven.db data/raven.db
# Node.js modules for Grunt.js # Local configuration
config/raven.yaml
# Node modules for Grunt.js
node_modules/ node_modules/
# Compiled CSS and JS assets # Compiled CSS and JS
public/css/ public/css/
public/js/ public/js/

13
Gemfile Normal file
View File

@ -0,0 +1,13 @@
source 'https://rubygems.org'
gem 'sinatra', '~> 3.0'
gem 'sinatra-contrib', '~> 3.0'
gem 'puma', '~> 6.0'
gem 'sequel', '~> 5.63'
gem 'sqlite3', '~> 1.5'
# Use rerun gem to auto-reload app
gem 'guard-rack'
gem 'wdm', '>= 0.1.0' if Gem.win_platform?

79
Gemfile.lock Normal file
View File

@ -0,0 +1,79 @@
GEM
remote: https://rubygems.org/
specs:
coderay (1.1.3)
ffi (1.15.5)
ffi (1.15.5-x64-mingw-ucrt)
formatador (1.1.0)
guard (2.18.0)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
pry (>= 0.13.0)
shellany (~> 0.0)
thor (>= 0.18.1)
guard-rack (2.2.1)
ffi
guard (~> 2.3)
spoon
listen (3.8.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
lumberjack (1.2.8)
method_source (1.0.0)
multi_json (1.15.0)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
nenv (0.3.0)
nio4r (2.5.8)
notiffany (0.1.3)
nenv (~> 0.1)
shellany (~> 0.0)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
puma (6.0.0)
nio4r (~> 2.0)
rack (2.2.4)
rack-protection (3.0.4)
rack
rb-fsevent (0.11.2)
rb-inotify (0.10.1)
ffi (~> 1.0)
ruby2_keywords (0.0.5)
sequel (5.63.0)
shellany (0.0.1)
sinatra (3.0.4)
mustermann (~> 3.0)
rack (~> 2.2, >= 2.2.4)
rack-protection (= 3.0.4)
tilt (~> 2.0)
sinatra-contrib (3.0.4)
multi_json
mustermann (~> 3.0)
rack-protection (= 3.0.4)
sinatra (= 3.0.4)
tilt (~> 2.0)
spoon (0.0.6)
ffi
sqlite3 (1.5.4-x64-mingw-ucrt)
sqlite3 (1.5.4-x86_64-linux)
thor (1.2.1)
tilt (2.0.11)
PLATFORMS
x64-mingw-ucrt
x86_64-linux
DEPENDENCIES
guard-rack
puma (~> 6.0)
sequel (~> 5.63)
sinatra (~> 3.0)
sinatra-contrib (~> 3.0)
sqlite3 (~> 1.5)
BUNDLED WITH
2.3.7

View File

@ -19,6 +19,21 @@ module.exports = function(grunt) {
} }
}, },
coffee: {
options: {
sourceMap: true,
style: 'compressed'
},
files: {
expand: true,
flatten: true,
cwd: 'assets/coffee',
src: ['*.coffee'],
dest: 'public/js',
ext: '.js'
}
},
watch: { watch: {
css: { css: {
files: ['assets/styles/**/*.scss'], files: ['assets/styles/**/*.scss'],
@ -27,15 +42,24 @@ module.exports = function(grunt) {
atBegin: true, atBegin: true,
spawn: false spawn: false
} }
},
js: {
files: ['assets/coffee/*.coffee'],
tasks: ['coffee'],
options: {
atBegin: true,
spawn: false
}
} }
} }
}); });
// Load plugins. // Load plugins.
grunt.loadNpmTasks('grunt-contrib-watch');
grunt.loadNpmTasks('grunt-contrib-sass'); grunt.loadNpmTasks('grunt-contrib-sass');
grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-contrib-watch');
// CLI tasks. // Default task(s).
grunt.registerTask('default', ['sass']); grunt.registerTask('default', ['sass', 'coffee']);
}; };

6
Guardfile Normal file
View File

@ -0,0 +1,6 @@
guard 'rack' do
watch('Gemfile.lock')
watch('config.ru')
watch('server.rb')
watch(%r{^(lib)/.*})
end

20
Rakefile Normal file
View File

@ -0,0 +1,20 @@
require 'bundler/setup'
require 'sequel'
require 'sqlite3'
namespace :db do
task :migrate do
%x{sequel -m 'db/migrations/' 'sqlite://data/raven.db'}
end
end
namespace :server do
task :start do
system("puma -C config/puma.rb")
end
task :dev do
%x{guard}
end
end

26
app/config.rb Normal file
View File

@ -0,0 +1,26 @@
require 'yaml'
class Config
DEFAULT_CONFIG = 'config/defaults.yaml'
def initialize(config_path)
@data = YAML::load_file(DEFAULT_CONFIG)
if File.exists?(config_path)
@data.merge!(YAML::load_file(config_path))
end
end
def get(key, depth = 0)
bits = key.split('.')
value = @data
bits.each do |bit|
value = value[bit]
end
return value
end
end

View File

@ -1,22 +0,0 @@
package db
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
var (
DB *gorm.DB
)
func InitDatabase() {
database, err := gorm.Open(sqlite.Open("data/raven.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// Migrate the schema
database.AutoMigrate(&Item{})
DB = database
}

View File

@ -1,18 +0,0 @@
package db
import (
"time"
"gorm.io/gorm"
)
type Item struct {
gorm.Model
Name string
Manufacturer string
Type string
SerialNumber string
SkuNumber string
PurchasedFrom string
PurchasedAt time.Time
}

21
app/helpers.rb Normal file
View File

@ -0,0 +1,21 @@
module Helpers
def nullable(value)
if (value) and (value != '')
return value
else
return 'N/a'
end
end
def date_format(date)
dt = date.to_datetime
return dt.strftime('%B %d, %Y @ %I:%M:%S %p %Z')
end
def date_format_input(date)
dt = date.to_datetime
return dt.strftime('%Y-%m-%dT%H:%M:%S')
end
end

5
app/models/ip_address.rb Normal file
View File

@ -0,0 +1,5 @@
class IpAddress < Sequel::Model
end

17
app/models/item.rb Normal file
View File

@ -0,0 +1,17 @@
class Item < Sequel::Model
one_to_many :item_comments
def getLink()
return "/item/#{self.id}"
end
def type_selected?(option)
if self.type == option
return 'selected'
else
return ''
end
end
end

View File

@ -0,0 +1,5 @@
class ItemComment < Sequel::Model
many_to_one :items
end

9
app/models/license.rb Normal file
View File

@ -0,0 +1,9 @@
class License < Sequel::Model
one_to_many :license_comments
def getLink()
return "/license/#{self.id}"
end
end

View File

@ -0,0 +1,5 @@
class LicenseComment < Sequel::Model
many_to_one :licenses
end

9
app/routes.rb Normal file
View File

@ -0,0 +1,9 @@
require_relative 'routes/index.rb'
require_relative 'routes/item.rb'
require_relative 'routes/license.rb'
require_relative 'routes/search.rb'
require_relative 'routes/ip_tracker.rb'

16
app/routes/index.rb Normal file
View File

@ -0,0 +1,16 @@
class Raven
class IndexController
get '/' do
items = Item.reverse(:updated_at).limit(10).all()
licenses = License.reverse(:updated_at).limit(10).all()
erb :index, :locals => {
:title => 'Dashboard',
:items => items,
:licenses => licenses
}
end
end
end

40
app/routes/ip_tracker.rb Normal file
View File

@ -0,0 +1,40 @@
require 'ipaddr'
class Raven
class IpTrackerController
get '/' do
ip_addresses = IpAddress.all()
ip_addresses.sort! { |a,b| IPAddr.new( a.address ) <=> IPAddr.new( b.address ) }
erb :'ip/ip-tracker', :locals => {
:title => 'IP Tracker',
:ip_addresses => ip_addresses
}
end
get '/add' do
erb :'ip/add', :locals => {
:title => 'Add IP Address'
}
end
post '/add' do
ip = IpAddress.create(
address: params[:ip_address],
dns_name: params[:ip_dns],
comment: params[:ip_comment]
)
redirect '/ip-tracker'
end
get '/delete/:ip_id' do
ip = IpAddress.where(id: params[:ip_id]).first()
ip.delete()
redirect '/ip-tracker'
end
end
end

83
app/routes/item.rb Normal file
View File

@ -0,0 +1,83 @@
class Raven
class ItemController
get '/' do
redirect '/item/list'
end
get '/list' do
items = Item.reverse(:updated_at).all()
erb :'item/list', :locals => {
:title => 'List of Items',
:items => items
}
end
get '/create' do
erb :'item/create', :locals => {
:title => 'Create New Item'
}
end
post '/create' do
item = Item.create(
name: params[:item_name],
serial_number: params[:item_serial],
sku_number: params[:item_sku],
purchased_from: params[:item_purchase_from],
purchased_at: params[:item_purchase_date],
manufacturer: params[:item_manufacturer],
type: params[:item_type]
)
redirect "/item/#{item.id}"
end
get '/:item_id' do
item = Item.where(id: params[:item_id]).first()
puts "#{item.name}"
erb :'item/view', :locals => {
:title => item.name,
:item => item
}
end
get '/:item_id/edit' do
item = Item.where(id: params[:item_id]).first()
puts "#{item.name}"
erb :'item/edit', :locals => {
:title => "Editing: #{item.name}",
:item => item
}
end
post '/:item_id/edit' do
item = Item.where(id: params[:item_id]).first()
item.name = params[:item_name]
item.serial_number = params[:item_serial]
item.sku_number = params[:item_sku]
item.purchased_from = params[:item_purchase_from]
item.purchased_at = params[:item_purchase_date]
item.manufacturer = params[:item_manufacturer]
item.type = params[:item_type]
item.save()
redirect "/item/#{item.id}"
end
get '/:item_id/delete' do
item = Item.where(id: params[:item_id]).first()
item.delete()
redirect '/item/list'
end
post '/:item_id/comment' do
item = Item.first(id: params[:item_id])
comment = ItemComment.create(body: params[:comment_body])
item.add_item_comment(comment)
redirect "/item/#{item.id}"
end
end
end

83
app/routes/license.rb Normal file
View File

@ -0,0 +1,83 @@
class Raven
class LicenseController
get '/' do
redirect '/license/list'
end
get '/list' do
licenses = License.reverse(:updated_at).all()
erb :'license/list', :locals => {
:title => 'List of Licenses',
:licenses => licenses
}
end
get '/create' do
erb :'license/create', :locals => {
:title => 'Create New License'
}
end
post '/create' do
license = License.create(
name: params[:license_name],
key: params[:license_key],
manufacturer: params[:license_manufacturer],
seats_used: params[:license_seats_used],
seats_total: params[:license_seats_total],
purchased_from: params[:license_purchase_from],
purchased_at: params[:license_purchase_date]
)
redirect "/license/#{license.id}"
end
get '/:license_id' do
license = License.where(id: params[:license_id]).first()
puts "#{license.name}"
erb :'license/view', :locals => {
:title => license.name,
:license => license
}
end
get '/:license_id/edit' do
license = License.where(id: params[:license_id]).first()
puts "#{license.name}"
erb :'license/edit', :locals => {
:title => "Editing: #{license.name}",
:license => license
}
end
post '/:license_id/edit' do
license = License.where(id: params[:license_id]).first()
license.name = params[:license_name]
license.key = params[:license_key]
license.manufacturer = params[:license_manufacturer]
license.seats_used = params[:license_seats_used]
license.seats_total = params[:license_seats_total]
license.purchased_from = params[:license_purchase_from]
license.purchased_at = params[:license_purchase_date]
license.save()
redirect "/license/#{license.id}"
end
get '/:license_id/delete' do
license = License.where(id: params[:license_id]).first()
license.delete()
redirect '/license/list'
end
post '/:license_id/comment' do
license = License.first(id: params[:license_id])
comment = LicenseComment.create(body: params[:comment_body])
license.add_license_comment(comment)
redirect "/license/#{license.id}"
end
end
end

19
app/routes/search.rb Normal file
View File

@ -0,0 +1,19 @@
class Raven
class SearchController
get '/' do
search_parameter = params[:query]
items = Item.where(Sequel.ilike(:name, "%#{search_parameter}%")).all()
licenses = License.where(Sequel.ilike(:name, "%#{search_parameter}%")).all()
results = items.concat(licenses)
erb :'search/list', :locals => {
:title => 'Search Results',
:results => results,
:query => search_parameter
}
end
end
end

View File

@ -1,15 +0,0 @@
package form
import (
"time"
)
type Item struct {
Name string `form:"item_name"`
Manufacturer string `form:"item_manufacturer"`
Type string `form:"item_type"`
SerialNumber string `form:"item_serial"`
SkuNumber string `form:"item_sku"`
PurchasedFrom string `form:"item_purchase_from"`
PurchasedAt time.Time `form:"item_purchase_date"`
}

View File

@ -1,53 +0,0 @@
package web
import (
"net/http"
"github.com/flamego/binding"
"github.com/flamego/flamego"
"github.com/flamego/template"
"git.metaunix.net/metaunix/raven/app/db"
"git.metaunix.net/metaunix/raven/app/web/form"
)
func RegisterRoutes(f *flamego.Flame) {
// index route - landing page for the user
f.Get("/", func(t template.Template, data template.Data) {
data["title"] = "Dashboard"
t.HTML(http.StatusOK, "index")
})
// item list route - lists all of the items in the database
f.Group("/item", func() {
f.Get("/list", func(t template.Template, data template.Data) {
var items []db.Item
db.DB.Find(&items)
data["inventory"] = items
data["title"] = "List of Inventory"
t.HTML(http.StatusOK, "item/list")
})
f.Get("/create", func(t template.Template, data template.Data) {
data["title"] = "Create New Item"
t.HTML(http.StatusOK, "item/create")
})
f.Post("/create", binding.Form(form.Item{}), func(c flamego.Context, form form.Item) {
// Create new Item object
item := db.Item{
Name: form.Name,
SerialNumber: form.SerialNumber,
SkuNumber: form.SkuNumber,
Manufacturer: form.Manufacturer,
Type: form.Type,
PurchasedFrom: form.PurchasedFrom,
PurchasedAt: form.PurchasedAt,
}
db.DB.Create(&item)
// Redirect user to items list
c.Redirect("/item/list")
})
})
}

View File

@ -0,0 +1,43 @@
$(document).ready( ->
$('#nav-toggle').on('click', toggleNav)
if getCookie('navCollapsed') == 'true'
$('body').addClass('collapsed')
$('#mobile-nav-toggle').on('click', toggleMobileNav)
)
toggleNav = () ->
bodyElem = $('body')
if bodyElem.hasClass('collapsed')
bodyElem.removeClass('collapsed')
setCookie('navCollapsed', 'false')
else
bodyElem.addClass('collapsed')
setCookie('navCollapsed', 'true')
toggleMobileNav = () ->
navElem = $('#mobile-nav')
if navElem.hasClass('expanded')
navElem.removeClass('expanded')
else
navElem.addClass('expanded')
getCookie = (cName) ->
name = cName + '='
cDecoded = decodeURIComponent(document.cookie)
#to be careful
cArr = cDecoded.split('; ')
res = undefined
cArr.forEach (val) ->
if val.indexOf(name) == 0
res = val.substring(name.length)
return
res
setCookie = (cName, cValue, expDays = 30) ->
date = new Date
date.setTime date.getTime() + expDays * 24 * 60 * 60 * 1000
expires = 'expires=' + date.toUTCString()
document.cookie = cName + '=' + cValue + '; ' + expires + '; path=/'
return

View File

@ -5,6 +5,96 @@ $box-shadow-2: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
body{ body{
background: lightgrey; background: lightgrey;
padding-left: $nav-width;
transition: padding-left 230ms ease-in-out;
}
.card{
padding: 20px 30px;
background: white;
border-radius: 5px;
box-shadow: $box-shadow-2;
}
@media screen and (max-width: 992px) {
body,
body.collapsed{
padding-left: 0 !important;
}
#main-nav{
display: none;
}
#mobile-nav{
display: block;
}
}
@media screen and (min-width: 993px) {
#mobile-nav{
display: none;
}
}
#mobile-nav{
width: calc(100% + 16px);
height: 55px;
margin-top: -8px;
margin-left: -8px;
margin-bottom: 15px;
background: #212121;
color: white;
font-size: 3rem;
box-shadow: $box-shadow-1;
overflow-y: hidden;
transition: height 230ms ease-in-out;
span{
display: inline-block;
width: 100%;
padding-top: 7px;
}
ul{
list-style: none;
li{
margin: 0;
border-bottom: 1px solid #999;
&:first-child{
border-top: 1px solid #999;
}
i{
display: none;
position: absolute;
right: 18px;
margin-top: 5px;
font-size: 3rem;
}
}
a{
display: block;
box-sizing: border-box;
width: 100%;
padding: 10px 15px;
color: limegreen;
font-size: 2.5rem;
text-decoration: none;
transition: all 230ms ease-in-out;
&:hover{
background: rgba(255, 255, 255, 0.1);
}
}
}
}
#mobile-nav.expanded{
height: 300px;
i{
display: inline;
}
} }
#main-nav{ #main-nav{
@ -18,9 +108,21 @@ body{
color: white; color: white;
box-shadow: $box-shadow-1; box-shadow: $box-shadow-1;
box-sizing: border-box; box-sizing: border-box;
transition: left 230ms ease-in-out;
h3{ h3{
text-align: center; padding-left: 15px;
text-align: left;
i{
position: absolute;
right: 22px;
margin-top: 5px;
font-size: 3rem;
&:hover{
cursor: pointer;
}
}
} }
ul{ ul{
@ -33,6 +135,13 @@ body{
&:first-child{ &:first-child{
border-top: 1px solid #999; border-top: 1px solid #999;
} }
i{
position: absolute;
right: 18px;
margin-top: 5px;
font-size: 3rem;
}
} }
a{ a{
@ -52,11 +161,57 @@ body{
} }
} }
body.collapsed{
padding-left: 64px;
}
body.collapsed #main-nav{
left: calc($nav-width * -1 + 64px);
}
#main-actions{
width: 100%;
max-width: 100%;
margin-top: 0;
margin-bottom: 25px;
form,
input{
margin: 0;
}
}
#main-wrapper{ #main-wrapper{
max-width: 1200px; max-width: 1200px;
margin-top: 25px; margin-top: 25px;
padding: 20px 30px; }
background: white;
border-radius: 5px; #main-wrapper.container.fluid{
box-shadow: $box-shadow-2; width: 100%;
max-width: 100%;
margin: 0;
}
#site-header{
margin-bottom: 0;
}
#item-header,
#license-header{
.item-name,
.license-name{
margin-bottom: 5px;
}
.item-created,
.item-updated,
.license-created,
.license-updated{
color: #666;
font-size: 1.75rem;
font-style: italic;
}
}
.u-text-centered{
text-align: center;
} }

9
config.ru Normal file
View File

@ -0,0 +1,9 @@
# Load application config
require_relative 'app/config.rb'
$conf = Config.new(File.join(__dir__, 'data/defaults.yaml'))
# Load Sinatra server
require_relative './server.rb'
# Run application
run Raven

7
config/defaults.yaml Normal file
View File

@ -0,0 +1,7 @@
server:
address: '127.0.0.1'
port: 6200
database:
adapter: 'sqlite'
database: 'data/raven.db'

6
config/puma.rb Normal file
View File

@ -0,0 +1,6 @@
# Load application config
require './app/config.rb'
$conf = Config.new(File.join(__dir__, 'config/raven.yaml'))
bind_address = "tcp://#{$conf.get('server.address')}:#{$conf.get('server.port')}"
bind bind_address

View File

@ -0,0 +1,22 @@
Sequel.migration do
up do
create_table(:items) do
primary_key :id
String :name, null: false
String :manufacturer
String :serial_number
String :sku_number
String :type
String :purchased_from
DateTime :purchased_at
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
end
down do
drop_table(:items)
end
end

View File

@ -0,0 +1,20 @@
Sequel.migration do
up do
create_table(:item_comments) do
primary_key :id
String :body, null: false
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
alter_table(:item_comments) do
add_foreign_key :item_id, :items
end
end
down do
drop_table(:item_comments)
end
end

View File

@ -0,0 +1,22 @@
Sequel.migration do
up do
create_table(:licenses) do
primary_key :id
String :name, null: false
String :key, null: false
String :manufacturer
Integer :seats_used, default: 0
Integer :seats_total, default: 1
String :purchased_from
DateTime :purchased_at
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
end
down do
drop_table(:licenses)
end
end

View File

@ -0,0 +1,20 @@
Sequel.migration do
up do
create_table(:license_comments) do
primary_key :id
String :body, null: false
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
alter_table(:license_comments) do
add_foreign_key :license_id, :licenses
end
end
down do
drop_table(:license_comments)
end
end

View File

@ -0,0 +1,18 @@
Sequel.migration do
up do
create_table(:ip_addresses) do
primary_key :id
String :address, null: false
String :dns_name
String :comment
DateTime :created_at, default: Sequel::CURRENT_TIMESTAMP
DateTime :updated_at, default: Sequel::CURRENT_TIMESTAMP
end
end
down do
drop_table(:ip_addresses)
end
end

25
go.mod
View File

@ -1,25 +0,0 @@
module git.metaunix.net/metaunix/raven
go 1.18
require (
github.com/alecthomas/participle/v2 v2.0.0-beta.5 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/flamego/binding v1.2.0 // indirect
github.com/flamego/flamego v1.7.0 // indirect
github.com/flamego/template v1.1.0 // indirect
github.com/flamego/validator v1.0.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-sqlite3 v1.14.16 // indirect
github.com/pkg/errors v0.9.1 // indirect
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 // indirect
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/sqlite v1.4.3 // indirect
gorm.io/gorm v1.24.2 // indirect
)

65
go.sum
View File

@ -1,65 +0,0 @@
github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo=
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/flamego/binding v1.2.0 h1:rSEurfmhoFHjUooXcG4NJHZAGm918Pq52VjG1Ft6lp4=
github.com/flamego/binding v1.2.0/go.mod h1:hRtlg8mVujsuYWxnd0xAYLSET4MxoqXzg/KWP7OtSz0=
github.com/flamego/flamego v1.7.0 h1:c1Lu16PBAZKkpsjHw42vwotdoQnMMpUi60ITP41W12w=
github.com/flamego/flamego v1.7.0/go.mod h1:dnVMBJyHKaxjcqRVN93taSK+YB/9p+Op1GdLIuA1hFQ=
github.com/flamego/template v1.1.0 h1:iYtCzY3TeYpsoQiGApFXw2qycKdMzimz2gkO/SlcksM=
github.com/flamego/template v1.1.0/go.mod h1:bgnmEXNumarhQIUzFgn18CDG6u8cM6X09c7UOTwZcxM=
github.com/flamego/validator v1.0.0 h1:ixuWHVgiVGp4pVGtUn/0d6HBjZJbbXfJHDNkxW+rZoY=
github.com/flamego/validator v1.0.0/go.mod h1:POYn0/5iW4sdamdPAYPrzqN6DFC4YaczY0gYY+Pyx5E=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63 h1:kETrAMYZq6WVGPa8IIixL0CaEcIUNi+1WX7grUoi3y8=
golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678 h1:J27LZFQBFoihqXoegpscI10HpjZ7B5WQLLKL2FZXQKw=
golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU=
gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.2 h1:9wR6CFD+G8nOusLdvkZelOEhpJVwwHzpQOUM+REd6U0=
gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=

65
package-lock.json generated
View File

@ -11,6 +11,7 @@
"devDependencies": { "devDependencies": {
"grunt": "^1.5.3", "grunt": "^1.5.3",
"grunt-cli": "^1.4.3", "grunt-cli": "^1.4.3",
"grunt-contrib-coffee": "^2.1.0",
"grunt-contrib-sass": "^2.0.0", "grunt-contrib-sass": "^2.0.0",
"grunt-contrib-watch": "^1.1.0", "grunt-contrib-watch": "^1.1.0",
"sass": "^1.55.0" "sass": "^1.55.0"
@ -192,6 +193,19 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/coffeescript": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.7.0.tgz",
"integrity": "sha512-hzWp6TUE2d/jCcN67LrW1eh5b/rSDKQK6oD6VMLlggYVUUFexgTH9z3dNYihzX4RMhze5FTUsUmOXViJKFQR/A==",
"dev": true,
"bin": {
"cake": "bin/cake",
"coffee": "bin/coffee"
},
"engines": {
"node": ">=6"
}
},
"node_modules/color-convert": { "node_modules/color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -640,6 +654,24 @@
"nopt": "bin/nopt.js" "nopt": "bin/nopt.js"
} }
}, },
"node_modules/grunt-contrib-coffee": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-2.1.0.tgz",
"integrity": "sha512-lgP+pPY3mHl+gqAU0T+7BcocBWu0FyeeJnAG/iIp2I0GPa5LvZJ7Wqga6QwKQtQCTs+1gPEa12nuap9Lj08lhw==",
"dev": true,
"dependencies": {
"chalk": "^2.4.2",
"coffeescript": "^2.3.2",
"lodash": "^4.17.11",
"uri-path": "^1.0.0"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"grunt": ">=0.4.5"
}
},
"node_modules/grunt-contrib-sass": { "node_modules/grunt-contrib-sass": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/grunt-contrib-sass/-/grunt-contrib-sass-2.0.0.tgz", "resolved": "https://registry.npmjs.org/grunt-contrib-sass/-/grunt-contrib-sass-2.0.0.tgz",
@ -1679,6 +1711,15 @@
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==",
"dev": true "dev": true
}, },
"node_modules/uri-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz",
"integrity": "sha512-8pMuAn4KacYdGMkFaoQARicp4HSw24/DHOVKWqVRJ8LhhAwPPFpdGvdL9184JVmUwe7vz7Z9n6IqI6t5n2ELdg==",
"dev": true,
"engines": {
"node": ">= 0.10"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -1881,6 +1922,12 @@
"readdirp": "~3.6.0" "readdirp": "~3.6.0"
} }
}, },
"coffeescript": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/coffeescript/-/coffeescript-2.7.0.tgz",
"integrity": "sha512-hzWp6TUE2d/jCcN67LrW1eh5b/rSDKQK6oD6VMLlggYVUUFexgTH9z3dNYihzX4RMhze5FTUsUmOXViJKFQR/A==",
"dev": true
},
"color-convert": { "color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -2229,6 +2276,18 @@
} }
} }
}, },
"grunt-contrib-coffee": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/grunt-contrib-coffee/-/grunt-contrib-coffee-2.1.0.tgz",
"integrity": "sha512-lgP+pPY3mHl+gqAU0T+7BcocBWu0FyeeJnAG/iIp2I0GPa5LvZJ7Wqga6QwKQtQCTs+1gPEa12nuap9Lj08lhw==",
"dev": true,
"requires": {
"chalk": "^2.4.2",
"coffeescript": "^2.3.2",
"lodash": "^4.17.11",
"uri-path": "^1.0.0"
}
},
"grunt-contrib-sass": { "grunt-contrib-sass": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/grunt-contrib-sass/-/grunt-contrib-sass-2.0.0.tgz", "resolved": "https://registry.npmjs.org/grunt-contrib-sass/-/grunt-contrib-sass-2.0.0.tgz",
@ -3013,6 +3072,12 @@
} }
} }
}, },
"uri-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/uri-path/-/uri-path-1.0.0.tgz",
"integrity": "sha512-8pMuAn4KacYdGMkFaoQARicp4HSw24/DHOVKWqVRJ8LhhAwPPFpdGvdL9184JVmUwe7vz7Z9n6IqI6t5n2ELdg==",
"dev": true
},
"util-deprecate": { "util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View File

@ -26,6 +26,7 @@
"devDependencies": { "devDependencies": {
"grunt": "^1.5.3", "grunt": "^1.5.3",
"grunt-cli": "^1.4.3", "grunt-cli": "^1.4.3",
"grunt-contrib-coffee": "^2.1.0",
"grunt-contrib-sass": "^2.0.0", "grunt-contrib-sass": "^2.0.0",
"grunt-contrib-watch": "^1.1.0", "grunt-contrib-watch": "^1.1.0",
"sass": "^1.55.0" "sass": "^1.55.0"

View File

@ -1,31 +0,0 @@
package main
import (
"github.com/flamego/flamego"
"github.com/flamego/template"
"git.metaunix.net/metaunix/raven/app/db"
"git.metaunix.net/metaunix/raven/app/web"
)
func main() {
f := flamego.New()
// Initialize the database connection
db.InitDatabase()
// Initialize template engine
f.Use(template.Templater(template.Options{
Directory: "views",
}))
f.Use(flamego.Static(
flamego.StaticOptions{
Directory: "public",
},
))
web.RegisterRoutes(f)
f.Run()
}

71
server.rb Normal file
View File

@ -0,0 +1,71 @@
# frozen_string_literal: true
require 'logger'
require 'sequel'
require 'sqlite3'
require 'sinatra/base'
require 'rack/protection'
# 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 models
Dir.glob('./app/models/*.rb').sort().each { |f| require f }
# Base Sinatra app
class Raven < Sinatra::Base
@@my_app = {}
def self.new(*) self < Raven ? super : Rack::URLMap.new(@@my_app) end
def self.map(url) @@my_app[url] = self end
# Enable and configure sessions
enable :sessions
# Enable rack protection middleware
use Rack::Protection
# Set up static file serving
enable :static
set :public_folder, File.join(__dir__, '/public')
# Set up our view engine
set :views, File.join(settings.root, '/views')
# Initialize logging
logger = Logger.new($stdout)
logger.level = Logger::INFO
# Load helper functions
require_relative 'app/helpers'
helpers Helpers
## Map controllers
# Top-level routes controller
class IndexController < Raven
map '/'
end
# Item routes controller
class ItemController < Raven
map '/item'
end
# License routes controller
class LicenseController < Raven
map '/license'
end
# Search routes controller
class SearchController < Raven
map '/search'
end
# IP tracker routes controller
class IpTrackerController < Raven
map '/ip-tracker'
end
end
# Load controllers
Dir.glob('./app/routes.rb').sort().each { |f| require f }

57
views/index.erb Normal file
View File

@ -0,0 +1,57 @@
<div class="row">
<div class="twelve columns">
<h1 id="site-header">Welcome to Raven</h1>
</div>
</div>
<hr>
<div class="row">
<div class="six columns">
<h3>Recent inventory updates</h3>
<% if items.length > 0 %>
<table class="u-full-width">
<thead>
<tr>
<th>Item name</th>
<th>Updated at</th>
</tr>
</thead>
<tbody>
<% items.each do |item| %>
<tr>
<td><%= item.name %></td>
<td><%= date_format(item.updated_at) %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>There are no items to display.</p>
<% end %>
</div>
<div class="six columns">
<h3>Recent license updates</h3>
<% if licenses.length > 0 %>
<table class="u-full-width">
<thead>
<tr>
<th>License name</th>
<th>Updated at</th>
</tr>
</thead>
<tbody>
<% licenses.each do |license| %>
<tr>
<td><%= license.name %></td>
<td><%= date_format(license.updated_at) %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>There are no licenses to display.</p>
<% end %>
</div>
</div>

View File

@ -1,9 +0,0 @@
{{ template "layout_header" . }}
<div class="row">
<div class="twelve columns">
<p>This is a test.</p>
</div>
</div>
{{ template "layout_footer" . }}

32
views/ip/add.erb Normal file
View File

@ -0,0 +1,32 @@
<div class="row">
<div class="twelve columns">
<h1>Add new IP address</h1>
</div>
</div>
<div class="row">
<div class="twelve columns">
<form action="/ip-tracker/add" method="POST" class="u-full-width">
<div class="row">
<div class="six columns">
<label for="ip_address">IP address:</label>
<input class="u-full-width" type="text" placeholder="192.168.1.20" id="ip_address" name="ip_address">
</div>
<div class="six columns">
<label for="ip_dns">DNS name:</label>
<input class="u-full-width" type="text" placeholder="test.example.com" id="ip_dns" name="ip_dns">
</div>
</div>
<div class="row">
<div class="twelve columns">
<label for="ip_comment">Comments:</label>
<input class="u-full-width" type="text" placeholder="My thoughts on this address..." id="ip_comment" name="ip_comment">
</div>
</div>
<input class="button-primary u-full-width" type="submit" value="Submit">
</form>
</div>
</div>

31
views/ip/ip-tracker.erb Normal file
View File

@ -0,0 +1,31 @@
<div class="row">
<div class="twelve columns">
<% if ip_addresses.length > 0 %>
<p><a href="/ip-tracker/add">Add new address</a></p>
<table class="u-full-width">
<thead>
<tr>
<th>Address</th>
<th>DNS Name</th>
<th>Comments</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<% ip_addresses.each do |ip| %>
<tr>
<td><%= ip.address %></td>
<td><%= ip.dns_name %></td>
<td><%= ip.comment %></td>
<td>
<a href="/ip-tracker/delete/<%= ip.id %>"><i class="fa-solid fa-trash"></i></a>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>There are no IP addresses to show at this time. Trying <a href="/ip-tracker/add">adding some</a>.</p>
<% end %>
</div>
</div>

View File

@ -1,5 +1,3 @@
{{ template "layout_header" . }}
<div class="row"> <div class="row">
<div class="twelve columns"> <div class="twelve columns">
<h1>Create new item</h1> <h1>Create new item</h1>
@ -64,5 +62,3 @@
</form> </form>
</div> </div>
</div> </div>
{{ template "layout_footer" . }}

64
views/item/edit.erb Normal file
View File

@ -0,0 +1,64 @@
<div class="row">
<div class="twelve columns">
<h1>Editing item: <%= item.name %></h1>
</div>
</div>
<div class="row">
<div class="twelve columns">
<form action="/item/<%= item.id %>/edit" method="POST" class="u-full-width">
<div class="row">
<div class="columns twelve">
<label for="item_name">Item name:</label>
<input class="u-full-width" type="text" placeholder="My new item" id="item_name" name="item_name" required value="<%= item.name %>">
</div>
</div>
<div class="row">
<div class="six columns">
<label for="item_serial">Serial number:</label>
<input class="u-full-width" type="text" placeholder="0123456789" id="item_serial" name="item_serial" value="<%= item.serial_number %>">
</div>
<div class="six columns">
<label for="item_sku">SKU number:</label>
<input class="u-full-width" type="text" placeholder="ABC12345678" id="item_sku" name="item_sku" value="<%= item.sku_number %>">
</div>
</div>
<div class="row">
<div class="six columns">
<label for="item_purchase_from">Purchased from:</label>
<input class="u-full-width" type="text" placeholder="Newegg" id="item_purchase_from" name="item_purchase_from" value="<%= item.purchased_from %>">
</div>
<div class="six columns">
<label for="item_purchase_date">Purchased at:</label>
<input class="u-full-width" type="datetime-local" id="item_purchase_date" name="item_purchase_date" value="<%= date_format_input(item.purchased_at) %>">
</div>
</div>
<div class="row">
<div class="six columns">
<label for="item_manufacturer">Manufacturer:</label>
<input class="u-full-width" type="text" placeholder="Manufacturer" id="item_manufacturer" name="item_manufacturer" value="<%= item.manufacturer %>">
</div>
<div class="six columns">
<label for="item_type">Item type</label>
<select class="u-full-width" id="item_type" name="item_type">
<option value="cpu" <%= item.type_selected?('cpu') %>>Processor</option>
<option value="motherboard" <%= item.type_selected?('motherboard') %>>Motherboard</option>
<option value="memory" <%= item.type_selected?('memory') %>>Memory (RAM)</option>
<option value="psu" <%= item.type_selected?('psu') %>>Power Supply</option>
<option value="case" <%= item.type_selected?('case') %>>Case</option>
<option value="storage" <%= item.type_selected?('storage') %>>Storage Device</option>
<option value="gpu" <%= item.type_selected?('gpu') %>>Graphics Card</option>
</select>
</div>
</div>
<input class="button-primary u-full-width" type="submit" value="Submit">
</form>
</div>
</div>

21
views/item/list.erb Normal file
View File

@ -0,0 +1,21 @@
<div class="row">
<div class="twelve columns">
<h1>Hardware Inventory List</h1>
</div>
</div>
<div class="row">
<div class="twelve columns">
<p><a href="/item/create">Create new item</a></p>
<% if items.length > 0 %>
<ul>
<% items.each do |item| %>
<li><a href="/item/<%= item.id %>"><%= item.name %></a></li>
<% end %>
</ul>
<% else %>
<p>There is nothing registered in your inventory yet.</p>
<% end %>
</div>
</div>

View File

@ -1,13 +0,0 @@
{{ template "layout_header" . }}
<p><a href="/item/create">Create new item</a></p>
<ul>
{{ range .inventory }}
<li>{{ .Name }}</li>
{{ else }}
<p>There are currently no items registered in your inventory.</p>
{{ end }}
</ul>
{{ template "layout_footer" . }}

65
views/item/view.erb Normal file
View File

@ -0,0 +1,65 @@
<div id="item-header" class="row">
<div class="twelve columns">
<h1 class="item-name"><%= item.name %></h1>
<h4 class="item-created">Item added at: <%= date_format(item.created_at) %></h4>
<% if item.updated_at %>
<h4 class="item-updated">Last updated at: <%= date_format(item.updated_at) %></h4>
<% end %>
</div>
</div>
<div class="row">
<div class="twelve columns">
<p class="inventory-actions">
<a href="/item/<%= item.id %>/edit"><i class="fa-solid fa-pen-to-square"></i></a>
<a href="/item/<%= item.id %>/delete"><i class="fa-solid fa-trash"></i></a>
</p>
<table class="u-full-width">
<thead>
<tr>
<th>Item type:</th>
<th>Manufacturer:</th>
<th>Serial number:</th>
<th>SKU number:</th>
<th>Vendor:</th>
<th>Purchase Date:</th>
</tr>
</thead>
<tbody>
<tr>
<td><%= item.type %></td>
<td><%= nullable(item.manufacturer) %></td>
<td><%= nullable(item.serial_number) %></td>
<td><%= nullable(item.sku_number) %></td>
<td><%= nullable(item.purchased_from) %></td>
<td><%= nullable(date_format(item.purchased_at)) %></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="twelve columns">
<% if item.item_comments.length > 0 %>
<ul class="u-full-width">
<% item.item_comments.each do |comment| %>
<li><%= comment.body %></li>
<% end %>
</ul>
<% else %>
<p>There are no comments to display at this time.</p>
<% end %>
</div>
</div>
<div class="row">
<div class="twelve columns">
<form action="/item/<%= item.id %>/comment" method="POST" class="u-full-width">
<label for="comment_body">Add a comment:</label>
<textarea name="comment_body" id="comment_body" class="u-full-width" cols="30" rows="10"></textarea>
<input class="button button-primary" type="submit" value="Submit">
</form>
</div>
</div>

26
views/layout.erb Normal file
View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %> | Raven</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/all.min.css">
<link rel="stylesheet" href="/css/kraken.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script src="/js/gargoyle.js"></script>
</head>
<body>
<!-- Main navigation -->
<%= erb :'layout/navbar', :locals => locals %>
<!-- Mobile navigatin -->
<%= erb :'layout/mobile_navbar', :locals => locals %>
<!-- Inventory search/actions bar -->
<%= erb :'layout/actions', :locals => locals %>
<div id="main-wrapper" class="container fluid card">
<%= yield %>
</div>
</body>
</html>

9
views/layout/actions.erb Normal file
View File

@ -0,0 +1,9 @@
<div id="main-actions" class="container fluid card">
<div class="row">
<div class="six columns">
<form action="/search" class="u-full-width">
<input id="search-field" class="u-full-width" type="text" placeholder="Search your inventory..." name="query" required <%= defined?(query) ? 'value="' + query + '"' : '' %>>
</form>
</div>
</div>
</div>

View File

@ -1,5 +0,0 @@
{{ define "layout_footer" }}
</div>
</body>
</html>
{{ end }}

View File

@ -1,17 +0,0 @@
{{ define "layout_header" }}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ .title }} | Raven</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css">
<link rel="stylesheet" href="/css/kraken.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
</head>
<body>
{{ template "navbar" . }}
<div id="main-wrapper" class="container">
{{ end }}

View File

@ -0,0 +1,9 @@
<nav id="mobile-nav">
<span class="u-text-centered"><i id="mobile-nav-toggle" class="fa-solid fa-bars"></i></span>
<ul>
<li><a href="/">Dashboard <i class="fa-solid fa-gauge"></i></a></li>
<li><a href="/item/list">Items <i class="fa-solid fa-desktop"></i></a></li>
<li><a href="/license/list">Licenses <i class="fa-solid fa-floppy-disk"></i></a></li>
<li><a href="/ip-tracker">IP Tracker <i class="fa-solid fa-network-wired"></i></a></li>
</ul>
</nav>

9
views/layout/navbar.erb Normal file
View File

@ -0,0 +1,9 @@
<nav id="main-nav">
<h3>Raven <i id="nav-toggle" class="fa-solid fa-bars"></i></h3>
<ul>
<li><a href="/">Dashboard <i class="fa-solid fa-gauge"></i></a></li>
<li><a href="/item/list">Items <i class="fa-solid fa-desktop"></i></a></li>
<li><a href="/license/list">Licenses <i class="fa-solid fa-floppy-disk"></i></a></li>
<li><a href="/ip-tracker">IP Tracker <i class="fa-solid fa-network-wired"></i></a></li>
</ul>
</nav>

View File

@ -1,13 +0,0 @@
{{ define "navbar" }}
<nav id="main-nav">
<h3>Raven</h3>
<ul>
<li><a href="/">Dashboard</a></li>
<li><a href="/item/list">Items</a></li>
<li><a href="/license/list">Licenses</a></li>
</ul>
</nav>
{{ end }}

56
views/license/create.erb Normal file
View File

@ -0,0 +1,56 @@
<div class="row">
<div class="twelve columns">
<h1>Create new license</h1>
</div>
</div>
<div class="row">
<div class="twelve columns">
<form action="/license/create" method="POST" class="u-full-width">
<div class="row">
<div class="columns twelve">
<label for="license_name">Item name:</label>
<input class="u-full-width" type="text" placeholder="My new license" id="license_name" name="license_name" required>
</div>
</div>
<div class="row">
<div class="six columns">
<label for="license_key">License key:</label>
<input class="u-full-width" type="text" placeholder="acbd1234efgh5678" id="license_key" required name="license_key">
</div>
<div class="six columns">
<label for="license_manufacturer">Manufacturer:</label>
<input class="u-full-width" type="text" placeholder="Manufacturer" id="license_manufacturer" name="license_manufacturer">
</div>
</div>
<div class="row">
<div class="six columns">
<label for="license_seats_used">Seats used on license:</label>
<input class="u-full-width" type="number" placeholder="0" id="license_seats_used" name="license_seats_used">
</div>
<div class="six columns">
<label for="license_seats_total">Total seats on license:</label>
<input class="u-full-width" type="number" placeholder="1" id="license_seats_total" name="license_seats_total">
</div>
</div>
<div class="row">
<div class="six columns">
<label for="license_purchase_from">Purchased from:</label>
<input class="u-full-width" type="text" placeholder="Newegg" id="license_purchase_from" name="license_purchase_from">
</div>
<div class="six columns">
<label for="license_purchase_date">Purchased at:</label>
<input class="u-full-width" type="datetime-local" id="license_purchase_date" name="license_purchase_date">
</div>
</div>
<input class="button-primary u-full-width" type="submit" value="Submit">
</form>
</div>
</div>

56
views/license/edit.erb Normal file
View File

@ -0,0 +1,56 @@
<div class="row">
<div class="twelve columns">
<h1>Editing: <%= license.name %></h1>
</div>
</div>
<div class="row">
<div class="twelve columns">
<form action="/license/<%= license.id %>/edit" method="POST" class="u-full-width">
<div class="row">
<div class="columns twelve">
<label for="license_name">Item name:</label>
<input class="u-full-width" type="text" placeholder="My new license" id="license_name" name="license_name" required value="<%= license.name %>">
</div>
</div>
<div class="row">
<div class="six columns">
<label for="license_key">License key:</label>
<input class="u-full-width" type="text" placeholder="acbd1234efgh5678" id="license_key" name="license_key" required value="<%= license.key %>">
</div>
<div class="six columns">
<label for="license_manufacturer">Manufacturer:</label>
<input class="u-full-width" type="text" placeholder="Manufacturer" id="license_manufacturer" name="license_manufacturer" value="<%= license.manufacturer %>">
</div>
</div>
<div class="row">
<div class="six columns">
<label for="license_seats_used">Seats used on license:</label>
<input class="u-full-width" type="number" placeholder="0" id="license_seats_used" name="license_seats_used" value="<%= license.seats_used %>">
</div>
<div class="six columns">
<label for="license_seats_total">Total seats on license:</label>
<input class="u-full-width" type="number" placeholder="1" id="license_seats_total" name="license_seats_total" value="<%= license.seats_total %>">
</div>
</div>
<div class="row">
<div class="six columns">
<label for="license_purchase_from">Purchased from:</label>
<input class="u-full-width" type="text" placeholder="Newegg" id="license_purchase_from" name="license_purchase_from" value="<%= license.purchased_from %>">
</div>
<div class="six columns">
<label for="license_purchase_date">Purchased at:</label>
<input class="u-full-width" type="datetime-local" id="license_purchase_date" name="license_purchase_date" value="<%= date_format_input(license.purchased_at) %>">
</div>
</div>
<input class="button-primary u-full-width" type="submit" value="Submit">
</form>
</div>
</div>

21
views/license/list.erb Normal file
View File

@ -0,0 +1,21 @@
<div class="row">
<div class="twelve columns">
<h1>Software Licenses List</h1>
</div>
</div>
<div class="row">
<div class="twelve columns">
<p><a href="/license/create">Create new license</a></p>
<% if licenses.length > 0 %>
<ul>
<% licenses.each do |license| %>
<li><a href="/license/<%= license.id %>"><%= license.name %></a></li>
<% end %>
</ul>
<% else %>
<p>There is nothing registered in your inventory yet.</p>
<% end %>
</div>
</div>

65
views/license/view.erb Normal file
View File

@ -0,0 +1,65 @@
<div id="license-header" class="row">
<div class="twelve columns">
<h1 class="license-name"><%= license.name %></h1>
<h4 class="license-created">License added at: <%= date_format(license.created_at) %></h4>
<% if license.updated_at %>
<h4 class="license-updated">Last updated at: <%= date_format(license.updated_at) %></h4>
<% end %>
</div>
</div>
<div class="row">
<div class="twelve columns">
<p class="inventory-actions">
<a href="/license/<%= license.id %>/edit"><i class="fa-solid fa-pen-to-square"></i></a>
<a href="/license/<%= license.id %>/delete"><i class="fa-solid fa-trash"></i></a>
</p>
<table class="u-full-width">
<thead>
<tr>
<th>License key:</th>
<th>Manufacturer:</th>
<th>Seats used:</th>
<th>Seats total:</th>
<th>Vendor:</th>
<th>Purchase Date:</th>
</tr>
</thead>
<tbody>
<tr>
<td><%= license.key %></td>
<td><%= nullable(license.manufacturer) %></td>
<td><%= nullable(license.seats_used) %></td>
<td><%= nullable(license.seats_total) %></td>
<td><%= nullable(license.purchased_from) %></td>
<td><%= nullable(date_format(license.purchased_at)) %></td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="twelve columns">
<% if license.license_comments.length > 0 %>
<ul class="u-full-width">
<% license.license_comments.each do |comment| %>
<li><%= comment.body %></li>
<% end %>
</ul>
<% else %>
<p>There are no comments to display at this time.</p>
<% end %>
</div>
</div>
<div class="row">
<div class="twelve columns">
<form action="/license/<%= license.id %>/comment" method="POST" class="u-full-width">
<label for="comment_body">Add a comment:</label>
<textarea name="comment_body" id="comment_body" class="u-full-width" cols="30" rows="10"></textarea>
<input class="button button-primary" type="submit" value="Submit">
</form>
</div>
</div>

14
views/search/list.erb Normal file
View File

@ -0,0 +1,14 @@
<div class="row">
<div class="twelve columns">
<h4>Matching inventory:</h4>
<% if results.length > 0 %>
<ul class="u-full-width">
<% results.each do |r| %>
<li><a href="<%= r.getLink() %>"><%= r.name %></a></li>
<% end %>
</ul>
<% else %>
<p>Sorry, nothing in your hardware inventory matches that search term.</p>
<% end %>
</div>
</div>