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,90 +86,14 @@ 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 {
return c.JsonErrorBadRequest(err, "Please provide a valid multipart Reader request")
}
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 {
return c.JsonBadRequest("Name is empty or file is empty")
}
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 c.JsonBadRequest("Model with that name already exists!")
}
row, err = handle.Db.Query("insert into models (user_id, name) values ($1, $2) returning id", c.User.Id, name)
if err != nil || !row.Next() {
return c.Error500(err)
}
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)
return c.SendJSON(id)
}
read_form, err := r.MultipartReader()
if err != nil { if err != nil {
LoadBasedOnAnswer(c.Mode, w, "models/add.html", c.AddMap(nil)) return c.JsonErrorBadRequest(err, "Please provide a valid multipart Reader request")
return nil
} }
var name string var name string
@ -195,8 +119,7 @@ func handleAdd(handle *Handle) {
} }
if name == "" || len(file) == 0 { if name == "" || len(file) == 0 {
LoadBasedOnAnswer(c.Mode, w, "models/add.html", c.AddMap(nil)) return c.JsonBadRequest("Name is empty or file is empty")
return nil
} }
row, err := handle.Db.Query("select id from models where name=$1 and user_id=$2;", name, c.User.Id) row, err := handle.Db.Query("select id from models where name=$1 and user_id=$2;", name, c.User.Id)
@ -205,27 +128,14 @@ func handleAdd(handle *Handle) {
} }
if row.Next() { if row.Next() {
LoadBasedOnAnswer(c.Mode, w, "models/add.html", c.AddMap(AnyMap{ return c.JsonBadRequest("Model with that name already exists!")
"NameFoundError": true,
"Name": name,
}))
return nil
} }
_, err = handle.Db.Exec("insert into models (user_id, name) values ($1, $2)", c.User.Id, name) row, err = handle.Db.Query("insert into models (user_id, name) values ($1, $2) returning id", c.User.Id, name)
if err != nil { if err != nil || !row.Next() {
return c.Error500(err) 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 var id string
err = row.Scan(&id) err = row.Scan(&id)
if err != nil { if err != nil {
@ -239,6 +149,7 @@ func handleAdd(handle *Handle) {
if err != nil { if err != nil {
return c.Error500(err) return c.Error500(err)
} }
f, err := os.Create(path.Join(dir_path, "baseimage.png")) f, err := os.Create(path.Join(dir_path, "baseimage.png"))
if err != nil { if err != nil {
return c.Error500(err) return c.Error500(err)
@ -250,7 +161,6 @@ func handleAdd(handle *Handle) {
c.Logger.Warn("Created model with id %s! Started to proccess image!\n", "id", id) c.Logger.Warn("Created model with id %s! Started to proccess image!\n", "id", id)
go loadBaseImage(c, id) go loadBaseImage(c, id)
Redirect("/models/edit?id="+id, c.Mode, w, r) return c.SendJSON(id)
return nil
}) })
} }

View File

@ -1,148 +1,69 @@
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 {
id, err := GetIdFromUrl(r, "id")
if err != nil {
return c.JsonBadRequest("Model Class not found!")
}
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 c.JsonBadRequest("Page is not a number")
}
page = page_url_number
}
var class_row struct {
Name string
Model_id string
}
err = utils.GetDBOnce(c, &class_row, "model_classes where id=$1", id)
if err == NotFoundError {
return c.JsonBadRequest("Model Class not found!")
} else if err != nil {
return c.Error500(err)
}
type baserow struct {
Id string `json:"id"`
File_Path string `json:"file_path"`
Model_Mode int `json:"model_mode"`
Status int `json:"status"`
}
rows, err := utils.GetDbMultitple[baserow](c, "model_data_point where class_id=$1 limit 11 offset $2", id, page*10)
if err != nil {
return c.Error500(err)
}
type ReturnType struct {
ImageList []*baserow `json:"image_list"`
Page int `json:"page"`
ShowNext bool `json:"showNext"`
}
max_len := min(11, len(rows))
return c.SendJSON(ReturnType{
ImageList: rows[0:max_len],
Page: page,
ShowNext: len(rows) == 11,
})
}
func HandleList(handle *Handle) { func HandleList(handle *Handle) {
handle.Get("/models/data/list", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Get("/models/data/list", func(c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) { if !c.CheckAuthLevel(1) {
return nil return nil
} }
if c.Mode == JSON {
return models_data_list_json(w, r, c)
}
id, err := GetIdFromUrl(r, "id") id, err := GetIdFromUrl(c, "id")
if err != nil { if err != nil {
return ErrorCode(err, 400, c.AddMap(nil)) 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 ErrorCode(err, http.StatusBadRequest, c.AddMap(nil)) return c.JsonBadRequest("Page is not a number")
} }
page = page_url_number page = page_url_number
} }
class_rows, err := handle.Db.Query("select name, model_id from model_classes where id=$1;", id) var class_row struct {
if err != nil { Name string
return Error500(err) Model_id string
}
defer class_rows.Close()
if !class_rows.Next() {
return ErrorCode(nil, 404, c.AddMap(nil))
} }
name := "" err = utils.GetDBOnce(c, &class_row, "model_classes where id=$1", id)
model_id := "" if err == NotFoundError {
if err = class_rows.Scan(&name, &model_id); err != nil { return c.JsonBadRequest("Model Class not found!")
return Error500(nil) } else if err != nil {
return c.Error500(err)
} }
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 { type baserow struct {
Id string Id string `json:"id"`
FilePath string File_Path string `json:"file_path"`
Mode int Model_Mode int `json:"model_mode"`
Status int Status int `json:"status"`
} }
got := []baserow{} rows, err := utils.GetDbMultitple[baserow](c, "model_data_point where class_id=$1 limit 11 offset $2", id, page*10)
if err != nil {
for rows.Next() { return c.Error500(err)
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)) type ReturnType struct {
ImageList []*baserow `json:"image_list"`
Page int `json:"page"`
ShowNext bool `json:"showNext"`
}
LoadDefineTemplate(w, "/models/edit.html", "data-model-create-class-table-table", c.AddMap(AnyMap{ max_len := min(11, len(rows))
"List": got[0:max_len],
"Page": page, return c.SendJSON(ReturnType{
"Id": id, ImageList: rows[0:max_len],
"Name": name, Page: page,
"Model": model, ShowNext: len(rows) == 11,
"ShowNext": len(got) == 11, })
}))
return nil
}) })
} }

View File

@ -152,8 +152,8 @@ func processZipFile(c *Context, model *BaseModel) {
} }
func processZipFileExpand(c *Context, model *BaseModel) { func processZipFileExpand(c *Context, model *BaseModel) {
var err error var err error
failed := func(msg string) { failed := func(msg string) {
c.Logger.Error(msg, "err", err) c.Logger.Error(msg, "err", err)
ModelUpdateStatus(c, model.Id, READY_FAILED) ModelUpdateStatus(c, model.Id, READY_FAILED)
@ -161,7 +161,7 @@ func processZipFileExpand(c *Context, model *BaseModel) {
reader, err := zip.OpenReader(path.Join("savedData", model.Id, "expand_data.zip")) reader, err := zip.OpenReader(path.Join("savedData", model.Id, "expand_data.zip"))
if err != nil { if err != nil {
failed("Faield to proccess zip file failed to open reader\n") failed("Faield to proccess zip file failed to open reader\n")
return return
} }
defer reader.Close() defer reader.Close()
@ -257,8 +257,8 @@ func processZipFileExpand(c *Context, model *BaseModel) {
c.Logger.Warn("Not failling updating data point to status -1") c.Logger.Warn("Not failling updating data point to status -1")
message := "Image did not have valid format for the model" message := "Image did not have valid format for the model"
if err = model_classes.UpdateDataPointStatus(c.Db, data_point_id, -1, &message); err != nil { if err = model_classes.UpdateDataPointStatus(c.Db, data_point_id, -1, &message); err != nil {
failed(fmt.Sprintf("Failed to update data point status")) failed(fmt.Sprintf("Failed to update data point status"))
return return
} }
} }
} }
@ -268,130 +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 {
return c.JsonBadRequest("Please provide a valid form data request!")
}
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 c.JsonBadRequest("Please provide a valid form data request!")
}
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.SendJSONStatus(http.StatusNotFound, "Model not found")
} 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)
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
// ------
handle.PostJSON("/models/data/class/upload", func(w http.ResponseWriter, r *http.Request, c *Context) *Error {
if !CheckAuthLevel(1, w, r, c) {
return nil
}
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!")
} }
@ -418,26 +300,86 @@ func handleDataUpload(handle *Handle) {
} }
} }
c.Logger.Info("Trying to expand model", "id", id) model, err := GetBaseModel(handle.Db, id)
if err == ModelNotFoundError {
return c.SendJSONStatus(http.StatusNotFound, "Model not found")
} else if err != nil {
return c.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 c.Error500(err)
}
defer f.Close()
f.Write(file)
ModelUpdateStatus(c, id, PREPARING_ZIP_FILE)
go processZipFile(c, model)
return c.SendJSON(model.Id)
})
// ------
// ------ CLASS DATA UPLOAD
// ------
handle.Post("/models/data/class/upload", func(c *Context) *Error {
if !c.CheckAuthLevel(1) {
return nil
}
read_form, err := c.R.MultipartReader()
if err != nil {
return c.JsonBadRequest("Please provide a valid form data request!")
}
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 c.JsonBadRequest("Please provide a valid form data request!")
}
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()
}
}
c.Logger.Info("Trying to expand model", "id", id)
model, err := GetBaseModel(handle.Db, id) model, err := GetBaseModel(handle.Db, id)
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
if model.Status != READY {
return c.JsonBadRequest("Model not in the correct state to add a more classes")
} }
// TODO work in allowing the model to add new in the pre ready moment
if model.Status != READY {
return c.JsonBadRequest("Model not in the correct state to add a more classes")
}
// TODO mk this path configurable // TODO mk this path configurable
dir_path := path.Join("savedData", id) dir_path := path.Join("savedData", id)
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,115 +392,68 @@ 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"`
}
var dat ModelData
if err := c.ToJSON(r, &dat); err != nil {
return err
}
model, err := GetBaseModel(handle.Db, dat.Id)
if err == ModelNotFoundError {
return c.SendJSONStatus(http.StatusNotFound, "Model not found")
} else if err != nil {
return Error500(err)
}
delete_path := "base_data.zip"
if model.Status == READY_FAILED {
delete_path = "expand_data.zip"
} else if model.Status != FAILED_PREPARING_ZIP_FILE {
return c.JsonBadRequest("Model not in the correct status")
}
err = os.Remove(path.Join("savedData", model.Id, delete_path))
if err != nil {
return Error500(err)
}
if model.Status != READY_FAILED {
err = os.RemoveAll(path.Join("savedData", model.Id, "data"))
if err != nil {
return Error500(err)
}
} else {
c.Logger.Warn("Handle failed to remove the savedData when deleteing the zip file while expanding")
}
if model.Status != READY_FAILED {
_, err = handle.Db.Exec("delete from model_classes where model_id=$1;", model.Id)
if err != nil {
return Error500(err)
}
} else {
_, 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 {
return Error500(err)
}
}
if model.Status != READY_FAILED {
ModelUpdateStatus(c, model.Id, CONFIRM_PRE_TRAINING)
} else {
ModelUpdateStatus(c, model.Id, READY)
}
return c.SendJSON(model.Id)
} }
f, err := MyParseForm(r) var dat ModelData
if err != nil {
return ErrorCode(err, 400, c.AddMap(nil)) if err := c.ToJSON(&dat); err != nil {
return err
} }
if !CheckId(f, "id") { model, err := GetBaseModel(handle.Db, dat.Id)
return ErrorCode(err, 400, c.AddMap(nil))
}
id := f.Get("id")
model, err := GetBaseModel(handle.Db, id)
if err == ModelNotFoundError { if err == ModelNotFoundError {
return ErrorCode(nil, http.StatusNotFound, AnyMap{ return c.SendJSONStatus(http.StatusNotFound, "Model not found")
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
} else if err != nil { } else if err != nil {
return Error500(err) return c.Error500(err)
} }
if model.Status != FAILED_PREPARING_ZIP_FILE { delete_path := "base_data.zip"
// TODO add message
return ErrorCode(nil, 400, c.AddMap(nil)) if model.Status == READY_FAILED {
delete_path = "expand_data.zip"
} else if model.Status != FAILED_PREPARING_ZIP_FILE {
return c.JsonBadRequest("Model not in the correct status")
} }
err = os.Remove(path.Join("savedData", id, "base_data.zip")) err = os.Remove(path.Join("savedData", model.Id, delete_path))
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
err = os.RemoveAll(path.Join("savedData", id, "data")) if model.Status != READY_FAILED {
if err != nil { err = os.RemoveAll(path.Join("savedData", model.Id, "data"))
return Error500(err) if err != nil {
return c.Error500(err)
}
} else {
c.Logger.Warn("Handle failed to remove the savedData when deleteing the zip file while expanding")
} }
_, err = handle.Db.Exec("delete from model_classes where model_id=$1;", id) if model.Status != READY_FAILED {
if err != nil { _, err = handle.Db.Exec("delete from model_classes where model_id=$1;", model.Id)
return Error500(err) if err != nil {
return c.Error500(err)
}
} else {
_, 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 {
return c.Error500(err)
}
} }
ModelUpdateStatus(c, id, CONFIRM_PRE_TRAINING) if model.Status != READY_FAILED {
Redirect("/models/edit?id="+id, c.Mode, w, r) ModelUpdateStatus(c, model.Id, CONFIRM_PRE_TRAINING)
return nil } else {
ModelUpdateStatus(c, model.Id, READY)
}
return c.SendJSON(model.Id)
}) })
} }

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,94 +28,29 @@ 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 {
return err_
}
var model struct {
Id string
Name string
Status int
}
err := utils.GetDBOnce(c, &model, "models where id=$1 and user_id=$2;", dat.Id, c.User.Id)
if err == NotFoundError {
return c.SendJSONStatus(http.StatusNotFound, "Model not found!")
} else if 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:
return deleteModelJSON(c, dat.Id)
case READY:
fallthrough
case CONFIRM_PRE_TRAINING:
if dat.Name == nil {
return c.JsonBadRequest("Provided name does not match the model name")
}
if *dat.Name != model.Name {
return c.JsonBadRequest("Provided name does not match the model name")
}
return deleteModelJSON(c, dat.Id)
default:
c.Logger.Warn("Do not know how to handle model in status", "status", model.Status)
return c.JsonBadRequest("Model in invalid status")
}
} }
// This is required to parse delete forms with bodies if err_ := c.ToJSON(&dat); err_ != nil {
f, err := MyParseForm(r) return err_
if err != nil {
return c.ErrorCode(err, 400, nil)
} }
if !CheckId(f, "id") { var model struct {
return c.ErrorCode(nil, http.StatusNotFound, AnyMap{ Id string
"NotFoundMessage": "Model not found", Name string
"GoBackLink": "/models", Status int
})
} }
id := f.Get("id") err := utils.GetDBOnce(c, &model, "models where id=$1 and user_id=$2;", dat.Id, c.User.Id)
if err == NotFoundError {
// TODO handle admin users return c.SendJSONStatus(http.StatusNotFound, "Model not found!")
rows, err := handle.Db.Query("select name, status from models where id=$1 and user_id=$2;", id, c.User.Id) } else if err != nil {
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) return c.Error500(err)
} }
@ -155,32 +62,23 @@ func handleDelete(handle *Handle) {
case FAILED_PREPARING_TRAINING: case FAILED_PREPARING_TRAINING:
fallthrough fallthrough
case FAILED_PREPARING: case FAILED_PREPARING:
deleteModel(handle, id, w, c, model) return deleteModelJSON(c, dat.Id)
return nil
case READY: case READY:
fallthrough fallthrough
case CONFIRM_PRE_TRAINING: case CONFIRM_PRE_TRAINING:
if CheckEmpty(f, "name") { if dat.Name == nil {
return c.Error400(nil, "Name is empty", w, "/models/edit.html", "delete-model-card", AnyMap{ return c.JsonBadRequest("Provided name does not match the model name")
"NameDoesNotMatch": true,
"Model": model,
})
} }
name := f.Get("name") if *dat.Name != model.Name {
if name != model.Name { return c.JsonBadRequest("Provided name does not match the 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 deleteModelJSON(c, dat.Id)
return nil
default: default:
panic("Do not know how to handle model in status:" + strconv.Itoa(model.Status)) c.Logger.Warn("Do not know how to handle model in status", "status", model.Status)
return c.JsonBadRequest("Model in invalid 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,79 +9,30 @@ 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)
if err != nil { if err != nil {
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,25 +48,15 @@ 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 {
Id string Id string
@ -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) return c.Error500(err)
} }
// Handle errors return c.SendJSON(model)
// 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)
}
cls, err := model_classes.ListClasses(c, id)
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 type Row struct {
if c.Mode == JSON { Name string `json:"name"`
rows, err := handle.Db.Query("select id, name from models where user_id=$1;", c.User.Id) Id string `json:"id"`
if err != nil {
return Error500(err)
}
defer rows.Close()
type Row struct {
Name string `json:"name"`
Id string `json:"id"`
}
got, err := utils.GetDbMultitple[Row](c, "models where user_id=$1", c.User.Id);
if err != nil {
c.Logger.Warn("HERE 6")
return c.Error500(nil)
}
return c.SendJSON(got)
} }
rows, err := handle.Db.Query("select id, name from 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 {
return Error500(err) return c.Error500(nil)
}
defer rows.Close()
type row struct {
Name string
Id string
} }
got := []row{} return c.SendJSON(got)
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,143 +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 {
// 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 c.JsonBadRequest("Invalid multipart data")
}
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.JsonBadRequest("Models not found")
} else if err != nil {
return c.Error500(err)
}
if model.Status != READY {
return c.JsonBadRequest("Model not ready to run images")
}
def := JustId{}
err = GetDBOnce(c, &def, "model_definition where model_id=$1", model.Id)
if err == NotFoundError {
return c.JsonBadRequest("Could not find definition")
} else if err != nil {
return c.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 c.Error500(err)
}
defer img_file.Close()
img_file.Write(file)
if !testImgForModel(c, model, img_path) {
return c.JsonBadRequest("Provided image does not match the model")
}
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 c.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 c.Error500(err)
}
if !rows.Next() {
return c.SendJSON(nil)
}
var name string
if err = rows.Scan(&name); err != nil {
return c.Error500(err)
}
returnValue := struct {
Class string `json:"class"`
Confidence float32 `json:"confidence"`
}{
Class: name,
Confidence: confidence,
}
return c.SendJSON(returnValue)
}
read_form, err := 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
@ -265,7 +134,7 @@ func handleRun(handle *Handle) {
if err_part == io.EOF { if err_part == io.EOF {
break break
} else if err_part != nil { } else if err_part != nil {
return &Error{Code: http.StatusBadRequest} return c.JsonBadRequest("Invalid multipart data")
} }
if part.FormName() == "id" { if part.FormName() == "id" {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
@ -281,27 +150,21 @@ func handleRun(handle *Handle) {
model, err := GetBaseModel(handle.Db, id) model, err := GetBaseModel(handle.Db, id)
if err == ModelNotFoundError { if err == ModelNotFoundError {
return ErrorCode(nil, http.StatusNotFound, AnyMap{ return c.JsonBadRequest("Models not found")
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
} else if err != nil { } else if err != nil {
return Error500(err) return c.Error500(err)
} }
if model.Status != READY { if model.Status != READY {
// TODO improve this return c.JsonBadRequest("Model not ready to run images")
return ErrorCode(nil, 400, c.AddMap(nil))
} }
def := JustId{} def := JustId{}
err = GetDBOnce(c, &def, "model_definition where model_id=$1", model.Id) err = GetDBOnce(c, &def, "model_definition where model_id=$1", model.Id)
if err == NotFoundError { if err == NotFoundError {
// TODO improve this return c.JsonBadRequest("Could not find definition")
fmt.Printf("Could not find definition\n")
return ErrorCode(nil, 400, c.AddMap(nil))
} else if err != nil { } else if err != nil {
return Error500(err) return c.Error500(err)
} }
def_id := def.Id def_id := def.Id
@ -313,19 +176,13 @@ func handleRun(handle *Handle) {
img_file, err := os.Create(img_path) img_file, err := os.Create(img_path)
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
defer img_file.Close() defer img_file.Close()
img_file.Write(file) img_file.Write(file)
if !testImgForModel(c, model, img_path) { if !testImgForModel(c, model, img_path) {
LoadDefineTemplate(w, "/models/edit.html", "run-model-card", c.AddMap(AnyMap{ return c.JsonBadRequest("Provided image does not match the model")
"Model": model,
"NotFound": false,
"Result": nil,
"ImageError": true,
}))
return nil
} }
root := tg.NewRoot() root := tg.NewRoot()
@ -344,7 +201,7 @@ func handleRun(handle *Handle) {
exec_results := tg.Exec(root, []tf.Output{tf_img.Value()}, nil, &tf.SessionOptions{}) exec_results := tg.Exec(root, []tf.Output{tf_img.Value()}, nil, &tf.SessionOptions{})
inputImage, err := tf.NewTensor(exec_results[0].Value()) inputImage, err := tf.NewTensor(exec_results[0].Value())
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
vi := -1 vi := -1
@ -368,27 +225,25 @@ func handleRun(handle *Handle) {
rows, err := handle.Db.Query("select name from model_classes where model_id=$1 and class_order=$2;", model.Id, vi) rows, err := handle.Db.Query("select name from model_classes where model_id=$1 and class_order=$2;", model.Id, vi)
if err != nil { if err != nil {
return Error500(err) return c.Error500(err)
} }
if !rows.Next() { if !rows.Next() {
LoadDefineTemplate(w, "/models/edit.html", "run-model-card", c.AddMap(AnyMap{ return c.SendJSON(nil)
"Model": model,
"NotFound": true,
"Result": nil,
"Confidence": confidence,
}))
return nil
} }
var name string var name string
if err = rows.Scan(&name); err != nil { if err = rows.Scan(&name); err != nil {
return nil return c.Error500(err)
} }
LoadDefineTemplate(w, "/models/edit.html", "run-model-card", c.AddMap(AnyMap{ returnValue := struct {
"Model": model, Class string `json:"class"`
"Result": name, Confidence float32 `json:"confidence"`
})) }{
return nil Class: name,
Confidence: confidence,
}
return c.SendJSON(returnValue)
}) })
} }

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,82 +9,39 @@ 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 {
return err;
}
model, err := GetBaseModel(c.Db, dat.Id)
if err == ModelNotFoundError {
return c.JsonBadRequest("Model not found");
} else if err != nil {
// TODO improve response
return c.Error500(err)
}
if model.Status != FAILED_PREPARING_TRAINING && model.Status != FAILED_TRAINING {
return c.JsonBadRequest("Model is not in status that be reset")
}
os.RemoveAll(path.Join("savedData", model.Id, "defs"))
_, err = c.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)
return c.SendJSON(model.Id)
} }
f, err := MyParseForm(r) if err := c.ToJSON(&dat); err != nil {
if err != nil { return err
// TODO improve response
return c.ErrorCode(nil, 400, c.AddMap(nil))
} }
if !CheckId(f, "id") { model, err := GetBaseModel(c.Db, dat.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 { if err == ModelNotFoundError {
return c.ErrorCode(nil, http.StatusNotFound, AnyMap{ return c.JsonBadRequest("Model not found")
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
})
} else if err != nil { } else if err != nil {
// TODO improve response // TODO improve response
return c.Error500(err) return c.Error500(err)
} }
if model.Status != FAILED_PREPARING_TRAINING && model.Status != FAILED_TRAINING { if model.Status != FAILED_PREPARING_TRAINING && model.Status != FAILED_TRAINING {
// TODO improve response return c.JsonBadRequest("Model is not in status that be reset")
return c.ErrorCode(nil, 400, c.AddMap(nil))
} }
os.RemoveAll(path.Join("savedData", model.Id, "defs")) os.RemoveAll(path.Join("savedData", model.Id, "defs"))
_, err = handle.Db.Exec("delete from model_definition where model_id=$1", model.Id) _, err = c.Db.Exec("delete from model_definition where model_id=$1", model.Id)
if err != nil { if err != nil {
// TODO improve response // TODO improve response
return c.Error500(err) return c.Error500(err)
} }
ModelUpdateStatus(c, model.Id, CONFIRM_PRE_TRAINING) ModelUpdateStatus(c, model.Id, CONFIRM_PRE_TRAINING)
Redirect("/models/edit?id="+model.Id, c.Mode, w, r) return c.SendJSON(model.Id)
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)
@ -702,13 +715,13 @@ func trainModelExp(c *Context, model *BaseModel) {
ModelUpdateStatus(c, model.Id, FAILED_TRAINING) ModelUpdateStatus(c, model.Id, FAILED_TRAINING)
} }
var definitions TrainModelRowUsables var definitions TrainModelRowUsables
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 {
failed("No Definitions defined!") failed("No Definitions defined!")
return return
@ -810,38 +823,38 @@ func trainModelExp(c *Context, model *BaseModel) {
} }
} }
// Set the class status to trained // Set the class status to trained
err = setModelClassStatus(c, MODEL_CLASS_STATUS_TRAINED, "model_id=$1 and status=$2;", model.Id, MODEL_CLASS_STATUS_TRAINING) err = setModelClassStatus(c, MODEL_CLASS_STATUS_TRAINED, "model_id=$1 and status=$2;", model.Id, MODEL_CLASS_STATUS_TRAINING)
if err != nil { if err != nil {
failed("Failed to set class status") failed("Failed to set class status")
return return
} }
var dat JustId
err = GetDBOnce(c, &dat, "model_definition where model_id=$1 and status=$2 order by accuracy desc limit 1;", model.Id, MODEL_DEFINITION_STATUS_TRANIED) var dat JustId
if err == NotFoundError {
err = GetDBOnce(c, &dat, "model_definition where model_id=$1 and status=$2 order by accuracy desc limit 1;", model.Id, MODEL_DEFINITION_STATUS_TRANIED)
if err == NotFoundError {
failed("All definitions failed to train!") failed("All definitions failed to train!")
return return
} else if err != nil { } else if err != nil {
failed("DB: failed to read definition") failed("DB: failed to read definition")
return return
} }
if _, err = c.Db.Exec("update model_definition set status=$1 where id=$2;", MODEL_DEFINITION_STATUS_READY, dat.Id); err != nil { if _, err = c.Db.Exec("update model_definition set status=$1 where id=$2;", MODEL_DEFINITION_STATUS_READY, dat.Id); err != nil {
failed("Failed to update model definition") failed("Failed to update model definition")
return return
} }
to_delete, err := GetDbMultitple[JustId](c, "model_definition where status!=$1 and model_id=$2", MODEL_DEFINITION_STATUS_READY, model.Id) to_delete, err := GetDbMultitple[JustId](c, "model_definition where status!=$1 and model_id=$2", MODEL_DEFINITION_STATUS_READY, model.Id)
if err != nil { if err != nil {
failed("Failed to select model_definition to delete") failed("Failed to select model_definition to delete")
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))
} }
// TODO Check if returning also works here // TODO Check if returning also works here
if _, err = c.Db.Exec("delete from model_definition where status!=$1 and model_id=$2;", MODEL_DEFINITION_STATUS_READY, model.Id); err != nil { if _, err = c.Db.Exec("delete from model_definition where status!=$1 and model_id=$2;", MODEL_DEFINITION_STATUS_READY, model.Id); err != nil {
@ -1293,163 +1306,81 @@ 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 {
var dat struct {
Id string `json:"id"`
ModelType string `json:"model_type"`
NumberOfModels int `json:"number_of_models"`
Accuracy int `json:"accuracy"`
}
if err_ := c.ToJSON(r, &dat); err_ != nil {
return err_
}
if dat.Id == "" {
return c.JsonBadRequest("Please provide a id")
}
modelTypeId := 1
if dat.ModelType == "expandable" {
modelTypeId = 2
} else if dat.ModelType != "simple" {
return c.JsonBadRequest("Invalid model type!")
}
model, err := GetBaseModel(c.Db, dat.Id)
if err == ModelNotFoundError {
return c.JsonBadRequest("Model not found")
} else if err != nil {
return c.Error500(err)
}
if model.Status != CONFIRM_PRE_TRAINING {
return c.JsonBadRequest("Model in invalid status for training")
}
if modelTypeId == 2 {
full_error := generateExpandableDefinitions(c, model, dat.Accuracy, dat.NumberOfModels)
if full_error != nil {
return full_error
}
} else {
full_error := generateDefinitions(c, model, dat.Accuracy, dat.NumberOfModels)
if full_error != nil {
return full_error
}
}
if modelTypeId == 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, modelTypeId, model.Id)
if err != nil {
fmt.Println("Failed to update model status")
fmt.Println(err)
// TODO improve this response
return Error500(err)
}
return c.SendJSON(model.Id)
}
func handleTrain(handle *Handle) { func handleTrain(handle *Handle) {
handle.Post("/models/train", func(w http.ResponseWriter, r *http.Request, c *Context) *Error { handle.Post("/models/train", 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 {
return handle_models_train_json(w, r, c) Id string `json:"id"`
ModelType string `json:"model_type"`
NumberOfModels int `json:"number_of_models"`
Accuracy int `json:"accuracy"`
} }
r.ParseForm() if err_ := c.ToJSON(&dat); err_ != nil {
f := r.Form return err_
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") if dat.Id == "" {
model_type_id := 1 return c.JsonBadRequest("Please provide a id")
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) modelTypeId := 1
if dat.ModelType == "expandable" {
modelTypeId = 2
} else if dat.ModelType != "simple" {
return c.JsonBadRequest("Invalid model type!")
}
model, err := GetBaseModel(c.Db, dat.Id)
if err == ModelNotFoundError { if err == ModelNotFoundError {
return ErrorCode(nil, http.StatusNotFound, c.AddMap(AnyMap{ return c.JsonBadRequest("Model not found")
"NotFoundMessage": "Model not found",
"GoBackLink": "/models",
}))
} else if err != nil { } else if err != nil {
// TODO improve this response return c.Error500(err)
return Error500(err)
} }
if model.Status != CONFIRM_PRE_TRAINING { if model.Status != CONFIRM_PRE_TRAINING {
// TODO improve this response return c.JsonBadRequest("Model in invalid status for training")
return ErrorCode(nil, 400, c.AddMap(nil))
} }
if model_type_id == 2 { if modelTypeId == 2 {
full_error := generateExpandableDefinitions(c, model, accuracy, number_of_models) full_error := generateExpandableDefinitions(c, model, dat.Accuracy, dat.NumberOfModels)
if full_error != nil { if full_error != nil {
return full_error return full_error
} }
} else { } else {
full_error := generateDefinitions(c, model, accuracy, number_of_models) full_error := generateDefinitions(c, model, dat.Accuracy, dat.NumberOfModels)
if full_error != nil { if full_error != nil {
return full_error return full_error
} }
} }
if model_type_id == 2 { if modelTypeId == 2 {
go trainModelExp(c, model) go trainModelExp(c, model)
} else { } else {
go trainModel(c, model) 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) _, err = c.Db.Exec("update models set status = $1, model_type = $2 where id = $3", TRAINING, modelTypeId, model.Id)
if err != nil { if err != nil {
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)
} }
Redirect("/models/edit?id="+model.Id, c.Mode, w, r)
return nil return c.SendJSON(model.Id)
}) })
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,202 +53,162 @@ 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 {
LoadBasedOnAnswer(context.Mode, w, "404.html", data) e = c.SendJSON(500)
return
} }
w.WriteHeader(err.Code) if e != nil {
if err.Code == http.StatusBadRequest { c.Logger.Error("Something went very wront while trying to send and error message")
LoadBasedOnAnswer(context.Mode, w, "400.html", data) c.Writer.Write([]byte("505"))
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)
err := decoder.Decode(dat) decoder := json.NewDecoder(c.R.Body)
if err != nil {
return c.Error500(err)
}
return nil err := decoder.Decode(dat)
if err != nil {
return c.Error500(err)
}
return nil
} }
func (c Context) SendJSON(dat any) *Error { func (c Context) SendJSON(dat any) *Error {
c.Writer.Header().Add("content-type", "application/json") c.Writer.Header().Add("content-type", "application/json")
text, err := json.Marshal(dat) text, err := json.Marshal(dat)
if err != nil { if err != nil {
return c.Error500(err) return c.Error500(err)
} }
if _, err = c.Writer.Write(text); err != nil { if _, err = c.Writer.Write(text); err != nil {
return c.Error500(err) return c.Error500(err)
} }
return nil return nil
} }
func (c Context) SendJSONStatus(status int, dat any) *Error { func (c Context) SendJSONStatus(status int, dat any) *Error {
c.Writer.Header().Add("content-type", "application/json") c.Writer.Header().Add("content-type", "application/json")
c.Writer.WriteHeader(status) c.Writer.WriteHeader(status)
text, err := json.Marshal(dat) text, err := json.Marshal(dat)
if err != nil { if err != nil {
return c.Error500(err) return c.Error500(err)
} }
if _, err = c.Writer.Write(text); err != nil { if _, err = c.Writer.Write(text); err != nil {
return c.Error500(err) return c.Error500(err)
} }
return nil return nil
} }
func (c Context) JsonBadRequest(dat any) *Error { func (c Context) JsonBadRequest(dat any) *Error {
c.SetReportCaller(true) c.SetReportCaller(true)
c.Logger.Warn("Request failed with a bad request", "dat", dat) c.Logger.Warn("Request failed with a bad request", "dat", dat)
c.SetReportCaller(false) c.SetReportCaller(false)
return c.SendJSONStatus(http.StatusBadRequest, dat) return c.SendJSONStatus(http.StatusBadRequest, dat)
} }
func (c Context) JsonErrorBadRequest(err error, dat any) *Error { func (c Context) JsonErrorBadRequest(err error, dat any) *Error {
c.SetReportCaller(true) c.SetReportCaller(true)
c.Logger.Error("Error while processing request", "err", err, "dat", dat) c.Logger.Error("Error while processing request", "err", err, "dat", dat)
c.SetReportCaller(false) c.SetReportCaller(false)
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 { t := r.Header.Get("token")
for _, r := range r.Cookies() { if t != "" {
if r.Name == "auth" { token = &t
token = &r.Value }
}
}
} else {
t := r.Header.Get("token")
if 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.WriteHeader(http.StatusUnauthorized)
w.Header().Set("Location", path) w.Write([]byte("\"Not Authorized\""))
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) { func (c *Context) Logoff() { contextlessLogoff(c.Writer) }
if (mode == JSON) {
w.WriteHeader(http.StatusUnauthorized)
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) NotAuth() {
if mode == JSON { c.Writer.WriteHeader(http.StatusUnauthorized)
w.WriteHeader(http.StatusForbidden) e := c.SendJSON("Not Authorized")
w.Write([]byte("\"You can not access this resource!\"")) if e != nil {
return c.Writer.WriteHeader(http.StatusInternalServerError)
} c.Writer.Write([]byte("You can not access this resource!"))
if mode&(HTMLFULL|HTML) != 0 {
w.WriteHeader(http.StatusForbidden)
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,9 +383,9 @@ 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):]
found := false found := false
@ -704,52 +426,50 @@ 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) {
w.Header().Add("Access-Control-Allow-Origin", "*")
w.Header().Add("Access-Control-Allow-Headers", "*")
w.Header().Add("Access-Control-Allow-Methods", "*")
// Decide answertype // Decide answertype
ans := NORMAL if !(r.Header.Get("content-type") == "application/json" || r.Header.Get("response-type") == "application/json") {
if r.Header.Get("HX-Request") == "true" || r.Header.Get("Request-Type") == "html" { w.WriteHeader(500)
ans = HTML w.Write([]byte("Please set content-type to application/json or set response-type to application/json\n"))
}
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-Headers", "*")
w.Header().Add("Access-Control-Allow-Methods", "*")
//Login state
context, err := x.createContext(x, ans, r, w)
if err != nil {
Logoff(ans, w, r)
return return
} }
// context.Logger.Info("Parsing", "path", r.URL.Path) 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
context, err := x.createContext(x, r, w)
if err != nil {
contextlessLogoff(w)
return
}
// 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
}
if r.Method == "OPTIONS" {
return return
} }
if r.Method == "OPTIONS" {
return
}
panic("TODO handle method: " + r.Method) panic("TODO handle method: " + r.Method)
}) })

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)

439
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,187 +75,68 @@ 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 { type UserLogin struct {
if c.Mode == JSON { Email string `json:"email"`
Password string `json:"password"`
type UserLogin struct {
Email string `json:"email"`
Password string `json:"password"`
}
var dat UserLogin
if err := c.ToJSON(r, &dat); err != nil {
return err
}
// TODO Give this to the generateToken function
token, login := generateToken(db, dat.Email, dat.Password)
if !login {
return c.SendJSONStatus(http.StatusUnauthorized, "Email or password are incorrect")
}
user, err := dbtypes.UserFromToken(c.Db, token)
if err != nil {
return c.Error500(err)
}
type UserReturn struct {
Token string `json:"token"`
Id string `json:"id"`
UserType int `json:"user_type"`
Username string `json:"username"`
Email string `json:"email"`
}
userReturn := UserReturn{
Token: token,
Id: user.Id,
UserType: user.UserType,
Username: user.Username,
Email: user.Email,
}
return c.SendJSON(userReturn)
} }
r.ParseForm() var dat UserLogin
f := r.Form
if CheckEmpty(f, "email") || CheckEmpty(f, "password") { if err := c.ToJSON(&dat); err != nil {
LoadBasedOnAnswer(c.Mode, w, "login.html", c.AddMap(AnyMap{ return err
"Submited": true,
}))
return nil
} }
email := f.Get("email")
password := f.Get("password")
// TODO Give this to the generateToken function // TODO Give this to the generateToken function
expiration := time.Now().Add(24 * time.Hour) token, login := generateToken(db, dat.Email, dat.Password)
token, login := generateToken(db, email, password)
if !login { if !login {
LoadBasedOnAnswer(c.Mode, w, "login.html", c.AddMap(AnyMap{ return c.SendJSONStatus(http.StatusUnauthorized, "Email or password are incorrect")
"Submited": true,
"NoUserOrPassword": true,
"Email": email,
}))
return nil
} }
cookie := &http.Cookie{Name: "auth", Value: token, HttpOnly: false, Expires: expiration} user, err := dbtypes.UserFromToken(c.Db, token)
http.SetCookie(w, cookie) if err != nil {
return c.Error500(err)
}
w.Header().Set("Location", "/") type UserReturn struct {
w.WriteHeader(http.StatusSeeOther) Token string `json:"token"`
return nil Id string `json:"id"`
UserType int `json:"user_type"`
Username string `json:"username"`
Email string `json:"email"`
}
userReturn := UserReturn{
Token: token,
Id: user.Id,
UserType: user.UserType,
Username: user.Username,
Email: user.Email,
}
return c.SendJSON(userReturn)
}) })
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 { type UserLogin struct {
if c.Mode == JSON { Username string `json:"username"`
type UserLogin struct { Email string `json:"email"`
Username string `json:"username"` Password string `json:"password"`
Email string `json:"email"`
Password string `json:"password"`
}
var dat UserLogin
if err := c.ToJSON(r, &dat); err != nil {
return err
}
if len(dat.Username) == 0 || len(dat.Password) == 0 || len(dat.Email) == 0 {
return c.SendJSONStatus(http.StatusBadRequest, "Please provide a valid json")
}
rows, err := db.Query("select username, email from users where username=$1 or email=$2;", dat.Username, dat.Email)
if err != nil {
return c.Error500(err)
}
defer rows.Close()
if rows.Next() {
var db_username string
var db_email string
err = rows.Scan(&db_username, &db_email)
if err != nil {
return c.Error500(err)
}
if db_email == dat.Email {
return c.SendJSONStatus(http.StatusBadRequest, "Email already in use!")
}
if db_username == dat.Username {
return c.SendJSONStatus(http.StatusBadRequest, "Username already in use!")
}
panic("Unrechable")
}
if len([]byte(dat.Password)) > 68 {
return c.JsonBadRequest("Password is to long!")
}
salt := generateSalt()
hash_password, err := hashPassword(dat.Password, salt)
if err != nil {
return c.Error500(err)
}
_, err = db.Exec("insert into users (username, email, salt, password) values ($1, $2, $3, $4);", dat.Username, dat.Email, salt, hash_password)
if err != nil {
return c.Error500(err)
}
// TODO Give this to the generateToken function
token, login := generateToken(db, dat.Email, dat.Password)
if !login {
return c.SendJSONStatus(500, "Could not login after creatting account please try again later")
}
user, err := dbtypes.UserFromToken(c.Db, token)
if err != nil {
return c.Error500(err)
}
type UserReturn struct {
Token string `json:"token"`
Id string `json:"id"`
UserType int `json:"user_type"`
Username string `json:"username"`
Email string `json:"email"`
}
userReturn := UserReturn{
Token: token,
Id: user.Id,
UserType: user.UserType,
Username: user.Username,
Email: user.Email,
}
return c.SendJSON(userReturn)
} }
r.ParseForm() var dat UserLogin
f := r.Form
if CheckEmpty(f, "email") || CheckEmpty(f, "password") || CheckEmpty(f, "username") { if err := c.ToJSON(&dat); err != nil {
LoadBasedOnAnswer(c.Mode, w, "register.html", AnyMap{ return err
"Submited": true,
})
return nil
} }
email := f.Get("email") if len(dat.Username) == 0 || len(dat.Password) == 0 || len(dat.Email) == 0 {
username := f.Get("username") return c.SendJSONStatus(http.StatusBadRequest, "Please provide a valid json")
password := f.Get("password") }
rows, err := db.Query("select username, email from users where username=$1 or email=$2;", username, email) rows, err := db.Query("select username, email from users where username=$1 or email=$2;", dat.Username, dat.Email)
if err != nil { if err != nil {
panic("TODO handle this") return c.Error500(err)
} }
defer rows.Close() defer rows.Close()
@ -265,85 +145,95 @@ func usersEndpints(db *sql.DB, handle *Handle) {
var db_email string var db_email string
err = rows.Scan(&db_username, &db_email) err = rows.Scan(&db_username, &db_email)
if err != nil { if err != nil {
panic("TODO handle this better") return c.Error500(err)
} }
LoadBasedOnAnswer(c.Mode, w, "register.html", AnyMap{ if db_email == dat.Email {
"Submited": true, return c.SendJSONStatus(http.StatusBadRequest, "Email already in use!")
"Email": email, }
"Username": username, if db_username == dat.Username {
"EmailError": db_email == email, return c.SendJSONStatus(http.StatusBadRequest, "Username already in use!")
"UserError": db_username == username, }
}) panic("Unrechable")
return nil
} }
if len([]byte(password)) > 68 { if len([]byte(dat.Password)) > 68 {
LoadBasedOnAnswer(c.Mode, w, "register.html", AnyMap{ return c.JsonBadRequest("Password is to long!")
"Submited": true,
"Email": email,
"Username": username,
"PasswordToLong": true,
})
return nil
} }
salt := generateSalt() salt := generateSalt()
hash_password, err := hashPassword(password, salt) hash_password, err := hashPassword(dat.Password, salt)
if err != nil { if err != nil {
return &Error{ return c.Error500(err)
Code: http.StatusInternalServerError,
}
} }
_, err = db.Exec("insert into users (username, email, salt, password) values ($1, $2, $3, $4);", username, email, salt, hash_password) _, err = db.Exec("insert into users (username, email, salt, password) values ($1, $2, $3, $4);", dat.Username, dat.Email, salt, hash_password)
if err != nil { if err != nil {
return &Error{ return c.Error500(err)
Code: http.StatusInternalServerError,
}
} }
// TODO Give this to the generateToken function // TODO Give this to the generateToken function
expiration := time.Now().Add(24 * time.Hour) token, login := generateToken(db, dat.Email, dat.Password)
token, login := generateToken(db, email, password)
if !login { if !login {
msg := "Login failed" return c.SendJSONStatus(500, "Could not login after creatting account please try again later")
return &Error{
Code: http.StatusInternalServerError,
Msg: &msg,
}
} }
cookie := &http.Cookie{Name: "auth", Value: token, HttpOnly: false, Expires: expiration} user, err := dbtypes.UserFromToken(c.Db, token)
http.SetCookie(w, cookie) if err != nil {
w.Header().Set("Location", "/") return c.Error500(err)
w.WriteHeader(http.StatusSeeOther) }
return nil
type UserReturn struct {
Token string `json:"token"`
Id string `json:"id"`
UserType int `json:"user_type"`
Username string `json:"username"`
Email string `json:"email"`
}
userReturn := UserReturn{
Token: token,
Id: user.Id,
UserType: user.UserType,
Username: user.Username,
Email: user.Email,
}
return c.SendJSON(userReturn)
}) })
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) user, err := dbtypes.UserFromToken(c.Db, *c.Token)
if err != nil {
return c.Error500(err)
} }
LoadBasedOnAnswer(c.Mode, w, "users/edit.html", c.AddMap(AnyMap{ type UserReturn struct {
"Email": c.User.Email, Id string `json:"id"`
})) UserType int `json:"user_type"`
return nil 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,108 +307,38 @@ 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) var dat struct {
Old_Password string `json:"old_password"`
Password string `json:"password"`
Password2 string `json:"password2"`
} }
r.ParseForm() if err := c.ToJSON(&dat); err != nil {
return err
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 dat.Password == "" {
if err != nil { return c.JsonBadRequest("Password can not be empty")
return c.Error500(err)
} }
LoadBasedOnAnswer(c.Mode, w, "users/edit.html", c.AddMap(AnyMap{ if dat.Password != dat.Password2 {
"Email": r.Form.Get("email"), return c.JsonBadRequest("New passwords did not match")
}))
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 {
Old_Password string `json:"old_password"`
Password string `json:"password"`
Password2 string `json:"password2"`
}
if err := c.ToJSON(r, &dat); err != nil {
return err
}
if dat.Password == "" {
return c.JsonBadRequest("Password can not be empty")
}
if dat.Password != dat.Password2 {
return c.JsonBadRequest("New passwords did not match")
}
c.Logger.Warn("test", "dat", dat)
_, login := generateToken(db, c.User.Email, dat.Old_Password)
if !login {
return c.JsonBadRequest("Password is incorrect");
}
salt := generateSalt()
hash_password, err := hashPassword(dat.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)
}
return c.SendJSON(c.User.Id)
} }
r.ParseForm() c.Logger.Warn("test", "dat", dat)
f := r.Form
if CheckEmpty(f, "old_password") || CheckEmpty(f, "password") || CheckEmpty(f, "password2") { _, login := generateToken(db, c.User.Email, dat.Old_Password)
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 { if !login {
return c.Error400(nil, "Password was incorrect", w, "users/edit.html", "mainbody", c.AddMap(AnyMap{ return c.JsonBadRequest("Password is incorrect")
"Email": c.User.Email,
"NoUserOrPassword": true,
}))
} }
salt := generateSalt() salt := generateSalt()
hash_password, err := hashPassword(password, salt) hash_password, err := hashPassword(dat.Password, salt)
if err != nil { if err != nil {
return c.Error500(err) return c.Error500(err)
} }
@ -528,17 +348,8 @@ func usersEndpints(db *sql.DB, handle *Handle) {
return c.Error500(err) return c.Error500(err)
} }
LoadBasedOnAnswer(c.Mode, w, "users/edit.html", c.AddMap(AnyMap{ return c.SendJSON(c.User.Id)
"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}}