feat: removed non svelte front page

This commit is contained in:
Andre Henriques 2024-03-09 10:52:08 +00:00
parent 0d37ba8d59
commit 274d7d22aa
32 changed files with 614 additions and 3695 deletions

View File

@ -86,14 +86,12 @@ func loadBaseImage(c *Context, id string) {
} }
func handleAdd(handle *Handle) { func handleAdd(handle *Handle) {
handle.GetHTML("/models/add", AnswerTemplate("models/add.html", nil, 1)) handle.Post("/models/add", func(c *Context) *Error {
handle.Post("/models/add", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { if !c.CheckAuthLevel(1) {
if !CheckAuthLevel(1, w, r, c) {
return nil return nil
} }
if c.Mode == JSON { read_form, err := c.R.MultipartReader()
read_form, err := r.MultipartReader()
if err != nil { if err != nil {
return c.JsonErrorBadRequest(err, "Please provide a valid multipart Reader request") return c.JsonErrorBadRequest(err, "Please provide a valid multipart Reader request")
} }
@ -164,93 +162,5 @@ func handleAdd(handle *Handle) {
go loadBaseImage(c, id) go loadBaseImage(c, id)
return c.SendJSON(id) return c.SendJSON(id)
}
read_form, err := r.MultipartReader()
if err != nil {
LoadBasedOnAnswer(c.Mode, w, "models/add.html", c.AddMap(nil))
return nil
}
var name string
var file []byte
for {
part, err_part := read_form.NextPart()
if err_part == io.EOF {
break
} else if err_part != nil {
return &Error{Code: http.StatusBadRequest}
}
if part.FormName() == "name" {
buf := new(bytes.Buffer)
buf.ReadFrom(part)
name = buf.String()
}
if part.FormName() == "file" {
buf := new(bytes.Buffer)
buf.ReadFrom(part)
file = buf.Bytes()
}
}
if name == "" || len(file) == 0 {
LoadBasedOnAnswer(c.Mode, w, "models/add.html", c.AddMap(nil))
return nil
}
row, err := handle.Db.Query("select id from models where name=$1 and user_id=$2;", name, c.User.Id)
if err != nil {
return c.Error500(err)
}
if row.Next() {
LoadBasedOnAnswer(c.Mode, w, "models/add.html", c.AddMap(AnyMap{
"NameFoundError": true,
"Name": name,
}))
return nil
}
_, err = handle.Db.Exec("insert into models (user_id, name) values ($1, $2)", c.User.Id, name)
if err != nil {
return c.Error500(err)
}
row, err = handle.Db.Query("select id from models where name=$1 and user_id=$2;", name, c.User.Id)
if err != nil {
return c.Error500(err)
}
if !row.Next() {
return &Error{Code: http.StatusInternalServerError}
}
var id string
err = row.Scan(&id)
if err != nil {
return c.Error500(err)
}
// TODO mk this path configurable
dir_path := path.Join("savedData", id)
err = os.Mkdir(dir_path, os.ModePerm)
if err != nil {
return c.Error500(err)
}
f, err := os.Create(path.Join(dir_path, "baseimage.png"))
if err != nil {
return c.Error500(err)
}
defer f.Close()
f.Write(file)
c.Logger.Warn("Created model with id %s! Started to proccess image!\n", "id", id)
go loadBaseImage(c, id)
Redirect("/models/edit?id="+id, c.Mode, w, r)
return nil
}) })
} }

View File

@ -1,23 +1,26 @@
package model_classes package model_classes
import ( import (
"net/http"
"strconv" "strconv"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/utils"
"git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
) )
func models_data_list_json(w http.ResponseWriter, r *http.Request, c *Context) *Error { func HandleList(handle *Handle) {
id, err := GetIdFromUrl(r, "id") handle.Get("/models/data/list", func(c *Context) *Error {
if !c.CheckAuthLevel(1) {
return nil
}
id, err := GetIdFromUrl(c, "id")
if err != nil { if err != nil {
return c.JsonBadRequest("Model Class not found!") return c.JsonBadRequest("Model Class not found!")
} }
page := 0 page := 0
if r.URL.Query().Has("page") { if c.R.URL.Query().Has("page") {
page_url := r.URL.Query().Get("page") page_url := c.R.URL.Query().Get("page")
page_url_number, err := strconv.Atoi(page_url) page_url_number, err := strconv.Atoi(page_url)
if err != nil { if err != nil {
return c.JsonBadRequest("Page is not a number") return c.JsonBadRequest("Page is not a number")
@ -62,87 +65,5 @@ func models_data_list_json(w http.ResponseWriter, r *http.Request, c *Context) *
Page: page, Page: page,
ShowNext: len(rows) == 11, ShowNext: len(rows) == 11,
}) })
}
func HandleList(handle *Handle) {
handle.Get("/models/data/list", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) {
return nil
}
if c.Mode == JSON {
return models_data_list_json(w, r, c)
}
id, err := GetIdFromUrl(r, "id")
if err != nil {
return ErrorCode(err, 400, c.AddMap(nil))
}
page := 0
if r.URL.Query().Has("page") {
page_url := r.URL.Query().Get("page")
page_url_number, err := strconv.Atoi(page_url)
if err != nil {
return ErrorCode(err, http.StatusBadRequest, c.AddMap(nil))
}
page = page_url_number
}
class_rows, err := handle.Db.Query("select name, model_id from model_classes where id=$1;", id)
if err != nil {
return Error500(err)
}
defer class_rows.Close()
if !class_rows.Next() {
return ErrorCode(nil, 404, c.AddMap(nil))
}
name := ""
model_id := ""
if err = class_rows.Scan(&name, &model_id); err != nil {
return Error500(nil)
}
model, err := GetBaseModel(c.Db, model_id)
if err != nil {
return Error500(err)
}
rows, err := handle.Db.Query("select id, file_path, model_mode, status from model_data_point where class_id=$1 limit 11 offset $2;", id, page*10)
if err != nil {
return Error500(err)
}
defer rows.Close()
type baserow struct {
Id string
FilePath string
Mode int
Status int
}
got := []baserow{}
for rows.Next() {
nrow := baserow{}
err = rows.Scan(&nrow.Id, &nrow.FilePath, &nrow.Mode, &nrow.Status)
if err != nil {
return Error500(err)
}
got = append(got, nrow)
}
max_len := min(11, len(got))
LoadDefineTemplate(w, "/models/edit.html", "data-model-create-class-table-table", c.AddMap(AnyMap{
"List": got[0:max_len],
"Page": page,
"Id": id,
"Name": name,
"Model": model,
"ShowNext": len(got) == 11,
}))
return nil
}) })
} }

View File

@ -268,13 +268,12 @@ func processZipFileExpand(c *Context, model *BaseModel) {
} }
func handleDataUpload(handle *Handle) { func handleDataUpload(handle *Handle) {
handle.Post("/models/data/upload", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Post("/models/data/upload", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode == JSON { read_form, err := c.R.MultipartReader()
read_form, err := r.MultipartReader()
if err != nil { if err != nil {
return c.JsonBadRequest("Please provide a valid form data request!") return c.JsonBadRequest("Please provide a valid form data request!")
} }
@ -305,7 +304,7 @@ func handleDataUpload(handle *Handle) {
if err == ModelNotFoundError { if err == ModelNotFoundError {
return c.SendJSONStatus(http.StatusNotFound, "Model not found") return c.SendJSONStatus(http.StatusNotFound, "Model not found")
} else if err != nil { } else if err != nil {
return Error500(err) return c.Error500(err)
} }
// TODO mk this path configurable // TODO mk this path configurable
@ -313,7 +312,7 @@ func handleDataUpload(handle *Handle) {
f, err := os.Create(path.Join(dir_path, "base_data.zip")) f, err := os.Create(path.Join(dir_path, "base_data.zip"))
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
defer f.Close() defer f.Close()
@ -324,74 +323,17 @@ func handleDataUpload(handle *Handle) {
go processZipFile(c, model) go processZipFile(c, model)
return c.SendJSON(model.Id) return c.SendJSON(model.Id)
}
read_form, err := r.MultipartReader()
if err != nil {
LoadBasedOnAnswer(c.Mode, w, "models/add.html", c.AddMap(nil))
return nil
}
var id string
var file []byte
for {
part, err_part := read_form.NextPart()
if err_part == io.EOF {
break
} else if err_part != nil {
return &Error{Code: http.StatusBadRequest}
}
if part.FormName() == "id" {
buf := new(bytes.Buffer)
buf.ReadFrom(part)
id = buf.String()
}
if part.FormName() == "file" {
buf := new(bytes.Buffer)
buf.ReadFrom(part)
file = buf.Bytes()
}
}
model, err := GetBaseModel(handle.Db, id)
if err == ModelNotFoundError {
return c.ErrorCode(nil, http.StatusNotFound, AnyMap{
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
} else if err != nil {
return Error500(err)
}
// TODO mk this path configurable
dir_path := path.Join("savedData", id)
f, err := os.Create(path.Join(dir_path, "base_data.zip"))
if err != nil {
return Error500(err)
}
defer f.Close()
f.Write(file)
ModelUpdateStatus(c, id, PREPARING_ZIP_FILE)
go processZipFile(c, model)
Redirect("/models/edit?id="+id, c.Mode, w, r)
return nil
}) })
// ------ // ------
// ------ CLASS DATA UPLOAD // ------ CLASS DATA UPLOAD
// ------ // ------
handle.PostJSON("/models/data/class/upload", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Post("/models/data/class/upload", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
read_form, err := r.MultipartReader() read_form, err := c.R.MultipartReader()
if err != nil { if err != nil {
return c.JsonBadRequest("Please provide a valid form data request!") return c.JsonBadRequest("Please provide a valid form data request!")
} }
@ -424,7 +366,7 @@ func handleDataUpload(handle *Handle) {
if err == ModelNotFoundError { if err == ModelNotFoundError {
return c.SendJSONStatus(http.StatusNotFound, "Model not found") return c.SendJSONStatus(http.StatusNotFound, "Model not found")
} else if err != nil { } else if err != nil {
return Error500(err) return c.Error500(err)
} }
// TODO work in allowing the model to add new in the pre ready moment // TODO work in allowing the model to add new in the pre ready moment
@ -437,7 +379,7 @@ func handleDataUpload(handle *Handle) {
f, err := os.Create(path.Join(dir_path, "expand_data.zip")) f, err := os.Create(path.Join(dir_path, "expand_data.zip"))
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
defer f.Close() defer f.Close()
@ -450,11 +392,10 @@ func handleDataUpload(handle *Handle) {
return c.SendJSON(model.Id) return c.SendJSON(model.Id)
}) })
handle.Delete("/models/data/delete-zip-file", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Delete("/models/data/delete-zip-file", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode == JSON {
type ModelData struct { type ModelData struct {
Id string `json:"id"` Id string `json:"id"`
@ -462,7 +403,7 @@ func handleDataUpload(handle *Handle) {
var dat ModelData var dat ModelData
if err := c.ToJSON(r, &dat); err != nil { if err := c.ToJSON(&dat); err != nil {
return err return err
} }
@ -470,7 +411,7 @@ func handleDataUpload(handle *Handle) {
if err == ModelNotFoundError { if err == ModelNotFoundError {
return c.SendJSONStatus(http.StatusNotFound, "Model not found") return c.SendJSONStatus(http.StatusNotFound, "Model not found")
} else if err != nil { } else if err != nil {
return Error500(err) return c.Error500(err)
} }
delete_path := "base_data.zip" delete_path := "base_data.zip"
@ -483,13 +424,13 @@ func handleDataUpload(handle *Handle) {
err = os.Remove(path.Join("savedData", model.Id, delete_path)) err = os.Remove(path.Join("savedData", model.Id, delete_path))
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
if model.Status != READY_FAILED { if model.Status != READY_FAILED {
err = os.RemoveAll(path.Join("savedData", model.Id, "data")) err = os.RemoveAll(path.Join("savedData", model.Id, "data"))
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
} else { } else {
c.Logger.Warn("Handle failed to remove the savedData when deleteing the zip file while expanding") c.Logger.Warn("Handle failed to remove the savedData when deleteing the zip file while expanding")
@ -498,12 +439,12 @@ func handleDataUpload(handle *Handle) {
if model.Status != READY_FAILED { if model.Status != READY_FAILED {
_, err = handle.Db.Exec("delete from model_classes where model_id=$1;", model.Id) _, err = handle.Db.Exec("delete from model_classes where model_id=$1;", model.Id)
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
} else { } else {
_, err = handle.Db.Exec("delete from model_classes where model_id=$1 and status=$2;", model.Id, MODEL_CLASS_STATUS_TO_TRAIN) _, err = handle.Db.Exec("delete from model_classes where model_id=$1 and status=$2;", model.Id, MODEL_CLASS_STATUS_TO_TRAIN)
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
} }
@ -514,51 +455,5 @@ func handleDataUpload(handle *Handle) {
} }
return c.SendJSON(model.Id) return c.SendJSON(model.Id)
}
f, err := MyParseForm(r)
if err != nil {
return ErrorCode(err, 400, c.AddMap(nil))
}
if !CheckId(f, "id") {
return ErrorCode(err, 400, c.AddMap(nil))
}
id := f.Get("id")
model, err := GetBaseModel(handle.Db, id)
if err == ModelNotFoundError {
return ErrorCode(nil, http.StatusNotFound, AnyMap{
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
} else if err != nil {
return Error500(err)
}
if model.Status != FAILED_PREPARING_ZIP_FILE {
// TODO add message
return ErrorCode(nil, 400, c.AddMap(nil))
}
err = os.Remove(path.Join("savedData", id, "base_data.zip"))
if err != nil {
return Error500(err)
}
err = os.RemoveAll(path.Join("savedData", id, "data"))
if err != nil {
return Error500(err)
}
_, err = handle.Db.Exec("delete from model_classes where model_id=$1;", id)
if err != nil {
return Error500(err)
}
ModelUpdateStatus(c, id, CONFIRM_PRE_TRAINING)
Redirect("/models/edit?id="+id, c.Mode, w, r)
return nil
}) })
} }

View File

@ -4,40 +4,12 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"strconv"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/utils"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
utils "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" utils "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
) )
func deleteModel(handle *Handle, id string, w http.ResponseWriter, c *Context, model BaseModel) {
c.Logger.Warnf("Removing model with id: %s", id)
_, err := handle.Db.Exec("delete from models where id=$1;", id)
if err != nil {
c.Logger.Error(err)
panic("TODO handle better deleteModel failed delete database query")
}
model_path := path.Join("./savedData", id)
c.Logger.Warnf("Removing folder of model with id: %s at %s", id, model_path)
err = os.RemoveAll(model_path)
if err != nil {
c.Logger.Error(err)
panic("TODO handle better deleteModel failed to delete folder")
}
if c.Mode == HTML {
// TODO move this to a constant so i don't forget
w.WriteHeader(309)
c.Mode = HTMLFULL
}
LoadBasedOnAnswer(c.Mode, w, "/models/delete.html", c.AddMap(AnyMap{
"Model": model,
}))
}
func deleteModelJSON(c *Context, id string) *Error { func deleteModelJSON(c *Context, id string) *Error {
c.Logger.Warnf("Removing model with id: %s", id) c.Logger.Warnf("Removing model with id: %s", id)
_, err := c.Db.Exec("delete from models where id=$1;", id) _, err := c.Db.Exec("delete from models where id=$1;", id)
@ -56,16 +28,16 @@ func deleteModelJSON(c *Context, id string) *Error {
} }
func handleDelete(handle *Handle) { func handleDelete(handle *Handle) {
handle.Delete("/models/delete", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Delete("/models/delete", func(c *Context) *Error {
if c.CheckAuthLevel(1) {
if c.Mode == JSON { return nil
}
var dat struct { var dat struct {
Id string `json:"id" validate:"required"` Id string `json:"id" validate:"required"`
Name *string `json:"name,omitempty"` Name *string `json:"name,omitempty"`
} }
if err_ := c.ToJSON(r, &dat); err_ != nil { if err_ := c.ToJSON(&dat); err_ != nil {
return err_ return err_
} }
@ -108,79 +80,5 @@ func handleDelete(handle *Handle) {
c.Logger.Warn("Do not know how to handle model in status", "status", model.Status) c.Logger.Warn("Do not know how to handle model in status", "status", model.Status)
return c.JsonBadRequest("Model in invalid status") return c.JsonBadRequest("Model in invalid status")
} }
}
// This is required to parse delete forms with bodies
f, err := MyParseForm(r)
if err != nil {
return c.ErrorCode(err, 400, nil)
}
if !CheckId(f, "id") {
return c.ErrorCode(nil, http.StatusNotFound, AnyMap{
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
}
id := f.Get("id")
// TODO handle admin users
rows, err := handle.Db.Query("select name, status from models where id=$1 and user_id=$2;", id, c.User.Id)
if err != nil {
return c.ErrorCode(err, http.StatusInternalServerError, nil)
}
defer rows.Close()
if !rows.Next() {
c.Logger.Warn("Could not find model for", id, c.User.Id)
return c.ErrorCode(nil, http.StatusNotFound, AnyMap{
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
}
model := BaseModel{}
model.Id = id
if err = rows.Scan(&model.Name, &model.Status); err != nil {
return c.Error500(err)
}
switch model.Status {
case FAILED_TRAINING:
fallthrough
case FAILED_PREPARING_ZIP_FILE:
fallthrough
case FAILED_PREPARING_TRAINING:
fallthrough
case FAILED_PREPARING:
deleteModel(handle, id, w, c, model)
return nil
case READY:
fallthrough
case CONFIRM_PRE_TRAINING:
if CheckEmpty(f, "name") {
return c.Error400(nil, "Name is empty", w, "/models/edit.html", "delete-model-card", AnyMap{
"NameDoesNotMatch": true,
"Model": model,
})
}
name := f.Get("name")
if name != model.Name {
LoadDefineTemplate(w, "/models/edit.html", "delete-model-card", c.AddMap(AnyMap{
"NameDoesNotMatch": true,
"Model": model,
}))
return nil
}
deleteModel(handle, id, w, c, model)
return nil
default:
panic("Do not know how to handle model in status:" + strconv.Itoa(model.Status))
}
}) })
} }

View File

@ -2,7 +2,6 @@ package models
import ( import (
"fmt" "fmt"
"net/http"
model_classes "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/classes" model_classes "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/classes"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/utils"
@ -10,64 +9,15 @@ import (
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
) )
func handleJson(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) {
return nil
}
id, err := GetIdFromUrl(r, "id")
if err != nil {
return c.JsonBadRequest("Model not found")
}
type rowmodel struct {
Name string `json:"name"`
Status int `json:"status"`
Id string `json:"id"`
Width *int `json:"width"`
Height *int `json:"height"`
Color_mode *string `json:"color_mode"`
Format string `json:"format"`
Model_type int `json:"model_type"`
}
var model rowmodel = rowmodel{}
err = utils.GetDBOnce(c, &model, "models where id=$1 and user_id=$2", id, c.User.Id)
if err == NotFoundError {
return c.SendJSONStatus(404, "Model not found")
} else if err != nil {
return c.Error500(err)
}
return c.SendJSON(model)
/*
switch model.Status {
case TRAINING:
}
*/
}
func handleEdit(handle *Handle) { func handleEdit(handle *Handle) {
handle.Get("/models/edit/classes", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Get("/models/edit/classes", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode != JSON {
return c.ErrorCode(nil, 400, AnyMap{})
}
id, err := GetIdFromUrl(r, "id") model, err_ := c.GetModelFromId("id")
if err != nil { if err_ != nil {
return c.SendJSONStatus(http.StatusNotFound, "Model not found") return err_
}
model, err := GetBaseModel(c.Db, id)
if err == ModelNotFoundError {
return c.SendJSONStatus(http.StatusNotFound, "Model not found")
} else if err != nil {
return c.Error500(err)
} }
wrong_number, err := model_classes.GetNumberOfWrongDataPoints(c.Db, model.Id) wrong_number, err := model_classes.GetNumberOfWrongDataPoints(c.Db, model.Id)
@ -75,14 +25,14 @@ func handleEdit(handle *Handle) {
return c.Error500(err) return c.Error500(err)
} }
cls, err := model_classes.ListClasses(c, id) cls, err := model_classes.ListClasses(c, model.Id)
if err != nil { if err != nil {
return c.Error500(err) return c.Error500(err)
} }
has_data, err := model_classes.ModelHasDataPoints(handle.Db, id) has_data, err := model_classes.ModelHasDataPoints(handle.Db, model.Id)
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
type ReturnType struct { type ReturnType struct {
@ -98,24 +48,14 @@ func handleEdit(handle *Handle) {
}) })
}) })
handle.Get("/models/edit/definitions", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Get("/models/edit/definitions", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode != JSON {
return c.ErrorCode(nil, 400, AnyMap{})
}
id, err := GetIdFromUrl(r, "id") model, err_ := c.GetModelFromId("id")
if err != nil { if err_ != nil {
return c.SendJSONStatus(http.StatusNotFound, "Model not found") return err_
}
model, err := GetBaseModel(c.Db, id)
if err == ModelNotFoundError {
return c.SendJSONStatus(http.StatusNotFound, "Model not found")
} else if err != nil {
return c.Error500(err)
} }
type defrow struct { type defrow struct {
@ -243,217 +183,39 @@ func handleEdit(handle *Handle) {
return c.SendJSON(defsToReturn) return c.SendJSON(defsToReturn)
}) })
handle.Get("/models/edit", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Get("/models/edit", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode == JSON { if !c.CheckAuthLevel(1) {
return handleJson(w, r, c) return nil
} }
id, err := GetIdFromUrl(r, "id") id, err := GetIdFromUrl(c, "id")
if err != nil { if err != nil {
return ErrorCode(nil, http.StatusNotFound, AnyMap{ return c.JsonBadRequest("Model not found")
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
}
// TODO handle admin users
rows, err := handle.Db.Query("select name, status, width, height, color_mode, format, model_type from models where id=$1 and user_id=$2;", id, c.User.Id)
if err != nil {
return Error500(err)
}
defer rows.Close()
if !rows.Next() {
return ErrorCode(nil, http.StatusNotFound, AnyMap{
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
} }
type rowmodel struct { type rowmodel struct {
Name string Name string `json:"name"`
Status int Status int `json:"status"`
Id string Id string `json:"id"`
Width *int Width *int `json:"width"`
Height *int Height *int `json:"height"`
Color_mode *string Color_mode *string `json:"color_mode"`
Format string Format string `json:"format"`
Type int Model_type int `json:"model_type"`
} }
var model rowmodel = rowmodel{} var model rowmodel = rowmodel{}
model.Id = id err = utils.GetDBOnce(c, &model, "models where id=$1 and user_id=$2", id, c.User.Id)
if err == NotFoundError {
err = rows.Scan(&model.Name, &model.Status, &model.Width, &model.Height, &model.Color_mode, &model.Format, &model.Type) return c.SendJSONStatus(404, "Model not found")
if err != nil { } else if err != nil {
return Error500(err)
}
// Handle errors
// All errors will be negative
if model.Status < 0 {
LoadBasedOnAnswer(c.Mode, w, "/models/edit.html", c.AddMap(AnyMap{
"Model": model,
}))
return nil
}
switch model.Status {
case PREPARING:
LoadBasedOnAnswer(c.Mode, w, "/models/edit.html", c.AddMap(AnyMap{
"Model": model,
}))
case CONFIRM_PRE_TRAINING:
wrong_number, err := model_classes.GetNumberOfWrongDataPoints(c.Db, model.Id)
if err != nil {
return c.Error500(err) return c.Error500(err)
} }
cls, err := model_classes.ListClasses(c, id) return c.SendJSON(model)
if err != nil {
return c.Error500(err)
}
has_data, err := model_classes.ModelHasDataPoints(handle.Db, id)
if err != nil {
return Error500(err)
}
LoadBasedOnAnswer(c.Mode, w, "/models/edit.html", c.AddMap(AnyMap{
"Model": model,
"Classes": cls,
"HasData": has_data,
"NumberOfInvalidImages": wrong_number,
}))
case READY:
LoadBasedOnAnswer(c.Mode, w, "/models/edit.html", c.AddMap(AnyMap{
"Model": model,
}))
case TRAINING:
type defrow struct {
Id string
Status int
EpochProgress int
Epoch int
Accuracy float64
}
defs := []defrow{}
if model.Type == 2 {
def_rows, err := c.Db.Query("select md.id, md.status, md.epoch, h.epoch_progress, h.accuracy from model_definition as md inner join exp_model_head as h on h.def_id = md.id where md.model_id=$1 order by md.created_on asc", model.Id)
if err != nil {
return c.Error500(err)
}
defer def_rows.Close()
for def_rows.Next() {
var def defrow
err = def_rows.Scan(&def.Id, &def.Status, &def.Epoch, &def.EpochProgress, &def.Accuracy)
if err != nil {
return c.Error500(err)
}
defs = append(defs, def)
}
} else {
def_rows, err := c.Db.Query("select id, status, epoch, epoch_progress, accuracy from model_definition where model_id=$1 order by created_on asc", model.Id)
if err != nil {
return c.Error500(err)
}
defer def_rows.Close()
for def_rows.Next() {
var def defrow
err = def_rows.Scan(&def.Id, &def.Status, &def.Epoch, &def.EpochProgress, &def.Accuracy)
if err != nil {
return c.Error500(err)
}
defs = append(defs, def)
}
}
type layerdef struct {
id string
LayerType int
Shape string
}
layers := []layerdef{}
for _, def := range defs {
if def.Status == MODEL_DEFINITION_STATUS_TRAINING {
rows, err := c.Db.Query("select id, layer_type, shape from model_definition_layer where def_id=$1 order by layer_order asc;", def.Id)
if err != nil {
return c.Error500(err)
}
defer rows.Close()
for rows.Next() {
var layerdef layerdef
err = rows.Scan(&layerdef.id, &layerdef.LayerType, &layerdef.Shape)
if err != nil {
return c.Error500(err)
}
layers = append(layers, layerdef)
}
if model.Type == 2 {
type lastLayerType struct {
Id string
Range_start int
Range_end int
}
var lastLayer lastLayerType
err := GetDBOnce(c, &lastLayer, "exp_model_head where def_id=$1 and status=3;", def.Id)
if err != nil {
return c.Error500(err)
}
layers = append(layers, layerdef{
id: lastLayer.Id,
LayerType: LAYER_DENSE,
Shape: fmt.Sprintf("%d, 1", lastLayer.Range_end-lastLayer.Range_start+1),
}) })
}
break
}
}
sep_mod := 100
if len(layers) > 8 {
sep_mod = 100 - (len(layers)-8)*10
}
if sep_mod < 10 {
sep_mod = 10
}
LoadBasedOnAnswer(c.Mode, w, "/models/edit.html", c.AddMap(AnyMap{
"Model": model,
"Defs": defs,
"Layers": layers,
"SepMod": sep_mod,
}))
case PREPARING_ZIP_FILE:
LoadBasedOnAnswer(c.Mode, w, "/models/edit.html", c.AddMap(AnyMap{
"Model": model,
}))
default:
fmt.Printf("Unkown Status: %d\n", model.Status)
return Error500(nil)
}
return nil
})
} }

View File

@ -1,66 +1,26 @@
package models package models
import ( import (
"net/http"
"git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils" . "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
) )
func handleList(handle *Handle) { func handleList(handle *Handle) {
handle.Get("/models", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Get("/models", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
// TODO handle admin
if c.Mode == JSON {
rows, err := handle.Db.Query("select id, name from models where user_id=$1;", c.User.Id)
if err != nil {
return Error500(err)
}
defer rows.Close()
type Row struct { type Row struct {
Name string `json:"name"` Name string `json:"name"`
Id string `json:"id"` Id string `json:"id"`
} }
got, err := utils.GetDbMultitple[Row](c, "models where user_id=$1", c.User.Id); got, err := utils.GetDbMultitple[Row](c, "models where user_id=$1", c.User.Id)
if err != nil { if err != nil {
c.Logger.Warn("HERE 6")
return c.Error500(nil) return c.Error500(nil)
} }
return c.SendJSON(got) return c.SendJSON(got)
}
rows, err := handle.Db.Query("select id, name from models where user_id=$1;", c.User.Id)
if err != nil {
return Error500(err)
}
defer rows.Close()
type row struct {
Name string
Id string
}
got := []row{}
for rows.Next() {
var r row
err = rows.Scan(&r.Id, &r.Name)
if err != nil {
return Error500(err)
}
got = append(got, r)
}
LoadBasedOnAnswer(c.Mode, w, "/models/list.html", c.AddMap(AnyMap{
"List": got,
}))
return nil
}) })
} }

View File

@ -2,9 +2,7 @@ package models
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"net/http"
"os" "os"
"path" "path"
@ -118,16 +116,14 @@ func runModelExp(c *Context, model *BaseModel, def_id string, inputImage *tf.Ten
} }
func handleRun(handle *Handle) { func handleRun(handle *Handle) {
handle.Post("/models/run", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Post("/models/run", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode == JSON {
read_form, err := r.MultipartReader() read_form, err := c.R.MultipartReader()
if err != nil { if err != nil {
// TODO improve message return c.JsonBadRequest("Invalid muilpart body")
return ErrorCode(nil, 400, nil)
} }
var id string var id string
@ -249,146 +245,5 @@ func handleRun(handle *Handle) {
} }
return c.SendJSON(returnValue) return c.SendJSON(returnValue)
}
read_form, err := r.MultipartReader()
if err != nil {
// TODO improve message
return ErrorCode(nil, 400, nil)
}
var id string
var file []byte
for {
part, err_part := read_form.NextPart()
if err_part == io.EOF {
break
} else if err_part != nil {
return &Error{Code: http.StatusBadRequest}
}
if part.FormName() == "id" {
buf := new(bytes.Buffer)
buf.ReadFrom(part)
id = buf.String()
}
if part.FormName() == "file" {
buf := new(bytes.Buffer)
buf.ReadFrom(part)
file = buf.Bytes()
}
}
model, err := GetBaseModel(handle.Db, id)
if err == ModelNotFoundError {
return ErrorCode(nil, http.StatusNotFound, AnyMap{
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
} else if err != nil {
return Error500(err)
}
if model.Status != READY {
// TODO improve this
return ErrorCode(nil, 400, c.AddMap(nil))
}
def := JustId{}
err = GetDBOnce(c, &def, "model_definition where model_id=$1", model.Id)
if err == NotFoundError {
// TODO improve this
fmt.Printf("Could not find definition\n")
return ErrorCode(nil, 400, c.AddMap(nil))
} else if err != nil {
return Error500(err)
}
def_id := def.Id
// TODO create a database table with tasks
run_path := path.Join("/tmp", model.Id, "runs")
os.MkdirAll(run_path, os.ModePerm)
img_path := path.Join(run_path, "img."+model.Format)
img_file, err := os.Create(img_path)
if err != nil {
return Error500(err)
}
defer img_file.Close()
img_file.Write(file)
if !testImgForModel(c, model, img_path) {
LoadDefineTemplate(w, "/models/edit.html", "run-model-card", c.AddMap(AnyMap{
"Model": model,
"NotFound": false,
"Result": nil,
"ImageError": true,
}))
return nil
}
root := tg.NewRoot()
var tf_img *image.Image = nil
switch model.Format {
case "png":
tf_img = ReadPNG(root, img_path, int64(model.ImageMode))
case "jpeg":
tf_img = ReadJPG(root, img_path, int64(model.ImageMode))
default:
panic("Not sure what to do with '" + model.Format + "'")
}
exec_results := tg.Exec(root, []tf.Output{tf_img.Value()}, nil, &tf.SessionOptions{})
inputImage, err := tf.NewTensor(exec_results[0].Value())
if err != nil {
return Error500(err)
}
vi := -1
var confidence float32 = 0
if model.ModelType == 2 {
c.Logger.Info("Running model normal", "model", model.Id, "def", def_id)
vi, confidence, err = runModelExp(c, model, def_id, inputImage)
if err != nil {
return c.Error500(err)
}
} else {
c.Logger.Info("Running model normal", "model", model.Id, "def", def_id)
vi, confidence, err = runModelNormal(c, model, def_id, inputImage)
if err != nil {
return c.Error500(err)
}
}
os.RemoveAll(run_path)
rows, err := handle.Db.Query("select name from model_classes where model_id=$1 and class_order=$2;", model.Id, vi)
if err != nil {
return Error500(err)
}
if !rows.Next() {
LoadDefineTemplate(w, "/models/edit.html", "run-model-card", c.AddMap(AnyMap{
"Model": model,
"NotFound": true,
"Result": nil,
"Confidence": confidence,
}))
return nil
}
var name string
if err = rows.Scan(&name); err != nil {
return nil
}
LoadDefineTemplate(w, "/models/edit.html", "run-model-card", c.AddMap(AnyMap{
"Model": model,
"Result": name,
}))
return nil
}) })
} }

View File

@ -7,7 +7,4 @@ import (
func HandleTrainEndpoints(handle *Handle) { func HandleTrainEndpoints(handle *Handle) {
handleTrain(handle) handleTrain(handle)
handleRest(handle) handleRest(handle)
//TODO remove
handleTest(handle)
} }

View File

@ -1,7 +1,6 @@
package models_train package models_train
import ( import (
"net/http"
"os" "os"
"path" "path"
@ -10,22 +9,21 @@ import (
) )
func handleRest(handle *Handle) { func handleRest(handle *Handle) {
handle.Delete("/models/train/reset", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Delete("/models/train/reset", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode == JSON {
var dat struct { var dat struct {
Id string `json:"id"` Id string `json:"id"`
} }
if err := c.ToJSON(r, &dat); err != nil { if err := c.ToJSON(&dat); err != nil {
return err; return err
} }
model, err := GetBaseModel(c.Db, dat.Id) model, err := GetBaseModel(c.Db, dat.Id)
if err == ModelNotFoundError { if err == ModelNotFoundError {
return c.JsonBadRequest("Model not found"); return c.JsonBadRequest("Model not found")
} else if err != nil { } else if err != nil {
// TODO improve response // TODO improve response
return c.Error500(err) return c.Error500(err)
@ -45,47 +43,5 @@ func handleRest(handle *Handle) {
ModelUpdateStatus(c, model.Id, CONFIRM_PRE_TRAINING) ModelUpdateStatus(c, model.Id, CONFIRM_PRE_TRAINING)
return c.SendJSON(model.Id) return c.SendJSON(model.Id)
}
f, err := MyParseForm(r)
if err != nil {
// TODO improve response
return c.ErrorCode(nil, 400, c.AddMap(nil))
}
if !CheckId(f, "id") {
// TODO improve response
return c.ErrorCode(nil, 400, c.AddMap(nil))
}
id := f.Get("id")
model, err := GetBaseModel(handle.Db, id)
if err == ModelNotFoundError {
return c.ErrorCode(nil, http.StatusNotFound, AnyMap{
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
} else if err != nil {
// TODO improve response
return c.Error500(err)
}
if model.Status != FAILED_PREPARING_TRAINING && model.Status != FAILED_TRAINING {
// TODO improve response
return c.ErrorCode(nil, 400, c.AddMap(nil))
}
os.RemoveAll(path.Join("savedData", model.Id, "defs"))
_, err = handle.Db.Exec("delete from model_definition where model_id=$1", model.Id)
if err != nil {
// TODO improve response
return c.Error500(err)
}
ModelUpdateStatus(c, model.Id, CONFIRM_PRE_TRAINING)
Redirect("/models/edit?id="+model.Id, c.Mode, w, r)
return nil
}) })
} }

View File

@ -1,175 +0,0 @@
package models_train
import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path"
"strconv"
"strings"
"text/template"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
)
/*import (
tf "github.com/galeone/tensorflow/tensorflow/go"
tg "github.com/galeone/tfgo"
"github.com/galeone/tfgo/image"
"github.com/galeone/tfgo/image/filter"
"github.com/galeone/tfgo/image/padding"
)*/
func getDir() string {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
return dir
}
func shapeToSize(shape string) string {
split := strings.Split(shape, ",")
return strings.Join(split[:len(split) - 1], ",")
}
func handleTest(handle *Handle) {
handle.Post("/models/train/test", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) {
return nil
}
id, err := GetIdFromUrl(r, "id")
if err != nil {
return ErrorCode(err, 400, c.AddMap(nil))
}
rows, err := handle.Db.Query("select mc.name, mdp.file_path from model_classes as mc join model_data_point as mdp on mdp.class_id = mc.id where mdp.model_mode = 1 and mc.model_id = $1 limit 1;", id)
if err != nil {
return Error500(err)
}
defer rows.Close()
if !rows.Next() {
return Error500(err)
}
var name string
var file_path string
err = rows.Scan(&name, &file_path)
if err != nil {
return Error500(err)
}
file_path = strings.Replace(file_path, "file://", "", 1)
img_path := path.Join("savedData", id, "data", "training", name, file_path)
fmt.Printf("%s\n", img_path)
definitions, err := handle.Db.Query("select id from model_definition where model_id=$1 and status=2 limit 1;", id)
if err != nil {
return Error500(err)
}
defer definitions.Close()
if !definitions.Next() {
fmt.Println("Did not find definition")
return Error500(nil)
}
var definition_id string
if err = definitions.Scan(&definition_id); err != nil {
return Error500(err)
}
layers, err := handle.Db.Query("select layer_type, shape from model_definition_layer where def_id=$1 order by layer_order asc;", definition_id)
if err != nil {
return Error500(err)
}
defer layers.Close()
type layerrow struct {
LayerType int
Shape string
}
got := []layerrow{}
for layers.Next() {
var row = layerrow{}
if err = layers.Scan(&row.LayerType, &row.Shape); err != nil {
return Error500(err)
}
row.Shape = shapeToSize(row.Shape)
got = append(got, row)
}
// Generate folder
run_path := path.Join("/tmp", id, "defs", definition_id)
err = os.MkdirAll(run_path, os.ModePerm)
if err != nil {
return Error500(err)
}
f, err := os.Create(path.Join(run_path, "run.py"))
if err != nil {
return Error500(err)
}
defer f.Close()
fmt.Printf("Using path: %s\n", run_path)
tmpl, err := template.New("python_model_template.py").ParseFiles("views/py/python_model_template.py")
if err != nil {
return Error500(err)
}
if err = tmpl.Execute(f, AnyMap{
"Layers": got,
"Size": got[0].Shape,
"DataDir": path.Join(getDir(), "savedData", id, "data", "training"),
}); err != nil {
return Error500(err)
}
cmd := exec.Command("bash", "-c", fmt.Sprintf("cd %s && python run.py", run_path))
_, err = cmd.Output()
if err != nil {
return Error500(err)
}
result_path := path.Join("savedData", id, "defs", definition_id)
os.MkdirAll(result_path, os.ModePerm)
err = exec.Command("cp", path.Join(run_path, "model.keras"), path.Join(result_path, "model.keras")).Run()
if err != nil {
return Error500(err)
}
accuracy_file, err := os.Open(path.Join(run_path, "accuracy.val"))
if err != nil {
return Error500(err)
}
defer accuracy_file.Close()
accuracy_file_bytes, err := io.ReadAll(accuracy_file)
if err != nil {
return Error500(err)
}
accuracy, err := strconv.ParseFloat(string(accuracy_file_bytes), 64)
if err != nil {
return Error500(err)
}
w.Write([]byte(strconv.FormatFloat(accuracy, 'f', -1, 64)))
return nil
})
}

View File

@ -6,12 +6,12 @@ import (
"fmt" "fmt"
"io" "io"
"math" "math"
"net/http"
"os" "os"
"os/exec" "os/exec"
"path" "path"
"sort" "sort"
"strconv" "strconv"
"strings"
"text/template" "text/template"
model_classes "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/classes" model_classes "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/classes"
@ -23,6 +23,19 @@ import (
const EPOCH_PER_RUN = 20 const EPOCH_PER_RUN = 20
const MAX_EPOCH = 100 const MAX_EPOCH = 100
func shapeToSize(shape string) string {
split := strings.Split(shape, ",")
return strings.Join(split[:len(split)-1], ",")
}
func getDir() string {
dir, err := os.Getwd()
if err != nil {
panic(err)
}
return dir
}
func MakeDefenition(db *sql.DB, model_id string, target_accuracy int) (id string, err error) { func MakeDefenition(db *sql.DB, model_id string, target_accuracy int) (id string, err error) {
id = "" id = ""
rows, err := db.Query("insert into model_definition (model_id, target_accuracy) values ($1, $2) returning id;", model_id, target_accuracy) rows, err := db.Query("insert into model_definition (model_id, target_accuracy) values ($1, $2) returning id;", model_id, target_accuracy)
@ -706,7 +719,7 @@ func trainModelExp(c *Context, model *BaseModel) {
definitions, err = GetDbMultitple[TrainModelRowUsable](c, "model_definition where status=$1 and model_id=$2", MODEL_DEFINITION_STATUS_INIT, model.Id) definitions, err = GetDbMultitple[TrainModelRowUsable](c, "model_definition where status=$1 and model_id=$2", MODEL_DEFINITION_STATUS_INIT, model.Id)
if err != nil { if err != nil {
failed("Failed to get definitions"); failed("Failed to get definitions")
return return
} }
if len(definitions) == 0 { if len(definitions) == 0 {
@ -839,7 +852,7 @@ func trainModelExp(c *Context, model *BaseModel) {
return return
} }
for _, d := range(to_delete) { for _, d := range to_delete {
os.RemoveAll(path.Join("savedData", model.Id, "defs", d.Id)) os.RemoveAll(path.Join("savedData", model.Id, "defs", d.Id))
} }
@ -1293,7 +1306,12 @@ func generateExpandableDefinitions(c *Context, model *BaseModel, target_accuracy
return nil return nil
} }
func handle_models_train_json(w http.ResponseWriter, r *http.Request, c *Context) *Error { func handleTrain(handle *Handle) {
handle.Post("/models/train", func(c *Context) *Error {
if !c.CheckAuthLevel(1) {
return nil
}
var dat struct { var dat struct {
Id string `json:"id"` Id string `json:"id"`
ModelType string `json:"model_type"` ModelType string `json:"model_type"`
@ -1301,7 +1319,7 @@ func handle_models_train_json(w http.ResponseWriter, r *http.Request, c *Context
Accuracy int `json:"accuracy"` Accuracy int `json:"accuracy"`
} }
if err_ := c.ToJSON(r, &dat); err_ != nil { if err_ := c.ToJSON(&dat); err_ != nil {
return err_ return err_
} }
@ -1350,106 +1368,19 @@ func handle_models_train_json(w http.ResponseWriter, r *http.Request, c *Context
fmt.Println("Failed to update model status") fmt.Println("Failed to update model status")
fmt.Println(err) fmt.Println(err)
// TODO improve this response // TODO improve this response
return Error500(err) return c.Error500(err)
} }
return c.SendJSON(model.Id) return c.SendJSON(model.Id)
}
func handleTrain(handle *Handle) {
handle.Post("/models/train", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) {
return nil
}
if c.Mode == JSON {
return handle_models_train_json(w, r, c)
}
r.ParseForm()
f := r.Form
number_of_models := 0
accuracy := 0
if !CheckId(f, "id") || CheckEmpty(f, "model_type") || !CheckNumber(f, "number_of_models", &number_of_models) || !CheckNumber(f, "accuracy", &accuracy) {
// TODO improve this response
return ErrorCode(nil, 400, c.AddMap(nil))
}
id := f.Get("id")
model_type_id := 1
model_type_form := f.Get("model_type")
if model_type_form == "expandable" {
model_type_id = 2
} else if model_type_form != "simple" {
return c.Error400(nil, "Invalid model type!", w, "/models/edit.html", "train-model-card", AnyMap{
"HasData": true,
"ErrorMessage": "Invalid model type!",
})
}
model, err := GetBaseModel(handle.Db, id)
if err == ModelNotFoundError {
return ErrorCode(nil, http.StatusNotFound, c.AddMap(AnyMap{
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
}))
} else if err != nil {
// TODO improve this response
return Error500(err)
}
if model.Status != CONFIRM_PRE_TRAINING {
// TODO improve this response
return ErrorCode(nil, 400, c.AddMap(nil))
}
if model_type_id == 2 {
full_error := generateExpandableDefinitions(c, model, accuracy, number_of_models)
if full_error != nil {
return full_error
}
} else {
full_error := generateDefinitions(c, model, accuracy, number_of_models)
if full_error != nil {
return full_error
}
}
if model_type_id == 2 {
go trainModelExp(c, model)
} else {
go trainModel(c, model)
}
_, err = c.Db.Exec("update models set status = $1, model_type = $2 where id = $3", TRAINING, model_type_id, model.Id)
if err != nil {
fmt.Println("Failed to update model status")
fmt.Println(err)
// TODO improve this response
return Error500(err)
}
Redirect("/models/edit?id="+model.Id, c.Mode, w, r)
return nil
}) })
handle.Get("/model/epoch/update", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Get("/model/epoch/update", func(c *Context) *Error {
// TODO check auth level f := c.R.URL.Query()
if c.Mode != NORMAL {
// This should only handle normal requests
c.Logger.Warn("This function only works with normal")
return c.UnsafeErrorCode(nil, 400, nil)
}
f := r.URL.Query()
accuracy := 0.0 accuracy := 0.0
if !CheckId(f, "model_id") || !CheckId(f, "definition") || CheckEmpty(f, "epoch") || !CheckFloat64(f, "accuracy", &accuracy) { if !CheckId(f, "model_id") || !CheckId(f, "definition") || CheckEmpty(f, "epoch") || !CheckFloat64(f, "accuracy", &accuracy) {
c.Logger.Warn("Invalid: model_id or definition or epoch or accuracy") return c.JsonBadRequest("Invalid: model_id or definition or epoch or accuracy")
return c.UnsafeErrorCode(nil, 400, nil)
} }
accuracy = accuracy * 100 accuracy = accuracy * 100
@ -1458,9 +1389,7 @@ func handleTrain(handle *Handle) {
def_id := f.Get("definition") def_id := f.Get("definition")
epoch, err := strconv.Atoi(f.Get("epoch")) epoch, err := strconv.Atoi(f.Get("epoch"))
if err != nil { if err != nil {
c.Logger.Warn("Epoch is not a number") return c.JsonBadRequest("Epoch is not a number")
// No need to improve message because this function is only called internaly
return c.UnsafeErrorCode(nil, 400, nil)
} }
rows, err := c.Db.Query("select md.status from model_definition as md where md.model_id=$1 and md.id=$2", model_id, def_id) rows, err := c.Db.Query("select md.status from model_definition as md where md.model_id=$1 and md.id=$2", model_id, def_id)
@ -1482,8 +1411,7 @@ func handleTrain(handle *Handle) {
if status != 3 { if status != 3 {
c.Logger.Warn("Definition not on status 3(training)", "status", status) c.Logger.Warn("Definition not on status 3(training)", "status", status)
// No need to improve message because this function is only called internaly return c.JsonBadRequest("Definition not on status 3(training)")
return c.UnsafeErrorCode(nil, 400, nil)
} }
c.Logger.Info("Updated model_definition!", "model", model_id, "progress", epoch, "accuracy", accuracy) c.Logger.Info("Updated model_definition!", "model", model_id, "progress", epoch, "accuracy", accuracy)
@ -1495,21 +1423,13 @@ func handleTrain(handle *Handle) {
return nil return nil
}) })
handle.Get("/model/head/epoch/update", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Get("/model/head/epoch/update", func(c *Context) *Error {
// TODO check auth level f := c.R.URL.Query()
if c.Mode != NORMAL {
// This should only handle normal requests
c.Logger.Warn("This function only works with normal")
return c.UnsafeErrorCode(nil, 400, nil)
}
f := r.URL.Query()
accuracy := 0.0 accuracy := 0.0
if !CheckId(f, "head_id") || CheckEmpty(f, "epoch") || !CheckFloat64(f, "accuracy", &accuracy) { if !CheckId(f, "head_id") || CheckEmpty(f, "epoch") || !CheckFloat64(f, "accuracy", &accuracy) {
c.Logger.Warn("Invalid: model_id or head_id or epoch or accuracy") return c.JsonBadRequest("Invalid: model_id or definition or epoch or accuracy")
return c.UnsafeErrorCode(nil, 400, nil)
} }
accuracy = accuracy * 100 accuracy = accuracy * 100
@ -1517,9 +1437,7 @@ func handleTrain(handle *Handle) {
head_id := f.Get("head_id") head_id := f.Get("head_id")
epoch, err := strconv.Atoi(f.Get("epoch")) epoch, err := strconv.Atoi(f.Get("epoch"))
if err != nil { if err != nil {
c.Logger.Warn("Epoch is not a number") return c.JsonBadRequest("Epoch is not a number")
// No need to improve message because this function is only called internaly
return c.UnsafeErrorCode(nil, 400, nil)
} }
rows, err := c.Db.Query("select hd.status from exp_model_head as hd where hd.id=$1;", head_id) rows, err := c.Db.Query("select hd.status from exp_model_head as hd where hd.id=$1;", head_id)
@ -1541,8 +1459,7 @@ func handleTrain(handle *Handle) {
if status != 3 { if status != 3 {
c.Logger.Warn("Head not on status 3(training)", "status", status) c.Logger.Warn("Head not on status 3(training)", "status", status)
// No need to improve message because this function is only called internaly return c.JsonBadRequest("Head not on status 3(training)")
return c.UnsafeErrorCode(nil, 400, nil)
} }
c.Logger.Info("Updated model_head!", "head", head_id, "progress", epoch, "accuracy", accuracy) c.Logger.Info("Updated model_head!", "head", head_id, "progress", epoch, "accuracy", accuracy)

View File

@ -1,14 +0,0 @@
package models_utils
import (
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/utils"
)
// TODO make this return and caller handle error
func ModelUpdateStatus(c *Context, id string, status int) {
_, err := c.Db.Exec("update models set status=$1 where id=$2;", status, id)
if err != nil {
c.Logger.Error("Failed to update model status", "err", err)
c.Logger.Warn("TODO Maybe handle better")
}
}

View File

@ -13,194 +13,28 @@ import (
"time" "time"
dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types" dbtypes "git.andr3h3nriqu3s.com/andr3/fyp/logic/db_types"
. "git.andr3h3nriqu3s.com/andr3/fyp/logic/models/utils"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"github.com/goccy/go-json" "github.com/goccy/go-json"
) )
func Mul(n1 int, n2 int) int {
return n1 * n2
}
func Add(n1 int, n2 int) int {
return n1 + n2
}
func baseLoadTemplate(base string, path string) (*template.Template, any) {
funcs := map[string]any{
"startsWith": strings.HasPrefix,
"replace": strings.Replace,
"mul": Mul,
"add": Add,
}
return template.New(base).Funcs(funcs).ParseFiles(
"./views/"+base,
"./views/"+path,
"./views/partials/header.html",
)
}
func loadTemplate(path string) (*template.Template, any) {
return baseLoadTemplate("layout.html", path)
}
func LoadView(writer http.ResponseWriter, path string, data interface{}) {
tmpl, err := loadTemplate(path)
if err != nil {
fmt.Printf("Failed to load view %s\n", path)
fmt.Println(err)
if path == "500.html" {
writer.Write([]byte("<h1>Failed to load 500.html check console for more info</h1>"))
} else {
LoadView(writer, "500.html", nil)
}
return
}
if err := tmpl.Execute(writer, data); err != nil {
fmt.Printf("Failed to load view %s\n", path)
fmt.Println(err)
writer.WriteHeader(http.StatusInternalServerError)
if path == "500.html" {
writer.Write([]byte("<h1>Failed to load 500.html check console for more info</h1>"))
} else {
LoadView(writer, "500.html", nil)
}
return
}
}
/** Only returns the html without template */
func LoadHtml(writer http.ResponseWriter, path string, data interface{}) {
tmpl, err := baseLoadTemplate("html.html", path)
if err != nil {
fmt.Printf("Failed to load template %s\n", path)
fmt.Println(err)
writer.WriteHeader(http.StatusInternalServerError)
if path == "500.html" {
writer.Write([]byte("<h1>Failed to load 500.html check console for more info</h1>"))
} else {
LoadHtml(writer, "500.html", nil)
}
return
}
if err := tmpl.Execute(writer, data); err != nil {
fmt.Printf("Failed to execute template %s\n", path)
fmt.Println(err)
writer.WriteHeader(http.StatusInternalServerError)
if path == "500.html" {
writer.Write([]byte("<h1>Failed to load 500.html check console for more info</h1>"))
} else {
LoadHtml(writer, "500.html", nil)
}
return
}
}
func LoadDefineTemplate(writer http.ResponseWriter, path string, base string, data AnyMap) {
if data == nil {
data = map[string]interface{}{
"Error": true,
}
} else {
data["Error"] = true
}
funcs := map[string]any{
"startsWith": strings.HasPrefix,
"mul": Mul,
"replace": strings.Replace,
"add": Add,
}
tmpl, err := template.New("").Funcs(funcs).Parse("{{template \"" + base + "\" . }}")
if err != nil {
panic("Lol")
}
tmpl, err = tmpl.ParseFiles(
"./views/"+path,
"./views/partials/header.html",
)
if err != nil {
fmt.Printf("Failed to load template %s\n", path)
fmt.Println(err)
writer.WriteHeader(http.StatusInternalServerError)
if path == "500.html" {
writer.Write([]byte("<h1>Failed to load 500.html check console for more info</h1>"))
} else {
LoadHtml(writer, "500.html", nil)
}
return
}
if err := tmpl.Execute(writer, data); err != nil {
fmt.Printf("Failed to execute template %s\n", path)
fmt.Println(err)
writer.WriteHeader(http.StatusInternalServerError)
if path == "500.html" {
writer.Write([]byte("<h1>Failed to load 500.html check console for more info</h1>"))
} else {
LoadHtml(writer, "500.html", nil)
}
return
}
}
type AnyMap = map[string]interface{} type AnyMap = map[string]interface{}
type Error struct { type Error struct {
Code int Code int
Msg *string data any
data AnyMap
}
type AnswerType int
const (
NORMAL AnswerType = 1 << iota
HTML
JSON
HTMLFULL
)
func LoadBasedOnAnswer(ans AnswerType, w http.ResponseWriter, path string, data map[string]interface{}) {
if ans == NORMAL {
LoadView(w, path, data)
return
} else if ans == HTML {
LoadHtml(w, path, data)
return
} else if ans == HTMLFULL {
if data == nil {
LoadHtml(w, path, map[string]interface{}{
"App": true,
})
} else {
data["App"] = true
LoadHtml(w, path, data)
}
return
} else if ans == JSON {
panic("TODO JSON!")
} else {
panic("unreachable")
}
} }
type HandleFunc struct { type HandleFunc struct {
path string path string
mode AnswerType fn func(c *Context) *Error
fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error
} }
type Handler interface { type Handler interface {
New() New()
Startup() Startup()
Get(fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) Get(fn func(c *Context) *Error)
Post(fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) Post(fn func(c *Context) *Error)
} }
type Handle struct { type Handle struct {
@ -219,139 +53,91 @@ func decodeBody(r *http.Request) (string, *Error) {
return string(body[:]), nil return string(body[:]), nil
} }
func handleError(err *Error, w http.ResponseWriter, context *Context) { func handleError(err *Error, c *Context) {
if err != nil { if err != nil {
data := context.AddMap(err.data) c.Logger.Warn("Responded with error", "code", err.Code, "data", err.data)
if err.Code == http.StatusNotFound { c.Writer.WriteHeader(err.Code)
if context.Mode == HTML { var e *Error
w.WriteHeader(309) if err.data != nil {
context.Mode = HTMLFULL e = c.SendJSON(err.data)
} else {
e = c.SendJSON(500)
} }
LoadBasedOnAnswer(context.Mode, w, "404.html", data) if e != nil {
return c.Logger.Error("Something went very wront while trying to send and error message")
} c.Writer.Write([]byte("505"))
w.WriteHeader(err.Code)
if err.Code == http.StatusBadRequest {
LoadBasedOnAnswer(context.Mode, w, "400.html", data)
return
}
if err.Msg != nil {
w.Write([]byte(*err.Msg))
} }
} }
} }
func (x *Handle) Get(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { func (x *Handle) Get(path string, fn func(c *Context) *Error) {
x.gets = append(x.gets, HandleFunc{path, NORMAL | HTML | HTMLFULL | JSON, fn}) x.gets = append(x.gets, HandleFunc{path, fn})
} }
func (x *Handle) GetHTML(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { func (x *Handle) Post(path string, fn func(c *Context) *Error) {
x.gets = append(x.gets, HandleFunc{path, NORMAL | HTML | HTMLFULL, fn}) x.posts = append(x.posts, HandleFunc{path, fn})
} }
func (x *Handle) GetJSON(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { func (x *Handle) Delete(path string, fn func(c *Context) *Error) {
x.gets = append(x.gets, HandleFunc{path, JSON, fn}) x.deletes = append(x.deletes, HandleFunc{path, fn})
} }
func (x *Handle) Post(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) { func (x *Handle) handleGets(context *Context) {
x.posts = append(x.posts, HandleFunc{path, NORMAL | HTML | HTMLFULL | JSON, fn})
}
func (x *Handle) PostHTML(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) {
x.posts = append(x.posts, HandleFunc{path, NORMAL | HTML | HTMLFULL, fn})
}
func (x *Handle) PostJSON(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) {
x.posts = append(x.posts, HandleFunc{path, JSON, fn})
}
func (x *Handle) Delete(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) {
x.deletes = append(x.deletes, HandleFunc{path, NORMAL | HTML | HTMLFULL | JSON, fn})
}
func (x *Handle) DeleteHTML(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) {
x.deletes = append(x.deletes, HandleFunc{path, NORMAL | HTML | HTMLFULL, fn})
}
func (x *Handle) DeleteJSON(path string, fn func(w http.ResponseWriter, r *http.Request, c *Context) *Error) {
x.deletes = append(x.deletes, HandleFunc{path, JSON, fn})
}
func (x *Handle) handleGets(w http.ResponseWriter, r *http.Request, context *Context) {
for _, s := range x.gets { for _, s := range x.gets {
if s.path == r.URL.Path && context.Mode&s.mode != 0 { if s.path == context.R.URL.Path {
handleError(s.fn(w, r, context), w, context) handleError(s.fn(context), context)
return return
} }
} }
if context.Mode != HTMLFULL { handleError(&Error{404, "Endpoint not found"}, context)
w.WriteHeader(http.StatusNotFound)
}
LoadBasedOnAnswer(context.Mode, w, "404.html", context.AddMap(nil))
} }
func (x *Handle) handlePosts(w http.ResponseWriter, r *http.Request, context *Context) { func (x *Handle) handlePosts(context *Context) {
for _, s := range x.posts { for _, s := range x.posts {
if s.path == r.URL.Path && context.Mode&s.mode != 0 { if s.path == context.R.URL.Path {
handleError(s.fn(w, r, context), w, context) handleError(s.fn(context), context)
return return
} }
} }
if context.Mode != HTMLFULL { handleError(&Error{404, "Endpoint not found"}, context)
w.WriteHeader(http.StatusNotFound)
}
LoadBasedOnAnswer(context.Mode, w, "404.html", context.AddMap(nil))
} }
func (x *Handle) handleDeletes(w http.ResponseWriter, r *http.Request, context *Context) { func (x *Handle) handleDeletes(context *Context) {
for _, s := range x.deletes { for _, s := range x.deletes {
if s.path == r.URL.Path && context.Mode&s.mode != 0 { if s.path == context.R.URL.Path {
handleError(s.fn(w, r, context), w, context) handleError(s.fn(context), context)
return return
} }
} }
if context.Mode != HTMLFULL { handleError(&Error{404, "Endpoint not found"}, context)
w.WriteHeader(http.StatusNotFound)
}
LoadBasedOnAnswer(context.Mode, w, "404.html", context.AddMap(nil))
} }
func CheckAuthLevel(authLevel int, w http.ResponseWriter, r *http.Request, c *Context) bool { func (c *Context) CheckAuthLevel(authLevel int) bool {
if authLevel > 0 { if authLevel > 0 {
if c.requireAuth(w, r) { if c.requireAuth() {
Logoff(c.Mode, w, r) c.Logoff()
return false return false
} }
if c.User.UserType < authLevel { if c.User.UserType < authLevel {
notAuth(c.Mode, w, r) c.NotAuth()
return false return false
} }
} }
return true return true
} }
func AnswerTemplate(path string, data AnyMap, authLevel int) func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
return func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if !CheckAuthLevel(authLevel, w, r, c) {
return nil
}
LoadBasedOnAnswer(c.Mode, w, path, c.AddMap(data))
return nil
}
}
type Context struct { type Context struct {
Token *string Token *string
User *dbtypes.User User *dbtypes.User
Mode AnswerType
Logger *log.Logger Logger *log.Logger
Db *sql.DB Db *sql.DB
Writer http.ResponseWriter Writer http.ResponseWriter
R *http.Request
} }
func (c Context) ToJSON(r *http.Request, dat any) *Error { func (c Context) ToJSON(dat any) *Error {
decoder := json.NewDecoder(r.Body) decoder := json.NewDecoder(c.R.Body)
err := decoder.Decode(dat) err := decoder.Decode(dat)
if err != nil { if err != nil {
@ -400,21 +186,29 @@ func (c Context) JsonErrorBadRequest(err error, dat any) *Error {
return c.SendJSONStatus(http.StatusBadRequest, dat) return c.SendJSONStatus(http.StatusBadRequest, dat)
} }
func (c Context) Error400(err error, message string, w http.ResponseWriter, path string, base string, data AnyMap) *Error { func (c *Context) GetModelFromId(id_path string) (*BaseModel, *Error) {
c.SetReportCaller(true)
c.Logger.Error(message) id, err := GetIdFromUrl(c, id_path)
c.SetReportCaller(false)
if err != nil { if err != nil {
c.Logger.Errorf("Something went wrong returning with: %d\n.Err:\n", http.StatusBadRequest) return nil, c.SendJSONStatus(http.StatusNotFound, "Model not found")
c.Logger.Error(err)
} }
if c.Mode == JSON { model, err := GetBaseModel(c.Db, id)
return &Error{http.StatusBadRequest, nil, c.AddMap(data)} if err == ModelNotFoundError {
return nil, c.SendJSONStatus(http.StatusNotFound, "Model not found")
} else if err != nil {
return nil, c.Error500(err)
} }
LoadDefineTemplate(w, path, base, c.AddMap(data)) return model, nil
return nil }
func ModelUpdateStatus(c *Context, id string, status int) {
_, err := c.Db.Exec("update models set status=$1 where id=$2;", status, id)
if err != nil {
c.Logger.Error("Failed to update model status", "err", err)
c.Logger.Warn("TODO Maybe handle better")
}
} }
func (c Context) SetReportCaller(report bool) { func (c Context) SetReportCaller(report bool) {
@ -427,7 +221,7 @@ func (c Context) SetReportCaller(report bool) {
} }
} }
func (c Context) ErrorCode(err error, code int, data AnyMap) *Error { func (c Context) ErrorCode(err error, code int, data any) *Error {
if code == 400 { if code == 400 {
c.SetReportCaller(true) c.SetReportCaller(true)
c.Logger.Warn("When returning BadRequest(400) please use context.Error400\n") c.Logger.Warn("When returning BadRequest(400) please use context.Error400\n")
@ -437,32 +231,14 @@ func (c Context) ErrorCode(err error, code int, data AnyMap) *Error {
c.Logger.Errorf("Something went wrong returning with: %d\n.Err:\n", code) c.Logger.Errorf("Something went wrong returning with: %d\n.Err:\n", code)
c.Logger.Error(err) c.Logger.Error(err)
} }
return &Error{code, nil, c.AddMap(data)} return &Error{code, data}
}
func (c Context) UnsafeErrorCode(err error, code int, data AnyMap) *Error {
if err != nil {
c.Logger.Errorf("Something went wrong returning with: %d\n.Err:\n", code)
c.Logger.Error(err)
}
return &Error{code, nil, c.AddMap(data)}
} }
func (c Context) Error500(err error) *Error { func (c Context) Error500(err error) *Error {
return c.ErrorCode(err, http.StatusInternalServerError, nil) return c.ErrorCode(err, http.StatusInternalServerError, nil)
} }
func (c Context) AddMap(m AnyMap) AnyMap { func (c *Context) requireAuth() bool {
if m == nil {
return map[string]interface{}{
"Context": c,
}
}
m["Context"] = c
return m
}
func (c *Context) requireAuth(w http.ResponseWriter, r *http.Request) bool {
if c.User == nil { if c.User == nil {
return true return true
} }
@ -471,7 +247,7 @@ func (c *Context) requireAuth(w http.ResponseWriter, r *http.Request) bool {
var LogoffError = errors.New("Invalid token!") var LogoffError = errors.New("Invalid token!")
func (x Handle) createContext(handler *Handle, mode AnswerType, r *http.Request, w http.ResponseWriter) (*Context, error) { func (x Handle) createContext(handler *Handle, r *http.Request, w http.ResponseWriter) (*Context, error) {
var token *string var token *string
@ -482,27 +258,18 @@ func (x Handle) createContext(handler *Handle, mode AnswerType, r *http.Request,
Prefix: r.URL.Path, Prefix: r.URL.Path,
}) })
if mode != JSON {
for _, r := range r.Cookies() {
if r.Name == "auth" {
token = &r.Value
}
}
} else {
t := r.Header.Get("token") t := r.Header.Get("token")
if t != "" { if t != "" {
token = &t token = &t
} }
}
// TODO check that the token is still valid // TODO check that the token is still valid
if token == nil { if token == nil {
return &Context{ return &Context{
Mode: mode,
Logger: logger, Logger: logger,
Db: handler.Db, Db: handler.Db,
Writer: w, Writer: w,
R: r,
}, nil }, nil
} }
@ -511,52 +278,22 @@ func (x Handle) createContext(handler *Handle, mode AnswerType, r *http.Request,
return nil, errors.Join(err, LogoffError) return nil, errors.Join(err, LogoffError)
} }
return &Context{token, user, mode, logger, handler.Db, w}, nil return &Context{token, user, logger, handler.Db, w, r}, nil
} }
// TODO check if I can use http.Redirect func contextlessLogoff(w http.ResponseWriter) {
func Redirect(path string, mode AnswerType, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", path)
if mode == JSON {
w.WriteHeader(http.StatusSeeOther)
w.Write([]byte(path))
return
}
if mode&(HTMLFULL|HTML) != 0 {
w.Header().Add("HX-Redirect", path)
w.WriteHeader(204)
} else {
w.WriteHeader(http.StatusSeeOther)
}
}
func Logoff(mode AnswerType, w http.ResponseWriter, r *http.Request) {
if (mode == JSON) {
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("\"Not Authorized\"")) w.Write([]byte("\"Not Authorized\""))
} else {
// Delete cookie
cookie := &http.Cookie{
Name: "auth",
Value: "",
Expires: time.Unix(0, 0),
}
http.SetCookie(w, cookie)
Redirect("/login", mode, w, r)
}
} }
func notAuth(mode AnswerType, w http.ResponseWriter, r *http.Request) { func (c *Context) Logoff() { contextlessLogoff(c.Writer) }
if mode == JSON {
w.WriteHeader(http.StatusForbidden) func (c *Context) NotAuth() {
w.Write([]byte("\"You can not access this resource!\"")) c.Writer.WriteHeader(http.StatusUnauthorized)
return e := c.SendJSON("Not Authorized")
} if e != nil {
if mode&(HTMLFULL|HTML) != 0 { c.Writer.WriteHeader(http.StatusInternalServerError)
w.WriteHeader(http.StatusForbidden) c.Writer.Write([]byte("You can not access this resource!"))
w.Write([]byte("You can not access this resource!"))
} else {
w.WriteHeader(http.StatusForbidden)
} }
} }
@ -583,21 +320,6 @@ func (x Handle) StaticFiles(pathTest string, fileType string, contentType string
}) })
} }
func ErrorCode(err error, code int, data AnyMap) *Error {
log.Warn("This function is deprecated please use the one provided by context")
// TODO Improve Logging
if err != nil {
fmt.Printf("Something went wrong returning with: %d\n.Err:\n", code)
fmt.Println(err)
}
return &Error{code, nil, data}
}
func Error500(err error) *Error {
log.Warn("This function is deprecated please use the one provided by context")
return ErrorCode(err, http.StatusInternalServerError, nil)
}
func (x Handle) ReadFiles(pathTest string, baseFilePath string, fileType string, contentType string) { func (x Handle) ReadFiles(pathTest string, baseFilePath string, fileType string, contentType string) {
http.HandleFunc(pathTest, func(w http.ResponseWriter, r *http.Request) { http.HandleFunc(pathTest, func(w http.ResponseWriter, r *http.Request) {
user_path := r.URL.Path[len(pathTest):] user_path := r.URL.Path[len(pathTest):]
@ -661,7 +383,7 @@ func (x Handle) ReadTypesFiles(pathTest string, baseFilePath string, fileTypes [
} }
func (x Handle) ReadTypesFilesApi(pathTest string, baseFilePath string, fileTypes []string, contentTypes []string) { func (x Handle) ReadTypesFilesApi(pathTest string, baseFilePath string, fileTypes []string, contentTypes []string) {
http.HandleFunc("/api" + pathTest, func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/api"+pathTest, func(w http.ResponseWriter, r *http.Request) {
r.URL.Path = strings.Replace(r.URL.Path, "/api", "", 1) r.URL.Path = strings.Replace(r.URL.Path, "/api", "", 1)
user_path := r.URL.Path[len(pathTest):] user_path := r.URL.Path[len(pathTest):]
@ -704,47 +426,45 @@ func NewHandler(db *sql.DB) *Handle {
x := &Handle{db, gets, posts, deletes} x := &Handle{db, gets, posts, deletes}
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// Decide answertype
ans := NORMAL
if r.Header.Get("HX-Request") == "true" || r.Header.Get("Request-Type") == "html" {
ans = HTML
}
if r.Header.Get("Request-Type") == "htmlfull" {
ans = HTMLFULL
}
if r.Header.Get("content-type") == "application/json" || r.Header.Get("response-type") == "application/json" {
ans = JSON
}
if !strings.HasPrefix(r.URL.Path, "/api") {
return
}
r.URL.Path = strings.Replace(r.URL.Path, "/api", "", 1)
w.Header().Add("Access-Control-Allow-Origin", "*") w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Headers", "*") w.Header().Add("Access-Control-Allow-Headers", "*")
w.Header().Add("Access-Control-Allow-Methods", "*") w.Header().Add("Access-Control-Allow-Methods", "*")
// Decide answertype
if !(r.Header.Get("content-type") == "application/json" || r.Header.Get("response-type") == "application/json") {
w.WriteHeader(500)
w.Write([]byte("Please set content-type to application/json or set response-type to application/json\n"))
return
}
if !strings.HasPrefix(r.URL.Path, "/api") {
w.WriteHeader(404)
w.Write([]byte("Path not found"))
return
}
r.URL.Path = strings.Replace(r.URL.Path, "/api", "", 1)
//Login state //Login state
context, err := x.createContext(x, ans, r, w) context, err := x.createContext(x, r, w)
if err != nil { if err != nil {
Logoff(ans, w, r) contextlessLogoff(w)
return return
} }
// context.Logger.Info("Parsing", "path", r.URL.Path) // context.Logger.Info("Parsing", "path", r.URL.Path)
if r.Method == "GET" { if r.Method == "GET" {
x.handleGets(w, r, context) x.handleGets(context)
return return
} }
if r.Method == "POST" { if r.Method == "POST" {
x.handlePosts(w, r, context) x.handlePosts(context)
return return
} }
if r.Method == "DELETE" { if r.Method == "DELETE" {
x.handleDeletes(w, r, context) x.handleDeletes(context)
return return
} }
if r.Method == "OPTIONS" { if r.Method == "OPTIONS" {

View File

@ -58,12 +58,12 @@ func IsValidUUID(u string) bool {
return err == nil return err == nil
} }
func GetIdFromUrl(r *http.Request, target string) (string, error) { func GetIdFromUrl(c *Context, target string) (string, error) {
if !r.URL.Query().Has(target) { if !c.R.URL.Query().Has(target) {
return "", errors.New("Query does not have " + target) return "", errors.New("Query does not have " + target)
} }
id := r.URL.Query().Get("id") id := c.R.URL.Query().Get("id")
if len(id) == 0 { if len(id) == 0 {
return "", errors.New("Query is empty for " + target) return "", errors.New("Query is empty for " + target)
} }

View File

@ -50,8 +50,6 @@ func main() {
handle.ReadTypesFiles("/savedData/", ".", []string{".png", ".jpeg"}, []string{"image/png", "image/jpeg"}) handle.ReadTypesFiles("/savedData/", ".", []string{".png", ".jpeg"}, []string{"image/png", "image/jpeg"})
handle.ReadTypesFilesApi("/savedData/", ".", []string{".png", ".jpeg"}, []string{"image/png", "image/jpeg"}) handle.ReadTypesFilesApi("/savedData/", ".", []string{".png", ".jpeg"}, []string{"image/png", "image/jpeg"})
handle.GetHTML("/", AnswerTemplate("index.html", nil, 0))
usersEndpints(db, handle) usersEndpints(db, handle)
HandleModels(handle) HandleModels(handle)

259
users.go
View File

@ -6,7 +6,6 @@ import (
"encoding/hex" "encoding/hex"
"io" "io"
"net/http" "net/http"
"time"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@ -76,10 +75,7 @@ func generateToken(db *sql.DB, email string, password string) (string, bool) {
} }
func usersEndpints(db *sql.DB, handle *Handle) { func usersEndpints(db *sql.DB, handle *Handle) {
handle.GetHTML("/login", AnswerTemplate("login.html", nil, 0)) handle.Post("/login", func(c *Context) *Error {
handle.Post("/login", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if c.Mode == JSON {
type UserLogin struct { type UserLogin struct {
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"` Password string `json:"password"`
@ -87,7 +83,7 @@ func usersEndpints(db *sql.DB, handle *Handle) {
var dat UserLogin var dat UserLogin
if err := c.ToJSON(r, &dat); err != nil { if err := c.ToJSON(&dat); err != nil {
return err return err
} }
@ -119,44 +115,9 @@ func usersEndpints(db *sql.DB, handle *Handle) {
} }
return c.SendJSON(userReturn) return c.SendJSON(userReturn)
}
r.ParseForm()
f := r.Form
if CheckEmpty(f, "email") || CheckEmpty(f, "password") {
LoadBasedOnAnswer(c.Mode, w, "login.html", c.AddMap(AnyMap{
"Submited": true,
}))
return nil
}
email := f.Get("email")
password := f.Get("password")
// TODO Give this to the generateToken function
expiration := time.Now().Add(24 * time.Hour)
token, login := generateToken(db, email, password)
if !login {
LoadBasedOnAnswer(c.Mode, w, "login.html", c.AddMap(AnyMap{
"Submited": true,
"NoUserOrPassword": true,
"Email": email,
}))
return nil
}
cookie := &http.Cookie{Name: "auth", Value: token, HttpOnly: false, Expires: expiration}
http.SetCookie(w, cookie)
w.Header().Set("Location", "/")
w.WriteHeader(http.StatusSeeOther)
return nil
}) })
handle.GetHTML("/register", AnswerTemplate("register.html", nil, 0)) handle.Post("/register", func(c *Context) *Error {
handle.Post("/register", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if c.Mode == JSON {
type UserLogin struct { type UserLogin struct {
Username string `json:"username"` Username string `json:"username"`
Email string `json:"email"` Email string `json:"email"`
@ -165,7 +126,7 @@ func usersEndpints(db *sql.DB, handle *Handle) {
var dat UserLogin var dat UserLogin
if err := c.ToJSON(r, &dat); err != nil { if err := c.ToJSON(&dat); err != nil {
return err return err
} }
@ -238,112 +199,41 @@ func usersEndpints(db *sql.DB, handle *Handle) {
} }
return c.SendJSON(userReturn) return c.SendJSON(userReturn)
}
r.ParseForm()
f := r.Form
if CheckEmpty(f, "email") || CheckEmpty(f, "password") || CheckEmpty(f, "username") {
LoadBasedOnAnswer(c.Mode, w, "register.html", AnyMap{
"Submited": true,
})
return nil
}
email := f.Get("email")
username := f.Get("username")
password := f.Get("password")
rows, err := db.Query("select username, email from users where username=$1 or email=$2;", username, email)
if err != nil {
panic("TODO handle this")
}
defer rows.Close()
if rows.Next() {
var db_username string
var db_email string
err = rows.Scan(&db_username, &db_email)
if err != nil {
panic("TODO handle this better")
}
LoadBasedOnAnswer(c.Mode, w, "register.html", AnyMap{
"Submited": true,
"Email": email,
"Username": username,
"EmailError": db_email == email,
"UserError": db_username == username,
})
return nil
}
if len([]byte(password)) > 68 {
LoadBasedOnAnswer(c.Mode, w, "register.html", AnyMap{
"Submited": true,
"Email": email,
"Username": username,
"PasswordToLong": true,
})
return nil
}
salt := generateSalt()
hash_password, err := hashPassword(password, salt)
if err != nil {
return &Error{
Code: http.StatusInternalServerError,
}
}
_, err = db.Exec("insert into users (username, email, salt, password) values ($1, $2, $3, $4);", username, email, salt, hash_password)
if err != nil {
return &Error{
Code: http.StatusInternalServerError,
}
}
// TODO Give this to the generateToken function
expiration := time.Now().Add(24 * time.Hour)
token, login := generateToken(db, email, password)
if !login {
msg := "Login failed"
return &Error{
Code: http.StatusInternalServerError,
Msg: &msg,
}
}
cookie := &http.Cookie{Name: "auth", Value: token, HttpOnly: false, Expires: expiration}
http.SetCookie(w, cookie)
w.Header().Set("Location", "/")
w.WriteHeader(http.StatusSeeOther)
return nil
}) })
handle.Get("/user/info", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { // TODO allow admin users to update this data
if !CheckAuthLevel(1, w, r, c) { handle.Get("/user/info", func(c *Context) *Error {
if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode == JSON {
return c.Error500(nil)
}
LoadBasedOnAnswer(c.Mode, w, "users/edit.html", c.AddMap(AnyMap{ user, err := dbtypes.UserFromToken(c.Db, *c.Token)
"Email": c.User.Email, if err != nil {
})) return c.Error500(err)
return nil }
type UserReturn struct {
Id string `json:"id"`
UserType int `json:"user_type"`
Username string `json:"username"`
Email string `json:"email"`
}
userReturn := UserReturn{
Id: user.Id,
UserType: user.UserType,
Username: user.Username,
Email: user.Email,
}
return c.SendJSON(userReturn)
}) })
// Handles updating users // Handles updating users
handle.Post("/user/info", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Post("/user/info", func(c *Context) *Error {
if !CheckAuthLevel(int(dbtypes.User_Normal), w, r, c) { if !c.CheckAuthLevel(int(dbtypes.User_Normal)) {
return nil return nil
} }
if c.Mode != JSON {
return c.Error500(nil)
}
type UserData struct { type UserData struct {
Id string `json:"id"` Id string `json:"id"`
@ -352,7 +242,7 @@ func usersEndpints(db *sql.DB, handle *Handle) {
var dat UserData var dat UserData
if err := c.ToJSON(r, &dat); err != nil { if err := c.ToJSON(&dat); err != nil {
return err return err
} }
@ -417,38 +307,10 @@ func usersEndpints(db *sql.DB, handle *Handle) {
return c.SendJSON(toReturnUser) return c.SendJSON(toReturnUser)
}) })
handle.Post("/user/info/email", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Post("/user/info/password", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode == JSON {
return c.Error500(nil)
}
r.ParseForm()
if CheckEmpty(r.Form, "email") {
return c.Error400(nil, "Email Not provided", w, "users/edit.html", "mainbody", c.AddMap(AnyMap{
"Email": c.User.Email,
}))
}
_, err := c.Db.Exec("update users set email=$1 where id=$2", r.Form.Get("email"), c.User.Id)
if err != nil {
return c.Error500(err)
}
LoadBasedOnAnswer(c.Mode, w, "users/edit.html", c.AddMap(AnyMap{
"Email": r.Form.Get("email"),
}))
return nil
})
handle.Post("/user/info/password", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) {
return nil
}
if c.Mode == JSON {
var dat struct { var dat struct {
Old_Password string `json:"old_password"` Old_Password string `json:"old_password"`
@ -456,7 +318,7 @@ func usersEndpints(db *sql.DB, handle *Handle) {
Password2 string `json:"password2"` Password2 string `json:"password2"`
} }
if err := c.ToJSON(r, &dat); err != nil { if err := c.ToJSON(&dat); err != nil {
return err return err
} }
@ -472,7 +334,7 @@ func usersEndpints(db *sql.DB, handle *Handle) {
_, login := generateToken(db, c.User.Email, dat.Old_Password) _, login := generateToken(db, c.User.Email, dat.Old_Password)
if !login { if !login {
return c.JsonBadRequest("Password is incorrect"); return c.JsonBadRequest("Password is incorrect")
} }
salt := generateSalt() salt := generateSalt()
@ -487,58 +349,7 @@ func usersEndpints(db *sql.DB, handle *Handle) {
} }
return c.SendJSON(c.User.Id) return c.SendJSON(c.User.Id)
}
r.ParseForm()
f := r.Form
if CheckEmpty(f, "old_password") || CheckEmpty(f, "password") || CheckEmpty(f, "password2") {
return c.Error400(nil, "OldPassword, Password or Password2 not provided!", w, "users/edit.html", "mainbody", c.AddMap(AnyMap{
"Email": c.User.Email,
"NoUserOrPassword": true,
}))
}
password := f.Get("password")
password2 := f.Get("password2")
if password != password2 {
return c.Error400(nil, "New passwords did not match", w, "users/edit.html", "mainbody", c.AddMap(AnyMap{
"Email": c.User.Email,
"PasswordNotTheSame": true,
}))
}
_, login := generateToken(db, c.User.Email, f.Get("old_password"))
if !login {
return c.Error400(nil, "Password was incorrect", w, "users/edit.html", "mainbody", c.AddMap(AnyMap{
"Email": c.User.Email,
"NoUserOrPassword": true,
}))
}
salt := generateSalt()
hash_password, err := hashPassword(password, salt)
if err != nil {
return c.Error500(err)
}
_, err = db.Exec("update users set salt=$1, password=$2 where id=$3", salt, hash_password, c.User.Id)
if err != nil {
return c.Error500(err)
}
LoadBasedOnAnswer(c.Mode, w, "users/edit.html", c.AddMap(AnyMap{
"email": c.User.Email,
}))
return nil
}) })
handle.Get("/logout", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { // TODO create function to remove token
if c.Mode == JSON {
panic("TODO handle json")
}
Logoff(c.Mode, w, r)
return nil
})
} }

View File

@ -1,28 +0,0 @@
{{ define "mainbody" }}
<div class="page404">
<div>
<h1>
404
</h1>
{{ if .NotFoundMessage }}
<h2>
{{ .NotFoundMessage }}
</h2>
{{ if .GoBackLink }}
<div class="description">
<a hx-get="{{ .GoBackLink }}" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
👈 Go back
</a>
</div>
{{ end }}
{{ else }}
<h2>
Page Not found
</h2>
<div class="description">
The page you were looking for does not exist
</div>
{{ end }}
</div>
</div>
{{ end }}

View File

@ -1,6 +0,0 @@
{{ define "head" }}
<title>
Error Page
</title>
{{ end }}
{{ define "body" }}Heyyyyyy Err {{ end }}

View File

@ -1,19 +0,0 @@
{{ if .Full }}
<body>
{{ block "body" . }}
{{ block "header.html" . }} {{end}}
<div class="app">
{{ block "mainbody" . }} {{end}}
</div>
{{end}}
</body>
{{ else }}
{{if .App }}
<div class="app">
{{ block "mainbody" . }} {{end}}
</div>
{{ else }}
{{ block "mainbody" . }} {{end}}
{{end}}
{{end}}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View File

@ -1,6 +0,0 @@
{{ define "title"}} Home : AI Stuff {{ end }}
{{ define "mainbody" }}
Main Page TODO
{{ end }}

View File

@ -1,150 +0,0 @@
function tabs() {
for (const elm of document.querySelectorAll(".tabs")) {
let count = 0;
let selected = 0;
for (const child of elm.children) {
if (child.tagName == "BUTTON") {
count++;
if (child.classList.contains("selected")) {
selected++;
}
if (!child.getAttribute("data-armed")) {
child.addEventListener("click", () => {
if (!child.classList.contains("selected")) {
for (const elm of child.parentElement.children) {
elm.classList.remove("selected");
}
child.classList.add("selected");
for (const childElm of child.parentElement.children) {
if (childElm.classList.contains("selected")) {
childElm.classList.remove("selected")
}
}
document.querySelector(`.tabs .content[data-tab="${
child.getAttribute("data-tab")
}"]`)?.classList.add("selected");
}
});
child.setAttribute("data-armed", "true")
}
}
}
if (selected > 1 || selected == 0) {
for (const child of elm.children) {
child.classList.remove("selected");
}
for (const child of elm.children) {
if (child.tagName == "BUTTON") {
child.classList.add("selected");
document.querySelector(`.tabs .content[data-tab="${
child.getAttribute("data-tab")
}"]`)?.classList.add("selected");
break;
}
}
}
}
for (const elm of document.querySelectorAll(".tabs-header")) {
let count = 0;
let selected = 0;
for (const child of elm.children[0].children) {
if (child.tagName == "BUTTON") {
count++;
if (child.classList.contains("selected")) {
selected++;
}
if (!child.getAttribute("data-armed")) {
child.addEventListener("click", () => {
if (!child.classList.contains("selected")) {
for (const elm of child.parentElement.children) {
elm.classList.remove("selected");
}
child.classList.add("selected");
for (const childElm of child.parentElement.parentElement.children) {
if (childElm.classList.contains("selected")) {
childElm.classList.remove("selected")
}
}
document.querySelector(`.tabs-header .content[data-tab="${
child.getAttribute("data-tab")
}"]`)?.classList.add("selected");
}
});
child.setAttribute("data-armed", "true")
}
}
}
if (selected > 1 || selected == 0) {
for (const child of elm.children) {
child.classList.remove("selected");
}
for (const child of elm.children[0].children) {
if (child.tagName == "BUTTON") {
child.classList.add("selected");
document.querySelector(`.tabs .content[data-tab="${
child.getAttribute("data-tab")
}"]`)?.classList.add("selected");
break;
}
}
}
}
}
function load() {
for (const elm of document.querySelectorAll("form > button")) {
elm.addEventListener('click', (e) => {
e.target.parentElement.classList.add("submitted");
});
}
for (const elm of document.querySelectorAll("button.icon")) {
elm.addEventListener('click', (e) => {
e.preventDefault()
const input = document.querySelectorAll('form .file-upload input[type="file"]')[0];
if (input) {
input.click();
input.addEventListener('change', (e) => {
const file = input.files[0];
if (!file) return;
elm.setAttribute("disabled", "true");
const spanToReplace = document.querySelector('.file-upload .icon span');
const imgToReplace = document.querySelector('.file-upload .icon img');
if (!imgToReplace || !spanToReplace) return;
if (imgToReplace.getAttribute("replace")) {
const fileReader = new FileReader();
fileReader.onloadend = () => {
imgToReplace.setAttribute("src", fileReader.result)
elm.classList.add("adapt");
}
fileReader.readAsDataURL(file)
}
if (spanToReplace.getAttribute("replace")) {
spanToReplace.innerHTML = spanToReplace.getAttribute("replace")
}
})
}
});
}
tabs();
}
window.onload = load;
htmx.on('htmx:afterSwap', load);
htmx.on('htmx:beforeSwap', (env) => {
if (env.detail.xhr.status === 401) {
window.location = "/login"
} else
// 309 is the code I selected for html to htmlfull change
if (env.detail.xhr.status === 309) {
env.detail.target = htmx.find(".app")
}
});

View File

@ -1,29 +0,0 @@
<!DOCTYPE html>
<html>
<head>
{{ block "header" . }}
<title>
{{ block "title" . }}
Ai stuff
{{ end }}
</title>
{{ end }}
<link rel="stylesheet" href="/styles/main.css" >
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
{{ block "other_imports" . }} {{end}}
</head>
<body>
<script src="https://unpkg.com/htmx.org@1.9.5"></script>
<script src="/js/main.js"></script>
{{ block "js_imports" . }} {{end}}
{{ block "body" . }}
{{ block "header.html" . }} {{end}}
<div class="app">
{{ block "mainbody" . }} {{end}}
</div>
{{end}}
</body>
</html>

View File

@ -1,34 +0,0 @@
{{ define "title"}} Home : AI Stuff {{ end }}
{{ define "mainbody" }}
<div class="login-page">
<div>
<h1>
Login
</h1>
<form method="post" action="/login" {{if .Submited}}class="submitted"{{end}} >
<fieldset>
<label for="email">Email</label>
<input type="email" required name="email" {{if .Email}} value="{{.Email}}" {{end}} />
</fieldset>
<fieldset>
<label for="password">Password</label>
<input required name="password" type="password" />
{{if .NoUserOrPassword}}
<span class="form-msg error">
Either the password or the email are incorrect
</span>
{{end}}
</fieldset>
<button>
Login
</button>
<div class="spacer"></div>
<a class="simple-link text-center w100 spacer" hx-get="/register" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
Register
</a>
</form>
</div>
</div>
{{ end }}

View File

@ -1,44 +0,0 @@
{{define "title"}}
Create New Model : AI Stuff
{{end}}
{{define "mainbody"}}
<main>
<h1>
Create new Model
</h1>
<form enctype="multipart/form-data" action="/models/add" method="POST" {{if .Submited}}class="submitted"{{end}} >
<fieldset>
<label for="name">Name</label>
<input id="name" name="name" required {{if .Name}} value="{{.Name}}" {{end}} />
{{if .NameFoundError}}
<span class="form-msg error">
You already have a model with that name.
</span>
{{end}}
</fieldset>
<fieldset class="file-upload" >
<label for="file">Base image</label>
<div class="form-msg">
Please provide a base image.<br/>
This image is a sample of the images that you are going to classfiy.
</div>
<div class="icon-holder">
<button class="icon">
<img replace="true" src="/imgs/upload-icon.png" />
<span replace="Image selected">
Upload image
</span>
</button>
<input id="file" name="file" type="file" required accept="image/png,image/jpeg" />
</div>
</fieldset>
<button hx-indicator="#spinner" >
Create
</button>
<button disabled id="spinner" class="htmx-indicator">
Create
</button>
</form>
</main>
{{end}}

View File

@ -1,12 +0,0 @@
{{ define "mainbody" }}
<main>
<h2 class="text-center">
Model {{ .Model.Name }} was deleted!
</h2>
<div class="description text-center">
<a hx-get="/models" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
👈 Go back
</a>
</div>
</main>
{{ end }}

View File

@ -1,556 +0,0 @@
{{ define "title" }}
Model: {{ .Model.Name }}
{{ end }}
{{ define "base-model-card" }}
<div class="card model-card">
<h1>
{{ .Model.Name }}
</h1>
<div class="second-line">
<img src="/savedData/{{ .Model.Id }}/baseimage.png" />
<div class="info">
<div>
<span class="bold bigger">Image Type:</span> {{ .Model.Color_mode }}
</div>
<div>
<span class="bold bigger">Image Size:</span> {{ .Model.Width }}x{{ .Model.Height }}
</div>
</div>
</div>
</div>
{{ end }}
{{ define "delete-model-card" }}
<form hx-delete="/models/delete" hx-headers='{"REQUEST-TYPE": "html"}' hx-swap="outerHTML" {{ if .Error }} class="submitted" {{end}} >
<fieldset>
<label for="name">
To delete this model please type "{{ .Model.Name }}":
</label>
<input name="name" id="name" required />
{{ if .NameDoesNotMatch }}
<span class="form-msg red">
Name does not match "{{ .Model.Name }}"
</span>
{{ end }}
</fieldset>
<input type="hidden" name="id" value="{{ .Model.Id }}" />
<button class="danger">
Delete
</button>
</form>
{{ end }}
{{/* Is called from a diffrent endpoint so that it does not matter where this is from :) which means that . can mean what ever I want */}}
{{ define "data-model-create-class-table-table" }}
<div>
<table>
<thead>
<tr>
<th>
File Path
</th>
<th>
Mode
</th>
<th>
<!-- Img -->
</th>
<th>
<!-- Status -->
</th>
</tr>
</thead>
<tbody>
{{range .List}}
<tr>
<td>
{{ if eq .FilePath "id://" }}
Managed
{{ else }}
{{.FilePath}}
{{ end }}
</td>
<td>
{{ if (eq .Mode 2) }}
Testing
{{ else }}
Training
{{ end }}
</td>
<td class="text-center">
{{ if startsWith .FilePath "id://" }}
<img src="/savedData/{{ $.Model.Id }}/data/{{ .Id }}.{{ $.Model.Format }}" height="30px" width="30px" style="object-fit: contain;" />
{{ else }}
TODO
img {{ .FilePath }}
{{ end }}
</td>
<td class="text-center">
{{ if eq .Status 1 }}
<span class="bi bi-check-circle-fill" style="color: green"></span>
{{ else }}
<span class="bi bi-exclamation-triangle-fill" style="color: red"></span>
{{ end }}
</td>
</tr>
{{end}}
</tbody>
</table>
<div class="flex justify-center align-center">
<div class="grow-1 flex justify-end align-center ">
{{ if gt .Page 0 }}
<button
hx-get="/models/data/list?id={{ .Id }}&page={{ add .Page -1 }}"
hx-target=".content[data-tab='{{ .Name }}']"
hx-swap="innerHTML"
hx-headers='{"REQUEST-TYPE": "html"}'
data-tab="{{ .Name }}">
Prev
</button>
{{ end }}
</div>
<div style="padding: 10px;">
{{ .Page }}
</div>
<div class="grow-1 flex justify-start align-center">
{{ if .ShowNext }}
<button
hx-get="/models/data/list?id={{ .Id }}&page={{ add .Page 1 }}"
hx-target=".content[data-tab='{{ .Name }}']"
hx-swap="innerHTML"
hx-headers='{"REQUEST-TYPE": "html"}'
data-tab="{{ .Name }}">
Next
</button>
{{ end }}
</div>
</div>
</div>
{{ end }}
{{ define "data-model-create-class-table" }}
{{ if eq (len .Classes) 0 }}
TODO CREATE TABLE
{{else}}
<div class="tabs-header">
{{/* Handle the case where there are to many buttons */}}
<div class="header">
{{ range .Classes }}
{{/* TODO Auto Load 1st */}}
<button
hx-get="/models/data/list?id={{ .Id }}"
hx-target=".content[data-tab='{{ .Name }}']"
hx-swap="innerHTML"
hx-headers='{"REQUEST-TYPE": "html"}'
hx-trigger="click"
class="tab"
data-tab="{{ .Name }}">
{{ .Name }}
</button>
{{ end }}
</div>
{{ range $i, $a := .Classes }}
{{ if eq $i 0}}
<div
hx-get="/models/data/list?id={{ .Id }}"
hx-target=".content[data-tab='{{ $a.Name }}']"
hx-swap="innerHTML"
hx-headers='{"REQUEST-TYPE": "html"}'
hx-trigger="load"
class="content"
data-tab="{{ $a.Name }}">
</div>
{{ else }}
<div
class="content"
data-tab="{{ $a.Name }}" >
</div>
{{ end }}
{{ end }}
</div>
{{end}}
{{ end }}
{{ define "data-model-card" }}
<div class="card">
<h3>
Training data
</h3>
{{ if eq (len .Classes) 0 }}
<p>
You need to upload data so the model can train.
</p>
<div class="tabs">
<button class="tab" data-tab="upload">
Upload
</button>
<button class="tab" data-tab="create_class">
Create Class
</button>
<button class="tab" data-tab="api">
Api
</button>
<div class="content" data-tab="upload">
<form hx-headers='{"REQUEST-TYPE": "htmlfull"}' enctype="multipart/form-data" hx-post="/models/data/upload">
<input type="hidden" name="id" value={{.Model.Id}} />
<fieldset class="file-upload" >
<label for="file">Data file</label>
<div class="form-msg">
Please provide a file that has the training and testing data<br/>
The file must have 2 folders one with testing images and one with training images. <br/>
Each of the folders will contain the classes of the model. The folders must be the same in testing and training.
The class folders must have the images for the classes.
<pre>
training\
class1\
img1.png
img2.png
img2.png
...
class2\
img1.png
img2.png
img2.png
...
...
testing\
class1\
img1.png
img2.png
img2.png
...
class2\
img1.png
img2.png
img2.png
...
...
</pre>
</div>
<div class="icon-holder">
<button class="icon">
<img replace="icon" src="/imgs/upload-icon.png" />
<span replace="File Selected">
Upload Zip File
</span>
</button>
<input id="file" name="file" type="file" required accept="application/zip" />
</div>
</fieldset>
<button hx-indicator="#upload-notifier" onClick="window.requestAnimationFrame(() => this.remove())">
Add
</button>
<button disabled id="upload-notifier" class="htmx-indicator">
Uploading
</button>
</form>
</div>
<div class="content" data-tab="create_class">
{{ template "data-model-create-class-table" . }}
</div>
<div class="content" data-tab="api">
TODO
</div>
</div>
{{ else }}
<p>
You need to upload data so the model can train.
</p>
{{ if gt .NumberOfInvalidImages 0 }}
<p class="danger">
There are images {{ .NumberOfInvalidImages }} that were loaded that do not have the correct format. These images will be delete when the model trains.
</p>
{{ end }}
<div class="tabs">
<button class="tab" data-tab="create_class">
Create Class
</button>
<button class="tab" data-tab="api">
Api
</button>
<div class="content" data-tab="create_class">
{{ template "data-model-create-class-table" . }}
</div>
<div class="content" data-tab="api">
TODO
</div>
</div>
{{ end }}
</div>
{{ end }}
{{ define "train-model-card" }}
<form hx-post="/models/train" hx-headers='{"REQUEST-TYPE": "html"}' hx-swap="outerHTML" {{ if .Error }} class="submitted" {{end}} >
{{ if .HasData }}
{{ if .NumberOfInvalidImages }}
{{ if gt .NumberOfInvalidImages 0 }}
<p class="danger">
There are images {{ .NumberOfInvalidImages }} that were loaded that do not have the correct format. These images will be delete when the model trains.
</p>
<input type="hidden" value="{{ .NumberOfInvalidImages }}" name="id" />
{{ end }}
{{ end }}
{{ if .ErrorMessage }}
<p class="danger">
{{ .ErrorMessage }}
</p>
{{ end }}
{{/* TODO expading mode */}}
<input type="hidden" value="{{ .Model.Id }}" name="id" />
<fieldset>
<legend>
Model Type
</legend>
<div class="input-radial">
<input id="model_type_simple" value="simple" name="model_type" type="radio" checked />
<label for="model_type_simple">Simple</label><br/>
<input id="model_type_expandable" value="expandable" name="model_type" type="radio" />
<label for="model_type_expandable">Expandable</label>
</div>
</fieldset>
{{/* TODO allow more models to be created */}}
<fieldset>
<label for="number_of_models">Number of Models</label>
<input id="number_of_models" type="number" name="number_of_models" value="1" />
</fieldset>
{{/* TODO to Change the acc */}}
<fieldset>
<label for="accuracy">Target accuracy</label>
<input id="accuracy" type="number" name="accuracy" value="95" />
</fieldset>
{{/* TODO allow to chose the base of the model */}}
{{/* TODO allow to change the shape of the model */}}
<button>
Train
</button>
{{ else }}
<h2>
To train the model please provide data to the model first
</h2>
{{ end }}
</form>
{{ end }}
{{ define "run-model-card" }}
<form hx-headers='{"REQUEST-TYPE": "html"}' enctype="multipart/form-data" hx-post="/models/run" hx-swap="outerHTML">
<input type="hidden" name="id" value={{.Model.Id}} />
<fieldset class="file-upload" >
<label for="file">Image</label>
<div class="form-msg">
Run image through them model and get the result
</div>
<div class="icon-holder">
<button class="icon">
<img replace="icon" src="/imgs/upload-icon.png" />
<span replace="File Selected">
Image File
</span>
</button>
{{ if .ImageError }}
<span class="form-msg error">
The provided image was not valid for this model
</span>
{{ end }}
<input id="file" name="file" type="file" required accept="image/png" />
</div>
</fieldset>
<button>
Run
</button>
{{ if .NotFound }}
<div class="result">
<h1>
The class was not found
</h1>
</div>
{{ else if .Result }}
<div>
<h1>
Result
</h1>
The image was classified as {{.Result}}
</div>
{{ end }}
</form>
{{ end }}
{{ define "mainbody" }}
<main>
{{ if (eq .Model.Status 1) }}
<div>
<h1 class="text-center">
{{ .Model.Name }}
</h1>
<!-- TODO add cool animation -->
<h2 class="text-center">
Preparing the model
</h2>
</div>
{{ else if (eq .Model.Status -1) }}
<div>
<h1 class="text-center">
{{ .Model.Name }}
</h1>
<!-- TODO improve message -->
<h2 class="text-center">
Failed to prepare model
</h2>
<form hx-delete="/models/delete">
<input type="hidden" name="id" value="{{ .Model.Id }}" />
<button class="danger">
Delete
</button>
</form>
</div>
{{/* PRE TRAINING STATUS */}}
{{ else if (eq .Model.Status 2) }}
{{ template "base-model-card" . }}
{{ template "data-model-card" . }}
{{ template "train-model-card" . }}
{{ template "delete-model-card" . }}
{{/* FAILED TO PROCCESS THE ZIPFILE */}}
{{ else if (eq .Model.Status -2)}}
{{ template "base-model-card" . }}
<form hx-delete="/models/data/delete-zip-file" hx-headers='{"REQUEST-TYPE": "html"}' hx-swap="outerHTML">
Failed to proccess the zip file.<br/>
Delete file and proccess again.<br/>
<br/>
<div class="spacer" ></div>
<input type="hidden" name="id" value="{{ .Model.Id }}" />
<button class="danger">
Delete Zip File
</button>
</form>
{{ template "delete-model-card" . }}
{{/* PROCCESS THE ZIPFILE */}}
{{ else if (eq .Model.Status 3)}}
{{ template "base-model-card" . }}
<div class="card" hx-get="/models/edit?id={{ .Model.Id }}" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push="true" hx-swap="outerHTML" hx-target=".app" hx-trigger="load delay:2s" >
{{/* TODO improve this */}}
Processing zip file...
</div>
{{/* FAILED TO Prepare for training */}}
{{ else if or (eq .Model.Status -3) (eq .Model.Status -4)}}
{{ template "base-model-card" . }}
<form hx-delete="/models/train/reset" hx-headers='{"REQUEST-TYPE": "html"}' hx-swap="outerHTML">
Failed Prepare for training.<br/>
<div class="spacer" ></div>
<input type="hidden" name="id" value="{{ .Model.Id }}" />
<button class="danger">
Try Again
</button>
</form>
{{ template "delete-model-card" . }}
{{ else if (eq .Model.Status 4)}}
{{ template "base-model-card" . }}
<div class="card" hx-get="/models/edit?id={{ .Model.Id }}" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push="true" hx-swap="outerHTML" hx-target=".app" hx-trigger="load delay:2s" >
{{/* TODO improve this */}}
Training the model...<br/>
{{/* TODO Add progress status on definitions */}}
<table>
<thead>
<tr>
<th>
Done Progress
</th>
<th>
Training Round Progress
</th>
<th>
Accuracy
</th>
<th>
Status
</th>
</tr>
</thead>
<tbody>
{{ range .Defs}}
<tr>
<td>
{{.Epoch}}
</td>
<td>
{{.EpochProgress}}/20
</td>
<td>
{{.Accuracy}}%
</td>
<td style="text-align: center;">
{{ if (eq .Status 2) }}
<span class="bi bi-book" style="color: green;"></span>
{{ else if (eq .Status 3) }}
<span class="bi bi-book-half" style="color: green;"></span>
{{ else if (eq .Status 6) }}
<span class="bi bi-book-half" style="color: orange;"></span>
{{ else if (eq .Status -3) }}
<span class="bi bi-book-half" style="color: red;"></span>
{{ else }}
{{.Status}}
{{ end }}
</td>
</tr>
{{ if (eq .Status 3) }}
<tr>
<td colspan="4">
<svg viewBox="0 200 1000 600">
{{ range $i, $layer := $.Layers }}
{{ if (eq $layer.LayerType 1)}}
<polygon
points="50,450 200,250 200,550 50,750"
stroke="black"
stroke-width="2"
fill="green"></polygon>
{{ else if (eq $layer.LayerType 4)}}
<polygon
points="{{add 50 (mul $i $.SepMod) }},450 {{ add 200 (mul $i $.SepMod) }},250 {{add 200 (mul $i $.SepMod)}},550 {{ add 50 (mul $i $.SepMod) }},750"
stroke="black"
stroke-width="2"
fill="orange">
</polygon>
{{ else if (eq $layer.LayerType 3)}}
<polygon
points="{{add 50 (mul $i $.SepMod) }},450 {{ add 200 (mul $i $.SepMod) }},250 {{add 200 (mul $i $.SepMod)}},550 {{ add 50 (mul $i $.SepMod) }},750"
stroke="black"
stroke-width="2"
fill="red">
</polygon>
{{ else if (eq $layer.LayerType 2)}}
<polygon
points="{{add 50 (mul $i $.SepMod) }},550 {{ add 200 (mul $i $.SepMod) }},350 {{add 200 (mul $i $.SepMod)}},450 {{ add 50 (mul $i $.SepMod) }},650"
stroke="black"
stroke-width="2"
fill="blue">
</polygon>
{{ else }}
<div>
{{ .LayerType }}
{{ .Shape }}
</div>
{{ end }}
{{ end }}
</svg>
</td>
</tr>
{{ end }}
{{ end }}
</tbody>
</table>
{{/* TODO Add ability to stop training */}}
</div>
{{/* Model Ready */}}
{{ else if (eq .Model.Status 5)}}
{{ template "base-model-card" . }}
{{ template "run-model-card" . }}
{{ template "delete-model-card" . }}
{{ else }}
<h1>
Unknown Status of the model.
</h1>
{{ end }}
</main>
{{ end }}

View File

@ -1,52 +0,0 @@
{{define "title"}}
Models : AI Stuff
{{end}}
{{define "mainbody"}}
<main>
{{ if (lt 0 (len .List)) }}
<div class="list-header">
<h2>My Models</h2>
<div class="expand"></div>
<a class="button" hx-get="/models/add" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
New
</a>
</div>
<table>
<thead>
<tr>
<th>
Name
</th>
<th>
<!-- Open Button -->
</th>
</tr>
</thead>
<tbody>
{{range .List}}
<tr>
<td>
{{.Name}}
</td>
<td class="text-center">
<a class="button simple" hx-get="/models/edit?id={{.Id}}" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
Edit
</a>
</td>
</tr>
{{end}}
</tbody>
</table>
{{else}}
<h2 class="text-center">
You don't have any models
</h2>
<div class="text-center">
<a class="button padded" hx-get="/models/add" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
Create a new model
</a>
</div>
{{end}}
</main>
{{end}}

View File

@ -1,33 +0,0 @@
<nav>
<ul>
<li>
<a hx-get="/" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
Index
</a>
</li>
{{ if .Context.User }}
<li>
<a hx-get="/models" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
Models
</a>
</li>
{{end}}
<li class="expand"></li>
{{ if .Context.User }}
<li>
<a hx-get="/user/info" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
User Info
</a>
<a hx-get="/logout" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
Logout
</a>
</li>
{{else}}
<li>
<a hx-get="/login" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
Login
</a>
</li>
{{end}}
</ul>
</nav>

View File

@ -1,49 +0,0 @@
{{define "title"}}
Register : AI Stuff
{{end}}
{{define "mainbody"}}
<div class="login-page">
<div>
<h1>
Register
</h1>
<form method="post" action="/register" {{if .Submited}}class="submitted"{{end}} >
<fieldset>
<label for="username">Username</label>
<input required name="username" value="{{.Username}}" />
{{if .UserError}}
<span class="form-msg error">
Username already in use
</span>
{{end}}
</fieldset>
<fieldset>
<label for="email">Email</label>
<input type="email" required name="email" value="{{.Email}}" />
{{if .EmailError}}
<span class="form-msg error">
Email already in use
</span>
{{end}}
</fieldset>
<fieldset>
<label for="password">Password</label>
<input required name="password" type="password" />
{{if .PasswordToLong}}
<span class="form-msg error">
Password is to long
</span>
{{end}}
</fieldset>
<button>
Register
</button>
<div class="spacer"></div>
<a class="simple-link text-center w100" hx-get="/login" hx-headers='{"REQUEST-TYPE": "htmlfull"}' hx-push-url="true" hx-swap="outerHTML" hx-target=".app">
Login
</a>
</form>
</div>
</div>
{{end}}

View File

@ -1,424 +0,0 @@
*{box-sizing: border-box;font-family: 'Roboto', sans-serif;}
:root {
--white: #ffffff;
--grey: #dbdcde;
--light-grey: #fafafa;
--main: #fca311;
--sec: #14213d;
--black: #000000;
--red: 212, 38, 38;
--green: 92, 199, 30;
}
body {
margin: 0;
padding: 0;
}
main {
padding: 20px 15vw;
}
.flex {
display: flex;
}
.justify-center {
justify-content: center;
}
.justify-start {
justify-content: start;
}
.justify-end {
justify-content: end;
}
.align-center {
align-items: center;
}
.grow-1 {
flex-grow: 1;
}
.w100 {
width: 100%;
display: block;
}
.text-center {
text-align: center;
}
.simple-link {
color: var(--sec);
text-decoration: none;
}
.bold {
font-weight: bold;
}
.bigger {
font-size: 1.1rem;
}
.danger {
color: red;
}
/* Generic */
.button,
button {
border-radius: 10px;
text-align: center;
padding: 3px 6px;
border: none;
box-shadow: 0 2px 8px 1px #66666655;
background: var(--main);
color: var(--black);
}
.button.padded,
button.padded {
padding: 10px;
}
.button.danger,
button.danger {
background: rgb(var(--red));
color: white;
font-weight: bold;
}
/* Nav bar */
nav {
background: #ececec;
margin: 0;
box-shadow: 0 0 8px 1px #888888ef;
height: 60px;
}
nav ul {
display: flex;
margin: 0;
padding: 20px 40px;
}
nav ul li {
list-style: none;
padding-left: 10px;
}
nav ul li:first-child {
padding: 0;
}
nav ul .expand {
flex-grow: 1
}
nav ul li a {
text-decoration: none;
color: black;
}
/* 404 page */
.page404 {
display: grid;
place-items: center;
height: calc(100vh - 60px);
text-align: center;
}
.page404 h1 {
font-size: 10em;
margin: 0;
}
.page404 h2 {
font-size: 5em;
margin: 0;
margin-bottom: 0.3em;
}
.page404 div.description {
font-size: 1.5em;
}
/* Login Page */
.login-page {
display: grid;
place-items: center;
margin-bottom: 40px;
}
.login-page > div {
width: 40vw;
}
.login-page h1 {
text-align: center;
}
/* forms */
a {
cursor: pointer;
}
form {
padding: 30px;
margin: 20px 0;
border-radius: 10px;
box-shadow: 2px 5px 8px 2px #66666655;
}
.card form {
padding: 0;
border-radius: none;
box-shadow: none;
}
form label,
form fieldset legend {
display: block;
padding-bottom: 5px;
font-size: 1.2rem;
}
form input {
border: none;
box-shadow: 0 2px 5px 1px #66666655;
border-radius: 5px;
padding: 10px;
width: 100%;
}
form input:invalid:focus,
form.submitted input:invalid {
box-shadow: 0 2px 5px 1px rgba(var(--red), 0.2);
}
form.submitted input:valid {
box-shadow: 0 2px 5px 1px rgba(var(--green), 0.2);
}
form .spacer {
padding-bottom: 10px;
}
form fieldset {
padding-bottom: 15px;
border: none;
}
form fieldset .form-msg {
font-size: 0.9rem;
}
form fieldset .error {
color: rgb(var(--red))
}
form button {
font-size: 1.2rem;
margin-left: 50%;
width: 50%;
transform: translateX(-50%);
padding: 10px;
}
form .input-radial input[type="radio"] {
width: auto;
box-shadow: none;
}
form .input-radial label {
display: inline;
font-size: 1rem;
}
/* Upload files */
form fieldset.file-upload input[type="file"] {
height: 1px;
width: 1px;
padding: 0;
box-shadow: none;
}
form fieldset.file-upload .icon-holder {
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
font-size: 1rem;
}
form fieldset.file-upload .icon-holder .icon {
height: 150px;
width: 150px;
padding: 20px;
border-radius: 10px;
background: none;
transform: none;
margin: 0;
font-size: 1rem;
transition: all 1s;
}
form fieldset.file-upload .icon-holder .icon.adapt {
width: auto;
height: auto;
max-width: 80%;
max-height: 80%;
min-height: 150px;
min-width: 150px;
padding: 20px;
}
form fieldset.file-upload:has(input[type="file"]:invalid:focus) .icon,
form.submitted fieldset.file-upload:has(input[type="file"]:invalid:focus) .icon {
box-shadow: 0 2px 5px 1px rgba(var(--red), 0.2);
}
form.submitted fieldset.file-upload:has(input[type="file"]:valid:focus) .icon{
box-shadow: 0 2px 5px 1px rgba(var(--green), 0.2);
}
form fieldset.file-upload .icon-holder .icon img {
display: block;
width: 100%;
height: 80%;
object-fit: contain;
text-align: center;
transition: all 1s;
}
form fieldset.file-upload .icon-holder .icon span {
display: block;
width: 100%;
padding-top: 10px;
text-align: center;
}
/* Lists */
.list-header {
display: flex;
padding-bottom: 10px;
}
.list-header h2 {
margin: 0;
padding: 10px 5px;
}
.list-header .expand {
flex-grow: 1;
}
.list-header .button,
.list-header button {
padding: 10px 10px;
height: calc(100% - 20px);
margin-top: 5px;
}
/* Table */
table {
width: 100%;
box-shadow: 0 2px 8px 1px #66666622;
border-radius: 10px;
border-collapse: collapse;
overflow: hidden;
}
table thead {
background: #60606022;
}
table tr td,
table tr th {
border-left: 1px solid #22222244;
padding: 15px;
}
table tr td:first-child,
table tr th:first-child {
border-left: none;
}
table tr td button,
table tr td .button {
padding: 5px 10px;
box-shadow: 0 2px 5px 1px #66666655;
}
.card {
box-shadow: 0 2px 5px 1px #66666655;
padding: 20px;
border-radius: 10px;
margin: 20px 0;
}
.card h3 {
margin-top: 0;
}
/* Model stuff */
.model-card h1 {
margin: 0;
padding-bottom: 10px;
}
.model-card img {
width: 25%;
height: 100%;
object-fit: contain;
}
.model-card .second-line {
display: flex;
gap: 20px;
}
/* Tabs code */
.tabs {
border-radius: 5px;
overflow: hidden;
display: flex;
flex-wrap: wrap;
box-shadow: 0 2px 8px 1px #66666655;
gap: 0 5px;
}
.tabs-header .header {
display: flex;
overflow-x: scroll;
}
.tabs .content {
display: none;
padding: 5px;
width: 100%;
}
.tabs .tab {
padding: 5px;
background: var(--light-grey);
border-radius: 5px 5px 0 0;
box-shadow: none;
font-size: 1.1rem;
}
.tabs .tab.selected {
box-shadow: inset 0 2px 8px 1px #66666655;
}
.tabs .content.selected {
display: block;
box-shadow: 0 2px 2px 1px #66666655;
}

View File

@ -1,50 +0,0 @@
{{ define "title" }}
User Info
{{ end }}
{{define "mainbody"}}
<div class="login-page">
<div>
<h1>
User Infomation
</h1>
<form method="post" action="/user/info/email" {{if .Submited}}class="submitted"{{end}} >
<fieldset>
<label for="email">Email</label>
<input type="email" required name="email" {{if .Email}} value="{{.Email}}" {{end}} />
</fieldset>
<button>
Update
</button>
</form>
<form method="post" action="/user/info/password" {{if .Submited}}class="submitted"{{end}} >
<fieldset>
<label for="old_password">Old Password</label>
<input required name="old_password" type="password" />
{{if .NoUserOrPassword}}
<span class="form-msg error">
Either the password is incorrect
</span>
{{end}}
</fieldset>
<fieldset>
<label for="password">New Password</label>
<input required name="password" type="password" />
</fieldset>
<fieldset>
<label for="password2">Repeat New Password</label>
<input required name="password2" type="password" />
{{if .PasswordNotTheSame}}
<span class="form-msg error">
Either the passwords are not the same
</span>
{{end}}
</fieldset>
<button>
Update
</button>
</form>
</div>
</div>
{{end}}