Browse Source

Update index page. Buttons don't work

master
stew3254 3 years ago
parent
commit
58d635a8b3
  1. 1
      .gitignore
  2. 14
      API-REFERENCE.md
  3. 9
      Dockerfile
  4. 8
      docker-compose.yml
  5. 2
      go.mod
  6. 69
      html/css/index.css
  7. 32
      html/css/sidenav.css
  8. 0
      html/imgs/something.png
  9. 82
      html/index.html
  10. 4
      html/js/index.js
  11. 2
      sql/drop.sql
  12. 2
      sql/setup.sql
  13. 25
      src/db.go
  14. 12
      src/helper.go
  15. 39
      src/hook.go
  16. 10
      src/main.go
  17. 120
      src/server.go

1
.gitignore

@ -1,3 +1,4 @@
*.env
!.env-template
bin/
.vscode

14
API-REFERENCE.md

@ -0,0 +1,14 @@
# API Reference Documentation
This guide hopefully will explain how the API works. Any method undocumented, you can assume it will return a 405 error. If you go to a page that doesn't exist, it will return a 404 error.
## Index
* / "GET" -> A page designed for web users to go to in order to learn about creating hooks
## Hooks
* /hook/ "GET" -> Returns a list of all hooks in json form
* /hook/<hook_name> "GET" -> Does whatever your hook is configured to do on a GET. If nothing configured, return the hook in json form. If it doesn't exist, return 404
* /hook/<hook_name> "POST" -> Does whatever your hook is configured to do on a GET. If nothing configured, return the hook in json form. If it doesn't exist, return 404
* /hook/<hook_name> "PUT" -> Submit the hook parameters and this will create the hook if it doesn't exist, or update it if it does. (CURRENTLY NOT IMPLEMENTED)
* /hook/<hook_name> "DELETE" -> Delete the hook if it exists, does nothing if it doesn't
* /hook/<hook_name> "" -> Delete the hook if it exists, do nothing if it doesn't
* /hook/<hook_name>/configure "GET" -> Gives the web friendly configuration page for your hook (CURRENTLY NOT IMPLEMENTED)

9
Dockerfile

@ -2,9 +2,8 @@ FROM golang:latest
LABEL maintainer="Ryan Stewart <stewarrt@clarkson.edu>"
# USER www-data
WORKDIR /srv/website
EXPOSE 5000
COPY .env *sql go.mod go.sum ./
EXPOSE 3000
RUN go get "github.com/codegangsta/gin"
COPY go.mod go.sum ./
RUN go mod download
COPY src/* src/
RUN go build -o main src/*
CMD ./main
CMD gin -t src -b bin -a 5000 -i run main.go

8
docker-compose.yml

@ -1,4 +1,3 @@
# Use root/example as user/password credentials
version: '3.7'
services:
@ -16,8 +15,11 @@ services:
restart: unless-stopped
env_file: .env
ports:
- "5000:5000"
- "3000:3000"
depends_on:
- "db"
volumes:
- ./html:/srv/website/html
- ./html:/srv/website/html
- ./src:/srv/website/src
- ./sql:/srv/website/sql
- ./.env:/srv/website/.env

2
go.mod

@ -4,5 +4,5 @@ go 1.14
require (
github.com/go-sql-driver/mysql v1.5.0
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/errors v0.9.1
)

69
html/css/index.css

@ -1,16 +1,67 @@
/* Style page content */
html, body {
margin: 0;
padding: 0;
height: 100%;
background-color: #252525;
}
.main {
margin-left: 160px; /* Same as the width of the sidebar */
padding: 0px 20px;
margin: 0 12.5vw;
padding: 2vh 2vw;
min-height: 100%;
display: flex;
flex: 1
}
.main h1 {
font-weight: bold;
margin-bottom: 2vh;
}
.main h2 {
margin-top: 5vh;
margin-bottom: 2vh;
font-size: 1.25em;
font-weight: bold;
margin-top: 3vh;
margin-bottom: 2vh;
}
.main button {
padding: 0;
height: 50px;
min-width: 90px;
font-size: 1.2rem;
text-transform: uppercase;
color: white;
border: none;
}
.delete-button {
background-color: red;
}
.modify-button {
background-color: blue;
}
.create-button {
background-color: green;
}
.list-group-item {
padding: 0;
border: 1px solid;
}
.create, .hook {
display: flex;
}
.create input {
width: 100%;
margin: 0;
}
/* Debug */
.center {
margin-left: 160px; /* Same as the width of the sidebar */
position: absolute center;
.hook p {
margin: 0;
padding: 0 20px;
height: 100%;
width: 100%;
display: flex;
align-items: center;
}

32
html/css/sidenav.css

@ -1,32 +0,0 @@
/* The sidebar menu */
.sidenav {
height: 100%; /* Full-height: remove this if you want "auto" height */
width: 160px; /* Set the width of the sidebar */
position: fixed; /* Fixed Sidebar (stay in place on scroll) */
z-index: 1; /* Stay on top */
top: 0; /* Stay at the top */
left: 0;
background-color: #111; /* Black */
overflow-x: hidden; /* Disable horizontal scroll */
padding-top: 20px;
}
/* The navigation menu links */
.sidenav a {
padding: 6px 8px 6px 16px;
text-decoration: none;
font-size: 25px;
color: #818181;
display: block;
}
/* When you mouse over the navigation links, change their color */
.sidenav a:hover {
color: #f1f1f1;
}
/* On smaller screens, where height is less than 450px, change the style of the sidebar (less padding and a smaller font size) */
@media screen and (max-height: 450px) {
.sidenav {padding-top: 15px;}
.sidenav a {font-size: 18px;}
}

0
html/imgs/something.png

82
html/index.html

@ -1,44 +1,54 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSS only -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/sidenav.css">
<link rel="stylesheet" type="text/css" href="/css/index.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Captain Hook</title>
<!-- CSS only -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="/css/index.css">
</head>
<body>
<div class="sidenav container" >
<a href="/hook/create/">Create</a>
<a href="/hook/delete/">Delete</a>
</div>
<div class="test"></div>
<div class="main container-fluid">
<div id="header">
<h2>Captain Hook</h2>
<p>
A webhook server written in Go.
</p>
<div class="main card">
<div class="container-fluid">
<div id="header">
<h1>Captain Hook</h1>
<p>
A webhook server written in Go.
</p>
</div>
<h2>Create Hook</h2>
<ul class="list-group">
<li class="list-group-item">
<div class="create">
<input type="text" name="create">
<button type="submit" class="create-button">Create</button>
</div>
</li>
</ul>
{{if . -}}
<h2>Currently available hooks</h2>
<ul class="list-group hooks">
{{range . -}}
<li class="list-group-item">
<div class="hook">
<p>{{.Name}}</p>
<button type="submit" class="modify-button">Modify</button>
<button type="submit" class="delete-button">Delete</button>
</div>
</li>
{{- end}}
</ul>
{{- else}}
<h2>No hooks have been created</h2>
{{- end}}
</div>
</div>
{{if . -}}
<h4>Currently available hooks</h4>
<ul>
{{range . -}}
<li>{{.Name}}</li>
{{- end}}
</ul>
{{- else}}
<p><strong>No hooks have been created</strong></p>
{{- end}}
</div>
<div class="center">Testing</div>
<!-- Bootstrap JS -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<!-- <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> -->
<!-- Bootstrap JS -->
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/js/bootstrap.min.js" integrity="sha384-OgVRvuATP1z7JjHLkuOU7Xw704+h835Lr+6QL9UvYjZE3Ipu6Tp75j7Bh/kR0JKI" crossorigin="anonymous"></script>
<!-- <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> -->
<script src="/js/index.js"></script>
</body>
</html>

4
html/js/index.js

@ -0,0 +1,4 @@
// document.getElementById("aStupidButton").onclick = function (event) {
// event.preventDefault();
// // do stuff
// };

2
sql/drop.sql

@ -1 +1 @@
DROP TABLE IF EXISTS HOOKS;
DROP TABLE IF EXISTS Hooks;

2
sql/setup.sql

@ -1,4 +1,4 @@
CREATE TABLE IF NOT EXISTS HOOKS
CREATE TABLE IF NOT EXISTS Hooks
(
ID varchar(64) PRIMARY KEY
);

25
src/db.go

@ -42,17 +42,16 @@ func connectDB() (*sql.DB, error) {
if !exists {
attemptsStr = "5"
}
attempts, err := strconv.Atoi(attemptsStr)
attempts, err := strconv.Atoi(attemptsStr)
if err != nil {
attempts = 5
}
timeoutStr, exists := os.LookupEnv("DB_CONNECTION_TIMEOUT")
if !exists {
timeoutStr = "5"
}
timeout, err := strconv.Atoi(timeoutStr)
timeout, err := strconv.Atoi(timeoutStr)
if err != nil {
timeout = 5
}
@ -61,6 +60,7 @@ func connectDB() (*sql.DB, error) {
if err != nil {
return db, err
}
for i := 1; i <= attempts; i++ {
//// DEBUG: Making sure table is good
// if err := dropDB(db); err != nil {
@ -93,17 +93,14 @@ func connectDB() (*sql.DB, error) {
return db, nil
}
// func queryRow(db *sql.DB, name string) (*sql.Rows, error) {
// b, err := ioutil.ReadFile(name)
// if err != nil {
// return nil, err
// }
// return db.QueryRow(string(b)), nil
// }
// func query(db *sql.DB, name string) (interface{}, error) {
// db.Qu
// }
func pingDB(db *sql.DB) {
for {
if err := db.Ping(); err != nil {
log.Println("Failed to connect to database, Connection died")
}
time.Sleep(time.Second*5)
}
}
func execSQL(db *sql.DB, name string) error {
b, err := ioutil.ReadFile(name)

12
src/helper.go

@ -2,6 +2,7 @@ package main
import (
"log"
"net/http"
)
// Check errors and fail if they're bad
@ -11,6 +12,17 @@ func check(err error) {
}
}
// Log weird errors for web stuff
func logError(w http.ResponseWriter, err error) {
if err != nil {
log.Println(err)
writeError(w, errorMessage{
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
}
}
// Check if a value exists and fail if it doesn't
func checkExists(exists bool, msg string) {
if !exists {

39
src/hook.go

@ -6,39 +6,34 @@ import (
"net/http"
)
// HookHandler is an interface that satisfies an http handler.
// Something else here
type HookHandler interface {
ServeHTTP(http.ResponseWriter, *http.Request)
}
// Condition for a hook. It contains something to select.
// This could be a piece of json post data, a method, or http header
// The tester is the operation to do on the selector.
// For example: AND, OR, NOT, LESS THAN, GREATER THAN, EQUALS, CONTAINS
type Condition struct {
selector string
tester string
value string
}
type Hook struct {
Name string
params []Condition
actions []string
}
type ByName []Hook
func (h ByName) Len() int {
return len(h)
actions []Action
}
func (h ByName) Less(i, j int) bool {
return h[i].Name < h[j].Name
// Action results from when a condition is expressed
// For example, an action could run if a selector returns TRUE when tested
// while another action could be run when FALSE is returned
type Action interface {
Run() error
}
func (h ByName) Swap(i, j int) {
h[i], h[j] = h[j], h[i]
}
func (h Hook) String() string {
return h.Name
// Hook that will be run when a user hits the appropriate URL
// with the right methods and parameters.
type Hook struct {
Name string
conditions []Condition
}
func (h Hook) ServeHTTP(w http.ResponseWriter, r *http.Request) {

10
src/main.go

@ -1,12 +1,11 @@
package main
import (
"net/http"
"log"
"net/http"
"os"
)
// func init() {
// // Loads values from .env into the system
// if err := godotenv.Load(); err != nil {
@ -16,11 +15,12 @@ import (
func main() {
var err error
db, err = connectDB();
db, err = connectDB()
if err != nil {
log.Fatal(err)
}
defer db.Close()
go pingDB(db)
// Get listen address and port
addr, exists := os.LookupEnv("LISTEN")
@ -32,8 +32,10 @@ func main() {
http.HandleFunc("/hook/", hookHandler)
http.HandleFunc("/", index)
http.Handle("/css/", http.StripPrefix("/css/", http.FileServer(http.Dir("html/css"))))
http.Handle("/imgs/", http.StripPrefix("/imgs/", http.FileServer(http.Dir("html/imgs"))))
http.Handle("/js/", http.StripPrefix("/js/", http.FileServer(http.Dir("html/js"))))
// Start the server
log.Println("Starting server")
log.Fatal(http.ListenAndServe(addr+":"+port, nil))
}
}

120
src/server.go

@ -2,10 +2,13 @@ package main
import (
"database/sql"
"encoding/json"
"fmt"
"html/template"
"log"
"net/http"
"os"
_ "github.com/go-sql-driver/mysql"
)
@ -15,7 +18,7 @@ var db *sql.DB
// Used in writing out errors when they occur
type errorMessage struct {
StatusCode int
Msg string
Msg string
}
// Writes the given error message in the template. If not, just writes the message raw
@ -34,64 +37,60 @@ func writeError(w http.ResponseWriter, e errorMessage) {
// Get all the currently available hooks
func getHooks(w http.ResponseWriter) []Hook {
// Get all hooks from db
rows, err := db.Query("SELECT ID FROM HOOKS")
rows, err := db.Query("SELECT ID FROM Hooks")
if err != nil {
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
logError(w, err)
}
defer rows.Close()
// Look through hooks
var hooks []Hook
hooks := make([]Hook, 0)
for rows.Next() {
var hook Hook
err = rows.Scan(&hook.Name)
// Hook doesn't have a name (This shouldn't really happen)
if err != nil {
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
logError(w, err)
}
hooks = append(hooks, hook)
}
return hooks
}
// Run a particular hook (will probably come up with a better solution)
func runHook(w http.ResponseWriter, r *http.Request, hook Hook) {
b, err := json.Marshal(hook)
if err != nil {
logError(w, err)
} else {
fmt.Fprint(w, string(b))
}
}
// Show the index
func index(w http.ResponseWriter, r *http.Request) {
// Log request to this url
log.Println(r.URL.String())
log.Println(r.URL.String(), r.Method)
if r.URL.String() == "/" {
// Serve index template
t := template.Must(template.New("index.html").ParseFiles("html/index.html"))
err := t.Execute(w, getHooks(w))
if err != nil {
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
logError(w, err)
}
// Can't find the page
// Can't find the page
} else {
writeError(w, errorMessage {
writeError(w, errorMessage{
StatusCode: http.StatusNotFound,
Msg: "Not found",
Msg: "Not Found",
})
}
}
// Run a particular hook (will probably come up with a better solution)
func runHook(w http.ResponseWriter, r *http.Request, hook Hook) {
fmt.Fprintln(w, hook.Name)
}
// Handles requests to /hook/
func hookHandler(w http.ResponseWriter, r *http.Request) {
// Log request to this url
log.Println(r.URL.String())
log.Println(r.URL.String(), r.Method)
if r.URL.String() == "/hook/" {
switch r.Method {
// Display all hooks
@ -100,9 +99,9 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
// Method unsupported
default:
log.Println("y tho")
writeError(w, errorMessage {
writeError(w, errorMessage{
StatusCode: http.StatusMethodNotAllowed,
Msg: "Method Unsupported",
Msg: "Method Unsupported",
})
}
return
@ -115,23 +114,22 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
// Handle the hook
case http.MethodGet, http.MethodPost:
// See if the hook exists
row := db.QueryRow("SELECT EXISTS(SELECT id FROM HOOKS WHERE HOOKS.id = ?)", name)
row := db.QueryRow("SELECT EXISTS(SELECT id FROM Hooks WHERE Hooks.id = ?)", name)
var res int
row.Scan(&res)
// Hook doesn't exist
if res == 0 {
writeError(w, errorMessage {
writeError(w, errorMessage{
StatusCode: http.StatusNotFound,
Msg: "Not Found",
Msg: "Not Found",
})
return
}
// Construct the hook from the results
hook := Hook {
Name: name,
params: nil,
actions: nil,
hook := Hook{
Name: name,
conditions: nil,
}
// Run the user's hook
@ -147,9 +145,9 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
// Method unsupported
default:
writeError(w, errorMessage {
writeError(w, errorMessage{
StatusCode: http.StatusMethodNotAllowed,
Msg: "Method Not Allowed",
Msg: "Method Not Allowed",
})
}
}
@ -157,27 +155,21 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
// Shows all of the hooks
func showHooks(w http.ResponseWriter) {
hooks := getHooks(w)
allHooks := ""
if hooks != nil {
for _, h := range hooks {
allHooks += h.Name + "\n"
}
b, err := json.Marshal(hooks)
if err != nil {
logError(w, err)
} else {
allHooks = "Nothing Found\n"
fmt.Fprint(w, string(b))
}
fmt.Fprint(w, allHooks)
}
// Creates a new hook
func createHook(w http.ResponseWriter, name string) {
statement, err := db.Prepare("INSERT IGNORE INTO HOOKS(id) VALUES(?)")
statement, err := db.Prepare("INSERT IGNORE INTO Hooks(id) VALUES(?)")
// Shouldn't really happen, but incase the insert goes wrong
if err != nil {
log.Fatal(err)
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
logError(w, err)
os.Exit(1)
return
}
defer statement.Close()
@ -187,14 +179,10 @@ func createHook(w http.ResponseWriter, name string) {
// Delete a hook
func deleteHook(w http.ResponseWriter, name string) {
statement, err := db.Prepare("DELETE FROM HOOKS where ID=(?)")
statement, err := db.Prepare("DELETE FROM Hooks WHERE ID=(?)")
//Shouldn't ever error
if err != nil {
log.Println(err)
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
logError(w, err)
return
}
defer statement.Close()
@ -203,30 +191,16 @@ func deleteHook(w http.ResponseWriter, name string) {
res, err := statement.Exec(name)
// Shouldn't ever error
if err != nil {
log.Println(err)
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: err.Error(),
})
logError(w, err)
return
}
// Probably also shouldn't error
n, err := res.RowsAffected()
_, err = res.RowsAffected()
if err != nil {
log.Println(err)
fmt.Fprintln(w, err)
return
}
// Nothing was deleted
if n == 0 {
writeError(w, errorMessage {
StatusCode: http.StatusInternalServerError,
Msg: "Hook was not deleted",
})
logError(w, err)
return
}
fmt.Fprintln(w, name)
}
}
Loading…
Cancel
Save