convert to echo

This commit is contained in:
Seán C McCord 2018-05-16 23:01:06 -04:00
parent f5255969a7
commit 6ce4c20736
43 changed files with 2615 additions and 544 deletions

View file

@ -7,9 +7,14 @@ pipeline:
pull: true
commands:
- mkdir -p /go/bin /go/src
- go get github.com/revel/cmd/revel
- revel build github.com/CyCoreSystems/cycore-web ../tmp prod
- mv ../tmp .
- go get -u github.com/golang/dep/cmd/dep/...
- go get -u github.com/alecthomas/gometalinter/...
- go get -u github.com/mjibson/esc
- dep ensure
- go generate
- gometalinter --install
- gometalinter --vendor ./...
- go build
publish:
image: plugins/docker:17.05
repo: quay.io/cycore/web

4
.gitignore vendored
View file

@ -1,4 +1,2 @@
test-results/
tmp/
routes/
/vendor/
/cycore-web

View file

@ -1,3 +1,3 @@
FROM ulexus/go-minimal
COPY tmp/cycore-web /app
COPY tmp/src /src
COPY cycore-web /app
COPY assets /assets

99
Gopkg.lock generated Normal file
View file

@ -0,0 +1,99 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
name = "github.com/CyCoreSystems/sendinblue"
packages = ["."]
revision = "9b851c8f8e26b6bfc746aaa0dbaf8f7a78286ee0"
version = "v0.1.1"
[[projects]]
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
branch = "master"
name = "github.com/jmoiron/sqlx"
packages = [".","reflectx"]
revision = "2aeb6a910c2b94f2d5eb53d9895d80e27264ec41"
[[projects]]
name = "github.com/labstack/echo"
packages = [".","middleware"]
revision = "6d227dfea4d2e52cb76856120b3c17f758139b4e"
version = "3.3.5"
[[projects]]
name = "github.com/labstack/gommon"
packages = ["bytes","color","log","random"]
revision = "588f4e8bddc6cb45c27b448e925c7fd6a5545434"
version = "0.2.5"
[[projects]]
branch = "master"
name = "github.com/lib/pq"
packages = [".","oid"]
revision = "d34b9ff171c21ad295489235aec8b6626023cd04"
[[projects]]
name = "github.com/mattn/go-colorable"
packages = ["."]
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
branch = "master"
name = "github.com/valyala/bytebufferpool"
packages = ["."]
revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
[[projects]]
branch = "master"
name = "github.com/valyala/fasttemplate"
packages = ["."]
revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
[[projects]]
name = "go.uber.org/atomic"
packages = ["."]
revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289"
version = "v1.3.2"
[[projects]]
name = "go.uber.org/multierr"
packages = ["."]
revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a"
version = "v1.1.0"
[[projects]]
name = "go.uber.org/zap"
packages = [".","buffer","internal/bufferpool","internal/color","internal/exit","zapcore"]
revision = "eeedf312bc6c57391d84767a4cd413f02a917974"
version = "v1.8.0"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["acme","acme/autocert"]
revision = "1a580b3eff7814fc9b40602fd35256c63b50f491"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "7c87d13f8e835d2fb3a70a2912c811ed0c1d241b"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "663f98ffe10e2e1074511774007b7ec79196f112e44ab874c8e209c2ee0d9453"
solver-name = "gps-cdcl"
solver-version = 1

42
Gopkg.toml Normal file
View file

@ -0,0 +1,42 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/CyCoreSystems/sendinblue"
version = "0.1.1"
[[constraint]]
branch = "master"
name = "github.com/jmoiron/sqlx"
[[constraint]]
name = "github.com/labstack/echo"
version = "3.3.5"
[[constraint]]
branch = "master"
name = "github.com/lib/pq"
[[constraint]]
name = "go.uber.org/zap"
version = "1.8.0"

View file

@ -1,11 +0,0 @@
package controllers
import "github.com/revel/revel"
type App struct {
*revel.Controller
}
func (c App) Index() revel.Result {
return c.Render()
}

View file

@ -1,116 +0,0 @@
package controllers
import (
"bytes"
"encoding/json"
"html/template"
"os"
"time"
"github.com/CyCoreSystems/cycore-web/app/routes"
"github.com/CyCoreSystems/sendinblue"
"github.com/revel/revel"
)
var contactEmailT *template.Template
func init() {
contactEmailT = template.Must(template.New("contactEmail").Parse(contactEmailTemplate))
}
// Contact controller handles customer contact operations
type Contact struct {
*revel.Controller
}
// ContactRequest handles a customer contact request
func (c Contact) ContactRequest(name, email string) revel.Result {
c.Log.Info("received contact request", "name", name, "email", email, "source", c.ClientIP)
if name == "" {
c.Flash.Error("Please supply a name")
return c.Redirect(routes.App.Index())
}
if email == "" {
c.Flash.Error("Please supply an email address")
return c.Redirect(routes.App.Index())
}
emailBody, err := c.renderContactEmail(name, email)
if err != nil {
c.Log.Error("failed to render contact email", "error", err)
c.Flash.Error("Internal error encountered; please try again")
return c.Redirect(routes.App.Index())
}
msg := &sendinblue.Message{
Sender: &sendinblue.Address{
Name: "CyCore Systems, Inc",
Email: "sys@cycoresys.com",
},
To: c.getEmailContacts(),
Subject: "Contact Request",
HTMLContent: emailBody,
Tags: []string{"contact-request"},
}
if err = msg.Send(os.Getenv("SENDINBLUE_APIKEY")); err != nil {
c.Log.Error("failed to send contact email", "error", err)
c.Flash.Error("Request failed")
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(msg)
return c.Redirect(routes.App.Index())
}
c.Flash.Success("Request sent")
return c.Redirect(routes.App.Index())
}
func (c Contact) renderContactEmail(name, email string) (string, error) {
buf := new(bytes.Buffer)
err := contactEmailT.Execute(buf, struct {
Name string
Email string
Timestamp string
}{
Name: name,
Email: email,
Timestamp: time.Now().String(),
})
if err != nil {
return "", err
}
return buf.String(), nil
}
func (c Contact) getEmailContacts() []*sendinblue.Address {
var ret []*sendinblue.Address
if err := json.Unmarshal([]byte(os.Getenv("CONTACT_RECIPIENTS")), &ret); err != nil {
// Fall back to default if we fail to load from environment
c.Log.Warn("failed to load recipients from environment", "error", err)
ret = append(ret, &sendinblue.Address{
Name: "System Receiver",
Email: "sys@cycoresys.com",
})
}
return ret
}
var contactEmailTemplate = `
<html>
<body>
<h3>Web Contact Form</h3>
<ul>
<li><b>Name:</b> {{.Name}}</li>
<li><b>Email:</b> {{.Email}}</li>
<li><b>Timestamp:</b> {{.Timestamp}}</li>
</ul>
</body>
</html>
`

View file

@ -1,59 +0,0 @@
package app
import (
"os"
"github.com/revel/log15"
"github.com/revel/revel"
)
func init() {
// Filters is the default set of global filters.
revel.Filters = []revel.Filter{
revel.PanicFilter, // Recover from panics and display an error page instead.
revel.RouterFilter, // Use the routing table to select the right Action
revel.FilterConfiguringFilter, // A hook for adding or removing per-Action filters.
revel.ParamsFilter, // Parse parameters into Controller.Params.
revel.SessionFilter, // Restore and write the session cookie.
revel.FlashFilter, // Restore and write the flash cookie.
revel.ValidationFilter, // Restore kept validation errors and save new ones from cookie.
//revel.I18nFilter, // Resolve the requested language
HeaderFilter, // Add some security based headers
revel.InterceptorFilter, // Run interceptors around the action.
revel.CompressFilter, // Compress the result.
revel.ActionInvoker, // Invoke the action.
}
// register startup functions with OnAppStart
// ( order dependent )
// revel.OnAppStart(InitDB)
// revel.OnAppStart(FillCache)
revel.OnAppStart(InitLogger)
}
// HeaderFilter is a default set of headers to be added
// TODO turn this into revel.HeaderFilter
// should probably also have a filter for CSRF
// not sure if it can go in the same filter or not
var HeaderFilter = func(c *revel.Controller, fc []revel.Filter) {
// Add some common security headers
c.Response.Out.Header().Add("X-Frame-Options", "SAMEORIGIN")
c.Response.Out.Header().Add("X-XSS-Protection", "1; mode=block")
c.Response.Out.Header().Add("X-Content-Type-Options", "nosniff")
fc[0](c, fc[1:]) // Execute the next filter stage.
}
// InitLogger initializes the logging handler
func InitLogger() {
// If we are running inside kubernetes, use the oklog logger
if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
h, err := log15.NetHandler("tcp", "oklog.log:7651", log15.JsonFormat())
if err != nil {
revel.AppLog.Error("failed to construct network log handler", "error", err)
return
}
revel.RevelLog.SetHandler(h)
}
}

View file

@ -1,64 +0,0 @@
<style type="text/css">
#sidebar {
position: absolute;
right: 0px;
top:69px;
max-width: 75%;
z-index: 1000;
background-color: #fee;
border: thin solid grey;
padding: 10px;
}
#toggleSidebar {
position: absolute;
right: 0px;
top: 50px;
background-color: #fee;
}
</style>
<div id="sidebar" style="display:none;">
<h4>Available pipelines</h4>
<dl>
{{ range $index, $value := .}}
<dt>{{$index}}</dt>
<dd>{{$value}}</dd>
{{end}}
</dl>
<h4>Flash</h4>
<dl>
{{ range $index, $value := .flash}}
<dt>{{$index}}</dt>
<dd>{{$value}}</dd>
{{end}}
</dl>
<h4>Errors</h4>
<dl>
{{ range $index, $value := .errors}}
<dt>{{$index}}</dt>
<dd>{{$value}}</dd>
{{end}}
</dl>
</div>
<a id="toggleSidebar" href="#" class="toggles"><i class="glyphicon glyphicon-chevron-left"></i></a>
<script>
$sidebar = 0;
$('#toggleSidebar').click(function() {
if ($sidebar === 1) {
$('#sidebar').hide();
$('#toggleSidebar i').addClass('glyphicon-chevron-left');
$('#toggleSidebar i').removeClass('glyphicon-chevron-right');
$sidebar = 0;
}
else {
$('#sidebar').show();
$('#toggleSidebar i').addClass('glyphicon-chevron-right');
$('#toggleSidebar i').removeClass('glyphicon-chevron-left');
$sidebar = 1;
}
return false;
});
</script>

View file

@ -1,25 +0,0 @@
<div class="footer">
<div class="credit">
<a href="http://www.cycoresys.com">
Site by CyCore Systems
</a>
</div>
<div class="copy">
&copy; 2016 CyCore Systems
</div>
<!--
<div class="social">
<a href="">
<i class="fa fa-twitter"></i>
</a>
</div>
-->
</div>
{{if eq .RunMode "dev"}}
{{template "debug.html" .}}
{{end}}
</body>
</html>

12
assets/css/alert.import.less vendored Normal file
View file

@ -0,0 +1,12 @@
.alert {
width: 100%;
text-align: center;
}
.alert-error {
color: @red;
}
.alert-success {
color: @green;
}

1
assets/css/anim.import.less vendored Normal file
View file

@ -0,0 +1 @@
//ANIMATIONS

164
assets/css/base.import.less vendored Normal file
View file

@ -0,0 +1,164 @@
//PRESETS
body, div, nav, ul, li, a, label, span, table {
margin: 0;
padding: 0;
float: left;
box-sizing: border-box;
background-clip: border-box;
}
body {
min-height: 100%;
height: 100%;
background: @background;
}
ul {
list-style: none;
}
div {
float: left;
}
a {
text-decoration: none;
font-weight: bold;
padding: 5px 10px;
.border-radius(7px);
.fasttrans;
color: @key;
}
a:hover { .fasttrans; color: @keylight;}
.hide { display: none; }
//GRID COLUMNS
.twelve { .column(12); }
.ten { .column(10); }
.nine { .column(9); }
.eight { .column(8); }
.six { .column(6); }
.five { .column(5); }
.four { .column(4); }
.three { .column(3); }
.two { .column(2); }
//STRUCTURES
#page {
width: 100%;
height: 100%;
min-height: 500px;
background: white;
}
.unit { //used for tables
overflow: hidden;
.background-linear-gradient(#fff 25%, @lightgrey);
border: 1px solid @lightgrey;
.border-radius(5px);
.box-shadow(1px 1px 1px 0 rgba(0, 0, 0, 0.6));
}
.contentunit {
.background-linear-gradient(white, fadeout(@keylight, 98%));
.border-radius(10px);
border: 2px solid fadeout(@keylight, 60%);
}
.transcontentunit {
.contentunit;
background: transparent none;
border: 2px solid transparent;
}
.colorcontentunit {
.contentunit;
background: @key;
border: 2px solid @key;
font-size: 1.3em;
color: white;
padding: 20px;
}
label {
width: 40%;
text-align: right;
margin-left: 7px;
}
i {
color: white;
display: block;
text-align: center;
margin: 0 auto;
background-color: @key;
.border-radius(150px);
.box-shadow(0px 1px 2px 0 @darkestgrey);
.fasttrans;
}
i:hover { background-color: @keylight; .fasttrans; }
.social i {
font-size: 26px;
height: 32px;
width: 42px;
padding-top: 10px;
color: white;
}
.hide { display: none; }
.tag {
display: block;
background: fadeout(@keylight, 80%);
.border-radius(2px);
padding: 2px 5px;
margin: 2px 10px 2px 0;
border: 1px solid @lightestgrey;
color: @darkgrey;
font-weight: bold;
}
.footer {
position: relative;
width: 100%;
height: 50px;
padding: 50px 30px 0 30px;
.copy, .credit {
.sans;
font-size: 16px;
font-weight: normal;
line-height: 30px;
margin: 0;
width: auto;
padding: 0 10px;
}
.credit a {
margin: 0;
padding: 0;
line-height: 30px;
color: @key;
}
.credit a:hover {
color: @keydark;
}
.copy {
color: @darkgrey;
float: right;
}
.login {
float: right;
a { .sans; }
}
.social {
float: right;
height: 100%;
padding-right: 50px;
}
}

82
assets/css/button.import.less vendored Normal file
View file

@ -0,0 +1,82 @@
//BUTTONS
button { border: 0 none; }
button.demo {
background-color: @key;
border-top: 1px solid @keylight;
border-bottom: 1px solid @transblack;
.box-shadow(
inset 0 0 20px 5px @transblack,
inset 0 0 0 0 @keylight,
0 2px 4px 0 @lightblack);
text-shadow: -1px -1px 0 black, 1px 1px 0 @keylight;
color: white;
.slowtrans;
i {
color: @key;
font-size: 85px;
text-shadow: 0 0 0 transparent;
vertical-align: bottom;
position: relative;
top: -11px;
}
}
button.demo:hover {
// background-color: @keylight;
.box-shadow(
inset 0 0 20px 5px @transblack,
inset 0px 10px 50px 0 fadeout(@keylight, 70%),
0 2px 4px 0 @lightblack);
text-shadow: -1px -1px 0 black, 1px 1px 0 @key;
.slowtrans;
i {
color: white;
text-shadow: 0 0 4px @key;
.fasttrans;
}
}
button.large {
width: 800px;
margin: 0 auto;
height: 100px;
.border-radius(8px);
padding-top: 7px;
font-size: 80px;
line-height: 100px;
}
button.medium {
.column(5, 8);
.push(2);
height: 60px;
.border-radius(6px);
margin-top: 20px;
font-size: 40px;
line-height: 60px;
i { font-size: 50px; top: -6px; }
}
button.green:hover { background-color: @green; .fasttrans; }
button.small {
.column(2);
.push(2);
margin-top: 100px;
height: 50px;
.border-radius(6px);
font-size: 40px;
line-height: 50px;
}
button:focus { outline: none; }

76
assets/css/fonts.import.less vendored Normal file
View file

@ -0,0 +1,76 @@
//CHANGE IMPORT AND open/sans/bold
@import url(https://fonts.googleapis.com/css?family=Montserrat|Rokkitt:700|Rubik+Mono+One);
.open { font-family: Verdana, sans-serif; }
.sans { font-family: Monserrat, sans-serif; }
.bold { font-family: Rokkitt, sans-serif; }
* {
font-size: 16px;
line-height: 24px;
}
a {
font-weight: bold;
}
.nav a { .open; }
.brand {
.open;
display: block;
color: @grey;
margin: 0 auto;
text-align: center;
line-height: 48px;
}
.light {
color: white;
text-shadow: 1px 1px 2px @darkgrey, -1px -1px 1px @darkestgrey;
}
h1, h2, h3, h4, h5, h6 { .open; }
h1 {
font-size: 5em;
position: relative;
color: @red;
word-wrap: break-word;
font-weight: normal;
line-height: 2em;
text-shadow: -1px -1px 0 black;
}
h2 {
font-size: 3em;
line-height: 1.2em;
color: @key;
}
h3 {
font-size: 1.2em;
padding-left: 10px; padding-right: 10px;
}
h4 {
font-size: 2.2em;
line-height: 1em;
color: @keylight;
text-align: center;
}
h5, h6 { color: @darkergrey; }
h5 {
font-size: 1em;
}
h6 {
font-size: 1.1em;
}
em {
color: @keydark;
font-style: italic;
font-weight: normal;
padding: 2px;
.border-radius(2px);
}
.fix { color: @red; }

34
assets/css/forms.import.less vendored Normal file
View file

@ -0,0 +1,34 @@
label, .label, input, select, textarea {
font-size: 20px;
line-height: 20px;
display: inline-block;
.border-radius(3px);
vertical-align: top;
padding: 5px 10px;
border: 0 none;
float: left;
width: auto;
}
input, select, textarea {
}
input {
border: 1px solid @lightgrey;
}
input[type=submit] {
.bold;
padding: 7px 20px;
margin-left: 10px;
background: @keylight;
color: white;
.fasttrans;
}
input[type=submit]:hover, input[type=submit]:active {
background: saturate(@contrast, 20%);
.fasttrans;
}

65
assets/css/grid.import.less vendored Normal file
View file

@ -0,0 +1,65 @@
/////////////////
// Semantic.gs // for LESS: http://lesscss.org/
/////////////////
// Defaults which you can freely override
@column-width: 60;
@gutter-width: 0;
@columns: 12;
// Utility variable — you should never need to modify this
@gridsystem-width: (@column-width*@columns) + (@gutter-width*@columns) * 1px;
// Set @total-width to 100% for a fluid layout
@total-width: 100%;
// Uncomment these two lines and the star-hack width/margin lines below to enable sub-pixel fix for IE6 & 7. See http://tylertate.com/blog/2012/01/05/subpixel-rounding.html
// @min-width: 960;
// @correction: 0.5 / @min-width * 100 * 1%;
// The micro clearfix http://nicolasgallagher.com/micro-clearfix-hack/
.clearfix() {
*zoom:1;
&:before,
&:after {
content:"";
display:table;
}
&:after {
clear:both;
}
}
//////////
// GRID //
//////////
body {
width: 100%;
.clearfix;
}
.row(@columns:@columns) {
display: block;
width: @total-width*((@gutter-width + @gridsystem-width)/@gridsystem-width);
margin: 0 @total-width*(((@gutter-width*.5)/@gridsystem-width)*-1);
// *width: @total-width*((@gutter-width + @gridsystem-width)/@gridsystem-width)-@correction;
// *margin: 0 @total-width*(((@gutter-width*.5)/@gridsystem-width)*-1)-@correction;
.clearfix;
}
.column(@x,@columns:@columns) {
display: inline;
float: left;
width: @total-width*((((@gutter-width+@column-width)*@x)-@gutter-width) / @gridsystem-width);
margin: 0 @total-width*((@gutter-width*.5)/@gridsystem-width);
// *width: @total-width*((((@gutter-width+@column-width)*@x)-@gutter-width) / @gridsystem-width)-@correction;
// *margin: 0 @total-width*((@gutter-width*.5)/@gridsystem-width)-@correction;
}
.push(@offset:1) {
margin-left: @total-width*(((@gutter-width+@column-width)*@offset) / @gridsystem-width) + @total-width*((@gutter-width*.5)/@gridsystem-width);
}
.pull(@offset:1) {
margin-right: @total-width*(((@gutter-width+@column-width)*@offset) / @gridsystem-width) + @total-width*((@gutter-width*.5)/@gridsystem-width);
}

87
assets/css/master.less Normal file
View file

@ -0,0 +1,87 @@
@import 'grid.import.less';
@import 'var.import.less';
@import 'fonts.import.less';
@import 'rules.import.less';
@import 'anim.import.less';
@import 'base.import.less';
@import 'nav.import.less';
@import 'forms.import.less';
@import 'tables.import.less';
@import 'button.import.less';
@import 'media.import.less';
@import 'alert.import.less';
//ADD CUSTOM AND OVERRIDE CODE HERE
//HOME
#mainlogo {
background: url('/img/logo/Cycore_web_optimized.jpg')
center center no-repeat;
background-size: contain;
height: 500px;
}
#mainstatement {
color: @keydark;
text-align: center;
}
#maincontact {
h4 { text-align: center; }
form { padding: 20px;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
}
input { margin: 2px; flex: 1 0 auto; }
}
#mainservice {
padding: 0 10px;
ul { .twelve;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-around;
align-content: stretch;
li {
display: block;
height: 5em;
width: auto;
margin: 10px;
flex: 1 0 auto;
font-size: 1.5em;
font-weight: bold;
color: @darkergrey;
text-align: center;
padding: 1em;
padding-top: 2em;
.stripes;
}
.one {background-color: @contrast; }
.two {background-color: lighten(#2196f3, 10%);}
.three {background-color: fadeout(#4caf50, 10%);}
}
}

14
assets/css/media.import.less vendored Normal file
View file

@ -0,0 +1,14 @@
//COLUMN RESPONSE
//add here
//ADD BREAKPOINTS IF NECESSARY
@media all and (max-width: 1180px) {
}

39
assets/css/nav.import.less vendored Normal file
View file

@ -0,0 +1,39 @@
//NAV MENUS AND LINKS
.nav {
position: fixed;
height: 50px;
margin: 0 0;
padding: 0;
z-index: 1000;
background: black ;
background-size: contain;
display: flex;
justify-content: space-between;
align-items: center;
.logo { padding: 7px 0 0 0;
width: auto;
}
a {
display: block;
flex: 0 0 auto;
padding-top: 18px;
.two;
.slowtrans;
z-index: 1000;
color: @keylight;
}
a:hover {
.fasttrans;
}
}

142
assets/css/rules.import.less vendored Normal file
View file

@ -0,0 +1,142 @@
.placeholder (@color) {
input::-webkit-input-placeholder { color: @color; padding: 7px 10px 3px 10px;}
input:-moz-placeholder { color: @color;padding: 7px 10px 3px 10px; }
input::-moz-placeholder { color: @color;padding: 7px 10px 3px 10px; }
input:-ms-input-placeholder { color: @color;padding: 7px 10px 3px 10px; }
}
.border-radius (@radius) {
border-radius: @radius;
-moz-border-radius: @radius;
-webkit-border-radius: @radius;
background-clip: padding-box;
-moz-background-clip: padding;
-webkit-background-clip: padding-box;
}
.background-linear-gradient (@color1, @color2) {
background: linear-gradient(top, @color1, @color2);
background: -moz-linear-gradient(@color1, @color2); //FF3.6-
background: -webkit-linear-gradient(top, @color1, @color2); //S5.1, Chrome 10-
background: -o-linear-gradient(top, @color1, @color2); //O 11.1
background: -ms-linear-gradient(@color1, @color2); //IE10
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='@color1', endColorstr='@color2'); //IE6-7
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='@color1', endColorstr='@color2')"; //IE8-9
}
.background-linear-gradient (@color1, @color2, @color3) {
background: linear-gradient(top, @color1, @color2, @color3);
background: -moz-linear-gradient(@color1, @color2, @color3); //FF3.6-
background: -webkit-linear-gradient(top, @color1, @color2, @color3); //S5.1, Chrome 10-
background: -o-linear-gradient(top, @color1, @color2, @color3); //O 11.1
background: -ms-linear-gradient(@color1, @color2, @color3); //IE10
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='@color1', endColorstr='@color3'); //IE6-7
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr='@color1', endColorstr='@color3')"; //IE8-9
}
.background-gradient (@degrees, @color1, @color2, @color3, @color4, @color5, @color6, @color7) {
background-image: linear-gradient(@degrees, @color1, @color2, @color3, @color4, @color5, @color6, @color7);
background-image: -moz-linear-gradient(@degrees, @color1, @color2, @color3, @color4, @color5, @color6, @color7);
background-image: -webkit-linear-gradient(@degrees, @color1, @color2, @color3, @color4, @color5, @color6, @color7);
background-image: -o-linear-gradient(@degrees, @color1, @color2, @color3, @color4, @color5, @color6, @color7);
background-image: -ms-linear-gradient(@degrees, @color1, @color2, @color3, @color4, @color5, @color6, @color7);
}
.box-shadow (none) {
box-shadow: none;
-moz-box-shadow: none;
-webkit-box-shadow: none;
}
.box-shadow (@shadow) {
box-shadow: @shadow;
-moz-box-shadow: @shadow;
-webkit-box-shadow: @shadow;
}
.box-shadow (@shadow1, @shadow2) {
box-shadow: @shadow1, @shadow2;
-moz-box-shadow: @shadow1, @shadow2;
-webkit-box-shadow: @shadow1, @shadow2;
}
.box-shadow (@shadow1, @shadow2, @shadow3) {
box-shadow: @shadow1, @shadow2, @shadow3;
-moz-box-shadow: @shadow1, @shadow2, @shadow3;
-webkit-box-shadow: @shadow1, @shadow2, @shadow3;
}
.transition (@property, @duration, @function, @delay) {
-webkit-transition: @property @duration @function @delay;
-moz-transition: @property @duration @function @delay;
-o-transition: @property @duration @function @delay;
-ms-transition: @property @duration @function @delay;
transition: @property @duration @function @delay;
}
.transition (@property1, @duration1, @function1, @delay1, @property2, @duration2, @function2, @delay2) {
-webkit-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2;
-moz-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2;
-o-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2;
-ms-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2;
transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2;
}
.transition (@property1, @duration1, @function1, @delay1, @property2, @duration2, @function2, @delay2, @property3, @duration3, @function3, @delay3) {
-webkit-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3;
-moz-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3;
-o-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3;
-ms-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3;
transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3;
}
.transition (@property1, @duration1, @function1, @delay1, @property2, @duration2, @function2, @delay2, @property3, @duration3, @function3, @delay3, @property4, @duration4, @function4, @delay4) {
-webkit-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3, @property4 @duration4 @function4 @delay4;
-moz-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3, @property4 @duration4 @function4 @delay4;
-o-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3, @property4 @duration4 @function4 @delay4;
-ms-transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3, @property4 @duration4 @function4 @delay4;
transition: @property1 @duration1 @function1 @delay1, @property2 @duration2 @function2 @delay2, @property3 @duration3 @function3 @delay3, @property4 @duration4 @function4 @delay4;
}
.transition (inherit) {
-webkit-transition: inherit;
-moz-transition: inherit;
-o-transition: inherit;
-ms-transition: inherit;
transition: inherit;
}
.fasttrans { .transition(all, 0.4s, ease, 0s); }
.slowtrans { .transition(all, 2s, ease, 0s); }
.rotate (@deg) {
-webkit-transform: rotate(@deg);
-moz-transform: rotate(@deg);
-o-transform: rotate(@deg);
-ms-transform: rotate(@deg);
transform: rotate(@deg);
}
.scale(@amount) {
-webkit-transform: scale(@amount);
-moz-transform: scale(@amount);
-ms-transform: scale(@amount);
transform: scale(@amount);
}
.flipX {
-webkit-transform: scaleX(-1);
-moz-transform: scaleX(-1);
-ms-transform: scaleX(-1);
-o-transform: scaleX(-1);
transform: scaleX(-1);
}
//TEXTURES
.polka {
background:
radial-gradient(@lighttransblack 15%, transparent 16%) 0 0,
radial-gradient(@lighttransblack 15%, transparent 16%) 8px 8px,
radial-gradient(rgba(255,255,255,.01) 15%, transparent 20%) 0 1px,
radial-gradient(rgba(255,255,255,.01) 15%, transparent 20%) 8px 9px;
background-size: 8px 8px;
}
.stripes {
background: repeating-linear-gradient(
45deg,
transparent,
transparent 10px,
@transwhite 10px,
@transwhite 20px
);
}

49
assets/css/tables.import.less vendored Normal file
View file

@ -0,0 +1,49 @@
//TABLE
table {
.unit;
border-collapse: collapse;
background-clip: padding-box;
}
td, th {
margin: 0;
text-align: center;
vertical-align: middle;
padding: 7px 5px;
}
@color1: fadeout(#fff, 40%);
@color2: fadeout(lighten(desaturate(@highlightblue, 20%), 40%), 45%);
tr:nth-child(2n+1) td {
background: @color1;
}
tr:nth-child(2n) td {
background: @color2;
}
th {
background: @lightergrey;
color: @darkgrey;
}
tr:hover td:nth-child(1n) {
background-color: @highlightyellow;
}
td.header {
background: transparent !important;
color: @darkergrey;
text-shadow: 1px 1px 1px #fff;
font-weight: bold;
text-align: right;
padding-right: 15px;
}
table i {
color: @darkergrey;
}
table i:hover {
color: fadeout(@lightlightblue, 30%);
}
.large { width: 80%; margin-left: 10%; }

54
assets/css/var.import.less vendored Normal file
View file

@ -0,0 +1,54 @@
//COLOR VARIABLES
@background: white;
@border: @transblack;
//BRAND COLORS
@key: #2d65af; //CHANGE ME!! NOT >>
@keylight: #38b4e7;
@keylightest: lighten(@key, 25%);
@keydark: darken(@key, 15%);
@keydarker: darken(@key, 29%);
@keydarkest: darken(@key, 36%);
@contrast: #ff9900;
@contrastdark: darken(@contrast, 28%);
@contrastlight: lighten(@contrast, 15%);
@contrastlightest: lighten(@contrast, 23%);
//FUNCTION COLORS
@lightlightblue: lighten(@highlightblue, 14%);
@highlightblue: #0069ff;
@darkhighlightblue: darken(@highlightblue, 10%);
@orange: #FF8300;
@highlightyellow: #fcf4b5;
@gold: #FFEF02;
@red: #aa0000;
@purple: #1100ff;
@green: #008d00;
@darkgreen: darken(desaturate(@green, 10%), 10%);
//MONO
@lightestgrey: #eee;
@lightergrey: #ddd;
@lightgrey: #ccc;
@grey: #aaa;
@darkgrey: #888;
@darkergrey: #555;
@darkestgrey: #222;
@lighttransblack: rgba(0,0,0,0.02);
@transblack: rgba(0, 0, 0, 0.2);
@lightblack: rgba(0, 0, 0, 0.5);
@midblack: rgba(0, 0, 0, 0.8);
@transwhite: rgba(255, 255, 255, 0.05);
@lightwhite: rgba(255, 255, 255, 0.1);
@midwhite: rgba(255, 255, 255, 0.6);

BIN
assets/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

52
assets/js/home.js Normal file
View file

@ -0,0 +1,52 @@
function getCookie(name) {
if (!document.cookie) {
return null;
}
const xsrfCookies = document.cookie.split(';')
.map(c => c.trim())
.filter(c => c.startsWith(name + '='));
if (xsrfCookies.length === 0) {
return null;
}
return decodeURIComponent(xsrfCookies[0].split('=')[1]);
}
function contactRequest() {
var name = document.querySelector('input[name=name]').value
var email = document.querySelector('input[name=email]').value
var success = document.querySelector('div.alert-success')
var failure = document.querySelector('div.alert-error')
// Reset response holders
success.innerHTML = ''
failure.innerHTML = ''
fetch("contact/request", {
body: JSON.stringify({
name: name,
email: email
}),
headers: new Headers({
'Content-Type': 'application/json',
'X-XSRF-TOKEN': getCookie('_csrf')
}),
method: 'POST'
})
.then(function(resp) {
if(resp.ok) {
success.innerHTML = 'Request Sent'
return
}
return resp.json()
})
.then(function(o) {
failure.innerHTML = o.message
})
.catch( function(err) {
failure.innerHTML = 'network error'
})
}

View file

@ -1,211 +0,0 @@
################################################################################
# Revel configuration file
# See:
# http://revel.github.io/manual/appconf.html
# for more detailed documentation.
################################################################################
# This sets the `AppName` variable which can be used in your code as
# `if revel.AppName {...}`
app.name = cycore-web
# A secret string which is passed to cryptographically sign the cookie to prevent
# (and detect) user modification.
# Keep this string secret or users will be able to inject arbitrary cookie values
# into your application
app.secret = pggqixEuKBuN8nuwrUAkTyrjCnx5YtRNh4Y7TIHvmRKh0SXrqcqPxDny6UtMyxLL
# Revel running behind proxy like nginx, haproxy, etc
app.behind.proxy = false
# The IP address on which to listen.
http.addr = 0.0.0.0
# The port on which to listen.
http.port = 9000
# Whether to use SSL or not.
http.ssl = false
# Path to an X509 certificate file, if using SSL.
#http.sslcert =
# Path to an X509 certificate key, if using SSL.
#http.sslkey =
# For any cookies set by Revel (Session,Flash,Error) these properties will set
# the fields of:
# http://golang.org/pkg/net/http/#Cookie
#
# Each cookie set by Revel is prefixed with this string.
cookie.prefix = CYCORE
# A secure cookie has the secure attribute enabled and is only used via HTTPS,
# ensuring that the cookie is always encrypted when transmitting from client to
# server. This makes the cookie less likely to be exposed to cookie theft via
# eavesdropping.
#
# In dev mode, this will default to false, otherwise it will
# default to true.
# cookie.secure = false
# Limit cookie access to a given domain
#cookie.domain =
# Define when your session cookie expires. Possible values:
# "720h"
# A time duration (http://golang.org/pkg/time/#ParseDuration) after which
# the cookie expires and the session is invalid.
# "session"
# Sets a session cookie which invalidates the session when the user close
# the browser.
session.expires = 720h
# The date format used by Revel. Possible formats defined by the Go `time`
# package (http://golang.org/pkg/time/#Parse)
format.date = 2006-01-02
format.datetime = 2006-01-02 15:04
# Timeout specifies a time limit for request (in seconds) made by a single client.
# A Timeout of zero means no timeout.
timeout.read = 90
timeout.write = 60
# Determines whether the template rendering should use chunked encoding.
# Chunked encoding can decrease the time to first byte on the client side by
# sending data before the entire template has been fully rendered.
results.chunked = false
# Prefixes for each log message line
# User can override these prefix values within any section
# For e.g: [dev], [prod], etc
log.trace.prefix = "TRACE "
log.info.prefix = "INFO "
log.warn.prefix = "WARN "
log.error.prefix = "ERROR "
# The default language of this application.
i18n.default_language = en
# The default format when message is missing.
# The original message shows in %s
#i18n.unknown_format = "??? %s ???"
# Module to serve static content such as CSS, JavaScript and Media files
# Allows Routes like this:
# `Static.ServeModule("modulename","public")`
module.static=github.com/revel/modules/static
################################################################################
# Section: dev
# This section is evaluated when running Revel in dev mode. Like so:
# `revel run path/to/myapp`
[dev]
# This sets `DevMode` variable to `true` which can be used in your code as
# `if revel.DevMode {...}`
# or in your templates with
# `<no value>`
mode.dev = true
# Pretty print JSON/XML when calling RenderJson/RenderXml
results.pretty = true
# Automatically watches your applicaton files and recompiles on-demand
watch = true
# If you set watch.mode = "eager", the server starts to recompile
# your application every time your application's files change.
watch.mode = "normal"
# Watch the entire $GOPATH for code changes. Default is false.
#watch.gopath = true
# Module to run code tests in the browser
# See:
# http://revel.github.io/manual/testing.html
module.testrunner = github.com/revel/modules/testrunner
# Where to log the various Revel logs
log.trace.output = off
log.info.output = stderr
log.warn.output = stderr
log.error.output = stderr
# Revel log flags. Possible flags defined by the Go `log` package,
# please refer https://golang.org/pkg/log/#pkg-constants
# Go log is "Bits or'ed together to control what's printed"
# Examples:
# 0 => just log the message, turn off the flags
# 3 => log.LstdFlags (log.Ldate|log.Ltime)
# 19 => log.Ldate|log.Ltime|log.Lshortfile
# 23 => log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile
log.trace.flags = 19
log.info.flags = 19
log.warn.flags = 19
log.error.flags = 19
# Revel request access log
# Access log line format:
# RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath
# Sample format:
# 2016/05/25 17:46:37.112 127.0.0.1 200 270.157µs GET /
log.request.output = stderr
################################################################################
# Section: prod
# This section is evaluated when running Revel in production mode. Like so:
# `revel run path/to/myapp prod`
# See:
# [dev] section for documentation of the various settings
[prod]
mode.dev = false
results.pretty = false
watch = false
module.testrunner =
log.trace.output = off
log.info.output = off
log.warn.output = stderr
log.error.output = stderr
# Revel log flags. Possible flags defined by the Go `log` package,
# please refer https://golang.org/pkg/log/#pkg-constants
# Go log is "Bits or'ed together to control what's printed"
# Examples:
# 0 => just log the message, turn off the flags
# 3 => log.LstdFlags (log.Ldate|log.Ltime)
# 19 => log.Ldate|log.Ltime|log.Lshortfile
# 23 => log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile
log.trace.flags = 3
log.info.flags = 3
log.warn.flags = 3
log.error.flags = 3
# Revel request access log
# Access log line format:
# RequestStartTime ClientIP ResponseStatus RequestLatency HTTPMethod URLPath
# Sample format:
# 2016/05/25 17:46:37.112 127.0.0.1 200 270.157µs GET /
# Example:
# log.request.output = %(app.name)s-request.log
log.request.output = off
app.behind.proxy = true
app.secret = ${COOKIE_SECRET}

View file

@ -1,22 +0,0 @@
# Routes
# This file defines all application routes (Higher priority routes first)
# ~~~~
module:testrunner
GET / App.Index
# Handle contact requests
POST /contact/request Contact.ContactRequest
# Ignore favicon requests
GET /favicon.ico 404
# Map static resources from the /app/public folder to the /public path
GET /public/*filepath Static.Serve("public")
# GPG Public key
GET /scm.asc Static.Serve("public", "scm.asc")
# Catch all
* /:controller/:action :controller.:action

124
contact.go Normal file
View file

@ -0,0 +1,124 @@
package main
import (
"bytes"
"encoding/json"
"errors"
"html/template"
"net/http"
"os"
"time"
"github.com/CyCoreSystems/sendinblue"
"github.com/labstack/echo"
)
var contactEmailT *template.Template
func init() {
contactEmailT = template.Must(template.New("contactEmail").Parse(contactEmailTemplate))
}
// ContactRequest describes the parameters of a contact request
type ContactRequest struct {
Name string `json:"name" form:"name" query:"name"`
Email string `json:"email" form:"email" query:"email"`
}
// contactRequest handles a customer contact request
func contactRequest(c echo.Context) (err error) {
cc := c.(*Context)
req := new(ContactRequest)
if err = cc.Bind(req); err != nil {
cc.Log.Warnf("failed to parse input: %s", err.Error())
return c.JSON(http.StatusBadRequest, NewError(errors.New("failed to read request")))
}
cc.Log.Debugf(`received contact request from "%s" <%s> (%s)`, req.Name, req.Email, c.RealIP())
if req.Name == "" {
cc.Log.Warn("empty name")
return c.JSON(http.StatusBadRequest, NewError(errors.New("please supply a name")))
}
if req.Email == "" {
cc.Log.Warn("empty email")
return c.JSON(http.StatusBadRequest, NewError(errors.New("please supply an email")))
}
emailBody, err := renderContactEmail(req.Name, req.Email)
if err != nil {
cc.Log.Errorf("failed to render email body: %s", err.Error())
return c.JSON(http.StatusInternalServerError, NewError(errors.New("internal error; please retry")))
}
msg := &sendinblue.Message{
Sender: &sendinblue.Address{
Name: "CyCore Systems, Inc",
Email: "sys@cycoresys.com",
},
To: getEmailContacts(),
Subject: "Contact Request",
HTMLContent: emailBody,
Tags: []string{"contact-request"},
}
if err = msg.Send(os.Getenv("SENDINBLUE_APIKEY")); err != nil {
cc.Log.Errorf("failed to send contact email: %s", err.Error())
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
enc.Encode(msg)
return c.JSON(http.StatusBadGateway, NewError(errors.New("internal error; please retry")))
}
cc.Log.Debug("contact request sent")
return nil
}
func renderContactEmail(name, email string) (string, error) {
buf := new(bytes.Buffer)
err := contactEmailT.Execute(buf, struct {
Name string
Email string
Timestamp string
}{
Name: name,
Email: email,
Timestamp: time.Now().String(),
})
if err != nil {
return "", err
}
return buf.String(), nil
}
func getEmailContacts() []*sendinblue.Address {
var ret []*sendinblue.Address
if err := json.Unmarshal([]byte(os.Getenv("CONTACT_RECIPIENTS")), &ret); err != nil {
// Fall back to default if we fail to load from environment
ret = append(ret, &sendinblue.Address{
Name: "System Receiver",
Email: "sys@cycoresys.com",
})
}
return ret
}
var contactEmailTemplate = `
<html>
<body>
<h3>Web Contact Form</h3>
<ul>
<li><b>Name:</b> {{.Name}}</li>
<li><b>Email:</b> {{.Email}}</li>
<li><b>Timestamp:</b> {{.Timestamp}}</li>
</ul>
</body>
</html>
`

18
context.go Normal file
View file

@ -0,0 +1,18 @@
package main
import (
"github.com/jmoiron/sqlx"
"github.com/labstack/echo"
"go.uber.org/zap"
)
// Context is the custom context for this web server
type Context struct {
echo.Context
// DB is the database connection
DB *sqlx.DB
// Log is the core logger
Log *zap.SugaredLogger
}

45
db/db.go Normal file
View file

@ -0,0 +1,45 @@
package db
import (
"fmt"
"os"
"os/user"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // load Postgresql driver here, since we have no access to main
)
var db *sqlx.DB
// Connect attempts to establish a database connection
func Connect() error {
if db != nil {
return nil
}
var username = "nobody"
u, err := user.Current()
if err == nil {
username = u.Username
}
dsn := fmt.Sprintf("postgresql://%s@localhost:26257/cycore?sslmode=disable", username)
if os.Getenv("DSN") != "" {
dsn = os.Getenv("DSN")
}
db, err = sqlx.Open("postgres", dsn)
return err
}
// Get returns a database connection handle for the database
func Get() *sqlx.DB {
if err := Connect(); err != nil {
panic("no database connection: " + err.Error())
}
return db
}

4
dev.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
go generate
go build
./cycore-web -debug

100
main.go Normal file
View file

@ -0,0 +1,100 @@
package main
//go:generate esc -o static.go -prefix assets -ignore \.map$ assets
import (
"flag"
"html/template"
"net/http"
"github.com/CyCoreSystems/cycore-web/db"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"go.uber.org/zap"
)
var addr string
var debug bool
// Error indicates an error from processing
type Error struct {
Message string `json:"message"`
}
// NewError converts a standard error to an error response
func NewError(err error) *Error {
return &Error{
Message: err.Error(),
}
}
func init() {
flag.StringVar(&addr, "addr", ":9000", "listen address")
flag.BoolVar(&debug, "debug", false, "run with debug logging")
}
func main() {
flag.Parse()
var err error
var logger *zap.Logger
if debug {
logger, err = zap.NewDevelopment()
} else {
logger, err = zap.NewProduction()
}
if err != nil {
panic("failed to create logger: " + err.Error())
}
defer logger.Sync() // nolint
log := logger.Sugar()
err = db.Connect()
if err != nil {
log.Panicf("failed to open database: %v", err)
}
defer db.Get().Close() // nolint
e := echo.New()
//e.Use(middleware.CSRF())
e.Use(middleware.Gzip())
e.Use(middleware.Logger())
e.Use(middleware.Recover())
//e.Use(middleware.Secure())
// Create custom context
e.Use(func(h echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
cc := &Context{
Context: c,
DB: db.Get(),
Log: log,
}
return h(cc)
}
})
e.Renderer = &Template{
templates: template.Must(template.ParseGlob("views/*.html")),
}
assets := http.FileServer(FS(debug))
e.GET("/css/*", echo.WrapHandler(assets))
e.GET("/img/*", echo.WrapHandler(assets))
e.GET("/js/*", echo.WrapHandler(assets))
e.GET("/scm.asc", echo.WrapHandler(assets))
e.GET("/", home)
e.POST("/contact/request", contactRequest)
log.Fatal(e.Start(addr))
}
func home(c echo.Context) error {
return c.Render(http.StatusOK, "index.html", nil)
}

1261
static.go Normal file

File diff suppressed because it is too large Load diff

18
templates.go Normal file
View file

@ -0,0 +1,18 @@
package main
import (
"html/template"
"io"
"github.com/labstack/echo"
)
// Template implements echo.Renderer
type Template struct {
templates *template.Template
}
// Render executes the selected template with the given data
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}

View file

@ -1,21 +0,0 @@
package tests
import "github.com/revel/revel/testing"
type AppTest struct {
testing.TestSuite
}
func (t *AppTest) Before() {
println("Set up")
}
func (t *AppTest) TestThatIndexPageWorks() {
t.Get("/")
t.AssertOk()
t.AssertContentType("text/html; charset=utf-8")
}
func (t *AppTest) After() {
println("Tear down")
}

View file

@ -1,5 +1,5 @@
<h4>Set up a no-obligation fifteen minute call now
<form action="/contact/request" method="POST">
<form action="#" onsubmit="contactRequest()">
<input type="text" name="name" placeholder="Your Name" REQUIRED />
<input type="email" name="email" placeholder="Your Email" REQUIRED />
<input type="submit" name="submit" value="Let's Chat" />

12
views/footer.html Normal file
View file

@ -0,0 +1,12 @@
<div class="footer">
<div class="credit">
<a href="http://www.cycoresys.com">Site by CyCore Systems</a>
</div>
<div class="copy">
&copy; 2018 CyCore Systems
</div>
</div>
</body>
</html>

View file

@ -2,18 +2,18 @@
<html>
<head>
<title>{{.title}}</title>
<title>CyCore Systems, Inc</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="CyCore Systems, Inc.">
<link rel="shortcut icon" type="image/png" href="/public/img/favicon.png">
<link rel="stylesheet/less" type="text/css" href="/public/css/master.less"/>
<link rel="shortcut icon" type="image/png" href="/img/favicon.png">
<link rel="stylesheet/less" type="text/css" href="/css/master.less"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/less.js/2.7.1/less.min.js"></script>
{{range .moreStyles}}
<link rel="stylesheet" type="text/css" href="/public/{{.}}">
{{end}}
{{range .moreScripts}}
<script src="/public/{{.}}" type="text/javascript" charset="utf-8"></script>
<script src="/js/{{.}}" type="text/javascript" charset="utf-8"></script>
{{end}}
</head>
<body>

View file

@ -1,4 +1,3 @@
{{set . "title" "CyCore Systems"}}
{{template "header.html" .}}
<div id="page">
@ -11,7 +10,9 @@
</h2>
</div>
<div class="transcontentunit twelve" id="maincontact">
{{template "flash.html" .}}
<div class="alert alert-success"></div>
<div class="alert alert-error"></div>
{{template "contact.html" .}}
</div>
<div class="transcontentunit twelve" id="mainservice">
@ -28,4 +29,6 @@
</div>
</div>
<script src="/js/home.js" type="text/javascript" charset="utf-8"></script>
{{template "footer.html" .}}