Compare commits

..

5 Commits

24 changed files with 260 additions and 284 deletions

64
.gitignore vendored
View File

@ -1,15 +1,67 @@
# 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 # 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/

11
Gemfile Normal file
View File

@ -0,0 +1,11 @@
source 'https://rubygems.org'
gem 'sinatra', '~> 3.0'
gem 'puma', '~> 6.0'
gem 'sequel', '~> 5.63'
gem 'sqlite3', '~> 1.5'
# Use rerun gem to auto-reload app
gem 'rerun'

45
Gemfile.lock Normal file
View File

@ -0,0 +1,45 @@
GEM
remote: https://rubygems.org/
specs:
ffi (1.15.5)
ffi (1.15.5-x64-mingw-ucrt)
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
nio4r (2.5.8)
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)
rerun (0.13.1)
listen (~> 3.0)
ruby2_keywords (0.0.5)
sequel (5.63.0)
sinatra (3.0.4)
mustermann (~> 3.0)
rack (~> 2.2, >= 2.2.4)
rack-protection (= 3.0.4)
tilt (~> 2.0)
sqlite3 (1.5.4-x64-mingw-ucrt)
sqlite3 (1.5.4-x86_64-linux)
tilt (2.0.11)
PLATFORMS
x64-mingw-ucrt
x86_64-linux
DEPENDENCIES
puma (~> 6.0)
rerun
sequel (~> 5.63)
sinatra (~> 3.0)
sqlite3 (~> 1.5)
BUNDLED WITH
2.3.7

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
}

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")
})
})
}

3
data/defaults.yaml Normal file
View File

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

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
DateTime :updated_at
end
end
down do
drop_table(:items)
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=

20
lib/config.rb Normal file
View File

@ -0,0 +1,20 @@
require 'yaml'
class Config
def initialize(config_path)
@data = YAML::load_file(config_path)
end
def get(key, depth = 0)
bits = key.split('.')
value = @data
bits.each do |bit|
value = value[bit]
end
return value
end
end

5
lib/models/item.rb Normal file
View File

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

37
lib/routes.rb Normal file
View File

@ -0,0 +1,37 @@
get '/' do
items = Item.all
erb :index, :locals => {
:title => 'Dashboard',
:items => items
}
end
get '/item' do
redirect '/item/list'
end
get '/item/list' do
items = Item.all
erb :'item/list', :locals => {
:title => 'List of Items',
:items => items
}
end
get '/item/create' do
erb :'item/create', :locals => {
:title => 'Create New Item'
}
end
post '/item/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

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()
}

27
raven.rb Normal file
View File

@ -0,0 +1,27 @@
require 'logger'
require 'sequel'
require 'sqlite3'
require 'sinatra'
require_relative 'lib/config.rb'
set :public_folder, __dir__ + '/public'
set :views, settings.root + '/views'
# Load configuration file
conf = Config.new(File.join(__dir__, 'data/defaults.yaml'))
# Initialize logging
logger = Logger.new(STDOUT)
logger.level = Logger::INFO
# 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
require_relative 'lib/models/item.rb'
# Register route handlers
require_relative 'lib/routes.rb'

11
views/index.erb Normal file
View File

@ -0,0 +1,11 @@
<p>This is a test.</p>
<% if items.length > 0 %>
<ul>
<% items.each do |item| %>
<li><%= item.name %></li>
<% end %>
</ul>
<% else %>
<p>There are no items to display.</p>
<% end %>

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" . }}

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" . }}

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

@ -0,0 +1,7 @@
<p><a href="/item/create">Create new item</a></p>
<ul>
<% items.each do |item| %>
<li><%= item.name %></li>
<% end %>
</ul>

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" . }}

View File

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

View File

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

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 }}